feat(toc): basic support for docs

This commit is contained in:
Elian Doran 2025-12-18 11:46:13 +02:00
parent b93c80fe7b
commit 704dcd011e
No known key found for this signature in database
4 changed files with 58 additions and 46 deletions

View File

@ -1,40 +1,41 @@
import froca from "../services/froca.js";
import RootCommandExecutor from "./root_command_executor.js";
import Entrypoints from "./entrypoints.js";
import options from "../services/options.js";
import utils, { hasTouchBar } from "../services/utils.js";
import zoomComponent from "./zoom.js";
import TabManager from "./tab_manager.js";
import Component from "./component.js";
import keyboardActionsService from "../services/keyboard_actions.js";
import linkService, { type ViewScope } from "../services/link.js";
import MobileScreenSwitcherExecutor, { type Screen } from "./mobile_screen_switcher.js";
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 { ResolveOptions } from "../widgets/dialogs/delete_notes.js";
import type { PromptDialogOptions } from "../widgets/dialogs/prompt.js";
import type { ConfirmWithMessageOptions, ConfirmWithTitleOptions } from "../widgets/dialogs/confirm.js";
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 { NativeImage, TouchBar } from "electron";
import TouchBarComponent from "./touch_bar.js";
import type { CKTextEditor } from "@triliumnext/ckeditor5";
import type CodeMirror from "@triliumnext/codemirror";
import { StartupChecks } from "./startup_checks.js";
import type { CreateNoteOpts } from "../services/note_create.js";
import { ColumnComponent } from "tabulator-tables";
import { ChooseNoteTypeCallback } from "../widgets/dialogs/note_type_chooser.jsx";
import type RootContainer from "../widgets/containers/root_container.js";
import { SqlExecuteResults } from "@triliumnext/commons";
import { AddLinkOpts } from "../widgets/dialogs/add_link.jsx";
import { IncludeNoteOpts } from "../widgets/dialogs/include_note.jsx";
import type { NativeImage, TouchBar } from "electron";
import { ColumnComponent } from "tabulator-tables";
import type { Attribute } from "../services/attribute_parser.js";
import froca from "../services/froca.js";
import { initLocale,t } from "../services/i18n.js";
import keyboardActionsService from "../services/keyboard_actions.js";
import linkService, { type ViewScope } from "../services/link.js";
import type LoadResults from "../services/load_results.js";
import type { CreateNoteOpts } from "../services/note_create.js";
import options from "../services/options.js";
import toast from "../services/toast.js";
import utils, { hasTouchBar } from "../services/utils.js";
import { ReactWrappedWidget } from "../widgets/basic_widget.js";
import type { MarkdownImportOpts } from "../widgets/dialogs/markdown_import.jsx";
import type RootContainer from "../widgets/containers/root_container.js";
import { AddLinkOpts } from "../widgets/dialogs/add_link.jsx";
import type { ConfirmWithMessageOptions, ConfirmWithTitleOptions } from "../widgets/dialogs/confirm.js";
import type { ResolveOptions } from "../widgets/dialogs/delete_notes.js";
import { IncludeNoteOpts } from "../widgets/dialogs/include_note.jsx";
import type { InfoProps } from "../widgets/dialogs/info.jsx";
import type { MarkdownImportOpts } from "../widgets/dialogs/markdown_import.jsx";
import { ChooseNoteTypeCallback } from "../widgets/dialogs/note_type_chooser.jsx";
import type { PromptDialogOptions } from "../widgets/dialogs/prompt.js";
import type NoteTreeWidget from "../widgets/note_tree.js";
import Component from "./component.js";
import Entrypoints from "./entrypoints.js";
import MainTreeExecutors from "./main_tree_executors.js";
import MobileScreenSwitcherExecutor, { type Screen } from "./mobile_screen_switcher.js";
import type { default as NoteContext, GetTextEditorCallback } from "./note_context.js";
import RootCommandExecutor from "./root_command_executor.js";
import ShortcutComponent from "./shortcut_component.js";
import { StartupChecks } from "./startup_checks.js";
import TabManager from "./tab_manager.js";
import TouchBarComponent from "./touch_bar.js";
import zoomComponent from "./zoom.js";
interface Layout {
getRootWidget: (appContext: AppContext) => RootContainer;
@ -447,6 +448,7 @@ type EventMappings = {
};
searchRefreshed: { ntxId?: string | null };
textEditorRefreshed: { ntxId?: string | null, editor: CKTextEditor };
contentElRefreshed: { ntxId?: string | null, contentEl: HTMLElement };
hoistedNoteChanged: {
noteId: string;
ntxId: string | null;
@ -695,12 +697,10 @@ $(window).on("beforeunload", () => {
console.log(`Component ${component.componentId} is not finished saving its state.`);
allSaved = false;
}
} else {
if (!listener()) {
} else if (!listener()) {
allSaved = false;
}
}
}
if (!allSaved) {
toast.showMessage(t("app_context.please_wait_for_save"), 10000);
@ -708,7 +708,7 @@ $(window).on("beforeunload", () => {
}
});
$(window).on("hashchange", function () {
$(window).on("hashchange", () => {
const { notePath, ntxId, viewScope, searchString } = linkService.parseNavigationStateFromUrl(window.location.href);
if (notePath || ntxId) {

View File

@ -1119,6 +1119,7 @@ export function useTextEditor(noteContext: NoteContext | null | undefined) {
export function useContentElement(noteContext: NoteContext | null | undefined) {
const [ contentElement, setContentElement ] = useState<HTMLElement | null>(null);
const requestIdRef = useRef(0);
const [, forceUpdate] = useState(0);
useEffect(() => {
const requestId = ++requestIdRef.current;
@ -1126,8 +1127,16 @@ export function useContentElement(noteContext: NoteContext | null | undefined) {
// Prevent stale async.
if (requestId !== requestIdRef.current) return;
setContentElement(contentElement?.[0] ?? null);
forceUpdate(v => v + 1);
});
}, [ noteContext ]);
// React to content changes initializing.
useTriliumEvent("contentElRefreshed", ({ ntxId: eventNtxId, contentEl }) => {
if (eventNtxId !== noteContext?.ntxId) return;
setContentElement(contentEl);
forceUpdate(v => v + 1);
});
return contentElement;
}

View File

@ -27,9 +27,8 @@ export default function TableOfContents() {
return (
<RightPanelWidget title={t("toc.table_of_contents")}>
{noteType === "text" && (
isReadOnly ? <ReadOnlyTextTableOfContents /> : <EditableTextTableOfContents />
)}
{((noteType === "text" && isReadOnly) || (noteType === "doc")) && <ReadOnlyTextTableOfContents />}
{noteType === "text" && !isReadOnly && <EditableTextTableOfContents />}
</RightPanelWidget>
);
}

View File

@ -1,10 +1,12 @@
import { useEffect, useRef, useState } from "preact/hooks";
import { RawHtmlBlock } from "../react/RawHtml";
import renderDoc from "../../services/doc_renderer";
import "./Doc.css";
import { TypeWidgetProps } from "./type_widget";
import { useEffect, useRef } from "preact/hooks";
import appContext from "../../components/app_context";
import renderDoc from "../../services/doc_renderer";
import { useTriliumEvent } from "../react/hooks";
import { refToJQuerySelector } from "../react/react_utils";
import { TypeWidgetProps } from "./type_widget";
export default function Doc({ note, viewScope, ntxId }: TypeWidgetProps) {
const initialized = useRef<Promise<void> | null>(null);
@ -14,9 +16,11 @@ export default function Doc({ note, viewScope, ntxId }: TypeWidgetProps) {
if (!note) return;
initialized.current = renderDoc(note).then($content => {
containerRef.current?.replaceChildren(...$content);
if (!containerRef.current) return;
containerRef.current.replaceChildren(...$content);
appContext.triggerEvent("contentElRefreshed", { ntxId, contentEl: containerRef.current });
});
}, [ note ]);
}, [ note, ntxId ]);
useTriliumEvent("executeWithContentElement", async ({ resolve, ntxId: eventNtxId}) => {
if (eventNtxId !== ntxId) return;