sanity-plugin-internationalized-array 1.6.2 → 1.7.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 +106 -0
- package/lib/index.d.ts +10 -0
- package/lib/index.esm.js +13 -10
- package/lib/index.esm.js.map +1 -1
- package/lib/index.js +13 -10
- package/lib/index.js.map +1 -1
- package/package.json +4 -2
- package/src/components/InternationalizedArray.tsx +121 -44
- package/src/components/InternationalizedInput.tsx +8 -5
- package/src/constants.ts +1 -0
- package/src/plugin.tsx +3 -1
- package/src/schema/array.ts +3 -2
- package/src/types.ts +10 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sanity-plugin-internationalized-array",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"description": "Store localized fields in an array to save on attributes",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"sanity",
|
|
@@ -51,6 +51,7 @@
|
|
|
51
51
|
"dependencies": {
|
|
52
52
|
"@sanity/icons": "^2.2.2",
|
|
53
53
|
"@sanity/incompatible-plugin": "^1.0.4",
|
|
54
|
+
"@sanity/language-filter": "^3.1.2",
|
|
54
55
|
"@sanity/ui": "^1.2.2",
|
|
55
56
|
"fast-deep-equal": "^3.1.3",
|
|
56
57
|
"lodash.get": "^4.4.2",
|
|
@@ -97,7 +98,8 @@
|
|
|
97
98
|
},
|
|
98
99
|
"sanityPlugin": {
|
|
99
100
|
"verifyPackage": {
|
|
100
|
-
"eslintImports": false
|
|
101
|
+
"eslintImports": false,
|
|
102
|
+
"dependencies": false
|
|
101
103
|
}
|
|
102
104
|
}
|
|
103
105
|
}
|
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
import {AddIcon} from '@sanity/icons'
|
|
2
|
+
import {useLanguageFilterStudioContext} from '@sanity/language-filter'
|
|
2
3
|
import {Button, Grid, Stack, useToast} from '@sanity/ui'
|
|
3
4
|
import equal from 'fast-deep-equal'
|
|
4
|
-
import {useCallback, useDeferredValue, useEffect, useMemo} from 'react'
|
|
5
|
+
import React, {useCallback, useDeferredValue, useEffect, useMemo} from 'react'
|
|
5
6
|
import {
|
|
6
7
|
ArrayOfObjectsInputProps,
|
|
7
8
|
ArrayOfObjectsItem,
|
|
8
|
-
ArrayOfObjectsItemMember,
|
|
9
9
|
insert,
|
|
10
10
|
set,
|
|
11
11
|
setIfMissing,
|
|
12
12
|
useClient,
|
|
13
13
|
useFormBuilder,
|
|
14
|
+
useFormValue,
|
|
14
15
|
} from 'sanity'
|
|
15
16
|
import {suspend} from 'suspend-react'
|
|
16
17
|
|
|
17
18
|
import {namespace, version} from '../cache'
|
|
19
|
+
import {MAX_COLUMNS} from '../constants'
|
|
18
20
|
import type {ArraySchemaWithLanguageOptions, Value} from '../types'
|
|
19
21
|
import Feedback from './Feedback'
|
|
20
22
|
import {getSelectedValue} from './getSelectedValue'
|
|
@@ -30,6 +32,7 @@ export default function InternationalizedArray(
|
|
|
30
32
|
props: InternationalizedArrayProps
|
|
31
33
|
) {
|
|
32
34
|
const {members, value, schemaType, onChange} = props
|
|
35
|
+
|
|
33
36
|
const readOnly =
|
|
34
37
|
typeof schemaType.readOnly === 'boolean' ? schemaType.readOnly : false
|
|
35
38
|
const {options} = schemaType
|
|
@@ -41,7 +44,7 @@ export default function InternationalizedArray(
|
|
|
41
44
|
[options.select, deferredDocument]
|
|
42
45
|
)
|
|
43
46
|
|
|
44
|
-
const {apiVersion} = options
|
|
47
|
+
const {apiVersion, defaultLanguages} = options
|
|
45
48
|
const client = useClient({apiVersion})
|
|
46
49
|
const languages = Array.isArray(options.languages)
|
|
47
50
|
? options.languages
|
|
@@ -57,24 +60,76 @@ export default function InternationalizedArray(
|
|
|
57
60
|
{equal}
|
|
58
61
|
)
|
|
59
62
|
|
|
63
|
+
// Support updating the UI if languageFilter is installed
|
|
64
|
+
const {selectedLanguageIds, options: languageFilterOptions} =
|
|
65
|
+
useLanguageFilterStudioContext()
|
|
66
|
+
const documentType = useFormValue(['_type'])
|
|
67
|
+
const languageFilterEnabled =
|
|
68
|
+
typeof documentType === 'string' &&
|
|
69
|
+
languageFilterOptions.documentTypes.includes(documentType)
|
|
70
|
+
|
|
71
|
+
const filteredMembers = useMemo(
|
|
72
|
+
() =>
|
|
73
|
+
languageFilterEnabled
|
|
74
|
+
? members.filter((member) => {
|
|
75
|
+
// This member is the outer object created by the plugin
|
|
76
|
+
// Satisfy TS
|
|
77
|
+
if (member.kind !== 'item') {
|
|
78
|
+
return false
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// This is the inner "value" field member created by this plugin
|
|
82
|
+
const valueMember = member.item.members[0]
|
|
83
|
+
|
|
84
|
+
// Satisfy TS
|
|
85
|
+
if (valueMember.kind !== 'field') {
|
|
86
|
+
return false
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return languageFilterOptions.filterField(
|
|
90
|
+
member.item.schemaType,
|
|
91
|
+
valueMember,
|
|
92
|
+
selectedLanguageIds
|
|
93
|
+
)
|
|
94
|
+
})
|
|
95
|
+
: members,
|
|
96
|
+
[languageFilterEnabled, members, languageFilterOptions, selectedLanguageIds]
|
|
97
|
+
)
|
|
98
|
+
|
|
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
|
+
|
|
60
109
|
const handleAddLanguage = useCallback(
|
|
61
|
-
(
|
|
62
|
-
if (!
|
|
110
|
+
(param?: React.MouseEvent<HTMLButtonElement, MouseEvent> | string[]) => {
|
|
111
|
+
if (!filteredLanguages?.length) {
|
|
63
112
|
return
|
|
64
113
|
}
|
|
65
114
|
|
|
115
|
+
const languageIds: string[] = Array.isArray(param)
|
|
116
|
+
? param
|
|
117
|
+
: ([param?.currentTarget?.value].filter(Boolean) as string[])
|
|
66
118
|
const itemBase = {_type: `${schemaType.name}Value`}
|
|
67
119
|
|
|
68
120
|
// Create new items
|
|
69
|
-
const newItems =
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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}))
|
|
78
133
|
|
|
79
134
|
// Insert new items in the correct order
|
|
80
135
|
const languagesInUse = value?.length ? value.map((v) => v) : []
|
|
@@ -108,10 +163,25 @@ export default function InternationalizedArray(
|
|
|
108
163
|
|
|
109
164
|
onChange([setIfMissing([]), ...insertions])
|
|
110
165
|
},
|
|
111
|
-
[
|
|
166
|
+
[filteredLanguages, onChange, schemaType.name, value]
|
|
112
167
|
)
|
|
113
168
|
|
|
114
|
-
//
|
|
169
|
+
// Create default fields if the document is not yet created
|
|
170
|
+
const documentCreatedAt = useFormValue(['_createdAt'])
|
|
171
|
+
|
|
172
|
+
if (
|
|
173
|
+
// Array field is empty
|
|
174
|
+
!value &&
|
|
175
|
+
// Document form is in "not yet created" state
|
|
176
|
+
!documentCreatedAt &&
|
|
177
|
+
// Plugin config included default languages
|
|
178
|
+
defaultLanguages &&
|
|
179
|
+
defaultLanguages?.length > 0
|
|
180
|
+
) {
|
|
181
|
+
handleAddLanguage(defaultLanguages)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// TODO: This is reordering and re-setting the whole array, it could be surgical
|
|
115
185
|
const handleRestoreOrder = useCallback(() => {
|
|
116
186
|
if (!value?.length || !languages?.length) {
|
|
117
187
|
return
|
|
@@ -177,24 +247,34 @@ export default function InternationalizedArray(
|
|
|
177
247
|
[languages]
|
|
178
248
|
)
|
|
179
249
|
|
|
250
|
+
// Automatically restore order of fields
|
|
180
251
|
useEffect(() => {
|
|
181
252
|
if (languagesOutOfOrder.length > 0 && allKeysAreLanguages) {
|
|
182
253
|
handleRestoreOrder()
|
|
183
254
|
}
|
|
184
255
|
}, [languagesOutOfOrder, allKeysAreLanguages, handleRestoreOrder])
|
|
185
256
|
|
|
257
|
+
// compare value keys with possible languages
|
|
258
|
+
const allLanguagesArePresent = useMemo(() => {
|
|
259
|
+
const filteredLanguageIds = filteredLanguages.map((l) => l.id)
|
|
260
|
+
const languagesInUseIds = value ? value.map((v) => v._key) : []
|
|
261
|
+
|
|
262
|
+
return (
|
|
263
|
+
languagesInUseIds.length === filteredLanguageIds.length &&
|
|
264
|
+
languagesInUseIds.every((l) => filteredLanguageIds.includes(l))
|
|
265
|
+
)
|
|
266
|
+
}, [filteredLanguages, value])
|
|
267
|
+
|
|
186
268
|
if (!languagesAreValid) {
|
|
187
269
|
return <Feedback />
|
|
188
270
|
}
|
|
189
271
|
|
|
190
272
|
return (
|
|
191
|
-
<LanguageProvider value={{languages}}>
|
|
273
|
+
<LanguageProvider value={{languages: filteredLanguages}}>
|
|
192
274
|
<Stack space={2}>
|
|
193
275
|
{members?.length > 0 ? (
|
|
194
276
|
<>
|
|
195
|
-
{
|
|
196
|
-
{/* @ts-ignore */}
|
|
197
|
-
{members.map((member: ArrayOfObjectsItemMember) => {
|
|
277
|
+
{filteredMembers.map((member) => {
|
|
198
278
|
if (member.kind === 'item') {
|
|
199
279
|
return (
|
|
200
280
|
<ArrayOfObjectsItem
|
|
@@ -213,25 +293,18 @@ export default function InternationalizedArray(
|
|
|
213
293
|
</>
|
|
214
294
|
) : null}
|
|
215
295
|
|
|
216
|
-
{/* This now happens automatically */}
|
|
217
|
-
{/* {languagesOutOfOrder.length > 0 && allKeysAreLanguages ? (
|
|
218
|
-
<Button
|
|
219
|
-
tone="caution"
|
|
220
|
-
icon={RestoreIcon}
|
|
221
|
-
onClick={() => handleRestoreOrder()}
|
|
222
|
-
text="Restore order of languages"
|
|
223
|
-
/>
|
|
224
|
-
) : null} */}
|
|
225
|
-
|
|
226
296
|
{/* Show buttons if languages are configured */}
|
|
227
297
|
{/* Hide them if all languages have values */}
|
|
228
|
-
{
|
|
298
|
+
{filteredLanguages?.length > 0 && !allLanguagesArePresent ? (
|
|
229
299
|
<Stack space={2}>
|
|
230
300
|
{/* Hide language-specific buttons if there's only one */}
|
|
231
|
-
{/* No more than
|
|
232
|
-
{
|
|
233
|
-
<Grid
|
|
234
|
-
{
|
|
301
|
+
{/* No more than 7 columns */}
|
|
302
|
+
{filteredLanguages.length > 1 ? (
|
|
303
|
+
<Grid
|
|
304
|
+
columns={Math.min(filteredLanguages.length, MAX_COLUMNS)}
|
|
305
|
+
gap={2}
|
|
306
|
+
>
|
|
307
|
+
{filteredLanguages.map((language) => (
|
|
235
308
|
<Button
|
|
236
309
|
key={language.id}
|
|
237
310
|
tone="primary"
|
|
@@ -242,8 +315,14 @@ export default function InternationalizedArray(
|
|
|
242
315
|
Boolean(value?.find((item) => item._key === language.id))
|
|
243
316
|
}
|
|
244
317
|
text={language.id.toUpperCase()}
|
|
245
|
-
icon
|
|
246
|
-
|
|
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}
|
|
247
326
|
/>
|
|
248
327
|
))}
|
|
249
328
|
</Grid>
|
|
@@ -251,23 +330,21 @@ export default function InternationalizedArray(
|
|
|
251
330
|
<Button
|
|
252
331
|
tone="primary"
|
|
253
332
|
mode="ghost"
|
|
254
|
-
disabled={
|
|
255
|
-
readOnly || (value && value?.length >= languages?.length)
|
|
256
|
-
}
|
|
333
|
+
disabled={readOnly || allLanguagesArePresent}
|
|
257
334
|
icon={AddIcon}
|
|
258
335
|
text={
|
|
259
336
|
// eslint-disable-next-line no-nested-ternary
|
|
260
337
|
value?.length
|
|
261
338
|
? `Add missing ${
|
|
262
|
-
|
|
339
|
+
filteredLanguages.length - value.length === 1
|
|
263
340
|
? `language`
|
|
264
341
|
: `languages`
|
|
265
342
|
}`
|
|
266
|
-
:
|
|
267
|
-
? `Add ${
|
|
343
|
+
: filteredLanguages.length === 1
|
|
344
|
+
? `Add ${filteredLanguages[0].title} Field`
|
|
268
345
|
: `Add all languages`
|
|
269
346
|
}
|
|
270
|
-
onClick={
|
|
347
|
+
onClick={handleAddLanguage}
|
|
271
348
|
/>
|
|
272
349
|
</Stack>
|
|
273
350
|
) : null}
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
Spinner,
|
|
11
11
|
Stack,
|
|
12
12
|
} from '@sanity/ui'
|
|
13
|
-
import React, {useCallback, useMemo} from 'react'
|
|
13
|
+
import React, {useCallback, useContext, useMemo} from 'react'
|
|
14
14
|
import {ObjectItemProps, useFormValue} from 'sanity'
|
|
15
15
|
import {set, unset} from 'sanity'
|
|
16
16
|
|
|
@@ -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} = useContext(LanguageContext)
|
|
48
48
|
|
|
49
49
|
const languageKeysInUse = useMemo(
|
|
50
50
|
() => parentValue?.map((v) => v._key) ?? [],
|
|
@@ -56,7 +56,9 @@ export default function InternationalizedInput(
|
|
|
56
56
|
|
|
57
57
|
// Changes the key of this item, ideally to a valid language
|
|
58
58
|
const handleKeyChange = useCallback(
|
|
59
|
-
(
|
|
59
|
+
(event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
|
60
|
+
const languageId = event?.currentTarget?.value
|
|
61
|
+
|
|
60
62
|
if (
|
|
61
63
|
!value ||
|
|
62
64
|
!languages?.length ||
|
|
@@ -99,12 +101,13 @@ export default function InternationalizedInput(
|
|
|
99
101
|
fontSize={1}
|
|
100
102
|
key={language.id}
|
|
101
103
|
text={language.id.toLocaleUpperCase()}
|
|
102
|
-
|
|
104
|
+
value={language.id}
|
|
105
|
+
// @ts-expect-error
|
|
106
|
+
onClick={handleKeyChange}
|
|
103
107
|
/>
|
|
104
108
|
))}
|
|
105
109
|
</Menu>
|
|
106
110
|
}
|
|
107
|
-
placement="right"
|
|
108
111
|
popover={{portal: true}}
|
|
109
112
|
/>
|
|
110
113
|
)}
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const MAX_COLUMNS = 7
|
package/src/plugin.tsx
CHANGED
|
@@ -7,6 +7,7 @@ import {PluginConfig} from './types'
|
|
|
7
7
|
|
|
8
8
|
const CONFIG_DEFAULT: PluginConfig = {
|
|
9
9
|
languages: [],
|
|
10
|
+
defaultLanguages: [],
|
|
10
11
|
fieldTypes: [],
|
|
11
12
|
}
|
|
12
13
|
|
|
@@ -17,6 +18,7 @@ export const internationalizedArray = definePlugin<PluginConfig>(
|
|
|
17
18
|
select,
|
|
18
19
|
languages,
|
|
19
20
|
fieldTypes,
|
|
21
|
+
defaultLanguages,
|
|
20
22
|
} = {...CONFIG_DEFAULT, ...config}
|
|
21
23
|
|
|
22
24
|
return {
|
|
@@ -37,7 +39,7 @@ export const internationalizedArray = definePlugin<PluginConfig>(
|
|
|
37
39
|
schema: {
|
|
38
40
|
types: [
|
|
39
41
|
...fieldTypes.map((type) =>
|
|
40
|
-
array({type, apiVersion, select, languages})
|
|
42
|
+
array({type, apiVersion, select, languages, defaultLanguages})
|
|
41
43
|
),
|
|
42
44
|
...fieldTypes.map((type) => object({type})),
|
|
43
45
|
],
|
package/src/schema/array.ts
CHANGED
|
@@ -11,11 +11,12 @@ type ArrayFactoryConfig = {
|
|
|
11
11
|
apiVersion: string
|
|
12
12
|
select?: Record<string, string>
|
|
13
13
|
languages: Language[] | LanguageCallback
|
|
14
|
+
defaultLanguages?: string[]
|
|
14
15
|
type: string | FieldDefinition
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
export default (config: ArrayFactoryConfig): FieldDefinition<'array'> => {
|
|
18
|
-
const {apiVersion, select, languages, type} = config
|
|
19
|
+
const {apiVersion, select, languages, defaultLanguages, type} = config
|
|
19
20
|
const typeName = typeof type === `string` ? type : type.name
|
|
20
21
|
const arrayName = createFieldName(typeName)
|
|
21
22
|
const objectName = createFieldName(typeName, true)
|
|
@@ -27,7 +28,7 @@ export default (config: ArrayFactoryConfig): FieldDefinition<'array'> => {
|
|
|
27
28
|
components: {
|
|
28
29
|
input: InternationalizedArray,
|
|
29
30
|
},
|
|
30
|
-
options: {apiVersion, select, languages},
|
|
31
|
+
options: {apiVersion, select, languages, defaultLanguages},
|
|
31
32
|
// TODO: Resolve this typing issue with the inner object
|
|
32
33
|
// @ts-expect-error
|
|
33
34
|
of: [
|
package/src/types.ts
CHANGED
|
@@ -82,6 +82,15 @@ export type PluginConfig = {
|
|
|
82
82
|
* ```
|
|
83
83
|
*/
|
|
84
84
|
languages: Language[] | LanguageCallback
|
|
85
|
+
/**
|
|
86
|
+
* You can specify a list of language IDs that should be pre-filled when creating a new document
|
|
87
|
+
* ```tsx
|
|
88
|
+
* {
|
|
89
|
+
* defaultLanguages: ['en']
|
|
90
|
+
* }
|
|
91
|
+
* ```
|
|
92
|
+
*/
|
|
93
|
+
defaultLanguages?: string[]
|
|
85
94
|
/**
|
|
86
95
|
* Can be a string matching core field types, as well as custom ones:
|
|
87
96
|
* ```tsx
|
|
@@ -113,5 +122,6 @@ export type ArraySchemaWithLanguageOptions = ArraySchemaType & {
|
|
|
113
122
|
select?: Record<string, string>
|
|
114
123
|
languages: Language[] | LanguageCallback
|
|
115
124
|
apiVersion: string
|
|
125
|
+
defaultLanguages?: string[]
|
|
116
126
|
}
|
|
117
127
|
}
|