@xleddyl/nuxt-cms 0.2.1 → 0.2.3
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 +3 -1
- package/dist/module.d.mts +4 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +320 -33
- package/dist/runtime/app/blocks/CmsBlocks.d.vue.ts +45 -0
- package/dist/runtime/app/blocks/CmsBlocks.vue +36 -0
- package/dist/runtime/app/blocks/CmsBlocks.vue.d.ts +45 -0
- package/dist/runtime/app/blocks/resolve.d.ts +21 -0
- package/dist/runtime/app/blocks/resolve.js +37 -0
- package/dist/runtime/app/components/cms/BlockOutline.d.vue.ts +17 -0
- package/dist/runtime/app/components/cms/BlockOutline.vue +99 -0
- package/dist/runtime/app/components/cms/BlockOutline.vue.d.ts +17 -0
- package/dist/runtime/app/components/cms/BlockPalette.d.vue.ts +12 -0
- package/dist/runtime/app/components/cms/BlockPalette.vue +29 -0
- package/dist/runtime/app/components/cms/BlockPalette.vue.d.ts +12 -0
- package/dist/runtime/app/components/cms/BlocksField.d.vue.ts +2 -0
- package/dist/runtime/app/components/cms/BlocksField.vue +83 -3
- package/dist/runtime/app/components/cms/BlocksField.vue.d.ts +2 -0
- package/dist/runtime/app/components/cms/BrandMark.vue +2 -3
- package/dist/runtime/app/components/cms/ConfirmModal.vue +14 -1
- package/dist/runtime/app/components/cms/EntryForm.vue +9 -4
- package/dist/runtime/app/components/cms/FieldInput.d.vue.ts +2 -0
- package/dist/runtime/app/components/cms/FieldInput.vue +18 -1
- package/dist/runtime/app/components/cms/FieldInput.vue.d.ts +2 -0
- package/dist/runtime/app/components/cms/MediaGallery.vue +89 -11
- package/dist/runtime/app/components/cms/Modal.vue +0 -2
- package/dist/runtime/app/components/cms/SettingsEntry.vue +3 -2
- package/dist/runtime/app/components/cms/SettingsModal.vue +2 -1
- package/dist/runtime/app/components/cms/StatusBadge.d.vue.ts +2 -0
- package/dist/runtime/app/components/cms/StatusBadge.vue +9 -3
- package/dist/runtime/app/components/cms/StatusBadge.vue.d.ts +2 -0
- package/dist/runtime/app/components/cms/VisualEditor.d.vue.ts +37 -0
- package/dist/runtime/app/components/cms/VisualEditor.vue +533 -0
- package/dist/runtime/app/components/cms/VisualEditor.vue.d.ts +37 -0
- package/dist/runtime/app/composables/cms-confirm.d.ts +16 -7
- package/dist/runtime/app/composables/cms-confirm.js +1 -0
- package/dist/runtime/app/composables/cms-entry-disabled.d.ts +7 -1
- package/dist/runtime/app/composables/cms-entry-disabled.js +15 -0
- package/dist/runtime/app/composables/cms-entry.d.ts +7 -1
- package/dist/runtime/app/composables/cms-entry.js +51 -1
- package/dist/runtime/app/composables/cms-media-library.d.ts +1 -2
- package/dist/runtime/app/composables/cms-media-library.js +12 -19
- package/dist/runtime/app/composables/cms-runtime.d.ts +1 -0
- package/dist/runtime/app/composables/cms-settings.js +8 -2
- package/dist/runtime/app/layouts/cms-admin.vue +5 -0
- package/dist/runtime/app/layouts/cms-editor.d.vue.ts +13 -0
- package/dist/runtime/app/layouts/cms-editor.vue +14 -0
- package/dist/runtime/app/layouts/cms-editor.vue.d.ts +13 -0
- package/dist/runtime/app/pages/admin-collection.vue +33 -4
- package/dist/runtime/app/pages/admin-content.d.vue.ts +3 -0
- package/dist/runtime/app/pages/admin-content.vue +284 -0
- package/dist/runtime/app/pages/admin-content.vue.d.ts +3 -0
- package/dist/runtime/app/pages/admin-preview.d.vue.ts +3 -0
- package/dist/runtime/app/pages/admin-preview.vue +132 -0
- package/dist/runtime/app/pages/admin-preview.vue.d.ts +3 -0
- package/dist/runtime/app/preview/PreviewOverlay.d.vue.ts +15 -0
- package/dist/runtime/app/preview/PreviewOverlay.vue +362 -0
- package/dist/runtime/app/preview/PreviewOverlay.vue.d.ts +15 -0
- package/dist/runtime/app/utils/ui.d.ts +0 -2
- package/dist/runtime/app/utils/ui.js +1 -0
- package/dist/runtime/assets/main.css +1 -1
- package/dist/runtime/seed.js +11 -1
- package/dist/runtime/server/api/collection.get.js +3 -2
- package/dist/runtime/server/api/collection.post.js +24 -15
- package/dist/runtime/server/api/collection.put.js +12 -12
- package/dist/runtime/server/api/item.delete.js +2 -1
- package/dist/runtime/server/api/item.put.js +16 -14
- package/dist/runtime/server/api/media-folder.patch.js +10 -16
- package/dist/runtime/server/api/media-move.post.js +12 -8
- package/dist/runtime/server/api/media.delete.js +1 -1
- package/dist/runtime/server/api/media.post.js +2 -1
- package/dist/runtime/server/api/media.put.js +2 -1
- package/dist/runtime/server/plugins/migrate-sqlite.js +2 -2
- package/dist/runtime/server/routes/auth/login.post.js +28 -22
- package/dist/runtime/server/utils/db-d1.d.ts +0 -1
- package/dist/runtime/server/utils/db-d1.js +3 -4
- package/dist/runtime/server/utils/db-libsql.d.ts +0 -1
- package/dist/runtime/server/utils/db-libsql.js +3 -4
- package/dist/runtime/server/utils/db-postgres.d.ts +0 -1
- package/dist/runtime/server/utils/db-postgres.js +1 -4
- package/dist/runtime/server/utils/db-sqlite.d.ts +0 -1
- package/dist/runtime/server/utils/db-sqlite.js +4 -10
- package/dist/runtime/server/utils/graphql.js +25 -9
- package/dist/runtime/server/utils/libsql-migrations.d.ts +1 -0
- package/dist/runtime/server/utils/libsql-migrations.js +1 -1
- package/dist/runtime/server/utils/media-folders.js +14 -23
- package/dist/runtime/server/utils/media.js +2 -0
- package/dist/runtime/server/utils/migrate.d.ts +1 -0
- package/dist/runtime/server/utils/migrate.js +32 -0
- package/dist/runtime/server/utils/registry.d.ts +4 -0
- package/dist/runtime/server/utils/registry.js +16 -1
- package/dist/runtime/server/utils/relations.d.ts +1 -1
- package/dist/runtime/server/utils/relations.js +6 -9
- package/dist/runtime/server/utils/timestamps.d.ts +12 -0
- package/dist/runtime/server/utils/timestamps.js +48 -0
- package/dist/runtime/server/utils/users.js +3 -2
- package/dist/runtime/server/utils/visibility.d.ts +4 -0
- package/dist/runtime/server/utils/visibility.js +12 -0
- package/dist/runtime/server/utils/write-statements.d.ts +33 -0
- package/dist/runtime/server/utils/write-statements.js +75 -0
- package/dist/runtime/shared/graphql-sdl.js +4 -1
- package/dist/runtime/shared/index.d.ts +73 -5
- package/dist/runtime/shared/index.js +146 -0
- package/dist/runtime/shared/preview.d.ts +85 -0
- package/dist/runtime/shared/preview.js +219 -0
- package/dist/runtime/shared/timestamps.d.ts +4 -0
- package/dist/runtime/shared/timestamps.js +24 -0
- package/dist/runtime/shared/validation.d.ts +2 -0
- package/dist/runtime/shared/validation.js +15 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,6 +8,8 @@ nuxt-cms is a Nuxt module that leverages the Nitro server to ship a lightweight
|
|
|
8
8
|
|
|
9
9
|
- **Zero extra infrastructure**: the CMS runs inside your app's Nitro server; you deploy one thing.
|
|
10
10
|
- **Content types in code**: a `cms.config.ts` with `defineCmsConfig()` declares collections, single documents, relations, blocks and translatable fields; database schema, migrations and TypeScript types are generated from it.
|
|
11
|
+
- **Content with blocks**: news, blog and other content types with title, slug, cover, SEO fields and a body built from your own site components, drafts and scheduled publishing; `<CmsBlocks>` renders the body with typed props.
|
|
12
|
+
- **Visual editor**: content items (and any blocks field built from site components) are edited on a live preview of your own site, with drag and drop, a block palette, per-locale and mobile previews, scheduling and publishing.
|
|
11
13
|
- **Admin panel at `/cms`**: entry editing with validation, drafts, media library (S3-compatible storage, a directory on the server's disk, or a local mode backed directly by your `public/` folder), single-admin auth from env credentials.
|
|
12
14
|
- **Public GraphQL API**: read-only, typed end-to-end via gql.tada, with filtering, sorting and pagination; fields marked `private` stay out of it.
|
|
13
15
|
- **SQLite, Postgres, libSQL/Turso or Cloudflare D1**: a local file database by default, one config line to switch (including remote SQLite over the network).
|
|
@@ -99,7 +101,7 @@ Full documentation lives in [`docs/`](docs/README.md):
|
|
|
99
101
|
- [Configuration](docs/configuration.md) — every `cms.*` option and the `NUXT_CMS_*` env vars.
|
|
100
102
|
- [Database](docs/database.md) — SQLite, Postgres, libSQL/Turso and D1 drivers, migrations, studio.
|
|
101
103
|
- [Schema](docs/schema.md) — `defineCmsConfig`, entries, pages, field types, relations, blocks, i18n.
|
|
102
|
-
- [Querying content](docs/querying.md) — GraphQL API, `useCmsSingle` / `useCmsCollection` / `useCmsPage` / `useCms` / `$cmsQuery`, filters, sorting, pagination.
|
|
104
|
+
- [Querying content](docs/querying.md) — GraphQL API, `useCmsSingle` / `useCmsCollection` / `useCmsPage` / `useCmsContents` / `useCmsContent` / `useCms` / `$cmsQuery`, `<CmsBlocks>`, filters, sorting, pagination.
|
|
103
105
|
- [Admin panel & security](docs/admin.md) — pages, authentication, sessions, admin REST API.
|
|
104
106
|
- [Media](docs/media.md) — S3-compatible storage, filesystem storage on the server's disk, or local mode backed by your `public/` folder, upload flow, allowed file types.
|
|
105
107
|
- [Deployment](docs/deployment.md) — host/driver matrix, migrations on serverless, horizontal scaling, Cloudflare Workers.
|
package/dist/module.d.mts
CHANGED
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -3,20 +3,86 @@ import { existsSync } from 'node:fs';
|
|
|
3
3
|
import { createRequire } from 'node:module';
|
|
4
4
|
import { join, isAbsolute, resolve, relative, dirname } from 'node:path';
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
|
-
import { defineNuxtModule, createResolver, useLogger, addImports, addTemplate, addServerPlugin, addTypeTemplate, addVitePlugin, addComponentsDir, addRouteMiddleware, extendPages, addServerHandler, resolvePath } from '@nuxt/kit';
|
|
6
|
+
import { defineNuxtModule, createResolver, useLogger, addImports, addTemplate, addServerPlugin, addTypeTemplate, addVitePlugin, addComponentsDir, addRouteMiddleware, extendPages, addServerHandler, resolvePath, addComponent } from '@nuxt/kit';
|
|
7
7
|
import tailwindcss from '@tailwindcss/vite';
|
|
8
8
|
import svgLoader from 'vite-svg-loader';
|
|
9
9
|
import { introspectionFromSchema, buildSchema } from 'graphql';
|
|
10
10
|
import { minifyIntrospection, outputIntrospectionFile } from 'gql.tada/internal';
|
|
11
11
|
import { createJiti } from 'jiti';
|
|
12
|
-
import { typeName, blockTypeName, blocksFieldTypeName, renderGraphqlSdl } from '../dist/runtime/shared/graphql-sdl.js';
|
|
12
|
+
import { typeName, blockTypeName as blockTypeName$1, blocksFieldTypeName, renderGraphqlSdl } from '../dist/runtime/shared/graphql-sdl.js';
|
|
13
13
|
import { migrationsDirFor } from '../dist/runtime/shared/migrations-dir.js';
|
|
14
|
-
import { pageColumnFields, isTranslatableField, isTranslatableMediaField, isMultiSelect, pageAllFields, PAGE_PATH_FIELD, pageRoutes, layoutErrors, listErrors, mobileMediaKey, fieldConditions, pageFieldsTableName, pageMediaTableName, isRequiredField, pageFields,
|
|
14
|
+
import { entryFieldsFor, isPrivateField, blockTypeName, pageColumnFields, isTranslatableField, isTranslatableMediaField, isMultiSelect, pageAllFields, normalizeContentEntries, PAGE_PATH_FIELD, pageRoutes, isCollectionKind, layoutErrors, listErrors, mobileMediaKey, fieldConditions, CONTENT_SLUG_FIELD, pageFieldsTableName, pageMediaTableName, CONTENT_SYSTEM_FIELDS, isRequiredField, pageFields, mediaTypeFilter, DEFAULT_MEDIA_MAX_FILE_SIZE, normalizeCmsConfig } from '../dist/runtime/shared/index.js';
|
|
15
|
+
import { previewPathErrors, DEFAULT_CMS_PREVIEW_PATH } from '../dist/runtime/shared/preview.js';
|
|
15
16
|
import { scanMediaDirectory, readMediaFileMeta } from '../dist/runtime/server/utils/media-sync.js';
|
|
16
17
|
import { createHash } from 'node:crypto';
|
|
17
18
|
import { readFile } from 'node:fs/promises';
|
|
18
19
|
import { routePathsFromDir, resolvePageRoutes } from '../dist/runtime/shared/page-routes.js';
|
|
19
20
|
|
|
21
|
+
function componentPascalName(name) {
|
|
22
|
+
return name.split(/[-_\s]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
|
|
23
|
+
}
|
|
24
|
+
function collectBlockComponents(config) {
|
|
25
|
+
const refs = [];
|
|
26
|
+
for (const [entryName, entry] of Object.entries(config)) {
|
|
27
|
+
for (const [fieldKey, field] of Object.entries(entryFieldsFor(entry))) {
|
|
28
|
+
if (field.type !== "blocks" || isPrivateField(field)) continue;
|
|
29
|
+
for (const [blockName, block] of Object.entries(field.blocks ?? {})) {
|
|
30
|
+
if (typeof block.component !== "string" || !block.component.trim()) continue;
|
|
31
|
+
refs.push({
|
|
32
|
+
entry: entryName,
|
|
33
|
+
field: fieldKey,
|
|
34
|
+
block: blockName,
|
|
35
|
+
component: componentPascalName(block.component.trim()),
|
|
36
|
+
typeName: blockTypeName(entryName, fieldKey, blockName)
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return refs;
|
|
42
|
+
}
|
|
43
|
+
function collectPreviewComponents(config, fallback) {
|
|
44
|
+
const refs = [];
|
|
45
|
+
for (const [entryName, entry] of Object.entries(config)) {
|
|
46
|
+
if (entry.kind !== "content") continue;
|
|
47
|
+
const name = entry.preview?.component ?? fallback;
|
|
48
|
+
if (typeof name !== "string" || !name.trim()) continue;
|
|
49
|
+
refs.push({ entry: entryName, component: componentPascalName(name.trim()) });
|
|
50
|
+
}
|
|
51
|
+
return refs;
|
|
52
|
+
}
|
|
53
|
+
function missingPreviewComponents(refs, available) {
|
|
54
|
+
const known = new Set(available);
|
|
55
|
+
return refs.filter((ref) => !known.has(ref.component)).map(
|
|
56
|
+
(ref) => `cms.config entry '${ref.entry}': preview component '${ref.component}' is not a registered Nuxt component`
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
function missingBlockComponents(refs, available) {
|
|
60
|
+
const known = new Set(available);
|
|
61
|
+
return refs.filter((ref) => !known.has(ref.component)).map(
|
|
62
|
+
(ref) => `cms.config entry '${ref.entry}', field '${ref.field}', block '${ref.block}': component '${ref.component}' is not a registered Nuxt component`
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
function renderBlocksFile(refs, previews = []) {
|
|
66
|
+
const components = [...new Set([...refs, ...previews].map((ref) => ref.component))].sort();
|
|
67
|
+
const lines = [`import type { Component } from 'vue'`];
|
|
68
|
+
if (components.length)
|
|
69
|
+
lines.push(
|
|
70
|
+
`import { ${components.map((name) => `Lazy${name}`).join(", ")} } from '#components'`
|
|
71
|
+
);
|
|
72
|
+
lines.push(
|
|
73
|
+
``,
|
|
74
|
+
`export const cmsBlockComponents: Record<string, Component> = {`,
|
|
75
|
+
...refs.map((ref) => ` ${JSON.stringify(ref.typeName)}: Lazy${ref.component},`),
|
|
76
|
+
`}`,
|
|
77
|
+
``,
|
|
78
|
+
`export const cmsPreviewComponents: Record<string, Component> = {`,
|
|
79
|
+
...previews.map((ref) => ` ${JSON.stringify(ref.entry)}: Lazy${ref.component},`),
|
|
80
|
+
`}`,
|
|
81
|
+
``
|
|
82
|
+
);
|
|
83
|
+
return lines.join("\n");
|
|
84
|
+
}
|
|
85
|
+
|
|
20
86
|
async function collectMediaManifest(root) {
|
|
21
87
|
const files = await scanMediaDirectory(root);
|
|
22
88
|
return Promise.all(files.map((file) => readMediaFileMeta(root, file)));
|
|
@@ -92,9 +158,19 @@ const RESERVED_ENTRY_KEYS = [
|
|
|
92
158
|
"cms_users"
|
|
93
159
|
];
|
|
94
160
|
const RESERVED_COLUMNS = ["id", "status", "created_at", "updated_at"];
|
|
95
|
-
const TITLE_FIELD_TYPES = ["text", "slug", "email", "number", "date", "select"];
|
|
161
|
+
const TITLE_FIELD_TYPES = ["text", "slug", "email", "number", "date", "datetime", "select"];
|
|
96
162
|
const TRANSLATABLE_FIELD_TYPES = ["text", "richtext", "media"];
|
|
97
|
-
const CONDITION_FIELD_TYPES = [
|
|
163
|
+
const CONDITION_FIELD_TYPES = [
|
|
164
|
+
"select",
|
|
165
|
+
"boolean",
|
|
166
|
+
"text",
|
|
167
|
+
"number",
|
|
168
|
+
"date",
|
|
169
|
+
"datetime",
|
|
170
|
+
"email",
|
|
171
|
+
"slug"
|
|
172
|
+
];
|
|
173
|
+
const COMPONENT_RESERVED_KEYS = ["key", "ref", "class", "style"];
|
|
98
174
|
const RESERVED_TYPE_NAMES = [
|
|
99
175
|
"Query",
|
|
100
176
|
"Mutation",
|
|
@@ -110,6 +186,7 @@ const RESERVED_TYPE_NAMES = [
|
|
|
110
186
|
function validateConfig(config, i18n) {
|
|
111
187
|
const errors = [];
|
|
112
188
|
const locales = i18n?.locales ?? [];
|
|
189
|
+
normalizeContentEntries(config, { locales });
|
|
113
190
|
if (locales.length && !locales.includes(i18n.defaultLocale)) {
|
|
114
191
|
errors.push(
|
|
115
192
|
`cms.i18n: defaultLocale '${i18n.defaultLocale}' is not in locales [${locales.join(
|
|
@@ -143,8 +220,10 @@ function validateConfig(config, i18n) {
|
|
|
143
220
|
} else {
|
|
144
221
|
entryIds.set(entry.id, name);
|
|
145
222
|
}
|
|
146
|
-
if (
|
|
147
|
-
errors.push(`${at}: kind must be 'collection', 'single' or '
|
|
223
|
+
if (!["collection", "single", "page", "content"].includes(entry.kind))
|
|
224
|
+
errors.push(`${at}: kind must be 'collection', 'single', 'page' or 'content'`);
|
|
225
|
+
if (entry.kind === "content") errors.push(...contentErrors(at, entry));
|
|
226
|
+
else if (entry.preview !== void 0) errors.push(`${at}: preview needs kind 'content'`);
|
|
148
227
|
if (entry.kind === "page") {
|
|
149
228
|
if (pageEntry) {
|
|
150
229
|
errors.push(
|
|
@@ -196,7 +275,7 @@ function validateConfig(config, i18n) {
|
|
|
196
275
|
errors.push(`${at}: routes, include, exclude and overrides need kind 'page'`);
|
|
197
276
|
}
|
|
198
277
|
if (entry.kind !== "page" && entry.columns) errors.push(`${at}: columns need kind 'page'`);
|
|
199
|
-
if (entry.drafts && entry
|
|
278
|
+
if (entry.drafts && !isCollectionKind(entry))
|
|
200
279
|
errors.push(`${at}: drafts are only supported on collections`);
|
|
201
280
|
const tabIds = /* @__PURE__ */ new Set();
|
|
202
281
|
for (const tab of entry.tabs ?? []) {
|
|
@@ -237,8 +316,8 @@ function validateConfig(config, i18n) {
|
|
|
237
316
|
const allFields = entry.kind === "page" ? pageAllFields(entry) : entry.fields ?? {};
|
|
238
317
|
errors.push(...layoutErrors(at, allFields, entry.layout));
|
|
239
318
|
errors.push(...listErrors(at, allFields, entry.list, entry.drafts ? ["status"] : []));
|
|
240
|
-
if (entry.list && entry
|
|
241
|
-
errors.push(`${at}: list needs kind 'collection'`);
|
|
319
|
+
if (entry.list && !isCollectionKind(entry))
|
|
320
|
+
errors.push(`${at}: list needs kind 'collection' or 'content'`);
|
|
242
321
|
if (entry.icon !== void 0 && (typeof entry.icon !== "string" || !entry.icon))
|
|
243
322
|
errors.push(`${at}: icon must be a non-empty string`);
|
|
244
323
|
const columnNames = /* @__PURE__ */ new Set();
|
|
@@ -321,7 +400,7 @@ function validateConfig(config, i18n) {
|
|
|
321
400
|
if (!source) errors.push(`${fat}: slug source '${field.from}' is not a declared field`);
|
|
322
401
|
else if (source.type !== "text")
|
|
323
402
|
errors.push(`${fat}: slug source '${field.from}' must be a text field`);
|
|
324
|
-
else if (isTranslatableField(source))
|
|
403
|
+
else if (isTranslatableField(source) && !(entry.kind === "content" && key === CONTENT_SLUG_FIELD))
|
|
325
404
|
errors.push(`${fat}: slug source '${field.from}' cannot be translatable`);
|
|
326
405
|
}
|
|
327
406
|
if (field.type === "blocks") {
|
|
@@ -335,6 +414,7 @@ function validateConfig(config, i18n) {
|
|
|
335
414
|
errors.push(`${bat}: key must be a valid identifier`);
|
|
336
415
|
if (!block.fields || !Object.keys(block.fields).length)
|
|
337
416
|
errors.push(`${bat}: fields must not be empty`);
|
|
417
|
+
errors.push(...blockMetaErrors(bat, block));
|
|
338
418
|
for (const [blockFieldKey, blockField] of Object.entries(block.fields ?? {})) {
|
|
339
419
|
const bfat = `${bat}, field '${blockFieldKey}'`;
|
|
340
420
|
if (!IDENTIFIER.test(blockFieldKey))
|
|
@@ -376,8 +456,10 @@ function validateConfig(config, i18n) {
|
|
|
376
456
|
if (!field.to || !target) {
|
|
377
457
|
errors.push(`${fat}: relation target '${field.to}' is not in the registry`);
|
|
378
458
|
} else {
|
|
379
|
-
if (target
|
|
380
|
-
errors.push(
|
|
459
|
+
if (!isCollectionKind(target))
|
|
460
|
+
errors.push(
|
|
461
|
+
`${fat}: relation target '${field.to}' must be a collection or a content entry`
|
|
462
|
+
);
|
|
381
463
|
if (!entry.table && target.table)
|
|
382
464
|
errors.push(
|
|
383
465
|
`${fat}: relation target '${field.to}' uses a custom table: derived relations can only target derived entries`
|
|
@@ -428,6 +510,51 @@ function validateConfig(config, i18n) {
|
|
|
428
510
|
}
|
|
429
511
|
return errors;
|
|
430
512
|
}
|
|
513
|
+
function contentErrors(at, entry) {
|
|
514
|
+
const errors = [];
|
|
515
|
+
const system = CONTENT_SYSTEM_FIELDS;
|
|
516
|
+
for (const key of entry.declared?.fields ?? []) {
|
|
517
|
+
if (system.includes(key))
|
|
518
|
+
errors.push(`${at}: '${key}' is a system field of content entries, remove it from fields`);
|
|
519
|
+
}
|
|
520
|
+
if (entry.declared?.titleField !== void 0)
|
|
521
|
+
errors.push(`${at}: content entries use 'title' as titleField, remove titleField`);
|
|
522
|
+
if (entry.declared?.drafts !== void 0)
|
|
523
|
+
errors.push(`${at}: content entries always have drafts, remove the drafts option`);
|
|
524
|
+
if (!entry.blocks || typeof entry.blocks !== "object" || !Object.keys(entry.blocks).length)
|
|
525
|
+
errors.push(`${at}: content entries need a non-empty blocks map for the body`);
|
|
526
|
+
if (entry.table) errors.push(`${at}: content entries cannot use a custom table`);
|
|
527
|
+
if (entry.preview !== void 0) {
|
|
528
|
+
const component = entry.preview?.component;
|
|
529
|
+
if (!entry.preview || typeof entry.preview !== "object")
|
|
530
|
+
errors.push(`${at}: preview must be an object`);
|
|
531
|
+
else if (component !== void 0 && (typeof component !== "string" || !component.trim()))
|
|
532
|
+
errors.push(`${at}: preview.component must be a non-empty string`);
|
|
533
|
+
}
|
|
534
|
+
for (const key of Object.keys(entry.labels ?? {})) {
|
|
535
|
+
if (!system.includes(key))
|
|
536
|
+
errors.push(`${at}: labels names '${key}', which is not a system field`);
|
|
537
|
+
}
|
|
538
|
+
return errors;
|
|
539
|
+
}
|
|
540
|
+
function blockMetaErrors(at, block) {
|
|
541
|
+
const errors = [];
|
|
542
|
+
if (block.component !== void 0) {
|
|
543
|
+
if (typeof block.component !== "string" || !block.component.trim())
|
|
544
|
+
errors.push(`${at}: component must be a non-empty string`);
|
|
545
|
+
for (const key of Object.keys(block.fields ?? {})) {
|
|
546
|
+
if (COMPONENT_RESERVED_KEYS.includes(key))
|
|
547
|
+
errors.push(
|
|
548
|
+
`${at}, field '${key}': '${key}' cannot be a prop of the block component, rename it`
|
|
549
|
+
);
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
if (block.icon !== void 0 && (typeof block.icon !== "string" || !block.icon))
|
|
553
|
+
errors.push(`${at}: icon must be a non-empty string`);
|
|
554
|
+
if (block.description !== void 0 && typeof block.description !== "string")
|
|
555
|
+
errors.push(`${at}: description must be a string`);
|
|
556
|
+
return errors;
|
|
557
|
+
}
|
|
431
558
|
function pageColumnsErrors(at, entry) {
|
|
432
559
|
const errors = [];
|
|
433
560
|
if (entry.table) errors.push(`${at}: page entries cannot use a custom table`);
|
|
@@ -525,7 +652,7 @@ function tableExpr(name, entry, dialect) {
|
|
|
525
652
|
lines.push(columnExpr(key, field, dialect));
|
|
526
653
|
}
|
|
527
654
|
if (entry.drafts) lines.push(` status: text('status').notNull().default('draft'),`);
|
|
528
|
-
if (entry
|
|
655
|
+
if (isCollectionKind(entry)) lines.push(` createdAt: ${nowExpr(pg, "created_at")},`);
|
|
529
656
|
lines.push(` updatedAt: ${nowExpr(pg, "updated_at")},`);
|
|
530
657
|
return `export const ${name} = ${tableFn}('${name}', {
|
|
531
658
|
${lines.join("\n")}
|
|
@@ -638,13 +765,13 @@ ${[
|
|
|
638
765
|
|
|
639
766
|
const MEDIA_SELECTION = "key url type alt folder mime size width height";
|
|
640
767
|
function blocksSelection(entryName, key, field) {
|
|
641
|
-
const parts = ["type"];
|
|
768
|
+
const parts = ["__typename type"];
|
|
642
769
|
for (const [blockName, block] of Object.entries(field.blocks ?? {})) {
|
|
643
770
|
const fields = Object.entries(block.fields).map(
|
|
644
771
|
([blockFieldKey, blockField]) => blockField.type === "media" ? `${blockFieldKey} { ${MEDIA_SELECTION} }` : blockFieldKey
|
|
645
772
|
);
|
|
646
773
|
if (!fields.length) continue;
|
|
647
|
-
parts.push(`... on ${blockTypeName(entryName, key, blockName)} { ${fields.join(" ")} }`);
|
|
774
|
+
parts.push(`... on ${blockTypeName$1(entryName, key, blockName)} { ${fields.join(" ")} }`);
|
|
648
775
|
}
|
|
649
776
|
return parts.join(" ");
|
|
650
777
|
}
|
|
@@ -669,7 +796,7 @@ function entrySelection(config, name, entry, withRelations, fields = entryFields
|
|
|
669
796
|
}
|
|
670
797
|
parts.push(key);
|
|
671
798
|
}
|
|
672
|
-
if (entry
|
|
799
|
+
if (isCollectionKind(entry)) parts.push("createdAt");
|
|
673
800
|
parts.push("updatedAt");
|
|
674
801
|
return parts.join(" ");
|
|
675
802
|
}
|
|
@@ -677,7 +804,7 @@ function singleQuery(config, name, entry) {
|
|
|
677
804
|
const selection = entrySelection(config, name, entry, true);
|
|
678
805
|
return `query CmsSingle($locale: String) { ${name}(locale: $locale) { ${selection} } }`;
|
|
679
806
|
}
|
|
680
|
-
function collectionQuery(config, name, entry) {
|
|
807
|
+
function collectionQuery(config, name, entry, operation = "CmsCollection") {
|
|
681
808
|
const selection = entrySelection(config, name, entry, true);
|
|
682
809
|
const gqlType = typeName(name);
|
|
683
810
|
const args = [
|
|
@@ -687,7 +814,14 @@ function collectionQuery(config, name, entry) {
|
|
|
687
814
|
"$limit: Int",
|
|
688
815
|
"$offset: Int"
|
|
689
816
|
].join(", ");
|
|
690
|
-
return `query
|
|
817
|
+
return `query ${operation}(${args}) { ${name}(locale: $locale, filters: $filters, sort: $sort, limit: $limit, offset: $offset) { ${selection} } }`;
|
|
818
|
+
}
|
|
819
|
+
function contentListQuery(config, name, entry) {
|
|
820
|
+
return collectionQuery(config, name, entry, "CmsContents");
|
|
821
|
+
}
|
|
822
|
+
function contentItemQuery(config, name, entry) {
|
|
823
|
+
const selection = entrySelection(config, name, entry, true);
|
|
824
|
+
return `query CmsContent($slug: String!, $locale: String) { item: ${name}BySlug(slug: $slug, locale: $locale) { ${selection} } }`;
|
|
691
825
|
}
|
|
692
826
|
function pageQuery(config, name, entry, path) {
|
|
693
827
|
const selection = entrySelection(config, name, entry, true, pageFields(entry, path));
|
|
@@ -697,7 +831,18 @@ function renderQueriesFile(config) {
|
|
|
697
831
|
const singles = [];
|
|
698
832
|
const collections = [];
|
|
699
833
|
const pages = [];
|
|
834
|
+
const contents = [];
|
|
835
|
+
const contentItems = [];
|
|
700
836
|
for (const [name, entry] of Object.entries(config)) {
|
|
837
|
+
if (entry.kind === "content") {
|
|
838
|
+
contents.push(
|
|
839
|
+
` ${JSON.stringify(name)}: ${JSON.stringify(contentListQuery(config, name, entry))},`
|
|
840
|
+
);
|
|
841
|
+
contentItems.push(
|
|
842
|
+
` ${JSON.stringify(name)}: ${JSON.stringify(contentItemQuery(config, name, entry))},`
|
|
843
|
+
);
|
|
844
|
+
continue;
|
|
845
|
+
}
|
|
701
846
|
if (entry.kind === "page") {
|
|
702
847
|
for (const route of pageRoutes(entry)) {
|
|
703
848
|
pages.push(
|
|
@@ -726,6 +871,14 @@ function renderQueriesFile(config) {
|
|
|
726
871
|
`export const cmsPageQueries: Record<string, string> = {`,
|
|
727
872
|
...pages,
|
|
728
873
|
`}`,
|
|
874
|
+
``,
|
|
875
|
+
`export const cmsContentQueries: Record<string, string> = {`,
|
|
876
|
+
...contents,
|
|
877
|
+
`}`,
|
|
878
|
+
``,
|
|
879
|
+
`export const cmsContentItemQueries: Record<string, string> = {`,
|
|
880
|
+
...contentItems,
|
|
881
|
+
`}`,
|
|
729
882
|
``
|
|
730
883
|
].join("\n");
|
|
731
884
|
}
|
|
@@ -767,13 +920,17 @@ function fieldTsType(config, entryName, key, field) {
|
|
|
767
920
|
const base = scalarTsType(field);
|
|
768
921
|
return isRequiredField(field) ? base : `${base} | null`;
|
|
769
922
|
}
|
|
923
|
+
function blockPropsTypeName(entryName, fieldKey, blockName) {
|
|
924
|
+
return `${blockTypeName$1(entryName, fieldKey, blockName)}Props`;
|
|
925
|
+
}
|
|
770
926
|
function blockTypesTs(entryName, key, field) {
|
|
771
927
|
const defs = [];
|
|
772
928
|
const members = [];
|
|
773
929
|
for (const [blockName, block] of Object.entries(field.blocks ?? {})) {
|
|
774
|
-
const name = blockTypeName(entryName, key, blockName);
|
|
930
|
+
const name = blockTypeName$1(entryName, key, blockName);
|
|
931
|
+
const props = blockPropsTypeName(entryName, key, blockName);
|
|
775
932
|
members.push(name);
|
|
776
|
-
const lines = [
|
|
933
|
+
const lines = [];
|
|
777
934
|
for (const [blockFieldKey, blockField] of Object.entries(block.fields)) {
|
|
778
935
|
if (blockField.type === "media") {
|
|
779
936
|
lines.push(` ${blockFieldKey}: ${mediaTsType(blockField)} | null`);
|
|
@@ -782,9 +939,15 @@ function blockTypesTs(entryName, key, field) {
|
|
|
782
939
|
const base = scalarTsType(blockField);
|
|
783
940
|
lines.push(` ${blockFieldKey}: ${blockField.required ? base : `${base} | null`}`);
|
|
784
941
|
}
|
|
785
|
-
defs.push(`export interface ${
|
|
942
|
+
defs.push(`export interface ${props} {
|
|
786
943
|
${lines.join("\n")}
|
|
787
944
|
}`);
|
|
945
|
+
defs.push(
|
|
946
|
+
`export interface ${name} extends ${props} {
|
|
947
|
+
__typename?: '${name}'
|
|
948
|
+
type: '${blockName}'
|
|
949
|
+
}`
|
|
950
|
+
);
|
|
788
951
|
}
|
|
789
952
|
if (members.length)
|
|
790
953
|
defs.push(`export type ${blocksFieldTypeName(entryName, key)} = ${members.join(" | ")}`);
|
|
@@ -797,7 +960,7 @@ function entryTs(config, name, entry) {
|
|
|
797
960
|
if (isPrivateField(field)) continue;
|
|
798
961
|
lines.push(` ${key}: ${fieldTsType(config, name, key, field)}`);
|
|
799
962
|
}
|
|
800
|
-
if (entry
|
|
963
|
+
if (isCollectionKind(entry)) lines.push(" createdAt: string");
|
|
801
964
|
lines.push(" updatedAt: string");
|
|
802
965
|
return `export interface ${typeName(name)} {
|
|
803
966
|
${lines.join("\n")}
|
|
@@ -853,11 +1016,13 @@ ${lines.join("\n")}
|
|
|
853
1016
|
function entryMapsTs(config) {
|
|
854
1017
|
const singles = [];
|
|
855
1018
|
const collections = [];
|
|
1019
|
+
const contents = [];
|
|
856
1020
|
const pages = [];
|
|
857
1021
|
for (const [name, entry] of Object.entries(config)) {
|
|
858
1022
|
const line = ` ${JSON.stringify(name)}: ${typeName(name)}Auto`;
|
|
859
1023
|
if (entry.kind === "single") singles.push(line);
|
|
860
1024
|
else if (entry.kind === "page") pages.push(...pageTypesTs(name, entry));
|
|
1025
|
+
else if (entry.kind === "content") contents.push(line);
|
|
861
1026
|
else collections.push(line);
|
|
862
1027
|
}
|
|
863
1028
|
if (!pages.length) {
|
|
@@ -874,12 +1039,50 @@ ${singles.join("\n")}
|
|
|
874
1039
|
}`,
|
|
875
1040
|
`export interface CmsCollectionTypes {
|
|
876
1041
|
${collections.join("\n")}
|
|
1042
|
+
}`,
|
|
1043
|
+
`export interface CmsContentTypes {
|
|
1044
|
+
${contents.join("\n")}
|
|
877
1045
|
}`,
|
|
878
1046
|
`export type CmsSingleName = keyof CmsSingleTypes`,
|
|
879
1047
|
`export type CmsCollectionName = keyof CmsCollectionTypes`,
|
|
1048
|
+
`export type CmsContentName = keyof CmsContentTypes`,
|
|
880
1049
|
...pages
|
|
881
1050
|
];
|
|
882
1051
|
}
|
|
1052
|
+
function blockPropsMapTs(config) {
|
|
1053
|
+
const entries = [];
|
|
1054
|
+
for (const [name, entry] of Object.entries(config)) {
|
|
1055
|
+
const fields = [];
|
|
1056
|
+
for (const [key, field] of Object.entries(entryFieldsFor(entry))) {
|
|
1057
|
+
if (field.type !== "blocks" || isPrivateField(field)) continue;
|
|
1058
|
+
const blocks = Object.keys(field.blocks ?? {}).map(
|
|
1059
|
+
(blockName) => ` ${JSON.stringify(blockName)}: ${blockPropsTypeName(name, key, blockName)}`
|
|
1060
|
+
);
|
|
1061
|
+
if (blocks.length)
|
|
1062
|
+
fields.push(` ${JSON.stringify(key)}: {
|
|
1063
|
+
${blocks.join("\n")}
|
|
1064
|
+
}`);
|
|
1065
|
+
}
|
|
1066
|
+
if (fields.length) entries.push(` ${JSON.stringify(name)}: {
|
|
1067
|
+
${fields.join("\n")}
|
|
1068
|
+
}`);
|
|
1069
|
+
}
|
|
1070
|
+
return [
|
|
1071
|
+
`export interface CmsBlockPropTypes {
|
|
1072
|
+
${entries.join("\n")}
|
|
1073
|
+
}`,
|
|
1074
|
+
`export type CmsBlockEntryName = [keyof CmsBlockPropTypes] extends [never]
|
|
1075
|
+
? string
|
|
1076
|
+
: keyof CmsBlockPropTypes`,
|
|
1077
|
+
[
|
|
1078
|
+
`export type CmsBlockProps<`,
|
|
1079
|
+
` E extends keyof CmsBlockPropTypes,`,
|
|
1080
|
+
` F extends keyof CmsBlockPropTypes[E],`,
|
|
1081
|
+
` B extends keyof CmsBlockPropTypes[E][F],`,
|
|
1082
|
+
`> = CmsBlockPropTypes[E][F][B]`
|
|
1083
|
+
].join("\n")
|
|
1084
|
+
];
|
|
1085
|
+
}
|
|
883
1086
|
function renderTypesFile(config) {
|
|
884
1087
|
const parts = [
|
|
885
1088
|
`export type CmsMediaType = 'image' | 'video' | 'file'`,
|
|
@@ -904,6 +1107,7 @@ function renderTypesFile(config) {
|
|
|
904
1107
|
}
|
|
905
1108
|
for (const [name, entry] of Object.entries(config)) parts.push(autoTypeTs(config, name, entry));
|
|
906
1109
|
parts.push(...entryMapsTs(config));
|
|
1110
|
+
parts.push(...blockPropsMapTs(config));
|
|
907
1111
|
return `${parts.join("\n\n")}
|
|
908
1112
|
`;
|
|
909
1113
|
}
|
|
@@ -1007,6 +1211,10 @@ function resolveModuleOptions(options) {
|
|
|
1007
1211
|
},
|
|
1008
1212
|
graphql: {
|
|
1009
1213
|
maxDepth: options.graphql?.maxDepth ?? 8
|
|
1214
|
+
},
|
|
1215
|
+
preview: {
|
|
1216
|
+
path: options.preview?.path ?? DEFAULT_CMS_PREVIEW_PATH,
|
|
1217
|
+
component: options.preview?.component?.trim() ?? ""
|
|
1010
1218
|
}
|
|
1011
1219
|
};
|
|
1012
1220
|
}
|
|
@@ -1029,7 +1237,10 @@ async function loadCmsConfig(nuxt, resolver, configPathOption, i18n, logger) {
|
|
|
1029
1237
|
moduleCache: false,
|
|
1030
1238
|
alias: { "#nuxt-cms": resolver.resolve("./runtime/shared/index") }
|
|
1031
1239
|
});
|
|
1032
|
-
cmsConfig =
|
|
1240
|
+
cmsConfig = normalizeCmsConfig(
|
|
1241
|
+
await jiti.import(configPath, { default: true }),
|
|
1242
|
+
i18n
|
|
1243
|
+
);
|
|
1033
1244
|
} else {
|
|
1034
1245
|
logger.warn(
|
|
1035
1246
|
`[nuxt-cms] Config file not found: ${configPath}. Using an empty registry. Create a ${configPathOption}.ts with defineCmsConfig().`
|
|
@@ -1052,12 +1263,12 @@ async function loadCmsConfig(nuxt, resolver, configPathOption, i18n, logger) {
|
|
|
1052
1263
|
filename: "cms/config.ts",
|
|
1053
1264
|
write: true,
|
|
1054
1265
|
getContents: () => [
|
|
1055
|
-
`import {
|
|
1266
|
+
`import { normalizeCmsConfig } from '#nuxt-cms'`,
|
|
1056
1267
|
`import config from '${source}'`,
|
|
1057
1268
|
``,
|
|
1058
1269
|
`const pageRoutes = ${JSON.stringify(routesByEntry, null, 3)}`,
|
|
1059
1270
|
``,
|
|
1060
|
-
`
|
|
1271
|
+
`normalizeCmsConfig(config, ${JSON.stringify({ locales: i18n.locales })})`,
|
|
1061
1272
|
``,
|
|
1062
1273
|
`for (const [name, routes] of Object.entries(pageRoutes)) {`,
|
|
1063
1274
|
` const entry = Object(config)[name]`,
|
|
@@ -1126,7 +1337,7 @@ function addCmsTypeTemplates(nuxt, cmsConfig) {
|
|
|
1126
1337
|
``,
|
|
1127
1338
|
`declare const graphql: CmsGraphql`,
|
|
1128
1339
|
``,
|
|
1129
|
-
`// @ts-ignore
|
|
1340
|
+
`// @ts-ignore`,
|
|
1130
1341
|
`export type CmsDocument<Query extends string> = ReturnType<typeof graphql<Query, []>>`,
|
|
1131
1342
|
``,
|
|
1132
1343
|
`export type CmsResult<Query extends string> = string extends Query`,
|
|
@@ -1141,6 +1352,31 @@ function addCmsTypeTemplates(nuxt, cmsConfig) {
|
|
|
1141
1352
|
});
|
|
1142
1353
|
nuxt.options.alias["#cms-graphql"] = graphqlTemplate.dst;
|
|
1143
1354
|
}
|
|
1355
|
+
function addCmsBlocks(nuxt, resolver, cmsConfig, previewComponent) {
|
|
1356
|
+
const refs = collectBlockComponents(cmsConfig);
|
|
1357
|
+
const previews = collectPreviewComponents(cmsConfig, previewComponent);
|
|
1358
|
+
const blocksTemplate = addTemplate({
|
|
1359
|
+
filename: "cms/blocks.ts",
|
|
1360
|
+
write: true,
|
|
1361
|
+
getContents: () => renderBlocksFile(refs, previews)
|
|
1362
|
+
});
|
|
1363
|
+
nuxt.options.alias["#cms-blocks"] = blocksTemplate.dst;
|
|
1364
|
+
addComponent({
|
|
1365
|
+
name: "CmsBlocks",
|
|
1366
|
+
filePath: resolver.resolve("./runtime/app/blocks/CmsBlocks.vue")
|
|
1367
|
+
});
|
|
1368
|
+
if (!refs.length && !previews.length) return;
|
|
1369
|
+
nuxt.hook("components:extend", (components) => {
|
|
1370
|
+
const names = components.map((component) => component.pascalName);
|
|
1371
|
+
const errors = [
|
|
1372
|
+
...missingBlockComponents(refs, names),
|
|
1373
|
+
...missingPreviewComponents(previews, names)
|
|
1374
|
+
];
|
|
1375
|
+
if (errors.length)
|
|
1376
|
+
throw new Error(`[nuxt-cms] Invalid block components:
|
|
1377
|
+
${errors.join("\n")}`);
|
|
1378
|
+
});
|
|
1379
|
+
}
|
|
1144
1380
|
const module$1 = defineNuxtModule({
|
|
1145
1381
|
meta: {
|
|
1146
1382
|
name: "@xleddyl/nuxt-cms",
|
|
@@ -1192,17 +1428,25 @@ const module$1 = defineNuxtModule({
|
|
|
1192
1428
|
{ name: "$cmsQuery", from: queryStub },
|
|
1193
1429
|
{ name: "useCmsSingle", from: entryStub },
|
|
1194
1430
|
{ name: "useCmsCollection", from: entryStub },
|
|
1195
|
-
{ name: "useCmsPage", from: entryStub }
|
|
1431
|
+
{ name: "useCmsPage", from: entryStub },
|
|
1432
|
+
{ name: "useCmsContents", from: entryStub },
|
|
1433
|
+
{ name: "useCmsContent", from: entryStub }
|
|
1196
1434
|
]);
|
|
1197
|
-
|
|
1435
|
+
const disabledConfig = await loadCmsConfig(
|
|
1198
1436
|
nuxt,
|
|
1199
|
-
|
|
1437
|
+
resolver,
|
|
1438
|
+
resolved.configPath,
|
|
1439
|
+
resolved.i18n,
|
|
1440
|
+
logger
|
|
1200
1441
|
);
|
|
1442
|
+
addCmsTypeTemplates(nuxt, disabledConfig);
|
|
1443
|
+
addCmsBlocks(nuxt, resolver, disabledConfig, resolved.preview.component);
|
|
1201
1444
|
nuxt.options.runtimeConfig.public.cms = {
|
|
1202
1445
|
mediaBaseUrl: resolved.media.publicBaseUrl,
|
|
1203
1446
|
mediaStorage: resolved.media.storage,
|
|
1204
1447
|
mediaMaxFileSize: resolved.media.maxFileSize,
|
|
1205
|
-
i18n: resolved.i18n
|
|
1448
|
+
i18n: resolved.i18n,
|
|
1449
|
+
previewPath: resolved.preview.path
|
|
1206
1450
|
};
|
|
1207
1451
|
logger.info(
|
|
1208
1452
|
"[nuxt-cms] disabled: registering no-op query composables and generated types, skipping admin, server and database setup"
|
|
@@ -1255,8 +1499,26 @@ const module$1 = defineNuxtModule({
|
|
|
1255
1499
|
{ name: "$cmsQuery", from: queryComposables },
|
|
1256
1500
|
{ name: "useCmsSingle", from: entryComposables },
|
|
1257
1501
|
{ name: "useCmsCollection", from: entryComposables },
|
|
1258
|
-
{ name: "useCmsPage", from: entryComposables }
|
|
1502
|
+
{ name: "useCmsPage", from: entryComposables },
|
|
1503
|
+
{ name: "useCmsContents", from: entryComposables },
|
|
1504
|
+
{ name: "useCmsContent", from: entryComposables }
|
|
1259
1505
|
]);
|
|
1506
|
+
addCmsBlocks(nuxt, resolver, cmsConfig, resolved.preview.component);
|
|
1507
|
+
const previewErrors = previewPathErrors(resolved.preview.path, Object.keys(cmsConfig));
|
|
1508
|
+
if (previewErrors.length) throw new Error(`[nuxt-cms] ${previewErrors.join("\n")}`);
|
|
1509
|
+
const previewPath = resolved.preview.path;
|
|
1510
|
+
const previewRule = nuxt.options.routeRules?.[previewPath] ?? {};
|
|
1511
|
+
nuxt.options.routeRules = {
|
|
1512
|
+
...nuxt.options.routeRules,
|
|
1513
|
+
[previewPath]: {
|
|
1514
|
+
...previewRule,
|
|
1515
|
+
headers: {
|
|
1516
|
+
"x-robots-tag": "noindex, nofollow",
|
|
1517
|
+
"x-frame-options": "SAMEORIGIN",
|
|
1518
|
+
...previewRule.headers
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
};
|
|
1260
1522
|
const {
|
|
1261
1523
|
driver,
|
|
1262
1524
|
path: dbPath,
|
|
@@ -1433,7 +1695,8 @@ const module$1 = defineNuxtModule({
|
|
|
1433
1695
|
mediaBaseUrl: resolved.media.publicBaseUrl,
|
|
1434
1696
|
mediaStorage: resolved.media.storage,
|
|
1435
1697
|
mediaMaxFileSize: resolved.media.maxFileSize,
|
|
1436
|
-
i18n: resolved.i18n
|
|
1698
|
+
i18n: resolved.i18n,
|
|
1699
|
+
previewPath
|
|
1437
1700
|
};
|
|
1438
1701
|
addTypeTemplate(
|
|
1439
1702
|
{
|
|
@@ -1484,14 +1747,38 @@ const module$1 = defineNuxtModule({
|
|
|
1484
1747
|
name: "cms-admin",
|
|
1485
1748
|
file: resolver.resolve("./runtime/app/layouts/cms-admin.vue")
|
|
1486
1749
|
};
|
|
1750
|
+
app.layouts["cms-editor"] = {
|
|
1751
|
+
name: "cms-editor",
|
|
1752
|
+
file: resolver.resolve("./runtime/app/layouts/cms-editor.vue")
|
|
1753
|
+
};
|
|
1487
1754
|
});
|
|
1488
1755
|
addComponentsDir({ path: resolver.resolve("./runtime/app/components") });
|
|
1489
1756
|
addRouteMiddleware({
|
|
1490
1757
|
name: "cms-auth",
|
|
1491
1758
|
path: resolver.resolve("./runtime/app/middleware/cms-auth")
|
|
1492
1759
|
});
|
|
1760
|
+
const contentPages = Object.entries(cmsConfig).filter(([, entry]) => entry.kind === "content").flatMap(([name]) => [
|
|
1761
|
+
{
|
|
1762
|
+
name: `cms-admin-content-${name}-new`,
|
|
1763
|
+
path: `/cms/${name}/new`,
|
|
1764
|
+
file: resolver.resolve("./runtime/app/pages/admin-content.vue"),
|
|
1765
|
+
meta: { cmsEntry: name }
|
|
1766
|
+
},
|
|
1767
|
+
{
|
|
1768
|
+
name: `cms-admin-content-${name}`,
|
|
1769
|
+
path: `/cms/${name}/:id()`,
|
|
1770
|
+
file: resolver.resolve("./runtime/app/pages/admin-content.vue"),
|
|
1771
|
+
meta: { cmsEntry: name }
|
|
1772
|
+
}
|
|
1773
|
+
]);
|
|
1493
1774
|
extendPages((pages) => {
|
|
1494
1775
|
pages.unshift(
|
|
1776
|
+
{
|
|
1777
|
+
name: "cms-preview",
|
|
1778
|
+
path: previewPath,
|
|
1779
|
+
file: resolver.resolve("./runtime/app/pages/admin-preview.vue")
|
|
1780
|
+
},
|
|
1781
|
+
...contentPages,
|
|
1495
1782
|
{
|
|
1496
1783
|
name: "cms-admin",
|
|
1497
1784
|
path: "/cms",
|