#!/usr/bin/env python3 """Apply the shared gallery presentation and lightbox behavior to mission galleries.""" from __future__ import annotations import re from pathlib import Path ROOT = Path(__file__).resolve().parents[1] WIKI_DIR = ROOT / "data" / "wiki" GALLERY_FILES = sorted(WIKI_DIR.glob("guanghan_*_gallery_zh.html")) STYLE_END_RE = re.compile(r"", re.IGNORECASE) SCRIPT_RE = re.compile(r"\s*\s*", re.IGNORECASE | re.DOTALL) ASSET_IMAGE_RE = re.compile(r"]*\bdata-wiki-asset=)[^>]*>", re.IGNORECASE) CSS_BLOCK_RE = re.compile( r"\n?/\* mission-gallery-enhancements:start \*/.*?" r"/\* mission-gallery-enhancements:end \*/\n?", re.DOTALL, ) LEGACY_LIGHTBOX_CSS_RE = re.compile( r"\n?\.wiki-article img\[data-wiki-asset\][^\n]*\n" r".*?@media \(max-width: 820px\) \{ \.wiki-image-lightbox \{.*?\}\s*\}\n?", re.DOTALL, ) GALLERY_CSS = r""" /* mission-gallery-enhancements:start */ .wiki-article .gallery-grid { grid-template-columns: repeat(auto-fill, minmax(min(100%, 300px), 1fr)); align-items: stretch; } .wiki-article .gallery-item { display: grid; grid-template-rows: auto 1fr; overflow: hidden; content-visibility: auto; contain-intrinsic-size: 340px 300px; } .wiki-article .gallery-item img[data-wiki-asset] { width: 100%; aspect-ratio: 16 / 9; object-fit: contain; background: #202122; cursor: zoom-in; } .wiki-article .gallery-item .thumbcaption { padding: 8px 7px 7px; } .wiki-article .gallery-notice { margin: 1em 0 1.25em; padding: 10px 12px; border-left: 4px solid #36c; background: #f1f4fd; color: #202122; } .wiki-article .image-placeholder { position: relative; border: 1px dashed #72777d; background: linear-gradient(135deg, #f8f9fa, #eaecf0); color: #54595d; } .wiki-article.gallery-planning .gallery-grid { grid-template-columns: repeat(auto-fill, minmax(min(100%, 360px), 1fr)); gap: 12px; } .wiki-article.gallery-planning .gallery-item { display: block; content-visibility: visible; contain-intrinsic-size: none; border-color: #a2a9b1; background: #fff; box-shadow: 0 1px 2px rgba(32, 33, 34, 0.08); } .wiki-article.gallery-planning .image-placeholder { min-height: 0 !important; display: block; padding: 14px 14px 12px; border: 0; border-left: 4px solid #72777d; background: #f8f9fa; color: #202122; text-align: left; font-size: 13px; font-weight: 600; line-height: 1.65; } .wiki-article.gallery-planning .gallery-item .thumbcaption { padding: 10px 14px 12px; border-top: 1px solid #eaecf0; color: #54595d; } .wiki-article.gallery-planning > .image-placeholder { min-height: 0; padding: 22px 18px; border-left: 4px solid #72777d; } .wiki-image-lightbox { position: fixed; inset: 0; display: flex; align-items: center; justify-content: center; padding: 24px; background: rgba(32, 33, 34, 0.88); z-index: 10000; box-sizing: border-box; } .wiki-image-lightbox[hidden] { display: none; } .wiki-image-lightbox-panel { position: relative; display: grid; grid-template-columns: 44px minmax(0, 1fr) 44px; grid-template-areas: "close close close" "prev image next" "counter caption caption"; gap: 8px; width: min(1320px, 96vw); max-height: 94vh; overflow: auto; margin: 0; padding: 10px; border: 1px solid #a2a9b1; background: #fff; box-shadow: 0 12px 38px rgba(0, 0, 0, 0.35); box-sizing: border-box; } .wiki-image-lightbox-panel > img { grid-area: image; width: 100%; height: min(78vh, 900px); object-fit: contain; background: #000; } .wiki-image-lightbox-close { grid-area: close; justify-self: end; position: static; float: none; width: 34px; height: 34px; margin: 0; border: 1px solid #a2a9b1; border-radius: 3px; background: #eaecf0; color: #202122; font-size: 22px; line-height: 30px; cursor: pointer; } .wiki-image-lightbox-close:hover, .wiki-image-lightbox-close:focus { background: #fff; } .wiki-image-lightbox-prev { grid-area: prev; } .wiki-image-lightbox-next { grid-area: next; } .wiki-image-lightbox-nav { align-self: center; width: 40px; height: 56px; border: 1px solid #a2a9b1; border-radius: 3px; background: #eaecf0; color: #202122; font-size: 26px; cursor: pointer; } .wiki-image-lightbox-nav:hover, .wiki-image-lightbox-nav:focus { background: #fff; } .wiki-image-lightbox-counter { grid-area: counter; color: #54595d; white-space: nowrap; } .wiki-image-lightbox-panel .thumbcaption { grid-area: caption; padding: 0 4px 3px; } body.wiki-image-lightbox-open { overflow: hidden; } @media (max-width: 820px) { .wiki-article .gallery-grid { grid-template-columns: 1fr; } .wiki-image-lightbox-panel { grid-template-columns: 36px minmax(0, 1fr) 36px; width: 98vw; } .wiki-image-lightbox-panel > img { height: 70vh; } .wiki-image-lightbox-nav { width: 34px; height: 48px; } } /* mission-gallery-enhancements:end */ """.strip() GALLERY_JS = r"""""" def add_image_loading_attributes(match: re.Match[str]) -> str: tag = match.group(0) additions = [] if not re.search(r"\bloading=", tag, re.IGNORECASE): additions.append('loading="lazy"') if not re.search(r"\bdecoding=", tag, re.IGNORECASE): additions.append('decoding="async"') if not re.search(r"\bfetchpriority=", tag, re.IGNORECASE): additions.append('fetchpriority="low"') if not additions: return tag return tag[:-1].rstrip() + " " + " ".join(additions) + ">" def normalize(path: Path) -> bool: original = path.read_text(encoding="utf-8") updated = LEGACY_LIGHTBOX_CSS_RE.sub("\n", original) updated = CSS_BLOCK_RE.sub("\n", updated) updated = STYLE_END_RE.sub(GALLERY_CSS + "\n", updated, count=1) updated = ASSET_IMAGE_RE.sub(add_image_loading_attributes, updated) updated = SCRIPT_RE.sub("\n", updated).rstrip() updated += "\n" if updated == original: return False path.write_text(updated, encoding="utf-8", newline="\n") return True def main() -> int: changed = 0 for path in GALLERY_FILES: was_changed = normalize(path) changed += int(was_changed) print(f"gallery_normalized file={path.name} changed={str(was_changed).lower()}") print(f"gallery_normalized_summary files={len(GALLERY_FILES)} changed={changed}") return 0 if __name__ == "__main__": raise SystemExit(main())