mirror of
https://github.com/laurent22/joplin.git
synced 2024-11-24 08:12:24 +02:00
Desktop: Add option to toggle spellchecking for the markdown editor (#4109)
This commit is contained in:
parent
116413e78d
commit
858508bfa9
@ -22,6 +22,7 @@ import MenuUtils from '@joplin/lib/services/commands/MenuUtils';
|
||||
import CommandService from '@joplin/lib/services/CommandService';
|
||||
import { themeStyle } from '@joplin/lib/theme';
|
||||
import { ThemeAppearance } from '@joplin/lib/themes/type';
|
||||
import SpellCheckerService from '@joplin/lib/services/spellChecker/SpellCheckerService';
|
||||
import dialogs from '../../../dialogs';
|
||||
|
||||
const Note = require('@joplin/lib/models/Note.js');
|
||||
@ -255,54 +256,6 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
|
||||
}
|
||||
}, []);
|
||||
|
||||
const onEditorContextMenu = useCallback(() => {
|
||||
const menu = new Menu();
|
||||
|
||||
const hasSelectedText = editorRef.current && !!editorRef.current.getSelection() ;
|
||||
const clipboardText = clipboard.readText();
|
||||
|
||||
menu.append(
|
||||
new MenuItem({
|
||||
label: _('Cut'),
|
||||
enabled: hasSelectedText,
|
||||
click: async () => {
|
||||
editorCutText();
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
menu.append(
|
||||
new MenuItem({
|
||||
label: _('Copy'),
|
||||
enabled: hasSelectedText,
|
||||
click: async () => {
|
||||
editorCopyText();
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
menu.append(
|
||||
new MenuItem({
|
||||
label: _('Paste'),
|
||||
enabled: true,
|
||||
click: async () => {
|
||||
if (clipboardText) {
|
||||
editorPasteText();
|
||||
} else {
|
||||
// To handle pasting images
|
||||
onEditorPaste();
|
||||
}
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
menuUtils.pluginContextMenuItems(props.plugins, MenuItemLocation.EditorContextMenu).forEach((item: any) => {
|
||||
menu.append(new MenuItem(item));
|
||||
});
|
||||
|
||||
menu.popup(bridge().window());
|
||||
}, [props.content, editorCutText, editorPasteText, editorCopyText, onEditorPaste, props.plugins]);
|
||||
|
||||
const loadScript = async (script: any) => {
|
||||
return new Promise((resolve) => {
|
||||
let element: any = document.createElement('script');
|
||||
@ -629,6 +582,91 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
|
||||
editorRef.current.refresh();
|
||||
}, [rootSize, styles.editor, props.visiblePanes]);
|
||||
|
||||
// The below code adds support for spellchecking when it is enabled
|
||||
// It might be buggy, refer to the below issue
|
||||
// https://github.com/laurent22/joplin/pull/3974#issuecomment-718936703
|
||||
useEffect(() => {
|
||||
function pointerInsideEditor(x: number, y: number) {
|
||||
const elements = document.getElementsByClassName('codeMirrorEditor');
|
||||
if (!elements.length) return null;
|
||||
const rect = elements[0].getBoundingClientRect();
|
||||
return rect.x < x && rect.y < y && rect.right > x && rect.bottom > y;
|
||||
}
|
||||
|
||||
function onContextMenu(_event: any, params: any) {
|
||||
if (!pointerInsideEditor(params.x, params.y)) return;
|
||||
|
||||
const menu = new Menu();
|
||||
|
||||
const hasSelectedText = editorRef.current && !!editorRef.current.getSelection() ;
|
||||
const clipboardText = clipboard.readText();
|
||||
|
||||
menu.append(
|
||||
new MenuItem({
|
||||
label: _('Cut'),
|
||||
enabled: hasSelectedText,
|
||||
click: async () => {
|
||||
editorCutText();
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
menu.append(
|
||||
new MenuItem({
|
||||
label: _('Copy'),
|
||||
enabled: hasSelectedText,
|
||||
click: async () => {
|
||||
editorCopyText();
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
menu.append(
|
||||
new MenuItem({
|
||||
label: _('Paste'),
|
||||
enabled: true,
|
||||
click: async () => {
|
||||
if (clipboardText) {
|
||||
editorPasteText();
|
||||
} else {
|
||||
// To handle pasting images
|
||||
onEditorPaste();
|
||||
}
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
const spellCheckerMenuItems = SpellCheckerService.instance().contextMenuItems(params.misspelledWord, params.dictionarySuggestions);
|
||||
|
||||
for (const item of spellCheckerMenuItems) {
|
||||
menu.append(new MenuItem(item));
|
||||
}
|
||||
|
||||
// Typically CodeMirror handles all interactions itself (highlighting etc.)
|
||||
// But in the case of clicking a mispelled word, we need electron to handle the click
|
||||
// The result is that CodeMirror doesn't know what's been selected and doesn't
|
||||
// move the cursor into the correct location.
|
||||
// and when the user selects a new spelling it will be inserted in the wrong location
|
||||
// So in this situation, we use must manually align the internal codemirror selection
|
||||
// to the contextmenu selection
|
||||
if (editorRef.current && spellCheckerMenuItems.length > 0) {
|
||||
editorRef.current.alignSelection(params);
|
||||
}
|
||||
|
||||
menuUtils.pluginContextMenuItems(props.plugins, MenuItemLocation.EditorContextMenu).forEach((item: any) => {
|
||||
menu.append(new MenuItem(item));
|
||||
});
|
||||
|
||||
menu.popup();
|
||||
}
|
||||
|
||||
bridge().window().webContents.on('context-menu', onContextMenu);
|
||||
|
||||
return () => {
|
||||
bridge().window().webContents.off('context-menu', onContextMenu);
|
||||
};
|
||||
}, []);
|
||||
|
||||
function renderEditor() {
|
||||
|
||||
return (
|
||||
@ -646,7 +684,6 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
|
||||
plugins={props.plugins}
|
||||
onChange={codeMirror_change}
|
||||
onScroll={editor_scroll}
|
||||
onEditorContextMenu={onEditorContextMenu}
|
||||
onEditorPaste={onEditorPaste}
|
||||
/>
|
||||
</div>
|
||||
|
@ -27,6 +27,8 @@ import 'codemirror/keymap/sublime'; // Used for swapLineUp and swapLineDown
|
||||
|
||||
import 'codemirror/mode/meta';
|
||||
|
||||
import Setting from '@joplin/lib/models/Setting';
|
||||
|
||||
// import eventManager from '@joplin/lib/eventManager';
|
||||
|
||||
const { reg } = require('@joplin/lib/registry.js');
|
||||
@ -88,7 +90,6 @@ export interface EditorProps {
|
||||
plugins: PluginStates;
|
||||
onChange: any;
|
||||
onScroll: any;
|
||||
onEditorContextMenu: any;
|
||||
onEditorPaste: any;
|
||||
}
|
||||
|
||||
@ -122,13 +123,6 @@ function Editor(props: EditorProps, ref: any) {
|
||||
props.onScroll();
|
||||
}, [props.onScroll]);
|
||||
|
||||
// eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars
|
||||
const editor_mousedown = useCallback((_cm: any, event: any) => {
|
||||
if (event && event.button === 2) {
|
||||
props.onEditorContextMenu();
|
||||
}
|
||||
}, [props.onEditorContextMenu]);
|
||||
|
||||
// eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars
|
||||
const editor_paste = useCallback((_cm: any, _event: any) => {
|
||||
props.onEditorPaste();
|
||||
@ -163,7 +157,7 @@ function Editor(props: EditorProps, ref: any) {
|
||||
mode: props.mode,
|
||||
readOnly: props.readOnly,
|
||||
autoCloseBrackets: props.autoMatchBraces,
|
||||
inputStyle: 'textarea', // contenteditable loses cursor position on focus change, use textarea instead
|
||||
inputStyle: Setting.value('editor.spellcheckBeta') ? 'contenteditable' : 'textarea',
|
||||
lineWrapping: true,
|
||||
lineNumbers: false,
|
||||
indentWithTabs: true,
|
||||
@ -177,7 +171,6 @@ function Editor(props: EditorProps, ref: any) {
|
||||
setEditor(cm);
|
||||
cm.on('change', editor_change);
|
||||
cm.on('scroll', editor_scroll);
|
||||
cm.on('mousedown', editor_mousedown);
|
||||
cm.on('paste', editor_paste);
|
||||
cm.on('drop', editor_drop);
|
||||
cm.on('dragover', editor_drag);
|
||||
@ -191,7 +184,6 @@ function Editor(props: EditorProps, ref: any) {
|
||||
// Clean up codemirror
|
||||
cm.off('change', editor_change);
|
||||
cm.off('scroll', editor_scroll);
|
||||
cm.off('mousedown', editor_mousedown);
|
||||
cm.off('paste', editor_paste);
|
||||
cm.off('drop', editor_drop);
|
||||
cm.off('dragover', editor_drag);
|
||||
@ -250,7 +242,7 @@ function Editor(props: EditorProps, ref: any) {
|
||||
}
|
||||
}, [pluginOptions, editor]);
|
||||
|
||||
return <div style={props.style} ref={editorParent} />;
|
||||
return <div className='codeMirrorEditor' style={props.style} ref={editorParent} />;
|
||||
}
|
||||
|
||||
export default forwardRef(Editor);
|
||||
|
@ -100,5 +100,33 @@ export default function useCursorUtils(CodeMirror: any) {
|
||||
});
|
||||
});
|
||||
|
||||
// params are the oncontextmenu params
|
||||
CodeMirror.defineExtension('alignSelection', function(params: any) {
|
||||
// The below is a HACK that uses the selectionText from electron and the coordinates of
|
||||
// the click to determine what the codemirror selection should be
|
||||
const alignStrings = (s1: string, s2: string) => {
|
||||
for (let i = 0; i < s1.length; i++) {
|
||||
if (s1.substr(i, s2.length) === s2) { return i; }
|
||||
}
|
||||
return -1;
|
||||
};
|
||||
|
||||
const selectionText = params.selectionText;
|
||||
const coords = this.coordsChar({ left: params.x, top: params.y });
|
||||
const { anchor, head } = this.findWordAt(coords);
|
||||
const selectedWord = this.getRange(anchor, head);
|
||||
|
||||
if (selectionText.length > selectedWord.length) {
|
||||
const offset = alignStrings(selectionText, selectedWord);
|
||||
anchor.ch -= offset;
|
||||
head.ch = anchor.ch + selectionText.length;
|
||||
} else if (selectionText.length < selectedWord.length) {
|
||||
const offset = alignStrings(selectedWord, selectionText);
|
||||
anchor.ch += offset;
|
||||
head.ch = anchor.ch + selectionText.length;
|
||||
}
|
||||
|
||||
this.setSelection(anchor, head);
|
||||
});
|
||||
|
||||
}
|
||||
|
@ -775,6 +775,16 @@ class Setting extends BaseModel {
|
||||
},
|
||||
},
|
||||
|
||||
'editor.spellcheckBeta': {
|
||||
value: false,
|
||||
type: SettingItemType.Bool,
|
||||
public: true,
|
||||
appTypes: ['desktop'],
|
||||
advanced: true,
|
||||
label: () => 'Enable spell checking in Markdown editor? (WARNING BETA feature)',
|
||||
description: () => 'Spell checker in the Markdown editor was previously unstable (cursor location was not stable, sometimes edits would not be saved or reflected in the viewer, etc.) however it appears to be more reliable now. If you notice any issue, please report it on GitHub or the Joplin Forum (Help -> Joplin Forum)',
|
||||
},
|
||||
|
||||
'net.customCertificates': {
|
||||
value: '',
|
||||
type: SettingItemType.String,
|
||||
|
Loading…
Reference in New Issue
Block a user