mirror of
https://github.com/zadam/trilium.git
synced 2026-03-10 02:13:38 +01:00
refactor: fix full search
This commit is contained in:
parent
cc24c2a4dc
commit
bd1f6b7a0f
@ -122,13 +122,19 @@
|
||||
- ✅ `Ctrl+J / Jump to Note` 与 `attribute_detail.ts` 的普通 note 选择链路已抽查通过。
|
||||
- ⚠️ React 消费方整体仍应放在 **Step 5** 继续验收;`Move to` 等问题不属于 Step 3.3 本身已完成的范围。
|
||||
|
||||
#### Step 3.4: 特殊键盘事件拦截与附带按钮包容 (终极打磨)
|
||||
#### Step 3.4: 特殊键盘事件拦截与附带按钮包容 (终极打磨) ✅ 基本完成
|
||||
**目标:** 解决在旧 jQuery 中强绑定的 IME(中日韩等输入法)防抖问题,并恢复如 `Shift+Enter`、周边附加按钮(清除等)的正常运作。
|
||||
**工作内容:**
|
||||
- 将旧的输入法合成事件 (`compositionstart` / `compositionend`) 判断逻辑迁移到新的 `onInput` / `onKeyDown` 外围保护之中。
|
||||
- 重构对 `Shift+Enter` (唤起全文搜索)、`Ctrl+Enter` 等组合快捷键的劫持处理。
|
||||
- 修正周边辅助控件(例如搜索栏自带的 “最近笔记(钟表)”、“清除栏(X)” 按钮)因为 DOM 结构调整可能引发的影响。
|
||||
**验证方式:** 中文拼音输入法敲打途中不会错误地发起网络搜索;各种组合回车热键重新生效,整个搜索系统重回巅峰状态。
|
||||
**当前验证结果:**
|
||||
- ✅ `compositionstart` / `compositionend` 已恢复旧版保护逻辑:合成期间不发起搜索,结束后按“清空再恢复 query”的语义重新跑一次。
|
||||
- ✅ `Shift+Enter` 与 `Ctrl+Enter` 的快捷分发仍保留,并已按旧版语义接回全文搜索 / `search-notes`。
|
||||
- ✅ `autocomplete:opened` / `autocomplete:closed` 事件已重新补回,`readonly` 与“关闭时空输入框清理”逻辑重新对齐旧版。
|
||||
- ✅ 清空按钮、最近笔记按钮、全文搜索按钮都继续走 service 内部统一入口,而不是分散拼状态。
|
||||
- ⚠️ 这一步仍以 `note_autocomplete.ts` 核心行为为主;React 消费方的问题继续留在 **Step 5**。
|
||||
|
||||
---
|
||||
|
||||
|
||||
@ -62,6 +62,9 @@ interface ManagedInstance {
|
||||
autocomplete: CoreAutocompleteApi<Suggestion>;
|
||||
panelEl: HTMLElement;
|
||||
clearCursor: () => void;
|
||||
isPanelOpen: () => boolean;
|
||||
getQuery: () => string;
|
||||
suppressNextClosedReset: () => void;
|
||||
showQuery: (query: string) => void;
|
||||
openRecentNotes: () => void;
|
||||
cleanup: () => void;
|
||||
@ -261,7 +264,7 @@ function renderItems(
|
||||
}
|
||||
|
||||
async function autocompleteSourceForCKEditor(queryText: string) {
|
||||
const rows = await fetchSuggestions(queryText, { allowCreatingNotes: true });
|
||||
const rows = await fetchResolvedSuggestions(queryText, { allowCreatingNotes: true });
|
||||
return rows.map((row) => {
|
||||
return {
|
||||
action: row.action,
|
||||
@ -275,7 +278,20 @@ async function autocompleteSourceForCKEditor(queryText: string) {
|
||||
});
|
||||
}
|
||||
|
||||
async function fetchSuggestions(term: string, options: Options = {}): Promise<Suggestion[]> {
|
||||
function getSearchingSuggestion(term: string): Suggestion[] {
|
||||
if (term.trim().length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
noteTitle: term,
|
||||
highlightedNotePathTitle: t("quick-search.searching")
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
async function fetchResolvedSuggestions(term: string, options: Options = {}): Promise<Suggestion[]> {
|
||||
// Check if we're in command mode
|
||||
if (options.isCommandPalette && term.startsWith(">")) {
|
||||
const commandQuery = term.substring(1).trim();
|
||||
@ -305,12 +321,6 @@ async function fetchSuggestions(term: string, options: Options = {}): Promise<Su
|
||||
if (term.trim().length === 0) {
|
||||
return [];
|
||||
}
|
||||
return [
|
||||
{
|
||||
noteTitle: term,
|
||||
highlightedNotePathTitle: t("quick-search.searching")
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
const activeNoteId = appContext.tabManager.getActiveContextNoteId();
|
||||
@ -358,7 +368,7 @@ async function fetchSuggestionsWithDelay(term: string, options: Options): Promis
|
||||
return await new Promise<Suggestion[]>((resolve) => {
|
||||
clearTimeout(debounceTimeoutId);
|
||||
debounceTimeoutId = setTimeout(async () => {
|
||||
resolve(await fetchSuggestions(term, options));
|
||||
resolve(await fetchResolvedSuggestions(term, options));
|
||||
}, searchDelay);
|
||||
|
||||
if (searchDelay === 0) {
|
||||
@ -435,6 +445,9 @@ function clearText($el: JQuery<HTMLElement>) {
|
||||
const inputEl = $el[0] as HTMLInputElement;
|
||||
const instance = getManagedInstance($el);
|
||||
if (instance) {
|
||||
if (instance.isPanelOpen()) {
|
||||
instance.suppressNextClosedReset();
|
||||
}
|
||||
inputEl.value = "";
|
||||
instance.clearCursor();
|
||||
instance.autocomplete.setQuery("");
|
||||
@ -480,9 +493,13 @@ function fullTextSearch($el: JQuery<HTMLElement>, options: Options) {
|
||||
$el.trigger("focus");
|
||||
options.fastSearch = false;
|
||||
searchDelay = 0;
|
||||
resetSelectionState($el);
|
||||
|
||||
const instance = getManagedInstance($el);
|
||||
if (instance) {
|
||||
instance.clearCursor();
|
||||
instance.autocomplete.setQuery("");
|
||||
inputEl.value = "";
|
||||
instance.showQuery(searchString);
|
||||
}
|
||||
}
|
||||
@ -507,6 +524,9 @@ function initNoteAutocomplete($el: JQuery<HTMLElement>, options?: Options) {
|
||||
let currentQuery = inputEl.value;
|
||||
let shouldAutoselectTopItem = false;
|
||||
let shouldMirrorActiveItemToInput = false;
|
||||
let wasPanelOpen = false;
|
||||
let suppressNextClosedEmptyReset = false;
|
||||
let suggestionRequestId = 0;
|
||||
|
||||
const clearCursor = () => {
|
||||
shouldMirrorActiveItemToInput = false;
|
||||
@ -514,11 +534,28 @@ function initNoteAutocomplete($el: JQuery<HTMLElement>, options?: Options) {
|
||||
inputEl.value = currentQuery;
|
||||
};
|
||||
|
||||
const suppressNextClosedReset = () => {
|
||||
suppressNextClosedEmptyReset = true;
|
||||
};
|
||||
|
||||
const prepareForQueryChange = () => {
|
||||
shouldAutoselectTopItem = true;
|
||||
shouldMirrorActiveItemToInput = false;
|
||||
};
|
||||
|
||||
const rerunQuery = (query: string) => {
|
||||
if (!query.trim().length) {
|
||||
openRecentNotes();
|
||||
return;
|
||||
}
|
||||
|
||||
prepareForQueryChange();
|
||||
currentQuery = "";
|
||||
inputEl.value = "";
|
||||
autocomplete.setQuery("");
|
||||
showQuery(query);
|
||||
};
|
||||
|
||||
const onSelectItem = async (item: Suggestion) => {
|
||||
await handleSuggestionSelection($el, autocomplete, inputEl, item);
|
||||
};
|
||||
@ -539,9 +576,8 @@ function initNoteAutocomplete($el: JQuery<HTMLElement>, options?: Options) {
|
||||
inputEl.value = "";
|
||||
autocomplete.setQuery("");
|
||||
autocomplete.setActiveItemId(null);
|
||||
autocomplete.setIsOpen(false);
|
||||
|
||||
fetchSuggestions("", options).then((items) => {
|
||||
fetchResolvedSuggestions("", options).then((items) => {
|
||||
autocomplete.setCollections([{ source, items }]);
|
||||
autocomplete.setIsOpen(items.length > 0);
|
||||
});
|
||||
@ -582,6 +618,22 @@ function initNoteAutocomplete($el: JQuery<HTMLElement>, options?: Options) {
|
||||
if (isComposingInput) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (options.fastSearch === false && query.trim().length > 0) {
|
||||
const requestId = ++suggestionRequestId;
|
||||
|
||||
void fetchSuggestionsWithDelay(query, options).then((items) => {
|
||||
if (requestId !== suggestionRequestId || currentQuery !== query) {
|
||||
return;
|
||||
}
|
||||
|
||||
autocomplete.setCollections([{ source, items }]);
|
||||
autocomplete.setIsOpen(items.length > 0);
|
||||
});
|
||||
|
||||
return getSearchingSuggestion(query);
|
||||
}
|
||||
|
||||
return await fetchSuggestionsWithDelay(query, options);
|
||||
}
|
||||
},
|
||||
@ -594,6 +646,31 @@ function initNoteAutocomplete($el: JQuery<HTMLElement>, options?: Options) {
|
||||
const activeId = state.activeItemId ?? null;
|
||||
const activeItem = activeId !== null ? items[activeId] : null;
|
||||
currentQuery = state.query;
|
||||
const isPanelOpen = state.isOpen && items.length > 0;
|
||||
|
||||
if (isPanelOpen !== wasPanelOpen) {
|
||||
wasPanelOpen = isPanelOpen;
|
||||
|
||||
if (isPanelOpen) {
|
||||
$el.trigger("autocomplete:opened");
|
||||
|
||||
if (inputEl.readOnly) {
|
||||
suppressNextClosedReset();
|
||||
autocomplete.setIsOpen(false);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
$el.trigger("autocomplete:closed");
|
||||
|
||||
if (suppressNextClosedEmptyReset) {
|
||||
suppressNextClosedEmptyReset = false;
|
||||
} else if (!String(inputEl.value).trim()) {
|
||||
searchDelay = 0;
|
||||
resetSelectionState($el);
|
||||
$el.trigger("change");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (activeItem && shouldMirrorActiveItemToInput) {
|
||||
inputEl.value = getSuggestionInputValue(activeItem);
|
||||
@ -601,7 +678,7 @@ function initNoteAutocomplete($el: JQuery<HTMLElement>, options?: Options) {
|
||||
inputEl.value = state.query;
|
||||
}
|
||||
|
||||
if (state.isOpen && items.length > 0) {
|
||||
if (isPanelOpen) {
|
||||
renderItems(panelEl, items, activeId, (item) => {
|
||||
void onSelectItem(item);
|
||||
}, (index) => {
|
||||
@ -684,7 +761,7 @@ function initNoteAutocomplete($el: JQuery<HTMLElement>, options?: Options) {
|
||||
};
|
||||
const onCompositionEnd = (e: CompositionEvent) => {
|
||||
isComposingInput = false;
|
||||
handlers.onChange(e as any);
|
||||
rerunQuery(inputEl.value);
|
||||
};
|
||||
|
||||
inputEl.addEventListener("input", onInput);
|
||||
@ -707,7 +784,17 @@ function initNoteAutocomplete($el: JQuery<HTMLElement>, options?: Options) {
|
||||
}
|
||||
};
|
||||
|
||||
instanceMap.set(inputEl, { autocomplete, panelEl, clearCursor, showQuery, openRecentNotes, cleanup });
|
||||
instanceMap.set(inputEl, {
|
||||
autocomplete,
|
||||
panelEl,
|
||||
clearCursor,
|
||||
isPanelOpen: () => wasPanelOpen,
|
||||
getQuery: () => currentQuery,
|
||||
suppressNextClosedReset,
|
||||
showQuery,
|
||||
openRecentNotes,
|
||||
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"));
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user