@jacobbubu/md-to-lark 1.0.0

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.
Files changed (58) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +171 -0
  3. package/dist/btt/build-tree.js +79 -0
  4. package/dist/btt/index.js +1 -0
  5. package/dist/btt/types.js +1 -0
  6. package/dist/cli/publish-md-to-lark.js +15 -0
  7. package/dist/commands/publish-md/args.js +224 -0
  8. package/dist/commands/publish-md/command.js +97 -0
  9. package/dist/commands/publish-md/index.js +1 -0
  10. package/dist/commands/publish-md/input-resolver.js +48 -0
  11. package/dist/commands/publish-md/mermaid-render.js +17 -0
  12. package/dist/commands/publish-md/pipeline-transform.js +4 -0
  13. package/dist/commands/publish-md/preset-loader.js +113 -0
  14. package/dist/commands/publish-md/presets/medium.js +7 -0
  15. package/dist/commands/publish-md/presets/zh-format.js +8 -0
  16. package/dist/commands/publish-md/title-policy.js +93 -0
  17. package/dist/index.js +1 -0
  18. package/dist/interop/btt-to-last.js +79 -0
  19. package/dist/interop/codec-btt-to-last.js +435 -0
  20. package/dist/interop/codec-last-to-btt.js +383 -0
  21. package/dist/interop/codec-shared.js +722 -0
  22. package/dist/interop/index.js +2 -0
  23. package/dist/interop/last-to-btt.js +17 -0
  24. package/dist/lark/block-types.js +42 -0
  25. package/dist/lark/client.js +36 -0
  26. package/dist/lark/docx/ops.js +596 -0
  27. package/dist/lark/docx/render-btt.js +156 -0
  28. package/dist/lark/docx/render-models.js +1 -0
  29. package/dist/lark/docx/render-payload.js +338 -0
  30. package/dist/lark/docx/render-post-process.js +98 -0
  31. package/dist/lark/docx/render-table.js +87 -0
  32. package/dist/lark/docx/render-types.js +7 -0
  33. package/dist/lark/index.js +2 -0
  34. package/dist/lark/types.js +1 -0
  35. package/dist/last/api.js +1687 -0
  36. package/dist/last/index.js +3 -0
  37. package/dist/last/preview-terminal.js +296 -0
  38. package/dist/last/textual-block-types.js +19 -0
  39. package/dist/last/to-markdown.js +303 -0
  40. package/dist/last/types.js +11 -0
  41. package/dist/pipeline/hast-to-last.js +946 -0
  42. package/dist/pipeline/index.js +3 -0
  43. package/dist/pipeline/markdown/md-to-hast.js +34 -0
  44. package/dist/pipeline/markdown/prepare-markdown.js +1049 -0
  45. package/dist/preview/index.js +1 -0
  46. package/dist/preview/markdown-terminal.js +350 -0
  47. package/dist/publish/asset-adapter.js +123 -0
  48. package/dist/publish/btt-patch.js +65 -0
  49. package/dist/publish/common.js +139 -0
  50. package/dist/publish/ids.js +9 -0
  51. package/dist/publish/index.js +7 -0
  52. package/dist/publish/last-normalize.js +327 -0
  53. package/dist/publish/process-file.js +228 -0
  54. package/dist/publish/runtime.js +133 -0
  55. package/dist/publish/stage-cache.js +56 -0
  56. package/dist/shared/rate-limiter.js +18 -0
  57. package/dist/shared/retry.js +141 -0
  58. package/package.json +78 -0
