Merge pull request 'Codex/design 启用新的页面风格' (#10) from codex/design into master

Reviewed-on: #10
 -- [CST 2026-07-19 Sunday 16:12:13]
This commit is contained in:
biss committed 2026-07-19 16:12:13 +08:00
commit 4b015839af
112 files changed
+63309

No files matched your search

+45
View File
@@ -0,0 +1,45 @@
(() => {
const redirectPath = '/go/';
const urlParams = new URLSearchParams(window.location.search);
if (window.location.pathname === redirectPath || window.location.pathname === '/go') return;
if (urlParams.get('noExternalRedirect') === '1') return;
function getExternalUrl(anchor) {
const href = anchor.getAttribute('href');
if (!href || href.startsWith('#')) return null;
let targetUrl;
try {
targetUrl = new URL(href, window.location.href);
} catch {
return null;
}
if (!['http:', 'https:'].includes(targetUrl.protocol)) return null;
if (targetUrl.origin === window.location.origin) return null;
return targetUrl.href;
}
document.addEventListener('click', (event) => {
const anchor = event.target.closest?.('a[href]');
if (!anchor) return;
if (event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
const externalUrl = getExternalUrl(anchor);
if (!externalUrl) return;
event.preventDefault();
const redirectUrl = `${redirectPath}?url=${encodeURIComponent(externalUrl)}&from=${encodeURIComponent(window.location.href)}`;
const target = anchor.getAttribute('target');
if (target && target !== '_self') {
window.open(redirectUrl, target, 'noopener,noreferrer');
return;
}
window.location.href = redirectUrl;
});
})();
+104
View File
@@ -0,0 +1,104 @@
(() => {
if (!('serviceWorker' in navigator)) return;
const toast = document.querySelector('[data-pwa-toast]');
const title = toast?.querySelector('[data-pwa-title]');
const message = toast?.querySelector('[data-pwa-message]');
const primary = toast?.querySelector('[data-pwa-primary]');
const close = toast?.querySelector('[data-pwa-close]');
let installPrompt = null;
let waitingWorker = null;
let toastMode = '';
let refreshing = false;
const showToast = (mode, heading, detail, actionLabel) => {
if (!toast || !title || !message || !primary) return;
toastMode = mode;
title.textContent = heading;
message.textContent = detail;
primary.textContent = actionLabel;
primary.hidden = !actionLabel;
toast.hidden = false;
};
const hideToast = () => {
if (toast) toast.hidden = true;
};
const installDismissedRecently = () => {
try {
const dismissedAt = Number(localStorage.getItem('pwa-install-dismissed-at'));
return dismissedAt > 0 && Date.now() - dismissedAt < 30 * 86400000;
} catch {
return false;
}
};
window.addEventListener('beforeinstallprompt', (event) => {
event.preventDefault();
installPrompt = event;
if (!installDismissedRecently()) {
window.setTimeout(() => {
if (installPrompt) showToast('install', '安装 Bi\'s Blog', '添加到桌面,像应用一样打开并阅读缓存过的文章。', '立即安装');
}, 2500);
}
});
window.addEventListener('appinstalled', () => {
installPrompt = null;
showToast('ready', '安装完成', '博客已经添加到你的设备。', '知道了');
});
primary?.addEventListener('click', async () => {
if (toastMode === 'install' && installPrompt) {
await installPrompt.prompt();
await installPrompt.userChoice;
installPrompt = null;
hideToast();
return;
}
if (toastMode === 'update' && waitingWorker) {
refreshing = true;
waitingWorker.postMessage({ type: 'SKIP_WAITING' });
return;
}
hideToast();
});
close?.addEventListener('click', () => {
if (toastMode === 'install') {
try { localStorage.setItem('pwa-install-dismissed-at', String(Date.now())); } catch {}
}
hideToast();
});
navigator.serviceWorker.addEventListener('controllerchange', () => {
if (refreshing) window.location.reload();
});
navigator.serviceWorker.register('/sw.js', { scope: '/' }).then((registration) => {
if (registration.waiting && navigator.serviceWorker.controller) {
waitingWorker = registration.waiting;
showToast('update', '发现新版本', '刷新后即可使用博客的最新内容和功能。', '立即更新');
}
registration.addEventListener('updatefound', () => {
const worker = registration.installing;
worker?.addEventListener('statechange', () => {
if (worker.state === 'installed' && navigator.serviceWorker.controller) {
waitingWorker = worker;
showToast('update', '发现新版本', '刷新后即可使用博客的最新内容和功能。', '立即更新');
}
});
});
}).catch((error) => {
console.warn('PWA registration failed:', error);
});
const params = new URLSearchParams(window.location.search);
if (params.get('action') === 'search') {
window.setTimeout(() => document.querySelector('.search-typesense-trigger')?.click(), 350);
}
})();
+93
View File
@@ -0,0 +1,93 @@
function randomPost(trigger) {
const source = trigger instanceof Element
? trigger
: document.activeElement?.closest?.('.nav-random, [onclick*="randomPost"]');
const overlay = document.createElement('div');
const reduceMotion = window.matchMedia?.('(prefers-reduced-motion: reduce)').matches;
overlay.className = 'random-post-transition';
overlay.setAttribute('role', 'status');
overlay.setAttribute('aria-live', 'polite');
overlay.innerHTML = `
<div class="random-post-flight" aria-hidden="true"></div>
<div class="random-post-transition-card">
<i class="fa-solid fa-shuffle" aria-hidden="true"></i>
<span>\u6b63\u5728\u62bd\u53d6\u4e00\u7bc7\u6587\u7ae0</span>
</div>
`;
source?.classList.add('is-randomizing');
source?.setAttribute('aria-busy', 'true');
document.body.appendChild(overlay);
requestAnimationFrame(() => overlay.classList.add('is-visible'));
const finish = href => {
const delay = reduceMotion ? 80 : 1250;
window.setTimeout(() => {
location.href = href;
}, delay);
};
const startCardFlight = posts => {
if (reduceMotion) return;
const flight = overlay.querySelector('.random-post-flight');
if (!flight) return;
const candidates = posts
.filter(post => post && typeof post.href === 'string' && post.href.startsWith('/posts/'))
.map(post => ({
title: typeof post.title === 'string' && post.title.trim() ? post.title.trim() : '\u672a\u547d\u540d\u6587\u7ae0',
meta: typeof post.category === 'string' && post.category.trim() ? post.category.trim() : '\u968f\u673a\u6587\u7ae0',
}));
const pool = candidates.length ? candidates : [{ title: '\u968f\u673a\u6587\u7ae0', meta: '\u6b63\u5728\u62bd\u53d6' }];
const count = Math.min(9, Math.max(5, pool.length));
for (let index = 0; index < count; index += 1) {
const post = pool[Math.floor(Math.random() * pool.length)];
const card = document.createElement('div');
card.className = 'random-post-flying-card';
card.style.setProperty('--flight-delay', `${index * 0.11}s`);
card.style.setProperty('--flight-y', `${-42 + Math.random() * 84}px`);
card.style.setProperty('--flight-rotate', `${-8 + Math.random() * 16}deg`);
const meta = document.createElement('small');
const title = document.createElement('strong');
meta.textContent = post.meta;
title.textContent = post.title;
card.append(meta, title);
flight.appendChild(card);
}
};
const reset = () => {
source?.classList.remove('is-randomizing');
source?.removeAttribute('aria-busy');
overlay.classList.remove('is-visible');
window.setTimeout(() => overlay.remove(), reduceMotion ? 0 : 180);
};
fetch('/api/posts.json')
.then(res => {
if (!res.ok) throw new Error(`Failed to load posts: ${res.status}`);
return res.json();
})
.then(posts => {
const postLinks = posts
.map(post => post && post.href)
.filter(href => typeof href === 'string' && href.startsWith('/posts/'));
if (postLinks.length === 0) {
console.warn('No posts available for random navigation.');
reset();
return;
}
startCardFlight(posts);
finish(postLinks[Math.floor(Math.random() * postLinks.length)]);
})
.catch(error => {
console.error('Random post navigation failed:', error);
reset();
});
}
Loaded 3 of 112 files, more files were not shown because too many files have changed in this diff. Show more