@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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vdfor/confluence-sync",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "author": "vdfor",
5
5
  "license": "MIT",
6
6
  "private": false,
@@ -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, { background: 'white' });
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, '&lt;')
247
+ .replace(/>/g, '&gt;')
248
+ .replace(/"/g, '&quot;')
249
+ .replace(/'/g, '&apos;');
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