@xleddyl/nuxt-cms 0.4.2 → 0.4.4

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.
Files changed (59) hide show
  1. package/README.md +74 -96
  2. package/dist/module.json +1 -1
  3. package/dist/module.mjs +84 -16
  4. package/dist/runtime/app/components/cms/BlocksField.vue +30 -5
  5. package/dist/runtime/app/components/cms/DropdownMenu.vue +1 -5
  6. package/dist/runtime/app/components/cms/EntryDrawer.d.vue.ts +3 -0
  7. package/dist/runtime/app/components/cms/EntryDrawer.vue +32 -13
  8. package/dist/runtime/app/components/cms/EntryDrawer.vue.d.ts +3 -0
  9. package/dist/runtime/app/components/cms/EntryForm.d.vue.ts +1 -0
  10. package/dist/runtime/app/components/cms/EntryForm.vue +20 -3
  11. package/dist/runtime/app/components/cms/EntryForm.vue.d.ts +1 -0
  12. package/dist/runtime/app/components/cms/FieldInput.vue +2 -2
  13. package/dist/runtime/app/components/cms/Form.d.vue.ts +8 -6
  14. package/dist/runtime/app/components/cms/Form.vue +26 -5
  15. package/dist/runtime/app/components/cms/Form.vue.d.ts +8 -6
  16. package/dist/runtime/app/components/cms/FormField.vue +6 -1
  17. package/dist/runtime/app/components/cms/ImportModal.d.vue.ts +18 -0
  18. package/dist/runtime/app/components/cms/ImportModal.vue +252 -0
  19. package/dist/runtime/app/components/cms/ImportModal.vue.d.ts +18 -0
  20. package/dist/runtime/app/components/cms/ListFilters.d.vue.ts +18 -0
  21. package/dist/runtime/app/components/cms/ListFilters.vue +151 -0
  22. package/dist/runtime/app/components/cms/ListFilters.vue.d.ts +18 -0
  23. package/dist/runtime/app/components/cms/RelationField.vue +3 -1
  24. package/dist/runtime/app/components/cms/VisualEditor.vue +81 -12
  25. package/dist/runtime/app/pages/admin-collection.vue +158 -7
  26. package/dist/runtime/app/pages/admin-content.vue +18 -7
  27. package/dist/runtime/app/pages/admin-entry.vue +1 -0
  28. package/dist/runtime/app/pages/admin-preview.vue +6 -3
  29. package/dist/runtime/app/preview/PreviewOverlay.vue +4 -0
  30. package/dist/runtime/app/utils/ui.d.ts +2 -1
  31. package/dist/runtime/app/utils/ui.js +1 -0
  32. package/dist/runtime/assets/main.css +1 -1
  33. package/dist/runtime/server/api/collection-export.get.d.ts +2 -0
  34. package/dist/runtime/server/api/collection-export.get.js +67 -0
  35. package/dist/runtime/server/api/collection-import.post.d.ts +2 -0
  36. package/dist/runtime/server/api/collection-import.post.js +85 -0
  37. package/dist/runtime/server/api/collection.get.js +7 -15
  38. package/dist/runtime/server/utils/import.d.ts +31 -0
  39. package/dist/runtime/server/utils/import.js +202 -0
  40. package/dist/runtime/server/utils/list-query.d.ts +15 -0
  41. package/dist/runtime/server/utils/list-query.js +54 -0
  42. package/dist/runtime/server/utils/media-check.d.ts +2 -0
  43. package/dist/runtime/server/utils/media-check.js +1 -1
  44. package/dist/runtime/server/utils/media-references.js +4 -1
  45. package/dist/runtime/server/utils/registry.js +1 -1
  46. package/dist/runtime/server/utils/search.js +23 -4
  47. package/dist/runtime/shared/duplicate.d.ts +10 -0
  48. package/dist/runtime/shared/duplicate.js +54 -0
  49. package/dist/runtime/shared/index.d.ts +49 -2
  50. package/dist/runtime/shared/index.js +33 -0
  51. package/dist/runtime/shared/layout.d.ts +3 -0
  52. package/dist/runtime/shared/layout.js +35 -2
  53. package/dist/runtime/shared/list-filters.d.ts +20 -0
  54. package/dist/runtime/shared/list-filters.js +83 -0
  55. package/dist/runtime/shared/transfer.d.ts +70 -0
  56. package/dist/runtime/shared/transfer.js +256 -0
  57. package/dist/runtime/shared/validation.d.ts +15 -2
  58. package/dist/runtime/shared/validation.js +102 -9
  59. package/package.json +1 -1
