@jant/core 0.3.20 → 0.3.22
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/dist/app.js +60 -17
- package/dist/index.js +8 -0
- package/dist/lib/feed.js +112 -0
- package/dist/lib/navigation.js +9 -9
- package/dist/lib/render.js +48 -0
- package/dist/lib/theme-components.js +18 -18
- package/dist/lib/view.js +228 -0
- package/dist/routes/api/timeline.js +20 -16
- package/dist/routes/dash/collections.js +38 -10
- package/dist/routes/dash/navigation.js +22 -8
- package/dist/routes/dash/redirects.js +19 -5
- package/dist/routes/dash/settings.js +57 -15
- package/dist/routes/feed/rss.js +34 -78
- package/dist/routes/feed/sitemap.js +11 -26
- package/dist/routes/pages/archive.js +18 -195
- package/dist/routes/pages/collection.js +16 -70
- package/dist/routes/pages/home.js +25 -47
- package/dist/routes/pages/page.js +15 -27
- package/dist/routes/pages/post.js +25 -79
- package/dist/routes/pages/search.js +20 -130
- package/dist/theme/components/MediaGallery.js +10 -10
- package/dist/theme/components/PageForm.js +22 -8
- package/dist/theme/components/PostForm.js +22 -8
- package/dist/theme/components/index.js +1 -1
- package/dist/theme/components/timeline/ArticleCard.js +7 -11
- package/dist/theme/components/timeline/ImageCard.js +10 -13
- package/dist/theme/components/timeline/LinkCard.js +4 -7
- package/dist/theme/components/timeline/NoteCard.js +5 -8
- package/dist/theme/components/timeline/QuoteCard.js +3 -6
- package/dist/theme/components/timeline/ThreadPreview.js +9 -10
- package/dist/theme/components/timeline/TimelineFeed.js +8 -5
- package/dist/theme/components/timeline/TimelineItem.js +22 -2
- package/dist/theme/components/timeline/index.js +1 -1
- package/dist/theme/index.js +6 -3
- package/dist/theme/layouts/SiteLayout.js +10 -39
- package/dist/theme/pages/ArchivePage.js +157 -0
- package/dist/theme/pages/CollectionPage.js +63 -0
- package/dist/theme/pages/HomePage.js +26 -0
- package/dist/theme/pages/PostPage.js +48 -0
- package/dist/theme/pages/SearchPage.js +120 -0
- package/dist/theme/pages/SinglePage.js +23 -0
- package/dist/theme/pages/index.js +11 -0
- package/package.json +2 -1
- package/src/app.tsx +48 -17
- package/src/i18n/locales/en.po +171 -147
- package/src/i18n/locales/zh-Hans.po +171 -147
- package/src/i18n/locales/zh-Hant.po +171 -147
- package/src/index.ts +51 -2
- package/src/lib/__tests__/theme-components.test.ts +33 -14
- package/src/lib/__tests__/view.test.ts +375 -0
- package/src/lib/feed.ts +148 -0
- package/src/lib/navigation.ts +11 -11
- package/src/lib/render.tsx +67 -0
- package/src/lib/theme-components.ts +27 -35
- package/src/lib/view.ts +318 -0
- package/src/routes/api/__tests__/timeline.test.ts +3 -3
- package/src/routes/api/timeline.tsx +32 -25
- package/src/routes/dash/collections.tsx +30 -10
- package/src/routes/dash/navigation.tsx +20 -10
- package/src/routes/dash/redirects.tsx +15 -5
- package/src/routes/dash/settings.tsx +53 -15
- package/src/routes/feed/rss.ts +47 -94
- package/src/routes/feed/sitemap.ts +8 -30
- package/src/routes/pages/archive.tsx +24 -209
- package/src/routes/pages/collection.tsx +19 -75
- package/src/routes/pages/home.tsx +42 -76
- package/src/routes/pages/page.tsx +17 -28
- package/src/routes/pages/post.tsx +28 -86
- package/src/routes/pages/search.tsx +29 -151
- package/src/services/search.ts +2 -8
- package/src/theme/components/MediaGallery.tsx +12 -12
- package/src/theme/components/PageForm.tsx +20 -10
- package/src/theme/components/PostForm.tsx +20 -10
- package/src/theme/components/index.ts +1 -0
- package/src/theme/components/timeline/ArticleCard.tsx +7 -19
- package/src/theme/components/timeline/ImageCard.tsx +10 -20
- package/src/theme/components/timeline/LinkCard.tsx +4 -11
- package/src/theme/components/timeline/NoteCard.tsx +5 -12
- package/src/theme/components/timeline/QuoteCard.tsx +3 -10
- package/src/theme/components/timeline/ThreadPreview.tsx +5 -5
- package/src/theme/components/timeline/TimelineFeed.tsx +7 -3
- package/src/theme/components/timeline/TimelineItem.tsx +43 -4
- package/src/theme/components/timeline/index.ts +1 -1
- package/src/theme/index.ts +7 -3
- package/src/theme/layouts/SiteLayout.tsx +25 -77
- package/src/theme/layouts/index.ts +2 -1
- package/src/theme/pages/ArchivePage.tsx +160 -0
- package/src/theme/pages/CollectionPage.tsx +60 -0
- package/src/theme/pages/HomePage.tsx +42 -0
- package/src/theme/pages/PostPage.tsx +44 -0
- package/src/theme/pages/SearchPage.tsx +128 -0
- package/src/theme/pages/SinglePage.tsx +24 -0
- package/src/theme/pages/index.ts +13 -0
- package/src/types.ts +262 -38
|
@@ -106,6 +106,7 @@ function NewCollectionContent() {
|
|
|
106
106
|
<form
|
|
107
107
|
data-signals="{title: '', path: '', description: ''}"
|
|
108
108
|
data-on:submit__prevent="@post('/dash/collections')"
|
|
109
|
+
data-indicator="_loading"
|
|
109
110
|
class="flex flex-col gap-4 max-w-lg"
|
|
110
111
|
>
|
|
111
112
|
<div class="field">
|
|
@@ -166,11 +167,20 @@ function NewCollectionContent() {
|
|
|
166
167
|
</div>
|
|
167
168
|
|
|
168
169
|
<div class="flex gap-2">
|
|
169
|
-
<button type="submit" class="btn">
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
170
|
+
<button type="submit" class="btn" data-attr-disabled="$_loading">
|
|
171
|
+
<span data-show="!$_loading">
|
|
172
|
+
{t({
|
|
173
|
+
message: "Create Collection",
|
|
174
|
+
comment: "@context: Button to save new collection",
|
|
175
|
+
})}
|
|
176
|
+
</span>
|
|
177
|
+
<span data-show="$_loading">
|
|
178
|
+
{t({
|
|
179
|
+
message: "Processing...",
|
|
180
|
+
comment:
|
|
181
|
+
"@context: Loading text shown on submit button while request is in progress",
|
|
182
|
+
})}
|
|
183
|
+
</span>
|
|
174
184
|
</button>
|
|
175
185
|
<a href="/dash/collections" class="btn-outline">
|
|
176
186
|
{t({
|
|
@@ -297,6 +307,7 @@ function EditCollectionContent({ collection }: { collection: Collection }) {
|
|
|
297
307
|
<form
|
|
298
308
|
data-signals={signals}
|
|
299
309
|
data-on:submit__prevent={`@post('/dash/collections/${collection.id}')`}
|
|
310
|
+
data-indicator="_loading"
|
|
300
311
|
class="flex flex-col gap-4 max-w-lg"
|
|
301
312
|
>
|
|
302
313
|
<div class="field">
|
|
@@ -335,11 +346,20 @@ function EditCollectionContent({ collection }: { collection: Collection }) {
|
|
|
335
346
|
</div>
|
|
336
347
|
|
|
337
348
|
<div class="flex gap-2">
|
|
338
|
-
<button type="submit" class="btn">
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
349
|
+
<button type="submit" class="btn" data-attr-disabled="$_loading">
|
|
350
|
+
<span data-show="!$_loading">
|
|
351
|
+
{t({
|
|
352
|
+
message: "Update Collection",
|
|
353
|
+
comment: "@context: Button to save collection changes",
|
|
354
|
+
})}
|
|
355
|
+
</span>
|
|
356
|
+
<span data-show="$_loading">
|
|
357
|
+
{t({
|
|
358
|
+
message: "Processing...",
|
|
359
|
+
comment:
|
|
360
|
+
"@context: Loading text shown on submit button while request is in progress",
|
|
361
|
+
})}
|
|
362
|
+
</span>
|
|
343
363
|
</button>
|
|
344
364
|
<a href={`/dash/collections/${collection.id}`} class="btn-outline">
|
|
345
365
|
{t({
|
|
@@ -119,6 +119,7 @@ function NavigationFormContent({
|
|
|
119
119
|
<form
|
|
120
120
|
data-signals={signals}
|
|
121
121
|
data-on:submit__prevent={`@post('${action}')`}
|
|
122
|
+
data-indicator="_loading"
|
|
122
123
|
class="flex flex-col gap-4 max-w-lg"
|
|
123
124
|
>
|
|
124
125
|
<div class="field">
|
|
@@ -167,16 +168,25 @@ function NavigationFormContent({
|
|
|
167
168
|
</div>
|
|
168
169
|
|
|
169
170
|
<div class="flex gap-2">
|
|
170
|
-
<button type="submit" class="btn">
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
171
|
+
<button type="submit" class="btn" data-attr-disabled="$_loading">
|
|
172
|
+
<span data-show="!$_loading">
|
|
173
|
+
{isEdit
|
|
174
|
+
? t({
|
|
175
|
+
message: "Save Changes",
|
|
176
|
+
comment: "@context: Button to save edited navigation link",
|
|
177
|
+
})
|
|
178
|
+
: t({
|
|
179
|
+
message: "Create Link",
|
|
180
|
+
comment: "@context: Button to save new navigation link",
|
|
181
|
+
})}
|
|
182
|
+
</span>
|
|
183
|
+
<span data-show="$_loading">
|
|
184
|
+
{t({
|
|
185
|
+
message: "Processing...",
|
|
186
|
+
comment:
|
|
187
|
+
"@context: Loading text shown on submit button while request is in progress",
|
|
188
|
+
})}
|
|
189
|
+
</span>
|
|
180
190
|
</button>
|
|
181
191
|
<a href="/dash/navigation" class="btn-outline">
|
|
182
192
|
{t({
|
|
@@ -90,6 +90,7 @@ function NewRedirectContent() {
|
|
|
90
90
|
<form
|
|
91
91
|
data-signals="{fromPath: '', toPath: '', type: '301'}"
|
|
92
92
|
data-on:submit__prevent="@post('/dash/redirects')"
|
|
93
|
+
data-indicator="_loading"
|
|
93
94
|
class="flex flex-col gap-4 max-w-lg"
|
|
94
95
|
>
|
|
95
96
|
<div class="field">
|
|
@@ -157,11 +158,20 @@ function NewRedirectContent() {
|
|
|
157
158
|
</div>
|
|
158
159
|
|
|
159
160
|
<div class="flex gap-2">
|
|
160
|
-
<button type="submit" class="btn">
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
161
|
+
<button type="submit" class="btn" data-attr-disabled="$_loading">
|
|
162
|
+
<span data-show="!$_loading">
|
|
163
|
+
{t({
|
|
164
|
+
message: "Create Redirect",
|
|
165
|
+
comment: "@context: Button to save new redirect",
|
|
166
|
+
})}
|
|
167
|
+
</span>
|
|
168
|
+
<span data-show="$_loading">
|
|
169
|
+
{t({
|
|
170
|
+
message: "Processing...",
|
|
171
|
+
comment:
|
|
172
|
+
"@context: Loading text shown on submit button while request is in progress",
|
|
173
|
+
})}
|
|
174
|
+
</span>
|
|
165
175
|
</button>
|
|
166
176
|
<a href="/dash/redirects" class="btn-outline">
|
|
167
177
|
{t({
|
|
@@ -123,6 +123,7 @@ function GeneralContent({
|
|
|
123
123
|
<form
|
|
124
124
|
data-signals={generalSignals}
|
|
125
125
|
data-on:submit__prevent="@post('/dash/settings')"
|
|
126
|
+
data-indicator="_loading"
|
|
126
127
|
>
|
|
127
128
|
<div class="card">
|
|
128
129
|
<header>
|
|
@@ -188,11 +189,20 @@ function GeneralContent({
|
|
|
188
189
|
</section>
|
|
189
190
|
</div>
|
|
190
191
|
|
|
191
|
-
<button type="submit" class="btn mt-4">
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
192
|
+
<button type="submit" class="btn mt-4" data-attr-disabled="$_loading">
|
|
193
|
+
<span data-show="!$_loading">
|
|
194
|
+
{t({
|
|
195
|
+
message: "Save Settings",
|
|
196
|
+
comment: "@context: Button to save settings",
|
|
197
|
+
})}
|
|
198
|
+
</span>
|
|
199
|
+
<span data-show="$_loading">
|
|
200
|
+
{t({
|
|
201
|
+
message: "Processing...",
|
|
202
|
+
comment:
|
|
203
|
+
"@context: Loading text shown on submit button while request is in progress",
|
|
204
|
+
})}
|
|
205
|
+
</span>
|
|
196
206
|
</button>
|
|
197
207
|
</form>
|
|
198
208
|
</div>
|
|
@@ -346,6 +356,7 @@ function AccountContent({ userName }: { userName: string }) {
|
|
|
346
356
|
<form
|
|
347
357
|
data-signals={profileSignals}
|
|
348
358
|
data-on:submit__prevent="@post('/dash/settings/account')"
|
|
359
|
+
data-indicator="_profileLoading"
|
|
349
360
|
>
|
|
350
361
|
<div class="card">
|
|
351
362
|
<header>
|
|
@@ -374,17 +385,31 @@ function AccountContent({ userName }: { userName: string }) {
|
|
|
374
385
|
</section>
|
|
375
386
|
</div>
|
|
376
387
|
|
|
377
|
-
<button
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
388
|
+
<button
|
|
389
|
+
type="submit"
|
|
390
|
+
class="btn mt-4"
|
|
391
|
+
data-attr-disabled="$_profileLoading"
|
|
392
|
+
>
|
|
393
|
+
<span data-show="!$_profileLoading">
|
|
394
|
+
{t({
|
|
395
|
+
message: "Save Profile",
|
|
396
|
+
comment: "@context: Button to save profile",
|
|
397
|
+
})}
|
|
398
|
+
</span>
|
|
399
|
+
<span data-show="$_profileLoading">
|
|
400
|
+
{t({
|
|
401
|
+
message: "Processing...",
|
|
402
|
+
comment:
|
|
403
|
+
"@context: Loading text shown on submit button while request is in progress",
|
|
404
|
+
})}
|
|
405
|
+
</span>
|
|
382
406
|
</button>
|
|
383
407
|
</form>
|
|
384
408
|
|
|
385
409
|
<form
|
|
386
410
|
data-signals="{currentPassword: '', newPassword: '', confirmPassword: ''}"
|
|
387
411
|
data-on:submit__prevent="@post('/dash/settings/password')"
|
|
412
|
+
data-indicator="_passwordLoading"
|
|
388
413
|
>
|
|
389
414
|
<div class="card">
|
|
390
415
|
<header>
|
|
@@ -448,11 +473,24 @@ function AccountContent({ userName }: { userName: string }) {
|
|
|
448
473
|
</section>
|
|
449
474
|
</div>
|
|
450
475
|
|
|
451
|
-
<button
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
476
|
+
<button
|
|
477
|
+
type="submit"
|
|
478
|
+
class="btn mt-4"
|
|
479
|
+
data-attr-disabled="$_passwordLoading"
|
|
480
|
+
>
|
|
481
|
+
<span data-show="!$_passwordLoading">
|
|
482
|
+
{t({
|
|
483
|
+
message: "Change Password",
|
|
484
|
+
comment: "@context: Button to change password",
|
|
485
|
+
})}
|
|
486
|
+
</span>
|
|
487
|
+
<span data-show="$_passwordLoading">
|
|
488
|
+
{t({
|
|
489
|
+
message: "Processing...",
|
|
490
|
+
comment:
|
|
491
|
+
"@context: Loading text shown on submit button while request is in progress",
|
|
492
|
+
})}
|
|
493
|
+
</span>
|
|
456
494
|
</button>
|
|
457
495
|
</form>
|
|
458
496
|
</div>
|
package/src/routes/feed/rss.ts
CHANGED
|
@@ -3,24 +3,27 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { Hono } from "hono";
|
|
6
|
-
import type {
|
|
6
|
+
import type { Context } from "hono";
|
|
7
|
+
import type { Bindings, FeedData } from "../../types.js";
|
|
7
8
|
import type { AppVariables } from "../../app.js";
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
import {
|
|
9
|
+
import { defaultRssRenderer, defaultAtomRenderer } from "../../lib/feed.js";
|
|
10
|
+
import { getSiteLanguage } from "../../lib/config.js";
|
|
11
|
+
import { buildMediaMap } from "../../lib/media-helpers.js";
|
|
12
|
+
import { createMediaContext, toPostViews } from "../../lib/view.js";
|
|
11
13
|
|
|
12
14
|
type Env = { Bindings: Bindings; Variables: AppVariables };
|
|
13
15
|
|
|
14
16
|
export const rssRoutes = new Hono<Env>();
|
|
15
17
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
+
/**
|
|
19
|
+
* Build FeedData from the Hono context.
|
|
20
|
+
*/
|
|
21
|
+
async function buildFeedData(c: Context<Env>): Promise<FeedData> {
|
|
18
22
|
const all = await c.var.services.settings.getAll();
|
|
19
23
|
const siteName = all["SITE_NAME"] ?? "Jant";
|
|
20
24
|
const siteDescription = all["SITE_DESCRIPTION"] ?? "";
|
|
21
25
|
const siteUrl = c.env.SITE_URL;
|
|
22
|
-
const
|
|
23
|
-
const s3PublicUrl = c.env.S3_PUBLIC_URL;
|
|
26
|
+
const siteLanguage = await getSiteLanguage(c);
|
|
24
27
|
|
|
25
28
|
const posts = await c.var.services.posts.list({
|
|
26
29
|
visibility: ["featured", "quiet"],
|
|
@@ -29,45 +32,41 @@ rssRoutes.get("/", async (c) => {
|
|
|
29
32
|
|
|
30
33
|
// Batch load media for enclosures
|
|
31
34
|
const postIds = posts.map((p) => p.id);
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
35
|
+
const rawMediaMap = await c.var.services.media.getByPostIds(postIds);
|
|
36
|
+
const mediaCtx = createMediaContext(c);
|
|
37
|
+
const mediaMap = buildMediaMap(
|
|
38
|
+
rawMediaMap,
|
|
39
|
+
mediaCtx.r2PublicUrl,
|
|
40
|
+
mediaCtx.imageTransformUrl,
|
|
41
|
+
mediaCtx.s3PublicUrl,
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
// Transform to PostView[] with media
|
|
45
|
+
const postViews = toPostViews(
|
|
46
|
+
posts.map((p) => ({
|
|
47
|
+
...p,
|
|
48
|
+
mediaAttachments: mediaMap.get(p.id) ?? [],
|
|
49
|
+
})),
|
|
50
|
+
mediaCtx,
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
siteName,
|
|
55
|
+
siteDescription,
|
|
56
|
+
siteUrl,
|
|
57
|
+
siteLanguage,
|
|
58
|
+
posts: postViews,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
46
61
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
<link>${link}</link>
|
|
51
|
-
<guid isPermaLink="true">${link}</guid>
|
|
52
|
-
<pubDate>${pubDate}</pubDate>
|
|
53
|
-
<description><![CDATA[${post.contentHtml || ""}]]></description>${enclosure}
|
|
54
|
-
</item>`;
|
|
55
|
-
})
|
|
56
|
-
.join("");
|
|
62
|
+
// RSS 2.0 Feed - main feed at /feed
|
|
63
|
+
rssRoutes.get("/", async (c) => {
|
|
64
|
+
const feedData = await buildFeedData(c);
|
|
57
65
|
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
<channel>
|
|
61
|
-
<title>${escapeXml(siteName)}</title>
|
|
62
|
-
<link>${siteUrl}</link>
|
|
63
|
-
<description>${escapeXml(siteDescription)}</description>
|
|
64
|
-
<language>en</language>
|
|
65
|
-
<atom:link href="${siteUrl}/feed" rel="self" type="application/rss+xml"/>
|
|
66
|
-
${items}
|
|
67
|
-
</channel>
|
|
68
|
-
</rss>`;
|
|
66
|
+
const renderer = c.var.config.theme?.feed?.rss ?? defaultRssRenderer;
|
|
67
|
+
const xml = renderer(feedData);
|
|
69
68
|
|
|
70
|
-
return new Response(
|
|
69
|
+
return new Response(xml, {
|
|
71
70
|
headers: {
|
|
72
71
|
"Content-Type": "application/rss+xml; charset=utf-8",
|
|
73
72
|
},
|
|
@@ -76,60 +75,14 @@ rssRoutes.get("/", async (c) => {
|
|
|
76
75
|
|
|
77
76
|
// Atom Feed
|
|
78
77
|
rssRoutes.get("/atom.xml", async (c) => {
|
|
79
|
-
const
|
|
80
|
-
const siteName = all["SITE_NAME"] ?? "Jant";
|
|
81
|
-
const siteDescription = all["SITE_DESCRIPTION"] ?? "";
|
|
82
|
-
const siteUrl = c.env.SITE_URL;
|
|
83
|
-
|
|
84
|
-
const posts = await c.var.services.posts.list({
|
|
85
|
-
visibility: ["featured", "quiet"],
|
|
86
|
-
limit: 50,
|
|
87
|
-
});
|
|
78
|
+
const feedData = await buildFeedData(c);
|
|
88
79
|
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
const link = `${siteUrl}/p/${sqid.encode(post.id)}`;
|
|
92
|
-
const title = post.title || `Post #${post.id}`;
|
|
93
|
-
const updated = time.toISOString(post.updatedAt);
|
|
94
|
-
const published = time.toISOString(post.publishedAt);
|
|
80
|
+
const renderer = c.var.config.theme?.feed?.atom ?? defaultAtomRenderer;
|
|
81
|
+
const xml = renderer(feedData);
|
|
95
82
|
|
|
96
|
-
|
|
97
|
-
<entry>
|
|
98
|
-
<title>${escapeXml(title)}</title>
|
|
99
|
-
<link href="${link}" rel="alternate"/>
|
|
100
|
-
<id>${link}</id>
|
|
101
|
-
<published>${published}</published>
|
|
102
|
-
<updated>${updated}</updated>
|
|
103
|
-
<content type="html"><![CDATA[${post.contentHtml || ""}]]></content>
|
|
104
|
-
</entry>`;
|
|
105
|
-
})
|
|
106
|
-
.join("");
|
|
107
|
-
|
|
108
|
-
const now = time.toISOString(time.now());
|
|
109
|
-
|
|
110
|
-
const atom = `<?xml version="1.0" encoding="UTF-8"?>
|
|
111
|
-
<feed xmlns="http://www.w3.org/2005/Atom">
|
|
112
|
-
<title>${escapeXml(siteName)}</title>
|
|
113
|
-
<subtitle>${escapeXml(siteDescription)}</subtitle>
|
|
114
|
-
<link href="${siteUrl}" rel="alternate"/>
|
|
115
|
-
<link href="${siteUrl}/feed/atom.xml" rel="self"/>
|
|
116
|
-
<id>${siteUrl}/</id>
|
|
117
|
-
<updated>${now}</updated>
|
|
118
|
-
${entries}
|
|
119
|
-
</feed>`;
|
|
120
|
-
|
|
121
|
-
return new Response(atom, {
|
|
83
|
+
return new Response(xml, {
|
|
122
84
|
headers: {
|
|
123
85
|
"Content-Type": "application/atom+xml; charset=utf-8",
|
|
124
86
|
},
|
|
125
87
|
});
|
|
126
88
|
});
|
|
127
|
-
|
|
128
|
-
function escapeXml(str: string): string {
|
|
129
|
-
return str
|
|
130
|
-
.replace(/&/g, "&")
|
|
131
|
-
.replace(/</g, "<")
|
|
132
|
-
.replace(/>/g, ">")
|
|
133
|
-
.replace(/"/g, """)
|
|
134
|
-
.replace(/'/g, "'");
|
|
135
|
-
}
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
import { Hono } from "hono";
|
|
6
6
|
import type { Bindings } from "../../types.js";
|
|
7
7
|
import type { AppVariables } from "../../app.js";
|
|
8
|
-
import
|
|
9
|
-
import
|
|
8
|
+
import { defaultSitemapRenderer } from "../../lib/feed.js";
|
|
9
|
+
import { createMediaContext, toPostViewsFromPosts } from "../../lib/view.js";
|
|
10
10
|
|
|
11
11
|
type Env = { Bindings: Bindings; Variables: AppVariables };
|
|
12
12
|
|
|
@@ -21,36 +21,14 @@ sitemapRoutes.get("/sitemap.xml", async (c) => {
|
|
|
21
21
|
limit: 1000,
|
|
22
22
|
});
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
const lastmod = time.toISOString(post.updatedAt).split("T")[0];
|
|
28
|
-
const priority = post.visibility === "featured" ? "0.8" : "0.6";
|
|
24
|
+
// Transform to PostView[]
|
|
25
|
+
const mediaCtx = createMediaContext(c);
|
|
26
|
+
const postViews = toPostViewsFromPosts(posts, mediaCtx);
|
|
29
27
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
<loc>${loc}</loc>
|
|
33
|
-
<lastmod>${lastmod}</lastmod>
|
|
34
|
-
<priority>${priority}</priority>
|
|
35
|
-
</url>`;
|
|
36
|
-
})
|
|
37
|
-
.join("");
|
|
28
|
+
const renderer = c.var.config.theme?.feed?.sitemap ?? defaultSitemapRenderer;
|
|
29
|
+
const xml = renderer({ siteUrl, posts: postViews });
|
|
38
30
|
|
|
39
|
-
|
|
40
|
-
const homepageUrl = `
|
|
41
|
-
<url>
|
|
42
|
-
<loc>${siteUrl}/</loc>
|
|
43
|
-
<priority>1.0</priority>
|
|
44
|
-
<changefreq>daily</changefreq>
|
|
45
|
-
</url>`;
|
|
46
|
-
|
|
47
|
-
const sitemap = `<?xml version="1.0" encoding="UTF-8"?>
|
|
48
|
-
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
49
|
-
${homepageUrl}
|
|
50
|
-
${urls}
|
|
51
|
-
</urlset>`;
|
|
52
|
-
|
|
53
|
-
return new Response(sitemap, {
|
|
31
|
+
return new Response(xml, {
|
|
54
32
|
headers: {
|
|
55
33
|
"Content-Type": "application/xml; charset=utf-8",
|
|
56
34
|
},
|