You've already forked joplin
mirror of
https://github.com/laurent22/joplin.git
synced 2025-07-16 00:14:34 +02:00
Plugins: Add support for editor.scrollToText on desktop
This commit is contained in:
@ -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]) {
|
||||
|
@ -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';
|
||||
@ -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,
|
||||
|
@ -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';
|
||||
@ -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;
|
||||
}
|
||||
|
@ -154,6 +154,9 @@ const declarations: CommandDeclaration[] = [
|
||||
{
|
||||
name: 'editor.setText',
|
||||
},
|
||||
{
|
||||
name: 'editor.scrollToText',
|
||||
},
|
||||
{
|
||||
name: 'editor.focus',
|
||||
},
|
||||
|
@ -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;
|
||||
@ -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;
|
||||
}
|
||||
|
@ -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';
|
||||
@ -56,6 +57,7 @@ const index: any[] = [
|
||||
editAlarm,
|
||||
exportPdf,
|
||||
gotoAnything,
|
||||
hideEditorPlugin,
|
||||
hideModalMessage,
|
||||
leaveSharedFolder,
|
||||
moveToFolder,
|
||||
|
@ -47,6 +47,8 @@ export const runtime = (): CommandRuntime => {
|
||||
shownEditorViewIds.splice(idx, 1);
|
||||
}
|
||||
|
||||
logger.info('Shown editor IDs:', shownEditorViewIds);
|
||||
|
||||
Setting.setValue('plugins.shownEditorViewIds', shownEditorViewIds);
|
||||
},
|
||||
};
|
||||
|
@ -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();
|
||||
@ -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({
|
||||
|
@ -127,15 +127,16 @@ 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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private responder(type: ResponderComponentType, viewId: string, windowId: string) {
|
||||
return this.responders_[[type, viewId, windowId].join(':')];
|
||||
|
Reference in New Issue
Block a user