@@ -0,0 +1,54 @@
1
+ import { CONTENT_PUBLISHED_AT_FIELD, isCollectionKind, isTranslatableField } from "./index.js";
2
+ export const DUPLICATE_TITLE_SUFFIX = " (copy)";
3
+ const DUPLICATE_TOKEN_LENGTH = 4;
4
+ const DUPLICATE_TOKEN_ALPHABET = "abcdefghijklmnopqrstuvwxyz0123456789";
5
+ const COPIED_SLUG = new RegExp(`-copy-[a-z0-9]{${DUPLICATE_TOKEN_LENGTH}}$`);
6
+ export function isUniqueField(field) {
7
+ return field.type === "slug" || field.type === "relation" && field.cardinality === "one-to-one";
8
+ }
9
+ export function uniqueFieldKeys(entry) {
10
+ return Object.keys(entry.fields).filter((key) => isUniqueField(entry.fields[key]));
11
+ }
12
+ export function duplicateToken() {
13
+ const bytes = new Uint8Array(DUPLICATE_TOKEN_LENGTH);
14
+ globalThis.crypto.getRandomValues(bytes);
15
+ return [...bytes].map((byte) => DUPLICATE_TOKEN_ALPHABET[byte % DUPLICATE_TOKEN_ALPHABET.length]).join("");
16
+ }
17
+ export function duplicateSlug(value, token) {
18
+ return `${value.replace(COPIED_SLUG, "")}-copy-${token}`;
19
+ }
20
+ function suffixTitle(field, value) {
21
+ if (typeof value === "string") return value.trim() ? `${value}${DUPLICATE_TITLE_SUFFIX}` : value;
22
+ if (!value || typeof value !== "object" || !isTranslatableField(field)) return value;
23
+ return Object.fromEntries(
24
+ Object.entries(value).map(([locale, text]) => [
25
+ locale,
26
+ typeof text === "string" && text.trim() ? `${text}${DUPLICATE_TITLE_SUFFIX}` : text
27
+ ])
28
+ );
29
+ }
30
+ export function duplicateFormKeys(entry) {
31
+ const keys = Object.keys(entry.fields);
32
+ return isCollectionKind(entry) && entry.drafts && !keys.includes("status") ? [...keys, "status"] : keys;
33
+ }
34
+ export function duplicateEntryValues(entry, source, token = duplicateToken()) {
35
+ const picked = Object.fromEntries(
36
+ duplicateFormKeys(entry).map((key) => [key, source[key] ?? null])
37
+ );
38
+ const copy = JSON.parse(JSON.stringify(picked));
39
+ for (const [key, field] of Object.entries(entry.fields)) {
40
+ if (field.type === "slug") {
41
+ const slug = copy[key];
42
+ copy[key] = typeof slug === "string" && slug ? duplicateSlug(slug, token) : null;
43
+ } else if (isUniqueField(field)) {
44
+ copy[key] = null;
45
+ }
46
+ }
47
+ const titleField = entry.titleField ? entry.fields[entry.titleField] : void 0;
48
+ if (entry.titleField && titleField?.type === "text")
49
+ copy[entry.titleField] = suffixTitle(titleField, copy[entry.titleField]);
50
+ if (entry.kind === "content" && Object.hasOwn(copy, CONTENT_PUBLISHED_AT_FIELD))
51
+ copy[CONTENT_PUBLISHED_AT_FIELD] = null;
52
+ if (isCollectionKind(entry) && entry.drafts) copy.status = "draft";
53
+ return copy;
54
+ }
@@ -80,7 +80,44 @@ export interface BlockConfig {
80
80
  icon?: string;
81
81
  description?: string;
82
82
  fields: Record<string, FieldConfig>;
83
+ validate?: CmsBlockValidate;
83
84
  }
