@openeditor/react 0.0.28 → 0.0.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +39 -1
- package/dist/index.d.ts +8 -4
- package/dist/index.js +143 -126
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -19,6 +19,40 @@ Pass `attachmentRuntime` to `useOpenEditorController` for editable attachments a
|
|
|
19
19
|
|
|
20
20
|
This package intentionally does not import CSS and does not render styled menus. Use `@openeditor/ui` for the optional styled components.
|
|
21
21
|
|
|
22
|
+
## Read-only rendering and URL policy
|
|
23
|
+
|
|
24
|
+
`OpenEditorViewer` renders the portable document through an explicit registry of
|
|
25
|
+
built-in and structural renderers. Unknown nodes are preserved for forward
|
|
26
|
+
readability but carry `data-openeditor-unknown-node` so unsupported content never
|
|
27
|
+
silently appears to be a fully supported block. Built-in viewer output and safe
|
|
28
|
+
HTML export share stable `oe-*` classes and `data-openeditor-*` hooks for document
|
|
29
|
+
semantics such as columns, tasks, toggles, callouts, tables, pages, and files.
|
|
30
|
+
|
|
31
|
+
Viewer URLs use `openEditorPublicUrlPolicy` by default. The policy covers links,
|
|
32
|
+
images, pages, and attachments, including snapshots returned by page and
|
|
33
|
+
attachment runtimes. It accepts HTTP(S) and ordinary relative references, plus
|
|
34
|
+
`mailto:` and `tel:` for text links, while rejecting executable and local-preview
|
|
35
|
+
schemes. Owner previews can opt into a narrowly scoped policy:
|
|
36
|
+
|
|
37
|
+
```tsx
|
|
38
|
+
import {
|
|
39
|
+
OpenEditorViewer,
|
|
40
|
+
openEditorPublicUrlPolicy,
|
|
41
|
+
type OpenEditorUrlPolicy,
|
|
42
|
+
} from "@openeditor/react";
|
|
43
|
+
|
|
44
|
+
const previewUrlPolicy: OpenEditorUrlPolicy = (value, context) => {
|
|
45
|
+
if (context === "attachment" && value.startsWith("blob:")) return value;
|
|
46
|
+
return openEditorPublicUrlPolicy(value, context);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
<OpenEditorViewer document={document} urlPolicy={previewUrlPolicy} />;
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Custom viewer renderers receive both `urlPolicy` and a typed `resolveUrl` helper.
|
|
53
|
+
The host remains responsible for arbitrary custom renderer output and for actions
|
|
54
|
+
performed inside `openPage` or `openAttachment` callbacks.
|
|
55
|
+
|
|
22
56
|
## Block actions
|
|
23
57
|
|
|
24
58
|
Every addressable node has a persistent `openeditor-id`. The block interaction
|
|
@@ -208,7 +242,11 @@ const productCard = defineOpenEditorReactNode({
|
|
|
208
242
|
keywords: ["product", "commerce", "card"],
|
|
209
243
|
order: 250,
|
|
210
244
|
},
|
|
211
|
-
viewer: ({ node }) =>
|
|
245
|
+
viewer: ({ node, resolveUrl }) => (
|
|
246
|
+
<article>
|
|
247
|
+
<a href={resolveUrl(node.attrs?.href, "link")}>{String(node.attrs?.title ?? "")}</a>
|
|
248
|
+
</article>
|
|
249
|
+
),
|
|
212
250
|
exporters: {
|
|
213
251
|
html: {
|
|
214
252
|
acmeProductCard: ({ node, escapeHtml }) =>
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { ReactNode, CSSProperties, RefObject, ComponentType } from 'react';
|
|
3
|
-
import { OpenEditorBlockRef, OpenEditorDocument, BlockSpec, ProseMirrorNode, OpenEditorPageRuntime, OpenEditorAttachmentRuntime, OpenEditorPageSnapshot, OpenEditorAuthoringCapabilities } from '@openeditor/core';
|
|
4
|
-
export { OpenEditorBlockRef, OpenEditorPageRuntime, OpenEditorPageSnapshot } from '@openeditor/core';
|
|
3
|
+
import { OpenEditorBlockRef, OpenEditorDocument, BlockSpec, ProseMirrorNode, OpenEditorUrlPolicy, OpenEditorUrlContext, OpenEditorPageRuntime, OpenEditorAttachmentRuntime, OpenEditorPageSnapshot, OpenEditorAuthoringCapabilities } from '@openeditor/core';
|
|
4
|
+
export { OpenEditorBlockRef, OpenEditorPageRuntime, OpenEditorPageSnapshot, OpenEditorUrlContext, OpenEditorUrlPolicy, openEditorPublicUrlPolicy, openEditorUnsafeUrlPolicy } from '@openeditor/core';
|
|
5
5
|
import { ExportFormat, OpenEditorExporters } from '@openeditor/exporters';
|
|
6
6
|
export { seedDocument } from '@openeditor/extensions';
|
|
7
7
|
import { AnyExtension, NodeConfig } from '@tiptap/core';
|
|
@@ -17,7 +17,7 @@ type OpenEditorBlockInteractionStore = {
|
|
|
17
17
|
subscribe: (listener: () => void) => () => void;
|
|
18
18
|
};
|
|
19
19
|
|
|
20
|
-
declare const openEditorThemeTokenNames: readonly ["surface", "surfaceRaised", "surfaceMuted", "blockSurface", "text", "textSoft", "heading", "muted", "placeholder", "border", "borderStrong", "structuralLine", "accent", "accentText", "accentStrong", "buttonBackground", "buttonText", "codeBackground", "codeText", "link", "linkHover", "shadow", "fontSans", "fontMono", "radiusSmall", "radiusMedium", "radiusLarge", "spaceBlock", "spaceInline"];
|
|
20
|
+
declare const openEditorThemeTokenNames: readonly ["surface", "surfaceRaised", "surfaceMuted", "blockSurface", "text", "textSoft", "heading", "muted", "placeholder", "border", "borderStrong", "structuralLine", "accent", "accentText", "accentStrong", "buttonBackground", "buttonText", "codeBackground", "codeText", "link", "linkHover", "shadow", "fontSans", "fontMono", "radiusSmall", "radiusMedium", "radiusLarge", "spaceBlock", "spaceInline", "bodyFontSize", "bodyLineHeight", "headingFont", "headingLineHeight", "headingWeight", "heading1Size", "heading2Size", "heading3Size", "heading4Size", "heading5Size", "heading6Size"];
|
|
21
21
|
type OpenEditorThemeToken = (typeof openEditorThemeTokenNames)[number];
|
|
22
22
|
type OpenEditorTheme = Partial<Record<OpenEditorThemeToken, string>>;
|
|
23
23
|
type OpenEditorThemeStyle = CSSProperties & Record<`--oe-${string}`, string | undefined>;
|
|
@@ -169,6 +169,8 @@ type OpenEditorBlockPickerItem = {
|
|
|
169
169
|
type OpenEditorViewerRenderer = (context: {
|
|
170
170
|
node: ProseMirrorNode;
|
|
171
171
|
children: ReactNode;
|
|
172
|
+
urlPolicy: OpenEditorUrlPolicy;
|
|
173
|
+
resolveUrl: (value: unknown, context: OpenEditorUrlContext) => string | undefined;
|
|
172
174
|
}) => ReactNode;
|
|
173
175
|
type OpenEditorExtensionMenu = {
|
|
174
176
|
label?: string;
|
|
@@ -210,6 +212,8 @@ type OpenEditorViewerProps = {
|
|
|
210
212
|
extensions?: readonly OpenEditorReactExtension[];
|
|
211
213
|
pageRuntime?: OpenEditorPageRuntime;
|
|
212
214
|
attachmentRuntime?: OpenEditorAttachmentRuntime<any>;
|
|
215
|
+
/** Safe by default. Supply an explicit host policy for trusted preview URLs such as `blob:`. */
|
|
216
|
+
urlPolicy?: OpenEditorUrlPolicy;
|
|
213
217
|
};
|
|
214
218
|
declare module "@tiptap/core" {
|
|
215
219
|
interface Commands<ReturnType> {
|
|
@@ -250,7 +254,7 @@ declare const sortBlockPickerItems: (items: readonly OpenEditorBlockPickerItem[]
|
|
|
250
254
|
declare const getDefaultBlockPickerItems: (controller: OpenEditorController) => OpenEditorBlockPickerItem[];
|
|
251
255
|
declare const getDefaultSlashMenuItems: (controller: OpenEditorController) => OpenEditorSlashMenuItem[];
|
|
252
256
|
declare const OpenEditorContent: ({ controller, className, }: OpenEditorContentProps) => react.JSX.Element;
|
|
253
|
-
declare const OpenEditorViewer: ({ document, className, renderers, extensions, pageRuntime, attachmentRuntime, }: OpenEditorViewerProps) => react.JSX.Element;
|
|
257
|
+
declare const OpenEditorViewer: ({ document, className, renderers, extensions, pageRuntime, attachmentRuntime, urlPolicy, }: OpenEditorViewerProps) => react.JSX.Element;
|
|
254
258
|
declare const useOpenEditor: ({ initialDocument, editable, placeholder, onChange, onFilePasteDrop, pageRuntime, attachmentRuntime, enabledBlocks, slashMenuItems, extensions, blockActions, }?: OpenEditorReactProps) => OpenEditorController;
|
|
255
259
|
|
|
256
260
|
export { type InsertRange, type OpenEditorBlockAction, type OpenEditorBlockActionContext, OpenEditorBlockDragHandle, type OpenEditorBlockDragHandleProps, type OpenEditorBlockPickerItem, OpenEditorContent, type OpenEditorContentProps, type OpenEditorController, type OpenEditorExtensionMenu, type OpenEditorOverlayRect, OpenEditorPageHeader, type OpenEditorPageHeaderProps, type OpenEditorReactConfig, type OpenEditorReactExtension, type OpenEditorReactNodeExtension, type OpenEditorReactProps, type OpenEditorSlashMenuIcon, type OpenEditorSlashMenuIconProps, type OpenEditorSlashMenuItem, type OpenEditorTheme, OpenEditorThemeProvider, type OpenEditorThemeStyle, type OpenEditorThemeToken, OpenEditorViewer, type OpenEditorViewerProps, type OpenEditorViewerRenderer, type SelectionBubbleState, type SlashState, createOpenEditorThemeStyle, defineOpenEditorReactExtension, defineOpenEditorReactNode, formatAttachmentSize, getDefaultBlockPickerItems, getDefaultSlashMenuItems, openEditorThemeTokenNames, sortBlockPickerItems, sortSlashMenuItems, useOpenEditor, useOpenEditorBlockInteraction, useOpenEditorController, useOpenEditorThemeStyle };
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { DEFAULT_CALLOUT_EMOJI, normalizeEmoji, DEFAULT_PAGE_EMOJI, normalizeDocument, toProseMirrorDocument, createDocument as createDocument$1, isOpenEditorBlockEnabled, createBlockRegistry, OPENEDITOR_BLOCK_ID_ATTR, createBlockId, fromProseMirrorDocument, findBlockSpecForNode } from '@openeditor/core';
|
|
1
|
+
import { DEFAULT_CALLOUT_EMOJI, normalizeEmoji, DEFAULT_PAGE_EMOJI, normalizeDocument, toProseMirrorDocument, createDocument as createDocument$1, openEditorPublicUrlPolicy, isOpenEditorBlockEnabled, createBlockRegistry, OPENEDITOR_BLOCK_ID_ATTR, createBlockId, fromProseMirrorDocument, findBlockSpecForNode } from '@openeditor/core';
|
|
2
|
+
export { openEditorPublicUrlPolicy, openEditorUnsafeUrlPolicy } from '@openeditor/core';
|
|
2
3
|
import { Checkbox } from '@base-ui/react/checkbox';
|
|
3
4
|
import { shift as shift$1, autoUpdate, computePosition } from '@floating-ui/dom';
|
|
4
5
|
import { OpenEditorEmojiPicker } from '@openeditor/emoji';
|
|
5
|
-
import React, { createContext, forwardRef, useContext, useMemo, createRef, memo, createElement, version, useState, useEffect, useRef, useSyncExternalStore as useSyncExternalStore$1, useCallback, use, useDebugValue,
|
|
6
|
+
import React, { createContext, forwardRef, useContext, useMemo, createRef, memo, createElement, version, useState, useEffect, useRef, useSyncExternalStore as useSyncExternalStore$1, useCallback, use, useDebugValue, useLayoutEffect } from 'react';
|
|
6
7
|
import { jsxs, Fragment as Fragment$1, jsx } from 'react/jsx-runtime';
|
|
7
8
|
import { renderMermaidSVG } from 'beautiful-mermaid';
|
|
8
9
|
import { exportDocumentUnsafe, exportDocument } from '@openeditor/exporters';
|
|
@@ -6288,9 +6289,20 @@ var openEditorThemeTokenNames = [
|
|
|
6288
6289
|
"radiusMedium",
|
|
6289
6290
|
"radiusLarge",
|
|
6290
6291
|
"spaceBlock",
|
|
6291
|
-
"spaceInline"
|
|
6292
|
+
"spaceInline",
|
|
6293
|
+
"bodyFontSize",
|
|
6294
|
+
"bodyLineHeight",
|
|
6295
|
+
"headingFont",
|
|
6296
|
+
"headingLineHeight",
|
|
6297
|
+
"headingWeight",
|
|
6298
|
+
"heading1Size",
|
|
6299
|
+
"heading2Size",
|
|
6300
|
+
"heading3Size",
|
|
6301
|
+
"heading4Size",
|
|
6302
|
+
"heading5Size",
|
|
6303
|
+
"heading6Size"
|
|
6292
6304
|
];
|
|
6293
|
-
var cssName = (token) => `--oe-${token.replace(/[A-Z]/g, (
|
|
6305
|
+
var cssName = (token) => `--oe-${token.replace(/([a-z])([A-Z])/g, "$1-$2").replace(/([A-Za-z])(\d)/g, "$1-$2").replace(/(\d)([A-Z])/g, "$1-$2").toLowerCase()}`;
|
|
6294
6306
|
var createOpenEditorThemeStyle = (theme) => Object.fromEntries(
|
|
6295
6307
|
openEditorThemeTokenNames.flatMap(
|
|
6296
6308
|
(token) => theme[token] === void 0 ? [] : [[cssName(token), theme[token]]]
|
|
@@ -30999,16 +31011,7 @@ var createStandardAttributes = (attributeNames) => Object.fromEntries(
|
|
|
30999
31011
|
}
|
|
31000
31012
|
])
|
|
31001
31013
|
);
|
|
31002
|
-
var
|
|
31003
|
-
const normalized = value.trim().toLowerCase();
|
|
31004
|
-
if (normalized.startsWith("javascript:") || normalized.startsWith("vbscript:")) {
|
|
31005
|
-
return false;
|
|
31006
|
-
}
|
|
31007
|
-
if (normalized.startsWith("data:")) {
|
|
31008
|
-
return allowDataImage && normalized.startsWith("data:image/");
|
|
31009
|
-
}
|
|
31010
|
-
return true;
|
|
31011
|
-
};
|
|
31014
|
+
var isSafePastedUrl = (value, context, allowDataImage) => allowDataImage && /^data:image\//i.test(value.trim()) || openEditorPublicUrlPolicy(value, context) !== null;
|
|
31012
31015
|
var sanitizePastedHtml = (html) => {
|
|
31013
31016
|
const parser = new DOMParser();
|
|
31014
31017
|
const parsed = parser.parseFromString(html, "text/html");
|
|
@@ -31030,7 +31033,8 @@ var sanitizePastedHtml = (html) => {
|
|
|
31030
31033
|
const allowDataImage = element.tagName.toLowerCase() === "img";
|
|
31031
31034
|
for (const attribute of URL_ATTRIBUTES) {
|
|
31032
31035
|
const value = element.getAttribute(attribute);
|
|
31033
|
-
|
|
31036
|
+
const context = attribute === "src" ? "image" : "link";
|
|
31037
|
+
if (value !== null && !isSafePastedUrl(value, context, allowDataImage && attribute === "src")) {
|
|
31034
31038
|
element.removeAttribute(attribute);
|
|
31035
31039
|
}
|
|
31036
31040
|
}
|
|
@@ -31442,7 +31446,10 @@ var Attachment = Node3.create({
|
|
|
31442
31446
|
return ReactNodeViewRenderer(OpenEditorAttachmentNodeView);
|
|
31443
31447
|
}
|
|
31444
31448
|
});
|
|
31445
|
-
var OpenEditorAttachmentViewer = ({
|
|
31449
|
+
var OpenEditorAttachmentViewer = ({
|
|
31450
|
+
node,
|
|
31451
|
+
urlPolicy
|
|
31452
|
+
}) => {
|
|
31446
31453
|
const runtime = use(OpenEditorAttachmentRuntimeContext);
|
|
31447
31454
|
const cached = attachmentSnapshotFromNode(node);
|
|
31448
31455
|
const [state, setState] = useState({ phase: cached.attachmentId && runtime?.resolveAttachment ? "resolving" : cached.url ? "ready" : "missing", snapshot: cached });
|
|
@@ -31466,9 +31473,10 @@ var OpenEditorAttachmentViewer = ({ node }) => {
|
|
|
31466
31473
|
/* @__PURE__ */ jsx("span", { className: "oe-attachment-meta", children: state.phase === "resolving" ? "Loading\u2026" : state.phase === "missing" ? "Missing or inaccessible" : [formatAttachmentSize(snapshot.size), attachmentTypeLabel(snapshot)].filter(Boolean).join(" \xB7 ") })
|
|
31467
31474
|
] })
|
|
31468
31475
|
] });
|
|
31469
|
-
if (state.phase === "ready" && runtime?.openAttachment) return /* @__PURE__ */ jsx("button", { className: "oe-attachment oe-attachment-viewer", onClick: () => void runtime.openAttachment?.(snapshot), type: "button", children: content });
|
|
31470
|
-
|
|
31471
|
-
return /* @__PURE__ */ jsx("
|
|
31476
|
+
if (state.phase === "ready" && runtime?.openAttachment) return /* @__PURE__ */ jsx("button", { className: "oe-attachment oe-attachment-viewer", "data-openeditor-attachment": true, onClick: () => void runtime.openAttachment?.(snapshot), type: "button", children: content });
|
|
31477
|
+
const href = snapshot.url ? urlPolicy(snapshot.url, "attachment") : null;
|
|
31478
|
+
if (state.phase === "ready" && href) return /* @__PURE__ */ jsx("a", { className: "oe-attachment oe-attachment-viewer", "data-openeditor-attachment": true, href, rel: "noopener noreferrer", target: "_blank", children: content });
|
|
31479
|
+
return /* @__PURE__ */ jsx("div", { className: "oe-attachment oe-attachment-viewer", "data-openeditor-attachment": true, "data-state": state.phase, children: content });
|
|
31472
31480
|
};
|
|
31473
31481
|
var pageSnapshotFromNode = (node) => {
|
|
31474
31482
|
const pageId = typeof node.attrs?.pageId === "string" ? node.attrs.pageId : "";
|
|
@@ -31609,7 +31617,10 @@ var Page = Node3.create({
|
|
|
31609
31617
|
return ReactNodeViewRenderer(OpenEditorPageNodeView);
|
|
31610
31618
|
}
|
|
31611
31619
|
});
|
|
31612
|
-
var OpenEditorPageViewer = ({
|
|
31620
|
+
var OpenEditorPageViewer = ({
|
|
31621
|
+
node,
|
|
31622
|
+
urlPolicy
|
|
31623
|
+
}) => {
|
|
31613
31624
|
const runtime = use(OpenEditorPageRuntimeContext);
|
|
31614
31625
|
const cached = pageSnapshotFromNode(node);
|
|
31615
31626
|
const [page] = useResolvedPageSnapshot(cached, runtime);
|
|
@@ -31620,12 +31631,13 @@ var OpenEditorPageViewer = ({ node }) => {
|
|
|
31620
31631
|
/* @__PURE__ */ jsx("span", { className: "oe-page-arrow", "aria-hidden": "true", children: "\u2192" })
|
|
31621
31632
|
] });
|
|
31622
31633
|
if (page && runtime?.openPage) {
|
|
31623
|
-
return /* @__PURE__ */ jsx("button", { className: "oe-page oe-page-viewer", onClick: () => void runtime.openPage?.(page), type: "button", children: content });
|
|
31634
|
+
return /* @__PURE__ */ jsx("button", { className: "oe-page oe-page-viewer", "data-openeditor-page": true, onClick: () => void runtime.openPage?.(page), type: "button", children: content });
|
|
31624
31635
|
}
|
|
31625
|
-
|
|
31626
|
-
|
|
31636
|
+
const href = page?.href ? urlPolicy(page.href, "page") : null;
|
|
31637
|
+
if (href) {
|
|
31638
|
+
return /* @__PURE__ */ jsx("a", { className: "oe-page oe-page-viewer", "data-openeditor-page": true, href, children: content });
|
|
31627
31639
|
}
|
|
31628
|
-
return /* @__PURE__ */ jsx("div", { className: "oe-page oe-page-viewer", children: content });
|
|
31640
|
+
return /* @__PURE__ */ jsx("div", { className: "oe-page oe-page-viewer", "data-openeditor-page": true, children: content });
|
|
31629
31641
|
};
|
|
31630
31642
|
var OpenEditorPageHeader = ({
|
|
31631
31643
|
page,
|
|
@@ -31708,7 +31720,7 @@ var MermaidPreview = ({ code }) => {
|
|
|
31708
31720
|
};
|
|
31709
31721
|
var ToggleListViewerItem = ({ children, initiallyOpen }) => {
|
|
31710
31722
|
const [open, setOpen] = useState(initiallyOpen);
|
|
31711
|
-
return /* @__PURE__ */ jsxs("li", { className: "oe-toggle-list-item", "data-open": open ? "true" : "false", children: [
|
|
31723
|
+
return /* @__PURE__ */ jsxs("li", { className: "oe-toggle-list-item", "data-open": open ? "true" : "false", "data-openeditor-toggle-list-item": true, children: [
|
|
31712
31724
|
/* @__PURE__ */ jsx(
|
|
31713
31725
|
"button",
|
|
31714
31726
|
{
|
|
@@ -32559,7 +32571,7 @@ var getDefaultSlashMenuItems = (controller) => {
|
|
|
32559
32571
|
...controller.slashMenuItems
|
|
32560
32572
|
]);
|
|
32561
32573
|
};
|
|
32562
|
-
var renderTextWithMarks = (text, marks = [], key) => marks.reduceRight((children, mark, index) => {
|
|
32574
|
+
var renderTextWithMarks = (text, marks = [], key, urlPolicy) => marks.reduceRight((children, mark, index) => {
|
|
32563
32575
|
const nodeKey = `${key}-${mark.type}-${index}`;
|
|
32564
32576
|
switch (mark.type) {
|
|
32565
32577
|
case "bold":
|
|
@@ -32572,106 +32584,110 @@ var renderTextWithMarks = (text, marks = [], key) => marks.reduceRight((children
|
|
|
32572
32584
|
return /* @__PURE__ */ jsx("s", { children }, nodeKey);
|
|
32573
32585
|
case "code":
|
|
32574
32586
|
return /* @__PURE__ */ jsx("code", { children }, nodeKey);
|
|
32575
|
-
case "link":
|
|
32576
|
-
|
|
32587
|
+
case "link": {
|
|
32588
|
+
const href = typeof mark.attrs?.href === "string" ? urlPolicy(mark.attrs.href, "link") : null;
|
|
32589
|
+
if (!href) return children;
|
|
32590
|
+
const target = typeof mark.attrs?.target === "string" && ["_blank", "_self", "_parent", "_top"].includes(mark.attrs.target) ? mark.attrs.target : void 0;
|
|
32591
|
+
const declaredRel = typeof mark.attrs?.rel === "string" ? mark.attrs.rel.split(/\s+/).filter(Boolean) : [];
|
|
32592
|
+
const rel = target === "_blank" ? Array.from(/* @__PURE__ */ new Set([...declaredRel, "noopener", "noreferrer"])).join(" ") : declaredRel.join(" ") || void 0;
|
|
32593
|
+
return /* @__PURE__ */ jsx("a", { href, rel, target, children }, nodeKey);
|
|
32594
|
+
}
|
|
32577
32595
|
default:
|
|
32578
32596
|
return children;
|
|
32579
32597
|
}
|
|
32580
32598
|
}, text);
|
|
32581
|
-
var
|
|
32599
|
+
var resolveViewerUrl = (urlPolicy, value, context) => typeof value === "string" ? urlPolicy(value, context) ?? void 0 : void 0;
|
|
32600
|
+
var viewerTextAlignStyle = (node) => {
|
|
32601
|
+
const textAlign = node.attrs?.textAlign;
|
|
32602
|
+
return typeof textAlign === "string" && ["left", "center", "right", "justify"].includes(textAlign) ? { textAlign } : void 0;
|
|
32603
|
+
};
|
|
32604
|
+
var positiveIntegerAttribute = (value) => typeof value === "number" && Number.isInteger(value) && value > 0 ? value : void 0;
|
|
32605
|
+
var renderHeadingViewer = (node, children) => {
|
|
32606
|
+
const props = { style: viewerTextAlignStyle(node) };
|
|
32607
|
+
const level = typeof node.attrs?.level === "number" ? Math.min(Math.max(node.attrs.level, 1), 6) : 2;
|
|
32608
|
+
switch (level) {
|
|
32609
|
+
case 1:
|
|
32610
|
+
return /* @__PURE__ */ jsx("h1", { ...props, children });
|
|
32611
|
+
case 2:
|
|
32612
|
+
return /* @__PURE__ */ jsx("h2", { ...props, children });
|
|
32613
|
+
case 3:
|
|
32614
|
+
return /* @__PURE__ */ jsx("h3", { ...props, children });
|
|
32615
|
+
case 4:
|
|
32616
|
+
return /* @__PURE__ */ jsx("h4", { ...props, children });
|
|
32617
|
+
case 5:
|
|
32618
|
+
return /* @__PURE__ */ jsx("h5", { ...props, children });
|
|
32619
|
+
default:
|
|
32620
|
+
return /* @__PURE__ */ jsx("h6", { ...props, children });
|
|
32621
|
+
}
|
|
32622
|
+
};
|
|
32623
|
+
var BUILT_IN_VIEWER_RENDERERS = {
|
|
32624
|
+
paragraph: ({ node, children }) => /* @__PURE__ */ jsx("p", { style: viewerTextAlignStyle(node), children: node.content?.length ? children : /* @__PURE__ */ jsx("br", {}) }),
|
|
32625
|
+
heading: ({ node, children }) => renderHeadingViewer(node, children),
|
|
32626
|
+
bulletList: ({ children }) => /* @__PURE__ */ jsx("ul", { children }),
|
|
32627
|
+
orderedList: ({ node, children }) => /* @__PURE__ */ jsx("ol", { start: positiveIntegerAttribute(node.attrs?.start), children }),
|
|
32628
|
+
taskList: ({ children }) => /* @__PURE__ */ jsx("ul", { className: "oe-task-list", "data-openeditor-task-list": true, "data-type": "taskList", children }),
|
|
32629
|
+
blockquote: ({ children }) => /* @__PURE__ */ jsx("blockquote", { children }),
|
|
32630
|
+
codeBlock: ({ node }) => {
|
|
32631
|
+
const language = typeof node.attrs?.language === "string" && node.attrs.language.trim() ? node.attrs.language.trim() : null;
|
|
32632
|
+
return /* @__PURE__ */ jsx("pre", { children: /* @__PURE__ */ jsx("code", { className: language ? `language-${language}` : void 0, children: getNodeText(node) }) });
|
|
32633
|
+
},
|
|
32634
|
+
divider: () => /* @__PURE__ */ jsx("hr", {}),
|
|
32635
|
+
image: ({ node, resolveUrl }) => {
|
|
32636
|
+
const src = resolveUrl(node.attrs?.src, "image");
|
|
32637
|
+
if (!src) return null;
|
|
32638
|
+
const dimension = (value) => typeof value === "string" || typeof value === "number" ? value : void 0;
|
|
32639
|
+
return /* @__PURE__ */ jsx("img", { alt: typeof node.attrs?.alt === "string" ? node.attrs.alt : "", height: dimension(node.attrs?.height), src, title: typeof node.attrs?.title === "string" ? node.attrs.title : void 0, width: dimension(node.attrs?.width) });
|
|
32640
|
+
},
|
|
32641
|
+
columns: ({ node, children }) => {
|
|
32642
|
+
const columnCount = Math.min(Math.max(node.content?.length ?? 0, 2), 4);
|
|
32643
|
+
return /* @__PURE__ */ jsx("section", { className: "oe-columns", "data-openeditor-columns": node.content?.length ?? 0, style: { "--oe-column-count": columnCount }, children });
|
|
32644
|
+
},
|
|
32645
|
+
table: ({ children }) => /* @__PURE__ */ jsx("table", { "data-openeditor-table": "", children: /* @__PURE__ */ jsx("tbody", { children }) }),
|
|
32646
|
+
toggleList: ({ children }) => /* @__PURE__ */ jsx("ul", { className: "oe-toggle-list", "data-openeditor-toggle-list": true, children }),
|
|
32647
|
+
callout: ({ node, children }) => /* @__PURE__ */ jsxs("aside", { className: "oe-callout", "data-emoji": normalizeEmoji(node.attrs?.emoji, DEFAULT_CALLOUT_EMOJI), "data-openeditor-callout": true, children: [
|
|
32648
|
+
/* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "oe-callout-emoji", children: normalizeEmoji(node.attrs?.emoji, DEFAULT_CALLOUT_EMOJI) }),
|
|
32649
|
+
/* @__PURE__ */ jsx("div", { className: "oe-callout-content", children })
|
|
32650
|
+
] }),
|
|
32651
|
+
diagram: ({ node }) => {
|
|
32652
|
+
const code = typeof node.attrs?.code === "string" ? node.attrs.code : "";
|
|
32653
|
+
return /* @__PURE__ */ jsx("figure", { className: "oe-diagram", "data-openeditor-diagram": true, children: /* @__PURE__ */ jsx(MermaidPreview, { code }) });
|
|
32654
|
+
},
|
|
32655
|
+
page: ({ node, urlPolicy }) => /* @__PURE__ */ jsx(OpenEditorPageViewer, { node, urlPolicy }),
|
|
32656
|
+
attachment: ({ node, urlPolicy }) => /* @__PURE__ */ jsx(OpenEditorAttachmentViewer, { node, urlPolicy })
|
|
32657
|
+
};
|
|
32658
|
+
var STRUCTURAL_VIEWER_RENDERERS = {
|
|
32659
|
+
column: ({ children }) => /* @__PURE__ */ jsx("div", { className: "oe-column", "data-openeditor-column": true, children }),
|
|
32660
|
+
tableRow: ({ children }) => /* @__PURE__ */ jsx("tr", { children }),
|
|
32661
|
+
tableHeader: ({ node, children }) => /* @__PURE__ */ jsx("th", { colSpan: positiveIntegerAttribute(node.attrs?.colspan), rowSpan: positiveIntegerAttribute(node.attrs?.rowspan), scope: typeof node.attrs?.scope === "string" ? node.attrs.scope : void 0, children }),
|
|
32662
|
+
tableCell: ({ node, children }) => /* @__PURE__ */ jsx("td", { colSpan: positiveIntegerAttribute(node.attrs?.colspan), rowSpan: positiveIntegerAttribute(node.attrs?.rowspan), children }),
|
|
32663
|
+
listItem: ({ children }) => /* @__PURE__ */ jsx("li", { children }),
|
|
32664
|
+
taskItem: ({ node, children }) => {
|
|
32665
|
+
const checked = Boolean(node.attrs?.checked);
|
|
32666
|
+
return /* @__PURE__ */ jsxs("li", { className: "oe-task-item", "data-checked": checked ? "true" : "false", "data-openeditor-task-item": true, children: [
|
|
32667
|
+
/* @__PURE__ */ jsx("span", { className: "oe-task-item-checkbox", children: /* @__PURE__ */ jsx(OpenEditorTaskCheckbox, { checked, label: checked ? "Completed task" : "Incomplete task", readOnly: true, tabIndex: -1 }) }),
|
|
32668
|
+
/* @__PURE__ */ jsx("div", { children })
|
|
32669
|
+
] });
|
|
32670
|
+
},
|
|
32671
|
+
toggleListItem: ({ node, children }) => /* @__PURE__ */ jsx(ToggleListViewerItem, { initiallyOpen: node.attrs?.open !== false, children }),
|
|
32672
|
+
hardBreak: () => /* @__PURE__ */ jsx("br", {})
|
|
32673
|
+
};
|
|
32674
|
+
var renderViewerNode = (node, key, renderers, registry = defaultBlockRegistry, urlPolicy = openEditorPublicUrlPolicy) => {
|
|
32582
32675
|
if (node.type === "text") {
|
|
32583
|
-
return renderTextWithMarks(node.text ?? "", node.marks, key);
|
|
32676
|
+
return renderTextWithMarks(node.text ?? "", node.marks, key, urlPolicy);
|
|
32584
32677
|
}
|
|
32585
|
-
const children = node.content?.map((child, index) => renderViewerNode(child, `${key}-${index}`, renderers, registry)) ?? null;
|
|
32678
|
+
const children = node.content?.map((child, index) => renderViewerNode(child, `${key}-${index}`, renderers, registry, urlPolicy)) ?? null;
|
|
32586
32679
|
const blockKey = findBlockSpecForNode(registry, node)?.name ?? node.type;
|
|
32587
|
-
const
|
|
32588
|
-
|
|
32589
|
-
|
|
32590
|
-
|
|
32591
|
-
|
|
32592
|
-
|
|
32593
|
-
|
|
32594
|
-
|
|
32595
|
-
|
|
32596
|
-
|
|
32597
|
-
|
|
32598
|
-
return /* @__PURE__ */ jsx("h1", { children }, key);
|
|
32599
|
-
case 2:
|
|
32600
|
-
return /* @__PURE__ */ jsx("h2", { children }, key);
|
|
32601
|
-
case 3:
|
|
32602
|
-
return /* @__PURE__ */ jsx("h3", { children }, key);
|
|
32603
|
-
case 4:
|
|
32604
|
-
return /* @__PURE__ */ jsx("h4", { children }, key);
|
|
32605
|
-
case 5:
|
|
32606
|
-
return /* @__PURE__ */ jsx("h5", { children }, key);
|
|
32607
|
-
default:
|
|
32608
|
-
return /* @__PURE__ */ jsx("h6", { children }, key);
|
|
32609
|
-
}
|
|
32610
|
-
}
|
|
32611
|
-
case "bulletList":
|
|
32612
|
-
return /* @__PURE__ */ jsx("ul", { children }, key);
|
|
32613
|
-
case "orderedList":
|
|
32614
|
-
return /* @__PURE__ */ jsx("ol", { children }, key);
|
|
32615
|
-
case "taskList":
|
|
32616
|
-
return /* @__PURE__ */ jsx("ul", { "data-type": "taskList", children }, key);
|
|
32617
|
-
case "blockquote":
|
|
32618
|
-
return /* @__PURE__ */ jsx("blockquote", { children }, key);
|
|
32619
|
-
case "codeBlock":
|
|
32620
|
-
return /* @__PURE__ */ jsx("pre", { children: /* @__PURE__ */ jsx("code", { children: node.content?.map((child) => child.text ?? "").join("") }) }, key);
|
|
32621
|
-
case "divider":
|
|
32622
|
-
return /* @__PURE__ */ jsx("hr", {}, key);
|
|
32623
|
-
case "image":
|
|
32624
|
-
return /* @__PURE__ */ jsx("img", { src: typeof node.attrs?.src === "string" ? node.attrs.src : "", alt: typeof node.attrs?.alt === "string" ? node.attrs.alt : "" }, key);
|
|
32625
|
-
case "columns":
|
|
32626
|
-
return /* @__PURE__ */ jsx("section", { children }, key);
|
|
32627
|
-
case "table":
|
|
32628
|
-
return /* @__PURE__ */ jsx("table", { "data-openeditor-table": "", children: /* @__PURE__ */ jsx("tbody", { children }) }, key);
|
|
32629
|
-
case "toggleList":
|
|
32630
|
-
return /* @__PURE__ */ jsx("ul", { "data-openeditor-toggle-list": true, children }, key);
|
|
32631
|
-
case "callout":
|
|
32632
|
-
return /* @__PURE__ */ jsxs("aside", { className: "oe-callout", children: [
|
|
32633
|
-
/* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "oe-callout-emoji", children: normalizeEmoji(node.attrs?.emoji, DEFAULT_CALLOUT_EMOJI) }),
|
|
32634
|
-
/* @__PURE__ */ jsx("div", { className: "oe-callout-content", children })
|
|
32635
|
-
] }, key);
|
|
32636
|
-
case "diagram": {
|
|
32637
|
-
const code = typeof node.attrs?.code === "string" ? node.attrs.code : "";
|
|
32638
|
-
return /* @__PURE__ */ jsx("figure", { className: "oe-diagram", children: /* @__PURE__ */ jsx(MermaidPreview, { code }) }, key);
|
|
32639
|
-
}
|
|
32640
|
-
case "page":
|
|
32641
|
-
return /* @__PURE__ */ jsx(OpenEditorPageViewer, { node }, key);
|
|
32642
|
-
case "attachment":
|
|
32643
|
-
return /* @__PURE__ */ jsx(OpenEditorAttachmentViewer, { node }, key);
|
|
32644
|
-
default:
|
|
32645
|
-
if (node.type === "column") {
|
|
32646
|
-
return /* @__PURE__ */ jsx("div", { children }, key);
|
|
32647
|
-
}
|
|
32648
|
-
if (node.type === "tableRow") {
|
|
32649
|
-
return /* @__PURE__ */ jsx("tr", { children }, key);
|
|
32650
|
-
}
|
|
32651
|
-
if (node.type === "tableHeader") {
|
|
32652
|
-
return /* @__PURE__ */ jsx("th", { children }, key);
|
|
32653
|
-
}
|
|
32654
|
-
if (node.type === "tableCell") {
|
|
32655
|
-
return /* @__PURE__ */ jsx("td", { children }, key);
|
|
32656
|
-
}
|
|
32657
|
-
if (node.type === "listItem") {
|
|
32658
|
-
return /* @__PURE__ */ jsx("li", { children }, key);
|
|
32659
|
-
}
|
|
32660
|
-
if (node.type === "taskItem") {
|
|
32661
|
-
const checked = Boolean(node.attrs?.checked);
|
|
32662
|
-
return /* @__PURE__ */ jsxs("li", { "data-checked": checked ? "true" : "false", children: [
|
|
32663
|
-
/* @__PURE__ */ jsx("span", { className: "oe-task-item-checkbox", "aria-hidden": "true", children: /* @__PURE__ */ jsx(OpenEditorTaskCheckbox, { checked, label: "Task item checkbox", readOnly: true, tabIndex: -1 }) }),
|
|
32664
|
-
/* @__PURE__ */ jsx("div", { children })
|
|
32665
|
-
] }, key);
|
|
32666
|
-
}
|
|
32667
|
-
if (node.type === "toggleListItem") {
|
|
32668
|
-
return /* @__PURE__ */ jsx(ToggleListViewerItem, { initiallyOpen: node.attrs?.open !== false, children }, key);
|
|
32669
|
-
}
|
|
32670
|
-
if (node.type === "hardBreak") {
|
|
32671
|
-
return /* @__PURE__ */ jsx("br", {}, key);
|
|
32672
|
-
}
|
|
32673
|
-
return /* @__PURE__ */ jsx("div", { children }, key);
|
|
32674
|
-
}
|
|
32680
|
+
const builtInRenderer = Object.hasOwn(BUILT_IN_VIEWER_RENDERERS, blockKey) ? BUILT_IN_VIEWER_RENDERERS[blockKey] : void 0;
|
|
32681
|
+
const renderer = renderers?.[blockKey] ?? builtInRenderer ?? STRUCTURAL_VIEWER_RENDERERS[node.type];
|
|
32682
|
+
const context = {
|
|
32683
|
+
node,
|
|
32684
|
+
children,
|
|
32685
|
+
urlPolicy,
|
|
32686
|
+
resolveUrl: (value, urlContext) => resolveViewerUrl(urlPolicy, value, urlContext)
|
|
32687
|
+
};
|
|
32688
|
+
if (!renderer) return /* @__PURE__ */ jsx("div", { "data-openeditor-unknown-node": node.type, children }, key);
|
|
32689
|
+
const Renderer = renderer;
|
|
32690
|
+
return /* @__PURE__ */ createElement(Renderer, { ...context, key });
|
|
32675
32691
|
};
|
|
32676
32692
|
var OpenEditorContent = ({
|
|
32677
32693
|
controller,
|
|
@@ -32686,15 +32702,16 @@ var OpenEditorViewer = ({
|
|
|
32686
32702
|
renderers,
|
|
32687
32703
|
extensions = EMPTY_EXTENSIONS,
|
|
32688
32704
|
pageRuntime,
|
|
32689
|
-
attachmentRuntime
|
|
32705
|
+
attachmentRuntime,
|
|
32706
|
+
urlPolicy = openEditorPublicUrlPolicy
|
|
32690
32707
|
}) => {
|
|
32691
|
-
const registry = createReactBlockRegistry(extensions);
|
|
32692
|
-
const
|
|
32693
|
-
extensions.map((extension) => [extension.block.name, extension.viewer])
|
|
32694
|
-
|
|
32695
|
-
|
|
32708
|
+
const registry = useMemo(() => createReactBlockRegistry(extensions), [extensions]);
|
|
32709
|
+
const mergedRenderers = useMemo(() => ({
|
|
32710
|
+
...Object.fromEntries(extensions.map((extension) => [extension.block.name, extension.viewer])),
|
|
32711
|
+
...renderers
|
|
32712
|
+
}), [extensions, renderers]);
|
|
32696
32713
|
return /* @__PURE__ */ jsx(OpenEditorAttachmentRuntimeContext.Provider, { value: attachmentRuntime, children: /* @__PURE__ */ jsx(OpenEditorPageRuntimeContext.Provider, { value: pageRuntime, children: /* @__PURE__ */ jsx("div", { className, children: normalizeDocument(document2).content.map(
|
|
32697
|
-
(node, index) => renderViewerNode(node, `viewer-${index}`, mergedRenderers, registry)
|
|
32714
|
+
(node, index) => renderViewerNode(node, `viewer-${index}`, mergedRenderers, registry, urlPolicy)
|
|
32698
32715
|
) }) }) });
|
|
32699
32716
|
};
|
|
32700
32717
|
var useOpenEditor = useOpenEditorController;
|