@tinacms/app 0.0.0-b720fb9-20241203044105 → 0.0.0-b7db4c2-20250424014613

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.
@@ -1,36 +1,36 @@
1
- import React from 'react'
2
- import * as G from 'graphql'
3
- import { getIn } from 'final-form'
4
- import { z } from 'zod'
5
1
  // @ts-expect-error
6
- import schemaJson from 'SCHEMA_IMPORT'
7
- import { expandQuery, isConnectionType, isNodeType } from './expand-query'
2
+ import schemaJson from 'SCHEMA_IMPORT';
3
+ import { getIn } from 'final-form';
4
+ import * as G from 'graphql';
5
+ import React from 'react';
6
+ import { useSearchParams } from 'react-router-dom';
8
7
  import {
8
+ Client,
9
+ Collection,
10
+ ErrorDialog,
9
11
  Form,
10
- TinaCMS,
12
+ FormOptions,
13
+ GlobalFormPlugin,
11
14
  NAMER,
12
- TinaSchema,
13
- useCMS,
14
- resolveField,
15
- Collection,
16
15
  Template,
16
+ TinaCMS,
17
17
  TinaField,
18
- Client,
19
- FormOptions,
20
- GlobalFormPlugin,
18
+ TinaSchema,
21
19
  TinaState,
22
- ErrorDialog,
23
- } from 'tinacms'
24
- import { createForm, createGlobalForm, FormifyCallback } from './build-form'
20
+ resolveField,
21
+ useCMS,
22
+ } from 'tinacms';
23
+ import { z } from 'zod';
24
+ import { FormifyCallback, createForm, createGlobalForm } from './build-form';
25
+ import { showErrorModal } from './errors';
26
+ import { expandQuery, isConnectionType, isNodeType } from './expand-query';
25
27
  import type {
26
- PostMessage,
27
28
  Payload,
28
- SystemInfo,
29
+ PostMessage,
29
30
  ResolvedDocument,
30
- } from './types'
31
- import { getFormAndFieldNameFromMetadata } from './util'
32
- import { useSearchParams } from 'react-router-dom'
33
- import { showErrorModal } from './errors'
31
+ SystemInfo,
32
+ } from './types';
33
+ import { getFormAndFieldNameFromMetadata } from './util';
34
34
 
35
35
  const sysSchema = z.object({
36
36
  breadcrumbs: z.array(z.string()),
@@ -50,14 +50,14 @@ const sysSchema = z.object({
50
50
  format: z.string().optional().nullable(),
51
51
  matches: z.string().optional().nullable(),
52
52
  }),
53
- })
53
+ });
54
54
 
55
55
  const documentSchema: z.ZodType<ResolvedDocument> = z.object({
56
56
  _internalValues: z.record(z.unknown()),
57
57
  _internalSys: sysSchema,
58
- })
58
+ });
59
59
 
60
- const astNode = schemaJson as G.DocumentNode
60
+ const astNode = schemaJson as G.DocumentNode;
61
61
  const astNodeWithMeta: G.DocumentNode = {
62
62
  ...astNode,
63
63
  definitions: astNode.definitions.map((def) => {
@@ -103,7 +103,7 @@ const astNodeWithMeta: G.DocumentNode = {
103
103
  },
104
104
  },
105
105
  ],
106
- }
106
+ };
107
107
  }
108
108
  if (def.kind === 'ObjectTypeDefinition') {
109
109
  return {
@@ -147,68 +147,68 @@ const astNodeWithMeta: G.DocumentNode = {
147
147
  },
148
148
  },
149
149
  ],
150
- }
150
+ };
151
151
  }
152
- return def
152
+ return def;
153
153
  }),
154
- }
155
- const schema = G.buildASTSchema(astNode)
156
- const schemaForResolver = G.buildASTSchema(astNodeWithMeta)
154
+ };
155
+ const schema = G.buildASTSchema(astNode);
156
+ const schemaForResolver = G.buildASTSchema(astNodeWithMeta);
157
157
 
158
158
  const isRejected = (
159
159
  input: PromiseSettledResult<unknown>
160
- ): input is PromiseRejectedResult => input.status === 'rejected'
160
+ ): input is PromiseRejectedResult => input.status === 'rejected';
161
161
 
162
162
  const isFulfilled = <T>(
163
163
  input: PromiseSettledResult<T>
164
- ): input is PromiseFulfilledResult<T> => input.status === 'fulfilled'
164
+ ): input is PromiseFulfilledResult<T> => input.status === 'fulfilled';
165
165
 
