1
0
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:
Caleb John 2020-11-19 10:14:44 -07:00 committed by GitHub
parent 116413e78d
commit 858508bfa9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 128 additions and 61 deletions

View File

@ -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>

View File

@ -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);

View File

@ -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);
});
}

View File

@ -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,