diff --git a/packages/app-mobile/components/NoteEditor/CodeMirror.ts b/packages/app-mobile/components/NoteEditor/CodeMirror.ts index c146fa326..292a8854c 100644 --- a/packages/app-mobile/components/NoteEditor/CodeMirror.ts +++ b/packages/app-mobile/components/NoteEditor/CodeMirror.ts @@ -19,6 +19,8 @@ interface CodeMirrorResult { editor: EditorView; undo: Function; redo: Function; + select: (anchor: number, head: number)=> void; + insertText: (text: string)=> void; } function postMessage(name: string, data: any) { @@ -180,6 +182,13 @@ export function initCodeMirror(parentElement: any, initialText: string, theme: a postMessage('onChange', { value: editor.state.doc.toString() }); schedulePostUndoRedoDepthChange(editor); } + + if (!viewUpdate.state.selection.eq(viewUpdate.startState.selection)) { + const mainRange = viewUpdate.state.selection.main; + const selStart = mainRange.from; + const selEnd = mainRange.to; + postMessage('onSelectionChange', { selection: { start: selStart, end: selEnd } }); + } }), ], doc: initialText, @@ -197,5 +206,14 @@ export function initCodeMirror(parentElement: any, initialText: string, theme: a redo(editor); schedulePostUndoRedoDepthChange(editor, true); }, + select: (anchor: number, head: number) => { + editor.dispatch(editor.state.update({ + selection: { anchor, head }, + scrollIntoView: true, + })); + }, + insertText: (text: string) => { + editor.dispatch(editor.state.replaceSelection(text)); + }, }; } diff --git a/packages/app-mobile/components/NoteEditor/NoteEditor.tsx b/packages/app-mobile/components/NoteEditor/NoteEditor.tsx index d018b5e24..22a5ff290 100644 --- a/packages/app-mobile/components/NoteEditor/NoteEditor.tsx +++ b/packages/app-mobile/components/NoteEditor/NoteEditor.tsx @@ -15,14 +15,27 @@ export interface UndoRedoDepthChangeEvent { redoDepth: number; } +export interface Selection { + start: number; + end: number; +} + +export interface SelectionChangeEvent { + selection: Selection; +} + type ChangeEventHandler = (event: ChangeEvent)=> void; type UndoRedoDepthChangeHandler = (event: UndoRedoDepthChangeEvent)=> void; +type SelectionChangeEventHandler = (event: SelectionChangeEvent)=> void; interface Props { themeId: number; initialText: string; + initialSelection?: Selection; style: any; + onChange: ChangeEventHandler; + onSelectionChange: SelectionChangeEventHandler; onUndoRedoDepthChange: UndoRedoDepthChangeHandler; } @@ -212,11 +225,15 @@ function NoteEditor(props: Props, ref: any) { const [source, setSource] = useState(undefined); const webviewRef = useRef(null); + const setInitialSelectionJS = props.initialSelection ? ` + cm.select(${props.initialSelection.start}, ${props.initialSelection.end}); + ` : ''; + const injectedJavaScript = ` function postMessage(name, data) { window.ReactNativeWebView.postMessage(JSON.stringify({ data, - name, + name, })); } @@ -227,7 +244,7 @@ function NoteEditor(props: Props, ref: any) { // This variable is not used within this script // but is called using "injectJavaScript" from // the wrapper component. - let cm = null; + window.cm = null; try { ${shim.injectedJs('codeMirrorBundle')}; @@ -237,6 +254,7 @@ function NoteEditor(props: Props, ref: any) { const initialText = ${JSON.stringify(props.initialText)}; cm = codeMirrorBundle.initCodeMirror(parentElement, initialText, theme); + ${setInitialSelectionJS} } catch (e) { window.ReactNativeWebView.postMessage("error:" + e.message + ": " + JSON.stringify(e)) } finally { @@ -255,6 +273,14 @@ function NoteEditor(props: Props, ref: any) { redo: function() { webviewRef.current.injectJavaScript('cm.redo(); true;'); }, + select: (anchor: number, head: number) => { + webviewRef.current.injectJavaScript( + `cm.select(${JSON.stringify(anchor)}, ${JSON.stringify(head)}); true;` + ); + }, + insertText: (text: string) => { + webviewRef.current.injectJavaScript(`cm.insertText(${JSON.stringify(text)}); true;`); + }, }; }); @@ -301,6 +327,10 @@ function NoteEditor(props: Props, ref: any) { console.info('onUndoRedoDepthChange', event); props.onUndoRedoDepthChange(event); }, + + onSelectionChange: (event: SelectionChangeEvent) => { + props.onSelectionChange(event); + }, }; if (handlers[msg.name]) { diff --git a/packages/app-mobile/components/screens/Note.tsx b/packages/app-mobile/components/screens/Note.tsx index 2886d0265..ee935ab0d 100644 --- a/packages/app-mobile/components/screens/Note.tsx +++ b/packages/app-mobile/components/screens/Note.tsx @@ -488,7 +488,11 @@ class NoteScreenComponent extends BaseScreenComponent { } body_selectionChange(event: any) { - this.selection = event.nativeEvent.selection; + if (this.useEditorBeta()) { + this.selection = event.selection; + } else { + this.selection = event.nativeEvent.selection; + } } makeSaveAction() { @@ -708,9 +712,17 @@ class NoteScreenComponent extends BaseScreenComponent { const newNote = Object.assign({}, this.state.note); if (this.state.mode == 'edit' && !!this.selection) { + const newText = `\n${resourceTag}\n`; + const prefix = newNote.body.substring(0, this.selection.start); const suffix = newNote.body.substring(this.selection.end); - newNote.body = `${prefix}\n${resourceTag}\n${suffix}`; + newNote.body = `${prefix}${newText}${suffix}`; + + if (this.useEditorBeta()) { + // The beta editor needs to be explicitly informed of changes + // to the note's body + this.editorRef.current.insertText(newText); + } } else { newNote.body += `\n${resourceTag}`; } @@ -879,11 +891,6 @@ class NoteScreenComponent extends BaseScreenComponent { output.push({ title: _('Attach...'), onPress: async () => { - if (this.state.mode === 'edit' && this.useEditorBeta()) { - alert('Attaching files from the beta editor is not yet supported. You may do so from the viewer mode instead.'); - return; - } - const buttons = []; // On iOS, it will show "local files", which means certain files saved from the browser @@ -1125,7 +1132,9 @@ class NoteScreenComponent extends BaseScreenComponent { ref={this.editorRef} themeId={this.props.themeId} initialText={note.body} + initialSelection={this.selection} onChange={this.onBodyChange} + onSelectionChange={this.body_selectionChange} onUndoRedoDepthChange={this.onUndoRedoDepthChange} style={this.styles().bodyTextInput} />;