@moxn/kb-migrate 0.4.16 → 0.4.17

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.
@@ -188,41 +188,47 @@ async function uploadFileToNotion(client, url, filename, contentType) {
188
188
  * Post-process martian output to reconstruct callout blocks.
189
189
  *
190
190
  * Callouts are stored as blockquotes with an emoji prefix (e.g., `> 💡 tip text`).
191
- * Martian converts these to Notion `quote` blocks. This function detects the emoji
192
- * prefix pattern and converts matching quotes back to native Notion `callout` blocks.
191
+ * Martian converts these to Notion `quote` blocks with the actual text inside
192
+ * `children[].paragraph.rich_text` (not the top-level `rich_text`).
193
+ * This function detects the emoji prefix pattern in the first child paragraph
194
+ * and converts matching quotes to native Notion `callout` blocks.
193
195
  */
194
196
  function reconstructCallouts(blocks) {
195
197
  const emojiPrefixRe = /^(\p{Emoji_Presentation}|\p{Emoji}\uFE0F?)\s/u;
198
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
196
199
  return blocks.map((block) => {
197
200
  if (block.type !== 'quote')
198
201
  return block;
199
- const richText = block.quote?.rich_text;
200
- if (!richText?.length)
202
+ const children = block.quote?.children;
203
+ if (!children?.length)
201
204
  return block;
202
- const firstSegment = richText[0];
205
+ // Find the first child paragraph with text
206
+ const firstChild = children[0];
207
+ if (firstChild.type !== 'paragraph' || !firstChild.paragraph?.rich_text?.length)
208
+ return block;
209
+ const firstSegment = firstChild.paragraph.rich_text[0];
203
210
  if (firstSegment.type !== 'text' || !firstSegment.text?.content)
204
211
  return block;
205
212
  const match = firstSegment.text.content.match(emojiPrefixRe);
206
213
  if (!match)
207
- return block;
214
+ return block; // No emoji prefix — keep as regular quote
208
215
  const emoji = match[1];
209
216
  const strippedContent = firstSegment.text.content.slice(match[0].length);
210
- const updatedRichText = [
211
- { ...firstSegment, text: { ...firstSegment.text, content: strippedContent }, plain_text: strippedContent },
212
- ...richText.slice(1),
213
- ].filter((rt) => {
214
- const seg = rt;
215
- return seg.text?.content || seg.plain_text;
216
- });
217
- const quoteBlock = block;
217
+ // Build the callout's rich_text from the first child paragraph (stripped of emoji)
218
+ const calloutRichText = [
219
+ { ...firstSegment, text: { ...firstSegment.text, content: strippedContent } },
220
+ ...firstChild.paragraph.rich_text.slice(1),
221
+ ].filter((rt) => rt.text?.content);
222
+ // Remaining children become the callout's children
223
+ const remainingChildren = children.slice(1);
218
224
  return {
219
225
  object: 'block',
220
226
  type: 'callout',
221
227
  callout: {
222
- rich_text: updatedRichText,
228
+ rich_text: calloutRichText,
223
229
  icon: { type: 'emoji', emoji },
224
230
  color: 'default',
225
- ...(quoteBlock.quote?.children ? { children: quoteBlock.quote.children } : {}),
231
+ ...(remainingChildren.length > 0 ? { children: remainingChildren } : {}),
226
232
  },
227
233
  };
228
234
  });
@@ -265,14 +271,22 @@ async function sectionsToNotionBlocks(sections, options) {
265
271
  allReferences.push(...references);
266
272
  }
267
273
  text = stripInvalidLinks(text);
268
- // Split on standalone --- dividers and emit native Notion divider blocks
269
- const parts = text.split(/\n---\n/);
274
+ // Split on standalone --- dividers and emit native Notion divider blocks.
275
+ // Pad with newlines so --- at start/end of text is also caught.
276
+ const padded = '\n' + text + '\n';
277
+ const rawParts = padded.split('\n---\n');
278
+ const parts = rawParts.map((p, idx) => {
279
+ if (idx === 0)
280
+ p = p.slice(1); // remove leading \n pad
281
+ if (idx === rawParts.length - 1)
282
+ p = p.slice(0, -1); // remove trailing \n pad
283
+ return p;
284
+ });
270
285
  for (let i = 0; i < parts.length; i++) {
271
286
  if (i > 0) {
272
287
  flushText();
273
288
  allBlocks.push({ object: 'block', type: 'divider', divider: {} });
274
289
  }
275
- // Handle --- at start (empty first part) or end (empty last part)
276
290
  const part = parts[i];
277
291
  if (part.trim()) {
278
292
  pendingMarkdown.push(part);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moxn/kb-migrate",
3
- "version": "0.4.16",
3
+ "version": "0.4.17",
4
4
  "description": "Migration tool for importing documents into Moxn Knowledge Base from local files, Notion, Google Docs, and more",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",