@tinacms/app 0.0.0-d7c745e-20250102002342 → 0.0.0-d94de9b-20250707010010

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