Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion assets/ts/custom-cursor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,10 +133,17 @@ function handleMouseLeave(e: MouseEvent) {
}

function handleMouseEnter(e: MouseEvent) {
// document 级 mouseenter 的 target 是 document(非 Element),
// 不能在窗口进入时把 mouseover 已算好的状态重置为 default
if (!(e.target instanceof Element)) return;
updateStateFromTarget(e.target);
}

function handleClick(e: MouseEvent) {
function isArticleDetailPage() {
return document.body?.dataset.pageKind === "note";
}

function toggleClockEffect() {
if (document.documentElement.getAttribute('data-clock-cursor') !== 'true') {
return;
}
Expand All @@ -150,6 +157,16 @@ function handleClick(e: MouseEvent) {
}
}

function handleClick() {
if (isArticleDetailPage()) return;
toggleClockEffect();
}

function handleDoubleClick() {
if (!isArticleDetailPage()) return;
toggleClockEffect();
}

function handleVisibilityChange() {
if (document.hidden) breakIdleClock(false);
else if (clockController) clockController.updateColors();
Expand Down Expand Up @@ -216,6 +233,7 @@ function setupCustomCursor() {
document.addEventListener("mouseleave", handleMouseLeave);
document.addEventListener("mouseenter", handleMouseEnter);
document.addEventListener("click", handleClick, { passive: true });
document.addEventListener("dblclick", handleDoubleClick, { passive: true });
document.addEventListener("visibilitychange", handleVisibilityChange);
document.addEventListener("daybook:page-load", handlePageLoad);

Expand All @@ -232,6 +250,7 @@ function teardownCustomCursor() {
document.removeEventListener("mouseleave", handleMouseLeave);
document.removeEventListener("mouseenter", handleMouseEnter);
document.removeEventListener("click", handleClick);
document.removeEventListener("dblclick", handleDoubleClick);
document.removeEventListener("visibilitychange", handleVisibilityChange);
document.removeEventListener("daybook:page-load", handlePageLoad);

Expand Down
2 changes: 1 addition & 1 deletion assets/ts/embeds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { createFallbackElement, setupIframeEmbeds } from "./embed-loading.js";
import { setupImages } from "./image-loader.js";

(function () {
var compactNumberFormat = new Intl.NumberFormat("en_US", {
var compactNumberFormat = new Intl.NumberFormat("en-US", {
notation: "compact",
maximumFractionDigits: 1,
});
Expand Down
14 changes: 12 additions & 2 deletions assets/ts/mobile-toc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@
* Mobile TOC Controller
* Manages the floating action button (FAB) and the bottom sheet for the Table of Contents on small screens.
*/
function headingIdFromLink(link: HTMLAnchorElement): string {
const rawId = link.getAttribute("href")?.substring(1) || "";

try {
return decodeURIComponent(rawId);
} catch {
return rawId;
}
}

class MobileTocController {
private readonly fab: HTMLElement;
private readonly sheet: HTMLElement;
Expand Down Expand Up @@ -54,7 +64,7 @@ class MobileTocController {
this.listItems.forEach(item => {
const link = item.querySelector("a");
if (link) {
const id = link.getAttribute("href")?.substring(1);
const id = headingIdFromLink(link);
if (id) {
const element = document.getElementById(id);
if (element) {
Expand All @@ -73,7 +83,7 @@ class MobileTocController {
const link = item.querySelector("a");
link?.addEventListener("click", (e) => {
e.preventDefault();
const id = link.getAttribute("href")?.substring(1);
const id = headingIdFromLink(link);
const target = document.getElementById(id || "");
if (target) {
target.scrollIntoView({ behavior: "smooth" });
Expand Down
72 changes: 39 additions & 33 deletions assets/ts/theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,33 @@

let isTransitioning = false;

// 兜底:后台标签页中 View Transition 的 finished 可能永不 resolve,
// 导致 isTransitioning 锁死、后续点击被静默丢弃
function releaseTransitionLock(attributeName: string) {
clearThemeTransition(attributeName);
isTransitioning = false;
}

function guardedTransition(attributeName: string, updateCallback: () => void) {
isTransitioning = true;
try {
const transition = document.startViewTransition!(updateCallback);
transition.finished.then(function() {
releaseTransitionLock(attributeName);
}, function() {
releaseTransitionLock(attributeName);
});
// 2 秒超时兜底
setTimeout(function() {
releaseTransitionLock(attributeName);
}, 2000);
} catch (e) {
// startViewTransition 同步抛错时也要释放锁
updateCallback();
releaseTransitionLock(attributeName);
}
}

document.addEventListener("click", function (event: MouseEvent) {
if (isTransitioning) return;

Expand Down Expand Up @@ -299,18 +326,11 @@
setTimeout(function() {
root.style.setProperty("view-transition-name", "theme-toggle-transition");
root.dataset['themeChanging'] = "true";
const transition = document.startViewTransition!(function() {
guardedTransition("themeChanging", function() {
root.dataset['theme'] = nextResolved;
syncThemeButtons();
});
transition.finished.then(function() {
clearThemeTransition("themeChanging");
isTransitioning = false;
}, function() {
clearThemeTransition("themeChanging");
isTransitioning = false;
});
}, 350);
}, 350);
}
return;
}
Expand All @@ -324,26 +344,21 @@
const nextResolved = nextMode === "system" ? getSystemPreferredTheme() : nextMode;
const currentResolved = root.dataset['theme'];

// 立即更新按钮状态和模式,不等待 View Transition 捕获帧
// 用户点击后立刻看到图标变化,背景色通过过渡动画渐变
root.dataset['themeMode'] = nextMode;
storeThemeMode(nextMode);
syncThemeButtons();

if (nextResolved === currentResolved || !shouldAnimateTheme()) {
applyThemeMode(nextMode, true);
root.dataset['theme'] = nextResolved;
return;
}

isTransitioning = true;
// Start view transition immediately for desktop, active state gives physical feedback
root.style.setProperty("view-transition-name", "theme-toggle-transition");
root.dataset['themeChanging'] = "true";

const themeTransition = document.startViewTransition!(function () {
applyThemeMode(nextMode, true);
});

themeTransition.finished.then(function () {
clearThemeTransition("themeChanging");
isTransitioning = false;
}, function () {
clearThemeTransition("themeChanging");
isTransitioning = false;
guardedTransition("themeChanging", function () {
root.dataset['theme'] = nextResolved;
});

return;
Expand All @@ -367,18 +382,9 @@
setTimeout(function () {
root.style.setProperty("view-transition-name", "palette-toggle-transition");
root.dataset['paletteChanging'] = nextPalette === "warm" ? "to-warm" : "from-warm";

const paletteTransition = document.startViewTransition!(function () {
guardedTransition("paletteChanging", function () {
applyPalette(nextPalette, true);
});

paletteTransition.finished.then(function () {
clearThemeTransition("paletteChanging");
isTransitioning = false;
}, function () {
clearThemeTransition("paletteChanging");
isTransitioning = false;
});
}, 350);
}
});
Expand Down
12 changes: 11 additions & 1 deletion assets/ts/toc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,15 @@ class NoteTocController {
if (currentLink) {
event.preventDefault();
this.jumpToHeading(this.activeIndex);
return;
}

const tocLink = target.closest<HTMLAnchorElement>(".note-toc-panel a[href^=\"#\"]");
if (tocLink) {
event.preventDefault();
const id = headingIdFromLink(tocLink);
const headingIndex = this.headings.findIndex((heading) => heading.id === id);
this.jumpToHeading(headingIndex);
}
};

Expand All @@ -391,7 +400,8 @@ class NoteTocController {
});

const url = new URL(window.location.href);
url.hash = heading.id;
const rawHash = heading.link.getAttribute("href");
url.hash = rawHash?.startsWith("#") ? rawHash.slice(1) : heading.id;
const state = history.state;
const nextState = state && typeof state === "object"
? { ...state, url: url.href }
Expand Down
97 changes: 89 additions & 8 deletions internal/embedded/static/css/pages/note.css
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,10 @@
.article-meta-rows {
margin: 1.2rem 0 0;
display: flex;
flex-direction: column;
flex-direction: row;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 0.6rem;
color: var(--color-muted);
font-family: var(--font-meta);
Expand Down Expand Up @@ -188,6 +191,90 @@
padding: 4px 10px 0 0;
}

/* Article pages use the existing site-info column as the desktop reading rail. */
@media (min-width: 1281px) {
/* 整栏上移,让作者块与左侧常驻 logo(--persistent-logo-top)同高;
min-height 同步补偿,保持底部链接区相对视口底的位置不变 */
.notes-aside.note-page-aside {
--note-aside-lift: calc(clamp(56px, 7vw, 88px) - var(--persistent-logo-top));

margin-top: calc(-1 * var(--note-aside-lift));
min-height: calc(100vh - 176px + var(--note-aside-lift));
top: var(--persistent-logo-top);
}

.note-page-aside .notes-aside-identity {
align-self: flex-end;
max-width: 100%;
text-align: right;
}

.note-page-aside .notes-brand-name {
justify-content: flex-end;
}

.note-page-aside .notes-brand-cn {
font-size: clamp(1.05rem, 1.4vw, 1.35rem);
}

.note-page-aside .notes-brand-en {
font-size: clamp(0.95rem, 1.2vw, 1.15rem);
}

.note-page-aside .notes-brand-slogan {
font-size: 0.72rem;
margin-top: 0.45rem;
text-wrap: balance;
}

.note-page-aside .note-aside-toc {
margin-top: clamp(2.5rem, 7vh, 4rem);
min-width: 0;
}

.note-page-aside .note-toc {
font-size: 0.86rem;
}

.note-page-aside .note-toc-wrapper {
bottom: auto;
pointer-events: auto;
position: static;
right: auto;
top: auto;
width: 100%;
}

.note-page-aside .note-sticky-column,
.note-page-aside .note-toc-stage,
.note-page-aside .note-toc {
width: 100%;
}

.note-page-aside .note-sticky-column {
position: static;
}

.note-page-aside .note-toc {
margin-bottom: 0;
max-width: none;
}

.note-page-aside .note-toc-panel {
max-height: min(calc(100vh - 260px), 32rem);
}

/* 阅读控件沉到右栏底部、贴着工具行上方;auto 外边距吸收中部留白,
控件存在时工具行改用固定间距(不存在时保持原有 margin-top:auto 沉底) */
.note-page-aside .note-aside-reading-controls {
margin-top: auto;
}

.note-page-aside .note-aside-reading-controls ~ .notes-aside-controls {
margin-top: clamp(1.5rem, 3vh, 2.25rem);
}
}

@media (min-width: 116rem) {
.note-toc-wrapper {
--reading-toc-rail-eligible: 1;
Expand Down Expand Up @@ -354,13 +441,7 @@ html[data-reduced-motion="true"] .note-toc-stage .note-toc {
}

.note-tags {
margin-top: clamp(3rem, 5vw, 4.5rem);
}

.note-tags-divider {
border: 0;
border-top: 1px dashed var(--color-line);
margin: 0 0 1.5rem;
margin-top: clamp(0.75rem, 1.5vw, 1.25rem);
}

.note-tags-list {
Expand Down
16 changes: 15 additions & 1 deletion internal/embedded/static/js/custom-cursor.js
Original file line number Diff line number Diff line change
Expand Up @@ -386,9 +386,13 @@
}
}
function handleMouseEnter(e) {
if (!(e.target instanceof Element)) return;
updateStateFromTarget(e.target);
}
function handleClick(e) {
function isArticleDetailPage() {
return document.body?.dataset.pageKind === "note";
}
function toggleClockEffect() {
if (document.documentElement.getAttribute("data-clock-cursor") !== "true") {
return;
}
Expand All @@ -401,6 +405,14 @@
}
}
}
function handleClick() {
if (isArticleDetailPage()) return;
toggleClockEffect();
}
function handleDoubleClick() {
if (!isArticleDetailPage()) return;
toggleClockEffect();
}
function handleVisibilityChange() {
if (document.hidden) breakIdleClock(false);
else if (clockController) clockController.updateColors();
Expand Down Expand Up @@ -456,6 +468,7 @@
document.addEventListener("mouseleave", handleMouseLeave);
document.addEventListener("mouseenter", handleMouseEnter);
document.addEventListener("click", handleClick, { passive: true });
document.addEventListener("dblclick", handleDoubleClick, { passive: true });
document.addEventListener("visibilitychange", handleVisibilityChange);
document.addEventListener("daybook:page-load", handlePageLoad);
isInitialized = true;
Expand All @@ -469,6 +482,7 @@
document.removeEventListener("mouseleave", handleMouseLeave);
document.removeEventListener("mouseenter", handleMouseEnter);
document.removeEventListener("click", handleClick);
document.removeEventListener("dblclick", handleDoubleClick);
document.removeEventListener("visibilitychange", handleVisibilityChange);
document.removeEventListener("daybook:page-load", handlePageLoad);
if (rafId !== null) {
Expand Down
Loading