@nuasite/collections-admin 0.43.0-beta.3 → 0.43.0-beta.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.
- package/dist/types/app.js +240 -0
- package/dist/types/client.js +134 -0
- package/dist/types/entry-create.d.ts +1 -1
- package/dist/types/entry-create.d.ts.map +1 -1
- package/dist/types/entry-editor.d.ts +1 -1
- package/dist/types/entry-editor.d.ts.map +1 -1
- package/dist/types/field-editor.d.ts +1 -1
- package/dist/types/field-editor.d.ts.map +1 -1
- package/dist/types/field-view.d.ts +17 -0
- package/dist/types/field-view.d.ts.map +1 -0
- package/dist/types/field-view.js +77 -0
- package/dist/types/index.d.ts +1 -2
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +8 -0
- package/dist/types/media-picker.d.ts +1 -1
- package/dist/types/media-picker.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -2
- package/src/app.tsx +1 -1
- package/src/entry-create.tsx +1 -2
- package/src/entry-editor.tsx +9 -3
- package/src/field-editor.tsx +1 -2
- package/src/index.ts +4 -2
- package/src/media-picker.tsx +1 -1
- package/src/tsconfig.json +1 -0
- package/src/client.ts +0 -405
- package/src/form-model.ts +0 -182
package/src/form-model.ts
DELETED
|
@@ -1,182 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Pure draft model + field coercion for the entry editor (cms-headless F3.2).
|
|
3
|
-
*
|
|
4
|
-
* The sidecar speaks two slightly different frontmatter shapes:
|
|
5
|
-
* - `GET …/entries/:slug` returns `frontmatter: Record<string, { value: string; line: number }>`,
|
|
6
|
-
* where `value` is already stringified (objects/arrays are JSON).
|
|
7
|
-
* - `PATCH …` accepts `frontmatter?: Record<string, unknown>` of *native* values (merged), and a
|
|
8
|
-
* `409` `serverFrontmatter` is likewise native (not stringified).
|
|
9
|
-
*
|
|
10
|
-
* The editor works on a single native draft (`EntryDraft`): `frontmatter` is a
|
|
11
|
-
* `Record<string, unknown>` of native JS values keyed by field name, plus the
|
|
12
|
-
* markdown `body`. This module converts to/from the wire and coerces raw input
|
|
13
|
-
* (form strings) into the native value a `FieldType` expects. Keeping it pure
|
|
14
|
-
* (no React/DOM) makes the mapping unit-testable.
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
import type { CollectionEntry, FieldDefinition, FieldType } from '@nuasite/cms-types'
|
|
18
|
-
|
|
19
|
-
/** The editor's in-memory state: native frontmatter values + the markdown body. */
|
|
20
|
-
export interface EntryDraft {
|
|
21
|
-
frontmatter: Record<string, unknown>
|
|
22
|
-
body: string
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
26
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Parse one stringified frontmatter `value` (from `GET …/entries/:slug`) into the
|
|
31
|
-
* native value a field of `type` expects. Structural types (object/array) and
|
|
32
|
-
* unknowns fall back to a best-effort `JSON.parse`; scalars are coerced per type.
|
|
33
|
-
*/
|
|
34
|
-
export function parseWireValue(type: FieldType, raw: string): unknown {
|
|
35
|
-
switch (type) {
|
|
36
|
-
case 'boolean':
|
|
37
|
-
return raw === 'true' || raw === '1' || raw.toLowerCase() === 'yes'
|
|
38
|
-
case 'number':
|
|
39
|
-
case 'year':
|
|
40
|
-
case 'month': {
|
|
41
|
-
const n = Number(raw)
|
|
42
|
-
return raw.trim() === '' || Number.isNaN(n) ? raw : n
|
|
43
|
-
}
|
|
44
|
-
case 'array':
|
|
45
|
-
case 'object':
|
|
46
|
-
return parseJsonLoose(raw)
|
|
47
|
-
default:
|
|
48
|
-
return raw
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/** `JSON.parse` for structural values, falling back to the raw string when invalid. */
|
|
53
|
-
function parseJsonLoose(raw: string): unknown {
|
|
54
|
-
const trimmed = raw.trim()
|
|
55
|
-
if (trimmed === '') return undefined
|
|
56
|
-
if (!(trimmed.startsWith('{') || trimmed.startsWith('[') || trimmed.startsWith('"'))) return raw
|
|
57
|
-
try {
|
|
58
|
-
return JSON.parse(trimmed)
|
|
59
|
-
} catch {
|
|
60
|
-
return raw
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Build a native draft from a loaded entry, driven by the collection's fields.
|
|
66
|
-
* Frontmatter keys present on the entry but absent from the inferred schema are
|
|
67
|
-
* preserved verbatim (as raw strings) so a save never silently drops them.
|
|
68
|
-
*/
|
|
69
|
-
export function draftFromEntry(entry: CollectionEntry, fields: FieldDefinition[]): EntryDraft {
|
|
70
|
-
const byName = new Map(fields.map(f => [f.name, f] as const))
|
|
71
|
-
const frontmatter: Record<string, unknown> = {}
|
|
72
|
-
for (const [key, cell] of Object.entries(entry.frontmatter)) {
|
|
73
|
-
const field = byName.get(key)
|
|
74
|
-
frontmatter[key] = field ? parseWireValue(field.type, cell.value) : cell.value
|
|
75
|
-
}
|
|
76
|
-
return { frontmatter, body: entry.body }
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Build a fresh draft for a create form from the collection's fields, seeding
|
|
81
|
-
* each field with its `defaultValue` (when present) or a type-appropriate blank.
|
|
82
|
-
*/
|
|
83
|
-
export function draftForCreate(fields: FieldDefinition[]): EntryDraft {
|
|
84
|
-
const frontmatter: Record<string, unknown> = {}
|
|
85
|
-
for (const field of fields) {
|
|
86
|
-
if (field.hidden) continue
|
|
87
|
-
if (field.defaultValue !== undefined) {
|
|
88
|
-
frontmatter[field.name] = field.defaultValue
|
|
89
|
-
continue
|
|
90
|
-
}
|
|
91
|
-
frontmatter[field.name] = blankValue(field.type)
|
|
92
|
-
}
|
|
93
|
-
return { frontmatter, body: '' }
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/** A type-appropriate empty value used to seed create forms. */
|
|
97
|
-
export function blankValue(type: FieldType): unknown {
|
|
98
|
-
switch (type) {
|
|
99
|
-
case 'boolean':
|
|
100
|
-
return false
|
|
101
|
-
case 'array':
|
|
102
|
-
return []
|
|
103
|
-
case 'object':
|
|
104
|
-
return {}
|
|
105
|
-
default:
|
|
106
|
-
return ''
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Adopt a server-provided native frontmatter map (from a `409` `serverFrontmatter`)
|
|
112
|
-
* into a draft, re-coercing per field where a definition exists.
|
|
113
|
-
*/
|
|
114
|
-
export function draftFromServerFrontmatter(
|
|
115
|
-
serverFrontmatter: Record<string, unknown>,
|
|
116
|
-
serverBody: string | undefined,
|
|
117
|
-
fields: FieldDefinition[],
|
|
118
|
-
): EntryDraft {
|
|
119
|
-
const byName = new Map(fields.map(f => [f.name, f] as const))
|
|
120
|
-
const frontmatter: Record<string, unknown> = {}
|
|
121
|
-
for (const [key, value] of Object.entries(serverFrontmatter)) {
|
|
122
|
-
const field = byName.get(key)
|
|
123
|
-
// Server values are already native; only re-coerce when the value arrived as
|
|
124
|
-
// a string for a numeric/boolean field (e.g. YAML quirks).
|
|
125
|
-
frontmatter[key] = field && typeof value === 'string' ? parseWireValue(field.type, value) : value
|
|
126
|
-
}
|
|
127
|
-
return { frontmatter, body: serverBody ?? '' }
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* Coerce a raw form-control string into the native value a field expects. Used by
|
|
132
|
-
* the widgets, whose `<input>` values are always strings.
|
|
133
|
-
*/
|
|
134
|
-
export function coerceInput(type: FieldType, raw: string): unknown {
|
|
135
|
-
switch (type) {
|
|
136
|
-
case 'boolean':
|
|
137
|
-
return raw === 'true'
|
|
138
|
-
case 'number': {
|
|
139
|
-
if (raw.trim() === '') return undefined
|
|
140
|
-
const n = Number(raw)
|
|
141
|
-
return Number.isNaN(n) ? raw : n
|
|
142
|
-
}
|
|
143
|
-
case 'year':
|
|
144
|
-
case 'month': {
|
|
145
|
-
if (raw.trim() === '') return undefined
|
|
146
|
-
const n = Number(raw)
|
|
147
|
-
return Number.isNaN(n) ? raw : n
|
|
148
|
-
}
|
|
149
|
-
default:
|
|
150
|
-
return raw
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/** Render a native value back to a string for a text/number/date/select control. */
|
|
155
|
-
export function valueToInput(value: unknown): string {
|
|
156
|
-
if (value === undefined || value === null) return ''
|
|
157
|
-
if (typeof value === 'string') return value
|
|
158
|
-
if (typeof value === 'number' || typeof value === 'boolean') return String(value)
|
|
159
|
-
return JSON.stringify(value)
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/** Read a value as a boolean for toggle widgets, tolerating string encodings. */
|
|
163
|
-
export function valueToBoolean(value: unknown): boolean {
|
|
164
|
-
if (typeof value === 'boolean') return value
|
|
165
|
-
if (typeof value === 'string') return value === 'true' || value === '1' || value.toLowerCase() === 'yes'
|
|
166
|
-
return Boolean(value)
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/** Read a value as an array of items for repeater widgets. */
|
|
170
|
-
export function valueToArray(value: unknown): unknown[] {
|
|
171
|
-
return Array.isArray(value) ? value : []
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
/** Read a value as an object for nested-group widgets. */
|
|
175
|
-
export function valueToObject(value: unknown): Record<string, unknown> {
|
|
176
|
-
return isRecord(value) ? value : {}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/** Immutably set a top-level frontmatter key in a draft. */
|
|
180
|
-
export function setDraftField(draft: EntryDraft, name: string, value: unknown): EntryDraft {
|
|
181
|
-
return { ...draft, frontmatter: { ...draft.frontmatter, [name]: value } }
|
|
182
|
-
}
|