@tinacms/app 0.0.0-a6daef4-20250115020754 → 0.0.0-a6e7bb7-20250702023440

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