diff --git a/.github/scripts/prune-zh-duplicates.py b/.github/scripts/prune-zh-duplicates.py new file mode 100644 index 000000000..5fc0ed00e --- /dev/null +++ b/.github/scripts/prune-zh-duplicates.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 +"""Delete the zh duplicate site_libs/ and images/ trees after refs were +rewritten to the root trees by rewrite-zh-site-refs.py. + +Safety: aborts if any zh HTML reference still resolves inside the +duplicate trees (resolution-based, so refs pointing at the root trees +do not trigger a false abort). +""" +import os +import re +import shutil +import sys + +site = sys.argv[1] if len(sys.argv) > 1 else "_site" +zh_root = os.path.join(site, "zh") + +if not os.path.isdir(zh_root): + print(f"No zh directory at {zh_root}; nothing to prune") + sys.exit(0) + +# Any relative site_libs/ or images/ reference that resolves INSIDE the zh +# tree means the rewrite step did not cover it — refuse to delete. +ref_re = re.compile(r'["\'(=]\s*((?:\.\./)*)(?:site_libs|images)/[^"\'()\s>]+') +leftover = 0 +for dirpath, _dirnames, filenames in os.walk(zh_root): + for fname in filenames: + if not fname.endswith(".html"): + continue + fpath = os.path.join(dirpath, fname) + with open(fpath, encoding="utf-8") as fh: + content = fh.read() + for match in ref_re.finditer(content): + full_ref = match.group(0).lstrip("\"'(= ") + resolved = os.path.normpath( + os.path.join(os.path.dirname(fpath), match.group(1) + full_ref) + ) + if os.path.commonpath([resolved, zh_root]) == zh_root: + leftover += 1 + print(f"LEFTOVER: {fpath} -> {full_ref}") + break + +if leftover: + print(f"ERROR: {leftover} zh page(s) still reference the duplicate trees; refusing to delete") + sys.exit(1) + +removed = 0 +for tree in ("site_libs", "images"): + tree_path = os.path.join(zh_root, tree) + if os.path.isdir(tree_path): + size_mb = sum( + os.path.getsize(os.path.join(dp, fn)) + for dp, _, fns in os.walk(tree_path) + for fn in fns + ) // (1024 * 1024) + print(f"Deleting {tree} ({size_mb} MB)") + shutil.rmtree(tree_path) + removed += 1 + +print(f"Pruned {removed} duplicate tree(s) from zh/") diff --git a/.github/scripts/rewrite-zh-site-refs.py b/.github/scripts/rewrite-zh-site-refs.py new file mode 100644 index 000000000..caa11b631 --- /dev/null +++ b/.github/scripts/rewrite-zh-site-refs.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python3 +"""Rewrite zh-page asset references to share the root site trees. + +Background: babelquarto renders the zh pass into _site/zh/ and copies the +shared site_libs/ and images/ trees into _site/zh/ as byte-identical +duplicates of the root copies (_site/site_libs/, _site/images/), roughly +94 MB per deploy. This script rewrites every zh HTML reference that +resolves under zh/site_libs/ or zh/images/ to the root-tree equivalent, +so both zh duplicate trees can be deleted afterwards. + +Only references whose root-tree target exists are rewritten; anything +else is left untouched and reported. Run BEFORE deleting the duplicate +trees (deletion is done by prune-zh-duplicates.sh). +""" +import os +import re +import sys + +SITE = sys.argv[1] if len(sys.argv) > 1 else "_site" +ZH_ROOT = os.path.join(SITE, "zh") +# Matches a relative reference whose first segment is site_libs/ or images/ +# (optionally preceded by ../). Absolute, scheme-relative and srcset-less +# attributes only; url(...) inside CSS also covered via the ( prefix. +REF_RE = re.compile( + r'(?P["\'(=])\s*(?P(?:\.\./)*)?(?P(?:site_libs|images)/[^"\'()\s>]+)' +) + +rewritten = 0 +missing = 0 +files_touched = 0 + + +def repl(match, rel_dir): + global rewritten, missing + dots = match.group("dots") or "" + path = match.group("path") + # Resolve the reference against the page directory (zh-relative). + zh_target = os.path.normpath(os.path.join(SITE, rel_dir, dots + path)) + rel_to_zh_root = os.path.relpath(zh_target, ZH_ROOT) + if rel_to_zh_root.startswith(os.pardir): + missing += 1 + return match.group(0) + root_target = os.path.join(SITE, rel_to_zh_root) + if not os.path.isfile(root_target): + missing += 1 + return match.group(0) + fixed = os.path.relpath(root_target, os.path.join(SITE, rel_dir)) + rewritten += 1 + return match.group("prefix") + fixed + + +for dirpath, _dirnames, filenames in os.walk(ZH_ROOT): + for fname in filenames: + if not fname.endswith(".html"): + continue + fpath = os.path.join(dirpath, fname) + with open(fpath, encoding="utf-8") as fh: + content = fh.read() + # Directory of the zh page, relative to _site, e.g. "zh" or "zh/Omics" + rel_dir = os.path.relpath(dirpath, SITE) + + def sub(match): + return repl(match, rel_dir) + + new_content = REF_RE.sub(sub, content) + if new_content != content: + files_touched += 1 + with open(fpath, "w", encoding="utf-8") as fh: + fh.write(new_content) + +print( + f"Rewrote {rewritten} reference(s) across {files_touched} zh page(s); " + f"{missing} unresolved left as-is" +) diff --git a/.github/workflows/quarto-publish-update-cache.yml b/.github/workflows/quarto-publish-update-cache.yml index 588b9fe13..6c4f04366 100644 --- a/.github/workflows/quarto-publish-update-cache.yml +++ b/.github/workflows/quarto-publish-update-cache.yml @@ -524,6 +524,16 @@ jobs: - name: Point zh figure references at shared English figures run: python3 .github/scripts/rewrite-zh-figure-refs.py _site + # ── Deduplicate zh site_libs/ and images/ trees ────────── + # See quarto-publish.yml for full explanation. babelquarto copies + # the shared site_libs/ and images/ trees into _site/zh/ as + # byte-identical duplicates of the root copies (~94 MB). Rewrite zh + # HTML references to the root trees, then delete the duplicates. + - name: Deduplicate zh site_libs and images against root trees + run: | + python3 .github/scripts/rewrite-zh-site-refs.py _site + python3 .github/scripts/prune-zh-duplicates.py _site + # ── Lazy-load images ───────────────────────────────────── # See quarto-publish.yml for full explanation. Native # loading="lazy" defers offscreen figure fetches, with diff --git a/.github/workflows/quarto-publish.yml b/.github/workflows/quarto-publish.yml index 859fed68e..77d5c31d8 100644 --- a/.github/workflows/quarto-publish.yml +++ b/.github/workflows/quarto-publish.yml @@ -717,6 +717,18 @@ jobs: - name: Point zh figure references at shared English figures run: python3 .github/scripts/rewrite-zh-figure-refs.py _site + # ── Deduplicate zh site_libs/ and images/ trees ────────── + # babelquarto copies the shared site_libs/ and images/ trees into + # _site/zh/ as byte-identical duplicates of the root copies (~94 MB + # per deploy). Rewrite every zh HTML reference that resolves under + # zh/site_libs/ or zh/images/ to the root-tree equivalent, then + # delete the duplicate trees. The prune script refuses to delete if + # any zh page still resolves a reference inside the zh trees. + - name: Deduplicate zh site_libs and images against root trees + run: | + python3 .github/scripts/rewrite-zh-site-refs.py _site + python3 .github/scripts/prune-zh-duplicates.py _site + # ── Lazy-load images ───────────────────────────────────── # Figures dominate every page's payload. Native loading="lazy" # defers offscreen fetches (no JS, no layout shift - Quarto emits diff --git a/Omics/CellChatCirclePlot.zh.qmd b/Omics/CellChatCirclePlot.zh.qmd index b193e380d..dfdf6589d 100644 --- a/Omics/CellChatCirclePlot.zh.qmd +++ b/Omics/CellChatCirclePlot.zh.qmd @@ -112,7 +112,11 @@ cellchat <- aggregateNet(cellchat) 整体圈图汇总所有推断的通讯交互。每个节点代表一种细胞类型;节点大小与该细胞类型参与的交互数量成正比。弧宽编码通讯强度。 -```{r fig1-overall-circle, fig.width=9, fig.height=5, warning=FALSE} +```{r} +#| label: fig-1-overall-circle +#| fig-width: 9 +#| fig-height: 5 +#| warning: false #| fig-cap: "整体细胞-细胞通讯圈图" #| out.width: "95%" @@ -143,7 +147,11 @@ par(mfrow = c(1, 1)) 逐行对权重矩阵进行子集化,分别展示每种细胞类型的输出信号。 -```{r fig2-individual-circle, fig.width=10, fig.height=10, warning=FALSE} +```{r} +#| label: fig-2-individual-circle +#| fig-width: 10 +#| fig-height: 10 +#| warning: false #| fig-cap: "各细胞类型输出通讯圈图" #| out.width: "95%" @@ -174,7 +182,11 @@ par(mfrow = c(1, 1)) 弦图使用 `circlize` 提供了另一种圆形视图,清晰展示细胞类型对之间的双向通讯量。 -```{r fig3-chord, fig.width=7, fig.height=7, warning=FALSE} +```{r} +#| label: fig-3-chord +#| fig-width: 7 +#| fig-height: 7 +#| warning: false #| fig-cap: "细胞-细胞通讯弦图" #| out.width: "80%" @@ -209,7 +221,11 @@ circos.clear() 热图视图可快速识别主要的发送者-接收者细胞对及不对称通讯模式。 -```{r fig4-heatmap, fig.width=7, fig.height=6, warning=FALSE} +```{r} +#| label: fig-4-heatmap +#| fig-width: 7 +#| fig-height: 6 +#| warning: false #| fig-cap: "细胞-细胞通讯热图" #| out.width: "80%" diff --git a/Omics/ManhattanPlot.zh.qmd b/Omics/ManhattanPlot.zh.qmd index 557a6d626..460b635bc 100644 --- a/Omics/ManhattanPlot.zh.qmd +++ b/Omics/ManhattanPlot.zh.qmd @@ -208,7 +208,7 @@ ggplot()+ 由于ggplot2绘图是将一张张图叠加起来,因此可以将筛选后的SNP用geom_point叠加到原图上: ```{r} -#| label: fig-2.3Hlight +#| label: fig-2.3Highlight #| fig-cap: "高亮或标记snp" #| out.width: "95%" #| warning: false