@jacobbubu/md-to-lark 1.5.1 → 1.6.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
CHANGED
|
@@ -174,6 +174,7 @@ Guardrails:
|
|
|
174
174
|
- Single-file and recursive directory publish
|
|
175
175
|
- Title derivation, title prefix, and single-H1 promotion
|
|
176
176
|
- Local attachment and image detection with real upload
|
|
177
|
+
- Programmatic image display width control through `imageSizeResolver`
|
|
177
178
|
- Remote image download and standalone URL preparation
|
|
178
179
|
- Mermaid `text-drawing` and `board` output paths
|
|
179
180
|
- Opt-in single-dollar inline math parsing for LaTeX-heavy papers
|
|
@@ -11,11 +11,14 @@ const BLOCK_CONTAINER_TAGS = new Set([
|
|
|
11
11
|
'footer',
|
|
12
12
|
]);
|
|
13
13
|
const DEFAULT_ALIGN = 'left';
|
|
14
|
-
function createContext() {
|
|
14
|
+
function createContext(options = {}) {
|
|
15
15
|
return {
|
|
16
16
|
blocks: {},
|
|
17
17
|
blockCounter: 1,
|
|
18
18
|
inlineCounter: 1,
|
|
19
|
+
imageSizeContext: options.imageSizeContext ?? {},
|
|
20
|
+
warnedImageSizeKeys: new Set(),
|
|
21
|
+
...(options.imageSizeResolver ? { imageSizeResolver: options.imageSizeResolver } : {}),
|
|
19
22
|
};
|
|
20
23
|
}
|
|
21
24
|
function nextBlockId(ctx) {
|
|
@@ -145,9 +148,74 @@ function createDividerBlock(ctx, parentId) {
|
|
|
145
148
|
});
|
|
146
149
|
return blockId;
|
|
147
150
|
}
|
|
148
|
-
function
|
|
151
|
+
function warnInvalidImageDisplaySize(ctx, key, message) {
|
|
152
|
+
if (ctx.warnedImageSizeKeys.has(key))
|
|
153
|
+
return;
|
|
154
|
+
ctx.warnedImageSizeKeys.add(key);
|
|
155
|
+
console.warn(`[md-to-lark] ${message}`);
|
|
156
|
+
}
|
|
157
|
+
function buildImageSizeResolverContext(ctx, image) {
|
|
158
|
+
const context = {};
|
|
159
|
+
if (ctx.imageSizeContext.inputPath) {
|
|
160
|
+
context.inputPath = ctx.imageSizeContext.inputPath;
|
|
161
|
+
}
|
|
162
|
+
if (ctx.imageSizeContext.resourceBaseDir) {
|
|
163
|
+
context.resourceBaseDir = ctx.imageSizeContext.resourceBaseDir;
|
|
164
|
+
}
|
|
165
|
+
if (image.alt) {
|
|
166
|
+
context.alt = image.alt;
|
|
167
|
+
}
|
|
168
|
+
if (image.title) {
|
|
169
|
+
context.title = image.title;
|
|
170
|
+
}
|
|
171
|
+
return context;
|
|
172
|
+
}
|
|
173
|
+
function toPositiveRoundedWidth(value) {
|
|
174
|
+
if (!Number.isFinite(value) || value <= 0)
|
|
175
|
+
return undefined;
|
|
176
|
+
return Math.max(1, Math.round(value));
|
|
177
|
+
}
|
|
178
|
+
function applyImageDisplaySize(ctx, image, block) {
|
|
179
|
+
if (!ctx.imageSizeResolver || !image.sourceUrl) {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
const size = ctx.imageSizeResolver(image.sourceUrl, buildImageSizeResolverContext(ctx, image));
|
|
183
|
+
if (!size) {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
const hasWidthRatio = typeof size.widthRatio === 'number';
|
|
187
|
+
if (hasWidthRatio) {
|
|
188
|
+
const ratio = size.widthRatio;
|
|
189
|
+
if (Number.isFinite(ratio) && ratio > 0 && ratio <= 1) {
|
|
190
|
+
const baseWidth = block.payload.width ??
|
|
191
|
+
createDefaultImagePayload(block.parentId ? ctx.blocks[block.parentId]?.type : undefined).width ??
|
|
192
|
+
0;
|
|
193
|
+
const width = toPositiveRoundedWidth(baseWidth * ratio);
|
|
194
|
+
if (width !== undefined) {
|
|
195
|
+
block.payload.width = width;
|
|
196
|
+
}
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
warnInvalidImageDisplaySize(ctx, `widthRatio:${image.sourceUrl}:${String(size.widthRatio)}`, `Ignoring invalid image widthRatio for ${image.sourceUrl}: ${String(size.widthRatio)}. Expected 0 < widthRatio <= 1.`);
|
|
200
|
+
}
|
|
201
|
+
if (typeof size.widthPx === 'number') {
|
|
202
|
+
const width = toPositiveRoundedWidth(size.widthPx);
|
|
203
|
+
if (width !== undefined) {
|
|
204
|
+
block.payload.width = width;
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
warnInvalidImageDisplaySize(ctx, `widthPx:${image.sourceUrl}:${String(size.widthPx)}`, `Ignoring invalid image widthPx for ${image.sourceUrl}: ${String(size.widthPx)}. Expected widthPx > 0.`);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
function createImageBlock(ctx, parentId, sourceUrl, alt = null, title = null, linkHref = null) {
|
|
149
211
|
const blockId = nextBlockId(ctx);
|
|
150
212
|
const parentBlock = ctx.blocks[parentId];
|
|
213
|
+
const image = {
|
|
214
|
+
sourceUrl,
|
|
215
|
+
alt,
|
|
216
|
+
title,
|
|
217
|
+
linkHref,
|
|
218
|
+
};
|
|
151
219
|
const blockBase = {
|
|
152
220
|
id: blockId,
|
|
153
221
|
type: 'image',
|
|
@@ -162,9 +230,16 @@ function createImageBlock(ctx, parentId, sourceUrl, alt = null) {
|
|
|
162
230
|
if (alt) {
|
|
163
231
|
selectorAttrs.alt = alt;
|
|
164
232
|
}
|
|
233
|
+
if (title) {
|
|
234
|
+
selectorAttrs.title = title;
|
|
235
|
+
}
|
|
236
|
+
if (linkHref) {
|
|
237
|
+
selectorAttrs.linkHref = linkHref;
|
|
238
|
+
}
|
|
165
239
|
if (Object.keys(selectorAttrs).length > 0) {
|
|
166
240
|
blockBase.selector = { attrs: selectorAttrs };
|
|
167
241
|
}
|
|
242
|
+
applyImageDisplaySize(ctx, image, blockBase);
|
|
168
243
|
addBlock(ctx, blockBase);
|
|
169
244
|
return blockId;
|
|
170
245
|
}
|
|
@@ -370,17 +445,21 @@ function extractImageSourceFromElement(element) {
|
|
|
370
445
|
return {
|
|
371
446
|
sourceUrl: getStringProp(element, 'src'),
|
|
372
447
|
alt: getStringProp(element, 'alt'),
|
|
448
|
+
title: getStringProp(element, 'title'),
|
|
449
|
+
linkHref: null,
|
|
373
450
|
};
|
|
374
451
|
}
|
|
375
452
|
if (element.tagName !== 'a')
|
|
376
453
|
return null;
|
|
454
|
+
const href = getStringProp(element, 'href');
|
|
377
455
|
const meaningfulChildren = getMeaningfulChildren(getChildren(element));
|
|
378
456
|
if (meaningfulChildren.length !== 1)
|
|
379
457
|
return null;
|
|
380
458
|
const only = meaningfulChildren[0];
|
|
381
459
|
if (!only || !isElement(only))
|
|
382
460
|
return null;
|
|
383
|
-
|
|
461
|
+
const imageSource = extractImageSourceFromElement(only);
|
|
462
|
+
return imageSource ? { ...imageSource, linkHref: href } : null;
|
|
384
463
|
}
|
|
385
464
|
function findStandaloneImageInParagraph(paragraph) {
|
|
386
465
|
const meaningfulChildren = getMeaningfulChildren(getChildren(paragraph));
|
|
@@ -692,7 +771,7 @@ function convertTable(ctx, table, parentId) {
|
|
|
692
771
|
const cellBlock = ctx.blocks[cellId];
|
|
693
772
|
const richItem = cell ? findStandaloneRichItemInTableCell(cell) : null;
|
|
694
773
|
if (cellBlock?.type === 'table_cell' && richItem?.kind === 'image') {
|
|
695
|
-
const imageId = createImageBlock(ctx, cellId, richItem.sourceUrl, richItem.alt);
|
|
774
|
+
const imageId = createImageBlock(ctx, cellId, richItem.sourceUrl, richItem.alt, richItem.title, richItem.linkHref);
|
|
696
775
|
cellBlock.children = [imageId];
|
|
697
776
|
cells.push(cellId);
|
|
698
777
|
continue;
|
|
@@ -764,7 +843,9 @@ function hasNonWhitespaceInline(inlines) {
|
|
|
764
843
|
function convertParagraph(ctx, paragraph, parentId) {
|
|
765
844
|
const standaloneImage = findStandaloneImageInParagraph(paragraph);
|
|
766
845
|
if (standaloneImage?.sourceUrl) {
|
|
767
|
-
return [
|
|
846
|
+
return [
|
|
847
|
+
createImageBlock(ctx, parentId, standaloneImage.sourceUrl, standaloneImage.alt, standaloneImage.title, standaloneImage.linkHref),
|
|
848
|
+
];
|
|
768
849
|
}
|
|
769
850
|
const standaloneIframe = findStandaloneIframePayloadInParagraph(paragraph);
|
|
770
851
|
if (standaloneIframe) {
|
|
@@ -785,7 +866,7 @@ function convertParagraph(ctx, paragraph, parentId) {
|
|
|
785
866
|
const image = isElement(child) ? extractImageSourceFromElement(child) : null;
|
|
786
867
|
if (image?.sourceUrl) {
|
|
787
868
|
flushText();
|
|
788
|
-
ids.push(createImageBlock(ctx, parentId, image.sourceUrl, image.alt));
|
|
869
|
+
ids.push(createImageBlock(ctx, parentId, image.sourceUrl, image.alt, image.title, image.linkHref));
|
|
789
870
|
continue;
|
|
790
871
|
}
|
|
791
872
|
pendingInlineNodes.push(child);
|
|
@@ -843,7 +924,9 @@ function convertBlock(ctx, node, parentId) {
|
|
|
843
924
|
case 'table':
|
|
844
925
|
return convertTable(ctx, node, parentId);
|
|
845
926
|
case 'img':
|
|
846
|
-
return [
|
|
927
|
+
return [
|
|
928
|
+
createImageBlock(ctx, parentId, getStringProp(node, 'src'), getStringProp(node, 'alt'), getStringProp(node, 'title')),
|
|
929
|
+
];
|
|
847
930
|
case 'br':
|
|
848
931
|
return [
|
|
849
932
|
createTextualBlock(ctx, 'text', parentId, [
|
|
@@ -956,7 +1039,7 @@ function deepCloneBlock(value) {
|
|
|
956
1039
|
return JSON.parse(JSON.stringify(value));
|
|
957
1040
|
}
|
|
958
1041
|
export function hastToLAST(hast, options) {
|
|
959
|
-
const ctx = createContext();
|
|
1042
|
+
const ctx = createContext(options);
|
|
960
1043
|
const mode = options?.mode ?? 'fragment';
|
|
961
1044
|
const rootId = createTextualBlock(ctx, 'page', null, []);
|
|
962
1045
|
const root = ctx.blocks[rootId];
|
|
@@ -105,6 +105,15 @@ export async function processSingleMarkdownFile(params) {
|
|
|
105
105
|
const last = hastToLAST(hast, {
|
|
106
106
|
documentId: documentKey,
|
|
107
107
|
mode: 'fragment',
|
|
108
|
+
...(runtime.imageSizeResolver
|
|
109
|
+
? {
|
|
110
|
+
imageSizeResolver: runtime.imageSizeResolver,
|
|
111
|
+
imageSizeContext: {
|
|
112
|
+
inputPath: markdownPath,
|
|
113
|
+
resourceBaseDir,
|
|
114
|
+
},
|
|
115
|
+
}
|
|
116
|
+
: {}),
|
|
108
117
|
});
|
|
109
118
|
await writeLastStage(stagePaths, last);
|
|
110
119
|
ensureLastBlockBttIds(last);
|
package/dist/publish/runtime.js
CHANGED
|
@@ -122,6 +122,7 @@ export function buildPublishRuntime(options, env, markdownPresets) {
|
|
|
122
122
|
...(ytDlpPath ? { ytDlpPath } : {}),
|
|
123
123
|
mermaidRenderConfig,
|
|
124
124
|
markdownParseConfig,
|
|
125
|
+
...(options.imageSizeResolver ? { imageSizeResolver: options.imageSizeResolver } : {}),
|
|
125
126
|
prepareConfig: {
|
|
126
127
|
enabled: downloadRemoteImages,
|
|
127
128
|
timeoutMs: prepareTimeoutMs,
|
|
@@ -145,6 +146,9 @@ export function logPublishRuntimeSummary(runtime, inputCount, inputMode) {
|
|
|
145
146
|
if (runtime.markdownParseConfig.singleDollarTextMath) {
|
|
146
147
|
console.error('Markdown parse: single_dollar_text_math=true');
|
|
147
148
|
}
|
|
149
|
+
if (runtime.imageSizeResolver) {
|
|
150
|
+
console.error('Image display size resolver: enabled');
|
|
151
|
+
}
|
|
148
152
|
console.error(`Document URL base: ${runtime.documentBaseUrl}`);
|
|
149
153
|
if (runtime.resourceBaseDir) {
|
|
150
154
|
console.error(`Local asset base override: ${runtime.resourceBaseDir}`);
|