85
+ export type CmsValidationLevel = 'error' | 'warning';
86
+ export interface CmsValidationIssue {
87
+ field?: string;
88
+ message: string;
89
+ level?: CmsValidationLevel;
90
+ }
91
+ export interface CmsValidateContext {
92
+ locales: string[];
93
+ defaultLocale: string;
94
+ path?: string;
95
+ }
96
+ export interface CmsBlockValidateContext extends CmsValidateContext {
97
+ index: number;
98
+ }
99
+ export type CmsValidateResult = readonly CmsValidationIssue[] | null | undefined | void;
100
+ export type CmsValidate = (values: Record<string, unknown>, context: CmsValidateContext) => CmsValidateResult;
101
+ export type CmsBlockValidate = (values: Record<string, unknown>, context: CmsBlockValidateContext) => CmsValidateResult;
102
+ export interface SelectOption {
103
+ value: string;
104
+ label: string;
105
+ }
106
+ export type SelectOptionInput = string | {
107
+ readonly value: string;
108
+ readonly label: string;
109
+ };
110
+ export type CmsSelectOptionValue<O> = O extends string ? O : O extends {
111
+ readonly value: infer V extends string;
112
+ } ? V : never;
113
+ export type CmsSelectValue<F extends {
114
+ readonly options: readonly unknown[];
115
+ }> = CmsSelectOptionValue<F['options'][number]>;
116
+ export declare function selectOptions(field: Pick<FieldConfig, 'options'>): SelectOption[];
117
+ export declare function selectOptionValues(field: Pick<FieldConfig, 'options'>): string[];
118
+ export declare function selectOptionLabel(field: Pick<FieldConfig, 'options'>, value: unknown): string;
119
+ export declare function selectValuesMatching(field: Pick<FieldConfig, 'options'>, term: string): string[];
120
+ export declare function selectDisplayValue(field: Pick<FieldConfig, 'options'>, value: unknown): string;
84
121
  export type ConditionValue = string | number | boolean;
