@tinacms/app 0.0.0-899eda4-20250227042836 → 0.0.0-8a5bd87-20251217030816

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,68 @@ 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
+ ]);
559
564
  }
565
+ // TODO: This is causing a webpack HMR issue - look into refactoring this logic
566
+ // if (event.data.type === 'url-changed') {
567
+ // console.log('[EVENT_TRIGGERED] url-changed: ', event);
568
+ // cms.dispatch({
569
+ // type: 'sidebar:set-loading-state',
570
+ // value: true,
571
+ // });
572
+ // }
560
573
  },
561
574
  [cms, JSON.stringify(results)]
562
- )
575
+ );
563
576
 
564
577
  React.useEffect(() => {
565
578
  payloads.forEach((payload) => {
566
579
  if (payload.expandedData) {
567
- processPayload(payload)
580
+ processPayload(payload);
568
581
  }
569
- })
570
- }, [operationIndex])
582
+ });
583
+ }, [operationIndex]);
571
584
 
572
585
  React.useEffect(() => {
573
586
  return () => {
574
- setPayloads([])
575
- setResults([])
576
- cms.removeAllForms()
577
- cms.dispatch({ type: 'form-lists:clear' })
578
- }
579
- }, [url])
587
+ setPayloads([]);
588
+ setResults([]);
589
+ cms.removeAllForms();
590
+ cms.dispatch({ type: 'form-lists:clear' });
591
+ };
592
+ }, [url]);
580
593
 
581
594
  React.useEffect(() => {
582
595
  iframe.current?.contentWindow?.postMessage({
583
596
  type: 'quickEditEnabled',
584
597
  value: cms.state.sidebarDisplayState === 'open',
585
- })
586
- }, [cms.state.sidebarDisplayState])
598
+ });
599
+ }, [cms.state.sidebarDisplayState]);
587
600
 
588
601
  React.useEffect(() => {
589
- cms.dispatch({ type: 'set-edit-mode', value: 'visual' })
602
+ cms.dispatch({ type: 'set-edit-mode', value: 'visual' });
590
603
  if (iframe) {
591
- window.addEventListener('message', handleMessage)
604
+ window.addEventListener('message', handleMessage);
592
605
  }
593
606
 
594
607
  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)])
608
+ window.removeEventListener('message', handleMessage);
609
+ cms.removeAllForms();
610
+ cms.dispatch({ type: 'set-edit-mode', value: 'basic' });
611
+ };
612
+ }, [iframe.current, JSON.stringify(results)]);
600
613
 
601
614
  React.useEffect(() => {
602
615
  if (requestErrors.length) {
603
- showErrorModal('Unexpected error querying content', requestErrors, cms)
616
+ showErrorModal('Unexpected error querying content', requestErrors, cms);
604
617
  }
605
- }, [requestErrors])
606
- }
618
+ }, [requestErrors]);
619
+ };
607
620
 
