@tinacms/app 1.2.12 → 1.2.13

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,17 @@
1
1
  # @tinacms/app
2
2
 
3
+ ## 1.2.13
4
+
5
+ ### Patch Changes
6
+
7
+ - 7f95c1ce5: Reorganize the way fields are presented on the form to allow for deep-linking
8
+ - Updated dependencies [5a6018916]
9
+ - Updated dependencies [63dd98904]
10
+ - Updated dependencies [b3d98d159]
11
+ - Updated dependencies [7f95c1ce5]
12
+ - tinacms@1.5.6
13
+ - @tinacms/toolkit@1.7.2
14
+
3
15
  ## 1.2.12
4
16
 
5
17
  ### 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.13",
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.2",
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.6",
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
  )
@@ -144,7 +220,7 @@ export const useGraphQLReducer = (
144
220
  setPayloads(updatedPayloads)
145
221
  })
146
222
  }
147
- }, [payloads.map(({ id }) => id).join('.'), cms])
223
+ }, [JSON.stringify(payloads), cms])
148
224
 
149
225
  const processPayload = React.useCallback(
150
226
  (payload: Payload) => {
@@ -152,6 +228,8 @@ export const useGraphQLReducer = (
152
228
  if (!expandedQueryForResolver || !expandedData) {
153
229
  throw new Error(`Unable to process payload which has not been expanded`)
154
230
  }
231
+ const formListItems: TinaState['formLists'][number]['items'] = []
232
+ const formIds: string[] = []
155
233
 
156
234
  const result = G.graphqlSync({
157
235
  schema: schemaForResolver,
@@ -189,6 +267,13 @@ export const useGraphQLReducer = (
189
267
  if (fieldName === '_values') {
190
268
  return source._internalValues
191
269
  }
270
+ if (info.fieldName === '_content_source') {
271
+ const pathArray = G.responsePathAsArray(info.path)
272
+ return {
273
+ queryId: payload.id,
274
+ path: pathArray.slice(0, pathArray.length - 1),
275
+ }
276
+ }
192
277
  if (info.fieldName === '_tina_metadata') {
193
278
  if (value) {
194
279
  return value
@@ -198,6 +283,27 @@ export const useGraphQLReducer = (
198
283
  return {
199
284
  id: null,
200
285
  fields: [],
286
+ prefix: '',
287
+ }
288
+ }
289
+ if (isConnectionType(info.returnType)) {
290
+ const name = G.getNamedType(info.returnType).name
291
+ const connectionCollection = tinaSchema
292
+ .getCollections()
293
+ .find((collection) => {
294
+ const collectionName = NAMER.referenceConnectionType(
295
+ collection.namespace
296
+ )
297
+ if (collectionName === name) {
298
+ return true
299
+ }
300
+ return false
301
+ })
302
+ if (connectionCollection) {
303
+ formListItems.push({
304
+ type: 'list',
305
+ label: connectionCollection.label || connectionCollection.name,
306
+ })
201
307
  }
202
308
  }
203
309
  if (isNodeType(info.returnType)) {
@@ -236,19 +342,37 @@ export const useGraphQLReducer = (
236
342
  resolvedDocument = documentSchema.parse(value)
237
343
  }
238
344
  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
- }
345
+ formIds.push(id)
346
+ const existingForm = cms.state.forms.find(
347
+ (f) => f.tinaForm.id === id
348
+ )
349
+
350
+ const pathArray = G.responsePathAsArray(info.path)
351
+ const pathString = pathArray.join('.')
352
+ const ancestors = formListItems.filter((item) => {
353
+ if (item.type === 'document') {
354
+ return pathString.startsWith(item.path)
355
+ }
356
+ })
357
+ const parent = ancestors[ancestors.length - 1]
358
+ if (parent) {
359
+ if (parent.type === 'document') {
360
+ parent.subItems.push({
361
+ type: 'document',
362
+ path: pathString,
363
+ formId: id,
364
+ subItems: [],
250
365
  })
366
+ }
367
+ } else {
368
+ formListItems.push({
369
+ type: 'document',
370
+ path: pathString,
371
+ formId: id,
372
+ subItems: [],
373
+ })
251
374
  }
375
+
252
376
  if (!existingForm) {
253
377
  const { form, template } = buildForm({
254
378
  resolvedDocument,
@@ -262,15 +386,25 @@ export const useGraphQLReducer = (
262
386
  },
263
387
  { values: true }
264
388
  )
265
- return resolveDocument(resolvedDocument, template, form)
389
+ return resolveDocument(
390
+ resolvedDocument,
391
+ template,
392
+ form,
393
+ pathString
394
+ )
266
395
  } else {
267
- existingForm.addQuery(payload.id)
396
+ existingForm.tinaForm.addQuery(payload.id)
268
397
  const { template } = getTemplateForDocument(
269
398
  resolvedDocument,
270
399
  tinaSchema
271
400
  )
272
- existingForm.addQuery(payload.id)
273
- return resolveDocument(resolvedDocument, template, existingForm)
401
+ existingForm.tinaForm.addQuery(payload.id)
402
+ return resolveDocument(
403
+ resolvedDocument,
404
+ template,
405
+ existingForm.tinaForm,
406
+ pathString
407
+ )
274
408
  }
275
409
  }
276
410
  return value
@@ -295,56 +429,94 @@ export const useGraphQLReducer = (
295
429
  }
296
430
  })
297
431
  } else {
432
+ if (result.data) {
433
+ setResults((results) => [
434
+ ...results.filter((result) => result.id !== payload.id),
435
+ { id: payload.id, data: result.data },
436
+ ])
437
+ }
438
+ const activeField = searchParams.get('active-field')
439
+ if (activeField) {
440
+ setSearchParams({})
441
+ const [queryId, eventFieldName] = activeField.split('---')
442
+ if (queryId === payload.id) {
443
+ if (result?.data) {
444
+ cms.dispatch({
445
+ type: 'forms:set-active-field-name',
446
+ value: getFormAndFieldNameFromMetadata(
447
+ result.data,
448
+ eventFieldName
449
+ ),
450
+ })
451
+ }
452
+ cms.dispatch({
453
+ type: 'sidebar:set-display-state',
454
+ value: 'openOrFull',
455
+ })
456
+ }
457
+ }
298
458
  iframe.current?.contentWindow?.postMessage({
299
459
  type: 'updateData',
300
460
  id: payload.id,
301
461
  data: result.data,
302
462
  })
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
463
  }
464
+ cms.dispatch({
465
+ type: 'form-lists:add',
466
+ value: {
467
+ id: payload.id,
468
+ label: 'Anonymous Query', // TODO: grab the name of the query if it exists
469
+ items: formListItems,
470
+ formIds,
471
+ },
472
+ })
322
473
  },
323
474
  [resolvedDocuments.map((doc) => doc._internalSys.path).join('.')]
324
475
  )
325
476
 
326
- const notifyEditMode = React.useCallback(
477
+ const handleMessage = React.useCallback(
327
478
  (event: MessageEvent<PostMessage>) => {
479
+ if (event?.data?.type === 'quick-edit') {
480
+ cms.dispatch({
481
+ type: 'set-quick-editing-supported',
482
+ value: event.data.value,
483
+ })
484
+ iframe.current?.contentWindow?.postMessage({
485
+ type: 'quickEditEnabled',
486
+ value: cms.state.sidebarDisplayState === 'open',
487
+ })
488
+ }
328
489
  if (event?.data?.type === 'isEditMode') {
329
490
  iframe?.current?.contentWindow?.postMessage({
330
491
  type: 'tina:editMode',
331
492
  })
332
493
  }
333
- },
334
- []
335
- )
336
- const handleOpenClose = React.useCallback(
337
- (event: MessageEvent<PostMessage>) => {
494
+ if (event.data.type === 'field:selected') {
495
+ const [queryId, eventFieldName] = event.data.fieldName.split('---')
496
+ const result = results.find((res) => res.id === queryId)
497
+ if (result?.data) {
498
+ cms.dispatch({
499
+ type: 'forms:set-active-field-name',
500
+ value: getFormAndFieldNameFromMetadata(result.data, eventFieldName),
501
+ })
502
+ }
503
+ cms.dispatch({
504
+ type: 'sidebar:set-display-state',
505
+ value: 'openOrFull',
506
+ })
507
+ }
338
508
  if (event.data.type === 'close') {
339
509
  const payloadSchema = z.object({ id: z.string() })
340
510
  const { id } = payloadSchema.parse(event.data)
341
511
  setPayloads((previous) =>
342
512
  previous.filter((payload) => payload.id !== id)
343
513
  )
514
+ setResults((previous) => previous.filter((result) => result.id !== id))
344
515
  cms.forms.all().map((form) => {
345
516
  form.removeQuery(id)
346
517
  })
347
518
  cms.removeOrphanedForms()
519
+ cms.dispatch({ type: 'form-lists:remove', value: id })
348
520
  }
349
521
  if (event.data.type === 'open') {
350
522
  const payloadSchema = z.object({
@@ -360,7 +532,7 @@ export const useGraphQLReducer = (
360
532
  ])
361
533
  }
362
534
  },
363
- [cms]
535
+ [cms, JSON.stringify(results)]
364
536
  )
365
537
 
366
538
  React.useEffect(() => {
@@ -374,22 +546,31 @@ export const useGraphQLReducer = (
374
546
  React.useEffect(() => {
375
547
  return () => {
376
548
  setPayloads([])
549
+ setResults([])
377
550
  cms.removeAllForms()
551
+ cms.dispatch({ type: 'form-lists:clear' })
378
552
  }
379
553
  }, [url])
380
554
 
381
555
  React.useEffect(() => {
556
+ iframe.current?.contentWindow?.postMessage({
557
+ type: 'quickEditEnabled',
558
+ value: cms.state.sidebarDisplayState === 'open',
559
+ })
560
+ }, [cms.state.sidebarDisplayState])
561
+
562
+ React.useEffect(() => {
563
+ cms.dispatch({ type: 'set-edit-mode', value: 'visual' })
382
564
  if (iframe) {
383
- window.addEventListener('message', handleOpenClose)
384
- window.addEventListener('message', notifyEditMode)
565
+ window.addEventListener('message', handleMessage)
385
566
  }
386
567
 
387
568
  return () => {
388
- window.removeEventListener('message', handleOpenClose)
389
- window.removeEventListener('message', notifyEditMode)
569
+ window.removeEventListener('message', handleMessage)
390
570
  cms.removeAllForms()
571
+ cms.dispatch({ type: 'set-edit-mode', value: 'basic' })
391
572
  }
392
- }, [iframe.current])
573
+ }, [iframe.current, JSON.stringify(results)])
393
574
  }
394
575
 
395
576
  const onSubmit = async (
@@ -427,7 +608,8 @@ type Path = (string | number)[]
427
608
  const resolveDocument = (
428
609
  doc: ResolvedDocument,
429
610
  template: Template<true>,
430
- form: Form
611
+ form: Form,
612
+ pathToDocument: string
431
613
  ): ResolvedDocument => {
432
614
  // @ts-ignore AnyField and TinaField don't mix
433
615
  const fields = form.fields as TinaField<true>[]
@@ -438,6 +620,7 @@ const resolveDocument = (
438
620
  values: form.values,
439
621
  path,
440
622
  id,
623
+ pathToDocument,
441
624
  })
442
625
  const metadataFields: Record<string, string> = {}
443
626
  Object.keys(formValues).forEach((key) => {
@@ -450,7 +633,9 @@ const resolveDocument = (
450
633
  sys: doc._internalSys,
451
634
  values: form.values,
452
635
  _tina_metadata: {
636
+ prefix: pathToDocument,
453
637
  id: doc._internalSys.path,
638
+ name: '',
454
639
  fields: metadataFields,
455
640
  },
456
641
  _internalSys: doc._internalSys,
@@ -464,12 +649,14 @@ const resolveFormValue = <T extends Record<string, unknown>>({
464
649
  values,
465
650
  path,
466
651
  id,
652
+ pathToDocument,
467
653
  }: // tinaSchema,
468
654
  {
469
655
  fields: TinaField<true>[]
470
656
  values: T
471
657
  path: Path
472
658
  id: string
659
+ pathToDocument: string
473
660
  // tinaSchema: TinaSchema
474
661
  }): T & { __typename?: string } => {
475
662
  const accum: Record<string, unknown> = {}
@@ -486,6 +673,7 @@ const resolveFormValue = <T extends Record<string, unknown>>({
486
673
  value: v,
487
674
  path,
488
675
  id,
676
+ pathToDocument,
489
677
  })
490
678
  })
491
679
  return accum as T & { __typename?: string }
@@ -495,11 +683,13 @@ const resolveFieldValue = ({
495
683
  value,
496
684
  path,
497
685
  id,
686
+ pathToDocument,
498
687
  }: {
499
688
  field: TinaField<true>
500
689
  value: unknown
501
690
  path: Path
502
691
  id: string
692
+ pathToDocument: string
503
693
  }) => {
504
694
  switch (field.type) {
505
695
  case 'object': {
@@ -520,13 +710,16 @@ const resolveFieldValue = ({
520
710
  __typename: NAMER.dataTypeName(template.namespace),
521
711
  _tina_metadata: {
522
712
  id,
713
+ name: nextPath.join('.'),
523
714
  fields: metadataFields,
715
+ prefix: pathToDocument,
524
716
  },
525
717
  ...resolveFormValue({
526
718
  fields: template.fields,
527
719
  values: item,
528
720
  path: nextPath,
529
721
  id,
722
+ pathToDocument,
530
723
  }),
531
724
  }
532
725
  })
@@ -555,13 +748,16 @@ const resolveFieldValue = ({
555
748
  __typename: NAMER.dataTypeName(field.namespace),
556
749
  _tina_metadata: {
557
750
  id,
751
+ name: nextPath.join('.'),
558
752
  fields: metadataFields,
753
+ prefix: pathToDocument,
559
754
  },
560
755
  ...resolveFormValue({
561
756
  fields: templateFields,
562
757
  values: item,
563
- path,
758
+ path: nextPath,
564
759
  id,
760
+ pathToDocument,
565
761
  }),
566
762
  }
567
763
  })
@@ -576,13 +772,16 @@ const resolveFieldValue = ({
576
772
  __typename: NAMER.dataTypeName(field.namespace),
577
773
  _tina_metadata: {
578
774
  id,
775
+ name: nextPath.join('.'),
579
776
  fields: metadataFields,
777
+ prefix: pathToDocument,
580
778
  },
581
779
  ...resolveFormValue({
582
780
  fields: templateFields,
583
781
  values: value as any,
584
- path,
782
+ path: nextPath,
585
783
  id,
784
+ pathToDocument,
586
785
  }),
587
786
  }
588
787
  }
@@ -645,7 +844,7 @@ const expandPayload = async (payload: Payload, cms: TinaCMS) => {
645
844
  documentNode,
646
845
  })
647
846
  const expandedQueryForResolver = G.print(expandedDocumentNodeForResolver)
648
- return { ...payload, expandQuery, expandedData, expandedQueryForResolver }
847
+ return { ...payload, expandedQuery, expandedData, expandedQueryForResolver }
649
848
  }
650
849
 
651
850
  /**
@@ -738,12 +937,10 @@ const buildForm = ({
738
937
  }
739
938
  if (form) {
740
939
  if (shouldRegisterForm) {
741
- form.subscribe(() => {}, { values: true })
742
940
  if (collection.ui?.global) {
743
941
  cms.plugins.add(new GlobalFormPlugin(form))
744
- } else {
745
- cms.forms.add(form)
746
942
  }
943
+ cms.dispatch({ type: 'forms:add', value: form })
747
944
  }
748
945
  }
749
946
  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
+ }