upgrade astro
-- [CST 2026-10-02 Friday 21:58:32]
This commit is contained in:
commit
ebd245deb2
117 files changed
+65233
No files matched your search
@@ -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;
|
||||
});
|
||||
})();
|
||||
@@ -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);
|
||||
}
|
||||
})();
|
||||
@@ -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 117 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user