mirror of
https://github.com/zadam/trilium.git
synced 2026-03-22 08:13:46 +01:00
refactor: migrate react part
This commit is contained in:
parent
3ac2e2785d
commit
0dee06262b
@ -164,7 +164,7 @@
|
||||
|
||||
---
|
||||
|
||||
### Step 5: 迁移 `NoteAutocomplete.tsx` (React/Preact 组件)
|
||||
### Step 5: 迁移 `NoteAutocomplete.tsx` (React/Preact 组件) ✅ 基本完成
|
||||
**文件变更:**
|
||||
- `apps/client/src/widgets/react/NoteAutocomplete.tsx` — 传入容器 `<div>`,管理 `api` 生命周期
|
||||
|
||||
@ -173,8 +173,10 @@
|
||||
- `noteId` 和 `text` props 的动态更新正确
|
||||
|
||||
**当前状态:**
|
||||
- ⚠️ 尚未完成。虽然底层 `note_autocomplete.ts` 已经切到新实现,但 React 消费方仍需逐一验收。
|
||||
- ⚠️ 已抽查 `Move to`,当前功能不正常,说明 Step 5 仍存在待修复问题。
|
||||
- ✅ `NoteAutocomplete.tsx` 已移除残留的旧 `.autocomplete("val")` 调用,改为完全走 `note_autocomplete.ts` 暴露的 helper。
|
||||
- ✅ 组件现在会显式管理 headless autocomplete 的初始化/销毁生命周期,并清理 React 侧追加的 DOM / jQuery 监听,避免重复绑定。
|
||||
- ✅ `noteId` / `text` prop 同步已切到新状态流,`setNote()` 也会同步内部 query,避免仅改 DOM 值导致的状态漂移。
|
||||
- ⚠️ 仍需继续做手动回归验收,重点应覆盖 `Move to`、`Clone to`、`Include note`、`Add link`、bulk actions 等主要 React 消费方。
|
||||
|
||||
---
|
||||
|
||||
|
||||
@ -454,7 +454,7 @@ async function handleSuggestionSelection(
|
||||
$el.trigger("autocomplete:noteselected", [suggestion]);
|
||||
}
|
||||
|
||||
function clearText($el: JQuery<HTMLElement>) {
|
||||
export function clearText($el: JQuery<HTMLElement>) {
|
||||
searchDelay = 0;
|
||||
resetSelectionState($el);
|
||||
const inputEl = $el[0] as HTMLInputElement;
|
||||
@ -594,6 +594,7 @@ function initNoteAutocomplete($el: JQuery<HTMLElement>, options?: Options) {
|
||||
|
||||
fetchResolvedSuggestions("", options).then((items) => {
|
||||
autocomplete.setCollections([{ source, items }]);
|
||||
autocomplete.setActiveItemId(items.length > 0 ? 0 : null);
|
||||
autocomplete.setIsOpen(items.length > 0);
|
||||
});
|
||||
};
|
||||
@ -620,7 +621,9 @@ function initNoteAutocomplete($el: JQuery<HTMLElement>, options?: Options) {
|
||||
|
||||
const autocomplete = createAutocomplete<Suggestion>({
|
||||
openOnFocus: false, // Wait until we explicitly focus or type
|
||||
defaultActiveItemId: null,
|
||||
// Old autocomplete.js used `autoselect: true`, so the first item
|
||||
// should be immediately selectable when the panel opens.
|
||||
defaultActiveItemId: 0,
|
||||
shouldPanelOpen() {
|
||||
return true;
|
||||
},
|
||||
@ -794,6 +797,7 @@ function initNoteAutocomplete($el: JQuery<HTMLElement>, options?: Options) {
|
||||
inputEl.removeEventListener("compositionstart", onCompositionStart);
|
||||
inputEl.removeEventListener("compositionend", onCompositionEnd);
|
||||
stopPositioning();
|
||||
autocomplete.destroy();
|
||||
if (panelEl.parentElement) {
|
||||
panelEl.parentElement.removeChild(panelEl);
|
||||
}
|
||||
@ -836,6 +840,15 @@ function initNoteAutocomplete($el: JQuery<HTMLElement>, options?: Options) {
|
||||
return $el;
|
||||
}
|
||||
|
||||
export function destroyAutocomplete($el: JQuery<HTMLElement> | HTMLElement) {
|
||||
const inputEl = $el instanceof HTMLElement ? $el : $el[0] as HTMLInputElement;
|
||||
const instance = instanceMap.get(inputEl);
|
||||
if (instance) {
|
||||
instance.cleanup();
|
||||
instanceMap.delete(inputEl);
|
||||
}
|
||||
}
|
||||
|
||||
function init() {
|
||||
$.fn.getSelectedNotePath = function () {
|
||||
if (!String($(this).val())?.trim()) {
|
||||
@ -878,10 +891,19 @@ function init() {
|
||||
|
||||
$.fn.setNote = async function (noteId) {
|
||||
const note = noteId ? await froca.getNote(noteId, true) : null;
|
||||
const $el = $(this as unknown as HTMLElement);
|
||||
const instance = getManagedInstance($el);
|
||||
const noteTitle = note ? note.title : "";
|
||||
|
||||
$(this)
|
||||
.val(note ? note.title : "")
|
||||
$el
|
||||
.val(noteTitle)
|
||||
.setSelectedNotePath(noteId);
|
||||
|
||||
if (instance) {
|
||||
instance.clearCursor();
|
||||
instance.autocomplete.setQuery(noteTitle);
|
||||
instance.autocomplete.setIsOpen(false);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@ -902,6 +924,8 @@ export function triggerRecentNotes(inputElement: HTMLInputElement | null | undef
|
||||
|
||||
export default {
|
||||
autocompleteSourceForCKEditor,
|
||||
clearText,
|
||||
destroyAutocomplete,
|
||||
initNoteAutocomplete,
|
||||
showRecentNotes,
|
||||
showAllCommands,
|
||||
|
||||
@ -28,73 +28,97 @@ export default function NoteAutocomplete({ id, inputRef: externalInputRef, text,
|
||||
if (!ref.current) return;
|
||||
const $autoComplete = $(ref.current);
|
||||
|
||||
// clear any event listener added in previous invocation of this function
|
||||
$autoComplete
|
||||
.off("autocomplete:noteselected")
|
||||
.off("autocomplete:commandselected")
|
||||
|
||||
note_autocomplete.initNoteAutocomplete($autoComplete, {
|
||||
...opts,
|
||||
container: container?.current
|
||||
});
|
||||
if (onTextChange) {
|
||||
$autoComplete.on("input", () => onTextChange($autoComplete[0].value));
|
||||
}
|
||||
if (onKeyDown) {
|
||||
$autoComplete.on("keydown", (e) => e.originalEvent && onKeyDown(e.originalEvent));
|
||||
}
|
||||
if (onBlur) {
|
||||
$autoComplete.on("blur", () => onBlur($autoComplete.getSelectedNoteId() ?? ""));
|
||||
}
|
||||
}, [opts, container?.current]);
|
||||
|
||||
// On change event handlers.
|
||||
useEffect(() => {
|
||||
if (!ref.current) return;
|
||||
const $autoComplete = $(ref.current);
|
||||
const inputListener = () => onTextChange?.($autoComplete[0].value);
|
||||
const keyDownListener = (e) => e.originalEvent && onKeyDown?.(e.originalEvent);
|
||||
const blurListener = () => onBlur?.($autoComplete.getSelectedNoteId() ?? "");
|
||||
|
||||
if (onChange || noteIdChanged) {
|
||||
const autoCompleteListener = (_e, suggestion) => {
|
||||
onChange?.(suggestion);
|
||||
|
||||
if (noteIdChanged) {
|
||||
const noteId = suggestion?.notePath?.split("/")?.at(-1);
|
||||
noteIdChanged(noteId);
|
||||
}
|
||||
};
|
||||
const changeListener = (e) => {
|
||||
if (!ref.current?.value) {
|
||||
autoCompleteListener(e, null);
|
||||
}
|
||||
};
|
||||
$autoComplete
|
||||
.on("autocomplete:noteselected", autoCompleteListener)
|
||||
.on("autocomplete:externallinkselected", autoCompleteListener)
|
||||
.on("autocomplete:commandselected", autoCompleteListener)
|
||||
.on("change", changeListener);
|
||||
return () => {
|
||||
$autoComplete
|
||||
.off("autocomplete:noteselected", autoCompleteListener)
|
||||
.off("autocomplete:externallinkselected", autoCompleteListener)
|
||||
.off("autocomplete:commandselected", autoCompleteListener)
|
||||
.off("change", changeListener);
|
||||
};
|
||||
if (onTextChange) {
|
||||
$autoComplete.on("input", inputListener);
|
||||
}
|
||||
}, [opts, container?.current, onChange, noteIdChanged])
|
||||
if (onKeyDown) {
|
||||
$autoComplete.on("keydown", keyDownListener);
|
||||
}
|
||||
if (onBlur) {
|
||||
$autoComplete.on("blur", blurListener);
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (onTextChange) {
|
||||
$autoComplete.off("input", inputListener);
|
||||
}
|
||||
if (onKeyDown) {
|
||||
$autoComplete.off("keydown", keyDownListener);
|
||||
}
|
||||
if (onBlur) {
|
||||
$autoComplete.off("blur", blurListener);
|
||||
}
|
||||
};
|
||||
}, [onBlur, onKeyDown, onTextChange]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!ref.current) return;
|
||||
const $autoComplete = $(ref.current);
|
||||
if (!(onChange || noteIdChanged)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const autoCompleteListener = (_e, suggestion) => {
|
||||
onChange?.(suggestion);
|
||||
|
||||
if (noteIdChanged) {
|
||||
const noteId = suggestion?.notePath?.split("/")?.at(-1);
|
||||
noteIdChanged(noteId);
|
||||
}
|
||||
};
|
||||
const changeListener = (e) => {
|
||||
if (!ref.current?.value) {
|
||||
autoCompleteListener(e, null);
|
||||
}
|
||||
};
|
||||
|
||||
$autoComplete
|
||||
.on("autocomplete:noteselected", autoCompleteListener)
|
||||
.on("autocomplete:externallinkselected", autoCompleteListener)
|
||||
.on("autocomplete:commandselected", autoCompleteListener)
|
||||
.on("change", changeListener);
|
||||
|
||||
return () => {
|
||||
$autoComplete
|
||||
.off("autocomplete:noteselected", autoCompleteListener)
|
||||
.off("autocomplete:externallinkselected", autoCompleteListener)
|
||||
.off("autocomplete:commandselected", autoCompleteListener)
|
||||
.off("change", changeListener);
|
||||
};
|
||||
}, [onChange, noteIdChanged]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!ref.current) return;
|
||||
const $autoComplete = $(ref.current);
|
||||
|
||||
if (noteId) {
|
||||
$autoComplete.setNote(noteId);
|
||||
} else if (text) {
|
||||
note_autocomplete.setText($autoComplete, text);
|
||||
} else {
|
||||
$autoComplete.setSelectedNotePath("");
|
||||
$autoComplete.autocomplete("val", "");
|
||||
ref.current.value = "";
|
||||
void $autoComplete.setNote(noteId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (text !== undefined) {
|
||||
if (text) {
|
||||
note_autocomplete.setText($autoComplete, text);
|
||||
} else {
|
||||
note_autocomplete.clearText($autoComplete);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
note_autocomplete.clearText($autoComplete);
|
||||
}, [text, noteId]);
|
||||
|
||||
return (
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user