@tinacms/app 0.0.0-ad9a889-20250218035304 → 0.0.0-b25c5f0-20250618074247

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()),
@@ -45,19 +45,19 @@ const sysSchema = z.object({
45
45
  collection: z.object({
46
46
  name: z.string(),
47
47
  slug: z.string(),
48
- label: z.string(),
48
+ label: z.string().optional().nullable(),
49
49
  path: z.string(),
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,80 @@ export const useGraphQLReducer = (
301
303
  id: null,
302
304
  fields: [],
303
305
  prefix: '',
304
- }
306
+ };
305
307
  }
308
+
306
309
  if (isConnectionType(info.returnType)) {
307
- const name = G.getNamedType(info.returnType).name
310
+ const name = G.getNamedType(info.returnType).name;
308
311
  const connectionCollection = tinaSchema
309
312
  .getCollections()
310
313
  .find((collection) => {
311
314
  const collectionName = NAMER.referenceConnectionType(
312
315
  collection.namespace
313
- )
316
+ );
314
317
  if (collectionName === name) {
315
- return true
318
+ return true;
316
319
  }
317
- return false
318
- })
320
+ return false;
321
+ });
319
322
  if (connectionCollection) {
320
323
  formListItems.push({
321
324
  type: 'list',
322
325
  label: connectionCollection.label || connectionCollection.name,
323
- })
326
+ });
324
327
  }
325
328
  }
326
329
  if (isNodeType(info.returnType)) {
327
330
  if (!value) {
328
- return
331
+ return;
329
332
  }
330
- let resolvedDocument: ResolvedDocument
333
+ let resolvedDocument: ResolvedDocument;
331
334
  // This is a reference from another form
332
335
  if (typeof value === 'string') {
333
336
  const valueFromSetup = getIn(
334
337
  expandedData,
335
338
  G.responsePathAsArray(info.path).join('.')
336
- )
339
+ );
337
340
  const maybeResolvedDocument = resolvedDocuments.find(
338
341
  (doc) => doc._internalSys.path === value
339
- )
342
+ );
343
+
340
344
  // If we already have this document, use it.
341
345
  if (maybeResolvedDocument) {
342
- resolvedDocument = maybeResolvedDocument
346
+ resolvedDocument = maybeResolvedDocument;
343
347
  } else if (valueFromSetup) {
344
348
  // Else, even though in this context the value is a string because it's
345
349
  // resolved from a parent form, if the reference hasn't changed
346
350
  // from when we ran the setup query, we can avoid a data fetch
347
351
  // here and just grab it from the response
348
352
  const maybeResolvedDocument =
349
- documentSchema.parse(valueFromSetup)
353
+ documentSchema.parse(valueFromSetup);
354
+
350
355
  if (maybeResolvedDocument._internalSys.path === value) {
351
- resolvedDocument = maybeResolvedDocument
356
+ resolvedDocument = maybeResolvedDocument;
352
357
  } else {
353
- throw new NoFormError(`No form found`, value)
358
+ throw new NoFormError(`No form found`, value);
354
359
  }
355
360
  } else {
356
- throw new NoFormError(`No form found`, value)
361
+ throw new NoFormError(`No form found`, value);
357
362
  }
358
363
  } else {
359
- resolvedDocument = documentSchema.parse(value)
364
+ resolvedDocument = documentSchema.parse(value);
360
365
  }
361
- const id = resolvedDocument._internalSys.path
362
- formIds.push(id)
366
+ const id = resolvedDocument._internalSys.path;
367
+ formIds.push(id);
363
368
  const existingForm = cms.state.forms.find(
364
369
  (f) => f.tinaForm.id === id
365
- )
370
+ );
366
371
 
367
- const pathArray = G.responsePathAsArray(info.path)
368
- const pathString = pathArray.join('.')
372
+ const pathArray = G.responsePathAsArray(info.path);
373
+ const pathString = pathArray.join('.');
369
374
  const ancestors = formListItems.filter((item) => {
370
375
  if (item.type === 'document') {
371
- return pathString.startsWith(item.path)
376
+ return pathString.startsWith(item.path);
372
377
  }
373
- })
374
- const parent = ancestors[ancestors.length - 1]
378
+ });
379
+ const parent = ancestors[ancestors.length - 1];
375
380
  if (parent) {
376
381
  if (parent.type === 'document') {
377
382
  parent.subItems.push({
@@ -379,7 +384,7 @@ export const useGraphQLReducer = (
379
384
  path: pathString,
380
385
  formId: id,
381
386
  subItems: [],
382
- })
387
+ });
383
388
  }
