@windcoder/astro-theme-orchid 0.2.1 → 0.4.0
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/README.md +5 -3
- package/package.json +4 -2
- package/scripts/check-friends.mjs +227 -0
- package/scripts/generate-lqips.mjs +234 -20
- package/scripts/migrate-posts.mjs +375 -0
- package/src/components/AlbumCard.astro +4 -1
- package/src/components/Analytics.astro +3 -0
- package/src/components/BaseLayout.astro +222 -39
- package/src/components/DisplaySettings.astro +383 -0
- package/src/components/Header.astro +56 -2
- package/src/components/MusicPlayer.astro +45 -5
- package/src/components/MusicWidget.astro +265 -0
- package/src/components/PhotoCard.astro +13 -1
- package/src/components/PostCard.astro +4 -1
- package/src/components/SakuraEffect.astro +126 -28
- package/src/components/Sidebar.astro +28 -7
- package/src/components/analytics/WebVitals.astro +72 -0
- package/src/i18n/en.ts +42 -0
- package/src/i18n/ja.ts +213 -0
- package/src/i18n/ko.ts +212 -0
- package/src/i18n/resolve.ts +10 -1
- package/src/i18n/ru.ts +213 -0
- package/src/i18n/zh-CN.ts +43 -0
- package/src/i18n/zh-TW.ts +208 -0
- package/src/index.ts +23 -0
- package/src/plugins/rehype.ts +76 -4
- package/src/routes/bangumi.astro +110 -0
- package/src/routes/friends.astro +10 -2
- package/src/routes/manifest.webmanifest.ts +33 -0
- package/src/routes/page/[page].astro +3 -0
- package/src/routes/search.astro +24 -0
- package/src/routes/sponsor.astro +17 -4
- package/src/routes/sw.js.ts +127 -0
- package/src/schema.ts +446 -45
- package/src/scripts/app.ts +805 -37
- package/src/styles/global.css +939 -18
- package/src/utils/gallery.ts +3 -1
- package/src/utils/image.ts +79 -0
- package/src/utils/lqip.ts +21 -12
- package/src/utils/thumb.ts +26 -0
- package/src/vite.ts +95 -8
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
---
|
|
2
|
+
import BaseLayout from "virtual:theme/components/BaseLayout.astro";
|
|
3
|
+
import config from "virtual:theme/config";
|
|
4
|
+
import { t } from "../i18n/index.js";
|
|
5
|
+
|
|
6
|
+
/* 番组追番页(§4.93):构建期从 bgm.tv v0 API 拉取用户「在看」动画收藏,
|
|
7
|
+
渲染为静态页。外部依赖 fail-soft:请求失败不中断构建,页面照常生成并提示。 */
|
|
8
|
+
|
|
9
|
+
interface BgmSubject {
|
|
10
|
+
id: number;
|
|
11
|
+
name: string;
|
|
12
|
+
name_cn?: string;
|
|
13
|
+
score?: number;
|
|
14
|
+
eps?: number;
|
|
15
|
+
images?: { common?: string; medium?: string; large?: string };
|
|
16
|
+
}
|
|
17
|
+
interface BgmCollection {
|
|
18
|
+
updated_at?: string;
|
|
19
|
+
subject: BgmSubject;
|
|
20
|
+
ep_status?: number;
|
|
21
|
+
rate?: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const LIMIT = 50;
|
|
25
|
+
/* 50 * 20 = 1000 条安全上限:防止分页异常(total 失真)导致构建挂死 */
|
|
26
|
+
const MAX_PAGES = 20;
|
|
27
|
+
const UA =
|
|
28
|
+
"astro-theme-orchid (+https://github.com/iwinder/qy-astro-theme-orchid)";
|
|
29
|
+
|
|
30
|
+
let items: BgmCollection[] = [];
|
|
31
|
+
let failed = false;
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
const headers = { "User-Agent": UA, Accept: "application/json" };
|
|
35
|
+
for (let offset = 0; offset < LIMIT * MAX_PAGES; offset += LIMIT) {
|
|
36
|
+
const url = `https://api.bgm.tv/v0/users/${config.bangumi.userId}/collections?subject_type=2&type=3&limit=${LIMIT}&offset=${offset}`;
|
|
37
|
+
const res = await fetch(url, {
|
|
38
|
+
headers,
|
|
39
|
+
signal: AbortSignal.timeout(10_000),
|
|
40
|
+
});
|
|
41
|
+
if (!res.ok) throw new Error(`bgm.tv responded ${res.status}`);
|
|
42
|
+
const page = (await res.json()) as { data?: BgmCollection[] };
|
|
43
|
+
const batch = page.data ?? [];
|
|
44
|
+
items = items.concat(batch);
|
|
45
|
+
if (batch.length < LIMIT) break;
|
|
46
|
+
}
|
|
47
|
+
/* 最近在看优先:按收藏更新时间倒序 */
|
|
48
|
+
items.sort((a, b) => (b.updated_at ?? "").localeCompare(a.updated_at ?? ""));
|
|
49
|
+
} catch {
|
|
50
|
+
failed = true;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/* 进度文案:eps 有值时 clamp 到总集数并显示 x/y,否则只显示已看集数 */
|
|
54
|
+
function progress(item: BgmCollection): string {
|
|
55
|
+
const eps = item.subject.eps ?? 0;
|
|
56
|
+
const watched = item.ep_status ?? 0;
|
|
57
|
+
return eps > 0
|
|
58
|
+
? t("epProgress", {
|
|
59
|
+
n: Math.min(watched, eps),
|
|
60
|
+
total: eps,
|
|
61
|
+
})
|
|
62
|
+
: t("epProgressOpen", { n: watched });
|
|
63
|
+
}
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
<BaseLayout title={config.bangumi.title} description={config.bangumi.description} page="bangumi">
|
|
67
|
+
<header class="archive-head reveal">
|
|
68
|
+
<h1 class="page-title">{config.bangumi.title}</h1>
|
|
69
|
+
{
|
|
70
|
+
config.bangumi.description && (
|
|
71
|
+
<p class="page-sub">{config.bangumi.description}</p>
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
</header>
|
|
75
|
+
|
|
76
|
+
{
|
|
77
|
+
items.length > 0 ? (
|
|
78
|
+
<div class="bangumi-grid reveal">
|
|
79
|
+
{items.map((item) => {
|
|
80
|
+
const s = item.subject;
|
|
81
|
+
const cover = s.images?.common ?? s.images?.medium ?? s.images?.large;
|
|
82
|
+
return (
|
|
83
|
+
<a
|
|
84
|
+
class="bangumi-card card"
|
|
85
|
+
href={`https://bgm.tv/subject/${s.id}`}
|
|
86
|
+
target="_blank"
|
|
87
|
+
rel="noopener nofollow"
|
|
88
|
+
>
|
|
89
|
+
<div class="bangumi-cover">
|
|
90
|
+
{cover && <img src={cover} alt={s.name_cn || s.name} loading="lazy" decoding="async" />}
|
|
91
|
+
{typeof s.score === "number" && s.score > 0 && (
|
|
92
|
+
<span class="bangumi-score">{s.score.toFixed(1)}</span>
|
|
93
|
+
)}
|
|
94
|
+
</div>
|
|
95
|
+
<div class="bangumi-title">{s.name_cn || s.name}</div>
|
|
96
|
+
<div class="bangumi-meta">
|
|
97
|
+
<span class="bangumi-progress">{progress(item)}</span>
|
|
98
|
+
{typeof item.rate === "number" && item.rate > 0 && (
|
|
99
|
+
<span class="bangumi-rate">{t("myRate", { n: item.rate })}</span>
|
|
100
|
+
)}
|
|
101
|
+
</div>
|
|
102
|
+
</a>
|
|
103
|
+
);
|
|
104
|
+
})}
|
|
105
|
+
</div>
|
|
106
|
+
) : (
|
|
107
|
+
<p class="tag-empty reveal">{failed ? t("bangumiFail") : t("bangumiEmpty")}</p>
|
|
108
|
+
)
|
|
109
|
+
}
|
|
110
|
+
</BaseLayout>
|
package/src/routes/friends.astro
CHANGED
|
@@ -3,6 +3,7 @@ import { getEntry, render } from "astro:content";
|
|
|
3
3
|
import BaseLayout from "virtual:theme/components/BaseLayout.astro";
|
|
4
4
|
import config from "virtual:theme/config";
|
|
5
5
|
import { t } from "../i18n/index.js";
|
|
6
|
+
import { resolveSiteImage } from "../utils/image.js";
|
|
6
7
|
|
|
7
8
|
// 数据源 = themeConfig.friends.list(决策 8:与侧栏友链 widget 同源)
|
|
8
9
|
const friends = config.friends.list
|
|
@@ -13,6 +14,13 @@ const friends = config.friends.list
|
|
|
13
14
|
: b.weight - a.weight,
|
|
14
15
|
);
|
|
15
16
|
|
|
17
|
+
// 友链头像三格式解析(§4.97):空串跳过(留空用字头像)
|
|
18
|
+
const friendImgs = await Promise.all(
|
|
19
|
+
friends.map((f) =>
|
|
20
|
+
f.image ? resolveSiteImage(f.image) : Promise.resolve(""),
|
|
21
|
+
),
|
|
22
|
+
);
|
|
23
|
+
|
|
16
24
|
// 可选 spec 正文(src/content/spec/friends.md),渲染在卡片列表上方
|
|
17
25
|
const entry = await getEntry("spec", "friends").catch(() => null);
|
|
18
26
|
const { Content } = entry ? await render(entry) : { Content: null };
|
|
@@ -37,11 +45,11 @@ const apply = config.friends.apply;
|
|
|
37
45
|
{
|
|
38
46
|
friends.length > 0 ? (
|
|
39
47
|
<div class="friend-grid reveal">
|
|
40
|
-
{friends.map((f) => (
|
|
48
|
+
{friends.map((f, i) => (
|
|
41
49
|
<a class="friend-card card" href={f.site} target="_blank" rel="noopener">
|
|
42
50
|
<div class="friend-avatar">
|
|
43
51
|
{f.image ? (
|
|
44
|
-
<img src={
|
|
52
|
+
<img src={friendImgs[i]} alt={t('avatarAlt', { name: f.title })} loading="lazy" />
|
|
45
53
|
) : (
|
|
46
54
|
<span class="avatar-fallback">{f.title.slice(0, 1)}</span>
|
|
47
55
|
)}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import config from "virtual:theme/config";
|
|
2
|
+
import type { APIRoute } from "astro";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* PWA manifest(§4.95):构建期产出 /manifest.webmanifest。
|
|
6
|
+
* - scope/start_url 由 site 根路径派生(主题假定部署在站点根路径);
|
|
7
|
+
* - name/description 留空回落站点 title/description,short_name 留空回落 name;
|
|
8
|
+
* - icons 按 pwa.icons 全字段映射(可安装性至少需要 192x192 + 512x512 各一张);
|
|
9
|
+
* - enabled=false 时本端点根本不注入(index.ts 门控)。
|
|
10
|
+
*/
|
|
11
|
+
export const GET: APIRoute = ({ site }) => {
|
|
12
|
+
const base = new URL("/", site ?? new URL("https://blog.example.com"))
|
|
13
|
+
.pathname;
|
|
14
|
+
const manifest = {
|
|
15
|
+
name: config.pwa.name || config.title,
|
|
16
|
+
short_name: config.pwa.shortName || config.pwa.name || config.title,
|
|
17
|
+
description: config.pwa.description || config.description,
|
|
18
|
+
start_url: base,
|
|
19
|
+
scope: base,
|
|
20
|
+
display: config.pwa.display,
|
|
21
|
+
background_color: config.pwa.backgroundColor,
|
|
22
|
+
theme_color: config.pwa.themeColor,
|
|
23
|
+
icons: config.pwa.icons.map((icon) => ({
|
|
24
|
+
src: icon.src,
|
|
25
|
+
sizes: icon.sizes,
|
|
26
|
+
type: icon.type,
|
|
27
|
+
purpose: icon.purpose,
|
|
28
|
+
})),
|
|
29
|
+
};
|
|
30
|
+
return new Response(JSON.stringify(manifest, null, 2), {
|
|
31
|
+
headers: { "Content-Type": "application/manifest+json; charset=utf-8" },
|
|
32
|
+
});
|
|
33
|
+
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
import { getCollection } from "astro:content";
|
|
3
3
|
import BaseLayout from "virtual:theme/components/BaseLayout.astro";
|
|
4
|
+
import CategoryBar from "virtual:theme/components/CategoryBar.astro";
|
|
4
5
|
import Pagination from "virtual:theme/components/Pagination.astro";
|
|
5
6
|
import PostCard from "virtual:theme/components/PostCard.astro";
|
|
6
7
|
import Sidebar from "virtual:theme/components/Sidebar.astro";
|
|
@@ -45,6 +46,8 @@ const pagePosts = await hydratePosts(
|
|
|
45
46
|
data-default-layout={config.postList.defaultLayout}
|
|
46
47
|
data-grid-col={config.postList.gridColumnWidth}
|
|
47
48
|
>
|
|
49
|
+
{/* 分类导航栏:与首页一致,分页页保留入口 */}
|
|
50
|
+
<CategoryBar />
|
|
48
51
|
{pagePosts.map((post) => <PostCard post={post} featured={post.data.pinned} />)}
|
|
49
52
|
<Pagination page={page} total={total} basePath="/" />
|
|
50
53
|
</div>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
---
|
|
2
|
+
// 独立搜索页(§4.104):静态外壳,查询与分页全部在客户端完成(app.ts「独立搜索页」段)。
|
|
3
|
+
// 结果来自 Pagefind 构建期索引;URL 携带 ?q= 关键词与 ?page= 页码,可直接到达与分享。
|
|
4
|
+
// 整页 pagefindIgnore:搜索页自身不进索引,避免搜出无内容空壳页。
|
|
5
|
+
import BaseLayout from "virtual:theme/components/BaseLayout.astro";
|
|
6
|
+
import { t } from "../i18n/index.js";
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
<BaseLayout title={t('search')} page="search" pagefindIgnore>
|
|
10
|
+
<header class="archive-head reveal">
|
|
11
|
+
<h1 class="page-title">{t('search')}</h1>
|
|
12
|
+
</header>
|
|
13
|
+
<div class="search-page reveal">
|
|
14
|
+
<div class="search-page-bar">
|
|
15
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>
|
|
16
|
+
<input id="searchPageInput" type="search" placeholder={t('search')} autocomplete="off" enterkeyhint="search" aria-label={t('search')} />
|
|
17
|
+
</div>
|
|
18
|
+
{/* 初始文案由 SSR 输出,客户端按查询状态改写(加载中/结果数/无结果/索引不可用) */}
|
|
19
|
+
<p class="search-page-meta" id="searchPageMeta">{t('searchTypeSomething')}</p>
|
|
20
|
+
<div class="search-page-results" id="searchPageResults"></div>
|
|
21
|
+
{/* 分页条复用列表页 .pagination 样式,内容由客户端按结果总数渲染 */}
|
|
22
|
+
<nav class="pagination" id="searchPagePager" aria-label={t('pagination')} hidden></nav>
|
|
23
|
+
</div>
|
|
24
|
+
</BaseLayout>
|
package/src/routes/sponsor.astro
CHANGED
|
@@ -2,10 +2,23 @@
|
|
|
2
2
|
import BaseLayout from "virtual:theme/components/BaseLayout.astro";
|
|
3
3
|
import config from "virtual:theme/config";
|
|
4
4
|
import { t } from "../i18n/index.js";
|
|
5
|
+
import { resolveSiteImage } from "../utils/image.js";
|
|
5
6
|
|
|
6
7
|
const cfg = config.sponsor;
|
|
7
8
|
const methods = cfg.methods.filter((m) => m.enabled);
|
|
8
9
|
const sponsors = cfg.sponsors;
|
|
10
|
+
|
|
11
|
+
// 收款码/打赏者头像三格式解析(§4.97):空串跳过
|
|
12
|
+
const methodImgs = await Promise.all(
|
|
13
|
+
methods.map((m) =>
|
|
14
|
+
m.image ? resolveSiteImage(m.image) : Promise.resolve(""),
|
|
15
|
+
),
|
|
16
|
+
);
|
|
17
|
+
const sponsorAvatars = await Promise.all(
|
|
18
|
+
sponsors.map((s) =>
|
|
19
|
+
s.avatar ? resolveSiteImage(s.avatar) : Promise.resolve(""),
|
|
20
|
+
),
|
|
21
|
+
);
|
|
9
22
|
---
|
|
10
23
|
|
|
11
24
|
<BaseLayout title={cfg.title} page="sponsor">
|
|
@@ -25,7 +38,7 @@ const sponsors = cfg.sponsors;
|
|
|
25
38
|
{
|
|
26
39
|
methods.length > 0 && (
|
|
27
40
|
<div class="sponsor-grid reveal">
|
|
28
|
-
{methods.map((m) => (
|
|
41
|
+
{methods.map((m, i) => (
|
|
29
42
|
<div class="sponsor-method card">
|
|
30
43
|
<div class="sm-name">
|
|
31
44
|
{m.icon && <span class="sm-icon">{m.icon}</span>}
|
|
@@ -33,7 +46,7 @@ const sponsors = cfg.sponsors;
|
|
|
33
46
|
</div>
|
|
34
47
|
{m.description && <p class="sm-desc">{m.description}</p>}
|
|
35
48
|
{m.image ? (
|
|
36
|
-
<img class="sponsor-qr" src={
|
|
49
|
+
<img class="sponsor-qr" src={methodImgs[i]} alt={t('qrAlt', { name: m.name })} loading="lazy" />
|
|
37
50
|
) : m.link ? (
|
|
38
51
|
<a class="btn sm-link" href={m.link} target="_blank" rel="noopener">
|
|
39
52
|
{t('goSponsor')}
|
|
@@ -58,10 +71,10 @@ const sponsors = cfg.sponsors;
|
|
|
58
71
|
</h3>
|
|
59
72
|
{sponsors.length > 0 ? (
|
|
60
73
|
<div class="sponsor-list">
|
|
61
|
-
{sponsors.map((s) => (
|
|
74
|
+
{sponsors.map((s, i) => (
|
|
62
75
|
<div class="sponsor-item">
|
|
63
76
|
{s.avatar ? (
|
|
64
|
-
<img class="sponsor-item-avatar" src={
|
|
77
|
+
<img class="sponsor-item-avatar" src={sponsorAvatars[i]} alt={s.name} loading="lazy" />
|
|
65
78
|
) : (
|
|
66
79
|
<span class="sponsor-item-avatar avatar-fallback">{s.name.slice(0, 1)}</span>
|
|
67
80
|
)}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import type { APIRoute } from "astro";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* PWA Service Worker(§4.95):构建期生成 /sw.js 文本,手写、零客户端运行时依赖。
|
|
5
|
+
* - 缓存名带构建号(DEV 固定 "dev"):新构建上线后旧缓存整体作废,activate 全清;
|
|
6
|
+
* - 三条策略:① 导航请求 network-first,成功回填缓存,离线回落自身缓存、再兜底
|
|
7
|
+
* 预缓存的首页;② /_astro/ 产物带内容哈希,cache-first;③ 其余同源 GET 走
|
|
8
|
+
* stale-while-revalidate,缓存桶超 60 条删最早一条防膨胀;
|
|
9
|
+
* - 非 GET、跨域、/api/(随机数据端点)、/og/(分享图 PNG 较大)一律直连不缓存;
|
|
10
|
+
* - install 预缓存首页作离线兜底(不另造 offline 页),skipWaiting 即刻接管;
|
|
11
|
+
* activate 清全部旧缓存并 clients.claim()。
|
|
12
|
+
* SW 文本内不使用模板字符串(避免与本文件 TS 模板嵌套转义),常量经 JSON.stringify 注入。
|
|
13
|
+
*/
|
|
14
|
+
export const GET: APIRoute = ({ site }) => {
|
|
15
|
+
const base = new URL("/", site ?? new URL("https://blog.example.com"))
|
|
16
|
+
.pathname;
|
|
17
|
+
const cacheName =
|
|
18
|
+
"orchid-pwa-" + (import.meta.env.DEV ? "dev" : Date.now().toString(36));
|
|
19
|
+
const sw = `// 由 astro-theme-orchid 构建期生成(§4.95),请勿手改
|
|
20
|
+
const CACHE = ${JSON.stringify(cacheName)};
|
|
21
|
+
const BASE = ${JSON.stringify(base)};
|
|
22
|
+
const SCOPE_MAX = 60;
|
|
23
|
+
|
|
24
|
+
self.addEventListener("install", function (event) {
|
|
25
|
+
event.waitUntil(
|
|
26
|
+
caches
|
|
27
|
+
.open(CACHE)
|
|
28
|
+
.then(function (cache) {
|
|
29
|
+
return cache.add(new Request(BASE, { cache: "reload" }));
|
|
30
|
+
})
|
|
31
|
+
.then(function () {
|
|
32
|
+
return self.skipWaiting();
|
|
33
|
+
})
|
|
34
|
+
);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
self.addEventListener("activate", function (event) {
|
|
38
|
+
event.waitUntil(
|
|
39
|
+
caches
|
|
40
|
+
.keys()
|
|
41
|
+
.then(function (keys) {
|
|
42
|
+
return Promise.all(
|
|
43
|
+
keys
|
|
44
|
+
.filter(function (key) {
|
|
45
|
+
return key !== CACHE;
|
|
46
|
+
})
|
|
47
|
+
.map(function (key) {
|
|
48
|
+
return caches.delete(key);
|
|
49
|
+
})
|
|
50
|
+
);
|
|
51
|
+
})
|
|
52
|
+
.then(function () {
|
|
53
|
+
return self.clients.claim();
|
|
54
|
+
})
|
|
55
|
+
);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
self.addEventListener("fetch", function (event) {
|
|
59
|
+
var req = event.request;
|
|
60
|
+
if (req.method !== "GET") return;
|
|
61
|
+
var url = new URL(req.url);
|
|
62
|
+
if (url.origin !== self.location.origin) return;
|
|
63
|
+
var path = url.pathname;
|
|
64
|
+
if (path.indexOf(BASE + "api/") === 0 || path.indexOf(BASE + "og/") === 0) return;
|
|
65
|
+
|
|
66
|
+
if (req.mode === "navigate") {
|
|
67
|
+
event.respondWith(
|
|
68
|
+
fetch(req)
|
|
69
|
+
.then(function (res) {
|
|
70
|
+
var copy = res.clone();
|
|
71
|
+
caches.open(CACHE).then(function (cache) {
|
|
72
|
+
cache.put(req, copy);
|
|
73
|
+
});
|
|
74
|
+
return res;
|
|
75
|
+
})
|
|
76
|
+
.catch(function () {
|
|
77
|
+
return caches.match(req).then(function (hit) {
|
|
78
|
+
return hit || caches.match(BASE);
|
|
79
|
+
});
|
|
80
|
+
})
|
|
81
|
+
);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (path.indexOf(BASE + "_astro/") === 0) {
|
|
86
|
+
event.respondWith(
|
|
87
|
+
caches.match(req).then(function (hit) {
|
|
88
|
+
return hit || fetch(req);
|
|
89
|
+
})
|
|
90
|
+
);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
event.respondWith(
|
|
95
|
+
caches.match(req).then(function (hit) {
|
|
96
|
+
var network = fetch(req)
|
|
97
|
+
.then(function (res) {
|
|
98
|
+
if (res && res.ok) {
|
|
99
|
+
var copy = res.clone();
|
|
100
|
+
caches.open(CACHE).then(function (cache) {
|
|
101
|
+
cache.put(req, copy).then(function () {
|
|
102
|
+
caches.open(CACHE).then(function (bucket) {
|
|
103
|
+
bucket.keys().then(function (keys) {
|
|
104
|
+
if (keys.length > SCOPE_MAX) bucket.delete(keys[0]);
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
return res;
|
|
111
|
+
})
|
|
112
|
+
.catch(function () {
|
|
113
|
+
return hit;
|
|
114
|
+
});
|
|
115
|
+
if (hit) {
|
|
116
|
+
event.waitUntil(network);
|
|
117
|
+
return hit;
|
|
118
|
+
}
|
|
119
|
+
return network;
|
|
120
|
+
})
|
|
121
|
+
);
|
|
122
|
+
});
|
|
123
|
+
`;
|
|
124
|
+
return new Response(sw, {
|
|
125
|
+
headers: { "Content-Type": "text/javascript; charset=utf-8" },
|
|
126
|
+
});
|
|
127
|
+
};
|