mirror of
https://github.com/JimLiu/baoyu-skills.git
synced 2026-07-13 22:29:48 +08:00
Merge branch 'main' of https://github.com/JimLiu/baoyu-skills
This commit is contained in:
@@ -98,3 +98,68 @@ test('parseMarkdown resolves encoded spaces and literal percent image paths', as
|
|||||||
assert.equal(result.contentImages[0]?.localPath, path.join(root, 'Pasted image.png'));
|
assert.equal(result.contentImages[0]?.localPath, path.join(root, 'Pasted image.png'));
|
||||||
assert.equal(result.contentImages[1]?.localPath, path.join(root, '100%.png'));
|
assert.equal(result.contentImages[1]?.localPath, path.join(root, '100%.png'));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('parseMarkdown renders CJK-adjacent bold and italics (no literal asterisks)', async (t) => {
|
||||||
|
const root = await makeTempDir('x-md-to-html-cjk-bold-');
|
||||||
|
t.after(() => fs.rm(root, { recursive: true, force: true }));
|
||||||
|
|
||||||
|
const markdownPath = path.join(root, 'post.md');
|
||||||
|
const tempDir = path.join(root, 'tmp');
|
||||||
|
await fs.mkdir(tempDir, { recursive: true });
|
||||||
|
await fs.writeFile(
|
||||||
|
markdownPath,
|
||||||
|
[
|
||||||
|
'# 标题',
|
||||||
|
'',
|
||||||
|
'分工在变细。**国际大厂卷基础设施,中文项目卷场景落地。**这其实是生态成熟的表现。',
|
||||||
|
'',
|
||||||
|
'半角场景 **Top 10 里平均有 8 个** 项目。',
|
||||||
|
'',
|
||||||
|
'斜体 *数据来源 GitHub* 收尾。',
|
||||||
|
'',
|
||||||
|
'参考 **[docs][d]** 了解更多。',
|
||||||
|
'',
|
||||||
|
'[d]: https://example.com',
|
||||||
|
].join('\n'),
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await parseMarkdown(markdownPath, { tempDir });
|
||||||
|
|
||||||
|
// Bold directly adjacent to CJK (closing ** followed by CJK) must render.
|
||||||
|
assert.match(result.html, /<strong>国际大厂卷基础设施,中文项目卷场景落地。<\/strong>/);
|
||||||
|
assert.match(result.html, /<strong>Top 10 里平均有 8 个<\/strong>/);
|
||||||
|
// Italics.
|
||||||
|
assert.match(result.html, /<em>数据来源 GitHub<\/em>/);
|
||||||
|
// Reference-style links inside emphasis must render as links, not plain text.
|
||||||
|
assert.match(result.html, /<strong><a href="https:\/\/example\.com" rel="noopener noreferrer nofollow">docs<\/a><\/strong>/);
|
||||||
|
// No literal emphasis delimiters should leak into the output.
|
||||||
|
assert.doesNotMatch(result.html, /\*\*/);
|
||||||
|
assert.doesNotMatch(result.html, /(?<!\*)\*(?!\*)[^*\n]+\*(?!\*)/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('parseMarkdown does not decode author-written literal HTML entities into tags', async (t) => {
|
||||||
|
const root = await makeTempDir('x-md-to-html-entities-');
|
||||||
|
t.after(() => fs.rm(root, { recursive: true, force: true }));
|
||||||
|
|
||||||
|
const markdownPath = path.join(root, 'post.md');
|
||||||
|
const tempDir = path.join(root, 'tmp');
|
||||||
|
await fs.mkdir(tempDir, { recursive: true });
|
||||||
|
await fs.writeFile(
|
||||||
|
markdownPath,
|
||||||
|
[
|
||||||
|
'# 标题',
|
||||||
|
'',
|
||||||
|
'正文中写 <b>literal</b> 想显示字面标签。**加粗**收尾。',
|
||||||
|
'',
|
||||||
|
'代码里写 `<b>` 同样保留。',
|
||||||
|
].join('\n'),
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await parseMarkdown(markdownPath, { tempDir });
|
||||||
|
|
||||||
|
// CJK-adjacent bold still renders.
|
||||||
|
assert.match(result.html, /<strong>加粗<\/strong>/);
|
||||||
|
// Author-written literal entities must NOT be decoded into real tags.
|
||||||
|
assert.doesNotMatch(result.html, /<b>literal<\/b>/);
|
||||||
|
assert.match(result.html, /<b>literal<\/b>/);
|
||||||
|
});
|
||||||
|
|||||||
@@ -136,6 +136,21 @@ function highlightCode(code: string, lang: string): string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Normalize CJK-adjacent emphasis so `marked` renders it correctly.
|
||||||
|
//
|
||||||
|
// `marked`'s emphasis tokenizer treats a closing `**`/`*` directly followed by a
|
||||||
|
// CJK character as not right-flanking, so it leaves the delimiters literal
|
||||||
|
// (e.g. `**加粗**这` renders as plain text with the asterisks intact). We round-trip
|
||||||
|
// the markdown through `remark-cjk-friendly`, whose stringify serializes the
|
||||||
|
// boundary character as an HTML entity (`这`); the entity is treated as
|
||||||
|
// punctuation by `marked`'s flanking rules, so emphasis parses as expected.
|
||||||
|
//
|
||||||
|
// We deliberately do NOT decode the entities afterward. They are valid HTML
|
||||||
|
// character references that render correctly when the article HTML is pasted into
|
||||||
|
// the X editor, and `marked` only emits them for characters outside the emphasis
|
||||||
|
// span (the boundary char), never inside it. A blanket decode of the rendered
|
||||||
|
// HTML would risk turning author-written literal entities (e.g. `<b>`
|
||||||
|
// meant to display `<b>` as text) into real tags, so we leave them intact.
|
||||||
function preprocessCjkMarkdown(markdown: string): string {
|
function preprocessCjkMarkdown(markdown: string): string {
|
||||||
try {
|
try {
|
||||||
const processor = unified()
|
const processor = unified()
|
||||||
@@ -143,8 +158,7 @@ function preprocessCjkMarkdown(markdown: string): string {
|
|||||||
.use(remarkCjkFriendly)
|
.use(remarkCjkFriendly)
|
||||||
.use(remarkStringify);
|
.use(remarkStringify);
|
||||||
|
|
||||||
const result = String(processor.processSync(markdown));
|
return String(processor.processSync(markdown));
|
||||||
return result.replace(/&#x([0-9A-Fa-f]+);/g, (_, hex: string) => String.fromCodePoint(parseInt(hex, 16)));
|
|
||||||
} catch {
|
} catch {
|
||||||
return markdown;
|
return markdown;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user