@jasonshimmy/vite-plugin-cer-app 0.20.5 → 0.21.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/CHANGELOG.md +4 -0
- package/README.md +22 -0
- package/commits.txt +1 -1
- package/dist/plugin/content/path-utils.d.ts +3 -0
- package/dist/plugin/content/path-utils.d.ts.map +1 -1
- package/dist/plugin/content/path-utils.js +9 -2
- package/dist/plugin/content/path-utils.js.map +1 -1
- package/docs/content.md +82 -0
- package/e2e/cypress/e2e/content.cy.ts +37 -0
- package/e2e/kitchen-sink/app/pages/content-guides.ts +35 -0
- package/e2e/kitchen-sink/content/01.guides/01.index.md +8 -0
- package/e2e/kitchen-sink/content/01.guides/02.intro.md +8 -0
- package/e2e/kitchen-sink/content/01.guides/10.advanced.md +8 -0
- package/package.json +1 -1
- package/src/__tests__/plugin/content/path-utils.test.ts +16 -0
- package/src/plugin/content/path-utils.ts +10 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
|
+
## [v0.21.0] - 2026-04-12
|
|
5
|
+
|
|
6
|
+
- feat: add support for numeric ordering prefixes in content paths and update related tests (ffb0bc8)
|
|
7
|
+
|
|
4
8
|
## [v0.20.5] - 2026-04-12
|
|
5
9
|
|
|
6
10
|
- fix: enhance hydration handling and data caching in usePageData and routing middleware (305ae7f)
|
package/README.md
CHANGED
|
@@ -210,6 +210,28 @@ component('page-about', () => {
|
|
|
210
210
|
|
|
211
211
|
---
|
|
212
212
|
|
|
213
|
+
## Content Layer
|
|
214
|
+
|
|
215
|
+
Drop Markdown and JSON files into `content/` and query them with `queryContent()`.
|
|
216
|
+
|
|
217
|
+
Numeric ordering prefixes are supported on both directories and files. A leading `NN.` is stripped from the public content path, which lets you keep source-tree ordering without leaking the prefix into URLs:
|
|
218
|
+
|
|
219
|
+
```text
|
|
220
|
+
content/
|
|
221
|
+
01.docs/
|
|
222
|
+
01.getting-started.md -> /docs/getting-started
|
|
223
|
+
02.routing.md -> /docs/routing
|
|
224
|
+
02.blog/
|
|
225
|
+
01.index.md -> /blog
|
|
226
|
+
02.2026-04-01-hello.md -> /blog/hello
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Date-prefixed filenames still work the same way after the numeric prefix is removed.
|
|
230
|
+
|
|
231
|
+
See [docs/content.md](docs/content.md) for the full content-layer API and examples.
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
213
235
|
## Documentation
|
|
214
236
|
|
|
215
237
|
| Guide | Description |
|
package/commits.txt
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
-
|
|
1
|
+
- feat: add support for numeric ordering prefixes in content paths and update related tests (ffb0bc8)
|
|
@@ -4,12 +4,15 @@
|
|
|
4
4
|
* Rules:
|
|
5
5
|
* - Strip the content dir prefix
|
|
6
6
|
* - Strip the file extension
|
|
7
|
+
* - Strip `NN.` numeric ordering prefixes from all path segments
|
|
7
8
|
* - Strip `/index` suffix (so blog/index.md → /blog)
|
|
8
9
|
* - Strip `YYYY-MM-DD-` date prefix from the final slug segment
|
|
9
10
|
*
|
|
10
11
|
* Examples:
|
|
11
12
|
* index.md → /
|
|
13
|
+
* 01.about.md → /about
|
|
12
14
|
* about.md → /about
|
|
15
|
+
* 01.blog/02.hello.md → /blog/hello
|
|
13
16
|
* blog/index.md → /blog
|
|
14
17
|
* blog/2026-04-03-hello.md → /blog/hello
|
|
15
18
|
* docs/getting-started.md → /docs/getting-started
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"path-utils.d.ts","sourceRoot":"","sources":["../../../src/plugin/content/path-utils.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"path-utils.d.ts","sourceRoot":"","sources":["../../../src/plugin/content/path-utils.ts"],"names":[],"mappings":"AAMA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CA0B9E"}
|
|
@@ -1,16 +1,22 @@
|
|
|
1
1
|
import { relative } from 'pathe';
|
|
2
|
+
function stripNumericPrefix(segment) {
|
|
3
|
+
return segment.replace(/^\d+\./, '');
|
|
4
|
+
}
|
|
2
5
|
/**
|
|
3
6
|
* Maps a content file path to a `_path` URL path.
|
|
4
7
|
*
|
|
5
8
|
* Rules:
|
|
6
9
|
* - Strip the content dir prefix
|
|
7
10
|
* - Strip the file extension
|
|
11
|
+
* - Strip `NN.` numeric ordering prefixes from all path segments
|
|
8
12
|
* - Strip `/index` suffix (so blog/index.md → /blog)
|
|
9
13
|
* - Strip `YYYY-MM-DD-` date prefix from the final slug segment
|
|
10
14
|
*
|
|
11
15
|
* Examples:
|
|
12
16
|
* index.md → /
|
|
17
|
+
* 01.about.md → /about
|
|
13
18
|
* about.md → /about
|
|
19
|
+
* 01.blog/02.hello.md → /blog/hello
|
|
14
20
|
* blog/index.md → /blog
|
|
15
21
|
* blog/2026-04-03-hello.md → /blog/hello
|
|
16
22
|
* docs/getting-started.md → /docs/getting-started
|
|
@@ -21,8 +27,9 @@ export function fileToContentPath(filePath, contentDir) {
|
|
|
21
27
|
let rel = relative(contentDir, filePath);
|
|
22
28
|
rel = rel.replace(/\.(md|json)$/, '');
|
|
23
29
|
// Split into segments
|
|
24
|
-
const segments = rel.split('/');
|
|
25
|
-
// Strip date prefix (YYYY-MM-DD-) from the last segment
|
|
30
|
+
const segments = rel.split('/').map(stripNumericPrefix);
|
|
31
|
+
// Strip date prefix (YYYY-MM-DD-) from the last segment after removing any
|
|
32
|
+
// numeric ordering prefix (for example 01.2026-04-03-hello.md → /hello).
|
|
26
33
|
const last = segments[segments.length - 1];
|
|
27
34
|
const stripped = last.replace(/^\d{4}-\d{2}-\d{2}-/, '');
|
|
28
35
|
segments[segments.length - 1] = stripped;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"path-utils.js","sourceRoot":"","sources":["../../../src/plugin/content/path-utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAEhC
|
|
1
|
+
{"version":3,"file":"path-utils.js","sourceRoot":"","sources":["../../../src/plugin/content/path-utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAEhC,SAAS,kBAAkB,CAAC,OAAe;IACzC,OAAO,OAAO,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAA;AACtC,CAAC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,iBAAiB,CAAC,QAAgB,EAAE,UAAkB;IACpE,mDAAmD;IACnD,IAAI,GAAG,GAAG,QAAQ,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAA;IACxC,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC,CAAA;IAErC,sBAAsB;IACtB,MAAM,QAAQ,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAA;IAEvD,2EAA2E;IAC3E,yEAAyE;IACzE,MAAM,IAAI,GAAG,QAAQ,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;IAC1C,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,qBAAqB,EAAE,EAAE,CAAC,CAAA;IACxD,QAAQ,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,QAAQ,CAAA;IAExC,+DAA+D;IAC/D,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,IAAI,QAAQ,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,KAAK,OAAO,EAAE,CAAC;QACrE,QAAQ,CAAC,GAAG,EAAE,CAAA;IAChB,CAAC;IAED,qDAAqD;IACrD,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,IAAI,QAAQ,CAAC,CAAC,CAAC,KAAK,OAAO,EAAE,CAAC;QACrD,OAAO,GAAG,CAAA;IACZ,CAAC;IAED,MAAM,IAAI,GAAG,GAAG,GAAG,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IACrC,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;AAClC,CAAC"}
|
package/docs/content.md
CHANGED
|
@@ -28,6 +28,15 @@ content/
|
|
|
28
28
|
getting-started.md
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
+
Numeric ordering prefixes are also supported on both directories and files. A leading `NN.` is used for ordering in the source tree but stripped from the public content path:
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
content/
|
|
35
|
+
01.docs/
|
|
36
|
+
01.getting-started.md -> /docs/getting-started
|
|
37
|
+
02.routing.md -> /docs/routing
|
|
38
|
+
```
|
|
39
|
+
|
|
31
40
|
### 2. Query content in a page
|
|
32
41
|
|
|
33
42
|
```ts
|
|
@@ -145,6 +154,17 @@ Filenames starting with `YYYY-MM-DD-` have the date prefix stripped when computi
|
|
|
145
154
|
content/blog/2026-04-01-hello.md → _path: '/blog/hello'
|
|
146
155
|
```
|
|
147
156
|
|
|
157
|
+
### Numeric ordering prefixes
|
|
158
|
+
|
|
159
|
+
Directories and filenames starting with `NN.` have that numeric prefix stripped from the computed content path. This lets you control source-tree ordering without exposing the prefix in URLs:
|
|
160
|
+
|
|
161
|
+
```
|
|
162
|
+
content/01.docs/02.getting-started.md → _path: '/docs/getting-started'
|
|
163
|
+
content/02.blog/01.index.md → _path: '/blog'
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Numeric prefixes are removed from every path segment before the usual `index` and date-prefix handling runs.
|
|
167
|
+
|
|
148
168
|
### Index files
|
|
149
169
|
|
|
150
170
|
Files named `index.md` have `/index` stripped from their path:
|
|
@@ -341,6 +361,68 @@ export const loader = async () => {
|
|
|
341
361
|
}
|
|
342
362
|
```
|
|
343
363
|
|
|
364
|
+
### Common pattern: catch-all content route
|
|
365
|
+
|
|
366
|
+
Content-driven apps often use a catch-all page to resolve the current URL to a content document.
|
|
367
|
+
|
|
368
|
+
```ts
|
|
369
|
+
// app/pages/[...all].ts
|
|
370
|
+
component('page-all', () => {
|
|
371
|
+
const props = useProps({ all: '' })
|
|
372
|
+
const ssrData = usePageData<{ doc: ContentItem | null }>()
|
|
373
|
+
const doc = ref<ContentItem | null>(ssrData?.doc ?? null)
|
|
374
|
+
|
|
375
|
+
const contentPath = normalizeContentPath(props.all)
|
|
376
|
+
|
|
377
|
+
useHead({
|
|
378
|
+
title: doc.value?.title ?? 'Not found',
|
|
379
|
+
meta: doc.value?.description
|
|
380
|
+
? [{ name: 'description', content: doc.value.description }]
|
|
381
|
+
: [],
|
|
382
|
+
})
|
|
383
|
+
|
|
384
|
+
useOnConnected(async () => {
|
|
385
|
+
if (ssrData) return
|
|
386
|
+
doc.value = await queryContent(contentPath).first()
|
|
387
|
+
})
|
|
388
|
+
|
|
389
|
+
return html`
|
|
390
|
+
<article class="prose">
|
|
391
|
+
${
|
|
392
|
+
!doc.value
|
|
393
|
+
? html`
|
|
394
|
+
<h1>404</h1>
|
|
395
|
+
<p>No content found for <code>${contentPath}</code>.</p>
|
|
396
|
+
`
|
|
397
|
+
: doc.value._type === 'json'
|
|
398
|
+
? html`
|
|
399
|
+
<h1>${doc.value.title ?? contentPath}</h1>
|
|
400
|
+
<pre>${doc.value.body}</pre>
|
|
401
|
+
`
|
|
402
|
+
: html`
|
|
403
|
+
<h1>${doc.value.title ?? contentPath}</h1>
|
|
404
|
+
${doc.value.description ? html`<p>${doc.value.description}</p>` : ''}
|
|
405
|
+
${unsafeHTML(doc.value.body)}
|
|
406
|
+
`
|
|
407
|
+
}
|
|
408
|
+
</article>
|
|
409
|
+
`
|
|
410
|
+
})
|
|
411
|
+
|
|
412
|
+
export const loader = async ({ params }) => {
|
|
413
|
+
const contentPath = normalizeContentPath(params.all)
|
|
414
|
+
const doc = await queryContent(contentPath).first()
|
|
415
|
+
return { doc }
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
function normalizeContentPath(all: string | undefined) {
|
|
419
|
+
const slug = String(all ?? '').replace(/^\/+|\/+$/g, '')
|
|
420
|
+
return slug ? `/${slug}` : '/'
|
|
421
|
+
}
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
This pattern works well for documentation sites, blogs, and other apps where the route structure mirrors the `content/` directory. `queryContent('/docs/getting-started').first()` returns the full `ContentItem`, including `body`, `excerpt`, and `toc`.
|
|
425
|
+
|
|
344
426
|
---
|
|
345
427
|
|
|
346
428
|
## `useContentSearch()`
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* /content-index — queryContent().find() (all content)
|
|
6
6
|
* /content-blog — queryContent('/blog').find() (blog prefix, draft exclusion)
|
|
7
7
|
* /content-doc — queryContent('/docs/getting-started').first() (body + TOC)
|
|
8
|
+
* /content-guides — queryContent('/guides').find() (numeric dir/file prefixes)
|
|
8
9
|
* /content-search — useContentSearch() (MiniSearch, client-side)
|
|
9
10
|
* /content-fallback — title/description derived from body when frontmatter omits them
|
|
10
11
|
*/
|
|
@@ -157,6 +158,42 @@ describe('Content doc — queryContent("/docs/getting-started").first()', () =>
|
|
|
157
158
|
})
|
|
158
159
|
})
|
|
159
160
|
|
|
161
|
+
// ─── /content-guides ─────────────────────────────────────────────────────────
|
|
162
|
+
|
|
163
|
+
describe('Content guides — numeric directory and file prefixes', () => {
|
|
164
|
+
if (mode !== 'spa') {
|
|
165
|
+
it('pre-renders stripped guide paths and titles in initial HTML (SSR/SSG)', () => {
|
|
166
|
+
cy.request('/content-guides').then((response) => {
|
|
167
|
+
expect(response.body).to.include('Guides Home')
|
|
168
|
+
expect(response.body).to.include('Intro Guide')
|
|
169
|
+
expect(response.body).to.include('Advanced Guide')
|
|
170
|
+
expect(response.body).to.include('data-path="/guides"')
|
|
171
|
+
expect(response.body).to.include('data-path="/guides/intro"')
|
|
172
|
+
expect(response.body).to.include('data-path="/guides/advanced"')
|
|
173
|
+
expect(response.body).not.to.include('01.guides')
|
|
174
|
+
expect(response.body).not.to.include('/guides/02.intro')
|
|
175
|
+
})
|
|
176
|
+
})
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
it('renders guide items after hydration with stripped public paths', () => {
|
|
180
|
+
cy.visit('/content-guides')
|
|
181
|
+
cy.get('[data-cy=content-guides-item]', { timeout: 8000 }).should('have.length', 3)
|
|
182
|
+
cy.get('[data-cy=content-guides-item]').then(($items) => {
|
|
183
|
+
const paths = [...$items].map((el) => el.getAttribute('data-path'))
|
|
184
|
+
expect(paths).to.deep.equal(['/guides', '/guides/intro', '/guides/advanced'])
|
|
185
|
+
})
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
it('preserves numeric file ordering in queryContent results', () => {
|
|
189
|
+
cy.visit('/content-guides')
|
|
190
|
+
cy.get('[data-cy=content-guides-title]', { timeout: 8000 }).then(($titles) => {
|
|
191
|
+
const titles = [...$titles].map((el) => el.textContent?.trim())
|
|
192
|
+
expect(titles).to.deep.equal(['Guides Home', 'Intro Guide', 'Advanced Guide'])
|
|
193
|
+
})
|
|
194
|
+
})
|
|
195
|
+
})
|
|
196
|
+
|
|
160
197
|
// ─── /content-search ──────────────────────────────────────────────────────────
|
|
161
198
|
|
|
162
199
|
// Helper: set the search input value and fire the input event.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content guides listing page — exercises numeric directory/file prefixes in
|
|
3
|
+
* the content layer using `queryContent('/guides').find()`.
|
|
4
|
+
* Route: /content-guides
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
component('page-content-guides', () => {
|
|
8
|
+
useHead({ title: 'Content Guides — Kitchen Sink' })
|
|
9
|
+
|
|
10
|
+
const ssrData = usePageData<{ guides: ContentMeta[] }>()
|
|
11
|
+
const guides = ref<ContentMeta[]>(ssrData?.guides ?? [])
|
|
12
|
+
|
|
13
|
+
useOnConnected(async () => {
|
|
14
|
+
if (ssrData) return
|
|
15
|
+
guides.value = await queryContent('/guides').find()
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
return html`
|
|
19
|
+
<div>
|
|
20
|
+
<h1 data-cy="content-guides-heading">Guides</h1>
|
|
21
|
+
<ul data-cy="content-guides-list">
|
|
22
|
+
${guides.value.map((guide, index) => html`
|
|
23
|
+
<li data-cy="content-guides-item" data-path="${guide._path}" data-index="${index}">
|
|
24
|
+
<strong data-cy="content-guides-title">${guide.title ?? guide._path}</strong>
|
|
25
|
+
</li>
|
|
26
|
+
`)}
|
|
27
|
+
</ul>
|
|
28
|
+
</div>
|
|
29
|
+
`
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
export const loader = async () => {
|
|
33
|
+
const guides = await queryContent('/guides').find()
|
|
34
|
+
return { guides }
|
|
35
|
+
}
|
package/package.json
CHANGED
|
@@ -10,18 +10,34 @@ describe('fileToContentPath', () => {
|
|
|
10
10
|
expect(fileToContentPath(`${DIR}/index.md`, DIR)).toBe('/')
|
|
11
11
|
})
|
|
12
12
|
|
|
13
|
+
it('strips numeric prefix from a root-level file segment', () => {
|
|
14
|
+
expect(fileToContentPath(`${DIR}/01.about.md`, DIR)).toBe('/about')
|
|
15
|
+
})
|
|
16
|
+
|
|
13
17
|
it('maps about.md to /about', () => {
|
|
14
18
|
expect(fileToContentPath(`${DIR}/about.md`, DIR)).toBe('/about')
|
|
15
19
|
})
|
|
16
20
|
|
|
21
|
+
it('strips numeric prefixes from directory segments', () => {
|
|
22
|
+
expect(fileToContentPath(`${DIR}/01.docs/02.getting-started.md`, DIR)).toBe('/docs/getting-started')
|
|
23
|
+
})
|
|
24
|
+
|
|
17
25
|
it('maps blog/index.md to /blog', () => {
|
|
18
26
|
expect(fileToContentPath(`${DIR}/blog/index.md`, DIR)).toBe('/blog')
|
|
19
27
|
})
|
|
20
28
|
|
|
29
|
+
it('strips numeric prefix before handling index files', () => {
|
|
30
|
+
expect(fileToContentPath(`${DIR}/01.blog/02.index.md`, DIR)).toBe('/blog')
|
|
31
|
+
})
|
|
32
|
+
|
|
21
33
|
it('maps blog/2026-04-03-hello.md to /blog/hello (strips date prefix)', () => {
|
|
22
34
|
expect(fileToContentPath(`${DIR}/blog/2026-04-03-hello.md`, DIR)).toBe('/blog/hello')
|
|
23
35
|
})
|
|
24
36
|
|
|
37
|
+
it('strips numeric prefix before date prefix on the final segment', () => {
|
|
38
|
+
expect(fileToContentPath(`${DIR}/blog/01.2026-04-03-hello.md`, DIR)).toBe('/blog/hello')
|
|
39
|
+
})
|
|
40
|
+
|
|
25
41
|
it('maps docs/getting-started.md to /docs/getting-started', () => {
|
|
26
42
|
expect(fileToContentPath(`${DIR}/docs/getting-started.md`, DIR)).toBe('/docs/getting-started')
|
|
27
43
|
})
|
|
@@ -1,17 +1,24 @@
|
|
|
1
1
|
import { relative } from 'pathe'
|
|
2
2
|
|
|
3
|
+
function stripNumericPrefix(segment: string): string {
|
|
4
|
+
return segment.replace(/^\d+\./, '')
|
|
5
|
+
}
|
|
6
|
+
|
|
3
7
|
/**
|
|
4
8
|
* Maps a content file path to a `_path` URL path.
|
|
5
9
|
*
|
|
6
10
|
* Rules:
|
|
7
11
|
* - Strip the content dir prefix
|
|
8
12
|
* - Strip the file extension
|
|
13
|
+
* - Strip `NN.` numeric ordering prefixes from all path segments
|
|
9
14
|
* - Strip `/index` suffix (so blog/index.md → /blog)
|
|
10
15
|
* - Strip `YYYY-MM-DD-` date prefix from the final slug segment
|
|
11
16
|
*
|
|
12
17
|
* Examples:
|
|
13
18
|
* index.md → /
|
|
19
|
+
* 01.about.md → /about
|
|
14
20
|
* about.md → /about
|
|
21
|
+
* 01.blog/02.hello.md → /blog/hello
|
|
15
22
|
* blog/index.md → /blog
|
|
16
23
|
* blog/2026-04-03-hello.md → /blog/hello
|
|
17
24
|
* docs/getting-started.md → /docs/getting-started
|
|
@@ -23,9 +30,10 @@ export function fileToContentPath(filePath: string, contentDir: string): string
|
|
|
23
30
|
rel = rel.replace(/\.(md|json)$/, '')
|
|
24
31
|
|
|
25
32
|
// Split into segments
|
|
26
|
-
const segments = rel.split('/')
|
|
33
|
+
const segments = rel.split('/').map(stripNumericPrefix)
|
|
27
34
|
|
|
28
|
-
// Strip date prefix (YYYY-MM-DD-) from the last segment
|
|
35
|
+
// Strip date prefix (YYYY-MM-DD-) from the last segment after removing any
|
|
36
|
+
// numeric ordering prefix (for example 01.2026-04-03-hello.md → /hello).
|
|
29
37
|
const last = segments[segments.length - 1]
|
|
30
38
|
const stripped = last.replace(/^\d{4}-\d{2}-\d{2}-/, '')
|
|
31
39
|
segments[segments.length - 1] = stripped
|