fix: 按 Halo 应用市场审核意见整改外部资源与隐私(v1.2.5 修订)
- 默认零第三方自动请求:移除 21 个模板的 preconnect/dns-prefetch、PrismJS+autoloader、qrcode-generator 及内置一言/IP 定位 API - 一言与代码高亮配色改为自托管;欢迎弹窗自定义 IP 接口改为 opt-in;微信分享改为复制链接;LightGallery 按 GPLv3 使用 - README 新增「外部资源与隐私」「验证与回归测试」章节,修正无效链接并补充字体 OFL 来源;同步 docs 与修正虚构的目录树 - friends 等 12 处 #strings.substring 加空值保护;logo 由两份 1.5MB 压缩去重为单份 146KB - 新增 pnpm audit:external 与打包前置合规审计;制品补齐 34 个 docs 文档与预览图
This commit is contained in:
1 parent
c0ad7331d4
commit
dda86294b0
36 files changed
+830
-236
No files matched your search
@@ -29,7 +29,7 @@
|
||||
th:unless="${link.spec.logo}"
|
||||
class="wi-friends__avatar wi-friends__avatar--placeholder"
|
||||
>
|
||||
<span th:text="${#strings.substring(link.spec.displayName, 0, 1)}"></span>
|
||||
<span th:text="${!#strings.isEmpty(link.spec.displayName) ? #strings.substring(link.spec.displayName, 0, 1) : '·'}"></span>
|
||||
</div>
|
||||
<div class="wi-friends__info">
|
||||
<span class="wi-friends__name" th:text="${link.spec.displayName}"></span>
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
class="hero__description"
|
||||
th:if="${theme.config?.hero?.hero_description_mode == 'hitokoto'}"
|
||||
id="wi-hitokoto"
|
||||
th:attr="data-api=${theme.config?.hero?.hero_hitokoto_api ?: 'https://v1.hitokoto.cn/'},data-categories=${theme.config?.hero?.hero_hitokoto_categories},data-auto-refresh=${theme.config?.hero?.hero_hitokoto_auto_refresh == true ? 'true' : 'false'},data-refresh-interval=${theme.config?.hero?.hero_hitokoto_refresh_interval ?: 10000}"
|
||||
th:attr="data-api=${theme.config?.hero?.hero_hitokoto_api ?: ''},data-categories=${theme.config?.hero?.hero_hitokoto_categories},data-auto-refresh=${theme.config?.hero?.hero_hitokoto_auto_refresh == true ? 'true' : 'false'},data-refresh-interval=${theme.config?.hero?.hero_hitokoto_refresh_interval ?: 10000}"
|
||||
></p>
|
||||
<p
|
||||
class="hero__description"
|
||||
@@ -125,7 +125,10 @@
|
||||
var el = document.getElementById("wi-hitokoto");
|
||||
if (!el) return;
|
||||
|
||||
var api = el.getAttribute("data-api") || "https://v1.hitokoto.cn/";
|
||||
/* 默认读取主题内置的本地句子库(同域静态 JSON,零第三方请求)。
|
||||
仅当站点管理员在主题设置里显式填写自定义 API 时,才会产生外部请求。 */
|
||||
var api = el.getAttribute("data-api");
|
||||
var localUrl = "/themes/warm-island/assets/data/hitokoto.json";
|
||||
var categories = el.getAttribute("data-categories");
|
||||
var autoRefresh = el.getAttribute("data-auto-refresh") === "true";
|
||||
var refreshInterval = parseInt(el.getAttribute("data-refresh-interval"), 10) || 10000;
|
||||
@@ -151,15 +154,24 @@
|
||||
el.textContent = "\u2026";
|
||||
}
|
||||
|
||||
return fetch(buildUrl())
|
||||
return fetch(api ? buildUrl() : localUrl)
|
||||
.then(function (res) {
|
||||
if (!res.ok) throw new Error("HTTP " + res.status);
|
||||
return res.json();
|
||||
})
|
||||
.then(function (data) {
|
||||
var text = data.hitokoto || "";
|
||||
var from = data.from || data.from_who || "";
|
||||
if (from) {
|
||||
var text, from;
|
||||
if (!api && data && Array.isArray(data.sentences) && data.sentences.length) {
|
||||
/* 内置词库:随机取一条,避免每次刷新都是同一句 */
|
||||
var item = data.sentences[Math.floor(Math.random() * data.sentences.length)];
|
||||
text = item.text || "";
|
||||
from = item.from || "";
|
||||
} else {
|
||||
/* 站点显式配置的自定义一言 API */
|
||||
text = data.hitokoto || "";
|
||||
from = data.from || data.from_who || "";
|
||||
}
|
||||
if (from && text) {
|
||||
text += " \u2014\u2014 " + from;
|
||||
}
|
||||
|
||||
|
||||
@@ -73,7 +73,12 @@
|
||||
plugins: [lgZoom, lgThumbnail, lgFullscreen],
|
||||
speed: 280,
|
||||
mode: 'lg-fade',
|
||||
licenseKey: '0000-0000-0000-0000',
|
||||
/* lightGallery 2.9.0 以 GPLv3 发布,本主题同样采用 GPL-3.0,
|
||||
按其开源许可条款使用(不申请商业授权)。
|
||||
官方示例里的 '0000-0000-000-0000' 会触发
|
||||
"license key is not valid for production use" 警告,
|
||||
这里去掉占位字符串、显式声明许可依据,控制台不再出现该提示。 */
|
||||
licenseKey: 'GPLv3',
|
||||
download: false,
|
||||
counter: true,
|
||||
preload: 2,
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
th:unless="${comment.spec.owner.avatar}"
|
||||
class="wi-message-wall__avatar wi-message-wall__avatar--placeholder"
|
||||
>
|
||||
<span th:text="${#strings.substring(comment.spec.owner.displayName, 0, 1)}"></span>
|
||||
<span th:text="${!#strings.isEmpty(comment.spec.owner?.displayName) ? #strings.substring(comment.spec.owner?.displayName, 0, 1) : '·'}"></span>
|
||||
</div>
|
||||
<div class="wi-message-wall__meta">
|
||||
<span class="wi-message-wall__name" th:text="${comment.spec.owner.displayName}"></span>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
class="wi-welcome-popup"
|
||||
id="wi-welcome-popup"
|
||||
th:if="${theme.config?.welcome_popup?.welcome_popup_enabled == true}"
|
||||
th:attr="data-position=${theme.config?.welcome_popup?.welcome_popup_position ?: 'top-center'},data-duration=${theme.config?.welcome_popup?.welcome_popup_duration ?: 5},data-api=${theme.config?.welcome_popup?.welcome_popup_api},data-fallback-api=${theme.config?.welcome_popup?.welcome_popup_fallback_api},data-show-once=${theme.config?.welcome_popup?.welcome_popup_show_once != false ? 'true' : 'false'}"
|
||||
th:attr="data-position=${theme.config?.welcome_popup?.welcome_popup_position ?: 'top-center'},data-duration=${theme.config?.welcome_popup?.welcome_popup_duration ?: 5},data-api=${theme.config?.welcome_popup?.welcome_popup_api ?: ''},data-fallback-api=${theme.config?.welcome_popup?.welcome_popup_fallback_api ?: ''},data-show-once=${theme.config?.welcome_popup?.welcome_popup_show_once != false ? 'true' : 'false'}"
|
||||
aria-live="polite"
|
||||
aria-atomic="true"
|
||||
>
|
||||
@@ -16,7 +16,7 @@
|
||||
<circle cx="12" cy="10" r="3"></circle>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="wi-welcome-popup__content" id="wi-welcome-popup-text">加载中…</div>
|
||||
<div class="wi-welcome-popup__content" id="wi-welcome-popup-text">欢迎来访,愿你在这里度过一段安静的时光</div>
|
||||
<button class="wi-welcome-popup__close" type="button" aria-label="关闭">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M18 6 6 18"></path>
|
||||
@@ -46,8 +46,12 @@
|
||||
var closeBtn = popup.querySelector(".wi-welcome-popup__close");
|
||||
var progress = document.getElementById("wi-welcome-popup-progress");
|
||||
var duration = parseInt(popup.getAttribute("data-duration"), 10) || 5;
|
||||
var api = popup.getAttribute("data-api") || "https://myip.ipip.net/json";
|
||||
var fallbackApi = popup.getAttribute("data-fallback-api") || "https://r.inews.qq.com/api/ip2city?otype=json";
|
||||
/* 默认零外部请求:主题内置静态欢迎语,不向任何第三方发送访客 IP。
|
||||
仅当站点管理员在主题设置里显式填写自定义 IP 定位 API 时才会 fetch;
|
||||
一旦填写,浏览器会向该服务发送访客 IP,需自行在站点隐私说明中披露
|
||||
(详见 README「外部资源与隐私」)。 */
|
||||
var api = popup.getAttribute("data-api") || "";
|
||||
var fallbackApi = popup.getAttribute("data-fallback-api") || "";
|
||||
|
||||
var hideTimer = null;
|
||||
var progressRaf = null;
|
||||
@@ -91,6 +95,14 @@
|
||||
hideTimer = setTimeout(hidePopup, duration * 1000);
|
||||
}
|
||||
|
||||
function renderWelcome() {
|
||||
if (textEl) {
|
||||
textEl.textContent = "欢迎来访,愿你在这里度过一段安静的时光";
|
||||
}
|
||||
popup.classList.add("wi-welcome-popup--visible");
|
||||
startAutoHide();
|
||||
}
|
||||
|
||||
function renderMessage(location) {
|
||||
var loc = (location || "").trim();
|
||||
if (!loc) loc = "未知星球";
|
||||
@@ -102,26 +114,24 @@
|
||||
}
|
||||
|
||||
function fetchLocation() {
|
||||
if (textEl) {
|
||||
textEl.textContent = "加载中…";
|
||||
}
|
||||
popup.classList.add("wi-welcome-popup--visible");
|
||||
|
||||
/* 按顺序尝试主 API -> 备选 API */
|
||||
/* 按顺序尝试主 API -> 备选 API;两者都为空则完全不发外部请求 */
|
||||
var endpoints = [];
|
||||
if (api) endpoints.push(api);
|
||||
if (fallbackApi && fallbackApi !== api) endpoints.push(fallbackApi);
|
||||
|
||||
if (endpoints.length === 0) {
|
||||
renderMessage("");
|
||||
renderWelcome();
|
||||
return;
|
||||
}
|
||||
|
||||
if (textEl) textEl.textContent = "正在获取位置…";
|
||||
var idx = 0;
|
||||
|
||||
function tryNext() {
|
||||
if (idx >= endpoints.length) {
|
||||
renderMessage("");
|
||||
renderWelcome();
|
||||
return;
|
||||
}
|
||||
var url = endpoints[idx++];
|
||||
@@ -167,15 +177,14 @@
|
||||
});
|
||||
}
|
||||
|
||||
/* 解析多种 IP API 的返回格式:
|
||||
- IPIP: {"ret":"ok","data":{"location":["中国","河北","张家口","","联通"]}}
|
||||
- 腾讯 ip2city: {"ret":0,"province":"河北","city":"张家口",...}
|
||||
- ipapi.co / ip-api.com: {"country":"China","regionName":"Hebei","city":"Zhangjiakou"}
|
||||
- 太平洋: {"pro":"广东省","city":"广州市",...} */
|
||||
/* 解析常见 IP 定位接口的返回格式(仅在管理员显式配置自定义 API 时才会用到):
|
||||
- { data: { location: [...] } }
|
||||
- { province, city, ... } / { regionName, city, ... }
|
||||
- { pro, city, ... } */
|
||||
function parseLocation(data) {
|
||||
if (!data) return "";
|
||||
|
||||
/* IPIP: data.data.location 数组 */
|
||||
/* data.location 数组格式 */
|
||||
if (data.data && Array.isArray(data.data.location)) {
|
||||
var loc = data.data.location
|
||||
.filter(function (s) {
|
||||
|
||||
@@ -86,8 +86,8 @@ const fragmentAttr = layoutFragment ? "html (head, content)" : undefined;
|
||||
th:href="${theme.config?.basic?.favicon ?: '/favicon.ico'}"
|
||||
/>
|
||||
<link rel="alternate" type="application/rss+xml" th:title="${site.title}" th:href="@{/feed.xml}" />
|
||||
<link rel="preconnect" href="https://cdn.jsdelivr.net" />
|
||||
<link rel="dns-prefetch" href="https://cdn.jsdelivr.net" />
|
||||
<!-- 主题不预连接任何第三方 CDN:所有脚本、样式、字体均随主题自托管。
|
||||
分享链接等外链只在访客主动点击时打开,不产生自动外部请求。 -->
|
||||
<link rel="stylesheet" th:href="@{/assets/fonts/LXGWWenKai/result.css}" media="print" onload="this.media='all'" />
|
||||
<noscript><link rel="stylesheet" th:href="@{/assets/fonts/LXGWWenKai/result.css}" /></noscript>
|
||||
<style is:inline th:if="${theme.config?.style?.accent_color}" th:utext="${':root { --accent: ' + theme.config?.style?.accent_color + '; --accent-hover: ' + theme.config?.style?.accent_color + '; }'}">/*accent*/</style>
|
||||
|
||||
@@ -141,7 +141,7 @@ import Layout from "../layouts/Layout.astro";
|
||||
/>
|
||||
</div>
|
||||
<div class="wi-douban__poster wi-douban__poster--placeholder" th:if="${#strings.isEmpty(item.poster)}">
|
||||
<span th:text="${#strings.substring(item.name ?: '', 0, 1)}"></span>
|
||||
<span th:text="${!#strings.isEmpty(item.name) ? #strings.substring(item.name, 0, 1) : '·'}"></span>
|
||||
</div>
|
||||
<div class="wi-douban__body">
|
||||
<h3 class="wi-douban__name" th:text="${item.name}"></h3>
|
||||
|
||||
@@ -45,7 +45,7 @@ import Layout from "../layouts/Layout.astro";
|
||||
class="wi-equipments__cover wi-equipments__cover--placeholder"
|
||||
th:if="${#strings.isEmpty(equip.spec?.cover)}"
|
||||
>
|
||||
<span th:text="${#strings.substring(equip.spec?.displayName ?: '', 0, 1)}"></span>
|
||||
<span th:text="${!#strings.isEmpty(equip.spec?.displayName) ? #strings.substring(equip.spec?.displayName, 0, 1) : '·'}"></span>
|
||||
</div>
|
||||
<div class="wi-equipments__body">
|
||||
<h3
|
||||
|
||||
@@ -41,7 +41,7 @@ import Layout from "../layouts/Layout.astro";
|
||||
class="wi-friends-page__avatar wi-friends-page__avatar--placeholder"
|
||||
>
|
||||
<span
|
||||
th:text="${#strings.substring(spec.author ?: '', 0, 1)}"
|
||||
th:text="${!#strings.isEmpty(spec.author) ? #strings.substring(spec.author, 0, 1) : '·'}"
|
||||
></span>
|
||||
</div>
|
||||
<div class="wi-friends-page__author-info">
|
||||
|
||||
@@ -341,7 +341,7 @@ import Layout from "../layouts/Layout.astro";
|
||||
<span class="wi-id__avatar">
|
||||
<span
|
||||
class="wi-id__avatar-fallback"
|
||||
th:text="${#strings.substring(p.displayName, 0, 1)}"
|
||||
th:text="${!#strings.isEmpty(p.displayName) ? #strings.substring(p.displayName, 0, 1) : '·'}"
|
||||
>老</span
|
||||
>
|
||||
<img
|
||||
@@ -407,7 +407,7 @@ import Layout from "../layouts/Layout.astro";
|
||||
<span class="wi-id__avatar">
|
||||
<span
|
||||
class="wi-id__avatar-fallback"
|
||||
th:text="${#strings.substring(p.displayName, 0, 1)}"
|
||||
th:text="${!#strings.isEmpty(p.displayName) ? #strings.substring(p.displayName, 0, 1) : '·'}"
|
||||
>老</span
|
||||
>
|
||||
<img
|
||||
@@ -466,7 +466,7 @@ import Layout from "../layouts/Layout.astro";
|
||||
<span class="wi-id__avatar">
|
||||
<span
|
||||
class="wi-id__avatar-fallback"
|
||||
th:text="${#strings.substring(p.displayName, 0, 1)}"
|
||||
th:text="${!#strings.isEmpty(p.displayName) ? #strings.substring(p.displayName, 0, 1) : '·'}"
|
||||
>张</span
|
||||
>
|
||||
<img
|
||||
|
||||
@@ -70,7 +70,7 @@ import Layout from "../layouts/Layout.astro";
|
||||
class="wi-links__avatar wi-links__avatar--placeholder"
|
||||
>
|
||||
<span
|
||||
th:text="${#strings.substring(link.spec.displayName, 0, 1)}"
|
||||
th:text="${!#strings.isEmpty(link.spec.displayName) ? #strings.substring(link.spec.displayName, 0, 1) : '·'}"
|
||||
></span>
|
||||
</div>
|
||||
<!-- 可达性检测徽章(plugin-links 后台「友链检测」产生):绿点=可访问,红点=异常,未检测不显示 -->
|
||||
|
||||
@@ -43,7 +43,7 @@ import LightGallery from "../components/LightGallery.astro";
|
||||
class="wi-moment-detail__avatar wi-moment-detail__avatar--fallback"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<span th:text="${#strings.substring(site.title, 0, 1)}"></span>
|
||||
<span th:text="${!#strings.isEmpty(site.title) ? #strings.substring(site.title, 0, 1) : '·'}"></span>
|
||||
</div>
|
||||
|
||||
<div class="wi-moment-detail__main">
|
||||
|
||||
@@ -45,7 +45,7 @@ import LightGallery from "../components/LightGallery.astro";
|
||||
class="wi-moments-page__avatar wi-moments-page__avatar--fallback"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<span th:text="${#strings.substring(site.title, 0, 1)}"></span>
|
||||
<span th:text="${!#strings.isEmpty(site.title) ? #strings.substring(site.title, 0, 1) : '·'}"></span>
|
||||
</div>
|
||||
|
||||
<div class="wi-moments-page__main">
|
||||
|
||||
@@ -18,7 +18,7 @@ import Layout from "../layouts/Layout.astro";
|
||||
订阅
|
||||
</h1> <p class="wi-subs__subtitle" th:text="${theme.config?.subscriptions?.subscriptions_subtitle ?: '博友们的最新文章'}">
|
||||
博友们的最新文章
|
||||
</p> <span class="wi-page-accent"></span> </div> <th:block th:if="${feedPage != null and !#lists.isEmpty(feedPage.items)}"> <div class="wi-subs__flow"> <article class="wi-subs__card" th:each="item : ${feedPage.items}" th:with="authorName=${item.author ?: '匿名'}"> <div class="wi-subs__author"> <img th:if="${item.authorLogo}" th:src="${item.authorLogo}" th:alt="${authorName}" class="wi-subs__avatar" loading="lazy" decoding="async"> <div th:unless="${item.authorLogo}" class="wi-subs__avatar wi-subs__avatar--placeholder"> <span th:text="${#strings.substring(authorName, 0, 1)}"></span> </div> <div class="wi-subs__author-info"> <a th:if="${item.authorUrl}" th:href="${item.authorUrl}" target="_blank" rel="noopener noreferrer" class="wi-subs__author-name" th:text="${authorName}"></a> <span th:unless="${item.authorUrl}" class="wi-subs__author-name" th:text="${authorName}"></span> <time class="wi-subs__date" th:if="${item.publishedAt}" th:text="${#temporals.format(item.publishedAt, 'yyyy-MM-dd')}"></time> </div> </div> <div class="wi-subs__body"> <a th:href="${item.url}" target="_blank" rel="noopener noreferrer" class="wi-subs__title-link"> <h2 class="wi-subs__post-title" th:text="${item.title}"></h2> </a> <p class="wi-subs__summary" th:if="${item.summary}" th:text="${item.summary}"></p> </div> </article> </div> <nav class="wi-subs__pagination" th:if="${loadingMode != 'infinite_scroll' and (hasCursor or feedPage.hasNext)}" th:with="permalink=${singlePage.status.permalink}"> <a th:if="${hasCursor}" th:href="@{${permalink}}" class="wi-subs__page-link" th:text="'较新'">
|
||||
</p> <span class="wi-page-accent"></span> </div> <th:block th:if="${feedPage != null and !#lists.isEmpty(feedPage.items)}"> <div class="wi-subs__flow"> <article class="wi-subs__card" th:each="item : ${feedPage.items}" th:with="authorName=${item.author ?: '匿名'}"> <div class="wi-subs__author"> <img th:if="${item.authorLogo}" th:src="${item.authorLogo}" th:alt="${authorName}" class="wi-subs__avatar" loading="lazy" decoding="async"> <div th:unless="${item.authorLogo}" class="wi-subs__avatar wi-subs__avatar--placeholder"> <span th:text="${!#strings.isEmpty(authorName) ? #strings.substring(authorName, 0, 1) : '·'}"></span> </div> <div class="wi-subs__author-info"> <a th:if="${item.authorUrl}" th:href="${item.authorUrl}" target="_blank" rel="noopener noreferrer" class="wi-subs__author-name" th:text="${authorName}"></a> <span th:unless="${item.authorUrl}" class="wi-subs__author-name" th:text="${authorName}"></span> <time class="wi-subs__date" th:if="${item.publishedAt}" th:text="${#temporals.format(item.publishedAt, 'yyyy-MM-dd')}"></time> </div> </div> <div class="wi-subs__body"> <a th:href="${item.url}" target="_blank" rel="noopener noreferrer" class="wi-subs__title-link"> <h2 class="wi-subs__post-title" th:text="${item.title}"></h2> </a> <p class="wi-subs__summary" th:if="${item.summary}" th:text="${item.summary}"></p> </div> </article> </div> <nav class="wi-subs__pagination" th:if="${loadingMode != 'infinite_scroll' and (hasCursor or feedPage.hasNext)}" th:with="permalink=${singlePage.status.permalink}"> <a th:if="${hasCursor}" th:href="@{${permalink}}" class="wi-subs__page-link" th:text="'较新'">
|
||||
较新
|
||||
</a> <span th:unless="${hasCursor}" class="wi-subs__page-link wi-subs__page-link--placeholder" aria-hidden="true"></span> <span class="wi-subs__page-info"></span> <a th:if="${feedPage.hasNext}" th:href="@{|${permalink}?beforePublishedAt=${feedPage.nextBeforePublishedAt}&beforeId=${feedPage.nextBeforeId}|}" class="wi-subs__page-link" th:text="'较旧'">
|
||||
较旧
|
||||
|
||||
+68
-72
@@ -194,9 +194,11 @@ import LightGallery from "../components/LightGallery.astro";
|
||||
<button class="wi-wechat-qr__close" type="button" aria-label="Close">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>
|
||||
</button>
|
||||
<div class="wi-wechat-qr__title" th:text="'微信扫码分享'">微信扫码分享</div>
|
||||
<canvas class="wi-wechat-qr__canvas" id="wi-wechat-qr-canvas"></canvas>
|
||||
<div class="wi-wechat-qr__hint" th:text="'打开微信,扫描二维码即可分享'">打开微信,扫描二维码即可分享</div>
|
||||
<div class="wi-wechat-qr__title" th:text="'微信分享'">微信分享</div>
|
||||
<!-- 微信分享不加载任何远程二维码库:改为复制文章链接,由访客在微信内分享 -->
|
||||
<input class="wi-wechat-qr__url" id="wi-wechat-qr-url" type="text" readonly="readonly" aria-label="文章链接" />
|
||||
<button class="wi-wechat-qr__copy" id="wi-wechat-qr-copy" type="button" th:text="'复制链接'">复制链接</button>
|
||||
<div class="wi-wechat-qr__hint" th:text="'复制链接后,在微信中发送给好友'">复制链接后,在微信中发送给好友</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -295,37 +297,20 @@ import LightGallery from "../components/LightGallery.astro";
|
||||
|
||||
<LightGallery />
|
||||
|
||||
<!-- 代码高亮:主题不再加载远程语法高亮运行时与自动加载插件(后者会按代码语言
|
||||
从 CDN 动态拉取并执行组件,属于远程动态加载)。语法高亮改由应用市场的
|
||||
代码高亮插件或「API 扩展包」服务端高亮输出,主题只自托管两套 token 配色。 -->
|
||||
<link
|
||||
th:if="${theme.config?.article?.article_code_theme == 'cold'}"
|
||||
rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/themes/prism-tomorrow.min.css"
|
||||
href="/themes/warm-island/assets/vendor/prism-cold.css"
|
||||
/>
|
||||
<link
|
||||
th:if="${theme.config?.article?.article_code_theme != 'cold'}"
|
||||
rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/themes/prism.min.css"
|
||||
href="/themes/warm-island/assets/vendor/prism-warm.css"
|
||||
/>
|
||||
|
||||
<script th:if="${theme.config?.article?.article_show_share != false and #strings.contains(theme.config?.article?.article_share_channels ?: 'qq,wechat,weibo', 'wechat')}" is:inline defer src="https://cdn.jsdelivr.net/npm/qrcode-generator@1.4.4/qrcode.min.js"></script>
|
||||
|
||||
<script is:inline defer src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/prism.min.js"></script>
|
||||
<script is:inline defer src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/plugins/autoloader/prism-autoloader.min.js"></script>
|
||||
<script is:inline th:inline="none">
|
||||
(function () {
|
||||
if (typeof Prism !== "undefined") {
|
||||
Prism.plugins.autoloader.languages_path = "https://cdn.jsdelivr.net/npm/prismjs@1.29.0/components/";
|
||||
var body = document.getElementById("wi-post-body");
|
||||
if (body) {
|
||||
body.querySelectorAll("pre code").forEach(function (block) {
|
||||
if (!block.classList.contains("language-")) {
|
||||
Prism.highlightElement(block);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
<script is:inline th:inline="none">
|
||||
(function () {
|
||||
var progressBar = document.getElementById("wi-reading-progress");
|
||||
@@ -673,58 +658,45 @@ import LightGallery from "../components/LightGallery.astro";
|
||||
|
||||
var wechatBtn = document.querySelector(".wi-post__share-btn--wechat");
|
||||
var wechatQr = document.getElementById("wi-wechat-qr");
|
||||
var wechatQrCanvas = document.getElementById("wi-wechat-qr-canvas");
|
||||
var wechatUrlInput = document.getElementById("wi-wechat-qr-url");
|
||||
var wechatCopyBtn = document.getElementById("wi-wechat-qr-copy");
|
||||
var wechatQrClose = wechatQr ? wechatQr.querySelector(".wi-wechat-qr__close") : null;
|
||||
var wechatQrGenerated = false;
|
||||
|
||||
function generateQr(text, canvas) {
|
||||
var size = 200;
|
||||
var cellSize = 5;
|
||||
var qr = null;
|
||||
try {
|
||||
var typeNumber = 0;
|
||||
var errorCorrectionLevel = "M";
|
||||
var qrData = [];
|
||||
for (var i = 0; i < text.length; i++) {
|
||||
qrData.push(text.charCodeAt(i));
|
||||
}
|
||||
qr = qrcode(typeNumber, errorCorrectionLevel);
|
||||
qr.addData(text);
|
||||
qr.make();
|
||||
} catch (e) {
|
||||
return;
|
||||
}
|
||||
var moduleCount = qr.getModuleCount();
|
||||
var margin = 4;
|
||||
var totalCells = moduleCount + margin * 2;
|
||||
var canvasSize = totalCells * cellSize;
|
||||
canvas.width = canvasSize;
|
||||
canvas.height = canvasSize;
|
||||
var ctx = canvas.getContext("2d");
|
||||
ctx.fillStyle = "#ffffff";
|
||||
ctx.fillRect(0, 0, canvasSize, canvasSize);
|
||||
ctx.fillStyle = "#000000";
|
||||
for (var row = 0; row < moduleCount; row++) {
|
||||
for (var col = 0; col < moduleCount; col++) {
|
||||
if (qr.isDark(row, col)) {
|
||||
ctx.fillRect((col + margin) * cellSize, (row + margin) * cellSize, cellSize, cellSize);
|
||||
}
|
||||
}
|
||||
function resolveShareUrl() {
|
||||
var rawUrl = (wechatBtn && wechatBtn.getAttribute("data-url")) || window.location.pathname;
|
||||
return rawUrl.startsWith("http") ? rawUrl : window.location.origin + rawUrl;
|
||||
}
|
||||
|
||||
/* 复制链接后由访客在微信内分享,零远程脚本 */
|
||||
function copyShareUrl() {
|
||||
var url = resolveShareUrl();
|
||||
if (wechatUrlInput) wechatUrlInput.value = url;
|
||||
var done = function () {
|
||||
if (!wechatCopyBtn) return;
|
||||
wechatCopyBtn.textContent = "已复制";
|
||||
setTimeout(function () {
|
||||
wechatCopyBtn.textContent = "复制链接";
|
||||
}, 1800);
|
||||
};
|
||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||
navigator.clipboard.writeText(url).then(done, function () {
|
||||
if (wechatUrlInput) wechatUrlInput.select();
|
||||
});
|
||||
} else if (wechatUrlInput) {
|
||||
wechatUrlInput.select();
|
||||
try {
|
||||
document.execCommand("copy");
|
||||
done();
|
||||
} catch (e) {}
|
||||
}
|
||||
}
|
||||
|
||||
if (wechatBtn && wechatQr) {
|
||||
wechatBtn.addEventListener("click", function () {
|
||||
wechatQr.classList.add("wi-wechat-qr--visible");
|
||||
if (!wechatQrGenerated && wechatQrCanvas) {
|
||||
var rawUrl = wechatBtn.getAttribute("data-url") || window.location.pathname;
|
||||
var url = rawUrl.startsWith("http") ? rawUrl : window.location.origin + rawUrl;
|
||||
if (typeof qrcode !== "undefined") {
|
||||
generateQr(url, wechatQrCanvas);
|
||||
wechatQrGenerated = true;
|
||||
}
|
||||
}
|
||||
if (wechatUrlInput) wechatUrlInput.value = resolveShareUrl();
|
||||
});
|
||||
if (wechatCopyBtn) wechatCopyBtn.addEventListener("click", copyShareUrl);
|
||||
if (wechatQrClose) {
|
||||
wechatQrClose.addEventListener("click", function () {
|
||||
wechatQr.classList.remove("wi-wechat-qr--visible");
|
||||
@@ -1255,12 +1227,36 @@ import LightGallery from "../components/LightGallery.astro";
|
||||
margin-bottom: var(--space-md);
|
||||
}
|
||||
|
||||
.wi-wechat-qr__canvas {
|
||||
.wi-wechat-qr__url {
|
||||
display: block;
|
||||
margin: 0 auto var(--space-md);
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
margin: 0 0 var(--space-sm, 0.5rem);
|
||||
padding: 10px 12px;
|
||||
border: 1px solid var(--rule);
|
||||
border-radius: var(--radius-sm, 8px);
|
||||
background: #fff;
|
||||
padding: 8px;
|
||||
background: var(--bg-raised);
|
||||
color: var(--ink);
|
||||
font-size: var(--text-sm);
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.wi-wechat-qr__copy {
|
||||
display: block;
|
||||
width: 100%;
|
||||
margin: 0 0 var(--space-md);
|
||||
padding: 10px 12px;
|
||||
border: none;
|
||||
border-radius: var(--radius-sm, 8px);
|
||||
background: var(--accent);
|
||||
color: #fff;
|
||||
font-size: var(--text-sm);
|
||||
cursor: pointer;
|
||||
transition: opacity 0.15s ease;
|
||||
}
|
||||
|
||||
.wi-wechat-qr__copy:hover {
|
||||
opacity: 0.88;
|
||||
}
|
||||
|
||||
.wi-wechat-qr__hint {
|
||||
|
||||
Reference in new issue
Block a user