sanity-plugin-internationalized-array 3.2.2 → 4.0.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 +3 -34
- package/{lib → dist}/index.d.ts +41 -61
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +882 -0
- package/dist/index.js.map +1 -0
- package/package.json +36 -74
- package/lib/index.d.mts +0 -149
- package/lib/index.esm.js +0 -854
- package/lib/index.esm.js.map +0 -1
- package/lib/index.js +0 -863
- package/lib/index.js.map +0 -1
- package/lib/index.mjs +0 -854
- package/lib/index.mjs.map +0 -1
- package/sanity.json +0 -8
- package/src/cache.ts +0 -148
- package/src/components/AddButtons.tsx +0 -60
- package/src/components/DocumentAddButtons.tsx +0 -183
- package/src/components/Feedback.tsx +0 -28
- package/src/components/InternationalizedArray.tsx +0 -286
- package/src/components/InternationalizedArrayContext.tsx +0 -136
- package/src/components/InternationalizedField.tsx +0 -57
- package/src/components/InternationalizedInput.tsx +0 -257
- package/src/components/Preload.tsx +0 -31
- package/src/components/createFieldName.ts +0 -20
- package/src/components/getSelectedValue.ts +0 -31
- package/src/components/getToneFromValidation.ts +0 -20
- package/src/constants.ts +0 -18
- package/src/fieldActions/index.ts +0 -138
- package/src/index.ts +0 -3
- package/src/plugin.tsx +0 -87
- package/src/schema/array.ts +0 -148
- package/src/schema/object.ts +0 -36
- package/src/types.ts +0 -135
- package/src/utils/checkAllLanguagesArePresent.ts +0 -14
- package/src/utils/createAddAllTitle.ts +0 -16
- package/src/utils/createAddLanguagePatches.ts +0 -84
- package/src/utils/createValueSchemaTypeName.ts +0 -5
- package/src/utils/flattenSchemaType.ts +0 -63
- package/src/utils/getDocumentsToTranslate.ts +0 -66
- package/src/utils/getLanguageDisplay.ts +0 -13
- package/src/utils/getLanguagesFieldOption.ts +0 -16
- package/v2-incompatible.js +0 -11
|
@@ -1,257 +0,0 @@
|
|
|
1
|
-
import {RemoveCircleIcon} from '@sanity/icons'
|
|
2
|
-
import {
|
|
3
|
-
Button,
|
|
4
|
-
Card,
|
|
5
|
-
Flex,
|
|
6
|
-
Label,
|
|
7
|
-
Menu,
|
|
8
|
-
MenuButton,
|
|
9
|
-
MenuItem,
|
|
10
|
-
Spinner,
|
|
11
|
-
Stack,
|
|
12
|
-
Text,
|
|
13
|
-
Tooltip,
|
|
14
|
-
} from '@sanity/ui'
|
|
15
|
-
import type React from 'react'
|
|
16
|
-
import {ReactNode, useCallback, useMemo} from 'react'
|
|
17
|
-
import {type ObjectItemProps, useFormValue} from 'sanity'
|
|
18
|
-
import {set, unset} from 'sanity'
|
|
19
|
-
|
|
20
|
-
import {getLanguageDisplay} from '../utils/getLanguageDisplay'
|
|
21
|
-
import {getToneFromValidation} from './getToneFromValidation'
|
|
22
|
-
import {useInternationalizedArrayContext} from './InternationalizedArrayContext'
|
|
23
|
-
|
|
24
|
-
export type InternationalizedValue = {
|
|
25
|
-
_type: string
|
|
26
|
-
_key: string
|
|
27
|
-
value: string
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export default function InternationalizedInput(
|
|
31
|
-
props: ObjectItemProps<InternationalizedValue>
|
|
32
|
-
): ReactNode {
|
|
33
|
-
const parentValue = useFormValue(
|
|
34
|
-
props.path.slice(0, -1)
|
|
35
|
-
) as InternationalizedValue[]
|
|
36
|
-
|
|
37
|
-
// Extract the original onChange to avoid dependency issues
|
|
38
|
-
const originalOnChange = props.inputProps.onChange
|
|
39
|
-
|
|
40
|
-
// Create a wrapped onChange handler to intercept patches for paste operations
|
|
41
|
-
const wrappedOnChange = useCallback(
|
|
42
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
43
|
-
(patches: any) => {
|
|
44
|
-
// Ensure patches is an array before proceeding with paste logic
|
|
45
|
-
// For single patch operations (like unset), pass through directly
|
|
46
|
-
if (!Array.isArray(patches)) {
|
|
47
|
-
return originalOnChange(patches)
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// Check if this is a paste operation into an empty or uninitialized Portable Text field
|
|
51
|
-
const valueField = props.value?.value
|
|
52
|
-
const isEmptyOrUndefined =
|
|
53
|
-
valueField === undefined ||
|
|
54
|
-
valueField === null ||
|
|
55
|
-
(Array.isArray(valueField) && valueField.length === 0)
|
|
56
|
-
|
|
57
|
-
if (isEmptyOrUndefined) {
|
|
58
|
-
// Check for insert patches that are trying to operate on a non-existent structure
|
|
59
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
60
|
-
const hasProblematicInsert = patches.some((patch: any) => {
|
|
61
|
-
// Ensure patch exists and has required properties
|
|
62
|
-
if (!patch || typeof patch !== 'object') {
|
|
63
|
-
return false
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// Look for insert patches targeting the value field or direct array index
|
|
67
|
-
if (
|
|
68
|
-
patch.type === 'insert' &&
|
|
69
|
-
patch.path &&
|
|
70
|
-
Array.isArray(patch.path) &&
|
|
71
|
-
patch.path.length > 0
|
|
72
|
-
) {
|
|
73
|
-
// The path might be ['value', index] or just [index] depending on context
|
|
74
|
-
const isTargetingValue =
|
|
75
|
-
patch.path[0] === 'value' || typeof patch.path[0] === 'number'
|
|
76
|
-
return isTargetingValue
|
|
77
|
-
}
|
|
78
|
-
return false
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
if (hasProblematicInsert) {
|
|
82
|
-
// First, ensure the value field exists as an empty array if it doesn't
|
|
83
|
-
const initPatch =
|
|
84
|
-
valueField === undefined
|
|
85
|
-
? {type: 'setIfMissing', path: ['value'], value: []}
|
|
86
|
-
: null
|
|
87
|
-
|
|
88
|
-
// Transform the patches to ensure they work with the nested structure
|
|
89
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
90
|
-
const fixedPatches = patches.map((patch: any) => {
|
|
91
|
-
// Ensure patch exists and has required properties
|
|
92
|
-
if (!patch || typeof patch !== 'object') {
|
|
93
|
-
return patch
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
if (
|
|
97
|
-
patch.type === 'insert' &&
|
|
98
|
-
patch.path &&
|
|
99
|
-
Array.isArray(patch.path)
|
|
100
|
-
) {
|
|
101
|
-
// Ensure the path is correct for the nested structure
|
|
102
|
-
const fixedPath =
|
|
103
|
-
patch.path[0] === 'value'
|
|
104
|
-
? patch.path
|
|
105
|
-
: ['value', ...patch.path]
|
|
106
|
-
const fixedPatch = {...patch, path: fixedPath}
|
|
107
|
-
return fixedPatch
|
|
108
|
-
}
|
|
109
|
-
return patch
|
|
110
|
-
})
|
|
111
|
-
|
|
112
|
-
// If we need to initialize the field, include that patch first
|
|
113
|
-
const allPatches = initPatch
|
|
114
|
-
? [initPatch, ...fixedPatches]
|
|
115
|
-
: fixedPatches
|
|
116
|
-
|
|
117
|
-
return originalOnChange(allPatches)
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// For all other cases, pass through unchanged
|
|
122
|
-
return originalOnChange(patches)
|
|
123
|
-
},
|
|
124
|
-
[props.value, originalOnChange]
|
|
125
|
-
)
|
|
126
|
-
|
|
127
|
-
const inlineProps = {
|
|
128
|
-
...props.inputProps,
|
|
129
|
-
// This is the magic that makes inline editing work?
|
|
130
|
-
members: props.inputProps.members.filter(
|
|
131
|
-
(m) => m.kind === 'field' && m.name === 'value'
|
|
132
|
-
),
|
|
133
|
-
// This just overrides the type
|
|
134
|
-
// Remove this as it shouldn't be necessary?
|
|
135
|
-
value: props.value as InternationalizedValue,
|
|
136
|
-
// Use our wrapped onChange handler
|
|
137
|
-
onChange: wrappedOnChange,
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
const {validation, value, onChange, readOnly} = inlineProps
|
|
141
|
-
|
|
142
|
-
// The parent array contains the languages from the plugin config
|
|
143
|
-
const {languages, languageDisplay, defaultLanguages} =
|
|
144
|
-
useInternationalizedArrayContext()
|
|
145
|
-
|
|
146
|
-
const languageKeysInUse = useMemo(
|
|
147
|
-
() => parentValue?.map((v) => v._key) ?? [],
|
|
148
|
-
[parentValue]
|
|
149
|
-
)
|
|
150
|
-
const keyIsValid = languages?.length
|
|
151
|
-
? languages.find((l) => l.id === value._key)
|
|
152
|
-
: false
|
|
153
|
-
|
|
154
|
-
// Changes the key of this item, ideally to a valid language
|
|
155
|
-
const handleKeyChange = useCallback(
|
|
156
|
-
(event: React.MouseEvent<HTMLButtonElement, MouseEvent>): void => {
|
|
157
|
-
const languageId = event?.currentTarget?.value
|
|
158
|
-
|
|
159
|
-
if (
|
|
160
|
-
!value ||
|
|
161
|
-
!languages?.length ||
|
|
162
|
-
!languages.find((l) => l.id === languageId)
|
|
163
|
-
) {
|
|
164
|
-
return
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
onChange([set(languageId, ['_key'])])
|
|
168
|
-
},
|
|
169
|
-
[onChange, value, languages]
|
|
170
|
-
)
|
|
171
|
-
|
|
172
|
-
// Removes this item from the array
|
|
173
|
-
const handleUnset = useCallback((): void => {
|
|
174
|
-
onChange(unset())
|
|
175
|
-
}, [onChange])
|
|
176
|
-
|
|
177
|
-
if (!languages) {
|
|
178
|
-
return <Spinner />
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
const language = languages.find((l) => l.id === value._key)
|
|
182
|
-
const languageTitle: string =
|
|
183
|
-
keyIsValid && language
|
|
184
|
-
? getLanguageDisplay(languageDisplay, language.title, language.id)
|
|
185
|
-
: ''
|
|
186
|
-
|
|
187
|
-
const isDefault = defaultLanguages.includes(value._key)
|
|
188
|
-
|
|
189
|
-
const removeButton = (
|
|
190
|
-
<Button
|
|
191
|
-
mode="bleed"
|
|
192
|
-
icon={RemoveCircleIcon}
|
|
193
|
-
tone="critical"
|
|
194
|
-
disabled={readOnly || isDefault}
|
|
195
|
-
onClick={handleUnset}
|
|
196
|
-
/>
|
|
197
|
-
)
|
|
198
|
-
|
|
199
|
-
return (
|
|
200
|
-
<Card paddingTop={2} tone={getToneFromValidation(validation)}>
|
|
201
|
-
<Stack space={2}>
|
|
202
|
-
<Card tone="inherit">
|
|
203
|
-
{keyIsValid ? (
|
|
204
|
-
<Label muted size={1}>
|
|
205
|
-
{languageTitle}
|
|
206
|
-
</Label>
|
|
207
|
-
) : (
|
|
208
|
-
<MenuButton
|
|
209
|
-
button={<Button fontSize={1} text={`Change "${value._key}"`} />}
|
|
210
|
-
id={`${value._key}-change-key`}
|
|
211
|
-
menu={
|
|
212
|
-
<Menu>
|
|
213
|
-
{languages.map((lang) => (
|
|
214
|
-
<MenuItem
|
|
215
|
-
disabled={languageKeysInUse.includes(lang.id)}
|
|
216
|
-
fontSize={1}
|
|
217
|
-
key={lang.id}
|
|
218
|
-
text={lang.id.toLocaleUpperCase()}
|
|
219
|
-
value={lang.id}
|
|
220
|
-
// @ts-expect-error - fix typings
|
|
221
|
-
onClick={handleKeyChange}
|
|
222
|
-
/>
|
|
223
|
-
))}
|
|
224
|
-
</Menu>
|
|
225
|
-
}
|
|
226
|
-
popover={{portal: true}}
|
|
227
|
-
/>
|
|
228
|
-
)}
|
|
229
|
-
</Card>
|
|
230
|
-
<Flex align="center" gap={2}>
|
|
231
|
-
<Card flex={1} tone="inherit">
|
|
232
|
-
{props.inputProps.renderInput(inlineProps)}
|
|
233
|
-
</Card>
|
|
234
|
-
|
|
235
|
-
<Card tone="inherit">
|
|
236
|
-
{isDefault ? (
|
|
237
|
-
<Tooltip
|
|
238
|
-
content={
|
|
239
|
-
<Text muted size={1}>
|
|
240
|
-
Can't remove default language
|
|
241
|
-
</Text>
|
|
242
|
-
}
|
|
243
|
-
fallbackPlacements={['right', 'left']}
|
|
244
|
-
placement="top"
|
|
245
|
-
portal
|
|
246
|
-
>
|
|
247
|
-
<span>{removeButton}</span>
|
|
248
|
-
</Tooltip>
|
|
249
|
-
) : (
|
|
250
|
-
removeButton
|
|
251
|
-
)}
|
|
252
|
-
</Card>
|
|
253
|
-
</Flex>
|
|
254
|
-
</Stack>
|
|
255
|
-
</Card>
|
|
256
|
-
)
|
|
257
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import {memo} from 'react'
|
|
2
|
-
import {useClient} from 'sanity'
|
|
3
|
-
|
|
4
|
-
import {createCacheKey, peek, preloadWithKey, setFunctionCache} from '../cache'
|
|
5
|
-
import type {PluginConfig} from '../types'
|
|
6
|
-
|
|
7
|
-
export default memo(function Preload(
|
|
8
|
-
props: Required<Pick<PluginConfig, 'apiVersion' | 'languages'>>
|
|
9
|
-
) {
|
|
10
|
-
const client = useClient({apiVersion: props.apiVersion})
|
|
11
|
-
|
|
12
|
-
// Use the same cache key structure as the main component
|
|
13
|
-
// This should match the main component when selectedValue is empty
|
|
14
|
-
const cacheKey = createCacheKey({})
|
|
15
|
-
|
|
16
|
-
if (!Array.isArray(peek({}))) {
|
|
17
|
-
// eslint-disable-next-line require-await
|
|
18
|
-
preloadWithKey(async () => {
|
|
19
|
-
if (Array.isArray(props.languages)) {
|
|
20
|
-
return props.languages
|
|
21
|
-
}
|
|
22
|
-
const result = await props.languages(client, {})
|
|
23
|
-
// Populate function cache for sharing with other components
|
|
24
|
-
// Use the same key structure as the main component
|
|
25
|
-
setFunctionCache(props.languages, {}, result)
|
|
26
|
-
return result
|
|
27
|
-
}, cacheKey)
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
return null
|
|
31
|
-
})
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
export function camelCase(string: string): string {
|
|
2
|
-
return string.replace(/-([a-z])/g, (g) => g[1].toUpperCase())
|
|
3
|
-
}
|
|
4
|
-
|
|
5
|
-
export function titleCase(string: string): string {
|
|
6
|
-
return string
|
|
7
|
-
.split(` `)
|
|
8
|
-
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
9
|
-
.join(` `)
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export function pascalCase(string: string): string {
|
|
13
|
-
return titleCase(camelCase(string))
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function createFieldName(name: string, addValue = false): string {
|
|
17
|
-
return addValue
|
|
18
|
-
? [`internationalizedArray`, pascalCase(name), `Value`].join(``)
|
|
19
|
-
: [`internationalizedArray`, pascalCase(name)].join(``)
|
|
20
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import {get} from 'lodash'
|
|
2
|
-
|
|
3
|
-
export const getSelectedValue = (
|
|
4
|
-
select: Record<string, string> | undefined,
|
|
5
|
-
document:
|
|
6
|
-
| {
|
|
7
|
-
[x: string]: unknown
|
|
8
|
-
}
|
|
9
|
-
| undefined
|
|
10
|
-
): Record<string, unknown> => {
|
|
11
|
-
if (!select || !document) {
|
|
12
|
-
return {}
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const selection: Record<string, string> = select || {}
|
|
16
|
-
const selectedValue: Record<string, unknown> = {}
|
|
17
|
-
for (const [key, path] of Object.entries(selection)) {
|
|
18
|
-
let value = get(document, path)
|
|
19
|
-
if (Array.isArray(value)) {
|
|
20
|
-
// If there are references in the array, ensure they have `_ref` set, otherwise they are considered empty and can safely be ignored
|
|
21
|
-
value = value.filter((item) =>
|
|
22
|
-
typeof item === 'object'
|
|
23
|
-
? item?._type === 'reference' && '_ref' in item
|
|
24
|
-
: true
|
|
25
|
-
)
|
|
26
|
-
}
|
|
27
|
-
selectedValue[key] = value
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
return selectedValue
|
|
31
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import type {CardTone} from '@sanity/ui'
|
|
2
|
-
import type {FormNodeValidation} from 'sanity'
|
|
3
|
-
|
|
4
|
-
export function getToneFromValidation(
|
|
5
|
-
validations: FormNodeValidation[]
|
|
6
|
-
): CardTone | undefined {
|
|
7
|
-
if (!validations?.length) {
|
|
8
|
-
return undefined
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
const validationLevels = validations.map((v) => v.level)
|
|
12
|
-
|
|
13
|
-
if (validationLevels.includes('error')) {
|
|
14
|
-
return `critical`
|
|
15
|
-
} else if (validationLevels.includes('warning')) {
|
|
16
|
-
return `caution`
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
return undefined
|
|
20
|
-
}
|
package/src/constants.ts
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import {PluginConfig} from './types'
|
|
2
|
-
|
|
3
|
-
export const MAX_COLUMNS = {
|
|
4
|
-
codeOnly: 5,
|
|
5
|
-
titleOnly: 4,
|
|
6
|
-
titleAndCode: 3,
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export const CONFIG_DEFAULT: Required<PluginConfig> = {
|
|
10
|
-
languages: [],
|
|
11
|
-
select: {},
|
|
12
|
-
defaultLanguages: [],
|
|
13
|
-
fieldTypes: [],
|
|
14
|
-
apiVersion: '2025-10-15',
|
|
15
|
-
buttonLocations: ['field'],
|
|
16
|
-
buttonAddAll: true,
|
|
17
|
-
languageDisplay: 'codeOnly',
|
|
18
|
-
}
|
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
import {AddIcon, TranslateIcon} from '@sanity/icons'
|
|
2
|
-
import {useCallback} from 'react'
|
|
3
|
-
import {
|
|
4
|
-
defineDocumentFieldAction,
|
|
5
|
-
type DocumentFieldActionItem,
|
|
6
|
-
type DocumentFieldActionProps,
|
|
7
|
-
PatchEvent,
|
|
8
|
-
setIfMissing,
|
|
9
|
-
useFormValue,
|
|
10
|
-
} from 'sanity'
|
|
11
|
-
import {useDocumentPane} from 'sanity/structure'
|
|
12
|
-
|
|
13
|
-
import {useInternationalizedArrayContext} from '../components/InternationalizedArrayContext'
|
|
14
|
-
import type {Language, Value} from '../types'
|
|
15
|
-
import {checkAllLanguagesArePresent} from '../utils/checkAllLanguagesArePresent'
|
|
16
|
-
import {createAddAllTitle} from '../utils/createAddAllTitle'
|
|
17
|
-
import {createAddLanguagePatches} from '../utils/createAddLanguagePatches'
|
|
18
|
-
|
|
19
|
-
const createTranslateFieldActions: (
|
|
20
|
-
fieldActionProps: DocumentFieldActionProps,
|
|
21
|
-
context: {
|
|
22
|
-
languages: Language[]
|
|
23
|
-
filteredLanguages: Language[]
|
|
24
|
-
}
|
|
25
|
-
) => DocumentFieldActionItem[] = (
|
|
26
|
-
fieldActionProps,
|
|
27
|
-
{languages, filteredLanguages}
|
|
28
|
-
) =>
|
|
29
|
-
languages.map((language) => {
|
|
30
|
-
const value = useFormValue(fieldActionProps.path) as Value[]
|
|
31
|
-
const disabled =
|
|
32
|
-
value && Array.isArray(value)
|
|
33
|
-
? Boolean(value?.find((item) => item._key === language.id))
|
|
34
|
-
: false
|
|
35
|
-
const hidden = !filteredLanguages.some((f) => f.id === language.id)
|
|
36
|
-
|
|
37
|
-
const {onChange} = useDocumentPane()
|
|
38
|
-
|
|
39
|
-
const onAction = useCallback(() => {
|
|
40
|
-
const {schemaType, path} = fieldActionProps
|
|
41
|
-
|
|
42
|
-
const addLanguageKeys = [language.id]
|
|
43
|
-
const patches = createAddLanguagePatches({
|
|
44
|
-
addLanguageKeys,
|
|
45
|
-
schemaType,
|
|
46
|
-
languages,
|
|
47
|
-
filteredLanguages,
|
|
48
|
-
value,
|
|
49
|
-
path,
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
onChange(PatchEvent.from([setIfMissing([], path), ...patches]))
|
|
53
|
-
}, [language.id, value, onChange])
|
|
54
|
-
|
|
55
|
-
return {
|
|
56
|
-
type: 'action',
|
|
57
|
-
icon: AddIcon,
|
|
58
|
-
onAction,
|
|
59
|
-
title: language.title,
|
|
60
|
-
hidden,
|
|
61
|
-
disabled,
|
|
62
|
-
}
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
const AddMissingTranslationsFieldAction: (
|
|
66
|
-
fieldActionProps: DocumentFieldActionProps,
|
|
67
|
-
context: {
|
|
68
|
-
languages: Language[]
|
|
69
|
-
filteredLanguages: Language[]
|
|
70
|
-
}
|
|
71
|
-
) => DocumentFieldActionItem = (
|
|
72
|
-
fieldActionProps,
|
|
73
|
-
{languages, filteredLanguages}
|
|
74
|
-
) => {
|
|
75
|
-
const value = useFormValue(fieldActionProps.path) as Value[]
|
|
76
|
-
const disabled = value && value.length === filteredLanguages.length
|
|
77
|
-
const hidden = checkAllLanguagesArePresent(filteredLanguages, value)
|
|
78
|
-
|
|
79
|
-
const {onChange} = useDocumentPane()
|
|
80
|
-
|
|
81
|
-
const onAction = useCallback(() => {
|
|
82
|
-
const {schemaType, path} = fieldActionProps
|
|
83
|
-
|
|
84
|
-
const addLanguageKeys: string[] = []
|
|
85
|
-
const patches = createAddLanguagePatches({
|
|
86
|
-
addLanguageKeys,
|
|
87
|
-
schemaType,
|
|
88
|
-
languages,
|
|
89
|
-
filteredLanguages,
|
|
90
|
-
value,
|
|
91
|
-
path,
|
|
92
|
-
})
|
|
93
|
-
|
|
94
|
-
onChange(PatchEvent.from([setIfMissing([], path), ...patches]))
|
|
95
|
-
}, [fieldActionProps, filteredLanguages, languages, onChange, value])
|
|
96
|
-
|
|
97
|
-
return {
|
|
98
|
-
type: 'action',
|
|
99
|
-
icon: AddIcon,
|
|
100
|
-
onAction,
|
|
101
|
-
title: createAddAllTitle(value, filteredLanguages),
|
|
102
|
-
disabled,
|
|
103
|
-
hidden,
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
export const internationalizedArrayFieldAction = defineDocumentFieldAction({
|
|
108
|
-
name: 'internationalizedArray',
|
|
109
|
-
useAction(fieldActionProps) {
|
|
110
|
-
const isInternationalizedArrayField =
|
|
111
|
-
fieldActionProps?.schemaType?.type?.name.startsWith(
|
|
112
|
-
'internationalizedArray'
|
|
113
|
-
)
|
|
114
|
-
const {languages, filteredLanguages} = useInternationalizedArrayContext()
|
|
115
|
-
|
|
116
|
-
const translateFieldActions = createTranslateFieldActions(
|
|
117
|
-
fieldActionProps,
|
|
118
|
-
{languages, filteredLanguages}
|
|
119
|
-
)
|
|
120
|
-
|
|
121
|
-
return {
|
|
122
|
-
type: 'group',
|
|
123
|
-
icon: TranslateIcon,
|
|
124
|
-
title: 'Add Translation',
|
|
125
|
-
renderAsButton: true,
|
|
126
|
-
children: isInternationalizedArrayField
|
|
127
|
-
? [
|
|
128
|
-
...translateFieldActions,
|
|
129
|
-
AddMissingTranslationsFieldAction(fieldActionProps, {
|
|
130
|
-
languages,
|
|
131
|
-
filteredLanguages,
|
|
132
|
-
}),
|
|
133
|
-
]
|
|
134
|
-
: [],
|
|
135
|
-
hidden: !isInternationalizedArrayField,
|
|
136
|
-
}
|
|
137
|
-
},
|
|
138
|
-
})
|
package/src/index.ts
DELETED
package/src/plugin.tsx
DELETED
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
import {definePlugin, isObjectInputProps} from 'sanity'
|
|
2
|
-
|
|
3
|
-
import {InternationalizedArrayProvider} from './components/InternationalizedArrayContext'
|
|
4
|
-
import InternationalizedField from './components/InternationalizedField'
|
|
5
|
-
import Preload from './components/Preload'
|
|
6
|
-
import {CONFIG_DEFAULT} from './constants'
|
|
7
|
-
import {internationalizedArrayFieldAction} from './fieldActions'
|
|
8
|
-
import array from './schema/array'
|
|
9
|
-
import object from './schema/object'
|
|
10
|
-
import {PluginConfig} from './types'
|
|
11
|
-
import {flattenSchemaType} from './utils/flattenSchemaType'
|
|
12
|
-
|
|
13
|
-
export const internationalizedArray = definePlugin<PluginConfig>((config) => {
|
|
14
|
-
const pluginConfig = {...CONFIG_DEFAULT, ...config}
|
|
15
|
-
const {
|
|
16
|
-
apiVersion = '2025-10-15',
|
|
17
|
-
select,
|
|
18
|
-
languages,
|
|
19
|
-
fieldTypes,
|
|
20
|
-
defaultLanguages,
|
|
21
|
-
buttonLocations,
|
|
22
|
-
} = pluginConfig
|
|
23
|
-
|
|
24
|
-
return {
|
|
25
|
-
name: 'sanity-plugin-internationalized-array',
|
|
26
|
-
// Preload languages for use throughout the Studio
|
|
27
|
-
studio: Array.isArray(languages)
|
|
28
|
-
? undefined
|
|
29
|
-
: {
|
|
30
|
-
components: {
|
|
31
|
-
layout: (props) => (
|
|
32
|
-
<>
|
|
33
|
-
<Preload apiVersion={apiVersion} languages={languages} />
|
|
34
|
-
{props.renderDefault(props)}
|
|
35
|
-
</>
|
|
36
|
-
),
|
|
37
|
-
},
|
|
38
|
-
},
|
|
39
|
-
// Optional: render "add language" buttons as field actions
|
|
40
|
-
document: {
|
|
41
|
-
unstable_fieldActions: buttonLocations.includes('unstable__fieldAction')
|
|
42
|
-
? (prev) => [...prev, internationalizedArrayFieldAction]
|
|
43
|
-
: undefined,
|
|
44
|
-
},
|
|
45
|
-
// Wrap document editor with a language provider
|
|
46
|
-
form: {
|
|
47
|
-
components: {
|
|
48
|
-
field: (props) => <InternationalizedField {...props} />,
|
|
49
|
-
|
|
50
|
-
input: (props) => {
|
|
51
|
-
const isRootInput = props.id === 'root' && isObjectInputProps(props)
|
|
52
|
-
|
|
53
|
-
if (!isRootInput) {
|
|
54
|
-
return props.renderDefault(props)
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const flatFieldTypeNames = flattenSchemaType(props.schemaType).map(
|
|
58
|
-
(field) => field.type.name
|
|
59
|
-
)
|
|
60
|
-
const hasInternationalizedArray = flatFieldTypeNames.some((name) =>
|
|
61
|
-
name.startsWith('internationalizedArray')
|
|
62
|
-
)
|
|
63
|
-
|
|
64
|
-
if (!hasInternationalizedArray) {
|
|
65
|
-
return props.renderDefault(props)
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
return (
|
|
69
|
-
<InternationalizedArrayProvider
|
|
70
|
-
{...props}
|
|
71
|
-
internationalizedArray={pluginConfig}
|
|
72
|
-
/>
|
|
73
|
-
)
|
|
74
|
-
},
|
|
75
|
-
},
|
|
76
|
-
},
|
|
77
|
-
// Register custom schema types for the outer array and the inner object
|
|
78
|
-
schema: {
|
|
79
|
-
types: [
|
|
80
|
-
...fieldTypes.map((type) =>
|
|
81
|
-
array({type, apiVersion, select, languages, defaultLanguages})
|
|
82
|
-
),
|
|
83
|
-
...fieldTypes.map((type) => object({type})),
|
|
84
|
-
],
|
|
85
|
-
},
|
|
86
|
-
}
|
|
87
|
-
})
|