@moxn/kb-migrate 0.4.10 → 0.4.12
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 +68 -15
- package/package.json +1 -1
package/dist/targets/notion.js
CHANGED
|
@@ -62,17 +62,11 @@ function stripInvalidLinks(text) {
|
|
|
62
62
|
return displayText;
|
|
63
63
|
});
|
|
64
64
|
}
|
|
65
|
-
/**
|
|
66
|
-
* Convert a KB document's sections into a single markdown string.
|
|
67
|
-
* Section names become H2 headings (mirrors the import convention).
|
|
68
|
-
*
|
|
69
|
-
* If extractReferences is true, also extracts KB path references from
|
|
70
|
-
* the content and returns them separately (for two-pass export).
|
|
71
|
-
*/
|
|
72
65
|
function sectionsToMarkdown(sections, options) {
|
|
73
66
|
const parts = [];
|
|
74
67
|
const allReferences = [];
|
|
75
68
|
const databaseIds = [];
|
|
69
|
+
const media = [];
|
|
76
70
|
const extractRefs = options?.extractReferences ?? false;
|
|
77
71
|
for (const section of sections) {
|
|
78
72
|
parts.push(`## ${section.name}\n`);
|
|
@@ -91,15 +85,21 @@ function sectionsToMarkdown(sections, options) {
|
|
|
91
85
|
parts.push('');
|
|
92
86
|
}
|
|
93
87
|
else if (block.blockType === 'image' && block.url) {
|
|
94
|
-
|
|
88
|
+
const token = `MOXNMEDIA${media.length}PLACEHOLDER`;
|
|
89
|
+
media.push({ token, type: 'image', url: block.url, alt: block.alt });
|
|
90
|
+
parts.push(token);
|
|
95
91
|
parts.push('');
|
|
96
92
|
}
|
|
97
93
|
else if (block.blockType === 'document' && block.url) {
|
|
98
|
-
|
|
94
|
+
const token = `MOXNMEDIA${media.length}PLACEHOLDER`;
|
|
95
|
+
media.push({ token, type: 'file', url: block.url, filename: block.filename });
|
|
96
|
+
parts.push(token);
|
|
99
97
|
parts.push('');
|
|
100
98
|
}
|
|
101
99
|
else if (block.blockType === 'csv' && block.url) {
|
|
102
|
-
|
|
100
|
+
const token = `MOXNMEDIA${media.length}PLACEHOLDER`;
|
|
101
|
+
media.push({ token, type: 'embed', url: block.url, filename: block.filename || 'data.csv' });
|
|
102
|
+
parts.push(token);
|
|
103
103
|
parts.push('');
|
|
104
104
|
}
|
|
105
105
|
else if (block.blockType === 'database_embed' && block.databaseId) {
|
|
@@ -111,7 +111,60 @@ function sectionsToMarkdown(sections, options) {
|
|
|
111
111
|
}
|
|
112
112
|
}
|
|
113
113
|
}
|
|
114
|
-
return { markdown: parts.join('\n').trim(), references: allReferences, databaseIds };
|
|
114
|
+
return { markdown: parts.join('\n').trim(), references: allReferences, databaseIds, media };
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Replace media placeholder paragraphs in Notion blocks with proper
|
|
118
|
+
* image/file/embed blocks. Martian doesn't support images, so we
|
|
119
|
+
* post-process the converted blocks.
|
|
120
|
+
*/
|
|
121
|
+
function injectMediaBlocks(blocks, media) {
|
|
122
|
+
if (media.length === 0)
|
|
123
|
+
return blocks;
|
|
124
|
+
// Build a lookup from token to media info
|
|
125
|
+
const tokenMap = new Map(media.map((m) => [m.token, m]));
|
|
126
|
+
return blocks.map((block) => {
|
|
127
|
+
// Check if this is a paragraph containing a media placeholder
|
|
128
|
+
const b = block;
|
|
129
|
+
if (b.type !== 'paragraph' || !b.paragraph?.rich_text)
|
|
130
|
+
return block;
|
|
131
|
+
const text = b.paragraph.rich_text.map((rt) => rt.text?.content ?? '').join('').trim();
|
|
132
|
+
const mediaInfo = tokenMap.get(text);
|
|
133
|
+
if (!mediaInfo)
|
|
134
|
+
return block;
|
|
135
|
+
// Replace with proper Notion block
|
|
136
|
+
if (mediaInfo.type === 'image') {
|
|
137
|
+
return {
|
|
138
|
+
object: 'block',
|
|
139
|
+
type: 'image',
|
|
140
|
+
image: {
|
|
141
|
+
type: 'external',
|
|
142
|
+
external: { url: mediaInfo.url },
|
|
143
|
+
...(mediaInfo.alt ? { caption: [{ type: 'text', text: { content: mediaInfo.alt } }] } : {}),
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
if (mediaInfo.type === 'file') {
|
|
148
|
+
return {
|
|
149
|
+
object: 'block',
|
|
150
|
+
type: 'file',
|
|
151
|
+
file: {
|
|
152
|
+
type: 'external',
|
|
153
|
+
external: { url: mediaInfo.url },
|
|
154
|
+
caption: [{ type: 'text', text: { content: mediaInfo.filename || 'document' } }],
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
// For CSV/embeds, use a bookmark block (Notion doesn't have native CSV embed)
|
|
159
|
+
return {
|
|
160
|
+
object: 'block',
|
|
161
|
+
type: 'bookmark',
|
|
162
|
+
bookmark: {
|
|
163
|
+
url: mediaInfo.url,
|
|
164
|
+
caption: [{ type: 'text', text: { content: mediaInfo.filename || 'file' } }],
|
|
165
|
+
},
|
|
166
|
+
};
|
|
167
|
+
});
|
|
115
168
|
}
|
|
116
169
|
// Max 100 blocks per API call
|
|
117
170
|
const MAX_BLOCKS_PER_APPEND = 100;
|
|
@@ -360,8 +413,8 @@ export class NotionExportTarget extends ExportTarget {
|
|
|
360
413
|
// Notion page creation / update
|
|
361
414
|
// ============================================
|
|
362
415
|
async createNotionPage(doc) {
|
|
363
|
-
const { markdown } = sectionsToMarkdown(doc.sections, { extractReferences: true });
|
|
364
|
-
const blocks = markdownToBlocks(markdown);
|
|
416
|
+
const { markdown, media } = sectionsToMarkdown(doc.sections, { extractReferences: true });
|
|
417
|
+
const blocks = injectMediaBlocks(markdownToBlocks(markdown), media);
|
|
365
418
|
// First batch: up to 100 blocks as children of the new page
|
|
366
419
|
const firstBatch = blocks.slice(0, MAX_BLOCKS_PER_APPEND);
|
|
367
420
|
const remainingBlocks = blocks.slice(MAX_BLOCKS_PER_APPEND);
|
|
@@ -393,8 +446,8 @@ export class NotionExportTarget extends ExportTarget {
|
|
|
393
446
|
},
|
|
394
447
|
});
|
|
395
448
|
await this.clearPageContent(notionPageId);
|
|
396
|
-
const { markdown } = sectionsToMarkdown(doc.sections, { extractReferences: true });
|
|
397
|
-
const blocks = markdownToBlocks(markdown);
|
|
449
|
+
const { markdown, media } = sectionsToMarkdown(doc.sections, { extractReferences: true });
|
|
450
|
+
const blocks = injectMediaBlocks(markdownToBlocks(markdown), media);
|
|
398
451
|
await this.appendRemainingBlocks(notionPageId, blocks);
|
|
399
452
|
}
|
|
400
453
|
async clearPageContent(pageId) {
|
package/package.json
CHANGED