@jasonshimmy/vite-plugin-cer-app 0.19.3 → 0.20.1
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 +9 -0
- package/commits.txt +1 -1
- package/dist/cli/commands/preview.d.ts.map +1 -1
- package/dist/cli/commands/preview.js +2 -0
- package/dist/cli/commands/preview.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/plugin/build-ssg.d.ts.map +1 -1
- package/dist/plugin/build-ssg.js +11 -0
- package/dist/plugin/build-ssg.js.map +1 -1
- package/dist/plugin/content/emitter.d.ts +19 -0
- package/dist/plugin/content/emitter.d.ts.map +1 -0
- package/dist/plugin/content/emitter.js +42 -0
- package/dist/plugin/content/emitter.js.map +1 -0
- package/dist/plugin/content/index.d.ts +32 -0
- package/dist/plugin/content/index.d.ts.map +1 -0
- package/dist/plugin/content/index.js +199 -0
- package/dist/plugin/content/index.js.map +1 -0
- package/dist/plugin/content/parser.d.ts +18 -0
- package/dist/plugin/content/parser.d.ts.map +1 -0
- package/dist/plugin/content/parser.js +221 -0
- package/dist/plugin/content/parser.js.map +1 -0
- package/dist/plugin/content/path-utils.d.ts +19 -0
- package/dist/plugin/content/path-utils.d.ts.map +1 -0
- package/dist/plugin/content/path-utils.js +40 -0
- package/dist/plugin/content/path-utils.js.map +1 -0
- package/dist/plugin/content/scanner.d.ts +12 -0
- package/dist/plugin/content/scanner.d.ts.map +1 -0
- package/dist/plugin/content/scanner.js +18 -0
- package/dist/plugin/content/scanner.js.map +1 -0
- package/dist/plugin/content/search.d.ts +9 -0
- package/dist/plugin/content/search.d.ts.map +1 -0
- package/dist/plugin/content/search.js +24 -0
- package/dist/plugin/content/search.js.map +1 -0
- package/dist/plugin/dts-generator.d.ts.map +1 -1
- package/dist/plugin/dts-generator.js +10 -1
- package/dist/plugin/dts-generator.js.map +1 -1
- package/dist/plugin/index.d.ts.map +1 -1
- package/dist/plugin/index.js +4 -1
- package/dist/plugin/index.js.map +1 -1
- package/dist/plugin/transforms/auto-import.d.ts.map +1 -1
- package/dist/plugin/transforms/auto-import.js +2 -0
- package/dist/plugin/transforms/auto-import.js.map +1 -1
- package/dist/runtime/composables/index.d.ts +3 -0
- package/dist/runtime/composables/index.d.ts.map +1 -1
- package/dist/runtime/composables/index.js +2 -0
- package/dist/runtime/composables/index.js.map +1 -1
- package/dist/runtime/composables/use-content-search.d.ts +49 -0
- package/dist/runtime/composables/use-content-search.d.ts.map +1 -0
- package/dist/runtime/composables/use-content-search.js +101 -0
- package/dist/runtime/composables/use-content-search.js.map +1 -0
- package/dist/runtime/composables/use-content.d.ts +51 -0
- package/dist/runtime/composables/use-content.d.ts.map +1 -0
- package/dist/runtime/composables/use-content.js +127 -0
- package/dist/runtime/composables/use-content.js.map +1 -0
- package/dist/runtime/content/client.d.ts +20 -0
- package/dist/runtime/content/client.d.ts.map +1 -0
- package/dist/runtime/content/client.js +163 -0
- package/dist/runtime/content/client.js.map +1 -0
- package/dist/types/config.d.ts +2 -0
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/config.js.map +1 -1
- package/dist/types/content.d.ts +63 -0
- package/dist/types/content.d.ts.map +1 -0
- package/dist/types/content.js +2 -0
- package/dist/types/content.js.map +1 -0
- package/docs/composables.md +115 -10
- package/docs/configuration.md +33 -0
- package/docs/content.md +453 -0
- package/e2e/cypress/e2e/content.cy.ts +291 -0
- package/e2e/kitchen-sink/app/pages/content-blog.ts +37 -0
- package/e2e/kitchen-sink/app/pages/content-doc.ts +42 -0
- package/e2e/kitchen-sink/app/pages/content-fallback.ts +36 -0
- package/e2e/kitchen-sink/app/pages/content-index.ts +39 -0
- package/e2e/kitchen-sink/app/pages/content-search.ts +35 -0
- package/e2e/kitchen-sink/cer.config.ts +1 -0
- package/e2e/kitchen-sink/content/blog/2026-04-01-hello.md +26 -0
- package/e2e/kitchen-sink/content/blog/2026-04-02-draft.md +10 -0
- package/e2e/kitchen-sink/content/blog/index.md +8 -0
- package/e2e/kitchen-sink/content/blog/no-frontmatter.md +7 -0
- package/e2e/kitchen-sink/content/docs/getting-started.md +46 -0
- package/e2e/kitchen-sink/content/index.md +16 -0
- package/package.json +10 -7
- package/src/__tests__/plugin/build-ssg.test.ts +2 -1
- package/src/__tests__/plugin/content/emitter.test.ts +117 -0
- package/src/__tests__/plugin/content/loader.test.ts +162 -0
- package/src/__tests__/plugin/content/parser.test.ts +381 -0
- package/src/__tests__/plugin/content/path-utils.test.ts +53 -0
- package/src/__tests__/plugin/content/search.test.ts +119 -0
- package/src/__tests__/plugin/dts-generator.test.ts +39 -0
- package/src/__tests__/plugin/transforms/auto-import.test.ts +14 -0
- package/src/__tests__/runtime/use-content-search.test.ts +139 -0
- package/src/__tests__/runtime/use-content.test.ts +226 -0
- package/src/cli/commands/preview.ts +2 -0
- package/src/index.ts +3 -0
- package/src/plugin/build-ssg.ts +12 -0
- package/src/plugin/content/emitter.ts +50 -0
- package/src/plugin/content/index.ts +236 -0
- package/src/plugin/content/parser.ts +259 -0
- package/src/plugin/content/path-utils.ts +47 -0
- package/src/plugin/content/scanner.ts +26 -0
- package/src/plugin/content/search.ts +28 -0
- package/src/plugin/dts-generator.ts +10 -1
- package/src/plugin/index.ts +6 -1
- package/src/plugin/transforms/auto-import.ts +2 -0
- package/src/runtime/composables/index.ts +3 -0
- package/src/runtime/composables/use-content-search.ts +121 -0
- package/src/runtime/composables/use-content.ts +146 -0
- package/src/runtime/content/client.ts +168 -0
- package/src/types/config.ts +2 -0
- package/src/types/content.ts +66 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/** Heading extracted from Markdown during parsing. The `id` is slugified from the heading text and added as an HTML `id` attribute. */
|
|
2
|
+
export interface ContentHeading {
|
|
3
|
+
depth: 1 | 2 | 3 | 4 | 5 | 6;
|
|
4
|
+
/** Slugified heading text — matches the `id` attribute in the rendered body HTML. */
|
|
5
|
+
id: string;
|
|
6
|
+
/** Plain text of the heading. */
|
|
7
|
+
text: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Lean per-document metadata — returned by `.find()` and `.count()`.
|
|
11
|
+
* Kept small deliberately: no `body`, no `toc`, no `excerpt`.
|
|
12
|
+
* Use `description` for listing previews; set it in frontmatter.
|
|
13
|
+
*/
|
|
14
|
+
export interface ContentMeta {
|
|
15
|
+
/** URL path (e.g. `"/blog/hello"`). */
|
|
16
|
+
_path: string;
|
|
17
|
+
/** Source file type. */
|
|
18
|
+
_type: 'markdown' | 'json';
|
|
19
|
+
title?: string;
|
|
20
|
+
/** Use for listing previews — included in search index. */
|
|
21
|
+
description?: string;
|
|
22
|
+
date?: string;
|
|
23
|
+
draft?: boolean;
|
|
24
|
+
/** Any other frontmatter key. */
|
|
25
|
+
[key: string]: unknown;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Full document — returned by `.first()`.
|
|
29
|
+
* Superset of `ContentMeta`; includes `body`, `toc`, `_file`, and optional `excerpt`.
|
|
30
|
+
*/
|
|
31
|
+
export interface ContentItem extends ContentMeta {
|
|
32
|
+
/** Relative path from `content/` at the project root (e.g. `"blog/hello.md"`). */
|
|
33
|
+
_file: string;
|
|
34
|
+
/** Rendered HTML (Markdown) or the raw file contents (JSON files). */
|
|
35
|
+
body: string;
|
|
36
|
+
/** Extracted headings. Empty array for JSON files. */
|
|
37
|
+
toc: ContentHeading[];
|
|
38
|
+
/** HTML content before `<!-- more -->`. Absent when the marker is not present. */
|
|
39
|
+
excerpt?: string;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Search result item returned by `useContentSearch()`.
|
|
43
|
+
* Contains only the MiniSearch stored fields: `_path`, `title`, `description`.
|
|
44
|
+
*/
|
|
45
|
+
export interface ContentSearchResult {
|
|
46
|
+
_path: string;
|
|
47
|
+
title: string;
|
|
48
|
+
description?: string;
|
|
49
|
+
}
|
|
50
|
+
/** Content layer configuration. Controls the directory and draft behaviour. */
|
|
51
|
+
export interface CerContentConfig {
|
|
52
|
+
/**
|
|
53
|
+
* Content directory relative to the project root. Defaults to `'content'`,
|
|
54
|
+
* which resolves to `{root}/content/` — at the same level as `app/`, `server/`, and `public/`.
|
|
55
|
+
*/
|
|
56
|
+
dir?: string;
|
|
57
|
+
/**
|
|
58
|
+
* When `true`, draft items (`draft: true` in frontmatter) are included in
|
|
59
|
+
* production builds. Defaults to `false`.
|
|
60
|
+
*/
|
|
61
|
+
drafts?: boolean;
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=content.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content.d.ts","sourceRoot":"","sources":["../../src/types/content.ts"],"names":[],"mappings":"AAAA,uIAAuI;AACvI,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;IAC5B,qFAAqF;IACrF,EAAE,EAAE,MAAM,CAAA;IACV,iCAAiC;IACjC,IAAI,EAAE,MAAM,CAAA;CACb;AAED;;;;GAIG;AACH,MAAM,WAAW,WAAW;IAC1B,uCAAuC;IACvC,KAAK,EAAE,MAAM,CAAA;IACb,wBAAwB;IACxB,KAAK,EAAE,UAAU,GAAG,MAAM,CAAA;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,2DAA2D;IAC3D,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,iCAAiC;IACjC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CACvB;AAED;;;GAGG;AACH,MAAM,WAAW,WAAY,SAAQ,WAAW;IAC9C,kFAAkF;IAClF,KAAK,EAAE,MAAM,CAAA;IACb,sEAAsE;IACtE,IAAI,EAAE,MAAM,CAAA;IACZ,sDAAsD;IACtD,GAAG,EAAE,cAAc,EAAE,CAAA;IACrB,kFAAkF;IAClF,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED;;;GAGG;AACH,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAED,+EAA+E;AAC/E,MAAM,WAAW,gBAAgB;IAC/B;;;OAGG;IACH,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ;;;OAGG;IACH,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content.js","sourceRoot":"","sources":["../../src/types/content.ts"],"names":[],"mappings":""}
|
package/docs/composables.md
CHANGED
|
@@ -145,9 +145,9 @@ component('page-posts', () => {
|
|
|
145
145
|
const { data: posts, pending, error } = useFetch<Post[]>('/api/posts')
|
|
146
146
|
|
|
147
147
|
return html`
|
|
148
|
-
${pending.value
|
|
149
|
-
${error.value
|
|
150
|
-
<ul>${posts.value
|
|
148
|
+
${when(pending.value, () => html`<p>Loading…</p>`)}
|
|
149
|
+
${when(!!error.value, () => html`<p>Error: ${error.value!.message}</p>`)}
|
|
150
|
+
<ul>${each(posts.value ?? [], p => html`<li>${p.title}</li>`)}</ul>
|
|
151
151
|
`
|
|
152
152
|
})
|
|
153
153
|
```
|
|
@@ -165,10 +165,10 @@ component('page-nav', () => {
|
|
|
165
165
|
const { user, loggedIn, login, logout } = useAuth()
|
|
166
166
|
|
|
167
167
|
return html`
|
|
168
|
-
${
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
168
|
+
${match()
|
|
169
|
+
.when(loggedIn, () => html`<span>${user?.name}</span><button @click="${logout}">Log out</button>`)
|
|
170
|
+
.otherwise(() => html`<button @click="${() => login('github')}">Log in</button>`)
|
|
171
|
+
.done()}
|
|
172
172
|
`
|
|
173
173
|
})
|
|
174
174
|
```
|
|
@@ -736,10 +736,10 @@ component('locale-switcher', () => {
|
|
|
736
736
|
|
|
737
737
|
return html`
|
|
738
738
|
<nav>
|
|
739
|
-
${locales
|
|
739
|
+
${each(locales, (l) => html`
|
|
740
740
|
<a
|
|
741
|
-
href="${switchLocalePath(l)}"
|
|
742
|
-
aria-current="${l === locale ? 'true' : 'false'}"
|
|
741
|
+
:href="${switchLocalePath(l)}"
|
|
742
|
+
:aria-current="${l === locale ? 'true' : 'false'}"
|
|
743
743
|
>${l.toUpperCase()}</a>
|
|
744
744
|
`)}
|
|
745
745
|
</nav>
|
|
@@ -798,3 +798,108 @@ If you need it outside auto-imported directories:
|
|
|
798
798
|
```ts
|
|
799
799
|
import { navigateTo } from '@jasonshimmy/vite-plugin-cer-app/composables'
|
|
800
800
|
```
|
|
801
|
+
|
|
802
|
+
---
|
|
803
|
+
|
|
804
|
+
### `queryContent(path?)`
|
|
805
|
+
|
|
806
|
+
Queries content items from the file-based content layer. Returns a `QueryBuilder` that can be filtered, sorted, paginated, and terminated with `.find()`, `.first()`, or `.count()`.
|
|
807
|
+
|
|
808
|
+
Requires `content: {}` in `cer.config.ts`. See [content.md](./content.md) for full configuration, type reference, and rendering-mode behavior.
|
|
809
|
+
|
|
810
|
+
```ts
|
|
811
|
+
// All items
|
|
812
|
+
const all = await queryContent().find()
|
|
813
|
+
|
|
814
|
+
// Blog posts only (path prefix)
|
|
815
|
+
const posts = await queryContent('/blog').sortBy('date', 'desc').find()
|
|
816
|
+
|
|
817
|
+
// Single full document (includes body + TOC)
|
|
818
|
+
const doc = await queryContent('/docs/getting-started').first()
|
|
819
|
+
|
|
820
|
+
// Count
|
|
821
|
+
const total = await queryContent().count()
|
|
822
|
+
```
|
|
823
|
+
|
|
824
|
+
**QueryBuilder methods:**
|
|
825
|
+
|
|
826
|
+
| Method | Returns | Description |
|
|
827
|
+
|---|---|---|
|
|
828
|
+
| `.where(predicate)` | `QueryBuilder` | Predicate function — `(doc: ContentMeta) => boolean`. |
|
|
829
|
+
| `.sortBy(field, order?)` | `QueryBuilder` | Sort ascending (`'asc'`) or descending (`'desc'`). |
|
|
830
|
+
| `.limit(n)` | `QueryBuilder` | Return at most `n` items. |
|
|
831
|
+
| `.skip(n)` | `QueryBuilder` | Skip the first `n` items (pagination). |
|
|
832
|
+
| `.find()` | `Promise<ContentMeta[]>` | Execute, return lean metadata array. |
|
|
833
|
+
| `.first()` | `Promise<ContentItem \| null>` | Execute, return first full document with body and TOC. |
|
|
834
|
+
| `.count()` | `Promise<number>` | Execute, return count only. |
|
|
835
|
+
|
|
836
|
+
**Usage with a page loader:**
|
|
837
|
+
|
|
838
|
+
```ts
|
|
839
|
+
component('page-blog', () => {
|
|
840
|
+
const ssrData = usePageData<{ posts: ContentMeta[] }>()
|
|
841
|
+
const posts = ref<ContentMeta[]>(ssrData?.posts ?? [])
|
|
842
|
+
|
|
843
|
+
useOnConnected(async () => {
|
|
844
|
+
if (ssrData) return // hydrated from loader
|
|
845
|
+
posts.value = await queryContent('/blog').find()
|
|
846
|
+
})
|
|
847
|
+
|
|
848
|
+
return html`
|
|
849
|
+
<ul>
|
|
850
|
+
${each(posts.value, p => html`<li><a :href="${p._path}">${p.title}</a></li>`)}
|
|
851
|
+
</ul>
|
|
852
|
+
`
|
|
853
|
+
})
|
|
854
|
+
|
|
855
|
+
export const loader = async () => {
|
|
856
|
+
const posts = await queryContent('/blog').find()
|
|
857
|
+
return { posts }
|
|
858
|
+
}
|
|
859
|
+
```
|
|
860
|
+
|
|
861
|
+
If you need it outside auto-imported directories:
|
|
862
|
+
|
|
863
|
+
```ts
|
|
864
|
+
import { queryContent } from '@jasonshimmy/vite-plugin-cer-app/composables'
|
|
865
|
+
```
|
|
866
|
+
|
|
867
|
+
---
|
|
868
|
+
|
|
869
|
+
### `useContentSearch()`
|
|
870
|
+
|
|
871
|
+
Reactive full-text search over the content layer. Loads the MiniSearch index lazily on first use. Returns `query` and `results` refs that update reactively as the user types.
|
|
872
|
+
|
|
873
|
+
Requires `content: {}` in `cer.config.ts`. See [content.md](./content.md) for full documentation.
|
|
874
|
+
|
|
875
|
+
```ts
|
|
876
|
+
component('page-search', () => {
|
|
877
|
+
const { query, results } = useContentSearch()
|
|
878
|
+
|
|
879
|
+
return html`
|
|
880
|
+
<input type="search" :model="${query}" placeholder="Search…" />
|
|
881
|
+
<ul>
|
|
882
|
+
${each(results.value, r => html`
|
|
883
|
+
<li><a :href="${r._path}">${r.title}</a></li>
|
|
884
|
+
`)}
|
|
885
|
+
</ul>
|
|
886
|
+
`
|
|
887
|
+
})
|
|
888
|
+
```
|
|
889
|
+
|
|
890
|
+
**Return value:**
|
|
891
|
+
|
|
892
|
+
```ts
|
|
893
|
+
interface UseContentSearchReturn {
|
|
894
|
+
query: Ref<string> // bind with :model
|
|
895
|
+
results: Ref<ContentSearchResult[]> // reactive search results
|
|
896
|
+
}
|
|
897
|
+
```
|
|
898
|
+
|
|
899
|
+
Search activates when `query.value.length >= 2`. MiniSearch is loaded once and cached for the lifetime of the page. Searched fields are `title` and `description`.
|
|
900
|
+
|
|
901
|
+
If you need it outside auto-imported directories:
|
|
902
|
+
|
|
903
|
+
```ts
|
|
904
|
+
import { useContentSearch } from '@jasonshimmy/vite-plugin-cer-app/composables'
|
|
905
|
+
```
|
package/docs/configuration.md
CHANGED
|
@@ -220,6 +220,8 @@ import {
|
|
|
220
220
|
useCookie,
|
|
221
221
|
useSession,
|
|
222
222
|
useLocale,
|
|
223
|
+
queryContent,
|
|
224
|
+
useContentSearch,
|
|
223
225
|
defineMiddleware,
|
|
224
226
|
defineServerMiddleware,
|
|
225
227
|
navigateTo,
|
|
@@ -284,6 +286,37 @@ See [cli.md](./cli.md#cer-app-adapt) for full details.
|
|
|
284
286
|
|
|
285
287
|
---
|
|
286
288
|
|
|
289
|
+
## `content` options
|
|
290
|
+
|
|
291
|
+
Enables the file-based content layer. Drop Markdown or JSON files into `content/` at the project root and query them with `queryContent()` or search with `useContentSearch()`.
|
|
292
|
+
|
|
293
|
+
```ts
|
|
294
|
+
export default defineConfig({
|
|
295
|
+
content: {
|
|
296
|
+
dir: 'content', // default
|
|
297
|
+
drafts: false, // default
|
|
298
|
+
},
|
|
299
|
+
})
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
### `content.dir`
|
|
303
|
+
|
|
304
|
+
**Type:** `string`
|
|
305
|
+
**Default:** `'content'`
|
|
306
|
+
|
|
307
|
+
Content directory relative to the project root. The default resolves to `{root}/content/`.
|
|
308
|
+
|
|
309
|
+
### `content.drafts`
|
|
310
|
+
|
|
311
|
+
**Type:** `boolean`
|
|
312
|
+
**Default:** `false`
|
|
313
|
+
|
|
314
|
+
When `false`, files with `draft: true` in their frontmatter are excluded from the content store and search index in production builds.
|
|
315
|
+
|
|
316
|
+
See [content.md](./content.md) for full documentation.
|
|
317
|
+
|
|
318
|
+
---
|
|
319
|
+
|
|
287
320
|
## `i18n` options
|
|
288
321
|
|
|
289
322
|
Enables locale-aware URL routing and the `useLocale()` composable. No external package is required.
|