canopycms 0.0.68-int.104 → 0.0.68-int.105

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.
@@ -64,7 +64,7 @@ function unsupportedJsx(mdx, node) {
64
64
  }
65
65
  // Blocks that run into the block before them in a list item, by that block's type.
66
66
  const RUNS_INTO = {
67
- paragraph: ['paragraph', 'thematicBreak'],
67
+ paragraph: ['thematicBreak'],
68
68
  blockquote: ['paragraph', 'blockquote', 'table'],
69
69
  table: ['paragraph', 'table'],
70
70
  };
@@ -75,15 +75,11 @@ const BLOCK_NAMES = {
75
75
  thematicBreak: 'horizontal rule',
76
76
  };
77
77
  /**
78
- * Why MDXEditor would change what this list item or quote says, or null. Its import joins a list
79
- * item's adjacent paragraphs and moves a nested list into a new item after its own, so what follows
80
- * the list comes out before it; its export writes an item tight, so `---` underlines a paragraph and
81
- * a line continues a quote or table (`RUNS_INTO`); and 3.53 exports a quote as one paragraph.
78
+ * Why MDXEditor would change what this list item says, or null. Its import moves a nested list into
79
+ * a new item after its own, so what follows the list comes out before it; its export writes an item
80
+ * tight, so `---` underlines a paragraph and a line continues a quote or table (`RUNS_INTO`).
82
81
  */
