Files
halo-theme-WarmIsland/src/components/LightGallery.astro
T
nxxy335 a08782a959 fix: 修复记得页 500 与留言板裁切,统一页头,完善友链/瞬间/暗色/排版 (v1.1.12)
记得(/important-dates)
- 修复 500:th:classappend 里 BEM 类名 wi-id__car 的两个 __ 触发 Thymeleaf
  __...__ 预处理,提取片段含引号导致 Could not parse as expression;
  皮肤修饰类改为 wi-id-skin-cool/cute/neutral(表达式内不再出现 __)
- 牵挂的人卡片:倒计时改到右上角与日期卡片一致,生日一行显示不再折行;
  网格改为桌面三列 / 平板两列 / 手机一列
- 页头与订阅/记得统一(标题-副标题-强调线间距)

友链(/links)
- 再次消除两处 __ 预处理隐患:回执修饰类改 wi-links-check-ok/error,
  验证码 onerror 由 th:onerror="|...|" 改为普通静态属性(不参与表达式解析)
- 新增反链/可达性检测徽章:读取 link.status.verification,头像右下角
  绿点=访问正常(反链有效),红点=访问异常或未检测到反链,未检测不显示

留言板
- 移除 -124px 负 margin 补偿(头部卡片顶部被导航盖住的根因),
  装饰引号 top 改为正值不再被 overflow 裁掉

瞬间
- 修复九宫格失效根因:lightgallery 处理时把每张图包进 <a>,导致
  :has(:only-child) 恒真、全部图片退化为单列大图;改为直接把 img
  作为图库项(data-src),瀑布流样式同样补上 4 图 2x2 规则

