mirror of
				https://github.com/TriliumNext/Notes.git
				synced 2025-10-31 13:01:31 +08:00 
			
		
		
		
	chore(client/ts): port search_definition
This commit is contained in:
		
							parent
							
								
									b91b243432
								
							
						
					
					
						commit
						8ab0084e10
					
				| @ -288,6 +288,10 @@ type EventMappings = { | ||||
|     showHighlightsListWidget: { | ||||
|         noteId: string; | ||||
|     }; | ||||
|     showSearchError: { | ||||
|         error: string; | ||||
|     }; | ||||
|     searchRefreshed: { ntxId?: string | null }; | ||||
|     hoistedNoteChanged: { | ||||
|         noteId: string; | ||||
|         ntxId: string | null; | ||||
|  | ||||
| @ -14,9 +14,11 @@ import OrderBy from "../search_options/order_by.js"; | ||||
| import SearchScript from "../search_options/search_script.js"; | ||||
| import Limit from "../search_options/limit.js"; | ||||
| import Debug from "../search_options/debug.js"; | ||||
| import appContext from "../../components/app_context.js"; | ||||
| import appContext, { type EventData } from "../../components/app_context.js"; | ||||
| import bulkActionService from "../../services/bulk_action.js"; | ||||
| import { Dropdown } from "bootstrap"; | ||||
| import type FNote from "../../entities/fnote.js"; | ||||
| import type { AttributeType } from "../../entities/fattribute.js"; | ||||
| 
 | ||||
| const TPL = ` | ||||
| <div class="search-definition-widget"> | ||||
| @ -158,7 +160,21 @@ const TPL = ` | ||||
| 
 | ||||
| const OPTION_CLASSES = [SearchString, SearchScript, Ancestor, FastSearch, IncludeArchivedNotes, OrderBy, Limit, Debug]; | ||||
| 
 | ||||
| // TODO: Deduplicate with server
 | ||||
| interface SaveSearchNoteResponse { | ||||
|     notePath: string; | ||||
| } | ||||
| 
 | ||||
| export default class SearchDefinitionWidget extends NoteContextAwareWidget { | ||||
| 
 | ||||
|     private $component!: JQuery<HTMLElement>; | ||||
|     private $actionList!: JQuery<HTMLElement>; | ||||
|     private $searchOptions!: JQuery<HTMLElement>; | ||||
|     private $searchButton!: JQuery<HTMLElement>; | ||||
|     private $searchAndExecuteButton!: JQuery<HTMLElement>; | ||||
|     private $saveToNoteButton!: JQuery<HTMLElement>; | ||||
|     private $actionOptions!: JQuery<HTMLElement>; | ||||
| 
 | ||||
|     get name() { | ||||
|         return "searchDefinition"; | ||||
|     } | ||||
| @ -194,7 +210,7 @@ export default class SearchDefinitionWidget extends NoteContextAwareWidget { | ||||
|             const searchOptionName = $(event.target).attr("data-search-option-add"); | ||||
|             const clazz = OPTION_CLASSES.find((SearchOptionClass) => SearchOptionClass.optionName === searchOptionName); | ||||
| 
 | ||||
|             if (clazz) { | ||||
|             if (clazz && this.noteId) { | ||||
|                 await clazz.create(this.noteId); | ||||
|             } else { | ||||
|                 logError(t("search_definition.unknown_search_option", { searchOptionName })); | ||||
| @ -204,11 +220,13 @@ export default class SearchDefinitionWidget extends NoteContextAwareWidget { | ||||
|         }); | ||||
| 
 | ||||
|         this.$widget.on("click", "[data-action-add]", async (event) => { | ||||
|             Dropdown.getOrCreateInstance(this.$widget.find(".action-add-toggle")); | ||||
|             Dropdown.getOrCreateInstance(this.$widget.find(".action-add-toggle")[0]); | ||||
| 
 | ||||
|             const actionName = $(event.target).attr("data-action-add"); | ||||
| 
 | ||||
|             if (this.noteId && actionName) { | ||||
|                 await bulkActionService.addAction(this.noteId, actionName); | ||||
|             } | ||||
| 
 | ||||
|             this.refresh(); | ||||
|         }); | ||||
| @ -224,7 +242,7 @@ export default class SearchDefinitionWidget extends NoteContextAwareWidget { | ||||
| 
 | ||||
|         this.$saveToNoteButton = this.$widget.find(".save-to-note-button"); | ||||
|         this.$saveToNoteButton.on("click", async () => { | ||||
|             const { notePath } = await server.post("special-notes/save-search-note", { searchNoteId: this.noteId }); | ||||
|             const { notePath } = await server.post<SaveSearchNoteResponse>("special-notes/save-search-note", { searchNoteId: this.noteId }); | ||||
| 
 | ||||
|             await ws.waitForMaxKnownEntityChangeId(); | ||||
| 
 | ||||
| @ -236,24 +254,32 @@ export default class SearchDefinitionWidget extends NoteContextAwareWidget { | ||||
|     } | ||||
| 
 | ||||
|     async refreshResultsCommand() { | ||||
|         try { | ||||
|             const { error } = await froca.loadSearchNote(this.noteId); | ||||
| 
 | ||||
|             if (error) { | ||||
|                 this.handleEvent("showSearchError", { error }); | ||||
|         if (!this.noteId) { | ||||
|             return; | ||||
|         } | ||||
|         } catch (e) { | ||||
| 
 | ||||
|         try { | ||||
|             const result = await froca.loadSearchNote(this.noteId); | ||||
| 
 | ||||
|             if (result && result.error) { | ||||
|                 this.handleEvent("showSearchError", { error: result.error }); | ||||
|             } | ||||
|         } catch (e: any) { | ||||
|             toastService.showError(e.message); | ||||
|         } | ||||
| 
 | ||||
|         this.triggerEvent("searchRefreshed", { ntxId: this.noteContext.ntxId }); | ||||
|         this.triggerEvent("searchRefreshed", { ntxId: this.noteContext?.ntxId }); | ||||
|     } | ||||
| 
 | ||||
|     async refreshSearchDefinitionCommand() { | ||||
|         await this.refresh(); | ||||
|     } | ||||
| 
 | ||||
|     async refreshWithNote(note) { | ||||
|     async refreshWithNote(note: FNote) { | ||||
|         if (!this.note) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         this.$component.show(); | ||||
| 
 | ||||
|         this.$saveToNoteButton.toggle(note.isHiddenCompletely()); | ||||
| @ -263,7 +289,7 @@ export default class SearchDefinitionWidget extends NoteContextAwareWidget { | ||||
|         for (const OptionClass of OPTION_CLASSES) { | ||||
|             const { attributeType, optionName } = OptionClass; | ||||
| 
 | ||||
|             const attr = this.note.getAttribute(attributeType, optionName); | ||||
|             const attr = this.note.getAttribute(attributeType as AttributeType, optionName); | ||||
| 
 | ||||
|             this.$widget.find(`[data-search-option-add='${optionName}'`).toggle(!attr); | ||||
| 
 | ||||
| @ -271,14 +297,19 @@ export default class SearchDefinitionWidget extends NoteContextAwareWidget { | ||||
|                 const searchOption = new OptionClass(attr, this.note).setParent(this); | ||||
|                 this.child(searchOption); | ||||
| 
 | ||||
|                 this.$searchOptions.append(searchOption.render()); | ||||
|                 const renderedEl = searchOption.render(); | ||||
|                 if (renderedEl) { | ||||
|                     this.$searchOptions.append(renderedEl); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         const actions = bulkActionService.parseActions(this.note); | ||||
|         const renderedEls = actions | ||||
|             .map((action) => action.render()) | ||||
|             .filter((e) => e) as JQuery<HTMLElement>[]; | ||||
| 
 | ||||
|         this.$actionOptions.empty().append(...actions.map((action) => action.render())); | ||||
| 
 | ||||
|         this.$actionOptions.empty().append(...renderedEls); | ||||
|         this.$searchAndExecuteButton.css("visibility", actions.length > 0 ? "visible" : "_hidden"); | ||||
|     } | ||||
| 
 | ||||
| @ -294,7 +325,7 @@ export default class SearchDefinitionWidget extends NoteContextAwareWidget { | ||||
|         toastService.showMessage(t("search_definition.actions_executed"), 3000); | ||||
|     } | ||||
| 
 | ||||
|     entitiesReloadedEvent({ loadResults }) { | ||||
|     entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) { | ||||
|         // only refreshing deleted attrs, otherwise components update themselves
 | ||||
|         if (loadResults.getAttributeRows().find((attrRow) => attrRow.type === "label" && attrRow.name === "action" && attrRow.isDeleted)) { | ||||
|             this.refresh(); | ||||
| @ -2,24 +2,32 @@ import server from "../../services/server.js"; | ||||
| import ws from "../../services/ws.js"; | ||||
| import Component from "../../components/component.js"; | ||||
| import utils from "../../services/utils.js"; | ||||
| import { t } from "../../services/i18n.js"; // 新增的导入
 | ||||
| import { t } from "../../services/i18n.js"; | ||||
| import type FAttribute from "../../entities/fattribute.js"; | ||||
| import type FNote from "../../entities/fnote.js"; | ||||
| import type { AttributeType } from "../../entities/fattribute.js"; | ||||
| 
 | ||||
| export default class AbstractSearchOption extends Component { | ||||
|     constructor(attribute, note) { | ||||
| export default abstract class AbstractSearchOption extends Component { | ||||
| 
 | ||||
|     private attribute: FAttribute; | ||||
|     private note: FNote; | ||||
| 
 | ||||
|     constructor(attribute: FAttribute, note: FNote) { | ||||
|         super(); | ||||
| 
 | ||||
|         this.attribute = attribute; | ||||
|         this.note = note; | ||||
|     } | ||||
| 
 | ||||
|     static async setAttribute(noteId, type, name, value = "") { | ||||
|     static async setAttribute(noteId: string, type: AttributeType, name: string, value: string = "") { | ||||
|         await server.put(`notes/${noteId}/set-attribute`, { type, name, value }); | ||||
| 
 | ||||
|         await ws.waitForMaxKnownEntityChangeId(); | ||||
|     } | ||||
| 
 | ||||
|     async setAttribute(type, name, value = "") { | ||||
|         await this.constructor.setAttribute(this.note.noteId, type, name, value); | ||||
|     async setAttribute(type: AttributeType, name: string, value: string = "") { | ||||
|         // TODO: Find a better pattern.
 | ||||
|         await (this.constructor as any).setAttribute(this.note.noteId, type, name, value); | ||||
|     } | ||||
| 
 | ||||
|     render() { | ||||
| @ -29,29 +37,29 @@ export default class AbstractSearchOption extends Component { | ||||
|             $rendered | ||||
|                 .find(".search-option-del") | ||||
|                 .on("click", () => this.deleteOption()) | ||||
|                 .attr("title", t("abstract_search_option.remove_this_search_option")); // 使用 t 函数处理 i18n 字符串
 | ||||
|                 .attr("title", t("abstract_search_option.remove_this_search_option")); | ||||
| 
 | ||||
|             utils.initHelpDropdown($rendered); | ||||
| 
 | ||||
|             return $rendered; | ||||
|         } catch (e) { | ||||
|             logError(t("abstract_search_option.failed_rendering", { dto: JSON.stringify(this.attribute.dto), error: e.message, stack: e.stack })); // 使用 t 函数处理 i18n 字符串
 | ||||
|         } catch (e: any) { | ||||
|             logError(t("abstract_search_option.failed_rendering", { dto: JSON.stringify(this.attribute.dto), error: e.message, stack: e.stack })); | ||||
|             return null; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // to be overridden
 | ||||
|     doRender() {} | ||||
|     abstract doRender(): JQuery<HTMLElement>; | ||||
| 
 | ||||
|     async deleteOption() { | ||||
|         await this.deleteAttribute(this.constructor.attributeType, this.constructor.optionName); | ||||
|         // TODO: Find a better pattern.
 | ||||
|         await this.deleteAttribute((this.constructor as any).attributeType, (this.constructor as any).optionName); | ||||
| 
 | ||||
|         await ws.waitForMaxKnownEntityChangeId(); | ||||
| 
 | ||||
|         await this.triggerCommand("refreshSearchDefinition"); | ||||
|     } | ||||
| 
 | ||||
|     async deleteAttribute(type, name) { | ||||
|     async deleteAttribute(type: AttributeType, name: string) { | ||||
|         for (const attr of this.note.getOwnedAttributes()) { | ||||
|             if (attr.type === type && attr.name === name) { | ||||
|                 await server.remove(`notes/${this.note.noteId}/attributes/${attr.attributeId}`); | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Elian Doran
						Elian Doran