chore(react/type_widget): port content widget

This commit is contained in:
Elian Doran
2025-09-19 21:18:09 +03:00
parent db7cda3fe6
commit daa5ee93e9
31 changed files with 88 additions and 146 deletions

View File

@@ -10,6 +10,7 @@ import Doc from "./type_widgets/Doc";
import { TypeWidgetProps } from "./type_widgets/type_widget";
import ProtectedSession from "./type_widgets/ProtectedSession";
import Book from "./type_widgets/Book";
import ContentWidget from "./type_widgets/ContentWidget";
/**
* 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,
@@ -62,6 +63,7 @@ function getCorrespondingWidget(noteType: ExtendedNoteType | undefined, props: T
case "search": return <div className="note-detail-none note-detail-printable" />
case "protectedSession": return <ProtectedSession />
case "book": return <Book {...props} />
case "contentWidget": return <ContentWidget {...props} />
default: break;
}
}

View File

@@ -18,8 +18,8 @@ import sync from "../../services/sync";
import HelpButton from "../react/HelpButton";
import { TabContext } from "./ribbon-interface";
import Modal from "../react/Modal";
import { CodeMimeTypesList } from "../type_widgets_old/options/code_notes";
import { ContentLanguagesList } from "../type_widgets_old/options/i18n";
import { CodeMimeTypesList } from "../type_widgets/options/code_notes";
import { ContentLanguagesList } from "../type_widgets/options/i18n";
export default function BasicPropertiesTab({ note }: TabContext) {
return (

View File

@@ -0,0 +1,16 @@
.type-contentWidget .note-detail {
height: 100%;
}
.note-detail-content-widget {
height: 100%;
}
.note-detail-content-widget-content {
padding: 15px;
height: 100%;
}
.note-detail.full-height .note-detail-content-widget-content {
padding: 0;
}

View File

@@ -0,0 +1,60 @@
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";
import CodeNoteSettings from "./options/code_notes";
import ImageSettings from "./options/images";
import SpellcheckSettings from "./options/spellcheck";
import PasswordSettings from "./options/password";
import MultiFactorAuthenticationSettings from "./options/multi_factor_authentication";
import EtapiSettings from "./options/etapi";
import BackupSettings from "./options/backup";
import SyncOptions from "./options/sync";
import AiSettings from "./options/ai_settings";
import OtherSettings from "./options/other";
import InternationalizationOptions from "./options/i18n";
import AdvancedSettings from "./options/advanced";
import "./ContentWidget.css";
import { t } from "../../services/i18n";
export type OptionPages = "_optionsAppearance" | "_optionsShortcuts" | "_optionsTextNotes" | "_optionsCodeNotes" | "_optionsImages" | "_optionsSpellcheck" | "_optionsPassword" | "_optionsMFA" | "_optionsEtapi" | "_optionsBackup" | "_optionsSync" | "_optionsAi" | "_optionsOther" | "_optionsLocalization" | "_optionsAdvanced";
const CONTENT_WIDGETS: Record<OptionPages | "_backendLog", () => JSX.Element> = {
_optionsAppearance: AppearanceSettings,
_optionsShortcuts: ShortcutSettings,
_optionsTextNotes: TextNoteSettings,
_optionsCodeNotes: CodeNoteSettings,
_optionsImages: ImageSettings,
_optionsSpellcheck: SpellcheckSettings,
_optionsPassword: PasswordSettings,
_optionsMFA: MultiFactorAuthenticationSettings,
_optionsEtapi: EtapiSettings,
_optionsBackup: BackupSettings,
_optionsSync: SyncOptions,
_optionsAi: AiSettings,
_optionsOther: OtherSettings,
_optionsLocalization: InternationalizationOptions,
_optionsAdvanced: AdvancedSettings,
_backendLog: () => <></> // FIXME
}
/**
* Type widget that displays one or more widgets based on the type of note, generally used for options and other interactive notes such as the backend log.
*
* @param param0
* @returns
*/
export default function ContentWidget({ note }: TypeWidgetProps) {
const Content = CONTENT_WIDGETS[note.noteId];
return (
<div className="note-detail-content-widget note-detail-printable">
<div className={`note-detail-content-widget-content ${note.noteId.startsWith("_options") ? "options" : ""}`}>
{Content
? <Content />
: (t("content_widget.unknown_widget", { id: note.noteId }))}
</div>
</div>
)
}

