feat(wiki): 新增 GHC06 任务页与图库发布体系,级联更新既有页面
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,324 @@
|
||||
#!/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"</style>", re.IGNORECASE)
|
||||
SCRIPT_RE = re.compile(r"\s*<script>.*?</script>\s*", re.IGNORECASE | re.DOTALL)
|
||||
ASSET_IMAGE_RE = re.compile(r"<img\b(?=[^>]*\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"""<script>
|
||||
(function () {
|
||||
function ready(fn) {
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', fn, { once: true });
|
||||
} else {
|
||||
fn();
|
||||
}
|
||||
}
|
||||
|
||||
function captionFor(image) {
|
||||
var container = image.closest('.gallery-item, figure, td, tr, .infobox-image');
|
||||
var caption = container && container.querySelector('.thumbcaption');
|
||||
return caption ? caption.textContent.trim() : (image.alt || '');
|
||||
}
|
||||
|
||||
function install(article) {
|
||||
if (!article || article.dataset.galleryLightboxReady === '1') { return; }
|
||||
var images = Array.prototype.slice.call(article.querySelectorAll('img[data-wiki-asset]'));
|
||||
if (!images.length) { return; }
|
||||
article.dataset.galleryLightboxReady = '1';
|
||||
|
||||
var overlay = document.createElement('div');
|
||||
overlay.className = 'wiki-image-lightbox';
|
||||
overlay.hidden = true;
|
||||
overlay.innerHTML = '<figure class="wiki-image-lightbox-panel" role="dialog" aria-modal="true" aria-label="任务图集图片预览">' +
|
||||
'<button class="wiki-image-lightbox-close" type="button" aria-label="关闭图片预览">×</button>' +
|
||||
'<button class="wiki-image-lightbox-nav wiki-image-lightbox-prev" type="button" aria-label="上一张">‹</button>' +
|
||||
'<img alt="">' +
|
||||
'<button class="wiki-image-lightbox-nav wiki-image-lightbox-next" type="button" aria-label="下一张">›</button>' +
|
||||
'<span class="wiki-image-lightbox-counter" aria-live="polite"></span>' +
|
||||
'<figcaption class="thumbcaption"></figcaption></figure>';
|
||||
document.body.appendChild(overlay);
|
||||
|
||||
var previewImage = overlay.querySelector('img');
|
||||
var previewCaption = overlay.querySelector('figcaption');
|
||||
var counter = overlay.querySelector('.wiki-image-lightbox-counter');
|
||||
var closeButton = overlay.querySelector('.wiki-image-lightbox-close');
|
||||
var previousButton = overlay.querySelector('.wiki-image-lightbox-prev');
|
||||
var nextButton = overlay.querySelector('.wiki-image-lightbox-next');
|
||||
var activeIndex = 0;
|
||||
var lastTrigger = null;
|
||||
|
||||
function render(index) {
|
||||
activeIndex = (index + images.length) % images.length;
|
||||
var image = images[activeIndex];
|
||||
previewImage.src = image.dataset.wikiAsset || image.currentSrc || image.src;
|
||||
previewImage.alt = image.alt || '';
|
||||
previewCaption.textContent = captionFor(image);
|
||||
counter.textContent = (activeIndex + 1) + ' / ' + images.length;
|
||||
}
|
||||
|
||||
function closePreview() {
|
||||
overlay.hidden = true;
|
||||
previewImage.removeAttribute('src');
|
||||
document.body.classList.remove('wiki-image-lightbox-open');
|
||||
if (lastTrigger) { lastTrigger.focus(); }
|
||||
}
|
||||
|
||||
function openPreview(image, event) {
|
||||
if (event) { event.preventDefault(); event.stopPropagation(); }
|
||||
lastTrigger = image;
|
||||
render(images.indexOf(image));
|
||||
overlay.hidden = false;
|
||||
document.body.classList.add('wiki-image-lightbox-open');
|
||||
closeButton.focus();
|
||||
}
|
||||
|
||||
images.forEach(function (image) {
|
||||
image.tabIndex = 0;
|
||||
image.setAttribute('role', 'button');
|
||||
image.setAttribute('aria-label', '打开图片预览:' + (image.alt || '任务影像'));
|
||||
image.addEventListener('click', function (event) { openPreview(image, event); });
|
||||
image.addEventListener('keydown', function (event) {
|
||||
if (event.key === 'Enter' || event.key === ' ') { openPreview(image, event); }
|
||||
});
|
||||
});
|
||||
|
||||
previousButton.hidden = images.length < 2;
|
||||
nextButton.hidden = images.length < 2;
|
||||
previousButton.addEventListener('click', function () { render(activeIndex - 1); });
|
||||
nextButton.addEventListener('click', function () { render(activeIndex + 1); });
|
||||
closeButton.addEventListener('click', closePreview);
|
||||
overlay.addEventListener('click', function (event) {
|
||||
if (event.target === overlay) { closePreview(); }
|
||||
});
|
||||
document.addEventListener('keydown', function (event) {
|
||||
if (overlay.hidden) { return; }
|
||||
if (event.key === 'Escape') { closePreview(); }
|
||||
if (event.key === 'ArrowLeft') { render(activeIndex - 1); }
|
||||
if (event.key === 'ArrowRight') { render(activeIndex + 1); }
|
||||
});
|
||||
}
|
||||
|
||||
ready(function () {
|
||||
Array.prototype.forEach.call(document.querySelectorAll('.wiki-article'), install);
|
||||
});
|
||||
}());
|
||||
</script>"""
|
||||
|
||||
|
||||
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</style>", 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())
|
||||
Reference in New Issue
Block a user