@levino/shipyard-blog 0.7.4 → 0.7.5
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/astro/BlogArchive.astro +16 -22
- package/astro/BlogAuthorPage.astro +19 -139
- package/astro/BlogAuthorsIndex.astro +15 -37
- package/astro/BlogEntry.astro +14 -84
- package/astro/BlogIndex.astro +31 -40
- package/astro/BlogIndexPaginated.astro +23 -56
- package/astro/BlogTagPage.astro +17 -66
- package/astro/BlogTagsIndex.astro +17 -22
- package/astro/Layout.astro +10 -3
- package/astro/pages/BlogArchive.astro +22 -0
- package/astro/pages/BlogAuthorPage.astro +30 -0
- package/astro/pages/BlogAuthorsIndex.astro +23 -0
- package/astro/pages/BlogEntry.astro +36 -0
- package/astro/pages/BlogIndex.astro +22 -0
- package/astro/pages/BlogIndexPaginated.astro +29 -0
- package/astro/pages/BlogTagPage.astro +24 -0
- package/astro/pages/BlogTagsIndex.astro +22 -0
- package/astro/pages/feeds/atom.xml.ts +28 -0
- package/astro/pages/feeds/feed.json.ts +28 -0
- package/astro/pages/feeds/rss.xml.ts +28 -0
- package/package.json +5 -3
- package/src/feeds.ts +286 -0
- package/src/index.ts +82 -72
- package/src/staticPaths.ts +299 -0
- package/src/virtual.d.ts +6 -0
package/src/feeds.ts
ADDED
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
interface FeedPost {
|
|
2
|
+
id: string
|
|
3
|
+
data: {
|
|
4
|
+
date: Date
|
|
5
|
+
title: string
|
|
6
|
+
description: string
|
|
7
|
+
draft?: boolean
|
|
8
|
+
unlisted?: boolean
|
|
9
|
+
tags?: string[]
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface I18nConfig {
|
|
14
|
+
locales: (string | { codes: string[] })[]
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const escapeXml = (text: string): string =>
|
|
18
|
+
text
|
|
19
|
+
.replace(/&/g, '&')
|
|
20
|
+
.replace(/</g, '<')
|
|
21
|
+
.replace(/>/g, '>')
|
|
22
|
+
.replace(/"/g, '"')
|
|
23
|
+
.replace(/'/g, ''')
|
|
24
|
+
|
|
25
|
+
const buildUrl = (
|
|
26
|
+
baseUrl: string,
|
|
27
|
+
...segments: (string | undefined | null)[]
|
|
28
|
+
): string => {
|
|
29
|
+
const url = new URL(baseUrl)
|
|
30
|
+
const parts = [
|
|
31
|
+
url.pathname.replace(/\/$/, ''),
|
|
32
|
+
...segments
|
|
33
|
+
.filter(Boolean)
|
|
34
|
+
.map((s) => (s as string).replace(/^\/|\/$/g, '')),
|
|
35
|
+
]
|
|
36
|
+
url.pathname = `/${parts.filter(Boolean).join('/')}`
|
|
37
|
+
return url.toString()
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const filterPosts = (
|
|
41
|
+
allPosts: FeedPost[],
|
|
42
|
+
includeDraftsInDev: boolean,
|
|
43
|
+
currentLocale: string | undefined,
|
|
44
|
+
i18n: I18nConfig | null | undefined | false,
|
|
45
|
+
limit: number,
|
|
46
|
+
isDev: boolean,
|
|
47
|
+
): FeedPost[] =>
|
|
48
|
+
allPosts
|
|
49
|
+
.filter((post) => {
|
|
50
|
+
if (post.data.unlisted) return false
|
|
51
|
+
if (post.data.draft && !(isDev && includeDraftsInDev)) return false
|
|
52
|
+
return true
|
|
53
|
+
})
|
|
54
|
+
.filter((post) => {
|
|
55
|
+
if (i18n) {
|
|
56
|
+
const [pl] = post.id.split('/')
|
|
57
|
+
return pl === currentLocale
|
|
58
|
+
}
|
|
59
|
+
return true
|
|
60
|
+
})
|
|
61
|
+
.toSorted((a, b) => b.data.date.getTime() - a.data.date.getTime())
|
|
62
|
+
.slice(0, limit)
|
|
63
|
+
|
|
64
|
+
const getPostUrl = (
|
|
65
|
+
post: FeedPost,
|
|
66
|
+
routeBasePath: string,
|
|
67
|
+
baseUrl: string,
|
|
68
|
+
currentLocale: string | undefined,
|
|
69
|
+
i18n: I18nConfig | null | undefined | false,
|
|
70
|
+
): string => {
|
|
71
|
+
if (i18n && currentLocale) {
|
|
72
|
+
const slug = post.id.replace(`${currentLocale}/`, '')
|
|
73
|
+
return buildUrl(baseUrl, currentLocale, routeBasePath, slug)
|
|
74
|
+
}
|
|
75
|
+
return buildUrl(baseUrl, routeBasePath, post.id)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface FeedParams {
|
|
79
|
+
allPosts: FeedPost[]
|
|
80
|
+
blogConfig: {
|
|
81
|
+
routeBasePath: string
|
|
82
|
+
blogTitle: string
|
|
83
|
+
blogDescription?: string
|
|
84
|
+
includeDraftsInDev: boolean
|
|
85
|
+
feedOptions: {
|
|
86
|
+
limit: number
|
|
87
|
+
title?: string
|
|
88
|
+
description?: string
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
site: URL | undefined
|
|
92
|
+
currentLocale: string | undefined
|
|
93
|
+
i18n: I18nConfig | null | undefined | false
|
|
94
|
+
isDev: boolean
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export const createRssResponse = ({
|
|
98
|
+
allPosts,
|
|
99
|
+
blogConfig,
|
|
100
|
+
site,
|
|
101
|
+
currentLocale,
|
|
102
|
+
i18n,
|
|
103
|
+
isDev,
|
|
104
|
+
}: FeedParams): Response => {
|
|
105
|
+
const baseUrl = site?.toString() ?? 'https://example.com'
|
|
106
|
+
const { feedOptions, blogTitle, blogDescription, routeBasePath } = blogConfig
|
|
107
|
+
const posts = filterPosts(
|
|
108
|
+
allPosts,
|
|
109
|
+
blogConfig.includeDraftsInDev,
|
|
110
|
+
currentLocale,
|
|
111
|
+
i18n,
|
|
112
|
+
feedOptions.limit,
|
|
113
|
+
isDev,
|
|
114
|
+
)
|
|
115
|
+
const title = feedOptions.title ?? blogTitle
|
|
116
|
+
const description =
|
|
117
|
+
feedOptions.description ?? blogDescription ?? `${title} Feed`
|
|
118
|
+
const feedUrl = i18n
|
|
119
|
+
? buildUrl(baseUrl, currentLocale ?? '', routeBasePath, 'rss.xml')
|
|
120
|
+
: buildUrl(baseUrl, routeBasePath, 'rss.xml')
|
|
121
|
+
|
|
122
|
+
const items = posts
|
|
123
|
+
.map((post) => {
|
|
124
|
+
const postUrl = getPostUrl(
|
|
125
|
+
post,
|
|
126
|
+
routeBasePath,
|
|
127
|
+
baseUrl,
|
|
128
|
+
currentLocale,
|
|
129
|
+
i18n,
|
|
130
|
+
)
|
|
131
|
+
return [
|
|
132
|
+
' <item>',
|
|
133
|
+
` <title>${escapeXml(post.data.title)}</title>`,
|
|
134
|
+
` <link>${escapeXml(postUrl)}</link>`,
|
|
135
|
+
` <description>${escapeXml(post.data.description)}</description>`,
|
|
136
|
+
` <pubDate>${post.data.date.toUTCString()}</pubDate>`,
|
|
137
|
+
` <guid>${escapeXml(postUrl)}</guid>`,
|
|
138
|
+
' </item>',
|
|
139
|
+
].join('\n')
|
|
140
|
+
})
|
|
141
|
+
.join('\n')
|
|
142
|
+
|
|
143
|
+
const xml = [
|
|
144
|
+
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
145
|
+
'<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">',
|
|
146
|
+
' <channel>',
|
|
147
|
+
` <title>${escapeXml(title)}</title>`,
|
|
148
|
+
` <link>${escapeXml(baseUrl)}</link>`,
|
|
149
|
+
` <description>${escapeXml(description)}</description>`,
|
|
150
|
+
` <language>${currentLocale ?? 'en'}</language>`,
|
|
151
|
+
` <atom:link href="${escapeXml(feedUrl)}" rel="self" type="application/rss+xml"/>`,
|
|
152
|
+
` <lastBuildDate>${new Date().toUTCString()}</lastBuildDate>`,
|
|
153
|
+
items,
|
|
154
|
+
' </channel>',
|
|
155
|
+
'</rss>',
|
|
156
|
+
].join('\n')
|
|
157
|
+
|
|
158
|
+
return new Response(xml, {
|
|
159
|
+
headers: { 'Content-Type': 'application/rss+xml; charset=utf-8' },
|
|
160
|
+
})
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export const createAtomResponse = ({
|
|
164
|
+
allPosts,
|
|
165
|
+
blogConfig,
|
|
166
|
+
site,
|
|
167
|
+
currentLocale,
|
|
168
|
+
i18n,
|
|
169
|
+
isDev,
|
|
170
|
+
}: FeedParams): Response => {
|
|
171
|
+
const baseUrl = site?.toString() ?? 'https://example.com'
|
|
172
|
+
const { feedOptions, blogTitle, blogDescription, routeBasePath } = blogConfig
|
|
173
|
+
const posts = filterPosts(
|
|
174
|
+
allPosts,
|
|
175
|
+
blogConfig.includeDraftsInDev,
|
|
176
|
+
currentLocale,
|
|
177
|
+
i18n,
|
|
178
|
+
feedOptions.limit,
|
|
179
|
+
isDev,
|
|
180
|
+
)
|
|
181
|
+
const title = feedOptions.title ?? blogTitle
|
|
182
|
+
const feedUrl = i18n
|
|
183
|
+
? buildUrl(baseUrl, currentLocale ?? '', routeBasePath, 'atom.xml')
|
|
184
|
+
: buildUrl(baseUrl, routeBasePath, 'atom.xml')
|
|
185
|
+
|
|
186
|
+
const lastUpdated =
|
|
187
|
+
posts.length > 0
|
|
188
|
+
? posts[0].data.date.toISOString()
|
|
189
|
+
: new Date().toISOString()
|
|
190
|
+
const subtitle = feedOptions.description ?? blogDescription
|
|
191
|
+
|
|
192
|
+
const entries = posts
|
|
193
|
+
.map((post) => {
|
|
194
|
+
const postUrl = getPostUrl(
|
|
195
|
+
post,
|
|
196
|
+
routeBasePath,
|
|
197
|
+
baseUrl,
|
|
198
|
+
currentLocale,
|
|
199
|
+
i18n,
|
|
200
|
+
)
|
|
201
|
+
return [
|
|
202
|
+
' <entry>',
|
|
203
|
+
` <title>${escapeXml(post.data.title)}</title>`,
|
|
204
|
+
` <link href="${escapeXml(postUrl)}"/>`,
|
|
205
|
+
` <id>${escapeXml(postUrl)}</id>`,
|
|
206
|
+
` <updated>${post.data.date.toISOString()}</updated>`,
|
|
207
|
+
` <summary>${escapeXml(post.data.description)}</summary>`,
|
|
208
|
+
' </entry>',
|
|
209
|
+
].join('\n')
|
|
210
|
+
})
|
|
211
|
+
.join('\n')
|
|
212
|
+
|
|
213
|
+
const xml = [
|
|
214
|
+
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
215
|
+
`<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="${currentLocale ?? 'en'}">`,
|
|
216
|
+
` <title>${escapeXml(title)}</title>`,
|
|
217
|
+
` <link href="${escapeXml(baseUrl)}"/>`,
|
|
218
|
+
` <link href="${escapeXml(feedUrl)}" rel="self" type="application/atom+xml"/>`,
|
|
219
|
+
` <id>${escapeXml(baseUrl)}</id>`,
|
|
220
|
+
` <updated>${lastUpdated}</updated>`,
|
|
221
|
+
...(subtitle ? [` <subtitle>${escapeXml(subtitle)}</subtitle>`] : []),
|
|
222
|
+
entries,
|
|
223
|
+
'</feed>',
|
|
224
|
+
].join('\n')
|
|
225
|
+
|
|
226
|
+
return new Response(xml, {
|
|
227
|
+
headers: { 'Content-Type': 'application/atom+xml; charset=utf-8' },
|
|
228
|
+
})
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export const createJsonFeedResponse = ({
|
|
232
|
+
allPosts,
|
|
233
|
+
blogConfig,
|
|
234
|
+
site,
|
|
235
|
+
currentLocale,
|
|
236
|
+
i18n,
|
|
237
|
+
isDev,
|
|
238
|
+
}: FeedParams): Response => {
|
|
239
|
+
const baseUrl = site?.toString() ?? 'https://example.com'
|
|
240
|
+
const { feedOptions, blogTitle, blogDescription, routeBasePath } = blogConfig
|
|
241
|
+
const posts = filterPosts(
|
|
242
|
+
allPosts,
|
|
243
|
+
blogConfig.includeDraftsInDev,
|
|
244
|
+
currentLocale,
|
|
245
|
+
i18n,
|
|
246
|
+
feedOptions.limit,
|
|
247
|
+
isDev,
|
|
248
|
+
)
|
|
249
|
+
const title = feedOptions.title ?? blogTitle
|
|
250
|
+
const description = feedOptions.description ?? blogDescription
|
|
251
|
+
const feedUrl = i18n
|
|
252
|
+
? buildUrl(baseUrl, currentLocale ?? '', routeBasePath, 'feed.json')
|
|
253
|
+
: buildUrl(baseUrl, routeBasePath, 'feed.json')
|
|
254
|
+
|
|
255
|
+
const items = posts.map((post) => {
|
|
256
|
+
const postUrl = getPostUrl(
|
|
257
|
+
post,
|
|
258
|
+
routeBasePath,
|
|
259
|
+
baseUrl,
|
|
260
|
+
currentLocale,
|
|
261
|
+
i18n,
|
|
262
|
+
)
|
|
263
|
+
return {
|
|
264
|
+
id: postUrl,
|
|
265
|
+
url: postUrl,
|
|
266
|
+
title: post.data.title,
|
|
267
|
+
summary: post.data.description,
|
|
268
|
+
date_published: post.data.date.toISOString(),
|
|
269
|
+
...(post.data.tags?.length ? { tags: post.data.tags } : {}),
|
|
270
|
+
}
|
|
271
|
+
})
|
|
272
|
+
|
|
273
|
+
const feed = {
|
|
274
|
+
version: 'https://jsonfeed.org/version/1.1',
|
|
275
|
+
title,
|
|
276
|
+
home_page_url: baseUrl,
|
|
277
|
+
feed_url: feedUrl,
|
|
278
|
+
...(description ? { description } : {}),
|
|
279
|
+
language: currentLocale ?? 'en',
|
|
280
|
+
items,
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
return new Response(JSON.stringify(feed, null, 2), {
|
|
284
|
+
headers: { 'Content-Type': 'application/feed+json; charset=utf-8' },
|
|
285
|
+
})
|
|
286
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -166,13 +166,15 @@ export const blogConfigSchema = z.object({
|
|
|
166
166
|
/**
|
|
167
167
|
* The base path where blog routes will be mounted.
|
|
168
168
|
* @default 'blog'
|
|
169
|
-
* @example '
|
|
169
|
+
* @example 'blog' will mount at /blog/[...slug]
|
|
170
|
+
* @example 'news' will mount at /news/[...slug]
|
|
170
171
|
*/
|
|
171
172
|
routeBasePath: z.string().default('blog'),
|
|
172
173
|
/**
|
|
173
174
|
* The name of the content collection to use.
|
|
174
175
|
* Must match a collection defined in your content.config.ts.
|
|
175
|
-
*
|
|
176
|
+
* Defaults to the routeBasePath if not specified.
|
|
177
|
+
* @example 'blog' for a collection named 'blog'
|
|
176
178
|
*/
|
|
177
179
|
collectionName: z.string().optional(),
|
|
178
180
|
/**
|
|
@@ -300,26 +302,39 @@ const VIRTUAL_MODULE_ID = 'virtual:shipyard-blog/config'
|
|
|
300
302
|
const RESOLVED_VIRTUAL_MODULE_ID = `\0${VIRTUAL_MODULE_ID}`
|
|
301
303
|
const VIRTUAL_TAGS_MODULE_ID = 'virtual:shipyard-blog/tags'
|
|
302
304
|
const RESOLVED_VIRTUAL_TAGS_MODULE_ID = `\0${VIRTUAL_TAGS_MODULE_ID}`
|
|
305
|
+
const VIRTUAL_REGISTRY_ID = 'virtual:shipyard-blog/registry'
|
|
306
|
+
const RESOLVED_VIRTUAL_REGISTRY_ID = `\0${VIRTUAL_REGISTRY_ID}`
|
|
307
|
+
|
|
308
|
+
const blogRegistry: Record<
|
|
309
|
+
string,
|
|
310
|
+
{
|
|
311
|
+
blogConfig: BlogConfig
|
|
312
|
+
tagsMap: Record<string, unknown>
|
|
313
|
+
collectionName: string
|
|
314
|
+
}
|
|
315
|
+
> = {}
|
|
303
316
|
|
|
304
317
|
export default (options: Partial<BlogConfig> = {}): AstroIntegration => {
|
|
305
|
-
// Parse and validate config
|
|
306
318
|
const blogConfig = blogConfigSchema.parse(options)
|
|
307
319
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
// Defer loading to avoid issues at module parse time
|
|
312
|
-
// Tags will be loaded by the virtual module
|
|
320
|
+
let normalizedBasePath = blogConfig.routeBasePath
|
|
321
|
+
while (normalizedBasePath.startsWith('/')) {
|
|
322
|
+
normalizedBasePath = normalizedBasePath.slice(1)
|
|
313
323
|
}
|
|
324
|
+
while (normalizedBasePath.endsWith('/')) {
|
|
325
|
+
normalizedBasePath = normalizedBasePath.slice(0, -1)
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const resolvedCollectionName = blogConfig.collectionName ?? normalizedBasePath
|
|
329
|
+
|
|
330
|
+
let tagsMap: Record<string, unknown> = {}
|
|
314
331
|
|
|
315
332
|
return {
|
|
316
|
-
name:
|
|
333
|
+
name: `shipyard-blog-${normalizedBasePath}`,
|
|
317
334
|
hooks: {
|
|
318
335
|
'astro:config:setup': ({ injectRoute, config, updateConfig }) => {
|
|
319
|
-
// Load tags map now (at config setup time)
|
|
320
336
|
if (blogConfig.tagsMapPath) {
|
|
321
337
|
try {
|
|
322
|
-
// Resolve relative to CWD (project root)
|
|
323
338
|
const resolvedPath = blogConfig.tagsMapPath.startsWith('/')
|
|
324
339
|
? blogConfig.tagsMapPath
|
|
325
340
|
: `${process.cwd()}/${blogConfig.tagsMapPath}`
|
|
@@ -329,7 +344,6 @@ export default (options: Partial<BlogConfig> = {}): AstroIntegration => {
|
|
|
329
344
|
const rawData = parseYaml(fileContent)
|
|
330
345
|
|
|
331
346
|
if (rawData && typeof rawData === 'object') {
|
|
332
|
-
// Validate each tag against the schema
|
|
333
347
|
for (const [key, value] of Object.entries(rawData)) {
|
|
334
348
|
const result = tagSchema.safeParse(value)
|
|
335
349
|
if (result.success) {
|
|
@@ -339,149 +353,145 @@ export default (options: Partial<BlogConfig> = {}): AstroIntegration => {
|
|
|
339
353
|
}
|
|
340
354
|
}
|
|
341
355
|
} catch {
|
|
342
|
-
// Tags file doesn't exist or is invalid, use empty map
|
|
343
356
|
tagsMap = {}
|
|
344
357
|
}
|
|
345
358
|
}
|
|
346
359
|
|
|
347
|
-
|
|
360
|
+
blogRegistry[normalizedBasePath] = {
|
|
361
|
+
blogConfig,
|
|
362
|
+
tagsMap,
|
|
363
|
+
collectionName: resolvedCollectionName,
|
|
364
|
+
}
|
|
365
|
+
|
|
348
366
|
updateConfig({
|
|
349
367
|
vite: {
|
|
350
368
|
plugins: [
|
|
351
369
|
{
|
|
352
|
-
name:
|
|
370
|
+
name: `shipyard-blog-config-${normalizedBasePath}`,
|
|
353
371
|
resolveId(id) {
|
|
354
|
-
if (id ===
|
|
372
|
+
if (id === VIRTUAL_REGISTRY_ID)
|
|
373
|
+
return RESOLVED_VIRTUAL_REGISTRY_ID
|
|
374
|
+
if (id === VIRTUAL_MODULE_ID)
|
|
355
375
|
return RESOLVED_VIRTUAL_MODULE_ID
|
|
356
|
-
|
|
357
|
-
if (id === VIRTUAL_TAGS_MODULE_ID) {
|
|
376
|
+
if (id === VIRTUAL_TAGS_MODULE_ID)
|
|
358
377
|
return RESOLVED_VIRTUAL_TAGS_MODULE_ID
|
|
359
|
-
}
|
|
360
378
|
},
|
|
361
379
|
load(id) {
|
|
362
|
-
if (id ===
|
|
380
|
+
if (id === RESOLVED_VIRTUAL_REGISTRY_ID)
|
|
381
|
+
return `export default ${JSON.stringify(blogRegistry)}`
|
|
382
|
+
if (id === RESOLVED_VIRTUAL_MODULE_ID)
|
|
363
383
|
return `export default ${JSON.stringify(blogConfig)}`
|
|
364
|
-
|
|
365
|
-
if (id === RESOLVED_VIRTUAL_TAGS_MODULE_ID) {
|
|
384
|
+
if (id === RESOLVED_VIRTUAL_TAGS_MODULE_ID)
|
|
366
385
|
return `export default ${JSON.stringify(tagsMap)}`
|
|
367
|
-
}
|
|
368
386
|
},
|
|
369
387
|
},
|
|
370
388
|
],
|
|
371
389
|
},
|
|
372
390
|
})
|
|
373
391
|
|
|
374
|
-
|
|
375
|
-
const
|
|
392
|
+
const basePath = normalizedBasePath
|
|
393
|
+
const pageBase = '@levino/shipyard-blog/astro/pages'
|
|
376
394
|
|
|
377
395
|
if (config.i18n) {
|
|
378
|
-
// With i18n: use locale prefix
|
|
379
396
|
injectRoute({
|
|
380
397
|
pattern: `/[locale]/${basePath}`,
|
|
381
|
-
entrypoint:
|
|
398
|
+
entrypoint: `${pageBase}/BlogIndex.astro`,
|
|
382
399
|
})
|
|
383
400
|
injectRoute({
|
|
384
401
|
pattern: `/[locale]/${basePath}/page/[page]`,
|
|
385
|
-
entrypoint:
|
|
402
|
+
entrypoint: `${pageBase}/BlogIndexPaginated.astro`,
|
|
386
403
|
})
|
|
387
404
|
injectRoute({
|
|
388
405
|
pattern: `/[locale]/${basePath}/tags`,
|
|
389
|
-
entrypoint:
|
|
406
|
+
entrypoint: `${pageBase}/BlogTagsIndex.astro`,
|
|
390
407
|
})
|
|
391
408
|
injectRoute({
|
|
392
409
|
pattern: `/[locale]/${basePath}/tags/[tag]`,
|
|
393
|
-
entrypoint:
|
|
410
|
+
entrypoint: `${pageBase}/BlogTagPage.astro`,
|
|
394
411
|
})
|
|
395
412
|
if (blogConfig.archiveEnabled) {
|
|
396
413
|
injectRoute({
|
|
397
414
|
pattern: `/[locale]/${basePath}/archive`,
|
|
398
|
-
entrypoint:
|
|
415
|
+
entrypoint: `${pageBase}/BlogArchive.astro`,
|
|
399
416
|
})
|
|
400
417
|
}
|
|
401
418
|
if (blogConfig.authorsEnabled) {
|
|
402
419
|
injectRoute({
|
|
403
420
|
pattern: `/[locale]/${basePath}/authors`,
|
|
404
|
-
entrypoint:
|
|
421
|
+
entrypoint: `${pageBase}/BlogAuthorsIndex.astro`,
|
|
405
422
|
})
|
|
406
423
|
injectRoute({
|
|
407
424
|
pattern: `/[locale]/${basePath}/authors/[author]`,
|
|
408
|
-
entrypoint:
|
|
425
|
+
entrypoint: `${pageBase}/BlogAuthorPage.astro`,
|
|
409
426
|
})
|
|
410
427
|
}
|
|
411
428
|
injectRoute({
|
|
412
429
|
pattern: `/[locale]/${basePath}/[...slug]`,
|
|
413
|
-
entrypoint:
|
|
430
|
+
entrypoint: `${pageBase}/BlogEntry.astro`,
|
|
414
431
|
})
|
|
415
432
|
} else {
|
|
416
|
-
// Without i18n: direct path
|
|
417
433
|
injectRoute({
|
|
418
434
|
pattern: `/${basePath}`,
|
|
419
|
-
entrypoint:
|
|
435
|
+
entrypoint: `${pageBase}/BlogIndex.astro`,
|
|
420
436
|
})
|
|
421
437
|
injectRoute({
|
|
422
438
|
pattern: `/${basePath}/page/[page]`,
|
|
423
|
-
entrypoint:
|
|
439
|
+
entrypoint: `${pageBase}/BlogIndexPaginated.astro`,
|
|
424
440
|
})
|
|
425
441
|
injectRoute({
|
|
426
442
|
pattern: `/${basePath}/tags`,
|
|
427
|
-
entrypoint:
|
|
443
|
+
entrypoint: `${pageBase}/BlogTagsIndex.astro`,
|
|
428
444
|
})
|
|
429
445
|
injectRoute({
|
|
430
446
|
pattern: `/${basePath}/tags/[tag]`,
|
|
431
|
-
entrypoint:
|
|
447
|
+
entrypoint: `${pageBase}/BlogTagPage.astro`,
|
|
432
448
|
})
|
|
433
449
|
if (blogConfig.archiveEnabled) {
|
|
434
450
|
injectRoute({
|
|
435
451
|
pattern: `/${basePath}/archive`,
|
|
436
|
-
entrypoint:
|
|
452
|
+
entrypoint: `${pageBase}/BlogArchive.astro`,
|
|
437
453
|
})
|
|
438
454
|
}
|
|
439
455
|
if (blogConfig.authorsEnabled) {
|
|
440
456
|
injectRoute({
|
|
441
457
|
pattern: `/${basePath}/authors`,
|
|
442
|
-
entrypoint:
|
|
458
|
+
entrypoint: `${pageBase}/BlogAuthorsIndex.astro`,
|
|
443
459
|
})
|
|
444
460
|
injectRoute({
|
|
445
461
|
pattern: `/${basePath}/authors/[author]`,
|
|
446
|
-
entrypoint:
|
|
462
|
+
entrypoint: `${pageBase}/BlogAuthorPage.astro`,
|
|
447
463
|
})
|
|
448
464
|
}
|
|
449
465
|
injectRoute({
|
|
450
466
|
pattern: `/${basePath}/[...slug]`,
|
|
451
|
-
entrypoint:
|
|
467
|
+
entrypoint: `${pageBase}/BlogEntry.astro`,
|
|
452
468
|
})
|
|
453
469
|
}
|
|
454
470
|
|
|
455
|
-
// Inject feed routes if enabled
|
|
456
471
|
const { feedOptions } = blogConfig
|
|
457
|
-
if (feedOptions.rss
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
injectRoute({
|
|
481
|
-
pattern: `/${basePath}/feed.json`,
|
|
482
|
-
entrypoint: `@levino/shipyard-blog/astro/feeds/feed.json.ts`,
|
|
483
|
-
})
|
|
484
|
-
}
|
|
472
|
+
if (feedOptions.rss) {
|
|
473
|
+
injectRoute({
|
|
474
|
+
pattern: config.i18n
|
|
475
|
+
? `/[locale]/${basePath}/rss.xml`
|
|
476
|
+
: `/${basePath}/rss.xml`,
|
|
477
|
+
entrypoint: `${pageBase}/feeds/rss.xml.ts`,
|
|
478
|
+
})
|
|
479
|
+
}
|
|
480
|
+
if (feedOptions.atom) {
|
|
481
|
+
injectRoute({
|
|
482
|
+
pattern: config.i18n
|
|
483
|
+
? `/[locale]/${basePath}/atom.xml`
|
|
484
|
+
: `/${basePath}/atom.xml`,
|
|
485
|
+
entrypoint: `${pageBase}/feeds/atom.xml.ts`,
|
|
486
|
+
})
|
|
487
|
+
}
|
|
488
|
+
if (feedOptions.json) {
|
|
489
|
+
injectRoute({
|
|
490
|
+
pattern: config.i18n
|
|
491
|
+
? `/[locale]/${basePath}/feed.json`
|
|
492
|
+
: `/${basePath}/feed.json`,
|
|
493
|
+
entrypoint: `${pageBase}/feeds/feed.json.ts`,
|
|
494
|
+
})
|
|
485
495
|
}
|
|
486
496
|
},
|
|
487
497
|
},
|