1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-12-24 10:27:10 +02:00

Merge branch 'dev' of github.com:laurent22/joplin into dev

This commit is contained in:
Laurent Cozic 2020-11-19 18:35:56 +00:00
commit 592b9d95c6
4 changed files with 135 additions and 62 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');
@ -409,6 +362,12 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
padding-bottom: 400px !important;
}
.CodeMirror-sizer {
/* Add a fixed right padding to account for the appearance (and disappearance) */
/* of the sidebar */
padding-right: 10px !important;
}
.cm-header-1 {
font-size: 1.5em;
}
@ -623,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 (
@ -640,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

@ -715,7 +715,7 @@ class Setting extends BaseModel {
description: () => 'CSS file support is provided for your convenience, but they are advanced settings, and styles you define may break from one version to the next. If you want to use them, please know that it might require regular development work from you to keep them working. The Joplin team cannot make a commitment to keep the application HTML structure stable.',
},
autoUpdateEnabled: { value: false, type: SettingItemType.Bool, section: 'application', public: true, appTypes: ['desktop'], label: () => _('Automatically update the application') },
autoUpdateEnabled: { value: false, type: SettingItemType.Bool, section: 'application', public: platform !== 'linux', appTypes: ['desktop'], label: () => _('Automatically update the application') },
'autoUpdate.includePreReleases': { value: false, type: SettingItemType.Bool, section: 'application', public: true, appTypes: ['desktop'], label: () => _('Get pre-releases when checking for updates'), description: () => _('See the pre-release page for more details: %s', 'https://joplinapp.org/prereleases') },
'clipperServer.autoStart': { value: false, type: SettingItemType.Bool, public: false },
'sync.interval': {
@ -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,