mirror of
https://github.com/zadam/trilium.git
synced 2026-03-13 03:43:43 +01:00
refactor: extract common logic
This commit is contained in:
parent
2690256f27
commit
e5a9c0de49
@ -2,7 +2,7 @@ import type { AutocompleteApi as CoreAutocompleteApi, BaseItem } from "@algolia/
|
||||
import { createAutocomplete } from "@algolia/autocomplete-core";
|
||||
|
||||
import type { AttributeType } from "../entities/fattribute.js";
|
||||
import { withHeadlessSourceDefaults } from "./autocomplete_core.js";
|
||||
import { bindAutocompleteInput, createHeadlessPanelController, withHeadlessSourceDefaults } from "./autocomplete_core.js";
|
||||
import server from "./server.js";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@ -34,18 +34,6 @@ interface ManagedInstance {
|
||||
|
||||
const instanceMap = new WeakMap<HTMLElement, ManagedInstance>();
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Dropdown panel DOM helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function createPanelEl(): HTMLElement {
|
||||
const panel = document.createElement("div");
|
||||
panel.className = "aa-core-panel";
|
||||
panel.style.display = "none";
|
||||
document.body.appendChild(panel);
|
||||
return panel;
|
||||
}
|
||||
|
||||
function renderItems(panelEl: HTMLElement, items: NameItem[], activeItemId: number | null, onSelect: (item: NameItem) => void): void {
|
||||
panelEl.innerHTML = "";
|
||||
if (items.length === 0) {
|
||||
@ -70,19 +58,6 @@ function renderItems(panelEl: HTMLElement, items: NameItem[], activeItemId: numb
|
||||
panelEl.appendChild(list);
|
||||
}
|
||||
|
||||
function positionPanel(panelEl: HTMLElement, inputEl: HTMLElement): void {
|
||||
const rect = inputEl.getBoundingClientRect();
|
||||
const top = `${rect.bottom}px`;
|
||||
const left = `${rect.left}px`;
|
||||
const width = `${rect.width}px`;
|
||||
|
||||
panelEl.style.position = "fixed";
|
||||
if (panelEl.style.top !== top) panelEl.style.top = top;
|
||||
if (panelEl.style.left !== left) panelEl.style.left = left;
|
||||
if (panelEl.style.width !== width) panelEl.style.width = width;
|
||||
if (panelEl.style.display !== "block") panelEl.style.display = "block";
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Attribute name autocomplete — new (autocomplete-core, headless)
|
||||
// ---------------------------------------------------------------------------
|
||||
@ -104,27 +79,12 @@ function initAttributeNameAutocomplete({ $el, attributeType, open, onValueChange
|
||||
return;
|
||||
}
|
||||
|
||||
const panelEl = createPanelEl();
|
||||
const panelController = createHeadlessPanelController({ inputEl });
|
||||
const { panelEl } = panelController;
|
||||
|
||||
let isPanelOpen = false;
|
||||
let hasActiveItem = false;
|
||||
|
||||
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<NameItem>({
|
||||
openOnFocus: true,
|
||||
defaultActiveItemId: 0,
|
||||
@ -171,62 +131,48 @@ function initAttributeNameAutocomplete({ $el, attributeType, open, onValueChange
|
||||
autocomplete.setIsOpen(false);
|
||||
onValueChange?.(item.name);
|
||||
});
|
||||
startPositioning();
|
||||
panelController.startPositioning();
|
||||
} else {
|
||||
panelEl.style.display = "none";
|
||||
stopPositioning();
|
||||
panelController.hide();
|
||||
}
|
||||
|
||||
if (!state.isOpen) {
|
||||
panelEl.style.display = "none";
|
||||
stopPositioning();
|
||||
panelController.hide();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// Wire up the input events
|
||||
const handlers = autocomplete.getInputProps({ inputElement: inputEl });
|
||||
const onInput = (e: Event) => {
|
||||
handlers.onChange(e as any);
|
||||
};
|
||||
const onFocus = (e: Event) => {
|
||||
syncQueryFromInputValue(autocomplete);
|
||||
handlers.onFocus(e as any);
|
||||
};
|
||||
const onBlur = () => {
|
||||
// Delay to allow mousedown on panel items
|
||||
setTimeout(() => {
|
||||
autocomplete.setIsOpen(false);
|
||||
panelEl.style.display = "none";
|
||||
stopPositioning();
|
||||
onValueChange?.(inputEl.value);
|
||||
}, 50);
|
||||
};
|
||||
const onKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.key === "Enter" && isPanelOpen && hasActiveItem) {
|
||||
// Prevent the enter key from propagating to parent dialogs
|
||||
// (which might interpret it as "submit" or "save and close")
|
||||
e.stopPropagation();
|
||||
// We shouldn't preventDefault here because we want handlers.onKeyDown
|
||||
// to process it properly. OnSelect will correctly close the panel.
|
||||
const cleanupInputBindings = bindAutocompleteInput<NameItem>({
|
||||
inputEl,
|
||||
autocomplete,
|
||||
onInput(e, handlers) {
|
||||
handlers.onChange(e as any);
|
||||
},
|
||||
onFocus(e, handlers) {
|
||||
syncQueryFromInputValue(autocomplete);
|
||||
handlers.onFocus(e as any);
|
||||
},
|
||||
onBlur() {
|
||||
// Delay to allow mousedown on panel items
|
||||
setTimeout(() => {
|
||||
autocomplete.setIsOpen(false);
|
||||
panelController.hide();
|
||||
onValueChange?.(inputEl.value);
|
||||
}, 50);
|
||||
},
|
||||
onKeyDown(e, handlers) {
|
||||
if (e.key === "Enter" && isPanelOpen && hasActiveItem) {
|
||||
// Prevent the enter key from propagating to parent dialogs
|
||||
// (which might interpret it as "submit" or "save and close")
|
||||
e.stopPropagation();
|
||||
}
|
||||
handlers.onKeyDown(e as any);
|
||||
}
|
||||
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);
|
||||
}
|
||||
cleanupInputBindings();
|
||||
panelController.destroy();
|
||||
};
|
||||
|
||||
instanceMap.set(inputEl, { autocomplete, panelEl, cleanup });
|
||||
@ -235,7 +181,7 @@ function initAttributeNameAutocomplete({ $el, attributeType, open, onValueChange
|
||||
syncQueryFromInputValue(autocomplete);
|
||||
autocomplete.setIsOpen(true);
|
||||
autocomplete.refresh();
|
||||
startPositioning();
|
||||
panelController.startPositioning();
|
||||
}
|
||||
}
|
||||
|
||||
@ -268,28 +214,13 @@ function initLabelValueAutocomplete({ $el, open, nameCallback, onValueChange }:
|
||||
return;
|
||||
}
|
||||
|
||||
const panelEl = createPanelEl();
|
||||
const panelController = createHeadlessPanelController({ inputEl });
|
||||
const { panelEl } = panelController;
|
||||
|
||||
let isPanelOpen = false;
|
||||
let hasActiveItem = false;
|
||||
let isSelecting = false;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
let cachedAttributeName = "";
|
||||
let cachedAttributeValues: NameItem[] = [];
|
||||
|
||||
@ -363,63 +294,52 @@ function initLabelValueAutocomplete({ $el, open, nameCallback, onValueChange }:
|
||||
|
||||
if (state.isOpen && items.length > 0) {
|
||||
renderItems(panelEl, items, activeId, handleSelect);
|
||||
startPositioning();
|
||||
panelController.startPositioning();
|
||||
} else {
|
||||
panelEl.style.display = "none";
|
||||
stopPositioning();
|
||||
panelController.hide();
|
||||
}
|
||||
|
||||
if (!state.isOpen) {
|
||||
panelEl.style.display = "none";
|
||||
stopPositioning();
|
||||
panelController.hide();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const handlers = autocomplete.getInputProps({ inputElement: inputEl });
|
||||
const onInput = (e: Event) => {
|
||||
if (!isSelecting) {
|
||||
handlers.onChange(e as any);
|
||||
const cleanupInputBindings = bindAutocompleteInput<NameItem>({
|
||||
inputEl,
|
||||
autocomplete,
|
||||
onInput(e, handlers) {
|
||||
if (!isSelecting) {
|
||||
handlers.onChange(e as any);
|
||||
}
|
||||
},
|
||||
onFocus(e, handlers) {
|
||||
const attributeName = nameCallback ? nameCallback() : "";
|
||||
if (attributeName !== cachedAttributeName) {
|
||||
cachedAttributeName = "";
|
||||
cachedAttributeValues = [];
|
||||
}
|
||||
syncQueryFromInputValue(autocomplete);
|
||||
handlers.onFocus(e as any);
|
||||
},
|
||||
onBlur() {
|
||||
setTimeout(() => {
|
||||
autocomplete.setIsOpen(false);
|
||||
panelController.hide();
|
||||
onValueChange?.(inputEl.value);
|
||||
}, 50);
|
||||
},
|
||||
onKeyDown(e, handlers) {
|
||||
if (e.key === "Enter" && isPanelOpen && hasActiveItem) {
|
||||
e.stopPropagation();
|
||||
}
|
||||
handlers.onKeyDown(e as any);
|
||||
}
|
||||
};
|
||||
const onFocus = (e: Event) => {
|
||||
const attributeName = nameCallback ? nameCallback() : "";
|
||||
if (attributeName !== cachedAttributeName) {
|
||||
cachedAttributeName = "";
|
||||
cachedAttributeValues = [];
|
||||
}
|
||||
syncQueryFromInputValue(autocomplete);
|
||||
handlers.onFocus(e as any);
|
||||
};
|
||||
const onBlur = () => {
|
||||
setTimeout(() => {
|
||||
autocomplete.setIsOpen(false);
|
||||
panelEl.style.display = "none";
|
||||
stopPositioning();
|
||||
onValueChange?.(inputEl.value);
|
||||
}, 50);
|
||||
};
|
||||
const onKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.key === "Enter" && isPanelOpen && hasActiveItem) {
|
||||
e.stopPropagation();
|
||||
}
|
||||
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);
|
||||
}
|
||||
cleanupInputBindings();
|
||||
panelController.destroy();
|
||||
};
|
||||
|
||||
instanceMap.set(inputEl, { autocomplete, panelEl, cleanup });
|
||||
@ -428,7 +348,7 @@ function initLabelValueAutocomplete({ $el, open, nameCallback, onValueChange }:
|
||||
syncQueryFromInputValue(autocomplete);
|
||||
autocomplete.setIsOpen(true);
|
||||
autocomplete.refresh();
|
||||
startPositioning();
|
||||
panelController.startPositioning();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { AutocompleteSource, BaseItem } from "@algolia/autocomplete-core";
|
||||
import type { AutocompleteApi, AutocompleteSource, BaseItem } from "@algolia/autocomplete-core";
|
||||
|
||||
export function withHeadlessSourceDefaults<TItem extends BaseItem>(
|
||||
source: AutocompleteSource<TItem>
|
||||
@ -13,3 +13,160 @@ export function withHeadlessSourceDefaults<TItem extends BaseItem>(
|
||||
...source
|
||||
};
|
||||
}
|
||||
|
||||
interface HeadlessPanelControllerOptions {
|
||||
inputEl: HTMLElement;
|
||||
container?: HTMLElement | null;
|
||||
className?: string;
|
||||
containedClassName?: string;
|
||||
}
|
||||
|
||||
export function createHeadlessPanelController({
|
||||
inputEl,
|
||||
container,
|
||||
className = "aa-core-panel",
|
||||
containedClassName = "aa-core-panel--contained"
|
||||
}: HeadlessPanelControllerOptions) {
|
||||
const panelEl = document.createElement("div");
|
||||
panelEl.className = className;
|
||||
|
||||
const isContained = Boolean(container);
|
||||
if (isContained) {
|
||||
panelEl.classList.add(containedClassName);
|
||||
container!.appendChild(panelEl);
|
||||
} else {
|
||||
document.body.appendChild(panelEl);
|
||||
}
|
||||
|
||||
panelEl.style.display = "none";
|
||||
|
||||
let rafId: number | null = null;
|
||||
|
||||
const positionPanel = () => {
|
||||
if (isContained) {
|
||||
panelEl.style.position = "static";
|
||||
panelEl.style.top = "";
|
||||
panelEl.style.left = "";
|
||||
panelEl.style.width = "100%";
|
||||
panelEl.style.display = "block";
|
||||
return;
|
||||
}
|
||||
|
||||
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";
|
||||
};
|
||||
|
||||
const stopPositioning = () => {
|
||||
if (rafId !== null) {
|
||||
cancelAnimationFrame(rafId);
|
||||
rafId = null;
|
||||
}
|
||||
};
|
||||
|
||||
const startPositioning = () => {
|
||||
if (isContained) {
|
||||
positionPanel();
|
||||
return;
|
||||
}
|
||||
|
||||
if (rafId !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const update = () => {
|
||||
positionPanel();
|
||||
rafId = requestAnimationFrame(update);
|
||||
};
|
||||
|
||||
update();
|
||||
};
|
||||
|
||||
const hide = () => {
|
||||
panelEl.style.display = "none";
|
||||
stopPositioning();
|
||||
};
|
||||
|
||||
const destroy = () => {
|
||||
hide();
|
||||
panelEl.remove();
|
||||
};
|
||||
|
||||
return {
|
||||
panelEl,
|
||||
hide,
|
||||
destroy,
|
||||
startPositioning,
|
||||
stopPositioning
|
||||
};
|
||||
}
|
||||
|
||||
type InputHandlers<TItem extends BaseItem> = ReturnType<AutocompleteApi<TItem>["getInputProps"]>;
|
||||
|
||||
interface InputBinding<TEvent extends Event = Event> {
|
||||
type: string;
|
||||
listener: (event: TEvent) => void;
|
||||
}
|
||||
|
||||
interface BindAutocompleteInputOptions<TItem extends BaseItem> {
|
||||
inputEl: HTMLInputElement;
|
||||
autocomplete: AutocompleteApi<TItem>;
|
||||
onInput?: (event: Event, handlers: InputHandlers<TItem>) => void;
|
||||
onFocus?: (event: Event, handlers: InputHandlers<TItem>) => void;
|
||||
onBlur?: (event: Event, handlers: InputHandlers<TItem>) => void;
|
||||
onKeyDown?: (event: KeyboardEvent, handlers: InputHandlers<TItem>) => void;
|
||||
extraBindings?: InputBinding[];
|
||||
}
|
||||
|
||||
export function bindAutocompleteInput<TItem extends BaseItem>({
|
||||
inputEl,
|
||||
autocomplete,
|
||||
onInput,
|
||||
onFocus,
|
||||
onBlur,
|
||||
onKeyDown,
|
||||
extraBindings = []
|
||||
}: BindAutocompleteInputOptions<TItem>) {
|
||||
const handlers = autocomplete.getInputProps({ inputElement: inputEl });
|
||||
|
||||
const bindings: InputBinding[] = [
|
||||
{
|
||||
type: "input",
|
||||
listener: (event: Event) => {
|
||||
onInput?.(event, handlers);
|
||||
}
|
||||
},
|
||||
{
|
||||
type: "focus",
|
||||
listener: (event: Event) => {
|
||||
onFocus?.(event, handlers);
|
||||
}
|
||||
},
|
||||
{
|
||||
type: "blur",
|
||||
listener: (event: Event) => {
|
||||
onBlur?.(event, handlers);
|
||||
}
|
||||
},
|
||||
{
|
||||
type: "keydown",
|
||||
listener: (event: KeyboardEvent) => {
|
||||
onKeyDown?.(event, handlers);
|
||||
}
|
||||
},
|
||||
...extraBindings
|
||||
];
|
||||
|
||||
bindings.forEach(({ type, listener }) => {
|
||||
inputEl.addEventListener(type, listener as EventListener);
|
||||
});
|
||||
|
||||
return () => {
|
||||
bindings.forEach(({ type, listener }) => {
|
||||
inputEl.removeEventListener(type, listener as EventListener);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@ -3,7 +3,7 @@ import { createAutocomplete } from "@algolia/autocomplete-core";
|
||||
import type { MentionFeedObjectItem } from "@triliumnext/ckeditor5";
|
||||
|
||||
import appContext from "../components/app_context.js";
|
||||
import { withHeadlessSourceDefaults } from "./autocomplete_core.js";
|
||||
import { bindAutocompleteInput, createHeadlessPanelController, withHeadlessSourceDefaults } from "./autocomplete_core.js";
|
||||
import commandRegistry from "./command_registry.js";
|
||||
import froca from "./froca.js";
|
||||
import { t } from "./i18n.js";
|
||||
@ -72,37 +72,6 @@ interface ManagedInstance {
|
||||
|
||||
const instanceMap = new WeakMap<HTMLElement, ManagedInstance>();
|
||||
|
||||
function createPanelEl(container?: HTMLElement | null): HTMLElement {
|
||||
const panel = document.createElement("div");
|
||||
panel.className = "aa-core-panel aa-dropdown-menu";
|
||||
if (container) {
|
||||
panel.classList.add("aa-core-panel--contained");
|
||||
container.appendChild(panel);
|
||||
} else {
|
||||
document.body.appendChild(panel);
|
||||
}
|
||||
panel.style.display = "none";
|
||||
return panel;
|
||||
}
|
||||
|
||||
function positionPanel(panelEl: HTMLElement, inputEl: HTMLElement): void {
|
||||
if (panelEl.classList.contains("aa-core-panel--contained")) {
|
||||
panelEl.style.position = "static";
|
||||
panelEl.style.top = "";
|
||||
panelEl.style.left = "";
|
||||
panelEl.style.width = "100%";
|
||||
panelEl.style.display = "block";
|
||||
return;
|
||||
}
|
||||
|
||||
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 escapeHtml(text: string): string {
|
||||
return text
|
||||
.replaceAll("&", "&")
|
||||
@ -535,8 +504,12 @@ function initNoteAutocomplete($el: JQuery<HTMLElement>, options?: Options) {
|
||||
options = options || {};
|
||||
let isComposingInput = false;
|
||||
|
||||
const panelEl = createPanelEl(options.container);
|
||||
let rafId: number | null = null;
|
||||
const panelController = createHeadlessPanelController({
|
||||
inputEl,
|
||||
container: options.container,
|
||||
className: "aa-core-panel aa-dropdown-menu"
|
||||
});
|
||||
const { panelEl } = panelController;
|
||||
let currentQuery = inputEl.value;
|
||||
let shouldAutoselectTopItem = false;
|
||||
let shouldMirrorActiveItemToInput = false;
|
||||
@ -600,26 +573,6 @@ function initNoteAutocomplete($el: JQuery<HTMLElement>, options?: Options) {
|
||||
});
|
||||
};
|
||||
|
||||
function startPositioning() {
|
||||
if (panelEl.classList.contains("aa-core-panel--contained")) {
|
||||
positionPanel(panelEl, inputEl);
|
||||
return;
|
||||
}
|
||||
|
||||
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
|
||||
// Old autocomplete.js used `autoselect: true`, so the first item
|
||||
@ -713,68 +666,14 @@ function initNoteAutocomplete($el: JQuery<HTMLElement>, options?: Options) {
|
||||
return;
|
||||
}
|
||||
|
||||
startPositioning();
|
||||
panelController.startPositioning();
|
||||
} else {
|
||||
shouldAutoselectTopItem = false;
|
||||
panelEl.style.display = "none";
|
||||
stopPositioning();
|
||||
panelController.hide();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const handlers = autocomplete.getInputProps({ inputElement: inputEl });
|
||||
const onInput = (e: Event) => {
|
||||
const value = (e.currentTarget as HTMLInputElement).value;
|
||||
if (value.trim().length === 0) {
|
||||
openRecentNotes();
|
||||
return;
|
||||
}
|
||||
|
||||
prepareForQueryChange();
|
||||
handlers.onChange(e as any);
|
||||
};
|
||||
const onFocus = (e: Event) => {
|
||||
if (inputEl.readOnly) {
|
||||
autocomplete.setIsOpen(false);
|
||||
panelEl.style.display = "none";
|
||||
stopPositioning();
|
||||
return;
|
||||
}
|
||||
handlers.onFocus(e as any);
|
||||
};
|
||||
const onBlur = () => {
|
||||
if (options.container) {
|
||||
return;
|
||||
}
|
||||
setTimeout(() => {
|
||||
autocomplete.setIsOpen(false);
|
||||
panelEl.style.display = "none";
|
||||
stopPositioning();
|
||||
}, 50);
|
||||
};
|
||||
const onKeyDown = (e: KeyboardEvent) => {
|
||||
if (options.allowJumpToSearchNotes && e.ctrlKey && e.key === "Enter") {
|
||||
e.stopImmediatePropagation();
|
||||
e.preventDefault();
|
||||
void handleSuggestionSelection($el, autocomplete, inputEl, {
|
||||
action: "search-notes",
|
||||
noteTitle: inputEl.value
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.shiftKey && e.key === "Enter") {
|
||||
e.stopImmediatePropagation();
|
||||
e.preventDefault();
|
||||
fullTextSearch($el, options);
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.key === "ArrowDown" || e.key === "ArrowUp") {
|
||||
shouldMirrorActiveItemToInput = true;
|
||||
}
|
||||
handlers.onKeyDown(e as any);
|
||||
};
|
||||
const onCompositionStart = () => {
|
||||
isComposingInput = true;
|
||||
};
|
||||
@ -783,25 +682,69 @@ function initNoteAutocomplete($el: JQuery<HTMLElement>, options?: Options) {
|
||||
rerunQuery(inputEl.value);
|
||||
};
|
||||
|
||||
inputEl.addEventListener("input", onInput);
|
||||
inputEl.addEventListener("focus", onFocus);
|
||||
inputEl.addEventListener("blur", onBlur);
|
||||
inputEl.addEventListener("keydown", onKeyDown);
|
||||
inputEl.addEventListener("compositionstart", onCompositionStart);
|
||||
inputEl.addEventListener("compositionend", onCompositionEnd);
|
||||
const cleanupInputBindings = bindAutocompleteInput<Suggestion>({
|
||||
inputEl,
|
||||
autocomplete,
|
||||
onInput(e, handlers) {
|
||||
const value = (e.currentTarget as HTMLInputElement).value;
|
||||
if (value.trim().length === 0) {
|
||||
openRecentNotes();
|
||||
return;
|
||||
}
|
||||
|
||||
prepareForQueryChange();
|
||||
handlers.onChange(e as any);
|
||||
},
|
||||
onFocus(e, handlers) {
|
||||
if (inputEl.readOnly) {
|
||||
autocomplete.setIsOpen(false);
|
||||
panelController.hide();
|
||||
return;
|
||||
}
|
||||
handlers.onFocus(e as any);
|
||||
},
|
||||
onBlur() {
|
||||
if (options.container) {
|
||||
return;
|
||||
}
|
||||
setTimeout(() => {
|
||||
autocomplete.setIsOpen(false);
|
||||
panelController.hide();
|
||||
}, 50);
|
||||
},
|
||||
onKeyDown(e, handlers) {
|
||||
if (options.allowJumpToSearchNotes && e.ctrlKey && e.key === "Enter") {
|
||||
e.stopImmediatePropagation();
|
||||
e.preventDefault();
|
||||
void handleSuggestionSelection($el, autocomplete, inputEl, {
|
||||
action: "search-notes",
|
||||
noteTitle: inputEl.value
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.shiftKey && e.key === "Enter") {
|
||||
e.stopImmediatePropagation();
|
||||
e.preventDefault();
|
||||
fullTextSearch($el, options);
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.key === "ArrowDown" || e.key === "ArrowUp") {
|
||||
shouldMirrorActiveItemToInput = true;
|
||||
}
|
||||
handlers.onKeyDown(e as any);
|
||||
},
|
||||
extraBindings: [
|
||||
{ type: "compositionstart", listener: onCompositionStart },
|
||||
{ type: "compositionend", listener: onCompositionEnd }
|
||||
]
|
||||
});
|
||||
|
||||
const cleanup = () => {
|
||||
inputEl.removeEventListener("input", onInput);
|
||||
inputEl.removeEventListener("focus", onFocus);
|
||||
inputEl.removeEventListener("blur", onBlur);
|
||||
inputEl.removeEventListener("keydown", onKeyDown);
|
||||
inputEl.removeEventListener("compositionstart", onCompositionStart);
|
||||
inputEl.removeEventListener("compositionend", onCompositionEnd);
|
||||
stopPositioning();
|
||||
cleanupInputBindings();
|
||||
autocomplete.destroy();
|
||||
if (panelEl.parentElement) {
|
||||
panelEl.parentElement.removeChild(panelEl);
|
||||
}
|
||||
panelController.destroy();
|
||||
};
|
||||
|
||||
instanceMap.set(inputEl, {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user