mirror of
https://github.com/zadam/trilium.git
synced 2026-03-10 02:13:38 +01:00
refactor: migrate note_autocomplete core function
This commit is contained in:
parent
d363d2016e
commit
c0dd59458b
@ -82,26 +82,43 @@
|
||||
|
||||
---
|
||||
|
||||
### Step 3: 迁移笔记搜索自动补全核心
|
||||
**文件变更:**
|
||||
- `apps/client/src/services/note_autocomplete.ts` — `initNoteAutocomplete()` 改为直接调用 `autocomplete()`
|
||||
### Step 3: 迁移笔记搜索自动补全核心 (拆分为 4 个增量阶段)
|
||||
|
||||
**说明:**
|
||||
这是迁移中最复杂的部分,`initNoteAutocomplete()` 包含:
|
||||
- 复杂的 source 函数(带防抖、IME 处理)
|
||||
- 自定义 suggestion 模板(图标、路径高亮)
|
||||
- 多种选择类型分发(笔记、外部链接、命令)
|
||||
- `autocomplete("val", ...)` 等操作性 API 调用
|
||||
- 附带的辅助按钮(清除、最近笔记、全文搜索、跳转按钮)
|
||||
由于搜索自动补全模块(`note_autocomplete.ts`)承载了系统最为复杂的交互、多态分发与 UI,我们将其拆分为 4 个逐步可验证的子阶段:
|
||||
|
||||
消费者通过自定义 jQuery 事件(`autocomplete:noteselected` 等)接收结果,需要保持这些事件或改为回调。
|
||||
#### Step 3.1: 基础骨架与核心接口联通 (Headless 骨架) ✅ 完成
|
||||
**目标:** 用 `@algolia/autocomplete-core` 完全接管旧版的 `$el.autocomplete` 初始化,打通搜索接口。
|
||||
**工作内容:**
|
||||
- 在 `initNoteAutocomplete()` 中引入基于 `instanceMap` 的单例验证逻辑与 DOM 隔离。
|
||||
- 建立 `getSources`,实现调用 `server.get("api/search/autocomplete", ...)`。
|
||||
- 只做极其简单的 UI(比如简单的 `ul > li` text)将获取到的 `title` 渲染出来,确保网络流程畅通。
|
||||
**完成情况与验证 (**`apps/client/src/services/note_autocomplete.ts`**):**
|
||||
- ✅ 彻底移除了原依赖于 jQuery `autocomplete.js` 的各种初始化配置与繁复的字符串 DOM 拼接节点。
|
||||
- ✅ 实现了对 `Jump to Note (Ctrl+J)` 等真实组件的向下兼容事件 (`autocomplete:noteselected`) 无缝派发反馈。
|
||||
- ✅ 在跳往某个具体笔记或在新建 Relation 面板选用特定目标笔记时,基础请求和简装提示版均工作正常。
|
||||
|
||||
**验证方式:**
|
||||
- 搜索栏 → 输入笔记名称 → 应能看到搜索结果
|
||||
- 选择结果 → 应正确跳转到对应笔记
|
||||
- 命令面板(`>` 前缀)正常工作
|
||||
- 中文输入法不应中途触发搜索
|
||||
- Shift+Enter 全文搜索、Ctrl+Enter 搜索笔记
|
||||
#### Step 3.2: 复杂 UI 渲染重构与匹配高亮 (模板渲染)
|
||||
**目标:** 实现与原版相同级别(甚至更好)的视觉体验(例如笔记图标、上级路径显示、搜索词高亮标红等)。
|
||||
**工作内容:**
|
||||
- 重写原有的基于字符串或 jQuery 的构建 DOM 模板代码(专门处理带 `notePath` `icon` `isSearch` 判断等数据)。
|
||||
- 将 DOM 构建系统集成到 `onStateChange` 的渲染函数里,通过 `innerHTML` 拼装或 DOM 手工建立实现原生高性能面板。
|
||||
- 引入对应的样式 (`style.css`) 补全排版缺漏。
|
||||
**验证方式:** 下拉出的搜索面板变得非常美观,与系统的 Dark/Light 色调融合;笔记标题对应的图标出现,匹配的字样高亮突出。
|
||||
|
||||
#### Step 3.3: 差异化分发逻辑与对外事件抛出 (交互改造)
|
||||
**目标:** 支持该组件的多态性。它能在搜笔记之外搜命令(`>` 起手)、甚至是外部链接。同时能够被外部组件监听到选择动作。
|
||||
**工作内容:**
|
||||
- 在选择项(`onSelect`)的回调中,根据用户选的是“系统命令”、“外部链接”还是“普通笔记”走截然不同的行为逻辑。
|
||||
- 对外派发事件:原本通过 `$el.trigger("autocomplete:noteselected")` 的逻辑需要保留,以保证那些使用了搜索框的组件(例如右侧关系面板)依然能顺利收到选中反馈。
|
||||
**验证方式:** 选中某个建议项时能够真正实现页面的调转/关系绑定;输入 `>` 开头能够列举出所有快捷命令(如 Toggle Dark mode)。
|
||||
|
||||
#### Step 3.4: 特殊键盘事件拦截与附带按钮包容 (终极打磨)
|
||||
**目标:** 解决在旧 jQuery 中强绑定的 IME(中日韩等输入法)防抖问题,并恢复如 `Shift+Enter`、周边附加按钮(清除等)的正常运作。
|
||||
**工作内容:**
|
||||
- 将旧的输入法合成事件 (`compositionstart` / `compositionend`) 判断逻辑迁移到新的 `onInput` / `onKeyDown` 外围保护之中。
|
||||
- 重构对 `Shift+Enter` (唤起全文搜索)、`Ctrl+Enter` 等组合快捷键的劫持处理。
|
||||
- 修正周边辅助控件(例如搜索栏自带的 “最近笔记(钟表)”、“清除栏(X)” 按钮)因为 DOM 结构调整可能引发的影响。
|
||||
**验证方式:** 中文拼音输入法敲打途中不会错误地发起网络搜索;各种组合回车热键重新生效,整个搜索系统重回巅峰状态。
|
||||
|
||||
---
|
||||
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import { createAutocomplete } from "@algolia/autocomplete-core";
|
||||
import type { AutocompleteApi as CoreAutocompleteApi, BaseItem } from "@algolia/autocomplete-core";
|
||||
import server from "./server.js";
|
||||
import appContext from "../components/app_context.js";
|
||||
import noteCreateService from "./note_create.js";
|
||||
@ -24,7 +26,7 @@ function getSearchDelay(notesCount: number): number {
|
||||
let searchDelay = getSearchDelay(notesCount);
|
||||
|
||||
// TODO: Deduplicate with server.
|
||||
export interface Suggestion {
|
||||
export interface Suggestion extends BaseItem {
|
||||
noteTitle?: string;
|
||||
externalLink?: string;
|
||||
notePathTitle?: string;
|
||||
@ -54,6 +56,53 @@ export interface Options {
|
||||
isCommandPalette?: boolean;
|
||||
}
|
||||
|
||||
// --- Headless Autocomplete Helpers ---
|
||||
interface ManagedInstance {
|
||||
autocomplete: CoreAutocompleteApi<Suggestion>;
|
||||
panelEl: HTMLElement;
|
||||
cleanup: () => void;
|
||||
}
|
||||
|
||||
const instanceMap = new WeakMap<HTMLElement, ManagedInstance>();
|
||||
|
||||
function createPanelEl(): HTMLElement {
|
||||
const panel = document.createElement("div");
|
||||
panel.className = "aa-core-panel";
|
||||
panel.style.display = "none";
|
||||
document.body.appendChild(panel);
|
||||
return panel;
|
||||
}
|
||||
|
||||
function positionPanel(panelEl: HTMLElement, inputEl: HTMLElement): void {
|
||||
const rect = inputEl.getBoundingClientRect();
|
||||
panelEl.style.position = "fixed";
|
||||
panelEl.style.top = `${rect.bottom}px`;
|
||||
panelEl.style.left = `${rect.left}px`;
|
||||
panelEl.style.width = `${rect.width}px`;
|
||||
panelEl.style.display = "block";
|
||||
}
|
||||
|
||||
function renderItems(panelEl: HTMLElement, items: Suggestion[], activeId: number | null, onSelect: (item: Suggestion) => void) {
|
||||
if (items.length === 0) {
|
||||
panelEl.style.display = "none";
|
||||
return;
|
||||
}
|
||||
const list = document.createElement("ul");
|
||||
list.className = "aa-core-list";
|
||||
items.forEach((item, index) => {
|
||||
const li = document.createElement("li");
|
||||
li.className = "aa-core-item";
|
||||
if (index === activeId) li.classList.add("aa-core-item--active");
|
||||
|
||||
// Very basic simple UI for step 3.1
|
||||
li.textContent = item.highlightedNotePathTitle || item.noteTitle || "";
|
||||
li.onmousedown = (e) => { e.preventDefault(); onSelect(item); };
|
||||
list.appendChild(li);
|
||||
});
|
||||
panelEl.innerHTML = "";
|
||||
panelEl.appendChild(list);
|
||||
}
|
||||
|
||||
async function autocompleteSourceForCKEditor(queryText: string) {
|
||||
return await new Promise<MentionFeedObjectItem[]>((res, rej) => {
|
||||
autocompleteSource(
|
||||
@ -163,271 +212,216 @@ async function autocompleteSource(term: string, cb: (rows: Suggestion[]) => void
|
||||
function clearText($el: JQuery<HTMLElement>) {
|
||||
searchDelay = 0;
|
||||
$el.setSelectedNotePath("");
|
||||
$el.autocomplete("val", "").trigger("change");
|
||||
const inputEl = $el[0] as HTMLInputElement;
|
||||
const instance = instanceMap.get(inputEl);
|
||||
if (instance) {
|
||||
inputEl.value = "";
|
||||
instance.autocomplete.setQuery("");
|
||||
instance.autocomplete.setIsOpen(false);
|
||||
instance.autocomplete.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
function setText($el: JQuery<HTMLElement>, text: string) {
|
||||
$el.setSelectedNotePath("");
|
||||
$el.autocomplete("val", text.trim()).autocomplete("open");
|
||||
const inputEl = $el[0] as HTMLInputElement;
|
||||
const instance = instanceMap.get(inputEl);
|
||||
if (instance) {
|
||||
inputEl.value = text.trim();
|
||||
instance.autocomplete.setQuery(text.trim());
|
||||
instance.autocomplete.setIsOpen(true);
|
||||
instance.autocomplete.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
function showRecentNotes($el: JQuery<HTMLElement>) {
|
||||
searchDelay = 0;
|
||||
$el.setSelectedNotePath("");
|
||||
$el.autocomplete("val", "");
|
||||
$el.autocomplete("open");
|
||||
const inputEl = $el[0] as HTMLInputElement;
|
||||
const instance = instanceMap.get(inputEl);
|
||||
if (instance) {
|
||||
inputEl.value = "";
|
||||
instance.autocomplete.setQuery("");
|
||||
instance.autocomplete.setIsOpen(true);
|
||||
instance.autocomplete.refresh();
|
||||
}
|
||||
$el.trigger("focus");
|
||||
}
|
||||
|
||||
function showAllCommands($el: JQuery<HTMLElement>) {
|
||||
searchDelay = 0;
|
||||
$el.setSelectedNotePath("");
|
||||
$el.autocomplete("val", ">").autocomplete("open");
|
||||
const inputEl = $el[0] as HTMLInputElement;
|
||||
const instance = instanceMap.get(inputEl);
|
||||
if (instance) {
|
||||
inputEl.value = ">";
|
||||
instance.autocomplete.setQuery(">");
|
||||
instance.autocomplete.setIsOpen(true);
|
||||
instance.autocomplete.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
function fullTextSearch($el: JQuery<HTMLElement>, options: Options) {
|
||||
const searchString = $el.autocomplete("val") as unknown as string;
|
||||
if (options.fastSearch === false || searchString?.trim().length === 0) {
|
||||
const inputEl = $el[0] as HTMLInputElement;
|
||||
const searchString = inputEl.value;
|
||||
if (options.fastSearch === false || searchString.trim().length === 0) {
|
||||
return;
|
||||
}
|
||||
$el.trigger("focus");
|
||||
options.fastSearch = false;
|
||||
$el.autocomplete("val", "");
|
||||
$el.setSelectedNotePath("");
|
||||
searchDelay = 0;
|
||||
$el.autocomplete("val", searchString);
|
||||
|
||||
const instance = instanceMap.get(inputEl);
|
||||
if (instance) {
|
||||
instance.autocomplete.setQuery(searchString);
|
||||
instance.autocomplete.setIsOpen(true);
|
||||
instance.autocomplete.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
function initNoteAutocomplete($el: JQuery<HTMLElement>, options?: Options) {
|
||||
if ($el.hasClass("note-autocomplete-input")) {
|
||||
// clear any event listener added in previous invocation of this function
|
||||
$el.off("autocomplete:noteselected");
|
||||
$el.addClass("note-autocomplete-input");
|
||||
const inputEl = $el[0] as HTMLInputElement;
|
||||
|
||||
if (instanceMap.has(inputEl)) {
|
||||
return $el;
|
||||
}
|
||||
|
||||
options = options || {};
|
||||
|
||||
// Used to track whether the user is performing character composition with an input method (such as Chinese Pinyin, Japanese, Korean, etc.) and to avoid triggering a search during the composition process.
|
||||
let isComposingInput = false;
|
||||
$el.on("compositionstart", () => {
|
||||
isComposingInput = true;
|
||||
});
|
||||
$el.on("compositionend", () => {
|
||||
isComposingInput = false;
|
||||
const searchString = $el.autocomplete("val") as unknown as string;
|
||||
$el.autocomplete("val", "");
|
||||
$el.autocomplete("val", searchString);
|
||||
const panelEl = createPanelEl();
|
||||
let rafId: number | null = null;
|
||||
function startPositioning() {
|
||||
if (rafId !== null) return;
|
||||
const update = () => {
|
||||
positionPanel(panelEl, inputEl);
|
||||
rafId = requestAnimationFrame(update);
|
||||
};
|
||||
update();
|
||||
}
|
||||
function stopPositioning() {
|
||||
if (rafId !== null) {
|
||||
cancelAnimationFrame(rafId);
|
||||
rafId = null;
|
||||
}
|
||||
}
|
||||
|
||||
const autocomplete = createAutocomplete<Suggestion>({
|
||||
openOnFocus: false, // Wait until we explicitly focus or type
|
||||
defaultActiveItemId: null,
|
||||
shouldPanelOpen() {
|
||||
return true;
|
||||
},
|
||||
|
||||
getSources({ query }) {
|
||||
return [
|
||||
{
|
||||
sourceId: "note-suggestions",
|
||||
async getItems() {
|
||||
return new Promise<Suggestion[]>((resolve) => {
|
||||
clearTimeout(debounceTimeoutId);
|
||||
debounceTimeoutId = setTimeout(() => {
|
||||
autocompleteSource(query, resolve, options!);
|
||||
}, searchDelay);
|
||||
|
||||
if (searchDelay === 0) {
|
||||
searchDelay = getSearchDelay(notesCount);
|
||||
}
|
||||
});
|
||||
},
|
||||
getItemInputValue({ item }) {
|
||||
return item.noteTitle || item.notePathTitle || "";
|
||||
},
|
||||
onSelect({ item }) {
|
||||
inputEl.value = item.noteTitle || item.notePathTitle || "";
|
||||
autocomplete.setIsOpen(false);
|
||||
|
||||
// Fake selection handler for step 3.1
|
||||
$el.trigger("autocomplete:noteselected", [item]);
|
||||
},
|
||||
},
|
||||
];
|
||||
},
|
||||
|
||||
onStateChange({ state }) {
|
||||
const collections = state.collections;
|
||||
const items = collections.length > 0 ? (collections[0].items as Suggestion[]) : [];
|
||||
const activeId = state.activeItemId ?? null;
|
||||
|
||||
if (state.isOpen && items.length > 0) {
|
||||
renderItems(panelEl, items, activeId, (item) => {
|
||||
inputEl.value = item.noteTitle || item.notePathTitle || "";
|
||||
autocomplete.setIsOpen(false);
|
||||
// Also dispatch selected event
|
||||
$el.trigger("autocomplete:noteselected", [item]);
|
||||
});
|
||||
startPositioning();
|
||||
} else {
|
||||
panelEl.style.display = "none";
|
||||
stopPositioning();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
$el.addClass("note-autocomplete-input");
|
||||
const handlers = autocomplete.getInputProps({ inputElement: inputEl });
|
||||
const onInput = (e: Event) => {
|
||||
handlers.onChange(e as any);
|
||||
};
|
||||
const onFocus = (e: Event) => {
|
||||
handlers.onFocus(e as any);
|
||||
};
|
||||
const onBlur = () => {
|
||||
setTimeout(() => {
|
||||
autocomplete.setIsOpen(false);
|
||||
panelEl.style.display = "none";
|
||||
stopPositioning();
|
||||
}, 50);
|
||||
};
|
||||
const onKeyDown = (e: KeyboardEvent) => {
|
||||
handlers.onKeyDown(e as any);
|
||||
};
|
||||
|
||||
inputEl.addEventListener("input", onInput);
|
||||
inputEl.addEventListener("focus", onFocus);
|
||||
inputEl.addEventListener("blur", onBlur);
|
||||
inputEl.addEventListener("keydown", onKeyDown);
|
||||
|
||||
const cleanup = () => {
|
||||
inputEl.removeEventListener("input", onInput);
|
||||
inputEl.removeEventListener("focus", onFocus);
|
||||
inputEl.removeEventListener("blur", onBlur);
|
||||
inputEl.removeEventListener("keydown", onKeyDown);
|
||||
stopPositioning();
|
||||
if (panelEl.parentElement) {
|
||||
panelEl.parentElement.removeChild(panelEl);
|
||||
}
|
||||
};
|
||||
|
||||
instanceMap.set(inputEl, { autocomplete, panelEl, cleanup });
|
||||
|
||||
// Buttons UI logic
|
||||
const $clearTextButton = $("<a>").addClass("input-group-text input-clearer-button bx bxs-tag-x").prop("title", t("note_autocomplete.clear-text-field"));
|
||||
|
||||
const $showRecentNotesButton = $("<a>").addClass("input-group-text show-recent-notes-button bx bx-time").prop("title", t("note_autocomplete.show-recent-notes"));
|
||||
|
||||
const $fullTextSearchButton = $("<a>")
|
||||
.addClass("input-group-text full-text-search-button bx bx-search")
|
||||
.prop("title", `${t("note_autocomplete.full-text-search")} (Shift+Enter)`);
|
||||
|
||||
const $fullTextSearchButton = $("<a>").addClass("input-group-text full-text-search-button bx bx-search").prop("title", `${t("note_autocomplete.full-text-search")} (Shift+Enter)`);
|
||||
const $goToSelectedNoteButton = $("<a>").addClass("input-group-text go-to-selected-note-button bx bx-arrow-to-right");
|
||||
|
||||
if (!options.hideAllButtons) {
|
||||
$el.after($clearTextButton).after($showRecentNotesButton).after($fullTextSearchButton);
|
||||
}
|
||||
|
||||
if (!options.hideGoToSelectedNoteButton && !options.hideAllButtons) {
|
||||
$el.after($goToSelectedNoteButton);
|
||||
}
|
||||
|
||||
$clearTextButton.on("click", () => clearText($el));
|
||||
|
||||
$showRecentNotesButton.on("click", (e) => {
|
||||
showRecentNotes($el);
|
||||
|
||||
// this will cause the click not give focus to the "show recent notes" button
|
||||
// this is important because otherwise input will lose focus immediately and not show the results
|
||||
return false;
|
||||
});
|
||||
|
||||
$fullTextSearchButton.on("click", (e) => {
|
||||
fullTextSearch($el, options);
|
||||
fullTextSearch($el, options!);
|
||||
return false;
|
||||
});
|
||||
|
||||
let autocompleteOptions: AutoCompleteConfig = {};
|
||||
if (options.container) {
|
||||
autocompleteOptions.dropdownMenuContainer = options.container;
|
||||
autocompleteOptions.debug = true; // don't close on blur
|
||||
}
|
||||
|
||||
if (options.allowJumpToSearchNotes) {
|
||||
$el.on("keydown", (event) => {
|
||||
if (event.ctrlKey && event.key === "Enter") {
|
||||
// Prevent Ctrl + Enter from triggering autoComplete.
|
||||
event.stopImmediatePropagation();
|
||||
event.preventDefault();
|
||||
$el.trigger("autocomplete:selected", { action: "search-notes", noteTitle: $el.autocomplete("val") });
|
||||
}
|
||||
});
|
||||
}
|
||||
$el.on("keydown", async (event) => {
|
||||
if (event.shiftKey && event.key === "Enter") {
|
||||
// Prevent Enter from triggering autoComplete.
|
||||
event.stopImmediatePropagation();
|
||||
event.preventDefault();
|
||||
fullTextSearch($el, options);
|
||||
}
|
||||
});
|
||||
|
||||
$el.autocomplete(
|
||||
{
|
||||
...autocompleteOptions,
|
||||
appendTo: document.querySelector("body"),
|
||||
hint: false,
|
||||
autoselect: true,
|
||||
// openOnFocus has to be false, otherwise re-focus (after return from note type chooser dialog) forces
|
||||
// re-querying of the autocomplete source which then changes the currently selected suggestion
|
||||
openOnFocus: false,
|
||||
minLength: 0,
|
||||
tabAutocomplete: false
|
||||
},
|
||||
[
|
||||
{
|
||||
source: (term, cb) => {
|
||||
clearTimeout(debounceTimeoutId);
|
||||
debounceTimeoutId = setTimeout(() => {
|
||||
if (isComposingInput) {
|
||||
return;
|
||||
}
|
||||
autocompleteSource(term, cb, options);
|
||||
}, searchDelay);
|
||||
|
||||
if (searchDelay === 0) {
|
||||
searchDelay = getSearchDelay(notesCount);
|
||||
}
|
||||
},
|
||||
displayKey: "notePathTitle",
|
||||
templates: {
|
||||
suggestion: (suggestion) => {
|
||||
if (suggestion.action === "command") {
|
||||
let html = `<div class="command-suggestion">`;
|
||||
html += `<span class="command-icon ${suggestion.icon || "bx bx-terminal"}"></span>`;
|
||||
html += `<div class="command-content">`;
|
||||
html += `<div class="command-name">${suggestion.highlightedNotePathTitle}</div>`;
|
||||
if (suggestion.commandDescription) {
|
||||
html += `<div class="command-description">${suggestion.commandDescription}</div>`;
|
||||
}
|
||||
html += `</div>`;
|
||||
if (suggestion.commandShortcut) {
|
||||
html += `<kbd class="command-shortcut">${suggestion.commandShortcut}</kbd>`;
|
||||
}
|
||||
html += '</div>';
|
||||
return html;
|
||||
}
|
||||
// Add special class for search-notes action
|
||||
const actionClass = suggestion.action === "search-notes" ? "search-notes-action" : "";
|
||||
|
||||
// Choose appropriate icon based on action
|
||||
let iconClass = suggestion.icon ?? "bx bx-note";
|
||||
if (suggestion.action === "search-notes") {
|
||||
iconClass = "bx bx-search";
|
||||
} else if (suggestion.action === "create-note") {
|
||||
iconClass = "bx bx-plus";
|
||||
} else if (suggestion.action === "external-link") {
|
||||
iconClass = "bx bx-link-external";
|
||||
}
|
||||
|
||||
// Simplified HTML structure without nested divs
|
||||
let html = `<div class="note-suggestion ${actionClass}">`;
|
||||
html += `<span class="icon ${iconClass}"></span>`;
|
||||
html += `<span class="text">`;
|
||||
html += `<span class="search-result-title">${suggestion.highlightedNotePathTitle}</span>`;
|
||||
|
||||
// Add attribute snippet inline if available
|
||||
if (suggestion.highlightedAttributeSnippet) {
|
||||
html += `<span class="search-result-attributes">${suggestion.highlightedAttributeSnippet}</span>`;
|
||||
}
|
||||
|
||||
html += `</span>`;
|
||||
html += `</div>`;
|
||||
return html;
|
||||
}
|
||||
},
|
||||
// we can't cache identical searches because notes can be created / renamed, new recent notes can be added
|
||||
cache: false
|
||||
}
|
||||
]
|
||||
);
|
||||
|
||||
// TODO: Types fail due to "autocomplete:selected" not being registered in type definitions.
|
||||
($el as any).on("autocomplete:selected", async (event: Event, suggestion: Suggestion) => {
|
||||
if (suggestion.action === "command") {
|
||||
$el.autocomplete("close");
|
||||
$el.trigger("autocomplete:commandselected", [suggestion]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (suggestion.action === "external-link") {
|
||||
$el.setSelectedNotePath(null);
|
||||
$el.setSelectedExternalLink(suggestion.externalLink);
|
||||
|
||||
$el.autocomplete("val", suggestion.externalLink);
|
||||
|
||||
$el.autocomplete("close");
|
||||
|
||||
$el.trigger("autocomplete:externallinkselected", [suggestion]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (suggestion.action === "create-note") {
|
||||
const { success, noteType, templateNoteId, notePath } = await noteCreateService.chooseNoteType();
|
||||
if (!success) {
|
||||
return;
|
||||
}
|
||||
const { note } = await noteCreateService.createNote( notePath || suggestion.parentNoteId, {
|
||||
title: suggestion.noteTitle,
|
||||
activate: false,
|
||||
type: noteType,
|
||||
templateNoteId: templateNoteId
|
||||
});
|
||||
|
||||
const hoistedNoteId = appContext.tabManager.getActiveContext()?.hoistedNoteId;
|
||||
suggestion.notePath = note?.getBestNotePathString(hoistedNoteId);
|
||||
}
|
||||
|
||||
if (suggestion.action === "search-notes") {
|
||||
const searchString = suggestion.noteTitle;
|
||||
appContext.triggerCommand("searchNotes", { searchString });
|
||||
return;
|
||||
}
|
||||
|
||||
$el.setSelectedNotePath(suggestion.notePath);
|
||||
$el.setSelectedExternalLink(null);
|
||||
|
||||
$el.autocomplete("val", suggestion.noteTitle);
|
||||
|
||||
$el.autocomplete("close");
|
||||
|
||||
$el.trigger("autocomplete:noteselected", [suggestion]);
|
||||
});
|
||||
|
||||
$el.on("autocomplete:closed", () => {
|
||||
if (!String($el.val())?.trim()) {
|
||||
clearText($el);
|
||||
}
|
||||
});
|
||||
|
||||
$el.on("autocomplete:opened", () => {
|
||||
if ($el.attr("readonly")) {
|
||||
$el.autocomplete("close");
|
||||
}
|
||||
});
|
||||
|
||||
// clear any event listener added in previous invocation of this function
|
||||
$el.off("autocomplete:noteselected");
|
||||
|
||||
return $el;
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user