评论区改为自托管 twikoo 前端,并适配主题
- 前端脚本从 jsDelivr 改为自托管 public/assets/js/twikoo.min.js, 版本与后端对齐(1.7.20)。国内访问 jsDelivr 实测 3 秒以上且常被墙, 是评论区空白的根因 - 新增 public/assets/css/twikoo-custom.css 主题适配层:选择器加 #twikoo 前缀以覆盖运行时注入的样式,颜色引用主题变量,自动跟随亮/暗色 - 修复首屏重复初始化:astro:page-load 在首屏也会触发,用 dataset.mounted 去重 - 评论配置移除已无用的 version 字段;README 与操作指南同步更新
This commit is contained in:
@@ -6,86 +6,110 @@ const envId =
|
||||
const isEnabled = Boolean(envId)
|
||||
---
|
||||
|
||||
{isEnabled && (
|
||||
<section class="mt-8 pt-0 pb-12">
|
||||
<div class="px-6">
|
||||
<div class="max-w-3xl mx-auto">
|
||||
<div
|
||||
id="twikoo"
|
||||
data-env-id={envId}
|
||||
data-lang={commentConfig.lang}
|
||||
data-version={commentConfig.version}
|
||||
data-strip-trailing-slash={commentConfig.stripTrailingSlash ? "true" : "false"}
|
||||
></div>
|
||||
<script is:inline>
|
||||
(() => {
|
||||
const SCRIPT_ID = "twikoo-script"
|
||||
{
|
||||
isEnabled && (
|
||||
<section class="mt-12 pb-12" data-comments>
|
||||
{/* twikoo 的主样式由脚本运行时注入,这里只加主题适配层 */}
|
||||
<link rel="stylesheet" href="/assets/css/twikoo-custom.css" />
|
||||
|
||||
const loadScript = (version) => {
|
||||
if (window.twikoo) return Promise.resolve()
|
||||
<div class="px-6">
|
||||
<div class="max-w-3xl mx-auto">
|
||||
<h2 class="mb-6 flex items-center gap-3 text-2xl font-semibold text-foreground">
|
||||
<span class="h-6 w-1 rounded-full bg-primary" />
|
||||
评论
|
||||
</h2>
|
||||
|
||||
const existing = document.getElementById(SCRIPT_ID)
|
||||
if (existing) {
|
||||
if (existing.dataset.loaded === "true") return Promise.resolve()
|
||||
return new Promise((resolve, reject) => {
|
||||
existing.addEventListener("load", () => resolve(), { once: true })
|
||||
existing.addEventListener("error", () => {
|
||||
reject(new Error("Failed to load twikoo"))
|
||||
}, { once: true })
|
||||
})
|
||||
}
|
||||
<div
|
||||
id="twikoo"
|
||||
data-env-id={envId}
|
||||
data-lang={commentConfig.lang}
|
||||
data-strip-trailing-slash={commentConfig.stripTrailingSlash ? "true" : "false"}
|
||||
/>
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const script = document.createElement("script")
|
||||
script.id = SCRIPT_ID
|
||||
script.src = `https://cdn.jsdelivr.net/npm/twikoo@${version}/dist/twikoo.min.js`
|
||||
script.async = true
|
||||
script.onload = () => {
|
||||
script.dataset.loaded = "true"
|
||||
resolve()
|
||||
<script is:inline>
|
||||
;(() => {
|
||||
const SCRIPT_SRC = "/assets/js/twikoo.min.js"
|
||||
|
||||
const loadScript = () => {
|
||||
if (window.twikoo) return Promise.resolve()
|
||||
|
||||
const existing = document.querySelector("script[data-twikoo]")
|
||||
if (existing) {
|
||||
if (existing.dataset.loaded === "true") return Promise.resolve()
|
||||
return new Promise((resolve, reject) => {
|
||||
existing.addEventListener("load", () => resolve(), { once: true })
|
||||
existing.addEventListener(
|
||||
"error",
|
||||
() => reject(new Error("twikoo 脚本加载失败")),
|
||||
{ once: true },
|
||||
)
|
||||
})
|
||||
}
|
||||
script.onerror = () => reject(new Error("Failed to load twikoo"))
|
||||
document.head.appendChild(script)
|
||||
})
|
||||
}
|
||||
|
||||
// 评论归属的路径。去掉结尾斜杠,与站点历史评论数据保持一致。
|
||||
const getCommentPath = (stripTrailingSlash) => {
|
||||
const pathname = window.location.pathname
|
||||
if (stripTrailingSlash && pathname.length > 1 && pathname.endsWith("/")) {
|
||||
return pathname.slice(0, -1)
|
||||
}
|
||||
return pathname
|
||||
}
|
||||
|
||||
const mountComments = async () => {
|
||||
const container = document.getElementById("twikoo")
|
||||
if (!container) return
|
||||
|
||||
const envId = container.dataset.envId
|
||||
if (!envId) return
|
||||
|
||||
try {
|
||||
await loadScript(container.dataset.version)
|
||||
// 切页时容器已被换成新节点,清空可避免重复渲染
|
||||
container.innerHTML = ""
|
||||
await window.twikoo.init({
|
||||
envId,
|
||||
el: "#twikoo",
|
||||
lang: container.dataset.lang || "zh-CN",
|
||||
path: getCommentPath(container.dataset.stripTrailingSlash === "true"),
|
||||
return new Promise((resolve, reject) => {
|
||||
const script = document.createElement("script")
|
||||
script.src = SCRIPT_SRC
|
||||
script.async = true
|
||||
script.dataset.twikoo = "true"
|
||||
script.onload = () => {
|
||||
script.dataset.loaded = "true"
|
||||
resolve()
|
||||
}
|
||||
script.onerror = () => reject(new Error("twikoo 脚本加载失败: " + SCRIPT_SRC))
|
||||
document.head.appendChild(script)
|
||||
})
|
||||
} catch (error) {
|
||||
console.error("[twikoo] 初始化失败:", error)
|
||||
}
|
||||
}
|
||||
|
||||
mountComments()
|
||||
// 本站启用了 Astro 的 ClientRouter,切页后需要重新挂载
|
||||
document.addEventListener("astro:page-load", mountComments)
|
||||
})()
|
||||
</script>
|
||||
/**
|
||||
* 评论归属路径。本站 trailingSlash 为 "always",但评论 key 统一去掉
|
||||
* 结尾斜杠(/posts/xxx),与历史数据保持一致。
|
||||
*/
|
||||
const getCommentPath = (strip) => {
|
||||
const pathname = window.location.pathname
|
||||
if (strip && pathname.length > 1 && pathname.endsWith("/")) {
|
||||
return pathname.slice(0, -1)
|
||||
}
|
||||
return pathname
|
||||
}
|
||||
|
||||
const mount = async () => {
|
||||
const container = document.getElementById("twikoo")
|
||||
if (!container) return
|
||||
|
||||
// astro:page-load 在首屏也会触发,用标记避免重复初始化;
|
||||
// 切页时容器是新节点,没有这个标记,所以会正常重新挂载。
|
||||
if (container.dataset.mounted === "true") return
|
||||
|
||||
const envId = container.dataset.envId
|
||||
if (!envId) return
|
||||
|
||||
container.dataset.mounted = "true"
|
||||
|
||||
try {
|
||||
await loadScript()
|
||||
await window.twikoo.init({
|
||||
envId,
|
||||
el: "#twikoo",
|
||||
lang: container.dataset.lang || "zh-CN",
|
||||
path: getCommentPath(container.dataset.stripTrailingSlash === "true"),
|
||||
})
|
||||
} catch (error) {
|
||||
// 失败时解除标记,允许下次切页重试
|
||||
container.dataset.mounted = "false"
|
||||
console.error("[twikoo] 初始化失败:", error)
|
||||
}
|
||||
}
|
||||
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", mount)
|
||||
} else {
|
||||
mount()
|
||||
}
|
||||
document.addEventListener("astro:page-load", mount)
|
||||
})()
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
+10
-9
@@ -1,25 +1,26 @@
|
||||
/**
|
||||
* 评论系统配置。
|
||||
*
|
||||
* 这里配置的是 twikoo(https://twikoo.js.org)。留空 twikooEnvId
|
||||
* 则整个评论区不渲染。
|
||||
* 使用 twikoo(https://twikoo.js.org),后端为自托管实例。
|
||||
*
|
||||
* 也可以用环境变量 PUBLIC_TWIKOO_ENV_ID 覆盖,优先级高于本文件。
|
||||
* 前端脚本不走 CDN,而是自托管的 public/assets/js/twikoo.min.js —— 国内访问
|
||||
* jsDelivr 很不稳定,走 CDN 会导致评论区加载不出来。该文件版本与后端保持一致
|
||||
* (当前都是 1.7.20),升级后端时记得同步替换它。
|
||||
*
|
||||
* 主题适配样式在 public/assets/css/twikoo-custom.css。
|
||||
*/
|
||||
export const commentConfig = {
|
||||
/** twikoo 后端地址(部署 twikoo 的那个域名) */
|
||||
/** twikoo 后端地址(部署 twikoo 的那个域名)。留空则整个评论区不渲染。 */
|
||||
twikooEnvId: "https://twikoo.tianxuetao.top",
|
||||
|
||||
/** 评论界面语言 */
|
||||
lang: "zh-CN",
|
||||
|
||||
/** twikoo 前端脚本版本,从 jsDelivr 加载 */
|
||||
version: "1.7.4",
|
||||
|
||||
/**
|
||||
* 评论归属用的路径是否去掉结尾斜杠。
|
||||
* 评论归属路径是否去掉结尾斜杠。
|
||||
* 本站 trailingSlash 为 "always",但评论 key 统一存成不带斜杠的
|
||||
* `/posts/xxx` 形式,与该路径在旧站点上的历史数据保持一致。
|
||||
* `/posts/xxx` 形式,与后端里的历史数据保持一致 —— 改掉这条,
|
||||
* 老评论就会对不上。
|
||||
*/
|
||||
stripTrailingSlash: true,
|
||||
} as const
|
||||
|
||||
Reference in New Issue
Block a user