166
166
  export const useGraphQLReducer = (
167
167
  iframe: React.MutableRefObject<HTMLIFrameElement>,
168
168
  url: string
169
169
  ) => {
170
- const cms = useCMS()
171
- const tinaSchema = cms.api.tina.schema as TinaSchema
172
- const [payloads, setPayloads] = React.useState<Payload[]>([])
173
- const [requestErrors, setRequestErrors] = React.useState<string[]>([])
174
- const [searchParams, setSearchParams] = useSearchParams()
170
+ const cms = useCMS();
171
+ const tinaSchema = cms.api.tina.schema as TinaSchema;
172
+ const [payloads, setPayloads] = React.useState<Payload[]>([]);
173
+ const [requestErrors, setRequestErrors] = React.useState<string[]>([]);
174
+ const [searchParams, setSearchParams] = useSearchParams();
175
175
  const [results, setResults] = React.useState<
176
176
  {
177
- id: string
177
+ id: string;
178
178
  data:
179
179
  | {
180
- [key: string]: any
180
+ [key: string]: any;
181
181
  }
182
182
  | null
183
- | undefined
183
+ | undefined;
184
184
  }[]
185
- >([])
185
+ >([]);
186
186
  const [documentsToResolve, setDocumentsToResolve] = React.useState<string[]>(
187
187
  []
188
- )
188
+ );
189
189
  const [resolvedDocuments, setResolvedDocuments] = React.useState<
190
190
  ResolvedDocument[]
191
- >([])
192
- const [operationIndex, setOperationIndex] = React.useState(0)
191
+ >([]);
192
+ const [operationIndex, setOperationIndex] = React.useState(0);
193
193
 
194
- const activeField = searchParams.get('active-field')
194
+ const activeField = searchParams.get('active-field');
195
195
 
196
196
  React.useEffect(() => {
197
197
  const run = async () => {
198
198
  return Promise.all(
199
199
  documentsToResolve.map(async (documentId) => {
200
- return await getDocument(documentId, cms.api.tina)
200
+ return await getDocument(documentId, cms.api.tina);
201
201
  })
202
- )
203
- }
202
+ );
203
+ };
204
204
  if (documentsToResolve.length) {
205
205
  run().then((docs) => {
206
- setResolvedDocuments((resolvedDocs) => [...resolvedDocs, ...docs])
207
- setDocumentsToResolve([])
208
- setOperationIndex((i) => i + 1)
209
- })
206
+ setResolvedDocuments((resolvedDocs) => [...resolvedDocs, ...docs]);
207
+ setDocumentsToResolve([]);
208
+ setOperationIndex((i) => i + 1);
209
+ });
210
210
  }
211
- }, [documentsToResolve.join('.')])
211
+ }, [documentsToResolve.join('.')]);
212
212
 
213
213
  /**
214
214
  * Note: since React runs effects twice in development this will run twice for a given query
@@ -216,39 +216,41 @@ export const useGraphQLReducer = (
216
216
  */
217
217
  React.useEffect(() => {
218
218
  const run = async () => {
219
- setRequestErrors([])
219
+ setRequestErrors([]);
220
220
  // gather the errors and display an error message containing each error unique message
221
221
  return Promise.allSettled(
222
222
  payloads.map(async (payload) => {
223
223
  // This payload has already been expanded, skip it.
224
224
  if (payload.expandedQuery) {
225
- return payload
225
+ return payload;
226
226
  } else {
227
- const expandedPayload = await expandPayload(payload, cms)
228
- processPayload(expandedPayload)
229
- return expandedPayload
227
+ const expandedPayload = await expandPayload(payload, cms);
228
+ processPayload(expandedPayload);
229
+ return expandedPayload;
230
230
  }
231
231
  })
232
- )
233
- }
232
+ );
233
+ };
234
234
  if (payloads.length) {
235
235
  run().then((updatedPayloads) => {
236
- setPayloads(updatedPayloads.filter(isFulfilled).map((p) => p.value))
236
+ setPayloads(updatedPayloads.filter(isFulfilled).map((p) => p.value));
237
237
  setRequestErrors(
238
238
  updatedPayloads.filter(isRejected).map((p) => String(p.reason))
239
- )
240
- })
239
+ );
240
+ });
241
241
  }
242
- }, [JSON.stringify(payloads), cms])
242
+ }, [JSON.stringify(payloads), cms]);
243
243
 
