@openeditor/react 0.0.21 → 0.0.23

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 CHANGED
@@ -66,6 +66,7 @@ Keep the extension array referentially stable. Defining extensions at module
66
66
  scope, as below, is the simplest option.
67
67
 
68
68
  ```tsx
69
+ import { PackageOpen } from "lucide-react";
69
70
  import {
70
71
  defineOpenEditorReactNode,
71
72
  NodeViewWrapper,
@@ -113,7 +114,11 @@ const productCard = defineOpenEditorReactNode({
113
114
  ],
114
115
  },
115
116
  component: ProductCardNode,
116
- slashMenu: { keywords: ["product", "commerce", "card"] },
117
+ slashMenu: {
118
+ icon: PackageOpen,
119
+ keywords: ["product", "commerce", "card"],
120
+ order: 250,
121
+ },
117
122
  viewer: ({ node }) => <article>{String(node.attrs?.title ?? "")}</article>,
118
123
  exporters: {
119
124
  html: {
@@ -138,6 +143,14 @@ Pass the same extension array to `OpenEditorViewer`. The controller automaticall
138
143
  uses registered exporters for `controller.exports` and registered blocks appear
139
144
  in `getDefaultSlashMenuItems`, which powers `@openeditor/ui`.
140
145
 
146
+ Slash-menu entries accept an optional React `icon` component and numeric `order`.
147
+ OpenEditor assigns built-in entries orders in increments of 100, so consumer
148
+ blocks can be placed between them (for example, `order: 250` appears between
149
+ Heading 1 and Heading 2). Entries with the same order retain their declaration
150
+ order, and entries without an order appear after ordered entries. The same fields
151
+ are available on direct `slashMenuItems`; a menu's explicit `items` prop is also
152
+ sorted by this rule.
153
+
141
154
  Block `name` is the stable public identifier. Namespace consumer blocks to avoid
142
155
  collisions. `nodeType` is the serialized ProseMirror node type and defaults to
143
156
  `name`; use an identifier without punctuation when interoperability requires it.
package/dist/index.d.ts CHANGED
@@ -142,11 +142,20 @@ type SelectionBubbleState = {
142
142
  top: number;
143
143
  anchorRect?: OpenEditorOverlayRect;
144
144
  };
145
+ type OpenEditorSlashMenuIconProps = {
146
+ "aria-hidden"?: boolean;
147
+ className?: string;
148
+ size?: number;
149
+ strokeWidth?: number;
150
+ };
151
+ type OpenEditorSlashMenuIcon = (props: OpenEditorSlashMenuIconProps) => ReactNode;
145
152
  type OpenEditorSlashMenuItem = {
146
153
  key: string;
147
154
  label: string;
148
155
  group: string;
156
+ icon?: OpenEditorSlashMenuIcon;
149
157
  keywords?: readonly string[];
158
+ order?: number;
150
159
  execute: (context: {
151
160
  controller: OpenEditorController;
152
161
  range: InsertRange;
@@ -159,7 +168,9 @@ type OpenEditorViewerRenderer = (context: {
159
168
  type OpenEditorExtensionMenu = {
160
169
  label?: string;
161
170
  group?: string;
171
+ icon?: OpenEditorSlashMenuIcon;
162
172
  keywords?: readonly string[];
173
+ order?: number;
163
174
  };
164
175
  /**
165
176
  * Complete web integration for a consumer-owned block. The portable `block`
@@ -197,9 +208,10 @@ declare module "@tiptap/core" {
197
208
  }
198
209
  declare const formatAttachmentSize: (size: number | null) => string;
199
210
  declare const useOpenEditorController: ({ initialDocument, editable, placeholder, onChange, onFilePasteDrop, pageRuntime, attachmentRuntime, slashMenuItems, extensions, blockActions, }?: OpenEditorReactProps) => OpenEditorController;
211
+ declare const sortSlashMenuItems: (items: readonly OpenEditorSlashMenuItem[]) => OpenEditorSlashMenuItem[];
200
212
  declare const getDefaultSlashMenuItems: (controller: OpenEditorController) => OpenEditorSlashMenuItem[];
201
213
  declare const OpenEditorContent: ({ controller, className, }: OpenEditorContentProps) => react.JSX.Element;
202
214
  declare const OpenEditorViewer: ({ document, className, renderers, extensions, pageRuntime, attachmentRuntime, }: OpenEditorViewerProps) => react.JSX.Element;
203
215
  declare const useOpenEditor: ({ initialDocument, editable, placeholder, onChange, onFilePasteDrop, pageRuntime, attachmentRuntime, slashMenuItems, extensions, blockActions, }?: OpenEditorReactProps) => OpenEditorController;
204
216
 
205
- export { type InsertRange, type OpenEditorBlockAction, type OpenEditorBlockActionContext, type OpenEditorBlockTarget, OpenEditorContent, type OpenEditorContentProps, type OpenEditorController, type OpenEditorExtensionMenu, type OpenEditorOverlayRect, type OpenEditorPageRuntime, type OpenEditorPageSnapshot, type OpenEditorReactConfig, type OpenEditorReactExtension, type OpenEditorReactNodeExtension, type OpenEditorReactProps, type OpenEditorSlashMenuItem, type OpenEditorTheme, OpenEditorThemeProvider, type OpenEditorThemeStyle, type OpenEditorThemeToken, OpenEditorViewer, type OpenEditorViewerProps, type OpenEditorViewerRenderer, type SelectionBubbleState, type SlashState, createOpenEditorThemeStyle, defineOpenEditorReactExtension, defineOpenEditorReactNode, formatAttachmentSize, getDefaultSlashMenuItems, openEditorThemeTokenNames, useOpenEditor, useOpenEditorController, useOpenEditorThemeStyle };
217
+ export { type InsertRange, type OpenEditorBlockAction, type OpenEditorBlockActionContext, type OpenEditorBlockTarget, OpenEditorContent, type OpenEditorContentProps, type OpenEditorController, type OpenEditorExtensionMenu, type OpenEditorOverlayRect, type OpenEditorPageRuntime, type OpenEditorPageSnapshot, 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, getDefaultSlashMenuItems, openEditorThemeTokenNames, sortSlashMenuItems, useOpenEditor, useOpenEditorController, useOpenEditorThemeStyle };
package/dist/index.js CHANGED
@@ -32904,15 +32904,34 @@ var pageSnapshotFromNode = (node) => {
32904
32904
  href: typeof node.attrs?.href === "string" ? node.attrs.href : null
32905
32905
  };
32906
32906
  };
32907
+ var useResolvedPageSnapshot = (cached, runtime) => {
32908
+ const [resolved, setResolved] = useState(null);
32909
+ useEffect(() => {
32910
+ let active = true;
32911
+ setResolved(null);
32912
+ if (cached?.pageId && runtime?.resolvePage) {
32913
+ void runtime.resolvePage(cached.pageId).then((page) => {
32914
+ if (active && page) setResolved(page);
32915
+ }).catch(() => {
32916
+ if (active) setResolved(null);
32917
+ });
32918
+ }
32919
+ return () => {
32920
+ active = false;
32921
+ };
32922
+ }, [cached?.pageId, runtime]);
32923
+ return [resolved ?? cached, setResolved];
32924
+ };
32907
32925
  var OpenEditorPageNodeView = ({ editor, node, updateAttributes: updateAttributes2 }) => {
32908
32926
  const runtime = use(OpenEditorPageRuntimeContext);
32909
32927
  const themeStyle = useOpenEditorThemeStyle();
32910
32928
  const [creating, setCreating] = useState(false);
32911
32929
  const [error, setError] = useState(null);
32912
- const snapshot = pageSnapshotFromNode(node);
32913
- const icon = normalizeEmoji(node.attrs.icon, DEFAULT_PAGE_EMOJI);
32930
+ const cached = pageSnapshotFromNode(node);
32931
+ const [page, setResolvedPage] = useResolvedPageSnapshot(cached, runtime);
32932
+ const icon = normalizeEmoji(page?.icon, DEFAULT_PAGE_EMOJI);
32914
32933
  const openPage = () => {
32915
- if (snapshot) void runtime?.openPage?.(snapshot);
32934
+ if (page) void runtime?.openPage?.(page);
32916
32935
  };
32917
32936
  const createPage = async () => {
32918
32937
  if (!runtime?.createPage || creating) return;
@@ -32929,10 +32948,11 @@ var OpenEditorPageNodeView = ({ editor, node, updateAttributes: updateAttributes
32929
32948
  };
32930
32949
  const updateIcon = async (nextIcon) => {
32931
32950
  updateAttributes2({ icon: nextIcon });
32932
- if (!snapshot?.pageId || !runtime?.updatePageIcon) return;
32951
+ if (!page?.pageId || !runtime?.updatePageIcon) return;
32952
+ setResolvedPage({ ...page, icon: nextIcon });
32933
32953
  setError(null);
32934
32954
  try {
32935
- await runtime.updatePageIcon(snapshot.pageId, nextIcon);
32955
+ await runtime.updatePageIcon(page.pageId, nextIcon);
32936
32956
  } catch (cause) {
32937
32957
  setError(cause instanceof Error ? cause.message : "Could not update the page icon.");
32938
32958
  }
@@ -32940,22 +32960,22 @@ var OpenEditorPageNodeView = ({ editor, node, updateAttributes: updateAttributes
32940
32960
  return /* @__PURE__ */ jsxs(
32941
32961
  NodeViewWrapper,
32942
32962
  {
32943
- "aria-label": snapshot ? `Open ${snapshot.title}` : void 0,
32963
+ "aria-label": page ? `Open ${page.title}` : void 0,
32944
32964
  className: "oe-page",
32945
- "data-page-id": snapshot?.pageId ?? "",
32965
+ "data-page-id": page?.pageId ?? "",
32946
32966
  onClick: (event) => {
32947
32967
  if (event.target.closest(".oe-emoji-trigger, .oe-emoji-popover")) return;
32948
32968
  openPage();
32949
32969
  },
32950
32970
  onKeyDown: (event) => {
32951
32971
  if (event.target.closest(".oe-emoji-trigger, .oe-emoji-popover")) return;
32952
- if (snapshot && (event.key === "Enter" || event.key === " ")) {
32972
+ if (page && (event.key === "Enter" || event.key === " ")) {
32953
32973
  event.preventDefault();
32954
32974
  openPage();
32955
32975
  }
32956
32976
  },
32957
- role: snapshot ? "button" : void 0,
32958
- tabIndex: snapshot ? 0 : void 0,
32977
+ role: page ? "button" : void 0,
32978
+ tabIndex: page ? 0 : void 0,
32959
32979
  children: [
32960
32980
  /* @__PURE__ */ jsx("span", { contentEditable: false, children: /* @__PURE__ */ jsx(
32961
32981
  OpenEditorEmojiPicker,
@@ -32968,7 +32988,7 @@ var OpenEditorPageNodeView = ({ editor, node, updateAttributes: updateAttributes
32968
32988
  onEmojiSelect: (nextIcon) => void updateIcon(nextIcon)
32969
32989
  }
32970
32990
  ) }),
32971
- /* @__PURE__ */ jsx(
32991
+ page && page !== cached ? /* @__PURE__ */ jsx("div", { className: "oe-page-title", contentEditable: false, children: page.title }) : /* @__PURE__ */ jsx(
32972
32992
  NodeViewContent,
32973
32993
  {
32974
32994
  as: "div",
@@ -32976,7 +32996,7 @@ var OpenEditorPageNodeView = ({ editor, node, updateAttributes: updateAttributes
32976
32996
  contentEditable: false
32977
32997
  }
32978
32998
  ),
32979
- !snapshot && runtime?.createPage && editor.isEditable ? /* @__PURE__ */ jsx(
32999
+ !page && runtime?.createPage && editor.isEditable ? /* @__PURE__ */ jsx(
32980
33000
  "button",
32981
33001
  {
32982
33002
  className: "oe-page-action",
@@ -33015,20 +33035,7 @@ var Page = Node3.create({
33015
33035
  var OpenEditorPageViewer = ({ node }) => {
33016
33036
  const runtime = use(OpenEditorPageRuntimeContext);
33017
33037
  const cached = pageSnapshotFromNode(node);
33018
- const [resolved, setResolved] = useState(null);
33019
- useEffect(() => {
33020
- let active = true;
33021
- setResolved(null);
33022
- if (cached?.pageId && runtime?.resolvePage) {
33023
- void runtime.resolvePage(cached.pageId).then((page2) => {
33024
- if (active && page2) setResolved(page2);
33025
- });
33026
- }
33027
- return () => {
33028
- active = false;
33029
- };
33030
- }, [cached?.pageId, runtime]);
33031
- const page = resolved ?? cached;
33038
+ const [page] = useResolvedPageSnapshot(cached, runtime);
33032
33039
  const title = page?.title ?? (getNodeText(node) || "Untitled");
33033
33040
  const content = /* @__PURE__ */ jsxs(Fragment$1, { children: [
33034
33041
  /* @__PURE__ */ jsx("span", { className: "oe-page-icon", "aria-hidden": "true", children: normalizeEmoji(page?.icon, DEFAULT_PAGE_EMOJI) }),
@@ -33131,7 +33138,7 @@ var createEditorExtensions = (placeholder, customExtensions) => [
33131
33138
  index_default6,
33132
33139
  index_default5,
33133
33140
  index_default10.configure({ types: ["heading", "paragraph"] }),
33134
- index_default4.configure({ placeholder }),
33141
+ index_default4.configure({ placeholder, showOnlyCurrent: true }),
33135
33142
  Columns,
33136
33143
  Column,
33137
33144
  ToggleList,
@@ -33257,7 +33264,7 @@ var escapeClipboardHtml = (value) => value.replaceAll("&", "&amp;").replaceAll("
33257
33264
  var useOpenEditorController = ({
33258
33265
  initialDocument = seedDocument,
33259
33266
  editable = true,
33260
- placeholder = "Start writing...",
33267
+ placeholder = "Type '/' for commands",
33261
33268
  onChange,
33262
33269
  onFilePasteDrop,
33263
33270
  pageRuntime,
@@ -33651,20 +33658,28 @@ var useOpenEditorController = ({
33651
33658
  };
33652
33659
  return controller;
33653
33660
  };
33661
+ var sortSlashMenuItems = (items) => [...items].sort((left, right) => {
33662
+ const leftOrder = typeof left.order === "number" && Number.isFinite(left.order) ? left.order : Number.POSITIVE_INFINITY;
33663
+ const rightOrder = typeof right.order === "number" && Number.isFinite(right.order) ? right.order : Number.POSITIVE_INFINITY;
33664
+ if (leftOrder === rightOrder) return 0;
33665
+ return leftOrder - rightOrder;
33666
+ });
33654
33667
  var getDefaultSlashMenuItems = (controller) => {
33655
33668
  const internalController = controller;
33656
- return [
33657
- ...openEditorInsertPresets.map((preset) => ({
33669
+ return sortSlashMenuItems([
33670
+ ...openEditorInsertPresets.map((preset, index) => ({
33658
33671
  key: preset.key,
33659
33672
  label: preset.label,
33660
33673
  group: preset.group,
33661
33674
  keywords: [preset.key],
33675
+ order: (index + 1) * 100,
33662
33676
  execute: ({ controller: current, range }) => current.insertBlock(preset.key, range)
33663
33677
  })),
33664
33678
  {
33665
33679
  key: "moveBlockUp",
33666
33680
  label: "Move Block Up",
33667
33681
  group: "block",
33682
+ order: (openEditorInsertPresets.length + 1) * 100,
33668
33683
  execute: ({ range }) => {
33669
33684
  if (!internalController.editor) return false;
33670
33685
  internalController.editor.chain().focus().deleteRange(range).run();
@@ -33676,6 +33691,7 @@ var getDefaultSlashMenuItems = (controller) => {
33676
33691
  key: "moveBlockDown",
33677
33692
  label: "Move Block Down",
33678
33693
  group: "block",
33694
+ order: (openEditorInsertPresets.length + 2) * 100,
33679
33695
  execute: ({ range }) => {
33680
33696
  if (!internalController.editor) return false;
33681
33697
  internalController.editor.chain().focus().deleteRange(range).run();
@@ -33689,12 +33705,14 @@ var getDefaultSlashMenuItems = (controller) => {
33689
33705
  key: extension.block.name,
33690
33706
  label: menu?.label ?? extension.block.label,
33691
33707
  group: menu?.group ?? extension.block.group,
33708
+ icon: menu?.icon,
33692
33709
  keywords: menu?.keywords ?? [extension.block.name],
33710
+ order: menu?.order,
33693
33711
  execute: ({ controller: current, range }) => current.insertBlock(extension.block.name, range)
33694
33712
  };
33695
33713
  }),
33696
33714
  ...controller.slashMenuItems
33697
- ];
33715
+ ]);
33698
33716
  };
33699
33717
  var renderTextWithMarks = (text, marks = [], key) => marks.reduceRight((children, mark, index) => {
33700
33718
  const nodeKey = `${key}-${mark.type}-${index}`;
@@ -33836,6 +33854,6 @@ var OpenEditorViewer = ({
33836
33854
  };
33837
33855
  var useOpenEditor = useOpenEditorController;
33838
33856
 
33839
- export { NodeViewContent, NodeViewWrapper, OpenEditorContent, DragHandle2 as OpenEditorDragHandle, OpenEditorThemeProvider, OpenEditorViewer, createOpenEditorThemeStyle, defineOpenEditorReactExtension, defineOpenEditorReactNode, formatAttachmentSize, getDefaultSlashMenuItems, openEditorThemeTokenNames, useOpenEditor, useOpenEditorController, useOpenEditorThemeStyle };
33857
+ export { NodeViewContent, NodeViewWrapper, OpenEditorContent, DragHandle2 as OpenEditorDragHandle, OpenEditorThemeProvider, OpenEditorViewer, createOpenEditorThemeStyle, defineOpenEditorReactExtension, defineOpenEditorReactNode, formatAttachmentSize, getDefaultSlashMenuItems, openEditorThemeTokenNames, sortSlashMenuItems, useOpenEditor, useOpenEditorController, useOpenEditorThemeStyle };
33840
33858
  //# sourceMappingURL=index.js.map
33841
33859
  //# sourceMappingURL=index.js.map