@sanity/assist 1.2.15 → 2.0.0

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.
Files changed (52) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +551 -30
  3. package/dist/index.cjs.mjs +1 -0
  4. package/dist/index.d.ts +253 -11
  5. package/dist/index.esm.js +2405 -392
  6. package/dist/index.esm.js.map +1 -1
  7. package/dist/index.js +2399 -385
  8. package/dist/index.js.map +1 -1
  9. package/package.json +15 -14
  10. package/src/_lib/form/DocumentForm.tsx +3 -2
  11. package/src/_lib/form/constants.ts +1 -0
  12. package/src/assistDocument/AssistDocumentInput.tsx +24 -4
  13. package/src/assistDocument/RequestRunInstructionProvider.tsx +37 -21
  14. package/src/assistDocument/components/instruction/InstructionInput.tsx +5 -4
  15. package/src/assistDocument/components/instruction/InstructionOutputField.tsx +45 -0
  16. package/src/assistDocument/components/instruction/InstructionOutputInput.tsx +205 -0
  17. package/src/assistDocument/hooks/useStudioAssistDocument.ts +5 -32
  18. package/src/assistFormComponents/AssistField.tsx +11 -5
  19. package/src/assistFormComponents/AssistFormBlock.tsx +2 -3
  20. package/src/assistFormComponents/validation/listItem.tsx +2 -2
  21. package/src/assistInspector/AssistInspector.tsx +6 -0
  22. package/src/assistInspector/FieldAutocomplete.tsx +1 -0
  23. package/src/assistInspector/InstructionTaskHistoryButton.tsx +2 -3
  24. package/src/assistInspector/helpers.ts +9 -11
  25. package/src/assistLayout/AssistLayout.tsx +9 -9
  26. package/src/components/ImageContext.tsx +19 -9
  27. package/src/components/SafeValueInput.tsx +4 -1
  28. package/src/fieldActions/assistFieldActions.tsx +42 -13
  29. package/src/fieldActions/generateCaptionActions.tsx +2 -2
  30. package/src/fieldActions/generateImageActions.tsx +57 -0
  31. package/src/helpers/assistSupported.ts +10 -16
  32. package/src/helpers/conditionalMembers.test.ts +200 -0
  33. package/src/helpers/conditionalMembers.ts +127 -0
  34. package/src/helpers/typeUtils.ts +19 -5
  35. package/src/index.ts +3 -0
  36. package/src/plugin.tsx +14 -5
  37. package/src/presence/AssistAvatar.tsx +1 -1
  38. package/src/schemas/assistDocumentSchema.tsx +40 -1
  39. package/src/schemas/serialize/serializeSchema.test.ts +239 -8
  40. package/src/schemas/serialize/serializeSchema.ts +77 -10
  41. package/src/schemas/typeDefExtensions.ts +89 -5
  42. package/src/translate/FieldTranslationProvider.tsx +360 -0
  43. package/src/translate/getLanguageParams.ts +26 -0
  44. package/src/translate/languageStore.ts +18 -0
  45. package/src/translate/paths.test.ts +133 -0
  46. package/src/translate/paths.ts +175 -0
  47. package/src/translate/translateActions.tsx +188 -0
  48. package/src/translate/types.ts +160 -0
  49. package/src/types.ts +33 -12
  50. package/src/useApiClient.ts +130 -2
  51. package/src/assistLayout/AlphaMigration.tsx +0 -310
  52. package/src/legacy-types.ts +0 -72
