@byline/admin 4.2.0 → 4.4.0
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/fields/array/array-field.d.ts +12 -9
- package/dist/fields/array/array-field.js +46 -34
- package/dist/fields/blocks/blocks-field.js +1 -0
- package/dist/fields/draggable-context-menu.js +2 -1
- package/dist/fields/field-admin.d.ts +15 -0
- package/dist/fields/field-admin.js +11 -0
- package/dist/fields/field-admin.test.node.d.ts +8 -0
- package/dist/fields/field-renderer.d.ts +11 -4
- package/dist/fields/field-renderer.js +8 -9
- package/dist/fields/file/file-field.d.ts +1 -3
- package/dist/fields/file/file-field.js +2 -2
- package/dist/fields/group/group-field.d.ts +16 -9
- package/dist/fields/group/group-field.js +5 -4
- package/dist/fields/image/image-field.d.ts +1 -3
- package/dist/fields/image/image-field.js +2 -2
- package/dist/fields/sortable-item.d.ts +6 -0
- package/dist/fields/sortable-item.js +44 -25
- package/dist/forms/form-context.d.ts +20 -1
- package/dist/forms/form-context.js +3 -2
- package/dist/forms/form-renderer.js +4 -2
- package/dist/forms/upload-executor.js +50 -29
- package/package.json +10 -10
- package/src/fields/array/array-field.tsx +74 -43
- package/src/fields/blocks/blocks-field.tsx +5 -0
- package/src/fields/draggable-context-menu.tsx +9 -1
- package/src/fields/field-admin.test.node.ts +49 -0
- package/src/fields/field-admin.ts +39 -0
- package/src/fields/field-renderer.tsx +14 -7
- package/src/fields/file/file-field.tsx +4 -4
- package/src/fields/group/group-field.tsx +19 -11
- package/src/fields/image/image-field.tsx +4 -4
- package/src/fields/sortable-item.tsx +115 -34
- package/src/forms/form-context.tsx +30 -1
- package/src/forms/form-renderer.tsx +3 -1
- package/src/forms/upload-executor.test.node.ts +187 -0
- package/src/forms/upload-executor.ts +90 -29
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import type { Field, StoredFileValue, UploadConfig } from '@byline/core'
|
|
18
|
+
import { type PathSegment, parseInstancePath } from '@byline/core'
|
|
18
19
|
|
|
19
20
|
import { get as getNestedValue } from './nested-path'
|
|
20
21
|
import type { UploadFieldFn } from '../fields/field-services-types'
|
|
@@ -149,13 +150,16 @@ function buildUploadFormData(
|
|
|
149
150
|
formData.append('documentId', executionContext.documentId)
|
|
150
151
|
}
|
|
151
152
|
|
|
153
|
+
// Resolved once per upload, before locating the field: a `blocks` hop in
|
|
154
|
+
// the path can only be resolved by reading the addressed item's `_type`.
|
|
155
|
+
const formValues = executionContext?.getFormValues?.()
|
|
156
|
+
|
|
152
157
|
const contextPaths =
|
|
153
158
|
executionContext?.fields != null
|
|
154
|
-
? findUploadFieldByPath(executionContext.fields, fieldPath)?.context
|
|
159
|
+
? findUploadFieldByPath(executionContext.fields, fieldPath, formValues)?.context
|
|
155
160
|
: undefined
|
|
156
161
|
|
|
157
|
-
if (contextPaths && contextPaths.length > 0 &&
|
|
158
|
-
const formValues = executionContext.getFormValues()
|
|
162
|
+
if (contextPaths && contextPaths.length > 0 && formValues) {
|
|
159
163
|
for (const contextPath of contextPaths) {
|
|
160
164
|
const resolvedPath = resolveContextPath(fieldPath, contextPath)
|
|
161
165
|
if (resolvedPath === undefined) continue
|
|
@@ -263,43 +267,100 @@ function isRelationEnvelope(value: unknown): value is { targetDocumentId: string
|
|
|
263
267
|
|
|
264
268
|
/**
|
|
265
269
|
* Locate the upload-capable `image | file` schema field addressed by a form
|
|
266
|
-
* field path, walking `group` / `array` / `blocks` structures.
|
|
267
|
-
*
|
|
268
|
-
*
|
|
269
|
-
*
|
|
270
|
-
*
|
|
271
|
-
*
|
|
270
|
+
* field path, walking `group` / `array` / `blocks` structures.
|
|
271
|
+
*
|
|
272
|
+
* The path is an *instance* path (`content[1].gallery[0].poster`), so it
|
|
273
|
+
* carries no block type — a block item's type lives on the item itself, as
|
|
274
|
+
* `_type`. Passing `formValues` therefore lets a `blocks` hop be resolved
|
|
275
|
+
* exactly: read the addressed item, take its `_type`, descend into that
|
|
276
|
+
* block. Without form values the block type is genuinely unknowable from the
|
|
277
|
+
* path, so every block is tried and the result is accepted only when exactly
|
|
278
|
+
* one resolves — ambiguity returns `undefined` rather than a guess.
|
|
272
279
|
*/
|
|
273
280
|
function findUploadFieldByPath(
|
|
274
281
|
fields: readonly Field[],
|
|
275
|
-
fieldPath: string
|
|
282
|
+
fieldPath: string,
|
|
283
|
+
formValues?: Record<string, any>
|
|
276
284
|
): UploadConfig | undefined {
|
|
277
|
-
const
|
|
285
|
+
const parsed = parseInstancePath(fieldPath)
|
|
286
|
+
if (!parsed.ok) return undefined
|
|
287
|
+
return resolveUploadConfig(fields, parsed.segments, formValues)
|
|
288
|
+
}
|
|
278
289
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
290
|
+
/** Select the item an index / id segment addresses, when data is available. */
|
|
291
|
+
function selectItem(value: unknown, segment: PathSegment): unknown {
|
|
292
|
+
if (!Array.isArray(value)) return undefined
|
|
293
|
+
if (segment.kind === 'index') return value[segment.index]
|
|
294
|
+
if (segment.kind === 'id') {
|
|
295
|
+
return value.find((item) => (item as { _id?: unknown } | null)?._id === segment.id)
|
|
296
|
+
}
|
|
297
|
+
return undefined
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Walk instance-path segments against the schema, carrying the corresponding
|
|
302
|
+
* slice of form data alongside so `blocks` hops can read `_type`.
|
|
303
|
+
*/
|
|
304
|
+
function resolveUploadConfig(
|
|
305
|
+
fields: readonly Field[],
|
|
306
|
+
segments: readonly PathSegment[],
|
|
307
|
+
value: unknown
|
|
308
|
+
): UploadConfig | undefined {
|
|
309
|
+
const head = segments[0]
|
|
310
|
+
if (head == null || head.kind !== 'field') return undefined
|
|
285
311
|
|
|
286
|
-
|
|
287
|
-
|
|
312
|
+
const field = fields.find((candidate) => candidate.name === head.name)
|
|
313
|
+
if (field == null) return undefined
|
|
314
|
+
|
|
315
|
+
const fieldValue = (value as Record<string, unknown> | undefined)?.[head.name]
|
|
316
|
+
const rest = segments.slice(1)
|
|
317
|
+
|
|
318
|
+
if (rest.length === 0) {
|
|
319
|
+
return field.type === 'image' || field.type === 'file' ? field.upload : undefined
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (field.type === 'group') {
|
|
323
|
+
return resolveUploadConfig(field.fields, rest, fieldValue)
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (field.type === 'array') {
|
|
327
|
+
// An item selector may be absent when the caller addresses the array's
|
|
328
|
+
// child declaration rather than one item; the child fields are the same.
|
|
329
|
+
const selector = rest[0]
|
|
330
|
+
if (selector?.kind === 'index' || selector?.kind === 'id') {
|
|
331
|
+
return resolveUploadConfig(field.fields, rest.slice(1), selectItem(fieldValue, selector))
|
|
288
332
|
}
|
|
333
|
+
return resolveUploadConfig(field.fields, rest, undefined)
|
|
334
|
+
}
|
|
289
335
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
336
|
+
if (field.type === 'blocks') {
|
|
337
|
+
const selector = rest[0]
|
|
338
|
+
const remainder = selector?.kind === 'index' || selector?.kind === 'id' ? rest.slice(1) : rest
|
|
339
|
+
const item =
|
|
340
|
+
selector?.kind === 'index' || selector?.kind === 'id'
|
|
341
|
+
? selectItem(fieldValue, selector)
|
|
342
|
+
: undefined
|
|
343
|
+
|
|
344
|
+
const blockType = (item as { _type?: unknown } | undefined)?._type
|
|
345
|
+
if (typeof blockType === 'string') {
|
|
346
|
+
const block = field.blocks.find((candidate) => candidate.blockType === blockType)
|
|
347
|
+
return block == null ? undefined : resolveUploadConfig(block.fields, remainder, item)
|
|
293
348
|
}
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
349
|
+
|
|
350
|
+
// No data to disambiguate. Try every block and accept a unique answer;
|
|
351
|
+
// two matches mean the path genuinely does not identify one declaration.
|
|
352
|
+
let found: UploadConfig | undefined
|
|
353
|
+
let matches = 0
|
|
354
|
+
for (const block of field.blocks) {
|
|
355
|
+
const result = resolveUploadConfig(block.fields, remainder, undefined)
|
|
356
|
+
if (result !== undefined) {
|
|
357
|
+
matches += 1
|
|
358
|
+
found = result
|
|
359
|
+
}
|
|
300
360
|
}
|
|
301
|
-
return undefined
|
|
361
|
+
return matches === 1 ? found : undefined
|
|
302
362
|
}
|
|
363
|
+
|
|
303
364
|
return undefined
|
|
304
365
|
}
|
|
305
366
|
|