384
389
  } else {
385
390
  formListItems.push({
@@ -387,7 +392,7 @@ export const useGraphQLReducer = (
387
392
  path: pathString,
388
393
  formId: id,
389
394
  subItems: [],
390
- })
395
+ });
391
396
  }
392
397
 
393
398
  if (!existingForm) {
@@ -396,65 +401,65 @@ export const useGraphQLReducer = (
396
401
  tinaSchema,
397
402
  payloadId: payload.id,
398
403
  cms,
399
- })
404
+ });
400
405
  form.subscribe(
401
406
  () => {
402
- setOperationIndex((i) => i + 1)
407
+ setOperationIndex((i) => i + 1);
403
408
  },
404
409
  { values: true }
405
- )
410
+ );
406
411
  return resolveDocument(
407
412
  resolvedDocument,
408
413
  template,
409
414
  form,
410
415
  pathString
411
- )
416
+ );
412
417
  } else {
413
- existingForm.tinaForm.addQuery(payload.id)
418
+ existingForm.tinaForm.addQuery(payload.id);
414
419
  const { template } = getTemplateForDocument(
415
420
  resolvedDocument,
416
421
  tinaSchema
417
- )
418
- existingForm.tinaForm.addQuery(payload.id)
422
+ );
423
+ existingForm.tinaForm.addQuery(payload.id);
419
424
  return resolveDocument(
420
425
  resolvedDocument,
421
426
  template,
422
427
  existingForm.tinaForm,
423
428
  pathString
424
- )
429
+ );
425
430
  }
426
431
  }
427
- return value
432
+ return value;
428
433
  },
429
- })
434
+ });
430
435
  if (result.errors) {
431
436
  result.errors.forEach((error) => {
432
437
  if (
433
438
  error instanceof G.GraphQLError &&
434
439
  error.originalError instanceof NoFormError
435
440
  ) {
436
- const id = error.originalError.id
441
+ const id = error.originalError.id;
437
442
  setDocumentsToResolve((docs) => [
438
443
  ...docs.filter((doc) => doc !== id),
439
444
  id,
440
- ])
445
+ ]);
441
446
  } else {
442
- console.log(error)
447
+ console.log(error);
443
448
  // throw new Error(
444
449
  // `Error processing value change, please contact support`
445
450
  // )
446
451
  }
447
- })
452
+ });
448
453
  } else {
449
454
  if (result.data) {
450
455
  setResults((results) => [
451
456
  ...results.filter((result) => result.id !== payload.id),
452
457
  { id: payload.id, data: result.data },
453
- ])
458
+ ]);
454
459
  }
455
460
  if (activeField) {
456
- setSearchParams({})
457
- const [queryId, eventFieldName] = activeField.split('---')
461
+ setSearchParams({});
462
+ const [queryId, eventFieldName] = activeField.split('---');
458
463
  if (queryId === payload.id) {
459
464
  if (result?.data) {
460
465
  cms.dispatch({
@@ -463,19 +468,19 @@ export const useGraphQLReducer = (
463
468
  result.data,
464
469
  eventFieldName
465
470
  ),
466
- })
471
+ });
467
472
  }
468
473
  cms.dispatch({
469
474
  type: 'sidebar:set-display-state',
470
475
  value: 'openOrFull',
471
- })
476
+ });
472
477
  }
473
478
  }
474
479
  iframe.current?.contentWindow?.postMessage({
475
480
  type: 'updateData',
476
481
  id: payload.id,
477
482
  data: result.data,
478
- })
483
+ });
479
484
  }
480
485
  cms.dispatch({
481
486
  type: 'form-lists:add',
@@ -485,13 +490,13 @@ export const useGraphQLReducer = (
485
490
  items: formListItems,
486
491
  formIds,
487
492
  },
488
- })
493
+ });
489
494
  },
490
495
  [
491
496
  resolvedDocuments.map((doc) => doc._internalSys.path).join('.'),
492
497
  activeField,
493
498
  ]
494
- )
499
+ );
495
500
 
