@xleddyl/nuxt-cms 0.1.59 → 0.1.60
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 +33 -23
- package/dist/runtime/shared/index.d.ts +7 -1
- package/dist/runtime/shared/index.js +52 -0
- package/package.json +1 -1
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, fieldConditions, pageFieldsTableName, pageMediaTableName, isRequiredField, pageFields, isPrivateField, entryFieldsFor, mediaTypeFilter, DEFAULT_MEDIA_MAX_FILE_SIZE } from '../dist/runtime/shared/index.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';
|
|
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';
|
|
@@ -245,6 +245,13 @@ function validateConfig(config, i18n) {
|
|
|
245
245
|
if (!locales.length)
|
|
246
246
|
errors.push(`${fat}: translatable requires cms.i18n.locales in nuxt.config`);
|
|
247
247
|
}
|
|
248
|
+
if (field.mobile) {
|
|
249
|
+
const mobileKey = mobileMediaKey(key);
|
|
250
|
+
if (field.type !== "media")
|
|
251
|
+
errors.push(`${fat}: mobile is only supported on media fields`);
|
|
252
|
+
else if (allFields[mobileKey] && allFields[mobileKey].mobileOf !== key)
|
|
253
|
+
errors.push(`${fat}: mobile needs the key '${mobileKey}', which is already declared`);
|
|
254
|
+
}
|
|
248
255
|
if (field.type === "select") {
|
|
249
256
|
if (!field.options?.length)
|
|
250
257
|
errors.push(`${fat}: select requires a non-empty options array`);
|
|
@@ -337,6 +344,8 @@ function validateConfig(config, i18n) {
|
|
|
337
344
|
errors.push(`${bfat}: private fields are not supported inside blocks`);
|
|
338
345
|
if (blockField.showIf)
|
|
339
346
|
errors.push(`${bfat}: showIf is not supported inside blocks`);
|
|
347
|
+
if (blockField.mobile)
|
|
348
|
+
errors.push(`${bfat}: mobile is not supported inside blocks`);
|
|
340
349
|
if (blockField.type === "select" && !blockField.options?.length) {
|
|
341
350
|
errors.push(`${bfat}: select requires a non-empty options array`);
|
|
342
351
|
}
|
|
@@ -966,7 +975,7 @@ async function loadCmsConfig(nuxt, resolver, configPathOption, i18n, logger) {
|
|
|
966
975
|
moduleCache: false,
|
|
967
976
|
alias: { "#nuxt-cms": resolver.resolve("./runtime/shared/index") }
|
|
968
977
|
});
|
|
969
|
-
cmsConfig = await jiti.import(configPath, { default: true });
|
|
978
|
+
cmsConfig = expandMobileMedia(await jiti.import(configPath, { default: true }));
|
|
970
979
|
} else {
|
|
971
980
|
logger.warn(
|
|
972
981
|
`[nuxt-cms] Config file not found: ${configPath}. Using an empty registry \u2014 create a ${configPathOption}.ts with defineCmsConfig().`
|
|
@@ -984,27 +993,28 @@ async function loadCmsConfig(nuxt, resolver, configPathOption, i18n, logger) {
|
|
|
984
993
|
entry.pages = resolvePageRoutes(entry, discoveredRoutes);
|
|
985
994
|
routesByEntry[name] = entry.pages;
|
|
986
995
|
}
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
}
|
|
996
|
+
const source = (nuxt.options.alias["#cms-config"] ?? "").replace(/\\/g, "/").replace(/\.[cm]?[jt]s$/, "");
|
|
997
|
+
const wrapper = addTemplate({
|
|
998
|
+
filename: "cms/config.ts",
|
|
999
|
+
write: true,
|
|
1000
|
+
getContents: () => [
|
|
1001
|
+
`import { expandMobileMedia } from '#nuxt-cms'`,
|
|
1002
|
+
`import config from '${source}'`,
|
|
1003
|
+
``,
|
|
1004
|
+
`const pageRoutes = ${JSON.stringify(routesByEntry, null, 3)}`,
|
|
1005
|
+
``,
|
|
1006
|
+
`expandMobileMedia(config)`,
|
|
1007
|
+
``,
|
|
1008
|
+
`for (const [name, routes] of Object.entries(pageRoutes)) {`,
|
|
1009
|
+
` const entry = Object(config)[name]`,
|
|
1010
|
+
` if (entry) entry.pages = routes`,
|
|
1011
|
+
`}`,
|
|
1012
|
+
``,
|
|
1013
|
+
`export default config`,
|
|
1014
|
+
``
|
|
1015
|
+
].join("\n")
|
|
1016
|
+
});
|
|
1017
|
+
nuxt.options.alias["#cms-config"] = wrapper.dst;
|
|
1008
1018
|
const configErrors = validateConfig(cmsConfig, i18n);
|
|
1009
1019
|
if (configErrors.length) {
|
|
1010
1020
|
for (const error of configErrors) logger.error(error);
|
|
@@ -72,6 +72,8 @@ export interface FieldConfig {
|
|
|
72
72
|
blocks?: Record<string, BlockConfig>;
|
|
73
73
|
mediaType?: MediaType | MediaType[];
|
|
74
74
|
accept?: string[];
|
|
75
|
+
mobile?: boolean;
|
|
76
|
+
mobileOf?: string;
|
|
75
77
|
to?: string;
|
|
76
78
|
cardinality?: 'many-to-one' | 'one-to-one' | 'many-to-many';
|
|
77
79
|
onDelete?: 'set null' | 'cascade' | 'restrict';
|
|
@@ -121,6 +123,9 @@ export interface CmsEntry {
|
|
|
121
123
|
tabs?: CmsTab[];
|
|
122
124
|
table?: CmsTable;
|
|
123
125
|
}
|
|
126
|
+
export declare const MOBILE_MEDIA_SUFFIX = "Mobile";
|
|
127
|
+
export declare function mobileMediaKey(key: string): string;
|
|
128
|
+
export declare function expandMobileMedia<T extends Record<string, unknown>>(config: T): T;
|
|
124
129
|
export declare function entryTabs(entry: Pick<CmsEntry, 'tabs'>): CmsTab[];
|
|
125
130
|
export declare function fieldTab(field: FieldConfig, tabs: CmsTab[]): string | undefined;
|
|
126
131
|
export declare function fieldsByTab(fields: Record<string, FieldConfig>, tabs: CmsTab[]): Record<string, Record<string, FieldConfig>>;
|
|
@@ -191,6 +196,7 @@ export interface MediaFieldInput extends FieldInputBase {
|
|
|
191
196
|
mediaType?: MediaType | MediaType[];
|
|
192
197
|
accept?: string[];
|
|
193
198
|
translatable?: boolean;
|
|
199
|
+
mobile?: boolean;
|
|
194
200
|
}
|
|
195
201
|
export interface RelationFieldInput extends FieldInputBase {
|
|
196
202
|
type: 'relation';
|
|
@@ -199,7 +205,7 @@ export interface RelationFieldInput extends FieldInputBase {
|
|
|
199
205
|
onDelete?: 'set null' | 'cascade' | 'restrict';
|
|
200
206
|
}
|
|
201
207
|
type BlockField<T> = Omit<T, 'private' | 'showIf' | 'tab'>;
|
|
202
|
-
export type BlockFieldInput = BlockField<TextFieldInput> | BlockField<RichtextFieldInput> | BlockField<NumberFieldInput> | BlockField<BooleanFieldInput> | BlockField<DateFieldInput> | BlockField<EmailFieldInput> | BlockField<SelectFieldInput> | BlockField<JsonFieldInput> | BlockField<MediaFieldInput>;
|
|
208
|
+
export type BlockFieldInput = BlockField<TextFieldInput> | BlockField<RichtextFieldInput> | BlockField<NumberFieldInput> | BlockField<BooleanFieldInput> | BlockField<DateFieldInput> | BlockField<EmailFieldInput> | BlockField<SelectFieldInput> | BlockField<JsonFieldInput> | Omit<BlockField<MediaFieldInput>, 'mobile'>;
|
|
203
209
|
export interface BlockInput {
|
|
204
210
|
label: string;
|
|
205
211
|
fields: Record<string, BlockFieldInput>;
|
|
@@ -192,6 +192,58 @@ export function localizeBlocks(field, value, locale, defaultLocale) {
|
|
|
192
192
|
return value.map((item) => localizeBlock(field, item, locale, defaultLocale));
|
|
193
193
|
}
|
|
194
194
|
export const CMS_GRAPHQL_BATCH_LIMIT = 20;
|
|
195
|
+
export const MOBILE_MEDIA_SUFFIX = "Mobile";
|
|
196
|
+
export function mobileMediaKey(key) {
|
|
197
|
+
return `${key}${MOBILE_MEDIA_SUFFIX}`;
|
|
198
|
+
}
|
|
199
|
+
function mobileMediaField(key, field) {
|
|
200
|
+
const { mobile: _mobile, required: _required, ...rest } = field;
|
|
201
|
+
return {
|
|
202
|
+
...rest,
|
|
203
|
+
label: `${field.label} (mobile)`,
|
|
204
|
+
mobileOf: key
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
function withMobileMedia(fields, removed = () => false) {
|
|
208
|
+
const expanded = {};
|
|
209
|
+
for (const [key, field] of Object.entries(fields)) {
|
|
210
|
+
if (field?.mobileOf && Object.hasOwn(fields, field.mobileOf)) continue;
|
|
211
|
+
expanded[key] = field;
|
|
212
|
+
const mobileKey = mobileMediaKey(key);
|
|
213
|
+
if (field?.mobile && field.type === "media" && !Object.hasOwn(fields, mobileKey)) {
|
|
214
|
+
expanded[mobileKey] = mobileMediaField(key, field);
|
|
215
|
+
} else if (field?.mobile && fields[mobileKey]?.mobileOf === key) {
|
|
216
|
+
expanded[mobileKey] = fields[mobileKey];
|
|
217
|
+
} else if (!field && removed(key) && !Object.hasOwn(fields, mobileKey)) {
|
|
218
|
+
expanded[mobileKey] = null;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return expanded;
|
|
222
|
+
}
|
|
223
|
+
export function expandMobileMedia(config) {
|
|
224
|
+
for (const entry of Object.values(config)) {
|
|
225
|
+
if (!entry?.fields) continue;
|
|
226
|
+
const shared = entry.fields;
|
|
227
|
+
const hasMobile = (key) => !!shared[key]?.mobile && shared[key]?.type === "media";
|
|
228
|
+
entry.fields = withMobileMedia(shared);
|
|
229
|
+
if (entry.overrides) {
|
|
230
|
+
entry.overrides = Object.fromEntries(
|
|
231
|
+
Object.entries(entry.overrides).map(([path, override]) => [
|
|
232
|
+
path,
|
|
233
|
+
withMobileMedia(override, hasMobile)
|
|
234
|
+
])
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
if (entry.columns) {
|
|
238
|
+
entry.columns = [
|
|
239
|
+
...new Set(
|
|
240
|
+
entry.columns.flatMap((key) => hasMobile(key) ? [key, mobileMediaKey(key)] : [key])
|
|
241
|
+
)
|
|
242
|
+
];
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return config;
|
|
246
|
+
}
|
|
195
247
|
export function entryTabs(entry) {
|
|
196
248
|
return entry.tabs?.length ? entry.tabs : [];
|
|
197
249
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xleddyl/nuxt-cms",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.60",
|
|
4
4
|
"description": "Lightweight CMS that ships with your Nuxt app: runs on the Nitro server, content types defined in code, /cms admin panel, GraphQL API, SQLite or Postgres. No external CMS needed!",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Edoardo Alberti (https://github.com/xleddyl)",
|