244
244
  const processPayload = React.useCallback(
245
245
  (payload: Payload) => {
246
- const { expandedQueryForResolver, variables, expandedData } = payload
246
+ const { expandedQueryForResolver, variables, expandedData } = payload;
247
247
  if (!expandedQueryForResolver || !expandedData) {
248
- throw new Error(`Unable to process payload which has not been expanded`)
248
+ throw new Error(
249
+ `Unable to process payload which has not been expanded`
250
+ );
249
251
  }
250
- const formListItems: TinaState['formLists'][number]['items'] = []
251
- const formIds: string[] = []
252
+ const formListItems: TinaState['formLists'][number]['items'] = [];
253
+ const formIds: string[] = [];
252
254
 
253
255
  const result = G.graphqlSync({
254
256
  schema: schemaForResolver,
@@ -256,7 +258,7 @@ export const useGraphQLReducer = (
256
258
  variableValues: variables,
257
259
  rootValue: expandedData,
258
260
  fieldResolver: (source, args, context, info) => {
259
- const fieldName = info.fieldName
261
+ const fieldName = info.fieldName;
260
262
  /**
261
263
  * Since the `source` for this resolver is the query that
262
264
  * ran before passing it into `useTina`, we need to take aliases
@@ -265,35 +267,35 @@ export const useGraphQLReducer = (
265
267
  * solution as the `value` gets overwritten depending on the alias
266
268
  * query.
267
269
  */
268
- const aliases: string[] = []
270
+ const aliases: string[] = [];
269
271
  info.fieldNodes.forEach((fieldNode) => {
270
272
  if (fieldNode.alias) {
271
- aliases.push(fieldNode.alias.value)
273
+ aliases.push(fieldNode.alias.value);
272
274
  }
273
- })
274
- let value = source[fieldName] as unknown
275
+ });
276
+ let value = source[fieldName] as unknown;
275
277
  aliases.forEach((alias) => {
276
- const aliasValue = source[alias]
278
+ const aliasValue = source[alias];
277
279
  if (aliasValue) {
278
- value = aliasValue
280
+ value = aliasValue;
279
281
  }
280
- })
282
+ });
281
283
  if (fieldName === '_sys') {
282
- return source._internalSys
284
+ return source._internalSys;
283
285
  }
284
286
  if (fieldName === '_values') {
285
- return source._internalValues
287
+ return source._internalValues;
286
288
  }
287
289
  if (info.fieldName === '_content_source') {
288
- const pathArray = G.responsePathAsArray(info.path)
290
+ const pathArray = G.responsePathAsArray(info.path);
289
291
  return {
290
292
  queryId: payload.id,
291
293
  path: pathArray.slice(0, pathArray.length - 1),
292
- }
294
+ };
293
295
  }
294
296
  if (info.fieldName === '_tina_metadata') {
295
297
  if (value) {
296
- return value
298
+ return value;
297
299
  }
298
300
  // TODO: ensure all fields that have _tina_metadata
299
301
  // actually need it
@@ -301,77 +303,77 @@ export const useGraphQLReducer = (
301
303
  id: null,
302
304
  fields: [],
303
305
  prefix: '',
304
- }
306
+ };
305
307
  }
306
308
  if (isConnectionType(info.returnType)) {
307
- const name = G.getNamedType(info.returnType).name
309
+ const name = G.getNamedType(info.returnType).name;
308
310
  const connectionCollection = tinaSchema
309
311
  .getCollections()
310
312
  .find((collection) => {
311
313
  const collectionName = NAMER.referenceConnectionType(
312
314
  collection.namespace
313
- )
315
+ );
314
316
  if (collectionName === name) {
315
- return true
317
+ return true;
316
318
  }
317
- return false
318
- })
319
+ return false;
320
+ });
319
321
  if (connectionCollection) {
320
322
  formListItems.push({
321
323
  type: 'list',
322
324
  label: connectionCollection.label || connectionCollection.name,
323
- })
325
+ });
324
326
  }
325
327
  }
326
328
  if (isNodeType(info.returnType)) {
327
329
  if (!value) {
328
- return
330
+ return;
329
331
  }
330
- let resolvedDocument: ResolvedDocument
332
+ let resolvedDocument: ResolvedDocument;
331
333
  // This is a reference from another form
332
334
  if (typeof value === 'string') {
333
335
  const valueFromSetup = getIn(
334
336
  expandedData,
335
337
  G.responsePathAsArray(info.path).join('.')
336
- )
338
+ );
337
339
  const maybeResolvedDocument = resolvedDocuments.find(
338
340
  (doc) => doc._internalSys.path === value
339
- )
341
+ );
340
342
  // If we already have this document, use it.
341
343
  if (maybeResolvedDocument) {
342
- resolvedDocument = maybeResolvedDocument
344
+ resolvedDocument = maybeResolvedDocument;
343
345
  } else if (valueFromSetup) {
344
346
  // Else, even though in this context the value is a string because it's
345
347
  // resolved from a parent form, if the reference hasn't changed
346
348
  // from when we ran the setup query, we can avoid a data fetch
347
349
  // here and just grab it from the response
348
350
  const maybeResolvedDocument =
349
- documentSchema.parse(valueFromSetup)
351
+ documentSchema.parse(valueFromSetup);
350
352
  if (maybeResolvedDocument._internalSys.path === value) {
351
- resolvedDocument = maybeResolvedDocument
353
+ resolvedDocument = maybeResolvedDocument;
352
354
  } else {
353
- throw new NoFormError(`No form found`, value)
355
+ throw new NoFormError(`No form found`, value);
354
356
  }
355
357
  } else {
356
- throw new NoFormError(`No form found`, value)
358
+ throw new NoFormError(`No form found`, value);
357
359
  }
358
360
  } else {
359
- resolvedDocument = documentSchema.parse(value)
361
+ resolvedDocument = documentSchema.parse(value);
360
362
  }
361
- const id = resolvedDocument._internalSys.path
362
- formIds.push(id)
363
+ const id = resolvedDocument._internalSys.path;
364
+ formIds.push(id);
363
365
  const existingForm = cms.state.forms.find(
364
366
  (f) => f.tinaForm.id === id
365
- )
367
+ );
366
368
 
367
- const pathArray = G.responsePathAsArray(info.path)
368
- const pathString = pathArray.join('.')
369
+ const pathArray = G.responsePathAsArray(info.path);
370
+ const pathString = pathArray.join('.');
369
371
  const ancestors = formListItems.filter((item) => {
370
372
  if (item.type === 'document') {
371
- return pathString.startsWith(item.path)
373
+ return pathString.startsWith(item.path);
372
374
  }
373
- })
374
- const parent = ancestors[ancestors.length - 1]
375
+ });
376
+ const parent = ancestors[ancestors.length - 1];
375
377
  if (parent) {
376
378
  if (parent.type === 'document') {
377
379
  parent.subItems.push({
@@ -379,7 +381,7 @@ export const useGraphQLReducer = (
379
381
  path: pathString,
380
382
  formId: id,
381
383
  subItems: [],
382
- })
384
+ });
383
385
  }