@@ -0,0 +1,127 @@
1
+ /* eslint-disable max-depth */
2
+ import {
3
+ ArrayOfObjectsFormNode,
4
+ ArrayOfObjectsItemMember,
5
+ ArrayOfPrimitivesFormNode,
6
+ DocumentFormNode,
7
+ FieldsetState,
8
+ isObjectSchemaType,
9
+ ObjectFormNode,
10
+ Path,
11
+ pathToString,
12
+ SchemaType,
13
+ } from 'sanity'
14
+
15
+ const MAX_DEPTH = 8
16
+
17
+ export interface ConditionalMemberState {
18
+ path: string
19
+ hidden: boolean
20
+ readOnly: boolean
21
+ }
22
+
23
+ interface ConditionalMemberInnerState extends ConditionalMemberState {
24
+ conditional: boolean
25
+ }
26
+
27
+ /**
28
+ * This is used to statically determine the state of the functions on the server-side.
29
+ * Paths which has a schema with conditional config should be considered hidden: true and/or readOnly: true
30
+ * Only conditional paths are included, as static props can be determined from schema.
31
+ *
32
+ * Returns paths that has conditional hidden or readOnly schema config (function) and that.
33
+ * Form-state does not contain hidden members.
34
+ *
35
+ * Note:
36
+ * * If a parent path is hidden, no child paths are included
37
+ * * If a parent path is readOnly, no child paths are included
38
+ * * If a path is hidden, it is not included; only conditionally visible paths will be returned, with hidden: false
39
+ */
40
+ export function getConditionalMembers(docState: DocumentFormNode): ConditionalMemberState[] {
41
+ const doc: ConditionalMemberInnerState = {
42
+ path: '',
43
+ hidden: false,
44
+ readOnly: !!docState.readOnly,
45
+ conditional: typeof docState.schemaType.hidden === 'function',
46
+ }
47
+ return [doc, ...extractConditionalPaths(docState, MAX_DEPTH)]
48
+ .filter((v) => v.conditional)
49
+ .map(({conditional, ...state}) => ({...state}))
50
+ }
51
+
52
+ function isConditional(schemaType: SchemaType) {
53
+ return typeof schemaType.hidden === 'function' || typeof schemaType.readOnly === 'function'
54
+ }
55
+
56
+ function conditionalState(memberState: {
57
+ path: Path
58
+ schemaType: SchemaType
59
+ readOnly?: boolean
60
+ }): ConditionalMemberInnerState {
61
+ return {
62
+ path: pathToString(memberState.path),
63
+ readOnly: !!memberState.readOnly,
64
+ hidden: false, // if its in members, its not hidden
65
+ conditional: isConditional(memberState.schemaType),
66
+ }
67
+ }
68
+
69
+ function extractConditionalPaths(
70
+ node: ObjectFormNode | FieldsetState,
71
+ maxDepth: number
72
+ ): ConditionalMemberInnerState[] {
73
+ if (node.path.length >= maxDepth) {
74
+ return []
75
+ }
76
+
77
+ return node.members.reduce<ConditionalMemberInnerState[]>((acc, member) => {
78
+ if (member.kind === 'error') {
79
+ return acc
80
+ }
81
+ if (member.kind === 'field') {
82
+ const schemaType = member.field.schemaType
83
+ if (schemaType.jsonType === 'object') {
84
+ const innerFields = member.field.readOnly
85
+ ? []
86
+ : extractConditionalPaths(member.field as ObjectFormNode, maxDepth)
87
+ return [...acc, conditionalState(member.field), ...innerFields]
88
+ } else if (schemaType.jsonType === 'array') {
89
+ const array = member.field as ArrayOfObjectsFormNode | ArrayOfPrimitivesFormNode
90
+
91
+ let arrayPaths: ConditionalMemberInnerState[] = []
92
+ const isObjectsArray = array.members.some(
93
+ (m) => m.kind === 'item' && isObjectSchemaType(m.item.schemaType)
94
+ )
95
+ if (!array.readOnly) {
96
+ for (const arrayMember of array.members) {
97
+ if (arrayMember.kind === 'error') {
98
+ continue
99
+ }
100
+
101
+ const innerFields =
102
+ isObjectsArray && !arrayMember.item.readOnly
103
+ ? extractConditionalPaths((arrayMember as ArrayOfObjectsItemMember).item, maxDepth)
104
+ : []
105
+
106
+ arrayPaths = [...arrayPaths, conditionalState(arrayMember.item), ...innerFields]
107
+ }
108
+ }
109
+ return [...acc, conditionalState(array), ...arrayPaths]
110
+ }
111
+
112
+ return [...acc, conditionalState(member.field)]
113
+ } else if (member.kind === 'fieldSet') {
114
+ const conditionalFieldset = !!(node as ObjectFormNode).schemaType?.fieldsets?.some(
115
+ (f) => !f.single && f.name === member.fieldSet.name && typeof f.hidden === 'function'
116
+ )
117
+ const innerFields = extractConditionalPaths(member.fieldSet, maxDepth).map((f) => ({
118
+ ...f,
119
+ // if fieldset is conditional, visible fields must also be considered conditional
120
+ conditional: conditionalFieldset ?? f.conditional,
121
+ }))
122
+ return [...acc, ...innerFields]
123
+ }
124
+
125
+ return acc
126
+ }, [])
127
+ }
@@ -18,13 +18,27 @@ export function isImage(schemaType: SchemaType) {
18
18
  return isType(schemaType, 'image')
19
19
  }
