@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 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.12",
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.1",
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.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
@@ -47,11 +47,6 @@ const SetPreview = () => {
47
47
  ''
48
48
  )}`
49
49
  )
50
- // Override original 'rich-text' field with one that has raw mode support
51
- cms.fields.add({
52
- ...MdxFieldPluginExtendible,
53
- Component: Editor,
54
- })
55
50
  }, [])
56
51
  return null
57
52
  }
@@ -160,7 +160,7 @@ export const RawEditor = (props: RichTextType) => {
160
160
 
161
161
  return (
162
162
  <div className="relative">
163
- <div className="sticky -top-4 w-full flex justify-between mb-2 z-50 max-w-full">
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>
@@ -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
- [...(node.selectionSet?.selections || []), METADATA_FIELD] || [],
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, ZodError } from 'zod'
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
- }, [payloads.map(({ id }) => id).join('.'), cms])
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
- let existingForm = cms.forms.find(id)
240
- if (!existingForm) {
241
- cms.plugins
242
- .getType('screen')
243
- .all()
244
- .forEach((plugin) => {
245
- // @ts-ignore
246
- if (plugin?.form && plugin.form?.id === id) {
247
- // @ts-ignore
248
- existingForm = plugin.form
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(resolvedDocument, template, form)
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(resolvedDocument, template, existingForm)
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
- [resolvedDocuments.map((doc) => doc._internalSys.path).join('.')]
475
+ [
476
+ resolvedDocuments.map((doc) => doc._internalSys.path).join('.'),
477
+ activeField,
478
+ ]
324
479
  )
325
480
 
326
- const notifyEditMode = React.useCallback(
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
- const handleOpenClose = React.useCallback(
337
- (event: MessageEvent<PostMessage>) => {
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', handleOpenClose)
384
- window.addEventListener('message', notifyEditMode)
569
+ window.addEventListener('message', handleMessage)
385
570
  }
386
571
 
387
572
  return () => {
388
- window.removeEventListener('message', handleOpenClose)
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, expandQuery, expandedData, expandedQueryForResolver }
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
- const collection = tinaSchema.getCollectionByFullPath(id)
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
- type: 'open' | 'close' | 'isEditMode'
3
- id: string
4
- data: object
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
@@ -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
+ }