496
501
  const handleMessage = React.useCallback(
497
502
  (event: MessageEvent<PostMessage>) => {
@@ -499,50 +504,50 @@ export const useGraphQLReducer = (
499
504
  cms.dispatch({
500
505
  type: 'forms:set-active-form-id',
501
506
  value: event.data.formId,
502
- })
507
+ });
503
508
  }
504
509
 
505
510
  if (event?.data?.type === 'quick-edit') {
506
511
  cms.dispatch({
507
512
  type: 'set-quick-editing-supported',
508
513
  value: event.data.value,
509
- })
514
+ });
510
515
  iframe.current?.contentWindow?.postMessage({
511
516
  type: 'quickEditEnabled',
512
517
  value: cms.state.sidebarDisplayState === 'open',
513
- })
518
+ });
514
519
  }
515
520
  if (event?.data?.type === 'isEditMode') {
516
521
  iframe?.current?.contentWindow?.postMessage({
517
522
  type: 'tina:editMode',
518
- })
523
+ });
519
524
  }
520
525
  if (event.data.type === 'field:selected') {
521
- const [queryId, eventFieldName] = event.data.fieldName.split('---')
522
- const result = results.find((res) => res.id === queryId)
526
+ const [queryId, eventFieldName] = event.data.fieldName.split('---');
527
+ const result = results.find((res) => res.id === queryId);
523
528
  if (result?.data) {
524
529
  cms.dispatch({
525
530
  type: 'forms:set-active-field-name',
526
531
  value: getFormAndFieldNameFromMetadata(result.data, eventFieldName),
527
- })
532
+ });
528
533
  }
529
534
  cms.dispatch({
530
535
  type: 'sidebar:set-display-state',
531
536
  value: 'openOrFull',
532
- })
537
+ });
533
538
  }
534
539
  if (event.data.type === 'close') {
535
- const payloadSchema = z.object({ id: z.string() })
536
- const { id } = payloadSchema.parse(event.data)
540
+ const payloadSchema = z.object({ id: z.string() });
541
+ const { id } = payloadSchema.parse(event.data);
537
542
  setPayloads((previous) =>
538
543
  previous.filter((payload) => payload.id !== id)
539
- )
540
- setResults((previous) => previous.filter((result) => result.id !== id))
544
+ );
545
+ setResults((previous) => previous.filter((result) => result.id !== id));
541
546
  cms.forms.all().map((form) => {
542
- form.removeQuery(id)
543
- })
544
- cms.removeOrphanedForms()
545
- cms.dispatch({ type: 'form-lists:remove', value: id })
547
+ form.removeQuery(id);
548
+ });
549
+ cms.removeOrphanedForms();
550
+ cms.dispatch({ type: 'form-lists:remove', value: id });
546
551
  }
547
552
  if (event.data.type === 'open') {
548
553
  const payloadSchema = z.object({
@@ -550,60 +555,66 @@ export const useGraphQLReducer = (
550
555
  query: z.string(),
551
556
  variables: z.record(z.unknown()),
552
557
  data: z.record(z.unknown()),
553
- })
554
- const payload = payloadSchema.parse(event.data)
558
+ });
559
+ const payload = payloadSchema.parse(event.data);
555
560
  setPayloads((payloads) => [
556
561
  ...payloads.filter(({ id }) => id !== payload.id),
557
562
  payload,
558
- ])
563
+ ]);
564
+ }
565
+ if (event.data.type === 'url-changed') {
566
+ cms.dispatch({
567
+ type: 'sidebar:set-loading-state',
568
+ value: true,
569
+ });
559
570
  }
560
571
  },
561
572
  [cms, JSON.stringify(results)]
562
- )
573
+ );
563
574
 
564
575
  React.useEffect(() => {
565
576
  payloads.forEach((payload) => {
566
577
  if (payload.expandedData) {
567
- processPayload(payload)
578
+ processPayload(payload);
568
579
  }
569
- })
570
- }, [operationIndex])
580
+ });
581
+ }, [operationIndex]);
571
582
 
572
583
  React.useEffect(() => {
573
584
  return () => {
574
- setPayloads([])
575
- setResults([])
576
- cms.removeAllForms()
577
- cms.dispatch({ type: 'form-lists:clear' })
578
- }
579
- }, [url])
585
+ setPayloads([]);
586
+ setResults([]);
587
+ cms.removeAllForms();
588
+ cms.dispatch({ type: 'form-lists:clear' });
589
+ };
590
+ }, [url]);
580
591
 
