@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.
- package/LICENSE +1 -1
- package/README.md +17 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.esm.js +82 -9
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +81 -8
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
- package/src/components/DocumentInternationalizationMenu.tsx +3 -3
- package/src/components/LanguageOption.tsx +15 -8
- package/src/types.ts +32 -0
- package/src/utils/excludePaths.ts +123 -0
|
@@ -25,7 +25,7 @@ export function DocumentInternationalizationMenu(
|
|
|
25
25
|
props: DocumentInternationalizationMenuProps
|
|
26
26
|
) {
|
|
27
27
|
const {documentId} = props
|
|
28
|
-
const schemaType = props.schemaType
|
|
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 {
|
|
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:
|
|
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
|
-
|
|
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
|
+
}
|