hexo-theme-linen 1.0.28 → 1.0.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hexo-theme-linen",
3
- "version": "1.0.28",
3
+ "version": "1.0.30",
4
4
  "description": "A clean Hexo theme",
5
5
  "main": "index.js",
6
6
  "homepage": "https://github.com/lynanbreeze/hexo-theme-linen",
@@ -19,9 +19,11 @@ module.exports = function (hexo) {
19
19
  ...(hexo?.config?.theme_config?.lazyload || {}),
20
20
  };
21
21
 
22
- const placeholderImage = /^(https?:\/\/|\/|data:image)/i.test(loadingImage) || loadingImage?.startsWith('../img')
23
- ? `url(${loadingImage})`
24
- : loadingImage;
22
+ const placeholderImage =
23
+ /^(https?:\/\/|\/|data:image|\.\.\/)/i.test(loadingImage) ||
24
+ /\.(svg|png|jpg|jpeg|webp|gif)$/i.test(loadingImage)
25
+ ? `url(${loadingImage})`
26
+ : loadingImage;
25
27
 
26
28
  const injectedStyles = `.lazyload-outer-wrap .caption{
27
29
  width: 100%;
@@ -10,6 +10,8 @@
10
10
  }
11
11
  .nav-header.is-post {
12
12
  position: fixed;
13
+ transition: transform 0.28s cubic-bezier(0.16, 1, 0.3, 1);
14
+ will-change: transform;
13
15
  }
14
16
  .nav-header.is-post.hide {
15
17
  transform: translateY(-100%);
@@ -239,8 +239,6 @@ body {
239
239
  background-color: #fff;
240
240
  // backdrop-filter: saturate(180%) blur(20px);
241
241
  transform: translateY(0);
242
- transition: transform 0.28s cubic-bezier(0.16, 1, 0.3, 1);
243
- will-change: transform;
244
242
  z-index: 9;
245
243
  position: relative;
246
244
  top: 0;
@@ -369,7 +367,7 @@ body {
369
367
  }
370
368
  }
371
369
  &.hide {
372
- transform: translateY(-100%);
370
+ transform: translateY(0);
373
371
  }
374
372
  }
375
373
 
@@ -52,7 +52,7 @@ function switchHDR() {
52
52
  } else {
53
53
  var hrefAttr = link.getAttribute("href");
54
54
  if (hrefAttr && hrefAttr.includes(SDRPath)) {
55
- link.setAttribute("href", HDRPath);
55
+ link.setAttribute("href", hrefAttr.replace(SDRPath, HDRPath));
56
56
  }
57
57
  }
58
58
  } else {
@@ -61,7 +61,7 @@ function switchHDR() {
61
61
  } else {
62
62
  var hrefAttr = link.getAttribute("href");
63
63
  if (hrefAttr && hrefAttr.includes(HDRPath)) {
64
- link.setAttribute("href", SDRPath);
64
+ link.setAttribute("href", hrefAttr.replace(HDRPath, SDRPath));
65
65
  }
66
66
  }
67
67
  }
@@ -5,6 +5,7 @@ var isAnchoring = false;
5
5
  var anchoringId = null;
6
6
  var anchoringTimer = null;
7
7
  function triggerAnchoring() {
8
+ if (window.innerWidth > 1280) return;
8
9
  isAnchoring = true;
9
10
  var header = document.querySelector(".nav-header");
10
11
  if (header) {
@@ -13,6 +14,12 @@ function triggerAnchoring() {
13
14
  clearTimeout(anchoringTimer);
14
15
  anchoringTimer = setTimeout(function () {
15
16
  isAnchoring = false;
17
+ if (window.scrollY <= 60) {
18
+ var header = document.querySelector(".nav-header");
19
+ if (header) {
20
+ header.classList.remove("hide");
21
+ }
22
+ }
16
23
  }, 800);
17
24
  }
18
25
 
@@ -147,30 +154,27 @@ function isElementInViewport(el) {
147
154
  }
148
155
 
149
156
  function copyToClipboard(text) {
150
- if (navigator.clipboard) {
151
- copyToClipboard = function () {
152
- navigator.clipboard
153
- .writeText(text)
154
- .then(() => {})
155
- .catch((err) => {
156
- console.error("复制失败:", err);
157
- });
158
- };
159
- } else {
160
- copyToClipboard = function () {
161
- const textArea = document.createElement("textarea");
162
- textArea.value = text;
163
- document.body.appendChild(textArea);
164
- textArea.select();
165
- try {
166
- document.execCommand("copy");
167
- } catch (err) {
157
+ if (navigator.clipboard && window.isSecureContext) {
158
+ navigator.clipboard
159
+ .writeText(text)
160
+ .then(() => {})
161
+ .catch((err) => {
168
162
  console.error("复制失败:", err);
169
- }
170
- document.body.removeChild(textArea);
171
- };
163
+ });
164
+ } else {
165
+ const textArea = document.createElement("textarea");
166
+ textArea.value = text;
167
+ textArea.style.position = "fixed";
168
+ textArea.style.opacity = "0";
169
+ document.body.appendChild(textArea);
170
+ textArea.select();
171
+ try {
172
+ document.execCommand("copy");
173
+ } catch (err) {
174
+ console.error("复制失败:", err);
175
+ }
176
+ document.body.removeChild(textArea);
172
177
  }
173
- copyToClipboard(text);
174
178
  }
175
179
 
176
180
  function highlightAnchor(hash, highlightClass = "anchor-highlight") {
@@ -278,20 +282,28 @@ function onScroll() {
278
282
  typeof loadGitalk !== "undefined" && loadGitalk();
279
283
  }
280
284
 
281
- if (isAnchoring) {
282
- header.classList.add("hide");
283
- previousScrollY = currentScrollY;
284
- return;
285
+ var isMobilePost =
286
+ window.innerWidth <= 1280 && header.classList.contains("is-post");
287
+
288
+ // PC 端没有 header 隐藏逻辑,确保无 hide 类
289
+ if (!isMobilePost && header.classList.contains("hide")) {
290
+ header.classList.remove("hide");
285
291
  }
286
292
 
287
293
  // 接近顶部时(<= 60px),强制展开并退出,消除 iOS/Mac 橡皮筋回弹误判与闪烁
288
294
  if (currentScrollY <= 60) {
289
- header.classList.remove("hide");
295
+ if (isMobilePost) header.classList.remove("hide");
290
296
  if (backToTop) backToTop.classList.remove("visible");
291
297
  previousScrollY = currentScrollY;
292
298
  return;
293
299
  }
294
300
 
301
+ if (isMobilePost && isAnchoring) {
302
+ header.classList.add("hide");
303
+ previousScrollY = currentScrollY;
304
+ return;
305
+ }
306
+
295
307
  var scrollDelta = currentScrollY - previousScrollY;
296
308
  // 滤除手指微小抖动
297
309
  if (Math.abs(scrollDelta) < 8) {
@@ -300,7 +312,7 @@ function onScroll() {
300
312
 
301
313
  if (scrollDelta < 0) {
302
314
  // 向上滑动:即刻平滑展开导航栏
303
- if (!document.querySelector(".pswp--open")) {
315
+ if (isMobilePost && !document.querySelector(".pswp--open")) {
304
316
  header.classList.remove("hide");
305
317
  }
306
318
  if (backToTop && currentScrollY >= triggerHeight) {
@@ -309,7 +321,7 @@ function onScroll() {
309
321
  } else {
310
322
  // 向下滑动:离开顶部 60% 视口后平滑收起导航栏
311
323
  if (backToTop) backToTop.classList.remove("visible");
312
- if (currentScrollY > window.innerHeight * 0.6) {
324
+ if (isMobilePost && currentScrollY > window.innerHeight * 0.6) {
313
325
  header.classList.add("hide");
314
326
  }
315
327
  }
@@ -340,6 +352,11 @@ window.addEventListener("resize", function () {
340
352
  body.classList.add("resizing");
341
353
  viewportWidth = window.innerWidth;
342
354
 
355
+ if (window.innerWidth > 1280) {
356
+ const header = document.querySelector(".nav-header");
357
+ if (header) header.classList.remove("hide");
358
+ }
359
+
343
360
  clearTimeout(resizeTimer);
344
361
  resizeTimer = setTimeout(function () {
345
362
  body.classList.remove("resizing");
@@ -448,23 +465,46 @@ function handleClick(e) {
448
465
  maskElement.classList.remove("visible");
449
466
  setBodyScrollLocked(false);
450
467
  }
451
- if (targetId === "to-page-top") {
468
+ const firstTocLink = tocElement
469
+ ? tocElement.querySelector(".toc-item-link")
470
+ : null;
471
+ const isFirstToc =
472
+ target === firstTocLink ||
473
+ target.closest(".toc-item-link") === firstTocLink;
474
+
475
+ if (targetId === "to-page-top" || targetId === "page-top") {
452
476
  window.scrollTo({
453
477
  top: 0,
454
478
  left: 0,
455
479
  behavior: "instant",
456
480
  });
481
+ isAnchoring = false;
482
+ clearTimeout(anchoringTimer);
483
+ var header = document.querySelector(".nav-header");
484
+ if (header) {
485
+ header.classList.remove("hide");
486
+ }
457
487
  } else {
458
488
  anchor &&
459
489
  anchor.scrollIntoView({
460
490
  behavior: "instant",
461
491
  block: "start",
462
492
  });
463
- triggerAnchoring();
493
+ if (isFirstToc) {
494
+ isAnchoring = false;
495
+ clearTimeout(anchoringTimer);
496
+ var header = document.querySelector(".nav-header");
497
+ if (header) {
498
+ header.classList.remove("hide");
499
+ }
500
+ } else {
501
+ triggerAnchoring();
502
+ }
464
503
  }
465
504
  highlightAnchor(targetId);
466
505
  if (
467
506
  target.className?.includes("toc-item-link") &&
507
+ anchor &&
468
508
  isElementInViewport(anchor)
469
509
  ) {
470
510
  anchoringId = anchor.id;
@@ -548,24 +588,27 @@ function handleClick(e) {
548
588
  }
549
589
  }
550
590
 
591
+ var isClickHandlerBound = false;
551
592
  function bindClickHandler() {
593
+ if (isClickHandlerBound) return;
552
594
  document.addEventListener("click", handleClick, true);
595
+ isClickHandlerBound = true;
553
596
  }
554
597
 
555
598
  window.addEventListener("load", bindClickHandler);
556
- window.addEventListener("pageshow", (e) => {
557
- requestAnimationFrame(() => {
558
- bindClickHandler();
559
- });
599
+ window.addEventListener("pageshow", () => {
600
+ bindClickHandler();
560
601
  });
561
602
 
562
603
  function initAgeWarning() {
563
604
  const ageWarningElement = document.getElementById("post-age-warning");
564
605
  if (!ageWarningElement) return;
565
- const dateStr = document
566
- .querySelector("#post .post-meta .date")
567
- .getAttribute("datetime");
606
+ const dateElement = document.querySelector("#post .post-meta .date");
607
+ const dateStr = dateElement ? dateElement.getAttribute("datetime") : null;
608
+ if (!dateStr) return;
609
+
568
610
  const postDate = new Date(dateStr);
611
+ if (isNaN(postDate.getTime())) return;
569
612
  const now = new Date();
570
613
 
571
614
  const diffTime = now - postDate;
@@ -578,7 +621,7 @@ function initAgeWarning() {
578
621
  }
579
622
  } else {
580
623
  if (ageWarningElement) {
581
- ageWarningElement.style = "display: none";
624
+ ageWarningElement.style.display = "none";
582
625
  }
583
626
  }
584
627
  return diffDays;
@@ -1,4 +1,5 @@
1
1
  function replaceEmojis() {
2
+ if (!document.body) return;
2
3
  const replacements = [{ emoji: "✅", className: "custom-checked-checkbox" }];
3
4
 
4
5
  replacements.forEach(({ emoji, className }) => {
@@ -7,9 +8,16 @@ function replaceEmojis() {
7
8
  NodeFilter.SHOW_TEXT,
8
9
  {
9
10
  acceptNode(node) {
10
- return node.nodeValue.includes(emoji)
11
- ? NodeFilter.FILTER_ACCEPT
12
- : NodeFilter.FILTER_REJECT;
11
+ if (!node.nodeValue || !node.nodeValue.includes(emoji)) {
12
+ return NodeFilter.FILTER_REJECT;
13
+ }
14
+ if (
15
+ node.parentElement &&
16
+ node.parentElement.closest("pre, code, textarea, script, style")
17
+ ) {
18
+ return NodeFilter.FILTER_REJECT;
19
+ }
20
+ return NodeFilter.FILTER_ACCEPT;
13
21
  },
14
22
  },
15
23
  );