@xleddyl/nuxt-cms 0.1.27 → 0.1.29
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 +5 -4
- package/dist/module.d.mts +37 -30
- package/dist/module.json +1 -1
- package/dist/module.mjs +123 -35
- package/dist/runtime/app/components/cms/Alert.d.vue.ts +1 -2
- package/dist/runtime/app/components/cms/Alert.vue +2 -3
- package/dist/runtime/app/components/cms/Alert.vue.d.ts +1 -2
- package/dist/runtime/app/components/cms/BlocksField.vue +2 -2
- package/dist/runtime/app/components/cms/Button.d.vue.ts +1 -1
- package/dist/runtime/app/components/cms/Button.vue.d.ts +1 -1
- package/dist/runtime/app/components/cms/ConfirmModal.vue +1 -1
- package/dist/runtime/app/components/cms/EntryDrawer.vue +1 -1
- package/dist/runtime/app/components/cms/MediaField.vue +2 -2
- package/dist/runtime/app/components/cms/MediaGallery.vue +3 -3
- package/dist/runtime/app/components/cms/MediaUpload.vue +9 -4
- package/dist/runtime/app/components/cms/Modal.d.vue.ts +4 -4
- package/dist/runtime/app/components/cms/Modal.vue +4 -4
- package/dist/runtime/app/components/cms/Modal.vue.d.ts +4 -4
- package/dist/runtime/app/components/cms/PageHeader.d.vue.ts +0 -1
- package/dist/runtime/app/components/cms/PageHeader.vue +0 -4
- package/dist/runtime/app/components/cms/PageHeader.vue.d.ts +0 -1
- package/dist/runtime/app/components/cms/RichTextField.vue +1 -1
- package/dist/runtime/app/components/cms/Toaster.vue +6 -0
- package/dist/runtime/app/components/cms/TranslatableField.vue +19 -8
- package/dist/runtime/app/composables/cms-runtime.d.ts +1 -0
- package/dist/runtime/app/layouts/cms-admin.vue +2 -2
- package/dist/runtime/app/pages/admin-collection.vue +12 -7
- package/dist/runtime/app/pages/admin-entry.vue +2 -2
- package/dist/runtime/app/pages/admin-login.vue +5 -5
- package/dist/runtime/assets/main.css +1 -1
- package/dist/runtime/server/api/collection.get.d.ts +1 -3
- package/dist/runtime/server/api/collection.get.js +3 -2
- package/dist/runtime/server/api/collection.post.js +3 -3
- package/dist/runtime/server/api/collection.put.js +13 -3
- package/dist/runtime/server/api/item.get.js +2 -2
- package/dist/runtime/server/api/item.put.js +7 -2
- package/dist/runtime/server/api/media-presign.post.js +3 -9
- package/dist/runtime/server/utils/graphql.js +13 -1
- package/dist/runtime/server/utils/media.d.ts +2 -0
- package/dist/runtime/server/utils/media.js +9 -1
- package/dist/runtime/server/utils/registry.d.ts +2 -0
- package/dist/runtime/server/utils/registry.js +7 -0
- package/dist/runtime/shared/index.d.ts +11 -1
- package/dist/runtime/shared/index.js +70 -1
- package/dist/runtime/shared/validation.js +1 -1
- package/dist/types.d.mts +1 -1
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -20,10 +20,6 @@ Register the module and configure it under the `cms` key in `nuxt.config.ts`:
|
|
|
20
20
|
export default defineNuxtConfig({
|
|
21
21
|
modules: ['@xleddyl/nuxt-cms'],
|
|
22
22
|
cms: {
|
|
23
|
-
database: {
|
|
24
|
-
driver: 'sqlite', // 'sqlite' | 'postgres' (with `url`) | 'libsql' (Turso/remote, with `url` + `authToken`)
|
|
25
|
-
path: 'data/cms.db',
|
|
26
|
-
},
|
|
27
23
|
i18n: {
|
|
28
24
|
locales: ['en', 'it'],
|
|
29
25
|
defaultLocale: 'en',
|
|
@@ -32,6 +28,11 @@ export default defineNuxtConfig({
|
|
|
32
28
|
})
|
|
33
29
|
```
|
|
34
30
|
|
|
31
|
+
`database` defaults to SQLite (`{ driver: 'sqlite', path: 'data/cms.db' }`) and can be omitted
|
|
32
|
+
entirely. Switch it to `{ driver: 'postgres', url: '...' }` or
|
|
33
|
+
`{ driver: 'libsql', url: '...', authToken: '...' }` (Turso/remote); see
|
|
34
|
+
[Configuration](docs/configuration.md) for every option.
|
|
35
|
+
|
|
35
36
|
Then declare your content types in a `cms.config.ts` at the project root with `defineCmsConfig()`.
|
|
36
37
|
|
|
37
38
|
### Disabling the CMS
|
package/dist/module.d.mts
CHANGED
|
@@ -1,41 +1,48 @@
|
|
|
1
1
|
import * as _nuxt_schema from '@nuxt/schema';
|
|
2
|
-
import { MediaStorageMode } from '../dist/runtime/shared/index.js';
|
|
3
|
-
|
|
4
|
-
type Dialect = 'sqlite' | 'postgres';
|
|
5
|
-
type Driver = Dialect | 'libsql';
|
|
6
2
|
|
|
3
|
+
type ModuleOptionsDatabase = {
|
|
4
|
+
driver?: 'sqlite';
|
|
5
|
+
path?: string;
|
|
6
|
+
} | {
|
|
7
|
+
driver: 'postgres';
|
|
8
|
+
url?: string;
|
|
9
|
+
} | {
|
|
10
|
+
driver: 'libsql';
|
|
11
|
+
url?: string;
|
|
12
|
+
authToken?: string;
|
|
13
|
+
};
|
|
14
|
+
type ModuleOptionsMedia = {
|
|
15
|
+
storage?: 's3';
|
|
16
|
+
endpoint?: string;
|
|
17
|
+
region?: string;
|
|
18
|
+
bucket?: string;
|
|
19
|
+
accessKeyId?: string;
|
|
20
|
+
secretAccessKey?: string;
|
|
21
|
+
publicBaseUrl?: string;
|
|
22
|
+
presignExpiry?: number;
|
|
23
|
+
maxFileSize?: number;
|
|
24
|
+
} | {
|
|
25
|
+
storage: 'local';
|
|
26
|
+
publicBaseUrl: string;
|
|
27
|
+
};
|
|
7
28
|
interface ModuleOptions {
|
|
8
29
|
enabled?: boolean;
|
|
9
|
-
configPath
|
|
10
|
-
admin
|
|
11
|
-
email
|
|
12
|
-
password
|
|
13
|
-
};
|
|
14
|
-
database: {
|
|
15
|
-
driver?: Driver;
|
|
16
|
-
path?: string;
|
|
17
|
-
url?: string;
|
|
18
|
-
authToken?: string;
|
|
19
|
-
};
|
|
20
|
-
media: {
|
|
21
|
-
storage: MediaStorageMode;
|
|
22
|
-
endpoint: string;
|
|
23
|
-
region: string;
|
|
24
|
-
bucket: string;
|
|
25
|
-
publicBaseUrl: string;
|
|
26
|
-
presignExpiry: number;
|
|
27
|
-
accessKeyId: string;
|
|
28
|
-
secretAccessKey: string;
|
|
30
|
+
configPath?: string;
|
|
31
|
+
admin?: {
|
|
32
|
+
email?: string;
|
|
33
|
+
password?: string;
|
|
29
34
|
};
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
35
|
+
database?: ModuleOptionsDatabase;
|
|
36
|
+
media?: ModuleOptionsMedia;
|
|
37
|
+
i18n?: {
|
|
38
|
+
locales?: string[];
|
|
39
|
+
defaultLocale?: string;
|
|
33
40
|
};
|
|
34
|
-
graphql
|
|
35
|
-
maxDepth
|
|
41
|
+
graphql?: {
|
|
42
|
+
maxDepth?: number;
|
|
36
43
|
};
|
|
37
44
|
}
|
|
38
45
|
declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
|
|
39
46
|
|
|
40
47
|
export { _default as default };
|
|
41
|
-
export type { ModuleOptions };
|
|
48
|
+
export type { ModuleOptions, ModuleOptionsDatabase, ModuleOptionsMedia };
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -10,7 +10,7 @@ import { introspectionFromSchema, buildSchema } from 'graphql';
|
|
|
10
10
|
import { minifyIntrospection, outputIntrospectionFile } from 'gql.tada/internal';
|
|
11
11
|
import { createJiti } from 'jiti';
|
|
12
12
|
import { typeName, blockTypeName, blockUnionName, renderGraphqlSdl } from '../dist/runtime/shared/graphql-sdl.js';
|
|
13
|
-
import { isMultiSelect, isTranslatableField } from '../dist/runtime/shared/index.js';
|
|
13
|
+
import { isMultiSelect, isTranslatableField, isTranslatableMediaField, DEFAULT_MEDIA_MAX_FILE_SIZE } from '../dist/runtime/shared/index.js';
|
|
14
14
|
|
|
15
15
|
const IDENTIFIER = /^[a-z_]\w*$/i;
|
|
16
16
|
const RESERVED_ENTRY_KEYS = ["admin", "auth", "login", "media", "graphql", "cms_media"];
|
|
@@ -96,8 +96,10 @@ function validateConfig(config, i18n) {
|
|
|
96
96
|
columnNames.add(column);
|
|
97
97
|
}
|
|
98
98
|
if (field.translatable) {
|
|
99
|
-
if (field.type !== "text" && field.type !== "richtext")
|
|
100
|
-
errors.push(
|
|
99
|
+
if (field.type !== "text" && field.type !== "richtext" && field.type !== "media")
|
|
100
|
+
errors.push(
|
|
101
|
+
`${fat}: translatable is only supported on text, richtext and media fields`
|
|
102
|
+
);
|
|
101
103
|
if (!locales.length)
|
|
102
104
|
errors.push(`${fat}: translatable requires cms.i18n.locales in nuxt.config`);
|
|
103
105
|
}
|
|
@@ -207,7 +209,7 @@ function columnExpr(key, field, dialect) {
|
|
|
207
209
|
const col = snakeCase(key);
|
|
208
210
|
let expr;
|
|
209
211
|
if (isTranslatableField(field)) {
|
|
210
|
-
expr = jsonExpr(col, dialect);
|
|
212
|
+
expr = isTranslatableMediaField(field) ? `text('${col}')` : jsonExpr(col, dialect);
|
|
211
213
|
if (field.required) expr += ".notNull()";
|
|
212
214
|
return ` ${key}: ${expr},`;
|
|
213
215
|
}
|
|
@@ -304,7 +306,7 @@ function renderSchemaFile(config, dialect, resolveImport = (s) => s) {
|
|
|
304
306
|
if (pg && fields.some((f) => f.type === "date")) core.add("date");
|
|
305
307
|
if (pg && fields.some((f) => f.type === "boolean")) core.add("boolean");
|
|
306
308
|
if (pg && fields.some(
|
|
307
|
-
(f) => f.type === "json" || f.type === "blocks" || isTranslatableField(f) || isMultiSelect(f)
|
|
309
|
+
(f) => f.type === "json" || f.type === "blocks" || isTranslatableField(f) && !isTranslatableMediaField(f) || isMultiSelect(f)
|
|
308
310
|
))
|
|
309
311
|
core.add("jsonb");
|
|
310
312
|
if (pg) core.add("timestamp");
|
|
@@ -428,6 +430,64 @@ function resolveCmsEnabled(explicit, envValue) {
|
|
|
428
430
|
return true;
|
|
429
431
|
}
|
|
430
432
|
|
|
433
|
+
function resolveDatabaseOptions(database) {
|
|
434
|
+
if (database?.driver === "postgres") {
|
|
435
|
+
return { driver: "postgres", path: "data/cms.db", url: database.url ?? "", authToken: "" };
|
|
436
|
+
}
|
|
437
|
+
if (database?.driver === "libsql") {
|
|
438
|
+
return {
|
|
439
|
+
driver: "libsql",
|
|
440
|
+
path: "data/cms.db",
|
|
441
|
+
url: database.url ?? "",
|
|
442
|
+
authToken: database.authToken ?? ""
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
return { driver: "sqlite", path: database?.path ?? "data/cms.db", url: "", authToken: "" };
|
|
446
|
+
}
|
|
447
|
+
function resolveMediaOptions(media) {
|
|
448
|
+
if (media?.storage === "local") {
|
|
449
|
+
return {
|
|
450
|
+
storage: "local",
|
|
451
|
+
endpoint: "",
|
|
452
|
+
region: "auto",
|
|
453
|
+
bucket: "",
|
|
454
|
+
accessKeyId: "",
|
|
455
|
+
secretAccessKey: "",
|
|
456
|
+
presignExpiry: 600,
|
|
457
|
+
maxFileSize: DEFAULT_MEDIA_MAX_FILE_SIZE,
|
|
458
|
+
publicBaseUrl: media.publicBaseUrl
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
return {
|
|
462
|
+
storage: "s3",
|
|
463
|
+
endpoint: media?.endpoint ?? "",
|
|
464
|
+
region: media?.region ?? "auto",
|
|
465
|
+
bucket: media?.bucket ?? "",
|
|
466
|
+
accessKeyId: media?.accessKeyId ?? "",
|
|
467
|
+
secretAccessKey: media?.secretAccessKey ?? "",
|
|
468
|
+
presignExpiry: media?.presignExpiry ?? 600,
|
|
469
|
+
maxFileSize: media?.maxFileSize ?? DEFAULT_MEDIA_MAX_FILE_SIZE,
|
|
470
|
+
publicBaseUrl: media?.publicBaseUrl ?? ""
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
function resolveModuleOptions(options) {
|
|
474
|
+
return {
|
|
475
|
+
configPath: options.configPath ?? "cms.config",
|
|
476
|
+
admin: {
|
|
477
|
+
email: options.admin?.email ?? "",
|
|
478
|
+
password: options.admin?.password ?? ""
|
|
479
|
+
},
|
|
480
|
+
database: resolveDatabaseOptions(options.database),
|
|
481
|
+
media: resolveMediaOptions(options.media),
|
|
482
|
+
i18n: {
|
|
483
|
+
locales: options.i18n?.locales ?? [],
|
|
484
|
+
defaultLocale: options.i18n?.defaultLocale ?? "en"
|
|
485
|
+
},
|
|
486
|
+
graphql: {
|
|
487
|
+
maxDepth: options.graphql?.maxDepth ?? 8
|
|
488
|
+
}
|
|
489
|
+
};
|
|
490
|
+
}
|
|
431
491
|
const module$1 = defineNuxtModule({
|
|
432
492
|
meta: {
|
|
433
493
|
name: "@xleddyl/nuxt-cms",
|
|
@@ -445,9 +505,7 @@ const module$1 = defineNuxtModule({
|
|
|
445
505
|
},
|
|
446
506
|
database: {
|
|
447
507
|
driver: "sqlite",
|
|
448
|
-
path: "data/cms.db"
|
|
449
|
-
url: "",
|
|
450
|
-
authToken: ""
|
|
508
|
+
path: "data/cms.db"
|
|
451
509
|
},
|
|
452
510
|
media: {
|
|
453
511
|
storage: "s3",
|
|
@@ -456,6 +514,7 @@ const module$1 = defineNuxtModule({
|
|
|
456
514
|
bucket: "",
|
|
457
515
|
publicBaseUrl: "",
|
|
458
516
|
presignExpiry: 600,
|
|
517
|
+
maxFileSize: DEFAULT_MEDIA_MAX_FILE_SIZE,
|
|
459
518
|
accessKeyId: "",
|
|
460
519
|
secretAccessKey: ""
|
|
461
520
|
},
|
|
@@ -470,6 +529,7 @@ const module$1 = defineNuxtModule({
|
|
|
470
529
|
async setup(options, nuxt) {
|
|
471
530
|
const resolver = createResolver(import.meta.url);
|
|
472
531
|
const logger = useLogger("nuxt-cms");
|
|
532
|
+
const resolved = resolveModuleOptions(options);
|
|
473
533
|
if (!resolveCmsEnabled(options.enabled, process.env[CMS_ENABLED_ENV])) {
|
|
474
534
|
const stub = resolver.resolve("./runtime/app/composables/cms-query-disabled");
|
|
475
535
|
addImports([
|
|
@@ -477,16 +537,38 @@ const module$1 = defineNuxtModule({
|
|
|
477
537
|
{ name: "$cmsQuery", from: stub }
|
|
478
538
|
]);
|
|
479
539
|
nuxt.options.runtimeConfig.public.cms = {
|
|
480
|
-
mediaBaseUrl:
|
|
481
|
-
mediaStorage:
|
|
482
|
-
|
|
540
|
+
mediaBaseUrl: resolved.media.publicBaseUrl,
|
|
541
|
+
mediaStorage: resolved.media.storage,
|
|
542
|
+
mediaMaxFileSize: resolved.media.maxFileSize,
|
|
543
|
+
i18n: resolved.i18n
|
|
483
544
|
};
|
|
484
545
|
logger.info(
|
|
485
546
|
"[nuxt-cms] disabled: registering no-op useCms/$cmsQuery stubs, skipping admin, server and database setup"
|
|
486
547
|
);
|
|
487
548
|
return;
|
|
488
549
|
}
|
|
489
|
-
|
|
550
|
+
if ((resolved.database.driver === "postgres" || resolved.database.driver === "libsql") && !resolved.database.url && !process.env.NUXT_CMS_DATABASE_URL) {
|
|
551
|
+
logger.warn(
|
|
552
|
+
`[nuxt-cms] database.driver is '${resolved.database.driver}' but no url is configured (database.url or NUXT_CMS_DATABASE_URL); the app will fail to connect unless one is provided before the server starts.`
|
|
553
|
+
);
|
|
554
|
+
}
|
|
555
|
+
if (!Number.isInteger(resolved.media.maxFileSize) || resolved.media.maxFileSize <= 0) {
|
|
556
|
+
throw new Error(
|
|
557
|
+
`[nuxt-cms] media.maxFileSize must be a positive integer number of bytes, got ${resolved.media.maxFileSize}`
|
|
558
|
+
);
|
|
559
|
+
}
|
|
560
|
+
const s3KeysConfigured = [
|
|
561
|
+
resolved.media.endpoint,
|
|
562
|
+
resolved.media.bucket,
|
|
563
|
+
resolved.media.accessKeyId,
|
|
564
|
+
resolved.media.secretAccessKey
|
|
565
|
+
].some(Boolean);
|
|
566
|
+
if (resolved.media.storage === "s3" && s3KeysConfigured && !resolved.media.publicBaseUrl && !process.env.NUXT_PUBLIC_CMS_MEDIA_BASE_URL) {
|
|
567
|
+
logger.warn(
|
|
568
|
+
"[nuxt-cms] media.storage is 's3' with credentials configured but no publicBaseUrl (media.publicBaseUrl or NUXT_PUBLIC_CMS_MEDIA_BASE_URL); uploaded media URLs will be null."
|
|
569
|
+
);
|
|
570
|
+
}
|
|
571
|
+
const configPath = await resolvePath(resolved.configPath, { cwd: nuxt.options.rootDir });
|
|
490
572
|
nuxt.options.alias["#nuxt-cms"] = resolver.resolve("./runtime/shared/index");
|
|
491
573
|
nuxt.options.watch.push(configPath);
|
|
492
574
|
let cmsConfig = {};
|
|
@@ -499,11 +581,11 @@ const module$1 = defineNuxtModule({
|
|
|
499
581
|
cmsConfig = await jiti.import(configPath, { default: true });
|
|
500
582
|
} else {
|
|
501
583
|
logger.warn(
|
|
502
|
-
`[nuxt-cms] Config file not found: ${configPath}. Using an empty registry \u2014 create a ${
|
|
584
|
+
`[nuxt-cms] Config file not found: ${configPath}. Using an empty registry \u2014 create a ${resolved.configPath}.ts with defineCmsConfig().`
|
|
503
585
|
);
|
|
504
586
|
nuxt.options.alias["#cms-config"] = resolver.resolve("./runtime/shared/empty-config");
|
|
505
587
|
}
|
|
506
|
-
const configErrors = validateConfig(cmsConfig,
|
|
588
|
+
const configErrors = validateConfig(cmsConfig, resolved.i18n);
|
|
507
589
|
if (configErrors.length) {
|
|
508
590
|
for (const error of configErrors) logger.error(error);
|
|
509
591
|
throw new Error(
|
|
@@ -523,7 +605,7 @@ const module$1 = defineNuxtModule({
|
|
|
523
605
|
write: true,
|
|
524
606
|
getContents: () => renderSchemaFile(
|
|
525
607
|
cmsConfig,
|
|
526
|
-
|
|
608
|
+
resolved.database.driver === "postgres" ? "postgres" : "sqlite",
|
|
527
609
|
resolveImport
|
|
528
610
|
)
|
|
529
611
|
});
|
|
@@ -588,11 +670,11 @@ const module$1 = defineNuxtModule({
|
|
|
588
670
|
{ name: "$cmsQuery", from: resolver.resolve("./runtime/app/composables/cms-query") }
|
|
589
671
|
]);
|
|
590
672
|
const {
|
|
591
|
-
driver
|
|
592
|
-
path: dbPath
|
|
593
|
-
url: databaseUrl
|
|
594
|
-
authToken: databaseAuthToken
|
|
595
|
-
} =
|
|
673
|
+
driver,
|
|
674
|
+
path: dbPath,
|
|
675
|
+
url: databaseUrl,
|
|
676
|
+
authToken: databaseAuthToken
|
|
677
|
+
} = resolved.database;
|
|
596
678
|
nuxt.options.alias["#cms-db"] = resolver.resolve(
|
|
597
679
|
driver === "postgres" ? "./runtime/server/utils/db-postgres" : driver === "libsql" ? "./runtime/server/utils/db-libsql" : "./runtime/server/utils/db-sqlite"
|
|
598
680
|
);
|
|
@@ -645,11 +727,11 @@ const module$1 = defineNuxtModule({
|
|
|
645
727
|
});
|
|
646
728
|
}
|
|
647
729
|
const publicDir = resolve(nuxt.options.rootDir, nuxt.options.dir?.public ?? "public");
|
|
648
|
-
const mediaLocalRoot =
|
|
730
|
+
const mediaLocalRoot = resolved.media.storage === "local" && resolved.media.publicBaseUrl.startsWith("/") ? join(publicDir, ...resolved.media.publicBaseUrl.split("/").filter(Boolean)) : "";
|
|
649
731
|
const existingConfig = nuxt.options.runtimeConfig.cms ?? {};
|
|
650
732
|
nuxt.options.runtimeConfig.cms = {
|
|
651
|
-
adminEmail:
|
|
652
|
-
adminPassword:
|
|
733
|
+
adminEmail: resolved.admin.email,
|
|
734
|
+
adminPassword: resolved.admin.password,
|
|
653
735
|
databaseUrl,
|
|
654
736
|
databaseAuthToken,
|
|
655
737
|
dbPath: resolvedDbPath,
|
|
@@ -657,25 +739,27 @@ const module$1 = defineNuxtModule({
|
|
|
657
739
|
...existingConfig,
|
|
658
740
|
graphql: {
|
|
659
741
|
graphiql: nuxt.options.dev,
|
|
660
|
-
maxDepth:
|
|
742
|
+
maxDepth: resolved.graphql.maxDepth,
|
|
661
743
|
...existingConfig.graphql ?? {}
|
|
662
744
|
},
|
|
663
745
|
media: {
|
|
664
|
-
storage:
|
|
665
|
-
endpoint:
|
|
666
|
-
region:
|
|
667
|
-
bucket:
|
|
668
|
-
presignExpiry:
|
|
669
|
-
|
|
670
|
-
|
|
746
|
+
storage: resolved.media.storage,
|
|
747
|
+
endpoint: resolved.media.endpoint,
|
|
748
|
+
region: resolved.media.region,
|
|
749
|
+
bucket: resolved.media.bucket,
|
|
750
|
+
presignExpiry: resolved.media.presignExpiry,
|
|
751
|
+
maxFileSize: resolved.media.maxFileSize,
|
|
752
|
+
accessKeyId: resolved.media.accessKeyId,
|
|
753
|
+
secretAccessKey: resolved.media.secretAccessKey,
|
|
671
754
|
localRoot: mediaLocalRoot,
|
|
672
755
|
...existingConfig.media ?? {}
|
|
673
756
|
}
|
|
674
757
|
};
|
|
675
758
|
nuxt.options.runtimeConfig.public.cms = {
|
|
676
|
-
mediaBaseUrl:
|
|
677
|
-
mediaStorage:
|
|
678
|
-
|
|
759
|
+
mediaBaseUrl: resolved.media.publicBaseUrl,
|
|
760
|
+
mediaStorage: resolved.media.storage,
|
|
761
|
+
mediaMaxFileSize: resolved.media.maxFileSize,
|
|
762
|
+
i18n: resolved.i18n
|
|
679
763
|
};
|
|
680
764
|
addTypeTemplate(
|
|
681
765
|
{
|
|
@@ -702,7 +786,11 @@ const module$1 = defineNuxtModule({
|
|
|
702
786
|
);
|
|
703
787
|
addVitePlugin(tailwindcss());
|
|
704
788
|
addVitePlugin(svgLoader({ defaultImport: "url", svgoConfig: { plugins: ["prefixIds"] } }));
|
|
705
|
-
nuxt.options.css.push(
|
|
789
|
+
nuxt.options.css.push(
|
|
790
|
+
resolveImport("@fontsource-variable/hanken-grotesk/index.css"),
|
|
791
|
+
resolveImport("@fontsource/fragment-mono/index.css"),
|
|
792
|
+
resolver.resolve("./runtime/assets/main.css")
|
|
793
|
+
);
|
|
706
794
|
nuxt.hook("app:templates", (app) => {
|
|
707
795
|
app.layouts["cms-admin"] = {
|
|
708
796
|
name: "cms-admin",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="cms-alert" :class="`cms-alert-${color ?? '
|
|
3
|
-
<p v-if="title" class="
|
|
2
|
+
<div class="cms-alert" :class="`cms-alert-${color ?? 'neutral'}`" role="alert">
|
|
3
|
+
<p v-if="title" class="cms-alert-title">{{ title }}</p>
|
|
4
4
|
<slot />
|
|
5
5
|
</div>
|
|
6
6
|
</template>
|
|
@@ -8,7 +8,6 @@
|
|
|
8
8
|
<script setup>
|
|
9
9
|
defineProps({
|
|
10
10
|
color: { type: String, required: false },
|
|
11
|
-
variant: { type: String, required: false },
|
|
12
11
|
title: { type: String, required: false }
|
|
13
12
|
});
|
|
14
13
|
</script>
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<div class="flex items-center gap-1">
|
|
9
9
|
<button
|
|
10
10
|
type="button"
|
|
11
|
-
class="cms-
|
|
11
|
+
class="cms-block-label flex grow items-center gap-1.5 text-left"
|
|
12
12
|
:aria-label="isCollapsed(item) ? 'Expand block' : 'Collapse block'"
|
|
13
13
|
@click="toggleCollapsed(item)"
|
|
14
14
|
>
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
</template>
|
|
70
70
|
</div>
|
|
71
71
|
<CmsDropdownMenu :items="addItems" class="self-start">
|
|
72
|
-
<CmsButton label="Add block" icon="plus" variant="
|
|
72
|
+
<CmsButton label="Add block" icon="plus" variant="soft" />
|
|
73
73
|
</CmsDropdownMenu>
|
|
74
74
|
</div>
|
|
75
75
|
</template>
|
|
@@ -6,7 +6,7 @@ type __VLS_Props = {
|
|
|
6
6
|
trailingIcon?: boolean;
|
|
7
7
|
size?: 'xs' | 'sm' | 'md' | 'lg';
|
|
8
8
|
color?: 'primary' | 'neutral' | 'error' | 'success';
|
|
9
|
-
variant?: 'solid' | '
|
|
9
|
+
variant?: 'solid' | 'soft' | 'ghost';
|
|
10
10
|
disabled?: boolean;
|
|
11
11
|
loading?: boolean;
|
|
12
12
|
block?: boolean;
|
|
@@ -6,7 +6,7 @@ type __VLS_Props = {
|
|
|
6
6
|
trailingIcon?: boolean;
|
|
7
7
|
size?: 'xs' | 'sm' | 'md' | 'lg';
|
|
8
8
|
color?: 'primary' | 'neutral' | 'error' | 'success';
|
|
9
|
-
variant?: 'solid' | '
|
|
9
|
+
variant?: 'solid' | 'soft' | 'ghost';
|
|
10
10
|
disabled?: boolean;
|
|
11
11
|
loading?: boolean;
|
|
12
12
|
block?: boolean;
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
<div class="cms-form">
|
|
10
10
|
<p class="text-sm">{{ state.message }}</p>
|
|
11
11
|
<div class="cms-actions is-end">
|
|
12
|
-
<CmsButton label="Cancel" variant="
|
|
12
|
+
<CmsButton label="Cancel" variant="soft" color="neutral" @click="finish(false)" />
|
|
13
13
|
<CmsButton
|
|
14
14
|
:label="state.confirmLabel ?? 'Confirm'"
|
|
15
15
|
color="error"
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
<CmsButton
|
|
18
18
|
label="Replace"
|
|
19
19
|
icon="arrow-path"
|
|
20
|
-
variant="
|
|
20
|
+
variant="soft"
|
|
21
21
|
color="neutral"
|
|
22
22
|
size="xs"
|
|
23
23
|
@click="openGallery"
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
<CmsButton
|
|
26
26
|
label="Remove"
|
|
27
27
|
icon="trash"
|
|
28
|
-
variant="
|
|
28
|
+
variant="soft"
|
|
29
29
|
color="error"
|
|
30
30
|
size="xs"
|
|
31
31
|
@click="clear"
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
<CmsButton
|
|
54
54
|
label="New folder"
|
|
55
55
|
icon="folder-plus"
|
|
56
|
-
variant="
|
|
56
|
+
variant="soft"
|
|
57
57
|
size="sm"
|
|
58
58
|
@click="openNewFolder"
|
|
59
59
|
/>
|
|
@@ -120,7 +120,7 @@
|
|
|
120
120
|
</div>
|
|
121
121
|
|
|
122
122
|
<CmsEmptyState v-else-if="loadError" icon="exclamation-triangle" title="Could not load media">
|
|
123
|
-
<CmsButton label="Retry" icon="arrow-path" variant="
|
|
123
|
+
<CmsButton label="Retry" icon="arrow-path" variant="soft" @click="reload" />
|
|
124
124
|
</CmsEmptyState>
|
|
125
125
|
|
|
126
126
|
<CmsEmptyState
|
|
@@ -140,7 +140,7 @@
|
|
|
140
140
|
v-if="!readOnly"
|
|
141
141
|
label="Upload"
|
|
142
142
|
icon="plus"
|
|
143
|
-
variant="
|
|
143
|
+
variant="soft"
|
|
144
144
|
@click="uploadOpen = true"
|
|
145
145
|
/>
|
|
146
146
|
</CmsEmptyState>
|
|
@@ -36,7 +36,9 @@
|
|
|
36
36
|
</template>
|
|
37
37
|
|
|
38
38
|
<script setup>
|
|
39
|
+
import { formatFileSize } from "#nuxt-cms";
|
|
39
40
|
import { computed, ref } from "#imports";
|
|
41
|
+
import { useCmsRuntime } from "../../composables/cms-runtime";
|
|
40
42
|
import { useCmsToast } from "../../composables/cms-toast";
|
|
41
43
|
const props = defineProps({
|
|
42
44
|
multiple: { type: Boolean, required: false },
|
|
@@ -46,6 +48,7 @@ const props = defineProps({
|
|
|
46
48
|
});
|
|
47
49
|
const emit = defineEmits(["uploaded"]);
|
|
48
50
|
const toast = useCmsToast();
|
|
51
|
+
const { mediaMaxFileSize } = useCmsRuntime();
|
|
49
52
|
async function imageDimensions(file) {
|
|
50
53
|
if (!file.type.startsWith("image/")) return {};
|
|
51
54
|
try {
|
|
@@ -74,7 +77,6 @@ function matchesAccept(file) {
|
|
|
74
77
|
return file.type.toLowerCase() === pattern;
|
|
75
78
|
});
|
|
76
79
|
}
|
|
77
|
-
const MAX_FILE_SIZE = 10 * 1024 * 1024;
|
|
78
80
|
async function uploadOne(file) {
|
|
79
81
|
const presign = await $fetch("/api/cms/admin/media/presign", {
|
|
80
82
|
method: "POST",
|
|
@@ -106,11 +108,14 @@ async function uploadOne(file) {
|
|
|
106
108
|
}
|
|
107
109
|
async function handleFiles(list) {
|
|
108
110
|
let files = Array.from(list).filter(matchesAccept);
|
|
109
|
-
const tooLarge = files.filter((file) => file.size >
|
|
111
|
+
const tooLarge = files.filter((file) => file.size > mediaMaxFileSize);
|
|
110
112
|
for (const file of tooLarge) {
|
|
111
|
-
toast.add({
|
|
113
|
+
toast.add({
|
|
114
|
+
title: `File too large (max ${formatFileSize(mediaMaxFileSize)}): ${file.name}`,
|
|
115
|
+
color: "error"
|
|
116
|
+
});
|
|
112
117
|
}
|
|
113
|
-
files = files.filter((file) => file.size <=
|
|
118
|
+
files = files.filter((file) => file.size <= mediaMaxFileSize);
|
|
114
119
|
if (!props.multiple) files = files.slice(0, 1);
|
|
115
120
|
if (!files.length || uploading.value) return;
|
|
116
121
|
uploading.value = true;
|
|
@@ -6,13 +6,13 @@ type __VLS_ModelProps = {
|
|
|
6
6
|
'open'?: boolean;
|
|
7
7
|
};
|
|
8
8
|
type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
|
|
9
|
-
declare var
|
|
9
|
+
declare var __VLS_13: {}, __VLS_15: {}, __VLS_17: {};
|
|
10
10
|
type __VLS_Slots = {} & {
|
|
11
|
-
header?: (props: typeof
|
|
11
|
+
header?: (props: typeof __VLS_13) => any;
|
|
12
12
|
} & {
|
|
13
|
-
body?: (props: typeof
|
|
13
|
+
body?: (props: typeof __VLS_15) => any;
|
|
14
14
|
} & {
|
|
15
|
-
default?: (props: typeof
|
|
15
|
+
default?: (props: typeof __VLS_17) => any;
|
|
16
16
|
};
|
|
17
17
|
declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
18
18
|
"after:leave": () => any;
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
<Teleport to="body">
|
|
3
3
|
<!-- Wrapped in .cms-scope so the vendored Tailwind utilities still apply
|
|
4
4
|
once teleported outside the admin layout root. -->
|
|
5
|
-
<
|
|
6
|
-
<div class="cms-overlay" @click.self="close">
|
|
5
|
+
<Transition name="cms-modal">
|
|
6
|
+
<div v-if="open" class="cms-scope cms-overlay" @click.self="close">
|
|
7
7
|
<div
|
|
8
|
-
class="cms-modal
|
|
8
|
+
class="cms-modal"
|
|
9
9
|
:class="size ? `is-${size}` : void 0"
|
|
10
10
|
role="dialog"
|
|
11
11
|
aria-modal="true"
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
</div>
|
|
23
23
|
</div>
|
|
24
24
|
</div>
|
|
25
|
-
</
|
|
25
|
+
</Transition>
|
|
26
26
|
</Teleport>
|
|
27
27
|
</template>
|
|
28
28
|
|
|
@@ -6,13 +6,13 @@ type __VLS_ModelProps = {
|
|
|
6
6
|
'open'?: boolean;
|
|
7
7
|
};
|
|
8
8
|
type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
|
|
9
|
-
declare var
|
|
9
|
+
declare var __VLS_13: {}, __VLS_15: {}, __VLS_17: {};
|
|
10
10
|
type __VLS_Slots = {} & {
|
|
11
|
-
header?: (props: typeof
|
|
11
|
+
header?: (props: typeof __VLS_13) => any;
|
|
12
12
|
} & {
|
|
13
|
-
body?: (props: typeof
|
|
13
|
+
body?: (props: typeof __VLS_15) => any;
|
|
14
14
|
} & {
|
|
15
|
-
default?: (props: typeof
|
|
15
|
+
default?: (props: typeof __VLS_17) => any;
|
|
16
16
|
};
|
|
17
17
|
declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
18
18
|
"after:leave": () => any;
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<header class="cms-page-header">
|
|
3
3
|
<div class="cms-page-heading">
|
|
4
|
-
<div v-if="kicker" class="cms-kicker">
|
|
5
|
-
{{ kicker }}
|
|
6
|
-
</div>
|
|
7
4
|
<div class="cms-actions">
|
|
8
5
|
<h1 class="cms-title cms-title-md">
|
|
9
6
|
{{ title }}
|
|
@@ -17,7 +14,6 @@
|
|
|
17
14
|
|
|
18
15
|
<script setup>
|
|
19
16
|
defineProps({
|
|
20
|
-
kicker: { type: String, required: false },
|
|
21
17
|
title: { type: String, required: true }
|
|
22
18
|
});
|
|
23
19
|
</script>
|