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 ShortcutComponent from "./shortcut_component.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 { PromptDialogOptions } from "../widgets/dialogs/prompt.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 NoteTreeWidget from "../widgets/note_tree.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 TouchBarComponent from "./touch_bar.js";
import type { CKTextEditor } from "@triliumnext/ckeditor5";
@ -36,6 +33,7 @@ import { SqlExecuteResults } from "@triliumnext/commons";
import { AddLinkOpts } from "../widgets/dialogs/add_link.jsx";
import { IncludeNoteOpts } from "../widgets/dialogs/include_note.jsx";
import { ReactWrappedWidget } from "../widgets/basic_widget.js";
import { TypeWidget } from "../widgets/note_types.jsx";
interface Layout {
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}.
*/
executeWithContentElement: CommandData & ExecuteCommandData<JQuery<HTMLElement>>;
executeWithTypeWidget: CommandData & ExecuteCommandData<TypeWidget | null>;
executeWithTypeWidget: CommandData & ExecuteCommandData<ReactWrappedWidget | null>;
addTextToActiveEditor: CommandData & {
text: string;
};
@ -487,13 +485,8 @@ type EventMappings = {
relationMapResetZoomIn: { ntxId: string | null | undefined };
relationMapResetZoomOut: { ntxId: string | null | undefined };
activeNoteChanged: {};
showAddLinkDialog: {
textTypeWidget: EditableTextTypeWidget;
text: string;
};
showIncludeDialog: {
textTypeWidget: EditableTextTypeWidget;
};
showAddLinkDialog: AddLinkOpts;
showIncludeDialog: IncludeNoteOpts;
openBulkActionsDialog: {
selectedOrActiveNoteIds: string[];
};

View File

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

View File

@ -11,7 +11,7 @@ import RightPanelWidget from "../widgets/right_panel_widget.js";
import ws from "./ws.js";
import appContext from "../components/app_context.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 shortcutService from "./shortcuts.js";
import dialogService from "./dialog.js";
@ -19,7 +19,6 @@ import type FNote from "../entities/fnote.js";
import { t } from "./i18n.js";
import dayjs from "dayjs";
import type NoteContext from "../components/note_context.js";
import type NoteDetailWidget from "../widgets/note_detail.js";
import type Component from "../components/component.js";
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
* 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
*/

View File

@ -60,3 +60,14 @@ declare global {
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 "./NoteDetail.css";
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 { 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.
@ -214,7 +213,7 @@ function useNoteInfo() {
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;
if (!correspondingType) return null;

View File

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

View File

@ -1,9 +1,8 @@
import type { EventNames, EventData } from "../../components/app_context.js";
import NoteContext from "../../components/note_context.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 TypeWidget from "../type_widgets_old/type_widget.js";
const TPL = /*html*/`\
<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", () => {
const $typeWidgetEl = $dialog.find(".note-detail-printable");
if ($typeWidgetEl.length) {
const typeWidget = glob.getComponentByEl($typeWidgetEl[0]) as TypeWidget;
const typeWidget = glob.getComponentByEl($typeWidgetEl[0]) as ReactWrappedWidget;
typeWidget.cleanup();
}

View File

@ -4,9 +4,8 @@
*/
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 { 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,
@ -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";
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 {
view: NoteTypeView;
@ -36,7 +36,7 @@ export const TYPE_MAPPINGS: Record<ExtendedNoteType, NoteTypeMapping> = {
printable: true
},
search: {
view: () => <></>,
view: () => (props: TypeWidgetProps) => <></>,
className: "note-detail-none",
printable: true
},

View File

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

View File

@ -1,6 +1,5 @@
import { TypeWidgetProps } from "./type_widget";
import { JSX } from "preact/jsx-runtime";
import { OptionPages } from "../type_widgets_old/content_widget";
import AppearanceSettings from "./options/appearance";
import ShortcutSettings from "./options/shortcuts";
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) {
return new EditorWatchdog(ClassicEditor, watchdogConfig);
} else {

View File

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