@jacobbubu/md-to-lark 1.4.11 → 1.5.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.
- package/README.md +2 -0
- package/dist/commands/publish-md/args.js +8 -1
- package/dist/commands/publish-md/command.js +1 -3
- package/dist/commands/publish-md/pipeline-transform.js +1 -1
- package/dist/interop/btt-to-last.js +1 -1
- package/dist/lark/docx/render-btt.js +2 -2
- package/dist/lark/docx/render-payload.js +8 -2
- package/dist/lark/docx/render-post-process.js +3 -1
- package/dist/pipeline/markdown/md-to-hast.js +2 -2
- package/dist/preview/markdown-terminal.js +2 -1
- package/dist/publish/btt-patch.js +3 -1
- package/dist/publish/index.js +2 -2
- package/dist/publish/process-file.js +2 -2
- package/dist/publish/runtime.js +7 -0
- package/dist/shared/image-metadata.js +2 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -112,6 +112,7 @@ Presets, Mermaid, and stage artifacts:
|
|
|
112
112
|
npm run publish:md -- --input ./test-md/comp/comp.md --preset medium --dry-run
|
|
113
113
|
npm run publish:md -- --input ./test-md/comp/comp.md --preset zh-format --dry-run
|
|
114
114
|
npm run publish:md -- --input ./test-md/comp/comp.md --preset zh-format --preset ./my-preset.mjs --dry-run
|
|
115
|
+
npm run publish:md -- --input ./paper.md --single-dollar-text-math --dry-run
|
|
115
116
|
npm run publish:md -- --input ./tmp/generated/article.md --resource-base-dir ./source-assets --dry-run
|
|
116
117
|
npm run publish:md -- --input ./test-md/mermaid.md --mermaid-target board --dry-run
|
|
117
118
|
npm run publish:md -- --input ./test-md/comp/comp.md --pipeline-cache-dir ./out/debug-cache --dry-run
|
|
@@ -175,6 +176,7 @@ Guardrails:
|
|
|
175
176
|
- Local attachment and image detection with real upload
|
|
176
177
|
- Remote image download and standalone URL preparation
|
|
177
178
|
- Mermaid `text-drawing` and `board` output paths
|
|
179
|
+
- Opt-in single-dollar inline math parsing for LaTeX-heavy papers
|
|
178
180
|
- Table width heuristics and numeric-column right alignment
|
|
179
181
|
- Chinese Markdown formatting preset (`zh-format`)
|
|
180
182
|
- Ordered preset composition from CLI and programmatic usage
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
function usage() {
|
|
2
2
|
return [
|
|
3
|
-
'Usage: npm run publish:md -- --input <file.md|dir> [--title <doc_title_or_prefix>] [--date-prefix|--no-date-prefix] [--preset <preset_name_or_module_path>]... [--document-base-url <base_url>] [--resource-base-dir <dir>] [--folder <folder_token>] [--doc <document_id>] [--download-remote-images|--no-download-remote-images] [--yt-dlp-path <path>] [--yt-dlp-cookies-path <path>] [--pipeline-cache-dir <dir>] [--mermaid-target <text-drawing|board>] [--mermaid-board-syntax-type <int>] [--mermaid-board-style-type <int>] [--mermaid-board-diagram-type <int>] [--dry-run] [--help|-h]',
|
|
3
|
+
'Usage: npm run publish:md -- --input <file.md|dir> [--title <doc_title_or_prefix>] [--date-prefix|--no-date-prefix] [--preset <preset_name_or_module_path>]... [--document-base-url <base_url>] [--resource-base-dir <dir>] [--folder <folder_token>] [--doc <document_id>] [--download-remote-images|--no-download-remote-images] [--yt-dlp-path <path>] [--yt-dlp-cookies-path <path>] [--pipeline-cache-dir <dir>] [--single-dollar-text-math] [--mermaid-target <text-drawing|board>] [--mermaid-board-syntax-type <int>] [--mermaid-board-style-type <int>] [--mermaid-board-diagram-type <int>] [--dry-run] [--help|-h]',
|
|
4
4
|
'',
|
|
5
5
|
'Options:',
|
|
6
6
|
' --input Markdown file path, or directory path (publish all *.md recursively).',
|
|
@@ -17,6 +17,7 @@ function usage() {
|
|
|
17
17
|
' --yt-dlp-path Optional yt-dlp executable path for standalone URL extraction.',
|
|
18
18
|
' --yt-dlp-cookies-path Optional cookie file path passed to yt-dlp --cookies.',
|
|
19
19
|
' --pipeline-cache-dir Pipeline cache root directory. Default: ./out/pipeline-cache',
|
|
20
|
+
' --single-dollar-text-math Opt in to parsing $...$ as inline math. Default: disabled to avoid currency false positives.',
|
|
20
21
|
' --mermaid-target Mermaid render target: text-drawing (default) or board.',
|
|
21
22
|
' --mermaid-board-syntax-type Optional integer syntax_type for board createPlantuml (default: 2).',
|
|
22
23
|
' --mermaid-board-style-type Optional integer style_type for board createPlantuml.',
|
|
@@ -63,6 +64,7 @@ export function parsePublishMdArgs(argv, env = process.env) {
|
|
|
63
64
|
let ytDlpPath = '';
|
|
64
65
|
let ytDlpCookiesPath = '';
|
|
65
66
|
let pipelineCacheDir = '';
|
|
67
|
+
let singleDollarTextMath;
|
|
66
68
|
let mermaidTarget;
|
|
67
69
|
let mermaidBoardSyntaxType;
|
|
68
70
|
let mermaidBoardStyleType;
|
|
@@ -175,6 +177,10 @@ export function parsePublishMdArgs(argv, env = process.env) {
|
|
|
175
177
|
i += 1;
|
|
176
178
|
continue;
|
|
177
179
|
}
|
|
180
|
+
if (arg === '--single-dollar-text-math' || arg === '--single-dollar-math') {
|
|
181
|
+
singleDollarTextMath = true;
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
178
184
|
if (arg === '--mermaid-target') {
|
|
179
185
|
const value = argv[i + 1];
|
|
180
186
|
if (!value)
|
|
@@ -245,6 +251,7 @@ export function parsePublishMdArgs(argv, env = process.env) {
|
|
|
245
251
|
...(ytDlpPath.trim() ? { ytDlpPath: ytDlpPath.trim() } : {}),
|
|
246
252
|
...(ytDlpCookiesPath.trim() ? { ytDlpCookiesPath: ytDlpCookiesPath.trim() } : {}),
|
|
247
253
|
...(pipelineCacheDir.trim() ? { pipelineCacheDir: pipelineCacheDir.trim() } : {}),
|
|
254
|
+
...(singleDollarTextMath === undefined ? {} : { singleDollarTextMath }),
|
|
248
255
|
...(mermaidTarget ? { mermaidTarget } : {}),
|
|
249
256
|
...(mermaidBoardSyntaxType === undefined ? {} : { mermaidBoardSyntaxType }),
|
|
250
257
|
...(mermaidBoardStyleType === undefined ? {} : { mermaidBoardStyleType }),
|
|
@@ -73,9 +73,7 @@ export async function publishMdToLark(options, env = process.env) {
|
|
|
73
73
|
const runtime = buildPublishRuntime(options, env, markdownPresets);
|
|
74
74
|
logPublishRuntimeSummary(runtime, inputSet.markdownFiles.length, inputSet.mode);
|
|
75
75
|
const normalizedDocumentId = options.documentId ? normalizeDocumentId(options.documentId) : undefined;
|
|
76
|
-
const resolveTargetDocumentId = options.dryRun || normalizedDocumentId
|
|
77
|
-
? undefined
|
|
78
|
-
: createFolderDocumentResolver(runtime, options);
|
|
76
|
+
const resolveTargetDocumentId = options.dryRun || normalizedDocumentId ? undefined : createFolderDocumentResolver(runtime, options);
|
|
79
77
|
const results = [];
|
|
80
78
|
for (let index = 0; index < inputSet.markdownFiles.length; index += 1) {
|
|
81
79
|
const markdownPath = inputSet.markdownFiles[index];
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { applyStandaloneAttachmentTransforms } from '../../publish/asset-adapter.js';
|
|
2
2
|
export { patchBTTForMermaidAndAssets } from '../../publish/btt-patch.js';
|
|
3
3
|
export { buildPipelineDocumentId } from '../../publish/ids.js';
|
|
4
|
-
export { applyTableColumnWidthHeuristics, collectMermaidPatches, ensureLastBlockBttIds } from '../../publish/last-normalize.js';
|
|
4
|
+
export { applyTableColumnWidthHeuristics, collectMermaidPatches, ensureLastBlockBttIds, } from '../../publish/last-normalize.js';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { buildLASTIndexes, flattenTreeBlocks, fromRawBlockToLAST
|
|
1
|
+
import { buildLASTIndexes, flattenTreeBlocks, fromRawBlockToLAST } from './codec-btt-to-last.js';
|
|
2
2
|
import { normalizeDocumentIdToLAST } from './codec-last-to-btt.js';
|
|
3
3
|
export function convertBTTToLAST(bttDoc) {
|
|
4
4
|
const rawBlocks = Object.keys(bttDoc.flatBlocks ?? {}).length
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { createDocumentChildren
|
|
2
|
-
import { DEFAULT_MERMAID_RENDER_CONFIG
|
|
1
|
+
import { createDocumentChildren } from './ops.js';
|
|
2
|
+
import { DEFAULT_MERMAID_RENDER_CONFIG } from './render-types.js';
|
|
3
3
|
import { applyCreatedBoardMermaid, applyCreatedFileBlock, applyCreatedImageBlock } from './render-post-process.js';
|
|
4
4
|
import { renderCreatedTableNode } from './render-table.js';
|
|
5
5
|
import { buildCreatePayloadFromRawBlock, createRenderBatchEntry, getSourceBlockId, toObjectRecord, } from './render-payload.js';
|
|
@@ -124,7 +124,8 @@ export function canUseElementsOnlyPatch(rawBlock) {
|
|
|
124
124
|
if (effectiveEntries.length === 0)
|
|
125
125
|
return true;
|
|
126
126
|
for (const [key, value] of effectiveEntries) {
|
|
127
|
-
if (key === 'align' &&
|
|
127
|
+
if (key === 'align' &&
|
|
128
|
+
(value === 1 || value === 2 || value === 3 || value === 'left' || value === 'center' || value === 'right')) {
|
|
128
129
|
continue;
|
|
129
130
|
}
|
|
130
131
|
return false;
|
|
@@ -310,7 +311,12 @@ export function getSourceBlockId(node, rawBlockRecord) {
|
|
|
310
311
|
: node.blockId;
|
|
311
312
|
}
|
|
312
313
|
function isBatchSafeBlockType(blockType) {
|
|
313
|
-
if (blockType === 1 ||
|
|
314
|
+
if (blockType === 1 ||
|
|
315
|
+
blockType === 23 ||
|
|
316
|
+
blockType === 27 ||
|
|
317
|
+
blockType === 31 ||
|
|
318
|
+
blockType === 32 ||
|
|
319
|
+
blockType === 43) {
|
|
314
320
|
return false;
|
|
315
321
|
}
|
|
316
322
|
return true;
|
|
@@ -33,7 +33,9 @@ export async function applyCreatedImageBlock(client, documentId, createdBlockId,
|
|
|
33
33
|
...(typeof width === 'number' ? { width } : {}),
|
|
34
34
|
...(typeof proportionalHeight === 'number' ? { height: proportionalHeight } : {}),
|
|
35
35
|
...(image && typeof image.align === 'number' ? { align: image.align } : {}),
|
|
36
|
-
...(image && toObjectRecord(image.caption)
|
|
36
|
+
...(image && toObjectRecord(image.caption)
|
|
37
|
+
? { caption: toObjectRecord(image.caption) }
|
|
38
|
+
: {}),
|
|
37
39
|
...(image && typeof image.scale === 'number' ? { scale: image.scale } : {}),
|
|
38
40
|
};
|
|
39
41
|
let imageToken = await uploadBinaryToNode(client, 'docx_image', createdBlockId, localPath, authOptions, mediaLimiter);
|
|
@@ -4,12 +4,12 @@ import remarkGfm from 'remark-gfm';
|
|
|
4
4
|
import remarkMath from 'remark-math';
|
|
5
5
|
import remarkRehype from 'remark-rehype';
|
|
6
6
|
import { normalizeMarkdownBeforeParse } from './normalize-markdown.js';
|
|
7
|
-
export async function markdownToHast(markdown) {
|
|
7
|
+
export async function markdownToHast(markdown, options = {}) {
|
|
8
8
|
const content = normalizeMarkdownBeforeParse(markdown);
|
|
9
9
|
const processor = unified()
|
|
10
10
|
.use(remarkParse)
|
|
11
11
|
.use(remarkGfm)
|
|
12
|
-
.use(remarkMath, { singleDollarTextMath: false })
|
|
12
|
+
.use(remarkMath, { singleDollarTextMath: options.singleDollarTextMath ?? false })
|
|
13
13
|
.use(remarkRehype, { allowDangerousHtml: false });
|
|
14
14
|
const mdast = processor.parse(content);
|
|
15
15
|
const hast = await processor.run(mdast);
|
|
@@ -345,6 +345,7 @@ export function renderHASTToTerminal(root, options = {}) {
|
|
|
345
345
|
return `${ctx.lines.join('\n')}\n`;
|
|
346
346
|
}
|
|
347
347
|
export async function renderMarkdownToTerminal(markdown, options = {}) {
|
|
348
|
-
const
|
|
348
|
+
const parseOptions = options.singleDollarTextMath === undefined ? {} : { singleDollarTextMath: options.singleDollarTextMath };
|
|
349
|
+
const hast = await markdownToHast(markdown, parseOptions);
|
|
349
350
|
return renderHASTToTerminal(hast, options);
|
|
350
351
|
}
|
|
@@ -44,7 +44,9 @@ function applyRawBlockPatch(rawBlock, blockId, mermaidByBlockId, assetByBlockId,
|
|
|
44
44
|
file.media_kind = asset.mediaKind;
|
|
45
45
|
}
|
|
46
46
|
if (typeof file.view_type !== 'number') {
|
|
47
|
-
file.view_type = shouldUsePreviewView(path.extname(asset.fileName).toLowerCase(), asset.mediaKind ?? 'file')
|
|
47
|
+
file.view_type = shouldUsePreviewView(path.extname(asset.fileName).toLowerCase(), asset.mediaKind ?? 'file')
|
|
48
|
+
? 2
|
|
49
|
+
: 1;
|
|
48
50
|
}
|
|
49
51
|
rawBlock.file = file;
|
|
50
52
|
}
|
package/dist/publish/index.js
CHANGED
|
@@ -2,6 +2,6 @@ export { applyStandaloneAttachmentTransforms } from './asset-adapter.js';
|
|
|
2
2
|
export { patchBTTForMermaidAndAssets } from './btt-patch.js';
|
|
3
3
|
export { buildPipelineDocumentId } from './ids.js';
|
|
4
4
|
export { applyTableColumnWidthHeuristics, collectMermaidPatches, ensureLastBlockBttIds } from './last-normalize.js';
|
|
5
|
-
export { processSingleMarkdownFile } from './process-file.js';
|
|
6
|
-
export { buildPublishRuntime, logPublishRuntimeSummary } from './runtime.js';
|
|
5
|
+
export { processSingleMarkdownFile, } from './process-file.js';
|
|
6
|
+
export { buildPublishRuntime, logPublishRuntimeSummary, } from './runtime.js';
|
|
7
7
|
export { buildPipelineStagePaths, ensureDir, writeJson, writeSourceStage, writePrepareStage, writePrepareLogFile, writeHastStage, writeLastStage, writeBttStage, writePublishStageArtifact, } from './stage-cache.js';
|
|
@@ -2,7 +2,7 @@ import { readFile } from 'node:fs/promises';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { convertLASTToBTT } from '../interop/index.js';
|
|
4
4
|
import { clearDocumentContent, normalizeDocumentId } from '../lark/docx/ops.js';
|
|
5
|
-
import { renderBTTToDocument
|
|
5
|
+
import { renderBTTToDocument } from '../lark/docx/render-btt.js';
|
|
6
6
|
import { hastToLAST, markdownToHast, prepareMarkdownBeforePublish } from '../pipeline/index.js';
|
|
7
7
|
import { applySingleH1TitleRule, buildTitleForMarkdown } from '../commands/publish-md/title-policy.js';
|
|
8
8
|
import { applyStandaloneAttachmentTransforms } from './asset-adapter.js';
|
|
@@ -95,7 +95,7 @@ export async function processSingleMarkdownFile(params) {
|
|
|
95
95
|
markdown = prepareResult.preparedContent;
|
|
96
96
|
await writePrepareStage(stagePaths, markdown, prepareResult);
|
|
97
97
|
console.error(`[prepare ${index + 1}/${inputSet.markdownFiles.length}] rewritten=${prepareResult.rewrittenCount} downloaded=${prepareResult.downloadedCount} failed=${prepareResult.failedCount} log=${prepareResult.logFilePath}`);
|
|
98
|
-
const hast = await markdownToHast(markdown);
|
|
98
|
+
const hast = await markdownToHast(markdown, runtime.markdownParseConfig);
|
|
99
99
|
await writeHastStage(stagePaths, hast);
|
|
100
100
|
const h1RuleResult = options.title ? {} : applySingleH1TitleRule(hast);
|
|
101
101
|
const title = buildTitleForMarkdown(markdownPath, inputSet, options.title, h1RuleResult.derivedTitle, {
|
package/dist/publish/runtime.js
CHANGED
|
@@ -99,6 +99,9 @@ export function buildPublishRuntime(options, env, markdownPresets) {
|
|
|
99
99
|
...(mermaidBoardDiagramType === undefined ? {} : { diagramType: mermaidBoardDiagramType }),
|
|
100
100
|
},
|
|
101
101
|
};
|
|
102
|
+
const markdownParseConfig = {
|
|
103
|
+
singleDollarTextMath: options.singleDollarTextMath ?? false,
|
|
104
|
+
};
|
|
102
105
|
return {
|
|
103
106
|
env,
|
|
104
107
|
markdownPresets,
|
|
@@ -118,6 +121,7 @@ export function buildPublishRuntime(options, env, markdownPresets) {
|
|
|
118
121
|
downloadRemoteImages,
|
|
119
122
|
...(ytDlpPath ? { ytDlpPath } : {}),
|
|
120
123
|
mermaidRenderConfig,
|
|
124
|
+
markdownParseConfig,
|
|
121
125
|
prepareConfig: {
|
|
122
126
|
enabled: downloadRemoteImages,
|
|
123
127
|
timeoutMs: prepareTimeoutMs,
|
|
@@ -138,6 +142,9 @@ export function logPublishRuntimeSummary(runtime, inputCount, inputMode) {
|
|
|
138
142
|
console.error(runtime.mermaidRenderConfig.target === 'board'
|
|
139
143
|
? `Mermaid: target=board syntax_type=${String(runtime.mermaidRenderConfig.board.syntaxType)} style_type=${String(runtime.mermaidRenderConfig.board.styleType ?? '(default)')} diagram_type=${String(runtime.mermaidRenderConfig.board.diagramType ?? '(default)')}`
|
|
140
144
|
: 'Mermaid: target=text-drawing');
|
|
145
|
+
if (runtime.markdownParseConfig.singleDollarTextMath) {
|
|
146
|
+
console.error('Markdown parse: single_dollar_text_math=true');
|
|
147
|
+
}
|
|
141
148
|
console.error(`Document URL base: ${runtime.documentBaseUrl}`);
|
|
142
149
|
if (runtime.resourceBaseDir) {
|
|
143
150
|
console.error(`Local asset base override: ${runtime.resourceBaseDir}`);
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
const PNG_SIGNATURE = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
|
|
4
|
-
const JPEG_SOF_MARKERS = new Set([
|
|
5
|
-
0xc0, 0xc1, 0xc2, 0xc3, 0xc5, 0xc6, 0xc7, 0xc9, 0xca, 0xcb, 0xcd, 0xce, 0xcf,
|
|
6
|
-
]);
|
|
4
|
+
const JPEG_SOF_MARKERS = new Set([0xc0, 0xc1, 0xc2, 0xc3, 0xc5, 0xc6, 0xc7, 0xc9, 0xca, 0xcb, 0xcd, 0xce, 0xcf]);
|
|
7
5
|
function isPositiveInteger(value) {
|
|
8
6
|
return Number.isInteger(value) && value > 0;
|
|
9
7
|
}
|
|
@@ -68,9 +66,7 @@ function readJpegDimensions(buffer) {
|
|
|
68
66
|
return null;
|
|
69
67
|
}
|
|
70
68
|
function readWebpDimensions(buffer) {
|
|
71
|
-
if (buffer.length < 30 ||
|
|
72
|
-
buffer.toString('ascii', 0, 4) !== 'RIFF' ||
|
|
73
|
-
buffer.toString('ascii', 8, 12) !== 'WEBP') {
|
|
69
|
+
if (buffer.length < 30 || buffer.toString('ascii', 0, 4) !== 'RIFF' || buffer.toString('ascii', 8, 12) !== 'WEBP') {
|
|
74
70
|
return null;
|
|
75
71
|
}
|
|
76
72
|
const chunkType = buffer.toString('ascii', 12, 16);
|