608
621
  const onSubmit = async (
609
622
  collection: Collection<true>,
@@ -611,7 +624,7 @@ const onSubmit = async (
611
624
  payload: Record<string, unknown>,
612
625
  cms: TinaCMS
613
626
  ) => {
614
- const tinaSchema = cms.api.tina.schema
627
+ const tinaSchema = cms.api.tina.schema;
615
628
  try {
616
629
  const mutationString = `#graphql
617
630
  mutation UpdateDocument($collection: String!, $relativePath: String!, $params: DocumentUpdateMutation!) {
@@ -619,7 +632,7 @@ const onSubmit = async (
619
632
  __typename
620
633
  }
621
634
  }
622
- `
635
+ `;
623
636
 
624
637
  await cms.api.tina.request(mutationString, {
625
638
  variables: {
@@ -627,8 +640,8 @@ const onSubmit = async (
627
640
  relativePath: relativePath,
628
641
  params: tinaSchema.transformPayload(collection.name, payload),
629
642
  },
630
- })
631
- cms.alerts.success('Document saved!')
643
+ });
644
+ cms.alerts.success('Document saved!');
632
645
  } catch (e) {
633
646
  cms.alerts.error(() =>
634
647
  ErrorDialog({
@@ -636,12 +649,12 @@ const onSubmit = async (
636
649
  message: 'Tina caught an error while updating the page',
637
650
  error: e,
638
651
  })
639
- )
640
- console.error(e)
652
+ );
653
+ console.error(e);
641
654
  }
642
- }
655
+ };
643
656
 
644
- type Path = (string | number)[]
657
+ type Path = (string | number)[];
645
658
 
646
659
  const resolveDocument = (
647
660
  doc: ResolvedDocument,
@@ -650,20 +663,20 @@ const resolveDocument = (
650
663
  pathToDocument: string
651
664
  ): ResolvedDocument => {
652
665
  // @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 = []
666
+ const fields = form.fields as TinaField<true>[];
667
+ const id = doc._internalSys.path;
668
+ const path: Path = [];
656
669
  const formValues = resolveFormValue({
657
670
  fields: fields,
658
671
  values: form.values,
659
672
  path,
660
673
  id,
661
674
  pathToDocument,
662
- })
663
- const metadataFields: Record<string, string> = {}
675
+ });
676
+ const metadataFields: Record<string, string> = {};
664
677
  Object.keys(formValues).forEach((key) => {
665
- metadataFields[key] = [...path, key].join('.')
666
- })
678
+ metadataFields[key] = [...path, key].join('.');
679
+ });
667
680
 
668
681
  return {
669
682
  ...formValues,
@@ -679,8 +692,8 @@ const resolveDocument = (
679
692
  _internalSys: doc._internalSys,
680
693
  _internalValues: doc._internalValues,
681
694
  __typename: NAMER.dataTypeName(template.namespace),
682
- }
683
- }
695
+ };
696
+ };
684
697
 
685
698
  const resolveFormValue = <T extends Record<string, unknown>>({
686
699
  fields,
@@ -690,21 +703,21 @@ const resolveFormValue = <T extends Record<string, unknown>>({
690
703
  pathToDocument,
691
704
  }: // tinaSchema,
692
705
  {
693
- fields: TinaField<true>[]
694
- values: T
695
- path: Path
696
- id: string
697
- pathToDocument: string
706
+ fields: TinaField<true>[];
707
+ values: T;
708
+ path: Path;
709
+ id: string;
710
+ pathToDocument: string;
698
711
  // tinaSchema: TinaSchema
699
712
  }): T & { __typename?: string } => {
700
- const accum: Record<string, unknown> = {}
713
+ const accum: Record<string, unknown> = {};
701
714
  fields.forEach((field) => {
702
- const v = values[field.name]
715
+ const v = values[field.name];
703
716
  if (typeof v === 'undefined') {
704
- return
717
+ return;
705
718
  }
706
719
  if (v === null) {
707
- return
720
+ return;
708
721
  }
709
722
  accum[field.name] = resolveFieldValue({
710
723
  field,
@@ -712,10 +725,10 @@ const resolveFormValue = <T extends Record<string, unknown>>({
712
725
  path,
713
726
  id,
714
727
  pathToDocument,
715
- })
716
- })
717
- return accum as T & { __typename?: string }
718
- }
728
+ });
729
+ });
730
+ return accum as T & { __typename?: string };
731
+ };
719
732
  const resolveFieldValue = ({
720
733
  field,
721
734
  value,
@@ -723,11 +736,11 @@ const resolveFieldValue = ({
723
736
  id,
724
737
  pathToDocument,
725
738
  }: {
726
- field: TinaField<true>
727
- value: unknown
728
- path: Path
729
- id: string
730
- pathToDocument: string
739
+ field: TinaField<true>;
740
+ value: unknown;
741
+ path: Path;
742
+ id: string;
743
+ pathToDocument: string;
731
744
  }) => {
732
745
  switch (field.type) {
733
746
  case 'object': {
@@ -735,15 +748,17 @@ const resolveFieldValue = ({
735
748
  if (field.list) {
736
749
  if (Array.isArray(value)) {
737
750
  return value.map((item, index) => {
738
- const template = field.templates[item._template]
751
+ const template = field.templates[item._template];
739
752
  if (typeof template === 'string') {
740
- throw new Error('Global templates not supported')
753
+ throw new Error('Global templates not supported');
741
754
  }
742
- const nextPath = [...path, field.name, index]
743
- const metadataFields: Record<string, string> = {}
755
+ const nextPath = [...path, field.name, index];
756
+ const metadataFields: Record<string, string> = {};
744
757
  template.fields.forEach((field) => {
745
- metadataFields[field.name] = [...nextPath, field.name].join('.')
746
- })
758
+ metadataFields[field.name] = [...nextPath, field.name].join(
759
+ '.'
760
+ );
761
+ });
747
762
  return {
748
763
  __typename: NAMER.dataTypeName(template.namespace),
749
764
  _tina_metadata: {
@@ -759,29 +774,29 @@ const resolveFieldValue = ({
759
774
  id,
760
775
  pathToDocument,
761
776
  }),
762
- }
763
- })
777
+ };
778
+ });
764
779
  }
765
780
  } else {
766
781
  // not implemented
767
782
  }
768
783
  }
769
784
 
770
- const templateFields = field.fields
785
+ const templateFields = field.fields;
771
786
  if (typeof templateFields === 'string') {
772
- throw new Error('Global templates not supported')
787
+ throw new Error('Global templates not supported');
773
788
  }
774
789
  if (!templateFields) {
775
- throw new Error(`Expected to find sub-fields on field ${field.name}`)
790
+ throw new Error(`Expected to find sub-fields on field ${field.name}`);
776
791
  }
777
792
  if (field.list) {
778
793
  if (Array.isArray(value)) {
779
794
  return value.map((item, index) => {
780
- const nextPath = [...path, field.name, index]
781
- const metadataFields: Record<string, string> = {}
795
+ const nextPath = [...path, field.name, index];
796
+ const metadataFields: Record<string, string> = {};
782
797
  templateFields.forEach((field) => {
783
- metadataFields[field.name] = [...nextPath, field.name].join('.')
784
- })
798
+ metadataFields[field.name] = [...nextPath, field.name].join('.');
799
+ });
785
800
  return {
786
801
  __typename: NAMER.dataTypeName(field.namespace),
787
802
  _tina_metadata: {
@@ -797,15 +812,15 @@ const resolveFieldValue = ({
797
812
  id,
798
813
  pathToDocument,
799
814
  }),
800
- }
801
- })
815
+ };
816
+ });
802
817
  }
803
818
  } else {
804
- const nextPath = [...path, field.name]
805
- const metadataFields: Record<string, string> = {}
819
+ const nextPath = [...path, field.name];
820
+ const metadataFields: Record<string, string> = {};
806
821
  templateFields.forEach((field) => {
807
- metadataFields[field.name] = [...nextPath, field.name].join('.')
808
- })
822
+ metadataFields[field.name] = [...nextPath, field.name].join('.');
823
+ });
809
824
  return {
810
825
  __typename: NAMER.dataTypeName(field.namespace),
811
826
  _tina_metadata: {
@@ -821,18 +836,21 @@ const resolveFieldValue = ({
821
836
  id,
822
837
  pathToDocument,
823
838
  }),
824
- }
839
+ };
825
840
  }
826
841
  }
827
842
  default: {
828
- return value
843
+ return value;
829
844
  }
830
845
  }
831
- }
846
+ };
832
847
 
833
848
  const getDocument = async (id: string, tina: Client) => {
834
849
  const response = await tina.request<{
835
- node: { _internalSys: SystemInfo; _internalValues: Record<string, unknown> }
850
+ node: {
851
+ _internalSys: SystemInfo;
852
+ _internalValues: Record<string, unknown>;
853
+ };
836
854
  }>(
837
855
  `query GetNode($id: String!) {
838
856
  node(id: $id) {
@@ -865,29 +883,29 @@ _internalSys: _sys {
865
883
  }
866
884
  }`,
867
885
  { variables: { id: id } }
868
- )
869
- return response.node
870
- }
886
+ );
887
+ return response.node;
888
+ };
871
889
 
872
890
  const expandPayload = async (
873
891
  payload: Payload,
874
892
  cms: TinaCMS
875
893
  ): 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)
894
+ const { query, variables } = payload;
895
+ const documentNode = G.parse(query);
896
+ const expandedDocumentNode = expandQuery({ schema, documentNode });
897
+ const expandedQuery = G.print(expandedDocumentNode);
880
898
  const expandedData = await cms.api.tina.request<object>(expandedQuery, {
881
899
  variables,
882
- })
900
+ });
883
901
 
884
902
  const expandedDocumentNodeForResolver = expandQuery({
885
903
  schema: schemaForResolver,
886
904
  documentNode,
887
- })
888
- const expandedQueryForResolver = G.print(expandedDocumentNodeForResolver)
889
- return { ...payload, expandedQuery, expandedData, expandedQueryForResolver }
890
- }
905
+ });
906
+ const expandedQueryForResolver = G.print(expandedDocumentNodeForResolver);
907
+ return { ...payload, expandedQuery, expandedData, expandedQueryForResolver };
908
+ };
891
909
 
892
910
  /**
893
911
  * When we resolve the graphql data we check for these errors,
@@ -895,11 +913,11 @@ const expandPayload = async (
895
913
  * process it once we have that document
896
914
  */
897
915
  class NoFormError extends Error {
898
- id: string
916
+ id: string;
899
917
  constructor(msg: string, id: string) {
900
- super(msg)
901
- this.id = id
902
- Object.setPrototypeOf(this, NoFormError.prototype)
918
+ super(msg);
919
+ this.id = id;
920
+ Object.setPrototypeOf(this, NoFormError.prototype);
903
921
  }
904
922
  }
905
923
 
@@ -907,22 +925,22 @@ const getTemplateForDocument = (
907
925
  resolvedDocument: ResolvedDocument,
908
926
  tinaSchema: TinaSchema
909
927
  ) => {
910
- const id = resolvedDocument._internalSys.path
911
- let collection: Collection<true> | undefined
928
+ const id = resolvedDocument._internalSys.path;
929
+ let collection: Collection<true> | undefined;
912
930
  try {
913
- collection = tinaSchema.getCollectionByFullPath(id)
931
+ collection = tinaSchema.getCollectionByFullPath(id);
914
932
  } catch (e) {}
915
933
 
916
934
  if (!collection) {
917
- throw new Error(`Unable to determine collection for path ${id}`)
935
+ throw new Error(`Unable to determine collection for path ${id}`);
918
936
  }
919
937
 
920
938
  const template = tinaSchema.getTemplateForData({
921
939
  data: resolvedDocument._internalValues,
922
940
  collection,
923
- })
924
- return { template, collection }
925
- }
941
+ });
942
+ return { template, collection };
943
+ };
926
944
 
927
945
  const buildForm = ({
928
946
  resolvedDocument,
@@ -930,18 +948,18 @@ const buildForm = ({
930
948
  payloadId,
931
949
  cms,
932
950
  }: {
933
- resolvedDocument: ResolvedDocument
934
- tinaSchema: TinaSchema
935
- payloadId: string
936
- cms: TinaCMS
951
+ resolvedDocument: ResolvedDocument;
952
+ tinaSchema: TinaSchema;
953
+ payloadId: string;
954
+ cms: TinaCMS;
937
955
  }) => {
938
956
  const { template, collection } = getTemplateForDocument(
939
957
  resolvedDocument,
940
958
  tinaSchema
941
- )
942
- const id = resolvedDocument._internalSys.path
943
- let form: Form | undefined
944
- let shouldRegisterForm = true
959
+ );
960
+ const id = resolvedDocument._internalSys.path;
961
+ let form: Form | undefined;
962
+ let shouldRegisterForm = true;
945
963
  const formConfig: FormOptions<any> = {
946
964
  id,
947
965
  initialValues: resolvedDocument._internalValues,
@@ -954,10 +972,10 @@ const buildForm = ({
954
972
  cms
955
973
  ),
956
974
  label: collection.label || collection.name,
957
- }
975
+ };
958
976
  if (tinaSchema.config.config?.formifyCallback) {
959
977
  const callback = tinaSchema.config.config
960
- ?.formifyCallback as FormifyCallback
978
+ ?.formifyCallback as FormifyCallback;
961
979
  form =
962
980
  callback(
963
981
  {
@@ -967,30 +985,30 @@ const buildForm = ({
967
985
  formConfig,
968
986
  },
969
987
  cms
970
- ) || undefined
988
+ ) || undefined;
971
989
  if (!form) {
972
990
  // If the form isn't created from formify, we still
973
991
  // need it, just don't show it to the user.
974
- shouldRegisterForm = false
975
- form = new Form(formConfig)
992
+ shouldRegisterForm = false;
993
+ form = new Form(formConfig);
976
994
  }
977
995
  } else {
978
996
  if (collection.ui?.global) {
979
- form = createGlobalForm(formConfig)
997
+ form = createGlobalForm(formConfig);
980
998
  } else {
981
- form = createForm(formConfig)
999
+ form = createForm(formConfig);
982
1000
  }
983
1001
  }
984
1002
  if (form) {
985
1003
  if (shouldRegisterForm) {
986
1004
  if (collection.ui?.global) {
987
- cms.plugins.add(new GlobalFormPlugin(form))
1005
+ cms.plugins.add(new GlobalFormPlugin(form));
988
1006
  }
989
- cms.dispatch({ type: 'forms:add', value: form })
1007
+ cms.dispatch({ type: 'forms:add', value: form });
990
1008
  }
991
1009
  }
992
1010
  if (!form) {
993
- throw new Error(`No form registered for ${id}.`)
1011
+ throw new Error(`No form registered for ${id}.`);
994
1012
  }
995
- return { template, form }
996
- }
1013
+ return { template, form };
1014
+ };