83
82
  function rearrangedBlocks(node) {
84
- if (node.type === 'blockquote') {
85
- return node.children.length > 1 ? 'a quote with more than one paragraph or block' : null;
86
- }
87
83
  if (node.type !== 'listItem')
88
84
  return null;
89
85
  for (let i = 1; i < node.children.length; i++) {
@@ -92,20 +88,57 @@ function rearrangedBlocks(node) {
92
88
  if (before === 'list')
93
89
  return 'a list item with content after its nested list';
94
90
  if (RUNS_INTO[before]?.includes(after)) {
95
- return before === 'paragraph' && after === 'paragraph'
96
- ? 'a list item with more than one paragraph'
97
- : `a list item with a ${BLOCK_NAMES[before] ?? before} followed by a ${BLOCK_NAMES[after] ?? after}`;
91
+ return `a list item with a ${BLOCK_NAMES[before] ?? before} followed by a ${BLOCK_NAMES[after] ?? after}`;
98
92
  }
99
93
  }
100
94
  return null;
101
95
  }
96
+ const isLexicalParent = (node) => 'getChildren' in node && typeof node.getChildren === 'function';
97
+ const hasBlockChild = (node) => isLexicalParent(node) && node.getChildren().some((child) => !child.isInline());
98
+ // Writes `node`'s children into `target`: a run of inline children as one paragraph, a block as itself.
99
+ function writeBlocks(node, target, actions) {
100
+ let paragraph = null;
101
+ for (const child of isLexicalParent(node) ? node.getChildren() : []) {
102
+ if (!child.isInline()) {
103
+ paragraph = null;
104
+ actions.visit(child, target);
105
+ continue;
106
+ }
107
+ if (paragraph === null) {
108
+ const created = { type: 'paragraph', children: [] };
109
+ actions.appendToParent(target, created);
110
+ paragraph = created;
111
+ }
112
+ actions.visit(child, paragraph);
113
+ }
114
+ }
115
+ // MDXEditor 4 exports each child of a quote as a block, so inline children the toolbar's Quote,
116
+ // the `> ` shortcut or a merge put in a quote would come out one paragraph each.
117
+ const quoteExportVisitor = {
118
+ priority: 100,
119
+ testLexicalNode: (node) => node.getType() === 'quote',
120
+ visitLexicalNode({ lexicalNode, mdastParent, actions }) {
121
+ const quote = { type: 'blockquote', children: [] };
122
+ actions.appendToParent(mdastParent, quote);
123
+ writeBlocks(lexicalNode, quote, actions);
124
+ },
125
+ };
126
+ // Backspace at the start of a quote moves its paragraphs and lists into one paragraph, which
127
+ // MDXEditor would export as one run of text; they are written as the blocks they show.
128
+ const paragraphOfBlocksExportVisitor = {
129
+ priority: 100,
130
+ testLexicalNode: (node) => node.getType() === 'paragraph' && hasBlockChild(node),
131
+ visitLexicalNode({ lexicalNode, mdastParent, actions }) {
132
+ writeBlocks(lexicalNode, mdastParent, actions);
133
+ },
134
+ };
102
135
  /** Typed `string` because the mdast node union this package resolves omits the ESM node. */
103
136
  const ESM_NODE_TYPE = 'mdxjsEsm';
104
137
  /** Node types inside a table, imported by its cell editors rather than by visitors. */
105
138
  const CONSUMED_BY_PARENT_VISITOR = new Set(['tableRow', 'tableCell']);
106
139
  /** Creates the JSX plugins from MDXEditor's exports; call once per load of MDXEditor. */
107
140
  export function createMdxJsxPlugins(mdx) {
108
- const { NestedLexicalEditor, UnrecognizedMarkdownConstructError, addImportVisitor$, importVisitors$, isMdastJsxNode, jsxPlugin, realmPlugin, } = mdx;
141
+ const { NestedLexicalEditor, UnrecognizedMarkdownConstructError, addExportVisitor$, addImportVisitor$, importVisitors$, isMdastJsxNode, jsxPlugin, realmPlugin, } = mdx;
109
142
  /**
110
143
  * Shows an element's tag and attributes read-only (they are edited as source)
111
144
  * and edits its children, inline or block per the parsed node: one `*`
@@ -142,7 +175,6 @@ export function createMdxJsxPlugins(mdx) {
142
175
  testNode: (node) => node.type === ESM_NODE_TYPE ||
143
176
  node.type === 'table' ||
144
177
  node.type === 'listItem' ||
145
- node.type === 'blockquote' ||
146
178
  isMdastJsxNode(node),
147
179
  visitNode({ mdastNode, descriptors, actions }) {
148
180
  if (mdastNode.type === ESM_NODE_TYPE) {
@@ -155,7 +187,7 @@ export function createMdxJsxPlugins(mdx) {
155
187
  }
156
188
  };
157
189
  reject(mdastNode);
158
- if (mdastNode.type === 'listItem' || mdastNode.type === 'blockquote') {
190
+ if (mdastNode.type === 'listItem') {
159
191
  actions.nextVisitor();
160
192
  return;
161
193
  }
@@ -180,8 +212,14 @@ export function createMdxJsxPlugins(mdx) {
180
212
  realm.pub(addImportVisitor$, guard);
181
213
  },
182
214
  });
215
+ const blockExportPlugin = realmPlugin({
216
+ init(realm) {
217
+ realm.pub(addExportVisitor$, [quoteExportVisitor, paragraphOfBlocksExportVisitor]);
218
+ },
219
+ });
183
220
  return () => [
184
221
  jsxPlugin({ jsxComponentDescriptors: [catchAllJsxDescriptor] }),
185
222
  roundTripGuardPlugin(),
223
+ blockExportPlugin(),
186
224
  ];
187
225
  }
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const CANOPYCMS_VERSION = "0.0.68-int.104";
1
+ export declare const CANOPYCMS_VERSION = "0.0.68-int.105";
package/dist/version.js CHANGED
@@ -1,3 +1,3 @@
1
1
  // Generated by scripts/bump-version.mjs, its only writer. The package builds with plain
2
2
  // tsc (rootDir: src), so src/ cannot import ../package.json; version.test.ts keeps the two equal.
3
- export const CANOPYCMS_VERSION = '0.0.68-int.104';
3
+ export const CANOPYCMS_VERSION = '0.0.68-int.105';
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "//": "@codemirror/language, @lezer/highlight: workaround — @mdxeditor/editor uses cm6-theme-basic-light which peer-requires these but mdxeditor doesn't declare them as dependencies",
3
3
  "name": "canopycms",
4
- "version": "0.0.68-int.104",
4
+ "version": "0.0.68-int.105",
5
5
  "description": "CanopyCMS core package: schema-driven content, branch-aware editing, and editor UI for Next.js.",
6
6
  "license": "MIT",
7
7
  "repository": {
@@ -114,7 +114,7 @@
114
114
  "@mantine/hooks": "^7.14.2",
115
115
  "@mantine/modals": "^7.14.2",
116
116
  "@mantine/notifications": "^7.14.2",
117
- "@mdxeditor/editor": "^3.52.4",
117
+ "@mdxeditor/editor": "^4.3.2",
118
118
  "@octokit/auth-token": "^4.0.0",
119
119
  "@octokit/plugin-throttling": "^8.2.0",
120
120
  "@octokit/rest": "^20.0.2",