优化: - initArtalk 加节点级幂等守卫(mount.__atkInited,失败回滚), 消除 artalk.html/mypjax 多入口导致的 4× comments + 4× pv 重复请求 - fetch 包装层对无 body 的 GET/HEAD 去掉多余 content-type, 消掉 Artalk 对 GET /comments 的 CORS 预检(按完整 URL 缓存, 每篇文章都重付一次 RTT)。实测评论列表 +1150~1565ms → +626ms 修复: - 评论列表骨架屏改注入 .atk-list-body(原骨架在 #Comments 内, 被 Artalk.init 清空),list-loaded/故障/12s 兜底移除 - 友链申请弹窗抽成 modules/friendlink.js 进 page-only bundle (原内联脚本在 #pjax-container 外,pjax 后 flApplyOpenModal 未定义) - toast 统一走本地化 Message.js/Qmsg 适配层(保留 window.Toast/showToast 旧 API) 文档: - README / 架构总览 更新;新增 评论加载优化方案.md
192 lines
8.1 KiB
HTML
192 lines
8.1 KiB
HTML
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||
<title>{{ .Title }}</title>
|
||
<!-- SEO & Meta Tags -->
|
||
<meta name="description" content="{{ if .IsHome }}{{ .Site.Params.description }}{{ else }}{{ .Description | default .Summary | plainify | truncate 160 }}{{ end }}" />
|
||
<link rel="canonical" href="{{ .RelPermalink | absURL }}" />
|
||
|
||
{{- $keywords := slice "大学" "教程" "blog" "优世界" "优设计" "博客" "大学生博客" "生活" "文案" -}}
|
||
{{- if .Params.tags -}}
|
||
{{- $keywords = .Params.tags -}}
|
||
{{- end -}}
|
||
{{- if .Params.keywords -}}
|
||
{{- $keywords = .Params.keywords -}}
|
||
{{- end -}}
|
||
<meta name="keywords" content="{{ delimit $keywords "," }}" />
|
||
|
||
<!-- Open Graph -->
|
||
<meta property="og:title" content="{{ .Title }}" />
|
||
<meta property="og:description" content="{{ if .IsHome }}{{ .Site.Params.description }}{{ else }}{{ .Description | default .Summary | plainify | truncate 160 }}{{ end }}" />
|
||
<meta property="og:type" content="{{ if .IsPage }}article{{ else }}website{{ end }}" />
|
||
<meta property="og:url" content="{{ .Permalink }}" />
|
||
<meta property="og:site_name" content="{{ .Site.Title }}" />
|
||
<meta name="nilaila-verify" content="97dd49b8e6e941828e647b11bea73891">
|
||
|
||
{{- $image := .Site.Params.authorImage -}}
|
||
{{- if .Params.cover -}}
|
||
{{- $image = .Params.cover -}}
|
||
{{- end -}}
|
||
{{- if $image -}}
|
||
<meta property="og:image" content="{{ $image | absURL }}" />
|
||
<meta name="twitter:image" content="{{ $image | absURL }}" />
|
||
{{- end -}}
|
||
|
||
<!-- Twitter Card -->
|
||
<meta name="twitter:card" content="summary" />
|
||
<meta name="twitter:title" content="{{ .Title }}" />
|
||
<meta name="twitter:description" content="{{ if .IsHome }}{{ .Site.Params.description }}{{ else }}{{ .Description | default .Summary | plainify | truncate 160 }}{{ end }}" />
|
||
|
||
<!-- RSS Feed -->
|
||
<link rel="alternate" type="application/rss+xml" title="{{ site.Title }}" href="/rss.xml">
|
||
<!-- Favicon -->
|
||
<link rel="icon" type="image/x-icon" href="/favicon.ico">
|
||
<!-- Resource Hints -->
|
||
<link rel="preconnect" href="https://cravatar.cn">
|
||
{{- /* 评论 / RSS API:Artalk 与友链数据都发 CORS 跨域请求。
|
||
preconnect 必须带 crossorigin —— 浏览器对「带凭据」和「匿名」两条连接分池,
|
||
不带 crossorigin 预热的连接 CORS 请求无法复用,等于白预连(net::ERR 之外
|
||
还要重付一次 DNS + TCP + TLS)。dns-prefetch 兜底不支持 preconnect 的浏览器。 */ -}}
|
||
{{- $apiHosts := slice -}}
|
||
{{- with site.Params.artalk.server }}{{ $apiHosts = $apiHosts | append (strings.TrimSuffix "/" .) }}{{ end -}}
|
||
{{- with site.Params.rssapi.base }}{{ $apiHosts = $apiHosts | append (strings.TrimSuffix "/" .) }}{{ end -}}
|
||
{{- range ($apiHosts | uniq) }}
|
||
<link rel="preconnect" href="{{ . }}" crossorigin>
|
||
<link rel="dns-prefetch" href="{{ . }}">
|
||
{{- end }}
|
||
{{ with site.Params.rssapi.base }}<script>window.rssApiBase = "{{ . }}";</script>{{ end }}
|
||
<link rel="dns-prefetch" href="https://bsz.dusays.com:9001">
|
||
<!-- Analytics -->
|
||
<script async src="https://019e3dec-3312-7873-862a-3f56ac99ea83.spst2.com/ustat.js"></script>
|
||
<script>
|
||
(function () {
|
||
try {
|
||
var theme = localStorage.getItem('theme');
|
||
if (!theme) {
|
||
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
||
theme = 'dark';
|
||
} else {
|
||
theme = 'light';
|
||
}
|
||
}
|
||
document.documentElement.setAttribute('data-theme', theme);
|
||
|
||
// Listen for system changes
|
||
if (window.matchMedia) {
|
||
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', event => {
|
||
// Only switch if user hasn't manually set a preference
|
||
// Or maybe we should respect system change if user hasn't explicitly locked it?
|
||
// Common behavior: manual override sticks.
|
||
if (!localStorage.getItem('theme')) {
|
||
var newTheme = event.matches ? "dark" : "light";
|
||
document.documentElement.setAttribute('data-theme', newTheme);
|
||
}
|
||
});
|
||
}
|
||
} catch (e) {}
|
||
})();
|
||
</script>
|
||
<!-- Preload Fonts -->
|
||
<link rel="preload" href="{{ "font/zql-v2.woff2" | relURL }}" as="font" type="font/woff2" crossorigin>
|
||
<!-- remixicon 不预加载,避免与 CSS 中带 query 参数的 URL 不一致导致警告 -->
|
||
|
||
{{ $echartsHead := resources.Get "js/libs/echarts.min.js" | resources.Fingerprint }}
|
||
{{ $artalkHead := resources.Get "js/libs/Artalk.js" | resources.Fingerprint }}
|
||
|
||
<!-- Site Configuration -->
|
||
<script>
|
||
window.optionalAssetUrls = {
|
||
echarts: {{ $echartsHead.RelPermalink | jsonify | safeJS }},
|
||
artalk: {{ $artalkHead.RelPermalink | jsonify | safeJS }}
|
||
};
|
||
|
||
window.loadScriptOnce = window.loadScriptOnce || function(src, globalName, callback) {
|
||
if (globalName && window[globalName]) {
|
||
callback();
|
||
return;
|
||
}
|
||
var existing = document.querySelector('script[data-dynamic-src="' + src + '"]');
|
||
if (existing) {
|
||
existing.addEventListener('load', callback, { once: true });
|
||
return;
|
||
}
|
||
var script = document.createElement('script');
|
||
script.src = src;
|
||
script.defer = true;
|
||
script.dataset.dynamicSrc = src;
|
||
script.onload = callback;
|
||
document.head.appendChild(script);
|
||
};
|
||
|
||
window.siteConfig = {
|
||
artalk: {
|
||
server: "{{ .Site.Params.artalk.server }}",
|
||
site: "{{ .Site.Params.artalk.site }}",
|
||
emoticons: "{{ .Site.Params.artalk.emoticons | absURL }}",
|
||
gravatar: "{{ .Site.Params.artalk.gravatar }}"
|
||
},
|
||
author: {
|
||
avatar: "{{ .Site.Params.author.avatar }}",
|
||
description: "{{ .Site.Params.author.description }}"
|
||
},
|
||
barrage: {
|
||
enableHitokoto: {{ .Site.Params.barrage.enableHitokoto | default true }},
|
||
hitokotoUrl: "{{ .Site.Params.barrage.hitokotoUrl }}"
|
||
}
|
||
};
|
||
</script>
|
||
|
||
<!-- Stylesheets -->
|
||
{{ $normalize := resources.Get "css/libs/normalize.css" }}
|
||
{{ $remixicon := resources.Get "css/libs/remixicon.css" }}
|
||
{{ $uikit := resources.Get "css/libs/uikit.min.css" }}
|
||
{{ $nprogress := resources.Get "css/libs/nprogress.min.css" }}
|
||
{{ $message := resources.Get "css/libs/message.min.css" }}
|
||
{{ $fontSlices := resources.Get "css/font-slices.css" }}
|
||
{{ $main := resources.Get "css/main.css" }}
|
||
{{ $shortcodes := resources.Get "css/shortcodes.css" }}
|
||
|
||
{{ $style := slice $normalize $remixicon $uikit $nprogress $message $fontSlices $main $shortcodes | resources.Concat "css/style.css" | resources.Minify | resources.Fingerprint }}
|
||
<link rel="stylesheet" href="{{ $style.RelPermalink }}">
|
||
|
||
{{- if and .Site.Params.style.enableBackgroundImage .Site.Params.style.backgroundImageUrl -}}
|
||
<style>
|
||
body {
|
||
background-image: url('{{ .Site.Params.style.backgroundImageUrl }}') !important;
|
||
background-size: {{ .Site.Params.style.backgroundSize | default "cover" }} !important;
|
||
background-repeat: {{ .Site.Params.style.backgroundRepeat | default "no-repeat" }} !important;
|
||
background-position: {{ .Site.Params.style.backgroundPosition | default "center" }} !important;
|
||
background-attachment: {{ .Site.Params.style.backgroundAttachment | default "fixed" }} !important;
|
||
}
|
||
[data-theme="dark"] body {
|
||
background-image: url(/image/background/deno.webp) !important;
|
||
background-size: contain !important;
|
||
background-repeat: round !important;
|
||
background-position: center !important;
|
||
background-attachment: fixed !important;
|
||
}
|
||
[data-theme="dark"] body::before {
|
||
content: '' !important;
|
||
position: fixed !important;
|
||
top: 0 !important;
|
||
left: 0 !important;
|
||
width: 100% !important;
|
||
height: 100% !important;
|
||
background-color: rgba(0, 0, 0, 0.4) !important;
|
||
pointer-events: none !important;
|
||
z-index: -1 !important;
|
||
}
|
||
@media (max-width: 768px) {
|
||
body {
|
||
background-attachment: scroll !important;
|
||
}
|
||
}
|
||
</style>
|
||
{{- end -}}
|
||
|
||
<!-- <script src="/js/jquery.min.js"></script> -->
|
||
|
||
|
||
<script src="/js/instantpage.min.js" type="module" defer></script>
|
||
<meta name="template" content="Amigo" />
|
||
</head>
|