@xleddyl/nuxt-cms 0.1.60 → 0.2.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/dist/module.json +1 -1
- package/dist/module.mjs +137 -9
- package/dist/runtime/app/components/cms/BlocksField.vue +86 -10
- package/dist/runtime/app/components/cms/BrandMark.d.vue.ts +6 -0
- package/dist/runtime/app/components/cms/BrandMark.vue +19 -0
- package/dist/runtime/app/components/cms/BrandMark.vue.d.ts +6 -0
- package/dist/runtime/app/components/cms/Checkbox.d.vue.ts +11 -0
- package/dist/runtime/app/components/cms/Checkbox.vue +21 -0
- package/dist/runtime/app/components/cms/Checkbox.vue.d.ts +11 -0
- package/dist/runtime/app/components/cms/EmptyState.vue +4 -2
- package/dist/runtime/app/components/cms/EntryDrawer.vue +1 -0
- package/dist/runtime/app/components/cms/EntryForm.d.vue.ts +1 -0
- package/dist/runtime/app/components/cms/EntryForm.vue +105 -30
- package/dist/runtime/app/components/cms/EntryForm.vue.d.ts +1 -0
- package/dist/runtime/app/components/cms/FieldInput.vue +20 -5
- package/dist/runtime/app/components/cms/FormField.d.vue.ts +5 -3
- package/dist/runtime/app/components/cms/FormField.vue +8 -2
- package/dist/runtime/app/components/cms/FormField.vue.d.ts +5 -3
- package/dist/runtime/app/components/cms/Icon.vue +1 -1
- package/dist/runtime/app/components/cms/MediaDetail.d.vue.ts +16 -0
- package/dist/runtime/app/components/cms/MediaDetail.vue +169 -0
- package/dist/runtime/app/components/cms/MediaDetail.vue.d.ts +16 -0
- package/dist/runtime/app/components/cms/MediaField.vue +9 -2
- package/dist/runtime/app/components/cms/MediaFolderPicker.d.vue.ts +2 -2
- package/dist/runtime/app/components/cms/MediaFolderPicker.vue +55 -54
- package/dist/runtime/app/components/cms/MediaFolderPicker.vue.d.ts +2 -2
- package/dist/runtime/app/components/cms/MediaGallery.d.vue.ts +8 -2
- package/dist/runtime/app/components/cms/MediaGallery.vue +927 -336
- package/dist/runtime/app/components/cms/MediaGallery.vue.d.ts +8 -2
- package/dist/runtime/app/components/cms/MediaUpload.d.vue.ts +1 -0
- package/dist/runtime/app/components/cms/MediaUpload.vue +76 -139
- package/dist/runtime/app/components/cms/MediaUpload.vue.d.ts +1 -0
- package/dist/runtime/app/components/cms/PageHeader.d.vue.ts +5 -3
- package/dist/runtime/app/components/cms/PageHeader.vue +16 -6
- package/dist/runtime/app/components/cms/PageHeader.vue.d.ts +5 -3
- package/dist/runtime/app/components/cms/PasswordInput.d.vue.ts +17 -0
- package/dist/runtime/app/components/cms/PasswordInput.vue +77 -0
- package/dist/runtime/app/components/cms/PasswordInput.vue.d.ts +17 -0
- package/dist/runtime/app/components/cms/SelectMenu.vue +3 -5
- package/dist/runtime/app/components/cms/SettingsAccount.d.vue.ts +3 -0
- package/dist/runtime/app/components/cms/SettingsAccount.vue +138 -0
- package/dist/runtime/app/components/cms/SettingsAccount.vue.d.ts +3 -0
- package/dist/runtime/app/components/cms/SettingsColumns.d.vue.ts +16 -0
- package/dist/runtime/app/components/cms/SettingsColumns.vue +102 -0
- package/dist/runtime/app/components/cms/SettingsColumns.vue.d.ts +16 -0
- package/dist/runtime/app/components/cms/SettingsEntry.d.vue.ts +6 -0
- package/dist/runtime/app/components/cms/SettingsEntry.vue +133 -0
- package/dist/runtime/app/components/cms/SettingsEntry.vue.d.ts +6 -0
- package/dist/runtime/app/components/cms/SettingsLayout.d.vue.ts +16 -0
- package/dist/runtime/app/components/cms/SettingsLayout.vue +177 -0
- package/dist/runtime/app/components/cms/SettingsLayout.vue.d.ts +16 -0
- package/dist/runtime/app/components/cms/SettingsModal.d.vue.ts +16 -0
- package/dist/runtime/app/components/cms/SettingsModal.vue +179 -0
- package/dist/runtime/app/components/cms/SettingsModal.vue.d.ts +16 -0
- package/dist/runtime/app/components/cms/SettingsUsers.d.vue.ts +3 -0
- package/dist/runtime/app/components/cms/SettingsUsers.vue +270 -0
- package/dist/runtime/app/components/cms/SettingsUsers.vue.d.ts +3 -0
- package/dist/runtime/app/components/cms/Spinner.vue +2 -2
- package/dist/runtime/app/components/cms/Table.d.vue.ts +7 -2
- package/dist/runtime/app/components/cms/Table.vue +38 -1
- package/dist/runtime/app/components/cms/Table.vue.d.ts +7 -2
- package/dist/runtime/app/components/cms/Toaster.vue +1 -1
- package/dist/runtime/app/components/cms/WelcomeModal.d.vue.ts +3 -0
- package/dist/runtime/app/components/cms/WelcomeModal.vue +69 -0
- package/dist/runtime/app/components/cms/WelcomeModal.vue.d.ts +3 -0
- package/dist/runtime/app/composables/cms-account.d.ts +11 -0
- package/dist/runtime/app/composables/cms-account.js +23 -0
- package/dist/runtime/app/composables/cms-media-library.d.ts +75 -0
- package/dist/runtime/app/composables/cms-media-library.js +151 -0
- package/dist/runtime/app/composables/cms-media-uploader.d.ts +22 -0
- package/dist/runtime/app/composables/cms-media-uploader.js +244 -0
- package/dist/runtime/app/composables/cms-settings.d.ts +13 -0
- package/dist/runtime/app/composables/cms-settings.js +59 -0
- package/dist/runtime/app/composables/cms-theme.d.ts +9 -0
- package/dist/runtime/app/composables/cms-theme.js +27 -0
- package/dist/runtime/app/layouts/cms-admin.d.vue.ts +2 -2
- package/dist/runtime/app/layouts/cms-admin.vue +71 -17
- package/dist/runtime/app/layouts/cms-admin.vue.d.ts +2 -2
- package/dist/runtime/app/pages/admin-collection.vue +127 -101
- package/dist/runtime/app/pages/admin-entry.vue +1 -0
- package/dist/runtime/app/pages/admin-login.vue +18 -3
- package/dist/runtime/app/pages/admin-media.vue +19 -3
- package/dist/runtime/app/utils/ui.d.ts +2 -0
- package/dist/runtime/app/utils/ui.js +18 -0
- package/dist/runtime/assets/main.css +1 -1
- package/dist/runtime/server/api/account-password.put.d.ts +4 -0
- package/dist/runtime/server/api/account-password.put.js +38 -0
- package/dist/runtime/server/api/account-welcome.post.d.ts +4 -0
- package/dist/runtime/server/api/account-welcome.post.js +14 -0
- package/dist/runtime/server/api/account.put.d.ts +3 -0
- package/dist/runtime/server/api/account.put.js +25 -0
- package/dist/runtime/server/api/media-delete.post.d.ts +4 -0
- package/dist/runtime/server/api/media-delete.post.js +23 -0
- package/dist/runtime/server/api/media-folder.delete.js +6 -16
- package/dist/runtime/server/api/media-folder.patch.d.ts +5 -0
- package/dist/runtime/server/api/media-folder.patch.js +68 -0
- package/dist/runtime/server/api/media-folder.post.js +3 -15
- package/dist/runtime/server/api/media-move.post.d.ts +5 -0
- package/dist/runtime/server/api/media-move.post.js +28 -0
- package/dist/runtime/server/api/media-usage.post.d.ts +5 -0
- package/dist/runtime/server/api/media-usage.post.js +15 -0
- package/dist/runtime/server/api/settings.delete.d.ts +4 -0
- package/dist/runtime/server/api/settings.delete.js +12 -0
- package/dist/runtime/server/api/settings.get.d.ts +3 -0
- package/dist/runtime/server/api/settings.get.js +15 -0
- package/dist/runtime/server/api/settings.put.d.ts +3 -0
- package/dist/runtime/server/api/settings.put.js +14 -0
- package/dist/runtime/server/api/user.delete.d.ts +4 -0
- package/dist/runtime/server/api/user.delete.js +12 -0
- package/dist/runtime/server/api/user.put.d.ts +3 -0
- package/dist/runtime/server/api/user.put.js +27 -0
- package/dist/runtime/server/api/users.get.d.ts +3 -0
- package/dist/runtime/server/api/users.get.js +11 -0
- package/dist/runtime/server/api/users.post.d.ts +3 -0
- package/dist/runtime/server/api/users.post.js +32 -0
- package/dist/runtime/server/plugins/session-validate.d.ts +2 -0
- package/dist/runtime/server/plugins/session-validate.js +13 -0
- package/dist/runtime/server/routes/auth/login.post.js +18 -8
- package/dist/runtime/server/utils/media-folders.d.ts +86 -0
- package/dist/runtime/server/utils/media-folders.js +37 -0
- package/dist/runtime/server/utils/media-references.d.ts +7 -1
- package/dist/runtime/server/utils/media-references.js +40 -0
- package/dist/runtime/server/utils/media-usage.d.ts +2 -0
- package/dist/runtime/server/utils/media-usage.js +18 -0
- package/dist/runtime/server/utils/password.d.ts +3 -0
- package/dist/runtime/server/utils/password.js +31 -0
- package/dist/runtime/server/utils/require-admin.d.ts +6 -2
- package/dist/runtime/server/utils/require-admin.js +33 -28
- package/dist/runtime/server/utils/same-origin.d.ts +2 -0
- package/dist/runtime/server/utils/same-origin.js +24 -0
- package/dist/runtime/server/utils/settings.d.ts +16 -0
- package/dist/runtime/server/utils/settings.js +31 -0
- package/dist/runtime/server/utils/users.d.ts +15 -0
- package/dist/runtime/server/utils/users.js +34 -0
- package/dist/runtime/shared/index.d.ts +29 -1
- package/dist/runtime/shared/index.js +36 -2
- package/dist/runtime/shared/layout.d.ts +34 -0
- package/dist/runtime/shared/layout.js +130 -0
- package/dist/runtime/shared/users.d.ts +18 -0
- package/dist/runtime/shared/users.js +1 -0
- package/dist/runtime/shared/validation.js +5 -4
- package/package.json +3 -3
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -11,7 +11,7 @@ import { minifyIntrospection, outputIntrospectionFile } from 'gql.tada/internal'
|
|
|
11
11
|
import { createJiti } from 'jiti';
|
|
12
12
|
import { typeName, blockTypeName, 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, mobileMediaKey, fieldConditions, pageFieldsTableName, pageMediaTableName, isRequiredField, pageFields, isPrivateField, entryFieldsFor, mediaTypeFilter, DEFAULT_MEDIA_MAX_FILE_SIZE, expandMobileMedia } from '../dist/runtime/shared/index.js';
|
|
14
|
+
import { pageColumnFields, isTranslatableField, isTranslatableMediaField, isMultiSelect, pageAllFields, PAGE_PATH_FIELD, pageRoutes, layoutErrors, listErrors, mobileMediaKey, fieldConditions, pageFieldsTableName, pageMediaTableName, isRequiredField, pageFields, isPrivateField, entryFieldsFor, mediaTypeFilter, DEFAULT_MEDIA_MAX_FILE_SIZE, expandMobileMedia } from '../dist/runtime/shared/index.js';
|
|
15
15
|
import { scanMediaDirectory, readMediaFileMeta } from '../dist/runtime/server/utils/media-sync.js';
|
|
16
16
|
import { createHash } from 'node:crypto';
|
|
17
17
|
import { readFile } from 'node:fs/promises';
|
|
@@ -80,7 +80,17 @@ function renderMigrationsTypes(typesPath) {
|
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
const IDENTIFIER = /^[a-z_]\w*$/i;
|
|
83
|
-
const RESERVED_ENTRY_KEYS = [
|
|
83
|
+
const RESERVED_ENTRY_KEYS = [
|
|
84
|
+
"admin",
|
|
85
|
+
"auth",
|
|
86
|
+
"login",
|
|
87
|
+
"media",
|
|
88
|
+
"graphql",
|
|
89
|
+
"settings",
|
|
90
|
+
"cms_media",
|
|
91
|
+
"cms_settings",
|
|
92
|
+
"cms_users"
|
|
93
|
+
];
|
|
84
94
|
const RESERVED_COLUMNS = ["id", "status", "created_at", "updated_at"];
|
|
85
95
|
const TITLE_FIELD_TYPES = ["text", "slug", "email", "number", "date", "select"];
|
|
86
96
|
const TRANSLATABLE_FIELD_TYPES = ["text", "richtext", "media"];
|
|
@@ -225,6 +235,12 @@ function validateConfig(config, i18n) {
|
|
|
225
235
|
}
|
|
226
236
|
}
|
|
227
237
|
const allFields = entry.kind === "page" ? pageAllFields(entry) : entry.fields ?? {};
|
|
238
|
+
errors.push(...layoutErrors(at, allFields, entry.layout));
|
|
239
|
+
errors.push(...listErrors(at, allFields, entry.list, entry.drafts ? ["status"] : []));
|
|
240
|
+
if (entry.list && entry.kind !== "collection")
|
|
241
|
+
errors.push(`${at}: list needs kind 'collection'`);
|
|
242
|
+
if (entry.icon !== void 0 && (typeof entry.icon !== "string" || !entry.icon))
|
|
243
|
+
errors.push(`${at}: icon must be a non-empty string`);
|
|
228
244
|
const columnNames = /* @__PURE__ */ new Set();
|
|
229
245
|
for (const [key, field] of Object.entries(allFields)) {
|
|
230
246
|
const fat = `${at}, field '${key}'`;
|
|
@@ -383,7 +399,11 @@ function validateConfig(config, i18n) {
|
|
|
383
399
|
}
|
|
384
400
|
}
|
|
385
401
|
}
|
|
386
|
-
const sqlTableNames = /* @__PURE__ */ new Map([
|
|
402
|
+
const sqlTableNames = /* @__PURE__ */ new Map([
|
|
403
|
+
["cms_media", "the built-in media table"],
|
|
404
|
+
["cms_settings", "the built-in settings table"],
|
|
405
|
+
["cms_users", "the built-in users table"]
|
|
406
|
+
]);
|
|
387
407
|
for (const [name, entry] of Object.entries(config)) {
|
|
388
408
|
if (entry.table) continue;
|
|
389
409
|
const registerTable = (sqlName, source) => {
|
|
@@ -535,6 +555,30 @@ function pageRowsTableExprs(name, dialect) {
|
|
|
535
555
|
rowsTable(pageMediaTableName(name), `mediaKey: text('media_key').notNull()`)
|
|
536
556
|
];
|
|
537
557
|
}
|
|
558
|
+
function settingsTableExpr(dialect) {
|
|
559
|
+
const pg = dialect === "postgres";
|
|
560
|
+
const tableFn = pg ? "pgTable" : "sqliteTable";
|
|
561
|
+
return `export const cms_settings = ${tableFn}('cms_settings', {
|
|
562
|
+
key: text('key').primaryKey(),
|
|
563
|
+
value: text('value').notNull(),
|
|
564
|
+
updatedAt: ${nowExpr(pg, "updated_at")},
|
|
565
|
+
})`;
|
|
566
|
+
}
|
|
567
|
+
function usersTableExpr(dialect) {
|
|
568
|
+
const pg = dialect === "postgres";
|
|
569
|
+
const tableFn = pg ? "pgTable" : "sqliteTable";
|
|
570
|
+
return `export const cms_users = ${tableFn}('cms_users', {
|
|
571
|
+
id: text('id').primaryKey(),
|
|
572
|
+
email: text('email').notNull().unique(),
|
|
573
|
+
name: text('name'),
|
|
574
|
+
role: text('role').notNull().default('admin'),
|
|
575
|
+
passwordHash: text('password_hash').notNull(),
|
|
576
|
+
mustChangePassword: ${pg ? `boolean('must_change_password')` : `integer('must_change_password', { mode: 'boolean' })`}.notNull().default(true),
|
|
577
|
+
lastLoginAt: text('last_login_at'),
|
|
578
|
+
createdAt: ${nowExpr(pg, "created_at")},
|
|
579
|
+
updatedAt: ${nowExpr(pg, "updated_at")},
|
|
580
|
+
})`;
|
|
581
|
+
}
|
|
538
582
|
function mediaTableExpr(dialect) {
|
|
539
583
|
const pg = dialect === "postgres";
|
|
540
584
|
const tableFn = pg ? "pgTable" : "sqliteTable";
|
|
@@ -561,7 +605,7 @@ function renderSchemaFile(config, dialect, resolveImport = (s) => s) {
|
|
|
561
605
|
if (fields.some((f) => f.type === "number" && !f.integer))
|
|
562
606
|
core.add(pg ? "doublePrecision" : "real");
|
|
563
607
|
if (pg && fields.some((f) => f.type === "date")) core.add("date");
|
|
564
|
-
if (pg
|
|
608
|
+
if (pg) core.add("boolean");
|
|
565
609
|
if (pg && fields.some(
|
|
566
610
|
(f) => f.type === "json" || f.type === "blocks" || isTranslatableField(f) && !isTranslatableMediaField(f) || isMultiSelect(f)
|
|
567
611
|
))
|
|
@@ -581,9 +625,14 @@ function renderSchemaFile(config, dialect, resolveImport = (s) => s) {
|
|
|
581
625
|
const rows = rowsEntries.flatMap(([name]) => pageRowsTableExprs(name, dialect));
|
|
582
626
|
return `${imports.join("\n")}
|
|
583
627
|
|
|
584
|
-
${[
|
|
585
|
-
|
|
586
|
-
|
|
628
|
+
${[
|
|
629
|
+
mediaTableExpr(dialect),
|
|
630
|
+
settingsTableExpr(dialect),
|
|
631
|
+
usersTableExpr(dialect),
|
|
632
|
+
...tables,
|
|
633
|
+
...joins,
|
|
634
|
+
...rows
|
|
635
|
+
].join("\n\n")}
|
|
587
636
|
`;
|
|
588
637
|
}
|
|
589
638
|
|
|
@@ -1302,6 +1351,7 @@ const module$1 = defineNuxtModule({
|
|
|
1302
1351
|
driver === "postgres" ? "./runtime/server/plugins/migrate-postgres" : driver === "libsql" ? "./runtime/server/plugins/migrate-libsql" : driver === "d1" ? "./runtime/server/plugins/d1-binding" : "./runtime/server/plugins/migrate-sqlite"
|
|
1303
1352
|
)
|
|
1304
1353
|
);
|
|
1354
|
+
addServerPlugin(resolver.resolve("./runtime/server/plugins/session-validate"));
|
|
1305
1355
|
if (!nuxt.options.dev) {
|
|
1306
1356
|
addServerPlugin(resolver.resolve("./runtime/server/plugins/session-check"));
|
|
1307
1357
|
}
|
|
@@ -1382,13 +1432,21 @@ const module$1 = defineNuxtModule({
|
|
|
1382
1432
|
"",
|
|
1383
1433
|
"declare module '#auth-utils' {",
|
|
1384
1434
|
" interface User {",
|
|
1435
|
+
" id?: string",
|
|
1385
1436
|
" email: string",
|
|
1437
|
+
" name?: string | null",
|
|
1438
|
+
" role?: 'superadmin' | 'admin'",
|
|
1439
|
+
" firstLogin?: boolean",
|
|
1440
|
+
" passwordStamp?: string",
|
|
1386
1441
|
" }",
|
|
1387
1442
|
"}",
|
|
1388
1443
|
"",
|
|
1389
1444
|
"declare module '#imports' {",
|
|
1390
1445
|
" function getUserSession(event: H3Event): Promise<{ user?: import('#auth-utils').User }>",
|
|
1391
1446
|
" function setUserSession(event: H3Event, session: { user: import('#auth-utils').User }): Promise<unknown>",
|
|
1447
|
+
" function replaceUserSession(event: H3Event, session: { user: import('#auth-utils').User }): Promise<unknown>",
|
|
1448
|
+
" function clearUserSession(event: H3Event): Promise<boolean>",
|
|
1449
|
+
" const sessionHooks: { hook(name: 'fetch', fn: (session: { user?: import('#auth-utils').User }, event: H3Event) => void | Promise<void>): () => void }",
|
|
1392
1450
|
" function useStorage(base?: string): {",
|
|
1393
1451
|
" getItem<T>(key: string): Promise<T | null>",
|
|
1394
1452
|
" setItem<T>(key: string, value: T, options?: { ttl?: number }): Promise<void>",
|
|
@@ -1406,8 +1464,8 @@ const module$1 = defineNuxtModule({
|
|
|
1406
1464
|
addVitePlugin(tailwindcss());
|
|
1407
1465
|
addVitePlugin(svgLoader({ defaultImport: "url", svgoConfig: { plugins: ["prefixIds"] } }));
|
|
1408
1466
|
nuxt.options.css.push(
|
|
1409
|
-
resolveImport("@fontsource-variable/
|
|
1410
|
-
resolveImport("@fontsource/
|
|
1467
|
+
resolveImport("@fontsource-variable/geist/index.css"),
|
|
1468
|
+
resolveImport("@fontsource-variable/geist-mono/index.css"),
|
|
1411
1469
|
resolver.resolve("./runtime/assets/main.css")
|
|
1412
1470
|
);
|
|
1413
1471
|
nuxt.hook("app:templates", (app) => {
|
|
@@ -1495,6 +1553,26 @@ const module$1 = defineNuxtModule({
|
|
|
1495
1553
|
method: "delete",
|
|
1496
1554
|
handler: resolver.resolve("./runtime/server/api/media-folder.delete")
|
|
1497
1555
|
});
|
|
1556
|
+
addServerHandler({
|
|
1557
|
+
route: "/api/cms/admin/media/folders",
|
|
1558
|
+
method: "patch",
|
|
1559
|
+
handler: resolver.resolve("./runtime/server/api/media-folder.patch")
|
|
1560
|
+
});
|
|
1561
|
+
addServerHandler({
|
|
1562
|
+
route: "/api/cms/admin/media/move",
|
|
1563
|
+
method: "post",
|
|
1564
|
+
handler: resolver.resolve("./runtime/server/api/media-move.post")
|
|
1565
|
+
});
|
|
1566
|
+
addServerHandler({
|
|
1567
|
+
route: "/api/cms/admin/media/delete",
|
|
1568
|
+
method: "post",
|
|
1569
|
+
handler: resolver.resolve("./runtime/server/api/media-delete.post")
|
|
1570
|
+
});
|
|
1571
|
+
addServerHandler({
|
|
1572
|
+
route: "/api/cms/admin/media/usage",
|
|
1573
|
+
method: "post",
|
|
1574
|
+
handler: resolver.resolve("./runtime/server/api/media-usage.post")
|
|
1575
|
+
});
|
|
1498
1576
|
if (mediaFilesystemBase) {
|
|
1499
1577
|
addServerHandler({
|
|
1500
1578
|
route: "/api/cms/admin/media/upload",
|
|
@@ -1506,6 +1584,56 @@ const module$1 = defineNuxtModule({
|
|
|
1506
1584
|
handler: resolver.resolve("./runtime/server/routes/media-file")
|
|
1507
1585
|
});
|
|
1508
1586
|
}
|
|
1587
|
+
addServerHandler({
|
|
1588
|
+
route: "/api/cms/users",
|
|
1589
|
+
method: "get",
|
|
1590
|
+
handler: resolver.resolve("./runtime/server/api/users.get")
|
|
1591
|
+
});
|
|
1592
|
+
addServerHandler({
|
|
1593
|
+
route: "/api/cms/users",
|
|
1594
|
+
method: "post",
|
|
1595
|
+
handler: resolver.resolve("./runtime/server/api/users.post")
|
|
1596
|
+
});
|
|
1597
|
+
addServerHandler({
|
|
1598
|
+
route: "/api/cms/users/:id",
|
|
1599
|
+
method: "put",
|
|
1600
|
+
handler: resolver.resolve("./runtime/server/api/user.put")
|
|
1601
|
+
});
|
|
1602
|
+
addServerHandler({
|
|
1603
|
+
route: "/api/cms/users/:id",
|
|
1604
|
+
method: "delete",
|
|
1605
|
+
handler: resolver.resolve("./runtime/server/api/user.delete")
|
|
1606
|
+
});
|
|
1607
|
+
addServerHandler({
|
|
1608
|
+
route: "/api/cms/account",
|
|
1609
|
+
method: "put",
|
|
1610
|
+
handler: resolver.resolve("./runtime/server/api/account.put")
|
|
1611
|
+
});
|
|
1612
|
+
addServerHandler({
|
|
1613
|
+
route: "/api/cms/account/password",
|
|
1614
|
+
method: "put",
|
|
1615
|
+
handler: resolver.resolve("./runtime/server/api/account-password.put")
|
|
1616
|
+
});
|
|
1617
|
+
addServerHandler({
|
|
1618
|
+
route: "/api/cms/account/welcome",
|
|
1619
|
+
method: "post",
|
|
1620
|
+
handler: resolver.resolve("./runtime/server/api/account-welcome.post")
|
|
1621
|
+
});
|
|
1622
|
+
addServerHandler({
|
|
1623
|
+
route: "/api/cms/admin/settings",
|
|
1624
|
+
method: "get",
|
|
1625
|
+
handler: resolver.resolve("./runtime/server/api/settings.get")
|
|
1626
|
+
});
|
|
1627
|
+
addServerHandler({
|
|
1628
|
+
route: "/api/cms/admin/settings/:key",
|
|
1629
|
+
method: "put",
|
|
1630
|
+
handler: resolver.resolve("./runtime/server/api/settings.put")
|
|
1631
|
+
});
|
|
1632
|
+
addServerHandler({
|
|
1633
|
+
route: "/api/cms/admin/settings/:key",
|
|
1634
|
+
method: "delete",
|
|
1635
|
+
handler: resolver.resolve("./runtime/server/api/settings.delete")
|
|
1636
|
+
});
|
|
1509
1637
|
const api = "/api/cms/admin/:collection";
|
|
1510
1638
|
addServerHandler({
|
|
1511
1639
|
route: api,
|
|
@@ -9,20 +9,25 @@
|
|
|
9
9
|
'is-muted': isHidden(item),
|
|
10
10
|
'is-dragging': dragIndex === index,
|
|
11
11
|
'is-drop-target': dropIndex === index && dragIndex !== index,
|
|
12
|
-
'is-missing': hasMissingMedia(item)
|
|
12
|
+
'is-missing': hasMissingMedia(item),
|
|
13
|
+
'is-media-only': mediaOnly
|
|
13
14
|
}"
|
|
14
15
|
draggable="true"
|
|
16
|
+
:tabindex="mediaOnly ? 0 : void 0"
|
|
17
|
+
:aria-label="mediaOnly ? `${barTitle(item)}, use the arrow keys to reorder` : void 0"
|
|
15
18
|
@dragstart="onDragStart(index, $event)"
|
|
16
19
|
@dragover.prevent="dropIndex = index"
|
|
17
20
|
@dragleave="onDragLeave(index)"
|
|
18
21
|
@drop.prevent="onDrop(index)"
|
|
19
22
|
@dragend="onDragEnd"
|
|
23
|
+
@keydown.self="onTileKey($event, index)"
|
|
20
24
|
>
|
|
21
|
-
<
|
|
22
|
-
|
|
25
|
+
<component
|
|
26
|
+
:is="mediaOnly ? 'div' : 'button'"
|
|
27
|
+
:type="mediaOnly ? void 0 : 'button'"
|
|
23
28
|
class="cms-block-preview"
|
|
24
|
-
:aria-label="`Edit ${labelOf(item)}`"
|
|
25
|
-
@click="
|
|
29
|
+
:aria-label="mediaOnly ? void 0 : `Edit ${labelOf(item)}`"
|
|
30
|
+
@click="onPreviewClick(index)"
|
|
26
31
|
>
|
|
27
32
|
<img
|
|
28
33
|
v-if="previewOf(item)?.kind === 'image'"
|
|
@@ -43,9 +48,28 @@
|
|
|
43
48
|
<CmsIcon v-else :name="iconOf(item)" class="size-7" />
|
|
44
49
|
|
|
45
50
|
<CmsIcon v-if="isHidden(item)" name="eye-slash" class="cms-block-hidden size-4" />
|
|
46
|
-
</button>
|
|
47
51
|
|
|
48
|
-
|
|
52
|
+
<template v-if="mediaOnly">
|
|
53
|
+
<span
|
|
54
|
+
v-if="hasMissingMedia(item)"
|
|
55
|
+
class="cms-block-missing"
|
|
56
|
+
title="Not found in the media library"
|
|
57
|
+
>
|
|
58
|
+
<CmsIcon name="exclamation-triangle" class="size-3.5" />
|
|
59
|
+
</span>
|
|
60
|
+
<button
|
|
61
|
+
type="button"
|
|
62
|
+
class="cms-block-remove"
|
|
63
|
+
:aria-label="`Remove ${barTitle(item)}`"
|
|
64
|
+
title="Remove"
|
|
65
|
+
@click.stop="remove(index)"
|
|
66
|
+
>
|
|
67
|
+
<CmsIcon name="x-mark" class="size-3.5" />
|
|
68
|
+
</button>
|
|
69
|
+
</template>
|
|
70
|
+
</component>
|
|
71
|
+
|
|
72
|
+
<div v-if="!mediaOnly" class="cms-block-bar">
|
|
49
73
|
<button
|
|
50
74
|
type="button"
|
|
51
75
|
class="cms-block-grip"
|
|
@@ -84,10 +108,22 @@
|
|
|
84
108
|
</CmsDropdownMenu>
|
|
85
109
|
<button v-else type="button" class="cms-block-add" @click="addFirst">
|
|
86
110
|
<CmsIcon name="plus" class="size-5" />
|
|
87
|
-
<span>Add</span>
|
|
111
|
+
<span>{{ mediaOnly ? addMediaLabel : "Add" }}</span>
|
|
88
112
|
</button>
|
|
89
113
|
</div>
|
|
90
114
|
|
|
115
|
+
<CmsModal v-if="mediaOnly" v-model:open="pickerOpen" title="Media" size="lg">
|
|
116
|
+
<template #body>
|
|
117
|
+
<CmsMediaGallery
|
|
118
|
+
v-model:folder="pickerFolder"
|
|
119
|
+
selectable
|
|
120
|
+
:media-type="mediaOnly.field.mediaType"
|
|
121
|
+
:accept="mediaOnly.field.accept"
|
|
122
|
+
@select="addMedia"
|
|
123
|
+
/>
|
|
124
|
+
</template>
|
|
125
|
+
</CmsModal>
|
|
126
|
+
|
|
91
127
|
<CmsModal v-model:open="editorOpen" :title="editorTitle">
|
|
92
128
|
<template #body>
|
|
93
129
|
<div class="flex flex-col gap-4">
|
|
@@ -95,6 +131,7 @@
|
|
|
95
131
|
v-for="(blockField, blockKey) in editorFields"
|
|
96
132
|
:key="blockKey"
|
|
97
133
|
:label="blockField.label"
|
|
134
|
+
:icon="fieldIcon(blockField)"
|
|
98
135
|
:required="blockField.required"
|
|
99
136
|
>
|
|
100
137
|
<CmsFieldInput
|
|
@@ -157,9 +194,10 @@ import {
|
|
|
157
194
|
mediaTypeForKey,
|
|
158
195
|
pickTranslatedMedia
|
|
159
196
|
} from "#nuxt-cms";
|
|
160
|
-
import { computed, ref } from "#imports";
|
|
197
|
+
import { computed, ref, useState } from "#imports";
|
|
161
198
|
import { useCmsMediaKeys } from "../../composables/cms-media-keys";
|
|
162
199
|
import { useCmsRuntime } from "../../composables/cms-runtime";
|
|
200
|
+
import { fieldIcon } from "../../utils/ui";
|
|
163
201
|
const props = defineProps({
|
|
164
202
|
field: { type: Object, required: true },
|
|
165
203
|
locale: { type: String, required: false }
|
|
@@ -169,6 +207,29 @@ const { mediaBaseUrl, i18n } = useCmsRuntime();
|
|
|
169
207
|
const mediaKeys = useCmsMediaKeys();
|
|
170
208
|
const blocks = computed(() => props.field.blocks ?? {});
|
|
171
209
|
const items = computed(() => Array.isArray(model.value) ? model.value : []);
|
|
210
|
+
const mediaOnly = computed(() => {
|
|
211
|
+
const types = Object.entries(blocks.value);
|
|
212
|
+
if (types.length !== 1) return null;
|
|
213
|
+
const [type, block] = types[0];
|
|
214
|
+
const fields = Object.entries(block.fields);
|
|
215
|
+
if (fields.length !== 1) return null;
|
|
216
|
+
const [key, field] = fields[0];
|
|
217
|
+
if (field.type !== "media" || isTranslatableMediaField(field)) return null;
|
|
218
|
+
return { type, key, field };
|
|
219
|
+
});
|
|
220
|
+
const addMediaLabel = computed(() => {
|
|
221
|
+
const types = [mediaOnly.value?.field.mediaType ?? []].flat();
|
|
222
|
+
return types.length === 1 && types[0] === "image" ? "Add photo" : "Add media";
|
|
223
|
+
});
|
|
224
|
+
const pickerOpen = ref(false);
|
|
225
|
+
const pickerFolder = useState("cms-media-picker-folder", () => null);
|
|
226
|
+
function addMedia(media) {
|
|
227
|
+
const target = mediaOnly.value;
|
|
228
|
+
if (!target) return;
|
|
229
|
+
mediaKeys.remember(media.key);
|
|
230
|
+
model.value = [...items.value, { type: target.type, [target.key]: media.key }];
|
|
231
|
+
pickerOpen.value = false;
|
|
232
|
+
}
|
|
172
233
|
let uidCounter = 0;
|
|
173
234
|
const uids = /* @__PURE__ */ new WeakMap();
|
|
174
235
|
function uidFor(item) {
|
|
@@ -245,10 +306,25 @@ function add(type) {
|
|
|
245
306
|
const block = blocks.value[type];
|
|
246
307
|
if (!block) return;
|
|
247
308
|
const empty = Object.fromEntries(Object.keys(block.fields).map((k) => [k, null]));
|
|
309
|
+
const index = items.value.length;
|
|
248
310
|
model.value = [...items.value, { type, ...empty }];
|
|
249
|
-
openEditor(
|
|
311
|
+
openEditor(index);
|
|
312
|
+
}
|
|
313
|
+
function onTileKey(event, index) {
|
|
314
|
+
if (!mediaOnly.value) return;
|
|
315
|
+
const action = event.key === "ArrowLeft" ? () => move(index, -1) : event.key === "ArrowRight" ? () => move(index, 1) : event.key === "Delete" || event.key === "Backspace" ? () => remove(index) : null;
|
|
316
|
+
if (!action) return;
|
|
317
|
+
event.preventDefault();
|
|
318
|
+
action();
|
|
319
|
+
}
|
|
320
|
+
function onPreviewClick(index) {
|
|
321
|
+
if (!mediaOnly.value) openEditor(index);
|
|
250
322
|
}
|
|
251
323
|
function addFirst() {
|
|
324
|
+
if (mediaOnly.value) {
|
|
325
|
+
pickerOpen.value = true;
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
252
328
|
const first = Object.keys(blocks.value)[0];
|
|
253
329
|
if (first) add(first);
|
|
254
330
|
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
type __VLS_Props = {
|
|
2
|
+
size?: 'md' | 'lg';
|
|
3
|
+
};
|
|
4
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
5
|
+
declare const _default: typeof __VLS_export;
|
|
6
|
+
export default _default;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<span class="cms-brand-mark" :class="{ 'is-lg': size === 'lg' }" aria-hidden="true">
|
|
3
|
+
<svg viewBox="0 0 16 16" fill="none">
|
|
4
|
+
<path
|
|
5
|
+
d="M4.25 12.25V7.5a3.75 3.75 0 0 1 7.5 0v4.75"
|
|
6
|
+
stroke="currentColor"
|
|
7
|
+
stroke-width="2"
|
|
8
|
+
stroke-linecap="round"
|
|
9
|
+
/>
|
|
10
|
+
<circle cx="8" cy="12.25" r="1.25" fill="currentColor" />
|
|
11
|
+
</svg>
|
|
12
|
+
</span>
|
|
13
|
+
</template>
|
|
14
|
+
|
|
15
|
+
<script setup>
|
|
16
|
+
defineProps({
|
|
17
|
+
size: { type: String, required: false }
|
|
18
|
+
});
|
|
19
|
+
</script>
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
type __VLS_Props = {
|
|
2
|
+
size?: 'md' | 'lg';
|
|
3
|
+
};
|
|
4
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
5
|
+
declare const _default: typeof __VLS_export;
|
|
6
|
+
export default _default;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
type __VLS_Props = {
|
|
2
|
+
checked?: boolean;
|
|
3
|
+
indeterminate?: boolean;
|
|
4
|
+
};
|
|
5
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
6
|
+
toggle: () => any;
|
|
7
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
8
|
+
onToggle?: (() => any) | undefined;
|
|
9
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
10
|
+
declare const _default: typeof __VLS_export;
|
|
11
|
+
export default _default;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<button
|
|
3
|
+
type="button"
|
|
4
|
+
role="checkbox"
|
|
5
|
+
:aria-checked="indeterminate ? 'mixed' : checked"
|
|
6
|
+
class="cms-checkbox"
|
|
7
|
+
:class="{ 'is-on': checked || indeterminate }"
|
|
8
|
+
@click.stop="emit('toggle')"
|
|
9
|
+
>
|
|
10
|
+
<CmsIcon v-if="indeterminate" name="minus" class="size-3" />
|
|
11
|
+
<CmsIcon v-else-if="checked" name="check" class="size-3" />
|
|
12
|
+
</button>
|
|
13
|
+
</template>
|
|
14
|
+
|
|
15
|
+
<script setup>
|
|
16
|
+
defineProps({
|
|
17
|
+
checked: { type: Boolean, required: false },
|
|
18
|
+
indeterminate: { type: Boolean, required: false }
|
|
19
|
+
});
|
|
20
|
+
const emit = defineEmits(["toggle"]);
|
|
21
|
+
</script>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
type __VLS_Props = {
|
|
2
|
+
checked?: boolean;
|
|
3
|
+
indeterminate?: boolean;
|
|
4
|
+
};
|
|
5
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
6
|
+
toggle: () => any;
|
|
7
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
8
|
+
onToggle?: (() => any) | undefined;
|
|
9
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
10
|
+
declare const _default: typeof __VLS_export;
|
|
11
|
+
export default _default;
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="cms-empty" :class="{ 'is-fill': fill }">
|
|
3
|
-
<
|
|
4
|
-
|
|
3
|
+
<span class="cms-empty-icon">
|
|
4
|
+
<CmsIcon :name="icon" class="size-5" />
|
|
5
|
+
</span>
|
|
6
|
+
<p class="cms-title cms-title-sm">{{ title }}</p>
|
|
5
7
|
<p v-if="body" class="cms-subtitle max-w-md">
|
|
6
8
|
{{ body }}
|
|
7
9
|
</p>
|