mirror of
https://github.com/zadam/trilium.git
synced 2026-03-10 02:13:38 +01:00
refactor: remove old autocomplete declare
This commit is contained in:
parent
249fec6191
commit
0fc2c9f263
@ -232,13 +232,21 @@
|
||||
|
||||
---
|
||||
|
||||
### Step 8: 更新类型声明
|
||||
### Step 8: 更新类型声明 ✅ 完成
|
||||
**文件变更:**
|
||||
- `apps/client/src/types.d.ts` — 移除 `AutoCompleteConfig`、`AutoCompleteArg`、jQuery `.autocomplete()` 方法
|
||||
- `apps/client/src/widgets/PromotedAttributes.tsx` — 移除最后残留的 `$input.autocomplete(...)` 调用,改为复用 `attribute_autocomplete.ts` 的 headless label value autocomplete
|
||||
- `apps/client/src/services/autocomplete_core.ts` — 收紧 headless source 默认类型,补齐 internal source 所需默认钩子
|
||||
- `apps/client/src/services/note_autocomplete.ts` — 移除对不存在的 `autocomplete.destroy()` 调用,清理类型不兼容点
|
||||
|
||||
**验证方式:**
|
||||
- TypeScript 编译无错误
|
||||
|
||||
**当前完成情况:**
|
||||
- ✅ `types.d.ts` 中遗留的 `AutoCompleteConfig`、`AutoCompleteArg` 与 jQuery `.autocomplete()` 扩展声明已删除。
|
||||
- ✅ `PromotedAttributes.tsx` 不再依赖旧版 `autocomplete.js` 类型或初始化流程,至此 client 代码中已无 `.autocomplete(...)` 调用残留。
|
||||
- ✅ 运行 `pnpm exec tsc -p apps/client/tsconfig.app.json --noEmit` 通过。
|
||||
|
||||
---
|
||||
|
||||
### Step 9: 移除旧库和 Polyfill
|
||||
|
||||
@ -117,7 +117,7 @@ function initAttributeNameAutocomplete({ $el, attributeType, open, onValueChange
|
||||
|
||||
getSources({ query }) {
|
||||
return [
|
||||
withHeadlessSourceDefaults<NameItem>({
|
||||
withHeadlessSourceDefaults({
|
||||
sourceId: "attribute-names",
|
||||
getItems() {
|
||||
const type = typeof attributeType === "function" ? attributeType() : attributeType;
|
||||
@ -303,7 +303,7 @@ function initLabelValueAutocomplete({ $el, open, nameCallback, onValueChange }:
|
||||
|
||||
getSources({ query }) {
|
||||
return [
|
||||
withHeadlessSourceDefaults<NameItem>({
|
||||
withHeadlessSourceDefaults({
|
||||
sourceId: "attribute-values",
|
||||
async getItems() {
|
||||
const attributeName = nameCallback ? nameCallback() : "";
|
||||
|
||||
@ -2,11 +2,13 @@ import type { AutocompleteApi, AutocompleteSource, BaseItem } from "@algolia/aut
|
||||
|
||||
export const HEADLESS_AUTOCOMPLETE_PANEL_SELECTOR = ".aa-core-panel";
|
||||
|
||||
type HeadlessSourceDefaults = Required<Pick<AutocompleteSource<any>, "getItemUrl" | "onActive" | "onResolve">>;
|
||||
|
||||
const headlessAutocompleteClosers = new Set<() => void>();
|
||||
|
||||
export function withHeadlessSourceDefaults<TItem extends BaseItem>(
|
||||
source: AutocompleteSource<TItem>
|
||||
): AutocompleteSource<TItem> {
|
||||
export function withHeadlessSourceDefaults<TSource extends AutocompleteSource<any>>(
|
||||
source: TSource
|
||||
): TSource & HeadlessSourceDefaults {
|
||||
return {
|
||||
getItemUrl() {
|
||||
return undefined;
|
||||
@ -14,8 +16,11 @@ export function withHeadlessSourceDefaults<TItem extends BaseItem>(
|
||||
onActive() {
|
||||
// Headless consumers handle highlight side effects themselves.
|
||||
},
|
||||
onResolve() {
|
||||
// Headless consumers resolve and render items manually.
|
||||
},
|
||||
...source
|
||||
};
|
||||
} as TSource & HeadlessSourceDefaults;
|
||||
}
|
||||
|
||||
export function registerHeadlessAutocompleteCloser(close: () => void) {
|
||||
@ -171,8 +176,8 @@ export function bindAutocompleteInput<TItem extends BaseItem>({
|
||||
},
|
||||
{
|
||||
type: "keydown",
|
||||
listener: (event: KeyboardEvent) => {
|
||||
onKeyDown?.(event, handlers);
|
||||
listener: (event: Event) => {
|
||||
onKeyDown?.(event as KeyboardEvent, handlers);
|
||||
}
|
||||
},
|
||||
...extraBindings
|
||||
|
||||
@ -190,7 +190,7 @@ function renderSuggestion(item: Suggestion): string {
|
||||
}
|
||||
|
||||
function createSuggestionSource(options: Options, onSelectItem: (item: Suggestion) => void) {
|
||||
return withHeadlessSourceDefaults<Suggestion>({
|
||||
return withHeadlessSourceDefaults({
|
||||
sourceId: "note-suggestions",
|
||||
async getItems({ query }: { query: string }) {
|
||||
return await fetchResolvedSuggestions(query, options);
|
||||
@ -776,7 +776,6 @@ function initNoteAutocomplete($el: JQuery<HTMLElement>, options?: Options) {
|
||||
const cleanup = () => {
|
||||
unregisterGlobalCloser();
|
||||
cleanupInputBindings();
|
||||
autocomplete.destroy();
|
||||
panelController.destroy();
|
||||
};
|
||||
|
||||
|
||||
28
apps/client/src/types.d.ts
vendored
28
apps/client/src/types.d.ts
vendored
@ -6,7 +6,6 @@ import type { PrintReport } from "./print";
|
||||
import type { lint } from "./services/eslint";
|
||||
import type { Froca } from "./services/froca-interface";
|
||||
import { Library } from "./services/library_loader";
|
||||
import { Suggestion } from "./services/note_autocomplete";
|
||||
import server from "./services/server";
|
||||
import utils from "./services/utils";
|
||||
|
||||
@ -83,34 +82,7 @@ declare global {
|
||||
"note-load-progress": CustomEvent<{ progress: number }>;
|
||||
}
|
||||
|
||||
interface AutoCompleteConfig {
|
||||
appendTo?: HTMLElement | null;
|
||||
hint?: boolean;
|
||||
openOnFocus?: boolean;
|
||||
minLength?: number;
|
||||
tabAutocomplete?: boolean;
|
||||
autoselect?: boolean;
|
||||
dropdownMenuContainer?: HTMLElement;
|
||||
debug?: boolean;
|
||||
}
|
||||
|
||||
type AutoCompleteCallback = (values: AutoCompleteArg[]) => void;
|
||||
|
||||
interface AutoCompleteArg {
|
||||
name?: string;
|
||||
value?: string;
|
||||
notePathTitle?: string;
|
||||
displayKey?: "name" | "value" | "notePathTitle";
|
||||
cache?: boolean;
|
||||
source?: (term: string, cb: AutoCompleteCallback) => void,
|
||||
templates?: {
|
||||
suggestion: (suggestion: Suggestion) => string | undefined
|
||||
}
|
||||
}
|
||||
|
||||
interface JQuery {
|
||||
autocomplete: (action?: "close" | "open" | "destroy" | "val" | AutoCompleteConfig, args?: AutoCompleteArg[] | string) => JQuery<HTMLElement>;
|
||||
|
||||
getSelectedNotePath(): string | undefined;
|
||||
getSelectedNoteId(): string | null;
|
||||
setSelectedNotePath(notePath: string | null | undefined);
|
||||
|
||||
@ -8,6 +8,7 @@ import { Dispatch, StateUpdater, useCallback, useEffect, useRef, useState } from
|
||||
import NoteContext from "../components/note_context";
|
||||
import FAttribute from "../entities/fattribute";
|
||||
import FNote from "../entities/fnote";
|
||||
import attributeAutocompleteService from "../services/attribute_autocomplete";
|
||||
import { Attribute } from "../services/attribute_parser";
|
||||
import attributes from "../services/attributes";
|
||||
import { t } from "../services/i18n";
|
||||
@ -36,7 +37,7 @@ interface CellProps {
|
||||
setCellToFocus(cell: Cell): void;
|
||||
}
|
||||
|
||||
type OnChangeEventData = TargetedEvent<HTMLInputElement, Event> | InputEvent | JQuery.TriggeredEvent<HTMLInputElement, undefined, HTMLInputElement, HTMLInputElement>;
|
||||
type OnChangeEventData = TargetedEvent<HTMLInputElement, Event> | InputEvent;
|
||||
type OnChangeListener = (e: OnChangeEventData) => void | Promise<void>;
|
||||
|
||||
export default function PromotedAttributes() {
|
||||
@ -200,11 +201,7 @@ function LabelInput(props: CellProps & { inputId: string }) {
|
||||
}, [ cell, componentId, note, setCells ]);
|
||||
const extraInputProps: InputHTMLAttributes = {};
|
||||
|
||||
useTextLabelAutocomplete(inputId, valueAttr, definition, (e) => {
|
||||
if (e.currentTarget instanceof HTMLInputElement) {
|
||||
setDraft(e.currentTarget.value);
|
||||
}
|
||||
});
|
||||
useTextLabelAutocomplete(inputId, valueAttr, definition, setDraft);
|
||||
|
||||
// React to model changes.
|
||||
useEffect(() => {
|
||||
@ -413,55 +410,31 @@ function InputButton({ icon, className, title, onClick }: {
|
||||
);
|
||||
}
|
||||
|
||||
function useTextLabelAutocomplete(inputId: string, valueAttr: Attribute, definition: DefinitionObject, onChangeListener: OnChangeListener) {
|
||||
const [ attributeValues, setAttributeValues ] = useState<{ value: string }[] | null>(null);
|
||||
|
||||
// Obtain data.
|
||||
function useTextLabelAutocomplete(inputId: string, valueAttr: Attribute, definition: DefinitionObject, onValueChange: (value: string) => void) {
|
||||
useEffect(() => {
|
||||
if (definition.labelType !== "text") {
|
||||
return;
|
||||
}
|
||||
|
||||
server.get<string[]>(`attribute-values/${encodeURIComponent(valueAttr.name)}`).then((_attributesValues) => {
|
||||
setAttributeValues(_attributesValues.map((attribute) => ({ value: attribute })));
|
||||
});
|
||||
}, [ definition.labelType, valueAttr.name ]);
|
||||
|
||||
// Initialize autocomplete.
|
||||
useEffect(() => {
|
||||
if (attributeValues?.length === 0) return;
|
||||
const el = document.getElementById(inputId) as HTMLInputElement | null;
|
||||
if (!el) return;
|
||||
if (!el) {
|
||||
return;
|
||||
}
|
||||
|
||||
const $input = $(el);
|
||||
$input.autocomplete(
|
||||
{
|
||||
appendTo: document.querySelector("body"),
|
||||
hint: false,
|
||||
autoselect: false,
|
||||
openOnFocus: true,
|
||||
minLength: 0,
|
||||
tabAutocomplete: false
|
||||
},
|
||||
[
|
||||
{
|
||||
displayKey: "value",
|
||||
source (term, cb) {
|
||||
term = term.toLowerCase();
|
||||
attributeAutocompleteService.initLabelValueAutocomplete({
|
||||
$el: $input,
|
||||
open: false,
|
||||
nameCallback: () => valueAttr.name,
|
||||
onValueChange: (value) => {
|
||||
onValueChange(value);
|
||||
}
|
||||
});
|
||||
|
||||
const filtered = (attributeValues ?? []).filter((attr) => attr.value.toLowerCase().includes(term));
|
||||
|
||||
cb(filtered);
|
||||
}
|
||||
}
|
||||
]
|
||||
);
|
||||
|
||||
$input.off("autocomplete:selected");
|
||||
$input.on("autocomplete:selected", onChangeListener);
|
||||
|
||||
return () => $input.autocomplete("destroy");
|
||||
}, [ inputId, attributeValues, onChangeListener ]);
|
||||
return () => {
|
||||
attributeAutocompleteService.destroyAutocomplete($input);
|
||||
};
|
||||
}, [ definition.labelType, inputId, onValueChange, valueAttr.name ]);
|
||||
}
|
||||
|
||||
async function updateAttribute(note: FNote, cell: Cell, componentId: string, value: string | undefined, setCells: Dispatch<StateUpdater<Cell[] | undefined>>) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user