@vdfor/confluence-sync 0.4.0 → 0.4.1
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.
Potentially problematic release.
This version of @vdfor/confluence-sync might be problematic. Click here for more details.
package/package.json
CHANGED
package/src/confluence.mjs
CHANGED
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
import axios from 'axios';
|
|
2
2
|
import { marked } from 'marked';
|
|
3
|
-
import { extname } from 'node:path';
|
|
3
|
+
import { extname, dirname, join } from 'node:path';
|
|
4
4
|
import { tmpdir } from 'node:os';
|
|
5
5
|
import { writeFile, unlink } from 'node:fs/promises';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
6
7
|
|
|
7
8
|
// ── Mermaid 处理 ─────────────────────────────────────────────────────────────
|
|
8
9
|
|
|
9
10
|
let sebastianRenderer = null;
|
|
11
|
+
let cjkFontRegistry = null;
|
|
12
|
+
|
|
13
|
+
/** 项目内置 CJK 字体(思源黑体可变) */
|
|
14
|
+
const PROJECT_FONT_DIR = join(dirname(fileURLToPath(import.meta.url)), 'fonts');
|
|
15
|
+
const CJK_FONT_FILE = join(PROJECT_FONT_DIR, 'SourceHanSansSC-VF.ttf');
|
|
16
|
+
|
|
10
17
|
async function getSebastianRenderer() {
|
|
11
18
|
if (!sebastianRenderer) {
|
|
12
19
|
const sebastian = await import('sebastianjs');
|
|
@@ -15,6 +22,32 @@ async function getSebastianRenderer() {
|
|
|
15
22
|
return sebastianRenderer;
|
|
16
23
|
}
|
|
17
24
|
|
|
25
|
+
/**
|
|
26
|
+
* 创建注册了项目内置 CJK 字体的 FontRegistry
|
|
27
|
+
* mermaid 节点标题默认用 bold,所以需要同时覆盖 regular + bold + italic
|
|
28
|
+
*/
|
|
29
|
+
async function getCjkFontRegistry() {
|
|
30
|
+
if (cjkFontRegistry) return cjkFontRegistry;
|
|
31
|
+
|
|
32
|
+
const { FontRegistry } = await import('sebastianjs/src/geometry/fonts.js');
|
|
33
|
+
const registry = new FontRegistry();
|
|
34
|
+
|
|
35
|
+
// 注册思源黑体 SC 可变字体,覆盖所有字重(variable font 可以自己插值不同 weight)
|
|
36
|
+
// 家族名用常见别名,CSS font-family 匹配时能命中
|
|
37
|
+
registry.registerFont(CJK_FONT_FILE, { family: 'Source Han Sans SC', weight: 400 });
|
|
38
|
+
registry.registerFont(CJK_FONT_FILE, { family: 'Source Han Sans SC', weight: 700 });
|
|
39
|
+
registry.registerFont(CJK_FONT_FILE, { family: 'Source Han Sans SC', weight: 400, italic: true });
|
|
40
|
+
registry.registerFont(CJK_FONT_FILE, { family: 'Source Han Sans SC', weight: 700, italic: true });
|
|
41
|
+
|
|
42
|
+
// 也注册成 generic 别名(mermaid 默认字体栈里有 sans-serif)
|
|
43
|
+
registry.registerFont(CJK_FONT_FILE, { family: 'sans-serif', weight: 400 });
|
|
44
|
+
registry.registerFont(CJK_FONT_FILE, { family: 'sans-serif', weight: 700 });
|
|
45
|
+
|
|
46
|
+
registry.setDefaultFamily('Source Han Sans SC');
|
|
47
|
+
cjkFontRegistry = registry;
|
|
48
|
+
return registry;
|
|
49
|
+
}
|
|
50
|
+
|
|
18
51
|
/**
|
|
19
52
|
* 检测并替换 markdown 中的 ```mermaid 代码块为图片占位符
|
|
20
53
|
* 使用 sebastianjs(本地、零浏览器、零外网依赖)渲染为 PNG
|
|
@@ -28,6 +61,7 @@ export async function preprocessMermaid(md) {
|
|
|
28
61
|
if (!matches.length) return { md, images: [], hasMermaid: false };
|
|
29
62
|
|
|
30
63
|
const { renderPng } = await getSebastianRenderer();
|
|
64
|
+
const fontRegistry = await getCjkFontRegistry();
|
|
31
65
|
const images = [];
|
|
32
66
|
let result = md;
|
|
33
67
|
let offset = 0;
|
|
@@ -40,7 +74,11 @@ export async function preprocessMermaid(md) {
|
|
|
40
74
|
|
|
41
75
|
try {
|
|
42
76
|
const t0 = Date.now();
|
|
43
|
-
const { data: pngBuffer } = await renderPng(code, {
|
|
77
|
+
const { data: pngBuffer } = await renderPng(code, {
|
|
78
|
+
background: 'white',
|
|
79
|
+
fontFamily: 'Source Han Sans SC',
|
|
80
|
+
fontRegistry,
|
|
81
|
+
});
|
|
44
82
|
const t1 = Date.now();
|
|
45
83
|
await writeFile(file, pngBuffer);
|
|
46
84
|
images.push({ file, name });
|
|
@@ -201,6 +239,16 @@ export async function updatePage(id, title, bodyHtml, currentVersion, minorEdit
|
|
|
201
239
|
}
|
|
202
240
|
}
|
|
203
241
|
|
|
242
|
+
// ── HTML 转义 ────────────────────────────────────────────────────────────────
|
|
243
|
+
function escapeHtml(str) {
|
|
244
|
+
return String(str)
|
|
245
|
+
.replace(/&/g, '&')
|
|
246
|
+
.replace(/</g, '<')
|
|
247
|
+
.replace(/>/g, '>')
|
|
248
|
+
.replace(/"/g, '"')
|
|
249
|
+
.replace(/'/g, ''');
|
|
250
|
+
}
|
|
251
|
+
|
|
204
252
|
// ── Markdown → Confluence Storage Format ───────────────────────────────────────
|
|
205
253
|
|
|
206
254
|
/**
|
|
@@ -261,7 +309,7 @@ export function mdToConfluence(md) {
|
|
|
261
309
|
break;
|
|
262
310
|
default:
|
|
263
311
|
if (tok.tokens) out += renderTokens(tok.tokens, listDepth);
|
|
264
|
-
else if (tok.text) out += tok.text;
|
|
312
|
+
else if (tok.text) out += escapeHtml(tok.text);
|
|
265
313
|
}
|
|
266
314
|
}
|
|
267
315
|
return out;
|
|
@@ -273,7 +321,7 @@ export function mdToConfluence(md) {
|
|
|
273
321
|
for (const tok of toks) {
|
|
274
322
|
switch (tok.type) {
|
|
275
323
|
case 'text':
|
|
276
|
-
out += tok.tokens ? renderInline(tok.tokens) : tok.text;
|
|
324
|
+
out += tok.tokens ? renderInline(tok.tokens) : escapeHtml(tok.text);
|
|
277
325
|
break;
|
|
278
326
|
case 'strong':
|
|
279
327
|
out += `<strong>${renderInline(tok.tokens)}</strong>`;
|
|
@@ -282,16 +330,16 @@ export function mdToConfluence(md) {
|
|
|
282
330
|
out += `<em>${renderInline(tok.tokens)}</em>`;
|
|
283
331
|
break;
|
|
284
332
|
case 'codespan':
|
|
285
|
-
out += `<code>${tok.text}</code>`;
|
|
333
|
+
out += `<code>${escapeHtml(tok.text)}</code>`;
|
|
286
334
|
break;
|
|
287
335
|
case 'del':
|
|
288
336
|
out += `<s>${renderInline(tok.tokens)}</s>`;
|
|
289
337
|
break;
|
|
290
338
|
case 'link':
|
|
291
|
-
out += `<a href="${tok.href}">${renderInline(tok.tokens)}</a>`;
|
|
339
|
+
out += `<a href="${escapeHtml(tok.href)}">${renderInline(tok.tokens)}</a>`;
|
|
292
340
|
break;
|
|
293
341
|
case 'image':
|
|
294
|
-
out += `<ac:image><ri:attachment ri:filename="${tok.href}" /></ac:image>`;
|
|
342
|
+
out += `<ac:image><ri:attachment ri:filename="${escapeHtml(tok.href)}" /></ac:image>`;
|
|
295
343
|
break;
|
|
296
344
|
case 'br':
|
|
297
345
|
out += '<br/>';
|
|
@@ -299,6 +347,11 @@ export function mdToConfluence(md) {
|
|
|
299
347
|
case 'escape':
|
|
300
348
|
out += tok.text;
|
|
301
349
|
break;
|
|
350
|
+
case 'html':
|
|
351
|
+
// marked 识别的 inline HTML(如 <div>foo</div>、<即实际未使用>)
|
|
352
|
+
// 我们不支持用户写原生 HTML → 一律转义
|
|
353
|
+
out += escapeHtml(tok.text);
|
|
354
|
+
break;
|
|
302
355
|
default:
|
|
303
356
|
out += tok.text || '';
|
|
304
357
|
}
|
|
Binary file
|
|
Binary file
|