Files
docmost/apps/client/src/features/editor/readonly-page-editor.tsx
T
SalihuandPhilipinho 7bd73f77a4 feat: lightbox media display (#2420)
* lightbox media display init

* revert uninteded changes

* load page media more accurately

* restrict lock file changes to installed packages

* revert changes in lock file

* update lockfile

* - support diagrams
- support readonly mode
- use softer backdrop
- other enhancments

---------

Co-authored-by: Philipinho <16838612+Philipinho@users.noreply.github.com>
2026-08-25 02:43:19 +01:00

160 lines
4.4 KiB
TypeScript

import "@/features/editor/styles/index.css";
import React, {
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from "react";
import { Editor, EditorProvider } from "@tiptap/react";
import { mainExtensions } from "@/features/editor/extensions/extensions";
import { Document } from "@tiptap/extension-document";
import { Heading, UniqueID } from "@docmost/editor-ext";
import { Text } from "@tiptap/extension-text";
import { Placeholder } from "@tiptap/extension-placeholder";
import { useAtom } from "jotai";
import {
lightboxRequestAtom,
readOnlyEditorAtom,
} from "@/features/editor/atoms/editor-atoms.ts";
import { useEditorScroll } from "./hooks/use-editor-scroll";
import { TransclusionLookupProvider } from "@/features/editor/components/transclusion/transclusion-lookup-context";
import LightboxView, {
getLightboxClickRequest,
} from "@/features/editor/components/common/lightbox-view";
interface PageEditorProps {
title: string;
content: any;
pageId?: string;
printMode?: boolean;
/**
* When rendering inside a public share, pass the share's id (or key). Lookups
* for transclusion content then resolve against the share graph instead of
* the viewer's personal permissions, so a share never leaks source content
* that isn't itself shared.
*/
shareId?: string;
}
export default function ReadonlyPageEditor({
title,
content,
pageId,
printMode = false,
shareId,
}: PageEditorProps) {
const [, setReadOnlyEditor] = useAtom(readOnlyEditorAtom);
const [lightboxRequest, setLightboxRequest] = useAtom(lightboxRequestAtom);
const [contentEditor, setContentEditor] = useState<Editor | null>(null);
const isComponentMounted = useRef(false);
const editorCreated = useRef(false);
const canScroll = useCallback(
() => isComponentMounted.current && editorCreated.current,
[isComponentMounted, editorCreated],
);
const initialScrollTo = window.location.hash
? window.location.hash.slice(1)
: "";
const { handleScrollTo } = useEditorScroll({ canScroll, initialScrollTo });
useEffect(() => {
isComponentMounted.current = true;
}, []);
useEffect(() => {
if (!shareId) return;
setLightboxRequest(null);
}, [pageId, shareId]);
const extensions = useMemo(() => {
const excludedExtensions = new Set([
"uniqueID",
...(printMode ? ["tableHeaderPin", "tableReadonlySort"] : []),
]);
const filteredExtensions = mainExtensions.filter(
(ext) => !excludedExtensions.has(ext.name),
);
return [
...filteredExtensions,
UniqueID.configure({
types: ["heading", "paragraph"],
updateDocument: false,
}),
];
}, [printMode]);
const titleExtensions = [
Document.extend({
content: "heading",
}),
Heading,
Text,
Placeholder.configure({
placeholder: "Untitled",
showOnlyWhenEditable: false,
}),
];
return (
<TransclusionLookupProvider shareId={shareId}>
<div className="page-title">
<EditorProvider
editable={false}
immediatelyRender={true}
textDirection="auto"
extensions={titleExtensions}
content={title}
></EditorProvider>
</div>
<EditorProvider
editable={false}
immediatelyRender={true}
textDirection="auto"
extensions={extensions}
content={content}
editorProps={
shareId
? {
handleClickOn: (_view, _pos, node) => {
const request = getLightboxClickRequest(node);
if (!request) return false;
setLightboxRequest(request);
return true;
},
}
: undefined
}
onCreate={({ editor }) => {
if (editor) {
if (pageId) {
// @ts-ignore
editor.storage.pageId = pageId;
}
// @ts-ignore
setReadOnlyEditor(editor);
setContentEditor(editor);
handleScrollTo(editor);
editorCreated.current = true;
}
}}
></EditorProvider>
{shareId && contentEditor && (
<LightboxView
editor={contentEditor}
open={!!lightboxRequest}
src={lightboxRequest?.src ?? ""}
type={lightboxRequest?.type ?? "image"}
onClose={() => setLightboxRequest(null)}
/>
)}
<div style={{ paddingBottom: "20vh" }}></div>
</TransclusionLookupProvider>
);
}