@phtngyn/cms 0.1.0-beta.1 → 0.1.0-beta.2
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 +192 -9
- package/package.json +30 -15
- package/src/Configuration-Br7tE9Qt.d.ts +68 -0
- package/src/{Configuration-DyKo0sLS.js → Configuration-CA_jvQKG.js} +26 -147
- package/src/Configuration-CA_jvQKG.js.map +1 -0
- package/src/Configuration.d.ts +2 -44
- package/src/Configuration.js +0 -1
- package/src/Configuration.js.map +1 -1
- package/src/{DocumentEngine-DYObwMDY.js → DocumentEngine-CgiBQU4g.js} +75 -27
- package/src/DocumentEngine-CgiBQU4g.js.map +1 -0
- package/src/DocumentModel-BB9igGC7.d.ts +34 -0
- package/src/DocumentModel.d.ts +2 -34
- package/src/DocumentModel.js +13 -3
- package/src/DocumentModel.js.map +1 -1
- package/src/Forms-BsJMev5_.d.ts +74 -0
- package/src/Forms-CfcI3tPU.js +79 -0
- package/src/Forms-CfcI3tPU.js.map +1 -0
- package/src/Forms-DxbkU3_5.js +367 -0
- package/src/Forms-DxbkU3_5.js.map +1 -0
- package/src/Forms.d.ts +35 -0
- package/src/Forms.js +13 -0
- package/src/Forms.js.map +1 -0
- package/src/{HttpPaths-BbS21Jy8.js → HttpPaths-c9nLZ9H2.js} +6 -2
- package/src/HttpPaths-c9nLZ9H2.js.map +1 -0
- package/src/I18n-DZK4CkJF.d.ts +21 -0
- package/src/I18n.d.ts +2 -0
- package/src/I18n.js +1 -0
- package/src/Module.js +57 -11
- package/src/Module.js.map +1 -1
- package/src/S3MediaObjects-JKMAWyVB.js +2664 -0
- package/src/S3MediaObjects-JKMAWyVB.js.map +1 -0
- package/src/Seed.d.ts +6 -0
- package/src/Seed.js +34 -0
- package/src/Seed.js.map +1 -0
- package/src/Seo-402YumpT.d.ts +198 -0
- package/src/{connection-CBTY-TzB.js → connection-B-iz0Y9Y.js} +5 -7
- package/src/connection-B-iz0Y9Y.js.map +1 -0
- package/src/{installation-DeuBiFRE.js → installation-DgF2M1gL.js} +1005 -2242
- package/src/installation-DgF2M1gL.js.map +1 -0
- package/src/internal/authoring-catalog.js +10 -4
- package/src/internal/authoring-catalog.js.map +1 -1
- package/src/internal/authoring-compiler.js +0 -1
- package/src/internal/authoring-compiler.js.map +1 -1
- package/src/internal/authoring-metadata.js +4 -4
- package/src/internal/authoring-metadata.js.map +1 -1
- package/src/internal/cms-config.js +0 -1
- package/src/internal/cms-config.js.map +1 -1
- package/src/internal/configuration.js +23 -10
- package/src/internal/configuration.js.map +1 -1
- package/src/internal/document-model.js +65 -20
- package/src/internal/document-model.js.map +1 -1
- package/src/internal/form-capabilities.js +12 -0
- package/src/internal/form-capabilities.js.map +1 -0
- package/src/internal/i18n.js +47 -0
- package/src/internal/i18n.js.map +1 -0
- package/src/internal/studio-app.js +2 -2
- package/src/internal/studio-app.js.map +1 -1
- package/src/{location-DtYv2aes.js → location-CFKTeK1K.js} +2 -3
- package/src/location-CFKTeK1K.js.map +1 -0
- package/src/{metadata-DE1Mc2td.js → metadata-BEZPWh-3.js} +3 -72
- package/src/metadata-BEZPWh-3.js.map +1 -0
- package/src/{models-C3witQBK.js → models-BWm1pTAf.js} +3 -2
- package/src/models-BWm1pTAf.js.map +1 -0
- package/src/{models-BkMgeaOL.js → models-DwYioyNV.js} +203 -32
- package/src/models-DwYioyNV.js.map +1 -0
- package/src/runtime/blocks.js +22 -9
- package/src/runtime/blocks.js.map +1 -1
- package/src/runtime/collaboration.js.map +1 -1
- package/src/runtime/delivery/DocumentRenderer.js +7 -4
- package/src/runtime/delivery/DocumentRenderer.js.map +1 -1
- package/src/runtime/delivery/blocks.js +3 -1
- package/src/runtime/delivery/blocks.js.map +1 -1
- package/src/runtime/delivery/context.js +1 -1
- package/src/runtime/delivery/document-page.js +12 -12
- package/src/runtime/delivery/document-page.js.map +1 -1
- package/src/runtime/delivery/document.js +4 -2
- package/src/runtime/delivery/document.js.map +1 -1
- package/src/runtime/delivery/error.js.map +1 -1
- package/src/runtime/delivery/plugin.js +22 -2
- package/src/runtime/delivery/plugin.js.map +1 -1
- package/src/runtime/delivery/types.d.ts +5 -0
- package/src/runtime/delivery/useCmsDocumentSeo.d.ts +56 -0
- package/src/runtime/delivery/useCmsDocumentSeo.js +99 -0
- package/src/runtime/delivery/useCmsDocumentSeo.js.map +1 -0
- package/src/runtime/delivery/useDocument.js +2 -2
- package/src/runtime/delivery/useDocument.js.map +1 -1
- package/src/runtime/delivery/useDocumentQuery.js +2 -2
- package/src/runtime/delivery/useDocumentQuery.js.map +1 -1
- package/src/runtime/forms/document.js +38 -0
- package/src/runtime/forms/document.js.map +1 -0
- package/src/runtime/forms/useCmsForm.js +149 -0
- package/src/runtime/forms/useCmsForm.js.map +1 -0
- package/src/runtime/i18n/translator.js +22 -0
- package/src/runtime/i18n/translator.js.map +1 -0
- package/src/runtime/i18n/useCmsI18n.d.ts +9 -0
- package/src/runtime/i18n/useCmsI18n.js +29 -0
- package/src/runtime/i18n/useCmsI18n.js.map +1 -0
- package/src/runtime/installation.js +1 -1
- package/src/runtime/plugin.js +1 -1
- package/src/runtime/plugin.js.map +1 -1
- package/src/runtime/preview/comark-document.js +0 -1
- package/src/runtime/preview/comark-document.js.map +1 -1
- package/src/runtime/preview/connection.js +1 -1
- package/src/runtime/preview/controller.js +10 -3
- package/src/runtime/preview/controller.js.map +1 -1
- package/src/runtime/preview/overlay.js.map +1 -1
- package/src/runtime/preview/plugin.client.js +59 -48
- package/src/runtime/preview/plugin.client.js.map +1 -1
- package/src/runtime/preview/protocol.js.map +1 -1
- package/src/runtime/preview/store.js +1 -1
- package/src/runtime/preview/targets.js +0 -1
- package/src/runtime/preview/targets.js.map +1 -1
- package/src/runtime/sitemap-entries.js +17 -0
- package/src/runtime/sitemap-entries.js.map +1 -0
- package/src/runtime/sitemap.js +24 -0
- package/src/runtime/sitemap.js.map +1 -0
- package/src/runtime/studio-app.js.map +1 -1
- package/src/runtime/studio-entry.js +2 -2
- package/src/runtime/studio-entry.js.map +1 -1
- package/src/runtime/studio-navigation.js +1 -1
- package/src/scalars-B2lgGUnj.js +26 -0
- package/src/scalars-B2lgGUnj.js.map +1 -0
- package/src/{store-CfFFx9Ua.js → store-DjJs3I7k.js} +12 -7
- package/src/store-DjJs3I7k.js.map +1 -0
- package/studio/{CreateDocumentDialog--L9lTfx6.js → CreateDocumentDialog-CTeIFlPb.js} +44 -44
- package/studio/{collaboration-DUuu678R.js → collaboration-DcK2F6v9.js} +7 -6
- package/studio/{documentPlanning-BXX04Woe.js → documentPlanning-Bfv-d7OU.js} +7310 -6479
- package/studio/{editor-PexnXkJY.js → editor-Bzlh3zXt.js} +7965 -7955
- package/studio/host.css +1 -1
- package/studio/host.js +10416 -7981
- package/src/Configuration-DyKo0sLS.js.map +0 -1
- package/src/DocumentEngine-DYObwMDY.js.map +0 -1
- package/src/HttpPaths-BbS21Jy8.js.map +0 -1
- package/src/StudioHttp-jR2-bc01.js +0 -718
- package/src/StudioHttp-jR2-bc01.js.map +0 -1
- package/src/connection-CBTY-TzB.js.map +0 -1
- package/src/installation-DeuBiFRE.js.map +0 -1
- package/src/location-DtYv2aes.js.map +0 -1
- package/src/metadata-DE1Mc2td.js.map +0 -1
- package/src/models-BkMgeaOL.js.map +0 -1
- package/src/models-C3witQBK.js.map +0 -1
- package/src/store-CfFFx9Ua.js.map +0 -1
package/README.md
CHANGED
|
@@ -2,12 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
A CMS for Nuxt with an embedded Studio, collaborative editing, PostgreSQL persistence, and S3-compatible media storage. Install one module, configure your services in `cms.config.ts`, and create your first administrator in Studio. Database migrations and authentication are managed by the module.
|
|
4
4
|
|
|
5
|
+
## Working on this package
|
|
6
|
+
|
|
7
|
+
This is the consumer guide. For implementation changes, start at [Module](./src/Module.ts) for Nuxt registration, [Configuration](./src/Configuration.ts) and [configuration compilation](./src/internal/configuration.ts) for setup, [runtime](./src/runtime/) for delivery, and [package exports and scripts](./package.json) for the current public surface. Follow the feature into its owning package and tests before changing behavior; examples below illustrate usage rather than define the current contract. The [canonical example](../../examples/nuxt/README.md) exercises the module end to end.
|
|
8
|
+
|
|
5
9
|
## Install
|
|
6
10
|
|
|
7
11
|
Requires Node.js 24, Nuxt `>=4.5.0 <5`, and Vue `>=3.6.0-rc.6 <4`. Deploy as a Node server with WebSocket support; static hosting is not supported.
|
|
8
12
|
|
|
9
13
|
```sh
|
|
10
|
-
pnpm add @phtngyn/cms@beta
|
|
14
|
+
pnpm add @phtngyn/cms@beta effect
|
|
11
15
|
```
|
|
12
16
|
|
|
13
17
|
Register the module in `nuxt.config.ts`:
|
|
@@ -26,7 +30,7 @@ Create `cms.config.ts` beside `nuxt.config.ts`:
|
|
|
26
30
|
|
|
27
31
|
```ts
|
|
28
32
|
import { defineCmsConfig } from '@phtngyn/cms/Configuration'
|
|
29
|
-
import {
|
|
33
|
+
import { Schema } from 'effect'
|
|
30
34
|
|
|
31
35
|
function env(name: string): string {
|
|
32
36
|
const value = process.env[name]
|
|
@@ -44,7 +48,7 @@ export default defineCmsConfig({
|
|
|
44
48
|
pages: {
|
|
45
49
|
label: 'Page',
|
|
46
50
|
route: '/:path*',
|
|
47
|
-
schema:
|
|
51
|
+
schema: Schema.Struct({ title: Schema.NonEmptyString }),
|
|
48
52
|
source: '.',
|
|
49
53
|
},
|
|
50
54
|
},
|
|
@@ -70,9 +74,81 @@ These environment variable names are examples; configuration is executable TypeS
|
|
|
70
74
|
|
|
71
75
|
For storage using the AWS credential chain, omit `credentials`. To create a missing bucket automatically, set `provisionBucket: true` and grant bucket-creation permission. Set `media: false` to start without uploads.
|
|
72
76
|
|
|
77
|
+
## Search and sharing
|
|
78
|
+
|
|
79
|
+
The module installs the focused Nuxt robots, sitemap, schema.org, and SEO utility modules. Published CMS pages receive canonical, robots, social-sharing, and structured-data metadata automatically. The sitemap contains only published, indexable documents and uses their publication revision time for `lastmod`; localized variants are emitted as alternatives when they share a document key.
|
|
80
|
+
|
|
81
|
+
Administrators can edit and publish site-wide SEO defaults at `/_cms/app/seo`. Editors can override search and sharing fields per document, while omitted values inherit the site-wide revision. Draft preview uses working SEO settings; ordinary website requests use published settings.
|
|
82
|
+
|
|
83
|
+
The initial site-wide revision is derived from `baseUrl`. To seed explicit defaults and restrict the structured identity choices Studio offers, add `seo` to `cms.config.ts`:
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
seo: {
|
|
87
|
+
defaults: {
|
|
88
|
+
canonicalOrigin: 'https://example.com',
|
|
89
|
+
siteName: 'Example',
|
|
90
|
+
titleTemplate: '%s · Example',
|
|
91
|
+
defaultDescription: 'Example publishes useful things.',
|
|
92
|
+
index: true,
|
|
93
|
+
follow: true,
|
|
94
|
+
identity: {
|
|
95
|
+
type: 'Organization',
|
|
96
|
+
name: 'Example',
|
|
97
|
+
url: 'https://example.com',
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
capabilities: {
|
|
101
|
+
identityTypes: ['Organization'],
|
|
102
|
+
structuredDataTypes: ['WebPage', 'Article'],
|
|
103
|
+
generatedSocialImageTemplateIds: [],
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Keep raw robots directives and Nuxt module configuration in application code. The CMS owns semantic index/follow choices and generated page metadata; it does not accept arbitrary JSON-LD or inline HTML.
|
|
109
|
+
|
|
110
|
+
## Collection schemas
|
|
111
|
+
|
|
112
|
+
Effect schemas work directly. The CMS derives Studio inputs from their encoded structure and runs the original decoder when validating documents, preserving defaults, transformations, and asynchronous validation. Decoders must not require external Effect services, and their output must be a JSON-compatible object.
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
import { Effect, Schema } from 'effect'
|
|
116
|
+
import { CalendarDate, field } from '@phtngyn/cms/DocumentModel'
|
|
117
|
+
|
|
118
|
+
const pageSchema = Schema.Struct({
|
|
119
|
+
description: field(Schema.String.annotate({ default: '' }), { input: 'textarea' }).pipe(
|
|
120
|
+
Schema.withDecodingDefaultKey(Effect.succeed('')),
|
|
121
|
+
),
|
|
122
|
+
publishedAt: Schema.optionalKey(field(CalendarDate, { label: 'Published on' })),
|
|
123
|
+
title: Schema.NonEmptyString,
|
|
124
|
+
})
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Use `Schema.optionalKey` for optional fields and `Schema.Finite` for numeric fields. An encoded `default` annotation supplies Studio's initial value; `Schema.withDecodingDefaultKey` applies that default during validation. `CalendarDate` validates calendar dates stored as `YYYY-MM-DD` strings. `field` adds presentation annotations to the encoded side of an Effect schema.
|
|
128
|
+
|
|
129
|
+
Other libraries are supported through a schema implementing **both Standard Schema V1 and Standard JSON Schema V1**. Standard Schema supplies validation and typed output; Standard JSON Schema's `jsonSchema.input({ target: 'draft-2020-12' })` supplies the editor structure. A library that only implements validation needs an adapter adding JSON Schema conversion; Standard Schema alone cannot generate a form.
|
|
130
|
+
|
|
131
|
+
For example, an existing Standard Schema validator can be adapted with its library's JSON Schema converters:
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
const collectionSchema = {
|
|
135
|
+
'~standard': {
|
|
136
|
+
...validator['~standard'],
|
|
137
|
+
jsonSchema: {
|
|
138
|
+
input: () => inputJsonSchema,
|
|
139
|
+
output: () => outputJsonSchema,
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Supply actual draft 2020-12 schemas from the chosen library, using its input and output conversion modes respectively. Foreign schemas use JSON Schema `title`, `description`, `default`, and `x-cms-input` for presentation. The CMS reads only the input structure and retains the original validator for execution.
|
|
146
|
+
|
|
147
|
+
Studio supports strings, calendar dates, finite numbers, booleans, string arrays, choices, and nested objects. Non-recursive local references are resolved. Ambiguous unions, structural intersections, recursive schemas, and unsupported field shapes fail configuration rather than producing incomplete forms.
|
|
148
|
+
|
|
73
149
|
## Start editing
|
|
74
150
|
|
|
75
|
-
1.
|
|
151
|
+
1. Start your Nuxt application and open its `/_cms/app` route. In this repository, `pnpm dev` runs the canonical example at `http://localhost:3000/_cms/app`.
|
|
76
152
|
2. Enter your initialization token and create the first administrator. Initialization then closes.
|
|
77
153
|
3. Create a page in Studio, edit it, and publish it. The module provides page routing and rendering; your app must render `<NuxtPage />` in `app/app.vue` if it has a custom app shell. Existing app routes can provide custom page layouts.
|
|
78
154
|
|
|
@@ -86,14 +162,15 @@ Draft delivery requires the current Studio session and document access permissio
|
|
|
86
162
|
|
|
87
163
|
## Customize
|
|
88
164
|
|
|
89
|
-
- Define collections with
|
|
165
|
+
- Define collections with Effect Schema in `documents.collections`. Import `field` from `@phtngyn/cms/DocumentModel` to add field labels and input hints.
|
|
90
166
|
- Use the auto-imported `useDocument` and `useDocumentQuery` composables and `DocumentRenderer`, `DocumentScope`, and `DocumentField` components for custom rendering.
|
|
91
167
|
- Import `defineCmsBlock` from `@phtngyn/cms/Authoring` to declare editable props and slots in your Nuxt Vue components.
|
|
92
168
|
- Optionally provide `initialSources` to import Markdown documents once when the installation is first claimed.
|
|
93
169
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
170
|
+
For a folder of published Markdown files, import `seedFromDirectory` from `@phtngyn/cms/Seed` and set
|
|
171
|
+
`initialSources: seedFromDirectory(new URL('./seed/', import.meta.url))` in `cms.config.ts`.
|
|
172
|
+
The folder is read when Nuxt loads the configuration. Paths within it become document source paths;
|
|
173
|
+
for example, `seed/en/index.md` becomes `en/index.md`. Files are published on the first claim.
|
|
97
174
|
|
|
98
175
|
## Studio and authoring during development
|
|
99
176
|
|
|
@@ -113,7 +190,7 @@ A collection can restrict available component blocks:
|
|
|
113
190
|
pages: {
|
|
114
191
|
label: 'Page',
|
|
115
192
|
route: '/:path*',
|
|
116
|
-
schema:
|
|
193
|
+
schema: Schema.Struct({ title: Schema.String }),
|
|
117
194
|
body: {
|
|
118
195
|
blocks: ['content-hero'],
|
|
119
196
|
groups: ['Editorial'],
|
|
@@ -159,3 +236,109 @@ Every list item needs a required `id: string` in its Vue type. Studio generates
|
|
|
159
236
|
Collaboration merges object fields and list-item fields independently. List ordering is separate from item values; simultaneous insertions at the same position retain both items in deterministic identity order. Removing an item takes precedence over concurrent edits to it. Competing edits to the same field use Yjs conflict resolution. Authoring schemas and controls update through the existing HMR path without reseeding content.
|
|
160
237
|
|
|
161
238
|
See the example's `ContentSection.vue` for a complete component-local declaration with images, links, layout settings, repeatable features and nested blocks.
|
|
239
|
+
|
|
240
|
+
## Headless forms
|
|
241
|
+
|
|
242
|
+
Register a form under the kebab-case name of your Nuxt Vue renderer. `defineCmsForm` is a normal TypeScript helper, not a compiler macro. It declares the question types and validation rules editors may use:
|
|
243
|
+
|
|
244
|
+
```ts
|
|
245
|
+
import { defineCmsConfig } from '@phtngyn/cms/Configuration'
|
|
246
|
+
import { defineCmsForm } from '@phtngyn/cms/Forms'
|
|
247
|
+
|
|
248
|
+
// Inside your existing defineCmsConfig configuration:
|
|
249
|
+
forms: {
|
|
250
|
+
'contact-form': {
|
|
251
|
+
definition: defineCmsForm({
|
|
252
|
+
label: 'Contact form',
|
|
253
|
+
fieldTypes: ['text', 'email', 'textarea', 'number', 'checkbox', 'select'],
|
|
254
|
+
rules: ['required', 'minLength', 'maxLength', 'min', 'max'],
|
|
255
|
+
}),
|
|
256
|
+
onSubmit: async ({ form, values }) => {
|
|
257
|
+
// Server only: deliver or persist validated answers here.
|
|
258
|
+
// values are keyed by stable question IDs; form.steps contains their labels.
|
|
259
|
+
},
|
|
260
|
+
},
|
|
261
|
+
},
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
Create `ContactForm.vue` with an optional `cmsForm?: CmsFormDefinition` prop. The registration supplies its Studio authoring controls; do not also declare `defineCmsBlock` for that renderer. Forms appear in the Forms block group and follow collection block permissions. Editors add steps, questions, choices and permitted rules in the property panel. One step produces a single-step form. Question and option IDs are managed by Studio; labels can change without changing identities. Select answers contain option IDs. There is no website input markup or CSS supplied by CMS.
|
|
265
|
+
|
|
266
|
+
A valid authored form receives `cmsForm` from `DocumentRenderer`. Incomplete drafts do not; show a helpful placeholder until the form is valid. Mount your custom form contents only when that definition exists:
|
|
267
|
+
|
|
268
|
+
```ts
|
|
269
|
+
import type { CmsFormDefinition } from '@phtngyn/cms/Forms'
|
|
270
|
+
import { useCmsForm } from '@phtngyn/cms/Forms'
|
|
271
|
+
|
|
272
|
+
const props = defineProps<{ definition: CmsFormDefinition }>()
|
|
273
|
+
const {
|
|
274
|
+
fields,
|
|
275
|
+
steps,
|
|
276
|
+
values,
|
|
277
|
+
errors,
|
|
278
|
+
currentStep,
|
|
279
|
+
stepIndex,
|
|
280
|
+
isFirstStep,
|
|
281
|
+
isLastStep,
|
|
282
|
+
isSubmitting,
|
|
283
|
+
isSuccess,
|
|
284
|
+
isPreview,
|
|
285
|
+
error,
|
|
286
|
+
next,
|
|
287
|
+
previous,
|
|
288
|
+
validate,
|
|
289
|
+
submit,
|
|
290
|
+
reset,
|
|
291
|
+
} = useCmsForm(() => props.definition)
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
The composable accepts a value, ref, or getter and returns destructurable refs and functions. Bind controls to `values[field.id]`. Number answers must be numbers or null, checkbox answers booleans, and other answers strings. Empty optional answers reach the server handler as null; a required checkbox must be checked. `next()` validates the current step, `previous()` preserves answers, and `submit()` validates every step and returns whether submission succeeded. Validation errors are keyed by question ID; `_form` holds a form-wide error. Render accessible labels, error announcements, progress, and focus behavior in your application.
|
|
295
|
+
|
|
296
|
+
Definition replacements preserve answers for matching IDs and field types within the same form, clear validation state, and cancel pending client requests. `reset()` clears answers and submission state. The composable prevents overlapping submissions and repeat submission after success until reset. Scope disposal cancels client work. Cancellation cannot undo a server handler that has already started. An optional `submit(payload, signal)` transport override supports advanced integrations; it must return `CmsFormResult` and preserve server-side validation.
|
|
297
|
+
|
|
298
|
+
The endpoint resolves the current published form and checks its exact serialized revision, including capabilities and question settings. Stale pages must reload. Draft preview validates locally without invoking the handler. Invalid structure prevents publication; invalid answers and unknown answer keys never reach the handler. Dynamic editor-created questions have runtime-validated answer types, not compile-time-known answer keys.
|
|
299
|
+
|
|
300
|
+
Requests are limited to 128 KiB and 60 submissions per minute per server process, shared across forms. Browser requests with a foreign Origin are rejected. For deployments with multiple replicas or stronger spam protection, enforce a shared limit or challenge at your edge. There is no automatic retry, durable deduplication, inbox, email delivery, upload, branching, or save-and-resume in this release. Application handlers should account for retries after an uncertain network outcome. Configuration changes use Nuxt's restart lifecycle.
|
|
301
|
+
|
|
302
|
+
See `examples/nuxt/app/components/FormContents.vue` for a Tailwind-rendered example. Its server handler acknowledges submissions without storing answers.
|
|
303
|
+
|
|
304
|
+
## Website languages
|
|
305
|
+
|
|
306
|
+
Declare languages and developer-owned website strings together in `cms.config.ts`:
|
|
307
|
+
|
|
308
|
+
```ts
|
|
309
|
+
i18n: {
|
|
310
|
+
defaultLocale: 'en',
|
|
311
|
+
fallbackLocale: 'en',
|
|
312
|
+
prefixDefaultLocale: false,
|
|
313
|
+
locales: {
|
|
314
|
+
en: { name: 'English', messages: {
|
|
315
|
+
navigation: { home: 'Home' },
|
|
316
|
+
footer: { copyright: '© {year} Acme' },
|
|
317
|
+
} },
|
|
318
|
+
de: { name: 'Deutsch', messages: {
|
|
319
|
+
navigation: { home: 'Startseite' },
|
|
320
|
+
} },
|
|
321
|
+
},
|
|
322
|
+
},
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
Locale codes also define the first directory of document source paths: `en/index.md` serves `/`, and `de/index.md` serves `/de`. Collection source matching starts after that directory. Markdown contains the editor-authored language already; it needs no translation key. Markdown and source paths remain stored in document revisions, with collaborative working edits checkpointed into Markdown. Seed files import once; changing them does not synchronize an existing database.
|
|
326
|
+
|
|
327
|
+
Use the auto-imported composable in website components:
|
|
328
|
+
|
|
329
|
+
```ts
|
|
330
|
+
const { t, locale } = useCmsI18n()
|
|
331
|
+
const navigation = computed(() => [{ label: t('navigation.home'), to: locale.value === 'de' ? '/de' : '/' }])
|
|
332
|
+
t('footer.copyright', { year: 2026 })
|
|
333
|
+
useHead({ htmlAttrs: { lang: locale } })
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
`locale` is a read-only computed value derived from the current URL, including in Studio's website preview. Switch languages by navigating to a localized URL. For documents with different authored slugs, supply their actual destinations. Lists can explicitly pass `locale.value` to `useDocumentQuery`.
|
|
337
|
+
|
|
338
|
+
Message keys receive generated TypeScript completion. Dictionaries contain nested plain strings; dots are reserved for key paths. Named `{parameters}` accept strings and numbers; missing parameters remain visible. Messages fall back from the URL locale to `fallbackLocale`, then to the key. Omit `fallbackLocale` (or set it to `null`) to disable fallback. The same explicit fallback policy applies to document lookup. Development warns about missing requested-locale messages. Render messages as text, and use native `Intl` for date and number formatting.
|
|
339
|
+
|
|
340
|
+
Only validated public i18n data enters the website bundle. Installation credentials and handlers stay server-side. Small dictionaries can stay inline; larger ones can be imported into the same config. Configuration changes restart Nuxt. Omitting `i18n` preserves unlocalized source paths; the composable returns an empty locale and untranslated keys.
|
|
341
|
+
|
|
342
|
+
## License
|
|
343
|
+
|
|
344
|
+
Proprietary. Copyright (c) 2026 Phat (phtngyn). All rights reserved. Use, copying, modification, and redistribution require prior written permission. Third-party components retain their own licenses.
|
package/package.json
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"directory": "packages/nuxt"
|
|
15
15
|
},
|
|
16
16
|
"name": "@phtngyn/cms",
|
|
17
|
-
"version": "0.1.0-beta.
|
|
17
|
+
"version": "0.1.0-beta.2",
|
|
18
18
|
"type": "module",
|
|
19
19
|
"files": [
|
|
20
20
|
"studio",
|
|
@@ -38,38 +38,53 @@
|
|
|
38
38
|
"./DocumentModel": {
|
|
39
39
|
"types": "./src/DocumentModel.d.ts",
|
|
40
40
|
"import": "./src/DocumentModel.js"
|
|
41
|
+
},
|
|
42
|
+
"./Seed": {
|
|
43
|
+
"types": "./src/Seed.d.ts",
|
|
44
|
+
"import": "./src/Seed.js"
|
|
45
|
+
},
|
|
46
|
+
"./I18n": {
|
|
47
|
+
"types": "./src/I18n.d.ts",
|
|
48
|
+
"import": "./src/I18n.js"
|
|
49
|
+
},
|
|
50
|
+
"./Forms": {
|
|
51
|
+
"types": "./src/Forms.d.ts",
|
|
52
|
+
"import": "./src/Forms.js"
|
|
41
53
|
}
|
|
42
54
|
},
|
|
43
55
|
"dependencies": {
|
|
44
|
-
"@aws-sdk/client-s3": "^3.
|
|
45
|
-
"@comark/nuxt": "^0.
|
|
46
|
-
"@comark/vue": "^0.
|
|
47
|
-
"@effect/platform-node-shared": "^4.0.0-rc.
|
|
48
|
-
"@effect/sql-pg": "^4.0.0-rc.
|
|
49
|
-
"@hocuspocus/server": "^4.
|
|
56
|
+
"@aws-sdk/client-s3": "^3.1131.0",
|
|
57
|
+
"@comark/nuxt": "^0.7.0",
|
|
58
|
+
"@comark/vue": "^0.7.0",
|
|
59
|
+
"@effect/platform-node-shared": "^4.0.0-rc.115",
|
|
60
|
+
"@effect/sql-pg": "^4.0.0-rc.115",
|
|
61
|
+
"@hocuspocus/server": "^4.7.0",
|
|
50
62
|
"@nuxt/kit": "^4.5.2",
|
|
51
63
|
"@nuxt/schema": "^4.5.2",
|
|
64
|
+
"@nuxtjs/robots": "^6.2.2",
|
|
65
|
+
"@nuxtjs/sitemap": "^8.5.1",
|
|
52
66
|
"@tiptap/core": "^3.31.3",
|
|
53
67
|
"@tiptap/starter-kit": "^3.31.3",
|
|
54
68
|
"@tiptap/y-tiptap": "^3.0.9",
|
|
55
|
-
"@vue/compiler-sfc": "^3.6.0-rc.
|
|
56
|
-
"better-auth": "^1.7.
|
|
57
|
-
"comark": "^0.
|
|
69
|
+
"@vue/compiler-sfc": "^3.6.0-rc.8",
|
|
70
|
+
"better-auth": "^1.7.4",
|
|
71
|
+
"comark": "^0.7.0",
|
|
58
72
|
"diff": "^9.0.0",
|
|
59
73
|
"drizzle-orm": "^1.0.0-rc.5-169397b",
|
|
60
|
-
"effect": "^4.0.0-rc.
|
|
74
|
+
"effect": "^4.0.0-rc.115",
|
|
61
75
|
"h3": "^1.15.11",
|
|
62
76
|
"jiti": "^2.7.0",
|
|
63
77
|
"nitropack": "^2.13.4",
|
|
78
|
+
"nuxt-schema-org": "^6.3.1",
|
|
79
|
+
"nuxt-seo-utils": "^8.5.0",
|
|
64
80
|
"pg": "^8.23.0",
|
|
65
81
|
"typescript": "npm:typescript-native-bridge@6.0.3-bridge.16.tsgo.7.0.2",
|
|
66
|
-
"vite": "^8.
|
|
82
|
+
"vite": "^8.3.0",
|
|
67
83
|
"vue-component-meta": "^3.3.11",
|
|
68
|
-
"yjs": "^13.6.32"
|
|
69
|
-
"zod": "^4.5.4"
|
|
84
|
+
"yjs": "^13.6.32"
|
|
70
85
|
},
|
|
71
86
|
"peerDependencies": {
|
|
72
87
|
"nuxt": ">=4.5.0 <5.0.0",
|
|
73
88
|
"vue": ">=3.6.0-rc.6 <4.0.0"
|
|
74
89
|
}
|
|
75
|
-
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { t as CmsI18nOptions } from "./I18n-DZK4CkJF.js";
|
|
2
|
+
import { n as FormDefinition, s as FormValues, t as FormCapabilities } from "./Forms-BsJMev5_.js";
|
|
3
|
+
import { i as CmsDocumentModelOptions } from "./DocumentModel-BB9igGC7.js";
|
|
4
|
+
import { r as SeoConfiguration } from "./Seo-402YumpT.js";
|
|
5
|
+
import { Cause, Context, Crypto, DateTime, Effect, Layer, Option, Result, Schema, Scope, Stream } from "effect";
|
|
6
|
+
import { Extension, Node } from "@tiptap/core";
|
|
7
|
+
import "comark";
|
|
8
|
+
//#region ../../node_modules/.pnpm/prosemirror-view@1.42.3/node_modules/prosemirror-view/dist/index.d.ts
|
|
9
|
+
declare global {
|
|
10
|
+
interface Node {}
|
|
11
|
+
}
|
|
12
|
+
//#endregion
|
|
13
|
+
//#region ../server/src/Forms.d.ts
|
|
14
|
+
type FormSubmissionHandler = (submission: {
|
|
15
|
+
readonly form: FormDefinition;
|
|
16
|
+
readonly values: FormValues;
|
|
17
|
+
}) => void | PromiseLike<void>;
|
|
18
|
+
//#endregion
|
|
19
|
+
//#region src/Configuration.d.ts
|
|
20
|
+
type CmsSeoOptions = Schema.Codec.Encoded<typeof SeoConfiguration>;
|
|
21
|
+
interface CmsInitialDocument {
|
|
22
|
+
readonly markdown: string;
|
|
23
|
+
readonly published: boolean;
|
|
24
|
+
readonly sourcePath: string;
|
|
25
|
+
}
|
|
26
|
+
type CmsInitialDocumentProvider = () => PromiseLike<ReadonlyArray<CmsInitialDocument>> | ReadonlyArray<CmsInitialDocument>;
|
|
27
|
+
interface CmsCleanupOptions {
|
|
28
|
+
readonly initialDelayMillis?: number;
|
|
29
|
+
readonly intervalMillis?: number;
|
|
30
|
+
}
|
|
31
|
+
interface CmsMediaStorageCredentials {
|
|
32
|
+
readonly accessKeyId: string;
|
|
33
|
+
readonly secretAccessKey: string;
|
|
34
|
+
readonly sessionToken?: string;
|
|
35
|
+
}
|
|
36
|
+
interface CmsMediaStorageOptions {
|
|
37
|
+
readonly bucket: string;
|
|
38
|
+
readonly credentials?: CmsMediaStorageCredentials;
|
|
39
|
+
readonly endpoint?: string;
|
|
40
|
+
readonly forcePathStyle?: boolean;
|
|
41
|
+
readonly provisionBucket?: boolean;
|
|
42
|
+
readonly region: string;
|
|
43
|
+
}
|
|
44
|
+
interface CmsMediaOptions {
|
|
45
|
+
readonly maxUploadBytes?: number;
|
|
46
|
+
readonly publicBaseUrl: string;
|
|
47
|
+
readonly storage: CmsMediaStorageOptions;
|
|
48
|
+
}
|
|
49
|
+
interface CmsConfiguration {
|
|
50
|
+
readonly forms?: Readonly<Record<string, {
|
|
51
|
+
readonly definition: FormCapabilities;
|
|
52
|
+
readonly onSubmit: FormSubmissionHandler;
|
|
53
|
+
}>>;
|
|
54
|
+
readonly baseUrl: string;
|
|
55
|
+
readonly cleanup?: false | CmsCleanupOptions;
|
|
56
|
+
readonly databaseUrl: string;
|
|
57
|
+
readonly documents: CmsDocumentModelOptions;
|
|
58
|
+
readonly i18n?: CmsI18nOptions;
|
|
59
|
+
readonly initialSources?: CmsInitialDocumentProvider;
|
|
60
|
+
readonly initializationToken: string;
|
|
61
|
+
readonly media?: false | CmsMediaOptions;
|
|
62
|
+
readonly seo?: CmsSeoOptions;
|
|
63
|
+
readonly secret: string;
|
|
64
|
+
}
|
|
65
|
+
declare function defineCmsConfig<const Configuration extends CmsConfiguration>(configuration: Configuration): Configuration;
|
|
66
|
+
//#endregion
|
|
67
|
+
export { CmsMediaOptions as a, CmsSeoOptions as c, CmsInitialDocumentProvider as i, defineCmsConfig as l, CmsConfiguration as n, CmsMediaStorageCredentials as o, CmsInitialDocument as r, CmsMediaStorageOptions as s, CmsCleanupOptions as t };
|
|
68
|
+
//# sourceMappingURL=Configuration-Br7tE9Qt.d.ts.map
|