mirror of
				https://github.com/TriliumNext/Notes.git
				synced 2025-10-31 04:51:31 +08:00 
			
		
		
		
	Merge branch 'custom-search-dialog'
This commit is contained in:
		
						commit
						82fcc97ed2
					
				| @ -367,25 +367,61 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain | |||||||
|     /** |     /** | ||||||
|      * Adds given text to the editor cursor |      * Adds given text to the editor cursor | ||||||
|      * |      * | ||||||
|  |      * @deprecated use addTextToActiveContextEditor() instead | ||||||
|      * @param {string} text - this must be clear text, HTML is not supported. |      * @param {string} text - this must be clear text, HTML is not supported. | ||||||
|      * @method |      * @method | ||||||
|      */ |      */ | ||||||
|     this.addTextToActiveTabEditor = text => appContext.triggerCommand('addTextToActiveEditor', {text}); |     this.addTextToActiveTabEditor = text => { | ||||||
|  |         console.warn("api.addTextToActiveTabEditor() is deprecated, use addTextToActiveContextEditor() instead."); | ||||||
|  | 
 | ||||||
|  |         return appContext.triggerCommand('addTextToActiveEditor', {text}); | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Adds given text to the editor cursor | ||||||
|  |      * | ||||||
|  |      * @param {string} text - this must be clear text, HTML is not supported. | ||||||
|  |      * @method | ||||||
|  |      */ | ||||||
|  |     this.addTextToActiveContextEditor = text => appContext.triggerCommand('addTextToActiveEditor', {text}); | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * @method | ||||||
|  |      * @deprecated use getActiveContextNote() instead | ||||||
|  |      * @returns {NoteShort} active note (loaded into right pane) | ||||||
|  |      */ | ||||||
|  |     this.getActiveTabNote = () => { | ||||||
|  |         console.warn("api.getActiveTabNote() is deprecated, use getActiveContextNote() instead."); | ||||||
|  | 
 | ||||||
|  |         return appContext.tabManager.getActiveContextNote(); | ||||||
|  |     }; | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * @method |      * @method | ||||||
|      * @returns {NoteShort} active note (loaded into right pane) |      * @returns {NoteShort} active note (loaded into right pane) | ||||||
|      */ |      */ | ||||||
|     this.getActiveTabNote = () => appContext.tabManager.getActiveContextNote(); |     this.getActiveContextNote = () => appContext.tabManager.getActiveContextNote(); | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * See https://ckeditor.com/docs/ckeditor5/latest/api/module_core_editor_editor-Editor.html for a documentation on the returned instance. | ||||||
|  |      * | ||||||
|  |      * @deprecated use getActiveContextTextEditor() | ||||||
|  |      * @method | ||||||
|  |      * @param [callback] - callback receiving "textEditor" instance | ||||||
|  |      */ | ||||||
|  |     this.getActiveTabTextEditor = callback => { | ||||||
|  |         console.warn("api.getActiveTabTextEditor() is deprecated, use getActiveContextTextEditor() instead."); | ||||||
|  | 
 | ||||||
|  |         return appContext.tabManager.getActiveContextTextEditor(callback); | ||||||
|  |     }; | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * See https://ckeditor.com/docs/ckeditor5/latest/api/module_core_editor_editor-Editor.html for a documentation on the returned instance. |      * See https://ckeditor.com/docs/ckeditor5/latest/api/module_core_editor_editor-Editor.html for a documentation on the returned instance. | ||||||
|      * |      * | ||||||
|      * @method |      * @method | ||||||
|      * @param [callback] - deprecated (use returned promise): callback receiving "textEditor" instance |  | ||||||
|      * @returns {Promise<CKEditor>} instance of CKEditor |      * @returns {Promise<CKEditor>} instance of CKEditor | ||||||
|      */ |      */ | ||||||
|     this.getActiveTabTextEditor = callback => new Promise(resolve => appContext.triggerCommand('executeInActiveTextEditor', {callback, resolve})); |     this.getActiveContextTextEditor = () => appContext.tabManager.getActiveContextTextEditor(); | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * See https://codemirror.net/doc/manual.html#api |      * See https://codemirror.net/doc/manual.html#api | ||||||
| @ -393,7 +429,7 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain | |||||||
|      * @method |      * @method | ||||||
|      * @returns {Promise<CodeMirror>} instance of CodeMirror |      * @returns {Promise<CodeMirror>} instance of CodeMirror | ||||||
|      */ |      */ | ||||||
|     this.getActiveTabCodeEditor = () => new Promise(resolve => appContext.triggerCommand('executeInActiveCodeEditor', {callback: resolve})); |     this.getActiveContextCodeEditor = () => appContext.tabManager.getActiveContextCodeEditor(); | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Get access to the widget handling note detail. Methods like `getWidgetType()` and `getTypeWidget()` to get to the |      * Get access to the widget handling note detail. Methods like `getWidgetType()` and `getTypeWidget()` to get to the | ||||||
| @ -406,9 +442,20 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain | |||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * @method |      * @method | ||||||
|  |      * @deprecated use getActiveContextNotePath() instead | ||||||
|      * @returns {Promise<string|null>} returns note path of active note or null if there isn't active note |      * @returns {Promise<string|null>} returns note path of active note or null if there isn't active note | ||||||
|      */ |      */ | ||||||
|     this.getActiveTabNotePath = () => appContext.tabManager.getActiveContextNotePath(); |     this.getActiveTabNotePath = () => { | ||||||
|  |         console.warn("api.getActiveTabNotePath() is deprecated, use getActiveContextNotePath() instead."); | ||||||
|  | 
 | ||||||
|  |         return appContext.tabManager.getActiveContextNotePath(); | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * @method | ||||||
|  |      * @returns {Promise<string|null>} returns note path of active note or null if there isn't active note | ||||||
|  |      */ | ||||||
|  |     this.getActiveContextNotePath = () => appContext.tabManager.getActiveContextNotePath(); | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Returns component which owns given DOM element (the nearest parent component in DOM tree) |      * Returns component which owns given DOM element (the nearest parent component in DOM tree) | ||||||
|  | |||||||
							
								
								
									
										11
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										11
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @ -25,7 +25,6 @@ | |||||||
|         "ejs": "3.1.8", |         "ejs": "3.1.8", | ||||||
|         "electron-debug": "3.2.0", |         "electron-debug": "3.2.0", | ||||||
|         "electron-dl": "3.3.1", |         "electron-dl": "3.3.1", | ||||||
|         "electron-find": "1.0.7", |  | ||||||
|         "electron-window-state": "5.0.3", |         "electron-window-state": "5.0.3", | ||||||
|         "express": "4.18.1", |         "express": "4.18.1", | ||||||
|         "express-partial-content": "1.0.2", |         "express-partial-content": "1.0.2", | ||||||
| @ -3575,11 +3574,6 @@ | |||||||
|         "url": "https://github.com/sponsors/sindresorhus" |         "url": "https://github.com/sponsors/sindresorhus" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "node_modules/electron-find": { |  | ||||||
|       "version": "1.0.7", |  | ||||||
|       "resolved": "https://registry.npmjs.org/electron-find/-/electron-find-1.0.7.tgz", |  | ||||||
|       "integrity": "sha512-C2FQJuk8567P2a2loBNwl5c8kwOTQVMB0capgHtPI7zKwZG16X0UxG+sNYZExQfnJ0PA+ecECA/4LcXxQa2TCA==" |  | ||||||
|     }, |  | ||||||
|     "node_modules/electron-installer-common": { |     "node_modules/electron-installer-common": { | ||||||
|       "version": "0.10.3", |       "version": "0.10.3", | ||||||
|       "resolved": "https://registry.npmjs.org/electron-installer-common/-/electron-installer-common-0.10.3.tgz", |       "resolved": "https://registry.npmjs.org/electron-installer-common/-/electron-installer-common-0.10.3.tgz", | ||||||
| @ -13742,11 +13736,6 @@ | |||||||
|         "unused-filename": "^2.1.0" |         "unused-filename": "^2.1.0" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "electron-find": { |  | ||||||
|       "version": "1.0.7", |  | ||||||
|       "resolved": "https://registry.npmjs.org/electron-find/-/electron-find-1.0.7.tgz", |  | ||||||
|       "integrity": "sha512-C2FQJuk8567P2a2loBNwl5c8kwOTQVMB0capgHtPI7zKwZG16X0UxG+sNYZExQfnJ0PA+ecECA/4LcXxQa2TCA==" |  | ||||||
|     }, |  | ||||||
|     "electron-installer-common": { |     "electron-installer-common": { | ||||||
|       "version": "0.10.3", |       "version": "0.10.3", | ||||||
|       "resolved": "https://registry.npmjs.org/electron-installer-common/-/electron-installer-common-0.10.3.tgz", |       "resolved": "https://registry.npmjs.org/electron-installer-common/-/electron-installer-common-0.10.3.tgz", | ||||||
|  | |||||||
| @ -40,7 +40,6 @@ | |||||||
|     "ejs": "3.1.8", |     "ejs": "3.1.8", | ||||||
|     "electron-debug": "3.2.0", |     "electron-debug": "3.2.0", | ||||||
|     "electron-dl": "3.3.1", |     "electron-dl": "3.3.1", | ||||||
|     "electron-find": "1.0.7", |  | ||||||
|     "electron-window-state": "5.0.3", |     "electron-window-state": "5.0.3", | ||||||
|     "express": "4.18.1", |     "express": "4.18.1", | ||||||
|     "express-partial-content": "1.0.2", |     "express-partial-content": "1.0.2", | ||||||
|  | |||||||
| @ -16,7 +16,7 @@ async function convertMarkdownToHtml(text) { | |||||||
| 
 | 
 | ||||||
|     const result = writer.render(parsed); |     const result = writer.render(parsed); | ||||||
| 
 | 
 | ||||||
|     appContext.triggerCommand('executeInActiveTextEditor', { |     appContext.triggerCommand('executeInTextEditor', { | ||||||
|         callback: textEditor => { |         callback: textEditor => { | ||||||
|             const viewFragment = textEditor.data.processor.toView(result); |             const viewFragment = textEditor.data.processor.toView(result); | ||||||
|             const modelFragment = textEditor.data.toModel(viewFragment); |             const modelFragment = textEditor.data.toModel(viewFragment); | ||||||
| @ -24,7 +24,8 @@ async function convertMarkdownToHtml(text) { | |||||||
|             textEditor.model.insertContent(modelFragment, textEditor.model.document.selection); |             textEditor.model.insertContent(modelFragment, textEditor.model.document.selection); | ||||||
| 
 | 
 | ||||||
|             toastService.showMessage("Markdown content has been imported into the document."); |             toastService.showMessage("Markdown content has been imported into the document."); | ||||||
|         } |         }, | ||||||
|  |         ntxId: this.ntxId | ||||||
|     }); |     }); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -48,6 +48,7 @@ import BookmarkButtons from "../widgets/bookmark_buttons.js"; | |||||||
| import NoteWrapperWidget from "../widgets/note_wrapper.js"; | import NoteWrapperWidget from "../widgets/note_wrapper.js"; | ||||||
| import BacklinksWidget from "../widgets/backlinks.js"; | import BacklinksWidget from "../widgets/backlinks.js"; | ||||||
| import SharedInfoWidget from "../widgets/shared_info.js"; | import SharedInfoWidget from "../widgets/shared_info.js"; | ||||||
|  | import FindWidget from "../widgets/find.js"; | ||||||
| 
 | 
 | ||||||
| export default class DesktopLayout { | export default class DesktopLayout { | ||||||
|     constructor(customWidgets) { |     constructor(customWidgets) { | ||||||
| @ -161,6 +162,7 @@ export default class DesktopLayout { | |||||||
|                                         .child(new SearchResultWidget()) |                                         .child(new SearchResultWidget()) | ||||||
|                                         .child(new SqlResultWidget()) |                                         .child(new SqlResultWidget()) | ||||||
|                                 ) |                                 ) | ||||||
|  |                                 .child(new FindWidget()) | ||||||
|                                 .child(...this.customWidgets.get('node-detail-pane')) |                                 .child(...this.customWidgets.get('node-detail-pane')) | ||||||
|                             ) |                             ) | ||||||
|                         ) |                         ) | ||||||
|  | |||||||
| @ -39,29 +39,6 @@ export default class Entrypoints extends Component { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     findInTextCommand() { |  | ||||||
|         if (!utils.isElectron()) { |  | ||||||
|             return; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         const remote = utils.dynamicRequire('@electron/remote'); |  | ||||||
|         const {FindInPage} = utils.dynamicRequire('electron-find'); |  | ||||||
|         const findInPage = new FindInPage(remote.getCurrentWebContents(), { |  | ||||||
|             offsetTop: 10, |  | ||||||
|             offsetRight: 10, |  | ||||||
|             boxBgColor: 'var(--main-background-color)', |  | ||||||
|             boxShadowColor: '#000', |  | ||||||
|             inputColor: 'var(--input-text-color)', |  | ||||||
|             inputBgColor: 'var(--input-background-color)', |  | ||||||
|             inputFocusColor: '#555', |  | ||||||
|             textColor: 'var(--main-text-color)', |  | ||||||
|             textHoverBgColor: '#555', |  | ||||||
|             caseSelectedColor: 'var(--main-border-color)' |  | ||||||
|         }); |  | ||||||
| 
 |  | ||||||
|         findInPage.openFindWindow(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     async createNoteIntoInboxCommand() { |     async createNoteIntoInboxCommand() { | ||||||
|         const inboxNote = await dateNoteService.getInboxNote(); |         const inboxNote = await dateNoteService.getInboxNote(); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -339,25 +339,61 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain | |||||||
|     /** |     /** | ||||||
|      * Adds given text to the editor cursor |      * Adds given text to the editor cursor | ||||||
|      * |      * | ||||||
|  |      * @deprecated use addTextToActiveContextEditor() instead | ||||||
|      * @param {string} text - this must be clear text, HTML is not supported. |      * @param {string} text - this must be clear text, HTML is not supported. | ||||||
|      * @method |      * @method | ||||||
|      */ |      */ | ||||||
|     this.addTextToActiveTabEditor = text => appContext.triggerCommand('addTextToActiveEditor', {text}); |     this.addTextToActiveTabEditor = text => { | ||||||
|  |         console.warn("api.addTextToActiveTabEditor() is deprecated, use addTextToActiveContextEditor() instead."); | ||||||
|  | 
 | ||||||
|  |         return appContext.triggerCommand('addTextToActiveEditor', {text}); | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Adds given text to the editor cursor | ||||||
|  |      * | ||||||
|  |      * @param {string} text - this must be clear text, HTML is not supported. | ||||||
|  |      * @method | ||||||
|  |      */ | ||||||
|  |     this.addTextToActiveContextEditor = text => appContext.triggerCommand('addTextToActiveEditor', {text}); | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * @method | ||||||
|  |      * @deprecated use getActiveContextNote() instead | ||||||
|  |      * @returns {NoteShort} active note (loaded into right pane) | ||||||
|  |      */ | ||||||
|  |     this.getActiveTabNote = () => { | ||||||
|  |         console.warn("api.getActiveTabNote() is deprecated, use getActiveContextNote() instead."); | ||||||
|  | 
 | ||||||
|  |         return appContext.tabManager.getActiveContextNote(); | ||||||
|  |     }; | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * @method |      * @method | ||||||
|      * @returns {NoteShort} active note (loaded into right pane) |      * @returns {NoteShort} active note (loaded into right pane) | ||||||
|      */ |      */ | ||||||
|     this.getActiveTabNote = () => appContext.tabManager.getActiveContextNote(); |     this.getActiveContextNote = () => appContext.tabManager.getActiveContextNote(); | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * See https://ckeditor.com/docs/ckeditor5/latest/api/module_core_editor_editor-Editor.html for a documentation on the returned instance.
 | ||||||
|  |      * | ||||||
|  |      * @deprecated use getActiveContextTextEditor() | ||||||
|  |      * @method | ||||||
|  |      * @param [callback] - callback receiving "textEditor" instance | ||||||
|  |      */ | ||||||
|  |     this.getActiveTabTextEditor = callback => { | ||||||
|  |         console.warn("api.getActiveTabTextEditor() is deprecated, use getActiveContextTextEditor() instead."); | ||||||
|  | 
 | ||||||
|  |         return appContext.tabManager.getActiveContext()?.getTextEditor(callback); | ||||||
|  |     }; | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * See https://ckeditor.com/docs/ckeditor5/latest/api/module_core_editor_editor-Editor.html for a documentation on the returned instance.
 |      * See https://ckeditor.com/docs/ckeditor5/latest/api/module_core_editor_editor-Editor.html for a documentation on the returned instance.
 | ||||||
|      * |      * | ||||||
|      * @method |      * @method | ||||||
|      * @param [callback] - deprecated (use returned promise): callback receiving "textEditor" instance |  | ||||||
|      * @returns {Promise<CKEditor>} instance of CKEditor |      * @returns {Promise<CKEditor>} instance of CKEditor | ||||||
|      */ |      */ | ||||||
|     this.getActiveTabTextEditor = callback => new Promise(resolve => appContext.triggerCommand('executeInActiveTextEditor', {callback, resolve})); |     this.getActiveContextTextEditor = () => appContext.tabManager.getActiveContext()?.getTextEditor(); | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * See https://codemirror.net/doc/manual.html#api
 |      * See https://codemirror.net/doc/manual.html#api
 | ||||||
| @ -365,7 +401,7 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain | |||||||
|      * @method |      * @method | ||||||
|      * @returns {Promise<CodeMirror>} instance of CodeMirror |      * @returns {Promise<CodeMirror>} instance of CodeMirror | ||||||
|      */ |      */ | ||||||
|     this.getActiveTabCodeEditor = () => new Promise(resolve => appContext.triggerCommand('executeInActiveCodeEditor', {callback: resolve})); |     this.getActiveContextCodeEditor = () => appContext.tabManager.getActiveContext()?.getCodeEditor(); | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Get access to the widget handling note detail. Methods like `getWidgetType()` and `getTypeWidget()` to get to the |      * Get access to the widget handling note detail. Methods like `getWidgetType()` and `getTypeWidget()` to get to the | ||||||
| @ -378,9 +414,20 @@ function FrontendScriptApi(startNote, currentNote, originEntity = null, $contain | |||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * @method |      * @method | ||||||
|  |      * @deprecated use getActiveContextNotePath() instead | ||||||
|      * @returns {Promise<string|null>} returns note path of active note or null if there isn't active note |      * @returns {Promise<string|null>} returns note path of active note or null if there isn't active note | ||||||
|      */ |      */ | ||||||
|     this.getActiveTabNotePath = () => appContext.tabManager.getActiveContextNotePath(); |     this.getActiveTabNotePath = () => { | ||||||
|  |         console.warn("api.getActiveTabNotePath() is deprecated, use getActiveContextNotePath() instead."); | ||||||
|  | 
 | ||||||
|  |         return appContext.tabManager.getActiveContextNotePath(); | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * @method | ||||||
|  |      * @returns {Promise<string|null>} returns note path of active note or null if there isn't active note | ||||||
|  |      */ | ||||||
|  |     this.getActiveContextNotePath = () => appContext.tabManager.getActiveContextNotePath(); | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Returns component which owns given DOM element (the nearest parent component in DOM tree) |      * Returns component which owns given DOM element (the nearest parent component in DOM tree) | ||||||
|  | |||||||
| @ -226,6 +226,21 @@ class NoteContext extends Component { | |||||||
|             && this.note.mime !== 'text/x-sqlite;schema=trilium' |             && this.note.mime !== 'text/x-sqlite;schema=trilium' | ||||||
|             && !this.note.hasLabel('hideChildrenOverview'); |             && !this.note.hasLabel('hideChildrenOverview'); | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     async getTextEditor(callback) { | ||||||
|  |         return new Promise(resolve => appContext.triggerCommand('executeInTextEditor', { | ||||||
|  |             callback, | ||||||
|  |             resolve, | ||||||
|  |             ntxId: this.ntxId | ||||||
|  |         })); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     async getCodeEditor() { | ||||||
|  |         return new Promise(resolve => appContext.triggerCommand('executeInCodeEditor', { | ||||||
|  |             resolve, | ||||||
|  |             ntxId: this.ntxId | ||||||
|  |         })); | ||||||
|  |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export default NoteContext; | export default NoteContext; | ||||||
|  | |||||||
							
								
								
									
										235
									
								
								src/public/app/widgets/find.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										235
									
								
								src/public/app/widgets/find.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,235 @@ | |||||||
|  | /** | ||||||
|  |  * (c) Antonio Tejada 2022 | ||||||
|  |  * https://github.com/antoniotejada/Trilium-FindWidget
 | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | import NoteContextAwareWidget from "./note_context_aware_widget.js"; | ||||||
|  | import FindInText from "./find_in_text.js"; | ||||||
|  | import FindInCode from "./find_in_code.js"; | ||||||
|  | 
 | ||||||
|  | const findWidgetDelayMillis = 200; | ||||||
|  | const waitForEnter = (findWidgetDelayMillis < 0); | ||||||
|  | 
 | ||||||
|  | // tabIndex=-1 on the checkbox labels is necessary so when clicking on the label
 | ||||||
|  | // the focusout handler is called with relatedTarget equal to the label instead
 | ||||||
|  | // of undefined. It's -1 instead of > 0, so they don't tabstop
 | ||||||
|  | const TPL = ` | ||||||
|  | <div style="contain: none;"> | ||||||
|  |     <style> | ||||||
|  |         .find-widget-box { | ||||||
|  |             padding: 10px; | ||||||
|  |             border-top: 1px solid var(--main-border-color);  | ||||||
|  |             align-items: center; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         .find-widget-box > * { | ||||||
|  |             margin-right: 15px; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         .find-widget-box { | ||||||
|  |             display: flex; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         .find-widget-found-wrapper { | ||||||
|  |             font-weight: bold; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         .find-widget-search-term-input { | ||||||
|  |             max-width: 250px; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         .find-widget-spacer { | ||||||
|  |             flex-grow: 1; | ||||||
|  |         } | ||||||
|  |     </style> | ||||||
|  | 
 | ||||||
|  |     <div class="find-widget-box"> | ||||||
|  |         <input type="text" class="form-control find-widget-search-term-input"> | ||||||
|  |          | ||||||
|  |         <div class="form-check"> | ||||||
|  |             <label tabIndex="-1" class="form-check-label"> | ||||||
|  |                 <input type="checkbox" class="form-check-input find-widget-case-sensitive-checkbox">  | ||||||
|  |                 case sensitive | ||||||
|  |             </label> | ||||||
|  |         </div> | ||||||
|  | 
 | ||||||
|  |         <div class="form-check"> | ||||||
|  |             <label tabIndex="-1" class="form-check-label"> | ||||||
|  |                 <input type="checkbox" class="form-check-input find-widget-match-words-checkbox">  | ||||||
|  |                 match words | ||||||
|  |             </label> | ||||||
|  |         </div> | ||||||
|  |          | ||||||
|  |         <div class="find-widget-found-wrapper"> | ||||||
|  |             <span class="find-widget-current-found">0</span> | ||||||
|  |             / | ||||||
|  |             <span class="find-widget-total-found">0</span> | ||||||
|  |         </div> | ||||||
|  |          | ||||||
|  |         <div class="find-widget-spacer"></div> | ||||||
|  |          | ||||||
|  |         <div class="find-widget-close-button"><button class="btn icon-action bx bx-x"></button></div> | ||||||
|  |     </div> | ||||||
|  | </div>`; | ||||||
|  | 
 | ||||||
|  | export default class FindWidget extends NoteContextAwareWidget { | ||||||
|  |     constructor() { | ||||||
|  |         super(); | ||||||
|  | 
 | ||||||
|  |         this.searchTerm = null; | ||||||
|  | 
 | ||||||
|  |         this.textHandler = new FindInText(this); | ||||||
|  |         this.codeHandler = new FindInCode(this); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     doRender() { | ||||||
|  |         this.$widget = $(TPL); | ||||||
|  |         this.$findBox = this.$widget.find('.find-widget-box'); | ||||||
|  |         this.$findBox.hide(); | ||||||
|  |         this.$input = this.$widget.find('.find-widget-search-term-input'); | ||||||
|  |         this.$currentFound = this.$widget.find('.find-widget-current-found'); | ||||||
|  |         this.$totalFound = this.$widget.find('.find-widget-total-found'); | ||||||
|  |         this.$caseSensitiveCheckbox = this.$widget.find(".find-widget-case-sensitive-checkbox"); | ||||||
|  |         this.$caseSensitiveCheckbox.change(() => this.performFind()); | ||||||
|  |         this.$matchWordsCheckbox = this.$widget.find(".find-widget-match-words-checkbox"); | ||||||
|  |         this.$matchWordsCheckbox.change(() => this.performFind()); | ||||||
|  |         this.$closeButton = this.$widget.find(".find-widget-close-button"); | ||||||
|  |         this.$closeButton.on("click", () => this.closeSearch()); | ||||||
|  | 
 | ||||||
|  |         this.$input.keydown(async e => { | ||||||
|  |             if ((e.metaKey || e.ctrlKey) && (e.key === 'F' || e.key === 'f')) { | ||||||
|  |                 // If ctrl+f is pressed when the findbox is shown, select the
 | ||||||
|  |                 // whole input to find
 | ||||||
|  |                 this.$input.select(); | ||||||
|  |             } else if (e.key === 'Enter' || e.key === 'F3') { | ||||||
|  |                 await this.findNext(e); | ||||||
|  |                 e.preventDefault(); | ||||||
|  |                 return false; | ||||||
|  |             } else if (e.key === 'Escape') { | ||||||
|  |                 await this.closeSearch(); | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         this.$input.on('input', () => this.startSearch()); | ||||||
|  | 
 | ||||||
|  |         return this.$widget; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     startSearch() { | ||||||
|  |         // XXX This should clear the previous search immediately in all cases
 | ||||||
|  |         //     (the search is stale when waitforenter but also while the
 | ||||||
|  |         //     delay is running for non waitforenter case)
 | ||||||
|  |         if (!waitForEnter) { | ||||||
|  |             // Clear the previous timeout if any, it's ok if timeoutId is
 | ||||||
|  |             // null or undefined
 | ||||||
|  |             clearTimeout(this.timeoutId); | ||||||
|  | 
 | ||||||
|  |             // Defer the search a few millis so the search doesn't start
 | ||||||
|  |             // immediately, as this can cause search word typing lag with
 | ||||||
|  |             // one or two-char searchwords and long notes
 | ||||||
|  |             // See https://github.com/antoniotejada/Trilium-FindWidget/issues/1
 | ||||||
|  |             this.timeoutId = setTimeout(async () => { | ||||||
|  |                 this.timeoutId = null; | ||||||
|  |                 await this.performFind(); | ||||||
|  |             }, findWidgetDelayMillis); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     async findNext(e) { | ||||||
|  |         const searchTerm = this.$input.val(); | ||||||
|  |         if (waitForEnter && this.searchTerm !== searchTerm) { | ||||||
|  |             await this.performFind(); | ||||||
|  |         } | ||||||
|  |         const totalFound = parseInt(this.$totalFound.text()); | ||||||
|  |         const currentFound = parseInt(this.$currentFound.text()) - 1; | ||||||
|  | 
 | ||||||
|  |         if (totalFound > 0) { | ||||||
|  |             const direction = e.shiftKey ? -1 : 1; | ||||||
|  |             let nextFound = currentFound + direction; | ||||||
|  |             // Wrap around
 | ||||||
|  |             if (nextFound > totalFound - 1) { | ||||||
|  |                 nextFound = 0; | ||||||
|  |             } else if (nextFound < 0) { | ||||||
|  |                 nextFound = totalFound - 1; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             this.$currentFound.text(nextFound + 1); | ||||||
|  | 
 | ||||||
|  |             await this.handler.findNext(direction, currentFound, nextFound); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     async findInTextEvent() { | ||||||
|  |         if (!this.isActiveNoteContext()) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // Only writeable text and code supported
 | ||||||
|  |         const readOnly = await this.noteContext.isReadOnly(); | ||||||
|  | 
 | ||||||
|  |         if (readOnly || !['text', 'code'].includes(this.note.type) || !this.$findBox.is(":hidden")) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         this.$findBox.show(); | ||||||
|  |         this.$input.focus(); | ||||||
|  |         this.$totalFound.text(0); | ||||||
|  |         this.$currentFound.text(0); | ||||||
|  | 
 | ||||||
|  |         const searchTerm = await this.handler.getInitialSearchTerm(); | ||||||
|  | 
 | ||||||
|  |         this.$input.val(searchTerm || ""); | ||||||
|  | 
 | ||||||
|  |         // Directly perform the search if there's some text to
 | ||||||
|  |         // find, without delaying or waiting for enter
 | ||||||
|  |         if (searchTerm !== "") { | ||||||
|  |             this.$input.select(); | ||||||
|  |             await this.performFind(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** Perform the find and highlight the find results. */ | ||||||
|  |     async performFind() { | ||||||
|  |         const searchTerm = this.$input.val(); | ||||||
|  |         const matchCase = this.$caseSensitiveCheckbox.prop("checked"); | ||||||
|  |         const wholeWord = this.$matchWordsCheckbox.prop("checked"); | ||||||
|  | 
 | ||||||
|  |         const {totalFound, currentFound} = await this.handler.performFind(searchTerm, matchCase, wholeWord); | ||||||
|  | 
 | ||||||
|  |         this.$totalFound.text(totalFound); | ||||||
|  |         this.$currentFound.text(currentFound); | ||||||
|  | 
 | ||||||
|  |         this.searchTerm = searchTerm; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     async closeSearch() { | ||||||
|  |         this.$findBox.hide(); | ||||||
|  | 
 | ||||||
|  |         // Restore any state, if there's a current occurrence clear markers
 | ||||||
|  |         // and scroll to and select the last occurrence
 | ||||||
|  |         const totalFound = parseInt(this.$totalFound.text()); | ||||||
|  |         const currentFound = parseInt(this.$currentFound.text()) - 1; | ||||||
|  | 
 | ||||||
|  |         if (totalFound > 0) { | ||||||
|  |             await this.handler.cleanup(totalFound, currentFound); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         this.searchTerm = null; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     async entitiesReloadedEvent({loadResults}) { | ||||||
|  |         if (loadResults.isNoteContentReloaded(this.noteId)) { | ||||||
|  |             this.refresh(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     isEnabled() { | ||||||
|  |         return super.isEnabled() && ['text', 'code'].includes(this.note.type); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     get handler() { | ||||||
|  |         return this.note.type === "code" | ||||||
|  |             ? this.codeHandler | ||||||
|  |             : this.textHandler; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										198
									
								
								src/public/app/widgets/find_in_code.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										198
									
								
								src/public/app/widgets/find_in_code.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,198 @@ | |||||||
|  | // ck-find-result and ck-find-result_selected are the styles ck-editor
 | ||||||
|  | // uses for highlighting matches, use the same one on CodeMirror
 | ||||||
|  | // for consistency
 | ||||||
|  | const FIND_RESULT_SELECTED_CSS_CLASSNAME = "ck-find-result_selected"; | ||||||
|  | const FIND_RESULT_CSS_CLASSNAME = "ck-find-result"; | ||||||
|  | 
 | ||||||
|  | const escapeRegExp = str => str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); | ||||||
|  | 
 | ||||||
|  | export default class FindInCode { | ||||||
|  |     constructor(parent) { | ||||||
|  |         this.parent = parent; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     async getCodeEditor() { | ||||||
|  |         return this.parent.noteContext.getCodeEditor(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     async getInitialSearchTerm() { | ||||||
|  |         const codeEditor = await this.getCodeEditor(); | ||||||
|  | 
 | ||||||
|  |         // highlightSelectionMatches is the overlay that highlights
 | ||||||
|  |         // the words under the cursor. This occludes the search
 | ||||||
|  |         // markers style, save it, disable it. Will be restored when
 | ||||||
|  |         // the focus is back into the note
 | ||||||
|  |         this.oldHighlightSelectionMatches = codeEditor.getOption("highlightSelectionMatches"); | ||||||
|  |         codeEditor.setOption("highlightSelectionMatches", false); | ||||||
|  | 
 | ||||||
|  |         // Fill in the findbox with the current selection if any
 | ||||||
|  |         const selectedText = codeEditor.getSelection() | ||||||
|  |         if (selectedText !== "") { | ||||||
|  |             return selectedText; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     async performFind(searchTerm, matchCase, wholeWord) { | ||||||
|  |         let findResult = null; | ||||||
|  |         let totalFound = 0; | ||||||
|  |         let currentFound = -1; | ||||||
|  | 
 | ||||||
|  |         // See https://codemirror.net/addon/search/searchcursor.js for tips
 | ||||||
|  |         const codeEditor = await this.getCodeEditor(); | ||||||
|  |         const doc = codeEditor.doc; | ||||||
|  |         const text = doc.getValue(); | ||||||
|  | 
 | ||||||
|  |         // Clear all markers
 | ||||||
|  |         if (this.findResult != null) { | ||||||
|  |             codeEditor.operation(() => { | ||||||
|  |                 for (let i = 0; i < this.findResult.length; ++i) { | ||||||
|  |                     const marker = this.findResult[i]; | ||||||
|  |                     marker.clear(); | ||||||
|  |                 } | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if (searchTerm !== "") { | ||||||
|  |             searchTerm = escapeRegExp(searchTerm); | ||||||
|  | 
 | ||||||
|  |             // Find and highlight matches
 | ||||||
|  |             // Find and highlight matches
 | ||||||
|  |             // XXX Using \\b and not using the unicode flag probably doesn't
 | ||||||
|  |             //     work with non ascii alphabets, findAndReplace uses a more
 | ||||||
|  |             //     complicated regexp, see
 | ||||||
|  |             //     https://github.com/ckeditor/ckeditor5/blob/b95e2faf817262ac0e1e21993d9c0bde3f1be594/packages/ckeditor5-find-and-replace/src/utils.js#L145
 | ||||||
|  |             const wholeWordChar = wholeWord ? "\\b" : ""; | ||||||
|  |             const re = new RegExp(wholeWordChar + searchTerm + wholeWordChar, | ||||||
|  |                 'g' + (matchCase ? '' : 'i')); | ||||||
|  |             let curLine = 0; | ||||||
|  |             let curChar = 0; | ||||||
|  |             let curMatch = null; | ||||||
|  |             findResult = []; | ||||||
|  |             // All those markText take several seconds on eg this ~500-line
 | ||||||
|  |             // script, batch them inside an operation so they become
 | ||||||
|  |             // unnoticeable. Alternatively, an overlay could be used, see
 | ||||||
|  |             // https://codemirror.net/addon/search/match-highlighter.js ?
 | ||||||
|  |             codeEditor.operation(() => { | ||||||
|  |                 for (let i = 0; i < text.length; ++i) { | ||||||
|  |                     // Fetch next match if it's the first time or
 | ||||||
|  |                     // if past the current match start
 | ||||||
|  |                     if ((curMatch == null) || (curMatch.index < i)) { | ||||||
|  |                         curMatch = re.exec(text); | ||||||
|  |                         if (curMatch == null) { | ||||||
|  |                             // No more matches
 | ||||||
|  |                             break; | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                     // Create a non-selected highlight marker for the match, the
 | ||||||
|  |                     // selected marker highlight will be done later
 | ||||||
|  |                     if (i === curMatch.index) { | ||||||
|  |                         let fromPos = { "line" : curLine, "ch" : curChar }; | ||||||
|  |                         // XXX If multiline is supported, this needs to
 | ||||||
|  |                         //     recalculate curLine since the match may span
 | ||||||
|  |                         //     lines
 | ||||||
|  |                         let toPos = { "line" : curLine, "ch" : curChar + curMatch[0].length}; | ||||||
|  |                         // XXX or css = "color: #f3"
 | ||||||
|  |                         let marker = doc.markText( fromPos, toPos, { "className" : FIND_RESULT_CSS_CLASSNAME }); | ||||||
|  |                         findResult.push(marker); | ||||||
|  | 
 | ||||||
|  |                         // Set the first match beyond the cursor as current
 | ||||||
|  |                         // match
 | ||||||
|  |                         if (currentFound === -1) { | ||||||
|  |                             const cursorPos = codeEditor.getCursor(); | ||||||
|  |                             if ((fromPos.line > cursorPos.line) || | ||||||
|  |                                 ((fromPos.line === cursorPos.line) && | ||||||
|  |                                     (fromPos.ch >= cursorPos.ch))){ | ||||||
|  |                                 currentFound = totalFound; | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|  | 
 | ||||||
|  |                         totalFound++; | ||||||
|  |                     } | ||||||
|  |                     // Do line and char position tracking
 | ||||||
|  |                     if (text[i] === "\n") { | ||||||
|  |                         curLine++; | ||||||
|  |                         curChar = 0; | ||||||
|  |                     } else { | ||||||
|  |                         curChar++; | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         this.findResult = findResult; | ||||||
|  | 
 | ||||||
|  |         // Calculate curfound if not already, highlight it as selected
 | ||||||
|  |         if (totalFound > 0) { | ||||||
|  |             currentFound = Math.max(0, currentFound) | ||||||
|  |             let marker = findResult[currentFound]; | ||||||
|  |             let pos = marker.find(); | ||||||
|  |             codeEditor.scrollIntoView(pos.to); | ||||||
|  |             marker.clear(); | ||||||
|  |             findResult[currentFound] = doc.markText( pos.from, pos.to, | ||||||
|  |                 { "className" : FIND_RESULT_SELECTED_CSS_CLASSNAME } | ||||||
|  |             ); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return { | ||||||
|  |             totalFound, | ||||||
|  |             currentFound: currentFound + 1 | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     async findNext(direction, currentFound, nextFound) { | ||||||
|  |         const codeEditor = await this.getCodeEditor(); | ||||||
|  |         const doc = codeEditor.doc; | ||||||
|  | 
 | ||||||
|  |         //
 | ||||||
|  |         // Dehighlight current, highlight & scrollIntoView next
 | ||||||
|  |         //
 | ||||||
|  | 
 | ||||||
|  |         let marker = this.findResult[currentFound]; | ||||||
|  |         let pos = marker.find(); | ||||||
|  |         marker.clear(); | ||||||
|  |         marker = doc.markText( | ||||||
|  |             pos.from, pos.to, | ||||||
|  |             { "className" : FIND_RESULT_CSS_CLASSNAME } | ||||||
|  |         ); | ||||||
|  |         this.findResult[currentFound] = marker; | ||||||
|  | 
 | ||||||
|  |         marker = this.findResult[nextFound]; | ||||||
|  |         pos = marker.find(); | ||||||
|  |         marker.clear(); | ||||||
|  |         marker = doc.markText( | ||||||
|  |             pos.from, pos.to, | ||||||
|  |             { "className" : FIND_RESULT_SELECTED_CSS_CLASSNAME } | ||||||
|  |         ); | ||||||
|  |         this.findResult[nextFound] = marker; | ||||||
|  | 
 | ||||||
|  |         codeEditor.scrollIntoView(pos.from); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     async cleanup(totalFound, currentFound) { | ||||||
|  |         const codeEditor = await this.getCodeEditor(); | ||||||
|  | 
 | ||||||
|  |         if (totalFound > 0) { | ||||||
|  |             const doc = codeEditor.doc; | ||||||
|  |             const pos = this.findResult[currentFound].find(); | ||||||
|  |             // Note setting the selection sets the cursor to
 | ||||||
|  |             // the end of the selection and scrolls it into
 | ||||||
|  |             // view
 | ||||||
|  |             doc.setSelection(pos.from, pos.to); | ||||||
|  |             // Clear all markers
 | ||||||
|  |             codeEditor.operation(() => { | ||||||
|  |                 for (let i = 0; i < this.findResult.length; ++i) { | ||||||
|  |                     let marker = this.findResult[i]; | ||||||
|  |                     marker.clear(); | ||||||
|  |                 } | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|  |         // Restore the highlightSelectionMatches setting
 | ||||||
|  |         codeEditor.setOption("highlightSelectionMatches", this.oldHighlightSelectionMatches); | ||||||
|  |         this.findResult = null; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     async close() { | ||||||
|  |         const codeEditor = await this.getCodeEditor(); | ||||||
|  |         codeEditor.focus(); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										120
									
								
								src/public/app/widgets/find_in_text.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										120
									
								
								src/public/app/widgets/find_in_text.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,120 @@ | |||||||
|  | export default class FindInText { | ||||||
|  |     constructor(parent) { | ||||||
|  |         this.parent = parent; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     async getTextEditor() { | ||||||
|  |         return this.parent.noteContext.getTextEditor(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     async getInitialSearchTerm() { | ||||||
|  |         const textEditor = await this.getTextEditor(); | ||||||
|  | 
 | ||||||
|  |         const selection = textEditor.model.document.selection; | ||||||
|  |         const range = selection.getFirstRange(); | ||||||
|  | 
 | ||||||
|  |         for (const item of range.getItems()) { | ||||||
|  |             // Fill in the findbox with the current selection if
 | ||||||
|  |             // any
 | ||||||
|  |             return item.data; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     async performFind(searchTerm, matchCase, wholeWord) { | ||||||
|  |         // Do this even if the searchTerm is empty so the markers are cleared and
 | ||||||
|  |         // the counters updated
 | ||||||
|  |         const textEditor = await this.getTextEditor(); | ||||||
|  |         const model = textEditor.model; | ||||||
|  |         let findResult = null; | ||||||
|  |         let totalFound = 0; | ||||||
|  |         let currentFound = -1; | ||||||
|  | 
 | ||||||
|  |         // Clear
 | ||||||
|  |         const findAndReplaceEditing = textEditor.plugins.get('FindAndReplaceEditing'); | ||||||
|  |         findAndReplaceEditing.state.clear(model); | ||||||
|  |         findAndReplaceEditing.stop(); | ||||||
|  |         if (searchTerm !== "") { | ||||||
|  |             // Parameters are callback/text, options.matchCase=false, options.wholeWords=false
 | ||||||
|  |             // See https://github.com/ckeditor/ckeditor5/blob/b95e2faf817262ac0e1e21993d9c0bde3f1be594/packages/ckeditor5-find-and-replace/src/findcommand.js#L44
 | ||||||
|  |             // XXX Need to use the callback version for regexp
 | ||||||
|  |             // searchTerm = escapeRegExp(searchTerm);
 | ||||||
|  |             // let re = new RegExp(searchTerm, 'gi');
 | ||||||
|  |             // let m = text.match(re);
 | ||||||
|  |             // totalFound = m ? m.length : 0;
 | ||||||
|  |             const options = { "matchCase" : matchCase, "wholeWords" : wholeWord }; | ||||||
|  |             findResult = textEditor.execute('find', searchTerm, options); | ||||||
|  |             totalFound = findResult.results.length; | ||||||
|  |             // Find the result beyond the cursor
 | ||||||
|  |             const cursorPos = model.document.selection.getLastPosition(); | ||||||
|  |             for (let i = 0; i < findResult.results.length; ++i) { | ||||||
|  |                 const marker = findResult.results.get(i).marker; | ||||||
|  |                 const fromPos = marker.getStart(); | ||||||
|  |                 if (fromPos.compareWith(cursorPos) !== "before") { | ||||||
|  |                     currentFound = i; | ||||||
|  |                     break; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         this.findResult = findResult; | ||||||
|  | 
 | ||||||
|  |         // Calculate curfound if not already, highlight it as
 | ||||||
|  |         // selected
 | ||||||
|  |         if (totalFound > 0) { | ||||||
|  |             currentFound = Math.max(0, currentFound); | ||||||
|  |             // XXX Do this accessing the private data?
 | ||||||
|  |             // See
 | ||||||
|  |             // https://github.com/ckeditor/ckeditor5/blob/b95e2faf817262ac0e1e21993d9c0bde3f1be594/packages/ckeditor5-find-and-replace/src/findnextcommand.js
 | ||||||
|  |             for (let i = 0 ; i < currentFound; ++i) { | ||||||
|  |                 textEditor.execute('findNext', searchTerm); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return { | ||||||
|  |             totalFound, | ||||||
|  |             currentFound: currentFound + 1 | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     async findNext(direction, currentFound, nextFound) { | ||||||
|  |         const textEditor = await this.getTextEditor(); | ||||||
|  | 
 | ||||||
|  |         // There are no parameters for findNext/findPrev
 | ||||||
|  |         // See https://github.com/ckeditor/ckeditor5/blob/b95e2faf817262ac0e1e21993d9c0bde3f1be594/packages/ckeditor5-find-and-replace/src/findnextcommand.js#L57
 | ||||||
|  |         // curFound wrap around above assumes findNext and
 | ||||||
|  |         // findPrevious wraparound, which is what they do
 | ||||||
|  |         if (direction > 0) { | ||||||
|  |             textEditor.execute('findNext'); | ||||||
|  |         } else { | ||||||
|  |             textEditor.execute('findPrevious'); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     async cleanup(totalFound, currentFound) { | ||||||
|  |         if (totalFound > 0) { | ||||||
|  |             const textEditor = await this.getTextEditor(); | ||||||
|  |             // Clear the markers and set the caret to the
 | ||||||
|  |             // current occurrence
 | ||||||
|  |             const model = textEditor.model; | ||||||
|  |             const range = this.findResult.results.get(currentFound).marker.getRange(); | ||||||
|  |             // From
 | ||||||
|  |             // https://github.com/ckeditor/ckeditor5/blob/b95e2faf817262ac0e1e21993d9c0bde3f1be594/packages/ckeditor5-find-and-replace/src/findandreplace.js#L92
 | ||||||
|  |             // XXX Roll our own since already done for codeEditor and
 | ||||||
|  |             //     will probably allow more refactoring?
 | ||||||
|  |             let findAndReplaceEditing = textEditor.plugins.get('FindAndReplaceEditing'); | ||||||
|  |             findAndReplaceEditing.state.clear(model); | ||||||
|  |             findAndReplaceEditing.stop(); | ||||||
|  |             model.change(writer => { | ||||||
|  |                 writer.setSelection(range, 0); | ||||||
|  |             }); | ||||||
|  |             textEditor.editing.view.scrollToTheSelection(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         this.findResult = null; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     async close() { | ||||||
|  |         const textEditor = await this.getTextEditor(); | ||||||
|  |         textEditor.focus(); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -171,13 +171,13 @@ export default class EditableCodeTypeWidget extends TypeWidget { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async executeInActiveCodeEditorEvent({callback}) { |     async executeInCodeEditorEvent({resolve, ntxId}) { | ||||||
|         if (!this.isActive()) { |         if (!this.isNoteContext(ntxId)) { | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         await this.initialized; |         await this.initialized; | ||||||
| 
 | 
 | ||||||
|         callback(this.codeEditor); |         resolve(this.codeEditor); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -229,8 +229,8 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget { | |||||||
|         return !selection.isCollapsed; |         return !selection.isCollapsed; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async executeInActiveTextEditorEvent({callback, resolve}) { |     async executeInTextEditorEvent({callback, resolve, ntxId}) { | ||||||
|         if (!this.isActive()) { |         if (!this.isNoteContext(ntxId)) { | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 zadam
						zadam