sanity-plugin-internationalized-array 1.7.0 → 1.9.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/README.md +29 -7
- package/lib/index.d.ts +10 -10
- package/lib/index.esm.js +19 -13
- package/lib/index.esm.js.map +1 -1
- package/lib/index.js +19 -13
- package/lib/index.js.map +1 -1
- package/package.json +2 -2
- package/src/components/AddButtons.tsx +39 -0
- package/src/components/DocumentAddButtons.tsx +112 -0
- package/src/components/InternationalizedArray.tsx +84 -173
- package/src/components/InternationalizedArrayContext.tsx +106 -0
- package/src/components/InternationalizedInput.tsx +3 -3
- package/src/constants.ts +12 -0
- package/src/fieldActions/index.ts +132 -0
- package/src/plugin.tsx +73 -40
- package/src/schema/array.ts +3 -2
- package/src/types.ts +10 -10
- package/src/utils/checkAllLanguagesArePresent.ts +14 -0
- package/src/utils/createAddAllTitle.ts +16 -0
- package/src/utils/createAddLanguagePatches.ts +76 -0
- package/src/utils/createValueSchemaTypeName.ts +5 -0
- package/src/components/languageContext.tsx +0 -9
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sanity-plugin-internationalized-array",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.0",
|
|
4
4
|
"description": "Store localized fields in an array to save on attributes",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"sanity",
|
|
@@ -83,7 +83,7 @@
|
|
|
83
83
|
"react-dom": "^18",
|
|
84
84
|
"react-is": "^18",
|
|
85
85
|
"rimraf": "^4.1.2",
|
|
86
|
-
"sanity": "^3.
|
|
86
|
+
"sanity": "^3.14.1",
|
|
87
87
|
"semantic-release": "^20.1.0",
|
|
88
88
|
"typescript": "^4.9.5"
|
|
89
89
|
},
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import {AddIcon} from '@sanity/icons'
|
|
2
|
+
import {Button, Grid} from '@sanity/ui'
|
|
3
|
+
import React from 'react'
|
|
4
|
+
|
|
5
|
+
import {MAX_COLUMNS} from '../constants'
|
|
6
|
+
import {Language, Value} from '../types'
|
|
7
|
+
|
|
8
|
+
type AddButtonsProps = {
|
|
9
|
+
languages: Language[]
|
|
10
|
+
readOnly: boolean
|
|
11
|
+
value: Value[] | undefined
|
|
12
|
+
onClick: (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export default function AddButtons(props: AddButtonsProps) {
|
|
16
|
+
const {languages, readOnly, value, onClick} = props
|
|
17
|
+
|
|
18
|
+
return languages.length > 0 ? (
|
|
19
|
+
<Grid columns={Math.min(languages.length, MAX_COLUMNS)} gap={2}>
|
|
20
|
+
{languages.map((language) => (
|
|
21
|
+
<Button
|
|
22
|
+
key={language.id}
|
|
23
|
+
tone="primary"
|
|
24
|
+
mode="ghost"
|
|
25
|
+
fontSize={1}
|
|
26
|
+
disabled={
|
|
27
|
+
readOnly ||
|
|
28
|
+
Boolean(value?.find((item) => item._key === language.id))
|
|
29
|
+
}
|
|
30
|
+
text={language.id.toUpperCase()}
|
|
31
|
+
// Only show plus icon if there's one row or less
|
|
32
|
+
icon={languages.length > MAX_COLUMNS ? undefined : AddIcon}
|
|
33
|
+
value={language.id}
|
|
34
|
+
onClick={onClick}
|
|
35
|
+
/>
|
|
36
|
+
))}
|
|
37
|
+
</Grid>
|
|
38
|
+
) : null
|
|
39
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import {Box, Stack, Text, useToast} from '@sanity/ui'
|
|
2
|
+
import React, {useCallback, useMemo} from 'react'
|
|
3
|
+
import {
|
|
4
|
+
insert,
|
|
5
|
+
isSanityDocument,
|
|
6
|
+
ObjectSchemaType,
|
|
7
|
+
PatchEvent,
|
|
8
|
+
setIfMissing,
|
|
9
|
+
} from 'sanity'
|
|
10
|
+
import {useDocumentPane} from 'sanity/desk'
|
|
11
|
+
|
|
12
|
+
import {createValueSchemaTypeName} from '../utils/createValueSchemaTypeName'
|
|
13
|
+
import AddButtons from './AddButtons'
|
|
14
|
+
import {useInternationalizedArrayContext} from './InternationalizedArrayContext'
|
|
15
|
+
|
|
16
|
+
type DocumentAddButtonsProps = {
|
|
17
|
+
schemaType: ObjectSchemaType
|
|
18
|
+
value: Record<string, any> | undefined
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export default function DocumentAddButtons(props: DocumentAddButtonsProps) {
|
|
22
|
+
const {filteredLanguages} = useInternationalizedArrayContext()
|
|
23
|
+
const {fields} = props.schemaType
|
|
24
|
+
const value = isSanityDocument(props.value) ? props.value : undefined
|
|
25
|
+
|
|
26
|
+
const toast = useToast()
|
|
27
|
+
const {onChange} = useDocumentPane()
|
|
28
|
+
|
|
29
|
+
// Find every internationalizedArray field at the document root
|
|
30
|
+
// TODO: This should be a recursive search through nested fields
|
|
31
|
+
const internationalizedArrayFields = useMemo(
|
|
32
|
+
() =>
|
|
33
|
+
fields.filter((field) =>
|
|
34
|
+
field.type.name.startsWith('internationalizedArray')
|
|
35
|
+
),
|
|
36
|
+
[fields]
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
const handleDocumentButtonClick = useCallback(
|
|
40
|
+
(event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
|
41
|
+
const languageId = event.currentTarget.value
|
|
42
|
+
if (!languageId) {
|
|
43
|
+
toast.push({
|
|
44
|
+
status: 'error',
|
|
45
|
+
title: 'No language selected',
|
|
46
|
+
})
|
|
47
|
+
return
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (internationalizedArrayFields.length === 0) {
|
|
51
|
+
toast.push({
|
|
52
|
+
status: 'error',
|
|
53
|
+
title: 'No internationalizedArray fields found in document root',
|
|
54
|
+
})
|
|
55
|
+
return
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Find every internationalizedArray field that is empty for the selected language
|
|
59
|
+
const emptyLanguageFields = internationalizedArrayFields.filter(
|
|
60
|
+
(field) => {
|
|
61
|
+
const fieldValue = value?.[field.name]
|
|
62
|
+
const fieldValueLanguage =
|
|
63
|
+
fieldValue && Array.isArray(fieldValue)
|
|
64
|
+
? fieldValue.find((v) => v._key === languageId)
|
|
65
|
+
: undefined
|
|
66
|
+
|
|
67
|
+
return !fieldValueLanguage
|
|
68
|
+
}
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
// Write a new patch for each empty field
|
|
72
|
+
const patches = emptyLanguageFields
|
|
73
|
+
.map((field) => {
|
|
74
|
+
const fieldKey = field.name
|
|
75
|
+
|
|
76
|
+
return [
|
|
77
|
+
setIfMissing([], [fieldKey]),
|
|
78
|
+
insert(
|
|
79
|
+
[
|
|
80
|
+
{
|
|
81
|
+
_key: languageId,
|
|
82
|
+
_type: createValueSchemaTypeName(field.type),
|
|
83
|
+
},
|
|
84
|
+
],
|
|
85
|
+
'after',
|
|
86
|
+
[fieldKey, -1]
|
|
87
|
+
),
|
|
88
|
+
]
|
|
89
|
+
})
|
|
90
|
+
.flat()
|
|
91
|
+
|
|
92
|
+
onChange(PatchEvent.from(patches))
|
|
93
|
+
},
|
|
94
|
+
[internationalizedArrayFields, onChange, toast, value]
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
return (
|
|
98
|
+
<Stack space={3}>
|
|
99
|
+
<Box>
|
|
100
|
+
<Text size={1} weight="semibold">
|
|
101
|
+
Add translation to internationalized fields
|
|
102
|
+
</Text>
|
|
103
|
+
</Box>
|
|
104
|
+
<AddButtons
|
|
105
|
+
languages={filteredLanguages}
|
|
106
|
+
readOnly={false}
|
|
107
|
+
value={undefined}
|
|
108
|
+
onClick={handleDocumentButtonClick}
|
|
109
|
+
/>
|
|
110
|
+
</Stack>
|
|
111
|
+
)
|
|
112
|
+
}
|
|
@@ -1,31 +1,28 @@
|
|
|
1
1
|
import {AddIcon} from '@sanity/icons'
|
|
2
2
|
import {useLanguageFilterStudioContext} from '@sanity/language-filter'
|
|
3
|
-
import {Button, Grid, Stack, useToast} from '@sanity/ui'
|
|
4
|
-
import
|
|
5
|
-
import React, {useCallback, useDeferredValue, useEffect, useMemo} from 'react'
|
|
3
|
+
import {Button, Card, Grid, Stack, Text, useToast} from '@sanity/ui'
|
|
4
|
+
import React, {useCallback, useEffect, useMemo} from 'react'
|
|
6
5
|
import {
|
|
7
6
|
ArrayOfObjectsInputProps,
|
|
8
7
|
ArrayOfObjectsItem,
|
|
9
|
-
|
|
8
|
+
ArraySchemaType,
|
|
10
9
|
set,
|
|
11
10
|
setIfMissing,
|
|
12
|
-
useClient,
|
|
13
|
-
useFormBuilder,
|
|
14
11
|
useFormValue,
|
|
15
12
|
} from 'sanity'
|
|
16
|
-
import {suspend} from 'suspend-react'
|
|
17
13
|
|
|
18
|
-
import {namespace, version} from '../cache'
|
|
19
14
|
import {MAX_COLUMNS} from '../constants'
|
|
20
|
-
import type {
|
|
15
|
+
import type {Value} from '../types'
|
|
16
|
+
import {checkAllLanguagesArePresent} from '../utils/checkAllLanguagesArePresent'
|
|
17
|
+
import {createAddAllTitle} from '../utils/createAddAllTitle'
|
|
18
|
+
import {createAddLanguagePatches} from '../utils/createAddLanguagePatches'
|
|
19
|
+
import AddButtons from './AddButtons'
|
|
21
20
|
import Feedback from './Feedback'
|
|
22
|
-
import {
|
|
23
|
-
// TODO: Move this provider to the root component
|
|
24
|
-
import {LanguageProvider} from './languageContext'
|
|
21
|
+
import {useInternationalizedArrayContext} from './InternationalizedArrayContext'
|
|
25
22
|
|
|
26
23
|
export type InternationalizedArrayProps = ArrayOfObjectsInputProps<
|
|
27
24
|
Value,
|
|
28
|
-
|
|
25
|
+
ArraySchemaType
|
|
29
26
|
>
|
|
30
27
|
|
|
31
28
|
export default function InternationalizedArray(
|
|
@@ -35,30 +32,15 @@ export default function InternationalizedArray(
|
|
|
35
32
|
|
|
36
33
|
const readOnly =
|
|
37
34
|
typeof schemaType.readOnly === 'boolean' ? schemaType.readOnly : false
|
|
38
|
-
const {options} = schemaType
|
|
39
35
|
const toast = useToast()
|
|
40
|
-
const {value: document} = useFormBuilder()
|
|
41
|
-
const deferredDocument = useDeferredValue(document)
|
|
42
|
-
const selectedValue = useMemo(
|
|
43
|
-
() => getSelectedValue(options.select, deferredDocument),
|
|
44
|
-
[options.select, deferredDocument]
|
|
45
|
-
)
|
|
46
36
|
|
|
47
|
-
const {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
if (typeof options.languages === 'function') {
|
|
55
|
-
return options.languages(client, selectedValue)
|
|
56
|
-
}
|
|
57
|
-
return options.languages
|
|
58
|
-
},
|
|
59
|
-
[version, namespace, selectedValue],
|
|
60
|
-
{equal}
|
|
61
|
-
)
|
|
37
|
+
const {
|
|
38
|
+
languages,
|
|
39
|
+
filteredLanguages,
|
|
40
|
+
defaultLanguages,
|
|
41
|
+
buttonAddAll,
|
|
42
|
+
buttonLocations,
|
|
43
|
+
} = useInternationalizedArrayContext()
|
|
62
44
|
|
|
63
45
|
// Support updating the UI if languageFilter is installed
|
|
64
46
|
const {selectedLanguageIds, options: languageFilterOptions} =
|
|
@@ -96,74 +78,27 @@ export default function InternationalizedArray(
|
|
|
96
78
|
[languageFilterEnabled, members, languageFilterOptions, selectedLanguageIds]
|
|
97
79
|
)
|
|
98
80
|
|
|
99
|
-
const filteredLanguages = useMemo(
|
|
100
|
-
() =>
|
|
101
|
-
languageFilterEnabled
|
|
102
|
-
? languages.filter((language) =>
|
|
103
|
-
selectedLanguageIds.includes(language.id)
|
|
104
|
-
)
|
|
105
|
-
: languages,
|
|
106
|
-
[languageFilterEnabled, languages, selectedLanguageIds]
|
|
107
|
-
)
|
|
108
|
-
|
|
109
81
|
const handleAddLanguage = useCallback(
|
|
110
82
|
(param?: React.MouseEvent<HTMLButtonElement, MouseEvent> | string[]) => {
|
|
111
83
|
if (!filteredLanguages?.length) {
|
|
112
84
|
return
|
|
113
85
|
}
|
|
114
86
|
|
|
115
|
-
const
|
|
87
|
+
const addLanguageKeys: string[] = Array.isArray(param)
|
|
116
88
|
? param
|
|
117
89
|
: ([param?.currentTarget?.value].filter(Boolean) as string[])
|
|
118
|
-
const itemBase = {_type: `${schemaType.name}Value`}
|
|
119
|
-
|
|
120
|
-
// Create new items
|
|
121
|
-
const newItems =
|
|
122
|
-
Array.isArray(languageIds) && languageIds.length > 0
|
|
123
|
-
? // Just one for this language
|
|
124
|
-
languageIds.map((id) => ({...itemBase, _key: id}))
|
|
125
|
-
: // Or one for every missing language
|
|
126
|
-
filteredLanguages
|
|
127
|
-
.filter((language) =>
|
|
128
|
-
value?.length
|
|
129
|
-
? !value.find((v) => v._key === language.id)
|
|
130
|
-
: true
|
|
131
|
-
)
|
|
132
|
-
.map((language) => ({...itemBase, _key: language.id}))
|
|
133
|
-
|
|
134
|
-
// Insert new items in the correct order
|
|
135
|
-
const languagesInUse = value?.length ? value.map((v) => v) : []
|
|
136
|
-
|
|
137
|
-
const insertions = newItems.map((item) => {
|
|
138
|
-
// What's the original index of this language?
|
|
139
|
-
const languageIndex = languages.findIndex((l) => item._key === l.id)
|
|
140
|
-
|
|
141
|
-
// What languages are there beyond that index?
|
|
142
|
-
const remainingLanguages = languages.slice(languageIndex + 1)
|
|
143
90
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
// Keep local state up to date incase multiple insertions are being made
|
|
151
|
-
if (nextLanguageIndex < 0) {
|
|
152
|
-
languagesInUse.push(item)
|
|
153
|
-
} else {
|
|
154
|
-
languagesInUse.splice(nextLanguageIndex, 0, item)
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
return nextLanguageIndex < 0
|
|
158
|
-
? // No next language (-1), add to end of array
|
|
159
|
-
insert([item], 'after', [nextLanguageIndex])
|
|
160
|
-
: // Next language found, insert before that
|
|
161
|
-
insert([item], 'before', [nextLanguageIndex])
|
|
91
|
+
const patches = createAddLanguagePatches({
|
|
92
|
+
addLanguageKeys,
|
|
93
|
+
schemaType,
|
|
94
|
+
languages,
|
|
95
|
+
filteredLanguages,
|
|
96
|
+
value,
|
|
162
97
|
})
|
|
163
98
|
|
|
164
|
-
onChange([setIfMissing([]), ...
|
|
99
|
+
onChange([setIfMissing([]), ...patches])
|
|
165
100
|
},
|
|
166
|
-
[filteredLanguages, onChange, schemaType
|
|
101
|
+
[filteredLanguages, languages, onChange, schemaType, value]
|
|
167
102
|
)
|
|
168
103
|
|
|
169
104
|
// Create default fields if the document is not yet created
|
|
@@ -255,100 +190,76 @@ export default function InternationalizedArray(
|
|
|
255
190
|
}, [languagesOutOfOrder, allKeysAreLanguages, handleRestoreOrder])
|
|
256
191
|
|
|
257
192
|
// compare value keys with possible languages
|
|
258
|
-
const allLanguagesArePresent = useMemo(
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
return (
|
|
263
|
-
languagesInUseIds.length === filteredLanguageIds.length &&
|
|
264
|
-
languagesInUseIds.every((l) => filteredLanguageIds.includes(l))
|
|
265
|
-
)
|
|
266
|
-
}, [filteredLanguages, value])
|
|
193
|
+
const allLanguagesArePresent = useMemo(
|
|
194
|
+
() => checkAllLanguagesArePresent(filteredLanguages, value),
|
|
195
|
+
[filteredLanguages, value]
|
|
196
|
+
)
|
|
267
197
|
|
|
268
198
|
if (!languagesAreValid) {
|
|
269
199
|
return <Feedback />
|
|
270
200
|
}
|
|
271
201
|
|
|
202
|
+
const addButtonsAreVisible =
|
|
203
|
+
// Plugin was configured to display buttons here (default!)
|
|
204
|
+
buttonLocations.includes('field') &&
|
|
205
|
+
// There's at least one language visible
|
|
206
|
+
filteredLanguages?.length > 0 &&
|
|
207
|
+
// Not every language has a value yet
|
|
208
|
+
!allLanguagesArePresent
|
|
209
|
+
const fieldHasMembers = members?.length > 0
|
|
210
|
+
|
|
272
211
|
return (
|
|
273
|
-
<
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
disabled={
|
|
314
|
-
readOnly ||
|
|
315
|
-
Boolean(value?.find((item) => item._key === language.id))
|
|
316
|
-
}
|
|
317
|
-
text={language.id.toUpperCase()}
|
|
318
|
-
// Only show plus icon if there's one row or less
|
|
319
|
-
icon={
|
|
320
|
-
filteredLanguages.length > MAX_COLUMNS
|
|
321
|
-
? undefined
|
|
322
|
-
: AddIcon
|
|
323
|
-
}
|
|
324
|
-
value={language.id}
|
|
325
|
-
onClick={handleAddLanguage}
|
|
326
|
-
/>
|
|
327
|
-
))}
|
|
328
|
-
</Grid>
|
|
329
|
-
) : null}
|
|
212
|
+
<Stack space={2}>
|
|
213
|
+
{fieldHasMembers ? (
|
|
214
|
+
<>
|
|
215
|
+
{filteredMembers.map((member) => {
|
|
216
|
+
if (member.kind === 'item') {
|
|
217
|
+
return (
|
|
218
|
+
<ArrayOfObjectsItem
|
|
219
|
+
key={member.key}
|
|
220
|
+
member={member}
|
|
221
|
+
renderItem={props.renderItem}
|
|
222
|
+
renderField={props.renderField}
|
|
223
|
+
renderInput={props.renderInput}
|
|
224
|
+
renderPreview={props.renderPreview}
|
|
225
|
+
/>
|
|
226
|
+
)
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return null
|
|
230
|
+
})}
|
|
231
|
+
</>
|
|
232
|
+
) : null}
|
|
233
|
+
|
|
234
|
+
{/* Give some feedback in the UI so the field doesn't look "missing" */}
|
|
235
|
+
{!addButtonsAreVisible && !fieldHasMembers ? (
|
|
236
|
+
<Card border tone="transparent" padding={3} radius={2}>
|
|
237
|
+
<Text size={1}>
|
|
238
|
+
This internationalized field currently has no translations.
|
|
239
|
+
</Text>
|
|
240
|
+
</Card>
|
|
241
|
+
) : null}
|
|
242
|
+
|
|
243
|
+
{addButtonsAreVisible ? (
|
|
244
|
+
<Stack space={2}>
|
|
245
|
+
<AddButtons
|
|
246
|
+
languages={filteredLanguages}
|
|
247
|
+
value={value}
|
|
248
|
+
readOnly={readOnly}
|
|
249
|
+
onClick={handleAddLanguage}
|
|
250
|
+
/>
|
|
251
|
+
{buttonAddAll ? (
|
|
330
252
|
<Button
|
|
331
253
|
tone="primary"
|
|
332
254
|
mode="ghost"
|
|
333
255
|
disabled={readOnly || allLanguagesArePresent}
|
|
334
256
|
icon={AddIcon}
|
|
335
|
-
text={
|
|
336
|
-
// eslint-disable-next-line no-nested-ternary
|
|
337
|
-
value?.length
|
|
338
|
-
? `Add missing ${
|
|
339
|
-
filteredLanguages.length - value.length === 1
|
|
340
|
-
? `language`
|
|
341
|
-
: `languages`
|
|
342
|
-
}`
|
|
343
|
-
: filteredLanguages.length === 1
|
|
344
|
-
? `Add ${filteredLanguages[0].title} Field`
|
|
345
|
-
: `Add all languages`
|
|
346
|
-
}
|
|
257
|
+
text={createAddAllTitle(value, filteredLanguages)}
|
|
347
258
|
onClick={handleAddLanguage}
|
|
348
259
|
/>
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
</
|
|
260
|
+
) : null}
|
|
261
|
+
</Stack>
|
|
262
|
+
) : null}
|
|
263
|
+
</Stack>
|
|
353
264
|
)
|
|
354
265
|
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import {useLanguageFilterStudioContext} from '@sanity/language-filter'
|
|
2
|
+
import {Stack} from '@sanity/ui'
|
|
3
|
+
import equal from 'fast-deep-equal'
|
|
4
|
+
import {createContext, useContext, useDeferredValue, useMemo} from 'react'
|
|
5
|
+
import {ObjectInputProps, useClient, useFormBuilder} from 'sanity'
|
|
6
|
+
import {suspend} from 'suspend-react'
|
|
7
|
+
|
|
8
|
+
import {namespace, version} from '../cache'
|
|
9
|
+
import {CONFIG_DEFAULT} from '../constants'
|
|
10
|
+
import {Language, PluginConfig} from '../types'
|
|
11
|
+
import DocumentAddButtons from './DocumentAddButtons'
|
|
12
|
+
import {getSelectedValue} from './getSelectedValue'
|
|
13
|
+
|
|
14
|
+
// This provider makes the plugin config available to all components in the document form
|
|
15
|
+
// But with languages resolved and filtered languages updated base on @sanity/language-filter
|
|
16
|
+
|
|
17
|
+
type InternationalizedArrayContextProps = Required<PluginConfig> & {
|
|
18
|
+
languages: Language[]
|
|
19
|
+
filteredLanguages: Language[]
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const InternationalizedArrayContext =
|
|
23
|
+
createContext<InternationalizedArrayContextProps>({
|
|
24
|
+
...CONFIG_DEFAULT,
|
|
25
|
+
languages: [],
|
|
26
|
+
filteredLanguages: [],
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
export function useInternationalizedArrayContext() {
|
|
30
|
+
return useContext(InternationalizedArrayContext)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
type InternationalizedArrayProviderProps = ObjectInputProps & {
|
|
34
|
+
internationalizedArray: Required<PluginConfig>
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function InternationalizedArrayProvider(
|
|
38
|
+
props: InternationalizedArrayProviderProps
|
|
39
|
+
) {
|
|
40
|
+
const {internationalizedArray} = props
|
|
41
|
+
|
|
42
|
+
const client = useClient({apiVersion: internationalizedArray.apiVersion})
|
|
43
|
+
const {value: document} = useFormBuilder()
|
|
44
|
+
const deferredDocument = useDeferredValue(document)
|
|
45
|
+
const selectedValue = useMemo(
|
|
46
|
+
() => getSelectedValue(internationalizedArray.select, deferredDocument),
|
|
47
|
+
[internationalizedArray.select, deferredDocument]
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
// Fetch or return languages
|
|
51
|
+
const languages = Array.isArray(internationalizedArray.languages)
|
|
52
|
+
? internationalizedArray.languages
|
|
53
|
+
: suspend(
|
|
54
|
+
// eslint-disable-next-line require-await
|
|
55
|
+
async () => {
|
|
56
|
+
if (typeof internationalizedArray.languages === 'function') {
|
|
57
|
+
return internationalizedArray.languages(client, selectedValue)
|
|
58
|
+
}
|
|
59
|
+
return internationalizedArray.languages
|
|
60
|
+
},
|
|
61
|
+
[version, namespace],
|
|
62
|
+
{equal}
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
// Filter out some languages if language filter is enabled
|
|
66
|
+
const {selectedLanguageIds, options: languageFilterOptions} =
|
|
67
|
+
useLanguageFilterStudioContext()
|
|
68
|
+
|
|
69
|
+
const filteredLanguages = useMemo(() => {
|
|
70
|
+
const documentType = deferredDocument ? deferredDocument._type : undefined
|
|
71
|
+
const languageFilterEnabled =
|
|
72
|
+
typeof documentType === 'string' &&
|
|
73
|
+
languageFilterOptions.documentTypes.includes(documentType)
|
|
74
|
+
|
|
75
|
+
return languageFilterEnabled
|
|
76
|
+
? languages.filter((language) =>
|
|
77
|
+
selectedLanguageIds.includes(language.id)
|
|
78
|
+
)
|
|
79
|
+
: languages
|
|
80
|
+
}, [deferredDocument, languageFilterOptions, languages, selectedLanguageIds])
|
|
81
|
+
|
|
82
|
+
const showDocumentButtons =
|
|
83
|
+
internationalizedArray.buttonLocations.includes('document')
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<InternationalizedArrayContext.Provider
|
|
87
|
+
value={{
|
|
88
|
+
...internationalizedArray,
|
|
89
|
+
languages,
|
|
90
|
+
filteredLanguages,
|
|
91
|
+
}}
|
|
92
|
+
>
|
|
93
|
+
{showDocumentButtons ? (
|
|
94
|
+
<Stack space={5}>
|
|
95
|
+
<DocumentAddButtons
|
|
96
|
+
schemaType={props.schemaType}
|
|
97
|
+
value={props.value}
|
|
98
|
+
/>
|
|
99
|
+
{props.renderDefault(props)}
|
|
100
|
+
</Stack>
|
|
101
|
+
) : (
|
|
102
|
+
props.renderDefault(props)
|
|
103
|
+
)}
|
|
104
|
+
</InternationalizedArrayContext.Provider>
|
|
105
|
+
)
|
|
106
|
+
}
|
|
@@ -10,12 +10,12 @@ import {
|
|
|
10
10
|
Spinner,
|
|
11
11
|
Stack,
|
|
12
12
|
} from '@sanity/ui'
|
|
13
|
-
import React, {useCallback,
|
|
13
|
+
import React, {useCallback, useMemo} from 'react'
|
|
14
14
|
import {ObjectItemProps, useFormValue} from 'sanity'
|
|
15
15
|
import {set, unset} from 'sanity'
|
|
16
16
|
|
|
17
17
|
import {getToneFromValidation} from './getToneFromValidation'
|
|
18
|
-
import {
|
|
18
|
+
import {useInternationalizedArrayContext} from './InternationalizedArrayContext'
|
|
19
19
|
|
|
20
20
|
type InternationalizedValue = {
|
|
21
21
|
_type: string
|
|
@@ -44,7 +44,7 @@ export default function InternationalizedInput(
|
|
|
44
44
|
const {validation, value, onChange, readOnly} = inlineProps
|
|
45
45
|
|
|
46
46
|
// The parent array contains the languages from the plugin config
|
|
47
|
-
const {languages} =
|
|
47
|
+
const {languages} = useInternationalizedArrayContext()
|
|
48
48
|
|
|
49
49
|
const languageKeysInUse = useMemo(
|
|
50
50
|
() => parentValue?.map((v) => v._key) ?? [],
|
package/src/constants.ts
CHANGED
|
@@ -1 +1,13 @@
|
|
|
1
|
+
import {PluginConfig} from './types'
|
|
2
|
+
|
|
1
3
|
export const MAX_COLUMNS = 7
|
|
4
|
+
|
|
5
|
+
export const CONFIG_DEFAULT: Required<PluginConfig> = {
|
|
6
|
+
languages: [],
|
|
7
|
+
select: {},
|
|
8
|
+
defaultLanguages: [],
|
|
9
|
+
fieldTypes: [],
|
|
10
|
+
apiVersion: '2022-11-27',
|
|
11
|
+
buttonLocations: ['field'],
|
|
12
|
+
buttonAddAll: true,
|
|
13
|
+
}
|