@moxn/kb-migrate 0.4.16 → 0.4.18
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/targets/notion.js +59 -19
- package/package.json +1 -1
package/dist/targets/notion.js
CHANGED
|
@@ -187,19 +187,54 @@ async function uploadFileToNotion(client, url, filename, contentType) {
|
|
|
187
187
|
/**
|
|
188
188
|
* Post-process martian output to reconstruct callout blocks.
|
|
189
189
|
*
|
|
190
|
-
* Callouts
|
|
191
|
-
*
|
|
192
|
-
*
|
|
190
|
+
* Callouts may arrive as either:
|
|
191
|
+
* 1. `quote` blocks (when stored with `> ` prefix) — text in children[0].paragraph.rich_text
|
|
192
|
+
* 2. `paragraph` blocks (when stored as plain text) — text in paragraph.rich_text
|
|
193
|
+
*
|
|
194
|
+
* In both cases, if the first text segment starts with an emoji followed by a space,
|
|
195
|
+
* the block is converted to a native Notion `callout` with that emoji as the icon.
|
|
193
196
|
*/
|
|
194
197
|
function reconstructCallouts(blocks) {
|
|
195
198
|
const emojiPrefixRe = /^(\p{Emoji_Presentation}|\p{Emoji}\uFE0F?)\s/u;
|
|
199
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
196
200
|
return blocks.map((block) => {
|
|
201
|
+
// Case 1: paragraph with emoji prefix
|
|
202
|
+
if (block.type === 'paragraph') {
|
|
203
|
+
const richText = block.paragraph?.rich_text;
|
|
204
|
+
if (!richText?.length)
|
|
205
|
+
return block;
|
|
206
|
+
const firstSegment = richText[0];
|
|
207
|
+
if (firstSegment.type !== 'text' || !firstSegment.text?.content)
|
|
208
|
+
return block;
|
|
209
|
+
const match = firstSegment.text.content.match(emojiPrefixRe);
|
|
210
|
+
if (!match)
|
|
211
|
+
return block;
|
|
212
|
+
const emoji = match[1];
|
|
213
|
+
const strippedContent = firstSegment.text.content.slice(match[0].length);
|
|
214
|
+
const calloutRichText = [
|
|
215
|
+
{ ...firstSegment, text: { ...firstSegment.text, content: strippedContent } },
|
|
216
|
+
...richText.slice(1),
|
|
217
|
+
].filter((rt) => rt.text?.content);
|
|
218
|
+
return {
|
|
219
|
+
object: 'block',
|
|
220
|
+
type: 'callout',
|
|
221
|
+
callout: {
|
|
222
|
+
rich_text: calloutRichText,
|
|
223
|
+
icon: { type: 'emoji', emoji },
|
|
224
|
+
color: 'default',
|
|
225
|
+
},
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
// Case 2: quote with emoji prefix in first child paragraph
|
|
197
229
|
if (block.type !== 'quote')
|
|
198
230
|
return block;
|
|
199
|
-
const
|
|
200
|
-
if (!
|
|
231
|
+
const children = block.quote?.children;
|
|
232
|
+
if (!children?.length)
|
|
233
|
+
return block;
|
|
234
|
+
const firstChild = children[0];
|
|
235
|
+
if (firstChild.type !== 'paragraph' || !firstChild.paragraph?.rich_text?.length)
|
|
201
236
|
return block;
|
|
202
|
-
const firstSegment =
|
|
237
|
+
const firstSegment = firstChild.paragraph.rich_text[0];
|
|
203
238
|
if (firstSegment.type !== 'text' || !firstSegment.text?.content)
|
|
204
239
|
return block;
|
|
205
240
|
const match = firstSegment.text.content.match(emojiPrefixRe);
|
|
@@ -207,22 +242,19 @@ function reconstructCallouts(blocks) {
|
|
|
207
242
|
return block;
|
|
208
243
|
const emoji = match[1];
|
|
209
244
|
const strippedContent = firstSegment.text.content.slice(match[0].length);
|
|
210
|
-
const
|
|
211
|
-
{ ...firstSegment, text: { ...firstSegment.text, content: strippedContent }
|
|
212
|
-
...
|
|
213
|
-
].filter((rt) =>
|
|
214
|
-
|
|
215
|
-
return seg.text?.content || seg.plain_text;
|
|
216
|
-
});
|
|
217
|
-
const quoteBlock = block;
|
|
245
|
+
const calloutRichText = [
|
|
246
|
+
{ ...firstSegment, text: { ...firstSegment.text, content: strippedContent } },
|
|
247
|
+
...firstChild.paragraph.rich_text.slice(1),
|
|
248
|
+
].filter((rt) => rt.text?.content);
|
|
249
|
+
const remainingChildren = children.slice(1);
|
|
218
250
|
return {
|
|
219
251
|
object: 'block',
|
|
220
252
|
type: 'callout',
|
|
221
253
|
callout: {
|
|
222
|
-
rich_text:
|
|
254
|
+
rich_text: calloutRichText,
|
|
223
255
|
icon: { type: 'emoji', emoji },
|
|
224
256
|
color: 'default',
|
|
225
|
-
...(
|
|
257
|
+
...(remainingChildren.length > 0 ? { children: remainingChildren } : {}),
|
|
226
258
|
},
|
|
227
259
|
};
|
|
228
260
|
});
|
|
@@ -265,14 +297,22 @@ async function sectionsToNotionBlocks(sections, options) {
|
|
|
265
297
|
allReferences.push(...references);
|
|
266
298
|
}
|
|
267
299
|
text = stripInvalidLinks(text);
|
|
268
|
-
// Split on standalone --- dividers and emit native Notion divider blocks
|
|
269
|
-
|
|
300
|
+
// Split on standalone --- dividers and emit native Notion divider blocks.
|
|
301
|
+
// Pad with newlines so --- at start/end of text is also caught.
|
|
302
|
+
const padded = '\n' + text + '\n';
|
|
303
|
+
const rawParts = padded.split('\n---\n');
|
|
304
|
+
const parts = rawParts.map((p, idx) => {
|
|
305
|
+
if (idx === 0)
|
|
306
|
+
p = p.slice(1); // remove leading \n pad
|
|
307
|
+
if (idx === rawParts.length - 1)
|
|
308
|
+
p = p.slice(0, -1); // remove trailing \n pad
|
|
309
|
+
return p;
|
|
310
|
+
});
|
|
270
311
|
for (let i = 0; i < parts.length; i++) {
|
|
271
312
|
if (i > 0) {
|
|
272
313
|
flushText();
|
|
273
314
|
allBlocks.push({ object: 'block', type: 'divider', divider: {} });
|
|
274
315
|
}
|
|
275
|
-
// Handle --- at start (empty first part) or end (empty last part)
|
|
276
316
|
const part = parts[i];
|
|
277
317
|
if (part.trim()) {
|
|
278
318
|
pendingMarkdown.push(part);
|
package/package.json
CHANGED