@sanity/document-internationalization 2.0.2 → 2.1.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.
@@ -25,7 +25,7 @@ export function DocumentInternationalizationMenu(
25
25
  props: DocumentInternationalizationMenuProps
26
26
  ) {
27
27
  const {documentId} = props
28
- const schemaType = props.schemaType.name
28
+ const schemaType = props.schemaType
29
29
  const {languageField, supportedLanguages} =
30
30
  useDocumentInternationalizationContext()
31
31
 
@@ -64,7 +64,7 @@ export function DocumentInternationalizationMenu(
64
64
  }, [loading, metadata?._id])
65
65
 
66
66
  // Duplicate a new language version from the most recent version of this document
67
- const {draft, published} = useEditState(documentId, schemaType)
67
+ const {draft, published} = useEditState(documentId, schemaType.name)
68
68
  const source = draft || published
69
69
 
70
70
  // Check for data issues
@@ -197,7 +197,7 @@ export function DocumentInternationalizationMenu(
197
197
  return null
198
198
  }
199
199
 
200
- if (!schemaType) {
200
+ if (!schemaType || !schemaType.name) {
201
201
  return null
202
202
  }
203
203
 
@@ -11,17 +11,18 @@ import {
11
11
  } from '@sanity/ui'
12
12
  import {uuid} from '@sanity/uuid'
13
13
  import {useCallback} from 'react'
14
- import {SanityDocument, useClient} from 'sanity'
14
+ import {ObjectSchemaType, SanityDocument, useClient} from 'sanity'
15
15
 
16
- import {API_VERSION, METADATA_SCHEMA_NAME} from '../constants'
16
+ import {METADATA_SCHEMA_NAME} from '../constants'
17
17
  import {useOpenInNewPane} from '../hooks/useOpenInNewPane'
18
18
  import {Language, Metadata, TranslationReference} from '../types'
19
19
  import {createReference} from '../utils/createReference'
20
+ import {removeExcludedPaths} from '../utils/excludePaths'
20
21
  import {useDocumentInternationalizationContext} from './DocumentInternationalizationContext'
21
22
 
22
23
  type LanguageOptionProps = {
23
24
  language: Language
24
- schemaType: string
25
+ schemaType: ObjectSchemaType
25
26
  documentId: string
26
27
  disabled: boolean
27
28
  current: boolean
@@ -53,7 +54,7 @@ export default function LanguageOption(props: LanguageOptionProps) {
53
54
  const client = useClient({apiVersion})
54
55
  const toast = useToast()
55
56
 
56
- const open = useOpenInNewPane(translation?.value?._ref, schemaType)
57
+ const open = useOpenInNewPane(translation?.value?._ref, schemaType.name)
57
58
  const handleOpen = useCallback(() => open(), [open])
58
59
 
59
60
  const handleCreate = useCallback(async () => {
@@ -73,32 +74,38 @@ export default function LanguageOption(props: LanguageOptionProps) {
73
74
 
74
75
  // 1. Duplicate source document
75
76
  const newTranslationDocumentId = uuid()
76
- const newTranslationDocument = {
77
+ let newTranslationDocument = {
77
78
  ...source,
78
79
  _id: `drafts.${newTranslationDocumentId}`,
79
80
  // 2. Update language of the translation
80
81
  [languageField]: language.id,
81
82
  }
82
83
 
84
+ // Remove fields / paths we don't want to duplicate
85
+ newTranslationDocument = removeExcludedPaths(
86
+ newTranslationDocument,
87
+ schemaType
88
+ ) as SanityDocument
89
+
83
90
  transaction.create(newTranslationDocument)
84
91
 
85
92
  // 3. Maybe create the metadata document
86
93
  const sourceReference = createReference(
87
94
  sourceLanguageId,
88
95
  documentId,
89
- schemaType,
96
+ schemaType.name,
90
97
  !weakReferences
91
98
  )
92
99
  const newTranslationReference = createReference(
93
100
  language.id,
94
101
  newTranslationDocumentId,
95
- schemaType,
102
+ schemaType.name,
96
103
  !weakReferences
97
104
  )
98
105
  const newMetadataDocument = {
99
106
  _id: metadataId,
100
107
  _type: METADATA_SCHEMA_NAME,
101
- schemaTypes: [schemaType],
108
+ schemaTypes: [schemaType.name],
102
109
  translations: [sourceReference],
103
110
  }
104
111
 
package/src/types.ts CHANGED
@@ -1,3 +1,5 @@
1
+ /* eslint-disable no-unused-vars */
2
+
1
3
  import type {
2
4
  FieldDefinition,
3
5
  KeyedObject,
@@ -47,3 +49,33 @@ export type DocumentInternationalizationMenuProps = {
47
49
  schemaType: ObjectSchemaType
48
50
  documentId: string
49
51
  }
52
+
53
+ // Extend Sanity schema definitions
54
+ export interface DocumentInternationalizationSchemaOpts {
55
+ documentInternationalization?: {
56
+ /** Set to true to disable duplication of this field or type */
57
+ exclude?: boolean
58
+ }
59
+ }
60
+
61
+ declare module 'sanity' {
62
+ interface ArrayOptions extends DocumentInternationalizationSchemaOpts {}
63
+ interface BlockOptions extends DocumentInternationalizationSchemaOpts {}
64
+ interface BooleanOptions extends DocumentInternationalizationSchemaOpts {}
65
+ interface CrossDatasetReferenceOptions
66
+ extends DocumentInternationalizationSchemaOpts {}
67
+ interface DateOptions extends DocumentInternationalizationSchemaOpts {}
68
+ interface DatetimeOptions extends DocumentInternationalizationSchemaOpts {}
69
+ interface FileOptions extends DocumentInternationalizationSchemaOpts {}
70
+ interface GeopointOptions extends DocumentInternationalizationSchemaOpts {}
71
+ interface ImageOptions extends DocumentInternationalizationSchemaOpts {}
72
+ interface NumberOptions extends DocumentInternationalizationSchemaOpts {}
73
+ interface ObjectOptions extends DocumentInternationalizationSchemaOpts {}
74
+ interface ReferenceBaseOptions
75
+ extends DocumentInternationalizationSchemaOpts {}
76
+ interface SlugOptions extends DocumentInternationalizationSchemaOpts {}
77
+ interface StringOptions extends DocumentInternationalizationSchemaOpts {}
78
+ interface TextOptions extends DocumentInternationalizationSchemaOpts {}
79
+ interface UrlOptions extends DocumentInternationalizationSchemaOpts {}
80
+ interface EmailOptions extends DocumentInternationalizationSchemaOpts {}
81
+ }
@@ -0,0 +1,123 @@
1
+ import {extractWithPath, Mutation} from '@sanity/mutator'
2
+ import {
3
+ isDocumentSchemaType,
4
+ ObjectSchemaType,
5
+ Path,
6
+ pathToString,
7
+ SanityDocument,
8
+ SchemaType,
9
+ } from 'sanity'
10
+
11
+ export interface DocumentMember {
12
+ schemaType: SchemaType
13
+ path: Path
14
+ name: string
15
+ value: unknown
16
+ }
17
+
18
+ export function removeExcludedPaths(
19
+ doc: SanityDocument | null,
20
+ schemaType: ObjectSchemaType
21
+ ): SanityDocument | null {
22
+ // If the supplied doc is null or the schemaType
23
+ // isn't a document, return as is.
24
+ if (!isDocumentSchemaType(schemaType) || !doc) {
25
+ return doc
26
+ }
27
+
28
+ // The extractPaths function gets all the fields in the doc with
29
+ // a value, along with their schemaTypes and paths. We'll end up
30
+ // with an array of paths in string form which we want to exclude
31
+ const pathsToExclude: string[] = extractPaths(doc, schemaType, [])
32
+ // We filter for any fields which should be excluded from the document
33
+ // duplicate action, based on the schemaType option being set.
34
+ .filter(
35
+ (field) =>
36
+ field.schemaType?.options?.documentInternationalization?.exclude ===
37
+ true
38
+ )
39
+ // then we return the stringified version of the path
40
+ .map((field) => {
41
+ return pathToString(field.path)
42
+ })
43
+
44
+ // Now we can use the Mutation class from @sanity/mutator to patch the document
45
+ // to remove all the paths that are for one of the excluded fields. This is just
46
+ // done locally, and the documents themselves are not patched in the Content Lake.
47
+ const mut = new Mutation({
48
+ mutations: [
49
+ {
50
+ patch: {
51
+ id: doc._id,
52
+ unset: pathsToExclude,
53
+ },
54
+ },
55
+ ],
56
+ })
57
+
58
+ return mut.apply(doc) as SanityDocument
59
+ }
60
+
61
+ function extractPaths(
62
+ doc: SanityDocument,
63
+ schemaType: ObjectSchemaType,
64
+ path: Path
65
+ ): DocumentMember[] {
66
+ return schemaType.fields.reduce<DocumentMember[]>((acc, field) => {
67
+ const fieldPath = [...path, field.name]
68
+ const fieldSchema = field.type
69
+ const {value} = extractWithPath(pathToString(fieldPath), doc)[0] ?? {}
70
+ if (!value) {
71
+ return acc
72
+ }
73
+
74
+ const thisFieldWithPath: DocumentMember = {
75
+ path: fieldPath,
76
+ name: field.name,
77
+ schemaType: fieldSchema,
78
+ value,
79
+ }
80
+
81
+ if (fieldSchema.jsonType === 'object') {
82
+ const innerFields = extractPaths(doc, fieldSchema, fieldPath)
83
+
84
+ return [...acc, thisFieldWithPath, ...innerFields]
85
+ } else if (
86
+ fieldSchema.jsonType === 'array' &&
87
+ fieldSchema.of.length &&
88
+ fieldSchema.of.some((item) => 'fields' in item)
89
+ ) {
90
+ const {value: arrayValue} =
91
+ extractWithPath(pathToString(fieldPath), doc)[0] ?? {}
92
+
93
+ let arrayPaths: DocumentMember[] = []
94
+ if ((arrayValue as any)?.length) {
95
+ for (const item of arrayValue as any[]) {
96
+ const itemPath = [...fieldPath, {_key: item._key}]
97
+ let itemSchema = fieldSchema.of.find((t) => t.name === item._type)
98
+ if (!item._type) {
99
+ itemSchema = fieldSchema.of[0]
100
+ }
101
+ if (item._key && itemSchema) {
102
+ const innerFields = extractPaths(
103
+ doc,
104
+ itemSchema as ObjectSchemaType,
105
+ itemPath
106
+ )
107
+ const arrayMember = {
108
+ path: itemPath,
109
+ name: item._key,
110
+ schemaType: itemSchema,
111
+ value: item,
112
+ }
113
+ arrayPaths = [...arrayPaths, arrayMember, ...innerFields]
114
+ }
115
+ }
116
+ }
117
+
118
+ return [...acc, thisFieldWithPath, ...arrayPaths]
119
+ }
120
+
121
+ return [...acc, thisFieldWithPath]
122
+ }, [])
123
+ }