581
592
  React.useEffect(() => {
582
593
  iframe.current?.contentWindow?.postMessage({
583
594
  type: 'quickEditEnabled',
584
595
  value: cms.state.sidebarDisplayState === 'open',
585
- })
586
- }, [cms.state.sidebarDisplayState])
596
+ });
597
+ }, [cms.state.sidebarDisplayState]);
587
598
 
588
599
  React.useEffect(() => {
589
- cms.dispatch({ type: 'set-edit-mode', value: 'visual' })
600
+ cms.dispatch({ type: 'set-edit-mode', value: 'visual' });
590
601
  if (iframe) {
591
- window.addEventListener('message', handleMessage)
602
+ window.addEventListener('message', handleMessage);
592
603
  }
593
604
 
594
605
  return () => {
595
- window.removeEventListener('message', handleMessage)
596
- cms.removeAllForms()
597
- cms.dispatch({ type: 'set-edit-mode', value: 'basic' })
598
- }
599
- }, [iframe.current, JSON.stringify(results)])
606
+ window.removeEventListener('message', handleMessage);
607
+ cms.removeAllForms();
608
+ cms.dispatch({ type: 'set-edit-mode', value: 'basic' });
609
+ };
610
+ }, [iframe.current, JSON.stringify(results)]);
600
611
 
601
612
  React.useEffect(() => {
602
613
  if (requestErrors.length) {
603
- showErrorModal('Unexpected error querying content', requestErrors, cms)
614
+ showErrorModal('Unexpected error querying content', requestErrors, cms);
604
615
  }
605
- }, [requestErrors])
606
- }
616
+ }, [requestErrors]);
617
+ };
607
618
 