View File

@@ -0,0 +1 @@
export const CODE_THEME_DEFAULT_PREFIX = "default:";

View File

@@ -8,7 +8,7 @@ import { useTriliumOption, useTriliumOptionBool, useTriliumOptionJson } from "..
import OptionsSection from "./components/OptionsSection";
import { useEffect, useMemo, useRef } from "preact/hooks";
import codeNoteSample from "./samples/code_note.txt?raw";
import { DEFAULT_PREFIX } from "../abstract_code_type_widget";
import { CODE_THEME_DEFAULT_PREFIX as DEFAULT_PREFIX } from "../constants";
import { MimeType } from "@triliumnext/commons";
import mime_types from "../../../services/mime_types";
import CheckboxList from "./components/CheckboxList";
@@ -58,7 +58,7 @@ function Appearance() {
<OptionsSection title={t("code_theme.title")}>
<div className="row" style={{ marginBottom: "15px" }}>
<FormGroup name="color-scheme" label={t("code_theme.color-scheme")} className="col-md-6" style={{ marginBottom: 0 }}>
<FormSelect
<FormSelect
values={themes}
keyProperty="id" titleProperty="name"
currentValue={codeNoteTheme} onChange={setCodeNoteTheme}
@@ -148,7 +148,7 @@ export function CodeMimeTypesList() {
plainTextMimeType.enabled = true;
plainTextMimeType.disabled = true;
}
for (const mimeType of ungroupedMimeTypes) {
const initial = mimeType.title.charAt(0).toUpperCase();
if (!result[initial]) {
@@ -157,7 +157,7 @@ export function CodeMimeTypesList() {
result[initial].push(mimeType);
}
return result;
}, [ codeNotesMimeTypes ]);
}, [ codeNotesMimeTypes ]);
return (
<ul class="options-mime-types">
@@ -174,4 +174,4 @@ export function CodeMimeTypesList() {
))}
</ul>
);
}
}

View File

@@ -1,5 +1,5 @@
import OptionsSection from "./OptionsSection";
import type { OptionPages } from "../../content_widget";
import type { OptionPages } from "../../ContentWidget";
import { t } from "../../../../services/i18n";
interface RelatedSettingsProps {
@@ -21,4 +21,4 @@ export default function RelatedSettings({ items }: RelatedSettingsProps) {
</nav>
</OptionsSection>
);
}
}

View File

