chore(react/type_widgets): fix type errors

This commit is contained in:
Elian Doran 2025-10-05 19:55:25 +03:00
parent 6ffe8a2eb5
commit 7930745a01
No known key found for this signature in database
14 changed files with 44 additions and 126 deletions

View File

@ -13,7 +13,6 @@ import MainTreeExecutors from "./main_tree_executors.js";
import toast from "../services/toast.js"; import toast from "../services/toast.js";
import ShortcutComponent from "./shortcut_component.js"; import ShortcutComponent from "./shortcut_component.js";
import { t, initLocale } from "../services/i18n.js"; import { t, initLocale } from "../services/i18n.js";
import type NoteDetailWidget from "../widgets/note_detail.js";
import type { ResolveOptions } from "../widgets/dialogs/delete_notes.js"; import type { ResolveOptions } from "../widgets/dialogs/delete_notes.js";
import type { PromptDialogOptions } from "../widgets/dialogs/prompt.js"; import type { PromptDialogOptions } from "../widgets/dialogs/prompt.js";
import type { ConfirmWithMessageOptions, ConfirmWithTitleOptions } from "../widgets/dialogs/confirm.js"; import type { ConfirmWithMessageOptions, ConfirmWithTitleOptions } from "../widgets/dialogs/confirm.js";
@ -21,8 +20,6 @@ import type LoadResults from "../services/load_results.js";
import type { Attribute } from "../services/attribute_parser.js"; import type { Attribute } from "../services/attribute_parser.js";
import type NoteTreeWidget from "../widgets/note_tree.js"; import type NoteTreeWidget from "../widgets/note_tree.js";
import type { default as NoteContext, GetTextEditorCallback } from "./note_context.js"; import type { default as NoteContext, GetTextEditorCallback } from "./note_context.js";
import type TypeWidget from "../widgets/type_widgets_old/type_widget.js";
import type EditableTextTypeWidget from "../widgets/type_widgets_old/editable_text.js";
import type { NativeImage, TouchBar } from "electron"; import type { NativeImage, TouchBar } from "electron";
import TouchBarComponent from "./touch_bar.js"; import TouchBarComponent from "./touch_bar.js";
import type { CKTextEditor } from "@triliumnext/ckeditor5"; import type { CKTextEditor } from "@triliumnext/ckeditor5";
@ -36,6 +33,7 @@ import { SqlExecuteResults } from "@triliumnext/commons";
import { AddLinkOpts } from "../widgets/dialogs/add_link.jsx"; import { AddLinkOpts } from "../widgets/dialogs/add_link.jsx";
import { IncludeNoteOpts } from "../widgets/dialogs/include_note.jsx"; import { IncludeNoteOpts } from "../widgets/dialogs/include_note.jsx";
import { ReactWrappedWidget } from "../widgets/basic_widget.js"; import { ReactWrappedWidget } from "../widgets/basic_widget.js";
import { TypeWidget } from "../widgets/note_types.jsx";
interface Layout { interface Layout {
getRootWidget: (appContext: AppContext) => RootContainer; getRootWidget: (appContext: AppContext) => RootContainer;
@ -214,7 +212,7 @@ export type CommandMappings = {
* Generally should not be invoked manually, as it is used by {@link NoteContext.getContentElement}. * Generally should not be invoked manually, as it is used by {@link NoteContext.getContentElement}.
*/ */
executeWithContentElement: CommandData & ExecuteCommandData<JQuery<HTMLElement>>; executeWithContentElement: CommandData & ExecuteCommandData<JQuery<HTMLElement>>;
executeWithTypeWidget: CommandData & ExecuteCommandData<TypeWidget | null>; executeWithTypeWidget: CommandData & ExecuteCommandData<ReactWrappedWidget | null>;
addTextToActiveEditor: CommandData & { addTextToActiveEditor: CommandData & {
text: string; text: string;
}; };
@ -487,13 +485,8 @@ type EventMappings = {
relationMapResetZoomIn: { ntxId: string | null | undefined }; relationMapResetZoomIn: { ntxId: string | null | undefined };
relationMapResetZoomOut: { ntxId: string | null | undefined }; relationMapResetZoomOut: { ntxId: string | null | undefined };
activeNoteChanged: {}; activeNoteChanged: {};
showAddLinkDialog: { showAddLinkDialog: AddLinkOpts;
textTypeWidget: EditableTextTypeWidget; showIncludeDialog: IncludeNoteOpts;
text: string;
};
showIncludeDialog: {
textTypeWidget: EditableTextTypeWidget;
};
openBulkActionsDialog: { openBulkActionsDialog: {
selectedOrActiveNoteIds: string[]; selectedOrActiveNoteIds: string[];
}; };

View File

@ -9,10 +9,11 @@ import hoistedNoteService from "../services/hoisted_note.js";
import options from "../services/options.js"; import options from "../services/options.js";
import type { ViewScope } from "../services/link.js"; import type { ViewScope } from "../services/link.js";
import type FNote from "../entities/fnote.js"; import type FNote from "../entities/fnote.js";
import type TypeWidget from "../widgets/type_widgets_old/type_widget.js";
import type { CKTextEditor } from "@triliumnext/ckeditor5"; import type { CKTextEditor } from "@triliumnext/ckeditor5";
import type CodeMirror from "@triliumnext/codemirror"; import type CodeMirror from "@triliumnext/codemirror";
import { closeActiveDialog } from "../services/dialog.js"; import { closeActiveDialog } from "../services/dialog.js";
import { TypeWidget } from "../widgets/note_types.jsx";
import { ReactWrappedWidget } from "../widgets/basic_widget.js";
export interface SetNoteOpts { export interface SetNoteOpts {
triggerSwitchEvent?: unknown; triggerSwitchEvent?: unknown;
@ -395,7 +396,7 @@ class NoteContext extends Component implements EventListener<"entitiesReloaded">
async getTypeWidget() { async getTypeWidget() {
return this.timeout( return this.timeout(
new Promise<TypeWidget | null>((resolve) => new Promise<ReactWrappedWidget | null>((resolve) =>
appContext.triggerCommand("executeWithTypeWidget", { appContext.triggerCommand("executeWithTypeWidget", {
resolve, resolve,
ntxId: this.ntxId ntxId: this.ntxId

View File

@ -11,7 +11,7 @@ import RightPanelWidget from "../widgets/right_panel_widget.js";
import ws from "./ws.js"; import ws from "./ws.js";
import appContext from "../components/app_context.js"; import appContext from "../components/app_context.js";
import NoteContextAwareWidget from "../widgets/note_context_aware_widget.js"; import NoteContextAwareWidget from "../widgets/note_context_aware_widget.js";
import BasicWidget from "../widgets/basic_widget.js"; import BasicWidget, { ReactWrappedWidget } from "../widgets/basic_widget.js";
import SpacedUpdate from "./spaced_update.js"; import SpacedUpdate from "./spaced_update.js";
import shortcutService from "./shortcuts.js"; import shortcutService from "./shortcuts.js";
import dialogService from "./dialog.js"; import dialogService from "./dialog.js";
@ -19,7 +19,6 @@ import type FNote from "../entities/fnote.js";
import { t } from "./i18n.js"; import { t } from "./i18n.js";
import dayjs from "dayjs"; import dayjs from "dayjs";
import type NoteContext from "../components/note_context.js"; import type NoteContext from "../components/note_context.js";
import type NoteDetailWidget from "../widgets/note_detail.js";
import type Component from "../components/component.js"; import type Component from "../components/component.js";
import { formatLogMessage } from "@triliumnext/commons"; import { formatLogMessage } from "@triliumnext/commons";
@ -317,7 +316,7 @@ export interface Api {
* Get access to the widget handling note detail. Methods like `getWidgetType()` and `getTypeWidget()` to get to the * Get access to the widget handling note detail. Methods like `getWidgetType()` and `getTypeWidget()` to get to the
* implementation of actual widget type. * implementation of actual widget type.
*/ */
getActiveNoteDetailWidget(): Promise<NoteDetailWidget>; getActiveNoteDetailWidget(): Promise<ReactWrappedWidget>;
/** /**
* @returns returns a note path of active note or null if there isn't active note * @returns returns a note path of active note or null if there isn't active note
*/ */

View File

@ -60,3 +60,14 @@ declare global {
windowControlsOverlay?: unknown; windowControlsOverlay?: unknown;
} }
} }
declare module "preact" {
namespace JSX {
interface IntrinsicElements {
webview: {
src: string;
class: string;
}
}
}
}

View File

@ -7,9 +7,8 @@ import { isValidElement, VNode } from "preact";
import { TypeWidgetProps } from "./type_widgets/type_widget"; import { TypeWidgetProps } from "./type_widgets/type_widget";
import "./NoteDetail.css"; import "./NoteDetail.css";
import attributes from "../services/attributes"; import attributes from "../services/attributes";
import { ExtendedNoteType, TYPE_MAPPINGS } from "./note_types"; import { ExtendedNoteType, TYPE_MAPPINGS, TypeWidget } from "./note_types";
import { dynamicRequire, isMobile } from "../services/utils"; import { dynamicRequire, isMobile } from "../services/utils";
import { ReactWrappedWidget } from "./basic_widget";
/** /**
* The note detail is in charge of rendering the content of a note, by determining its type (e.g. text, code) and using the appropriate view widget. * The note detail is in charge of rendering the content of a note, by determining its type (e.g. text, code) and using the appropriate view widget.
@ -214,7 +213,7 @@ function useNoteInfo() {
return { note, type, mime, noteContext, parentComponent }; return { note, type, mime, noteContext, parentComponent };
} }
async function getCorrespondingWidget(type: ExtendedNoteType): Promise<null | ((props: TypeWidgetProps) => VNode)> { async function getCorrespondingWidget(type: ExtendedNoteType): Promise<null | TypeWidget> {
const correspondingType = TYPE_MAPPINGS[type].view; const correspondingType = TYPE_MAPPINGS[type].view;
if (!correspondingType) return null; if (!correspondingType) return null;

View File

@ -8,9 +8,8 @@ import Button from "../react/Button";
import { Suggestion, triggerRecentNotes } from "../../services/note_autocomplete"; import { Suggestion, triggerRecentNotes } from "../../services/note_autocomplete";
import tree from "../../services/tree"; import tree from "../../services/tree";
import froca from "../../services/froca"; import froca from "../../services/froca";
import EditableTextTypeWidget, { type BoxSize } from "../type_widgets_old/editable_text";
import { useTriliumEvent } from "../react/hooks"; import { useTriliumEvent } from "../react/hooks";
import { CKEditorApi } from "../type_widgets/text/CKEditorWithWatchdog"; import { type BoxSize, CKEditorApi } from "../type_widgets/text/CKEditorWithWatchdog";
export interface IncludeNoteOpts { export interface IncludeNoteOpts {
editorApi: CKEditorApi; editorApi: CKEditorApi;
@ -19,7 +18,7 @@ export interface IncludeNoteOpts {
export default function IncludeNoteDialog() { export default function IncludeNoteDialog() {
const editorApiRef = useRef<CKEditorApi>(null); const editorApiRef = useRef<CKEditorApi>(null);
const [suggestion, setSuggestion] = useState<Suggestion | null>(null); const [suggestion, setSuggestion] = useState<Suggestion | null>(null);
const [boxSize, setBoxSize] = useState("medium"); const [boxSize, setBoxSize] = useState<string>("medium");
const [shown, setShown] = useState(false); const [shown, setShown] = useState(false);
useTriliumEvent("showIncludeNoteDialog", ({ editorApi }) => { useTriliumEvent("showIncludeNoteDialog", ({ editorApi }) => {

View File

@ -1,9 +1,8 @@
import type { EventNames, EventData } from "../../components/app_context.js"; import type { EventNames, EventData } from "../../components/app_context.js";
import NoteContext from "../../components/note_context.js"; import NoteContext from "../../components/note_context.js";
import { openDialog } from "../../services/dialog.js"; import { openDialog } from "../../services/dialog.js";
import BasicWidget from "../basic_widget.js"; import BasicWidget, { ReactWrappedWidget } from "../basic_widget.js";
import Container from "../containers/container.js"; import Container from "../containers/container.js";
import TypeWidget from "../type_widgets_old/type_widget.js";
const TPL = /*html*/`\ const TPL = /*html*/`\
<div class="popup-editor-dialog modal fade mx-auto" tabindex="-1" role="dialog"> <div class="popup-editor-dialog modal fade mx-auto" tabindex="-1" role="dialog">
@ -130,7 +129,7 @@ export default class PopupEditorDialog extends Container<BasicWidget> {
$dialog.on("hidden.bs.modal", () => { $dialog.on("hidden.bs.modal", () => {
const $typeWidgetEl = $dialog.find(".note-detail-printable"); const $typeWidgetEl = $dialog.find(".note-detail-printable");
if ($typeWidgetEl.length) { if ($typeWidgetEl.length) {
const typeWidget = glob.getComponentByEl($typeWidgetEl[0]) as TypeWidget; const typeWidget = glob.getComponentByEl($typeWidgetEl[0]) as ReactWrappedWidget;
typeWidget.cleanup(); typeWidget.cleanup();
} }

View File

@ -4,9 +4,8 @@
*/ */
import { NoteType } from "@triliumnext/commons"; import { NoteType } from "@triliumnext/commons";
import TypeWidget from "./type_widgets_old/type_widget"; import { VNode, type JSX } from "preact";
import { TypeWidgetProps } from "./type_widgets/type_widget"; import { TypeWidgetProps } from "./type_widgets/type_widget";
import { VNode } from "preact";
/** /**
* A `NoteType` altered by the note detail widget, taking into consideration whether the note is editable or not and adding special note types such as an empty one, * A `NoteType` altered by the note detail widget, taking into consideration whether the note is editable or not and adding special note types such as an empty one,
@ -14,7 +13,8 @@ import { VNode } from "preact";
*/ */
export type ExtendedNoteType = Exclude<NoteType, "launcher" | "text" | "code"> | "empty" | "readOnlyCode" | "readOnlyText" | "editableText" | "editableCode" | "attachmentDetail" | "attachmentList" | "protectedSession" | "aiChat"; export type ExtendedNoteType = Exclude<NoteType, "launcher" | "text" | "code"> | "empty" | "readOnlyCode" | "readOnlyText" | "editableText" | "editableCode" | "attachmentDetail" | "attachmentList" | "protectedSession" | "aiChat";
type NoteTypeView = () => Promise<{ default: TypeWidget } | TypeWidget> | ((props: TypeWidgetProps) => VNode); export type TypeWidget = ((props: TypeWidgetProps) => VNode | JSX.Element);
type NoteTypeView = () => (Promise<{ default: TypeWidget } | TypeWidget> | TypeWidget);
interface NoteTypeMapping { interface NoteTypeMapping {
view: NoteTypeView; view: NoteTypeView;
@ -36,7 +36,7 @@ export const TYPE_MAPPINGS: Record<ExtendedNoteType, NoteTypeMapping> = {
printable: true printable: true
}, },
search: { search: {
view: () => <></>, view: () => (props: TypeWidgetProps) => <></>,
className: "note-detail-none", className: "note-detail-none",
printable: true printable: true
}, },

View File

@ -23,9 +23,13 @@ export default function Book({ note }: TypeWidgetProps) {
} }
}); });
return (shouldDisplayNoChildrenWarning && return (
<Alert type="warning" className="note-detail-book-empty-help"> <>
<RawHtml html={t("book.no_children_help")} /> {shouldDisplayNoChildrenWarning && (
</Alert> <Alert type="warning" className="note-detail-book-empty-help">
<RawHtml html={t("book.no_children_help")} />
</Alert>
)}
</>
) )
} }

View File

@ -86,7 +86,7 @@ export default function Canvas({ note, noteContext }: TypeWidgetProps) {
) )
} }
function usePersistence(note: FNote, noteContext: NoteContext, apiRef: RefObject<ExcalidrawImperativeAPI>, theme: AppState["theme"], isReadOnly: boolean): Partial<ExcalidrawProps> { function usePersistence(note: FNote, noteContext: NoteContext | null | undefined, apiRef: RefObject<ExcalidrawImperativeAPI>, theme: AppState["theme"], isReadOnly: boolean): Partial<ExcalidrawProps> {
const libraryChanged = useRef(false); const libraryChanged = useRef(false);
/** /**

View File

@ -1,6 +1,5 @@
import { TypeWidgetProps } from "./type_widget"; import { TypeWidgetProps } from "./type_widget";
import { JSX } from "preact/jsx-runtime"; import { JSX } from "preact/jsx-runtime";
import { OptionPages } from "../type_widgets_old/content_widget";
import AppearanceSettings from "./options/appearance"; import AppearanceSettings from "./options/appearance";
import ShortcutSettings from "./options/shortcuts"; import ShortcutSettings from "./options/shortcuts";
import TextNoteSettings from "./options/text_notes"; import TextNoteSettings from "./options/text_notes";

View File

@ -181,7 +181,7 @@ export default function CKEditorWithWatchdog({ containerRef: externalContainerRe
); );
} }
function buildWatchdog(isClassicEditor: boolean, watchdogConfig?: WatchdogConfig) { function buildWatchdog(isClassicEditor: boolean, watchdogConfig?: WatchdogConfig): EditorWatchdog<CKTextEditor> {
if (isClassicEditor) { if (isClassicEditor) {
return new EditorWatchdog(ClassicEditor, watchdogConfig); return new EditorWatchdog(ClassicEditor, watchdogConfig);
} else { } else {

View File

@ -1,7 +1,7 @@
import appContext from "../../../components/app_context"; import appContext from "../../../components/app_context";
import content_renderer from "../../../services/content_renderer"; import content_renderer from "../../../services/content_renderer";
import froca from "../../../services/froca"; import froca from "../../../services/froca";
import link from "../../../services/link"; import link, { ViewScope } from "../../../services/link";
import utils from "../../../services/utils"; import utils from "../../../services/utils";
export async function loadIncludedNote(noteId: string, $el: JQuery<HTMLElement>) { export async function loadIncludedNote(noteId: string, $el: JQuery<HTMLElement>) {
@ -66,7 +66,7 @@ async function openImageInNewTab($img: JQuery<HTMLElement>, activate: boolean =
} }
} }
async function parseFromImage($img: JQuery<HTMLElement>) { async function parseFromImage($img: JQuery<HTMLElement>): Promise<{ noteId: string; viewScope: ViewScope } | null> {
const imgSrc = $img.prop("src"); const imgSrc = $img.prop("src");
const imageNoteMatch = imgSrc.match(/\/api\/images\/([A-Za-z0-9_]+)\//); const imageNoteMatch = imgSrc.match(/\/api\/images\/([A-Za-z0-9_]+)\//);
@ -81,9 +81,10 @@ async function parseFromImage($img: JQuery<HTMLElement>) {
if (attachmentMatch) { if (attachmentMatch) {
const attachmentId = attachmentMatch[1]; const attachmentId = attachmentMatch[1];
const attachment = await froca.getAttachment(attachmentId); const attachment = await froca.getAttachment(attachmentId);
if (!attachment) return null;
return { return {
noteId: attachment?.ownerId, noteId: attachment.ownerId,
viewScope: { viewScope: {
viewMode: "attachments", viewMode: "attachments",
attachmentId: attachmentId attachmentId: attachmentId

View File

@ -1,87 +0,0 @@
import NoteContextAwareWidget from "../note_context_aware_widget.js";
import appContext, { type EventData, type EventNames } from "../../components/app_context.js";
import type FNote from "../../entities/fnote.js";
import type NoteDetailWidget from "../note_detail.js";
import type SpacedUpdate from "../../services/spaced_update.js";
import type EmptyTypeWidget from "./empty.js";
/**
* The base class for all the note types.
*/
export default abstract class TypeWidget extends NoteContextAwareWidget {
spacedUpdate!: SpacedUpdate;
// for overriding
static getType() {}
doRender() {
return super.doRender();
}
doRefresh(note: FNote): void | Promise<void> {}
async refresh() {
const thisWidgetType = (this.constructor as any).getType();
const noteWidgetType = await (this.parent as NoteDetailWidget).getWidgetType();
if (thisWidgetType !== noteWidgetType) {
this.toggleInt(false);
this.cleanup();
} else {
this.toggleInt(true);
// Avoid passing nullable this.note down to doRefresh().
if (thisWidgetType !== "empty" && this.note) {
await this.doRefresh(this.note);
} else if (thisWidgetType === "empty") {
// EmptyTypeWidget is a special case, since it's used for a new tab where there's no note.
await (this as unknown as EmptyTypeWidget).doRefresh();
}
this.triggerEvent("noteDetailRefreshed", { ntxId: this.noteContext?.ntxId });
}
}
isActive() {
return this.$widget.is(":visible") && this.noteContext?.ntxId === appContext.tabManager.activeNtxId;
}
/** @returns {Promise<Object>|*} promise resolving note data. Note data is an object with content. */
getData() {}
focus() {}
scrollToEnd() {
// Do nothing by default.
}
dataSaved() {
// Do nothing by default.
}
/**
* {@inheritdoc}
*
* By default:
*
* - `activeContextChanged` is intercepted and converted to a `setNoteContext` event to avoid `refresh()`.
* - `entitiesReloaded` and `refreshData` are passed as-is.
* - any other event is not passed to the children.
*/
handleEventInChildren<T extends EventNames>(name: T, data: EventData<T>) {
if (["activeContextChanged", "setNoteContext"].includes(name)) {
// won't trigger .refresh();
return super.handleEventInChildren("setNoteContext", data as EventData<"activeContextChanged">);
} else if (name === "entitiesReloaded" || name === "refreshData") {
return super.handleEventInChildren(name, data);
} else {
return Promise.resolve();
}
}
cleanup(): void {
super.cleanup();
}
}