20
20
 
21
- export function getCaptionFieldOption(schemaType: SchemaType | undefined): string | undefined {
21
+ export function getDescriptionFieldOption(schemaType: SchemaType | undefined): string | undefined {
22
22
  if (!schemaType) {
23
23
  return undefined
24
24
  }
25
- const captionField = (schemaType.options as ImageOptions)?.captionField
26
- if (captionField) {
27
- return captionField
25
+ const descriptionField = (schemaType.options as ImageOptions)?.aiAssist?.imageDescriptionField
26
+ if (descriptionField) {
27
+ return descriptionField
28
28
  }
29
- return getCaptionFieldOption(schemaType.type)
29
+ return getDescriptionFieldOption(schemaType.type)
30
+ }
31
+
32
+ export function getImageInstructionFieldOption(
33
+ schemaType: SchemaType | undefined
34
+ ): string | undefined {
35
+ if (!schemaType) {
36
+ return undefined
37
+ }
38
+ const imageInstructionField = (schemaType.options as ImageOptions)?.aiAssist
39
+ ?.imageInstructionField
40
+ if (imageInstructionField) {
41
+ return imageInstructionField
42
+ }
43
+ return getImageInstructionFieldOption(schemaType.type)
30
44
  }
package/src/index.ts CHANGED
@@ -1,6 +1,9 @@
1
1
  export * from './schemas/typeDefExtensions'
2
2
  export * from './schemas/serialize/SchemTypeTool'
3
3
 
4
+ export {defaultLanguageOutputs} from './translate/paths'
5
+ export * from './translate/types'
6
+
4
7
  export {contextDocumentTypeName} from './types'
5
8
 
6
9
  export {assist} from './plugin'
package/src/plugin.tsx CHANGED
@@ -15,12 +15,11 @@ import {createAssistDocumentPresence} from './presence/AssistDocumentPresence'
15
15
  import {isSchemaAssistEnabled} from './helpers/assistSupported'
16
16
  import {isImage} from './helpers/typeUtils'
17
17
  import {ImageContextProvider} from './components/ImageContext'
18
+ import {TranslationConfig} from './translate/types'
19
+ import {assistDocumentTypeName} from './types'
18
20
 
19
21
  export interface AssistPluginConfig {
20
- /**
21
- * Set this to false to disable model migration from the alpha version of this plugin
22
- */
23
- alphaMigration?: boolean
22
+ translate?: TranslationConfig
24
23
 
25
24
  /**
26
25
  * @internal
@@ -36,16 +35,23 @@ export const assist = definePlugin<AssistPluginConfig | void>((config) => {
36
35
  schema: {
37
36
  types: schemaTypes,
38
37
  },
38
+ i18n: {
39
+ bundles: [{}],
40
+ },
39
41
 
40
42
  document: {
41
43
  inspectors: (prev, context) => {
42
- const docSchema = context.schema.get(context.documentType)
44
+ const documentType = context.documentType
45
+ const docSchema = context.schema.get(documentType)
43
46
  if (docSchema && isSchemaAssistEnabled(docSchema)) {
44
47
  return [...prev, assistInspector]
45
48
  }
46
49
  return prev
47
50
  },
48
51
  unstable_fieldActions: (prev, {documentType, schema}) => {
52
+ if (documentType === assistDocumentTypeName) {
53
+ return []
54
+ }
49
55
  const docSchema = schema.get(documentType)
50
56
  if (docSchema && isSchemaAssistEnabled(docSchema)) {
51
57
  return [...prev, assistFieldActions]
@@ -53,6 +59,9 @@ export const assist = definePlugin<AssistPluginConfig | void>((config) => {
53
59
  return prev
54
60
  },
55
61
  unstable_languageFilter: (prev, {documentId, schema, schemaType}) => {
62
+ if (schemaType === assistDocumentTypeName) {
63
+ return []
64
+ }
56
65
  const docSchema = schema.get(schemaType)
57
66
  if (docSchema && isObjectSchemaType(docSchema) && isSchemaAssistEnabled(docSchema)) {
58
67
  return [...prev, createAssistDocumentPresence(documentId, docSchema)]
@@ -88,7 +88,7 @@ export function AssistAvatar(props: {state?: 'present' | 'active'}) {
88
88
  </Outline>
89
89
  <IconDisc>
90
90
  <Text as="span" size={0} style={{color: 'inherit'}}>
91
- <SparklesIcon />
91
+ <SparklesIcon style={{color: 'inherit'}} />
92
92
  </Text>
93
93
  </IconDisc>
94
94
  </Root>
@@ -18,6 +18,8 @@ import {
18
18
  instructionContextTypeName,
19
19
  instructionTaskTypeName,
20
20
  instructionTypeName,
21
+ outputFieldTypeName,
22
+ outputTypeTypeName,
21
23
  promptTypeName,
22
24
  userInputTypeName,
23
25
  } from '../types'
@@ -37,11 +39,13 @@ import {PromptInput} from '../assistDocument/components/instruction/PromptInput'
37
39
  import {instructionGuideUrl} from '../constants'
38
40
  import {InstructionsArrayField} from '../assistDocument/components/InstructionsArrayField'
39
41
  import {getFieldRefsWithDocument} from '../assistInspector/helpers'
42
+ import {InstructionOutputField} from '../assistDocument/components/instruction/InstructionOutputField'
43
+ import {InstructionOutputInput} from '../assistDocument/components/instruction/InstructionOutputInput'
40
44
 
41
45
  export const fieldReference = defineType({
42
46
  type: 'object',
43
47
  name: fieldReferenceTypeName,
44
- title: 'Document field',
48
+ title: 'Field',
45
49
  icon: ThListIcon,
46
50
 
47
51
  fields: [
@@ -329,6 +333,41 @@ export const instruction = defineType({
329
333
  return context.currentUser?.id ?? ''
330
334
  },
331
335
  }),
336
+ defineField({
337
+ type: 'array',
338
+ name: 'output',
339
+ title: 'Output filter',
340
+ components: {
341
+ input: InstructionOutputInput,
342
+ field: InstructionOutputField,
343
+ },
344
+ of: [
345
+ defineArrayMember({
346
+ type: 'object' as const,
347
+ name: outputFieldTypeName,
348
+ title: 'Output field',
349
+ fields: [
350
+ {
351
+ type: 'string',
352
+ name: 'path',
353
+ title: 'Path',
354
+ },
355
+ ],
356
+ }),
357
+ defineArrayMember({
358
+ type: 'object' as const,
359
+ name: outputTypeTypeName,
360
+ title: 'Output type',
361
+ fields: [
362
+ {
363
+ type: 'string',
364
+ name: 'type',
365
+ title: 'Type',
366
+ },
367
+ ],
368
+ }),
369
+ ],
370
+ }),
332
371
  ],
333
372
  })
334
373
 
@@ -32,7 +32,7 @@ describe('serializeSchema', () => {
32
32
  name: 'article',
33
33
  fields: [{type: 'string', name: 'title'}],
34
34
  options: {
35
- aiWritingAssistance: {
35
+ aiAssist: {
36
36
  exclude: true,
37
37
  },
38
38
  },
@@ -123,7 +123,7 @@ describe('serializeSchema', () => {
123
123
  })
124
124
 
125
125
  test('should not serialize excluded fields or types or types with every member excluded', () => {
126
- const options: AssistOptions = {aiWritingAssistance: {exclude: true}}
126
+ const options: AssistOptions = {aiAssist: {exclude: true}}
127
127
 
128
128
  const schema = Schema.compile({
129
129
  name: 'test',
@@ -138,14 +138,14 @@ describe('serializeSchema', () => {
138
138
  name: 'excludedObject',
139
139
  // all fields excluded should exclude field
140
140
  fields: [{type: 'string', name: 'title', options}],
141
- options: {aiWritingAssistance: {exclude: true}},
141
+ options: {aiAssist: {exclude: true}},
142
142
  }),
143
143
  defineField({
144
144
  type: 'array',
145
145
  name: 'excludedArray',
146
146
  // all items excluded should exclude field
147
147
  of: [{type: 'object', name: 'remove', options}, {type: 'excluded'}],
148
- options: {aiWritingAssistance: {exclude: true}},
148
+ options: {aiAssist: {exclude: true}},
149
149
  }),
150
150
  //image without extra fields should be excluded
151
151
  defineField({type: 'image', name: 'image'}),
@@ -169,7 +169,7 @@ describe('serializeSchema', () => {
169
169
  type: 'object',
170
170
  name: 'excluded',
171
171
  fields: [{type: 'string', name: 'title', options}],
172
- options: {aiWritingAssistance: {exclude: true}},
172
+ options: {aiAssist: {exclude: true}},
173
173
  }),
174
174
  ...mockStudioTypes,
175
175
  ],
@@ -484,7 +484,7 @@ describe('serializeSchema', () => {
484
484
  ])
485
485
  })
486
486
 
487
- test('should exclude truthy hidden and readonly', () => {
487
+ test('should annotate truthy readonly', () => {
488
488
  const schema = Schema.compile({
489
489
  name: 'test',
490
490
  types: [
@@ -492,7 +492,7 @@ describe('serializeSchema', () => {
492
492
  type: 'document',
493
493
  name: 'article',
494
494
  fields: [
495
- {type: 'string', name: 'title', hidden: () => true},
495
+ {type: 'string', name: 'title', readOnly: () => true},
496
496
  {type: 'some', name: 'some'},
497
497
  ],
498
498
  },
@@ -507,6 +507,237 @@ describe('serializeSchema', () => {
507
507
 
508
508
  const serializedTypes = serializeSchema(schema, {leanFormat: true})
509
509
 
510
- expect(serializedTypes).toEqual([])
510
+ expect(serializedTypes).toEqual([
511
+ {
512
+ name: 'article',
513
+ title: 'Article',
514
+ type: 'document',
515
+ fields: [
516
+ {name: 'title', readOnly: 'function', title: 'Title', type: 'string'},
517
+ {name: 'some', readOnly: true, title: 'Some', type: 'some'},
518
+ ],
519
+ },
520
+ {
521
+ name: 'some',
522
+ readOnly: true,
523
+ title: 'Some',
524
+ type: 'object',
525
+ fields: [{name: 'title', title: 'Title', type: 'string'}],
526
+ },
527
+ ])
528
+ })
529
+
530
+ test('should annotate truthy hidden', () => {
531
+ const schema = Schema.compile({
532
+ name: 'test',
533
+ types: [
534
+ {
535
+ type: 'document',
536
+ name: 'article',
537
+ fields: [
538
+ {type: 'string', name: 'title', hidden: true},
539
+ {type: 'some', name: 'some'},
540
+ ],
541
+ },
542
+ defineType({
543
+ type: 'object',
544
+ name: 'some',
545
+ hidden: () => true,
546
+ fields: [{type: 'string', name: 'title'}],
547
+ }),
548
+ ],
549
+ })
550
+
551
+ const serializedTypes = serializeSchema(schema, {leanFormat: true})
552
+
553
+ expect(serializedTypes).toEqual([
554
+ {
555
+ name: 'article',
556
+ title: 'Article',
557
+ type: 'document',
558
+ fields: [
559
+ {hidden: true, name: 'title', title: 'Title', type: 'string'},
560
+ {hidden: 'function', name: 'some', title: 'Some', type: 'some'},
561
+ ],
562
+ },
563
+ {
564
+ hidden: 'function',
565
+ name: 'some',
566
+ title: 'Some',
567
+ type: 'object',
568
+ fields: [{name: 'title', title: 'Title', type: 'string'}],
569
+ },
570
+ ])
571
+ })
572
+
573
+ test('should serialize annotations', () => {
574
+ const schema = Schema.compile({
575
+ name: 'test',
576
+ types: [
577
+ defineType({
578
+ type: 'object',
579
+ name: 'annotation',
580
+ fields: [defineField({type: 'string', name: 'fact', title: 'Fact'})],
581
+ }),
582
+ defineType({
583
+ name: 'blockAlias',
584
+ type: 'block',
585
+ marks: {annotations: [{type: 'annotation'}]},
586
+ }),
587
+ defineType({
588
+ type: 'document',
589
+ name: 'article',
590
+ fields: [
591
+ {
592
+ type: 'array',
593
+ name: 'inlinePte',
594
+ of: [
595
+ defineArrayMember({
596
+ type: 'block',
597
+ marks: {
598
+ annotations: [
599
+ defineArrayMember({
600
+ type: 'object',
601
+ name: 'inline-annotation',
602
+ fields: [defineField({type: 'string', name: 'fact', title: 'Fact'})],
603
+ }),
604
+ ],
605
+ },
606
+ }),
607
+ ],
608
+ },
609
+ {type: 'array', name: 'pte', of: [{type: 'blockAlias'}]},
610
+ ],
611
+ }),
612
+ ],
613
+ })
614
+
615
+ const serializedTypes = serializeSchema(schema, {leanFormat: true})
616
+
617
+ expect(serializedTypes).toEqual([
618
+ {
619
+ name: 'annotation',
620
+ title: 'Annotation',
621
+ type: 'object',
622
+ fields: [{name: 'fact', title: 'Fact', type: 'string'}],
623
+ },
624
+ {
625
+ name: 'article',
626
+ title: 'Article',
627
+ type: 'document',
628
+ fields: [
629
+ {
630
+ name: 'inlinePte',
631
+ title: 'Inline Pte',
632
+ type: 'array',
633
+ of: [
634
+ {
635
+ name: 'block',
636
+ title: 'Block',
637
+ type: 'block',
638
+ inlineOf: [],
639
+ annotations: [
640
+ {
641
+ name: 'inline-annotation',
642
+ title: 'Inline Annotation',
643
+ type: 'object',
644
+ fields: [{name: 'fact', title: 'Fact', type: 'string'}],
645
+ },
646
+ ],
647
+ },
648
+ ],
649
+ },
650
+ {
651
+ name: 'pte',
652
+ of: [{name: 'blockAlias', title: 'Block', type: 'blockAlias'}],
653
+ title: 'Pte',
654
+ type: 'array',
655
+ },
656
+ ],
657
+ },
658
+ {
659
+ name: 'blockAlias',
660
+ title: 'Block',
661
+ type: 'block',
662
+ annotations: [{name: 'annotation', title: 'Annotation', type: 'annotation'}],
663
+ inlineOf: [],
664
+ },
665
+ ])
666
+ })
667
+
668
+ test('should serialize inline block types', () => {
669
+ const schema = Schema.compile({
670
+ name: 'test',
671
+ types: [
672
+ defineType({
673
+ type: 'object',
674
+ name: 'inline-block',
675
+ fields: [
676
+ defineField({type: 'string', name: 'fact', title: 'Fact'}),
677
+ defineField({type: 'blockAlias', name: 'recurse', title: 'Recursive'}),
678
+ ],
679
+ }),
680
+ defineType({
681
+ name: 'blockAlias',
682
+ type: 'block',
683
+ of: [{type: 'inline-block'}],
684
+ }),
685
+ defineType({
686
+ type: 'document',
687
+ name: 'article',
688
+ fields: [{type: 'array', name: 'pte', of: [{type: 'blockAlias'}]}],
689
+ }),
690
+ ],
691
+ })
692
+
693
+ const serializedTypes = serializeSchema(schema, {leanFormat: true})
694
+
695
+ expect(serializedTypes).toEqual([
696
+ {
697
+ name: 'article',
698
+ title: 'Article',
699
+ type: 'document',
700
+ fields: [
701
+ {
702
+ name: 'pte',
703
+ of: [
704
+ {
705
+ name: 'blockAlias',
706
+ title: 'Block',
707
+ type: 'blockAlias',
708
+ },
709
+ ],
710
+ title: 'Pte',
711
+ type: 'array',
712
+ },
713
+ ],
714
+ },
715
+ {
716
+ name: 'blockAlias',
717
+ title: 'Block',
718
+ type: 'block',
719
+ annotations: [],
720
+ inlineOf: [
721
+ {
722
+ name: 'inline-block',
723
+ title: 'Inline Block',
724
+ type: 'inline-block',
725
+ },
726
+ ],
727
+ },
728
+ {
729
+ name: 'inline-block',
730
+ title: 'Inline Block',
731
+ type: 'object',
732
+ fields: [
733
+ {name: 'fact', title: 'Fact', type: 'string'},
734
+ {
735
+ name: 'recurse',
736
+ title: 'Recursive',
737
+ type: 'blockAlias',
738
+ },
739
+ ],
740
+ },
741
+ ])
511
742
  })
512
743
  })