| 
									
										
										
										
											2025-09-05 11:54:58 +03:00
										 |  |  | import { Inputs, MutableRef, useCallback, useContext, useDebugValue, useEffect, useLayoutEffect, useMemo, useRef, useState } from "preact/hooks"; | 
					
						
							|  |  |  | import { CommandListenerData, EventData, EventNames } from "../../components/app_context"; | 
					
						
							| 
									
										
										
										
											2025-08-20 21:50:06 +03:00
										 |  |  | import { ParentComponent } from "./react_utils"; | 
					
						
							| 
									
										
										
										
											2025-08-08 23:23:07 +03:00
										 |  |  | import SpacedUpdate from "../../services/spaced_update"; | 
					
						
							| 
									
										
										
										
											2025-08-29 00:47:47 +03:00
										 |  |  | import { KeyboardActionNames, OptionNames } from "@triliumnext/commons"; | 
					
						
							| 
									
										
										
										
											2025-08-14 23:54:32 +03:00
										 |  |  | import options, { type OptionValue } from "../../services/options"; | 
					
						
							| 
									
										
										
										
											2025-08-30 18:48:34 +03:00
										 |  |  | import utils, { escapeRegExp, reloadFrontendApp } from "../../services/utils"; | 
					
						
							| 
									
										
										
										
											2025-08-20 23:53:13 +03:00
										 |  |  | import NoteContext from "../../components/note_context"; | 
					
						
							| 
									
										
										
										
											2025-08-22 23:12:14 +03:00
										 |  |  | import BasicWidget, { ReactWrappedWidget } from "../basic_widget"; | 
					
						
							| 
									
										
										
										
											2025-08-21 10:44:58 +03:00
										 |  |  | import FNote from "../../entities/fnote"; | 
					
						
							| 
									
										
										
										
											2025-08-21 15:50:14 +03:00
										 |  |  | import attributes from "../../services/attributes"; | 
					
						
							| 
									
										
										
										
											2025-08-22 21:04:04 +03:00
										 |  |  | import FBlob from "../../entities/fblob"; | 
					
						
							| 
									
										
										
										
											2025-08-22 23:12:14 +03:00
										 |  |  | import NoteContextAwareWidget from "../note_context_aware_widget"; | 
					
						
							| 
									
										
										
										
											2025-08-24 21:18:48 +03:00
										 |  |  | import { RefObject, VNode } from "preact"; | 
					
						
							| 
									
										
										
										
											2025-08-23 12:31:54 +03:00
										 |  |  | import { Tooltip } from "bootstrap"; | 
					
						
							| 
									
										
										
										
											2025-09-04 23:35:18 +03:00
										 |  |  | import { CSSProperties, DragEventHandler } from "preact/compat"; | 
					
						
							| 
									
										
										
										
											2025-08-29 00:47:47 +03:00
										 |  |  | import keyboard_actions from "../../services/keyboard_actions"; | 
					
						
							| 
									
										
										
										
											2025-08-30 18:48:34 +03:00
										 |  |  | import Mark from "mark.js"; | 
					
						
							| 
									
										
										
										
											2025-09-04 23:35:18 +03:00
										 |  |  | import { DragData } from "../note_tree"; | 
					
						
							| 
									
										
										
										
											2025-09-05 11:54:58 +03:00
										 |  |  | import Component from "../../components/component"; | 
					
						
							| 
									
										
										
										
											2025-08-14 21:05:24 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-24 22:14:42 +03:00
										 |  |  | export function useTriliumEvent<T extends EventNames>(eventName: T, handler: (data: EventData<T>) => void) { | 
					
						
							| 
									
										
										
										
											2025-08-25 17:17:56 +03:00
										 |  |  |     const parentComponent = useContext(ParentComponent); | 
					
						
							| 
									
										
										
										
											2025-08-27 17:36:22 +03:00
										 |  |  |     useLayoutEffect(() => { | 
					
						
							| 
									
										
										
										
											2025-08-25 17:17:56 +03:00
										 |  |  |         parentComponent?.registerHandler(eventName, handler); | 
					
						
							|  |  |  |         return (() => parentComponent?.removeHandler(eventName, handler)); | 
					
						
							| 
									
										
										
										
											2025-08-25 14:48:00 +03:00
										 |  |  |     }, [ eventName, handler ]); | 
					
						
							| 
									
										
										
										
											2025-08-25 11:01:12 +03:00
										 |  |  |     useDebugValue(eventName); | 
					
						
							| 
									
										
										
										
											2025-08-24 22:14:42 +03:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2025-08-08 20:08:06 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-24 22:14:42 +03:00
										 |  |  | export function useTriliumEvents<T extends EventNames>(eventNames: T[], handler: (data: EventData<T>, eventName: T) => void) { | 
					
						
							| 
									
										
										
										
											2025-08-25 17:17:56 +03:00
										 |  |  |     const parentComponent = useContext(ParentComponent); | 
					
						
							| 
									
										
										
										
											2025-08-30 18:48:34 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-27 17:36:22 +03:00
										 |  |  |     useLayoutEffect(() => { | 
					
						
							| 
									
										
										
										
											2025-08-25 14:27:32 +03:00
										 |  |  |         const handlers: ({ eventName: T, callback: (data: EventData<T>) => void })[] = []; | 
					
						
							|  |  |  |         for (const eventName of eventNames) { | 
					
						
							|  |  |  |             handlers.push({ eventName, callback: (data) => { | 
					
						
							|  |  |  |                 handler(data, eventName); | 
					
						
							|  |  |  |             }}) | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2025-08-30 18:48:34 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-24 22:14:42 +03:00
										 |  |  |         for (const { eventName, callback } of handlers) { | 
					
						
							| 
									
										
										
										
											2025-08-25 17:17:56 +03:00
										 |  |  |             parentComponent?.registerHandler(eventName, callback); | 
					
						
							| 
									
										
										
										
											2025-08-24 22:14:42 +03:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2025-08-30 18:48:34 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-25 14:27:32 +03:00
										 |  |  |         return (() => { | 
					
						
							|  |  |  |             for (const { eventName, callback } of handlers) { | 
					
						
							| 
									
										
										
										
											2025-08-25 17:17:56 +03:00
										 |  |  |                 parentComponent?.removeHandler(eventName, callback); | 
					
						
							| 
									
										
										
										
											2025-08-25 14:27:32 +03:00
										 |  |  |             } | 
					
						
							|  |  |  |         }); | 
					
						
							| 
									
										
										
										
											2025-08-25 14:48:00 +03:00
										 |  |  |     }, [ eventNames, handler ]); | 
					
						
							| 
									
										
										
										
											2025-08-25 14:27:32 +03:00
										 |  |  |     useDebugValue(() => eventNames.join(", ")); | 
					
						
							| 
									
										
										
										
											2025-08-20 23:53:13 +03:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-24 15:48:53 +03:00
										 |  |  | export function useSpacedUpdate(callback: () => void | Promise<void>, interval = 1000) { | 
					
						
							| 
									
										
										
										
											2025-08-09 09:15:54 +03:00
										 |  |  |     const callbackRef = useRef(callback); | 
					
						
							| 
									
										
										
										
											2025-09-05 17:18:02 +03:00
										 |  |  |     const spacedUpdateRef = useRef<SpacedUpdate>(new SpacedUpdate( | 
					
						
							|  |  |  |         () => callbackRef.current(), | 
					
						
							|  |  |  |         interval | 
					
						
							|  |  |  |     )); | 
					
						
							| 
									
										
										
										
											2025-08-08 23:23:07 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-09 09:15:54 +03:00
										 |  |  |     // Update callback ref when it changes
 | 
					
						
							|  |  |  |     useEffect(() => { | 
					
						
							|  |  |  |         callbackRef.current = callback; | 
					
						
							| 
									
										
										
										
											2025-08-21 13:13:48 +03:00
										 |  |  |     }, [callback]); | 
					
						
							| 
									
										
										
										
											2025-08-09 09:15:54 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  |     // Update interval if it changes
 | 
					
						
							|  |  |  |     useEffect(() => { | 
					
						
							|  |  |  |         spacedUpdateRef.current?.setUpdateInterval(interval); | 
					
						
							|  |  |  |     }, [interval]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return spacedUpdateRef.current; | 
					
						
							| 
									
										
										
										
											2025-08-14 17:36:11 +03:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-18 09:34:16 +03:00
										 |  |  | /** | 
					
						
							|  |  |  |  * Allows a React component to read and write a Trilium option, while also watching for external changes. | 
					
						
							| 
									
										
										
										
											2025-08-30 18:48:34 +03:00
										 |  |  |  * | 
					
						
							| 
									
										
										
										
											2025-08-18 09:34:16 +03:00
										 |  |  |  * Conceptually, `useTriliumOption` works just like `useState`, but the value is also automatically updated if | 
					
						
							|  |  |  |  * the option is changed somewhere else in the client. | 
					
						
							| 
									
										
										
										
											2025-08-30 18:48:34 +03:00
										 |  |  |  * | 
					
						
							| 
									
										
										
										
											2025-08-18 09:34:16 +03:00
										 |  |  |  * @param name the name of the option to listen for. | 
					
						
							|  |  |  |  * @param needsRefresh whether to reload the frontend whenever the value is changed. | 
					
						
							|  |  |  |  * @returns an array where the first value is the current option value and the second value is the setter. | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2025-08-14 23:54:32 +03:00
										 |  |  | export function useTriliumOption(name: OptionNames, needsRefresh?: boolean): [string, (newValue: OptionValue) => Promise<void>] { | 
					
						
							| 
									
										
										
										
											2025-08-22 15:11:12 +03:00
										 |  |  |     const initialValue = options.get(name); | 
					
						
							|  |  |  |     const [ value, setValue ] = useState(initialValue); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const wrappedSetValue = useMemo(() => { | 
					
						
							|  |  |  |         return async (newValue: OptionValue) => { | 
					
						
							|  |  |  |             await options.save(name, newValue); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if (needsRefresh) { | 
					
						
							|  |  |  |                 reloadFrontendApp(`option change: ${name}`); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     }, [ name, needsRefresh ]); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-24 21:18:48 +03:00
										 |  |  |     useTriliumEvent("entitiesReloaded", useCallback(({ loadResults }) => { | 
					
						
							| 
									
										
										
										
											2025-08-22 15:11:12 +03:00
										 |  |  |         if (loadResults.getOptionNames().includes(name)) { | 
					
						
							|  |  |  |             const newValue = options.get(name); | 
					
						
							|  |  |  |             setValue(newValue); | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2025-08-27 19:13:30 +03:00
										 |  |  |      }, [ name, setValue ])); | 
					
						
							| 
									
										
										
										
											2025-08-22 15:11:12 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-25 11:01:12 +03:00
										 |  |  |     useDebugValue(name); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-22 15:11:12 +03:00
										 |  |  |     return [ | 
					
						
							|  |  |  |         value, | 
					
						
							|  |  |  |         wrappedSetValue | 
					
						
							|  |  |  |     ] | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-20 20:34:00 +03:00
										 |  |  | /** | 
					
						
							|  |  |  |  * Similar to {@link useTriliumOption}, but the value is converted to and from a boolean instead of a string. | 
					
						
							| 
									
										
										
										
											2025-08-30 18:48:34 +03:00
										 |  |  |  * | 
					
						
							| 
									
										
										
										
											2025-08-20 20:34:00 +03:00
										 |  |  |  * @param name the name of the option to listen for. | 
					
						
							|  |  |  |  * @param needsRefresh whether to reload the frontend whenever the value is changed. | 
					
						
							|  |  |  |  * @returns an array where the first value is the current option value and the second value is the setter. | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2025-08-18 09:34:16 +03:00
										 |  |  | export function useTriliumOptionBool(name: OptionNames, needsRefresh?: boolean): [boolean, (newValue: boolean) => Promise<void>] { | 
					
						
							|  |  |  |     const [ value, setValue ] = useTriliumOption(name, needsRefresh); | 
					
						
							| 
									
										
										
										
											2025-08-25 11:01:12 +03:00
										 |  |  |     useDebugValue(name); | 
					
						
							| 
									
										
										
										
											2025-08-14 18:26:22 +03:00
										 |  |  |     return [ | 
					
						
							|  |  |  |         (value === "true"), | 
					
						
							|  |  |  |         (newValue) => setValue(newValue ? "true" : "false") | 
					
						
							|  |  |  |     ] | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-20 20:34:00 +03:00
										 |  |  | /** | 
					
						
							|  |  |  |  * Similar to {@link useTriliumOption}, but the value is converted to and from a int instead of a string. | 
					
						
							| 
									
										
										
										
											2025-08-30 18:48:34 +03:00
										 |  |  |  * | 
					
						
							| 
									
										
										
										
											2025-08-20 20:34:00 +03:00
										 |  |  |  * @param name the name of the option to listen for. | 
					
						
							|  |  |  |  * @param needsRefresh whether to reload the frontend whenever the value is changed. | 
					
						
							|  |  |  |  * @returns an array where the first value is the current option value and the second value is the setter. | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2025-08-14 23:54:32 +03:00
										 |  |  | export function useTriliumOptionInt(name: OptionNames): [number, (newValue: number) => Promise<void>] { | 
					
						
							|  |  |  |     const [ value, setValue ] = useTriliumOption(name); | 
					
						
							| 
									
										
										
										
											2025-08-25 11:01:12 +03:00
										 |  |  |     useDebugValue(name); | 
					
						
							| 
									
										
										
										
											2025-08-14 23:54:32 +03:00
										 |  |  |     return [ | 
					
						
							|  |  |  |         (parseInt(value, 10)), | 
					
						
							|  |  |  |         (newValue) => setValue(newValue) | 
					
						
							|  |  |  |     ] | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-20 20:34:00 +03:00
										 |  |  | /** | 
					
						
							|  |  |  |  * Similar to {@link useTriliumOption}, but the object value is parsed to and from a JSON instead of a string. | 
					
						
							| 
									
										
										
										
											2025-08-30 18:48:34 +03:00
										 |  |  |  * | 
					
						
							| 
									
										
										
										
											2025-08-20 20:34:00 +03:00
										 |  |  |  * @param name the name of the option to listen for. | 
					
						
							|  |  |  |  * @returns an array where the first value is the current option value and the second value is the setter. | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2025-08-15 10:26:25 +03:00
										 |  |  | export function useTriliumOptionJson<T>(name: OptionNames): [ T, (newValue: T) => Promise<void> ] { | 
					
						
							|  |  |  |     const [ value, setValue ] = useTriliumOption(name); | 
					
						
							| 
									
										
										
										
											2025-08-25 11:01:12 +03:00
										 |  |  |     useDebugValue(name); | 
					
						
							| 
									
										
										
										
											2025-08-15 10:26:25 +03:00
										 |  |  |     return [ | 
					
						
							|  |  |  |         (JSON.parse(value) as T), | 
					
						
							|  |  |  |         (newValue => setValue(JSON.stringify(newValue))) | 
					
						
							|  |  |  |     ]; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-20 20:34:00 +03:00
										 |  |  | /** | 
					
						
							| 
									
										
										
										
											2025-08-30 18:48:34 +03:00
										 |  |  |  * Similar to {@link useTriliumOption}, but operates with multiple options at once. | 
					
						
							|  |  |  |  * | 
					
						
							| 
									
										
										
										
											2025-08-20 20:34:00 +03:00
										 |  |  |  * @param names the name of the option to listen for. | 
					
						
							|  |  |  |  * @returns an array where the first value is a map where the keys are the option names and the values, and the second value is the setter which takes in the same type of map and saves them all at once. | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2025-08-15 11:21:19 +03:00
										 |  |  | export function useTriliumOptions<T extends OptionNames>(...names: T[]) { | 
					
						
							|  |  |  |     const values: Record<string, string> = {}; | 
					
						
							|  |  |  |     for (const name of names) { | 
					
						
							|  |  |  |         values[name] = options.get(name); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-25 11:01:12 +03:00
										 |  |  |     useDebugValue(() => names.join(", ")); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-15 11:21:19 +03:00
										 |  |  |     return [ | 
					
						
							|  |  |  |         values as Record<T, string>, | 
					
						
							| 
									
										
										
										
											2025-08-18 19:47:40 +03:00
										 |  |  |         options.saveMany | 
					
						
							| 
									
										
										
										
											2025-08-15 11:21:19 +03:00
										 |  |  |     ] as const; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-14 17:53:24 +03:00
										 |  |  | /** | 
					
						
							|  |  |  |  * Generates a unique name via a random alphanumeric string of a fixed length. | 
					
						
							| 
									
										
										
										
											2025-08-30 18:48:34 +03:00
										 |  |  |  * | 
					
						
							| 
									
										
										
										
											2025-08-14 17:53:24 +03:00
										 |  |  |  * <p> | 
					
						
							|  |  |  |  * Generally used to assign names to inputs that are unique, especially useful for widgets inside tabs. | 
					
						
							| 
									
										
										
										
											2025-08-30 18:48:34 +03:00
										 |  |  |  * | 
					
						
							| 
									
										
										
										
											2025-08-14 17:53:24 +03:00
										 |  |  |  * @param prefix a prefix to add to the unique name. | 
					
						
							|  |  |  |  * @returns a name with the given prefix and a random alpanumeric string appended to it. | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2025-08-19 23:34:25 +03:00
										 |  |  | export function useUniqueName(prefix?: string) { | 
					
						
							|  |  |  |     return useMemo(() => (prefix ? prefix + "-" : "") + utils.randomString(10), [ prefix ]); | 
					
						
							| 
									
										
										
										
											2025-08-20 23:53:13 +03:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export function useNoteContext() { | 
					
						
							|  |  |  |     const [ noteContext, setNoteContext ] = useState<NoteContext>(); | 
					
						
							|  |  |  |     const [ notePath, setNotePath ] = useState<string | null | undefined>(); | 
					
						
							| 
									
										
										
										
											2025-08-30 18:48:34 +03:00
										 |  |  |     const [ note, setNote ] = useState<FNote | null | undefined>(); | 
					
						
							| 
									
										
										
										
											2025-08-28 20:04:47 +03:00
										 |  |  |     const [ refreshCounter, setRefreshCounter ] = useState(0); | 
					
						
							| 
									
										
										
										
											2025-08-21 12:13:30 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  |     useEffect(() => { | 
					
						
							|  |  |  |         setNote(noteContext?.note); | 
					
						
							|  |  |  |     }, [ notePath ]); | 
					
						
							| 
									
										
										
										
											2025-08-20 23:53:13 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-27 13:06:57 +03:00
										 |  |  |     useTriliumEvents([ "setNoteContext", "activeContextChanged", "noteSwitchedAndActivated", "noteSwitched" ], ({ noteContext }) => { | 
					
						
							| 
									
										
										
										
											2025-08-25 13:51:43 +03:00
										 |  |  |         setNoteContext(noteContext); | 
					
						
							| 
									
										
										
										
											2025-08-30 18:48:34 +03:00
										 |  |  |         setNotePath(noteContext.notePath); | 
					
						
							| 
									
										
										
										
											2025-08-20 23:53:13 +03:00
										 |  |  |     }); | 
					
						
							| 
									
										
										
										
											2025-08-24 21:18:48 +03:00
										 |  |  |     useTriliumEvent("frocaReloaded", () => { | 
					
						
							| 
									
										
										
										
											2025-08-21 12:13:30 +03:00
										 |  |  |         setNote(noteContext?.note); | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2025-08-28 20:04:47 +03:00
										 |  |  |     useTriliumEvent("noteTypeMimeChanged", ({ noteId }) => { | 
					
						
							|  |  |  |         if (noteId === note?.noteId) { | 
					
						
							|  |  |  |             setRefreshCounter(refreshCounter + 1); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2025-08-25 11:01:12 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-25 18:00:10 +03:00
										 |  |  |     const parentComponent = useContext(ParentComponent) as ReactWrappedWidget; | 
					
						
							| 
									
										
										
										
											2025-08-25 11:01:12 +03:00
										 |  |  |     useDebugValue(() => `notePath=${notePath}, ntxId=${noteContext?.ntxId}`); | 
					
						
							| 
									
										
										
										
											2025-08-20 23:53:13 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  |     return { | 
					
						
							| 
									
										
										
										
											2025-08-21 12:13:30 +03:00
										 |  |  |         note: note, | 
					
						
							| 
									
										
										
										
											2025-08-20 23:53:13 +03:00
										 |  |  |         noteId: noteContext?.note?.noteId, | 
					
						
							|  |  |  |         notePath: noteContext?.notePath, | 
					
						
							|  |  |  |         hoistedNoteId: noteContext?.hoistedNoteId, | 
					
						
							| 
									
										
										
										
											2025-08-21 10:05:53 +03:00
										 |  |  |         ntxId: noteContext?.ntxId, | 
					
						
							| 
									
										
										
										
											2025-08-21 11:08:33 +03:00
										 |  |  |         viewScope: noteContext?.viewScope, | 
					
						
							|  |  |  |         componentId: parentComponent.componentId, | 
					
						
							| 
									
										
										
										
											2025-08-21 13:00:05 +03:00
										 |  |  |         noteContext, | 
					
						
							|  |  |  |         parentComponent | 
					
						
							| 
									
										
										
										
											2025-08-20 23:53:13 +03:00
										 |  |  |     }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-21 10:44:58 +03:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-21 10:53:59 +03:00
										 |  |  | /** | 
					
						
							|  |  |  |  * Allows a React component to listen to obtain a property of a {@link FNote} while also automatically watching for changes, either via the user changing to a different note or the property being changed externally. | 
					
						
							| 
									
										
										
										
											2025-08-30 18:48:34 +03:00
										 |  |  |  * | 
					
						
							| 
									
										
										
										
											2025-08-21 10:53:59 +03:00
										 |  |  |  * @param note the {@link FNote} whose property to obtain. | 
					
						
							|  |  |  |  * @param property a property of a {@link FNote} to obtain the value from (e.g. `title`, `isProtected`). | 
					
						
							|  |  |  |  * @param componentId optionally, constricts the refresh of the value if an update occurs externally via the component ID of a legacy widget. This can be used to avoid external data replacing fresher, user-inputted data. | 
					
						
							|  |  |  |  * @returns the value of the requested property. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | export function useNoteProperty<T extends keyof FNote>(note: FNote | null | undefined, property: T, componentId?: string) { | 
					
						
							| 
									
										
										
										
											2025-08-25 18:00:10 +03:00
										 |  |  |     const [, setValue ] = useState<FNote[T] | undefined>(note?.[property]); | 
					
						
							|  |  |  |     const refreshValue = () => setValue(note?.[property]); | 
					
						
							| 
									
										
										
										
											2025-08-21 10:53:59 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  |     // Watch for note changes.
 | 
					
						
							| 
									
										
										
										
											2025-08-25 18:00:10 +03:00
										 |  |  |     useEffect(() => refreshValue(), [ note, note?.[property] ]); | 
					
						
							| 
									
										
										
										
											2025-08-21 10:53:59 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  |     // Watch for external changes.
 | 
					
						
							| 
									
										
										
										
											2025-08-24 21:18:48 +03:00
										 |  |  |     useTriliumEvent("entitiesReloaded", ({ loadResults }) => { | 
					
						
							| 
									
										
										
										
											2025-08-25 18:00:10 +03:00
										 |  |  |         if (loadResults.isNoteReloaded(note?.noteId, componentId)) { | 
					
						
							| 
									
										
										
										
											2025-08-21 12:13:30 +03:00
										 |  |  |             refreshValue(); | 
					
						
							| 
									
										
										
										
											2025-08-21 10:44:58 +03:00
										 |  |  |         } | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2025-08-21 11:08:33 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-25 11:01:12 +03:00
										 |  |  |     useDebugValue(property); | 
					
						
							| 
									
										
										
										
											2025-08-25 18:00:10 +03:00
										 |  |  |     return note?.[property]; | 
					
						
							| 
									
										
										
										
											2025-08-21 12:55:33 +03:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2025-08-21 15:50:14 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-24 17:20:40 +03:00
										 |  |  | export function useNoteRelation(note: FNote | undefined | null, relationName: string): [string | null | undefined, (newValue: string) => void] { | 
					
						
							|  |  |  |     const [ relationValue, setRelationValue ] = useState<string | null | undefined>(note?.getRelationValue(relationName)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     useEffect(() => setRelationValue(note?.getRelationValue(relationName) ?? null), [ note ]); | 
					
						
							| 
									
										
										
										
											2025-08-24 21:18:48 +03:00
										 |  |  |     useTriliumEvent("entitiesReloaded", ({ loadResults }) => { | 
					
						
							| 
									
										
										
										
											2025-08-24 17:20:40 +03:00
										 |  |  |         for (const attr of loadResults.getAttributeRows()) { | 
					
						
							|  |  |  |             if (attr.type === "relation" && attr.name === relationName && attributes.isAffecting(attr, note)) { | 
					
						
							|  |  |  |                 setRelationValue(attr.value ?? null); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const setter = useCallback((value: string | undefined) => { | 
					
						
							|  |  |  |         if (note) { | 
					
						
							|  |  |  |             attributes.setAttribute(note, "relation", relationName, value) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     }, [note]); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-25 11:01:12 +03:00
										 |  |  |     useDebugValue(relationName); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-24 17:20:40 +03:00
										 |  |  |     return [ | 
					
						
							|  |  |  |         relationValue, | 
					
						
							|  |  |  |         setter | 
					
						
							|  |  |  |     ] as const; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-24 18:02:18 +03:00
										 |  |  | /** | 
					
						
							|  |  |  |  * Allows a React component to read or write a note's label while also reacting to changes in value. | 
					
						
							| 
									
										
										
										
											2025-08-30 18:48:34 +03:00
										 |  |  |  * | 
					
						
							| 
									
										
										
										
											2025-08-24 18:02:18 +03:00
										 |  |  |  * @param note the note whose label to read/write. | 
					
						
							|  |  |  |  * @param labelName the name of the label to read/write. | 
					
						
							|  |  |  |  * @returns an array where the first element is the getter and the second element is the setter. The setter has a special behaviour for convenience: if the value is undefined, the label is created without a value (e.g. a tag), if the value is null then the label is removed. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | export function useNoteLabel(note: FNote | undefined | null, labelName: string): [string | null | undefined, (newValue: string | null | undefined) => void] { | 
					
						
							| 
									
										
										
										
											2025-08-21 22:19:26 +03:00
										 |  |  |     const [ labelValue, setLabelValue ] = useState<string | null | undefined>(note?.getLabelValue(labelName)); | 
					
						
							| 
									
										
										
										
											2025-08-21 15:50:14 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-22 12:14:53 +03:00
										 |  |  |     useEffect(() => setLabelValue(note?.getLabelValue(labelName) ?? null), [ note ]); | 
					
						
							| 
									
										
										
										
											2025-08-24 21:18:48 +03:00
										 |  |  |     useTriliumEvent("entitiesReloaded", ({ loadResults }) => { | 
					
						
							| 
									
										
										
										
											2025-08-21 15:50:14 +03:00
										 |  |  |         for (const attr of loadResults.getAttributeRows()) { | 
					
						
							|  |  |  |             if (attr.type === "label" && attr.name === labelName && attributes.isAffecting(attr, note)) { | 
					
						
							| 
									
										
										
										
											2025-08-30 19:33:32 +03:00
										 |  |  |                 if (!attr.isDeleted) { | 
					
						
							|  |  |  |                     setLabelValue(attr.value); | 
					
						
							|  |  |  |                 } else { | 
					
						
							|  |  |  |                     setLabelValue(null); | 
					
						
							|  |  |  |                 } | 
					
						
							| 
									
										
										
										
											2025-08-21 15:50:14 +03:00
										 |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-24 18:02:18 +03:00
										 |  |  |     const setter = useCallback((value: string | null | undefined) => { | 
					
						
							| 
									
										
										
										
											2025-08-21 15:50:14 +03:00
										 |  |  |         if (note) { | 
					
						
							| 
									
										
										
										
											2025-08-24 18:02:18 +03:00
										 |  |  |             if (value || value === undefined) { | 
					
						
							|  |  |  |                 attributes.setLabel(note.noteId, labelName, value) | 
					
						
							|  |  |  |             } else if (value === null) { | 
					
						
							|  |  |  |                 attributes.removeOwnedLabelByName(note, labelName); | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2025-08-21 15:50:14 +03:00
										 |  |  |         } | 
					
						
							|  |  |  |     }, [note]); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-25 11:01:12 +03:00
										 |  |  |     useDebugValue(labelName); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-21 15:50:14 +03:00
										 |  |  |     return [ | 
					
						
							|  |  |  |         labelValue, | 
					
						
							|  |  |  |         setter | 
					
						
							|  |  |  |     ] as const; | 
					
						
							| 
									
										
										
										
											2025-08-21 22:19:26 +03:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-11 19:14:54 +03:00
										 |  |  | export function useNoteLabelWithDefault(note: FNote | undefined | null, labelName: string, defaultValue: string): [string, (newValue: string | null | undefined) => void] { | 
					
						
							|  |  |  |     const [ labelValue, setLabelValue ] = useNoteLabel(note, labelName); | 
					
						
							|  |  |  |     return [ labelValue ?? defaultValue, setLabelValue]; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-22 12:14:53 +03:00
										 |  |  | export function useNoteLabelBoolean(note: FNote | undefined | null, labelName: string): [ boolean, (newValue: boolean) => void] { | 
					
						
							|  |  |  |     const [ labelValue, setLabelValue ] = useState<boolean>(!!note?.hasLabel(labelName)); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     useEffect(() => setLabelValue(!!note?.hasLabel(labelName)), [ note ]); | 
					
						
							| 
									
										
										
										
											2025-08-21 22:19:26 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-24 21:18:48 +03:00
										 |  |  |     useTriliumEvent("entitiesReloaded", ({ loadResults }) => { | 
					
						
							| 
									
										
										
										
											2025-08-21 22:19:26 +03:00
										 |  |  |         for (const attr of loadResults.getAttributeRows()) { | 
					
						
							|  |  |  |             if (attr.type === "label" && attr.name === labelName && attributes.isAffecting(attr, note)) { | 
					
						
							|  |  |  |                 setLabelValue(!attr.isDeleted); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const setter = useCallback((value: boolean) => { | 
					
						
							|  |  |  |         if (note) { | 
					
						
							|  |  |  |             if (value) { | 
					
						
							|  |  |  |                 attributes.setLabel(note.noteId, labelName, ""); | 
					
						
							|  |  |  |             } else { | 
					
						
							|  |  |  |                 attributes.removeOwnedLabelByName(note, labelName); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     }, [note]); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-25 11:01:12 +03:00
										 |  |  |     useDebugValue(labelName); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-21 22:19:26 +03:00
										 |  |  |     return [ labelValue, setter ] as const; | 
					
						
							| 
									
										
										
										
											2025-08-22 21:04:04 +03:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-06 18:48:58 +03:00
										 |  |  | export function useNoteLabelInt(note: FNote | undefined | null, labelName: string): [ number | undefined, (newValue: number) => void] { | 
					
						
							|  |  |  |     const [ value, setValue ] = useNoteLabel(note, labelName); | 
					
						
							|  |  |  |     useDebugValue(labelName); | 
					
						
							|  |  |  |     return [ | 
					
						
							|  |  |  |         (value ? parseInt(value, 10) : undefined), | 
					
						
							|  |  |  |         (newValue) => setValue(String(newValue)) | 
					
						
							|  |  |  |     ] | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-22 21:04:04 +03:00
										 |  |  | export function useNoteBlob(note: FNote | null | undefined): [ FBlob | null | undefined ] { | 
					
						
							|  |  |  |     const [ blob, setBlob ] = useState<FBlob | null>(); | 
					
						
							| 
									
										
										
										
											2025-08-30 18:48:34 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-22 21:04:04 +03:00
										 |  |  |     function refresh() { | 
					
						
							| 
									
										
										
										
											2025-08-30 18:48:34 +03:00
										 |  |  |         note?.getBlob().then(setBlob); | 
					
						
							| 
									
										
										
										
											2025-08-22 21:04:04 +03:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     useEffect(refresh, [ note?.noteId ]); | 
					
						
							| 
									
										
										
										
											2025-08-24 21:18:48 +03:00
										 |  |  |     useTriliumEvent("entitiesReloaded", ({ loadResults }) => { | 
					
						
							| 
									
										
										
										
											2025-08-22 21:04:04 +03:00
										 |  |  |         if (note && loadResults.hasRevisionForNote(note.noteId)) { | 
					
						
							|  |  |  |             refresh(); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-25 18:00:10 +03:00
										 |  |  |     useDebugValue(note?.noteId); | 
					
						
							| 
									
										
										
										
											2025-08-25 11:01:12 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-22 21:04:04 +03:00
										 |  |  |     return [ blob ] as const; | 
					
						
							| 
									
										
										
										
											2025-08-22 23:12:14 +03:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-23 13:13:01 +03:00
										 |  |  | export function useLegacyWidget<T extends BasicWidget>(widgetFactory: () => T, { noteContext, containerClassName, containerStyle }: { | 
					
						
							| 
									
										
										
										
											2025-08-22 23:12:14 +03:00
										 |  |  |     noteContext?: NoteContext; | 
					
						
							| 
									
										
										
										
											2025-08-22 23:45:31 +03:00
										 |  |  |     containerClassName?: string; | 
					
						
							| 
									
										
										
										
											2025-08-23 13:13:01 +03:00
										 |  |  |     containerStyle?: CSSProperties; | 
					
						
							| 
									
										
										
										
											2025-08-23 10:47:46 +03:00
										 |  |  | } = {}): [VNode, T] { | 
					
						
							| 
									
										
										
										
											2025-08-22 23:24:00 +03:00
										 |  |  |     const ref = useRef<HTMLDivElement>(null); | 
					
						
							| 
									
										
										
										
											2025-08-22 23:12:14 +03:00
										 |  |  |     const parentComponent = useContext(ParentComponent); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-22 23:33:02 +03:00
										 |  |  |     // Render the widget once.
 | 
					
						
							|  |  |  |     const [ widget, renderedWidget ] = useMemo(() => { | 
					
						
							|  |  |  |         const widget = widgetFactory(); | 
					
						
							| 
									
										
										
										
											2025-08-22 23:24:00 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-22 23:33:02 +03:00
										 |  |  |         if (parentComponent) { | 
					
						
							|  |  |  |             parentComponent.child(widget); | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2025-08-22 23:24:00 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  |         if (noteContext && widget instanceof NoteContextAwareWidget) { | 
					
						
							|  |  |  |             widget.setNoteContextEvent({ noteContext }); | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2025-08-30 18:48:34 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-22 23:33:02 +03:00
										 |  |  |         const renderedWidget = widget.render(); | 
					
						
							|  |  |  |         return [ widget, renderedWidget ]; | 
					
						
							| 
									
										
										
										
											2025-08-22 23:45:31 +03:00
										 |  |  |     }, []); | 
					
						
							| 
									
										
										
										
											2025-08-22 23:24:00 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-22 23:33:02 +03:00
										 |  |  |     // Attach the widget to the parent.
 | 
					
						
							|  |  |  |     useEffect(() => { | 
					
						
							|  |  |  |         if (ref.current) { | 
					
						
							|  |  |  |             ref.current.innerHTML = ""; | 
					
						
							|  |  |  |             renderedWidget.appendTo(ref.current); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     }, [ renderedWidget ]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Inject the note context.
 | 
					
						
							|  |  |  |     useEffect(() => { | 
					
						
							|  |  |  |         if (noteContext && widget instanceof NoteContextAwareWidget) { | 
					
						
							|  |  |  |             widget.activeContextChangedEvent({ noteContext }); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     }, [ noteContext ]); | 
					
						
							| 
									
										
										
										
											2025-08-22 23:12:14 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-25 11:01:12 +03:00
										 |  |  |     useDebugValue(widget); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-23 13:13:01 +03:00
										 |  |  |     return [ <div className={containerClassName} style={containerStyle} ref={ref} />, widget ] | 
					
						
							| 
									
										
										
										
											2025-08-23 11:00:25 +03:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-23 11:19:35 +03:00
										 |  |  | /** | 
					
						
							|  |  |  |  * Attaches a {@link ResizeObserver} to the given ref and reads the bounding client rect whenever it changes. | 
					
						
							| 
									
										
										
										
											2025-08-30 18:48:34 +03:00
										 |  |  |  * | 
					
						
							| 
									
										
										
										
											2025-08-23 11:19:35 +03:00
										 |  |  |  * @param ref a ref to a {@link HTMLElement} to determine the size and observe the changes in size. | 
					
						
							|  |  |  |  * @returns the size of the element, reacting to changes. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | export function useElementSize(ref: RefObject<HTMLElement>) { | 
					
						
							|  |  |  |     const [ size, setSize ] = useState<DOMRect | undefined>(ref.current?.getBoundingClientRect()); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-23 11:00:25 +03:00
										 |  |  |     useEffect(() => { | 
					
						
							|  |  |  |         if (!ref.current) { | 
					
						
							|  |  |  |             return; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-23 11:19:35 +03:00
										 |  |  |         function onResize() { | 
					
						
							|  |  |  |             setSize(ref.current?.getBoundingClientRect()); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-23 11:00:25 +03:00
										 |  |  |         const element = ref.current; | 
					
						
							| 
									
										
										
										
											2025-08-23 11:19:35 +03:00
										 |  |  |         const resizeObserver = new ResizeObserver(onResize); | 
					
						
							| 
									
										
										
										
											2025-08-23 11:00:25 +03:00
										 |  |  |         resizeObserver.observe(element); | 
					
						
							|  |  |  |         return () => { | 
					
						
							|  |  |  |             resizeObserver.unobserve(element); | 
					
						
							|  |  |  |             resizeObserver.disconnect(); | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2025-08-23 11:19:35 +03:00
										 |  |  |     }, [ ref ]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return size; | 
					
						
							| 
									
										
										
										
											2025-08-23 11:12:14 +03:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-23 11:19:35 +03:00
										 |  |  | /** | 
					
						
							|  |  |  |  * Obtains the inner width and height of the window, as well as reacts to changes in size. | 
					
						
							| 
									
										
										
										
											2025-08-30 18:48:34 +03:00
										 |  |  |  * | 
					
						
							| 
									
										
										
										
											2025-08-23 11:19:35 +03:00
										 |  |  |  * @returns the width and height of the window. | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2025-08-23 11:12:14 +03:00
										 |  |  | export function useWindowSize() { | 
					
						
							|  |  |  |     const [ size, setSize ] = useState<{ windowWidth: number, windowHeight: number }>({ | 
					
						
							|  |  |  |         windowWidth: window.innerWidth, | 
					
						
							|  |  |  |         windowHeight: window.innerHeight | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2025-08-30 18:48:34 +03:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-23 11:12:14 +03:00
										 |  |  |     useEffect(() => { | 
					
						
							|  |  |  |         function onResize() { | 
					
						
							|  |  |  |             setSize({ | 
					
						
							|  |  |  |                 windowWidth: window.innerWidth, | 
					
						
							|  |  |  |                 windowHeight: window.innerHeight | 
					
						
							|  |  |  |             }); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         window.addEventListener("resize", onResize); | 
					
						
							|  |  |  |         return () => window.removeEventListener("resize", onResize); | 
					
						
							| 
									
										
										
										
											2025-08-24 20:31:39 +03:00
										 |  |  |     }, []); | 
					
						
							| 
									
										
										
										
											2025-08-23 11:12:14 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  |     return size; | 
					
						
							| 
									
										
										
										
											2025-08-23 12:31:54 +03:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export function useTooltip(elRef: RefObject<HTMLElement>, config: Partial<Tooltip.Options>) { | 
					
						
							|  |  |  |     useEffect(() => { | 
					
						
							|  |  |  |         if (!elRef?.current) return; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const $el = $(elRef.current); | 
					
						
							| 
									
										
										
										
											2025-08-24 16:41:44 +03:00
										 |  |  |         $el.tooltip("dispose"); | 
					
						
							|  |  |  |         $el.tooltip(config); | 
					
						
							| 
									
										
										
										
											2025-08-23 12:31:54 +03:00
										 |  |  |     }, [ elRef, config ]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const showTooltip = useCallback(() => { | 
					
						
							|  |  |  |         if (!elRef?.current) return; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const $el = $(elRef.current); | 
					
						
							|  |  |  |         $el.tooltip("show"); | 
					
						
							| 
									
										
										
										
											2025-08-24 16:41:44 +03:00
										 |  |  |     }, [ elRef, config ]); | 
					
						
							| 
									
										
										
										
											2025-08-23 12:31:54 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  |     const hideTooltip = useCallback(() => { | 
					
						
							|  |  |  |         if (!elRef?.current) return; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const $el = $(elRef.current); | 
					
						
							|  |  |  |         $el.tooltip("hide"); | 
					
						
							|  |  |  |     }, [ elRef ]); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-25 11:01:12 +03:00
										 |  |  |     useDebugValue(config.title); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-23 12:31:54 +03:00
										 |  |  |     return { showTooltip, hideTooltip }; | 
					
						
							| 
									
										
										
										
											2025-08-23 20:44:03 +03:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-27 11:59:07 +03:00
										 |  |  | /** | 
					
						
							|  |  |  |  * Similar to {@link useTooltip}, but doesn't expose methods to imperatively hide or show the tooltip. | 
					
						
							| 
									
										
										
										
											2025-08-30 18:48:34 +03:00
										 |  |  |  * | 
					
						
							| 
									
										
										
										
											2025-08-27 11:59:07 +03:00
										 |  |  |  * @param elRef the element to bind the tooltip to. | 
					
						
							|  |  |  |  * @param config optionally, the tooltip configuration. | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2025-08-29 00:47:47 +03:00
										 |  |  | export function useStaticTooltip(elRef: RefObject<Element>, config?: Partial<Tooltip.Options>) { | 
					
						
							| 
									
										
										
										
											2025-08-27 11:59:07 +03:00
										 |  |  |     useEffect(() => { | 
					
						
							| 
									
										
										
										
											2025-08-29 11:54:16 +03:00
										 |  |  |         const hasTooltip = config?.title || elRef.current?.getAttribute("title"); | 
					
						
							|  |  |  |         if (!elRef?.current || !hasTooltip) return; | 
					
						
							| 
									
										
										
										
											2025-08-27 11:59:07 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  |         const $el = $(elRef.current); | 
					
						
							|  |  |  |         $el.tooltip(config); | 
					
						
							|  |  |  |         return () => { | 
					
						
							|  |  |  |             $el.tooltip("dispose"); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     }, [ elRef, config ]); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-29 19:29:15 +03:00
										 |  |  | export function useStaticTooltipWithKeyboardShortcut(elRef: RefObject<Element>, title: string, actionName: KeyboardActionNames | undefined) { | 
					
						
							| 
									
										
										
										
											2025-08-29 00:47:47 +03:00
										 |  |  |     const [ keyboardShortcut, setKeyboardShortcut ] = useState<string[]>(); | 
					
						
							|  |  |  |     useStaticTooltip(elRef, { | 
					
						
							|  |  |  |         title: keyboardShortcut?.length ? `${title} (${keyboardShortcut?.join(",")})` : title | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     useEffect(() => { | 
					
						
							|  |  |  |         if (actionName) { | 
					
						
							|  |  |  |             keyboard_actions.getAction(actionName).then(action => setKeyboardShortcut(action?.effectiveShortcuts)); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     }, [actionName]); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-25 17:17:56 +03:00
										 |  |  | // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
 | 
					
						
							| 
									
										
										
										
											2025-08-25 18:00:10 +03:00
										 |  |  | export function useLegacyImperativeHandlers(handlers: Record<string, Function>) { | 
					
						
							| 
									
										
										
										
											2025-08-23 20:44:03 +03:00
										 |  |  |     const parentComponent = useContext(ParentComponent); | 
					
						
							| 
									
										
										
										
											2025-08-25 18:00:10 +03:00
										 |  |  |     useEffect(() => { | 
					
						
							| 
									
										
										
										
											2025-08-25 17:17:56 +03:00
										 |  |  |         Object.assign(parentComponent as never, handlers); | 
					
						
							| 
									
										
										
										
											2025-08-25 18:00:10 +03:00
										 |  |  |     }, [ handlers ]); | 
					
						
							| 
									
										
										
										
											2025-08-25 18:41:48 +03:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export function useSyncedRef<T>(externalRef?: RefObject<T>, initialValue: T | null = null): RefObject<T> { | 
					
						
							|  |  |  |     const ref = useRef<T>(initialValue); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     useEffect(() => { | 
					
						
							|  |  |  |         if (externalRef) { | 
					
						
							|  |  |  |             externalRef.current = ref.current; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     }, [ ref, externalRef ]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return ref; | 
					
						
							| 
									
										
										
										
											2025-08-30 18:48:34 +03:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-30 19:03:18 +03:00
										 |  |  | export function useImperativeSearchHighlighlighting(highlightedTokens: string[] | null | undefined) { | 
					
						
							| 
									
										
										
										
											2025-08-30 18:48:34 +03:00
										 |  |  |     const mark = useRef<Mark>(); | 
					
						
							|  |  |  |     const highlightRegex = useMemo(() => { | 
					
						
							|  |  |  |         if (!highlightedTokens?.length) return null; | 
					
						
							|  |  |  |         const regex = highlightedTokens.map((token) => escapeRegExp(token)).join("|"); | 
					
						
							|  |  |  |         return new RegExp(regex, "gi") | 
					
						
							|  |  |  |     }, [ highlightedTokens ]); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-30 19:07:06 +03:00
										 |  |  |     return (el: HTMLElement | null | undefined) => { | 
					
						
							| 
									
										
										
										
											2025-08-30 19:03:18 +03:00
										 |  |  |         if (!el || !highlightRegex) return; | 
					
						
							| 
									
										
										
										
											2025-08-30 18:48:34 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  |         if (!mark.current) { | 
					
						
							| 
									
										
										
										
											2025-08-30 19:03:18 +03:00
										 |  |  |             mark.current = new Mark(el); | 
					
						
							| 
									
										
										
										
											2025-08-30 18:48:34 +03:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-08-30 19:03:18 +03:00
										 |  |  |         mark.current.unmark(); | 
					
						
							| 
									
										
										
										
											2025-08-30 18:48:34 +03:00
										 |  |  |         mark.current.markRegExp(highlightRegex, { | 
					
						
							|  |  |  |             element: "span", | 
					
						
							|  |  |  |             className: "ck-find-result" | 
					
						
							|  |  |  |         }); | 
					
						
							| 
									
										
										
										
											2025-08-30 19:03:18 +03:00
										 |  |  |     }; | 
					
						
							| 
									
										
										
										
											2025-08-30 18:48:34 +03:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2025-09-04 23:35:18 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | export function useNoteTreeDrag(containerRef: MutableRef<HTMLElement | null | undefined>, callback: (data: DragData[], e: DragEvent) => void) { | 
					
						
							|  |  |  |     useEffect(() => { | 
					
						
							|  |  |  |         const container = containerRef.current; | 
					
						
							|  |  |  |         if (!container) return; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         function onDragOver(e: DragEvent) { | 
					
						
							|  |  |  |             // Allow drag.
 | 
					
						
							|  |  |  |             e.preventDefault(); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         function onDrop(e: DragEvent) { | 
					
						
							|  |  |  |             const data = e.dataTransfer?.getData('text'); | 
					
						
							|  |  |  |             if (!data) { | 
					
						
							|  |  |  |                 return; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             const parsedData = JSON.parse(data) as DragData[]; | 
					
						
							|  |  |  |             if (!parsedData.length) { | 
					
						
							|  |  |  |                 return; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             callback(parsedData, e); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         container.addEventListener("dragover", onDragOver); | 
					
						
							|  |  |  |         container.addEventListener("drop", onDrop); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return () => { | 
					
						
							|  |  |  |             container.removeEventListener("dragover", onDragOver); | 
					
						
							|  |  |  |             container.removeEventListener("drop", onDrop); | 
					
						
							|  |  |  |         }; | 
					
						
							|  |  |  |     }, [ containerRef, callback ]); | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2025-09-05 11:54:58 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | export function useTouchBar( | 
					
						
							|  |  |  |     factory: (context: CommandListenerData<"buildTouchBar"> & { parentComponent: Component | null }) => void, | 
					
						
							|  |  |  |     inputs: Inputs | 
					
						
							|  |  |  | ) { | 
					
						
							|  |  |  |     const parentComponent = useContext(ParentComponent); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     useLegacyImperativeHandlers({ | 
					
						
							|  |  |  |         buildTouchBarCommand(context: CommandListenerData<"buildTouchBar">) { | 
					
						
							|  |  |  |             return factory({ | 
					
						
							|  |  |  |                 ...context, | 
					
						
							|  |  |  |                 parentComponent | 
					
						
							|  |  |  |             }); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     useEffect(() => { | 
					
						
							|  |  |  |         parentComponent?.triggerCommand("refreshTouchBar"); | 
					
						
							|  |  |  |     }, inputs); | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2025-09-05 17:33:46 +03:00
										 |  |  | 
 | 
					
						
							|  |  |  | export function useResizeObserver(ref: RefObject<HTMLElement>, callback: () => void) { | 
					
						
							|  |  |  |     const resizeObserver = useRef<ResizeObserver>(null); | 
					
						
							|  |  |  |     useEffect(() => { | 
					
						
							|  |  |  |         resizeObserver.current?.disconnect(); | 
					
						
							|  |  |  |         const observer = new ResizeObserver(callback); | 
					
						
							|  |  |  |         resizeObserver.current = observer; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (ref.current) { | 
					
						
							|  |  |  |             observer.observe(ref.current); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return () => observer.disconnect(); | 
					
						
							|  |  |  |     }, [ callback, ref ]); | 
					
						
							|  |  |  | } |