@jacobbubu/md-to-lark 1.5.1 → 1.7.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 +5 -1
- package/dist/commands/publish-md/args.js +63 -1
- package/dist/commands/publish-md/command.js +35 -9
- package/dist/index.js +1 -0
- package/dist/pipeline/hast-to-last.js +375 -9
- package/dist/pipeline/index.js +1 -0
- package/dist/pipeline/markdown/md-to-semantic-hast.js +591 -0
- package/dist/protocol/capabilities.js +31 -0
- package/dist/protocol/contract.js +253 -0
- package/dist/protocol/frontmatter.js +43 -0
- package/dist/protocol/index.js +3 -0
- package/dist/protocol/types.js +1 -0
- package/dist/publish/asset-adapter.js +4 -1
- package/dist/publish/image-size-manifest.js +133 -0
- package/dist/publish/lark-tree-validator.js +31 -0
- package/dist/publish/process-file.js +178 -8
- package/dist/publish/render-report.js +45 -0
- package/dist/publish/runtime.js +4 -0
- package/dist/publish/stage-cache.js +29 -6
- package/package.json +4 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { toString } from 'hast-util-to-string';
|
|
2
2
|
import { createDefaultImagePayload } from '../last/image-defaults.js';
|
|
3
3
|
import { LAST_TEXTUAL_BLOCK_TYPE_SET } from '../last/textual-block-types.js';
|
|
4
|
+
import { parseDirectiveWidth } from './markdown/md-to-semantic-hast.js';
|
|
4
5
|
const BLOCK_CONTAINER_TAGS = new Set([
|
|
5
6
|
'article',
|
|
6
7
|
'section',
|
|
@@ -11,11 +12,15 @@ const BLOCK_CONTAINER_TAGS = new Set([
|
|
|
11
12
|
'footer',
|
|
12
13
|
]);
|
|
13
14
|
const DEFAULT_ALIGN = 'left';
|
|
14
|
-
function createContext() {
|
|
15
|
+
function createContext(options = {}) {
|
|
15
16
|
return {
|
|
16
17
|
blocks: {},
|
|
17
18
|
blockCounter: 1,
|
|
18
19
|
inlineCounter: 1,
|
|
20
|
+
imageSizeContext: options.imageSizeContext ?? {},
|
|
21
|
+
warnedImageSizeKeys: new Set(),
|
|
22
|
+
semanticTarget: options.semanticTarget ?? {},
|
|
23
|
+
...(options.imageSizeResolver ? { imageSizeResolver: options.imageSizeResolver } : {}),
|
|
19
24
|
};
|
|
20
25
|
}
|
|
21
26
|
function nextBlockId(ctx) {
|
|
@@ -145,9 +150,86 @@ function createDividerBlock(ctx, parentId) {
|
|
|
145
150
|
});
|
|
146
151
|
return blockId;
|
|
147
152
|
}
|
|
148
|
-
function
|
|
153
|
+
function warnInvalidImageDisplaySize(ctx, key, message) {
|
|
154
|
+
if (ctx.warnedImageSizeKeys.has(key))
|
|
155
|
+
return;
|
|
156
|
+
ctx.warnedImageSizeKeys.add(key);
|
|
157
|
+
console.warn(`[md-to-lark] ${message}`);
|
|
158
|
+
}
|
|
159
|
+
function buildImageSizeResolverContext(ctx, image) {
|
|
160
|
+
const context = {};
|
|
161
|
+
if (ctx.imageSizeContext.inputPath) {
|
|
162
|
+
context.inputPath = ctx.imageSizeContext.inputPath;
|
|
163
|
+
}
|
|
164
|
+
if (ctx.imageSizeContext.resourceBaseDir) {
|
|
165
|
+
context.resourceBaseDir = ctx.imageSizeContext.resourceBaseDir;
|
|
166
|
+
}
|
|
167
|
+
if (image.alt) {
|
|
168
|
+
context.alt = image.alt;
|
|
169
|
+
}
|
|
170
|
+
if (image.title) {
|
|
171
|
+
context.title = image.title;
|
|
172
|
+
}
|
|
173
|
+
return context;
|
|
174
|
+
}
|
|
175
|
+
function toPositiveRoundedWidth(value, maximum = 1000) {
|
|
176
|
+
if (!Number.isFinite(value) || value <= 0)
|
|
177
|
+
return undefined;
|
|
178
|
+
return Math.min(maximum, Math.max(1, Math.round(value)));
|
|
179
|
+
}
|
|
180
|
+
function applyImageDisplaySize(ctx, image, block) {
|
|
181
|
+
if (!ctx.imageSizeResolver || !image.sourceUrl) {
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
const size = ctx.imageSizeResolver(image.sourceUrl, buildImageSizeResolverContext(ctx, image));
|
|
185
|
+
if (!size) {
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
const applyAspectRatio = () => {
|
|
189
|
+
if (typeof size.aspectRatio === 'number' &&
|
|
190
|
+
Number.isFinite(size.aspectRatio) &&
|
|
191
|
+
size.aspectRatio > 0 &&
|
|
192
|
+
typeof block.payload.width === 'number') {
|
|
193
|
+
block.payload.height = Math.max(1, Math.round(block.payload.width / size.aspectRatio));
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
const hasWidthRatio = typeof size.widthRatio === 'number';
|
|
197
|
+
if (hasWidthRatio) {
|
|
198
|
+
const ratio = size.widthRatio;
|
|
199
|
+
if (Number.isFinite(ratio) && ratio > 0 && ratio <= 1) {
|
|
200
|
+
const baseWidth = block.payload.width ??
|
|
201
|
+
createDefaultImagePayload(block.parentId ? ctx.blocks[block.parentId]?.type : undefined).width ??
|
|
202
|
+
0;
|
|
203
|
+
const width = toPositiveRoundedWidth(baseWidth * ratio);
|
|
204
|
+
if (width !== undefined) {
|
|
205
|
+
block.payload.width = width;
|
|
206
|
+
applyAspectRatio();
|
|
207
|
+
}
|
|
208
|
+
return true;
|
|
209
|
+
}
|
|
210
|
+
warnInvalidImageDisplaySize(ctx, `widthRatio:${image.sourceUrl}:${String(size.widthRatio)}`, `Ignoring invalid image widthRatio for ${image.sourceUrl}: ${String(size.widthRatio)}. Expected 0 < widthRatio <= 1.`);
|
|
211
|
+
}
|
|
212
|
+
if (typeof size.widthPx === 'number') {
|
|
213
|
+
const maximum = createDefaultImagePayload(block.parentId ? ctx.blocks[block.parentId]?.type : undefined).width ?? 1000;
|
|
214
|
+
const width = toPositiveRoundedWidth(size.widthPx, maximum);
|
|
215
|
+
if (width !== undefined) {
|
|
216
|
+
block.payload.width = width;
|
|
217
|
+
applyAspectRatio();
|
|
218
|
+
return true;
|
|
219
|
+
}
|
|
220
|
+
warnInvalidImageDisplaySize(ctx, `widthPx:${image.sourceUrl}:${String(size.widthPx)}`, `Ignoring invalid image widthPx for ${image.sourceUrl}: ${String(size.widthPx)}. Expected widthPx > 0.`);
|
|
221
|
+
}
|
|
222
|
+
return false;
|
|
223
|
+
}
|
|
224
|
+
function createImageBlock(ctx, parentId, sourceUrl, alt = null, title = null, linkHref = null, displayOverride) {
|
|
149
225
|
const blockId = nextBlockId(ctx);
|
|
150
226
|
const parentBlock = ctx.blocks[parentId];
|
|
227
|
+
const image = {
|
|
228
|
+
sourceUrl,
|
|
229
|
+
alt,
|
|
230
|
+
title,
|
|
231
|
+
linkHref,
|
|
232
|
+
};
|
|
151
233
|
const blockBase = {
|
|
152
234
|
id: blockId,
|
|
153
235
|
type: 'image',
|
|
@@ -162,12 +244,90 @@ function createImageBlock(ctx, parentId, sourceUrl, alt = null) {
|
|
|
162
244
|
if (alt) {
|
|
163
245
|
selectorAttrs.alt = alt;
|
|
164
246
|
}
|
|
247
|
+
if (title) {
|
|
248
|
+
selectorAttrs.title = title;
|
|
249
|
+
}
|
|
250
|
+
if (linkHref) {
|
|
251
|
+
selectorAttrs.linkHref = linkHref;
|
|
252
|
+
}
|
|
165
253
|
if (Object.keys(selectorAttrs).length > 0) {
|
|
166
254
|
blockBase.selector = { attrs: selectorAttrs };
|
|
167
255
|
}
|
|
256
|
+
const resolverMatched = applyImageDisplaySize(ctx, image, blockBase);
|
|
257
|
+
const shouldApplyOverride = Boolean(displayOverride && (!displayOverride.fallback || !resolverMatched));
|
|
258
|
+
if (shouldApplyOverride && displayOverride?.widthRatio !== undefined) {
|
|
259
|
+
const ratio = displayOverride.widthRatio;
|
|
260
|
+
if (Number.isFinite(ratio) && ratio > 0 && ratio <= 1) {
|
|
261
|
+
const baseWidth = createDefaultImagePayload(parentBlock?.type).width ?? blockBase.payload.width ?? 0;
|
|
262
|
+
blockBase.payload.width = Math.max(1, Math.round(baseWidth * ratio));
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
else if (shouldApplyOverride && displayOverride?.widthPx !== undefined && displayOverride.widthPx > 0) {
|
|
266
|
+
const maximum = createDefaultImagePayload(parentBlock?.type).width ?? 1000;
|
|
267
|
+
blockBase.payload.width = Math.min(maximum, Math.max(1, Math.round(displayOverride.widthPx)));
|
|
268
|
+
}
|
|
269
|
+
if (shouldApplyOverride && displayOverride?.aspectRatio && blockBase.payload.width) {
|
|
270
|
+
blockBase.payload.height = Math.max(1, Math.round(blockBase.payload.width / displayOverride.aspectRatio));
|
|
271
|
+
}
|
|
272
|
+
if (displayOverride?.align)
|
|
273
|
+
blockBase.payload.align = displayOverride.align;
|
|
168
274
|
addBlock(ctx, blockBase);
|
|
169
275
|
return blockId;
|
|
170
276
|
}
|
|
277
|
+
function attachSemanticMeta(ctx, blockId, role, semanticId) {
|
|
278
|
+
const block = ctx.blocks[blockId];
|
|
279
|
+
if (!block)
|
|
280
|
+
return;
|
|
281
|
+
block.selector = {
|
|
282
|
+
...(block.selector ?? {}),
|
|
283
|
+
labels: [...(block.selector?.labels ?? []), role],
|
|
284
|
+
attrs: {
|
|
285
|
+
...(block.selector?.attrs ?? {}),
|
|
286
|
+
semanticRole: role,
|
|
287
|
+
...(semanticId ? { semanticId } : {}),
|
|
288
|
+
},
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
function normalizeCalloutColor(value) {
|
|
292
|
+
return value?.trim().toLowerCase().replaceAll('-', '_') || undefined;
|
|
293
|
+
}
|
|
294
|
+
function calloutStyleForElement(element, footnote) {
|
|
295
|
+
const target = footnote ? undefined : getStringProp(element, 'type');
|
|
296
|
+
const defaults = {
|
|
297
|
+
note: { backgroundColor: 'light_gray', borderColor: 'gray' },
|
|
298
|
+
info: { backgroundColor: 'light_blue', borderColor: 'blue' },
|
|
299
|
+
tip: { backgroundColor: 'light_green', borderColor: 'green' },
|
|
300
|
+
warning: { backgroundColor: 'light_yellow', borderColor: 'yellow' },
|
|
301
|
+
danger: { backgroundColor: 'light_red', borderColor: 'red' },
|
|
302
|
+
important: { backgroundColor: 'light_purple', borderColor: 'purple' },
|
|
303
|
+
};
|
|
304
|
+
const style = defaults[target ?? 'note'] ?? defaults.note;
|
|
305
|
+
return {
|
|
306
|
+
backgroundColor: style.backgroundColor,
|
|
307
|
+
borderColor: style.borderColor,
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
function createCalloutBlock(ctx, parentId, element, footnote) {
|
|
311
|
+
const blockId = nextBlockId(ctx);
|
|
312
|
+
const base = calloutStyleForElement(element, footnote);
|
|
313
|
+
const footnoteTarget = ctx.semanticTarget.footnotes;
|
|
314
|
+
const backgroundColor = footnote ? normalizeCalloutColor(footnoteTarget?.background_color ?? null) : undefined;
|
|
315
|
+
const borderColor = footnote ? normalizeCalloutColor(footnoteTarget?.border_color ?? null) : undefined;
|
|
316
|
+
addBlock(ctx, {
|
|
317
|
+
id: blockId,
|
|
318
|
+
type: 'callout',
|
|
319
|
+
parentId,
|
|
320
|
+
children: [],
|
|
321
|
+
payload: {
|
|
322
|
+
...base,
|
|
323
|
+
...(backgroundColor ? { backgroundColor: backgroundColor } : {}),
|
|
324
|
+
...(borderColor ? { borderColor: borderColor } : {}),
|
|
325
|
+
...(footnoteTarget?.icon ? { emojiId: footnoteTarget.icon } : {}),
|
|
326
|
+
},
|
|
327
|
+
});
|
|
328
|
+
attachSemanticMeta(ctx, blockId, footnote ? 'footnote' : 'callout', getStringProp(element, 'semanticId') ?? undefined);
|
|
329
|
+
return blockId;
|
|
330
|
+
}
|
|
171
331
|
function createIframeBlock(ctx, parentId, url, iframeType) {
|
|
172
332
|
const blockId = nextBlockId(ctx);
|
|
173
333
|
addBlock(ctx, {
|
|
@@ -351,7 +511,7 @@ function parseInlineNodes(ctx, nodes, marks = createDefaultMarks()) {
|
|
|
351
511
|
if (node.tagName === 'u') {
|
|
352
512
|
nextMarks.underline = true;
|
|
353
513
|
}
|
|
354
|
-
if (node.tagName === 'a') {
|
|
514
|
+
if (node.tagName === 'a' || node.tagName === 'm2l-footnote-reference') {
|
|
355
515
|
const href = getStringProp(node, 'href');
|
|
356
516
|
nextMarks.link = href ? { url: href } : null;
|
|
357
517
|
}
|
|
@@ -370,17 +530,21 @@ function extractImageSourceFromElement(element) {
|
|
|
370
530
|
return {
|
|
371
531
|
sourceUrl: getStringProp(element, 'src'),
|
|
372
532
|
alt: getStringProp(element, 'alt'),
|
|
533
|
+
title: getStringProp(element, 'title'),
|
|
534
|
+
linkHref: null,
|
|
373
535
|
};
|
|
374
536
|
}
|
|
375
537
|
if (element.tagName !== 'a')
|
|
376
538
|
return null;
|
|
539
|
+
const href = getStringProp(element, 'href');
|
|
377
540
|
const meaningfulChildren = getMeaningfulChildren(getChildren(element));
|
|
378
541
|
if (meaningfulChildren.length !== 1)
|
|
379
542
|
return null;
|
|
380
543
|
const only = meaningfulChildren[0];
|
|
381
544
|
if (!only || !isElement(only))
|
|
382
545
|
return null;
|
|
383
|
-
|
|
546
|
+
const imageSource = extractImageSourceFromElement(only);
|
|
547
|
+
return imageSource ? { ...imageSource, linkHref: href } : null;
|
|
384
548
|
}
|
|
385
549
|
function findStandaloneImageInParagraph(paragraph) {
|
|
386
550
|
const meaningfulChildren = getMeaningfulChildren(getChildren(paragraph));
|
|
@@ -692,7 +856,7 @@ function convertTable(ctx, table, parentId) {
|
|
|
692
856
|
const cellBlock = ctx.blocks[cellId];
|
|
693
857
|
const richItem = cell ? findStandaloneRichItemInTableCell(cell) : null;
|
|
694
858
|
if (cellBlock?.type === 'table_cell' && richItem?.kind === 'image') {
|
|
695
|
-
const imageId = createImageBlock(ctx, cellId, richItem.sourceUrl, richItem.alt);
|
|
859
|
+
const imageId = createImageBlock(ctx, cellId, richItem.sourceUrl, richItem.alt, richItem.title, richItem.linkHref);
|
|
696
860
|
cellBlock.children = [imageId];
|
|
697
861
|
cells.push(cellId);
|
|
698
862
|
continue;
|
|
@@ -764,7 +928,9 @@ function hasNonWhitespaceInline(inlines) {
|
|
|
764
928
|
function convertParagraph(ctx, paragraph, parentId) {
|
|
765
929
|
const standaloneImage = findStandaloneImageInParagraph(paragraph);
|
|
766
930
|
if (standaloneImage?.sourceUrl) {
|
|
767
|
-
return [
|
|
931
|
+
return [
|
|
932
|
+
createImageBlock(ctx, parentId, standaloneImage.sourceUrl, standaloneImage.alt, standaloneImage.title, standaloneImage.linkHref),
|
|
933
|
+
];
|
|
768
934
|
}
|
|
769
935
|
const standaloneIframe = findStandaloneIframePayloadInParagraph(paragraph);
|
|
770
936
|
if (standaloneIframe) {
|
|
@@ -785,7 +951,7 @@ function convertParagraph(ctx, paragraph, parentId) {
|
|
|
785
951
|
const image = isElement(child) ? extractImageSourceFromElement(child) : null;
|
|
786
952
|
if (image?.sourceUrl) {
|
|
787
953
|
flushText();
|
|
788
|
-
ids.push(createImageBlock(ctx, parentId, image.sourceUrl, image.alt));
|
|
954
|
+
ids.push(createImageBlock(ctx, parentId, image.sourceUrl, image.alt, image.title, image.linkHref));
|
|
789
955
|
continue;
|
|
790
956
|
}
|
|
791
957
|
pendingInlineNodes.push(child);
|
|
@@ -796,6 +962,189 @@ function convertParagraph(ctx, paragraph, parentId) {
|
|
|
796
962
|
}
|
|
797
963
|
return [createTextualBlock(ctx, 'text', parentId, parseInlineNodes(ctx, getChildren(paragraph)))];
|
|
798
964
|
}
|
|
965
|
+
function collectSemanticImages(element) {
|
|
966
|
+
const images = [];
|
|
967
|
+
const visit = (node) => {
|
|
968
|
+
if (!isElement(node))
|
|
969
|
+
return;
|
|
970
|
+
const image = extractImageSourceFromElement(node);
|
|
971
|
+
if (image?.sourceUrl) {
|
|
972
|
+
images.push(image);
|
|
973
|
+
return;
|
|
974
|
+
}
|
|
975
|
+
for (const child of getChildren(node))
|
|
976
|
+
visit(child);
|
|
977
|
+
};
|
|
978
|
+
for (const child of getChildren(element))
|
|
979
|
+
visit(child);
|
|
980
|
+
return images;
|
|
981
|
+
}
|
|
982
|
+
function findDirectSemanticChild(element, tagName) {
|
|
983
|
+
return getChildren(element).find((child) => isElement(child) && child.tagName === tagName);
|
|
984
|
+
}
|
|
985
|
+
function createSemanticTextBlock(ctx, element, parentId, role, semanticId) {
|
|
986
|
+
const blockId = createTextualBlock(ctx, 'text', parentId, parseInlineNodes(ctx, getChildren(element)));
|
|
987
|
+
attachSemanticMeta(ctx, blockId, role, semanticId);
|
|
988
|
+
return blockId;
|
|
989
|
+
}
|
|
990
|
+
function parseSemanticAlign(element) {
|
|
991
|
+
return parseAlignValue(getStringProp(element, 'align'));
|
|
992
|
+
}
|
|
993
|
+
function semanticAnnotationIds(ctx, wrapper, parentId, semanticId) {
|
|
994
|
+
const result = {};
|
|
995
|
+
for (const role of ['caption', 'note', 'source']) {
|
|
996
|
+
const element = findDirectSemanticChild(wrapper, `m2l-${role}`);
|
|
997
|
+
if (element)
|
|
998
|
+
result[role] = createSemanticTextBlock(ctx, element, parentId, role, semanticId);
|
|
999
|
+
}
|
|
1000
|
+
return result;
|
|
1001
|
+
}
|
|
1002
|
+
function convertSemanticFigure(ctx, figure, parentId) {
|
|
1003
|
+
const semanticId = getStringProp(figure, 'semanticId') ?? undefined;
|
|
1004
|
+
const display = parseDirectiveWidth(getStringProp(figure, 'width') ?? '');
|
|
1005
|
+
const fallbackRatio = ctx.semanticTarget.figures?.default_width_ratio;
|
|
1006
|
+
const effectiveDisplay = display ??
|
|
1007
|
+
(typeof fallbackRatio === 'number' && fallbackRatio > 0 && fallbackRatio <= 1
|
|
1008
|
+
? { widthRatio: fallbackRatio, fallback: true }
|
|
1009
|
+
: undefined);
|
|
1010
|
+
const align = parseSemanticAlign(figure);
|
|
1011
|
+
const imageIds = collectSemanticImages(figure).map((image) => {
|
|
1012
|
+
const imageId = createImageBlock(ctx, parentId, image.sourceUrl, image.alt, image.title, image.linkHref, {
|
|
1013
|
+
...(effectiveDisplay ?? {}),
|
|
1014
|
+
...(align ? { align } : {}),
|
|
1015
|
+
});
|
|
1016
|
+
attachSemanticMeta(ctx, imageId, 'figure-image', semanticId);
|
|
1017
|
+
return imageId;
|
|
1018
|
+
});
|
|
1019
|
+
const annotations = semanticAnnotationIds(ctx, figure, parentId, semanticId);
|
|
1020
|
+
const target = ctx.semanticTarget.figures;
|
|
1021
|
+
const ids = [];
|
|
1022
|
+
if (target?.caption_position === 'above' && annotations.caption)
|
|
1023
|
+
ids.push(annotations.caption);
|
|
1024
|
+
if (target?.source_position === 'above' && annotations.source)
|
|
1025
|
+
ids.push(annotations.source);
|
|
1026
|
+
if (target?.note_position === 'above' && annotations.note)
|
|
1027
|
+
ids.push(annotations.note);
|
|
1028
|
+
ids.push(...imageIds);
|
|
1029
|
+
if (target?.caption_position !== 'above' && annotations.caption)
|
|
1030
|
+
ids.push(annotations.caption);
|
|
1031
|
+
if (target?.source_position === 'below-caption' && annotations.source)
|
|
1032
|
+
ids.push(annotations.source);
|
|
1033
|
+
if (target?.note_position !== 'above' && annotations.note)
|
|
1034
|
+
ids.push(annotations.note);
|
|
1035
|
+
if (target?.source_position !== 'above' && target?.source_position !== 'below-caption' && annotations.source) {
|
|
1036
|
+
ids.push(annotations.source);
|
|
1037
|
+
}
|
|
1038
|
+
return ids;
|
|
1039
|
+
}
|
|
1040
|
+
function convertSemanticTable(ctx, wrapper, parentId) {
|
|
1041
|
+
const semanticId = getStringProp(wrapper, 'semanticId') ?? undefined;
|
|
1042
|
+
const table = getChildren(wrapper).find((child) => isElement(child) && child.tagName === 'table');
|
|
1043
|
+
const annotations = semanticAnnotationIds(ctx, wrapper, parentId, semanticId);
|
|
1044
|
+
const tableIds = table ? convertTable(ctx, table, parentId) : [];
|
|
1045
|
+
for (const tableId of tableIds)
|
|
1046
|
+
attachSemanticMeta(ctx, tableId, 'semantic-table', semanticId);
|
|
1047
|
+
const target = ctx.semanticTarget.tables;
|
|
1048
|
+
const ids = [];
|
|
1049
|
+
if (target?.caption_position !== 'below' && annotations.caption)
|
|
1050
|
+
ids.push(annotations.caption);
|
|
1051
|
+
if (target?.source_position === 'above' && annotations.source)
|
|
1052
|
+
ids.push(annotations.source);
|
|
1053
|
+
ids.push(...tableIds);
|
|
1054
|
+
if (target?.caption_position === 'below' && annotations.caption)
|
|
1055
|
+
ids.push(annotations.caption);
|
|
1056
|
+
if (annotations.note)
|
|
1057
|
+
ids.push(annotations.note);
|
|
1058
|
+
if (target?.source_position !== 'above' && annotations.source)
|
|
1059
|
+
ids.push(annotations.source);
|
|
1060
|
+
return ids;
|
|
1061
|
+
}
|
|
1062
|
+
function convertSemanticEquation(ctx, wrapper, parentId) {
|
|
1063
|
+
const semanticId = getStringProp(wrapper, 'semanticId') ?? undefined;
|
|
1064
|
+
const ids = [];
|
|
1065
|
+
const equationNumber = getStringProp(wrapper, 'equationNumber');
|
|
1066
|
+
let numberAttached = false;
|
|
1067
|
+
for (const child of getChildren(wrapper)) {
|
|
1068
|
+
if (isElement(child) && ['m2l-caption', 'm2l-note', 'm2l-source'].includes(child.tagName))
|
|
1069
|
+
continue;
|
|
1070
|
+
for (const blockId of convertBlock(ctx, child, parentId)) {
|
|
1071
|
+
attachSemanticMeta(ctx, blockId, 'equation', semanticId);
|
|
1072
|
+
const block = ctx.blocks[blockId];
|
|
1073
|
+
if (equationNumber && !numberAttached && block && isTextualBlockNode(block)) {
|
|
1074
|
+
block.payload.inlines.push({
|
|
1075
|
+
id: nextInlineId(ctx),
|
|
1076
|
+
kind: 'text_run',
|
|
1077
|
+
marks: createDefaultMarks(),
|
|
1078
|
+
text: ` (${equationNumber})`,
|
|
1079
|
+
});
|
|
1080
|
+
numberAttached = true;
|
|
1081
|
+
}
|
|
1082
|
+
ids.push(blockId);
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
const annotations = semanticAnnotationIds(ctx, wrapper, parentId, semanticId);
|
|
1086
|
+
if (annotations.caption)
|
|
1087
|
+
ids.push(annotations.caption);
|
|
1088
|
+
if (annotations.note)
|
|
1089
|
+
ids.push(annotations.note);
|
|
1090
|
+
if (annotations.source)
|
|
1091
|
+
ids.push(annotations.source);
|
|
1092
|
+
return ids;
|
|
1093
|
+
}
|
|
1094
|
+
function prependFootnoteLabel(ctx, blockId, label) {
|
|
1095
|
+
const block = ctx.blocks[blockId];
|
|
1096
|
+
if (!block || !isTextualBlockNode(block))
|
|
1097
|
+
return;
|
|
1098
|
+
block.payload.inlines.unshift({
|
|
1099
|
+
id: nextInlineId(ctx),
|
|
1100
|
+
kind: 'text_run',
|
|
1101
|
+
marks: { ...createDefaultMarks(), bold: true },
|
|
1102
|
+
text: `[${label}] `,
|
|
1103
|
+
});
|
|
1104
|
+
}
|
|
1105
|
+
function convertSemanticCallout(ctx, element, parentId, footnote) {
|
|
1106
|
+
const output = [];
|
|
1107
|
+
let calloutId = createCalloutBlock(ctx, parentId, element, footnote);
|
|
1108
|
+
output.push(calloutId);
|
|
1109
|
+
let firstText = true;
|
|
1110
|
+
const appendConverted = (blockId) => {
|
|
1111
|
+
const block = ctx.blocks[blockId];
|
|
1112
|
+
if (!block)
|
|
1113
|
+
return;
|
|
1114
|
+
if (block.type === 'image' || block.type === 'table' || block.type === 'file' || block.type === 'board') {
|
|
1115
|
+
block.parentId = parentId;
|
|
1116
|
+
attachSemanticMeta(ctx, blockId, footnote ? 'footnote-media-sibling' : 'callout-media-sibling', getStringProp(element, 'semanticId') ?? undefined);
|
|
1117
|
+
output.push(blockId);
|
|
1118
|
+
calloutId = createCalloutBlock(ctx, parentId, element, footnote);
|
|
1119
|
+
output.push(calloutId);
|
|
1120
|
+
return;
|
|
1121
|
+
}
|
|
1122
|
+
const callout = ctx.blocks[calloutId];
|
|
1123
|
+
if (!callout || callout.type !== 'callout')
|
|
1124
|
+
return;
|
|
1125
|
+
block.parentId = calloutId;
|
|
1126
|
+
appendChild(callout, blockId);
|
|
1127
|
+
if (footnote && firstText && isTextualBlockNode(block)) {
|
|
1128
|
+
prependFootnoteLabel(ctx, blockId, getStringProp(element, 'semanticId') ?? '?');
|
|
1129
|
+
firstText = false;
|
|
1130
|
+
}
|
|
1131
|
+
};
|
|
1132
|
+
for (const child of getChildren(element)) {
|
|
1133
|
+
const current = ctx.blocks[calloutId];
|
|
1134
|
+
if (!current || current.type !== 'callout')
|
|
1135
|
+
continue;
|
|
1136
|
+
for (const blockId of convertBlock(ctx, child, calloutId))
|
|
1137
|
+
appendConverted(blockId);
|
|
1138
|
+
}
|
|
1139
|
+
for (const blockId of [...output]) {
|
|
1140
|
+
const block = ctx.blocks[blockId];
|
|
1141
|
+
if (block?.type === 'callout' && block.children.length === 0) {
|
|
1142
|
+
delete ctx.blocks[blockId];
|
|
1143
|
+
output.splice(output.indexOf(blockId), 1);
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
return output;
|
|
1147
|
+
}
|
|
799
1148
|
function convertBlock(ctx, node, parentId) {
|
|
800
1149
|
if (isWhitespaceTextNode(node)) {
|
|
801
1150
|
return [];
|
|
@@ -842,8 +1191,25 @@ function convertBlock(ctx, node, parentId) {
|
|
|
842
1191
|
return [createDividerBlock(ctx, parentId)];
|
|
843
1192
|
case 'table':
|
|
844
1193
|
return convertTable(ctx, node, parentId);
|
|
1194
|
+
case 'm2l-figure':
|
|
1195
|
+
return convertSemanticFigure(ctx, node, parentId);
|
|
1196
|
+
case 'm2l-table':
|
|
1197
|
+
return convertSemanticTable(ctx, node, parentId);
|
|
1198
|
+
case 'm2l-equation':
|
|
1199
|
+
return convertSemanticEquation(ctx, node, parentId);
|
|
1200
|
+
case 'm2l-callout':
|
|
1201
|
+
return convertSemanticCallout(ctx, node, parentId, false);
|
|
1202
|
+
case 'm2l-footnote':
|
|
1203
|
+
return convertSemanticCallout(ctx, node, parentId, true);
|
|
1204
|
+
case 'm2l-unknown-directive':
|
|
1205
|
+
case 'm2l-caption':
|
|
1206
|
+
case 'm2l-note':
|
|
1207
|
+
case 'm2l-source':
|
|
1208
|
+
return convertUnknownElement(ctx, node, parentId);
|
|
845
1209
|
case 'img':
|
|
846
|
-
return [
|
|
1210
|
+
return [
|
|
1211
|
+
createImageBlock(ctx, parentId, getStringProp(node, 'src'), getStringProp(node, 'alt'), getStringProp(node, 'title')),
|
|
1212
|
+
];
|
|
847
1213
|
case 'br':
|
|
848
1214
|
return [
|
|
849
1215
|
createTextualBlock(ctx, 'text', parentId, [
|
|
@@ -956,7 +1322,7 @@ function deepCloneBlock(value) {
|
|
|
956
1322
|
return JSON.parse(JSON.stringify(value));
|
|
957
1323
|
}
|
|
958
1324
|
export function hastToLAST(hast, options) {
|
|
959
|
-
const ctx = createContext();
|
|
1325
|
+
const ctx = createContext(options);
|
|
960
1326
|
const mode = options?.mode ?? 'fragment';
|
|
961
1327
|
const rootId = createTextualBlock(ctx, 'page', null, []);
|
|
962
1328
|
const root = ctx.blocks[rootId];
|
package/dist/pipeline/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export { markdownToHast } from './markdown/md-to-hast.js';
|
|
2
|
+
export { markdownToSemanticHast, parseDirectiveWidth } from './markdown/md-to-semantic-hast.js';
|
|
2
3
|
export { prepareMarkdownBeforePublish } from './markdown/prepare-markdown.js';
|
|
3
4
|
export { hastToLAST } from './hast-to-last.js';
|