@tinacms/app 1.2.12 → 1.2.14
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/CHANGELOG.md +25 -0
- package/package.json +3 -3
- package/src/App.tsx +0 -5
- package/src/fields/rich-text/monaco/index.tsx +1 -1
- package/src/lib/expand-query.ts +38 -1
- package/src/lib/graphql-reducer.ts +263 -58
- package/src/lib/types.ts +8 -5
- package/src/lib/util.ts +129 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
# @tinacms/app
|
|
2
2
|
|
|
3
|
+
## 1.2.14
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 52b1762e2: Prevent unhandled promise rejection when not able to determine collection for file
|
|
8
|
+
- 16b0c8073: Fix issue where activeField search param would be called twice, resulting in the incorrect form being focused.
|
|
9
|
+
- Updated dependencies [70c74bb55]
|
|
10
|
+
- Updated dependencies [385c8a865]
|
|
11
|
+
- Updated dependencies [ccd928bc3]
|
|
12
|
+
- Updated dependencies [1aea2c6a4]
|
|
13
|
+
- @tinacms/toolkit@1.7.3
|
|
14
|
+
- tinacms@1.5.7
|
|
15
|
+
|
|
16
|
+
## 1.2.13
|
|
17
|
+
|
|
18
|
+
### Patch Changes
|
|
19
|
+
|
|
20
|
+
- 7f95c1ce5: Reorganize the way fields are presented on the form to allow for deep-linking
|
|
21
|
+
- Updated dependencies [5a6018916]
|
|
22
|
+
- Updated dependencies [63dd98904]
|
|
23
|
+
- Updated dependencies [b3d98d159]
|
|
24
|
+
- Updated dependencies [7f95c1ce5]
|
|
25
|
+
- tinacms@1.5.6
|
|
26
|
+
- @tinacms/toolkit@1.7.2
|
|
27
|
+
|
|
3
28
|
## 1.2.12
|
|
4
29
|
|
|
5
30
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tinacms/app",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.14",
|
|
4
4
|
"main": "src/main.tsx",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"devDependencies": {
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"@heroicons/react": "1.0.6",
|
|
15
15
|
"@monaco-editor/react": "4.4.5",
|
|
16
16
|
"@tinacms/mdx": "1.3.10",
|
|
17
|
-
"@tinacms/toolkit": "1.7.
|
|
17
|
+
"@tinacms/toolkit": "1.7.3",
|
|
18
18
|
"@xstate/react": "3.0.0",
|
|
19
19
|
"final-form": "4.20.7",
|
|
20
20
|
"graphiql": "^2.4.0",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"react-is": "17.0.2",
|
|
28
28
|
"react-router-dom": "6.3.0",
|
|
29
29
|
"tailwindcss": "^3.2.7",
|
|
30
|
-
"tinacms": "1.5.
|
|
30
|
+
"tinacms": "1.5.7",
|
|
31
31
|
"typescript": "^4.6.4",
|
|
32
32
|
"zod": "^3.14.3"
|
|
33
33
|
}
|
package/src/App.tsx
CHANGED
|
@@ -160,7 +160,7 @@ export const RawEditor = (props: RichTextType) => {
|
|
|
160
160
|
|
|
161
161
|
return (
|
|
162
162
|
<div className="relative">
|
|
163
|
-
<div className="sticky
|
|
163
|
+
<div className="sticky top-1 w-full flex justify-between mb-2 z-50 max-w-full">
|
|
164
164
|
<Button onClick={() => props.setRawMode(false)}>
|
|
165
165
|
View in rich-text editor
|
|
166
166
|
</Button>
|
package/src/lib/expand-query.ts
CHANGED
|
@@ -67,6 +67,13 @@ const addTypenameToDocument = (doc: G.DocumentNode) => {
|
|
|
67
67
|
})
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
+
const CONTENT_SOURCE_FIELD: G.FieldNode = {
|
|
71
|
+
kind: G.Kind.FIELD,
|
|
72
|
+
name: {
|
|
73
|
+
kind: G.Kind.NAME,
|
|
74
|
+
value: '_content_source',
|
|
75
|
+
},
|
|
76
|
+
}
|
|
70
77
|
const METADATA_FIELD: G.FieldNode = {
|
|
71
78
|
kind: G.Kind.FIELD,
|
|
72
79
|
name: {
|
|
@@ -94,7 +101,11 @@ const addMetadataField = (
|
|
|
94
101
|
selections: [],
|
|
95
102
|
}),
|
|
96
103
|
selections:
|
|
97
|
-
[
|
|
104
|
+
[
|
|
105
|
+
...(node.selectionSet?.selections || []),
|
|
106
|
+
METADATA_FIELD,
|
|
107
|
+
CONTENT_SOURCE_FIELD,
|
|
108
|
+
] || [],
|
|
98
109
|
},
|
|
99
110
|
}
|
|
100
111
|
}
|
|
@@ -229,3 +240,29 @@ export const isNodeType = (type: G.GraphQLOutputType) => {
|
|
|
229
240
|
}
|
|
230
241
|
}
|
|
231
242
|
}
|
|
243
|
+
|
|
244
|
+
export const isConnectionType = (type: G.GraphQLOutputType) => {
|
|
245
|
+
const namedType = G.getNamedType(type)
|
|
246
|
+
if (G.isInterfaceType(namedType)) {
|
|
247
|
+
if (namedType.name === 'Connection') {
|
|
248
|
+
return true
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
if (G.isUnionType(namedType)) {
|
|
252
|
+
const types = namedType.getTypes()
|
|
253
|
+
if (
|
|
254
|
+
types.every((type) => {
|
|
255
|
+
return type.getInterfaces().some((intfc) => intfc.name === 'Connection')
|
|
256
|
+
})
|
|
257
|
+
) {
|
|
258
|
+
return true
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
if (G.isObjectType(namedType)) {
|
|
262
|
+
if (
|
|
263
|
+
namedType.getInterfaces().some((intfc) => intfc.name === 'Connection')
|
|
264
|
+
) {
|
|
265
|
+
return true
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import * as G from 'graphql'
|
|
3
3
|
import { getIn } from 'final-form'
|
|
4
|
-
import { z
|
|
4
|
+
import { z } from 'zod'
|
|
5
5
|
// @ts-expect-error
|
|
6
6
|
import schemaJson from 'SCHEMA_IMPORT'
|
|
7
|
-
import { expandQuery, isNodeType } from './expand-query'
|
|
7
|
+
import { expandQuery, isConnectionType, isNodeType } from './expand-query'
|
|
8
8
|
import {
|
|
9
9
|
Form,
|
|
10
10
|
TinaCMS,
|
|
@@ -18,15 +18,17 @@ import {
|
|
|
18
18
|
Client,
|
|
19
19
|
FormOptions,
|
|
20
20
|
GlobalFormPlugin,
|
|
21
|
+
TinaState,
|
|
21
22
|
} from 'tinacms'
|
|
22
23
|
import { createForm, createGlobalForm, FormifyCallback } from './build-form'
|
|
23
24
|
import type {
|
|
24
25
|
PostMessage,
|
|
25
26
|
Payload,
|
|
26
27
|
SystemInfo,
|
|
27
|
-
Document,
|
|
28
28
|
ResolvedDocument,
|
|
29
29
|
} from './types'
|
|
30
|
+
import { getFormAndFieldNameFromMetadata } from './util'
|
|
31
|
+
import { useSearchParams } from 'react-router-dom'
|
|
30
32
|
|
|
31
33
|
const sysSchema = z.object({
|
|
32
34
|
breadcrumbs: z.array(z.string()),
|
|
@@ -56,6 +58,50 @@ const astNode = schemaJson as G.DocumentNode
|
|
|
56
58
|
const astNodeWithMeta: G.DocumentNode = {
|
|
57
59
|
...astNode,
|
|
58
60
|
definitions: astNode.definitions.map((def) => {
|
|
61
|
+
if (def.kind === 'InterfaceTypeDefinition') {
|
|
62
|
+
return {
|
|
63
|
+
...def,
|
|
64
|
+
fields: [
|
|
65
|
+
...(def.fields || []),
|
|
66
|
+
{
|
|
67
|
+
kind: 'FieldDefinition',
|
|
68
|
+
name: {
|
|
69
|
+
kind: 'Name',
|
|
70
|
+
value: '_tina_metadata',
|
|
71
|
+
},
|
|
72
|
+
arguments: [],
|
|
73
|
+
type: {
|
|
74
|
+
kind: 'NonNullType',
|
|
75
|
+
type: {
|
|
76
|
+
kind: 'NamedType',
|
|
77
|
+
name: {
|
|
78
|
+
kind: 'Name',
|
|
79
|
+
value: 'JSON',
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
kind: 'FieldDefinition',
|
|
86
|
+
name: {
|
|
87
|
+
kind: 'Name',
|
|
88
|
+
value: '_content_source',
|
|
89
|
+
},
|
|
90
|
+
arguments: [],
|
|
91
|
+
type: {
|
|
92
|
+
kind: 'NonNullType',
|
|
93
|
+
type: {
|
|
94
|
+
kind: 'NamedType',
|
|
95
|
+
name: {
|
|
96
|
+
kind: 'Name',
|
|
97
|
+
value: 'JSON',
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
],
|
|
103
|
+
}
|
|
104
|
+
}
|
|
59
105
|
if (def.kind === 'ObjectTypeDefinition') {
|
|
60
106
|
return {
|
|
61
107
|
...def,
|
|
@@ -79,6 +125,24 @@ const astNodeWithMeta: G.DocumentNode = {
|
|
|
79
125
|
},
|
|
80
126
|
},
|
|
81
127
|
},
|
|
128
|
+
{
|
|
129
|
+
kind: 'FieldDefinition',
|
|
130
|
+
name: {
|
|
131
|
+
kind: 'Name',
|
|
132
|
+
value: '_content_source',
|
|
133
|
+
},
|
|
134
|
+
arguments: [],
|
|
135
|
+
type: {
|
|
136
|
+
kind: 'NonNullType',
|
|
137
|
+
type: {
|
|
138
|
+
kind: 'NamedType',
|
|
139
|
+
name: {
|
|
140
|
+
kind: 'Name',
|
|
141
|
+
value: 'JSON',
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
},
|
|
82
146
|
],
|
|
83
147
|
}
|
|
84
148
|
}
|
|
@@ -95,6 +159,18 @@ export const useGraphQLReducer = (
|
|
|
95
159
|
const cms = useCMS()
|
|
96
160
|
const tinaSchema = cms.api.tina.schema as TinaSchema
|
|
97
161
|
const [payloads, setPayloads] = React.useState<Payload[]>([])
|
|
162
|
+
const [searchParams, setSearchParams] = useSearchParams()
|
|
163
|
+
const [results, setResults] = React.useState<
|
|
164
|
+
{
|
|
165
|
+
id: string
|
|
166
|
+
data:
|
|
167
|
+
| {
|
|
168
|
+
[key: string]: any
|
|
169
|
+
}
|
|
170
|
+
| null
|
|
171
|
+
| undefined
|
|
172
|
+
}[]
|
|
173
|
+
>([])
|
|
98
174
|
const [documentsToResolve, setDocumentsToResolve] = React.useState<string[]>(
|
|
99
175
|
[]
|
|
100
176
|
)
|
|
@@ -103,6 +179,8 @@ export const useGraphQLReducer = (
|
|
|
103
179
|
>([])
|
|
104
180
|
const [operationIndex, setOperationIndex] = React.useState(0)
|
|
105
181
|
|
|
182
|
+
const activeField = searchParams.get('active-field')
|
|
183
|
+
|
|
106
184
|
React.useEffect(() => {
|
|
107
185
|
const run = async () => {
|
|
108
186
|
return Promise.all(
|
|
@@ -144,7 +222,7 @@ export const useGraphQLReducer = (
|
|
|
144
222
|
setPayloads(updatedPayloads)
|
|
145
223
|
})
|
|
146
224
|
}
|
|
147
|
-
}, [
|
|
225
|
+
}, [JSON.stringify(payloads), cms])
|
|
148
226
|
|
|
149
227
|
const processPayload = React.useCallback(
|
|
150
228
|
(payload: Payload) => {
|
|
@@ -152,6 +230,8 @@ export const useGraphQLReducer = (
|
|
|
152
230
|
if (!expandedQueryForResolver || !expandedData) {
|
|
153
231
|
throw new Error(`Unable to process payload which has not been expanded`)
|
|
154
232
|
}
|
|
233
|
+
const formListItems: TinaState['formLists'][number]['items'] = []
|
|
234
|
+
const formIds: string[] = []
|
|
155
235
|
|
|
156
236
|
const result = G.graphqlSync({
|
|
157
237
|
schema: schemaForResolver,
|
|
@@ -189,6 +269,13 @@ export const useGraphQLReducer = (
|
|
|
189
269
|
if (fieldName === '_values') {
|
|
190
270
|
return source._internalValues
|
|
191
271
|
}
|
|
272
|
+
if (info.fieldName === '_content_source') {
|
|
273
|
+
const pathArray = G.responsePathAsArray(info.path)
|
|
274
|
+
return {
|
|
275
|
+
queryId: payload.id,
|
|
276
|
+
path: pathArray.slice(0, pathArray.length - 1),
|
|
277
|
+
}
|
|
278
|
+
}
|
|
192
279
|
if (info.fieldName === '_tina_metadata') {
|
|
193
280
|
if (value) {
|
|
194
281
|
return value
|
|
@@ -198,6 +285,27 @@ export const useGraphQLReducer = (
|
|
|
198
285
|
return {
|
|
199
286
|
id: null,
|
|
200
287
|
fields: [],
|
|
288
|
+
prefix: '',
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
if (isConnectionType(info.returnType)) {
|
|
292
|
+
const name = G.getNamedType(info.returnType).name
|
|
293
|
+
const connectionCollection = tinaSchema
|
|
294
|
+
.getCollections()
|
|
295
|
+
.find((collection) => {
|
|
296
|
+
const collectionName = NAMER.referenceConnectionType(
|
|
297
|
+
collection.namespace
|
|
298
|
+
)
|
|
299
|
+
if (collectionName === name) {
|
|
300
|
+
return true
|
|
301
|
+
}
|
|
302
|
+
return false
|
|
303
|
+
})
|
|
304
|
+
if (connectionCollection) {
|
|
305
|
+
formListItems.push({
|
|
306
|
+
type: 'list',
|
|
307
|
+
label: connectionCollection.label || connectionCollection.name,
|
|
308
|
+
})
|
|
201
309
|
}
|
|
202
310
|
}
|
|
203
311
|
if (isNodeType(info.returnType)) {
|
|
@@ -236,19 +344,37 @@ export const useGraphQLReducer = (
|
|
|
236
344
|
resolvedDocument = documentSchema.parse(value)
|
|
237
345
|
}
|
|
238
346
|
const id = resolvedDocument._internalSys.path
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
347
|
+
formIds.push(id)
|
|
348
|
+
const existingForm = cms.state.forms.find(
|
|
349
|
+
(f) => f.tinaForm.id === id
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
const pathArray = G.responsePathAsArray(info.path)
|
|
353
|
+
const pathString = pathArray.join('.')
|
|
354
|
+
const ancestors = formListItems.filter((item) => {
|
|
355
|
+
if (item.type === 'document') {
|
|
356
|
+
return pathString.startsWith(item.path)
|
|
357
|
+
}
|
|
358
|
+
})
|
|
359
|
+
const parent = ancestors[ancestors.length - 1]
|
|
360
|
+
if (parent) {
|
|
361
|
+
if (parent.type === 'document') {
|
|
362
|
+
parent.subItems.push({
|
|
363
|
+
type: 'document',
|
|
364
|
+
path: pathString,
|
|
365
|
+
formId: id,
|
|
366
|
+
subItems: [],
|
|
250
367
|
})
|
|
368
|
+
}
|
|
369
|
+
} else {
|
|
370
|
+
formListItems.push({
|
|
371
|
+
type: 'document',
|
|
372
|
+
path: pathString,
|
|
373
|
+
formId: id,
|
|
374
|
+
subItems: [],
|
|
375
|
+
})
|
|
251
376
|
}
|
|
377
|
+
|
|
252
378
|
if (!existingForm) {
|
|
253
379
|
const { form, template } = buildForm({
|
|
254
380
|
resolvedDocument,
|
|
@@ -262,15 +388,25 @@ export const useGraphQLReducer = (
|
|
|
262
388
|
},
|
|
263
389
|
{ values: true }
|
|
264
390
|
)
|
|
265
|
-
return resolveDocument(
|
|
391
|
+
return resolveDocument(
|
|
392
|
+
resolvedDocument,
|
|
393
|
+
template,
|
|
394
|
+
form,
|
|
395
|
+
pathString
|
|
396
|
+
)
|
|
266
397
|
} else {
|
|
267
|
-
existingForm.addQuery(payload.id)
|
|
398
|
+
existingForm.tinaForm.addQuery(payload.id)
|
|
268
399
|
const { template } = getTemplateForDocument(
|
|
269
400
|
resolvedDocument,
|
|
270
401
|
tinaSchema
|
|
271
402
|
)
|
|
272
|
-
existingForm.addQuery(payload.id)
|
|
273
|
-
return resolveDocument(
|
|
403
|
+
existingForm.tinaForm.addQuery(payload.id)
|
|
404
|
+
return resolveDocument(
|
|
405
|
+
resolvedDocument,
|
|
406
|
+
template,
|
|
407
|
+
existingForm.tinaForm,
|
|
408
|
+
pathString
|
|
409
|
+
)
|
|
274
410
|
}
|
|
275
411
|
}
|
|
276
412
|
return value
|
|
@@ -295,56 +431,96 @@ export const useGraphQLReducer = (
|
|
|
295
431
|
}
|
|
296
432
|
})
|
|
297
433
|
} else {
|
|
434
|
+
if (result.data) {
|
|
435
|
+
setResults((results) => [
|
|
436
|
+
...results.filter((result) => result.id !== payload.id),
|
|
437
|
+
{ id: payload.id, data: result.data },
|
|
438
|
+
])
|
|
439
|
+
}
|
|
440
|
+
if (activeField) {
|
|
441
|
+
setSearchParams({})
|
|
442
|
+
const [queryId, eventFieldName] = activeField.split('---')
|
|
443
|
+
if (queryId === payload.id) {
|
|
444
|
+
if (result?.data) {
|
|
445
|
+
cms.dispatch({
|
|
446
|
+
type: 'forms:set-active-field-name',
|
|
447
|
+
value: getFormAndFieldNameFromMetadata(
|
|
448
|
+
result.data,
|
|
449
|
+
eventFieldName
|
|
450
|
+
),
|
|
451
|
+
})
|
|
452
|
+
}
|
|
453
|
+
cms.dispatch({
|
|
454
|
+
type: 'sidebar:set-display-state',
|
|
455
|
+
value: 'openOrFull',
|
|
456
|
+
})
|
|
457
|
+
}
|
|
458
|
+
}
|
|
298
459
|
iframe.current?.contentWindow?.postMessage({
|
|
299
460
|
type: 'updateData',
|
|
300
461
|
id: payload.id,
|
|
301
462
|
data: result.data,
|
|
302
463
|
})
|
|
303
|
-
|
|
304
|
-
// This can be improved, for now we just need something to test with
|
|
305
|
-
const elements =
|
|
306
|
-
iframe.current?.contentWindow?.document.querySelectorAll<HTMLElement>(
|
|
307
|
-
`[data-tinafield]`
|
|
308
|
-
)
|
|
309
|
-
if (elements) {
|
|
310
|
-
for (let i = 0; i < elements.length; i++) {
|
|
311
|
-
const el = elements[i]
|
|
312
|
-
el.onclick = () => {
|
|
313
|
-
const tinafield = el.getAttribute('data-tinafield')
|
|
314
|
-
cms.events.dispatch({
|
|
315
|
-
type: 'field:selected',
|
|
316
|
-
value: tinafield,
|
|
317
|
-
})
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
464
|
}
|
|
465
|
+
cms.dispatch({
|
|
466
|
+
type: 'form-lists:add',
|
|
467
|
+
value: {
|
|
468
|
+
id: payload.id,
|
|
469
|
+
label: 'Anonymous Query', // TODO: grab the name of the query if it exists
|
|
470
|
+
items: formListItems,
|
|
471
|
+
formIds,
|
|
472
|
+
},
|
|
473
|
+
})
|
|
322
474
|
},
|
|
323
|
-
[
|
|
475
|
+
[
|
|
476
|
+
resolvedDocuments.map((doc) => doc._internalSys.path).join('.'),
|
|
477
|
+
activeField,
|
|
478
|
+
]
|
|
324
479
|
)
|
|
325
480
|
|
|
326
|
-
const
|
|
481
|
+
const handleMessage = React.useCallback(
|
|
327
482
|
(event: MessageEvent<PostMessage>) => {
|
|
483
|
+
if (event?.data?.type === 'quick-edit') {
|
|
484
|
+
cms.dispatch({
|
|
485
|
+
type: 'set-quick-editing-supported',
|
|
486
|
+
value: event.data.value,
|
|
487
|
+
})
|
|
488
|
+
iframe.current?.contentWindow?.postMessage({
|
|
489
|
+
type: 'quickEditEnabled',
|
|
490
|
+
value: cms.state.sidebarDisplayState === 'open',
|
|
491
|
+
})
|
|
492
|
+
}
|
|
328
493
|
if (event?.data?.type === 'isEditMode') {
|
|
329
494
|
iframe?.current?.contentWindow?.postMessage({
|
|
330
495
|
type: 'tina:editMode',
|
|
331
496
|
})
|
|
332
497
|
}
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
498
|
+
if (event.data.type === 'field:selected') {
|
|
499
|
+
const [queryId, eventFieldName] = event.data.fieldName.split('---')
|
|
500
|
+
const result = results.find((res) => res.id === queryId)
|
|
501
|
+
if (result?.data) {
|
|
502
|
+
cms.dispatch({
|
|
503
|
+
type: 'forms:set-active-field-name',
|
|
504
|
+
value: getFormAndFieldNameFromMetadata(result.data, eventFieldName),
|
|
505
|
+
})
|
|
506
|
+
}
|
|
507
|
+
cms.dispatch({
|
|
508
|
+
type: 'sidebar:set-display-state',
|
|
509
|
+
value: 'openOrFull',
|
|
510
|
+
})
|
|
511
|
+
}
|
|
338
512
|
if (event.data.type === 'close') {
|
|
339
513
|
const payloadSchema = z.object({ id: z.string() })
|
|
340
514
|
const { id } = payloadSchema.parse(event.data)
|
|
341
515
|
setPayloads((previous) =>
|
|
342
516
|
previous.filter((payload) => payload.id !== id)
|
|
343
517
|
)
|
|
518
|
+
setResults((previous) => previous.filter((result) => result.id !== id))
|
|
344
519
|
cms.forms.all().map((form) => {
|
|
345
520
|
form.removeQuery(id)
|
|
346
521
|
})
|
|
347
522
|
cms.removeOrphanedForms()
|
|
523
|
+
cms.dispatch({ type: 'form-lists:remove', value: id })
|
|
348
524
|
}
|
|
349
525
|
if (event.data.type === 'open') {
|
|
350
526
|
const payloadSchema = z.object({
|
|
@@ -360,7 +536,7 @@ export const useGraphQLReducer = (
|
|
|
360
536
|
])
|
|
361
537
|
}
|
|
362
538
|
},
|
|
363
|
-
[cms]
|
|
539
|
+
[cms, JSON.stringify(results)]
|
|
364
540
|
)
|
|
365
541
|
|
|
366
542
|
React.useEffect(() => {
|
|
@@ -374,22 +550,31 @@ export const useGraphQLReducer = (
|
|
|
374
550
|
React.useEffect(() => {
|
|
375
551
|
return () => {
|
|
376
552
|
setPayloads([])
|
|
553
|
+
setResults([])
|
|
377
554
|
cms.removeAllForms()
|
|
555
|
+
cms.dispatch({ type: 'form-lists:clear' })
|
|
378
556
|
}
|
|
379
557
|
}, [url])
|
|
380
558
|
|
|
381
559
|
React.useEffect(() => {
|
|
560
|
+
iframe.current?.contentWindow?.postMessage({
|
|
561
|
+
type: 'quickEditEnabled',
|
|
562
|
+
value: cms.state.sidebarDisplayState === 'open',
|
|
563
|
+
})
|
|
564
|
+
}, [cms.state.sidebarDisplayState])
|
|
565
|
+
|
|
566
|
+
React.useEffect(() => {
|
|
567
|
+
cms.dispatch({ type: 'set-edit-mode', value: 'visual' })
|
|
382
568
|
if (iframe) {
|
|
383
|
-
window.addEventListener('message',
|
|
384
|
-
window.addEventListener('message', notifyEditMode)
|
|
569
|
+
window.addEventListener('message', handleMessage)
|
|
385
570
|
}
|
|
386
571
|
|
|
387
572
|
return () => {
|
|
388
|
-
window.removeEventListener('message',
|
|
389
|
-
window.removeEventListener('message', notifyEditMode)
|
|
573
|
+
window.removeEventListener('message', handleMessage)
|
|
390
574
|
cms.removeAllForms()
|
|
575
|
+
cms.dispatch({ type: 'set-edit-mode', value: 'basic' })
|
|
391
576
|
}
|
|
392
|
-
}, [iframe.current])
|
|
577
|
+
}, [iframe.current, JSON.stringify(results)])
|
|
393
578
|
}
|
|
394
579
|
|
|
395
580
|
const onSubmit = async (
|
|
@@ -427,7 +612,8 @@ type Path = (string | number)[]
|
|
|
427
612
|
const resolveDocument = (
|
|
428
613
|
doc: ResolvedDocument,
|
|
429
614
|
template: Template<true>,
|
|
430
|
-
form: Form
|
|
615
|
+
form: Form,
|
|
616
|
+
pathToDocument: string
|
|
431
617
|
): ResolvedDocument => {
|
|
432
618
|
// @ts-ignore AnyField and TinaField don't mix
|
|
433
619
|
const fields = form.fields as TinaField<true>[]
|
|
@@ -438,6 +624,7 @@ const resolveDocument = (
|
|
|
438
624
|
values: form.values,
|
|
439
625
|
path,
|
|
440
626
|
id,
|
|
627
|
+
pathToDocument,
|
|
441
628
|
})
|
|
442
629
|
const metadataFields: Record<string, string> = {}
|
|
443
630
|
Object.keys(formValues).forEach((key) => {
|
|
@@ -450,7 +637,9 @@ const resolveDocument = (
|
|
|
450
637
|
sys: doc._internalSys,
|
|
451
638
|
values: form.values,
|
|
452
639
|
_tina_metadata: {
|
|
640
|
+
prefix: pathToDocument,
|
|
453
641
|
id: doc._internalSys.path,
|
|
642
|
+
name: '',
|
|
454
643
|
fields: metadataFields,
|
|
455
644
|
},
|
|
456
645
|
_internalSys: doc._internalSys,
|
|
@@ -464,12 +653,14 @@ const resolveFormValue = <T extends Record<string, unknown>>({
|
|
|
464
653
|
values,
|
|
465
654
|
path,
|
|
466
655
|
id,
|
|
656
|
+
pathToDocument,
|
|
467
657
|
}: // tinaSchema,
|
|
468
658
|
{
|
|
469
659
|
fields: TinaField<true>[]
|
|
470
660
|
values: T
|
|
471
661
|
path: Path
|
|
472
662
|
id: string
|
|
663
|
+
pathToDocument: string
|
|
473
664
|
// tinaSchema: TinaSchema
|
|
474
665
|
}): T & { __typename?: string } => {
|
|
475
666
|
const accum: Record<string, unknown> = {}
|
|
@@ -486,6 +677,7 @@ const resolveFormValue = <T extends Record<string, unknown>>({
|
|
|
486
677
|
value: v,
|
|
487
678
|
path,
|
|
488
679
|
id,
|
|
680
|
+
pathToDocument,
|
|
489
681
|
})
|
|
490
682
|
})
|
|
491
683
|
return accum as T & { __typename?: string }
|
|
@@ -495,11 +687,13 @@ const resolveFieldValue = ({
|
|
|
495
687
|
value,
|
|
496
688
|
path,
|
|
497
689
|
id,
|
|
690
|
+
pathToDocument,
|
|
498
691
|
}: {
|
|
499
692
|
field: TinaField<true>
|
|
500
693
|
value: unknown
|
|
501
694
|
path: Path
|
|
502
695
|
id: string
|
|
696
|
+
pathToDocument: string
|
|
503
697
|
}) => {
|
|
504
698
|
switch (field.type) {
|
|
505
699
|
case 'object': {
|
|
@@ -520,13 +714,16 @@ const resolveFieldValue = ({
|
|
|
520
714
|
__typename: NAMER.dataTypeName(template.namespace),
|
|
521
715
|
_tina_metadata: {
|
|
522
716
|
id,
|
|
717
|
+
name: nextPath.join('.'),
|
|
523
718
|
fields: metadataFields,
|
|
719
|
+
prefix: pathToDocument,
|
|
524
720
|
},
|
|
525
721
|
...resolveFormValue({
|
|
526
722
|
fields: template.fields,
|
|
527
723
|
values: item,
|
|
528
724
|
path: nextPath,
|
|
529
725
|
id,
|
|
726
|
+
pathToDocument,
|
|
530
727
|
}),
|
|
531
728
|
}
|
|
532
729
|
})
|
|
@@ -555,13 +752,16 @@ const resolveFieldValue = ({
|
|
|
555
752
|
__typename: NAMER.dataTypeName(field.namespace),
|
|
556
753
|
_tina_metadata: {
|
|
557
754
|
id,
|
|
755
|
+
name: nextPath.join('.'),
|
|
558
756
|
fields: metadataFields,
|
|
757
|
+
prefix: pathToDocument,
|
|
559
758
|
},
|
|
560
759
|
...resolveFormValue({
|
|
561
760
|
fields: templateFields,
|
|
562
761
|
values: item,
|
|
563
|
-
path,
|
|
762
|
+
path: nextPath,
|
|
564
763
|
id,
|
|
764
|
+
pathToDocument,
|
|
565
765
|
}),
|
|
566
766
|
}
|
|
567
767
|
})
|
|
@@ -576,13 +776,16 @@ const resolveFieldValue = ({
|
|
|
576
776
|
__typename: NAMER.dataTypeName(field.namespace),
|
|
577
777
|
_tina_metadata: {
|
|
578
778
|
id,
|
|
779
|
+
name: nextPath.join('.'),
|
|
579
780
|
fields: metadataFields,
|
|
781
|
+
prefix: pathToDocument,
|
|
580
782
|
},
|
|
581
783
|
...resolveFormValue({
|
|
582
784
|
fields: templateFields,
|
|
583
785
|
values: value as any,
|
|
584
|
-
path,
|
|
786
|
+
path: nextPath,
|
|
585
787
|
id,
|
|
788
|
+
pathToDocument,
|
|
586
789
|
}),
|
|
587
790
|
}
|
|
588
791
|
}
|
|
@@ -645,7 +848,7 @@ const expandPayload = async (payload: Payload, cms: TinaCMS) => {
|
|
|
645
848
|
documentNode,
|
|
646
849
|
})
|
|
647
850
|
const expandedQueryForResolver = G.print(expandedDocumentNodeForResolver)
|
|
648
|
-
return { ...payload,
|
|
851
|
+
return { ...payload, expandedQuery, expandedData, expandedQueryForResolver }
|
|
649
852
|
}
|
|
650
853
|
|
|
651
854
|
/**
|
|
@@ -667,7 +870,11 @@ const getTemplateForDocument = (
|
|
|
667
870
|
tinaSchema: TinaSchema
|
|
668
871
|
) => {
|
|
669
872
|
const id = resolvedDocument._internalSys.path
|
|
670
|
-
|
|
873
|
+
let collection: Collection<true> | undefined
|
|
874
|
+
try {
|
|
875
|
+
collection = tinaSchema.getCollectionByFullPath(id)
|
|
876
|
+
} catch (e) {}
|
|
877
|
+
|
|
671
878
|
if (!collection) {
|
|
672
879
|
throw new Error(`Unable to determine collection for path ${id}`)
|
|
673
880
|
}
|
|
@@ -738,12 +945,10 @@ const buildForm = ({
|
|
|
738
945
|
}
|
|
739
946
|
if (form) {
|
|
740
947
|
if (shouldRegisterForm) {
|
|
741
|
-
form.subscribe(() => {}, { values: true })
|
|
742
948
|
if (collection.ui?.global) {
|
|
743
949
|
cms.plugins.add(new GlobalFormPlugin(form))
|
|
744
|
-
} else {
|
|
745
|
-
cms.forms.add(form)
|
|
746
950
|
}
|
|
951
|
+
cms.dispatch({ type: 'forms:add', value: form })
|
|
747
952
|
}
|
|
748
953
|
}
|
|
749
954
|
if (!form) {
|
package/src/lib/types.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
export type PostMessage =
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
export type PostMessage =
|
|
2
|
+
| {
|
|
3
|
+
type: 'open' | 'close' | 'isEditMode'
|
|
4
|
+
id: string
|
|
5
|
+
data: object
|
|
6
|
+
}
|
|
7
|
+
| { type: 'field:selected'; fieldName: string }
|
|
8
|
+
| { type: 'quick-edit'; value: boolean }
|
|
6
9
|
|
|
7
10
|
export type Payload = {
|
|
8
11
|
id: string
|
package/src/lib/util.ts
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
const charCodeOfDot = '.'.charCodeAt(0)
|
|
2
|
+
const reEscapeChar = /\\(\\)?/g
|
|
3
|
+
const rePropName = RegExp(
|
|
4
|
+
// Match anything that isn't a dot or bracket.
|
|
5
|
+
'[^.[\\]]+' +
|
|
6
|
+
'|' +
|
|
7
|
+
// Or match property names within brackets.
|
|
8
|
+
'\\[(?:' +
|
|
9
|
+
// Match a non-string expression.
|
|
10
|
+
'([^"\'][^[]*)' +
|
|
11
|
+
'|' +
|
|
12
|
+
// Or match strings (supports escaping characters).
|
|
13
|
+
'(["\'])((?:(?!\\2)[^\\\\]|\\\\.)*?)\\2' +
|
|
14
|
+
')\\]' +
|
|
15
|
+
'|' +
|
|
16
|
+
// Or match "" as the space between consecutive dots or empty brackets.
|
|
17
|
+
'(?=(?:\\.|\\[\\])(?:\\.|\\[\\]|$))',
|
|
18
|
+
'g'
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Converts `string` to a property path array.
|
|
23
|
+
*
|
|
24
|
+
* @private
|
|
25
|
+
* @param {string} string The string to convert.
|
|
26
|
+
* @returns {Array} Returns the property path array.
|
|
27
|
+
*/
|
|
28
|
+
const stringToPath = (string: string) => {
|
|
29
|
+
const result = []
|
|
30
|
+
if (string.charCodeAt(0) === charCodeOfDot) {
|
|
31
|
+
result.push('')
|
|
32
|
+
}
|
|
33
|
+
string.replace(rePropName, (match, expression, quote, subString) => {
|
|
34
|
+
let key = match
|
|
35
|
+
if (quote) {
|
|
36
|
+
key = subString.replace(reEscapeChar, '$1')
|
|
37
|
+
} else if (expression) {
|
|
38
|
+
key = expression.trim()
|
|
39
|
+
}
|
|
40
|
+
result.push(key)
|
|
41
|
+
})
|
|
42
|
+
return result
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const keysCache: { [key: string]: string[] } = {}
|
|
46
|
+
const keysRegex = /[.[\]]+/
|
|
47
|
+
|
|
48
|
+
const toPath = (key: string): string[] => {
|
|
49
|
+
if (key === null || key === undefined || !key.length) {
|
|
50
|
+
return []
|
|
51
|
+
}
|
|
52
|
+
if (typeof key !== 'string') {
|
|
53
|
+
throw new Error('toPath() expects a string')
|
|
54
|
+
}
|
|
55
|
+
if (keysCache[key] == null) {
|
|
56
|
+
/**
|
|
57
|
+
* The following patch fixes issue 456, introduced since v4.20.3:
|
|
58
|
+
*
|
|
59
|
+
* Before v4.20.3, i.e. in v4.20.2, a `key` like 'choices[]' would map to ['choices']
|
|
60
|
+
* (e.g. an array of choices used where 'choices[]' is name attribute of an input of type checkbox).
|
|
61
|
+
*
|
|
62
|
+
* Since v4.20.3, a `key` like 'choices[]' would map to ['choices', ''] which is wrong and breaks
|
|
63
|
+
* this kind of inputs e.g. in React.
|
|
64
|
+
*
|
|
65
|
+
* v4.20.3 introduced an unwanted breaking change, this patch fixes it, see the issue at the link below.
|
|
66
|
+
*
|
|
67
|
+
* @see https://github.com/final-form/final-form/issues/456
|
|
68
|
+
*/
|
|
69
|
+
if (key.endsWith('[]')) {
|
|
70
|
+
// v4.20.2 (a `key` like 'choices[]' should map to ['choices'], which is fine).
|
|
71
|
+
keysCache[key] = key.split(keysRegex).filter(Boolean)
|
|
72
|
+
} else {
|
|
73
|
+
// v4.20.3 (a `key` like 'choices[]' maps to ['choices', ''], which breaks applications relying on inputs like `<input type="checkbox" name="choices[]" />`).
|
|
74
|
+
keysCache[key] = stringToPath(key)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return keysCache[key]
|
|
78
|
+
}
|
|
79
|
+
export const getDeepestMetadata = (state: Object, complexKey: string): any => {
|
|
80
|
+
// Intentionally using iteration rather than recursion
|
|
81
|
+
const path = toPath(complexKey)
|
|
82
|
+
let current: any = state
|
|
83
|
+
let metadata: any
|
|
84
|
+
for (let i = 0; i < path.length; i++) {
|
|
85
|
+
const key = path[i]
|
|
86
|
+
if (
|
|
87
|
+
current === undefined ||
|
|
88
|
+
current === null ||
|
|
89
|
+
typeof current !== 'object' ||
|
|
90
|
+
(Array.isArray(current) && isNaN(Number(key)))
|
|
91
|
+
) {
|
|
92
|
+
return undefined
|
|
93
|
+
}
|
|
94
|
+
const value = current[key]
|
|
95
|
+
if (value?._tina_metadata) {
|
|
96
|
+
metadata = value._tina_metadata
|
|
97
|
+
}
|
|
98
|
+
current = value
|
|
99
|
+
}
|
|
100
|
+
return metadata
|
|
101
|
+
}
|
|
102
|
+
export const getFormAndFieldNameFromMetadata = (
|
|
103
|
+
object: object,
|
|
104
|
+
eventFieldName: string
|
|
105
|
+
) => {
|
|
106
|
+
let formId
|
|
107
|
+
let n
|
|
108
|
+
const value = getDeepestMetadata(object, eventFieldName)
|
|
109
|
+
if (value) {
|
|
110
|
+
if (value.prefix) {
|
|
111
|
+
const fieldName = eventFieldName.slice(value?.prefix?.length + 1)
|
|
112
|
+
const localFieldName = value.name
|
|
113
|
+
? fieldName.slice(value?.name?.length + 1)
|
|
114
|
+
: fieldName
|
|
115
|
+
if (localFieldName) {
|
|
116
|
+
// If localFieldName is tags.2, just use `tags`
|
|
117
|
+
if (!isNaN(Number(localFieldName.split('.')[1]))) {
|
|
118
|
+
n = value.fields[localFieldName.split('.')[0]]
|
|
119
|
+
} else {
|
|
120
|
+
n = value.fields[localFieldName]
|
|
121
|
+
}
|
|
122
|
+
} else {
|
|
123
|
+
n = value.name
|
|
124
|
+
}
|
|
125
|
+
formId = value.id
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return { formId, fieldName: n }
|
|
129
|
+
}
|