@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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@xleddyl/nuxt-cms",
3
3
  "configKey": "cms",
4
- "version": "0.1.59",
4
+ "version": "0.1.60",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "unknown"
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
- if (Object.keys(routesByEntry).length) {
988
- const source = (nuxt.options.alias["#cms-config"] ?? "").replace(/\\/g, "/").replace(/\.[cm]?[jt]s$/, "");
989
- const wrapper = addTemplate({
990
- filename: "cms/config.ts",
991
- write: true,
992
- getContents: () => [
993
- `import config from '${source}'`,
994
- ``,
995
- `const pageRoutes = ${JSON.stringify(routesByEntry, null, 3)}`,
996
- ``,
997
- `for (const [name, routes] of Object.entries(pageRoutes)) {`,
998
- ` const entry = Object(config)[name]`,
999
- ` if (entry) entry.pages = routes`,
1000
- `}`,
1001
- ``,
1002
- `export default config`,
1003
- ``
1004
- ].join("\n")
1005
- });
1006
- nuxt.options.alias["#cms-config"] = wrapper.dst;
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.59",
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)",