@@ -0,0 +1 @@
1
+ export { renderHASTToTerminal, renderMarkdownToTerminal } from './markdown-terminal.js';
@@ -0,0 +1,350 @@
1
+ import { markdownToHast } from '../pipeline/markdown/md-to-hast.js';
2
+ const ANSI = {
3
+ reset: '\u001b[0m',
4
+ bold: '\u001b[1m',
5
+ italic: '\u001b[3m',
6
+ underline: '\u001b[4m',
7
+ strike: '\u001b[9m',
8
+ inverse: '\u001b[7m',
9
+ dim: '\u001b[2m',
10
+ blue: '\u001b[34m',
11
+ red: '\u001b[31m',
12
+ };
13
+ function withAnsi(enabled, text, ...codes) {
14
+ if (!enabled || text.length === 0)
15
+ return text;
16
+ const start = codes.filter((x) => Boolean(x));
17
+ if (start.length === 0)
18
+ return text;
19
+ return `${start.join('')}${text}${ANSI.reset}`;
20
+ }
21
+ function isElement(node, tagName) {
22
+ if (node.type !== 'element')
23
+ return false;
24
+ if (!tagName)
25
+ return true;
26
+ return node.tagName === tagName;
27
+ }
28
+ function asChildren(node) {
29
+ return [...node.children];
30
+ }
31
+ function toText(node) {
32
+ if (node.type === 'text') {
33
+ return node.value;
34
+ }
35
+ if (!isElement(node)) {
36
+ return '';
37
+ }
38
+ const children = asChildren(node);
39
+ return children.map((child) => toText(child)).join('');
40
+ }
41
+ function getClassNames(element) {
42
+ const raw = element.properties?.className;
43
+ if (Array.isArray(raw)) {
44
+ return raw.map((item) => String(item));
45
+ }
46
+ if (typeof raw === 'string') {
47
+ return raw.split(/\s+/).filter((x) => x.length > 0);
48
+ }
49
+ return [];
50
+ }
51
+ function parseCodeLanguage(codeElement) {
52
+ const classNames = getClassNames(codeElement);
53
+ for (const name of classNames) {
54
+ if (name.startsWith('language-') && name.length > 'language-'.length) {
55
+ return name.slice('language-'.length);
56
+ }
57
+ }
58
+ return 'text';
59
+ }
60
+ function renderInline(node, ctx) {
61
+ if (node.type === 'text') {
62
+ return node.value;
63
+ }
64
+ if (!isElement(node)) {
65
+ return '';
66
+ }
67
+ const childrenText = asChildren(node)
68
+ .map((child) => renderInline(child, ctx))
69
+ .join('');
70
+ if (node.tagName === 'strong') {
71
+ return withAnsi(ctx.options.color, childrenText, ANSI.bold);
72
+ }
73
+ if (node.tagName === 'em') {
74
+ return withAnsi(ctx.options.color, childrenText, ANSI.italic);
75
+ }
76
+ if (node.tagName === 'del') {
77
+ return withAnsi(ctx.options.color, childrenText, ANSI.strike);
78
+ }
79
+ if (node.tagName === 'code') {
80
+ return withAnsi(ctx.options.color, childrenText, ANSI.inverse);
81
+ }
82
+ if (node.tagName === 'a') {
83
+ const href = typeof node.properties?.href === 'string' ? node.properties.href : '';
84
+ const text = childrenText.length > 0 ? childrenText : href;
85
+ if (!href) {
86
+ return text;
87
+ }
88
+ return `${withAnsi(ctx.options.color, text, ANSI.blue, ANSI.underline)}(${href})`;
89
+ }
90
+ if (node.tagName === 'br') {
91
+ return '\n';
92
+ }
93
+ if (node.tagName === 'img') {
94
+ const alt = typeof node.properties?.alt === 'string' ? node.properties.alt : '';
95
+ const src = typeof node.properties?.src === 'string' ? node.properties.src : '';
96
+ return withAnsi(ctx.options.color, `![${alt}](${src})`, ANSI.dim);
97
+ }
98
+ return childrenText;
99
+ }
100
+ function renderInlineChildren(nodes, ctx) {
101
+ return nodes.map((node) => renderInline(node, ctx)).join('');
102
+ }
103
+ function pushLine(ctx, line = '') {
104
+ ctx.lines.push(line);
105
+ }
106
+ function indent(depth) {
107
+ return ' '.repeat(Math.max(0, depth));
108
+ }
109
+ function pushSectionGap(ctx) {
110
+ for (let i = 0; i < ctx.options.sectionGapLines; i += 1) {
111
+ pushLine(ctx, '');
112
+ }
113
+ }
114
+ function renderParagraph(element, ctx, depth) {
115
+ const text = renderInlineChildren(asChildren(element), ctx);
116
+ const prefix = indent(depth);
117
+ const lines = text.split('\n');
118
+ for (let i = 0; i < lines.length; i += 1) {
119
+ const line = lines[i] ?? '';
120
+ pushLine(ctx, `${prefix}${line}`);
121
+ }
122
+ }
123
+ function renderHeading(element, ctx, depth) {
124
+ const level = Number(element.tagName.slice(1));
125
+ const marker = '#'.repeat(Math.max(1, Math.min(6, Number.isFinite(level) ? level : 1)));
126
+ const text = renderInlineChildren(asChildren(element), ctx);
127
+ pushLine(ctx, `${indent(depth)}${withAnsi(ctx.options.color, `${marker} ${text}`, ANSI.bold)}`);
128
+ }
129
+ function renderCodeBlock(element, ctx, depth) {
130
+ const prefix = indent(depth);
131
+ const code = asChildren(element).find((node) => isElement(node, 'code'));
132
+ const language = code ? parseCodeLanguage(code) : 'text';
133
+ const raw = code ? toText(code) : toText(element);
134
+ pushLine(ctx, `${prefix}${withAnsi(ctx.options.color, `\`\`\`${language}`, ANSI.dim)}`);
135
+ const lines = raw.replace(/\n$/, '').split('\n');
136
+ for (const line of lines) {
137
+ pushLine(ctx, `${prefix}${line}`);
138
+ }
139
+ pushLine(ctx, `${prefix}${withAnsi(ctx.options.color, '\`\`\`', ANSI.dim)}`);
140
+ }
141
+ function renderBlockquote(element, ctx, depth) {
142
+ const temp = {
143
+ options: ctx.options,
144
+ lines: [],
145
+ };
146
+ for (const child of asChildren(element)) {
147
+ renderBlock(child, temp, 0);
148
+ }
149
+ for (const line of temp.lines) {
150
+ const prefix = indent(depth);
151
+ if (line.trim().length === 0) {
152
+ pushLine(ctx, `${prefix}>`);
153
+ }
154
+ else {
155
+ pushLine(ctx, `${prefix}> ${line}`);
156
+ }
157
+ }
158
+ }
159
+ function renderTable(element, ctx, depth) {
160
+ const rows = [];
161
+ let headerRows = 0;
162
+ const appendRows = (container, isHeader) => {
163
+ const trNodes = asChildren(container).filter((child) => isElement(child, 'tr'));
164
+ for (const tr of trNodes) {
165
+ const cellNodes = asChildren(tr).filter((child) => isElement(child));
166
+ const cells = cellNodes
167
+ .filter((cell) => cell.tagName === 'th' || cell.tagName === 'td')
168
+ .map((cell) => renderInlineChildren(asChildren(cell), ctx));
169
+ rows.push(cells);
170
+ if (isHeader) {
171
+ headerRows += 1;
172
+ }
173
+ }
174
+ };
175
+ for (const child of asChildren(element)) {
176
+ if (!isElement(child))
177
+ continue;
178
+ if (child.tagName === 'thead') {
179
+ appendRows(child, true);
180
+ continue;
181
+ }
182
+ if (child.tagName === 'tbody' || child.tagName === 'tfoot') {
183
+ appendRows(child, false);
184
+ continue;
185
+ }
186
+ if (child.tagName === 'tr') {
187
+ appendRows({
188
+ type: 'element',
189
+ tagName: 'tbody',
190
+ properties: {},
191
+ children: [child],
192
+ }, false);
193
+ }
194
+ }
195
+ if (rows.length === 0) {
196
+ pushLine(ctx, `${indent(depth)}${withAnsi(ctx.options.color, '[table]', ANSI.dim)}`);
197
+ return;
198
+ }
199
+ const colCount = rows.reduce((max, row) => Math.max(max, row.length), 0);
200
+ const widths = new Array(colCount).fill(3);
201
+ for (const row of rows) {
202
+ for (let i = 0; i < colCount; i += 1) {
203
+ const cell = row[i] ?? '';
204
+ widths[i] = Math.max(widths[i] ?? 3, cell.length);
205
+ }
206
+ }
207
+ const formatRow = (row) => {
208
+ const cells = [];
209
+ for (let i = 0; i < colCount; i += 1) {
210
+ const content = row[i] ?? '';
211
+ const width = widths[i] ?? 3;
212
+ cells.push(content.padEnd(width, ' '));
213
+ }
214
+ return `| ${cells.join(' | ')} |`;
215
+ };
216
+ const prefix = indent(depth);
217
+ pushLine(ctx, `${prefix}${formatRow(rows[0] ?? [])}`);
218
+ if (headerRows > 0 || rows.length > 1) {
219
+ const separator = widths.map((width) => '-'.repeat(Math.max(3, width))).join(' | ');
220
+ pushLine(ctx, `${prefix}| ${separator} |`);
221
+ }
222
+ for (let i = 1; i < rows.length; i += 1) {
223
+ const row = rows[i];
224
+ if (!row)
225
+ continue;
226
+ pushLine(ctx, `${prefix}${formatRow(row)}`);
227
+ }
228
+ }
229
+ function renderListItem(li, ctx, depth, marker) {
230
+ const prefix = indent(depth);
231
+ const children = asChildren(li);
232
+ const nestedLists = children.filter((child) => isElement(child) && (child.tagName === 'ul' || child.tagName === 'ol'));
233
+ const nonNested = children.filter((child) => !(isElement(child) && (child.tagName === 'ul' || child.tagName === 'ol')));
234
+ const firstParagraph = nonNested.find((child) => isElement(child, 'p'));
235
+ const firstLine = firstParagraph
236
+ ? renderInlineChildren(asChildren(firstParagraph), ctx)
237
+ : renderInlineChildren(nonNested, ctx);
238
+ pushLine(ctx, `${prefix}${marker} ${firstLine}`.trimEnd());
239
+ const extraBlocks = nonNested.filter((child) => child !== firstParagraph);
240
+ for (const block of extraBlocks) {
241
+ if (!isElement(block))
242
+ continue;
243
+ if (block.tagName === 'p') {
244
+ renderParagraph(block, ctx, depth + 1);
245
+ }
246
+ else if (block.tagName === 'pre') {
247
+ renderCodeBlock(block, ctx, depth + 1);
248
+ }
249
+ else if (block.tagName === 'blockquote') {
250
+ renderBlockquote(block, ctx, depth + 1);
251
+ }
252
+ }
253
+ for (const list of nestedLists) {
254
+ renderList(list, ctx, depth + 1);
255
+ }
256
+ }
257
+ function renderList(element, ctx, depth) {
258
+ const isOrdered = element.tagName === 'ol';
259
+ const rawStart = element.properties?.start;
260
+ const start = typeof rawStart === 'number' ? rawStart : Number(rawStart ?? 1);
261
+ let counter = Number.isFinite(start) && start > 0 ? start : 1;
262
+ const items = asChildren(element).filter((child) => isElement(child, 'li'));
263
+ for (const item of items) {
264
+ const marker = isOrdered ? `${counter}.` : '•';
265
+ renderListItem(item, ctx, depth, marker);
266
+ if (isOrdered) {
267
+ counter += 1;
268
+ }
269
+ }
270
+ }
271
+ function renderBlock(node, ctx, depth) {
272
+ if (node.type === 'text') {
273
+ const text = node.value.trim();
274
+ if (text.length > 0) {
275
+ pushLine(ctx, `${indent(depth)}${text}`);
276
+ }
277
+ return;
278
+ }
279
+ if (!isElement(node)) {
280
+ return;
281
+ }
282
+ switch (node.tagName) {
283
+ case 'p':
284
+ renderParagraph(node, ctx, depth);
285
+ break;
286
+ case 'h1':
287
+ case 'h2':
288
+ case 'h3':
289
+ case 'h4':
290
+ case 'h5':
291
+ case 'h6':
292
+ renderHeading(node, ctx, depth);
293
+ break;
294
+ case 'ul':
295
+ case 'ol':
296
+ renderList(node, ctx, depth);
297
+ break;
298
+ case 'pre':
299
+ renderCodeBlock(node, ctx, depth);
300
+ break;
301
+ case 'blockquote':
302
+ renderBlockquote(node, ctx, depth);
303
+ break;
304
+ case 'hr':
305
+ pushLine(ctx, `${indent(depth)}${withAnsi(ctx.options.color, '---', ANSI.dim)}`);
306
+ break;
307
+ case 'table':
308
+ renderTable(node, ctx, depth);
309
+ break;
310
+ default: {
311
+ const inlineText = renderInlineChildren(asChildren(node), ctx);
312
+ if (inlineText.trim().length > 0) {
313
+ pushLine(ctx, `${indent(depth)}${inlineText}`);
314
+ }
315
+ break;
316
+ }
317
+ }
318
+ }
319
+ export function renderHASTToTerminal(root, options = {}) {
320
+ const ctx = {
321
+ options: {
322
+ color: options.color ?? true,
323
+ sectionGapLines: options.sectionGapLines ?? 1,
324
+ showHeader: options.showHeader ?? true,
325
+ },
326
+ lines: [],
327
+ };
328
+ if (ctx.options.showHeader) {
329
+ pushLine(ctx, withAnsi(ctx.options.color, 'Markdown Terminal Preview', ANSI.dim));
330
+ pushLine(ctx, '');
331
+ }
332
+ const blocks = asChildren(root);
333
+ for (let i = 0; i < blocks.length; i += 1) {
334
+ const block = blocks[i];
335
+ if (!block)
336
+ continue;
337
+ renderBlock(block, ctx, 0);
338
+ if (i !== blocks.length - 1) {
339
+ pushSectionGap(ctx);
340
+ }
341
+ }
342
+ while (ctx.lines.length > 0 && ctx.lines[ctx.lines.length - 1]?.trim() === '') {
343
+ ctx.lines.pop();
344
+ }
345
+ return `${ctx.lines.join('\n')}\n`;
346
+ }
347
+ export async function renderMarkdownToTerminal(markdown, options = {}) {
348
+ const hast = await markdownToHast(markdown);
349
+ return renderHASTToTerminal(hast, options);
350
+ }
@@ -0,0 +1,123 @@
1
+ import { existsSync } from 'node:fs';
2
+ import path from 'node:path';
3
+ import { createDefaultMarks, extractTextFromInlines, firstInlineLinkUrl, getPathExtension, inferMediaKind, isTextualBlock, resolveLocalPathFromSource, shouldUsePreviewView, stripQueryAndHash, } from './common.js';
4
+ export function applyStandaloneAttachmentTransforms(last, baseDir) {
5
+ const localAssetByBlockId = new Map();
6
+ for (const block of Object.values(last.blocks)) {
7
+ if (!isTextualBlock(block) || block.type !== 'text') {
8
+ continue;
9
+ }
10
+ if (block.children.length > 0) {
11
+ continue;
12
+ }
13
+ const linkUrl = firstInlineLinkUrl(block);
14
+ if (!linkUrl) {
15
+ continue;
16
+ }
17
+ const resolvedPath = resolveLocalPathFromSource(linkUrl, baseDir);
18
+ if (!resolvedPath) {
19
+ continue;
20
+ }
21
+ if (!existsSync(resolvedPath)) {
22
+ continue;
23
+ }
24
+ const extension = getPathExtension(linkUrl);
25
+ const mediaKind = inferMediaKind(extension);
26
+ const displayName = extractTextFromInlines(block.payload.inlines).trim();
27
+ const fileName = displayName.length > 0 ? displayName : path.basename(stripQueryAndHash(linkUrl));
28
+ if (mediaKind === 'image') {
29
+ const transformed = {
30
+ id: block.id,
31
+ ...(block.bttId ? { bttId: block.bttId } : {}),
32
+ type: 'image',
33
+ parentId: block.parentId,
34
+ children: [],
35
+ payload: {
36
+ width: 0,
37
+ height: 0,
38
+ token: '',
39
+ align: 'left',
40
+ },
41
+ selector: {
42
+ attrs: {
43
+ sourceUrl: linkUrl,
44
+ },
45
+ },
46
+ };
47
+ last.blocks[block.id] = transformed;
48
+ localAssetByBlockId.set(block.id, {
49
+ kind: 'image',
50
+ absolutePath: resolvedPath,
51
+ fileName: path.basename(resolvedPath),
52
+ mediaKind,
53
+ });
54
+ continue;
55
+ }
56
+ const transformed = {
57
+ id: block.id,
58
+ ...(block.bttId ? { bttId: block.bttId } : {}),
59
+ type: 'file',
60
+ parentId: block.parentId,
61
+ children: [],
62
+ payload: {
63
+ name: fileName,
64
+ viewType: shouldUsePreviewView(extension, mediaKind) ? 2 : 1,
65
+ },
66
+ selector: {
67
+ attrs: {
68
+ sourceUrl: linkUrl,
69
+ },
70
+ },
71
+ };
72
+ last.blocks[block.id] = transformed;
73
+ localAssetByBlockId.set(block.id, {
74
+ kind: 'file',
75
+ absolutePath: resolvedPath,
76
+ fileName: path.basename(resolvedPath),
77
+ mediaKind,
78
+ });
79
+ }
80
+ for (const block of Object.values(last.blocks)) {
81
+ if (block.type !== 'image')
82
+ continue;
83
+ const sourceUrl = block.selector && block.selector.attrs && typeof block.selector.attrs.sourceUrl === 'string'
84
+ ? String(block.selector.attrs.sourceUrl)
85
+ : '';
86
+ if (!sourceUrl)
87
+ continue;
88
+ const resolvedPath = resolveLocalPathFromSource(sourceUrl, baseDir);
89
+ if (!resolvedPath)
90
+ continue;
91
+ if (!existsSync(resolvedPath)) {
92
+ last.blocks[block.id] = {
93
+ id: block.id,
94
+ ...(block.bttId ? { bttId: block.bttId } : {}),
95
+ type: 'text',
96
+ parentId: block.parentId,
97
+ children: [],
98
+ payload: {
99
+ style: {
100
+ align: 'left',
101
+ language: null,
102
+ },
103
+ inlines: [
104
+ {
105
+ id: `i_missing_${block.id}`,
106
+ kind: 'text_run',
107
+ marks: createDefaultMarks(),
108
+ text: sourceUrl,
109
+ },
110
+ ],
111
+ },
112
+ };
113
+ continue;
114
+ }
115
+ localAssetByBlockId.set(block.id, {
116
+ kind: 'image',
117
+ absolutePath: resolvedPath,
118
+ fileName: path.basename(resolvedPath),
119
+ mediaKind: 'image',
120
+ });
121
+ }
122
+ return localAssetByBlockId;
123
+ }
@@ -0,0 +1,65 @@
1
+ import path from 'node:path';
2
+ import { getLarkBlockTypeName } from '../lark/index.js';
3
+ import { DEFAULT_MERMAID_RENDER_CONFIG, } from '../lark/docx/render-types.js';
4
+ import { shouldUsePreviewView, toObjectRecord } from './common.js';
5
+ const MERMAID_COMPONENT_TYPE_ID = 'blk_631fefbbae02400430b8f9f4';
6
+ function applyRawBlockPatch(rawBlock, blockId, mermaidByBlockId, assetByBlockId, mermaidRenderConfig) {
7
+ const mermaid = mermaidByBlockId.get(blockId);
8
+ if (mermaid) {
9
+ rawBlock.block_type = mermaidRenderConfig.target === 'board' ? 43 : 40;
10
+ for (const key of Object.keys(rawBlock)) {
11
+ if (key === 'block_id' || key === 'parent_id' || key === 'children' || key === 'block_type')
12
+ continue;
13
+ delete rawBlock[key];
14
+ }
15
+ if (mermaidRenderConfig.target === 'board') {
16
+ rawBlock.board = {};
17
+ }
18
+ else {
19
+ rawBlock.add_ons = {
20
+ component_type_id: MERMAID_COMPONENT_TYPE_ID,
21
+ record: JSON.stringify({
22
+ data: mermaid.code,
23
+ theme: 'default',
24
+ view: 'chart',
25
+ }),
26
+ };
27
+ }
28
+ }
29
+ const asset = assetByBlockId.get(blockId);
30
+ if (!asset)
31
+ return;
32
+ if (asset.kind === 'image') {
33
+ rawBlock.block_type = 27;
34
+ const image = toObjectRecord(rawBlock.image) ?? {};
35
+ image.local_path = asset.absolutePath;
36
+ rawBlock.image = image;
37
+ return;
38
+ }
39
+ rawBlock.block_type = 23;
40
+ const file = toObjectRecord(rawBlock.file) ?? {};
41
+ file.local_path = asset.absolutePath;
42
+ file.name = typeof file.name === 'string' && file.name.trim().length > 0 ? file.name : asset.fileName;
43
+ if (asset.mediaKind) {
44
+ file.media_kind = asset.mediaKind;
45
+ }
46
+ if (typeof file.view_type !== 'number') {
47
+ file.view_type = shouldUsePreviewView(path.extname(asset.fileName).toLowerCase(), asset.mediaKind ?? 'file') ? 2 : 1;
48
+ }
49
+ rawBlock.file = file;
50
+ }
51
+ export function patchBTTForMermaidAndAssets(btt, mermaidByBlockId, assetByBlockId, options) {
52
+ const mermaidRenderConfig = options?.mermaidRender ?? DEFAULT_MERMAID_RENDER_CONFIG;
53
+ const walk = (node) => {
54
+ applyRawBlockPatch(node.rawBlock, node.blockId, mermaidByBlockId, assetByBlockId, mermaidRenderConfig);
55
+ node.blockType = node.rawBlock.block_type;
56
+ node.blockTypeName = getLarkBlockTypeName(node.blockType);
57
+ for (const child of node.children) {
58
+ walk(child);
59
+ }
60
+ };
61
+ walk(btt.root);
62
+ for (const [blockId, rawBlock] of Object.entries(btt.flatBlocks)) {
63
+ applyRawBlockPatch(rawBlock, blockId, mermaidByBlockId, assetByBlockId, mermaidRenderConfig);
64
+ }
65
+ }
@@ -0,0 +1,139 @@
1
+ import path from 'node:path';
2
+ import { LAST_TEXTUAL_BLOCK_TYPE_SET } from '../last/textual-block-types.js';
3
+ const IMAGE_EXTENSIONS = new Set(['.png', '.jpg', '.jpeg', '.gif', '.webp', '.bmp', '.svg']);
4
+ const VIDEO_EXTENSIONS = new Set(['.mp4', '.mov', '.m4v', '.avi', '.mkv', '.webm']);
5
+ const AUDIO_EXTENSIONS = new Set(['.mp3', '.wav', '.aac', '.m4a', '.flac', '.ogg', '.oga', '.opus']);
6
+ const PREVIEWABLE_FILE_EXTENSIONS = new Set([
7
+ '.pdf',
8
+ '.doc',
9
+ '.docx',
10
+ '.xls',
11
+ '.xlsx',
12
+ '.ppt',
13
+ '.pptx',
14
+ ...VIDEO_EXTENSIONS,
15
+ ...AUDIO_EXTENSIONS,
16
+ ]);
17
+ export function toObjectRecord(value) {
18
+ return value && typeof value === 'object' ? value : null;
19
+ }
20
+ export function isTextualBlock(block) {
21
+ return LAST_TEXTUAL_BLOCK_TYPE_SET.has(block.type);
22
+ }
23
+ export function stripQueryAndHash(url) {
24
+ return url.split('#', 1)[0]?.split('?', 1)[0] ?? url;
25
+ }
26
+ export function safeDecodeURIComponent(input) {
27
+ try {
28
+ return decodeURIComponent(input);
29
+ }
30
+ catch {
31
+ return input;
32
+ }
33
+ }
34
+ export function getPathExtension(url) {
35
+ const clean = safeDecodeURIComponent(stripQueryAndHash(url));
36
+ return path.extname(clean).toLowerCase();
37
+ }
38
+ export function isHttpLike(url) {
39
+ return /^[a-z][a-z0-9+.-]*:/i.test(url);
40
+ }
41
+ export function resolveLocalPathFromSource(sourceUrl, baseDir) {
42
+ const raw = sourceUrl.trim();
43
+ if (!raw)
44
+ return null;
45
+ if (isHttpLike(raw)) {
46
+ if (/^https?:/i.test(raw)) {
47
+ return null;
48
+ }
49
+ return null;
50
+ }
51
+ const decoded = safeDecodeURIComponent(stripQueryAndHash(raw));
52
+ const absolute = path.isAbsolute(decoded) ? decoded : path.resolve(baseDir, decoded);
53
+ return absolute;
54
+ }
55
+ export function inferMediaKind(extension) {
56
+ if (IMAGE_EXTENSIONS.has(extension))
57
+ return 'image';
58
+ if (VIDEO_EXTENSIONS.has(extension))
59
+ return 'video';
60
+ if (AUDIO_EXTENSIONS.has(extension))
61
+ return 'audio';
62
+ return 'file';
63
+ }
64
+ export function shouldUsePreviewView(extension, mediaKind) {
65
+ if (mediaKind === 'video' || mediaKind === 'audio')
66
+ return true;
67
+ return PREVIEWABLE_FILE_EXTENSIONS.has(extension);
68
+ }
69
+ export function createDefaultMarks() {
70
+ return {
71
+ bold: false,
72
+ italic: false,
73
+ strikethrough: false,
74
+ underline: false,
75
+ inlineCode: false,
76
+ textColor: null,
77
+ backgroundColor: null,
78
+ link: null,
79
+ };
80
+ }
81
+ export function extractTextFromInlines(inlines) {
82
+ let out = '';
83
+ for (const inline of inlines) {
84
+ if (inline.kind === 'text_run') {
85
+ out += inline.text ?? '';
86
+ continue;
87
+ }
88
+ if (inline.kind === 'mention_user') {
89
+ out += inline.userId ?? '';
90
+ continue;
91
+ }
92
+ if (inline.kind === 'equation') {
93
+ out += inline.latex ?? '';
94
+ continue;
95
+ }
96
+ if (inline.kind === 'mention_doc') {
97
+ out += inline.title ?? '';
98
+ continue;
99
+ }
100
+ if (inline.kind === 'link_preview') {
101
+ out += inline.title ?? inline.url ?? '';
102
+ }
103
+ }
104
+ return out;
105
+ }
106
+ export function firstInlineLinkUrl(block) {
107
+ if (block.payload.inlines.length === 0)
108
+ return null;
109
+ let resolvedUrl = '';
110
+ for (const inline of block.payload.inlines) {
111
+ if (inline.kind !== 'text_run')
112
+ return null;
113
+ const linkUrl = inline.marks.link?.url;
114
+ if (typeof linkUrl !== 'string' || linkUrl.trim().length === 0) {
115
+ return null;
116
+ }
117
+ const normalized = linkUrl.trim();
118
+ if (!resolvedUrl) {
119
+ resolvedUrl = normalized;
120
+ continue;
121
+ }
122
+ if (resolvedUrl !== normalized) {
123
+ return null;
124
+ }
125
+ }
126
+ return resolvedUrl || null;
127
+ }
128
+ export function toPlainTextFromInlineList(inlines) {
129
+ let out = '';
130
+ for (const inline of inlines) {
131
+ if (inline.kind === 'text_run') {
132
+ out += inline.text ?? '';
133
+ }
134
+ else {
135
+ out += extractTextFromInlines([inline]);
136
+ }
137
+ }
138
+ return out;
139
+ }
@@ -0,0 +1,9 @@
1
+ import path from 'node:path';
2
+ export function buildPipelineDocumentId(inputFile) {
3
+ const base = path.basename(inputFile, path.extname(inputFile));
4
+ const safe = base
5
+ .toLowerCase()
6
+ .replace(/[^a-z0-9]+/g, '-')
7
+ .replace(/^-+|-+$/g, '');
8
+ return safe ? `md-${safe}` : 'md-doc';
9
+ }
@@ -0,0 +1,7 @@
1
+ export { applyStandaloneAttachmentTransforms } from './asset-adapter.js';
2
+ export { patchBTTForMermaidAndAssets } from './btt-patch.js';
3
+ export { buildPipelineDocumentId } from './ids.js';
4
+ export { applyTableColumnWidthHeuristics, collectMermaidPatches, ensureLastBlockBttIds } from './last-normalize.js';
5
+ export { processSingleMarkdownFile } from './process-file.js';
6
+ export { buildPublishRuntime, logPublishRuntimeSummary } from './runtime.js';
7
+ export { buildPipelineStagePaths, ensureDir, writeJson, writeSourceStage, writePrepareStage, writePrepareLogFile, writeHastStage, writeLastStage, writeBttStage, writePublishStageArtifact, } from './stage-cache.js';