@openeditor/react 0.0.27 → 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/dist/index.js CHANGED
@@ -1,11 +1,12 @@
1
- import { DEFAULT_CALLOUT_EMOJI, normalizeEmoji, DEFAULT_PAGE_EMOJI, normalizeDocument, toProseMirrorDocument, createDocument as createDocument$1, 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, useRef, useEffect, useSyncExternalStore as useSyncExternalStore$1, useCallback, use, useDebugValue, Fragment as Fragment$2, useLayoutEffect } from 'react';
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
- import { toExportBundle } from '@openeditor/exporters';
9
+ import { exportDocumentUnsafe, exportDocument } from '@openeditor/exporters';
9
10
  import { openEditorInsertPresets, resolveOpenEditorInsertBlock, defaultBlockRegistry } from '@openeditor/extensions';
10
11
  export { seedDocument } from '@openeditor/extensions';
11
12
  import ReactDOM, { flushSync, createPortal } from 'react-dom';
@@ -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, (character) => `-${character.toLowerCase()}`)}`;
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 isSafeUrl = (value, allowDataImage) => {
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
- if (value !== null && !isSafeUrl(value, allowDataImage)) {
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 = ({ node }) => {
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
- if (state.phase === "ready" && snapshot.url) return /* @__PURE__ */ jsx("a", { className: "oe-attachment oe-attachment-viewer", href: snapshot.url, rel: "noreferrer", target: "_blank", children: content });
31471
- return /* @__PURE__ */ jsx("div", { className: "oe-attachment oe-attachment-viewer", "data-state": state.phase, children: content });
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 : "";
@@ -31524,11 +31532,12 @@ var OpenEditorPageNodeView = ({ editor, node, updateAttributes: updateAttributes
31524
31532
  };
31525
31533
  const updateIcon = async (nextIcon) => {
31526
31534
  updateAttributes2({ icon: nextIcon });
31527
- if (!page?.pageId || !runtime?.updatePageIcon) return;
31535
+ if (!page?.pageId || !runtime) return;
31528
31536
  setResolvedPage({ ...page, icon: nextIcon });
31529
31537
  setError(null);
31530
31538
  try {
31531
- await runtime.updatePageIcon(page.pageId, nextIcon);
31539
+ const updated = await runtime.updatePage(page.pageId, { icon: nextIcon });
31540
+ if (updated) setResolvedPage(updated);
31532
31541
  } catch (cause) {
31533
31542
  setError(cause instanceof Error ? cause.message : "Could not update the page icon.");
31534
31543
  }
@@ -31557,7 +31566,7 @@ var OpenEditorPageNodeView = ({ editor, node, updateAttributes: updateAttributes
31557
31566
  OpenEditorEmojiPicker,
31558
31567
  {
31559
31568
  className: "oe-page-icon-trigger",
31560
- disabled: !editor.isEditable,
31569
+ disabled: !editor.isEditable || !runtime,
31561
31570
  emoji: icon,
31562
31571
  label: "Change page emoji",
31563
31572
  popupStyle: themeStyle,
@@ -31608,7 +31617,10 @@ var Page = Node3.create({
31608
31617
  return ReactNodeViewRenderer(OpenEditorPageNodeView);
31609
31618
  }
31610
31619
  });
31611
- var OpenEditorPageViewer = ({ node }) => {
31620
+ var OpenEditorPageViewer = ({
31621
+ node,
31622
+ urlPolicy
31623
+ }) => {
31612
31624
  const runtime = use(OpenEditorPageRuntimeContext);
31613
31625
  const cached = pageSnapshotFromNode(node);
31614
31626
  const [page] = useResolvedPageSnapshot(cached, runtime);
@@ -31619,12 +31631,68 @@ var OpenEditorPageViewer = ({ node }) => {
31619
31631
  /* @__PURE__ */ jsx("span", { className: "oe-page-arrow", "aria-hidden": "true", children: "\u2192" })
31620
31632
  ] });
31621
31633
  if (page && runtime?.openPage) {
31622
- 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 });
31623
31635
  }
31624
- if (page?.href) {
31625
- return /* @__PURE__ */ jsx("a", { className: "oe-page oe-page-viewer", href: page.href, children: content });
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 });
31626
31639
  }
31627
- 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 });
31641
+ };
31642
+ var OpenEditorPageHeader = ({
31643
+ page,
31644
+ runtime,
31645
+ className = "oe-page-header",
31646
+ onPageChange
31647
+ }) => {
31648
+ const [title, setTitle] = useState(page.title);
31649
+ const [icon, setIcon] = useState(normalizeEmoji(page.icon, DEFAULT_PAGE_EMOJI));
31650
+ const [error, setError] = useState(null);
31651
+ useEffect(() => setTitle(page.title), [page.pageId, page.title]);
31652
+ useEffect(() => setIcon(normalizeEmoji(page.icon, DEFAULT_PAGE_EMOJI)), [page.icon, page.pageId]);
31653
+ const updatePage = async (update) => {
31654
+ setError(null);
31655
+ try {
31656
+ const resolved = await runtime.updatePage(page.pageId, update);
31657
+ const next = resolved ?? { ...page, ...update };
31658
+ setTitle(next.title);
31659
+ setIcon(normalizeEmoji(next.icon, DEFAULT_PAGE_EMOJI));
31660
+ onPageChange?.(next);
31661
+ } catch (cause) {
31662
+ setError(cause instanceof Error ? cause.message : "Could not update the page.");
31663
+ }
31664
+ };
31665
+ return /* @__PURE__ */ jsxs("header", { className: `oe-page-header${className === "oe-page-header" ? "" : ` ${className}`}`, children: [
31666
+ /* @__PURE__ */ jsx(
31667
+ OpenEditorEmojiPicker,
31668
+ {
31669
+ emoji: icon,
31670
+ label: "Change page icon",
31671
+ onEmojiSelect: (nextIcon) => {
31672
+ setIcon(nextIcon);
31673
+ void updatePage({ icon: nextIcon });
31674
+ }
31675
+ }
31676
+ ),
31677
+ /* @__PURE__ */ jsx(
31678
+ "input",
31679
+ {
31680
+ "aria-label": "Page title",
31681
+ className: "oe-page-header-title",
31682
+ onBlur: () => {
31683
+ const nextTitle = title.trim() || "Untitled";
31684
+ setTitle(nextTitle);
31685
+ if (nextTitle !== page.title) void updatePage({ title: nextTitle });
31686
+ },
31687
+ onChange: (event) => setTitle(event.target.value),
31688
+ onKeyDown: (event) => {
31689
+ if (event.key === "Enter") event.currentTarget.blur();
31690
+ },
31691
+ value: title
31692
+ }
31693
+ ),
31694
+ error ? /* @__PURE__ */ jsx("span", { className: "oe-page-error", role: "alert", children: error }) : null
31695
+ ] });
31628
31696
  };
31629
31697
  var MermaidPreview = ({ code }) => {
31630
31698
  const rendered = useMemo(() => {
@@ -31652,7 +31720,7 @@ var MermaidPreview = ({ code }) => {
31652
31720
  };
31653
31721
  var ToggleListViewerItem = ({ children, initiallyOpen }) => {
31654
31722
  const [open, setOpen] = useState(initiallyOpen);
31655
- 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: [
31656
31724
  /* @__PURE__ */ jsx(
31657
31725
  "button",
31658
31726
  {
@@ -31698,7 +31766,7 @@ var MermaidDiagram = Node3.create({
31698
31766
  return ReactNodeViewRenderer(OpenEditorDiagramNodeView);
31699
31767
  }
31700
31768
  });
31701
- var createEditorExtensions = (placeholder, customExtensions) => [
31769
+ var createEditorExtensions = (getPlaceholder, customExtensions) => [
31702
31770
  index_default12.configure({
31703
31771
  attributeName: OPENEDITOR_BLOCK_ID_ATTR,
31704
31772
  types: "all",
@@ -31719,7 +31787,7 @@ var createEditorExtensions = (placeholder, customExtensions) => [
31719
31787
  index_default6,
31720
31788
  index_default5,
31721
31789
  index_default10.configure({ types: ["heading", "paragraph"] }),
31722
- index_default4.configure({ placeholder, showOnlyCurrent: true }),
31790
+ index_default4.configure({ placeholder: () => getPlaceholder(), showOnlyCurrent: true }),
31723
31791
  Columns,
31724
31792
  Column,
31725
31793
  ToggleList,
@@ -31825,12 +31893,12 @@ var useOpenEditorController = ({
31825
31893
  onFilePasteDrop,
31826
31894
  pageRuntime,
31827
31895
  attachmentRuntime,
31896
+ enabledBlocks,
31828
31897
  slashMenuItems = [],
31829
31898
  extensions = EMPTY_EXTENSIONS,
31830
31899
  blockActions = []
31831
31900
  } = {}) => {
31832
31901
  const [document2, setDocument] = useState(() => normalizeDocument(initialDocument));
31833
- const [initialEditorContent] = useState(() => toProseMirrorDocument(document2));
31834
31902
  const [selectedBlock, setSelectedBlock] = useState("paragraph");
31835
31903
  const [slashState, setSlashState] = useState(null);
31836
31904
  const [selectionBubbleState, setSelectionBubbleState] = useState(null);
@@ -31839,12 +31907,29 @@ var useOpenEditorController = ({
31839
31907
  const slashStateRef = useRef(null);
31840
31908
  const selectionBubbleStateRef = useRef(null);
31841
31909
  const documentMetaRef = useRef(document2.meta);
31910
+ const placeholderRef = useRef(placeholder);
31911
+ placeholderRef.current = placeholder;
31912
+ const onChangeRef = useRef(onChange);
31913
+ onChangeRef.current = onChange;
31914
+ const onFilePasteDropRef = useRef(onFilePasteDrop);
31915
+ onFilePasteDropRef.current = onFilePasteDrop;
31916
+ const isBlockEnabled = (blockName) => {
31917
+ const canonicalName = /^heading[1-6]$/.test(blockName) ? "heading" : blockName;
31918
+ if (!isOpenEditorBlockEnabled(canonicalName, { enabledBlocks })) return false;
31919
+ if (canonicalName === "attachment") {
31920
+ return Boolean(attachmentRuntime?.selectAttachment && attachmentRuntime.uploadAttachment);
31921
+ }
31922
+ if (canonicalName === "page") return Boolean(pageRuntime?.createPage);
31923
+ return true;
31924
+ };
31925
+ const isBlockEnabledRef = useRef(isBlockEnabled);
31926
+ isBlockEnabledRef.current = isBlockEnabled;
31842
31927
  const registry = useMemo(() => createReactBlockRegistry(extensions), [extensions]);
31843
31928
  const exporters = useMemo(() => createReactExporters(extensions), [extensions]);
31844
31929
  const editor = useEditor({
31845
31930
  immediatelyRender: false,
31846
- extensions: createEditorExtensions(placeholder, extensions),
31847
- content: initialEditorContent,
31931
+ extensions: createEditorExtensions(() => placeholderRef.current, extensions),
31932
+ content: toProseMirrorDocument(document2),
31848
31933
  editable,
31849
31934
  editorProps: {
31850
31935
  attributes: {
@@ -31861,6 +31946,12 @@ var useOpenEditorController = ({
31861
31946
  try {
31862
31947
  const payload = JSON.parse(encoded);
31863
31948
  if (payload.format === "openeditor-blocks" && payload.version === 1 && Array.isArray(payload.blocks)) {
31949
+ const firstBlock = payload.blocks[0];
31950
+ const blockName = firstBlock ? findBlockSpecForNode(registry, firstBlock)?.name : void 0;
31951
+ if (blockName && !isBlockEnabledRef.current(blockName)) {
31952
+ event.preventDefault();
31953
+ return true;
31954
+ }
31864
31955
  const nodes = payload.blocks.map(
31865
31956
  (node) => view.state.schema.nodeFromJSON(regenerateClipboardNodeIds(node))
31866
31957
  );
@@ -31877,7 +31968,7 @@ var useOpenEditorController = ({
31877
31968
  }
31878
31969
  }
31879
31970
  const file = event.clipboardData?.files?.[0];
31880
- if (!file || !onFilePasteDrop?.(file)) {
31971
+ if (!file || !isBlockEnabledRef.current("attachment") || !onFilePasteDropRef.current?.(file)) {
31881
31972
  return false;
31882
31973
  }
31883
31974
  event.preventDefault();
@@ -31885,7 +31976,7 @@ var useOpenEditorController = ({
31885
31976
  },
31886
31977
  handleDrop: (_view, event, _slice, moved) => {
31887
31978
  const file = event.dataTransfer?.files?.[0];
31888
- if (moved || !file || !onFilePasteDrop?.(file)) {
31979
+ if (moved || !file || !isBlockEnabledRef.current("attachment") || !onFilePasteDropRef.current?.(file)) {
31889
31980
  return false;
31890
31981
  }
31891
31982
  event.preventDefault();
@@ -31907,7 +31998,7 @@ var useOpenEditorController = ({
31907
31998
  documentMetaRef.current
31908
31999
  );
31909
32000
  setDocument(nextDocument);
31910
- onChange?.(nextDocument);
32001
+ onChangeRef.current?.(nextDocument);
31911
32002
  setSlashState(getSlashState(currentEditor));
31912
32003
  setSelectionBubbleState(
31913
32004
  isPointerSelectingRef.current ? null : getSelectionBubbleState(currentEditor)
@@ -31923,10 +32014,12 @@ var useOpenEditorController = ({
31923
32014
  }
31924
32015
  }
31925
32016
  }, [extensions]);
31926
- const exports = useMemo(
31927
- () => toExportBundle(document2, exporters),
31928
- [document2, exporters]
31929
- );
32017
+ useEffect(() => {
32018
+ editor?.setEditable(editable, false);
32019
+ }, [editable, editor]);
32020
+ useEffect(() => {
32021
+ if (editor && !editor.isDestroyed) editor.view.dispatch(editor.state.tr);
32022
+ }, [editor, placeholder]);
31930
32023
  useEffect(() => {
31931
32024
  slashStateRef.current = slashState;
31932
32025
  }, [slashState]);
@@ -31975,7 +32068,7 @@ var useOpenEditorController = ({
31975
32068
  };
31976
32069
  }, [editor]);
31977
32070
  const insertBlock = (blockName = selectedBlock, range) => {
31978
- if (!editor) {
32071
+ if (!editor || !isBlockEnabled(blockName)) {
31979
32072
  return false;
31980
32073
  }
31981
32074
  const block = resolveOpenEditorInsertBlock(blockName, registry);
@@ -31985,14 +32078,14 @@ var useOpenEditorController = ({
31985
32078
  return range ? commandChain.deleteRange(range) : commandChain;
31986
32079
  };
31987
32080
  if (blockName === "page" && pageRuntime?.createPage) {
31988
- const prepared = chain().run();
31989
- if (!prepared) return false;
31990
32081
  setSlashState(null);
31991
32082
  void pageRuntime.createPage({
31992
32083
  title: "Untitled",
31993
32084
  icon: DEFAULT_PAGE_EMOJI
31994
32085
  }).then((created) => {
31995
- editor.chain().focus().insertContent({
32086
+ const insertion = editor.chain().focus();
32087
+ const prepared = range ? insertion.deleteRange(range) : insertion;
32088
+ prepared.insertContent({
31996
32089
  ...block,
31997
32090
  attrs: {
31998
32091
  ...block.attrs,
@@ -32002,9 +32095,7 @@ var useOpenEditorController = ({
32002
32095
  },
32003
32096
  content: [{ type: "text", text: created.title || "Untitled" }]
32004
32097
  }).run();
32005
- }).catch(() => {
32006
- editor.chain().focus().insertContent(block).run();
32007
- });
32098
+ }).catch(() => void 0);
32008
32099
  return true;
32009
32100
  }
32010
32101
  const inserted = chain().insertContent(block).run();
@@ -32017,7 +32108,9 @@ var useOpenEditorController = ({
32017
32108
  }
32018
32109
  const normalized = normalizeDocument(nextDocument);
32019
32110
  documentMetaRef.current = normalized.meta;
32020
- editor.commands.setContent(toProseMirrorDocument(normalized));
32111
+ editor.commands.setContent(toProseMirrorDocument(normalized), { emitUpdate: false });
32112
+ setDocument(normalized);
32113
+ onChangeRef.current?.(normalized);
32021
32114
  };
32022
32115
  const resolveBlock = (block) => block ?? (editor ? selectedBlockRef(editor, registry) : null);
32023
32116
  const resolveBlockAt = (position) => editor ? blockRefAtPosition(editor, position, registry) : null;
@@ -32091,10 +32184,10 @@ var useOpenEditorController = ({
32091
32184
  editor.chain().focus().extendMarkRange("link").setLink({ href: url }).run();
32092
32185
  };
32093
32186
  const setParagraph = () => {
32094
- editor?.chain().focus().setParagraph().run();
32187
+ if (isBlockEnabled("paragraph")) editor?.chain().focus().setParagraph().run();
32095
32188
  };
32096
32189
  const toggleHeading = (level) => {
32097
- editor?.chain().focus().toggleHeading({ level }).run();
32190
+ if (isBlockEnabled("heading")) editor?.chain().focus().toggleHeading({ level }).run();
32098
32191
  };
32099
32192
  const toggleBold = () => {
32100
32193
  editor?.chain().focus().toggleBold().run();
@@ -32112,19 +32205,19 @@ var useOpenEditorController = ({
32112
32205
  editor?.chain().focus().toggleCode().run();
32113
32206
  };
32114
32207
  const toggleBulletList = () => {
32115
- editor?.chain().focus().toggleBulletList().run();
32208
+ if (isBlockEnabled("bulletList")) editor?.chain().focus().toggleBulletList().run();
32116
32209
  };
32117
32210
  const toggleOrderedList = () => {
32118
- editor?.chain().focus().toggleOrderedList().run();
32211
+ if (isBlockEnabled("orderedList")) editor?.chain().focus().toggleOrderedList().run();
32119
32212
  };
32120
32213
  const toggleTaskList = () => {
32121
- editor?.chain().focus().toggleTaskList().run();
32214
+ if (isBlockEnabled("taskList")) editor?.chain().focus().toggleTaskList().run();
32122
32215
  };
32123
32216
  const toggleBlockquote = () => {
32124
- editor?.chain().focus().toggleBlockquote().run();
32217
+ if (isBlockEnabled("blockquote")) editor?.chain().focus().toggleBlockquote().run();
32125
32218
  };
32126
32219
  const toggleCodeBlock = () => {
32127
- editor?.chain().focus().toggleCodeBlock().run();
32220
+ if (isBlockEnabled("codeBlock")) editor?.chain().focus().toggleCodeBlock().run();
32128
32221
  };
32129
32222
  const undo2 = () => {
32130
32223
  editor?.chain().focus().undo().run();
@@ -32135,7 +32228,8 @@ var useOpenEditorController = ({
32135
32228
  const controller = {
32136
32229
  document: document2,
32137
32230
  editor,
32138
- exports,
32231
+ export: (format) => exportDocument(document2, format),
32232
+ exportUnsafe: (format) => exportDocumentUnsafe(document2, format, exporters),
32139
32233
  ready: editor !== null,
32140
32234
  editable,
32141
32235
  placeholder,
@@ -32158,6 +32252,7 @@ var useOpenEditorController = ({
32158
32252
  undo: undo2,
32159
32253
  redo: redo2,
32160
32254
  insertBlock,
32255
+ isBlockEnabled,
32161
32256
  setLink,
32162
32257
  getActiveLink: () => editor?.getAttributes("link").href,
32163
32258
  selectBlock,
@@ -32178,6 +32273,7 @@ var useOpenEditorController = ({
32178
32273
  extensions,
32179
32274
  pageRuntime,
32180
32275
  attachmentRuntime,
32276
+ enabledBlocks,
32181
32277
  slashState,
32182
32278
  selectionBubbleState
32183
32279
  };
@@ -32402,9 +32498,9 @@ var sortMenuItems = (items) => [...items].sort((left, right) => {
32402
32498
  });
32403
32499
  var sortSlashMenuItems = (items) => sortMenuItems(items);
32404
32500
  var sortBlockPickerItems = (items) => sortMenuItems(items);
32405
- var getExtensionInsertMenu = (extension) => extension.insertMenu ?? extension.slashMenu;
32501
+ var getExtensionInsertMenu = (extension) => extension.insertMenu;
32406
32502
  var getDefaultBlockPickerItems = (controller) => sortBlockPickerItems([
32407
- ...openEditorInsertPresets.map((preset, index) => ({
32503
+ ...openEditorInsertPresets.filter((preset) => controller.isBlockEnabled(preset.key)).map((preset, index) => ({
32408
32504
  key: preset.key,
32409
32505
  label: preset.label,
32410
32506
  group: preset.group,
@@ -32412,7 +32508,7 @@ var getDefaultBlockPickerItems = (controller) => sortBlockPickerItems([
32412
32508
  order: (index + 1) * 100,
32413
32509
  insert: () => controller.insertBlock(preset.key)
32414
32510
  })),
32415
- ...controller.extensions.filter((extension) => getExtensionInsertMenu(extension) !== false).map((extension) => {
32511
+ ...controller.extensions.filter((extension) => getExtensionInsertMenu(extension) !== false && controller.isBlockEnabled(extension.block.name)).map((extension) => {
32416
32512
  const menu = getExtensionInsertMenu(extension) || void 0;
32417
32513
  return {
32418
32514
  key: extension.block.name,
@@ -32428,7 +32524,7 @@ var getDefaultBlockPickerItems = (controller) => sortBlockPickerItems([
32428
32524
  var getDefaultSlashMenuItems = (controller) => {
32429
32525
  const internalController = controller;
32430
32526
  return sortSlashMenuItems([
32431
- ...openEditorInsertPresets.map((preset, index) => ({
32527
+ ...openEditorInsertPresets.filter((preset) => controller.isBlockEnabled(preset.key)).map((preset, index) => ({
32432
32528
  key: preset.key,
32433
32529
  label: preset.label,
32434
32530
  group: preset.group,
@@ -32460,7 +32556,7 @@ var getDefaultSlashMenuItems = (controller) => {
32460
32556
  return true;
32461
32557
  }
32462
32558
  },
32463
- ...controller.extensions.filter((extension) => getExtensionInsertMenu(extension) !== false).map((extension) => {
32559
+ ...controller.extensions.filter((extension) => getExtensionInsertMenu(extension) !== false && controller.isBlockEnabled(extension.block.name)).map((extension) => {
32464
32560
  const menu = getExtensionInsertMenu(extension) || void 0;
32465
32561
  return {
32466
32562
  key: extension.block.name,
@@ -32475,7 +32571,7 @@ var getDefaultSlashMenuItems = (controller) => {
32475
32571
  ...controller.slashMenuItems
32476
32572
  ]);
32477
32573
  };
32478
- var renderTextWithMarks = (text, marks = [], key) => marks.reduceRight((children, mark, index) => {
32574
+ var renderTextWithMarks = (text, marks = [], key, urlPolicy) => marks.reduceRight((children, mark, index) => {
32479
32575
  const nodeKey = `${key}-${mark.type}-${index}`;
32480
32576
  switch (mark.type) {
32481
32577
  case "bold":
@@ -32488,106 +32584,110 @@ var renderTextWithMarks = (text, marks = [], key) => marks.reduceRight((children
32488
32584
  return /* @__PURE__ */ jsx("s", { children }, nodeKey);
32489
32585
  case "code":
32490
32586
  return /* @__PURE__ */ jsx("code", { children }, nodeKey);
32491
- case "link":
32492
- return /* @__PURE__ */ jsx("a", { href: typeof mark.attrs?.href === "string" ? mark.attrs.href : void 0, children }, nodeKey);
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
+ }
32493
32595
  default:
32494
32596
  return children;
32495
32597
  }
32496
32598
  }, text);
32497
- var renderViewerNode = (node, key, renderers, registry = defaultBlockRegistry) => {
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) => {
32498
32675
  if (node.type === "text") {
32499
- return renderTextWithMarks(node.text ?? "", node.marks, key);
32676
+ return renderTextWithMarks(node.text ?? "", node.marks, key, urlPolicy);
32500
32677
  }
32501
- 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;
32502
32679
  const blockKey = findBlockSpecForNode(registry, node)?.name ?? node.type;
32503
- const customRenderer = renderers?.[blockKey];
32504
- if (customRenderer) {
32505
- return /* @__PURE__ */ jsx(Fragment$2, { children: customRenderer({ node, children }) }, key);
32506
- }
32507
- switch (blockKey) {
32508
- case "paragraph":
32509
- return /* @__PURE__ */ jsx("p", { children: node.content?.length ? children : /* @__PURE__ */ jsx("br", {}) }, key);
32510
- case "heading": {
32511
- const level = typeof node.attrs?.level === "number" ? Math.min(Math.max(node.attrs.level, 1), 6) : 2;
32512
- switch (level) {
32513
- case 1:
32514
- return /* @__PURE__ */ jsx("h1", { children }, key);
32515
- case 2:
32516
- return /* @__PURE__ */ jsx("h2", { children }, key);
32517
- case 3:
32518
- return /* @__PURE__ */ jsx("h3", { children }, key);
32519
- case 4:
32520
- return /* @__PURE__ */ jsx("h4", { children }, key);
32521
- case 5:
32522
- return /* @__PURE__ */ jsx("h5", { children }, key);
32523
- default:
32524
- return /* @__PURE__ */ jsx("h6", { children }, key);
32525
- }
32526
- }
32527
- case "bulletList":
32528
- return /* @__PURE__ */ jsx("ul", { children }, key);
32529
- case "orderedList":
32530
- return /* @__PURE__ */ jsx("ol", { children }, key);
32531
- case "taskList":
32532
- return /* @__PURE__ */ jsx("ul", { "data-type": "taskList", children }, key);
32533
- case "blockquote":
32534
- return /* @__PURE__ */ jsx("blockquote", { children }, key);
32535
- case "codeBlock":
32536
- return /* @__PURE__ */ jsx("pre", { children: /* @__PURE__ */ jsx("code", { children: node.content?.map((child) => child.text ?? "").join("") }) }, key);
32537
- case "divider":
32538
- return /* @__PURE__ */ jsx("hr", {}, key);
32539
- case "image":
32540
- return /* @__PURE__ */ jsx("img", { src: typeof node.attrs?.src === "string" ? node.attrs.src : "", alt: typeof node.attrs?.alt === "string" ? node.attrs.alt : "" }, key);
32541
- case "columns":
32542
- return /* @__PURE__ */ jsx("section", { children }, key);
32543
- case "table":
32544
- return /* @__PURE__ */ jsx("table", { "data-openeditor-table": "", children: /* @__PURE__ */ jsx("tbody", { children }) }, key);
32545
- case "toggleList":
32546
- return /* @__PURE__ */ jsx("ul", { "data-openeditor-toggle-list": true, children }, key);
32547
- case "callout":
32548
- return /* @__PURE__ */ jsxs("aside", { className: "oe-callout", children: [
32549
- /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "oe-callout-emoji", children: normalizeEmoji(node.attrs?.emoji, DEFAULT_CALLOUT_EMOJI) }),
32550
- /* @__PURE__ */ jsx("div", { className: "oe-callout-content", children })
32551
- ] }, key);
32552
- case "diagram": {
32553
- const code = typeof node.attrs?.code === "string" ? node.attrs.code : "";
32554
- return /* @__PURE__ */ jsx("figure", { className: "oe-diagram", children: /* @__PURE__ */ jsx(MermaidPreview, { code }) }, key);
32555
- }
32556
- case "page":
32557
- return /* @__PURE__ */ jsx(OpenEditorPageViewer, { node }, key);
32558
- case "attachment":
32559
- return /* @__PURE__ */ jsx(OpenEditorAttachmentViewer, { node }, key);
32560
- default:
32561
- if (node.type === "column") {
32562
- return /* @__PURE__ */ jsx("div", { children }, key);
32563
- }
32564
- if (node.type === "tableRow") {
32565
- return /* @__PURE__ */ jsx("tr", { children }, key);
32566
- }
32567
- if (node.type === "tableHeader") {
32568
- return /* @__PURE__ */ jsx("th", { children }, key);
32569
- }
32570
- if (node.type === "tableCell") {
32571
- return /* @__PURE__ */ jsx("td", { children }, key);
32572
- }
32573
- if (node.type === "listItem") {
32574
- return /* @__PURE__ */ jsx("li", { children }, key);
32575
- }
32576
- if (node.type === "taskItem") {
32577
- const checked = Boolean(node.attrs?.checked);
32578
- return /* @__PURE__ */ jsxs("li", { "data-checked": checked ? "true" : "false", children: [
32579
- /* @__PURE__ */ jsx("span", { className: "oe-task-item-checkbox", "aria-hidden": "true", children: /* @__PURE__ */ jsx(OpenEditorTaskCheckbox, { checked, label: "Task item checkbox", readOnly: true, tabIndex: -1 }) }),
32580
- /* @__PURE__ */ jsx("div", { children })
32581
- ] }, key);
32582
- }
32583
- if (node.type === "toggleListItem") {
32584
- return /* @__PURE__ */ jsx(ToggleListViewerItem, { initiallyOpen: node.attrs?.open !== false, children }, key);
32585
- }
32586
- if (node.type === "hardBreak") {
32587
- return /* @__PURE__ */ jsx("br", {}, key);
32588
- }
32589
- return /* @__PURE__ */ jsx("div", { children }, key);
32590
- }
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 });
32591
32691
  };
32592
32692
  var OpenEditorContent = ({
32593
32693
  controller,
@@ -32602,19 +32702,20 @@ var OpenEditorViewer = ({
32602
32702
  renderers,
32603
32703
  extensions = EMPTY_EXTENSIONS,
32604
32704
  pageRuntime,
32605
- attachmentRuntime
32705
+ attachmentRuntime,
32706
+ urlPolicy = openEditorPublicUrlPolicy
32606
32707
  }) => {
32607
- const registry = createReactBlockRegistry(extensions);
32608
- const extensionRenderers = Object.fromEntries(
32609
- extensions.map((extension) => [extension.block.name, extension.viewer])
32610
- );
32611
- const mergedRenderers = { ...extensionRenderers, ...renderers };
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]);
32612
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(
32613
- (node, index) => renderViewerNode(node, `viewer-${index}`, mergedRenderers, registry)
32714
+ (node, index) => renderViewerNode(node, `viewer-${index}`, mergedRenderers, registry, urlPolicy)
32614
32715
  ) }) }) });
32615
32716
  };
32616
32717
  var useOpenEditor = useOpenEditorController;
32617
32718
 
32618
- export { NodeViewContent, NodeViewWrapper, OpenEditorBlockDragHandle, OpenEditorContent, OpenEditorThemeProvider, OpenEditorViewer, createOpenEditorThemeStyle, defineOpenEditorReactExtension, defineOpenEditorReactNode, formatAttachmentSize, getDefaultBlockPickerItems, getDefaultSlashMenuItems, openEditorThemeTokenNames, sortBlockPickerItems, sortSlashMenuItems, useOpenEditor, useOpenEditorBlockInteraction, useOpenEditorController, useOpenEditorThemeStyle };
32719
+ export { NodeViewContent, NodeViewWrapper, OpenEditorBlockDragHandle, OpenEditorContent, OpenEditorPageHeader, OpenEditorThemeProvider, OpenEditorViewer, createOpenEditorThemeStyle, defineOpenEditorReactExtension, defineOpenEditorReactNode, formatAttachmentSize, getDefaultBlockPickerItems, getDefaultSlashMenuItems, openEditorThemeTokenNames, sortBlockPickerItems, sortSlashMenuItems, useOpenEditor, useOpenEditorBlockInteraction, useOpenEditorController, useOpenEditorThemeStyle };
32619
32720
  //# sourceMappingURL=index.js.map
32620
32721
  //# sourceMappingURL=index.js.map