在 <script> 里 {{ .Title | jsonify }} 会被 Go 模板按 JS 上下文再转义一层:
标题「留言」渲染成 pageTitle: "\"留言\"",前端每次上报 page_title 都自带
一对引号 —— 评论入库、邮件标题因此出现《"留言"》,历史上 pages 表一堆标题带
引号(17 行)也是这个根因。jsonify 后用 safeJS 标明这就是最终 JS 字面量,
`</` 仍按惯例转义兜底;已验证中文/emoji 标题字面量均合法
284 lines
12 KiB
HTML
284 lines
12 KiB
HTML
{{ $artalkCss := resources.Get "css/libs/Artalk.css" | resources.Minify | resources.Fingerprint }}
|
||
<link id="artalk-css" rel="stylesheet" href="{{ $artalkCss.RelPermalink }}">
|
||
<style>
|
||
.atk-loading{
|
||
background: #FFFFFF;
|
||
}
|
||
.atk-loading-editor-box {
|
||
margin-bottom: 30px;
|
||
border: 1px solid var(--frame-color);
|
||
border-radius: 4px;
|
||
padding: 10px;
|
||
background: #fff;
|
||
}
|
||
[data-theme="dark"] .atk-loading-editor-box {
|
||
border-color: var(--frame-dark-color);
|
||
background: transparent;
|
||
}
|
||
/* ================= 评论徽章统一规格(单一来源) =================
|
||
几何沿用原设计:inline-block + margin-left:4px + padding 0 6px + 12px/500
|
||
+ line-height:18px + 1px 描边 + 3px 圆角。
|
||
artalk.js 会把所有徽章(博主/置顶/头衔/友链/友圈/官职)都挂上 .atk-cmt-badge
|
||
并拆掉 Artalk 的 .atk-badge-wrap 包装层,所以几何只在这里定义一次,
|
||
不会再出现两种 DOM / 两种尺寸。各类型只保留配色差异。
|
||
注:margin-right 清零是为了不叠加 Artalk 给"非最后一个徽章"的 6px 右间距
|
||
(否则 徽章→徽章 间距一宽一窄,看起来不齐)。 */
|
||
.atk-comment .atk-cmt-badge,
|
||
.atk-fl-card .atk-cmt-badge,
|
||
.atk-comment .atk-badge,
|
||
.atk-fl-card .atk-badge,
|
||
.atk-comment .atk-pinned-badge,
|
||
.atk-fl-card .atk-pinned-badge,
|
||
.atk-comment .atk-title-badge,
|
||
.atk-fl-card .atk-title-badge,
|
||
.atk-comment .atk-link-badge,
|
||
.atk-fl-card .atk-link-badge,
|
||
.atk-comment .atk-feed-badge,
|
||
.atk-fl-card .atk-feed-badge,
|
||
.atk-comment .atk-rank-badge,
|
||
.atk-fl-card .atk-rank-badge {
|
||
display: inline-block !important;
|
||
margin-left: 4px !important;
|
||
margin-right: 0 !important;
|
||
padding: 0 6px !important;
|
||
font-size: 12px !important;
|
||
font-weight: 600 !important;
|
||
border-radius: 3px !important;
|
||
line-height: 18px !important;
|
||
vertical-align: middle !important;
|
||
pointer-events: none;
|
||
white-space: nowrap;
|
||
}
|
||
.atk-link-badge {
|
||
color: #059669;
|
||
background: rgba(5,150,105,0.08);
|
||
border: 1px solid rgba(5,150,105,0.15);
|
||
}
|
||
.atk-feed-badge {
|
||
color: #d97706;
|
||
background: rgba(217,119,6,0.08);
|
||
border: 1px solid rgba(217,119,6,0.15);
|
||
}
|
||
.atk-rank-badge {
|
||
color: #6b7280;
|
||
background: rgba(107,114,128,0.08);
|
||
border: 1px solid rgba(107,114,128,0.15);
|
||
}
|
||
.atk-title-badge {
|
||
color: #b8860b;
|
||
background: rgba(184,134,11,0.08);
|
||
border: 1px solid rgba(184,134,11,0.22);
|
||
}
|
||
/* 博主(.atk-badge):实底 —— 白字 + 浅黑底
|
||
(!important 是为了盖掉 Artalk 写在元素上的内联背景色) */
|
||
.atk-comment .atk-badge,
|
||
.atk-fl-card .atk-badge {
|
||
border: 1px solid #333333;
|
||
color: #ffffff !important;
|
||
background: #333333 !important;
|
||
}
|
||
/* 置顶徽章:实底 —— 白字 + 浅红底(比 #d9534f / #ff6262 更浅,不刺眼) */
|
||
.atk-comment .atk-pinned-badge,
|
||
.atk-fl-card .atk-pinned-badge {
|
||
border: 1px solid #e05a5a;
|
||
color: #ffffff !important;
|
||
background: #e05a5a !important;
|
||
}
|
||
/* ---- 暗色模式配色 ---- */
|
||
[data-theme="dark"] .atk-link-badge {
|
||
color: #34d399;
|
||
background: rgba(52,211,153,0.12);
|
||
border-color: rgba(52,211,153,0.2);
|
||
}
|
||
[data-theme="dark"] .atk-feed-badge {
|
||
color: #fbbf24;
|
||
background: rgba(251,191,36,0.12);
|
||
border-color: rgba(251,191,36,0.2);
|
||
}
|
||
[data-theme="dark"] .atk-rank-badge {
|
||
color: rgb(148,163,184);
|
||
background: rgba(148,163,184,0.1);
|
||
border-color: rgba(148,163,184,0.18);
|
||
}
|
||
[data-theme="dark"] .atk-title-badge {
|
||
color: #fbbf24;
|
||
background: rgba(251,191,36,0.12);
|
||
border-color: rgba(251,191,36,0.2);
|
||
}
|
||
/* ================= 友链申请入口卡片 =================
|
||
PJAX 下每次切页评论区都会重建卡片 → 入场动画与打字机自然重播;
|
||
打字机定时器在 artalk.js 里会检测卡片是否还在文档中(切页后自动停)。 */
|
||
|
||
/* 被转换成卡片的评论:整条从列表里隐藏(卡片已移到列表上方单独显示,
|
||
用 class + !important 声明式隐藏,Artalk 重渲染也压得住) */
|
||
.atk-comment-wrap.atk-fl-apply {
|
||
display: none !important;
|
||
}
|
||
/* 卡片容器:固定在评论列表最上方 */
|
||
.atk-fl-card-wrap {
|
||
margin: 0 0 6px;
|
||
}
|
||
|
||
/* ---- 移动端:卡片更紧凑(隐藏头像 + 昵称行 + 收紧间距 + 微调字号) ----
|
||
窄屏把「头像 + 昵称(小赵) + 博主徽章」整行去掉:这三个元素在手机宽度下
|
||
会把打字机文案挤成一两行、按钮也跟着缩小,观感很差。去掉之后卡片只剩
|
||
「说明文案 + 申请友链按钮」,信息密度反而更合理。
|
||
昵称与徽章都在 .atk-fl-nick 这一行里(徽章是 artalk.js 克隆后 append 进去的),
|
||
所以按 .atk-fl-nick 隐藏即可;徽章再多写一条做兜底(Webkit 老版本对
|
||
父级 display:none 的继承没问题,这里只是防御式写法)。 */
|
||
@media (max-width: 640px) {
|
||
.atk-fl-card {
|
||
gap: 8px !important;
|
||
padding: 10px 12px !important;
|
||
}
|
||
.atk-fl-card .atk-fl-avatar {
|
||
display: none !important; /* 窄屏省掉头像,把宽度让给文字 */
|
||
}
|
||
.atk-fl-card .atk-fl-nick,
|
||
.atk-fl-card .atk-fl-nick .atk-badge,
|
||
.atk-fl-card .atk-fl-nick .atk-cmt-badge {
|
||
display: none !important; /* 昵称(小赵)+ 博主徽章:移动端不显示 */
|
||
}
|
||
.atk-fl-card .atk-fl-desc {
|
||
font-size: 12.5px !important;
|
||
letter-spacing: 0.2px !important;
|
||
margin-top: 0 !important; /* 昵称行没了,上间距一并去掉 */
|
||
}
|
||
.atk-fl-card .atk-fl-btn {
|
||
font-size: 12.5px !important;
|
||
padding: 5px 11px !important;
|
||
letter-spacing: 0.2px !important;
|
||
}
|
||
}
|
||
.atk-fl-card {
|
||
animation: flApplyIn .5s cubic-bezier(.22, 1, .36, 1) both;
|
||
}
|
||
@keyframes flApplyIn {
|
||
from { opacity: 0; transform: translateY(12px) scale(.98); }
|
||
to { opacity: 1; transform: none; }
|
||
}
|
||
.atk-fl-card:hover {
|
||
border-color: var(--theme-main-30, rgba(7,193,96,.3)) !important;
|
||
}
|
||
/* 打字机光标 */
|
||
.atk-fl-card .atk-fl-caret {
|
||
display: inline-block;
|
||
width: 1px;
|
||
height: 1em;
|
||
margin-left: 2px;
|
||
vertical-align: -2px;
|
||
background: currentColor;
|
||
animation: flCaret .9s steps(1, end) infinite;
|
||
}
|
||
@keyframes flCaret {
|
||
0%, 49% { opacity: 1; }
|
||
50%, 100% { opacity: 0; }
|
||
}
|
||
/* 申请友链按钮:轻微上浮 + 主题色投影 */
|
||
.atk-fl-card .atk-fl-btn {
|
||
transition: transform .2s ease, box-shadow .2s ease;
|
||
}
|
||
.atk-fl-card:hover .atk-fl-btn {
|
||
transform: translateY(-1px);
|
||
box-shadow: 0 4px 12px var(--theme-main-20, rgba(7,193,96,.2));
|
||
}
|
||
.atk-fl-card .atk-fl-btn:active {
|
||
transform: translateY(0);
|
||
}
|
||
/* 尊重系统"减弱动态效果":只关掉入场动画和按钮位移,
|
||
打字机与光标保留(打字机是文字渐显,不属于会造成不适的位移类动效) */
|
||
@media (prefers-reduced-motion: reduce) {
|
||
.atk-fl-card { animation: none; }
|
||
.atk-fl-card .atk-fl-btn { transition: none; }
|
||
.atk-fl-card:hover .atk-fl-btn { transform: none; box-shadow: none; }
|
||
}
|
||
.atk-main {
|
||
backface-visibility: hidden;
|
||
will-change: transform;
|
||
-webkit-font-smoothing: antialiased;
|
||
}
|
||
</style>
|
||
<!-- Artalk -->
|
||
<div id="Comments" uk-scrollspy="target: > *; cls: uk-animation-slide-bottom-small; delay: 100">
|
||
<!-- Loading Skeleton -->
|
||
<div class="atk-loading-placeholder">
|
||
<!-- Editor Skeleton -->
|
||
<div class="atk-loading-editor-box">
|
||
<div style="display: flex; gap: 10px; margin-bottom: 10px;">
|
||
<div class="atk-loading-line" style="width: 32%; height: 34px; border-radius: 4px; margin-bottom: 0;"></div>
|
||
<div class="atk-loading-line" style="width: 32%; height: 34px; border-radius: 4px; margin-bottom: 0;"></div>
|
||
<div class="atk-loading-line" style="width: 32%; height: 34px; border-radius: 4px; margin-bottom: 0;"></div>
|
||
</div>
|
||
<div class="atk-loading-line" style="width: 100%; height: 120px; border-radius: 4px; margin-bottom: 10px;"></div>
|
||
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||
<div class="atk-loading-line" style="width: 120px; height: 20px; margin-bottom: 0;"></div>
|
||
<div class="atk-loading-line" style="width: 80px; height: 34px; border-radius: 4px; margin-bottom: 0;"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Comment List Skeleton -->
|
||
<div class="atk-loading-item">
|
||
<div class="atk-loading-avatar"></div>
|
||
<div class="atk-loading-content">
|
||
<div class="atk-loading-line width-name"></div>
|
||
<div class="atk-loading-line width-80"></div>
|
||
<div class="atk-loading-line width-60"></div>
|
||
</div>
|
||
</div>
|
||
<div class="atk-loading-item">
|
||
<div class="atk-loading-avatar"></div>
|
||
<div class="atk-loading-content">
|
||
<div class="atk-loading-line width-name"></div>
|
||
<div class="atk-loading-line width-60"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<!-- JS -->
|
||
<!-- <script src="/js/Artalk.js"></script> -->
|
||
<!-- ── /api/v2/conf 内联快照 ─────────────────────────────────────────────
|
||
构建期由 .cnb.yml 拉一次 /api/v2/conf 落到 data/artalk_conf.json,这里原样内联。
|
||
artalk.js 对匿名 /conf 请求直接本地合成响应,省掉一次跨境 RTT ——
|
||
Artalk 内部 conf 与评论列表是「严格串行」的,去掉这一跳首屏即少一整个往返。
|
||
文件不存在时(本地预览 / 拉取失败)不输出,前端自动回退到远程请求。 -->
|
||
{{ with site.Data.artalk_conf }}
|
||
{{- $snapJson := . | jsonify }}
|
||
{{- $snapJson = replace $snapJson "</" "<\\/" }}
|
||
<script>window.__artalkConfSnapshot = {{ $snapJson | safeJS }};</script>
|
||
{{ end }}
|
||
<script>
|
||
// Configuration for Artalk and AI Comment
|
||
window.artalkConfig = {
|
||
el: '#Comments',
|
||
server: '{{ .Site.Params.artalk.server }}',
|
||
site: '{{ .Site.Params.artalk.site }}',
|
||
pageKey: '{{ .RelPermalink }}',
|
||
{{/* pageTitle 必须显式传:Artalk 不传时会读 document.title,而 PJAX 切页
|
||
早期脚本执行时 document.title 还是上一个页面的(经典竞态),历史上
|
||
数据库里一堆文章页的标题被污染成「留言」就是这么来的。head.html 的
|
||
<title> 就是纯 .Title,这里与它保持一致。
|
||
|
||
★ 必须 safeJS:这段在 <script> 里,Go 模板按 JS 上下文再转义一层,
|
||
`jsonify` 的结果会被二次编码 —— 标题 留言 会变成 "\"留言\"",
|
||
评论入库/邮件标题就带上了一对引号(《"留言"》)。safeJS 明确告诉
|
||
模板「这就是最终 JS 字面量」,不再二次转义;`</` 仍按惯例转义兜底。 */}}
|
||
{{ $t := .Title | jsonify }}
|
||
pageTitle: {{ replace $t "</" "<\\/" | safeJS }},
|
||
emoticons: '{{ .Site.Params.artalk.emoticons | absURL }}',
|
||
aiComment: null
|
||
};
|
||
|
||
// Execute Initialization immediately if function exists (PJAX re-execution),
|
||
// otherwise wait for DOMContentLoaded (Initial load)
|
||
(function() {
|
||
if (typeof window.initArtalk === 'function') {
|
||
window.initArtalk();
|
||
} else {
|
||
document.addEventListener("DOMContentLoaded", function() {
|
||
if (typeof window.initArtalk === 'function') {
|
||
window.initArtalk();
|
||
}
|
||
});
|
||
}
|
||
})();
|
||
</script> |