mirror of https://github.com/laurent22/joplin.git
Destkop: Fix selection-related issues
parent
49451dba20
commit
b83eee751f
|
@ -5,7 +5,7 @@ import { useState, useEffect, useRef, forwardRef, useCallback, useImperativeHand
|
|||
import { EditorCommand, NoteBodyEditorProps } from '../../utils/types';
|
||||
import { commandAttachFileToBody, handlePasteEvent } from '../../utils/resourceHandling';
|
||||
import { ScrollOptions, ScrollOptionTypes } from '../../utils/types';
|
||||
import { textOffsetToCursorPosition, useScrollHandler, useRootWidth, usePrevious, lineLeftSpaces, selectionRangeCurrentLine, selectionRangePreviousLine, currentTextOffset, textOffsetSelection, selectedText, useSelectionRange } from './utils';
|
||||
import { textOffsetToCursorPosition, useScrollHandler, useRootWidth, usePrevious, lineLeftSpaces, selectionRange, selectionRangeCurrentLine, selectionRangePreviousLine, currentTextOffset, textOffsetSelection, selectedText } from './utils';
|
||||
import useListIdent from './utils/useListIdent';
|
||||
import Toolbar from './Toolbar';
|
||||
import styles_ from './styles';
|
||||
|
@ -94,17 +94,11 @@ function AceEditor(props: NoteBodyEditorProps, ref: any) {
|
|||
const contentKeyHasChangedRef = useRef(false);
|
||||
contentKeyHasChangedRef.current = previousContentKey !== props.contentKey;
|
||||
|
||||
// The selection range changes all the time, when the caret moves or
|
||||
// when the selection changes, so it's best not to make it part of the
|
||||
// state as it would trigger too many unecessary updates.
|
||||
const selectionRangeRef = useRef(null);
|
||||
selectionRangeRef.current = useSelectionRange(editor);
|
||||
|
||||
const rootWidth = useRootWidth({ rootRef });
|
||||
|
||||
const { resetScroll, setEditorPercentScroll, setViewerPercentScroll, editor_scroll } = useScrollHandler(editor, webviewRef, props.onScroll);
|
||||
|
||||
useListIdent({ editor, selectionRangeRef });
|
||||
useListIdent({ editor });
|
||||
|
||||
const aceEditor_change = useCallback((newBody: string) => {
|
||||
props_onChangeRef.current({ changeId: null, content: newBody });
|
||||
|
@ -113,7 +107,7 @@ function AceEditor(props: NoteBodyEditorProps, ref: any) {
|
|||
const wrapSelectionWithStrings = useCallback((string1: string, string2 = '', defaultText = '', replacementText: string = null, byLine = false) => {
|
||||
if (!editor) return;
|
||||
|
||||
const selection = textOffsetSelection(selectionRangeRef.current, props.content);
|
||||
const selection = textOffsetSelection(selectionRange(editor), props.content);
|
||||
|
||||
let newBody = props.content;
|
||||
|
||||
|
@ -140,7 +134,7 @@ function AceEditor(props: NoteBodyEditorProps, ref: any) {
|
|||
|
||||
newBody += props.content.substr(selection.end);
|
||||
|
||||
const r = selectionRangeRef.current;
|
||||
const r = selectionRange(editor);
|
||||
|
||||
// Because some insertion strings will have newlines, we'll need to account for them
|
||||
const str1Split = string1.split(/\r?\n/);
|
||||
|
@ -180,7 +174,7 @@ function AceEditor(props: NoteBodyEditorProps, ref: any) {
|
|||
}
|
||||
|
||||
setTimeout(() => {
|
||||
const range = selectionRangeRef.current;
|
||||
const range = selectionRange(editor);
|
||||
range.setStart(newRange.start.row, newRange.start.column);
|
||||
range.setEnd(newRange.end.row, newRange.end.column);
|
||||
editor.getSession().getSelection().setSelectionRange(range, false);
|
||||
|
@ -204,7 +198,7 @@ function AceEditor(props: NoteBodyEditorProps, ref: any) {
|
|||
|
||||
setTimeout(() => {
|
||||
if (middleText && newRange) {
|
||||
const range = selectionRangeRef.current;
|
||||
const range = selectionRange(editor);
|
||||
range.setStart(newRange.start.row, newRange.start.column);
|
||||
range.setEnd(newRange.end.row, newRange.end.column);
|
||||
editor.getSession().getSelection().setSelectionRange(range, false);
|
||||
|
@ -222,12 +216,12 @@ function AceEditor(props: NoteBodyEditorProps, ref: any) {
|
|||
|
||||
const addListItem = useCallback((string1, string2 = '', defaultText = '', byLine = false) => {
|
||||
let newLine = '\n';
|
||||
const range = selectionRangeRef.current;
|
||||
const range = selectionRange(editor);
|
||||
if (!range || (range.start.row === range.end.row && !selectionRangeCurrentLine(range, props.content))) {
|
||||
newLine = '';
|
||||
}
|
||||
wrapSelectionWithStrings(newLine + string1, string2, defaultText, null, byLine);
|
||||
}, [wrapSelectionWithStrings, props.content]);
|
||||
}, [wrapSelectionWithStrings, props.content, editor]);
|
||||
|
||||
useImperativeHandle(ref, () => {
|
||||
return {
|
||||
|
@ -283,7 +277,7 @@ function AceEditor(props: NoteBodyEditorProps, ref: any) {
|
|||
if (url) wrapSelectionWithStrings('[', `](${url})`);
|
||||
},
|
||||
textCode: () => {
|
||||
const selection = textOffsetSelection(selectionRangeRef.current, props.content);
|
||||
const selection = textOffsetSelection(selectionRange(editor), props.content);
|
||||
const string = props.content.substr(selection.start, selection.end - selection.start);
|
||||
|
||||
// Look for newlines
|
||||
|
@ -301,13 +295,14 @@ function AceEditor(props: NoteBodyEditorProps, ref: any) {
|
|||
},
|
||||
insertText: (value: any) => wrapSelectionWithStrings(value),
|
||||
attachFile: async () => {
|
||||
const selection = textOffsetSelection(selectionRangeRef.current, props.content);
|
||||
const selection = textOffsetSelection(selectionRange(editor), props.content);
|
||||
const newBody = await commandAttachFileToBody(props.content, null, { position: selection ? selection.start : 0 });
|
||||
if (newBody) aceEditor_change(newBody);
|
||||
},
|
||||
textNumberedList: () => {
|
||||
let bulletNumber = markdownUtils.olLineNumber(selectionRangeCurrentLine(selectionRangeRef.current, props.content));
|
||||
if (!bulletNumber) bulletNumber = markdownUtils.olLineNumber(selectionRangePreviousLine(selectionRangeRef.current, props.content));
|
||||
const selection = selectionRange(editor);
|
||||
let bulletNumber = markdownUtils.olLineNumber(selectionRangeCurrentLine(selection, props.content));
|
||||
if (!bulletNumber) bulletNumber = markdownUtils.olLineNumber(selectionRangePreviousLine(selection, props.content));
|
||||
if (!bulletNumber) bulletNumber = 0;
|
||||
addListItem(`${bulletNumber + 1}. `, '', _('List item'), true);
|
||||
},
|
||||
|
@ -346,12 +341,12 @@ function AceEditor(props: NoteBodyEditorProps, ref: any) {
|
|||
}, []);
|
||||
|
||||
const editorCutText = useCallback(() => {
|
||||
const text = selectedText(selectionRangeRef.current, props.content);
|
||||
const text = selectedText(selectionRange(editor), props.content);
|
||||
if (!text) return;
|
||||
|
||||
clipboard.writeText(text);
|
||||
|
||||
const s = textOffsetSelection(selectionRangeRef.current, props.content);
|
||||
const s = textOffsetSelection(selectionRange(editor), props.content);
|
||||
if (!s || s.start === s.end) return;
|
||||
|
||||
const s1 = props.content.substr(0, s.start);
|
||||
|
@ -360,7 +355,7 @@ function AceEditor(props: NoteBodyEditorProps, ref: any) {
|
|||
aceEditor_change(s1 + s2);
|
||||
|
||||
setTimeout(() => {
|
||||
const range = selectionRangeRef.current;
|
||||
const range = selectionRange(editor);
|
||||
range.setStart(range.start.row, range.start.column);
|
||||
range.setEnd(range.start.row, range.start.column);
|
||||
editor.getSession().getSelection().setSelectionRange(range, false);
|
||||
|
@ -369,9 +364,9 @@ function AceEditor(props: NoteBodyEditorProps, ref: any) {
|
|||
}, [props.content, editor, aceEditor_change]);
|
||||
|
||||
const editorCopyText = useCallback(() => {
|
||||
const text = selectedText(selectionRangeRef.current, props.content);
|
||||
const text = selectedText(selectionRange(editor), props.content);
|
||||
clipboard.writeText(text);
|
||||
}, [props.content]);
|
||||
}, [props.content, editor]);
|
||||
|
||||
const editorPasteText = useCallback(() => {
|
||||
wrapSelectionWithStrings(clipboard.readText(), '', '', '');
|
||||
|
@ -380,7 +375,7 @@ function AceEditor(props: NoteBodyEditorProps, ref: any) {
|
|||
const onEditorContextMenu = useCallback(() => {
|
||||
const menu = new Menu();
|
||||
|
||||
const hasSelectedText = !!selectedText(selectionRangeRef.current, props.content);
|
||||
const hasSelectedText = !!selectedText(selectionRange(editor), props.content);
|
||||
const clipboardText = clipboard.readText();
|
||||
|
||||
menu.append(
|
||||
|
@ -419,7 +414,7 @@ function AceEditor(props: NoteBodyEditorProps, ref: any) {
|
|||
);
|
||||
|
||||
menu.popup(bridge().window());
|
||||
}, [props.content, editorCutText, editorPasteText, editorCopyText, onEditorPaste]);
|
||||
}, [props.content, editorCutText, editorPasteText, editorCopyText, onEditorPaste, editor]);
|
||||
|
||||
function aceEditor_load(editor: any) {
|
||||
setEditor(editor);
|
||||
|
|
|
@ -42,69 +42,9 @@ export function selectedText(selectionRange: any, body: string) {
|
|||
return body.substr(selection.start, selection.end - selection.start);
|
||||
}
|
||||
|
||||
function selectionRangesEqual(s1:any, s2:any) {
|
||||
if (s1 === s2) return true;
|
||||
if (!s1 && !s2) return true;
|
||||
|
||||
if (s1 && !s2) return false;
|
||||
if (!s1 && s2) return false;
|
||||
|
||||
if (s1.start.row !== s2.start.row) return false;
|
||||
if (s1.start.column !== s2.start.column) return false;
|
||||
if (s1.end.row !== s2.end.row) return false;
|
||||
if (s1.end.column !== s2.end.column) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
export function useSelectionRange(editor: any) {
|
||||
const [selectionRange, setSelectionRange] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!editor) return () => {};
|
||||
|
||||
function updateSelection() {
|
||||
const ranges = editor.getSelection().getAllRanges();
|
||||
const firstRange = ranges && ranges.length ? ranges[0] : null;
|
||||
|
||||
// Ace Editor might sometimes send multiple "changeSelection" events
|
||||
// with the same selection range, which triggers unecessary updates
|
||||
// and even infinite rendering loops. So before setting it on the state
|
||||
// we deep compare the previous and new selection.
|
||||
// https://github.com/laurent22/joplin/issues/3200
|
||||
setSelectionRange((prev:any) => {
|
||||
if (selectionRangesEqual(prev, firstRange)) return prev;
|
||||
return firstRange;
|
||||
});
|
||||
|
||||
// if (process.platform === 'linux') {
|
||||
// const textRange = this.textOffsetSelection();
|
||||
// if (textRange.start != textRange.end) {
|
||||
// clipboard.writeText(this.state.note.body.slice(
|
||||
// Math.min(textRange.start, textRange.end),
|
||||
// Math.max(textRange.end, textRange.start)), 'selection');
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
function onSelectionChange() {
|
||||
updateSelection();
|
||||
}
|
||||
|
||||
function onFocus() {
|
||||
updateSelection();
|
||||
}
|
||||
|
||||
editor.getSession().selection.on('changeSelection', onSelectionChange);
|
||||
editor.on('focus', onFocus);
|
||||
|
||||
return () => {
|
||||
editor.getSession().selection.off('changeSelection', onSelectionChange);
|
||||
editor.off('focus', onFocus);
|
||||
};
|
||||
}, [editor]);
|
||||
|
||||
return selectionRange;
|
||||
export function selectionRange(editor:any) {
|
||||
const ranges = editor.getSelection().getAllRanges();
|
||||
return ranges && ranges.length ? ranges[0] : null;
|
||||
}
|
||||
|
||||
export function textOffsetToCursorPosition(offset: number, body: string) {
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import { useEffect } from 'react';
|
||||
import { selectionRange } from './index';
|
||||
|
||||
interface HookDependencies {
|
||||
editor: any,
|
||||
selectionRangeRef: any,
|
||||
}
|
||||
|
||||
export default function useListIdent(dependencies:HookDependencies) {
|
||||
const { editor, selectionRangeRef } = dependencies;
|
||||
const { editor } = dependencies;
|
||||
|
||||
useEffect(() => {
|
||||
if (!editor) return;
|
||||
|
@ -17,7 +17,7 @@ export default function useListIdent(dependencies:HookDependencies) {
|
|||
const originalEditorIndent = editor.indent;
|
||||
|
||||
editor.indent = function() {
|
||||
const range = selectionRangeRef.current;
|
||||
const range = selectionRange(editor);
|
||||
if (range.isEmpty()) {
|
||||
const row = range.start.row;
|
||||
const tokens = this.session.getTokens(row);
|
||||
|
|
Loading…
Reference in New Issue