@byline/ui 2.5.2 → 2.6.1
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/components/shimmer/shimmer.d.ts +13 -1
- package/dist/components/shimmer/shimmer.js +29 -20
- package/dist/components/shimmer/shimmer_module.css +4 -4
- package/dist/dnd/draggable-sortable/demo/draggable-list-demo.js +1 -1
- package/dist/react.d.ts +18 -54
- package/dist/react.js +0 -35
- package/dist/styles/styles.css +3 -0
- package/dist/uikit.d.ts +1 -0
- package/dist/uikit.js +1 -0
- package/package.json +2 -8
- package/src/components/shimmer/shimmer.module.css +8 -4
- package/src/components/shimmer/shimmer.tsx +34 -9
- package/src/dnd/draggable-sortable/demo/draggable-list-demo.tsx +1 -1
- package/src/react.ts +20 -68
- package/src/styles/functional/surfaces.css +13 -1
- package/src/uikit.ts +1 -0
- package/dist/admin/group.d.ts +0 -27
- package/dist/admin/group.js +0 -14
- package/dist/admin/group.module.js +0 -6
- package/dist/admin/group_module.css +0 -19
- package/dist/admin/row.d.ts +0 -25
- package/dist/admin/row.js +0 -8
- package/dist/admin/row.module.js +0 -5
- package/dist/admin/row_module.css +0 -18
- package/dist/admin/tabs.d.ts +0 -25
- package/dist/admin/tabs.js +0 -35
- package/dist/admin/tabs.module.js +0 -10
- package/dist/admin/tabs_module.css +0 -68
- package/dist/fields/array/array-field.d.ts +0 -14
- package/dist/fields/array/array-field.js +0 -176
- package/dist/fields/array/array-field.module.js +0 -11
- package/dist/fields/array/array-field_module.css +0 -32
- package/dist/fields/blocks/blocks-field.d.ts +0 -13
- package/dist/fields/blocks/blocks-field.js +0 -244
- package/dist/fields/blocks/blocks-field.module.js +0 -26
- package/dist/fields/blocks/blocks-field_module.css +0 -107
- package/dist/fields/checkbox/checkbox-field.d.ts +0 -16
- package/dist/fields/checkbox/checkbox-field.js +0 -28
- package/dist/fields/checkbox/checkbox-field.module.js +0 -6
- package/dist/fields/checkbox/checkbox-field_module.css +0 -4
- package/dist/fields/column-formatter.d.ts +0 -20
- package/dist/fields/column-formatter.js +0 -15
- package/dist/fields/date-time-formatter.d.ts +0 -16
- package/dist/fields/date-time-formatter.js +0 -8
- package/dist/fields/datetime/datetime-field.d.ts +0 -16
- package/dist/fields/datetime/datetime-field.js +0 -37
- package/dist/fields/datetime/datetime-field.module.js +0 -5
- package/dist/fields/datetime/datetime-field_module.css +0 -4
- package/dist/fields/draggable-context-menu.d.ts +0 -6
- package/dist/fields/draggable-context-menu.js +0 -83
- package/dist/fields/draggable-context-menu.module.js +0 -15
- package/dist/fields/draggable-context-menu_module.css +0 -91
- package/dist/fields/field-helpers.d.ts +0 -26
- package/dist/fields/field-helpers.js +0 -50
- package/dist/fields/field-renderer.d.ts +0 -37
- package/dist/fields/field-renderer.js +0 -206
- package/dist/fields/field-renderer.module.js +0 -8
- package/dist/fields/field-renderer_module.css +0 -11
- package/dist/fields/file/file-field.d.ts +0 -19
- package/dist/fields/file/file-field.js +0 -226
- package/dist/fields/file/file-field.module.js +0 -18
- package/dist/fields/file/file-field_module.css +0 -131
- package/dist/fields/file/file-upload-field.d.ts +0 -21
- package/dist/fields/file/file-upload-field.js +0 -128
- package/dist/fields/file/file-upload-field.module.js +0 -15
- package/dist/fields/file/file-upload-field_module.css +0 -74
- package/dist/fields/group/group-field.d.ts +0 -15
- package/dist/fields/group/group-field.js +0 -59
- package/dist/fields/group/group-field.module.js +0 -9
- package/dist/fields/group/group-field_module.css +0 -27
- package/dist/fields/image/image-field.d.ts +0 -19
- package/dist/fields/image/image-field.js +0 -242
- package/dist/fields/image/image-field.module.js +0 -22
- package/dist/fields/image/image-field_module.css +0 -121
- package/dist/fields/image/image-upload-field.d.ts +0 -21
- package/dist/fields/image/image-upload-field.js +0 -187
- package/dist/fields/image/image-upload-field.module.js +0 -19
- package/dist/fields/image/image-upload-field_module.css +0 -92
- package/dist/fields/local-date-time.d.ts +0 -27
- package/dist/fields/local-date-time.js +0 -49
- package/dist/fields/locale-badge.d.ts +0 -18
- package/dist/fields/locale-badge.js +0 -10
- package/dist/fields/locale-badge.module.js +0 -5
- package/dist/fields/locale-badge_module.css +0 -27
- package/dist/fields/numerical/numerical-field.d.ts +0 -18
- package/dist/fields/numerical/numerical-field.js +0 -74
- package/dist/fields/relation/relation-display.d.ts +0 -40
- package/dist/fields/relation/relation-display.js +0 -58
- package/dist/fields/relation/relation-display.module.js +0 -9
- package/dist/fields/relation/relation-display_module.css +0 -21
- package/dist/fields/relation/relation-field.d.ts +0 -18
- package/dist/fields/relation/relation-field.js +0 -146
- package/dist/fields/relation/relation-field.module.js +0 -13
- package/dist/fields/relation/relation-field_module.css +0 -62
- package/dist/fields/relation/relation-picker.d.ts +0 -49
- package/dist/fields/relation/relation-picker.js +0 -233
- package/dist/fields/relation/relation-picker.module.js +0 -26
- package/dist/fields/relation/relation-picker_module.css +0 -124
- package/dist/fields/relation/relation-summary.d.ts +0 -31
- package/dist/fields/relation/relation-summary.js +0 -50
- package/dist/fields/relation/relation-summary.module.js +0 -11
- package/dist/fields/relation/relation-summary_module.css +0 -37
- package/dist/fields/select/select-field.d.ts +0 -16
- package/dist/fields/select/select-field.js +0 -50
- package/dist/fields/select/select-field.module.js +0 -5
- package/dist/fields/select/select-field_module.css +0 -4
- package/dist/fields/sortable-item.d.ts +0 -15
- package/dist/fields/sortable-item.js +0 -80
- package/dist/fields/sortable-item.module.js +0 -22
- package/dist/fields/sortable-item_module.css +0 -124
- package/dist/fields/text/text-field.d.ts +0 -20
- package/dist/fields/text/text-field.js +0 -104
- package/dist/fields/text/text-field.module.js +0 -6
- package/dist/fields/text/text-field_module.css +0 -5
- package/dist/fields/text-area/text-area-field.d.ts +0 -20
- package/dist/fields/text-area/text-area-field.js +0 -105
- package/dist/fields/text-area/text-area-field.module.js +0 -6
- package/dist/fields/text-area/text-area-field_module.css +0 -5
- package/dist/fields/use-field-change-handler.d.ts +0 -23
- package/dist/fields/use-field-change-handler.js +0 -52
- package/dist/forms/document-actions.d.ts +0 -48
- package/dist/forms/document-actions.js +0 -469
- package/dist/forms/document-actions.module.js +0 -34
- package/dist/forms/document-actions_module.css +0 -118
- package/dist/forms/form-context.d.ts +0 -89
- package/dist/forms/form-context.js +0 -466
- package/dist/forms/form-renderer.d.ts +0 -98
- package/dist/forms/form-renderer.js +0 -591
- package/dist/forms/form-renderer.module.js +0 -46
- package/dist/forms/form-renderer_module.css +0 -245
- package/dist/forms/navigation-guard.d.ts +0 -54
- package/dist/forms/navigation-guard.js +0 -22
- package/dist/forms/path-widget.d.ts +0 -36
- package/dist/forms/path-widget.js +0 -107
- package/dist/forms/path-widget.module.js +0 -8
- package/dist/forms/path-widget_module.css +0 -29
- package/dist/forms/upload-executor.d.ts +0 -57
- package/dist/forms/upload-executor.js +0 -92
- package/dist/services/field-services-context.d.ts +0 -16
- package/dist/services/field-services-context.js +0 -13
- package/dist/services/field-services-types.d.ts +0 -63
- package/dist/services/field-services-types.js +0 -1
- package/dist/widgets/diff-viewer/diff-modal.d.ts +0 -22
- package/dist/widgets/diff-viewer/diff-modal.js +0 -146
- package/dist/widgets/diff-viewer/diff-modal.module.js +0 -14
- package/dist/widgets/diff-viewer/diff-modal_module.css +0 -56
- package/dist/widgets/status-badge/status-badge.d.ts +0 -25
- package/dist/widgets/status-badge/status-badge.js +0 -35
- package/dist/widgets/status-badge/status-badge.module.js +0 -7
- package/dist/widgets/status-badge/status-badge_module.css +0 -20
- package/src/admin/group.module.css +0 -41
- package/src/admin/group.tsx +0 -40
- package/src/admin/row.module.css +0 -32
- package/src/admin/row.tsx +0 -33
- package/src/admin/tabs.module.css +0 -107
- package/src/admin/tabs.tsx +0 -82
- package/src/fields/array/array-field.module.css +0 -48
- package/src/fields/array/array-field.tsx +0 -266
- package/src/fields/blocks/blocks-field.module.css +0 -148
- package/src/fields/blocks/blocks-field.tsx +0 -312
- package/src/fields/checkbox/checkbox-field.module.css +0 -4
- package/src/fields/checkbox/checkbox-field.tsx +0 -54
- package/src/fields/column-formatter.tsx +0 -31
- package/src/fields/date-time-formatter.tsx +0 -22
- package/src/fields/datetime/datetime-field.module.css +0 -13
- package/src/fields/datetime/datetime-field.tsx +0 -54
- package/src/fields/draggable-context-menu.module.css +0 -127
- package/src/fields/draggable-context-menu.tsx +0 -85
- package/src/fields/field-helpers.ts +0 -69
- package/src/fields/field-renderer.module.css +0 -22
- package/src/fields/field-renderer.tsx +0 -288
- package/src/fields/file/file-field.module.css +0 -153
- package/src/fields/file/file-field.tsx +0 -271
- package/src/fields/file/file-upload-field.module.css +0 -101
- package/src/fields/file/file-upload-field.tsx +0 -183
- package/src/fields/group/group-field.module.css +0 -43
- package/src/fields/group/group-field.tsx +0 -84
- package/src/fields/image/image-field.module.css +0 -155
- package/src/fields/image/image-field.tsx +0 -291
- package/src/fields/image/image-upload-field.module.css +0 -123
- package/src/fields/image/image-upload-field.tsx +0 -270
- package/src/fields/local-date-time.tsx +0 -88
- package/src/fields/locale-badge.module.css +0 -37
- package/src/fields/locale-badge.tsx +0 -32
- package/src/fields/numerical/numerical-field.tsx +0 -114
- package/src/fields/relation/relation-display.module.css +0 -36
- package/src/fields/relation/relation-display.tsx +0 -130
- package/src/fields/relation/relation-field.module.css +0 -83
- package/src/fields/relation/relation-field.tsx +0 -206
- package/src/fields/relation/relation-picker.module.css +0 -168
- package/src/fields/relation/relation-picker.tsx +0 -325
- package/src/fields/relation/relation-summary.module.css +0 -55
- package/src/fields/relation/relation-summary.tsx +0 -123
- package/src/fields/select/select-field.module.css +0 -13
- package/src/fields/select/select-field.tsx +0 -61
- package/src/fields/sortable-item.module.css +0 -167
- package/src/fields/sortable-item.tsx +0 -101
- package/src/fields/text/text-field.module.css +0 -13
- package/src/fields/text/text-field.tsx +0 -146
- package/src/fields/text-area/text-area-field.module.css +0 -13
- package/src/fields/text-area/text-area-field.tsx +0 -147
- package/src/fields/use-field-change-handler.ts +0 -112
- package/src/forms/document-actions.module.css +0 -160
- package/src/forms/document-actions.tsx +0 -487
- package/src/forms/form-context.tsx +0 -704
- package/src/forms/form-renderer.module.css +0 -321
- package/src/forms/form-renderer.tsx +0 -888
- package/src/forms/navigation-guard.tsx +0 -98
- package/src/forms/path-widget.module.css +0 -41
- package/src/forms/path-widget.test.tsx +0 -217
- package/src/forms/path-widget.tsx +0 -181
- package/src/forms/upload-executor.ts +0 -190
- package/src/services/field-services-context.tsx +0 -35
- package/src/services/field-services-types.ts +0 -68
- package/src/widgets/diff-viewer/diff-modal.module.css +0 -79
- package/src/widgets/diff-viewer/diff-modal.tsx +0 -184
- package/src/widgets/status-badge/status-badge.module.css +0 -31
- package/src/widgets/status-badge/status-badge.tsx +0 -69
|
@@ -1,190 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* This Source Code is subject to the terms of the Mozilla Public
|
|
3
|
-
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
|
-
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
5
|
-
*
|
|
6
|
-
* Copyright (c) Infonomic Company Limited
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Upload Executor
|
|
11
|
-
*
|
|
12
|
-
* Handles batch execution of pending file uploads at form submission time.
|
|
13
|
-
* This enables "deferred uploads" — files are selected/previewed immediately
|
|
14
|
-
* but only uploaded when the user clicks Save.
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
import type { StoredFileValue } from '@byline/core'
|
|
18
|
-
|
|
19
|
-
import type { UploadFieldFn } from '../services/field-services-types'
|
|
20
|
-
import type { PendingUpload } from './form-context'
|
|
21
|
-
|
|
22
|
-
export interface UploadResult {
|
|
23
|
-
fieldPath: string
|
|
24
|
-
success: boolean
|
|
25
|
-
storedFile?: StoredFileValue
|
|
26
|
-
error?: string
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export interface ExecuteUploadsResult {
|
|
30
|
-
/** All upload results (both successful and failed) */
|
|
31
|
-
results: UploadResult[]
|
|
32
|
-
/** Map of field path to StoredFileValue for successful uploads */
|
|
33
|
-
successful: Map<string, StoredFileValue>
|
|
34
|
-
/** Map of field path to error message for failed uploads */
|
|
35
|
-
errors: Map<string, string>
|
|
36
|
-
/** Whether all uploads succeeded */
|
|
37
|
-
allSucceeded: boolean
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Execute all pending uploads sequentially.
|
|
42
|
-
* Returns a result object with successful uploads and any errors.
|
|
43
|
-
*
|
|
44
|
-
* @param pendingUploads - Map of field path to PendingUpload
|
|
45
|
-
* @param uploadField - Host-provided upload transport (resolved via
|
|
46
|
-
* `useBylineFieldServices()` in the calling React tree)
|
|
47
|
-
* @returns Promise resolving to ExecuteUploadsResult
|
|
48
|
-
*/
|
|
49
|
-
export async function executeUploads(
|
|
50
|
-
pendingUploads: Map<string, PendingUpload>,
|
|
51
|
-
uploadField: UploadFieldFn
|
|
52
|
-
): Promise<ExecuteUploadsResult> {
|
|
53
|
-
const results: UploadResult[] = []
|
|
54
|
-
const successful = new Map<string, StoredFileValue>()
|
|
55
|
-
const errors = new Map<string, string>()
|
|
56
|
-
|
|
57
|
-
for (const [fieldPath, upload] of pendingUploads.entries()) {
|
|
58
|
-
const formData = new FormData()
|
|
59
|
-
formData.append('file', upload.file)
|
|
60
|
-
// Tell the server which upload-capable field this file belongs to.
|
|
61
|
-
// With per-field upload config a collection can have multiple
|
|
62
|
-
// image/file fields, each with its own constraints; the server's
|
|
63
|
-
// unique-default fallback covers the single-field case but rejects
|
|
64
|
-
// multi-field collections without an explicit selector.
|
|
65
|
-
formData.append('field', uploadFieldName(fieldPath))
|
|
66
|
-
|
|
67
|
-
try {
|
|
68
|
-
// Pass createDocument=false — we're uploading for an embedded field,
|
|
69
|
-
// the form's save action handles document creation/update.
|
|
70
|
-
const result = await uploadField(upload.collectionPath, formData, false)
|
|
71
|
-
|
|
72
|
-
results.push({
|
|
73
|
-
fieldPath,
|
|
74
|
-
success: true,
|
|
75
|
-
storedFile: result.storedFile,
|
|
76
|
-
})
|
|
77
|
-
successful.set(fieldPath, result.storedFile)
|
|
78
|
-
} catch (err: unknown) {
|
|
79
|
-
const message = err instanceof Error ? err.message : 'Upload failed'
|
|
80
|
-
results.push({
|
|
81
|
-
fieldPath,
|
|
82
|
-
success: false,
|
|
83
|
-
error: message,
|
|
84
|
-
})
|
|
85
|
-
errors.set(fieldPath, message)
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
return {
|
|
90
|
-
results,
|
|
91
|
-
successful,
|
|
92
|
-
errors,
|
|
93
|
-
allSucceeded: errors.size === 0,
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Extract the leaf field name from a `fieldPath`. Top-level upload
|
|
99
|
-
* fields (`'image'`, `'avatar'`) pass through unchanged; nested paths
|
|
100
|
-
* (`'profile.avatar'`) reduce to their last segment, since the
|
|
101
|
-
* server-side resolver matches against top-level field names today.
|
|
102
|
-
* Nested upload fields would need a richer transport selector when
|
|
103
|
-
* they land — the host resolver is the natural place to extend.
|
|
104
|
-
*/
|
|
105
|
-
function uploadFieldName(fieldPath: string): string {
|
|
106
|
-
const dot = fieldPath.lastIndexOf('.')
|
|
107
|
-
return dot === -1 ? fieldPath : fieldPath.slice(dot + 1)
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Progress callback type for upload execution with progress tracking.
|
|
112
|
-
*/
|
|
113
|
-
export type UploadProgressCallback = (info: {
|
|
114
|
-
current: number
|
|
115
|
-
total: number
|
|
116
|
-
fieldPath: string
|
|
117
|
-
status: 'uploading' | 'done' | 'error'
|
|
118
|
-
}) => void
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Execute uploads with progress callbacks.
|
|
122
|
-
* Useful for showing upload progress in the UI.
|
|
123
|
-
*/
|
|
124
|
-
export async function executeUploadsWithProgress(
|
|
125
|
-
pendingUploads: Map<string, PendingUpload>,
|
|
126
|
-
uploadField: UploadFieldFn,
|
|
127
|
-
onProgress?: UploadProgressCallback
|
|
128
|
-
): Promise<ExecuteUploadsResult> {
|
|
129
|
-
const results: UploadResult[] = []
|
|
130
|
-
const successful = new Map<string, StoredFileValue>()
|
|
131
|
-
const errors = new Map<string, string>()
|
|
132
|
-
|
|
133
|
-
const entries = Array.from(pendingUploads.entries())
|
|
134
|
-
const total = entries.length
|
|
135
|
-
|
|
136
|
-
for (let i = 0; i < entries.length; i++) {
|
|
137
|
-
const [fieldPath, upload] = entries[i]
|
|
138
|
-
|
|
139
|
-
onProgress?.({
|
|
140
|
-
current: i + 1,
|
|
141
|
-
total,
|
|
142
|
-
fieldPath,
|
|
143
|
-
status: 'uploading',
|
|
144
|
-
})
|
|
145
|
-
|
|
146
|
-
const formData = new FormData()
|
|
147
|
-
formData.append('file', upload.file)
|
|
148
|
-
formData.append('field', uploadFieldName(fieldPath))
|
|
149
|
-
|
|
150
|
-
try {
|
|
151
|
-
const result = await uploadField(upload.collectionPath, formData, false)
|
|
152
|
-
|
|
153
|
-
results.push({
|
|
154
|
-
fieldPath,
|
|
155
|
-
success: true,
|
|
156
|
-
storedFile: result.storedFile,
|
|
157
|
-
})
|
|
158
|
-
successful.set(fieldPath, result.storedFile)
|
|
159
|
-
|
|
160
|
-
onProgress?.({
|
|
161
|
-
current: i + 1,
|
|
162
|
-
total,
|
|
163
|
-
fieldPath,
|
|
164
|
-
status: 'done',
|
|
165
|
-
})
|
|
166
|
-
} catch (err: unknown) {
|
|
167
|
-
const message = err instanceof Error ? err.message : 'Upload failed'
|
|
168
|
-
results.push({
|
|
169
|
-
fieldPath,
|
|
170
|
-
success: false,
|
|
171
|
-
error: message,
|
|
172
|
-
})
|
|
173
|
-
errors.set(fieldPath, message)
|
|
174
|
-
|
|
175
|
-
onProgress?.({
|
|
176
|
-
current: i + 1,
|
|
177
|
-
total,
|
|
178
|
-
fieldPath,
|
|
179
|
-
status: 'error',
|
|
180
|
-
})
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
return {
|
|
185
|
-
results,
|
|
186
|
-
successful,
|
|
187
|
-
errors,
|
|
188
|
-
allSucceeded: errors.size === 0,
|
|
189
|
-
}
|
|
190
|
-
}
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* This Source Code is subject to the terms of the Mozilla Public
|
|
3
|
-
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
|
-
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
5
|
-
*
|
|
6
|
-
* Copyright (c) Infonomic Company Limited
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { createContext, type ReactNode, useContext } from 'react'
|
|
10
|
-
|
|
11
|
-
import type { BylineFieldServices } from './field-services-types'
|
|
12
|
-
|
|
13
|
-
const FieldServicesContext = createContext<BylineFieldServices | null>(null)
|
|
14
|
-
|
|
15
|
-
interface BylineFieldServicesProviderProps {
|
|
16
|
-
services: BylineFieldServices
|
|
17
|
-
children: ReactNode
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export const BylineFieldServicesProvider = ({
|
|
21
|
-
services,
|
|
22
|
-
children,
|
|
23
|
-
}: BylineFieldServicesProviderProps) => (
|
|
24
|
-
<FieldServicesContext.Provider value={services}>{children}</FieldServicesContext.Provider>
|
|
25
|
-
)
|
|
26
|
-
|
|
27
|
-
export const useBylineFieldServices = (): BylineFieldServices => {
|
|
28
|
-
const ctx = useContext(FieldServicesContext)
|
|
29
|
-
if (!ctx) {
|
|
30
|
-
throw new Error(
|
|
31
|
-
'@byline/ui: BylineFieldServicesProvider missing. Wrap your admin tree with <BylineFieldServicesProvider services={…} />.'
|
|
32
|
-
)
|
|
33
|
-
}
|
|
34
|
-
return ctx
|
|
35
|
-
}
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* This Source Code is subject to the terms of the Mozilla Public
|
|
3
|
-
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
|
-
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
5
|
-
*
|
|
6
|
-
* Copyright (c) Infonomic Company Limited
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Framework-neutral function contracts that field/form components in
|
|
11
|
-
* `@byline/ui` need from the host application. The host wires concrete
|
|
12
|
-
* implementations via `BylineFieldServicesProvider` — typically thin
|
|
13
|
-
* adapters around TanStack Start server functions, Next.js server
|
|
14
|
-
* actions, or any other RPC-style transport.
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
import type { StoredFileValue } from '@byline/core'
|
|
18
|
-
|
|
19
|
-
export interface CollectionListParams {
|
|
20
|
-
page?: number
|
|
21
|
-
page_size?: number
|
|
22
|
-
order?: string
|
|
23
|
-
desc?: boolean
|
|
24
|
-
query?: string
|
|
25
|
-
locale?: string
|
|
26
|
-
status?: string
|
|
27
|
-
fields?: string[]
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export interface CollectionListDoc {
|
|
31
|
-
id: string
|
|
32
|
-
path?: string
|
|
33
|
-
[field: string]: unknown
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export interface CollectionListResponse {
|
|
37
|
-
docs: CollectionListDoc[]
|
|
38
|
-
meta: { totalPages?: number; [k: string]: unknown }
|
|
39
|
-
included: { collection: { id: string; [k: string]: unknown } }
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export type GetCollectionDocumentsFn = (input: {
|
|
43
|
-
collection: string
|
|
44
|
-
params: CollectionListParams
|
|
45
|
-
}) => Promise<CollectionListResponse>
|
|
46
|
-
|
|
47
|
-
export interface UploadedFileResult {
|
|
48
|
-
documentId?: string
|
|
49
|
-
documentVersionId?: string
|
|
50
|
-
/**
|
|
51
|
-
* The persisted file value, including the `variants` array with
|
|
52
|
-
* `storagePath`, `storageUrl`, `width`, `height`, and `format` for each
|
|
53
|
-
* generated derivative. Single source of truth — the legacy top-level
|
|
54
|
-
* `variants: { name, url }[]` is gone.
|
|
55
|
-
*/
|
|
56
|
-
storedFile: StoredFileValue
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export type UploadFieldFn = (
|
|
60
|
-
collection: string,
|
|
61
|
-
formData: FormData,
|
|
62
|
-
createDocument?: boolean
|
|
63
|
-
) => Promise<UploadedFileResult>
|
|
64
|
-
|
|
65
|
-
export interface BylineFieldServices {
|
|
66
|
-
getCollectionDocuments: GetCollectionDocumentsFn
|
|
67
|
-
uploadField: UploadFieldFn
|
|
68
|
-
}
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* DiffModal — side-by-side document version comparison modal.
|
|
3
|
-
*
|
|
4
|
-
* Override handles:
|
|
5
|
-
* .byline-diff-modal-header — modal header padding
|
|
6
|
-
* .byline-diff-modal-title-stack — heading + subtitle stack
|
|
7
|
-
* .byline-diff-modal-title — heading
|
|
8
|
-
* .byline-diff-modal-subtitle — subtitle line
|
|
9
|
-
* .byline-diff-modal-version — inline pill identifying the historical version
|
|
10
|
-
* .byline-diff-modal-content — scrollable content
|
|
11
|
-
* .byline-diff-modal-state — loading / error placeholder
|
|
12
|
-
* .byline-diff-modal-error — error placeholder colour modifier
|
|
13
|
-
* .byline-diff-modal-viewer — diff viewer wrapper
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
.header,
|
|
17
|
-
:global(.byline-diff-modal-header) {
|
|
18
|
-
padding-top: var(--spacing-16);
|
|
19
|
-
margin-bottom: var(--spacing-8);
|
|
20
|
-
flex-shrink: 0;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
.title-stack,
|
|
24
|
-
:global(.byline-diff-modal-title-stack) {
|
|
25
|
-
display: flex;
|
|
26
|
-
flex-direction: column;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
.title,
|
|
30
|
-
:global(.byline-diff-modal-title) {
|
|
31
|
-
margin: 0;
|
|
32
|
-
font-size: var(--font-size-xl);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
.subtitle,
|
|
36
|
-
:global(.byline-diff-modal-subtitle) {
|
|
37
|
-
margin: 0;
|
|
38
|
-
font-size: var(--font-size-sm);
|
|
39
|
-
color: var(--gray-400);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
.version,
|
|
43
|
-
:global(.byline-diff-modal-version) {
|
|
44
|
-
font-family: var(--font-family-mono);
|
|
45
|
-
font-size: var(--font-size-xs);
|
|
46
|
-
background-color: var(--canvas-700);
|
|
47
|
-
padding: 0 var(--spacing-4);
|
|
48
|
-
border-radius: var(--border-radius-sm);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
.content,
|
|
52
|
-
:global(.byline-diff-modal-content) {
|
|
53
|
-
flex: 1 1 0;
|
|
54
|
-
overflow: auto;
|
|
55
|
-
padding: 0;
|
|
56
|
-
min-height: 0;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
.state,
|
|
60
|
-
:global(.byline-diff-modal-state) {
|
|
61
|
-
display: flex;
|
|
62
|
-
align-items: center;
|
|
63
|
-
justify-content: center;
|
|
64
|
-
height: 100%;
|
|
65
|
-
gap: var(--spacing-12);
|
|
66
|
-
color: var(--gray-400);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
.error,
|
|
70
|
-
:global(.byline-diff-modal-error) {
|
|
71
|
-
color: var(--red-400);
|
|
72
|
-
gap: 0;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
.viewer,
|
|
76
|
-
:global(.byline-diff-modal-viewer) {
|
|
77
|
-
font-family: var(--font-family-mono);
|
|
78
|
-
font-size: var(--font-size-sm);
|
|
79
|
-
}
|
|
@@ -1,184 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* This Source Code is subject to the terms of the Mozilla Public
|
|
5
|
-
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
6
|
-
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
7
|
-
*
|
|
8
|
-
* Copyright (c) Infonomic Company Limited
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import { useEffect, useState } from 'react'
|
|
12
|
-
|
|
13
|
-
import cx from 'classnames'
|
|
14
|
-
import ReactDiffViewer, { DiffMethod } from 'react-diff-viewer-continued'
|
|
15
|
-
|
|
16
|
-
import { CloseIcon, IconButton, LoaderRing, Modal } from '../../uikit.js'
|
|
17
|
-
import styles from './diff-modal.module.css'
|
|
18
|
-
|
|
19
|
-
// Keys that are per-version metadata rather than content — strip before diffing
|
|
20
|
-
// so the diff focuses on meaningful content changes. ClientDocument-shape
|
|
21
|
-
// metadata keys after the Phase 7 admin migration.
|
|
22
|
-
const STRIP_KEYS = new Set([
|
|
23
|
-
'id',
|
|
24
|
-
'versionId',
|
|
25
|
-
'path',
|
|
26
|
-
'status',
|
|
27
|
-
'createdAt',
|
|
28
|
-
'updatedAt',
|
|
29
|
-
'hasPublishedVersion',
|
|
30
|
-
'_publishedVersion',
|
|
31
|
-
])
|
|
32
|
-
|
|
33
|
-
function stripMeta(doc: Record<string, unknown>): Record<string, unknown> {
|
|
34
|
-
// With the nested document shape, extract just the fields for diffing.
|
|
35
|
-
if (doc.fields && typeof doc.fields === 'object') {
|
|
36
|
-
return doc.fields as Record<string, unknown>
|
|
37
|
-
}
|
|
38
|
-
return Object.fromEntries(Object.entries(doc).filter(([k]) => !STRIP_KEYS.has(k)))
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export interface DiffModalProps {
|
|
42
|
-
isOpen: boolean
|
|
43
|
-
onDismiss: () => void
|
|
44
|
-
collection: string
|
|
45
|
-
documentId: string
|
|
46
|
-
/** The `versionId` of the historical version to compare. */
|
|
47
|
-
versionId: string
|
|
48
|
-
/** A human-readable label for the historical version (e.g. a date string). */
|
|
49
|
-
versionLabel: string
|
|
50
|
-
/** The already-loaded current (latest) version of the document. */
|
|
51
|
-
currentDocument: Record<string, unknown>
|
|
52
|
-
/** Content locale to compare — undefined / 'all' shows all locales. */
|
|
53
|
-
locale?: string
|
|
54
|
-
/**
|
|
55
|
-
* Host-provided loader for a historical document version. The diff
|
|
56
|
-
* modal is framework-neutral; callers wire this to whatever transport
|
|
57
|
-
* they use (e.g. `BylineAdminServices.getCollectionDocumentVersion` in
|
|
58
|
-
* the admin shell).
|
|
59
|
-
*/
|
|
60
|
-
loadHistoricalVersion: (
|
|
61
|
-
collection: string,
|
|
62
|
-
documentId: string,
|
|
63
|
-
versionId: string,
|
|
64
|
-
locale: string | undefined
|
|
65
|
-
) => Promise<Record<string, unknown>>
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export function DiffModal({
|
|
69
|
-
isOpen,
|
|
70
|
-
onDismiss,
|
|
71
|
-
collection,
|
|
72
|
-
documentId,
|
|
73
|
-
versionId,
|
|
74
|
-
versionLabel,
|
|
75
|
-
currentDocument,
|
|
76
|
-
locale,
|
|
77
|
-
loadHistoricalVersion,
|
|
78
|
-
}: DiffModalProps) {
|
|
79
|
-
const [historicalDoc, setHistoricalDoc] = useState<Record<string, unknown> | null>(null)
|
|
80
|
-
const [loading, setLoading] = useState(false)
|
|
81
|
-
const [error, setError] = useState<string | null>(null)
|
|
82
|
-
|
|
83
|
-
useEffect(() => {
|
|
84
|
-
if (!isOpen || !versionId) return
|
|
85
|
-
|
|
86
|
-
let cancelled = false
|
|
87
|
-
setLoading(true)
|
|
88
|
-
setError(null)
|
|
89
|
-
setHistoricalDoc(null)
|
|
90
|
-
|
|
91
|
-
loadHistoricalVersion(collection, documentId, versionId, locale)
|
|
92
|
-
.then((doc) => {
|
|
93
|
-
if (cancelled) return
|
|
94
|
-
setHistoricalDoc(doc)
|
|
95
|
-
})
|
|
96
|
-
.catch((err) => {
|
|
97
|
-
if (cancelled) return
|
|
98
|
-
setError(err instanceof Error ? err.message : 'Failed to load version')
|
|
99
|
-
})
|
|
100
|
-
.finally(() => {
|
|
101
|
-
if (!cancelled) setLoading(false)
|
|
102
|
-
})
|
|
103
|
-
|
|
104
|
-
return () => {
|
|
105
|
-
cancelled = true
|
|
106
|
-
}
|
|
107
|
-
}, [isOpen, collection, documentId, versionId, locale, loadHistoricalVersion])
|
|
108
|
-
|
|
109
|
-
const currentStr = currentDocument ? JSON.stringify(stripMeta(currentDocument), null, 2) : ''
|
|
110
|
-
|
|
111
|
-
const historicalStr = historicalDoc ? JSON.stringify(stripMeta(historicalDoc), null, 2) : ''
|
|
112
|
-
|
|
113
|
-
return (
|
|
114
|
-
<Modal isOpen={isOpen} closeOnOverlayClick={true} onDismiss={onDismiss}>
|
|
115
|
-
<Modal.Container
|
|
116
|
-
style={{
|
|
117
|
-
width: '96vw',
|
|
118
|
-
maxWidth: '96vw',
|
|
119
|
-
height: '90vh',
|
|
120
|
-
maxHeight: '90vh',
|
|
121
|
-
display: 'flex',
|
|
122
|
-
flexDirection: 'column',
|
|
123
|
-
overflow: 'hidden',
|
|
124
|
-
}}
|
|
125
|
-
>
|
|
126
|
-
<Modal.Header className={cx('byline-diff-modal-header', styles.header)}>
|
|
127
|
-
<div className={cx('byline-diff-modal-title-stack', styles['title-stack'])}>
|
|
128
|
-
<h3 className={cx('byline-diff-modal-title', styles.title)}>Version Comparison</h3>
|
|
129
|
-
<p className={cx('byline-diff-modal-subtitle', styles.subtitle)}>
|
|
130
|
-
Comparing{' '}
|
|
131
|
-
<span className={cx('byline-diff-modal-version', styles.version)}>
|
|
132
|
-
{versionLabel}
|
|
133
|
-
</span>{' '}
|
|
134
|
-
(left) against current version (right)
|
|
135
|
-
</p>
|
|
136
|
-
</div>
|
|
137
|
-
<IconButton onClick={onDismiss} size="xs" aria-label="Close comparison">
|
|
138
|
-
<CloseIcon width="15px" height="15px" />
|
|
139
|
-
</IconButton>
|
|
140
|
-
</Modal.Header>
|
|
141
|
-
|
|
142
|
-
<Modal.Content
|
|
143
|
-
className={cx('byline-diff-modal-content', styles.content)}
|
|
144
|
-
style={{ minHeight: 0 }}
|
|
145
|
-
>
|
|
146
|
-
{loading && (
|
|
147
|
-
<div className={cx('byline-diff-modal-state', styles.state)}>
|
|
148
|
-
<LoaderRing size={28} color="#666666" />
|
|
149
|
-
<span>Loading version…</span>
|
|
150
|
-
</div>
|
|
151
|
-
)}
|
|
152
|
-
|
|
153
|
-
{error && (
|
|
154
|
-
<div
|
|
155
|
-
className={cx(
|
|
156
|
-
'byline-diff-modal-state',
|
|
157
|
-
'byline-diff-modal-error',
|
|
158
|
-
styles.state,
|
|
159
|
-
styles.error
|
|
160
|
-
)}
|
|
161
|
-
>
|
|
162
|
-
{error}
|
|
163
|
-
</div>
|
|
164
|
-
)}
|
|
165
|
-
|
|
166
|
-
{!loading && !error && historicalDoc && (
|
|
167
|
-
<div className={cx('byline-diff-modal-viewer', styles.viewer)}>
|
|
168
|
-
<ReactDiffViewer
|
|
169
|
-
oldValue={historicalStr}
|
|
170
|
-
newValue={currentStr}
|
|
171
|
-
splitView={true}
|
|
172
|
-
compareMethod={DiffMethod.LINES}
|
|
173
|
-
useDarkTheme={true}
|
|
174
|
-
leftTitle={versionLabel}
|
|
175
|
-
rightTitle="Current version"
|
|
176
|
-
hideLineNumbers={false}
|
|
177
|
-
/>
|
|
178
|
-
</div>
|
|
179
|
-
)}
|
|
180
|
-
</Modal.Content>
|
|
181
|
-
</Modal.Container>
|
|
182
|
-
</Modal>
|
|
183
|
-
)
|
|
184
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* StatusBadge — workflow-status pill, optionally preceded by a "live" dot.
|
|
3
|
-
*
|
|
4
|
-
* Override handles:
|
|
5
|
-
* .byline-status-badge-wrap — the outer flex container
|
|
6
|
-
* .byline-status-badge-dot — the green "published version live" dot
|
|
7
|
-
* .byline-status-badge — the badge itself
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
.wrap,
|
|
11
|
-
:global(.byline-status-badge-wrap) {
|
|
12
|
-
display: inline-flex;
|
|
13
|
-
align-items: center;
|
|
14
|
-
gap: var(--spacing-4);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
.dot,
|
|
18
|
-
:global(.byline-status-badge-dot) {
|
|
19
|
-
display: inline-block;
|
|
20
|
-
width: 0.5rem;
|
|
21
|
-
height: 0.5rem;
|
|
22
|
-
border-radius: var(--border-radius-full);
|
|
23
|
-
background-color: var(--green-500);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
.badge,
|
|
27
|
-
:global(.byline-status-badge) {
|
|
28
|
-
padding: 0 0.375rem;
|
|
29
|
-
font-size: 0.65rem;
|
|
30
|
-
line-height: 1.5;
|
|
31
|
-
}
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* This Source Code is subject to the terms of the Mozilla Public
|
|
3
|
-
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
|
-
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
5
|
-
*
|
|
6
|
-
* Copyright (c) Infonomic Company Limited
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import type { WorkflowStatus } from '@byline/core'
|
|
10
|
-
import {
|
|
11
|
-
WORKFLOW_STATUS_ARCHIVED,
|
|
12
|
-
WORKFLOW_STATUS_DRAFT,
|
|
13
|
-
WORKFLOW_STATUS_PUBLISHED,
|
|
14
|
-
} from '@byline/core'
|
|
15
|
-
import cx from 'classnames'
|
|
16
|
-
|
|
17
|
-
import { Badge } from '../../uikit.js'
|
|
18
|
-
import styles from './status-badge.module.css'
|
|
19
|
-
|
|
20
|
-
function statusIntent(status: string): 'success' | 'warning' | 'info' | 'noeffect' {
|
|
21
|
-
switch (status) {
|
|
22
|
-
case WORKFLOW_STATUS_PUBLISHED:
|
|
23
|
-
return 'success'
|
|
24
|
-
case WORKFLOW_STATUS_DRAFT:
|
|
25
|
-
return 'warning'
|
|
26
|
-
case WORKFLOW_STATUS_ARCHIVED:
|
|
27
|
-
return 'info'
|
|
28
|
-
default:
|
|
29
|
-
return 'noeffect'
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Compact badge for workflow status values. Maps the three built-in
|
|
35
|
-
* statuses (draft, published, archived) to semantic intents and falls
|
|
36
|
-
* back to `noeffect` for any custom workflow statuses.
|
|
37
|
-
*
|
|
38
|
-
* When `hasPublishedVersion` is true and the current status is not
|
|
39
|
-
* `published`, a small green dot is rendered before the badge to
|
|
40
|
-
* indicate that a published version is live.
|
|
41
|
-
*
|
|
42
|
-
* Stable override handles: `.byline-status-badge-wrap`,
|
|
43
|
-
* `.byline-status-badge-dot`, `.byline-status-badge`.
|
|
44
|
-
*/
|
|
45
|
-
export const StatusBadge = ({
|
|
46
|
-
status,
|
|
47
|
-
workflowStatuses,
|
|
48
|
-
hasPublishedVersion,
|
|
49
|
-
}: {
|
|
50
|
-
status: string
|
|
51
|
-
workflowStatuses: WorkflowStatus[]
|
|
52
|
-
hasPublishedVersion?: boolean
|
|
53
|
-
}) => {
|
|
54
|
-
const label = workflowStatuses.find((s) => s.name === status)?.label ?? String(status ?? '')
|
|
55
|
-
|
|
56
|
-
return (
|
|
57
|
-
<span className={cx('byline-status-badge-wrap', styles.wrap)}>
|
|
58
|
-
{hasPublishedVersion === true && status !== 'published' && (
|
|
59
|
-
<span
|
|
60
|
-
title="A published version is live"
|
|
61
|
-
className={cx('byline-status-badge-dot', styles.dot)}
|
|
62
|
-
/>
|
|
63
|
-
)}
|
|
64
|
-
<Badge intent={statusIntent(status)} className={cx('byline-status-badge', styles.badge)}>
|
|
65
|
-
{label}
|
|
66
|
-
</Badge>
|
|
67
|
-
</span>
|
|
68
|
-
)
|
|
69
|
-
}
|