sanity-plugin-internationalized-array 0.0.7 → 1.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 +82 -32
- package/lib/cjs/index.js +487 -0
- package/lib/cjs/index.js.map +1 -0
- package/lib/esm/index.js +480 -0
- package/lib/esm/index.js.map +1 -0
- package/lib/types/index.d.ts +12 -0
- package/lib/types/index.d.ts.map +1 -0
- package/package.json +50 -36
- package/sanity.json +7 -6
- package/src/components/Feedback.tsx +27 -0
- package/src/components/InternationalizedArrayInput.tsx +222 -0
- package/src/{LanguageArray → components}/Table.tsx +0 -0
- package/src/components/createFieldName.ts +20 -0
- package/src/components/getToneFromValidation.ts +18 -0
- package/src/index.ts +1 -3
- package/src/plugin.tsx +23 -0
- package/src/schema/array.ts +69 -0
- package/src/schema/object.ts +29 -0
- package/src/types.ts +19 -9
- package/v2-incompatible.js +11 -0
- package/lib/LanguageArray/Table.js +0 -88
- package/lib/LanguageArray/Table.js.map +0 -1
- package/lib/LanguageArray/ValueInput.js +0 -17
- package/lib/LanguageArray/ValueInput.js.map +0 -1
- package/lib/LanguageArray/index.js +0 -253
- package/lib/LanguageArray/index.js.map +0 -1
- package/lib/hooks/useUnsetInputComponent.js +0 -32
- package/lib/hooks/useUnsetInputComponent.js.map +0 -1
- package/lib/index.js +0 -13
- package/lib/index.js.map +0 -1
- package/lib/internationalizedArray.js +0 -105
- package/lib/internationalizedArray.js.map +0 -1
- package/lib/types.js +0 -2
- package/lib/types.js.map +0 -1
- package/migrations/transformObjectToArray.js +0 -94
- package/src/LanguageArray/ValueInput.tsx +0 -6
- package/src/LanguageArray/index.tsx +0 -311
- package/src/hooks/useUnsetInputComponent.tsx +0 -17
- package/src/internationalizedArray.ts +0 -84
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
/* eslint-disable no-console */
|
|
2
|
-
import sanityClient from 'part:@sanity/base/client'
|
|
3
|
-
|
|
4
|
-
const client = sanityClient.withConfig({apiVersion: `2022-07-14`})
|
|
5
|
-
|
|
6
|
-
// Run this script with: `sanity exec --with-user-token migrations/transformObjectToArray.js`
|
|
7
|
-
|
|
8
|
-
// This example shows how you may write a migration script that migrates an internationalized object
|
|
9
|
-
// To an internationalized array
|
|
10
|
-
|
|
11
|
-
// Transforms fields from:
|
|
12
|
-
// "greeting" {
|
|
13
|
-
// "en": "hello",
|
|
14
|
-
// "fr": "bonjour"
|
|
15
|
-
// }
|
|
16
|
-
|
|
17
|
-
// To:
|
|
18
|
-
// "greeting": [
|
|
19
|
-
// { "_key": "en", "value": "hello" },
|
|
20
|
-
// { "_key": "fr", "value": "bonjour" },
|
|
21
|
-
// ]
|
|
22
|
-
|
|
23
|
-
// This will migrate documents in batches of 100 and continue patching until no more documents are
|
|
24
|
-
// returned from the query.
|
|
25
|
-
//
|
|
26
|
-
// This script can safely be run, even if documents are being concurrently modified by others.
|
|
27
|
-
// If a document gets modified in the time between fetch => submit patch, this script will fail,
|
|
28
|
-
// but can safely be re-run multiple times until it eventually runs out of documents to migrate.
|
|
29
|
-
|
|
30
|
-
// A few things to note:
|
|
31
|
-
// - This script will exit if any of the mutations fail due to a revision mismatch (which means the
|
|
32
|
-
// document was edited between fetch => update)
|
|
33
|
-
// - The query must eventually return an empty set, or else this script will continue indefinitely
|
|
34
|
-
|
|
35
|
-
// Fetching documents that matches the precondition for the migration.
|
|
36
|
-
// NOTE: This query should eventually return an empty set of documents to mark the migration
|
|
37
|
-
// as complete
|
|
38
|
-
|
|
39
|
-
const TYPE = `presenter`
|
|
40
|
-
const FIELD_NAME = `title`
|
|
41
|
-
|
|
42
|
-
const fetchDocuments = () =>
|
|
43
|
-
client.fetch(
|
|
44
|
-
`*[_type == $type
|
|
45
|
-
&& defined(${FIELD_NAME})
|
|
46
|
-
&& count(${FIELD_NAME}) == null
|
|
47
|
-
][0...100] {_id, _rev, ${FIELD_NAME}}`,
|
|
48
|
-
{type: TYPE}
|
|
49
|
-
)
|
|
50
|
-
|
|
51
|
-
const buildPatches = (docs) =>
|
|
52
|
-
docs.map((doc) => ({
|
|
53
|
-
id: doc._id,
|
|
54
|
-
patch: {
|
|
55
|
-
set: {
|
|
56
|
-
// Convert existing object to array
|
|
57
|
-
[FIELD_NAME]: Object.keys(doc[FIELD_NAME])
|
|
58
|
-
.filter((key) => key !== '_type')
|
|
59
|
-
.map((key) => ({
|
|
60
|
-
_key: key,
|
|
61
|
-
value: doc[FIELD_NAME][key],
|
|
62
|
-
})),
|
|
63
|
-
},
|
|
64
|
-
// this will cause the migration to fail if any of the documents has been
|
|
65
|
-
// modified since it was fetched.
|
|
66
|
-
ifRevisionID: doc._rev,
|
|
67
|
-
},
|
|
68
|
-
}))
|
|
69
|
-
|
|
70
|
-
const createTransaction = (patches) =>
|
|
71
|
-
patches.reduce((tx, patch) => tx.patch(patch.id, patch.patch), client.transaction())
|
|
72
|
-
|
|
73
|
-
const commitTransaction = (tx) => tx.commit()
|
|
74
|
-
|
|
75
|
-
const migrateNextBatch = async () => {
|
|
76
|
-
const documents = await fetchDocuments()
|
|
77
|
-
const patches = buildPatches(documents)
|
|
78
|
-
if (patches.length === 0) {
|
|
79
|
-
console.log('No more documents to migrate!')
|
|
80
|
-
return null
|
|
81
|
-
}
|
|
82
|
-
console.log(
|
|
83
|
-
`Migrating batch:\n %s`,
|
|
84
|
-
patches.map((patch) => `${patch.id} => ${JSON.stringify(patch.patch)}`).join('\n')
|
|
85
|
-
)
|
|
86
|
-
const transaction = createTransaction(patches)
|
|
87
|
-
await commitTransaction(transaction)
|
|
88
|
-
return migrateNextBatch()
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
migrateNextBatch().catch((err) => {
|
|
92
|
-
console.error(err)
|
|
93
|
-
process.exit(1)
|
|
94
|
-
})
|
|
@@ -1,311 +0,0 @@
|
|
|
1
|
-
import React, {forwardRef, useCallback, useMemo} from 'react'
|
|
2
|
-
import {Code, Text, Card, Label, Box, Stack, Button, Grid, Flex} from '@sanity/ui'
|
|
3
|
-
import {withDocument} from 'part:@sanity/form-builder'
|
|
4
|
-
import {PatchEvent, setIfMissing, insert, unset, set} from '@sanity/form-builder/PatchEvent'
|
|
5
|
-
import {AddIcon, RemoveIcon, RestoreIcon} from '@sanity/icons'
|
|
6
|
-
import {FormFieldValidationStatus} from '@sanity/base/components'
|
|
7
|
-
import {FieldPresence} from '@sanity/base/presence'
|
|
8
|
-
import {FormBuilderInput} from '@sanity/form-builder/lib/FormBuilderInput'
|
|
9
|
-
|
|
10
|
-
import ValueInput from './ValueInput'
|
|
11
|
-
import {Table, TableCell, TableRow} from './Table'
|
|
12
|
-
import {useUnsetInputComponent} from '../hooks/useUnsetInputComponent'
|
|
13
|
-
|
|
14
|
-
const schemaExample = {
|
|
15
|
-
name: 'title',
|
|
16
|
-
type: 'localisedArray',
|
|
17
|
-
options: {
|
|
18
|
-
languages: [
|
|
19
|
-
{id: 'en', title: 'English'},
|
|
20
|
-
{id: 'no', title: 'Norsk'},
|
|
21
|
-
],
|
|
22
|
-
},
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
type Value = {
|
|
26
|
-
_key: string
|
|
27
|
-
value?: string
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
type Language = {
|
|
31
|
-
id: string
|
|
32
|
-
title: string
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
type Options = {
|
|
36
|
-
languages: Language[]
|
|
37
|
-
showNativeInput: boolean
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const DEFAULT_OPTIONS = {
|
|
41
|
-
languages: [],
|
|
42
|
-
showNativeInput: false,
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const LanguageArrayWrapper = forwardRef(function CustomComponent(props, ref) {
|
|
46
|
-
const {onChange, onBlur, readOnly, presence, markers} = props
|
|
47
|
-
const value: Value[] = props?.value
|
|
48
|
-
|
|
49
|
-
// IMPORTANT: leaving out will cause the browser to lock up in an infinite loop
|
|
50
|
-
const type = useUnsetInputComponent(props.type)
|
|
51
|
-
const options: Options = type?.options ?? DEFAULT_OPTIONS
|
|
52
|
-
const {languages, showNativeInput} = options
|
|
53
|
-
|
|
54
|
-
const handleAddLanguage = useCallback(
|
|
55
|
-
(languageId?: string) => {
|
|
56
|
-
// Create new items
|
|
57
|
-
const newItems = languageId
|
|
58
|
-
? // Just one for this language
|
|
59
|
-
[{_key: languageId}]
|
|
60
|
-
: // Or one for every missing language
|
|
61
|
-
languages
|
|
62
|
-
.filter((language) =>
|
|
63
|
-
value?.length ? !value.find((v) => v._key === language.id) : true
|
|
64
|
-
)
|
|
65
|
-
.map((language) => ({_key: language.id}))
|
|
66
|
-
|
|
67
|
-
// Insert new items in the correct order
|
|
68
|
-
const languagesInUse = value?.length ? value.map((v) => v) : []
|
|
69
|
-
|
|
70
|
-
const insertions = newItems.map((item) => {
|
|
71
|
-
// What's the original index of this language?
|
|
72
|
-
const languageIndex = languages.findIndex((l) => item._key === l.id)
|
|
73
|
-
|
|
74
|
-
// What languages are there beyond that index?
|
|
75
|
-
const remainingLanguages = languages.slice(languageIndex + 1)
|
|
76
|
-
|
|
77
|
-
// So what is the index in the current value array of the next language in the language array?
|
|
78
|
-
const nextLanguageIndex = languagesInUse.findIndex((l) =>
|
|
79
|
-
remainingLanguages.find((r) => r.id === l._key)
|
|
80
|
-
)
|
|
81
|
-
|
|
82
|
-
// Keep local state up to date incase multiple insertions are being made
|
|
83
|
-
if (nextLanguageIndex < 0) {
|
|
84
|
-
languagesInUse.push(item)
|
|
85
|
-
} else {
|
|
86
|
-
languagesInUse.splice(nextLanguageIndex, 0, item)
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
return nextLanguageIndex < 0
|
|
90
|
-
? // No next language (-1), add to end of array
|
|
91
|
-
insert([item], 'after', [nextLanguageIndex])
|
|
92
|
-
: // Next language found, insert before that
|
|
93
|
-
insert([item], 'before', [nextLanguageIndex])
|
|
94
|
-
})
|
|
95
|
-
|
|
96
|
-
onChange(PatchEvent.from(setIfMissing([]), ...insertions))
|
|
97
|
-
},
|
|
98
|
-
[languages, onChange, value]
|
|
99
|
-
)
|
|
100
|
-
|
|
101
|
-
const handleUnsetByKey = useCallback(
|
|
102
|
-
(_key) => {
|
|
103
|
-
onChange(PatchEvent.from(unset([{_key}])))
|
|
104
|
-
},
|
|
105
|
-
[onChange]
|
|
106
|
-
)
|
|
107
|
-
|
|
108
|
-
const handleInnerValueChange = useCallback(
|
|
109
|
-
(patchEvent: PatchEvent, _key: string) => {
|
|
110
|
-
const inputValue = patchEvent.patches[0]?.value
|
|
111
|
-
const inputPath = [{_key}, `value`]
|
|
112
|
-
|
|
113
|
-
onChange(PatchEvent.from(inputValue ? set(inputValue, inputPath) : unset(inputPath)))
|
|
114
|
-
},
|
|
115
|
-
[onChange]
|
|
116
|
-
)
|
|
117
|
-
|
|
118
|
-
// TODO: This is lazy, reordering and re-setting the whole array – it should be surgical
|
|
119
|
-
const handleRestoreOrder = useCallback(() => {
|
|
120
|
-
// Create a new value array in the correct order
|
|
121
|
-
const updatedValue = value.reduce((acc, v) => {
|
|
122
|
-
const newIndex = languages.findIndex((l) => l.id === v._key)
|
|
123
|
-
|
|
124
|
-
acc[newIndex] = v
|
|
125
|
-
|
|
126
|
-
return acc
|
|
127
|
-
}, [])
|
|
128
|
-
|
|
129
|
-
onChange(PatchEvent.from(unset(), set(updatedValue)))
|
|
130
|
-
}, [languages, onChange, value])
|
|
131
|
-
|
|
132
|
-
// Check languages are in the correct order
|
|
133
|
-
const languagesOutOfOrder = useMemo(() => {
|
|
134
|
-
if (!value?.length) {
|
|
135
|
-
return []
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
const languagesInUse = languages.filter((l) => value.find((v) => v._key === l.id))
|
|
139
|
-
|
|
140
|
-
return value
|
|
141
|
-
.map((v, vIndex) => (vIndex === languagesInUse.findIndex((l) => l.id === v._key) ? null : v))
|
|
142
|
-
.filter(Boolean)
|
|
143
|
-
}, [value, languages])
|
|
144
|
-
|
|
145
|
-
// Check options are supplied and valid
|
|
146
|
-
const languagesAreValid = useMemo(
|
|
147
|
-
() => languages?.length && languages.every((item) => item.id && item.title),
|
|
148
|
-
[languages]
|
|
149
|
-
)
|
|
150
|
-
|
|
151
|
-
if (!languagesAreValid) {
|
|
152
|
-
return (
|
|
153
|
-
<Card tone="caution" border radius={2} padding={3}>
|
|
154
|
-
<Stack space={4}>
|
|
155
|
-
<Text>
|
|
156
|
-
An array of language objects must be passed into the <code>{type.name}</code> field as
|
|
157
|
-
options, each with an <code>id</code> and <code>title</code> field. Example:
|
|
158
|
-
</Text>
|
|
159
|
-
<Card padding={2} border radius={2}>
|
|
160
|
-
<Code size={1} language="javascript">
|
|
161
|
-
{JSON.stringify(schemaExample, null, 2)}
|
|
162
|
-
</Code>
|
|
163
|
-
</Card>
|
|
164
|
-
</Stack>
|
|
165
|
-
</Card>
|
|
166
|
-
)
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
const validationMarkers = markers?.length
|
|
170
|
-
? markers.filter((mark) => mark.type === `validation`)
|
|
171
|
-
: []
|
|
172
|
-
const invalidKeys = validationMarkers
|
|
173
|
-
.map((mark) => mark.path)
|
|
174
|
-
.flat()
|
|
175
|
-
.map((item) => item._key)
|
|
176
|
-
|
|
177
|
-
return (
|
|
178
|
-
<Stack space={3}>
|
|
179
|
-
<Box>
|
|
180
|
-
<Text size={1} weight="bold">
|
|
181
|
-
{type?.title ?? type.name}
|
|
182
|
-
</Text>
|
|
183
|
-
</Box>
|
|
184
|
-
{/* Loop over the values */}
|
|
185
|
-
{value?.length > 0 ? (
|
|
186
|
-
<Card>
|
|
187
|
-
<Table>
|
|
188
|
-
<tbody>
|
|
189
|
-
{value.map((item) => (
|
|
190
|
-
<TableRow
|
|
191
|
-
key={item._key}
|
|
192
|
-
tone={
|
|
193
|
-
// TODO: Move this logic somewhere else
|
|
194
|
-
invalidKeys.includes(item._key)
|
|
195
|
-
? `critical`
|
|
196
|
-
: undefined || languagesOutOfOrder.find((l) => l._key === item._key)
|
|
197
|
-
? `caution`
|
|
198
|
-
: undefined
|
|
199
|
-
}
|
|
200
|
-
>
|
|
201
|
-
{/* To render each individual field in this type */}
|
|
202
|
-
{type?.of?.length > 0 &&
|
|
203
|
-
type?.of.map((subType) => (
|
|
204
|
-
<>
|
|
205
|
-
{subType?.fields?.length > 0 ? (
|
|
206
|
-
<>
|
|
207
|
-
<TableCell>
|
|
208
|
-
<Box paddingRight={2}>
|
|
209
|
-
<Label muted size={1}>
|
|
210
|
-
{item._key}
|
|
211
|
-
</Label>
|
|
212
|
-
</Box>
|
|
213
|
-
</TableCell>
|
|
214
|
-
<TableCell paddingRight={2} style={{width: `100%`}}>
|
|
215
|
-
{/* There _should_ only be one field */}
|
|
216
|
-
{subType.fields.map((subTypeField) => (
|
|
217
|
-
<ValueInput
|
|
218
|
-
key={subTypeField.name}
|
|
219
|
-
onChange={(patchEvent) =>
|
|
220
|
-
handleInnerValueChange(patchEvent, item._key)
|
|
221
|
-
}
|
|
222
|
-
onBlur={onBlur}
|
|
223
|
-
// We don't want the array item to open onFocus
|
|
224
|
-
onFocus={() => null}
|
|
225
|
-
path={[{_key: item._key}, subTypeField.name]}
|
|
226
|
-
// focusPath={[{_key: item._key}, subTypeField.name]}
|
|
227
|
-
parent={item}
|
|
228
|
-
readOnly={readOnly}
|
|
229
|
-
type={subTypeField}
|
|
230
|
-
value={item.value}
|
|
231
|
-
level={props.level + 1}
|
|
232
|
-
markers={[]}
|
|
233
|
-
compareValue={props.compareValue}
|
|
234
|
-
/>
|
|
235
|
-
))}
|
|
236
|
-
</TableCell>
|
|
237
|
-
</>
|
|
238
|
-
) : null}
|
|
239
|
-
</>
|
|
240
|
-
))}
|
|
241
|
-
|
|
242
|
-
<TableCell>
|
|
243
|
-
<Flex align="center" justify="flex-end" gap={3}>
|
|
244
|
-
{presence?.length > 0 ? (
|
|
245
|
-
<FieldPresence maxAvatars={1} presence={presence} />
|
|
246
|
-
) : null}
|
|
247
|
-
{invalidKeys.includes(item._key) ? (
|
|
248
|
-
<Box paddingLeft={2}>
|
|
249
|
-
<FormFieldValidationStatus __unstable_markers={validationMarkers} />
|
|
250
|
-
</Box>
|
|
251
|
-
) : null}
|
|
252
|
-
<Button
|
|
253
|
-
mode="ghost"
|
|
254
|
-
icon={RemoveIcon}
|
|
255
|
-
tone="critical"
|
|
256
|
-
disabled={readOnly}
|
|
257
|
-
onClick={() => handleUnsetByKey(item._key)}
|
|
258
|
-
/>
|
|
259
|
-
</Flex>
|
|
260
|
-
</TableCell>
|
|
261
|
-
</TableRow>
|
|
262
|
-
))}
|
|
263
|
-
</tbody>
|
|
264
|
-
</Table>
|
|
265
|
-
</Card>
|
|
266
|
-
) : null}
|
|
267
|
-
|
|
268
|
-
{languagesOutOfOrder.length > 0 ? (
|
|
269
|
-
<Button
|
|
270
|
-
tone="caution"
|
|
271
|
-
disabled={languagesOutOfOrder.length > languages.length}
|
|
272
|
-
icon={RestoreIcon}
|
|
273
|
-
onClick={() => handleRestoreOrder()}
|
|
274
|
-
text="Restore order of languages"
|
|
275
|
-
/>
|
|
276
|
-
) : null}
|
|
277
|
-
|
|
278
|
-
{languages.length > 0 ? (
|
|
279
|
-
<Stack space={2}>
|
|
280
|
-
{/* No more than 5 columns */}
|
|
281
|
-
<Grid columns={Math.min(languages.length, 5)} gap={2}>
|
|
282
|
-
{languages.map((language) => (
|
|
283
|
-
<Button
|
|
284
|
-
key={language.id}
|
|
285
|
-
tone="primary"
|
|
286
|
-
mode="ghost"
|
|
287
|
-
fontSize={1}
|
|
288
|
-
disabled={readOnly || value?.find((item) => item._key === language.id)}
|
|
289
|
-
text={language.id.toUpperCase()}
|
|
290
|
-
icon={AddIcon}
|
|
291
|
-
onClick={() => handleAddLanguage(language.id)}
|
|
292
|
-
/>
|
|
293
|
-
))}
|
|
294
|
-
</Grid>
|
|
295
|
-
<Button
|
|
296
|
-
tone="primary"
|
|
297
|
-
mode="ghost"
|
|
298
|
-
disabled={readOnly || value?.length >= languages?.length}
|
|
299
|
-
icon={AddIcon}
|
|
300
|
-
text={value?.length ? `Add missing languages` : `Add all languages`}
|
|
301
|
-
onClick={() => handleAddLanguage()}
|
|
302
|
-
/>
|
|
303
|
-
</Stack>
|
|
304
|
-
) : null}
|
|
305
|
-
|
|
306
|
-
{showNativeInput ? <FormBuilderInput {...props} type={type} ref={ref} /> : null}
|
|
307
|
-
</Stack>
|
|
308
|
-
)
|
|
309
|
-
})
|
|
310
|
-
|
|
311
|
-
export default withDocument(LanguageArrayWrapper)
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import React, {ReactNode} from 'react'
|
|
2
|
-
|
|
3
|
-
export function useUnsetInputComponent(type: unknown, component?: ReactNode) {
|
|
4
|
-
return React.useMemo(() => unsetInputComponent(type, component), [type, component])
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
function unsetInputComponent(type, component) {
|
|
8
|
-
const t = {
|
|
9
|
-
...type,
|
|
10
|
-
inputComponent: type.inputComponent === component ? undefined : type.inputComponent,
|
|
11
|
-
}
|
|
12
|
-
const typeOfType = t.type ? unsetInputComponent(t.type, component) : undefined
|
|
13
|
-
return {
|
|
14
|
-
...t,
|
|
15
|
-
type: typeOfType,
|
|
16
|
-
}
|
|
17
|
-
}
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
import {ArrayConfig, Value} from './types'
|
|
2
|
-
import LanguageArray from './LanguageArray'
|
|
3
|
-
|
|
4
|
-
export function internationalizedArray(config: ArrayConfig) {
|
|
5
|
-
const {name = `title`, type = `string`, languages = [], showNativeInput = false} = config
|
|
6
|
-
const configValidation = Array.isArray(config?.validation)
|
|
7
|
-
? config.validation
|
|
8
|
-
: [config?.validation]
|
|
9
|
-
|
|
10
|
-
return {
|
|
11
|
-
name,
|
|
12
|
-
title: config?.title ?? undefined,
|
|
13
|
-
group: config?.group ?? undefined,
|
|
14
|
-
hidden: config?.hidden ?? undefined,
|
|
15
|
-
readOnly: config?.readOnly ?? undefined,
|
|
16
|
-
type: 'array',
|
|
17
|
-
inputComponent: LanguageArray,
|
|
18
|
-
options: {
|
|
19
|
-
languages,
|
|
20
|
-
showNativeInput,
|
|
21
|
-
},
|
|
22
|
-
of: [
|
|
23
|
-
{
|
|
24
|
-
type: 'object',
|
|
25
|
-
fields: [{name: 'value', type}],
|
|
26
|
-
preview: {
|
|
27
|
-
select: {title: 'value', key: '_key'},
|
|
28
|
-
prepare({title, key}) {
|
|
29
|
-
return {
|
|
30
|
-
title,
|
|
31
|
-
subtitle: key.toUpperCase(),
|
|
32
|
-
}
|
|
33
|
-
},
|
|
34
|
-
},
|
|
35
|
-
},
|
|
36
|
-
],
|
|
37
|
-
validation: (Rule) => [
|
|
38
|
-
Rule.max(languages.length).custom((value: Value[], context) => {
|
|
39
|
-
const {languages} = context.type.options
|
|
40
|
-
|
|
41
|
-
const nonLanguageKeys = value?.length
|
|
42
|
-
? value.filter((item) => !languages.find((language) => item._key === language.id))
|
|
43
|
-
: []
|
|
44
|
-
|
|
45
|
-
if (nonLanguageKeys.length) {
|
|
46
|
-
return {
|
|
47
|
-
message: `Array item keys must be valid languages registered to the field type`,
|
|
48
|
-
paths: nonLanguageKeys.map((item) => ({_key: item._key})),
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Ensure there's no duplicate `language` fields
|
|
53
|
-
const valuesByLanguage = value?.length
|
|
54
|
-
? value
|
|
55
|
-
.filter((item) => Boolean(item?._key))
|
|
56
|
-
.reduce((acc, cur) => {
|
|
57
|
-
if (acc[cur._key]) {
|
|
58
|
-
return {...acc, [cur._key]: [...acc[cur._key], cur]}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
return {
|
|
62
|
-
...acc,
|
|
63
|
-
[cur._key]: [cur],
|
|
64
|
-
}
|
|
65
|
-
}, {})
|
|
66
|
-
: {}
|
|
67
|
-
|
|
68
|
-
const duplicateValues = Object.values(valuesByLanguage)
|
|
69
|
-
.filter((item) => item?.length > 1)
|
|
70
|
-
.flat()
|
|
71
|
-
|
|
72
|
-
if (duplicateValues.length) {
|
|
73
|
-
return {
|
|
74
|
-
message: 'There can only be one field per language',
|
|
75
|
-
paths: duplicateValues.map((item) => ({_key: item._key})),
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
return true
|
|
80
|
-
}),
|
|
81
|
-
...configValidation,
|
|
82
|
-
],
|
|
83
|
-
}
|
|
84
|
-
}
|