384
386
  } else {
385
387
  formListItems.push({
@@ -387,7 +389,7 @@ export const useGraphQLReducer = (
387
389
  path: pathString,
388
390
  formId: id,
389
391
  subItems: [],
390
- })
392
+ });
391
393
  }
392
394
 
393
395
  if (!existingForm) {
@@ -396,65 +398,65 @@ export const useGraphQLReducer = (
396
398
  tinaSchema,
397
399
  payloadId: payload.id,
398
400
  cms,
399
- })
401
+ });
400
402
  form.subscribe(
401
403
  () => {
402
- setOperationIndex((i) => i + 1)
404
+ setOperationIndex((i) => i + 1);
403
405
  },
404
406
  { values: true }
405
- )
407
+ );
406
408
  return resolveDocument(
407
409
  resolvedDocument,
408
410
  template,
409
411
  form,
410
412
  pathString
411
- )
413
+ );
412
414
  } else {
413
- existingForm.tinaForm.addQuery(payload.id)
415
+ existingForm.tinaForm.addQuery(payload.id);
414
416
  const { template } = getTemplateForDocument(
415
417
  resolvedDocument,
416
418
  tinaSchema
417
- )
418
- existingForm.tinaForm.addQuery(payload.id)
419
+ );
420
+ existingForm.tinaForm.addQuery(payload.id);
419
421
  return resolveDocument(
420
422
  resolvedDocument,
421
423
  template,
422
424
  existingForm.tinaForm,
423
425
  pathString
424
- )
426
+ );
425
427
  }
426
428
  }
427
- return value
429
+ return value;
428
430
  },
429
- })
431
+ });
430
432
  if (result.errors) {
431
433
  result.errors.forEach((error) => {
432
434
  if (
433
435
  error instanceof G.GraphQLError &&
434
436
  error.originalError instanceof NoFormError
435
437
  ) {
436
- const id = error.originalError.id
438
+ const id = error.originalError.id;
437
439
  setDocumentsToResolve((docs) => [
438
440
  ...docs.filter((doc) => doc !== id),
439
441
  id,
440
- ])
442
+ ]);
441
443
  } else {
442
- console.log(error)
444
+ console.log(error);
443
445
  // throw new Error(
444
446
  // `Error processing value change, please contact support`
445
447
  // )
446
448
  }
447
- })
449
+ });
448
450
  } else {
449
451
  if (result.data) {
450
452
  setResults((results) => [
451
453
  ...results.filter((result) => result.id !== payload.id),
452
454
  { id: payload.id, data: result.data },
453
- ])
455
+ ]);
454
456
  }
455
457
  if (activeField) {
456
- setSearchParams({})
457
- const [queryId, eventFieldName] = activeField.split('---')
458
+ setSearchParams({});
459
+ const [queryId, eventFieldName] = activeField.split('---');
458
460
  if (queryId === payload.id) {
459
461
  if (result?.data) {
460
462
  cms.dispatch({
@@ -463,19 +465,19 @@ export const useGraphQLReducer = (
463
465
  result.data,
464
466
  eventFieldName
465
467
  ),
466
- })
468
+ });
467
469
  }
468
470
  cms.dispatch({
469
471
  type: 'sidebar:set-display-state',
470
472
  value: 'openOrFull',
471
- })
473
+ });
472
474
  }
473
475
  }
474
476
  iframe.current?.contentWindow?.postMessage({
475
477
  type: 'updateData',
476
478
  id: payload.id,
477
479
  data: result.data,
478
- })
480
+ });
479
481
  }
480
482
  cms.dispatch({
481
483
  type: 'form-lists:add',
@@ -485,57 +487,64 @@ export const useGraphQLReducer = (
485
487
  items: formListItems,
486
488
  formIds,
487
489
  },
488
- })
490
+ });
489
491
  },
490
492
  [
491
493
  resolvedDocuments.map((doc) => doc._internalSys.path).join('.'),
492
494
  activeField,
493
495
  ]
494
- )
496
+ );
495
497
 
496
498
  const handleMessage = React.useCallback(
497
499
  (event: MessageEvent<PostMessage>) => {
500
+ if (event.data.type === 'user-select-form') {
501
+ cms.dispatch({
502
+ type: 'forms:set-active-form-id',
503
+ value: event.data.formId,
504
+ });
505
+ }
506
+
498
507
  if (event?.data?.type === 'quick-edit') {
499
508
  cms.dispatch({
500
509
  type: 'set-quick-editing-supported',
501
510
  value: event.data.value,
502
- })
511
+ });
503
512
  iframe.current?.contentWindow?.postMessage({
504
513
  type: 'quickEditEnabled',
505
514
  value: cms.state.sidebarDisplayState === 'open',
506
- })
515
+ });
507
516
  }
