@sanity/document-internationalization 4.1.1 → 5.0.1
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 +44 -36
- package/dist/_chunks-es/{resources.mjs → resources.js} +1 -1
- package/dist/{_chunks-cjs → _chunks-es}/resources.js.map +1 -1
- package/dist/index.d.ts +112 -88
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1075 -1337
- package/dist/index.js.map +1 -1
- package/package.json +35 -69
- package/dist/_chunks-cjs/resources.js +0 -8
- package/dist/_chunks-es/resources.mjs.map +0 -1
- package/dist/_legacy/resources.esm.js +0 -9
- package/dist/_legacy/resources.esm.js.map +0 -1
- package/dist/index.d.mts +0 -108
- package/dist/index.esm.js +0 -1625
- package/dist/index.esm.js.map +0 -1
- package/dist/index.mjs +0 -1625
- package/dist/index.mjs.map +0 -1
- package/sanity.json +0 -8
- package/src/actions/DeleteMetadataAction.tsx +0 -93
- package/src/actions/DeleteTranslationAction.tsx +0 -102
- package/src/actions/DuplicateWithTranslationsAction.tsx +0 -251
- package/src/badges/index.tsx +0 -27
- package/src/components/BulkPublish/DocumentCheck.tsx +0 -90
- package/src/components/BulkPublish/Info.tsx +0 -28
- package/src/components/BulkPublish/InfoIcon.tsx +0 -34
- package/src/components/BulkPublish/index.tsx +0 -181
- package/src/components/ConstrainedBox.tsx +0 -6
- package/src/components/DeleteTranslationDialog/DocumentPreview.tsx +0 -19
- package/src/components/DeleteTranslationDialog/index.tsx +0 -111
- package/src/components/DeleteTranslationDialog/separateReferences.ts +0 -23
- package/src/components/DeleteTranslationFooter.tsx +0 -28
- package/src/components/DocumentInternationalizationContext.tsx +0 -47
- package/src/components/DocumentInternationalizationMenu.tsx +0 -235
- package/src/components/LanguageManage.tsx +0 -108
- package/src/components/LanguageOption.tsx +0 -259
- package/src/components/LanguagePatch.tsx +0 -67
- package/src/components/OptimisticallyStrengthen/ReferencePatcher.tsx +0 -50
- package/src/components/OptimisticallyStrengthen/index.tsx +0 -34
- package/src/components/Warning.tsx +0 -18
- package/src/constants.ts +0 -16
- package/src/hooks/useLanguageMetadata.tsx +0 -26
- package/src/hooks/useOpenInNewPane.tsx +0 -33
- package/src/i18n/index.ts +0 -21
- package/src/i18n/resources.ts +0 -7
- package/src/index.ts +0 -6
- package/src/plugin.tsx +0 -239
- package/src/schema/translation/metadata.ts +0 -68
- package/src/types.ts +0 -97
- package/src/utils/createReference.ts +0 -20
- package/src/utils/excludePaths.ts +0 -123
- package/v2-incompatible.js +0 -11
|
@@ -1,251 +0,0 @@
|
|
|
1
|
-
import {CopyIcon} from '@sanity/icons'
|
|
2
|
-
import {useToast} from '@sanity/ui'
|
|
3
|
-
import {uuid} from '@sanity/uuid'
|
|
4
|
-
import {useCallback, useMemo, useState} from 'react'
|
|
5
|
-
import {filter, firstValueFrom} from 'rxjs'
|
|
6
|
-
import {
|
|
7
|
-
DEFAULT_STUDIO_CLIENT_OPTIONS,
|
|
8
|
-
type DocumentActionComponent,
|
|
9
|
-
type Id,
|
|
10
|
-
InsufficientPermissionsMessage,
|
|
11
|
-
type PatchOperations,
|
|
12
|
-
useClient,
|
|
13
|
-
useCurrentUser,
|
|
14
|
-
useDocumentOperation,
|
|
15
|
-
useDocumentPairPermissions,
|
|
16
|
-
useDocumentStore,
|
|
17
|
-
useTranslation,
|
|
18
|
-
} from 'sanity'
|
|
19
|
-
import {useRouter} from 'sanity/router'
|
|
20
|
-
import {structureLocaleNamespace} from 'sanity/structure'
|
|
21
|
-
|
|
22
|
-
import {METADATA_SCHEMA_NAME, TRANSLATIONS_ARRAY_NAME} from '../constants'
|
|
23
|
-
import {useTranslationMetadata} from '../hooks/useLanguageMetadata'
|
|
24
|
-
import {documenti18nLocaleNamespace} from '../i18n'
|
|
25
|
-
|
|
26
|
-
const DISABLED_REASON_KEY = {
|
|
27
|
-
METADATA_NOT_FOUND: 'action.duplicate.disabled.missing-metadata',
|
|
28
|
-
MULTIPLE_METADATA: 'action.duplicate.disabled.multiple-metadata',
|
|
29
|
-
NOTHING_TO_DUPLICATE: 'action.duplicate.disabled.nothing-to-duplicate',
|
|
30
|
-
NOT_READY: 'action.duplicate.disabled.not-ready',
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export const DuplicateWithTranslationsAction: DocumentActionComponent = ({
|
|
34
|
-
id,
|
|
35
|
-
type,
|
|
36
|
-
onComplete,
|
|
37
|
-
}) => {
|
|
38
|
-
const documentStore = useDocumentStore()
|
|
39
|
-
const {duplicate} = useDocumentOperation(id, type)
|
|
40
|
-
const {navigateIntent} = useRouter()
|
|
41
|
-
const [isDuplicating, setDuplicating] = useState(false)
|
|
42
|
-
const [permissions, isPermissionsLoading] = useDocumentPairPermissions({
|
|
43
|
-
id,
|
|
44
|
-
type,
|
|
45
|
-
permission: 'duplicate',
|
|
46
|
-
})
|
|
47
|
-
const {data, loading: isMetadataDocumentLoading} = useTranslationMetadata(id)
|
|
48
|
-
const hasOneMetadataDocument = useMemo(() => {
|
|
49
|
-
return Array.isArray(data) && data.length <= 1
|
|
50
|
-
}, [data])
|
|
51
|
-
const metadataDocument = Array.isArray(data) && data.length ? data[0] : null
|
|
52
|
-
const client = useClient(DEFAULT_STUDIO_CLIENT_OPTIONS)
|
|
53
|
-
const toast = useToast()
|
|
54
|
-
const {t: s} = useTranslation(structureLocaleNamespace)
|
|
55
|
-
const {t: d} = useTranslation(documenti18nLocaleNamespace)
|
|
56
|
-
const currentUser = useCurrentUser()
|
|
57
|
-
|
|
58
|
-
const handle = useCallback(async () => {
|
|
59
|
-
setDuplicating(true)
|
|
60
|
-
|
|
61
|
-
try {
|
|
62
|
-
if (!metadataDocument) {
|
|
63
|
-
throw new Error('Metadata document not found')
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// 1. Duplicate the document and its localized versions
|
|
67
|
-
const translations = new Map<string, Id>()
|
|
68
|
-
await Promise.all(
|
|
69
|
-
metadataDocument[TRANSLATIONS_ARRAY_NAME].map(async (translation) => {
|
|
70
|
-
const dupeId = uuid()
|
|
71
|
-
const locale = translation._key
|
|
72
|
-
const docId = translation.value?._ref
|
|
73
|
-
|
|
74
|
-
if (!docId) {
|
|
75
|
-
throw new Error('Translation document not found')
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const {duplicate: duplicateTranslation} = await firstValueFrom(
|
|
79
|
-
documentStore.pair
|
|
80
|
-
.editOperations(docId, type)
|
|
81
|
-
.pipe(filter((op) => op.duplicate.disabled !== 'NOT_READY'))
|
|
82
|
-
)
|
|
83
|
-
|
|
84
|
-
if (duplicateTranslation.disabled) {
|
|
85
|
-
throw new Error('Cannot duplicate document')
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const duplicateTranslationSuccess = firstValueFrom(
|
|
89
|
-
documentStore.pair
|
|
90
|
-
.operationEvents(docId, type)
|
|
91
|
-
.pipe(filter((e) => e.op === 'duplicate' && e.type === 'success'))
|
|
92
|
-
)
|
|
93
|
-
duplicateTranslation.execute(dupeId)
|
|
94
|
-
await duplicateTranslationSuccess
|
|
95
|
-
|
|
96
|
-
translations.set(locale, dupeId)
|
|
97
|
-
})
|
|
98
|
-
)
|
|
99
|
-
|
|
100
|
-
// 2. Duplicate the metadata document
|
|
101
|
-
const {duplicate: duplicateMetadata} = await firstValueFrom(
|
|
102
|
-
documentStore.pair
|
|
103
|
-
.editOperations(metadataDocument._id, METADATA_SCHEMA_NAME)
|
|
104
|
-
.pipe(filter((op) => op.duplicate.disabled !== 'NOT_READY'))
|
|
105
|
-
)
|
|
106
|
-
|
|
107
|
-
if (duplicateMetadata.disabled) {
|
|
108
|
-
throw new Error('Cannot duplicate document')
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
const duplicateMetadataSuccess = firstValueFrom(
|
|
112
|
-
documentStore.pair
|
|
113
|
-
.operationEvents(metadataDocument._id, METADATA_SCHEMA_NAME)
|
|
114
|
-
.pipe(filter((e) => e.op === 'duplicate' && e.type === 'success'))
|
|
115
|
-
)
|
|
116
|
-
const dupeId = uuid()
|
|
117
|
-
duplicateMetadata.execute(dupeId)
|
|
118
|
-
await duplicateMetadataSuccess
|
|
119
|
-
|
|
120
|
-
// 3. Patch the duplicated metadata document to update the references
|
|
121
|
-
// TODO: use document store
|
|
122
|
-
// const {patch: patchMetadata} = await firstValueFrom(
|
|
123
|
-
// documentStore.pair
|
|
124
|
-
// .editOperations(dupeId, METADATA_SCHEMA_NAME)
|
|
125
|
-
// .pipe(filter((op) => op.patch.disabled !== 'NOT_READY'))
|
|
126
|
-
// )
|
|
127
|
-
|
|
128
|
-
// if (patchMetadata.disabled) {
|
|
129
|
-
// throw new Error('Cannot patch document')
|
|
130
|
-
// }
|
|
131
|
-
|
|
132
|
-
// await firstValueFrom(
|
|
133
|
-
// documentStore.pair
|
|
134
|
-
// .consistencyStatus(dupeId, METADATA_SCHEMA_NAME)
|
|
135
|
-
// .pipe(filter((isConsistant) => isConsistant))
|
|
136
|
-
// )
|
|
137
|
-
|
|
138
|
-
// const patchMetadataSuccess = firstValueFrom(
|
|
139
|
-
// documentStore.pair
|
|
140
|
-
// .operationEvents(dupeId, METADATA_SCHEMA_NAME)
|
|
141
|
-
// .pipe(filter((e) => e.op === 'patch' && e.type === 'success'))
|
|
142
|
-
// )
|
|
143
|
-
|
|
144
|
-
const patch: PatchOperations = {
|
|
145
|
-
set: Object.fromEntries(
|
|
146
|
-
Array.from(translations.entries()).map(([locale, documentId]) => [
|
|
147
|
-
`${TRANSLATIONS_ARRAY_NAME}[_key == "${locale}"].value._ref`,
|
|
148
|
-
documentId,
|
|
149
|
-
])
|
|
150
|
-
),
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// patchMetadata.execute([patch])
|
|
154
|
-
// await patchMetadataSuccess
|
|
155
|
-
await client.transaction().patch(dupeId, patch).commit()
|
|
156
|
-
|
|
157
|
-
// 4. Navigate to the duplicated document
|
|
158
|
-
navigateIntent('edit', {
|
|
159
|
-
id: Array.from(translations.values()).at(0),
|
|
160
|
-
type,
|
|
161
|
-
})
|
|
162
|
-
|
|
163
|
-
onComplete()
|
|
164
|
-
} catch (error) {
|
|
165
|
-
console.error(error)
|
|
166
|
-
toast.push({
|
|
167
|
-
status: 'error',
|
|
168
|
-
title: 'Error duplicating document',
|
|
169
|
-
description:
|
|
170
|
-
error instanceof Error
|
|
171
|
-
? error.message
|
|
172
|
-
: 'Failed to duplicate document',
|
|
173
|
-
})
|
|
174
|
-
} finally {
|
|
175
|
-
setDuplicating(false)
|
|
176
|
-
}
|
|
177
|
-
}, [
|
|
178
|
-
client,
|
|
179
|
-
documentStore.pair,
|
|
180
|
-
metadataDocument,
|
|
181
|
-
navigateIntent,
|
|
182
|
-
onComplete,
|
|
183
|
-
toast,
|
|
184
|
-
type,
|
|
185
|
-
])
|
|
186
|
-
|
|
187
|
-
return useMemo(() => {
|
|
188
|
-
if (!isPermissionsLoading && !permissions?.granted) {
|
|
189
|
-
return {
|
|
190
|
-
icon: CopyIcon,
|
|
191
|
-
disabled: true,
|
|
192
|
-
label: d('action.duplicate.label'),
|
|
193
|
-
title: (
|
|
194
|
-
<InsufficientPermissionsMessage
|
|
195
|
-
context="duplicate-document"
|
|
196
|
-
currentUser={currentUser}
|
|
197
|
-
/>
|
|
198
|
-
),
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
if (!isMetadataDocumentLoading && !metadataDocument) {
|
|
203
|
-
return {
|
|
204
|
-
icon: CopyIcon,
|
|
205
|
-
disabled: true,
|
|
206
|
-
label: d('action.duplicate.label'),
|
|
207
|
-
title: d(DISABLED_REASON_KEY.METADATA_NOT_FOUND),
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
if (!hasOneMetadataDocument) {
|
|
212
|
-
return {
|
|
213
|
-
icon: CopyIcon,
|
|
214
|
-
disabled: true,
|
|
215
|
-
label: d('action.duplicate.label'),
|
|
216
|
-
title: d(DISABLED_REASON_KEY.MULTIPLE_METADATA),
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
return {
|
|
221
|
-
icon: CopyIcon,
|
|
222
|
-
disabled:
|
|
223
|
-
isDuplicating ||
|
|
224
|
-
Boolean(duplicate.disabled) ||
|
|
225
|
-
isPermissionsLoading ||
|
|
226
|
-
isMetadataDocumentLoading,
|
|
227
|
-
label: isDuplicating
|
|
228
|
-
? s('action.duplicate.running.label')
|
|
229
|
-
: d('action.duplicate.label'),
|
|
230
|
-
title: duplicate.disabled
|
|
231
|
-
? s(DISABLED_REASON_KEY[duplicate.disabled])
|
|
232
|
-
: '',
|
|
233
|
-
onHandle: handle,
|
|
234
|
-
}
|
|
235
|
-
}, [
|
|
236
|
-
currentUser,
|
|
237
|
-
duplicate.disabled,
|
|
238
|
-
handle,
|
|
239
|
-
hasOneMetadataDocument,
|
|
240
|
-
isDuplicating,
|
|
241
|
-
isMetadataDocumentLoading,
|
|
242
|
-
isPermissionsLoading,
|
|
243
|
-
metadataDocument,
|
|
244
|
-
permissions?.granted,
|
|
245
|
-
s,
|
|
246
|
-
d,
|
|
247
|
-
])
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
DuplicateWithTranslationsAction.action = 'duplicate'
|
|
251
|
-
DuplicateWithTranslationsAction.displayName = 'DuplicateWithTranslationsAction'
|
package/src/badges/index.tsx
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import type {DocumentBadgeDescription, DocumentBadgeProps} from 'sanity'
|
|
2
|
-
|
|
3
|
-
import {useDocumentInternationalizationContext} from '../components/DocumentInternationalizationContext'
|
|
4
|
-
|
|
5
|
-
export function LanguageBadge(
|
|
6
|
-
props: DocumentBadgeProps
|
|
7
|
-
): DocumentBadgeDescription | null {
|
|
8
|
-
const source = props?.draft || props?.published
|
|
9
|
-
const {languageField, supportedLanguages} =
|
|
10
|
-
useDocumentInternationalizationContext()
|
|
11
|
-
const languageId = source?.[languageField]
|
|
12
|
-
|
|
13
|
-
if (!languageId) {
|
|
14
|
-
return null
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const language = Array.isArray(supportedLanguages)
|
|
18
|
-
? supportedLanguages.find((l) => l.id === languageId)
|
|
19
|
-
: null
|
|
20
|
-
|
|
21
|
-
// Currently we only show the language id if the supportedLanguages are async
|
|
22
|
-
return {
|
|
23
|
-
label: language?.id ?? String(languageId),
|
|
24
|
-
title: language?.title ?? undefined,
|
|
25
|
-
color: `primary`,
|
|
26
|
-
}
|
|
27
|
-
}
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
import {Card, Spinner} from '@sanity/ui'
|
|
2
|
-
import {useEffect, useMemo} from 'react'
|
|
3
|
-
import {Preview, useEditState, useSchema, useValidationStatus} from 'sanity'
|
|
4
|
-
|
|
5
|
-
type DocumentCheckProps = {
|
|
6
|
-
id: string
|
|
7
|
-
onCheckComplete: (id: string) => void
|
|
8
|
-
addInvalidId: (id: string) => void
|
|
9
|
-
removeInvalidId: (id: string) => void
|
|
10
|
-
addDraftId: (id: string) => void
|
|
11
|
-
removeDraftId: (id: string) => void
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
// Check if the document has a draft
|
|
15
|
-
// Check if that draft is valid
|
|
16
|
-
// Report back to parent that it can be added to bulk publish
|
|
17
|
-
export default function DocumentCheck(props: DocumentCheckProps) {
|
|
18
|
-
const {
|
|
19
|
-
id,
|
|
20
|
-
onCheckComplete,
|
|
21
|
-
addInvalidId,
|
|
22
|
-
removeInvalidId,
|
|
23
|
-
addDraftId,
|
|
24
|
-
removeDraftId,
|
|
25
|
-
} = props
|
|
26
|
-
const editState = useEditState(id, ``)
|
|
27
|
-
const {isValidating, validation} = useValidationStatus(id, ``)
|
|
28
|
-
const schema = useSchema()
|
|
29
|
-
|
|
30
|
-
const validationHasErrors = useMemo(() => {
|
|
31
|
-
return (
|
|
32
|
-
!isValidating &&
|
|
33
|
-
validation.length > 0 &&
|
|
34
|
-
validation.some((item) => item.level === 'error')
|
|
35
|
-
)
|
|
36
|
-
}, [isValidating, validation])
|
|
37
|
-
|
|
38
|
-
useEffect(() => {
|
|
39
|
-
if (validationHasErrors) {
|
|
40
|
-
addInvalidId(id)
|
|
41
|
-
} else {
|
|
42
|
-
removeInvalidId(id)
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
if (editState.draft) {
|
|
46
|
-
addDraftId(id)
|
|
47
|
-
} else {
|
|
48
|
-
removeDraftId(id)
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
if (!isValidating) {
|
|
52
|
-
onCheckComplete(id)
|
|
53
|
-
}
|
|
54
|
-
}, [
|
|
55
|
-
addDraftId,
|
|
56
|
-
addInvalidId,
|
|
57
|
-
editState.draft,
|
|
58
|
-
id,
|
|
59
|
-
isValidating,
|
|
60
|
-
onCheckComplete,
|
|
61
|
-
removeDraftId,
|
|
62
|
-
removeInvalidId,
|
|
63
|
-
validationHasErrors,
|
|
64
|
-
])
|
|
65
|
-
|
|
66
|
-
// We only care about drafts
|
|
67
|
-
if (!editState.draft) {
|
|
68
|
-
return null
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const schemaType = schema.get(editState.draft._type)
|
|
72
|
-
|
|
73
|
-
return (
|
|
74
|
-
<Card
|
|
75
|
-
border
|
|
76
|
-
padding={2}
|
|
77
|
-
tone={validationHasErrors ? `critical` : `positive`}
|
|
78
|
-
>
|
|
79
|
-
{editState.draft && schemaType ? (
|
|
80
|
-
<Preview
|
|
81
|
-
layout="default"
|
|
82
|
-
value={editState.draft}
|
|
83
|
-
schemaType={schemaType}
|
|
84
|
-
/>
|
|
85
|
-
) : (
|
|
86
|
-
<Spinner />
|
|
87
|
-
)}
|
|
88
|
-
</Card>
|
|
89
|
-
)
|
|
90
|
-
}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import {InfoOutlineIcon} from '@sanity/icons'
|
|
2
|
-
import {Box, Stack, Text} from '@sanity/ui'
|
|
3
|
-
|
|
4
|
-
import InfoIcon from './InfoIcon'
|
|
5
|
-
|
|
6
|
-
export default function Info() {
|
|
7
|
-
return (
|
|
8
|
-
<InfoIcon icon={InfoOutlineIcon} tone="primary">
|
|
9
|
-
<Stack padding={3} space={4} style={{maxWidth: 250}}>
|
|
10
|
-
<Box>
|
|
11
|
-
<Text size={1}>Bulk publishing uses the Scheduling API.</Text>
|
|
12
|
-
</Box>
|
|
13
|
-
<Box>
|
|
14
|
-
<Text size={1}>
|
|
15
|
-
Customized Document Actions in the Studio will not execute. Webhooks
|
|
16
|
-
will execute.
|
|
17
|
-
</Text>
|
|
18
|
-
</Box>
|
|
19
|
-
<Box>
|
|
20
|
-
<Text size={1}>
|
|
21
|
-
Validation is checked before rendering the button below, but the
|
|
22
|
-
Scheduling API will not check for – or enforce – validation.
|
|
23
|
-
</Text>
|
|
24
|
-
</Box>
|
|
25
|
-
</Stack>
|
|
26
|
-
</InfoIcon>
|
|
27
|
-
)
|
|
28
|
-
}
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import {Box, type ButtonTone, Text, Tooltip} from '@sanity/ui'
|
|
2
|
-
import type {ComponentType, PropsWithChildren} from 'react'
|
|
3
|
-
import {TextWithTone} from 'sanity'
|
|
4
|
-
|
|
5
|
-
type InfoIconProps = PropsWithChildren & {
|
|
6
|
-
icon: ComponentType
|
|
7
|
-
tone: ButtonTone
|
|
8
|
-
text?: string
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export default function InfoIcon(props: InfoIconProps) {
|
|
12
|
-
const {text, icon, tone, children} = props
|
|
13
|
-
const Icon = icon
|
|
14
|
-
|
|
15
|
-
return (
|
|
16
|
-
<Tooltip
|
|
17
|
-
animate
|
|
18
|
-
portal
|
|
19
|
-
content={
|
|
20
|
-
children ? (
|
|
21
|
-
<>{children}</>
|
|
22
|
-
) : (
|
|
23
|
-
<Box padding={2}>
|
|
24
|
-
<Text size={1}>{text}</Text>
|
|
25
|
-
</Box>
|
|
26
|
-
)
|
|
27
|
-
}
|
|
28
|
-
>
|
|
29
|
-
<TextWithTone tone={tone} size={1}>
|
|
30
|
-
<Icon />
|
|
31
|
-
</TextWithTone>
|
|
32
|
-
</Tooltip>
|
|
33
|
-
)
|
|
34
|
-
}
|
|
@@ -1,181 +0,0 @@
|
|
|
1
|
-
import {Button, Card, Dialog, Inline, Stack, Text, useToast} from '@sanity/ui'
|
|
2
|
-
import {useCallback, useState} from 'react'
|
|
3
|
-
import {TextWithTone, useClient, useWorkspace} from 'sanity'
|
|
4
|
-
|
|
5
|
-
import {API_VERSION} from '../../constants'
|
|
6
|
-
import type {TranslationReference} from '../../types'
|
|
7
|
-
import DocumentCheck from './DocumentCheck'
|
|
8
|
-
import Info from './Info'
|
|
9
|
-
|
|
10
|
-
export type BulkPublishProps = {
|
|
11
|
-
translations: TranslationReference[]
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
// A root-level component with UI for hitting the Publishing API
|
|
15
|
-
export default function BulkPublish(props: BulkPublishProps) {
|
|
16
|
-
const {translations} = props
|
|
17
|
-
const client = useClient({apiVersion: API_VERSION})
|
|
18
|
-
const {projectId, dataset} = useWorkspace()
|
|
19
|
-
const toast = useToast()
|
|
20
|
-
const [invalidIds, setInvalidIds] = useState<string[] | null>(null)
|
|
21
|
-
const [checkedIds, setCheckedIds] = useState<string[]>([])
|
|
22
|
-
|
|
23
|
-
const onCheckComplete = useCallback((id: string) => {
|
|
24
|
-
setCheckedIds((ids) => Array.from(new Set([...ids, id])))
|
|
25
|
-
}, [])
|
|
26
|
-
|
|
27
|
-
// Handle dialog
|
|
28
|
-
const [open, setOpen] = useState(false)
|
|
29
|
-
const onOpen = useCallback(() => setOpen(true), [])
|
|
30
|
-
const onClose = useCallback(() => setOpen(false), [])
|
|
31
|
-
|
|
32
|
-
const addInvalidId = useCallback((id: string) => {
|
|
33
|
-
setInvalidIds((ids) => (ids ? Array.from(new Set([...ids, id])) : [id]))
|
|
34
|
-
}, [])
|
|
35
|
-
|
|
36
|
-
const removeInvalidId = useCallback((id: string) => {
|
|
37
|
-
setInvalidIds((ids) => (ids ? ids.filter((i) => i !== id) : []))
|
|
38
|
-
}, [])
|
|
39
|
-
|
|
40
|
-
const [draftIds, setDraftIds] = useState<string[]>([])
|
|
41
|
-
|
|
42
|
-
const addDraftId = useCallback((id: string) => {
|
|
43
|
-
setDraftIds((ids) => Array.from(new Set([...ids, id])))
|
|
44
|
-
}, [])
|
|
45
|
-
|
|
46
|
-
const removeDraftId = useCallback((id: string) => {
|
|
47
|
-
setDraftIds((ids) => ids.filter((i) => i !== id))
|
|
48
|
-
}, [])
|
|
49
|
-
|
|
50
|
-
const handleBulkPublish = useCallback(() => {
|
|
51
|
-
const body = translations.map((translation) => ({
|
|
52
|
-
documentId: translation.value._ref,
|
|
53
|
-
}))
|
|
54
|
-
client
|
|
55
|
-
.request({
|
|
56
|
-
uri: `/publish/${projectId}/${dataset}`,
|
|
57
|
-
method: 'POST',
|
|
58
|
-
body,
|
|
59
|
-
})
|
|
60
|
-
.then(() => {
|
|
61
|
-
toast.push({
|
|
62
|
-
status: 'success',
|
|
63
|
-
title: 'Success',
|
|
64
|
-
description: 'Bulk publish complete',
|
|
65
|
-
})
|
|
66
|
-
})
|
|
67
|
-
.catch((err) => {
|
|
68
|
-
console.error(err)
|
|
69
|
-
toast.push({
|
|
70
|
-
status: 'error',
|
|
71
|
-
title: 'Error',
|
|
72
|
-
description: 'Bulk publish failed',
|
|
73
|
-
})
|
|
74
|
-
})
|
|
75
|
-
}, [translations, client, projectId, dataset, toast])
|
|
76
|
-
|
|
77
|
-
const disabled =
|
|
78
|
-
// Not all documents have been checked
|
|
79
|
-
checkedIds.length !== translations.length ||
|
|
80
|
-
// Some document(s) are invalid
|
|
81
|
-
Boolean(invalidIds && invalidIds?.length > 0) ||
|
|
82
|
-
// No documents are drafts
|
|
83
|
-
!draftIds.length
|
|
84
|
-
|
|
85
|
-
return translations?.length > 0 ? (
|
|
86
|
-
<Card padding={4} border radius={2}>
|
|
87
|
-
<Stack space={3}>
|
|
88
|
-
<Inline space={3}>
|
|
89
|
-
<Text weight="bold" size={1}>
|
|
90
|
-
Bulk publishing
|
|
91
|
-
</Text>
|
|
92
|
-
<Info />
|
|
93
|
-
</Inline>
|
|
94
|
-
|
|
95
|
-
<Stack>
|
|
96
|
-
<Button
|
|
97
|
-
onClick={onOpen}
|
|
98
|
-
text="Prepare bulk publishing"
|
|
99
|
-
mode="ghost"
|
|
100
|
-
/>
|
|
101
|
-
</Stack>
|
|
102
|
-
|
|
103
|
-
{open && (
|
|
104
|
-
<Dialog
|
|
105
|
-
animate
|
|
106
|
-
header="Bulk publishing"
|
|
107
|
-
id="bulk-publish-dialog"
|
|
108
|
-
onClose={onClose}
|
|
109
|
-
zOffset={1000}
|
|
110
|
-
width={3}
|
|
111
|
-
>
|
|
112
|
-
<Stack space={4} padding={4}>
|
|
113
|
-
{draftIds.length > 0 ? (
|
|
114
|
-
<Stack space={2}>
|
|
115
|
-
<Text size={1}>
|
|
116
|
-
There{' '}
|
|
117
|
-
{draftIds.length === 1
|
|
118
|
-
? `is 1 draft document`
|
|
119
|
-
: `are ${draftIds.length} draft documents`}
|
|
120
|
-
.
|
|
121
|
-
</Text>
|
|
122
|
-
{invalidIds && invalidIds.length > 0 ? (
|
|
123
|
-
<TextWithTone tone="critical" size={1}>
|
|
124
|
-
{invalidIds && invalidIds.length === 1
|
|
125
|
-
? `1 draft document has`
|
|
126
|
-
: `${
|
|
127
|
-
invalidIds && invalidIds.length
|
|
128
|
-
} draft documents have`}{' '}
|
|
129
|
-
validation issues that must addressed first
|
|
130
|
-
</TextWithTone>
|
|
131
|
-
) : (
|
|
132
|
-
<TextWithTone tone="positive" size={1}>
|
|
133
|
-
All drafts are valid and can be bulk published
|
|
134
|
-
</TextWithTone>
|
|
135
|
-
)}
|
|
136
|
-
</Stack>
|
|
137
|
-
) : null}
|
|
138
|
-
|
|
139
|
-
<Stack space={1}>
|
|
140
|
-
{translations
|
|
141
|
-
.filter((translation) => translation?.value?._ref)
|
|
142
|
-
.map((translation) => (
|
|
143
|
-
<DocumentCheck
|
|
144
|
-
key={translation._key}
|
|
145
|
-
id={translation.value._ref}
|
|
146
|
-
onCheckComplete={onCheckComplete}
|
|
147
|
-
addInvalidId={addInvalidId}
|
|
148
|
-
removeInvalidId={removeInvalidId}
|
|
149
|
-
addDraftId={addDraftId}
|
|
150
|
-
removeDraftId={removeDraftId}
|
|
151
|
-
/>
|
|
152
|
-
))}
|
|
153
|
-
</Stack>
|
|
154
|
-
{draftIds.length > 0 ? (
|
|
155
|
-
<Button
|
|
156
|
-
mode="ghost"
|
|
157
|
-
tone={
|
|
158
|
-
invalidIds && invalidIds?.length > 0
|
|
159
|
-
? 'caution'
|
|
160
|
-
: 'positive'
|
|
161
|
-
}
|
|
162
|
-
text={
|
|
163
|
-
draftIds.length === 1
|
|
164
|
-
? `Publish draft document`
|
|
165
|
-
: `Bulk publish ${draftIds.length} draft documents`
|
|
166
|
-
}
|
|
167
|
-
onClick={handleBulkPublish}
|
|
168
|
-
disabled={disabled}
|
|
169
|
-
/>
|
|
170
|
-
) : (
|
|
171
|
-
<Text muted size={1}>
|
|
172
|
-
No draft documents to publish
|
|
173
|
-
</Text>
|
|
174
|
-
)}
|
|
175
|
-
</Stack>
|
|
176
|
-
</Dialog>
|
|
177
|
-
)}
|
|
178
|
-
</Stack>
|
|
179
|
-
</Card>
|
|
180
|
-
) : null
|
|
181
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import {Preview, useSchema} from 'sanity'
|
|
2
|
-
import {Feedback} from 'sanity-plugin-utils'
|
|
3
|
-
|
|
4
|
-
type DocumentPreviewProps = {
|
|
5
|
-
value: unknown
|
|
6
|
-
type: string
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
// Wrapper of Preview just so that the schema type is satisfied by schema.get()
|
|
10
|
-
export default function DocumentPreview(props: DocumentPreviewProps) {
|
|
11
|
-
const schema = useSchema()
|
|
12
|
-
|
|
13
|
-
const schemaType = schema.get(props.type)
|
|
14
|
-
if (!schemaType) {
|
|
15
|
-
return <Feedback tone="critical" title="Schema type not found" />
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
return <Preview value={props.value} schemaType={schemaType} />
|
|
19
|
-
}
|