From bf57f18f110f207c9fd23efd4fad0db3a71cd927 Mon Sep 17 00:00:00 2001 From: CyC2018 Date: Sun, 1 Nov 2026 19:07:30 +0800 Subject: [PATCH 1/2] Update site_assets.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 构建时按行首的围栏识别代码块,正文中的行内代码 ```` ```json ```` 之后的图片也会发布并转换。 --- website/builder/site_assets.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/website/builder/site_assets.py b/website/builder/site_assets.py index e0ca012d..f77e17c5 100644 --- a/website/builder/site_assets.py +++ b/website/builder/site_assets.py @@ -18,6 +18,20 @@ IMAGE_SUFFIXES = {'.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp', '.ico', '.av PUNCTUATION = re.compile(r'''[\u2000-\u206f\u2e00-\u2e7f\\'!"#$%&()*+,./:;<=>?@\[\]^`{|}~]''') +# 代码块和注释:围栏必须在行首(允许缩进),用同样的字符和至少同样的长度结束。 +# 不能用不锚定的 ```...```:正文中的行内代码 ```` ```json ```` 会被当成代码块的开头,吞掉后面的图片引用。 +VERBATIM = re.compile(r'^[ \t]*(?P`{3,}|~{3,})[^\n]*\n[\s\S]*?^[ \t]*(?P=fence)[`~]*[ \t]*$|', re.M) + + +def split_verbatim(text): + """按代码块和注释切分:奇数下标是原样保留的部分。""" + parts, last = [], 0 + for match in VERBATIM.finditer(text): + parts += [text[last:match.start()], match[0]] + last = match.end() + return parts + [text[last:]] + + def plain_text(text): text = re.sub(r'', '', text) text = re.sub(r'<(?:script|style)\b[^>]*>[\s\S]*?', '', text, flags=re.I) @@ -125,7 +139,7 @@ def write_search_index(dist, paths): def referenced_paths(content): - content = re.sub(r'```[^\n]*\n[\s\S]*?```|', '', content) + content = VERBATIM.sub('', content) for match in re.finditer(r'''\]\((/[^\s)]+)\)|(?:src|href)=["'](/[^"']+)''', content): url = match[1] or match[2] if url.startswith(('/#', '//')): @@ -202,7 +216,7 @@ def optimize_images(dist, cache): def replace(match): before, url, after = (match[1], match[2], match[3]) if match[1] else (match[4], match[5], match[6]) return before + mapping.get(url, {}).get('path', url) + after - parts = re.split(r'(```[^\n]*\n[\s\S]*?```|)', path.read_text()) + parts = split_verbatim(path.read_text()) path.write_text(''.join(part if i % 2 else image_pattern.sub(replace, part) for i, part in enumerate(parts))) (dist / 'image-variants.json').write_text(json.dumps(mapping, ensure_ascii=False, indent=2, sort_keys=True) + '\n') return {'converted': len(mapping), 'originalBytes': sum(v['originalBytes'] for v in mapping.values()), From c3d21de4677b9bf7433b4ee8083cc0077b2922c3 Mon Sep 17 00:00:00 2001 From: CyC2018 Date: Sun, 1 Nov 2026 19:07:30 +0800 Subject: [PATCH 2/2] Update test_site_assets.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 补充测试:行内代码中的三个反引号不影响后面图片的发布。 --- website/builder/test_site_assets.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/website/builder/test_site_assets.py b/website/builder/test_site_assets.py index bfda55ab..cf6fb7f9 100644 --- a/website/builder/test_site_assets.py +++ b/website/builder/test_site_assets.py @@ -45,6 +45,12 @@ class SearchIndexTests(unittest.TestCase): content = '\n```html\n\n```\n\n[正文](/notes/示例.md)\n' self.assertEqual(set(referenced_paths(content)), {'notes/pics/actual.png', 'notes/示例.md'}) + def test_inline_triple_backticks_do_not_hide_later_images(self): + # 正文中的行内代码 ```` ```json ```` 不是代码块,后面的图片仍要发布 + content = ('例如包在 ```` ```json ```` 代码块里。\n\n\n\n' + '- 列表\n\n ```text\n \n ```\n\n\n') + self.assertEqual(set(referenced_paths(content)), {'notes/pics/a.png', 'notes/pics/b.png'}) + def test_image_manifest_stable_across_file_creation_order(self): from PIL import Image with tempfile.TemporaryDirectory() as directory: