@jacobbubu/md-to-lark 1.4.2 → 1.4.4

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.
@@ -1,3 +1,4 @@
1
+ import { getScaledHeightForLocalImage } from '../../shared/image-metadata.js';
1
2
  import { createBoardPlantumlNode, getDocumentBlockById, getRawDocumentBlockById, isRelationMismatchError, replaceFileBlock, replaceImageBlock, uploadBinaryToNode, } from './ops.js';
2
3
  import { toObjectRecord } from './render-payload.js';
3
4
  function extractWhiteboardId(rawBlock) {
@@ -25,9 +26,12 @@ export async function applyCreatedImageBlock(client, documentId, createdBlockId,
25
26
  const localPath = image && typeof image.local_path === 'string' ? image.local_path : '';
26
27
  if (!localPath)
27
28
  return null;
29
+ const width = image && typeof image.width === 'number' && image.width > 0 ? image.width : undefined;
30
+ const explicitHeight = image && typeof image.height === 'number' && image.height > 0 ? image.height : undefined;
31
+ const proportionalHeight = explicitHeight ?? getScaledHeightForLocalImage(localPath, width);
28
32
  const replaceOptions = {
29
- ...(image && typeof image.width === 'number' ? { width: image.width } : {}),
30
- ...(image && typeof image.height === 'number' ? { height: image.height } : {}),
33
+ ...(typeof width === 'number' ? { width } : {}),
34
+ ...(typeof proportionalHeight === 'number' ? { height: proportionalHeight } : {}),
31
35
  ...(image && typeof image.align === 'number' ? { align: image.align } : {}),
32
36
  ...(image && toObjectRecord(image.caption) ? { caption: toObjectRecord(image.caption) } : {}),
33
37
  ...(image && typeof image.scale === 'number' ? { scale: image.scale } : {}),
@@ -42,6 +42,9 @@ export function resolveLocalPathFromSource(sourceUrl, baseDir) {
42
42
  const raw = sourceUrl.trim();
43
43
  if (!raw)
44
44
  return null;
45
+ if (raw.startsWith('#')) {
46
+ return null;
47
+ }
45
48
  if (isHttpLike(raw)) {
46
49
  if (/^https?:/i.test(raw)) {
47
50
  return null;
@@ -49,6 +52,9 @@ export function resolveLocalPathFromSource(sourceUrl, baseDir) {
49
52
  return null;
50
53
  }
51
54
  const decoded = safeDecodeURIComponent(stripQueryAndHash(raw));
55
+ if (!decoded.trim()) {
56
+ return null;
57
+ }
52
58
  const absolute = path.isAbsolute(decoded) ? decoded : path.resolve(baseDir, decoded);
53
59
  return absolute;
54
60
  }
@@ -0,0 +1,152 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import path from 'node:path';
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
+ ]);
7
+ function isPositiveInteger(value) {
8
+ return Number.isInteger(value) && value > 0;
9
+ }
10
+ function normalizeDimensions(width, height) {
11
+ if (!isPositiveInteger(width) || !isPositiveInteger(height)) {
12
+ return null;
13
+ }
14
+ return { width, height };
15
+ }
16
+ function readPngDimensions(buffer) {
17
+ if (buffer.length < 24 || !buffer.subarray(0, 8).equals(PNG_SIGNATURE)) {
18
+ return null;
19
+ }
20
+ return normalizeDimensions(buffer.readUInt32BE(16), buffer.readUInt32BE(20));
21
+ }
22
+ function readGifDimensions(buffer) {
23
+ if (buffer.length < 10)
24
+ return null;
25
+ const signature = buffer.toString('ascii', 0, 6);
26
+ if (signature !== 'GIF87a' && signature !== 'GIF89a')
27
+ return null;
28
+ return normalizeDimensions(buffer.readUInt16LE(6), buffer.readUInt16LE(8));
29
+ }
30
+ function readBmpDimensions(buffer) {
31
+ if (buffer.length < 26 || buffer.toString('ascii', 0, 2) !== 'BM') {
32
+ return null;
33
+ }
34
+ const width = buffer.readInt32LE(18);
35
+ const height = Math.abs(buffer.readInt32LE(22));
36
+ return normalizeDimensions(width, height);
37
+ }
38
+ function readJpegDimensions(buffer) {
39
+ if (buffer.length < 4 || buffer[0] !== 0xff || buffer[1] !== 0xd8) {
40
+ return null;
41
+ }
42
+ let offset = 2;
43
+ while (offset + 4 <= buffer.length) {
44
+ while (offset < buffer.length && buffer[offset] === 0xff) {
45
+ offset += 1;
46
+ }
47
+ if (offset >= buffer.length)
48
+ return null;
49
+ const marker = buffer.readUInt8(offset);
50
+ offset += 1;
51
+ if (marker === 0xd8 || marker === 0xd9) {
52
+ continue;
53
+ }
54
+ if (offset + 2 > buffer.length)
55
+ return null;
56
+ const segmentLength = buffer.readUInt16BE(offset);
57
+ if (segmentLength < 2 || offset + segmentLength > buffer.length)
58
+ return null;
59
+ if (JPEG_SOF_MARKERS.has(marker)) {
60
+ if (segmentLength < 7)
61
+ return null;
62
+ const height = buffer.readUInt16BE(offset + 3);
63
+ const width = buffer.readUInt16BE(offset + 5);
64
+ return normalizeDimensions(width, height);
65
+ }
66
+ offset += segmentLength;
67
+ }
68
+ return null;
69
+ }
70
+ function readWebpDimensions(buffer) {
71
+ if (buffer.length < 30 ||
72
+ buffer.toString('ascii', 0, 4) !== 'RIFF' ||
73
+ buffer.toString('ascii', 8, 12) !== 'WEBP') {
74
+ return null;
75
+ }
76
+ const chunkType = buffer.toString('ascii', 12, 16);
77
+ if (chunkType === 'VP8X' && buffer.length >= 30) {
78
+ const width = 1 + buffer.readUIntLE(24, 3);
79
+ const height = 1 + buffer.readUIntLE(27, 3);
80
+ return normalizeDimensions(width, height);
81
+ }
82
+ if (chunkType === 'VP8L' && buffer.length >= 25) {
83
+ const byte20 = buffer.readUInt8(20);
84
+ const byte21 = buffer.readUInt8(21);
85
+ const byte22 = buffer.readUInt8(22);
86
+ const byte23 = buffer.readUInt8(23);
87
+ const byte24 = buffer.readUInt8(24);
88
+ const width = 1 + (((byte21 & 0x3f) << 8) | byte20);
89
+ const height = 1 + (((byte24 & 0x0f) << 10) | (byte23 << 2) | ((byte22 & 0xc0) >> 6));
90
+ return normalizeDimensions(width, height);
91
+ }
92
+ if (chunkType === 'VP8 ' && buffer.length >= 30) {
93
+ const width = buffer.readUInt16LE(26) & 0x3fff;
94
+ const height = buffer.readUInt16LE(28) & 0x3fff;
95
+ return normalizeDimensions(width, height);
96
+ }
97
+ return null;
98
+ }
99
+ function parseSvgLength(value) {
100
+ if (!value)
101
+ return null;
102
+ const match = value.trim().match(/^([0-9]+(?:\.[0-9]+)?)/);
103
+ if (!match)
104
+ return null;
105
+ const parsed = Number(match[1]);
106
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
107
+ }
108
+ function readSvgDimensions(filePath) {
109
+ const source = readFileSync(filePath, 'utf8');
110
+ if (!/<svg\b/i.test(source))
111
+ return null;
112
+ const widthMatch = source.match(/\bwidth\s*=\s*["']([^"']+)["']/i);
113
+ const heightMatch = source.match(/\bheight\s*=\s*["']([^"']+)["']/i);
114
+ const width = parseSvgLength(widthMatch?.[1]);
115
+ const height = parseSvgLength(heightMatch?.[1]);
116
+ if (width && height) {
117
+ return normalizeDimensions(Math.round(width), Math.round(height));
118
+ }
119
+ const viewBoxMatch = source.match(/\bviewBox\s*=\s*["']([^"']+)["']/i);
120
+ if (!viewBoxMatch)
121
+ return null;
122
+ const parts = (viewBoxMatch[1] ?? '')
123
+ .trim()
124
+ .split(/[\s,]+/)
125
+ .map((part) => Number(part));
126
+ if (parts.length !== 4 || parts.some((part) => !Number.isFinite(part))) {
127
+ return null;
128
+ }
129
+ return normalizeDimensions(Math.round(parts[2] ?? 0), Math.round(parts[3] ?? 0));
130
+ }
131
+ export function readLocalImageDimensions(filePath) {
132
+ const extension = path.extname(filePath).toLowerCase();
133
+ if (extension === '.svg') {
134
+ return readSvgDimensions(filePath);
135
+ }
136
+ const buffer = readFileSync(filePath);
137
+ return (readPngDimensions(buffer) ??
138
+ readJpegDimensions(buffer) ??
139
+ readGifDimensions(buffer) ??
140
+ readWebpDimensions(buffer) ??
141
+ readBmpDimensions(buffer));
142
+ }
143
+ export function getScaledHeightForWidth(dimensions, targetWidth) {
144
+ if (!dimensions || !Number.isFinite(targetWidth) || !targetWidth || targetWidth <= 0) {
145
+ return undefined;
146
+ }
147
+ const scaled = Math.round((dimensions.height * targetWidth) / dimensions.width);
148
+ return scaled > 0 ? scaled : undefined;
149
+ }
150
+ export function getScaledHeightForLocalImage(filePath, targetWidth) {
151
+ return getScaledHeightForWidth(readLocalImageDimensions(filePath), targetWidth);
152
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jacobbubu/md-to-lark",
3
- "version": "1.4.2",
3
+ "version": "1.4.4",
4
4
  "description": "Publish Markdown to Feishu docs with a stable pipeline.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",