508
517
  if (event?.data?.type === 'isEditMode') {
509
518
  iframe?.current?.contentWindow?.postMessage({
510
519
  type: 'tina:editMode',
511
- })
520
+ });
512
521
  }
513
522
  if (event.data.type === 'field:selected') {
514
- const [queryId, eventFieldName] = event.data.fieldName.split('---')
515
- const result = results.find((res) => res.id === queryId)
523
+ const [queryId, eventFieldName] = event.data.fieldName.split('---');
524
+ const result = results.find((res) => res.id === queryId);
516
525
  if (result?.data) {
517
526
  cms.dispatch({
518
527
  type: 'forms:set-active-field-name',
519
528
  value: getFormAndFieldNameFromMetadata(result.data, eventFieldName),
520
- })
529
+ });
521
530
  }
522
531
  cms.dispatch({
523
532
  type: 'sidebar:set-display-state',
524
533
  value: 'openOrFull',
525
- })
534
+ });
526
535
  }
527
536
  if (event.data.type === 'close') {
528
- const payloadSchema = z.object({ id: z.string() })
529
- const { id } = payloadSchema.parse(event.data)
537
+ const payloadSchema = z.object({ id: z.string() });
538
+ const { id } = payloadSchema.parse(event.data);
530
539
  setPayloads((previous) =>
531
540
  previous.filter((payload) => payload.id !== id)
532
- )
533
- setResults((previous) => previous.filter((result) => result.id !== id))
541
+ );
542
+ setResults((previous) => previous.filter((result) => result.id !== id));
534
543
  cms.forms.all().map((form) => {
535
- form.removeQuery(id)
536
- })
537
- cms.removeOrphanedForms()
538
- cms.dispatch({ type: 'form-lists:remove', value: id })
544
+ form.removeQuery(id);
545
+ });
546
+ cms.removeOrphanedForms();
547
+ cms.dispatch({ type: 'form-lists:remove', value: id });
539
548
  }
540
549
  if (event.data.type === 'open') {
541
550
  const payloadSchema = z.object({
@@ -543,60 +552,66 @@ export const useGraphQLReducer = (
543
552
  query: z.string(),
544
553
  variables: z.record(z.unknown()),
545
554
  data: z.record(z.unknown()),
546
- })
547
- const payload = payloadSchema.parse(event.data)
555
+ });
556
+ const payload = payloadSchema.parse(event.data);
548
557
  setPayloads((payloads) => [
549
558
  ...payloads.filter(({ id }) => id !== payload.id),
550
559
  payload,
551
- ])
560
+ ]);
561
+ }
562
+ if (event.data.type === 'url-changed') {
563
+ cms.dispatch({
564
+ type: 'sidebar:set-loading-state',
565
+ value: true,
566
+ });
552
567
  }
553
568
  },
554
569
  [cms, JSON.stringify(results)]
555
- )
570
+ );
556
571
 
557
572
  React.useEffect(() => {
558
573
  payloads.forEach((payload) => {
559
574
  if (payload.expandedData) {
560
- processPayload(payload)
575
+ processPayload(payload);
561
576
  }
562
- })
563
- }, [operationIndex])
577
+ });
578
+ }, [operationIndex]);
564
579
 
565
580
  React.useEffect(() => {
566
581
  return () => {
567
- setPayloads([])
568
- setResults([])
569
- cms.removeAllForms()
570
- cms.dispatch({ type: 'form-lists:clear' })
571
- }
572
- }, [url])
582
+ setPayloads([]);
583
+ setResults([]);
584
+ cms.removeAllForms();
585
+ cms.dispatch({ type: 'form-lists:clear' });
586
+ };
587
+ }, [url]);
573
588
 
574
589
  React.useEffect(() => {
575
590
  iframe.current?.contentWindow?.postMessage({
576
591
  type: 'quickEditEnabled',
577
592
  value: cms.state.sidebarDisplayState === 'open',
578
- })
579
- }, [cms.state.sidebarDisplayState])
593
+ });
594
+ }, [cms.state.sidebarDisplayState]);
580
595
 
581
596
  React.useEffect(() => {
582
- cms.dispatch({ type: 'set-edit-mode', value: 'visual' })
597
+ cms.dispatch({ type: 'set-edit-mode', value: 'visual' });
583
598
  if (iframe) {
584
- window.addEventListener('message', handleMessage)
599
+ window.addEventListener('message', handleMessage);
585
600
  }
586
601
 
587
602
  return () => {
588
- window.removeEventListener('message', handleMessage)
589
- cms.removeAllForms()
590
- cms.dispatch({ type: 'set-edit-mode', value: 'basic' })
591
- }
592
- }, [iframe.current, JSON.stringify(results)])
603
+ window.removeEventListener('message', handleMessage);
604
+ cms.removeAllForms();
605
+ cms.dispatch({ type: 'set-edit-mode', value: 'basic' });
606
+ };
607
+ }, [iframe.current, JSON.stringify(results)]);
593
608
 
594
609
  React.useEffect(() => {
595
610
  if (requestErrors.length) {
596
- showErrorModal('Unexpected error querying content', requestErrors, cms)
611
+ showErrorModal('Unexpected error querying content', requestErrors, cms);
597
612
  }
598
- }, [requestErrors])
599
- }
613
+ }, [requestErrors]);
614
+ };
600
615
 
