前言#
- 感觉 Hexo 编译太慢了,但是又不想用 WordPress 这种,还是想找个静态博客的框架,于是就选择了 Hugo,找了个看起来还可以的主题 Blowfish
参考魔改#
主题色#
- 参考上述文章进行了稍微调整,直接在
assets/css/custom.css中添加如下代码覆盖默认样式1:root { 2 --color-neutral: 255,255,255; 3 --color-neutral-50: 250,250,249; 4 --color-neutral-100: 245,245,244; 5 --color-neutral-200: 231,229,228; 6 --color-neutral-300: 214,211,209; 7 --color-neutral-400: 168,162,158; 8 --color-neutral-500: 120,113,108; 9 --color-neutral-600: 87,83,78; 10 --color-neutral-700: 68,64,60; 11 --color-neutral-800: 41,37,36; 12 --color-neutral-900: 28,25,23; 13 14 --color-primary-50: 255,255,255; 15 --color-primary-100: 255,255,255; 16 --color-primary-200: 255,255,255; 17 --color-primary-300: 242,211,223; 18 --color-primary-400: 225,151,181; 19 --color-primary-500: 208,92,138; 20 --color-primary-600: 199,60,115; 21 --color-primary-700: 170,49,97; 22 --color-primary-800: 138,40,79; 23 --color-primary-900: 106,31,61; 24 25 --color-secondary-50: 255,255,255; 26 --color-secondary-100: 255,255,255; 27 --color-secondary-200: 255,255,255; 28 --color-secondary-300: 255,242,219; 29 --color-secondary-400: 255,215,143; 30 --color-secondary-500: 255,188,66; 31 --color-secondary-600: 255,174,25; 32 --color-secondary-700: 239,155,0; 33 --color-secondary-800: 199,128,0; 34 --color-secondary-900: 158,102,0; 35}
标题鼠标悬停#
- 直接参考上述文章的做法,在
assets/css/custom.css中添加如下代码1/* 标题悬停时的颜色变化 */ 2nav a[href="/"]:hover { 3 color: rgb(var(--color-primary-600)); 4} 5/* 夜间模式下标题悬停时的颜色 */ 6.dark nav a[href="/"]:hover { 7 color: rgb(var(--color-primary-400)); 8}
文章目录添加悬停样式#
- 在
assets/css/custom.css中添加如下代码1#TableOfContents a { 2 position: relative; 3 padding-left: 0.75em; 4 display: block; 5 transition: color 0.3s ease; 6} 7 8#TableOfContents a::before { 9 content: ''; 10 position: absolute; 11 left: 0; 12 top: 0.2em; 13 bottom: 0.2em; 14 width: 4px; 15 background-color: rgb(var(--color-primary-500)); 16 transform: scaleY(0); 17 transition: transform 0.3s ease; 18 border-radius: 2px; 19} 20 21#TableOfContents a:hover::before { 22 transform: scaleY(1); 23}
顶部阅读进度条#
css 样式代码
1/* 顶部阅读进度条 */ 2.top-scroll-bar { 3 position: fixed; 4 top: 0; 5 left: 0; 6 z-index: 9999; 7 display: none; 8 width: 0; 9 height: 3px; 10 background: rgb(var(--color-primary-400)); 11 }真正发挥作用的代码,新建一个
layouts/partials/extend-footer.html1<!-- 进度条逻辑 --> 2<script> 3 window.addEventListener('scroll', () => { 4 const bar = document.querySelector('.top-scroll-bar'); 5 if (!bar) return; 6 const scrollTop = document.documentElement.scrollTop || document.body.scrollTop; 7 const scrollHeight = document.documentElement.scrollHeight - document.documentElement.clientHeight; 8 const width = (scrollTop / scrollHeight) * 100; 9 bar.style.width = width + '%'; 10 bar.style.display = 'block'; 11 }); 12</script>基于 Blowfish 主题提供的拓展选项,新建一个
layouts/partials/extend-head.html1<!-- 进度条 --> 2<div class="top-scroll-bar"></div>
文章密码功能#
把
themes/blowfish/layouts/_default/single.html复制到layouts/_default/single.html,然后在对应位置添加如下代码1{{ define "main" }} 2 {{ .Scratch.Set "scope" "single" }} 3 <article> 4 {{/* Hero */}} 5 {{ if .Params.showHero | default (site.Params.article.showHero | default false) }} 6 {{ $heroStyle := .Params.heroStyle }} 7 {{ if not $heroStyle }}{{ $heroStyle = site.Params.article.heroStyle }}{{ end }} 8 {{ $heroStyle := print "hero/" $heroStyle ".html" }} 9 {{ if templates.Exists ( printf "partials/%s" $heroStyle ) }} 10 {{ partial $heroStyle . }} 11 {{ else }} 12 {{ partial "hero/basic.html" . }} 13 {{ end }} 14 {{ end }} 15 <!-- 文章密码功能 --> 16 {{ if ( .Params.password | default "" ) }} 17 <script> 18 (function(){ 19 if (prompt('请输入文章密码') != "{{ .Params.password }}"){ 20 alert('密码错误!'); 21 if (history.length === 1) { 22 window.opener = null; 23 window.open('', '_self'); 24 window.close(); 25 } else { 26 history.back(); 27 } 28 } 29 })(); 30 </script> 31 {{ end }} 32 <!-- 文章密码功能结束 -->需要加密码的文章只需要在 Front Matter 中添加
password: xxx字段即可
短代码:防剧透的文本高斯模糊样式#
在
layouts/shortcodes新建blur.html1<span class="blur">{{.Inner | markdownify}}</span> 2 3<style> 4 /* 文本高斯模糊 */ 5 .blur { 6 filter: blur(4px); 7 transition: filter 0.3s ease; 8 } 9 .blur:hover { 10 filter: blur(0); 11 } 12</style>使用方法
1{{< blur >}}想要高斯模糊的文本{{</ blur >}}不受任何主题限制,鼠标悬停或屏幕点击后文字正常显示,且内文本支持 markdown 格式。
友链卡片#
修改了一下卡片背景和 hover 效果,支持分组和对应描述字段(组内乱序)
新建一个
layouts/_default/links.html,其中头像获取失败默认图片的 placeholder 为img/default.jpg,记得替换1{{ define "main" }} 2{{ .Scratch.Set "scope" "single" }} 3<article> 4 {{ if .Params.showHero | default (.Site.Params.article.showHero | default false) }} 5 {{ $heroStyle := .Params.heroStyle }} 6 {{ if not $heroStyle }}{{ $heroStyle = .Site.Params.article.heroStyle }}{{ end }} 7 {{ $heroStyle := print "hero/" $heroStyle ".html" }} 8 {{ if templates.Exists ( printf "partials/%s" $heroStyle ) }} 9 {{ partial $heroStyle . }} 10 {{ else }} 11 {{ partial "hero/basic.html" . }} 12 {{ end }} 13 {{ end }} 14 15 <header id="single_header" class="mt-5 max-w-prose"> 16 {{ if .Params.showBreadcrumbs | default (.Site.Params.article.showBreadcrumbs | default false) }} 17 {{ partial "breadcrumbs.html" . }} 18 {{ end }} 19 <h1 class="mt-0 text-4xl font-extrabold text-neutral-900 dark:text-neutral"> 20 {{ .Title }} 21 </h1> 22 23 <div class="mt-1 mb-6 text-base text-neutral-500 dark:text-neutral-400 print:hidden"> 24 {{ partial "article-meta/basic.html" (dict "context" . "scope" "single") }} 25 </div> 26 27 </header> 28 29 <section class="flex flex-col max-w-full mt-0 prose dark:prose-invert dark:marker:text-neutral-400 lg:flex-row"> 30 31 <div class="min-w-0 min-h-0 max-w-fit"> 32 33 <div class="article-content max-w-full mb-20 break-words"> 34 35 {{ .Content }} 36 37 <!-- 添加 not-prose 类来避免 prose 样式的影响 --> 38 <div class="not-prose"> 39 {{ $dataFile := .Params.linksFile }} 40 {{ if not $dataFile }} 41 {{ errorf "linksFile param is missing in front matter for page %s" .File.Path }} 42 {{ end }} 43 {{ $linksResource := resources.Get $dataFile }} 44 {{ if $linksResource }} 45 {{ $data := $linksResource | transform.Unmarshal }} 46 47 {{/* Determine if we are dealing with a list of categories or a single object with a 'links' key */}} 48 {{ $categories := slice }} 49 50 {{ if reflect.IsSlice $data }} 51 {{ $categories = $data }} 52 {{ else if reflect.IsMap $data }} 53 {{ if isset $data "links" }} 54 {{/* Convert old format to new format structure for consistent rendering */}} 55 {{ $categories = slice (dict "link_list" $data.links) }} 56 {{ end }} 57 {{ end }} 58 59 {{ range $categories }} 60 {{ if .class_name }} 61 <h2 class="mt-8 mb-4 text-2xl font-bold text-neutral-900 dark:text-neutral-100 flex items-center"> 62 {{ if .class_icon }}<i class="{{ .class_icon }} mr-2"></i>{{ end }} 63 {{ .class_name }} 64 </h2> 65 {{ end }} 66 {{ if .class_desc }} 67 <p class="mb-4 text-neutral-600 dark:text-neutral-400">{{ .class_desc }}</p> 68 {{ else }} 69 <p class="mb-4 text-neutral-600 dark:text-neutral-400">{{ " " | safeHTML }}</p> 70 {{ end }} 71 72 <div class="friends-links grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4"> 73 {{ range .link_list }} 74 {{ $name := .name }} 75 {{ $bio := .bio | default .descr }} 76 {{ $url := .url | default .link }} 77 {{ $avatar := .avatar }} 78 79 <a target="_blank" href="{{ $url }}" title="{{ $name }}" class="block h-full" rel="noopener noreferrer"> 80 <div class="friend-card group flex items-center p-4 bg-white dark:bg-neutral-800 rounded-xl shadow-sm border border-neutral-200 dark:border-neutral-700 h-full"> 81 <div class="flex-shrink-0 mr-4"> 82 <div class="friend-avatar-wrapper"> 83 <img class="friend-avatar lazy nozoom w-16 h-16 object-cover rounded-full block" loading="lazy" 84 src="{{ $avatar }}" 85 alt="{{ $name }}" 86 onerror="this.onerror=null; this.src='img/default.jpg';" 87 style="width: 64px; height: 64px;"> 88 </div> 89 </div> 90 <span class="inline-block w-4 h-4 mr-3"></span> 91 <div class="flex-grow min-w-0"> 92 <div class="flex items-center mb-2"> 93 <span class="font-medium text-neutral-900 dark:text-neutral-100 truncate" style="strong">{{ $name }}</span> 94 </div> 95 <p class="text-sm text-neutral-700 dark:text-neutral-300 line-clamp-2 m-0">{{ $bio }}</p> 96 </div> 97 </div> 98 </a> 99 {{ end }} 100 </div> 101 {{ end }} 102 {{ else }} 103 {{ errorf "Could not find data file: %s" $dataFile }} 104 {{ end }} 105 </div> 106 107 </div> 108 109 </div> 110 111 </section> 112 113</article> 114 115{{ $lazyloadJS := resources.Get "js/lazyload.iife.min.js" | fingerprint "sha256" }} 116<script type="text/javascript" src="{{ $lazyloadJS.RelPermalink }}" integrity="{{ $lazyloadJS.Data.Integrity }}"></script> 117 118<script> 119 // Random links 120 function shuffleArray(array) { 121 for (let i = array.length - 1; i > 0; i--) { 122 const j = Math.floor(Math.random() * (i + 1)); 123 [array[i], array[j]] = [array[j], array[i]]; 124 } 125 return array; 126 } 127 128 function randomizeLinks() { 129 const linkContainers = document.querySelectorAll('.friends-links'); 130 131 linkContainers.forEach(container => { 132 const links = Array.from(container.querySelectorAll('a')); 133 const shuffledLinks = shuffleArray(links); 134 135 links.forEach(link => link.remove()); 136 137 shuffledLinks.forEach(link => container.appendChild(link)); 138 }); 139 140 // Equalize heights after shuffling 141 setTimeout(equalizeHeights, 0); 142 } 143 144 function equalizeHeights() { 145 const cards = document.querySelectorAll('.friend-card'); 146 let maxHeight = 0; 147 148 // Reset height to auto to get natural height 149 cards.forEach(card => { 150 card.style.height = 'auto'; 151 }); 152 153 // Find max height 154 cards.forEach(card => { 155 if (card.offsetHeight > maxHeight) { 156 maxHeight = card.offsetHeight; 157 } 158 }); 159 160 // Apply max height to all cards 161 cards.forEach(card => { 162 card.style.height = maxHeight + 'px'; 163 }); 164 } 165 166 randomizeLinks(); 167 168 // Re-calculate on window resize 169 window.addEventListener('resize', equalizeHeights); 170</script> 171 172<script> 173 var lazyLoadInstance = new LazyLoad({ 174 // Your custom settings go here 175 }); 176</script> 177{{ end }}新建一个图片懒加载的 js 代码
assets/js/lazyload.iife.min.js1var LazyLoad = (function () { 2 "use strict"; 3 const e = "undefined" != typeof window, 4 t = 5 (e && !("onscroll" in window)) || 6 ("undefined" != typeof navigator && 7 /(gle|ing|ro)bot|crawl|spider/i.test(navigator.userAgent)), 8 a = e && window.devicePixelRatio > 1, 9 s = { 10 elements_selector: ".lazy", 11 container: t || e ? document : null, 12 threshold: 300, 13 thresholds: null, 14 data_src: "src", 15 data_srcset: "srcset", 16 data_sizes: "sizes", 17 data_bg: "bg", 18 data_bg_hidpi: "bg-hidpi", 19 data_bg_multi: "bg-multi", 20 data_bg_multi_hidpi: "bg-multi-hidpi", 21 data_bg_set: "bg-set", 22 data_poster: "poster", 23 class_applied: "applied", 24 class_loading: "loading", 25 class_loaded: "loaded", 26 class_error: "error", 27 class_entered: "entered", 28 class_exited: "exited", 29 unobserve_completed: !0, 30 unobserve_entered: !1, 31 cancel_on_exit: !0, 32 callback_enter: null, 33 callback_exit: null, 34 callback_applied: null, 35 callback_loading: null, 36 callback_loaded: null, 37 callback_error: null, 38 callback_finish: null, 39 callback_cancel: null, 40 use_native: !1, 41 restore_on_error: !1, 42 }, 43 n = (e) => Object.assign({}, s, e), 44 l = function (e, t) { 45 let a; 46 const s = "LazyLoad::Initialized", 47 n = new e(t); 48 try { 49 a = new CustomEvent(s, { detail: { instance: n } }); 50 } catch (e) { 51 (a = document.createEvent("CustomEvent")), 52 a.initCustomEvent(s, !1, !1, { instance: n }); 53 } 54 window.dispatchEvent(a); 55 }, 56 o = "src", 57 r = "srcset", 58 i = "sizes", 59 c = "poster", 60 d = "llOriginalAttrs", 61 _ = "data", 62 u = "loading", 63 g = "loaded", 64 b = "applied", 65 h = "error", 66 m = "native", 67 p = "data-", 68 v = "ll-status", 69 f = (e, t) => e.getAttribute(p + t), 70 E = (e) => f(e, v), 71 I = (e, t) => 72 ((e, t, a) => { 73 const s = p + t; 74 null !== a ? e.setAttribute(s, a) : e.removeAttribute(s); 75 })(e, v, t), 76 k = (e) => I(e, null), 77 A = (e) => null === E(e), 78 L = (e) => E(e) === m, 79 y = [u, g, b, h], 80 w = (e, t, a, s) => { 81 e && 82 "function" == typeof e && 83 (void 0 === s ? (void 0 === a ? e(t) : e(t, a)) : e(t, a, s)); 84 }, 85 C = (t, a) => { 86 e && "" !== a && t.classList.add(a); 87 }, 88 O = (t, a) => { 89 e && "" !== a && t.classList.remove(a); 90 }, 91 x = (e) => e.llTempImage, 92 M = (e, t) => { 93 if (!t) return; 94 const a = t._observer; 95 a && a.unobserve(e); 96 }, 97 z = (e, t) => { 98 e && (e.loadingCount += t); 99 }, 100 N = (e, t) => { 101 e && (e.toLoadCount = t); 102 }, 103 R = (e) => { 104 let t = []; 105 for (let a, s = 0; (a = e.children[s]); s += 1) 106 "SOURCE" === a.tagName && t.push(a); 107 return t; 108 }, 109 T = (e, t) => { 110 const a = e.parentNode; 111 a && "PICTURE" === a.tagName && R(a).forEach(t); 112 }, 113 G = (e, t) => { 114 R(e).forEach(t); 115 }, 116 D = [o], 117 H = [o, c], 118 V = [o, r, i], 119 F = [_], 120 B = (e) => !!e[d], 121 J = (e) => e[d], 122 S = (e) => delete e[d], 123 j = (e, t) => { 124 if (B(e)) return; 125 const a = {}; 126 t.forEach((t) => { 127 a[t] = e.getAttribute(t); 128 }), 129 (e[d] = a); 130 }, 131 P = (e, t) => { 132 if (!B(e)) return; 133 const a = J(e); 134 t.forEach((t) => { 135 ((e, t, a) => { 136 a ? e.setAttribute(t, a) : e.removeAttribute(t); 137 })(e, t, a[t]); 138 }); 139 }, 140 U = (e, t, a) => { 141 C(e, t.class_applied), 142 I(e, b), 143 a && (t.unobserve_completed && M(e, t), w(t.callback_applied, e, a)); 144 }, 145 $ = (e, t, a) => { 146 C(e, t.class_loading), 147 I(e, u), 148 a && (z(a, 1), w(t.callback_loading, e, a)); 149 }, 150 q = (e, t, a) => { 151 a && e.setAttribute(t, a); 152 }, 153 K = (e, t) => { 154 q(e, i, f(e, t.data_sizes)), 155 q(e, r, f(e, t.data_srcset)), 156 q(e, o, f(e, t.data_src)); 157 }, 158 Q = { 159 IMG: (e, t) => { 160 T(e, (e) => { 161 j(e, V), K(e, t); 162 }), 163 j(e, V), 164 K(e, t); 165 }, 166 IFRAME: (e, t) => { 167 j(e, D), q(e, o, f(e, t.data_src)); 168 }, 169 VIDEO: (e, t) => { 170 G(e, (e) => { 171 j(e, D), q(e, o, f(e, t.data_src)); 172 }), 173 j(e, H), 174 q(e, c, f(e, t.data_poster)), 175 q(e, o, f(e, t.data_src)), 176 e.load(); 177 }, 178 OBJECT: (e, t) => { 179 j(e, F), q(e, _, f(e, t.data_src)); 180 }, 181 }, 182 W = ["IMG", "IFRAME", "VIDEO", "OBJECT"], 183 X = (e, t) => { 184 !t || 185 ((e) => e.loadingCount > 0)(t) || 186 ((e) => e.toLoadCount > 0)(t) || 187 w(e.callback_finish, t); 188 }, 189 Y = (e, t, a) => { 190 e.addEventListener(t, a), (e.llEvLisnrs[t] = a); 191 }, 192 Z = (e, t, a) => { 193 e.removeEventListener(t, a); 194 }, 195 ee = (e) => !!e.llEvLisnrs, 196 te = (e) => { 197 if (!ee(e)) return; 198 const t = e.llEvLisnrs; 199 for (let a in t) { 200 const s = t[a]; 201 Z(e, a, s); 202 } 203 delete e.llEvLisnrs; 204 }, 205 ae = (e, t, a) => { 206 ((e) => { 207 delete e.llTempImage; 208 })(e), 209 z(a, -1), 210 ((e) => { 211 e && (e.toLoadCount -= 1); 212 })(a), 213 O(e, t.class_loading), 214 t.unobserve_completed && M(e, a); 215 }, 216 se = (e, t, a) => { 217 const s = x(e) || e; 218 ee(s) || 219 ((e, t, a) => { 220 ee(e) || (e.llEvLisnrs = {}); 221 const s = "VIDEO" === e.tagName ? "loadeddata" : "load"; 222 Y(e, s, t), Y(e, "error", a); 223 })( 224 s, 225 (n) => { 226 ((e, t, a, s) => { 227 const n = L(t); 228 ae(t, a, s), 229 C(t, a.class_loaded), 230 I(t, g), 231 w(a.callback_loaded, t, s), 232 n || X(a, s); 233 })(0, e, t, a), 234 te(s); 235 }, 236 (n) => { 237 ((e, t, a, s) => { 238 const n = L(t); 239 ae(t, a, s), 240 C(t, a.class_error), 241 I(t, h), 242 w(a.callback_error, t, s), 243 a.restore_on_error && P(t, V), 244 n || X(a, s); 245 })(0, e, t, a), 246 te(s); 247 } 248 ); 249 }, 250 ne = (e, t, s) => { 251 ((e) => W.indexOf(e.tagName) > -1)(e) 252 ? ((e, t, a) => { 253 se(e, t, a), 254 ((e, t, a) => { 255 const s = Q[e.tagName]; 256 s && (s(e, t), $(e, t, a)); 257 })(e, t, a); 258 })(e, t, s) 259 : ((e, t, s) => { 260 ((e) => { 261 e.llTempImage = document.createElement("IMG"); 262 })(e), 263 se(e, t, s), 264 ((e) => { 265 B(e) || (e[d] = { backgroundImage: e.style.backgroundImage }); 266 })(e), 267 ((e, t, s) => { 268 const n = f(e, t.data_bg), 269 l = f(e, t.data_bg_hidpi), 270 r = a && l ? l : n; 271 r && 272 ((e.style.backgroundImage = `url("${r}")`), 273 x(e).setAttribute(o, r), 274 $(e, t, s)); 275 })(e, t, s), 276 ((e, t, s) => { 277 const n = f(e, t.data_bg_multi), 278 l = f(e, t.data_bg_multi_hidpi), 279 o = a && l ? l : n; 280 o && ((e.style.backgroundImage = o), U(e, t, s)); 281 })(e, t, s), 282 ((e, t, a) => { 283 const s = f(e, t.data_bg_set); 284 if (!s) return; 285 let n = s.split("|").map((e) => `image-set(${e})`); 286 (e.style.backgroundImage = n.join()), U(e, t, a); 287 })(e, t, s); 288 })(e, t, s); 289 }, 290 le = (e) => { 291 e.removeAttribute(o), e.removeAttribute(r), e.removeAttribute(i); 292 }, 293 oe = (e) => { 294 T(e, (e) => { 295 P(e, V); 296 }), 297 P(e, V); 298 }, 299 re = { 300 IMG: oe, 301 IFRAME: (e) => { 302 P(e, D); 303 }, 304 VIDEO: (e) => { 305 G(e, (e) => { 306 P(e, D); 307 }), 308 P(e, H), 309 e.load(); 310 }, 311 OBJECT: (e) => { 312 P(e, F); 313 }, 314 }, 315 ie = (e, t) => { 316 ((e) => { 317 const t = re[e.tagName]; 318 t 319 ? t(e) 320 : ((e) => { 321 if (!B(e)) return; 322 const t = J(e); 323 e.style.backgroundImage = t.backgroundImage; 324 })(e); 325 })(e), 326 ((e, t) => { 327 A(e) || 328 L(e) || 329 (O(e, t.class_entered), 330 O(e, t.class_exited), 331 O(e, t.class_applied), 332 O(e, t.class_loading), 333 O(e, t.class_loaded), 334 O(e, t.class_error)); 335 })(e, t), 336 k(e), 337 S(e); 338 }, 339 ce = ["IMG", "IFRAME", "VIDEO"], 340 de = (e) => e.use_native && "loading" in HTMLImageElement.prototype, 341 _e = (e, t, a) => { 342 e.forEach((e) => 343 ((e) => e.isIntersecting || e.intersectionRatio > 0)(e) 344 ? ((e, t, a, s) => { 345 const n = ((e) => y.indexOf(E(e)) >= 0)(e); 346 I(e, "entered"), 347 C(e, a.class_entered), 348 O(e, a.class_exited), 349 ((e, t, a) => { 350 t.unobserve_entered && M(e, a); 351 })(e, a, s), 352 w(a.callback_enter, e, t, s), 353 n || ne(e, a, s); 354 })(e.target, e, t, a) 355 : ((e, t, a, s) => { 356 A(e) || 357 (C(e, a.class_exited), 358 ((e, t, a, s) => { 359 a.cancel_on_exit && 360 ((e) => E(e) === u)(e) && 361 "IMG" === e.tagName && 362 (te(e), 363 ((e) => { 364 T(e, (e) => { 365 le(e); 366 }), 367 le(e); 368 })(e), 369 oe(e), 370 O(e, a.class_loading), 371 z(s, -1), 372 k(e), 373 w(a.callback_cancel, e, t, s)); 374 })(e, t, a, s), 375 w(a.callback_exit, e, t, s)); 376 })(e.target, e, t, a) 377 ); 378 }, 379 ue = (e) => Array.prototype.slice.call(e), 380 ge = (e) => e.container.querySelectorAll(e.elements_selector), 381 be = (e) => ((e) => E(e) === h)(e), 382 he = (e, t) => ((e) => ue(e).filter(A))(e || ge(t)), 383 me = function (t, a) { 384 const s = n(t); 385 (this._settings = s), 386 (this.loadingCount = 0), 387 ((e, t) => { 388 de(e) || 389 (t._observer = new IntersectionObserver((a) => { 390 _e(a, e, t); 391 }, ((e) => ({ root: e.container === document ? null : e.container, rootMargin: e.thresholds || e.threshold + "px" }))(e))); 392 })(s, this), 393 ((t, a) => { 394 e && 395 ((a._onlineHandler = () => { 396 ((e, t) => { 397 var a; 398 ((a = ge(e)), ue(a).filter(be)).forEach((t) => { 399 O(t, e.class_error), k(t); 400 }), 401 t.update(); 402 })(t, a); 403 }), 404 window.addEventListener("online", a._onlineHandler)); 405 })(s, this), 406 this.update(a); 407 }; 408 return ( 409 (me.prototype = { 410 update: function (e) { 411 const a = this._settings, 412 s = he(e, a); 413 var n, l; 414 N(this, s.length), 415 t 416 ? this.loadAll(s) 417 : de(a) 418 ? ((e, t, a) => { 419 e.forEach((e) => { 420 -1 !== ce.indexOf(e.tagName) && 421 ((e, t, a) => { 422 e.setAttribute("loading", "lazy"), 423 se(e, t, a), 424 ((e, t) => { 425 const a = Q[e.tagName]; 426 a && a(e, t); 427 })(e, t), 428 I(e, m); 429 })(e, t, a); 430 }), 431 N(a, 0); 432 })(s, a, this) 433 : ((l = s), 434 ((e) => { 435 e.disconnect(); 436 })((n = this._observer)), 437 ((e, t) => { 438 t.forEach((t) => { 439 e.observe(t); 440 }); 441 })(n, l)); 442 }, 443 destroy: function () { 444 this._observer && this._observer.disconnect(), 445 e && window.removeEventListener("online", this._onlineHandler), 446 ge(this._settings).forEach((e) => { 447 S(e); 448 }), 449 delete this._observer, 450 delete this._settings, 451 delete this._onlineHandler, 452 delete this.loadingCount, 453 delete this.toLoadCount; 454 }, 455 loadAll: function (e) { 456 const t = this._settings; 457 he(e, t).forEach((e) => { 458 M(e, this), ne(e, t, this); 459 }); 460 }, 461 restoreAll: function () { 462 const e = this._settings; 463 ge(e).forEach((t) => { 464 ie(t, e); 465 }); 466 }, 467 }), 468 (me.load = (e, t) => { 469 const a = n(t); 470 ne(e, a); 471 }), 472 (me.resetStatus = (e) => { 473 k(e); 474 }), 475 e && 476 ((e, t) => { 477 if (t) 478 if (t.length) for (let a, s = 0; (a = t[s]); s += 1) l(e, a); 479 else l(e, t); 480 })(me, window.lazyLoadOptions), 481 me 482 ); 483})();存放友链的地方
assets/data/links.json1[ 2 { 3 "class_name": "组一", 4 "class_desc": "组一的描述。", 5 "link_list": [ 6 { 7 "name": "A", 8 "bio": "A的简介。", 9 "url": "https://a.example.com", 10 "avatar": "https://a.example.com/avatar.jpg" 11 }, 12 { 13 "name": "B", 14 "bio": "B的简介。", 15 "url": "https://b.example.com", 16 "avatar": "https://b.example.com/avatar.jpg" 17 } 18 ] 19 }, 20 { 21 "class_name": "组二", 22 "class_desc": "组二的描述。", 23 "link_list": [ 24 { 25 "name": "C", 26 "bio": "C的简介。", 27 "url": "https://c.example.com", 28 "avatar": "https://c.example.com/avatar.jpg" 29 } 30 ] 31 } 32]对应的 css 效果
1/* 友链 */ 2.friend-avatar-wrapper { 3 width: 64px; 4 aspect-ratio: 1 / 1; /* 保证是正方形,避免压扁 */ 5 border-radius: 50%; /* 圆形裁切 */ 6 overflow: hidden; 7 border: 2px solid #9ca3af; 8 display: flex; 9 align-items: center; 10 justify-content: center; 11 background-color: white; 12 box-sizing: border-box; 13 transition: transform 0.6s ease; 14} 15 16/* dark 模式下边框稍调亮 */ 17html.dark .friend-avatar-wrapper { 18 border-color: #d1d5db; /* light gray for dark mode */ 19} 20 21.friend-avatar { 22 width: 100%; 23 height: 100%; 24 object-fit: cover; 25 display: block; 26} 27 28/* 友链卡片悬停效果 */ 29.friend-card { 30 transition: transform 0.3s ease-in-out, box-shadow 0.3s ease-in-out; 31} 32 33.friend-card:hover { 34 transform: scale(1.05); 35 box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); 36}在对应的 Front Matter 中指定
layout: "links"即可调用这个模版进行渲染额外新增了手动指定 json 数据文件路径的 Front Matter 字段
linksFile,增加了一点点灵活性- 只要在对应的 Front Matter 中添加
linksFile: "data/xxxx.json"字段即可使用特定的数据
- 只要在对应的 Front Matter 中添加
卡片样式修改#
参考友链情况,修改了文章卡片的样式
复制
themes/blowfish/layouts/partials/article-link/card.html到layouts/partials/article-link/card.html稍微进行了调整1{{/* Used by 2 1. list.html and term.html (when the cardView option is enabled) 3 2. Recent articles template (when the cardView option is enabled) 4 3. Shortcode list.html 5*/}} 6{{ $disableImageOptimization := site.Store.Get "disableImageOptimization" }} 7 8{{ $page := .Page }} 9{{ $featured := "" }} 10{{ $featuredURL := "" }} 11{{ if not .Params.hideFeatureImage }} 12 {{/* frontmatter */}} 13 {{ with $page }} 14 {{ with .Params.featureimage }} 15 {{ if or (strings.HasPrefix . "http:") (strings.HasPrefix . "https:") }} 16 {{ if site.Params.hotlinkFeatureImage }} 17 {{ $featuredURL = . }} 18 {{ else }} 19 {{ $featured = resources.GetRemote . }} 20 {{ end }} 21 {{ else }} 22 {{ $featured = resources.Get . }} 23 {{ end }} 24 {{ end }} 25 26 {{/* page resources */}} 27 {{ if not (or $featured $featuredURL) }} 28 {{ $images := .Resources.ByType "image" }} 29 {{ range slice "*feature*" "*cover*" "*thumbnail*" }} 30 {{ if not $featured }}{{ $featured = $images.GetMatch . }}{{ end }} 31 {{ end }} 32 {{ end }} 33 34 {{/* fallback to default */}} 35 {{ if not (or $featured $featuredURL) }} 36 {{ $default := site.Store.Get "defaultFeaturedImage" }} 37 {{ if $default.url }} 38 {{ $featuredURL = $default.url }} 39 {{ else if $default.obj }} 40 {{ $featured = $default.obj }} 41 {{ end }} 42 {{ end }} 43 44 {{/* generate image URL if not hotlink */}} 45 {{ if not $featuredURL }} 46 {{ with $featured }} 47 {{ $featuredURL = .RelPermalink }} 48 {{ if not (or $disableImageOptimization (eq .MediaType.SubType "svg")) }} 49 {{ $featuredURL = (.Resize "600x").RelPermalink }} 50 {{ end }} 51 {{ end }} 52 {{ end }} 53 {{ end }} 54{{ end }} 55 56 57<article 58 class="friend-card relative min-h-full min-w-full overflow-hidden rounded-xl bg-white shadow-sm border border-neutral-200 dark:bg-neutral-800 dark:border-neutral-700"> 59 {{ with $featuredURL }} 60 <div class="flex-none relative overflow-hidden thumbnail_card"> 61 <img 62 src="{{ . }}" 63 alt="{{ $page.Title | plainify }}" 64 loading="lazy" 65 decoding="async" 66 class="not-prose absolute inset-0 w-full h-full object-cover"> 67 </div> 68 {{ end }} 69 {{ if and .Draft .Site.Params.article.showDraftLabel }} 70 <span class="absolute top-0 right-0 m-2"> 71 {{ partial "badge.html" (i18n "article.draft" | emojify) }} 72 </span> 73 {{ end }} 74 <div class="px-6 py-4"> 75 <header> 76 <a 77 {{ with $page.Params.externalUrl }} 78 href="{{ . }}" target="_blank" rel="external" 79 {{ else }} 80 href="{{ $page.RelPermalink }}" 81 {{ end }} 82 class="not-prose before:absolute before:inset-0 decoration-primary-500 dark:text-neutral text-xl font-bold text-neutral-800 hover:underline hover:underline-offset-2"> 83 <h2> 84 {{ .Title | emojify }} 85 {{ if .Params.externalUrl }} 86 <span class="cursor-default align-top text-xs text-neutral-400 dark:text-neutral-500"> 87 <span class="rtl:hidden">↗</span> 88 <span class="ltr:hidden">↖</span> 89 </span> 90 {{ end }} 91 </h2> 92 </a> 93 </header> 94 <div class="text-sm text-neutral-500 dark:text-neutral-400"> 95 {{ partial "article-meta/basic.html" . }} 96 </div> 97 {{ if .Params.showSummary | default (.Site.Params.list.showSummary | default false) }} 98 <div class="prose dark:prose-invert py-1">{{ .Summary | plainify }}</div> 99 {{ end }} 100 </div> 101 <div class="px-6 pt-4 pb-2"></div> 102</article>复制
themes/blowfish/layouts/partials/article-link/simple.html到layouts/partials/article-link/simple.html稍微进行了调整1{{/* Used by 2 1. list.html and term.html (when the cardView option is not enabled) 3 2. Recent articles template (when the cardView option is not enabled) 4 3. Shortcode list.html 5*/}} 6{{ $constrainItemsWidth := site.Params.list.constrainItemsWidth | default false }} 7{{ $disableImageOptimization := site.Store.Get "disableImageOptimization" }} 8 9{{/* Force card styling as per user request */}} 10{{ $cardClasses := "friend-card flex flex-col md:flex-row relative bg-white dark:bg-neutral-800 shadow-sm border border-neutral-200 dark:border-neutral-700 rounded-xl overflow-hidden" }} 11{{ $imgWrapperClasses := "" }} 12{{ $cardContentClasses := "p-4" }} 13 14{{ if $constrainItemsWidth }} 15 {{ $cardClasses = printf "%s max-w-prose" $cardClasses }} 16{{ end }} 17 18{{ $page := .Page }} 19{{ $featured := "" }} 20{{ $featuredURL := "" }} 21{{ if not .Params.hideFeatureImage }} 22 {{/* frontmatter */}} 23 {{ with $page }} 24 {{ with .Params.featureimage }} 25 {{ if or (strings.HasPrefix . "http:") (strings.HasPrefix . "https:") }} 26 {{ if site.Params.hotlinkFeatureImage }} 27 {{ $featuredURL = . }} 28 {{ else }} 29 {{ $featured = resources.GetRemote . }} 30 {{ end }} 31 {{ else }} 32 {{ $featured = resources.Get . }} 33 {{ end }} 34 {{ end }} 35 36 {{/* page resources */}} 37 {{ if not (or $featured $featuredURL) }} 38 {{ $images := .Resources.ByType "image" }} 39 {{ range slice "*feature*" "*cover*" "*thumbnail*" }} 40 {{ if not $featured }}{{ $featured = $images.GetMatch . }}{{ end }} 41 {{ end }} 42 {{ end }} 43 44 {{/* fallback to default */}} 45 {{ if not (or $featured $featuredURL) }} 46 {{ $default := site.Store.Get "defaultFeaturedImage" }} 47 {{ if $default.url }} 48 {{ $featuredURL = $default.url }} 49 {{ else if $default.obj }} 50 {{ $featured = $default.obj }} 51 {{ end }} 52 {{ end }} 53 54 {{/* generate image URL if not hotlink */}} 55 {{ if not $featuredURL }} 56 {{ with $featured }} 57 {{ $featuredURL = .RelPermalink }} 58 {{ if not (or $disableImageOptimization (eq .MediaType.SubType "svg")) }} 59 {{ $featuredURL = (.Resize "600x").RelPermalink }} 60 {{ end }} 61 {{ end }} 62 {{ end }} 63 {{ end }} 64{{ end }} 65 66 67<article class="{{ $cardClasses }}"> 68 {{ with $featuredURL }} 69 <div class="flex-none relative overflow-hidden {{ $imgWrapperClasses }} thumbnail"> 70 <img 71 src="{{ . }}" 72 alt="{{ $.Params.featuredImageAlt | default ($.Title | emojify) }}" 73 loading="lazy" 74 decoding="async" 75 class="not-prose absolute inset-0 w-full h-full object-cover"> 76 </div> 77 {{ end }} 78 <div class="{{ $cardContentClasses }}"> 79 <header class="items-center text-start text-xl font-semibold"> 80 <a 81 {{ with $page.Params.externalUrl }} 82 href="{{ . }}" target="_blank" rel="external" 83 {{ else }} 84 href="{{ $page.RelPermalink }}" 85 {{ end }} 86 class="not-prose before:absolute before:inset-0 decoration-primary-500 dark:text-neutral text-xl font-bold text-neutral-800 hover:underline hover:underline-offset-2"> 87 <h2> 88 {{ .Title | emojify }} 89 {{ if .Params.externalUrl }} 90 <span class="cursor-default align-top text-xs text-neutral-400 dark:text-neutral-500"> 91 <span class="rtl:hidden">↗</span> 92 <span class="ltr:hidden">↖</span> 93 </span> 94 {{ end }} 95 </h2> 96 </a> 97 {{ if and .Draft .Site.Params.article.showDraftLabel }} 98 <div class="ms-2">{{ partial "badge.html" (i18n "article.draft" | emojify) }}</div> 99 {{ end }} 100 {{ if templates.Exists "partials/extend-article-link.html" }} 101 {{ partial "extend-article-link.html" . }} 102 {{ end }} 103 </header> 104 <div class="text-sm text-neutral-500 dark:text-neutral-400"> 105 {{ partial "article-meta/basic.html" . }} 106 </div> 107 {{ if .Params.showSummary | default (.Site.Params.list.showSummary | default false) }} 108 <div class="prose dark:prose-invert max-w-fit py-1">{{ .Summary | plainify }}</div> 109 {{ end }} 110 </div> 111 <div class="px-6 pt-4 pb-2"></div> 112</article>
字体颜色和一些标记颜色#
略微调整了一下
1/* 调整正文字体颜色,使其更白更易读 */ 2.dark .prose { 3 color: #d1d5db; /* 稍微降低亮度,拉开与加粗的对比 */ 4} 5 6.dark .prose strong, 7.dark .prose h1, 8.dark .prose h2, 9.dark .prose h3, 10.dark .prose h4, 11.dark .prose h5, 12.dark .prose h6 { 13 color: #f2f2f2; 14} 15 16/* 调整列表标记颜色 */ 17.dark .prose ul > li::marker, 18.dark .prose ol > li::marker{ 19 color: rgb(var(--color-primary-300)); 20}复制
themes/blowfish/layouts/_default/_markup/render-heading.html到layouts/_default/_markup/render-heading.html调整文章小标题鼠标悬浮显示的#的颜色1{{ $anchor := anchorize .Anchor }} 2<h{{ .Level }} class="relative group">{{ .Text | safeHTML }} 3 <div id="{{ $anchor }}" class="anchor"></div> 4 {{ if .Page.Params.showHeadingAnchors | default (.Page.Site.Params.article.showHeadingAnchors | default true) }} 5 <span 6 class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none"> 7 <a class="text-primary-300 dark:text-primary-400 !no-underline" href="#{{ $anchor }}" aria-label="{{ i18n "article.anchor_label" }}">#</a> 8 </span> 9 {{ end }} 10</h{{ .Level }}>
网站永久链接#
- Hugo 永久链接
- 为了保持和之前 Hexo 站点的 URL 内容和结构一致,主要是在
hugo.toml中设置permalinks字段1[permalinks] 2 posts = "/posts/:slug/" - 这样就可以把文章的 URL 变成
yoursite.com/posts/your-slug/的形式,然后在archetypes/default.md中补充一个slug字段1... 2slug: {{ substr (md5 (printf "%s%s" .Date (replace .TranslationBaseName "-" " " | title))) 4 8 }} 3... - 这样通过 hugo new posts/xxx/index.md 创建的文章就会自动生成一个基于日期和标题做 md5 的唯一 slug
页脚信息#
这里包括版权信息、备案信息,还有“苟活”计时器
创建
layouts/partials/footer.html1<!-- 省略之前的内容 --> 2 <div class="flex items-center justify-between"> 3 {{/* Copyright */}} 4 {{ if .Site.Params.footer.showCopyright | default true }} 5 <p class="text-sm text-neutral-500 dark:text-neutral-400"> 6 <span class="inline-block beian-container"> 7 <a class="hover:underline hover:decoration-primary-400 hover:text-primary-500" href="https://beian.mps.gov.cn/#/query/webSearch?code=" rel="noreferrer" target="_blank"> 8 <img class="nozoom" src="备案图标" style="display:inline-block;vertical-align:sub;margin-right:2px;" width="16" height="16">网站备案号 9 </a> 10 <span class="separator mx-1">|</span> 11 <a href="https://beian.miit.gov.cn/" target="_blank" class="hover:underline hover:decoration-primary-400 hover:text-primary-500">ICP备案号</a> 12 </span> 13 <span class="block mt-1"> 14 {{- with replace .Site.Params.copyright "{ year }" now.Year }} 15 {{ . | markdownify }} 16 {{- else }} 17 © 18 {{ now.Format "2006" }} 19 {{ .Site.Params.Author.name | markdownify }} 20 {{- end }} 21 </span> 22 </p> 23 {{ end }} 24 25 {{/* Theme attribution */}} 26 {{ if .Site.Params.footer.showThemeAttribution | default true }} 27 <div class="text-sm text-neutral-500 dark:text-neutral-400 text-right"> 28 <span id="workboard"></span> 29 <span class="block mt-1"> 30 {{ $hugo := printf `<a class="hover:underline hover:decoration-primary-400 hover:text-primary-500" 31 href="https://gohugo.io/" target="_blank" rel="noopener noreferrer">Hugo</a>` 32 }} 33 {{ $blowfish := printf `<a class="hover:underline hover:decoration-primary-400 hover:text-primary-500" 34 href="https://blowfish.page/" target="_blank" rel="noopener noreferrer">Blowfish</a>` 35 }} 36 {{ i18n "footer.powered_by" (dict "Hugo" $hugo "Theme" $blowfish) | safeHTML }} 37 <span class="mx-1">|</span> 38 由 39 <a class="hover:decoration-primary-400 hover:text-primary-500" href="https://www.dogecloud.com/" target="_blank" rel="noopener noreferrer"> 40 <img class="nozoom" src="多吉云 logo" alt="DogeCloud" style="display:inline-block;vertical-align:middle;height:1em;margin:0 2px;position:relative;top:-2px;"> 41 </a> 42 提供CDN加速服务 43 </span> 44 </div> 45 {{ end }} 46 </div> 47 {{ if not .Site.Params.disableImageZoom | default true }} 48 <script> 49 mediumZoom(document.querySelectorAll("img:not(.nozoom)"), { 50 margin: 24, 51 background: "rgba(0,0,0,0.5)", 52 scrollOffset: 0, 53 }); 54 </script> 55 {{ end }} 56 {{ $jsProcess := resources.Get "js/process.js" }} 57 {{ $jsProcess = $jsProcess | resources.Minify | resources.Fingerprint (.Site.Params.fingerprintAlgorithm | default "sha512") }} 58 <script 59 type="text/javascript" 60 src="{{ $jsProcess.RelPermalink }}" 61 integrity="{{ $jsProcess.Data.Integrity }}"></script> 62 {{/* Extend footer - eg. for extra scripts, etc. */}} 63 {{ if templates.Exists "partials/extend-footer.html" }} 64 {{ partialCached "extend-footer.html" . }} 65 {{ end }} 66 67 {{/* Hidden SVG source for JS */}} 68 {{ with resources.Get "icons/heartbeat.svg" }} 69 <div id="heartbeat-svg-source" style="display:none;"> 70 {{ .Content | safeHTML }} 71 </div> 72 {{ end }} 73 74 {{/* Foot Timer */}} 75 {{ $footTimer := resources.Get "js/foot_timer.js" | fingerprint "sha256" }} 76 <script type="text/javascript" src="{{ $footTimer.RelPermalink }}" integrity="{{ $footTimer.Data.Integrity }}"></script> 77</footer>“苟活”计时器的 js 代码,创建
assets/js/foot_timer.js,其中Date("09/04/2022 00:00:00")为网站上线时间1var now=new Date;function createtime(){now.setTime(now.getTime()+1e3);var e=new Date("09/04/2022 00:00:00"),t=(now-e)/1e3/60/60/24,n=Math.floor(t),o=(now-e)/1e3/60/60-24*n,a=Math.floor(o);1==String(a).length&&(a="0"+a);var r=(now-e)/1e3/60-1440*n-60*a,i=Math.floor(r);1==String(i).length&&(i="0"+i);var l=(now-e)/1e3-86400*n-3600*a-60*i,w=Math.round(l);1==String(w).length&&(w="0"+w);let icon="";const iconSource=document.getElementById("heartbeat-svg-source");if(iconSource){icon=iconSource.innerHTML;if(icon.indexOf('id="heartbeat"')===-1){icon=icon.replace('<svg','<svg id="heartbeat"')}}else{icon='<i id="heartbeat" class="fas fa-heartbeat"></i>'}let d=`<div style="font-size:14px;font-weight:bold">自 2022-9 以来,小站苟活了 ${n} 天 ${a} 小时 ${i} 分 ${w} 秒 ${icon}</div>`;document.getElementById("workboard")&&(document.getElementById("workboard").innerHTML=d)}console.log(now),setInterval((()=>{createtime()}),1e3);
Not-By-AI Badge#
- 在页脚菜单栏末尾添加一个 Not-By-AI 徽章footer.html
1<footer id="site-footer" class="py-10 print:hidden"> 2 {{/* Footer menu */}} 3 {{ if .Site.Params.footer.showMenu | default true }} 4 {{ if .Site.Menus.footer }} 5 {{ $onlyIcon := true }} 6 {{ range .Site.Menus.footer }} 7 {{ if .Name }} 8 {{ $onlyIcon = false }} 9 {{ break }} 10 {{ end }} 11 {{ end }} 12 {{ $navClass := printf "flex flex-row pb-4 text-base font-medium text-neutral-500 dark:text-neutral-400 %s" (cond $onlyIcon "overflow-x-auto py-2" "") }} 13 {{ $ulClass := printf "flex list-none %s" (cond $onlyIcon "flex-row" "flex-col sm:flex-row") }} 14 {{ $liClass := printf "flex mb-1 text-end sm:mb-0 sm:me-7 sm:last:me-0 %s" (cond $onlyIcon "me-4" "") }} 15 <nav class="{{ $navClass }}"> 16 <ul class="{{ $ulClass }}"> 17 {{ range .Site.Menus.footer }} 18 <li class=" {{ $liClass }}"> 19 <a 20 class="decoration-primary-500 hover:underline hover:decoration-2 hover:underline-offset-2 flex items-center" 21 href="{{ .URL }}" 22 title="{{ .Title }}"> 23 {{ if .Pre }} 24 <span {{ if and .Pre .Name }}class="mr-1"{{ end }}> 25 {{ partial "icon.html" .Pre }} 26 </span> 27 {{ end }} 28 {{ .Name | markdownify }} 29 </a> 30 </li> 31 {{ end }} 32 <li class="{{ $liClass }}"> 33 <a class="hover:underline hover:decoration-primary-400 hover:text-primary-500" href="https://notbyai.fyi/" target="_blank" rel="noopener noreferrer"> 34 <img class="nozoom h-8" src="not-by-AI-badge.png" alt="Written by Human, Not by AI"> 35 </a> 36 </li> 37 </ul> 38 </nav> 39 {{ end }} 40 {{ end }} 41 <!-- 省略后续内容 -->
文章结尾版权信息#
在文章结尾添加版权信息
1 {{/* Body */}} 2 <section class="flex flex-col max-w-full mt-0 prose dark:prose-invert lg:flex-row"> 3 {{ $enableToc := site.Params.article.showTableOfContents | default false }} 4 {{ $enableToc = .Params.showTableOfContents | default $enableToc }} 5 {{ $showToc := and $enableToc (in .TableOfContents "<ul") }} 6 {{ $topClass := cond (hasPrefix site.Params.header.layout "fixed") "lg:top-[140px]" "lg:top-10" }} 7 {{ if $showToc }} 8 <div class="order-first lg:ml-auto px-0 lg:order-last lg:ps-8 lg:max-w-2xs"> 9 <div class="toc ps-5 print:hidden lg:sticky {{ $topClass }}"> 10 {{ if $showToc }} 11 {{ partial "toc.html" . }} 12 {{ end }} 13 </div> 14 </div> 15 {{ end }} 16 17 18 <div class="min-w-0 min-h-0 max-w-fit"> 19 {{ partial "series/series.html" . }} 20 <div class="article-content max-w-prose mb-20"> 21 {{ .Content }} 22 {{ $defaultReplyByEmail := site.Params.replyByEmail }} 23 {{ $replyByEmail := default $defaultReplyByEmail .Params.replyByEmail }} 24 {{ if $replyByEmail }} 25 <strong class="block mt-8"> 26 <a 27 target="_blank" 28 class="m-1 rounded bg-neutral-300 p-1.5 text-neutral-700 hover:bg-primary-500 hover:text-neutral dark:bg-neutral-700 dark:text-neutral-300 dark:hover:bg-primary-400 dark:hover:text-neutral-800" 29 href="mailto:{{ site.Params.Author.email }}?subject={{ replace (printf "Reply to %s" .Title) "\"" "'" }}"> 30 {{ i18n "article.reply_by_email" | default "Reply by Email" }} 31 </a> 32 </strong> 33 {{ end }} 34 </div> 35 {{ if (.Params.showAuthorBottom | default (site.Params.article.showAuthorBottom | default false)) }} 36 {{ template "SingleAuthor" . }} 37 {{ end }} 38 {{ partial "series/series-closed.html" . }} 39 {{ partial "sharing-links.html" . }} 40 {{ partial "related.html" . }} 41 </div> 42 43 {{ $translations := .AllTranslations }} 44 {{ with .File }} 45 {{ $path := .Path }} 46 {{ range $translations }} 47 {{ $lang := print "." .Lang ".md" }} 48 {{ $path = replace $path $lang ".md" }} 49 {{ end }} 50 {{ $jsPage := resources.Get "js/page.js" }} 51 {{ $jsPage = $jsPage | resources.Minify | resources.Fingerprint (site.Params.fingerprintAlgorithm | default "sha512") }} 52 <script 53 type="text/javascript" 54 src="{{ $jsPage.RelPermalink }}" 55 integrity="{{ $jsPage.Data.Integrity }}" 56 data-oid="views_{{ $path }}" 57 data-oid-likes="likes_{{ $path }}"></script> 58 {{ end }} 59 </section> 60 61 {{/* Footer */}} 62 <footer class="pt-8 max-w-prose print:hidden"> 63 <!-- 后续内容未调整,省略 -->对应渲染的 css 代码
1/* 文章版权信息样式 */ 2.post-copyright { 3 margin-top: 3rem; 4 padding: 1rem; 5 border-left: 4px solid rgb(var(--color-primary-500)); 6 background-color: rgb(var(--color-neutral-200)); 7 border-radius: 0 0.5rem 0.5rem 0; 8 box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05); 9 transition: transform 0.3s ease, box-shadow 0.3s ease; 10} 11 12.post-copyright .post-copyright-text { 13 margin-top: 0; 14 margin-bottom: 0; 15} 16 17.dark .post-copyright { 18 background-color: rgb(var(--color-neutral-800)); 19} 20 21.post-copyright:hover { 22 transform: translateY(-2px); 23 box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); 24} 25 26.post-copyright a { 27 transition: color 0.2s ease; 28} 29 30.post-copyright a:hover { 31 color: rgb(var(--color-primary-500)); 32}通过在文章 Front Matter 中添加
copyright: false字段可以在文章结尾不显示版权信息,即默认显示
选集列表调整#
调整一下显示的文字
复制
themes/blowfish/layouts/partials/series/series_base.html到layouts/partials/series/series_base.html进行修改1{{ if .Params.series }} 2 <summary 3 class="py-1 text-lg font-semibold cursor-pointer bg-primary-200 text-neutral-800 -ms-5 ps-5 dark:bg-primary-800 dark:text-neutral-100"> 4 {{ i18n "article.part_of_series" }} - 5 {{ index .Params.series 0 }} 6 </summary> 7 {{ $seriesName := strings.ToLower (index .Params.series 0) }} 8 {{ range $post := sort (index .Site.Taxonomies.series $seriesName) "Params.series_order" }} 9 {{ if eq $post.Permalink $.Page.Permalink }} 10 <div 11 class="py-1 border-dotted border-neutral-300 border-s-1 -ms-5 ps-5 dark:border-neutral-600"> 12 {{ i18n "article.part" }} {{ $post.Params.series_order }}: 13 {{ i18n "article.this_article" }} 14 </div> 15 {{ else }} 16 <div 17 class="py-1 border-dotted border-neutral-300 border-s-1 -ms-5 ps-5 dark:border-neutral-600"> 18 <a href="{{ $post.RelPermalink }}"> 19 {{ i18n "article.part" }} {{ $post.Params.series_order }}: 20 {{ $post.Params.title }} 21 </a> 22 </div> 23 {{ end }} 24 {{ end }} 25{{ end }}只渲染在文章开头(去掉了结尾的渲染),只需要新建一个空的
layouts/partials/series/series-closed.html即可
调整头部导航栏#
- 原来头部导航栏的 logo 竟然不支持 url 链接,复制
themes/blowfish/layouts/partials/header/basic.html到layouts/partials/header/basic.html进行修改1{{/* Logo section */}} 2{{ define "HeaderLogo" }} 3 {{ if .Site.Params.Logo }} 4 {{ $logo := resources.Get .Site.Params.Logo }} 5 {{ if $logo }} 6 <div> 7 <a href="{{ "" | relLangURL }}" class="flex"> 8 <span class="sr-only">{{ .Site.Title | markdownify }}</span> 9 {{ if eq $logo.MediaType.SubType "svg" }} 10 <span class="logo object-scale-down object-left nozoom"> 11 {{ $logo.Content | safeHTML }} 12 </span> 13 {{ else }} 14 <img 15 src="{{ $logo.RelPermalink }}" 16 width="{{ div $logo.Width 5 }}" 17 height="{{ div $logo.Height 5 }}" 18 class="logo max-h-[2rem] max-w-[2rem] object-scale-down object-left nozoom" 19 alt=""> 20 {{ end }} 21 </a> 22 </div> 23 {{ else }} 24 <div> 25 <a href="{{ "" | relLangURL }}" class="flex"> 26 <span class="sr-only">{{ .Site.Title | markdownify }}</span> 27 <img 28 src="{{ .Site.Params.Logo }}" 29 class="logo object-scale-down object-left nozoom" 30 style="width: 36px; height: 36px;" 31 alt=""> 32 </a> 33 </div> 34 {{ end }} 35 {{- end }} 36{{ end }} 37 38<!-- 省略中间一些的内容 --> 39<!-- 调整导航栏的 title --> 40{{/* ========== Render HTML ========== */}} 41<div 42 class="main-menu flex items-center justify-between py-6 md:justify-start gap-x-3 pt-[2px] pr-2 md:pr-4 pb-[3px] pl-0"> 43 {{ template "HeaderLogo" . }} 44 <div class="flex flex-1 items-center justify-between"> 45 <nav class="flex space-x-3"> 46 {{ if not .Site.Params.disableTextInHeader | default true }} 47 <a href="{{ "" | relLangURL }}" class="text-l font-bold"> 48 {{ .Site.Title | markdownify }} 49 </a> 50 {{ end }} 51 </nav> 52 {{ template "HeaderDesktopNavigation" . }} 53 {{ template "HeaderMobileToolbar" . }} 54 </div> 55 {{ template "HeaderMobileNavigation" . }} 56</div>
代码块配色调整#
- 参考 语法高亮
- 直接在
assets/css/custom.css中添加1/* Base Settings */ 2.chroma .lntd { vertical-align: top; padding: 0; margin: 0; border: 0; } 3.chroma .lntable { border-spacing: 0; padding: 0; margin: 0; border: 0; } 4.chroma .hl { background-color: #e5e5e5; } 5.chroma .lnt, .chroma .ln { margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f; } 6.chroma .line { display: flex; } 7 8 9/* --- Github Dark (Dark Mode) --- */ 10.dark .chroma { color: #e6edf3; } 11.dark .chroma .hl { background-color: #6e7681; } 12.dark .chroma .lnt, .dark .chroma .ln { color: #737679; } 13 14.dark .chroma .k, .dark .chroma .kd, .dark .chroma .kn, .dark .chroma .kr, .dark .chroma .kt, .dark .chroma .nn { color: #ff7b72; } 15.dark .chroma .kc, .dark .chroma .kp, .dark .chroma .no, .dark .chroma .nl, .dark .chroma .py, .dark .chroma .nv, .dark .chroma .vc, .dark .chroma .vg, .dark .chroma .vi, .dark .chroma .vm, .dark .chroma .ld, .dark .chroma .sa, .dark .chroma .dl, .dark .chroma .se, .dark .chroma .sh, .dark .chroma .sr { color: #79c0ff; } 16.dark .chroma .nc, .dark .chroma .ne, .dark .chroma .nf, .dark .chroma .fm, .dark .chroma .gh { color: #d2a8ff; font-weight: bold; } 17.dark .chroma .nd { color: #d2a8ff; font-weight: bold; } 18.dark .chroma .ni { color: #ffa657; } 19.dark .chroma .nt { color: #7ee787; } 20.dark .chroma .l, .dark .chroma .s, .dark .chroma .sb, .dark .chroma .sc, .dark .chroma .sd, .dark .chroma .s2, .dark .chroma .si, .dark .chroma .sx, .dark .chroma .s1, .dark .chroma .ss, .dark .chroma .m, .dark .chroma .mb, .dark .chroma .mf, .dark .chroma .mh, .dark .chroma .mi, .dark .chroma .il, .dark .chroma .mo { color: #a5d6ff; } 21.dark .chroma .o, .dark .chroma .ow, .dark .chroma .gt { color: #ff7b72; font-weight: bold; } 22.dark .chroma .c, .dark .chroma .ch, .dark .chroma .cm, .dark .chroma .c1, .dark .chroma .cs, .dark .chroma .cp, .dark .chroma .cpf, .dark .chroma .go, .dark .chroma .gp { color: #8b949e; } 23.dark .chroma .gd { color: #ffa198; background-color: #490202; } 24.dark .chroma .gi { color: #56d364; background-color: #0f5323; } 25.dark .chroma .w { color: #6e7681; }
图片懒加载占位符#
- 刚好复用友链卡片中构建的懒加载 js 代码和顶部阅读进度条中的
extend-footer.html - 在
extend-footer.html中的最后添加1{{ $lazyloadJS := resources.Get "js/lazyload.iife.min.js" | fingerprint "sha256" }} 2<script type="text/javascript" src="{{ $lazyloadJS.RelPermalink }}" integrity="{{ $lazyloadJS.Data.Integrity }}"></script> 3<script> 4 var lazyLoadInstance = new LazyLoad({ 5 elements_selector: ".lazy" 6 }); 7</script> - 重写 Markdown 图片渲染钩子,新建
layouts/_default/_markup/render-image.html,用于覆盖默认的图片渲染逻辑 - 新的逻辑是将真实图片地址放在 data-src 和 data-srcset 中,并把 src 属性设置为默认的 loading 图片的 URL
1{{- define "RenderImageSimple" -}} 2 {{- $imgObj := .imgObj -}} 3 {{- $src := .src -}} 4 {{- $alt := .alt -}} 5 <img 6 class="my-0 rounded-md lazy" 7 loading="lazy" 8 decoding="async" 9 fetchpriority="low" 10 alt="{{ $alt }}" 11 src="<默认 loading 图片的 URL>" 12 data-src="{{ $src }}" 13 {{ with $imgObj -}} 14 {{ with $imgObj.Width }}width="{{ . }}"{{ end }} 15 {{ with $imgObj.Height }}height="{{ . }}"{{ end }} 16 {{- end }}> 17{{- end -}} 18 19{{- define "RenderImageResponsive" -}} 20 {{- $imgObj := .imgObj -}} 21 {{- $alt := .alt -}} 22 {{- $originalWidth := $imgObj.Width -}} 23 24 {{- $img800 := $imgObj -}} 25 {{- $img1280 := $imgObj -}} 26 {{- if gt $originalWidth 800 -}} 27 {{- $img800 = $imgObj.Resize "800x" -}} 28 {{- end -}} 29 {{- if gt $originalWidth 1280 -}} 30 {{- $img1280 = $imgObj.Resize "1280x" -}} 31 {{- end -}} 32 33 {{- $srcset := printf "%s 800w, %s 1280w" $img800.RelPermalink $img1280.RelPermalink -}} 34 35 <img 36 class="my-0 rounded-md lazy" 37 loading="lazy" 38 decoding="async" 39 fetchpriority="auto" 40 alt="{{ $alt }}" 41 {{ with $imgObj.Width }}width="{{ . }}"{{ end }} 42 {{ with $imgObj.Height }}height="{{ . }}"{{ end }} 43 src="<默认 loading 图片的 URL>" 44 data-src="{{ $img800.RelPermalink }}" 45 data-srcset="{{ $srcset }}" 46 sizes="(min-width: 768px) 50vw, 65vw" 47 data-zoom-src="{{ $imgObj.RelPermalink }}"> 48{{- end -}} 49 50{{- define "RenderImageCaption" -}} 51 {{- with .caption -}} 52 <figcaption>{{ . | markdownify }}</figcaption> 53 {{- end -}} 54{{- end -}} 55 56{{- $disableImageOptimizationMD := .Page.Site.Params.disableImageOptimizationMD | default false -}} 57{{- $urlStr := .Destination | safeURL -}} 58{{- $url := urls.Parse $urlStr -}} 59{{- $altText := .Text -}} 60{{- $caption := .Title -}} 61{{- $isRemote := findRE "^(https?|data)" $url.Scheme -}} 62{{- $resource := "" -}} 63 64{{- if not $isRemote -}} 65 {{- $resource = or ($.Page.Resources.GetMatch $urlStr) (resources.Get $urlStr) -}} 66{{- end -}} 67 68<figure 69 {{- range $k, $v := .Attributes -}} 70 {{- if $v -}} 71 {{- printf " %s=%q" $k ($v | transform.HTMLEscape) | safeHTMLAttr -}} 72 {{- end -}} 73 {{- end -}}> 74 {{- if $isRemote -}} 75 {{- template "RenderImageSimple" (dict "imgObj" "" "src" $urlStr "alt" $altText) -}} 76 {{- else if $resource -}} 77 {{- $isSVG := eq $resource.MediaType.SubType "svg" -}} 78 {{- $shouldOptimize := and (not $disableImageOptimizationMD) (not $isSVG) -}} 79 {{- if $shouldOptimize -}} 80 {{- template "RenderImageResponsive" (dict "imgObj" $resource "alt" $altText) -}} 81 {{- else -}} 82 {{/* Not optimize image 83 If it is an SVG file, pass the permalink 84 Otherwise, pass the resource to allow width and height attributes 85 */}} 86 {{- if $isSVG -}} 87 {{- template "RenderImageSimple" (dict "imgObj" "" "src" $resource.RelPermalink "alt" $altText) -}} 88 {{- else -}} 89 {{- template "RenderImageSimple" (dict "imgObj" $resource "src" $resource.RelPermalink "alt" $altText) -}} 90 {{- end -}} 91 {{- end -}} 92 {{- else -}} 93 {{- template "RenderImageSimple" (dict "imgObj" "" "src" $urlStr "alt" $altText) -}} 94 {{- end -}} 95 96 {{- template "RenderImageCaption" (dict "caption" $caption) -}} 97</figure> - 重写 Figure 的 shortcode,新建
layouts/shortcodes/figure.html1{{ $disableImageOptimization := .Site.Params.disableImageOptimization | default false }} 2{{ if .Get "default" }} 3 {{ partial "hugo-embedded/shortcodes/figure-default.html" . }} 4{{ else }} 5 {{- $url := urls.Parse (.Get "src") }} 6 {{- $altText := .Get "alt" }} 7 {{- $caption := .Get "caption" }} 8 {{- $href := .Get "href" }} 9 {{- $class := .Get "class" }} 10 {{- $target := .Get "target" | default "_blank" }} 11 {{- $nozoom := .Get "nozoom" | default false -}} 12 13 <figure> 14 {{- with $href }}<a href="{{ . }}" {{ with $target }}target="{{ . }}"{{ end }} class="inline-block">{{ end -}} 15 {{- if findRE "^https?" $url.Scheme }} 16 <img class="my-0 rounded-md lazy{{ with $nozoom }} nozoom{{ end }}{{ with $class }} {{ . }}{{ end }}" src="<默认 loading 图片的 URL>" data-src="{{ $url.String }}" alt="{{ $altText }}" /> 17 {{- else }} 18 {{- $resource := "" }} 19 {{- if $.Page.Resources.GetMatch ($url.String) }} 20 {{- $resource = $.Page.Resources.GetMatch ($url.String) }} 21 {{- else if resources.GetMatch ($url.String) }} 22 {{- $resource = resources.Get ($url.String) }} 23 {{- end }} 24 {{- with $resource }} 25 {{- if or $disableImageOptimization (eq .MediaType.SubType "svg")}} 26 <img 27 class="my-0 rounded-md lazy{{ with $nozoom }} nozoom{{ end }}{{ with $class }} {{ . }}{{ end }}" 28 src="<默认 loading 图片的 URL>" 29 data-src="{{ .RelPermalink }}" 30 alt="{{ $altText }}" 31 /> 32 {{- else }} 33 <img 34 class="my-0 rounded-md lazy{{ with $nozoom }} nozoom{{ end }}{{ with $class }} {{ . }}{{ end }}" 35 loading="lazy" 36 decoding="async" 37 fetchpriority="auto" 38 alt="{{ $altText }}" 39 {{ with .Width }}width="{{ . }}"{{ end }} 40 {{ with .Height }}height="{{ . }}"{{ end }} 41 src="<默认 loading 图片的 URL>" 42 data-src="{{ (.Resize "800x").RelPermalink }}" 43 data-srcset=" 44 {{- (.Resize "800x").RelPermalink }} 800w, 45 {{- (.Resize "1280x").RelPermalink }} 1280w" 46 sizes="(min-width: 768px) 50vw, 65vw" 47 data-zoom-src="{{ .RelPermalink }}" 48 /> 49 {{- end }} 50 {{- else }} 51 <img class="my-0 rounded-md lazy{{ with $nozoom }} nozoom{{ end }}{{ with $class }} {{ . }}{{ end }}" src="<默认 loading 图片的 URL>" data-src="{{ $url.String }}" alt="{{ $altText }}" /> 52 {{- end }} 53 {{- end }} 54 {{ if $href }}</a>{{ end }} 55 {{ with $caption }}<figcaption>{{ . | markdownify }}</figcaption>{{ end }} 56 </figure> 57{{- end -}}
本文作者: SuburbiaXX
本文链接: https://suburbiaxx.fun/posts/f6af86d0/
版权声明: 本博客在未特别注明下默认使用 CC BY-NC-SA 4.0 许可协议。