608
619
  const onSubmit = async (
609
620
  collection: Collection<true>,
@@ -611,7 +622,7 @@ const onSubmit = async (
611
622
  payload: Record<string, unknown>,
612
623
  cms: TinaCMS
613
624
  ) => {
614
- const tinaSchema = cms.api.tina.schema
625
+ const tinaSchema = cms.api.tina.schema;
615
626
  try {
616
627
  const mutationString = `#graphql
617
628
  mutation UpdateDocument($collection: String!, $relativePath: String!, $params: DocumentUpdateMutation!) {
@@ -619,7 +630,7 @@ const onSubmit = async (
619
630
  __typename
620
631
  }
621
632
  }
622
- `
633
+ `;
623
634
 
624
635
  await cms.api.tina.request(mutationString, {
625
636
  variables: {
@@ -627,8 +638,8 @@ const onSubmit = async (
627
638
  relativePath: relativePath,
628
639
  params: tinaSchema.transformPayload(collection.name, payload),
629
640
  },
630
- })
631
- cms.alerts.success('Document saved!')
641
+ });
642
+ cms.alerts.success('Document saved!');
632
643
  } catch (e) {
633
644
  cms.alerts.error(() =>
634
645
  ErrorDialog({
@@ -636,12 +647,12 @@ const onSubmit = async (
636
647
  message: 'Tina caught an error while updating the page',
637
648
  error: e,
638
649
  })
639
- )
640
- console.error(e)
650
+ );
651
+ console.error(e);
641
652
  }
642
- }
653
+ };
643
654
 
644
- type Path = (string | number)[]
655
+ type Path = (string | number)[];
645
656
 
646
657
  const resolveDocument = (
647
658
  doc: ResolvedDocument,
@@ -650,20 +661,20 @@ const resolveDocument = (
650
661
  pathToDocument: string
651
662
  ): ResolvedDocument => {
652
663
  // @ts-ignore AnyField and TinaField don't mix
653
- const fields = form.fields as TinaField<true>[]
654
- const id = doc._internalSys.path
655
- const path: Path = []
664
+ const fields = form.fields as TinaField<true>[];
665
+ const id = doc._internalSys.path;
666
+ const path: Path = [];
656
667
  const formValues = resolveFormValue({
657
668
  fields: fields,
658
669
  values: form.values,
659
670
  path,
660
671
  id,
661
672
  pathToDocument,
662
- })
663
- const metadataFields: Record<string, string> = {}
673
+ });
674
+ const metadataFields: Record<string, string> = {};
664
675
  Object.keys(formValues).forEach((key) => {
665
- metadataFields[key] = [...path, key].join('.')
666
- })
676
+ metadataFields[key] = [...path, key].join('.');
677
+ });
667
678
 
668
679
  return {
669
680
  ...formValues,
@@ -679,8 +690,8 @@ const resolveDocument = (
679
690
  _internalSys: doc._internalSys,
680
691
  _internalValues: doc._internalValues,
681
692
  __typename: NAMER.dataTypeName(template.namespace),
682
- }
683
- }
693
+ };
694
+ };
684
695
 
685
696
  const resolveFormValue = <T extends Record<string, unknown>>({
686
697
  fields,
@@ -690,21 +701,21 @@ const resolveFormValue = <T extends Record<string, unknown>>({
690
701
  pathToDocument,
691
702
  }: // tinaSchema,
692
703
  {
693
- fields: TinaField<true>[]
694
- values: T
695
- path: Path
696
- id: string
697
- pathToDocument: string
704
+ fields: TinaField<true>[];
705
+ values: T;
706
+ path: Path;
707
+ id: string;
708
+ pathToDocument: string;
698
709
  // tinaSchema: TinaSchema
699
710
  }): T & { __typename?: string } => {
700
- const accum: Record<string, unknown> = {}
711
+ const accum: Record<string, unknown> = {};
701
712
  fields.forEach((field) => {
702
- const v = values[field.name]
713
+ const v = values[field.name];
703
714
  if (typeof v === 'undefined') {
704
- return
715
+ return;
705
716
  }
706
717
  if (v === null) {
707
- return
718
+ return;
708
719
  }
709
720
  accum[field.name] = resolveFieldValue({
710
721
  field,
@@ -712,10 +723,10 @@ const resolveFormValue = <T extends Record<string, unknown>>({
712
723
  path,
713
724
  id,
714
725
  pathToDocument,
715
- })
716
- })
717
- return accum as T & { __typename?: string }
718
- }
726
+ });
727
+ });
728
+ return accum as T & { __typename?: string };
729
+ };
719
730
  const resolveFieldValue = ({
720
731
  field,
721
732
  value,
@@ -723,11 +734,11 @@ const resolveFieldValue = ({
723
734
  id,
724
735
  pathToDocument,
725
736
  }: {
726
- field: TinaField<true>
727
- value: unknown
728
- path: Path
729
- id: string
730
- pathToDocument: string
737
+ field: TinaField<true>;
738
+ value: unknown;
739
+ path: Path;
740
+ id: string;
741
+ pathToDocument: string;
731
742
  }) => {
732
743
  switch (field.type) {
733
744
  case 'object': {
@@ -735,15 +746,17 @@ const resolveFieldValue = ({
735
746
  if (field.list) {
736
747
  if (Array.isArray(value)) {
737
748
  return value.map((item, index) => {
738
- const template = field.templates[item._template]
749
+ const template = field.templates[item._template];
739
750
  if (typeof template === 'string') {
740
- throw new Error('Global templates not supported')
751
+ throw new Error('Global templates not supported');
741
752
  }
742
- const nextPath = [...path, field.name, index]
743
- const metadataFields: Record<string, string> = {}
753
+ const nextPath = [...path, field.name, index];
754
+ const metadataFields: Record<string, string> = {};
744
755
  template.fields.forEach((field) => {
745
- metadataFields[field.name] = [...nextPath, field.name].join('.')
746
- })
756
+ metadataFields[field.name] = [...nextPath, field.name].join(
757
+ '.'
758
+ );
759
+ });
747
760
  return {
748
761
  __typename: NAMER.dataTypeName(template.namespace),
749
762
  _tina_metadata: {
@@ -759,29 +772,29 @@ const resolveFieldValue = ({
759
772
  id,
760
773
  pathToDocument,
761
774
  }),
762
- }
763
- })
775
+ };
776
+ });
764
777
  }
765
778
  } else {
766
779
  // not implemented
767
780
  }
768
781
  }
769
782
 
770
- const templateFields = field.fields
783
+ const templateFields = field.fields;
771
784
  if (typeof templateFields === 'string') {
772
- throw new Error('Global templates not supported')
785
+ throw new Error('Global templates not supported');
773
786
  }
774
787
  if (!templateFields) {
775
- throw new Error(`Expected to find sub-fields on field ${field.name}`)
788
+ throw new Error(`Expected to find sub-fields on field ${field.name}`);
776
789
  }
777
790
  if (field.list) {
778
791
  if (Array.isArray(value)) {
779
792
  return value.map((item, index) => {
780
- const nextPath = [...path, field.name, index]
781
- const metadataFields: Record<string, string> = {}
793
+ const nextPath = [...path, field.name, index];
794
+ const metadataFields: Record<string, string> = {};
782
795
  templateFields.forEach((field) => {
783
- metadataFields[field.name] = [...nextPath, field.name].join('.')
784
- })
796
+ metadataFields[field.name] = [...nextPath, field.name].join('.');
797
+ });
785
798
  return {
786
799
  __typename: NAMER.dataTypeName(field.namespace),
787
800
  _tina_metadata: {
@@ -797,15 +810,15 @@ const resolveFieldValue = ({
797
810
  id,
798
811
  pathToDocument,
799
812
  }),
800
- }
801
- })
813
+ };
814
+ });
802
815
  }
803
816
  } else {
804
- const nextPath = [...path, field.name]
805
- const metadataFields: Record<string, string> = {}
817
+ const nextPath = [...path, field.name];
818
+ const metadataFields: Record<string, string> = {};
806
819
  templateFields.forEach((field) => {
807
- metadataFields[field.name] = [...nextPath, field.name].join('.')
808
- })
820
+ metadataFields[field.name] = [...nextPath, field.name].join('.');
821
+ });
809
822
  return {
810
823
  __typename: NAMER.dataTypeName(field.namespace),
811
824
  _tina_metadata: {
@@ -821,18 +834,21 @@ const resolveFieldValue = ({
821
834
  id,
822
835
  pathToDocument,
823
836
  }),
824
- }
837
+ };
825
838
  }
826
839
  }
827
840
  default: {
828
- return value
841
+ return value;
829
842
  }
830
843
  }
831
- }
844
+ };
832
845
 
833
846
  const getDocument = async (id: string, tina: Client) => {
834
847
  const response = await tina.request<{
835
- node: { _internalSys: SystemInfo; _internalValues: Record<string, unknown> }
848
+ node: {
849
+ _internalSys: SystemInfo;
850
+ _internalValues: Record<string, unknown>;
851
+ };
836
852
  }>(
837
853
  `query GetNode($id: String!) {
838
854
  node(id: $id) {
@@ -865,29 +881,29 @@ _internalSys: _sys {
865
881
  }
866
882
  }`,
867
883
  { variables: { id: id } }
868
- )
869
- return response.node
870
- }
884
+ );
885
+ return response.node;
886
+ };
871
887
 
872
888
  const expandPayload = async (
873
889
  payload: Payload,
874
890
  cms: TinaCMS
875
891
  ): Promise<Payload> => {
876
- const { query, variables } = payload
877
- const documentNode = G.parse(query)
878
- const expandedDocumentNode = expandQuery({ schema, documentNode })
879
- const expandedQuery = G.print(expandedDocumentNode)
892
+ const { query, variables } = payload;
893
+ const documentNode = G.parse(query);
894
+ const expandedDocumentNode = expandQuery({ schema, documentNode });
895
+ const expandedQuery = G.print(expandedDocumentNode);
880
896
  const expandedData = await cms.api.tina.request<object>(expandedQuery, {
881
897
  variables,
882
- })
898
+ });
883
899
 
884
900
  const expandedDocumentNodeForResolver = expandQuery({
885
901
  schema: schemaForResolver,
886
902
  documentNode,
887
- })
888
- const expandedQueryForResolver = G.print(expandedDocumentNodeForResolver)
889
- return { ...payload, expandedQuery, expandedData, expandedQueryForResolver }
890
- }
903
+ });
904
+ const expandedQueryForResolver = G.print(expandedDocumentNodeForResolver);
905
+ return { ...payload, expandedQuery, expandedData, expandedQueryForResolver };
906
+ };
891
907
 
892
908
  /**
893
909
  * When we resolve the graphql data we check for these errors,
@@ -895,11 +911,11 @@ const expandPayload = async (
895
911
  * process it once we have that document
896
912
  */
897
913
  class NoFormError extends Error {
898
- id: string
914
+ id: string;
899
915
  constructor(msg: string, id: string) {
900
- super(msg)
901
- this.id = id
902
- Object.setPrototypeOf(this, NoFormError.prototype)
916
+ super(msg);
917
+ this.id = id;
918
+ Object.setPrototypeOf(this, NoFormError.prototype);
903
919
  }
904
920
  }
905
921
 
@@ -907,22 +923,22 @@ const getTemplateForDocument = (
907
923
  resolvedDocument: ResolvedDocument,
908
924
  tinaSchema: TinaSchema
909
925
  ) => {
910
- const id = resolvedDocument._internalSys.path
911
- let collection: Collection<true> | undefined
926
+ const id = resolvedDocument._internalSys.path;
927
+ let collection: Collection<true> | undefined;
912
928
  try {
913
- collection = tinaSchema.getCollectionByFullPath(id)
929
+ collection = tinaSchema.getCollectionByFullPath(id);
914
930
  } catch (e) {}
915
931
 
916
932
  if (!collection) {
917
- throw new Error(`Unable to determine collection for path ${id}`)
933
+ throw new Error(`Unable to determine collection for path ${id}`);
918
934
  }
919
935
 
920
936
  const template = tinaSchema.getTemplateForData({
921
937
  data: resolvedDocument._internalValues,
922
938
  collection,
923
- })
924
- return { template, collection }
925
- }
939
+ });
940
+ return { template, collection };
941
+ };
926
942
 
927
943
  const buildForm = ({
928
944
  resolvedDocument,
@@ -930,18 +946,18 @@ const buildForm = ({
930
946
  payloadId,
931
947
  cms,
932
948
  }: {
933
- resolvedDocument: ResolvedDocument
934
- tinaSchema: TinaSchema
935
- payloadId: string
936
- cms: TinaCMS
949
+ resolvedDocument: ResolvedDocument;
950
+ tinaSchema: TinaSchema;
951
+ payloadId: string;
952
+ cms: TinaCMS;
937
953
  }) => {
938
954
  const { template, collection } = getTemplateForDocument(
939
955
  resolvedDocument,
940
956
  tinaSchema
941
- )
942
- const id = resolvedDocument._internalSys.path
943
- let form: Form | undefined
944
- let shouldRegisterForm = true
957
+ );
958
+ const id = resolvedDocument._internalSys.path;
959
+ let form: Form | undefined;
960
+ let shouldRegisterForm = true;
945
961
  const formConfig: FormOptions<any> = {
946
962
  id,
947
963
  initialValues: resolvedDocument._internalValues,
@@ -954,10 +970,10 @@ const buildForm = ({
954
970
  cms
955
971
  ),
956
972
  label: collection.label || collection.name,
957
- }
973
+ };
958
974
  if (tinaSchema.config.config?.formifyCallback) {
959
975
  const callback = tinaSchema.config.config
960
- ?.formifyCallback as FormifyCallback
976
+ ?.formifyCallback as FormifyCallback;
961
977
  form =
962
978
  callback(
963
979
  {
@@ -967,30 +983,30 @@ const buildForm = ({
967
983
  formConfig,
968
984
  },
969
985
  cms
970
- ) || undefined
986
+ ) || undefined;
971
987
  if (!form) {
972
988
  // If the form isn't created from formify, we still
973
989
  // need it, just don't show it to the user.
974
- shouldRegisterForm = false
975
- form = new Form(formConfig)
990
+ shouldRegisterForm = false;
991
+ form = new Form(formConfig);
976
992
  }
977
993
  } else {
978
994
  if (collection.ui?.global) {
979
- form = createGlobalForm(formConfig)
995
+ form = createGlobalForm(formConfig);
980
996
  } else {
981
- form = createForm(formConfig)
997
+ form = createForm(formConfig);
982
998
  }
983
999
  }
984
1000
  if (form) {
985
1001
  if (shouldRegisterForm) {
986
1002
  if (collection.ui?.global) {
987
- cms.plugins.add(new GlobalFormPlugin(form))
1003
+ cms.plugins.add(new GlobalFormPlugin(form));
988
1004
  }
989
- cms.dispatch({ type: 'forms:add', value: form })
1005
+ cms.dispatch({ type: 'forms:add', value: form });
990
1006
  }
991
1007
  }
992
1008
  if (!form) {
993
- throw new Error(`No form registered for ${id}.`)
1009
+ throw new Error(`No form registered for ${id}.`);
994
1010
  }
995
- return { template, form }
996
- }
1011
+ return { template, form };
1012
+ };