@@ -5,7 +5,6 @@ import TypeWidget from "./type_widget.js";
import CodeMirror, { type EditorConfig } from "@triliumnext/codemirror";
import type { EventData } from "../../components/app_context.js";
export const DEFAULT_PREFIX = "default:";
/**
* An abstract {@link TypeWidget} which implements the CodeMirror editor, meant to be used as a parent for

View File

@@ -1,136 +0,0 @@
import TypeWidget from "./type_widget.js";
import type FNote from "../../entities/fnote.js";
import type NoteContextAwareWidget from "../note_context_aware_widget.js";
import { t } from "../../services/i18n.js";
import type { JSX } from "preact/jsx-runtime";
import AppearanceSettings from "./options/appearance.jsx";
import { disposeReactWidget, renderReactWidgetAtElement } from "../react/react_utils.jsx";
import ImageSettings from "./options/images.jsx";
import AdvancedSettings from "./options/advanced.jsx";
import InternationalizationOptions from "./options/i18n.jsx";
import SyncOptions from "./options/sync.jsx";
import EtapiSettings from "./options/etapi.js";
import BackupSettings from "./options/backup.js";
import SpellcheckSettings from "./options/spellcheck.js";
import PasswordSettings from "./options/password.jsx";
import ShortcutSettings from "./options/shortcuts.js";
import TextNoteSettings from "./options/text_notes.jsx";
import CodeNoteSettings from "./options/code_notes.jsx";
import OtherSettings from "./options/other.jsx";
import BackendLogWidget from "./content/backend_log.js";
import MultiFactorAuthenticationSettings from "./options/multi_factor_authentication.js";
import AiSettings from "./options/ai_settings.jsx";
import { unmountComponentAtNode } from "preact/compat";
const TPL = /*html*/`<div class="note-detail-content-widget note-detail-printable">
<style>
.type-contentWidget .note-detail {
height: 100%;
}
.note-detail-content-widget {
height: 100%;
}
.note-detail-content-widget-content {
padding: 15px;
height: 100%;
}
.note-detail.full-height .note-detail-content-widget-content {
padding: 0;
}
</style>
<div class="note-detail-content-widget-content"></div>
</div>`;
export type OptionPages = "_optionsAppearance" | "_optionsShortcuts" | "_optionsTextNotes" | "_optionsCodeNotes" | "_optionsImages" | "_optionsSpellcheck" | "_optionsPassword" | "_optionsMFA" | "_optionsEtapi" | "_optionsBackup" | "_optionsSync" | "_optionsAi" | "_optionsOther" | "_optionsLocalization" | "_optionsAdvanced";
const CONTENT_WIDGETS: Record<OptionPages | "_backendLog", ((typeof NoteContextAwareWidget)[] | JSX.Element)> = {
_optionsAppearance: <AppearanceSettings />,
_optionsShortcuts: <ShortcutSettings />,
_optionsTextNotes: <TextNoteSettings />,
_optionsCodeNotes: <CodeNoteSettings />,
_optionsImages: <ImageSettings />,
_optionsSpellcheck: <SpellcheckSettings />,
_optionsPassword: <PasswordSettings />,
_optionsMFA: <MultiFactorAuthenticationSettings />,
_optionsEtapi: <EtapiSettings />,
_optionsBackup: <BackupSettings />,
_optionsSync: <SyncOptions />,
_optionsAi: <AiSettings />,
_optionsOther: <OtherSettings />,
_optionsLocalization: <InternationalizationOptions />,
_optionsAdvanced: <AdvancedSettings />,
_backendLog: [
BackendLogWidget
]
};
/**
* Type widget that displays one or more widgets based on the type of note, generally used for options and other interactive notes such as the backend log.
*
* One important aspect is that, like its parent {@link TypeWidget}, the content widgets don't receive all events by default and they must be manually added
* to the propagation list in {@link TypeWidget.handleEventInChildren}.
*/
export default class ContentWidgetTypeWidget extends TypeWidget {
private $content!: JQuery<HTMLElement>;
static getType() {
return "contentWidget";
}
doRender() {
this.$widget = $(TPL);
this.$content = this.$widget.find(".note-detail-content-widget-content");
super.doRender();
}
async doRefresh(note: FNote) {
unmountComponentAtNode(this.$content[0]);
this.$content.empty();
this.children = [];
const contentWidgets = (CONTENT_WIDGETS as Record<string, (typeof NoteContextAwareWidget[] | JSX.Element)>)[note.noteId];
this.$content.toggleClass("options", note.noteId.startsWith("_options"));
// Unknown widget.
if (!contentWidgets) {
this.$content.append(t("content_widget.unknown_widget", { id: note.noteId }));
return;
}
// Legacy widget.
if (Array.isArray(contentWidgets)) {
for (const clazz of contentWidgets) {
const widget = new clazz();
if (this.noteContext) {
await widget.handleEvent("setNoteContext", { noteContext: this.noteContext });
}
this.child(widget);
this.$content.append(widget.render());
await widget.refresh();
}
return;
}
// React widget.
renderReactWidgetAtElement(this, contentWidgets, this.$content[0]);
}
cleanup(): void {
if (this.noteId) {
const contentWidgets = (CONTENT_WIDGETS as Record<string, (typeof NoteContextAwareWidget[] | JSX.Element)>)[this.noteId];
if (contentWidgets && !Array.isArray(contentWidgets)) {
disposeReactWidget(this.$content[0]);
}
}
super.cleanup();
}
}