sanity-plugin-internationalized-array 1.8.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/lib/index.d.ts +1 -1
- package/lib/index.esm.js +4 -4
- package/lib/index.esm.js.map +1 -1
- package/lib/index.js +4 -4
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/AddButtons.tsx +39 -0
- package/src/components/DocumentAddButtons.tsx +112 -0
- package/src/components/InternationalizedArray.tsx +19 -30
- package/src/components/InternationalizedArrayContext.tsx +16 -1
- package/src/fieldActions/index.ts +2 -2
- package/src/types.ts +1 -1
- package/src/utils/createAddLanguagePatches.ts +2 -1
- package/src/utils/createValueSchemaTypeName.ts +5 -0
package/package.json
CHANGED
|
@@ -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,6 +1,6 @@
|
|
|
1
1
|
import {AddIcon} from '@sanity/icons'
|
|
2
2
|
import {useLanguageFilterStudioContext} from '@sanity/language-filter'
|
|
3
|
-
import {Button, Grid, Stack, useToast} from '@sanity/ui'
|
|
3
|
+
import {Button, Card, Grid, Stack, Text, useToast} from '@sanity/ui'
|
|
4
4
|
import React, {useCallback, useEffect, useMemo} from 'react'
|
|
5
5
|
import {
|
|
6
6
|
ArrayOfObjectsInputProps,
|
|
@@ -16,6 +16,7 @@ import type {Value} from '../types'
|
|
|
16
16
|
import {checkAllLanguagesArePresent} from '../utils/checkAllLanguagesArePresent'
|
|
17
17
|
import {createAddAllTitle} from '../utils/createAddAllTitle'
|
|
18
18
|
import {createAddLanguagePatches} from '../utils/createAddLanguagePatches'
|
|
19
|
+
import AddButtons from './AddButtons'
|
|
19
20
|
import Feedback from './Feedback'
|
|
20
21
|
import {useInternationalizedArrayContext} from './InternationalizedArrayContext'
|
|
21
22
|
|
|
@@ -205,10 +206,11 @@ export default function InternationalizedArray(
|
|
|
205
206
|
filteredLanguages?.length > 0 &&
|
|
206
207
|
// Not every language has a value yet
|
|
207
208
|
!allLanguagesArePresent
|
|
209
|
+
const fieldHasMembers = members?.length > 0
|
|
208
210
|
|
|
209
211
|
return (
|
|
210
212
|
<Stack space={2}>
|
|
211
|
-
{
|
|
213
|
+
{fieldHasMembers ? (
|
|
212
214
|
<>
|
|
213
215
|
{filteredMembers.map((member) => {
|
|
214
216
|
if (member.kind === 'item') {
|
|
@@ -229,36 +231,23 @@ export default function InternationalizedArray(
|
|
|
229
231
|
</>
|
|
230
232
|
) : null}
|
|
231
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
|
+
|
|
232
243
|
{addButtonsAreVisible ? (
|
|
233
244
|
<Stack space={2}>
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
>
|
|
241
|
-
{filteredLanguages.map((language) => (
|
|
242
|
-
<Button
|
|
243
|
-
key={language.id}
|
|
244
|
-
tone="primary"
|
|
245
|
-
mode="ghost"
|
|
246
|
-
fontSize={1}
|
|
247
|
-
disabled={
|
|
248
|
-
readOnly ||
|
|
249
|
-
Boolean(value?.find((item) => item._key === language.id))
|
|
250
|
-
}
|
|
251
|
-
text={language.id.toUpperCase()}
|
|
252
|
-
// Only show plus icon if there's one row or less
|
|
253
|
-
icon={
|
|
254
|
-
filteredLanguages.length > MAX_COLUMNS ? undefined : AddIcon
|
|
255
|
-
}
|
|
256
|
-
value={language.id}
|
|
257
|
-
onClick={handleAddLanguage}
|
|
258
|
-
/>
|
|
259
|
-
))}
|
|
260
|
-
</Grid>
|
|
261
|
-
) : null}
|
|
245
|
+
<AddButtons
|
|
246
|
+
languages={filteredLanguages}
|
|
247
|
+
value={value}
|
|
248
|
+
readOnly={readOnly}
|
|
249
|
+
onClick={handleAddLanguage}
|
|
250
|
+
/>
|
|
262
251
|
{buttonAddAll ? (
|
|
263
252
|
<Button
|
|
264
253
|
tone="primary"
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {useLanguageFilterStudioContext} from '@sanity/language-filter'
|
|
2
|
+
import {Stack} from '@sanity/ui'
|
|
2
3
|
import equal from 'fast-deep-equal'
|
|
3
4
|
import {createContext, useContext, useDeferredValue, useMemo} from 'react'
|
|
4
5
|
import {ObjectInputProps, useClient, useFormBuilder} from 'sanity'
|
|
@@ -7,6 +8,7 @@ import {suspend} from 'suspend-react'
|
|
|
7
8
|
import {namespace, version} from '../cache'
|
|
8
9
|
import {CONFIG_DEFAULT} from '../constants'
|
|
9
10
|
import {Language, PluginConfig} from '../types'
|
|
11
|
+
import DocumentAddButtons from './DocumentAddButtons'
|
|
10
12
|
import {getSelectedValue} from './getSelectedValue'
|
|
11
13
|
|
|
12
14
|
// This provider makes the plugin config available to all components in the document form
|
|
@@ -77,6 +79,9 @@ export function InternationalizedArrayProvider(
|
|
|
77
79
|
: languages
|
|
78
80
|
}, [deferredDocument, languageFilterOptions, languages, selectedLanguageIds])
|
|
79
81
|
|
|
82
|
+
const showDocumentButtons =
|
|
83
|
+
internationalizedArray.buttonLocations.includes('document')
|
|
84
|
+
|
|
80
85
|
return (
|
|
81
86
|
<InternationalizedArrayContext.Provider
|
|
82
87
|
value={{
|
|
@@ -85,7 +90,17 @@ export function InternationalizedArrayProvider(
|
|
|
85
90
|
filteredLanguages,
|
|
86
91
|
}}
|
|
87
92
|
>
|
|
88
|
-
{
|
|
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
|
+
)}
|
|
89
104
|
</InternationalizedArrayContext.Provider>
|
|
90
105
|
)
|
|
91
106
|
}
|
|
@@ -28,7 +28,7 @@ const createTranslateFieldActions: (
|
|
|
28
28
|
const disabled =
|
|
29
29
|
value && Array.isArray(value)
|
|
30
30
|
? Boolean(value?.find((item) => item._key === language.id))
|
|
31
|
-
:
|
|
31
|
+
: false
|
|
32
32
|
const hidden = !filteredLanguages.some((f) => f.id === language.id)
|
|
33
33
|
|
|
34
34
|
const {onChange} = useDocumentPane()
|
|
@@ -67,7 +67,7 @@ const AddMissingTranslationsFieldAction: (
|
|
|
67
67
|
{languages, filteredLanguages}
|
|
68
68
|
) => {
|
|
69
69
|
const value = useFormValue(fieldActionProps.path) as Value[]
|
|
70
|
-
const disabled = value.length === filteredLanguages.length
|
|
70
|
+
const disabled = value && value.length === filteredLanguages.length
|
|
71
71
|
const hidden = checkAllLanguagesArePresent(filteredLanguages, value)
|
|
72
72
|
|
|
73
73
|
const {onChange} = useDocumentPane()
|
package/src/types.ts
CHANGED
|
@@ -118,7 +118,7 @@ export type PluginConfig = {
|
|
|
118
118
|
* Locations where the "+ EN" add language buttons are visible
|
|
119
119
|
* @defaultValue ['field']
|
|
120
120
|
* */
|
|
121
|
-
buttonLocations: ('field' | 'unstable__fieldAction')[]
|
|
121
|
+
buttonLocations: ('field' | 'unstable__fieldAction' | 'document')[]
|
|
122
122
|
/**
|
|
123
123
|
* Show or hide the "Add missing languages" button
|
|
124
124
|
* @defaultValue true
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {FormInsertPatch, insert, Path, SchemaType} from 'sanity'
|
|
2
2
|
|
|
3
3
|
import {Language, Value} from '../types'
|
|
4
|
+
import {createValueSchemaTypeName} from './createValueSchemaTypeName'
|
|
4
5
|
|
|
5
6
|
type AddConfig = {
|
|
6
7
|
// New keys to add to the field
|
|
@@ -27,7 +28,7 @@ export function createAddLanguagePatches(config: AddConfig): FormInsertPatch[] {
|
|
|
27
28
|
path = [],
|
|
28
29
|
} = config
|
|
29
30
|
|
|
30
|
-
const itemBase = {_type:
|
|
31
|
+
const itemBase = {_type: createValueSchemaTypeName(schemaType)}
|
|
31
32
|
|
|
32
33
|
// Create new items
|
|
33
34
|
const newItems =
|