@sanity/document-internationalization 1.0.0 → 1.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 +89 -47
- package/lib/index.esm.js +2 -0
- package/lib/index.esm.js.map +1 -0
- package/lib/index.js +2 -0
- package/lib/index.js.map +1 -0
- package/lib/src/index.d.ts +200 -0
- package/package.json +100 -43
- package/src/actions/DeleteWithi18nAction.tsx +118 -0
- package/src/actions/DuplicateWithi18nAction.tsx +77 -0
- package/src/actions/PublishWithi18nAction.ts +118 -0
- package/src/actions/index.ts +42 -0
- package/src/badges/LanguageBadge.tsx +22 -0
- package/src/constants/I18nDelimiter.ts +1 -0
- package/src/constants/I18nPrefix.ts +1 -0
- package/src/constants/IdStructure.ts +4 -0
- package/src/constants/LanguageCultures.ts +902 -0
- package/src/constants/ReferenceBehavior.ts +5 -0
- package/src/constants/SupportedEmojiFlagCodes.ts +251 -0
- package/src/constants/UiMessages.ts +56 -0
- package/src/constants/index.ts +7 -0
- package/src/documentI18n.tsx +33 -0
- package/src/hooks/index.ts +2 -0
- package/src/hooks/useConfig.ts +22 -0
- package/src/hooks/useDelayedFlag.ts +31 -0
- package/src/index.ts +6 -1
- package/src/language-select/components/LanguageSelect/LanguageConfigContext.tsx +4 -0
- package/src/language-select/components/LanguageSelect/LanguageSelect.tsx +188 -0
- package/src/language-select/components/LanguageSelect/LanguageSelectContext.ts +16 -0
- package/src/language-select/components/LanguageSelect/LanguageSelectLabel.tsx +12 -0
- package/src/language-select/components/LanguageSelect/LanguageSelectList.tsx +84 -0
- package/src/language-select/components/LanguageSelect/LanguageSelectListItem.tsx +251 -0
- package/src/language-select/components/LanguageSelect/index.tsx +25 -0
- package/src/language-select/components/SingleFlag/SingleFlag.tsx +44 -0
- package/src/language-select/components/SingleFlag/allEmojiFlagCodes.ts +251 -0
- package/src/language-select/components/SingleFlag/index.ts +1 -0
- package/src/language-select/components/SplitPaneIcon/SplitPaneIcon.tsx +25 -0
- package/src/language-select/components/SplitPaneIcon/index.ts +1 -0
- package/src/language-select/components/index.ts +2 -0
- package/src/language-select/hooks/index.ts +1 -0
- package/src/language-select/hooks/useLanguages.ts +37 -0
- package/src/language-select/hooks/useListeningQuery.ts +66 -0
- package/src/structure/IDefaultDocumentNodeStructureProps.ts +4 -0
- package/src/structure/components/MaintenanceTab/MaintenanceTab.tsx +13 -0
- package/src/structure/components/MaintenanceTab/MaintenanceTabContent.tsx +259 -0
- package/src/structure/components/MaintenanceTab/index.ts +1 -0
- package/src/structure/components/MaintenanceTabResult/MaintenanceTabResult.tsx +44 -0
- package/src/structure/components/MaintenanceTabResult/index.ts +1 -0
- package/src/structure/components/MaintenanceTabTypeSelector/MaintenanceTabTypeSelector.tsx +56 -0
- package/src/structure/components/MaintenanceTabTypeSelector/index.ts +1 -0
- package/src/structure/hooks/index.ts +1 -0
- package/src/structure/hooks/useDocumentsInformation.ts +110 -0
- package/src/structure/index.tsx +120 -0
- package/src/structure/utils/fixBaseDocumentRefs.ts +30 -0
- package/src/structure/utils/fixBaseLanguageMismatch.ts +21 -0
- package/src/structure/utils/fixIdStructureMismatchDocuments.ts +72 -0
- package/src/structure/utils/fixLanguageFields.ts +32 -0
- package/src/structure/utils/fixOrphanedDocuments.ts +22 -0
- package/src/structure/utils/fixTranslationRefs.ts +50 -0
- package/src/structure/utils/index.ts +6 -0
- package/src/types/IEditState.ts +6 -0
- package/src/types/IExtendedLanguageObject.ts +6 -0
- package/src/types/ILanguageObject.ts +4 -0
- package/src/types/ILanguageQuery.ts +13 -0
- package/src/types/IResolverProps.ts +10 -0
- package/src/types/ITranslationRef.ts +6 -0
- package/src/types/IType.ts +40 -0
- package/src/types/IUseDocumentOperationResult.ts +11 -0
- package/src/types/TFieldNamesConfig.ts +5 -0
- package/src/types/TLanguage.ts +3 -0
- package/src/types/TLanguagesOption.ts +4 -0
- package/src/types/TSchema.ts +6 -0
- package/src/types/Ti18nConfig.ts +33 -0
- package/src/types/Ti18nDocument.ts +14 -0
- package/src/types/Ti18nSchema.ts +13 -0
- package/src/types/index.ts +15 -0
- package/src/utils/applyConfig.ts +43 -0
- package/src/utils/baseToTop.ts +5 -0
- package/src/utils/buildDocId.ts +8 -0
- package/src/utils/createSanityReference.ts +11 -0
- package/src/utils/getBaseIdFromId.ts +20 -0
- package/src/utils/getBaseLanguage.ts +6 -0
- package/src/utils/getFlag.ts +38 -0
- package/src/utils/getLanguageFromDocument.ts +9 -0
- package/src/utils/getLanguageFromId.ts +13 -0
- package/src/utils/getLanguagesFromOption.ts +40 -0
- package/src/utils/getTranslationsForId.ts +39 -0
- package/src/utils/index.ts +14 -0
- package/src/utils/makeObjectKey.ts +3 -0
- package/src/utils/normalizeLanguageList.ts +26 -0
- package/src/utils/serializePath.ts +11 -0
- package/src/utils/updateIntlFieldsForDocument.ts +79 -0
- package/src/utils/useSanityClient.ts +6 -0
- package/src/validators/index.ts +1 -0
- package/src/validators/isSlugUnique.ts +56 -0
- package/src/withDocumentI18nPlugin.ts +24 -0
- package/v2-incompatible.js +1 -1
- package/lib/cjs/index.js +0 -539
- package/lib/cjs/index.js.map +0 -1
- package/lib/esm/index.js +0 -532
- package/lib/esm/index.js.map +0 -1
- package/lib/types/index.d.ts +0 -12
- package/lib/types/index.d.ts.map +0 -1
- package/src/LanguageManage.tsx +0 -25
- package/src/LanguageOption.tsx +0 -158
- package/src/LanguagePatch.tsx +0 -61
- package/src/MenuButton.tsx +0 -101
- package/src/badges/index.tsx +0 -23
- package/src/constants.ts +0 -1
- package/src/hooks/useLanguageMetadata.tsx +0 -21
- package/src/hooks/useOpenInNewPane.tsx +0 -27
- package/src/plugin.tsx +0 -104
- package/src/schema/translation/metadata.ts +0 -42
- package/src/types.ts +0 -24
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import {SanityDocument} from '@sanity/client'
|
|
2
|
+
import {useCallback, useEffect, useState} from 'react'
|
|
3
|
+
import {useEditState} from 'sanity'
|
|
4
|
+
import {ILanguageObject} from '../../types'
|
|
5
|
+
import {getLanguagesFromOption, useSanityClient, ApplyConfigResult} from '../../utils'
|
|
6
|
+
|
|
7
|
+
export function useLanguages(
|
|
8
|
+
config: ApplyConfigResult,
|
|
9
|
+
document: SanityDocument
|
|
10
|
+
): [boolean, ILanguageObject[]] {
|
|
11
|
+
const client = useSanityClient()
|
|
12
|
+
const {draft, published} = useEditState(document._id.replace(/^drafts\./, ''), document._type)
|
|
13
|
+
const [pending, setPending] = useState(false)
|
|
14
|
+
const [languages, setLanguages] = useState<ILanguageObject[]>([])
|
|
15
|
+
|
|
16
|
+
const loadOrReloadLanguages = useCallback(async () => {
|
|
17
|
+
const shouldReload =
|
|
18
|
+
languages.length === 0 || (config.shouldReload && config.shouldReload(draft ?? published))
|
|
19
|
+
if (shouldReload) {
|
|
20
|
+
setPending(true)
|
|
21
|
+
const languageObjects = await getLanguagesFromOption(
|
|
22
|
+
client,
|
|
23
|
+
config,
|
|
24
|
+
config.languages,
|
|
25
|
+
draft ?? published
|
|
26
|
+
)
|
|
27
|
+
setLanguages(languageObjects)
|
|
28
|
+
setPending(false)
|
|
29
|
+
}
|
|
30
|
+
}, [client, config, draft, published, languages])
|
|
31
|
+
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
loadOrReloadLanguages()
|
|
34
|
+
}, [draft, published, languages, config, loadOrReloadLanguages])
|
|
35
|
+
|
|
36
|
+
return [pending, languages]
|
|
37
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import {useEffect, useState, useRef} from 'react'
|
|
2
|
+
import {catchError, distinctUntilChanged} from 'rxjs/operators'
|
|
3
|
+
import isEqual from 'react-fast-compare'
|
|
4
|
+
import {useDocumentStore} from 'sanity'
|
|
5
|
+
|
|
6
|
+
type Params = Record<string, string | number | boolean | string[]>
|
|
7
|
+
|
|
8
|
+
interface ListenQueryOptions {
|
|
9
|
+
tag?: string
|
|
10
|
+
apiVersion?: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
type ReturnShape = {
|
|
14
|
+
loading: boolean
|
|
15
|
+
error: boolean
|
|
16
|
+
data: any
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
type Observable = {
|
|
20
|
+
unsubscribe: () => void
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const DEFAULT_PARAMS = {}
|
|
24
|
+
const DEFAULT_OPTIONS = {apiVersion: `v2022-05-09`}
|
|
25
|
+
|
|
26
|
+
export function useListeningQuery(
|
|
27
|
+
query: string,
|
|
28
|
+
params: Params = DEFAULT_PARAMS,
|
|
29
|
+
options: ListenQueryOptions = DEFAULT_OPTIONS
|
|
30
|
+
): ReturnShape {
|
|
31
|
+
const [loading, setLoading] = useState(true)
|
|
32
|
+
const [error, setError] = useState(false)
|
|
33
|
+
const [data, setData] = useState(null)
|
|
34
|
+
const subscription = useRef<null | Observable>(null)
|
|
35
|
+
|
|
36
|
+
const documentStore = useDocumentStore()
|
|
37
|
+
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
if (query) {
|
|
40
|
+
subscription.current = documentStore
|
|
41
|
+
.listenQuery(query, params, options)
|
|
42
|
+
.pipe(
|
|
43
|
+
distinctUntilChanged(isEqual),
|
|
44
|
+
catchError((err) => {
|
|
45
|
+
console.error(err)
|
|
46
|
+
setError(err)
|
|
47
|
+
setLoading(false)
|
|
48
|
+
setData(null)
|
|
49
|
+
|
|
50
|
+
return err
|
|
51
|
+
})
|
|
52
|
+
)
|
|
53
|
+
.subscribe((documents) => {
|
|
54
|
+
setData((current) => (isEqual(current, documents) ? current : documents))
|
|
55
|
+
setLoading(false)
|
|
56
|
+
setError(false)
|
|
57
|
+
})
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return () => {
|
|
61
|
+
return subscription.current ? subscription.current.unsubscribe() : undefined
|
|
62
|
+
}
|
|
63
|
+
}, [query, params, options])
|
|
64
|
+
|
|
65
|
+
return {loading, error, data}
|
|
66
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import React, {forwardRef, Ref, VoidFunctionComponent} from 'react'
|
|
2
|
+
import {Ti18nConfig} from '../../../types'
|
|
3
|
+
import {MaintenanceTabContent} from './MaintenanceTabContent'
|
|
4
|
+
|
|
5
|
+
export interface MaintenanceTabProps {
|
|
6
|
+
config: Ti18nConfig
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function createMaintenanceTabComponent(props: MaintenanceTabProps): VoidFunctionComponent {
|
|
10
|
+
return forwardRef(function MaintenanceTab(p: any, ref: Ref<HTMLInputElement>) {
|
|
11
|
+
return <MaintenanceTabContent {...props} ref={ref} />
|
|
12
|
+
})
|
|
13
|
+
}
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import React, {forwardRef, Ref, useCallback, useState} from 'react'
|
|
2
|
+
import {useSchema} from 'sanity'
|
|
3
|
+
import {Box, Button, Card, Code, Container, Dialog, Flex, Grid, Stack, Text} from '@sanity/ui'
|
|
4
|
+
import {Transaction} from '@sanity/client'
|
|
5
|
+
import styled from 'styled-components'
|
|
6
|
+
import {UiMessages} from '../../../constants'
|
|
7
|
+
import {MaintenanceTabTypeSelector} from '../MaintenanceTabTypeSelector'
|
|
8
|
+
import {useDocumentsInformation} from '../../hooks'
|
|
9
|
+
import {MaintenanceTabResult} from '../MaintenanceTabResult'
|
|
10
|
+
import {
|
|
11
|
+
fixBaseDocumentRefs,
|
|
12
|
+
fixBaseLanguageMismatch,
|
|
13
|
+
fixIdStructureMismatchDocuments,
|
|
14
|
+
fixLanguageFields,
|
|
15
|
+
fixOrphanedDocuments,
|
|
16
|
+
fixTranslationRefs,
|
|
17
|
+
} from '../../utils'
|
|
18
|
+
import {Ti18nConfig} from '../../../types'
|
|
19
|
+
import {useSanityClient} from '../../../utils'
|
|
20
|
+
import {useConfig} from '../../../hooks'
|
|
21
|
+
|
|
22
|
+
const StyledCodeCard = styled(Card)`
|
|
23
|
+
grid-column-start: 1;
|
|
24
|
+
grid-row-start: 1;
|
|
25
|
+
`
|
|
26
|
+
|
|
27
|
+
const StyledCode = styled(Code)`
|
|
28
|
+
word-break: break-word;
|
|
29
|
+
white-space: break-spaces;
|
|
30
|
+
`
|
|
31
|
+
|
|
32
|
+
const StyledDownloadCodeFlex = styled(Flex)`
|
|
33
|
+
grid-column-start: 1;
|
|
34
|
+
grid-row-start: 1;
|
|
35
|
+
`
|
|
36
|
+
|
|
37
|
+
export interface MaintenanceTabContentProps {
|
|
38
|
+
config: Ti18nConfig
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const MaintenanceTabContent = forwardRef(function MaintenanceTabContent(
|
|
42
|
+
{config: pluginConfig}: MaintenanceTabContentProps,
|
|
43
|
+
ref: Ref<HTMLInputElement>
|
|
44
|
+
) {
|
|
45
|
+
const [selectedSchema, setSelectedSchema] = useState('')
|
|
46
|
+
const client = useSanityClient()
|
|
47
|
+
const {
|
|
48
|
+
pending,
|
|
49
|
+
setPending,
|
|
50
|
+
documents,
|
|
51
|
+
baseDocuments,
|
|
52
|
+
translatedDocuments,
|
|
53
|
+
documentsSummaryInformation,
|
|
54
|
+
fetchInformation,
|
|
55
|
+
} = useDocumentsInformation(pluginConfig, selectedSchema)
|
|
56
|
+
const [pendingTransactions, setPendingTransactions] = useState<Transaction[] | null>(null)
|
|
57
|
+
const schemaRegistry = useSchema()
|
|
58
|
+
const config = useConfig(pluginConfig, selectedSchema)
|
|
59
|
+
|
|
60
|
+
const onSchemaTypeChange = useCallback((schemaName: string) => setSelectedSchema(schemaName), [])
|
|
61
|
+
|
|
62
|
+
const handleOpen = useCallback(() => setSelectedSchema(''), [])
|
|
63
|
+
|
|
64
|
+
const handleFixIdStructureMismatchDocuments = useCallback(() => {
|
|
65
|
+
setPendingTransactions(
|
|
66
|
+
fixIdStructureMismatchDocuments(client, config, selectedSchema, documents)
|
|
67
|
+
)
|
|
68
|
+
}, [client, config, selectedSchema, documents])
|
|
69
|
+
|
|
70
|
+
const handleFixMissingLanguageFields = useCallback(() => {
|
|
71
|
+
setPendingTransactions([fixLanguageFields(client, config, schemaRegistry, documents)])
|
|
72
|
+
}, [client, schemaRegistry, config, documents])
|
|
73
|
+
|
|
74
|
+
const handleFixTranslationRefs = useCallback(() => {
|
|
75
|
+
setPendingTransactions(fixTranslationRefs(client, config, baseDocuments, translatedDocuments))
|
|
76
|
+
}, [client, config, baseDocuments, translatedDocuments])
|
|
77
|
+
|
|
78
|
+
const handleFixBaseDocumntRefs = useCallback(() => {
|
|
79
|
+
setPendingTransactions([fixBaseDocumentRefs(client, config, translatedDocuments)])
|
|
80
|
+
}, [client, config, translatedDocuments])
|
|
81
|
+
|
|
82
|
+
const handleFixOrphanDocuments = useCallback(() => {
|
|
83
|
+
setPendingTransactions([
|
|
84
|
+
fixOrphanedDocuments(client, config, baseDocuments, translatedDocuments),
|
|
85
|
+
])
|
|
86
|
+
}, [client, config, baseDocuments, translatedDocuments])
|
|
87
|
+
|
|
88
|
+
const handleFixReferenceBehaviorMismatch = useCallback(() => {
|
|
89
|
+
setPendingTransactions(fixTranslationRefs(client, config, baseDocuments, translatedDocuments))
|
|
90
|
+
}, [client, config, baseDocuments, translatedDocuments])
|
|
91
|
+
|
|
92
|
+
const handleFixBaseLanguageMismatch = useCallback(async () => {
|
|
93
|
+
setPendingTransactions([await fixBaseLanguageMismatch(client, config, baseDocuments)])
|
|
94
|
+
}, [client, config, baseDocuments])
|
|
95
|
+
|
|
96
|
+
const handleDownloadCode = useCallback(() => {
|
|
97
|
+
if (pendingTransactions) {
|
|
98
|
+
const json = JSON.stringify(
|
|
99
|
+
pendingTransactions.map((transaction) => transaction.toJSON()),
|
|
100
|
+
null,
|
|
101
|
+
2
|
|
102
|
+
)
|
|
103
|
+
const blob = new Blob([json], {
|
|
104
|
+
type: 'application/json',
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
const url = URL.createObjectURL(blob)
|
|
108
|
+
const a = document.createElement('a')
|
|
109
|
+
a.href = url
|
|
110
|
+
a.download = 'transactions.json'
|
|
111
|
+
a.style.display = 'none'
|
|
112
|
+
document.body.appendChild(a)
|
|
113
|
+
a.click()
|
|
114
|
+
URL.revokeObjectURL(url)
|
|
115
|
+
}
|
|
116
|
+
}, [pendingTransactions])
|
|
117
|
+
|
|
118
|
+
const handleCancelPendingTransaction = useCallback(() => {
|
|
119
|
+
setPendingTransactions(null)
|
|
120
|
+
}, [setPendingTransactions])
|
|
121
|
+
|
|
122
|
+
const handleConfirmPendingTransaction = useCallback(async () => {
|
|
123
|
+
setPending(true)
|
|
124
|
+
try {
|
|
125
|
+
// run all transactions
|
|
126
|
+
if (pendingTransactions) {
|
|
127
|
+
await pendingTransactions.reduce<Promise<void>>(async (prev, transaction) => {
|
|
128
|
+
await prev
|
|
129
|
+
await transaction.commit()
|
|
130
|
+
}, Promise.resolve())
|
|
131
|
+
await fetchInformation(selectedSchema)
|
|
132
|
+
setPendingTransactions(null)
|
|
133
|
+
}
|
|
134
|
+
} catch (err) {
|
|
135
|
+
console.error(err)
|
|
136
|
+
// @TODO show error
|
|
137
|
+
} finally {
|
|
138
|
+
setPending(false)
|
|
139
|
+
}
|
|
140
|
+
}, [setPending, fetchInformation, selectedSchema, pendingTransactions])
|
|
141
|
+
|
|
142
|
+
return (
|
|
143
|
+
<Container width={1}>
|
|
144
|
+
<Stack space={2}>
|
|
145
|
+
<Box padding={4}>
|
|
146
|
+
<MaintenanceTabTypeSelector
|
|
147
|
+
value={selectedSchema}
|
|
148
|
+
onChange={onSchemaTypeChange}
|
|
149
|
+
onOpen={handleOpen}
|
|
150
|
+
ref={ref}
|
|
151
|
+
/>
|
|
152
|
+
</Box>
|
|
153
|
+
{!!selectedSchema && (
|
|
154
|
+
<Box paddingX={4}>
|
|
155
|
+
<Stack space={2}>
|
|
156
|
+
<MaintenanceTabResult
|
|
157
|
+
pending={pending}
|
|
158
|
+
count={documentsSummaryInformation.idStructureMismatch.length}
|
|
159
|
+
labelName="idStructureMismatch"
|
|
160
|
+
onClick={handleFixIdStructureMismatchDocuments}
|
|
161
|
+
/>
|
|
162
|
+
<MaintenanceTabResult
|
|
163
|
+
pending={pending}
|
|
164
|
+
count={documentsSummaryInformation.missingLanguageField.length}
|
|
165
|
+
labelName="missingLanguageField"
|
|
166
|
+
onClick={handleFixMissingLanguageFields}
|
|
167
|
+
/>
|
|
168
|
+
<MaintenanceTabResult
|
|
169
|
+
pending={pending}
|
|
170
|
+
count={documentsSummaryInformation.missingDocumentRefs.length}
|
|
171
|
+
labelName="missingDocumentRefs"
|
|
172
|
+
onClick={handleFixTranslationRefs}
|
|
173
|
+
/>
|
|
174
|
+
<MaintenanceTabResult
|
|
175
|
+
pending={pending}
|
|
176
|
+
count={documentsSummaryInformation.missingBaseDocumentRefs.length}
|
|
177
|
+
labelName="missingBaseDocumentRefs"
|
|
178
|
+
onClick={handleFixBaseDocumntRefs}
|
|
179
|
+
/>
|
|
180
|
+
<MaintenanceTabResult
|
|
181
|
+
pending={pending}
|
|
182
|
+
count={documentsSummaryInformation.orphanDocuments.length}
|
|
183
|
+
labelName="orphanDocuments"
|
|
184
|
+
onClick={handleFixOrphanDocuments}
|
|
185
|
+
/>
|
|
186
|
+
<MaintenanceTabResult
|
|
187
|
+
pending={pending}
|
|
188
|
+
count={documentsSummaryInformation.referenceBehaviorMismatch.length}
|
|
189
|
+
labelName="referenceBehaviorMismatch"
|
|
190
|
+
onClick={handleFixReferenceBehaviorMismatch}
|
|
191
|
+
/>
|
|
192
|
+
<MaintenanceTabResult
|
|
193
|
+
pending={pending}
|
|
194
|
+
count={documentsSummaryInformation.baseLanguageMismatch.length}
|
|
195
|
+
labelName="baseLanguageMismatch"
|
|
196
|
+
onClick={handleFixBaseLanguageMismatch}
|
|
197
|
+
/>
|
|
198
|
+
</Stack>
|
|
199
|
+
</Box>
|
|
200
|
+
)}
|
|
201
|
+
</Stack>
|
|
202
|
+
{!!pendingTransactions?.length && (
|
|
203
|
+
<Dialog
|
|
204
|
+
id="confirm-pending-transactions"
|
|
205
|
+
width={2}
|
|
206
|
+
header={UiMessages.translationsMaintenance.pendingTransactionDialog.header}
|
|
207
|
+
onClose={handleCancelPendingTransaction}
|
|
208
|
+
footer={
|
|
209
|
+
<Flex padding={3} justify="flex-end">
|
|
210
|
+
<Grid autoFlow="column" autoCols="auto" gap={2}>
|
|
211
|
+
<Button
|
|
212
|
+
tone="default"
|
|
213
|
+
loading={pending}
|
|
214
|
+
disabled={pending}
|
|
215
|
+
text={UiMessages.translationsMaintenance.pendingTransactionDialog.cancel}
|
|
216
|
+
onClick={handleCancelPendingTransaction}
|
|
217
|
+
/>
|
|
218
|
+
<Button
|
|
219
|
+
tone="critical"
|
|
220
|
+
loading={pending}
|
|
221
|
+
disabled={pending}
|
|
222
|
+
text={UiMessages.translationsMaintenance.pendingTransactionDialog.confirm}
|
|
223
|
+
onClick={handleConfirmPendingTransaction}
|
|
224
|
+
/>
|
|
225
|
+
</Grid>
|
|
226
|
+
</Flex>
|
|
227
|
+
}
|
|
228
|
+
>
|
|
229
|
+
<Stack padding={3} space={3}>
|
|
230
|
+
<Card padding={3} radius={2} shadow={1} tone="caution">
|
|
231
|
+
<Text size={2}>
|
|
232
|
+
{UiMessages.translationsMaintenance.pendingTransactionDialog.caution}
|
|
233
|
+
</Text>
|
|
234
|
+
</Card>
|
|
235
|
+
<Grid cols={1}>
|
|
236
|
+
<StyledCodeCard padding={3} radius={2} shadow={1} tone="default">
|
|
237
|
+
<StyledCode language="json">
|
|
238
|
+
{JSON.stringify(
|
|
239
|
+
pendingTransactions.map((transaction) => transaction.toJSON()),
|
|
240
|
+
null,
|
|
241
|
+
2
|
|
242
|
+
)}
|
|
243
|
+
</StyledCode>
|
|
244
|
+
</StyledCodeCard>
|
|
245
|
+
<StyledDownloadCodeFlex
|
|
246
|
+
padding={2}
|
|
247
|
+
justify="flex-end"
|
|
248
|
+
align="flex-start"
|
|
249
|
+
onClick={handleDownloadCode}
|
|
250
|
+
>
|
|
251
|
+
<Button text="Download" />
|
|
252
|
+
</StyledDownloadCodeFlex>
|
|
253
|
+
</Grid>
|
|
254
|
+
</Stack>
|
|
255
|
+
</Dialog>
|
|
256
|
+
)}
|
|
257
|
+
</Container>
|
|
258
|
+
)
|
|
259
|
+
})
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './MaintenanceTab'
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import {Box, Button, Card, Flex, Text} from '@sanity/ui'
|
|
3
|
+
import {UiMessages} from '../../../constants/UiMessages'
|
|
4
|
+
|
|
5
|
+
type Props = React.PropsWithChildren<{
|
|
6
|
+
pending?: boolean
|
|
7
|
+
count: number
|
|
8
|
+
labelName?: keyof typeof UiMessages['translationsMaintenance']
|
|
9
|
+
onClick?: (event: React.SyntheticEvent<HTMLButtonElement, Event>) => void
|
|
10
|
+
}>
|
|
11
|
+
|
|
12
|
+
export const MaintenanceTabResult: React.FC<Props> = ({
|
|
13
|
+
pending,
|
|
14
|
+
count,
|
|
15
|
+
labelName,
|
|
16
|
+
children,
|
|
17
|
+
onClick,
|
|
18
|
+
}) => {
|
|
19
|
+
return (
|
|
20
|
+
<Card padding={3} radius={2} shadow={1} tone={count > 0 ? `caution` : `default`}>
|
|
21
|
+
<Flex align="center">
|
|
22
|
+
<Box flex={1}>
|
|
23
|
+
<Text muted={count <= 0}>
|
|
24
|
+
<span>{count}</span>
|
|
25
|
+
<span> </span>
|
|
26
|
+
<span>
|
|
27
|
+
{labelName ? String(UiMessages.translationsMaintenance[labelName]) : children}
|
|
28
|
+
</span>
|
|
29
|
+
</Text>
|
|
30
|
+
</Box>
|
|
31
|
+
|
|
32
|
+
{count > 0 && (
|
|
33
|
+
<Button
|
|
34
|
+
padding={2}
|
|
35
|
+
fontSize={2}
|
|
36
|
+
disabled={pending}
|
|
37
|
+
onClick={onClick}
|
|
38
|
+
text={UiMessages.translationsMaintenance.fix}
|
|
39
|
+
/>
|
|
40
|
+
)}
|
|
41
|
+
</Flex>
|
|
42
|
+
</Card>
|
|
43
|
+
)
|
|
44
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './MaintenanceTabResult'
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import React, {forwardRef, Ref, useMemo} from 'react'
|
|
2
|
+
import {Autocomplete, Card, Stack, Text} from '@sanity/ui'
|
|
3
|
+
import {EarthGlobeIcon} from '@sanity/icons'
|
|
4
|
+
import {useSchema, DefaultPreview as Preview} from 'sanity'
|
|
5
|
+
import {Ti18nSchema} from '../../../types'
|
|
6
|
+
import {UiMessages} from '../../../constants'
|
|
7
|
+
|
|
8
|
+
type PreviewMedia = React.ComponentProps<typeof Preview>['media']
|
|
9
|
+
type Props = {
|
|
10
|
+
value: string
|
|
11
|
+
onChange: (value: string) => void
|
|
12
|
+
onOpen: () => void
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const MaintenanceTabTypeSelector = forwardRef(function MaintenanceTabTypeSelector(
|
|
16
|
+
{value, onChange, onOpen}: Props,
|
|
17
|
+
ref: Ref<HTMLInputElement>
|
|
18
|
+
) {
|
|
19
|
+
const schema = useSchema()
|
|
20
|
+
const i18nSchemas = useMemo(
|
|
21
|
+
() => schema._original?.types.filter((s: unknown) => !!(s as Ti18nSchema).i18n),
|
|
22
|
+
[schema]
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<Stack space={4}>
|
|
27
|
+
<Text>{UiMessages.translationsMaintenance.selectSchemaPlaceholder}</Text>
|
|
28
|
+
<Card>
|
|
29
|
+
<Autocomplete
|
|
30
|
+
ref={ref}
|
|
31
|
+
fontSize={[2, 2, 3]}
|
|
32
|
+
icon={EarthGlobeIcon}
|
|
33
|
+
id="i18n-schema-selector"
|
|
34
|
+
options={i18nSchemas?.map((option) => ({
|
|
35
|
+
value: option.name,
|
|
36
|
+
payload: option,
|
|
37
|
+
}))}
|
|
38
|
+
value={value}
|
|
39
|
+
placeholder="Search"
|
|
40
|
+
openButton={{onClick: () => onOpen}}
|
|
41
|
+
onChange={onChange}
|
|
42
|
+
renderValue={(v, option) => option?.payload.title || v}
|
|
43
|
+
renderOption={({payload}) => (
|
|
44
|
+
<Card padding={2} radius={2} as="button">
|
|
45
|
+
<Preview
|
|
46
|
+
layout="default"
|
|
47
|
+
title={payload.title}
|
|
48
|
+
media={payload.icon as PreviewMedia}
|
|
49
|
+
/>
|
|
50
|
+
</Card>
|
|
51
|
+
)}
|
|
52
|
+
/>
|
|
53
|
+
</Card>
|
|
54
|
+
</Stack>
|
|
55
|
+
)
|
|
56
|
+
})
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './MaintenanceTabTypeSelector'
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './useDocumentsInformation'
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import {useConfig} from '../../hooks'
|
|
3
|
+
import {I18nDelimiter, I18nPrefix, IdStructure, ReferenceBehavior} from '../../constants'
|
|
4
|
+
import {ILanguageObject, ITranslationRef, Ti18nConfig, Ti18nDocument} from '../../types'
|
|
5
|
+
import {
|
|
6
|
+
getBaseIdFromId,
|
|
7
|
+
getBaseLanguage,
|
|
8
|
+
getLanguagesFromOption,
|
|
9
|
+
useSanityClient,
|
|
10
|
+
} from '../../utils'
|
|
11
|
+
|
|
12
|
+
export const useDocumentsInformation = (pluginConfig: Ti18nConfig, schema: string) => {
|
|
13
|
+
const client = useSanityClient()
|
|
14
|
+
const config = useConfig(pluginConfig, schema)
|
|
15
|
+
const [pending, setPending] = React.useState(false)
|
|
16
|
+
const [documents, setDocuments] = React.useState<Ti18nDocument[]>([])
|
|
17
|
+
const [languages, setLanguages] = React.useState<ILanguageObject[]>([])
|
|
18
|
+
const baseDocuments = React.useMemo(() => {
|
|
19
|
+
if (config.idStructure === IdStructure.DELIMITER)
|
|
20
|
+
return documents.filter((d) => !d._id.includes(I18nDelimiter))
|
|
21
|
+
return documents.filter((d) => !d._id.startsWith(I18nPrefix))
|
|
22
|
+
}, [config, documents])
|
|
23
|
+
const translatedDocuments = React.useMemo(() => {
|
|
24
|
+
if (config.idStructure === IdStructure.DELIMITER)
|
|
25
|
+
return documents.filter((d) => d._id.includes(I18nDelimiter))
|
|
26
|
+
return documents.filter((d) => d._id.startsWith(I18nPrefix))
|
|
27
|
+
}, [config, documents])
|
|
28
|
+
const idStructureMismatchDocuments = React.useMemo(() => {
|
|
29
|
+
if (config.idStructure === IdStructure.DELIMITER)
|
|
30
|
+
return documents.filter((d) => d._id.startsWith(I18nPrefix))
|
|
31
|
+
return documents.filter((d) => d._id.includes(I18nDelimiter))
|
|
32
|
+
}, [config, documents])
|
|
33
|
+
|
|
34
|
+
const fetchInformation = React.useCallback(
|
|
35
|
+
async (selectedSchema: string) => {
|
|
36
|
+
setPending(true)
|
|
37
|
+
const [langs, result] = await Promise.all([
|
|
38
|
+
getLanguagesFromOption(client, config, config.languages),
|
|
39
|
+
client.fetch<Ti18nDocument[]>(`*[_type == $type && !(_id in path('drafts.**'))]`, {
|
|
40
|
+
type: selectedSchema,
|
|
41
|
+
}),
|
|
42
|
+
])
|
|
43
|
+
setLanguages(langs)
|
|
44
|
+
setDocuments(result)
|
|
45
|
+
setPending(false)
|
|
46
|
+
},
|
|
47
|
+
[config, client]
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
const documentsSummaryInformation = React.useMemo(() => {
|
|
51
|
+
const base = getBaseLanguage(languages, config.base)
|
|
52
|
+
const basedocuments = baseDocuments
|
|
53
|
+
const translateddocuments = translatedDocuments
|
|
54
|
+
const langFieldName = config.fieldNames?.lang
|
|
55
|
+
const refsFieldName = config.fieldNames?.references
|
|
56
|
+
const baseRefFieldName = config.fieldNames?.baseReference
|
|
57
|
+
return {
|
|
58
|
+
idStructureMismatch: idStructureMismatchDocuments,
|
|
59
|
+
missingLanguageField: documents.filter((d) => !d[langFieldName]),
|
|
60
|
+
missingDocumentRefs: basedocuments.filter((d) => {
|
|
61
|
+
const docs = translateddocuments.filter((dx) => getBaseIdFromId(dx._id) === d._id)
|
|
62
|
+
const refsCount = (Object.values(d[refsFieldName] || []) as any[]).filter(
|
|
63
|
+
(ref) => ref._type === 'reference' && !!ref._ref
|
|
64
|
+
).length
|
|
65
|
+
return refsCount != docs.length
|
|
66
|
+
}),
|
|
67
|
+
missingBaseDocumentRefs: translateddocuments.filter((d) => !d[baseRefFieldName]),
|
|
68
|
+
orphanDocuments: translateddocuments.filter((d) => {
|
|
69
|
+
const baseDoc = basedocuments.find((doc) => getBaseIdFromId(d._id) === doc._id)
|
|
70
|
+
if (baseDoc) return false
|
|
71
|
+
return true
|
|
72
|
+
}),
|
|
73
|
+
referenceBehaviorMismatch: basedocuments.filter((doc) => {
|
|
74
|
+
const refs: ITranslationRef[] = doc[refsFieldName] || []
|
|
75
|
+
if (config.referenceBehavior === ReferenceBehavior.DISABLED)
|
|
76
|
+
return Object.keys(refs).length > 0
|
|
77
|
+
if (config.referenceBehavior === ReferenceBehavior.WEAK)
|
|
78
|
+
return Object.values(refs).some((r) => !r._weak)
|
|
79
|
+
return Object.values(refs).some((r) => !!r._weak)
|
|
80
|
+
}),
|
|
81
|
+
baseLanguageMismatch: basedocuments.filter((doc) => {
|
|
82
|
+
return base?.id && doc[langFieldName] !== base.id
|
|
83
|
+
}),
|
|
84
|
+
}
|
|
85
|
+
}, [
|
|
86
|
+
config,
|
|
87
|
+
documents,
|
|
88
|
+
languages,
|
|
89
|
+
baseDocuments,
|
|
90
|
+
translatedDocuments,
|
|
91
|
+
idStructureMismatchDocuments,
|
|
92
|
+
])
|
|
93
|
+
|
|
94
|
+
React.useEffect(() => {
|
|
95
|
+
if (schema) {
|
|
96
|
+
fetchInformation(schema)
|
|
97
|
+
}
|
|
98
|
+
}, [schema])
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
pending,
|
|
102
|
+
setPending,
|
|
103
|
+
documents,
|
|
104
|
+
baseDocuments,
|
|
105
|
+
translatedDocuments,
|
|
106
|
+
idStructureMismatchDocuments,
|
|
107
|
+
documentsSummaryInformation,
|
|
108
|
+
fetchInformation,
|
|
109
|
+
}
|
|
110
|
+
}
|