85
122
  export interface FieldCondition {
86
123
  field: string;
@@ -102,10 +139,12 @@ export interface FieldConfig {
102
139
  textarea?: boolean;
103
140
  integer?: boolean;
104
141
  translatable?: boolean;
105
- options?: string[];
142
+ options?: readonly SelectOptionInput[];
106
143
  multiple?: boolean;
107
144
  from?: string;
108
145
  blocks?: Record<string, BlockConfig>;
146
+ min?: number;
147
+ max?: number;
109
148
  mediaType?: MediaType | MediaType[];
110
149
  accept?: string[];
111
150
  mobile?: boolean;
@@ -121,6 +160,7 @@ export declare function fieldConditions(field: FieldConfig): FieldCondition[];
121
160
  export declare function isFieldVisible(field: FieldConfig, values: Record<string, unknown> | null | undefined): boolean;
122
161
  export declare function isTranslatableField(field: FieldConfig): boolean;
123
162
  export declare function isTranslatableMediaField(field: FieldConfig): boolean;
163
+ export declare function defaultLocaleValue(value: unknown, defaultLocale: string): string | null;
124
164
  export declare function decodeTranslatableValue(value: unknown, defaultLocale: string): Record<string, string> | null;
125
165
  export declare const decodeTranslatableMedia: typeof decodeTranslatableValue;
126
166
  export declare function encodeTranslatableMedia(value: unknown): string | null;
@@ -132,6 +172,7 @@ export declare function isMultiSelect(field: FieldConfig): boolean;
132
172
  export declare function translatableFieldKeys(entry: EntryLike): string[];
133
173
  export declare function translatableBlockFieldKeys(block: BlockConfig): string[];
134
174
  export declare function hasTranslatableBlockFields(field: FieldConfig): boolean;
175
+ export declare function canAddBlock(field: Pick<FieldConfig, 'max'>, count: number): boolean;
135
176
  export declare const BLOCK_HIDDEN_KEY = "hidden";
136
177
  export declare function declaresHiddenField(block: BlockConfig | undefined): boolean;
137
178
  export declare function isHiddenBlock(item: unknown): boolean;
@@ -168,6 +209,7 @@ export interface CmsEntry {
168
209
  blocks?: Record<string, BlockConfig>;
169
210
  preview?: CmsPreviewOptions;
170
211
  declared?: CmsContentDeclared;
212
+ validate?: CmsValidate;
171
213
  }
172
214
  export interface CmsPreviewOptions {
173
215
  component?: string;
@@ -258,7 +300,7 @@ export interface SlugFieldInput extends FieldInputBase {
258
300
  }
259
301
  export interface SelectFieldInput extends FieldInputBase {
260
302
  type: 'select';
261
- options: string[];
303
+ options: readonly SelectOptionInput[];
262
304
  multiple?: boolean;
263
305
  }
264
306
  export interface JsonFieldInput extends FieldInputBase {
@@ -285,10 +327,13 @@ export interface BlockInput {
285
327
  icon?: string;
286
328
  description?: string;
287
329
  fields: Record<string, BlockFieldInput>;
330
+ validate?: CmsBlockValidate;
288
331
  }
289
332
  export interface BlocksFieldInput extends FieldInputBase {
290
333
  type: 'blocks';
291
334
  blocks: Record<string, BlockInput>;
335
+ min?: number;
336
+ max?: number;
292
337
  }
293
338
  export type CmsFieldInput = TextFieldInput | RichtextFieldInput | NumberFieldInput | BooleanFieldInput | DateFieldInput | DateTimeFieldInput | EmailFieldInput | SlugFieldInput | SelectFieldInput | JsonFieldInput | MediaFieldInput | RelationFieldInput | BlocksFieldInput;
294
339
  interface CmsEntryInputBase {
@@ -300,6 +345,7 @@ interface CmsEntryInputBase {
300
345
  drafts?: boolean;
301
346
  tabs?: CmsTab[];
302
347
  fields: Record<string, CmsFieldInput>;
348
+ validate?: CmsValidate;
303
349
  }
304
350
  export interface CmsCollectionInput extends CmsEntryInputBase {
305
351
  kind: 'collection';
@@ -333,6 +379,7 @@ export interface CmsContentInput {
333
379
  layout?: CmsLayoutBlock[];
334
380
  list?: CmsListInput;
335
381
  preview?: CmsPreviewOptions;
382
+ validate?: CmsValidate;
336
383
  titleField?: never;
337
384
  drafts?: never;
338
385
  }
@@ -123,6 +123,31 @@ export function groupMediaUsage(usage) {
123
123
  export function mediaUsageGroups(usage, keys) {
124
124
  return [...new Set(keys)].map((key) => ({ key, entries: groupMediaUsage(usage[key] ?? []) })).filter((group) => group.entries.length > 0);
125
125
  }
126
+ export function selectOptions(field) {
127
+ return (field.options ?? []).map(
128
+ (option) => typeof option === "string" ? { value: option, label: option } : {
129
+ value: String(option?.value ?? ""),
130
+ label: String(option?.label ?? option?.value ?? "")
131
+ }
132
+ );
133
+ }
134
+ export function selectOptionValues(field) {
135
+ return selectOptions(field).map((option) => option.value);
136
+ }
137
+ export function selectOptionLabel(field, value) {
138
+ const text = String(value);
139
+ return selectOptions(field).find((option) => option.value === text)?.label ?? text;
140
+ }
141
+ export function selectValuesMatching(field, term) {
142
+ const needle = term.trim().toLowerCase();
143
+ if (!needle) return [];
144
+ return selectOptions(field).filter((option) => option.label !== option.value).filter((option) => option.label.toLowerCase().includes(needle)).map((option) => option.value);
145
+ }
146
+ export function selectDisplayValue(field, value) {
147
+ if (value == null) return "";
148
+ if (Array.isArray(value)) return value.map((item) => selectOptionLabel(field, item)).join(", ");
149
+ return selectOptionLabel(field, value);
150
+ }
126
151
  export function isPrivateField(field) {
127
152
  return !!field.private;
128
153
  }
@@ -159,6 +184,11 @@ function parseJsonObject(raw) {
159
184
  }
160
185
  return null;
161
186
  }
187
+ export function defaultLocaleValue(value, defaultLocale) {
188
+ const values = decodeTranslatableValue(value, defaultLocale);
189
+ const text = values?.[defaultLocale];
190
+ return typeof text === "string" ? text : null;
191
+ }
162
192
  export function decodeTranslatableValue(value, defaultLocale) {
163
193
  if (value == null) return null;
164
194
  if (typeof value === "object") return value;
@@ -219,6 +249,9 @@ export function hasTranslatableBlockFields(field) {
219
249
  (block) => Object.values(block.fields).some(isTranslatableField)
220
250
  );
221
251
  }
252
+ export function canAddBlock(field, count) {
253
+ return field.max === void 0 || count < field.max;
254
+ }
222
255
  export const BLOCK_HIDDEN_KEY = "hidden";
223
256
  export function declaresHiddenField(block) {
224
257
  return !!block && Object.hasOwn(block.fields, BLOCK_HIDDEN_KEY);
@@ -19,6 +19,7 @@ export interface CmsFormLayout {
19
19
  }
20
20
  export interface CmsListInput {
21
21
  columns?: string[];
22
+ filters?: string[];
22
23
  }
23
24
  export interface CmsEntrySettings {
24
25
  columns?: string[];
@@ -30,4 +31,6 @@ export declare function resolveFormLayout(fields: Record<string, FieldConfig>, l
30
31
  export declare function defaultListColumns(fields: Record<string, FieldConfig>, list: CmsListInput | undefined, extra?: string[]): string[];
31
32
  export declare function resolveListColumns(fields: Record<string, FieldConfig>, saved: string[] | undefined, list: CmsListInput | undefined, extra?: string[]): string[];
32
33
  export declare function layoutErrors(at: string, fields: Record<string, FieldConfig>, input: CmsLayoutBlock[] | undefined): string[];
34
+ export declare function isListFilterField(field: FieldConfig): boolean;
35
+ export declare function listFilterKeys(fields: Record<string, FieldConfig>, list: CmsListInput | undefined, extra?: string[], columns?: string[]): string[];
33
36
  export declare function listErrors(at: string, fields: Record<string, FieldConfig>, list: CmsListInput | undefined, extra?: string[]): string[];
@@ -121,7 +121,40 @@ export function layoutErrors(at, fields, input) {
121
121
  }
122
122
  return errors;
123
123
  }
124
+ export function isListFilterField(field) {
125
+ if (field.type === "boolean") return true;
126
+ if (field.type === "select") return !field.multiple;
127
+ if (field.type === "relation") return field.cardinality !== "many-to-many";
128
+ return false;
129
+ }
130
+ export function listFilterKeys(fields, list, extra = [], columns) {
131
+ const eligible = [
132
+ ...Object.keys(fields).filter((key) => isListFilterField(fields[key])),
133
+ ...extra.filter((key) => !Object.hasOwn(fields, key))
134
+ ];
135
+ if (Array.isArray(list?.filters))
136
+ return [...new Set(list.filters.filter((key) => eligible.includes(key)))];
137
+ return columns ? eligible.filter((key) => columns.includes(key)) : eligible;
138
+ }
124
139
  export function listErrors(at, fields, list, extra = []) {
125
- if (!list?.columns) return [];
126
- return list.columns.filter((key) => !Object.hasOwn(fields, key) && !extra.includes(key)).map((key) => `${at}: list.columns names '${key}', which is not a declared field`);
140
+ const errors = [];
141
+ for (const key of list?.columns ?? []) {
142
+ if (!Object.hasOwn(fields, key) && !extra.includes(key))
143
+ errors.push(`${at}: list.columns names '${key}', which is not a declared field`);
144
+ }
145
+ if (list?.filters === void 0) return errors;
146
+ if (!Array.isArray(list.filters)) {
147
+ errors.push(`${at}: list.filters must be an array of field names`);
148
+ return errors;
149
+ }
150
+ for (const key of list.filters) {
151
+ const field = Object.hasOwn(fields, key) ? fields[key] : void 0;
152
+ if (!field && extra.includes(key)) continue;
153
+ if (!field) errors.push(`${at}: list.filters names '${key}', which is not a declared field`);
154
+ else if (!isListFilterField(field))
155
+ errors.push(
156
+ `${at}: list.filters names '${key}', which is not a single select, boolean or single relation field`
157
+ );
158
+ }
159
+ return errors;
127
160
  }
@@ -0,0 +1,20 @@
1
+ import type { CmsEntry, SelectOption } from './index.js';
2
+ export declare const LIST_FILTER_PREFIX = "filter.";
3
+ export declare const LIST_FILTER_STATUS = "status";
4
+ export declare const LIST_FILTER_ID_MAX = 256;
5
+ export type CmsListFilters = Record<string, string>;
6
+ type FilterEntry = Pick<CmsEntry, 'kind' | 'fields' | 'drafts' | 'list'>;
7
+ export declare function listFilterExtras(entry: FilterEntry): string[];
8
+ export declare function entryListFilterKeys(entry: FilterEntry, columns?: string[]): string[];
9
+ export declare function listFilterLabel(entry: FilterEntry, key: string): string;
10
+ export declare function listFilterOptions(entry: FilterEntry, key: string): SelectOption[] | null;
11
+ export declare function listFilterValueLabel(entry: FilterEntry, key: string, value: string): string;
12
+ export declare function readListFilterQuery(query: Record<string, unknown>): Record<string, unknown>;
13
+ export interface ListFilterParseResult {
14
+ filters: CmsListFilters;
15
+ errors: string[];
16
+ }
17
+ export declare function parseListFilters(entry: FilterEntry, query: Record<string, unknown>): ListFilterParseResult;
18
+ export declare function listFilterQuery(filters: CmsListFilters): Record<string, string>;
19
+ export declare function withoutListFilterQuery<T extends Record<string, unknown>>(query: T): Partial<T>;
20
+ export {};
@@ -0,0 +1,83 @@
1
+ import { isCollectionKind, listFilterKeys, selectOptions } from "./index.js";
2
+ export const LIST_FILTER_PREFIX = "filter.";
3
+ export const LIST_FILTER_STATUS = "status";
4
+ export const LIST_FILTER_ID_MAX = 256;
5
+ const STATUS_OPTIONS = [
6
+ { value: "draft", label: "Draft" },
7
+ { value: "published", label: "Published" }
8
+ ];
9
+ const BOOLEAN_OPTIONS = [
10
+ { value: "true", label: "Yes" },
11
+ { value: "false", label: "No" }
12
+ ];
13
+ function isStatusFilter(entry, key) {
14
+ return key === LIST_FILTER_STATUS && !Object.hasOwn(entry.fields, key);
15
+ }
16
+ export function listFilterExtras(entry) {
17
+ return isCollectionKind(entry) && entry.drafts ? [LIST_FILTER_STATUS] : [];
18
+ }
19
+ export function entryListFilterKeys(entry, columns) {
20
+ if (!isCollectionKind(entry)) return [];
21
+ return listFilterKeys(entry.fields, entry.list, listFilterExtras(entry), columns);
22
+ }
23
+ export function listFilterLabel(entry, key) {
24
+ if (isStatusFilter(entry, key)) return "Status";
25
+ return entry.fields[key]?.label ?? key;
26
+ }
27
+ export function listFilterOptions(entry, key) {
28
+ if (isStatusFilter(entry, key)) return STATUS_OPTIONS;
29
+ const field = entry.fields[key];
30
+ if (field?.type === "select") return selectOptions(field);
31
+ if (field?.type === "boolean") return BOOLEAN_OPTIONS;
32
+ return null;
33
+ }
34
+ export function listFilterValueLabel(entry, key, value) {
35
+ return listFilterOptions(entry, key)?.find((option) => option.value === value)?.label ?? value;
36
+ }
37
+ export function readListFilterQuery(query) {
38
+ const raw = {};
39
+ for (const [name, value] of Object.entries(query)) {
40
+ if (!name.startsWith(LIST_FILTER_PREFIX)) continue;
41
+ const key = name.slice(LIST_FILTER_PREFIX.length);
42
+ if (key) raw[key] = value;
43
+ }
44
+ return raw;
45
+ }
46
+ export function parseListFilters(entry, query) {
47
+ const allowed = new Set(entryListFilterKeys(entry));
48
+ const filters = {};
49
+ const errors = [];
50
+ for (const [key, raw] of Object.entries(readListFilterQuery(query))) {
51
+ if (!allowed.has(key)) {
52
+ errors.push(`Unknown filter '${key}'`);
53
+ continue;
54
+ }
55
+ if (typeof raw !== "string") {
56
+ errors.push(`Filter '${key}' takes one value`);
57
+ continue;
58
+ }
59
+ const value = raw.trim();
60
+ if (!value) continue;
61
+ const options = listFilterOptions(entry, key);
62
+ if (options && !options.some((option) => option.value === value)) {
63
+ errors.push(`Invalid value for filter '${key}'`);
64
+ continue;
65
+ }
66
+ if (!options && value.length > LIST_FILTER_ID_MAX) {
67
+ errors.push(`Invalid value for filter '${key}'`);
68
+ continue;
69
+ }
70
+ filters[key] = value;
71
+ }
72
+ return { filters, errors };
73
+ }
74
+ export function listFilterQuery(filters) {
75
+ return Object.fromEntries(
76
+ Object.entries(filters).filter(([, value]) => typeof value === "string" && value !== "").map(([key, value]) => [`${LIST_FILTER_PREFIX}${key}`, value])
77
+ );
78
+ }
79
+ export function withoutListFilterQuery(query) {
80
+ return Object.fromEntries(
81
+ Object.entries(query).filter(([name]) => !name.startsWith(LIST_FILTER_PREFIX))
82
+ );
83
+ }
@@ -0,0 +1,70 @@
1
+ import type { CmsEntry, CmsI18n, FieldConfig } from './index.js';
2
+ export type CmsTransferFormat = 'json' | 'csv';
3
+ export type CmsImportMode = 'create' | 'upsert';
4
+ export declare const TRANSFER_FORMATS: CmsTransferFormat[];
5
+ export declare const IMPORT_MODES: CmsImportMode[];
6
+ export declare const IMPORT_MAX_BYTES: number;
7
+ export declare const IMPORT_MAX_ROWS = 2000;
8
+ export declare const IMPORT_ID_PATTERN: RegExp;
9
+ export declare const TRANSFER_ID_KEY = "id";
10
+ export declare const TRANSFER_TIMESTAMP_KEYS: readonly ["createdAt", "updatedAt"];
11
+ type TransferEntry = Pick<CmsEntry, 'kind' | 'fields' | 'drafts'>;
12
+ export interface CmsImportIssue {
13
+ path: string;
14
+ message: string;
15
+ }
16
+ export interface CmsImportSourceRow {
17
+ index: number;
18
+ values: Record<string, unknown>;
19
+ errors: CmsImportIssue[];
20
+ }
21
+ export interface CmsImportSource {
22
+ rows: CmsImportSourceRow[];
23
+ errors: string[];
24
+ }
25
+ export type CmsImportAction = 'create' | 'update';
26
+ export interface CmsImportPreviewRow {
27
+ index: number;
28
+ id: string | null;
29
+ action: CmsImportAction;
30
+ title: string | null;
31
+ errors: CmsImportIssue[];
32
+ }
33
+ export interface CmsImportPreview {
34
+ format: CmsTransferFormat;
35
+ mode: CmsImportMode;
36
+ total: number;
37
+ valid: number;
38
+ create: number;
39
+ update: number;
40
+ errors: string[];
41
+ rows: CmsImportPreviewRow[];
42
+ imported: boolean;
43
+ }
44
+ export interface CmsExportFile {
45
+ collection: string;
46
+ exportedAt: string;
47
+ total: number;
48
+ items: Record<string, unknown>[];
49
+ }
50
+ export declare function canTransferCsv(entry: Pick<CmsEntry, 'fields'>): boolean;
51
+ export declare function transferKeys(entry: TransferEntry): string[];
52
+ export declare function exportRow(entry: TransferEntry, row: Record<string, unknown>): Record<string, unknown>;
53
+ export declare function exportFileName(name: string, format: CmsTransferFormat, now?: Date): string;
54
+ export declare function csvColumns(entry: TransferEntry, i18n: CmsI18n): string[];
55
+ export declare function encodeCsvCell(value: string): string;
56
+ export declare function encodeCsv(rows: string[][]): string;
57
+ export declare function parseCsv(text: string): string[][];
58
+ export declare function entryRowsToCsv(entry: TransferEntry, rows: Record<string, unknown>[], i18n: CmsI18n): string;
59
+ type CellResult = {
60
+ value: unknown;
61
+ } | {
62
+ error: string;
63
+ };
64
+ export declare function csvCellValue(field: FieldConfig, text: string): CellResult;
65
+ export declare function csvImportSource(entry: TransferEntry, text: string, i18n: CmsI18n): CmsImportSource;
66
+ export declare function jsonImportSource(entry: TransferEntry, text: string): CmsImportSource;
67
+ export declare function importSource(entry: TransferEntry, format: CmsTransferFormat, text: string, i18n: CmsI18n): CmsImportSource;
68
+ export declare function importRowTitle(entry: Pick<CmsEntry, 'fields' | 'titleField'>, values: Record<string, unknown>, defaultLocale: string): string | null;
69
+ export declare function importIssuePath(path: readonly PropertyKey[]): string;
70
+ export {};