feat(toc): basic support for read-only text

This commit is contained in:
Elian Doran 2025-12-18 11:12:27 +02:00
parent 41751c205c
commit b93c80fe7b
No known key found for this signature in database
3 changed files with 44 additions and 3 deletions

View File

@ -390,7 +390,7 @@ class NoteContext extends Component implements EventListener<"entitiesReloaded">
* If no content could be determined `null` is returned instead. * If no content could be determined `null` is returned instead.
*/ */
async getContentElement() { async getContentElement() {
return this.timeout<JQuery<HTMLElement>>( return this.timeout<JQuery<HTMLElement> | null>(
new Promise((resolve) => new Promise((resolve) =>
appContext.triggerCommand("executeWithContentElement", { appContext.triggerCommand("executeWithContentElement", {
resolve, resolve,

View File

@ -1115,3 +1115,19 @@ export function useTextEditor(noteContext: NoteContext | null | undefined) {
return textEditor; return textEditor;
} }
export function useContentElement(noteContext: NoteContext | null | undefined) {
const [ contentElement, setContentElement ] = useState<HTMLElement | null>(null);
const requestIdRef = useRef(0);
useEffect(() => {
const requestId = ++requestIdRef.current;
noteContext?.getContentElement().then(contentElement => {
// Prevent stale async.
if (requestId !== requestIdRef.current) return;
setContentElement(contentElement?.[0] ?? null);
});
}, [ noteContext ]);
return contentElement;
}

View File

@ -5,7 +5,7 @@ import clsx from "clsx";
import { useEffect, useState } from "preact/hooks"; import { useEffect, useState } from "preact/hooks";
import { t } from "../../services/i18n"; import { t } from "../../services/i18n";
import { useActiveNoteContext, useIsNoteReadOnly, useNoteProperty, useTextEditor } from "../react/hooks"; import { useActiveNoteContext, useContentElement, useIsNoteReadOnly, useNoteProperty, useTextEditor } from "../react/hooks";
import Icon from "../react/Icon"; import Icon from "../react/Icon";
import RightPanelWidget from "./RightPanelWidget"; import RightPanelWidget from "./RightPanelWidget";
@ -27,7 +27,9 @@ export default function TableOfContents() {
return ( return (
<RightPanelWidget title={t("toc.table_of_contents")}> <RightPanelWidget title={t("toc.table_of_contents")}>
{noteType === "text" && !isReadOnly && <EditableTextTableOfContents />} {noteType === "text" && (
isReadOnly ? <ReadOnlyTextTableOfContents /> : <EditableTextTableOfContents />
)}
</RightPanelWidget> </RightPanelWidget>
); );
} }
@ -159,3 +161,26 @@ function extractTocFromTextEditor(editor: CKTextEditor) {
return headings; return headings;
} }
//#endregion //#endregion
function ReadOnlyTextTableOfContents() {
const { noteContext } = useActiveNoteContext();
const contentEl = useContentElement(noteContext);
const headings = extractTocFromStaticHtml(contentEl);
return <AbstractTableOfContents headings={headings} />;
}
function extractTocFromStaticHtml(el: HTMLElement | null) {
if (!el) return [];
const headings: RawHeading[] = [];
for (const headingEl of el.querySelectorAll("h1,h2,h3,h4,h5,h6")) {
headings.push({
id: crypto.randomUUID(),
level: parseInt(headingEl.tagName.substring(1), 10),
text: headingEl.textContent
});
}
return headings;
}