diff --git a/ElectronClient/gui/NoteEditor/NoteBody/AceEditor/AceEditor.tsx b/ElectronClient/gui/NoteEditor/NoteBody/AceEditor/AceEditor.tsx index 17238b075e..6ec1a25ae5 100644 --- a/ElectronClient/gui/NoteEditor/NoteBody/AceEditor/AceEditor.tsx +++ b/ElectronClient/gui/NoteEditor/NoteBody/AceEditor/AceEditor.tsx @@ -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); diff --git a/ElectronClient/gui/NoteEditor/NoteBody/AceEditor/utils/index.ts b/ElectronClient/gui/NoteEditor/NoteBody/AceEditor/utils/index.ts index 28554bcf68..64844e97ce 100644 --- a/ElectronClient/gui/NoteEditor/NoteBody/AceEditor/utils/index.ts +++ b/ElectronClient/gui/NoteEditor/NoteBody/AceEditor/utils/index.ts @@ -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) { diff --git a/ElectronClient/gui/NoteEditor/NoteBody/AceEditor/utils/useListIdent.ts b/ElectronClient/gui/NoteEditor/NoteBody/AceEditor/utils/useListIdent.ts index 393822aee1..105e4ce153 100644 --- a/ElectronClient/gui/NoteEditor/NoteBody/AceEditor/utils/useListIdent.ts +++ b/ElectronClient/gui/NoteEditor/NoteBody/AceEditor/utils/useListIdent.ts @@ -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);