mirror of
https://github.com/laurent22/joplin.git
synced 2025-01-02 12:47:41 +02:00
Desktop: Allow searching when only the note viewer is visible and sync search with editor (#10866)
This commit is contained in:
parent
1edef99811
commit
b5313067cd
@ -841,9 +841,11 @@ packages/editor/CodeMirror/utils/formatting/toggleRegionFormatGlobally.test.js
|
|||||||
packages/editor/CodeMirror/utils/formatting/toggleRegionFormatGlobally.js
|
packages/editor/CodeMirror/utils/formatting/toggleRegionFormatGlobally.js
|
||||||
packages/editor/CodeMirror/utils/formatting/toggleSelectedLinesStartWith.js
|
packages/editor/CodeMirror/utils/formatting/toggleSelectedLinesStartWith.js
|
||||||
packages/editor/CodeMirror/utils/formatting/types.js
|
packages/editor/CodeMirror/utils/formatting/types.js
|
||||||
|
packages/editor/CodeMirror/utils/getSearchState.js
|
||||||
packages/editor/CodeMirror/utils/growSelectionToNode.js
|
packages/editor/CodeMirror/utils/growSelectionToNode.js
|
||||||
packages/editor/CodeMirror/utils/handlePasteEvent.js
|
packages/editor/CodeMirror/utils/handlePasteEvent.js
|
||||||
packages/editor/CodeMirror/utils/isInSyntaxNode.js
|
packages/editor/CodeMirror/utils/isInSyntaxNode.js
|
||||||
|
packages/editor/CodeMirror/utils/searchExtension.js
|
||||||
packages/editor/CodeMirror/utils/setupVim.js
|
packages/editor/CodeMirror/utils/setupVim.js
|
||||||
packages/editor/SelectionFormatting.js
|
packages/editor/SelectionFormatting.js
|
||||||
packages/editor/events.js
|
packages/editor/events.js
|
||||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -818,9 +818,11 @@ packages/editor/CodeMirror/utils/formatting/toggleRegionFormatGlobally.test.js
|
|||||||
packages/editor/CodeMirror/utils/formatting/toggleRegionFormatGlobally.js
|
packages/editor/CodeMirror/utils/formatting/toggleRegionFormatGlobally.js
|
||||||
packages/editor/CodeMirror/utils/formatting/toggleSelectedLinesStartWith.js
|
packages/editor/CodeMirror/utils/formatting/toggleSelectedLinesStartWith.js
|
||||||
packages/editor/CodeMirror/utils/formatting/types.js
|
packages/editor/CodeMirror/utils/formatting/types.js
|
||||||
|
packages/editor/CodeMirror/utils/getSearchState.js
|
||||||
packages/editor/CodeMirror/utils/growSelectionToNode.js
|
packages/editor/CodeMirror/utils/growSelectionToNode.js
|
||||||
packages/editor/CodeMirror/utils/handlePasteEvent.js
|
packages/editor/CodeMirror/utils/handlePasteEvent.js
|
||||||
packages/editor/CodeMirror/utils/isInSyntaxNode.js
|
packages/editor/CodeMirror/utils/isInSyntaxNode.js
|
||||||
|
packages/editor/CodeMirror/utils/searchExtension.js
|
||||||
packages/editor/CodeMirror/utils/setupVim.js
|
packages/editor/CodeMirror/utils/setupVim.js
|
||||||
packages/editor/SelectionFormatting.js
|
packages/editor/SelectionFormatting.js
|
||||||
packages/editor/events.js
|
packages/editor/events.js
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useRef, useState } from 'react';
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import shim from '@joplin/lib/shim';
|
import shim from '@joplin/lib/shim';
|
||||||
import Logger from '@joplin/utils/Logger';
|
import Logger from '@joplin/utils/Logger';
|
||||||
import CodeMirror5Emulation from '@joplin/editor/CodeMirror/CodeMirror5Emulation/CodeMirror5Emulation';
|
import CodeMirror5Emulation from '@joplin/editor/CodeMirror/CodeMirror5Emulation/CodeMirror5Emulation';
|
||||||
@ -20,16 +20,15 @@ export default function useEditorSearchExtension(CodeMirror: CodeMirror5Emulatio
|
|||||||
const overlayTimeoutRef = useRef(null);
|
const overlayTimeoutRef = useRef(null);
|
||||||
overlayTimeoutRef.current = overlayTimeout;
|
overlayTimeoutRef.current = overlayTimeout;
|
||||||
|
|
||||||
function clearMarkers() {
|
const clearMarkers = useCallback(() => {
|
||||||
for (let i = 0; i < markers.length; i++) {
|
for (let i = 0; i < markers.length; i++) {
|
||||||
markers[i].clear();
|
markers[i].clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
setMarkers([]);
|
setMarkers([]);
|
||||||
}
|
}, [markers]);
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
const clearOverlay = useCallback((cm: CodeMirror5Emulation) => {
|
||||||
function clearOverlay(cm: any) {
|
|
||||||
if (overlay) cm.removeOverlay(overlay);
|
if (overlay) cm.removeOverlay(overlay);
|
||||||
if (scrollbarMarks) {
|
if (scrollbarMarks) {
|
||||||
try {
|
try {
|
||||||
@ -47,10 +46,10 @@ export default function useEditorSearchExtension(CodeMirror: CodeMirror5Emulatio
|
|||||||
setOverlay(null);
|
setOverlay(null);
|
||||||
setScrollbarMarks(null);
|
setScrollbarMarks(null);
|
||||||
setOverlayTimeout(null);
|
setOverlayTimeout(null);
|
||||||
}
|
}, [scrollbarMarks, overlay, overlayTimeout]);
|
||||||
|
|
||||||
// Modified from codemirror/addons/search/search.js
|
// Modified from codemirror/addons/search/search.js
|
||||||
function searchOverlay(query: RegExp) {
|
const searchOverlay = useCallback((query: RegExp) => {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||||
return { token: function(stream: any) {
|
return { token: function(stream: any) {
|
||||||
query.lastIndex = stream.pos;
|
query.lastIndex = stream.pos;
|
||||||
@ -65,13 +64,13 @@ export default function useEditorSearchExtension(CodeMirror: CodeMirror5Emulatio
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
} };
|
} };
|
||||||
}
|
}, []);
|
||||||
|
|
||||||
// Highlights the currently active found work
|
// Highlights the currently active found work
|
||||||
// It's possible to get tricky with this functions and just use findNext/findPrev
|
// It's possible to get tricky with this functions and just use findNext/findPrev
|
||||||
// but this is fast enough and works more naturally with the current search logic
|
// but this is fast enough and works more naturally with the current search logic
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||||
function highlightSearch(cm: any, searchTerm: RegExp, index: number, scrollTo: boolean, withSelection: boolean) {
|
function highlightSearch(cm: CodeMirror5Emulation, searchTerm: RegExp, index: number, scrollTo: boolean, withSelection: boolean) {
|
||||||
const cursor = cm.getSearchCursor(searchTerm);
|
const cursor = cm.getSearchCursor(searchTerm);
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||||
@ -122,6 +121,12 @@ export default function useEditorSearchExtension(CodeMirror: CodeMirror5Emulatio
|
|||||||
options = { selectedIndex: 0, searchTimestamp: 0 };
|
options = { selectedIndex: 0, searchTimestamp: 0 };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options.showEditorMarkers === false) {
|
||||||
|
clearMarkers();
|
||||||
|
clearOverlay(this);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
clearMarkers();
|
clearMarkers();
|
||||||
|
|
||||||
// HIGHLIGHT KEYWORDS
|
// HIGHLIGHT KEYWORDS
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { RefObject, useEffect } from 'react';
|
import { RefObject, useEffect, useMemo, useRef } from 'react';
|
||||||
import usePrevious from '../../../../hooks/usePrevious';
|
import usePrevious from '../../../../hooks/usePrevious';
|
||||||
import { RenderedBody } from './types';
|
import { RenderedBody } from './types';
|
||||||
|
import { SearchMarkers } from '../../../utils/useSearchMarkers';
|
||||||
const debounce = require('debounce');
|
const debounce = require('debounce');
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@ -14,14 +15,31 @@ interface Props {
|
|||||||
|
|
||||||
noteContent: string;
|
noteContent: string;
|
||||||
renderedBody: RenderedBody;
|
renderedBody: RenderedBody;
|
||||||
|
showEditorMarkers: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const useEditorSearchHandler = (props: Props) => {
|
const useEditorSearchHandler = (props: Props) => {
|
||||||
const { webviewRef, editorRef, renderedBody, noteContent, searchMarkers } = props;
|
const {
|
||||||
|
webviewRef, editorRef, renderedBody, noteContent, searchMarkers, showEditorMarkers,
|
||||||
|
} = props;
|
||||||
|
|
||||||
const previousContent = usePrevious(noteContent);
|
const previousContent = usePrevious(noteContent);
|
||||||
const previousRenderedBody = usePrevious(renderedBody);
|
const previousRenderedBody = usePrevious(renderedBody);
|
||||||
const previousSearchMarkers = usePrevious(searchMarkers);
|
const previousSearchMarkers = usePrevious(searchMarkers);
|
||||||
|
const showEditorMarkersRef = useRef(showEditorMarkers);
|
||||||
|
showEditorMarkersRef.current = showEditorMarkers;
|
||||||
|
|
||||||
|
// Fixes https://github.com/laurent22/joplin/issues/7565
|
||||||
|
const debouncedMarkers = useMemo(() => debounce((searchMarkers: SearchMarkers) => {
|
||||||
|
if (!editorRef.current) return;
|
||||||
|
|
||||||
|
if (showEditorMarkersRef.current) {
|
||||||
|
const matches = editorRef.current.setMarkers(searchMarkers.keywords, searchMarkers.options);
|
||||||
|
props.setLocalSearchResultCount(matches);
|
||||||
|
} else {
|
||||||
|
editorRef.current.setMarkers(searchMarkers.keywords, { ...searchMarkers.options, showEditorMarkers: false });
|
||||||
|
}
|
||||||
|
}, 50), [editorRef, props.setLocalSearchResultCount]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!searchMarkers) return () => {};
|
if (!searchMarkers) return () => {};
|
||||||
@ -37,19 +55,7 @@ const useEditorSearchHandler = (props: Props) => {
|
|||||||
|
|
||||||
if (webviewRef.current && (searchMarkers !== previousSearchMarkers || textChanged)) {
|
if (webviewRef.current && (searchMarkers !== previousSearchMarkers || textChanged)) {
|
||||||
webviewRef.current.send('setMarkers', searchMarkers.keywords, searchMarkers.options);
|
webviewRef.current.send('setMarkers', searchMarkers.keywords, searchMarkers.options);
|
||||||
|
debouncedMarkers(searchMarkers);
|
||||||
if (editorRef.current) {
|
|
||||||
// Fixes https://github.com/laurent22/joplin/issues/7565
|
|
||||||
const debouncedMarkers = debounce(() => {
|
|
||||||
const matches = editorRef.current.setMarkers(searchMarkers.keywords, searchMarkers.options);
|
|
||||||
|
|
||||||
props.setLocalSearchResultCount(matches);
|
|
||||||
}, 50);
|
|
||||||
debouncedMarkers();
|
|
||||||
return () => {
|
|
||||||
debouncedMarkers.clear();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return () => {};
|
return () => {};
|
||||||
}, [
|
}, [
|
||||||
@ -62,6 +68,7 @@ const useEditorSearchHandler = (props: Props) => {
|
|||||||
previousContent,
|
previousContent,
|
||||||
previousRenderedBody,
|
previousRenderedBody,
|
||||||
renderedBody,
|
renderedBody,
|
||||||
|
debouncedMarkers,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
@ -686,6 +686,7 @@ function CodeMirror(props: NoteBodyEditorProps, ref: ForwardedRef<NoteBodyEditor
|
|||||||
editorRef,
|
editorRef,
|
||||||
noteContent: props.content,
|
noteContent: props.content,
|
||||||
renderedBody,
|
renderedBody,
|
||||||
|
showEditorMarkers: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const cellEditorStyle = useMemo(() => {
|
const cellEditorStyle = useMemo(() => {
|
||||||
|
@ -16,7 +16,7 @@ import { MarkupToHtml } from '@joplin/renderer';
|
|||||||
const { clipboard } = require('electron');
|
const { clipboard } = require('electron');
|
||||||
import { reg } from '@joplin/lib/registry';
|
import { reg } from '@joplin/lib/registry';
|
||||||
import ErrorBoundary from '../../../../ErrorBoundary';
|
import ErrorBoundary from '../../../../ErrorBoundary';
|
||||||
import { EditorKeymap, EditorLanguageType, EditorSettings, UserEventSource } from '@joplin/editor/types';
|
import { EditorKeymap, EditorLanguageType, EditorSettings, SearchState, UserEventSource } from '@joplin/editor/types';
|
||||||
import useStyles from '../utils/useStyles';
|
import useStyles from '../utils/useStyles';
|
||||||
import { EditorEvent, EditorEventType } from '@joplin/editor/events';
|
import { EditorEvent, EditorEventType } from '@joplin/editor/events';
|
||||||
import useScrollHandler from '../utils/useScrollHandler';
|
import useScrollHandler from '../utils/useScrollHandler';
|
||||||
@ -175,6 +175,9 @@ const CodeMirror = (props: NoteBodyEditorProps, ref: ForwardedRef<NoteBodyEditor
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
supportsCommand: (name: string) => {
|
supportsCommand: (name: string) => {
|
||||||
|
if (name === 'search' && !props.visiblePanes.includes('editor')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return name in commands || editorRef.current.supportsCommand(name);
|
return name in commands || editorRef.current.supportsCommand(name);
|
||||||
},
|
},
|
||||||
execCommand: async (cmd: EditorCommand) => {
|
execCommand: async (cmd: EditorCommand) => {
|
||||||
@ -197,7 +200,7 @@ const CodeMirror = (props: NoteBodyEditorProps, ref: ForwardedRef<NoteBodyEditor
|
|||||||
return commandOutput;
|
return commandOutput;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}, [props.content, commands, resetScroll, setEditorPercentScroll, setViewerPercentScroll]);
|
}, [props.content, props.visiblePanes, commands, resetScroll, setEditorPercentScroll, setViewerPercentScroll]);
|
||||||
|
|
||||||
const webview_domReady = useCallback(() => {
|
const webview_domReady = useCallback(() => {
|
||||||
setWebviewReady(true);
|
setWebviewReady(true);
|
||||||
@ -321,6 +324,7 @@ const CodeMirror = (props: NoteBodyEditorProps, ref: ForwardedRef<NoteBodyEditor
|
|||||||
editorRef,
|
editorRef,
|
||||||
noteContent: props.content,
|
noteContent: props.content,
|
||||||
renderedBody,
|
renderedBody,
|
||||||
|
showEditorMarkers: !props.useLocalSearch,
|
||||||
});
|
});
|
||||||
|
|
||||||
useContextMenu({
|
useContextMenu({
|
||||||
@ -330,6 +334,7 @@ const CodeMirror = (props: NoteBodyEditorProps, ref: ForwardedRef<NoteBodyEditor
|
|||||||
editorClassName: 'cm-editor',
|
editorClassName: 'cm-editor',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const lastSearchState = useRef<SearchState|null>(null);
|
||||||
const onEditorEvent = useCallback((event: EditorEvent) => {
|
const onEditorEvent = useCallback((event: EditorEvent) => {
|
||||||
if (event.kind === EditorEventType.Scroll) {
|
if (event.kind === EditorEventType.Scroll) {
|
||||||
editor_scroll();
|
editor_scroll();
|
||||||
@ -337,8 +342,17 @@ const CodeMirror = (props: NoteBodyEditorProps, ref: ForwardedRef<NoteBodyEditor
|
|||||||
codeMirror_change(event.value);
|
codeMirror_change(event.value);
|
||||||
} else if (event.kind === EditorEventType.SelectionRangeChange) {
|
} else if (event.kind === EditorEventType.SelectionRangeChange) {
|
||||||
setSelectionRange({ from: event.from, to: event.to });
|
setSelectionRange({ from: event.from, to: event.to });
|
||||||
|
} else if (event.kind === EditorEventType.UpdateSearchDialog) {
|
||||||
|
if (lastSearchState.current?.searchText !== event.searchState.searchText) {
|
||||||
|
props.setLocalSearch(event.searchState.searchText);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastSearchState.current?.dialogVisible !== event.searchState.dialogVisible) {
|
||||||
|
props.setShowLocalSearch(event.searchState.dialogVisible);
|
||||||
|
}
|
||||||
|
lastSearchState.current = event.searchState;
|
||||||
}
|
}
|
||||||
}, [editor_scroll, codeMirror_change]);
|
}, [editor_scroll, codeMirror_change, props.setLocalSearch, props.setShowLocalSearch]);
|
||||||
|
|
||||||
const editorSettings = useMemo((): EditorSettings => {
|
const editorSettings = useMemo((): EditorSettings => {
|
||||||
const isHTMLNote = props.contentMarkupLanguage === MarkupToHtml.MARKUP_LANGUAGE_HTML;
|
const isHTMLNote = props.contentMarkupLanguage === MarkupToHtml.MARKUP_LANGUAGE_HTML;
|
||||||
@ -389,6 +403,8 @@ const CodeMirror = (props: NoteBodyEditorProps, ref: ForwardedRef<NoteBodyEditor
|
|||||||
onEvent={onEditorEvent}
|
onEvent={onEditorEvent}
|
||||||
onLogMessage={logDebug}
|
onLogMessage={logDebug}
|
||||||
onEditorPaste={onEditorPaste}
|
onEditorPaste={onEditorPaste}
|
||||||
|
externalSearch={props.searchMarkers}
|
||||||
|
useLocalSearch={props.useLocalSearch}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -13,12 +13,15 @@ import { dirname } from 'path';
|
|||||||
import useKeymap from './utils/useKeymap';
|
import useKeymap from './utils/useKeymap';
|
||||||
import useEditorSearch from '../utils/useEditorSearchExtension';
|
import useEditorSearch from '../utils/useEditorSearchExtension';
|
||||||
import CommandService from '@joplin/lib/services/CommandService';
|
import CommandService from '@joplin/lib/services/CommandService';
|
||||||
|
import { SearchMarkers } from '../../../utils/useSearchMarkers';
|
||||||
|
|
||||||
interface Props extends EditorProps {
|
interface Props extends EditorProps {
|
||||||
style: React.CSSProperties;
|
style: React.CSSProperties;
|
||||||
pluginStates: PluginStates;
|
pluginStates: PluginStates;
|
||||||
|
|
||||||
onEditorPaste: (event: Event)=> void;
|
onEditorPaste: (event: Event)=> void;
|
||||||
|
externalSearch: SearchMarkers;
|
||||||
|
useLocalSearch: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Editor = (props: Props, ref: ForwardedRef<CodeMirrorControl>) => {
|
const Editor = (props: Props, ref: ForwardedRef<CodeMirrorControl>) => {
|
||||||
@ -117,6 +120,25 @@ const Editor = (props: Props, ref: ForwardedRef<CodeMirrorControl>) => {
|
|||||||
// eslint-disable-next-line @seiyab/react-hooks/exhaustive-deps -- Should run just once
|
// eslint-disable-next-line @seiyab/react-hooks/exhaustive-deps -- Should run just once
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!editor) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const searchState = editor.getSearchState();
|
||||||
|
const externalSearchText = props.externalSearch.keywords.map(k => k.value).join(' ') || searchState.searchText;
|
||||||
|
|
||||||
|
if (externalSearchText === searchState.searchText && searchState.dialogVisible === props.useLocalSearch) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
editor.setSearchState({
|
||||||
|
...searchState,
|
||||||
|
dialogVisible: props.useLocalSearch,
|
||||||
|
searchText: externalSearchText,
|
||||||
|
});
|
||||||
|
}, [editor, props.externalSearch, props.useLocalSearch]);
|
||||||
|
|
||||||
const theme = props.settings.themeData;
|
const theme = props.settings.themeData;
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!editor) return () => {};
|
if (!editor) return () => {};
|
||||||
|
@ -411,6 +411,9 @@ function NoteEditor(props: NoteEditorProps) {
|
|||||||
noteToolbar: null,
|
noteToolbar: null,
|
||||||
onScroll: onScroll,
|
onScroll: onScroll,
|
||||||
setLocalSearchResultCount: setLocalSearchResultCount,
|
setLocalSearchResultCount: setLocalSearchResultCount,
|
||||||
|
setLocalSearch: localSearch_change,
|
||||||
|
setShowLocalSearch,
|
||||||
|
useLocalSearch: showLocalSearch,
|
||||||
searchMarkers: searchMarkers,
|
searchMarkers: searchMarkers,
|
||||||
visiblePanes: props.noteVisiblePanes || ['editor', 'viewer'],
|
visiblePanes: props.noteVisiblePanes || ['editor', 'viewer'],
|
||||||
keyboardMode: Setting.value('editor.keyboardMode'),
|
keyboardMode: Setting.value('editor.keyboardMode'),
|
||||||
@ -506,6 +509,7 @@ function NoteEditor(props: NoteEditorProps) {
|
|||||||
onPrevious={localSearch_previous}
|
onPrevious={localSearch_previous}
|
||||||
onClose={localSearch_close}
|
onClose={localSearch_close}
|
||||||
visiblePanes={props.noteVisiblePanes}
|
visiblePanes={props.noteVisiblePanes}
|
||||||
|
editorType={props.bodyEditor}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ import { RenderResult, RenderResultPluginAsset } from '@joplin/renderer/types';
|
|||||||
import { Dispatch } from 'redux';
|
import { Dispatch } from 'redux';
|
||||||
import { ProcessResultsRow } from '@joplin/lib/services/search/SearchEngine';
|
import { ProcessResultsRow } from '@joplin/lib/services/search/SearchEngine';
|
||||||
import { DropHandler } from './useDropHandler';
|
import { DropHandler } from './useDropHandler';
|
||||||
|
import { SearchMarkers } from './useSearchMarkers';
|
||||||
|
|
||||||
export interface AllAssetsOptions {
|
export interface AllAssetsOptions {
|
||||||
contentMaxWidthTarget?: string;
|
contentMaxWidthTarget?: string;
|
||||||
@ -119,8 +120,11 @@ export interface NoteBodyEditorProps {
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||||
noteToolbar: any;
|
noteToolbar: any;
|
||||||
setLocalSearchResultCount(count: number): void;
|
setLocalSearchResultCount(count: number): void;
|
||||||
|
setLocalSearch(search: string): void;
|
||||||
|
setShowLocalSearch(show: boolean): void;
|
||||||
|
useLocalSearch: boolean;
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||||
searchMarkers: any;
|
searchMarkers: SearchMarkers;
|
||||||
visiblePanes: string[];
|
visiblePanes: string[];
|
||||||
keyboardMode: string;
|
keyboardMode: string;
|
||||||
resourceInfos: ResourceInfos;
|
resourceInfos: ResourceInfos;
|
||||||
|
@ -18,6 +18,7 @@ interface Props {
|
|||||||
resultCount: number;
|
resultCount: number;
|
||||||
selectedIndex: number;
|
selectedIndex: number;
|
||||||
visiblePanes: string[];
|
visiblePanes: string[];
|
||||||
|
editorType: string;
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||||
style: any;
|
style: any;
|
||||||
}
|
}
|
||||||
@ -26,6 +27,7 @@ class NoteSearchBar extends React.Component<Props> {
|
|||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
||||||
private backgroundColor: any;
|
private backgroundColor: any;
|
||||||
|
private searchInputRef: React.RefObject<HTMLInputElement>;
|
||||||
|
|
||||||
public constructor(props: Props) {
|
public constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
@ -40,6 +42,7 @@ class NoteSearchBar extends React.Component<Props> {
|
|||||||
this.focus = this.focus.bind(this);
|
this.focus = this.focus.bind(this);
|
||||||
|
|
||||||
this.backgroundColor = undefined;
|
this.backgroundColor = undefined;
|
||||||
|
this.searchInputRef = React.createRef();
|
||||||
}
|
}
|
||||||
|
|
||||||
public style() {
|
public style() {
|
||||||
@ -133,10 +136,8 @@ class NoteSearchBar extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public focus() {
|
public focus() {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
focus('NoteSearchBar::focus', this.searchInputRef.current);
|
||||||
focus('NoteSearchBar::focus', this.refs.searchInput as any);
|
this.searchInputRef.current?.select();
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
|
|
||||||
(this.refs.searchInput as any).select();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
@ -173,7 +174,9 @@ class NoteSearchBar extends React.Component<Props> {
|
|||||||
</div>
|
</div>
|
||||||
) : null;
|
) : null;
|
||||||
|
|
||||||
const allowScrolling = this.props.visiblePanes.indexOf('editor') >= 0;
|
const editorVisible = this.props.visiblePanes.includes('editor');
|
||||||
|
const usesEditorSearch = this.props.editorType === 'CodeMirror6' && editorVisible;
|
||||||
|
const allowScrolling = editorVisible;
|
||||||
|
|
||||||
const viewerWarning = (
|
const viewerWarning = (
|
||||||
<div style={textStyle}>
|
<div style={textStyle}>
|
||||||
@ -181,6 +184,8 @@ class NoteSearchBar extends React.Component<Props> {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (usesEditorSearch) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="note-search-bar" style={this.props.style}>
|
<div className="note-search-bar" style={this.props.style}>
|
||||||
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center' }}>
|
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center' }}>
|
||||||
@ -190,7 +195,7 @@ class NoteSearchBar extends React.Component<Props> {
|
|||||||
value={query}
|
value={query}
|
||||||
onChange={this.searchInput_change}
|
onChange={this.searchInput_change}
|
||||||
onKeyDown={this.searchInput_keyDown}
|
onKeyDown={this.searchInput_keyDown}
|
||||||
ref="searchInput"
|
ref={this.searchInputRef}
|
||||||
type="text"
|
type="text"
|
||||||
style={{ width: 200, marginRight: 5, backgroundColor: this.backgroundColor, color: theme.color }}
|
style={{ width: 200, marginRight: 5, backgroundColor: this.backgroundColor, color: theme.color }}
|
||||||
/>
|
/>
|
||||||
|
@ -63,5 +63,61 @@ test.describe('markdownEditor', () => {
|
|||||||
await mainWindow.keyboard.press('Home');
|
await mainWindow.keyboard.press('Home');
|
||||||
await expect(firstItemLocator).toBeFocused();
|
await expect(firstItemLocator).toBeFocused();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should sync local search between the viewer and editor', async ({ mainWindow }) => {
|
||||||
|
const mainScreen = new MainScreen(mainWindow);
|
||||||
|
await mainScreen.waitFor();
|
||||||
|
const noteEditor = mainScreen.noteEditor;
|
||||||
|
|
||||||
|
await mainScreen.createNewNote('Note');
|
||||||
|
|
||||||
|
await noteEditor.focusCodeMirrorEditor();
|
||||||
|
|
||||||
|
await mainWindow.keyboard.type('# Testing');
|
||||||
|
await mainWindow.keyboard.press('Enter');
|
||||||
|
await mainWindow.keyboard.press('Enter');
|
||||||
|
await mainWindow.keyboard.type('This is a test of search. `Test inline code`');
|
||||||
|
|
||||||
|
const viewer = noteEditor.getNoteViewerIframe();
|
||||||
|
await expect(viewer.locator('h1')).toHaveText('Testing');
|
||||||
|
|
||||||
|
const matches = viewer.locator('mark');
|
||||||
|
await expect(matches).toHaveCount(0);
|
||||||
|
|
||||||
|
await mainWindow.keyboard.press(process.platform === 'darwin' ? 'Meta+f' : 'Control+f');
|
||||||
|
await expect(noteEditor.editorSearchInput).toBeVisible();
|
||||||
|
|
||||||
|
await noteEditor.editorSearchInput.click();
|
||||||
|
await noteEditor.editorSearchInput.fill('test');
|
||||||
|
await mainWindow.keyboard.press('Enter');
|
||||||
|
|
||||||
|
// Should show at least one match in the viewer
|
||||||
|
await expect(matches).toHaveCount(3);
|
||||||
|
await expect(matches.first()).toBeAttached();
|
||||||
|
|
||||||
|
// Should show matches in code regions
|
||||||
|
await noteEditor.editorSearchInput.fill('inline code');
|
||||||
|
await mainWindow.keyboard.press('Enter');
|
||||||
|
await expect(matches).toHaveCount(1);
|
||||||
|
|
||||||
|
// Should continue searching after switching to view-only mode
|
||||||
|
await noteEditor.toggleEditorLayoutButton.click();
|
||||||
|
await noteEditor.toggleEditorLayoutButton.click();
|
||||||
|
await expect(noteEditor.codeMirrorEditor).not.toBeVisible();
|
||||||
|
await expect(noteEditor.editorSearchInput).not.toBeVisible();
|
||||||
|
await expect(noteEditor.viewerSearchInput).toBeVisible();
|
||||||
|
|
||||||
|
// Should stop searching after closing the search input
|
||||||
|
await noteEditor.viewerSearchInput.click();
|
||||||
|
await expect(matches).toHaveCount(1);
|
||||||
|
await mainWindow.keyboard.press('Escape');
|
||||||
|
await expect(noteEditor.viewerSearchInput).not.toBeVisible();
|
||||||
|
await expect(matches).toHaveCount(0);
|
||||||
|
|
||||||
|
// After showing the viewer again, search should still be hidden
|
||||||
|
await noteEditor.toggleEditorLayoutButton.click();
|
||||||
|
await expect(noteEditor.codeMirrorEditor).toBeVisible();
|
||||||
|
await expect(noteEditor.editorSearchInput).not.toBeVisible();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -7,6 +7,9 @@ export default class NoteEditorPage {
|
|||||||
public readonly noteTitleInput: Locator;
|
public readonly noteTitleInput: Locator;
|
||||||
public readonly attachFileButton: Locator;
|
public readonly attachFileButton: Locator;
|
||||||
public readonly toggleEditorsButton: Locator;
|
public readonly toggleEditorsButton: Locator;
|
||||||
|
public readonly toggleEditorLayoutButton: Locator;
|
||||||
|
public readonly editorSearchInput: Locator;
|
||||||
|
public readonly viewerSearchInput: Locator;
|
||||||
private readonly containerLocator: Locator;
|
private readonly containerLocator: Locator;
|
||||||
|
|
||||||
public constructor(private readonly page: Page) {
|
public constructor(private readonly page: Page) {
|
||||||
@ -16,6 +19,10 @@ export default class NoteEditorPage {
|
|||||||
this.noteTitleInput = this.containerLocator.locator('.title-input');
|
this.noteTitleInput = this.containerLocator.locator('.title-input');
|
||||||
this.attachFileButton = this.containerLocator.getByRole('button', { name: 'Attach file' });
|
this.attachFileButton = this.containerLocator.getByRole('button', { name: 'Attach file' });
|
||||||
this.toggleEditorsButton = this.containerLocator.getByRole('button', { name: 'Toggle editors' });
|
this.toggleEditorsButton = this.containerLocator.getByRole('button', { name: 'Toggle editors' });
|
||||||
|
this.toggleEditorLayoutButton = this.containerLocator.getByRole('button', { name: 'Toggle editor layout' });
|
||||||
|
// The editor and viewer have slightly different search UI
|
||||||
|
this.editorSearchInput = this.containerLocator.getByPlaceholder('Find');
|
||||||
|
this.viewerSearchInput = this.containerLocator.getByPlaceholder('Search...');
|
||||||
}
|
}
|
||||||
|
|
||||||
public toolbarButtonLocator(title: string) {
|
public toolbarButtonLocator(title: string) {
|
||||||
@ -26,7 +33,7 @@ export default class NoteEditorPage {
|
|||||||
// The note viewer can change content when the note re-renders. As such,
|
// The note viewer can change content when the note re-renders. As such,
|
||||||
// a new locator needs to be created after re-renders (and this can't be a
|
// a new locator needs to be created after re-renders (and this can't be a
|
||||||
// static property).
|
// static property).
|
||||||
return this.page.frame({ url: /.*note-viewer[/\\]index.html.*/ });
|
return this.page.frameLocator('[src$="note-viewer/index.html"]');
|
||||||
}
|
}
|
||||||
|
|
||||||
public getTinyMCEFrameLocator() {
|
public getTinyMCEFrameLocator() {
|
||||||
|
@ -4,12 +4,13 @@ import CodeMirror5Emulation from './CodeMirror5Emulation/CodeMirror5Emulation';
|
|||||||
import editorCommands from './editorCommands/editorCommands';
|
import editorCommands from './editorCommands/editorCommands';
|
||||||
import { Compartment, EditorSelection, Extension, StateEffect } from '@codemirror/state';
|
import { Compartment, EditorSelection, Extension, StateEffect } from '@codemirror/state';
|
||||||
import { updateLink } from './markdown/markdownCommands';
|
import { updateLink } from './markdown/markdownCommands';
|
||||||
import { SearchQuery, setSearchQuery } from '@codemirror/search';
|
import { searchPanelOpen, SearchQuery, setSearchQuery } from '@codemirror/search';
|
||||||
import PluginLoader from './pluginApi/PluginLoader';
|
import PluginLoader from './pluginApi/PluginLoader';
|
||||||
import customEditorCompletion, { editorCompletionSource, enableLanguageDataAutocomplete } from './pluginApi/customEditorCompletion';
|
import customEditorCompletion, { editorCompletionSource, enableLanguageDataAutocomplete } from './pluginApi/customEditorCompletion';
|
||||||
import { CompletionSource } from '@codemirror/autocomplete';
|
import { CompletionSource } from '@codemirror/autocomplete';
|
||||||
import { RegionSpec } from './utils/formatting/RegionSpec';
|
import { RegionSpec } from './utils/formatting/RegionSpec';
|
||||||
import toggleInlineSelectionFormat from './utils/formatting/toggleInlineSelectionFormat';
|
import toggleInlineSelectionFormat from './utils/formatting/toggleInlineSelectionFormat';
|
||||||
|
import getSearchState from './utils/getSearchState';
|
||||||
|
|
||||||
interface Callbacks {
|
interface Callbacks {
|
||||||
onUndoRedo(): void;
|
onUndoRedo(): void;
|
||||||
@ -153,7 +154,15 @@ export default class CodeMirrorControl extends CodeMirror5Emulation implements E
|
|||||||
this._callbacks.onSettingsChange(newSettings);
|
this._callbacks.onSettingsChange(newSettings);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getSearchState(): SearchState {
|
||||||
|
return getSearchState(this.editor.state);
|
||||||
|
}
|
||||||
|
|
||||||
public setSearchState(newState: SearchState) {
|
public setSearchState(newState: SearchState) {
|
||||||
|
if (newState.dialogVisible !== searchPanelOpen(this.editor.state)) {
|
||||||
|
this.execCommand(newState.dialogVisible ? EditorCommandType.ShowSearch : EditorCommandType.HideSearch);
|
||||||
|
}
|
||||||
|
|
||||||
const query = new SearchQuery({
|
const query = new SearchQuery({
|
||||||
search: newState.searchText,
|
search: newState.searchText,
|
||||||
caseSensitive: newState.caseSensitive,
|
caseSensitive: newState.caseSensitive,
|
||||||
@ -161,8 +170,11 @@ export default class CodeMirrorControl extends CodeMirror5Emulation implements E
|
|||||||
replace: newState.replaceText,
|
replace: newState.replaceText,
|
||||||
});
|
});
|
||||||
this.editor.dispatch({
|
this.editor.dispatch({
|
||||||
effects: setSearchQuery.of(query),
|
effects: [
|
||||||
|
setSearchQuery.of(query),
|
||||||
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public addStyles(...styles: Parameters<typeof EditorView.theme>) {
|
public addStyles(...styles: Parameters<typeof EditorView.theme>) {
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
import { Compartment, EditorState, Prec } from '@codemirror/state';
|
import { Compartment, EditorState, Prec } from '@codemirror/state';
|
||||||
import { indentOnInput, syntaxHighlighting } from '@codemirror/language';
|
import { indentOnInput, syntaxHighlighting } from '@codemirror/language';
|
||||||
import {
|
import { openSearchPanel, closeSearchPanel, searchPanelOpen } from '@codemirror/search';
|
||||||
openSearchPanel, closeSearchPanel, getSearchQuery, search,
|
|
||||||
} from '@codemirror/search';
|
|
||||||
|
|
||||||
import { classHighlighter } from '@lezer/highlight';
|
import { classHighlighter } from '@lezer/highlight';
|
||||||
|
|
||||||
@ -15,7 +13,7 @@ import { keymap, KeyBinding } from '@codemirror/view';
|
|||||||
import { searchKeymap } from '@codemirror/search';
|
import { searchKeymap } from '@codemirror/search';
|
||||||
import { historyKeymap } from '@codemirror/commands';
|
import { historyKeymap } from '@codemirror/commands';
|
||||||
|
|
||||||
import { SearchState, EditorProps, EditorSettings } from '../types';
|
import { EditorProps, EditorSettings } from '../types';
|
||||||
import { EditorEventType, SelectionRangeChangeEvent } from '../events';
|
import { EditorEventType, SelectionRangeChangeEvent } from '../events';
|
||||||
import {
|
import {
|
||||||
decreaseIndent, increaseIndent,
|
decreaseIndent, increaseIndent,
|
||||||
@ -32,6 +30,7 @@ import CodeMirrorControl from './CodeMirrorControl';
|
|||||||
import insertLineAfter from './editorCommands/insertLineAfter';
|
import insertLineAfter from './editorCommands/insertLineAfter';
|
||||||
import handlePasteEvent from './utils/handlePasteEvent';
|
import handlePasteEvent from './utils/handlePasteEvent';
|
||||||
import biDirectionalTextExtension from './utils/biDirectionalTextExtension';
|
import biDirectionalTextExtension from './utils/biDirectionalTextExtension';
|
||||||
|
import searchExtension from './utils/searchExtension';
|
||||||
|
|
||||||
const createEditor = (
|
const createEditor = (
|
||||||
parentElement: HTMLElement, props: EditorProps,
|
parentElement: HTMLElement, props: EditorProps,
|
||||||
@ -41,7 +40,6 @@ const createEditor = (
|
|||||||
|
|
||||||
props.onLogMessage('Initializing CodeMirror...');
|
props.onLogMessage('Initializing CodeMirror...');
|
||||||
|
|
||||||
let searchVisible = false;
|
|
||||||
|
|
||||||
// Handles firing an event when the undo/redo stack changes
|
// Handles firing an event when the undo/redo stack changes
|
||||||
let schedulePostUndoRedoDepthChangeId_: ReturnType<typeof setTimeout>|null = null;
|
let schedulePostUndoRedoDepthChangeId_: ReturnType<typeof setTimeout>|null = null;
|
||||||
@ -92,36 +90,6 @@ const createEditor = (
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSearchDialogUpdate = () => {
|
|
||||||
const query = getSearchQuery(editor.state);
|
|
||||||
const searchState: SearchState = {
|
|
||||||
searchText: query.search,
|
|
||||||
replaceText: query.replace,
|
|
||||||
useRegex: query.regexp,
|
|
||||||
caseSensitive: query.caseSensitive,
|
|
||||||
dialogVisible: searchVisible,
|
|
||||||
};
|
|
||||||
props.onEvent({
|
|
||||||
kind: EditorEventType.UpdateSearchDialog,
|
|
||||||
searchState,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const showSearchDialog = () => {
|
|
||||||
if (!searchVisible) {
|
|
||||||
openSearchPanel(editor);
|
|
||||||
}
|
|
||||||
searchVisible = true;
|
|
||||||
onSearchDialogUpdate();
|
|
||||||
};
|
|
||||||
|
|
||||||
const hideSearchDialog = () => {
|
|
||||||
if (searchVisible) {
|
|
||||||
closeSearchPanel(editor);
|
|
||||||
}
|
|
||||||
searchVisible = false;
|
|
||||||
onSearchDialogUpdate();
|
|
||||||
};
|
|
||||||
|
|
||||||
const globalSpellcheckEnabled = () => {
|
const globalSpellcheckEnabled = () => {
|
||||||
return editor.contentDOM.spellcheck;
|
return editor.contentDOM.spellcheck;
|
||||||
@ -188,11 +156,11 @@ const createEditor = (
|
|||||||
const keymapConfig = Prec.low(keymap.of([
|
const keymapConfig = Prec.low(keymap.of([
|
||||||
// Custom mod-f binding: Toggle the external dialog implementation
|
// Custom mod-f binding: Toggle the external dialog implementation
|
||||||
// (don't show/hide the Panel dialog).
|
// (don't show/hide the Panel dialog).
|
||||||
keyCommand('Mod-f', (_: EditorView) => {
|
keyCommand('Mod-f', (editor: EditorView) => {
|
||||||
if (searchVisible) {
|
if (searchPanelOpen(editor.state)) {
|
||||||
hideSearchDialog();
|
closeSearchPanel(editor);
|
||||||
} else {
|
} else {
|
||||||
showSearchDialog();
|
openSearchPanel(editor);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}),
|
}),
|
||||||
@ -226,22 +194,7 @@ const createEditor = (
|
|||||||
|
|
||||||
dynamicConfig.of(configFromSettings(props.settings)),
|
dynamicConfig.of(configFromSettings(props.settings)),
|
||||||
historyCompartment.of(history()),
|
historyCompartment.of(history()),
|
||||||
|
searchExtension(props.onEvent, props.settings),
|
||||||
search(settings.useExternalSearch ? {
|
|
||||||
createPanel(_: EditorView) {
|
|
||||||
return {
|
|
||||||
// The actual search dialog is implemented with react native,
|
|
||||||
// use a dummy element.
|
|
||||||
dom: document.createElement('div'),
|
|
||||||
mount() {
|
|
||||||
showSearchDialog();
|
|
||||||
},
|
|
||||||
destroy() {
|
|
||||||
hideSearchDialog();
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
} : undefined),
|
|
||||||
|
|
||||||
// Allows multiple selections and allows selecting a rectangle
|
// Allows multiple selections and allows selecting a rectangle
|
||||||
// with ctrl (as in CodeMirror 5)
|
// with ctrl (as in CodeMirror 5)
|
||||||
@ -295,6 +248,7 @@ const createEditor = (
|
|||||||
notifySelectionChange(viewUpdate);
|
notifySelectionChange(viewUpdate);
|
||||||
notifySelectionFormattingChange(viewUpdate);
|
notifySelectionFormattingChange(viewUpdate);
|
||||||
}),
|
}),
|
||||||
|
|
||||||
],
|
],
|
||||||
doc: initialText,
|
doc: initialText,
|
||||||
}),
|
}),
|
||||||
|
17
packages/editor/CodeMirror/utils/getSearchState.ts
Normal file
17
packages/editor/CodeMirror/utils/getSearchState.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { EditorState } from '@codemirror/state';
|
||||||
|
import { SearchState } from '../../types';
|
||||||
|
import { getSearchQuery, searchPanelOpen } from '@codemirror/search';
|
||||||
|
|
||||||
|
const getSearchState = (state: EditorState) => {
|
||||||
|
const query = getSearchQuery(state);
|
||||||
|
const searchState: SearchState = {
|
||||||
|
searchText: query.search,
|
||||||
|
replaceText: query.replace,
|
||||||
|
useRegex: query.regexp,
|
||||||
|
caseSensitive: query.caseSensitive,
|
||||||
|
dialogVisible: searchPanelOpen(state),
|
||||||
|
};
|
||||||
|
return searchState;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getSearchState;
|
40
packages/editor/CodeMirror/utils/searchExtension.ts
Normal file
40
packages/editor/CodeMirror/utils/searchExtension.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import { EditorState, Extension } from '@codemirror/state';
|
||||||
|
import { EditorView } from '@codemirror/view';
|
||||||
|
import { EditorSettings, OnEventCallback } from '../../types';
|
||||||
|
import getSearchState from './getSearchState';
|
||||||
|
import { EditorEventType } from '../../events';
|
||||||
|
import { search, searchPanelOpen, setSearchQuery } from '@codemirror/search';
|
||||||
|
|
||||||
|
const searchExtension = (onEvent: OnEventCallback, settings: EditorSettings): Extension => {
|
||||||
|
const onSearchDialogUpdate = (state: EditorState) => {
|
||||||
|
const newSearchState = getSearchState(state);
|
||||||
|
|
||||||
|
onEvent({
|
||||||
|
kind: EditorEventType.UpdateSearchDialog,
|
||||||
|
searchState: newSearchState,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return [
|
||||||
|
search(settings.useExternalSearch ? {
|
||||||
|
createPanel(_editor: EditorView) {
|
||||||
|
return {
|
||||||
|
// The actual search dialog is implemented with react native,
|
||||||
|
// use a dummy element.
|
||||||
|
dom: document.createElement('div'),
|
||||||
|
mount() { },
|
||||||
|
destroy() { },
|
||||||
|
};
|
||||||
|
},
|
||||||
|
} : undefined),
|
||||||
|
|
||||||
|
EditorState.transactionExtender.of((tr) => {
|
||||||
|
if (tr.effects.some(e => e.is(setSearchQuery)) || searchPanelOpen(tr.state) !== searchPanelOpen(tr.startState)) {
|
||||||
|
onSearchDialogUpdate(tr.state);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
export default searchExtension;
|
Loading…
Reference in New Issue
Block a user