feishu-md-sync 0.1.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/LICENSE +21 -0
- package/NOTICE +5 -0
- package/README.md +107 -0
- package/dist/adapters/feishu-adapter.d.ts +44 -0
- package/dist/adapters/feishu-adapter.js +1 -0
- package/dist/adapters/lark-cli-adapter.d.ts +45 -0
- package/dist/adapters/lark-cli-adapter.js +229 -0
- package/dist/cli/commands/core.d.ts +2 -0
- package/dist/cli/commands/core.js +135 -0
- package/dist/cli/commands/publish.d.ts +2 -0
- package/dist/cli/commands/publish.js +63 -0
- package/dist/cli/env.d.ts +27 -0
- package/dist/cli/env.js +113 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +52 -0
- package/dist/cli/output.d.ts +4 -0
- package/dist/cli/output.js +18 -0
- package/dist/config/sync-config.d.ts +13 -0
- package/dist/config/sync-config.js +60 -0
- package/dist/core/diff.d.ts +1 -0
- package/dist/core/diff.js +20 -0
- package/dist/core/doc-id.d.ts +12 -0
- package/dist/core/doc-id.js +41 -0
- package/dist/core/hash.d.ts +6 -0
- package/dist/core/hash.js +84 -0
- package/dist/core/markdown-canonical.d.ts +2 -0
- package/dist/core/markdown-canonical.js +11 -0
- package/dist/diff/run-diff.d.ts +23 -0
- package/dist/diff/run-diff.js +33 -0
- package/dist/feishu/types.d.ts +101 -0
- package/dist/feishu/types.js +1 -0
- package/dist/markdown/blocks.d.ts +5 -0
- package/dist/markdown/blocks.js +305 -0
- package/dist/markdown/from-blocks.d.ts +2 -0
- package/dist/markdown/from-blocks.js +110 -0
- package/dist/markdown/links.d.ts +3 -0
- package/dist/markdown/links.js +44 -0
- package/dist/merge/line-merge.d.ts +21 -0
- package/dist/merge/line-merge.js +270 -0
- package/dist/merge/merge-state.d.ts +33 -0
- package/dist/merge/merge-state.js +44 -0
- package/dist/merge/run-merge.d.ts +38 -0
- package/dist/merge/run-merge.js +139 -0
- package/dist/profiles/publish-profile.d.ts +9 -0
- package/dist/profiles/publish-profile.js +9 -0
- package/dist/publish/block-patch-plan.d.ts +34 -0
- package/dist/publish/block-patch-plan.js +223 -0
- package/dist/publish/block-state.d.ts +8 -0
- package/dist/publish/block-state.js +82 -0
- package/dist/publish/block-update.d.ts +4 -0
- package/dist/publish/block-update.js +40 -0
- package/dist/publish/profile-transform.d.ts +5 -0
- package/dist/publish/profile-transform.js +6 -0
- package/dist/publish/publish-plan.d.ts +35 -0
- package/dist/publish/publish-plan.js +113 -0
- package/dist/publish/run-publish.d.ts +25 -0
- package/dist/publish/run-publish.js +299 -0
- package/dist/publish/title.d.ts +9 -0
- package/dist/publish/title.js +20 -0
- package/dist/pull/run-pull.d.ts +23 -0
- package/dist/pull/run-pull.js +57 -0
- package/dist/receipts/publish-receipt.d.ts +49 -0
- package/dist/receipts/publish-receipt.js +52 -0
- package/dist/receipts/pull-receipt.d.ts +31 -0
- package/dist/receipts/pull-receipt.js +30 -0
- package/dist/status/run-status.d.ts +56 -0
- package/dist/status/run-status.js +131 -0
- package/dist/transform/include-tags.d.ts +6 -0
- package/dist/transform/include-tags.js +22 -0
- package/dist/transform/zilliz-publish.d.ts +5 -0
- package/dist/transform/zilliz-publish.js +42 -0
- package/dist/transform/zilliz-pull.d.ts +6 -0
- package/dist/transform/zilliz-pull.js +34 -0
- package/package.json +61 -0
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
import { normalizeMarkdownLinkUrl } from './links.js';
|
|
2
|
+
const BLOCK_TYPES = {
|
|
3
|
+
text: 2,
|
|
4
|
+
heading1: 3,
|
|
5
|
+
heading2: 4,
|
|
6
|
+
heading3: 5,
|
|
7
|
+
heading4: 6,
|
|
8
|
+
heading5: 7,
|
|
9
|
+
heading6: 8,
|
|
10
|
+
bullet: 12,
|
|
11
|
+
ordered: 13,
|
|
12
|
+
code: 14,
|
|
13
|
+
table: 31
|
|
14
|
+
};
|
|
15
|
+
export const LANG_IDS = {
|
|
16
|
+
plaintext: 1,
|
|
17
|
+
text: 1,
|
|
18
|
+
bash: 7,
|
|
19
|
+
shell: 62,
|
|
20
|
+
sh: 62,
|
|
21
|
+
cpp: 9,
|
|
22
|
+
c: 10,
|
|
23
|
+
go: 22,
|
|
24
|
+
golang: 22,
|
|
25
|
+
json: 28,
|
|
26
|
+
java: 29,
|
|
27
|
+
javascript: 30,
|
|
28
|
+
js: 30,
|
|
29
|
+
nodejs: 30,
|
|
30
|
+
node: 30,
|
|
31
|
+
markdown: 40,
|
|
32
|
+
python: 49,
|
|
33
|
+
py: 49,
|
|
34
|
+
restful: 7,
|
|
35
|
+
rest: 7,
|
|
36
|
+
sql: 57,
|
|
37
|
+
typescript: 64,
|
|
38
|
+
ts: 64,
|
|
39
|
+
yaml: 67,
|
|
40
|
+
yml: 67
|
|
41
|
+
};
|
|
42
|
+
export function languageIdForMarkdownLanguage(language) {
|
|
43
|
+
return LANG_IDS[language.toLowerCase()] ?? LANG_IDS.plaintext;
|
|
44
|
+
}
|
|
45
|
+
export function markdownToFeishuBlocks(markdown) {
|
|
46
|
+
const lines = markdown.replace(/\r\n/g, '\n').split('\n');
|
|
47
|
+
const blocks = [];
|
|
48
|
+
for (let index = 0; index < lines.length;) {
|
|
49
|
+
const line = lines[index] ?? '';
|
|
50
|
+
if (line.trim() === '') {
|
|
51
|
+
index += 1;
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
const fence = line.match(/^```(\S*)\s*$/);
|
|
55
|
+
if (fence) {
|
|
56
|
+
const lang = fence[1] || 'plaintext';
|
|
57
|
+
const codeLines = [];
|
|
58
|
+
index += 1;
|
|
59
|
+
while (index < lines.length && !/^```\s*$/.test(lines[index] ?? '')) {
|
|
60
|
+
codeLines.push(lines[index] ?? '');
|
|
61
|
+
index += 1;
|
|
62
|
+
}
|
|
63
|
+
if (index < lines.length)
|
|
64
|
+
index += 1;
|
|
65
|
+
blocks.push(codeBlock(codeLines.join('\n'), lang));
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (isTableStart(lines, index)) {
|
|
69
|
+
const tableLines = [];
|
|
70
|
+
while (index < lines.length && /^\s*\|.*\|\s*$/.test(lines[index] ?? '')) {
|
|
71
|
+
tableLines.push(lines[index] ?? '');
|
|
72
|
+
index += 1;
|
|
73
|
+
}
|
|
74
|
+
blocks.push(tableBlock(tableLines));
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
const heading = line.match(/^(#{1,6})\s+(.+)$/);
|
|
78
|
+
if (heading) {
|
|
79
|
+
blocks.push(headingBlock(heading[1].length, stripHeadingAnchor(heading[2])));
|
|
80
|
+
index += 1;
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
const listMarker = parseListMarker(line);
|
|
84
|
+
if (listMarker) {
|
|
85
|
+
while (index < lines.length) {
|
|
86
|
+
const item = parseListMarker(lines[index] ?? '');
|
|
87
|
+
if (!item)
|
|
88
|
+
break;
|
|
89
|
+
index += 1;
|
|
90
|
+
const childLines = [];
|
|
91
|
+
while (index < lines.length) {
|
|
92
|
+
const childLine = lines[index] ?? '';
|
|
93
|
+
if (childLine.trim() === '')
|
|
94
|
+
break;
|
|
95
|
+
if (parseListMarker(childLine))
|
|
96
|
+
break;
|
|
97
|
+
if (/^(#{1,6})\s+/.test(childLine) || /^```/.test(childLine) || isTableStart(lines, index))
|
|
98
|
+
break;
|
|
99
|
+
childLines.push(childLine.trim());
|
|
100
|
+
index += 1;
|
|
101
|
+
}
|
|
102
|
+
blocks.push(listBlock(item.text, item.ordered, childLines.map((childLine) => textBlock(childLine))));
|
|
103
|
+
}
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
const paragraphLines = [line.trim()];
|
|
107
|
+
index += 1;
|
|
108
|
+
while (index < lines.length &&
|
|
109
|
+
lines[index]?.trim() !== '' &&
|
|
110
|
+
!/^(#{1,6})\s+/.test(lines[index] ?? '') &&
|
|
111
|
+
!/^```/.test(lines[index] ?? '') &&
|
|
112
|
+
!parseListMarker(lines[index] ?? '') &&
|
|
113
|
+
!isTableStart(lines, index)) {
|
|
114
|
+
paragraphLines.push((lines[index] ?? '').trim());
|
|
115
|
+
index += 1;
|
|
116
|
+
}
|
|
117
|
+
blocks.push(textBlock(paragraphLines.join(' ')));
|
|
118
|
+
}
|
|
119
|
+
return blocks;
|
|
120
|
+
}
|
|
121
|
+
function textElement(content, style = {}) {
|
|
122
|
+
return {
|
|
123
|
+
text_run: {
|
|
124
|
+
content,
|
|
125
|
+
text_element_style: {
|
|
126
|
+
bold: false,
|
|
127
|
+
italic: false,
|
|
128
|
+
strikethrough: false,
|
|
129
|
+
underline: false,
|
|
130
|
+
inline_code: false,
|
|
131
|
+
...style
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
export function parseInlineText(text) {
|
|
137
|
+
const elements = [];
|
|
138
|
+
let cursor = 0;
|
|
139
|
+
const pattern = /(==[^=]+==|`[^`]+`|\*\*[^*]+\*\*|\*[^*\n]+\*|\[[^\]]+\]\([^)]+\))/g;
|
|
140
|
+
for (const match of text.matchAll(pattern)) {
|
|
141
|
+
const start = match.index ?? 0;
|
|
142
|
+
if (start > cursor) {
|
|
143
|
+
elements.push(textElement(text.slice(cursor, start)));
|
|
144
|
+
}
|
|
145
|
+
const token = match[0];
|
|
146
|
+
if (token.startsWith('==')) {
|
|
147
|
+
elements.push(textElement(token.slice(2, -2), { background_color: 5 }));
|
|
148
|
+
}
|
|
149
|
+
else if (token.startsWith('`')) {
|
|
150
|
+
elements.push(textElement(token.slice(1, -1), { inline_code: true }));
|
|
151
|
+
}
|
|
152
|
+
else if (token.startsWith('**')) {
|
|
153
|
+
elements.push(textElement(token.slice(2, -2), { bold: true }));
|
|
154
|
+
}
|
|
155
|
+
else if (token.startsWith('*')) {
|
|
156
|
+
elements.push(textElement(token.slice(1, -1), { italic: true }));
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
const link = token.match(/^\[([^\]]+)\]\(([^)]+)\)$/);
|
|
160
|
+
if (link) {
|
|
161
|
+
elements.push(textElement(link[1], { link: { url: normalizeMarkdownLinkUrl(link[2]) } }));
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
cursor = start + token.length;
|
|
165
|
+
}
|
|
166
|
+
if (cursor < text.length) {
|
|
167
|
+
elements.push(textElement(text.slice(cursor)));
|
|
168
|
+
}
|
|
169
|
+
return elements.length > 0 ? elements : [textElement('')];
|
|
170
|
+
}
|
|
171
|
+
function textBlock(text, style = {}) {
|
|
172
|
+
return {
|
|
173
|
+
block_type: BLOCK_TYPES.text,
|
|
174
|
+
text: {
|
|
175
|
+
elements: parseInlineText(text).map((element) => {
|
|
176
|
+
if (!element.text_run)
|
|
177
|
+
return element;
|
|
178
|
+
return {
|
|
179
|
+
...element,
|
|
180
|
+
text_run: {
|
|
181
|
+
...element.text_run,
|
|
182
|
+
text_element_style: {
|
|
183
|
+
...element.text_run.text_element_style,
|
|
184
|
+
...style
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
}),
|
|
189
|
+
style: { align: 1 }
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
function headingBlock(level, text) {
|
|
194
|
+
const safeLevel = Math.min(Math.max(level, 1), 6);
|
|
195
|
+
return {
|
|
196
|
+
block_type: BLOCK_TYPES[`heading${safeLevel}`],
|
|
197
|
+
[`heading${safeLevel}`]: {
|
|
198
|
+
elements: parseInlineText(text),
|
|
199
|
+
style: { align: 1 }
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
function listBlock(text, ordered, children = []) {
|
|
204
|
+
const key = ordered ? 'ordered' : 'bullet';
|
|
205
|
+
const block = {
|
|
206
|
+
block_type: ordered ? BLOCK_TYPES.ordered : BLOCK_TYPES.bullet,
|
|
207
|
+
[key]: {
|
|
208
|
+
elements: parseInlineText(text),
|
|
209
|
+
style: {}
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
if (children.length > 0)
|
|
213
|
+
block.children = children;
|
|
214
|
+
return block;
|
|
215
|
+
}
|
|
216
|
+
function codeBlock(text, lang) {
|
|
217
|
+
return {
|
|
218
|
+
block_type: BLOCK_TYPES.code,
|
|
219
|
+
code: {
|
|
220
|
+
elements: [textElement(text)],
|
|
221
|
+
style: {
|
|
222
|
+
language: languageIdForMarkdownLanguage(lang)
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
function parseListMarker(line) {
|
|
228
|
+
const bullet = line.match(/^\s*[-*]\s+(.+)$/);
|
|
229
|
+
if (bullet)
|
|
230
|
+
return { ordered: false, text: bullet[1].trim() };
|
|
231
|
+
const ordered = line.match(/^\s*\d+[.)]\s+(.+)$/);
|
|
232
|
+
if (ordered)
|
|
233
|
+
return { ordered: true, text: ordered[1].trim() };
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
function stripHeadingAnchor(text) {
|
|
237
|
+
return text.trim().replace(/\s*\{#[A-Za-z0-9_-]+\}\s*$/, '').trim();
|
|
238
|
+
}
|
|
239
|
+
function isTableStart(lines, index) {
|
|
240
|
+
return (/^\s*\|.*\|\s*$/.test(lines[index] ?? '') &&
|
|
241
|
+
/^\s*\|?\s*:?-{3,}:?\s*(\|\s*:?-{3,}:?\s*)+\|?\s*$/.test(lines[index + 1] ?? ''));
|
|
242
|
+
}
|
|
243
|
+
function tableBlock(lines) {
|
|
244
|
+
const dataRows = lines.filter((_, index) => index !== 1).map(parseTableRow);
|
|
245
|
+
const rowSize = dataRows.length;
|
|
246
|
+
const columnSize = Math.max(...dataRows.map((row) => row.length));
|
|
247
|
+
const cells = dataRows.flatMap((row) => {
|
|
248
|
+
return Array.from({ length: columnSize }, (_, index) => textBlock(row[index] ?? '', row === dataRows[0] ? { bold: true } : {}));
|
|
249
|
+
});
|
|
250
|
+
return {
|
|
251
|
+
block_type: BLOCK_TYPES.table,
|
|
252
|
+
table: {
|
|
253
|
+
property: {
|
|
254
|
+
row_size: rowSize,
|
|
255
|
+
column_size: columnSize,
|
|
256
|
+
merge_info: Array.from({ length: rowSize * columnSize }, () => null)
|
|
257
|
+
},
|
|
258
|
+
cells
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
function parseTableRow(line) {
|
|
263
|
+
const trimmed = line.trim();
|
|
264
|
+
const content = trimmed.startsWith('|') ? trimmed.slice(1) : trimmed;
|
|
265
|
+
const withoutTrailingPipe = hasUnescapedTrailingPipe(content) ? content.slice(0, -1) : content;
|
|
266
|
+
const cells = [];
|
|
267
|
+
let current = '';
|
|
268
|
+
let inCode = false;
|
|
269
|
+
let escaping = false;
|
|
270
|
+
for (const char of withoutTrailingPipe) {
|
|
271
|
+
if (escaping) {
|
|
272
|
+
current += char;
|
|
273
|
+
escaping = false;
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
if (char === '\\') {
|
|
277
|
+
escaping = true;
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
if (char === '`') {
|
|
281
|
+
inCode = !inCode;
|
|
282
|
+
current += char;
|
|
283
|
+
continue;
|
|
284
|
+
}
|
|
285
|
+
if (char === '|' && !inCode) {
|
|
286
|
+
cells.push(current.trim());
|
|
287
|
+
current = '';
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
current += char;
|
|
291
|
+
}
|
|
292
|
+
if (escaping)
|
|
293
|
+
current += '\\';
|
|
294
|
+
cells.push(current.trim());
|
|
295
|
+
return cells;
|
|
296
|
+
}
|
|
297
|
+
function hasUnescapedTrailingPipe(value) {
|
|
298
|
+
if (!value.endsWith('|'))
|
|
299
|
+
return false;
|
|
300
|
+
let backslashCount = 0;
|
|
301
|
+
for (let index = value.length - 2; index >= 0 && value[index] === '\\'; index -= 1) {
|
|
302
|
+
backslashCount += 1;
|
|
303
|
+
}
|
|
304
|
+
return backslashCount % 2 === 0;
|
|
305
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { normalizeMarkdownLinkUrl } from './links.js';
|
|
2
|
+
const LANGUAGE_BY_ID = {
|
|
3
|
+
7: 'bash',
|
|
4
|
+
9: 'cpp',
|
|
5
|
+
22: 'go',
|
|
6
|
+
28: 'json',
|
|
7
|
+
29: 'java',
|
|
8
|
+
30: 'javascript',
|
|
9
|
+
40: 'markdown',
|
|
10
|
+
49: 'python',
|
|
11
|
+
50: 'python',
|
|
12
|
+
57: 'sql',
|
|
13
|
+
64: 'typescript',
|
|
14
|
+
67: 'yaml'
|
|
15
|
+
};
|
|
16
|
+
export function feishuBlocksToMarkdown(blocks) {
|
|
17
|
+
const parts = blocks.map(renderBlock).filter((part) => part.trim() !== '');
|
|
18
|
+
return parts.length > 0 ? `${parts.join('\n\n')}\n` : '';
|
|
19
|
+
}
|
|
20
|
+
function renderBlock(block) {
|
|
21
|
+
if (block.block_type >= 3 && block.block_type <= 8) {
|
|
22
|
+
const level = block.block_type - 2;
|
|
23
|
+
const heading = block[`heading${level}`];
|
|
24
|
+
return `${'#'.repeat(level)} ${renderElements(heading?.elements)}`;
|
|
25
|
+
}
|
|
26
|
+
if (block.block_type === 2) {
|
|
27
|
+
return renderElements(block.text?.elements);
|
|
28
|
+
}
|
|
29
|
+
if (block.block_type === 12) {
|
|
30
|
+
return `- ${renderElements(block.bullet?.elements)}`;
|
|
31
|
+
}
|
|
32
|
+
if (block.block_type === 13) {
|
|
33
|
+
return `1. ${renderElements(block.ordered?.elements)}`;
|
|
34
|
+
}
|
|
35
|
+
if (block.block_type === 14) {
|
|
36
|
+
const code = block.code;
|
|
37
|
+
const lang = code?.style?.language ? LANGUAGE_BY_ID[code.style.language] ?? '' : '';
|
|
38
|
+
return `\`\`\`${lang}\n${renderElements(code?.elements)}\n\`\`\``;
|
|
39
|
+
}
|
|
40
|
+
if (block.block_type === 31) {
|
|
41
|
+
return renderTable(block);
|
|
42
|
+
}
|
|
43
|
+
if (block.block_type === 19) {
|
|
44
|
+
return renderCallout(block);
|
|
45
|
+
}
|
|
46
|
+
if (block.block_type === 49) {
|
|
47
|
+
return renderSourceSynced(block);
|
|
48
|
+
}
|
|
49
|
+
return `<!-- unsupported Feishu block_type ${block.block_type} omitted by pull -->`;
|
|
50
|
+
}
|
|
51
|
+
function renderSourceSynced(block) {
|
|
52
|
+
const children = Array.isArray(block.children) ? block.children.filter(isBlock) : [];
|
|
53
|
+
return children.map(renderBlock).filter((part) => part.trim() !== '').join('\n\n');
|
|
54
|
+
}
|
|
55
|
+
function renderElements(elements = []) {
|
|
56
|
+
return elements.map((element) => {
|
|
57
|
+
const run = element.text_run;
|
|
58
|
+
if (!run) {
|
|
59
|
+
return renderNonTextElement(element);
|
|
60
|
+
}
|
|
61
|
+
const style = run.text_element_style ?? {};
|
|
62
|
+
let text = run.content;
|
|
63
|
+
if (style.inline_code)
|
|
64
|
+
text = `\`${text}\``;
|
|
65
|
+
if (style.bold)
|
|
66
|
+
text = `**${text}**`;
|
|
67
|
+
if (style.link?.url)
|
|
68
|
+
text = `[${text}](${normalizeMarkdownLinkUrl(style.link.url)})`;
|
|
69
|
+
return text;
|
|
70
|
+
}).join('');
|
|
71
|
+
}
|
|
72
|
+
function renderNonTextElement(element) {
|
|
73
|
+
const mentionDoc = element.mention_doc;
|
|
74
|
+
if (mentionDoc?.title && mentionDoc.url) {
|
|
75
|
+
return `[${mentionDoc.title}](${mentionDoc.url})`;
|
|
76
|
+
}
|
|
77
|
+
if (mentionDoc?.title) {
|
|
78
|
+
return mentionDoc.title;
|
|
79
|
+
}
|
|
80
|
+
return '';
|
|
81
|
+
}
|
|
82
|
+
function renderTable(block) {
|
|
83
|
+
const table = block.table;
|
|
84
|
+
const rows = table?.property?.row_size ?? 0;
|
|
85
|
+
const cols = table?.property?.column_size ?? 0;
|
|
86
|
+
const cells = table?.cells ?? [];
|
|
87
|
+
if (rows === 0 || cols === 0)
|
|
88
|
+
return '';
|
|
89
|
+
const renderedRows = Array.from({ length: rows }, (_, row) => {
|
|
90
|
+
const values = Array.from({ length: cols }, (_, col) => renderBlock(cells[row * cols + col] ?? { block_type: 2 }));
|
|
91
|
+
return `| ${values.join(' | ')} |`;
|
|
92
|
+
});
|
|
93
|
+
const separator = `| ${Array.from({ length: cols }, () => '---').join(' | ')} |`;
|
|
94
|
+
return [renderedRows[0], separator, ...renderedRows.slice(1)].join('\n');
|
|
95
|
+
}
|
|
96
|
+
function renderCallout(block) {
|
|
97
|
+
const children = Array.isArray(block.children) ? block.children.filter(isBlock) : [];
|
|
98
|
+
const rendered = children.map(renderBlock).filter((part) => part.trim() !== '');
|
|
99
|
+
const body = dropCalloutTitle(rendered).join('\n\n');
|
|
100
|
+
return body ? `:::note\n${body}\n:::` : ':::note\n:::';
|
|
101
|
+
}
|
|
102
|
+
function dropCalloutTitle(rendered) {
|
|
103
|
+
if (rendered.length > 1 && /^notes?$/i.test(rendered[0]?.trim() ?? '')) {
|
|
104
|
+
return rendered.slice(1);
|
|
105
|
+
}
|
|
106
|
+
return rendered;
|
|
107
|
+
}
|
|
108
|
+
function isBlock(value) {
|
|
109
|
+
return Boolean(value && typeof value === 'object' && !Array.isArray(value) && 'block_type' in value);
|
|
110
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export function normalizeMarkdownLinkUrl(url) {
|
|
2
|
+
if (isAbsoluteHttpUrl(url))
|
|
3
|
+
return url;
|
|
4
|
+
let decoded;
|
|
5
|
+
try {
|
|
6
|
+
decoded = decodeURIComponent(url);
|
|
7
|
+
}
|
|
8
|
+
catch {
|
|
9
|
+
return url;
|
|
10
|
+
}
|
|
11
|
+
return isAbsoluteHttpUrl(decoded) ? decoded : url;
|
|
12
|
+
}
|
|
13
|
+
export function normalizeFeishuBlockLinkUrls(blocks) {
|
|
14
|
+
return normalizeValue(blocks);
|
|
15
|
+
}
|
|
16
|
+
function normalizeValue(value) {
|
|
17
|
+
if (Array.isArray(value)) {
|
|
18
|
+
return value.map(normalizeValue);
|
|
19
|
+
}
|
|
20
|
+
if (!isRecord(value)) {
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
23
|
+
const normalized = {};
|
|
24
|
+
for (const [key, child] of Object.entries(value)) {
|
|
25
|
+
normalized[key] = key === 'url' && typeof child === 'string'
|
|
26
|
+
? normalizeMarkdownLinkUrl(child)
|
|
27
|
+
: normalizeValue(child);
|
|
28
|
+
}
|
|
29
|
+
return normalized;
|
|
30
|
+
}
|
|
31
|
+
function isAbsoluteHttpUrl(url) {
|
|
32
|
+
if (!/^https?:\/\//i.test(url))
|
|
33
|
+
return false;
|
|
34
|
+
try {
|
|
35
|
+
const parsed = new URL(url);
|
|
36
|
+
return parsed.protocol === 'http:' || parsed.protocol === 'https:';
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function isRecord(value) {
|
|
43
|
+
return Boolean(value && typeof value === 'object' && !Array.isArray(value));
|
|
44
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export type MergeState = 'clean' | 'merged' | 'conflict';
|
|
2
|
+
export type LineMergeResult = {
|
|
3
|
+
markdown: string;
|
|
4
|
+
state: MergeState;
|
|
5
|
+
conflicts: number;
|
|
6
|
+
changed: boolean;
|
|
7
|
+
};
|
|
8
|
+
export type LineChange = {
|
|
9
|
+
baseStart: number;
|
|
10
|
+
baseEnd: number;
|
|
11
|
+
lines: string[];
|
|
12
|
+
};
|
|
13
|
+
export declare function mergeLines(input: {
|
|
14
|
+
base: string;
|
|
15
|
+
local: string;
|
|
16
|
+
remote: string;
|
|
17
|
+
}): LineMergeResult;
|
|
18
|
+
export declare function mergeWithoutBase(input: {
|
|
19
|
+
local: string;
|
|
20
|
+
remote: string;
|
|
21
|
+
}): LineMergeResult;
|