全局
- 暗色模式配色调为偏中性暖灰(bg #1e1c1a / raised #292623),降低黄褐闷感,
  同步 theme-color 与依赖旧色值的变量
- 正文排版:p 字号 1.05~1.15rem、行高 1.85,H1~H6 逐级放大、层级更清晰

新增「文章组件」插件适配
- 新增 src/styles/_widgets.scss,将插件 --xhhao-com-* 变量映射到主题变量,
  明暗模式自动跟随
2026-09-27 10:09:28 +08:00

270 lines
8.7 KiB
Plaintext

---
---
<script>
import 'lightgallery/css/lightgallery.css';
import 'lightgallery/css/lg-zoom.css';
import 'lightgallery/css/lg-thumbnail.css';
import 'lightgallery/css/lg-fullscreen.css';
import lightGallery from 'lightgallery';
import lgZoom from 'lightgallery/plugins/zoom';
import lgThumbnail from 'lightgallery/plugins/thumbnail';
import lgFullscreen from 'lightgallery/plugins/fullscreen';
var _initialized = new WeakSet();
function isInitialized(el) {
if (_initialized.has(el)) return true;
return false;
}
function markInitialized(el) {
_initialized.add(el);
}
function buildExifHtml(exifStr, caption) {
if (!exifStr) return caption || '';
var parts = exifStr.split('|');
if (parts.length < 8) return caption || '';
var make = parts[0], model = parts[1], lensModel = parts[2];
var fNumber = parts[3], exposureTime = parts[4], iso = parts[5];
var focalLength = parts[6], focalLengthIn35mm = parts[7];
var hasAnyExif = make || model || lensModel || fNumber || exposureTime || iso || focalLength;
if (!hasAnyExif) return caption || '';
var html = '<div class="wi-lg-caption">' + (caption || '') + '</div>';
html += '<div class="wi-lg-exif">';
if (make || model) {
html += '<span class="wi-lg-exif__item wi-lg-exif__camera">' + (make ? make + ' ' : '') + model + '</span>';
}
if (lensModel) {
html += '<span class="wi-lg-exif__item wi-lg-exif__lens">' + lensModel + '</span>';
}
html += '<div class="wi-lg-exif__params">';
if (fNumber) html += '<span class="wi-lg-exif__item wi-lg-exif__aperture">f/' + fNumber + '</span>';
if (exposureTime) html += '<span class="wi-lg-exif__item wi-lg-exif__shutter">' + exposureTime + 's</span>';
if (iso) html += '<span class="wi-lg-exif__item wi-lg-exif__iso">ISO ' + iso + '</span>';
if (focalLength) {
var focalText = focalLength + 'mm';
if (focalLengthIn35mm && focalLengthIn35mm !== focalLength) {
focalText += ' (eq. ' + focalLengthIn35mm + 'mm)';
}
html += '<span class="wi-lg-exif__item wi-lg-exif__focal">' + focalText + '</span>';
}
html += '</div></div>';
return html;
}
function preprocessExif(el) {
var items = el.querySelectorAll('.wi-lightgallery-item[data-exif]');
items.forEach(function(item) {
var exifStr = item.getAttribute('data-exif');
if (!exifStr) return;
var caption = item.getAttribute('data-sub-html') || '';
var html = buildExifHtml(exifStr, caption);
if (html && html !== caption) {
item.setAttribute('data-sub-html', html);
}
});
}
function createLgConfig() {
return {
selector: '.wi-lightgallery-item',
plugins: [lgZoom, lgThumbnail, lgFullscreen],
speed: 280,
mode: 'lg-fade',
licenseKey: '0000-0000-0000-0000',
download: false,
counter: true,
preload: 2,
thumbWidth: 80,
thumbHeight: '60px',
thumbMargin: 4,
zoomFromOrigin: true,
actualSize: false,
showZoomInOutIcons: true,
toggleThumb: true,
allowMediaOverlap: true,
mobileSettings: {
controls: true,
showCloseIcon: true,
download: false,
},
};
}
function initLg(el) {
if (isInitialized(el)) return;
var items = el.querySelectorAll('.wi-lightgallery-item');
if (items.length === 0) return;
preprocessExif(el);
lightGallery(el, createLgConfig());
markInitialized(el);
}
function isMobile() {
return window.innerWidth < 768;
}
function initLightGallery() {
initContentPages();
initPhotosPage();
initMomentsPage();
document.addEventListener('click', function(e) {
var toggleBtn = e.target.closest('.lg-toggle-thumb');
if (toggleBtn) {
e.stopPropagation();
var lgOuter = toggleBtn.closest('.lg-outer');
if (lgOuter) {
lgOuter.classList.toggle('wi-thumb-shown');
}
}
}, true);
document.addEventListener('click', function(e) {
var link = e.target.closest('.wi-lightgallery-item');
if (link) {
setTimeout(function() {
var lgOuter = document.querySelector('.lg-outer');
if (lgOuter && !lgOuter.classList.contains('wi-thumb-shown') && !isMobile()) {
lgOuter.classList.add('wi-thumb-shown');
}
}, 350);
}
});
}
function initContentPages() {
var selectors = ['.wi-post__body', '.wi-page__body'];
selectors.forEach(function(sel) {
var els = document.querySelectorAll(sel);
els.forEach(function(el) {
processContentImages(el);
initLg(el);
});
});
}
function initPhotosPage() {
var allGroups = document.querySelector('.wi-photos-page__all-groups');
if (allGroups) {
var links = allGroups.querySelectorAll('.wi-photos-page__link');
links.forEach(function(link) {
link.classList.add('wi-lightgallery-item');
});
initLg(allGroups);
return;
}
var singleGrid = document.querySelector('.wi-photos-page__grid:not(.wi-photos-page__all-groups .wi-photos-page__grid)');
if (singleGrid) {
var links2 = singleGrid.querySelectorAll('.wi-photos-page__link');
links2.forEach(function(link) {
link.classList.add('wi-lightgallery-item');
});
initLg(singleGrid);
return;
}
var grids = document.querySelectorAll('.wi-photos-page__grid');
grids.forEach(function(el) {
processPhotosGrid(el);
initLg(el);
});
}
function initMomentsPage() {
var selectors = ['.wi-moments-page__media', '.wi-moments-page__card-media', '.wi-moments-page__masonry-media'];
selectors.forEach(function(sel) {
var els = document.querySelectorAll(sel);
els.forEach(function(el) {
processMomentsMedia(el);
initLg(el);
});
});
}
function processContentImages(container) {
var images = container.querySelectorAll('img');
images.forEach(function(img) {
var src = img.getAttribute('src');
if (!src) return;
var w = img.getAttribute('width');
var h = img.getAttribute('height');
if ((w && parseInt(w) < 50) || (h && parseInt(h) < 50)) return;
var className = (img.getAttribute('class') || '').toLowerCase();
if (/emoji|icon|avatar|logo|badge|smiley/.test(className)) return;
if (img.getAttribute('role') === 'presentation') return;
if (img.getAttribute('aria-hidden') === 'true') return;
var parentA = img.closest('a');
if (parentA) {
if (parentA.getAttribute('target') === '_blank') return;
parentA.classList.add('wi-lightgallery-item');
if (!parentA.getAttribute('data-src')) {
var href = parentA.getAttribute('href') || src;
parentA.setAttribute('data-src', href);
}
var alt = img.getAttribute('alt') || '';
if (alt && !parentA.getAttribute('data-sub-html')) {
parentA.setAttribute('data-sub-html', alt);
}
} else {
var a = document.createElement('a');
a.setAttribute('href', src);
a.setAttribute('data-src', src);
a.classList.add('wi-lightgallery-item');
var alt2 = img.getAttribute('alt') || '';
if (alt2) {
a.setAttribute('data-sub-html', alt2);
}
img.parentNode.insertBefore(a, img);
a.appendChild(img);
}
});
}
function processPhotosGrid(container) {
var links = container.querySelectorAll('.wi-photos-page__link');
links.forEach(function(link) {
link.classList.add('wi-lightgallery-item');
});
}
function processMomentsMedia(container) {
// 注意:不要用 <a> 包裹瞬间图片。九宫格布局依赖
// .wi-moments-page__card-media 的直接子元素数量(:has/:nth-child/:only-child),
// 每张图各自包一层 <a> 后,img 永远是「唯一子元素」,全部图片都会退化成单列大图。
// 直接把 img 本身作为 lightgallery 项即可(data-src 提供原图)。
var images = container.querySelectorAll('img');
images.forEach(function(img) {
var src = img.getAttribute('src');
if (!src) return;
img.classList.add('wi-lightgallery-item');
if (!img.getAttribute('data-src')) {
img.setAttribute('data-src', src);
}
});
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', function() {
if ('requestIdleCallback' in window) {
requestIdleCallback(initLightGallery, { timeout: 2000 });
} else {
setTimeout(initLightGallery, 200);
}
});
} else {
if ('requestIdleCallback' in window) {
requestIdleCallback(initLightGallery, { timeout: 2000 });
} else {
setTimeout(initLightGallery, 200);
}
}
</script>