Skip to content

Commit

Permalink
Plugins: Add support for editor.scrollToText on desktop
Browse files Browse the repository at this point in the history
  • Loading branch information
laurent22 committed Nov 16, 2024
1 parent 8e3c817 commit 6eac8d9
Show file tree
Hide file tree
Showing 9 changed files with 92 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,10 @@ function CodeMirror(props: NoteBodyEditorProps, ref: ForwardedRef<NoteBodyEditor
reg.logger().warn('CodeMirror execCommand: unsupported command: ', value.name);
}
},
'editor.scrollToText': (_text: string) => {
reg.logger().warn('"editor.scrollToText" is unsupported in legacy editor - please use the new editor');
return false;
},
};

if (commands[cmd.name]) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@

import { RefObject, useMemo } from 'react';
import { CommandValue, DropCommandValue } from '../../../utils/types';
import { CommandValue, DropCommandValue, ScrollToTextValue } from '../../../utils/types';
import { commandAttachFileToBody } from '../../../utils/resourceHandling';
import { _ } from '@joplin/lib/locale';
import dialogs from '../../../../dialogs';
Expand Down Expand Up @@ -132,6 +132,28 @@ const useEditorCommands = (props: Props) => {
search: () => {
return editorRef.current.execCommand(EditorCommandType.ShowSearch);
},
'editor.scrollToText': (value: ScrollToTextValue) => {
value = {
scrollStrategy: 'start',
...value,
};

const valueToMarkdown = (value: ScrollToTextValue) => {
const conv: Record<typeof value.element, (text: string)=> string> = {
h1: text => `# ${text}`,
h2: text => `## ${text}`,
h3: text => `### ${text}`,
h4: text => `#### ${text}`,
h5: text => `##### ${text}`,
strong: text => `**${text}**`,
ul: text => `- ${text}`,
};

return conv[value.element](value.text);
};

return editorRef.current.scrollToText(valueToMarkdown(value), value.scrollStrategy);
},
};
}, [
props.visiblePanes, props.editorContent, props.editorCopyText, props.editorCutText, props.editorPaste,
Expand Down
24 changes: 23 additions & 1 deletion packages/app-desktop/gui/NoteEditor/NoteBody/TinyMCE/TinyMCE.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as React from 'react';
import { useState, useEffect, useCallback, useRef, forwardRef, useImperativeHandle, useMemo } from 'react';
import { ScrollOptions, ScrollOptionTypes, EditorCommand, NoteBodyEditorProps, ResourceInfos, HtmlToMarkdownHandler } from '../../utils/types';
import { ScrollOptions, ScrollOptionTypes, EditorCommand, NoteBodyEditorProps, ResourceInfos, HtmlToMarkdownHandler, ScrollToTextValue } from '../../utils/types';
import { resourcesStatus, commandAttachFileToBody, getResourcesFromPasteEvent, processPastedHtml } from '../../utils/resourceHandling';
import attachedResources from '@joplin/lib/utils/attachedResources';
import useScroll from './utils/useScroll';
Expand Down Expand Up @@ -244,6 +244,28 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
} else {
logger.warn('unsupported drop item: ', cmd);
}
} else if (cmd.name === 'editor.scrollToText') {

const cmdValue = cmd.value as ScrollToTextValue;

const findElementByText = (doc: Document, text: string, element: string) => {
const headers = doc.querySelectorAll(element);
for (const header of headers) {
if (header.textContent?.trim() === text) {
return header;
}
}
return null;
};

const contentDocument = editor.getDoc();
const targetElement = findElementByText(contentDocument, cmdValue.text, cmdValue.element);

if (targetElement) {
targetElement.scrollIntoView({ behavior: 'smooth', block: 'start' });
} else {
logger.warn('editor.scrollToText: Could not find text to scroll to :', cmdValue);
}
} else {
commandProcessed = false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,9 @@ const declarations: CommandDeclaration[] = [
{
name: 'editor.setText',
},
{
name: 'editor.scrollToText',
},
{
name: 'editor.focus',
},
Expand Down
10 changes: 10 additions & 0 deletions packages/app-desktop/gui/NoteEditor/utils/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { ProcessResultsRow } from '@joplin/lib/services/search/SearchEngine';
import { DropHandler } from './useDropHandler';
import { SearchMarkers } from './useSearchMarkers';
import { ParseOptions } from '@joplin/lib/HtmlToMd';
import { ScrollStrategy } from '@joplin/editor/CodeMirror/CodeMirrorControl';

export interface AllAssetsOptions {
contentMaxWidthTarget?: string;
Expand Down Expand Up @@ -271,3 +272,12 @@ export type DropCommandValue = ({
paths: string[];
createFileURL: boolean;
}) & DropCommandBase;

export interface ScrollToTextValue {
// Text should be plain text - it should not include Markdown characters as it needs to work
// with both TinyMCE and CodeMirror. To specific an element use the `element` property. For
// example to scroll to `## Scroll to this`, use `{ text: 'Scroll to this', element: 'h2' }`.
text: string;
element: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'strong' | 'ul';
scrollStrategy?: ScrollStrategy;
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import * as duplicateNote from './duplicateNote';
import * as editAlarm from './editAlarm';
import * as exportPdf from './exportPdf';
import * as gotoAnything from './gotoAnything';
import * as hideEditorPlugin from './hideEditorPlugin';
import * as hideModalMessage from './hideModalMessage';
import * as leaveSharedFolder from './leaveSharedFolder';
import * as moveToFolder from './moveToFolder';
Expand Down Expand Up @@ -56,6 +57,7 @@ const index: any[] = [
editAlarm,
exportPdf,
gotoAnything,
hideEditorPlugin,
hideModalMessage,
leaveSharedFolder,
moveToFolder,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ export const runtime = (): CommandRuntime => {
shownEditorViewIds.splice(idx, 1);
}

logger.info('Shown editor IDs:', shownEditorViewIds);

Setting.setValue('plugins.shownEditorViewIds', shownEditorViewIds);
},
};
Expand Down
17 changes: 17 additions & 0 deletions packages/editor/CodeMirror/CodeMirrorControl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ interface Callbacks {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
type EditorUserCommand = (...args: any[])=> any;

// Copied from CodeMirror source code since type is not exported
export type ScrollStrategy = 'nearest' | 'start' | 'end' | 'center';

export default class CodeMirrorControl extends CodeMirror5Emulation implements EditorControl {
private _pluginControl: PluginLoader;
private _userCommands: Map<string, EditorUserCommand> = new Map();
Expand Down Expand Up @@ -177,6 +180,20 @@ export default class CodeMirrorControl extends CodeMirror5Emulation implements E

}

public scrollToText(text: string, scrollStrategy: ScrollStrategy) {
const doc = this.editor.state.doc;
const index = doc.toString().indexOf(text);
const textFound = index >= 0;

if (textFound) {
this.editor.dispatch({
effects: EditorView.scrollIntoView(index, { y: scrollStrategy }),
});
}

return textFound;
}

public addStyles(...styles: Parameters<typeof EditorView.theme>) {
const compartment = new Compartment();
this.editor.dispatch({
Expand Down
15 changes: 8 additions & 7 deletions packages/lib/services/PostMessageService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,14 +127,15 @@ export default class PostMessageService {
}

if (!responder) {
logger.warn('Cannot respond to message because no responder was found', message);
logger.info('Cannot respond to message because no responder was found', message);
logger.info('Error was:', error);
} else {
responder({
responseId: message.id,
response: responseContent,
error,
});
}

responder({
responseId: message.id,
response: responseContent,
error,
});
}

private responder(type: ResponderComponentType, viewId: string, windowId: string) {
Expand Down

0 comments on commit 6eac8d9

Please sign in to comment.