601
616
  const onSubmit = async (
602
617
  collection: Collection<true>,
@@ -604,7 +619,7 @@ const onSubmit = async (
604
619
  payload: Record<string, unknown>,
605
620
  cms: TinaCMS
606
621
  ) => {
607
- const tinaSchema = cms.api.tina.schema
622
+ const tinaSchema = cms.api.tina.schema;
608
623
  try {
609
624
  const mutationString = `#graphql
610
625
  mutation UpdateDocument($collection: String!, $relativePath: String!, $params: DocumentUpdateMutation!) {
@@ -612,7 +627,7 @@ const onSubmit = async (
612
627
  __typename
613
628
  }
614
629
  }
615
- `
630
+ `;
616
631
 
617
632
  await cms.api.tina.request(mutationString, {
618
633
  variables: {
@@ -620,8 +635,8 @@ const onSubmit = async (
620
635
  relativePath: relativePath,
621
636
  params: tinaSchema.transformPayload(collection.name, payload),
622
637
  },
623
- })
624
- cms.alerts.success('Document saved!')
638
+ });
639
+ cms.alerts.success('Document saved!');
625
640
  } catch (e) {
626
641
  cms.alerts.error(() =>
627
642
  ErrorDialog({
@@ -629,12 +644,12 @@ const onSubmit = async (
629
644
  message: 'Tina caught an error while updating the page',
630
645
  error: e,
631
646
  })
632
- )
633
- console.error(e)
647
+ );
648
+ console.error(e);
634
649
  }
635
- }
650
+ };
636
651
 
637
- type Path = (string | number)[]
652
+ type Path = (string | number)[];
638
653
 
639
654
  const resolveDocument = (
640
655
  doc: ResolvedDocument,
@@ -643,20 +658,20 @@ const resolveDocument = (
643
658
  pathToDocument: string
644
659
  ): ResolvedDocument => {
645
660
  // @ts-ignore AnyField and TinaField don't mix
646
- const fields = form.fields as TinaField<true>[]
647
- const id = doc._internalSys.path
648
- const path: Path = []
661
+ const fields = form.fields as TinaField<true>[];
662
+ const id = doc._internalSys.path;
663
+ const path: Path = [];
649
664
  const formValues = resolveFormValue({
650
665
  fields: fields,
651
666
  values: form.values,
652
667
  path,
653
668
  id,
654
669
  pathToDocument,
655
- })
656
- const metadataFields: Record<string, string> = {}
670
+ });
671
+ const metadataFields: Record<string, string> = {};
657
672
  Object.keys(formValues).forEach((key) => {
658
- metadataFields[key] = [...path, key].join('.')
659
- })
673
+ metadataFields[key] = [...path, key].join('.');
674
+ });
660
675
 
661
676
  return {
662
677
  ...formValues,
@@ -672,8 +687,8 @@ const resolveDocument = (
672
687
  _internalSys: doc._internalSys,
673
688
  _internalValues: doc._internalValues,
674
689
  __typename: NAMER.dataTypeName(template.namespace),
675
- }
676
- }
690
+ };
691
+ };
677
692
 
678
693
  const resolveFormValue = <T extends Record<string, unknown>>({
679
694
  fields,
@@ -683,21 +698,21 @@ const resolveFormValue = <T extends Record<string, unknown>>({
683
698
  pathToDocument,
684
699
  }: // tinaSchema,
685
700
  {
686
- fields: TinaField<true>[]
687
- values: T
688
- path: Path
689
- id: string
690
- pathToDocument: string
701
+ fields: TinaField<true>[];
702
+ values: T;
703
+ path: Path;
704
+ id: string;
705
+ pathToDocument: string;
691
706
  // tinaSchema: TinaSchema
692
707
  }): T & { __typename?: string } => {
693
- const accum: Record<string, unknown> = {}
708
+ const accum: Record<string, unknown> = {};
694
709
  fields.forEach((field) => {
695
- const v = values[field.name]
710
+ const v = values[field.name];
696
711
  if (typeof v === 'undefined') {
697
- return
712
+ return;
698
713
  }
699
714
  if (v === null) {
700
- return
715
+ return;
701
716
  }
702
717
  accum[field.name] = resolveFieldValue({
703
718
  field,
@@ -705,10 +720,10 @@ const resolveFormValue = <T extends Record<string, unknown>>({
705
720
  path,
706
721
  id,
707
722
  pathToDocument,
708
- })
709
- })
710
- return accum as T & { __typename?: string }
711
- }
723
+ });
724
+ });
725
+ return accum as T & { __typename?: string };
726
+ };
712
727
  const resolveFieldValue = ({
713
728
  field,
714
729
  value,
@@ -716,11 +731,11 @@ const resolveFieldValue = ({
716
731
  id,
717
732
  pathToDocument,
718
733
  }: {
719
- field: TinaField<true>
720
- value: unknown
721
- path: Path
722
- id: string
723
- pathToDocument: string
734
+ field: TinaField<true>;
735
+ value: unknown;
736
+ path: Path;
737
+ id: string;
738
+ pathToDocument: string;
724
739
  }) => {
725
740
  switch (field.type) {
726
741
  case 'object': {
@@ -728,15 +743,17 @@ const resolveFieldValue = ({
728
743
  if (field.list) {
729
744
  if (Array.isArray(value)) {
730
745
  return value.map((item, index) => {
731
- const template = field.templates[item._template]
746
+ const template = field.templates[item._template];
732
747
  if (typeof template === 'string') {
733
- throw new Error('Global templates not supported')
748
+ throw new Error('Global templates not supported');
734
749
  }
735
- const nextPath = [...path, field.name, index]
736
- const metadataFields: Record<string, string> = {}
750
+ const nextPath = [...path, field.name, index];
751
+ const metadataFields: Record<string, string> = {};
737
752
  template.fields.forEach((field) => {
738
- metadataFields[field.name] = [...nextPath, field.name].join('.')
739
- })
753
+ metadataFields[field.name] = [...nextPath, field.name].join(
754
+ '.'
755
+ );
756
+ });
740
757
  return {
741
758
  __typename: NAMER.dataTypeName(template.namespace),
742
759
  _tina_metadata: {
@@ -752,29 +769,29 @@ const resolveFieldValue = ({
752
769
  id,
753
770
  pathToDocument,
754
771
  }),
755
- }
756
- })
772
+ };
773
+ });
757
774
  }
758
775
  } else {
759
776
  // not implemented
760
777
  }
761
778
  }
762
779
 
763
- const templateFields = field.fields
780
+ const templateFields = field.fields;
764
781
  if (typeof templateFields === 'string') {
765
- throw new Error('Global templates not supported')
782
+ throw new Error('Global templates not supported');
766
783
  }
767
784
  if (!templateFields) {
768
- throw new Error(`Expected to find sub-fields on field ${field.name}`)
785
+ throw new Error(`Expected to find sub-fields on field ${field.name}`);
769
786
  }
770
787
  if (field.list) {
771
788
  if (Array.isArray(value)) {
772
789
  return value.map((item, index) => {
773
- const nextPath = [...path, field.name, index]
774
- const metadataFields: Record<string, string> = {}
790
+ const nextPath = [...path, field.name, index];
791
+ const metadataFields: Record<string, string> = {};
775
792
  templateFields.forEach((field) => {
776
- metadataFields[field.name] = [...nextPath, field.name].join('.')
777
- })
793
+ metadataFields[field.name] = [...nextPath, field.name].join('.');
794
+ });
778
795
  return {
779
796
  __typename: NAMER.dataTypeName(field.namespace),
780
797
  _tina_metadata: {
@@ -790,15 +807,15 @@ const resolveFieldValue = ({
790
807
  id,
791
808
  pathToDocument,
792
809
  }),
793
- }
794
- })
810
+ };
811
+ });
795
812
  }
796
813
  } else {
797
- const nextPath = [...path, field.name]
798
- const metadataFields: Record<string, string> = {}
814
+ const nextPath = [...path, field.name];
815
+ const metadataFields: Record<string, string> = {};
799
816
  templateFields.forEach((field) => {
800
- metadataFields[field.name] = [...nextPath, field.name].join('.')
801
- })
817
+ metadataFields[field.name] = [...nextPath, field.name].join('.');
818
+ });
802
819
  return {
803
820
  __typename: NAMER.dataTypeName(field.namespace),
804
821
  _tina_metadata: {
@@ -814,18 +831,21 @@ const resolveFieldValue = ({
814
831
  id,
815
832
  pathToDocument,
816
833
  }),
817
- }
834
+ };
818
835
  }
819
836
  }
820
837
  default: {
821
- return value
838
+ return value;
822
839
  }
823
840
  }
824
- }
841
+ };
825
842
 
826
843
  const getDocument = async (id: string, tina: Client) => {
827
844
  const response = await tina.request<{
828
- node: { _internalSys: SystemInfo; _internalValues: Record<string, unknown> }
845
+ node: {
846
+ _internalSys: SystemInfo;
847
+ _internalValues: Record<string, unknown>;
848
+ };
829
849
  }>(
830
850
  `query GetNode($id: String!) {
831
851
  node(id: $id) {
@@ -858,29 +878,29 @@ _internalSys: _sys {
858
878
  }
859
879
  }`,
860
880
  { variables: { id: id } }
861
- )
862
- return response.node
863
- }
881
+ );
882
+ return response.node;
883
+ };
864
884
 
865
885
  const expandPayload = async (
866
886
  payload: Payload,
867
887
  cms: TinaCMS
868
888
  ): Promise<Payload> => {
869
- const { query, variables } = payload
870
- const documentNode = G.parse(query)
871
- const expandedDocumentNode = expandQuery({ schema, documentNode })
872
- const expandedQuery = G.print(expandedDocumentNode)
889
+ const { query, variables } = payload;
890
+ const documentNode = G.parse(query);
891
+ const expandedDocumentNode = expandQuery({ schema, documentNode });
892
+ const expandedQuery = G.print(expandedDocumentNode);
873
893
  const expandedData = await cms.api.tina.request<object>(expandedQuery, {
874
894
  variables,
875
- })
895
+ });
876
896
 
877
897
  const expandedDocumentNodeForResolver = expandQuery({
878
898
  schema: schemaForResolver,
879
899
  documentNode,
880
- })
881
- const expandedQueryForResolver = G.print(expandedDocumentNodeForResolver)
882
- return { ...payload, expandedQuery, expandedData, expandedQueryForResolver }
883
- }
900
+ });
901
+ const expandedQueryForResolver = G.print(expandedDocumentNodeForResolver);
902
+ return { ...payload, expandedQuery, expandedData, expandedQueryForResolver };
903
+ };
884
904
 
885
905
  /**
886
906
  * When we resolve the graphql data we check for these errors,
@@ -888,11 +908,11 @@ const expandPayload = async (
888
908
  * process it once we have that document
889
909
  */
890
910
  class NoFormError extends Error {
891
- id: string
911
+ id: string;
892
912
  constructor(msg: string, id: string) {
893
- super(msg)
894
- this.id = id
895
- Object.setPrototypeOf(this, NoFormError.prototype)
913
+ super(msg);
914
+ this.id = id;
915
+ Object.setPrototypeOf(this, NoFormError.prototype);
896
916
  }
897
917
  }
898
918
 
@@ -900,22 +920,22 @@ const getTemplateForDocument = (
900
920
  resolvedDocument: ResolvedDocument,
901
921
  tinaSchema: TinaSchema
902
922
  ) => {
903
- const id = resolvedDocument._internalSys.path
904
- let collection: Collection<true> | undefined
923
+ const id = resolvedDocument._internalSys.path;
924
+ let collection: Collection<true> | undefined;
905
925
  try {
906
- collection = tinaSchema.getCollectionByFullPath(id)
926
+ collection = tinaSchema.getCollectionByFullPath(id);
907
927
  } catch (e) {}
908
928
 
909
929
  if (!collection) {
910
- throw new Error(`Unable to determine collection for path ${id}`)
930
+ throw new Error(`Unable to determine collection for path ${id}`);
911
931
  }
912
932
 
913
933
  const template = tinaSchema.getTemplateForData({
914
934
  data: resolvedDocument._internalValues,
915
935
  collection,
916
- })
917
- return { template, collection }
918
- }
936
+ });
937
+ return { template, collection };
938
+ };
919
939
 
920
940
  const buildForm = ({
921
941
  resolvedDocument,
@@ -923,18 +943,18 @@ const buildForm = ({
923
943
  payloadId,
924
944
  cms,
925
945
  }: {
926
- resolvedDocument: ResolvedDocument
927
- tinaSchema: TinaSchema
928
- payloadId: string
929
- cms: TinaCMS
946
+ resolvedDocument: ResolvedDocument;
947
+ tinaSchema: TinaSchema;
948
+ payloadId: string;
949
+ cms: TinaCMS;
930
950
  }) => {
931
951
  const { template, collection } = getTemplateForDocument(
932
952
  resolvedDocument,
933
953
  tinaSchema
934
- )
935
- const id = resolvedDocument._internalSys.path
936
- let form: Form | undefined
937
- let shouldRegisterForm = true
954
+ );
955
+ const id = resolvedDocument._internalSys.path;
956
+ let form: Form | undefined;
957
+ let shouldRegisterForm = true;
938
958
  const formConfig: FormOptions<any> = {
939
959
  id,
940
960
  initialValues: resolvedDocument._internalValues,
@@ -947,10 +967,10 @@ const buildForm = ({
947
967
  cms
948
968
  ),
949
969
  label: collection.label || collection.name,
950
- }
970
+ };
951
971
  if (tinaSchema.config.config?.formifyCallback) {
952
972
  const callback = tinaSchema.config.config
953
- ?.formifyCallback as FormifyCallback
973
+ ?.formifyCallback as FormifyCallback;
954
974
  form =
955
975
  callback(
956
976
  {
@@ -960,30 +980,30 @@ const buildForm = ({
960
980
  formConfig,
961
981
  },
962
982
  cms
963
- ) || undefined
983
+ ) || undefined;
964
984
  if (!form) {
965
985
  // If the form isn't created from formify, we still
966
986
  // need it, just don't show it to the user.
967
- shouldRegisterForm = false
968
- form = new Form(formConfig)
987
+ shouldRegisterForm = false;
988
+ form = new Form(formConfig);
969
989
  }
970
990
  } else {
971
991
  if (collection.ui?.global) {
972
- form = createGlobalForm(formConfig)
992
+ form = createGlobalForm(formConfig);
973
993
  } else {
974
- form = createForm(formConfig)
994
+ form = createForm(formConfig);
975
995
  }
976
996
  }
977
997
  if (form) {
978
998
  if (shouldRegisterForm) {
979
999
  if (collection.ui?.global) {
980
- cms.plugins.add(new GlobalFormPlugin(form))
1000
+ cms.plugins.add(new GlobalFormPlugin(form));
981
1001
  }
982
- cms.dispatch({ type: 'forms:add', value: form })
1002
+ cms.dispatch({ type: 'forms:add', value: form });
983
1003
  }
984
1004
  }
985
1005
  if (!form) {
986
- throw new Error(`No form registered for ${id}.`)
1006
+ throw new Error(`No form registered for ${id}.`);
987
1007
  }
988
- return { template, form }
989
- }
1008
+ return { template, form };
1009
+ };