@sanity/document-internationalization 1.0.0 → 1.0.2
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 +99 -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
package/package.json
CHANGED
|
@@ -1,19 +1,41 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sanity/document-internationalization",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "
|
|
5
|
-
"
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"description": "Provides document level translations for Sanity Studio",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"sanity",
|
|
7
|
+
"cms",
|
|
8
|
+
"headless",
|
|
9
|
+
"realtime",
|
|
10
|
+
"content",
|
|
11
|
+
"sanity-plugin",
|
|
12
|
+
"i18n",
|
|
13
|
+
"localization"
|
|
14
|
+
],
|
|
15
|
+
"homepage": "https://github.com/sanity-io/document-internationalization#readme",
|
|
16
|
+
"bugs": {
|
|
17
|
+
"url": "https://github.com/sanity-io/document-internationalization/issues"
|
|
18
|
+
},
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git@github.com:sanity-io/document-internationalization.git"
|
|
22
|
+
},
|
|
6
23
|
"license": "MIT",
|
|
7
|
-
"
|
|
8
|
-
"main": "./lib/cjs/index.js",
|
|
9
|
-
"module": "./lib/esm/index.js",
|
|
10
|
-
"types": "./lib/types/index.d.ts",
|
|
24
|
+
"author": "Sanity.io <hello@sanity.io>",
|
|
11
25
|
"exports": {
|
|
12
26
|
".": {
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
|
|
27
|
+
"types": "./lib/src/index.d.ts",
|
|
28
|
+
"source": "./src/index.ts",
|
|
29
|
+
"import": "./lib/index.esm.js",
|
|
30
|
+
"require": "./lib/index.js",
|
|
31
|
+
"default": "./lib/index.esm.js"
|
|
32
|
+
},
|
|
33
|
+
"./package.json": "./package.json"
|
|
16
34
|
},
|
|
35
|
+
"main": "./lib/index.js",
|
|
36
|
+
"module": "./lib/index.esm.js",
|
|
37
|
+
"source": "./src/index.ts",
|
|
38
|
+
"types": "./lib/src/index.d.ts",
|
|
17
39
|
"files": [
|
|
18
40
|
"src",
|
|
19
41
|
"lib",
|
|
@@ -21,54 +43,88 @@
|
|
|
21
43
|
"sanity.json"
|
|
22
44
|
],
|
|
23
45
|
"scripts": {
|
|
46
|
+
"prebuild": "npm run clean && plugin-kit verify-package --silent && pkg-utils",
|
|
47
|
+
"build": "pkg-utils build --strict",
|
|
24
48
|
"clean": "rimraf lib",
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"build": "parcel build --no-cache",
|
|
28
|
-
"watch": "parcel watch",
|
|
49
|
+
"compile": "tsc --noEmit",
|
|
50
|
+
"eslint": "eslint --ext=.js,.jsx,.mjs,.ts,.tsx --quiet",
|
|
29
51
|
"link-watch": "plugin-kit link-watch",
|
|
30
|
-
"
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
"
|
|
34
|
-
"
|
|
52
|
+
"lint": "eslint .",
|
|
53
|
+
"lint:fix": "eslint . --quiet --fix",
|
|
54
|
+
"prepare": "husky install",
|
|
55
|
+
"prepublishOnly": "npm run build",
|
|
56
|
+
"watch": "pkg-utils watch"
|
|
35
57
|
},
|
|
36
|
-
"
|
|
37
|
-
"
|
|
58
|
+
"husky": {
|
|
59
|
+
"hooks": {
|
|
60
|
+
"pre-commit": "npm run lint:fix"
|
|
61
|
+
}
|
|
38
62
|
},
|
|
39
63
|
"dependencies": {
|
|
40
|
-
"@
|
|
41
|
-
"@sanity/
|
|
42
|
-
"@sanity/
|
|
64
|
+
"@cprecioso/country-flag-emoji": "^1.0.0",
|
|
65
|
+
"@sanity/color": "^2.1.20",
|
|
66
|
+
"@sanity/icons": "^2.0.0",
|
|
67
|
+
"@sanity/incompatible-plugin": "^1.0.4",
|
|
43
68
|
"@sanity/uuid": "^3.0.1",
|
|
44
|
-
"sanity
|
|
45
|
-
"
|
|
69
|
+
"@sanity/ui": "^1.0.0",
|
|
70
|
+
"html-react-parser": "^1.4.12",
|
|
71
|
+
"just-omit": "^2.0.1",
|
|
72
|
+
"just-safe-get": "^4.0.1",
|
|
73
|
+
"just-split": "^3.0.1",
|
|
74
|
+
"pinst": "^2.1.6",
|
|
75
|
+
"react-fast-compare": "^3.2.0",
|
|
76
|
+
"react-twemoji": "^0.5.0",
|
|
77
|
+
"twemoji": "^14.0.2"
|
|
46
78
|
},
|
|
47
79
|
"devDependencies": {
|
|
48
|
-
"@
|
|
49
|
-
"@
|
|
50
|
-
"@sanity/
|
|
51
|
-
"@
|
|
52
|
-
"@
|
|
53
|
-
"
|
|
80
|
+
"@commitlint/cli": "^17.2.0",
|
|
81
|
+
"@commitlint/config-conventional": "^17.2.0",
|
|
82
|
+
"@sanity/pkg-utils": "^1.17.2",
|
|
83
|
+
"@sanity/plugin-kit": "^2.1.5",
|
|
84
|
+
"@sanity/semantic-release-preset": "^2.0.2",
|
|
85
|
+
"@types/lodash": "^4.14.179",
|
|
86
|
+
"@types/react": "^18",
|
|
87
|
+
"@types/react-router": "^5.1.11",
|
|
88
|
+
"@types/react-router-dom": "^5.1.7",
|
|
89
|
+
"@types/resize-observer-browser": "^0.1.5",
|
|
90
|
+
"@types/styled-components": "^5.1.20",
|
|
91
|
+
"@typescript-eslint/eslint-plugin": "^5.42.0",
|
|
92
|
+
"@typescript-eslint/parser": "^5.42.0",
|
|
93
|
+
"eslint": "^8.26.0",
|
|
54
94
|
"eslint-config-prettier": "^8.5.0",
|
|
55
95
|
"eslint-config-sanity": "^6.0.0",
|
|
96
|
+
"eslint-plugin-cypress": "^2.12.1",
|
|
97
|
+
"eslint-plugin-es5": "^1.5.0",
|
|
98
|
+
"eslint-plugin-import": "^2.25.3",
|
|
56
99
|
"eslint-plugin-prettier": "^4.2.1",
|
|
57
|
-
"eslint-plugin-react": "^7.31.
|
|
100
|
+
"eslint-plugin-react": "^7.31.10",
|
|
58
101
|
"eslint-plugin-react-hooks": "^4.6.0",
|
|
59
|
-
"
|
|
102
|
+
"husky": "^8.0.1",
|
|
103
|
+
"lint-staged": "^13.0.3",
|
|
104
|
+
"lodash": "^4.17.21",
|
|
60
105
|
"prettier": "^2.7.1",
|
|
61
|
-
"
|
|
106
|
+
"prettier-plugin-packagejson": "^2.3.0",
|
|
107
|
+
"react": "^18",
|
|
108
|
+
"react-dom": "^18",
|
|
109
|
+
"react-router": "^5.1.0",
|
|
110
|
+
"react-router-dom": "^5.1.0",
|
|
62
111
|
"rimraf": "^3.0.2",
|
|
63
|
-
"
|
|
64
|
-
"
|
|
112
|
+
"rxjs": "^6.6.0",
|
|
113
|
+
"sanity": "^3.0.0",
|
|
114
|
+
"styled-components": "^5.3.1",
|
|
115
|
+
"typescript": "^4.8.4"
|
|
65
116
|
},
|
|
66
117
|
"peerDependencies": {
|
|
67
|
-
"
|
|
68
|
-
"
|
|
118
|
+
"lodash": "^4.0.0",
|
|
119
|
+
"react": "^18",
|
|
120
|
+
"react-dom": "^18",
|
|
121
|
+
"react-router": "^5.0.0",
|
|
122
|
+
"react-router-dom": "^5.0.0",
|
|
123
|
+
"rxjs": "^6.0.0",
|
|
124
|
+
"sanity": "^3.0.0",
|
|
125
|
+
"styled-components": "^5.0.0"
|
|
69
126
|
},
|
|
70
|
-
"
|
|
71
|
-
"
|
|
72
|
-
}
|
|
73
|
-
"homepage": "https://github.com/sanity-io/document-internationalization#readme"
|
|
127
|
+
"engines": {
|
|
128
|
+
"node": ">=14"
|
|
129
|
+
}
|
|
74
130
|
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import {TrashIcon} from '@sanity/icons'
|
|
3
|
+
import {useDocumentOperation, useEditState, useSyncState} from 'sanity'
|
|
4
|
+
import {ConfirmDeleteDialog} from 'sanity/desk'
|
|
5
|
+
import {useToast} from '@sanity/ui'
|
|
6
|
+
import type {DocumentActionComponent, DocumentActionDescription} from 'sanity'
|
|
7
|
+
import {IEditState, IUseDocumentOperationResult, Ti18nConfig} from '../types'
|
|
8
|
+
import {getBaseIdFromId, getTranslationsFor, useSanityClient} from '../utils'
|
|
9
|
+
import {UiMessages} from '../constants'
|
|
10
|
+
import {useConfig} from '../hooks'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* This code is mostly taken from the defualt DeleteAction provided by Sanity
|
|
14
|
+
*/
|
|
15
|
+
const DISABLED_REASON_TITLE = {
|
|
16
|
+
NOTHING_TO_DELETE: "This document doesn't yet exist or is already deleted",
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function createDeleteAction(pluginConfig: Ti18nConfig): DocumentActionComponent {
|
|
20
|
+
return function DeleteWith18nAction({id, type, onComplete}): DocumentActionDescription {
|
|
21
|
+
const toast = useToast()
|
|
22
|
+
const client = useSanityClient()
|
|
23
|
+
const config = useConfig(pluginConfig, type)
|
|
24
|
+
const baseDocumentId = React.useMemo(() => getBaseIdFromId(id), [id])
|
|
25
|
+
const baseDocumentEditState = useEditState(baseDocumentId, type) as IEditState
|
|
26
|
+
const syncState = useSyncState(id, type)
|
|
27
|
+
const baseDocumentSyncState = useSyncState(baseDocumentId, type)
|
|
28
|
+
const {delete: deleteOp} = useDocumentOperation(id, type) as IUseDocumentOperationResult
|
|
29
|
+
const [isDeleting, setIsDeleting] = React.useState(false)
|
|
30
|
+
const [isConfirmDialogOpen, setConfirmDialogOpen] = React.useState(false)
|
|
31
|
+
|
|
32
|
+
const onHandle = React.useCallback(() => {
|
|
33
|
+
setConfirmDialogOpen(true)
|
|
34
|
+
}, [])
|
|
35
|
+
|
|
36
|
+
const onDialogCancel = React.useCallback(() => {
|
|
37
|
+
setConfirmDialogOpen(false)
|
|
38
|
+
if (onComplete) onComplete()
|
|
39
|
+
}, [onComplete])
|
|
40
|
+
|
|
41
|
+
const onDialogConfirm = React.useCallback(async () => {
|
|
42
|
+
try {
|
|
43
|
+
setIsDeleting(true)
|
|
44
|
+
setConfirmDialogOpen(false)
|
|
45
|
+
|
|
46
|
+
if (baseDocumentEditState.draft || baseDocumentEditState.published) {
|
|
47
|
+
const baseTransaction = client.transaction()
|
|
48
|
+
if (baseDocumentEditState.draft) {
|
|
49
|
+
baseTransaction.delete(`drafts.${baseDocumentId}`)
|
|
50
|
+
}
|
|
51
|
+
if (baseDocumentEditState.published) {
|
|
52
|
+
baseTransaction.patch(baseDocumentId, {
|
|
53
|
+
unset: [config.fieldNames.references],
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
await baseTransaction.commit()
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const translatedDocuments = await getTranslationsFor(client, config, baseDocumentId, true)
|
|
60
|
+
const translationsTransaction = client.transaction()
|
|
61
|
+
translatedDocuments.forEach((doc) => {
|
|
62
|
+
translationsTransaction.delete(doc._id)
|
|
63
|
+
})
|
|
64
|
+
await translationsTransaction.commit()
|
|
65
|
+
|
|
66
|
+
deleteOp.execute()
|
|
67
|
+
if (onComplete) onComplete()
|
|
68
|
+
} catch (err) {
|
|
69
|
+
toast.push({
|
|
70
|
+
closable: true,
|
|
71
|
+
status: 'error',
|
|
72
|
+
title: (err as Error).toString(),
|
|
73
|
+
})
|
|
74
|
+
} finally {
|
|
75
|
+
setIsDeleting(true)
|
|
76
|
+
}
|
|
77
|
+
}, [
|
|
78
|
+
toast,
|
|
79
|
+
config,
|
|
80
|
+
client,
|
|
81
|
+
baseDocumentId,
|
|
82
|
+
baseDocumentEditState.draft,
|
|
83
|
+
baseDocumentEditState.published,
|
|
84
|
+
deleteOp,
|
|
85
|
+
onComplete,
|
|
86
|
+
])
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
onHandle,
|
|
90
|
+
tone: 'critical',
|
|
91
|
+
icon: TrashIcon,
|
|
92
|
+
disabled:
|
|
93
|
+
isDeleting ||
|
|
94
|
+
Boolean(deleteOp.disabled) ||
|
|
95
|
+
syncState.isSyncing ||
|
|
96
|
+
baseDocumentSyncState.isSyncing,
|
|
97
|
+
shortcut: 'CTRL+ALT+D',
|
|
98
|
+
title:
|
|
99
|
+
(deleteOp.disabled &&
|
|
100
|
+
DISABLED_REASON_TITLE[deleteOp.disabled as keyof typeof DISABLED_REASON_TITLE]) ||
|
|
101
|
+
'',
|
|
102
|
+
label: isDeleting ? UiMessages.deleteAll.deleting : UiMessages.deleteAll.buttonTitle,
|
|
103
|
+
dialog: isConfirmDialogOpen && {
|
|
104
|
+
type: 'dialog',
|
|
105
|
+
onClose: onComplete,
|
|
106
|
+
content: (
|
|
107
|
+
<ConfirmDeleteDialog
|
|
108
|
+
action="delete"
|
|
109
|
+
id={id}
|
|
110
|
+
type={type}
|
|
111
|
+
onCancel={onDialogCancel}
|
|
112
|
+
onConfirm={onDialogConfirm}
|
|
113
|
+
/>
|
|
114
|
+
),
|
|
115
|
+
},
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import {CopyIcon} from '@sanity/icons'
|
|
3
|
+
import {useDocumentOperation} from 'sanity'
|
|
4
|
+
import {useToast} from '@sanity/ui'
|
|
5
|
+
import {uuid} from '@sanity/uuid'
|
|
6
|
+
import type {DocumentActionComponent, DocumentActionDescription} from 'sanity'
|
|
7
|
+
import {
|
|
8
|
+
buildDocId,
|
|
9
|
+
getBaseIdFromId,
|
|
10
|
+
getLanguageFromDocument,
|
|
11
|
+
getTranslationsFor,
|
|
12
|
+
useSanityClient,
|
|
13
|
+
} from '../utils'
|
|
14
|
+
import {IUseDocumentOperationResult, Ti18nConfig} from '../types'
|
|
15
|
+
import {UiMessages} from '../constants'
|
|
16
|
+
import {useConfig} from '../hooks'
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* This code is mostly taken from the default DuplicateAction provided by Sanity
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
const DISABLED_REASON_TITLE = {
|
|
23
|
+
NOTHING_TO_DUPLICATE: "This document doesn't yet exist so there's nothing to duplicate",
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function createDuplicateAction(pluginConfig: Ti18nConfig): DocumentActionComponent {
|
|
27
|
+
return ({id, type, draft, published}): DocumentActionDescription => {
|
|
28
|
+
const toast = useToast()
|
|
29
|
+
const client = useSanityClient()
|
|
30
|
+
const config = useConfig(pluginConfig, type)
|
|
31
|
+
const baseDocumentId = getBaseIdFromId(id)
|
|
32
|
+
const {duplicate: duplicateOp} = useDocumentOperation(id, type) as IUseDocumentOperationResult
|
|
33
|
+
const [isDuplicating, setDuplicating] = React.useState(false)
|
|
34
|
+
|
|
35
|
+
const onDuplicate = React.useCallback(async () => {
|
|
36
|
+
setDuplicating(true)
|
|
37
|
+
try {
|
|
38
|
+
const dupeId = uuid()
|
|
39
|
+
const translations = await getTranslationsFor(client, config, baseDocumentId)
|
|
40
|
+
const transaction = client.transaction()
|
|
41
|
+
transaction.create({
|
|
42
|
+
...(draft ?? published),
|
|
43
|
+
_id: dupeId,
|
|
44
|
+
_type: type,
|
|
45
|
+
})
|
|
46
|
+
translations.forEach((t) => {
|
|
47
|
+
const isDraft = t._id.startsWith('drafts.')
|
|
48
|
+
const newId = buildDocId(config, dupeId, getLanguageFromDocument(t, config))
|
|
49
|
+
transaction.create({
|
|
50
|
+
...t,
|
|
51
|
+
_id: isDraft ? `drafts.${newId}` : newId,
|
|
52
|
+
})
|
|
53
|
+
})
|
|
54
|
+
await transaction.commit()
|
|
55
|
+
} catch (err) {
|
|
56
|
+
console.error(err)
|
|
57
|
+
toast.push({
|
|
58
|
+
description: (err as Error).message,
|
|
59
|
+
})
|
|
60
|
+
}
|
|
61
|
+
setDuplicating(false)
|
|
62
|
+
}, [client, config, toast, baseDocumentId, type, draft, published])
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
icon: CopyIcon,
|
|
66
|
+
disabled: Boolean(duplicateOp.disabled) || isDuplicating,
|
|
67
|
+
title:
|
|
68
|
+
(duplicateOp.disabled &&
|
|
69
|
+
DISABLED_REASON_TITLE[duplicateOp.disabled as keyof typeof DISABLED_REASON_TITLE]) ||
|
|
70
|
+
'',
|
|
71
|
+
label: isDuplicating
|
|
72
|
+
? UiMessages.duplicateAll.duplicating
|
|
73
|
+
: UiMessages.duplicateAll.buttonTitle,
|
|
74
|
+
onHandle: onDuplicate,
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
|
|
3
|
+
import {useToast} from '@sanity/ui'
|
|
4
|
+
import {CheckmarkIcon, PublishIcon} from '@sanity/icons'
|
|
5
|
+
import {useDocumentOperation, useEditState, useSyncState, useValidationStatus} from 'sanity'
|
|
6
|
+
import type {DocumentActionComponent, DocumentActionDescription} from 'sanity'
|
|
7
|
+
import {getBaseIdFromId, updateIntlFieldsForDocument, useSanityClient} from '../utils'
|
|
8
|
+
import {ReferenceBehavior, UiMessages} from '../constants'
|
|
9
|
+
import {IEditState, IUseDocumentOperationResult, Ti18nConfig} from '../types'
|
|
10
|
+
import {useConfig, useDelayedFlag} from '../hooks'
|
|
11
|
+
|
|
12
|
+
export function createPublishAction(pluginConfig: Ti18nConfig): DocumentActionComponent {
|
|
13
|
+
return ({type, id, onComplete}): DocumentActionDescription => {
|
|
14
|
+
const toast = useToast()
|
|
15
|
+
const client = useSanityClient()
|
|
16
|
+
const config = useConfig(pluginConfig, type)
|
|
17
|
+
const baseDocumentId = getBaseIdFromId(id)
|
|
18
|
+
const updatingIntlFieldsPromiseRef = React.useRef<Promise<void> | null>(null)
|
|
19
|
+
const [publishState, setPublishState] = React.useState<'publishing' | 'published' | null>(null)
|
|
20
|
+
const [updatingIntlFields, setUpdatingIntlFields] = React.useState(false)
|
|
21
|
+
const {draft, published} = useEditState(id, type) as IEditState
|
|
22
|
+
const baseDocumentEditState = useEditState(baseDocumentId, type) as IEditState
|
|
23
|
+
const {publish} = useDocumentOperation(id, type) as IUseDocumentOperationResult
|
|
24
|
+
const {isValidating, validation} = useValidationStatus(id, type)
|
|
25
|
+
const syncState = useSyncState(id, type)
|
|
26
|
+
const baseDocumentSyncState = useSyncState(baseDocumentId, type)
|
|
27
|
+
const disabled = useDelayedFlag(
|
|
28
|
+
!!(
|
|
29
|
+
publishState === 'published' ||
|
|
30
|
+
publishState === 'publishing' ||
|
|
31
|
+
updatingIntlFields ||
|
|
32
|
+
publish.disabled ||
|
|
33
|
+
syncState.isSyncing ||
|
|
34
|
+
baseDocumentSyncState.isSyncing ||
|
|
35
|
+
isValidating ||
|
|
36
|
+
validation.some((marker) => marker.level === 'error')
|
|
37
|
+
)
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
const label = React.useMemo(() => {
|
|
41
|
+
if (publishState === 'publishing') return UiMessages.publishing
|
|
42
|
+
if (updatingIntlFields) return UiMessages.updatingIntlFields
|
|
43
|
+
return UiMessages.publish
|
|
44
|
+
}, [publishState, updatingIntlFields])
|
|
45
|
+
|
|
46
|
+
const doUpdateIntlFields = React.useCallback(async () => {
|
|
47
|
+
setUpdatingIntlFields(true)
|
|
48
|
+
try {
|
|
49
|
+
const document = draft || published
|
|
50
|
+
if (document) {
|
|
51
|
+
await updateIntlFieldsForDocument(
|
|
52
|
+
client,
|
|
53
|
+
config,
|
|
54
|
+
document,
|
|
55
|
+
baseDocumentEditState.published
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
toast.push({
|
|
60
|
+
closable: true,
|
|
61
|
+
status: 'success',
|
|
62
|
+
title: UiMessages.intlFieldsUpdated,
|
|
63
|
+
})
|
|
64
|
+
} catch (err) {
|
|
65
|
+
console.error(err)
|
|
66
|
+
toast.push({
|
|
67
|
+
closable: true,
|
|
68
|
+
status: 'error',
|
|
69
|
+
title: (err as Error).toString(),
|
|
70
|
+
})
|
|
71
|
+
}
|
|
72
|
+
setUpdatingIntlFields(false)
|
|
73
|
+
}, [toast, draft, published, client, baseDocumentEditState.published, config])
|
|
74
|
+
|
|
75
|
+
const onHandle = React.useCallback(() => {
|
|
76
|
+
setPublishState('publishing')
|
|
77
|
+
const isTranslation = id !== baseDocumentId
|
|
78
|
+
if (
|
|
79
|
+
isTranslation &&
|
|
80
|
+
!baseDocumentEditState.published &&
|
|
81
|
+
config.referenceBehavior === ReferenceBehavior.STRONG
|
|
82
|
+
) {
|
|
83
|
+
throw new Error(UiMessages.errors.baseDocumentNotPublished)
|
|
84
|
+
} else {
|
|
85
|
+
publish.execute()
|
|
86
|
+
}
|
|
87
|
+
}, [id, baseDocumentId, publish, config, baseDocumentEditState.published])
|
|
88
|
+
|
|
89
|
+
React.useEffect(() => {
|
|
90
|
+
const didPublish = publishState === 'publishing' && !draft
|
|
91
|
+
|
|
92
|
+
const nextState = didPublish ? 'published' : null
|
|
93
|
+
const delay = didPublish ? 200 : 4000
|
|
94
|
+
const timer = setTimeout(() => {
|
|
95
|
+
setPublishState(nextState)
|
|
96
|
+
}, delay)
|
|
97
|
+
return () => clearTimeout(timer)
|
|
98
|
+
}, [publishState, draft])
|
|
99
|
+
|
|
100
|
+
React.useEffect(() => {
|
|
101
|
+
if (publishState === 'published') {
|
|
102
|
+
if (!updatingIntlFieldsPromiseRef.current) {
|
|
103
|
+
updatingIntlFieldsPromiseRef.current = doUpdateIntlFields()
|
|
104
|
+
.then(() => onComplete && onComplete())
|
|
105
|
+
.finally(() => (updatingIntlFieldsPromiseRef.current = null))
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}, [publishState, onComplete, doUpdateIntlFields])
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
disabled,
|
|
112
|
+
label,
|
|
113
|
+
icon: publishState === 'published' ? CheckmarkIcon : PublishIcon,
|
|
114
|
+
shortcut: disabled ? null : 'Ctrl+Alt+P',
|
|
115
|
+
onHandle,
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import {type DocumentActionsContext, type DocumentActionComponent} from 'sanity'
|
|
2
|
+
import {Ti18nConfig, Ti18nSchema} from '../types'
|
|
3
|
+
import {getBaseIdFromId} from '../utils'
|
|
4
|
+
import {createPublishAction} from './PublishWithi18nAction'
|
|
5
|
+
import {createDeleteAction} from './DeleteWithi18nAction'
|
|
6
|
+
import {createDuplicateAction} from './DuplicateWithi18nAction'
|
|
7
|
+
|
|
8
|
+
export {createPublishAction, createDeleteAction, createDuplicateAction}
|
|
9
|
+
|
|
10
|
+
export const resolveActions = (
|
|
11
|
+
prev: DocumentActionComponent[],
|
|
12
|
+
{schema, schemaType, documentId}: DocumentActionsContext,
|
|
13
|
+
pluginConfig: Ti18nConfig
|
|
14
|
+
): DocumentActionComponent[] => {
|
|
15
|
+
const type: Ti18nSchema = schema.get(schemaType) as Ti18nSchema
|
|
16
|
+
const isI18n = type && type.i18n
|
|
17
|
+
const isBase = documentId === getBaseIdFromId(documentId)
|
|
18
|
+
let actions = prev
|
|
19
|
+
|
|
20
|
+
if (isI18n) {
|
|
21
|
+
actions = actions.map((Action) => {
|
|
22
|
+
const isPublishAction = Action.action === 'publish'
|
|
23
|
+
const isDeleteAction = Action.action === 'delete'
|
|
24
|
+
|
|
25
|
+
if (isPublishAction) {
|
|
26
|
+
return createPublishAction(pluginConfig)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (isDeleteAction && isBase) {
|
|
30
|
+
return createDeleteAction(pluginConfig)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return Action
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
if (isBase) {
|
|
37
|
+
actions.push(createDuplicateAction(pluginConfig))
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return actions
|
|
42
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import {DocumentBadgeComponent} from 'sanity'
|
|
2
|
+
import {useConfig} from '../hooks'
|
|
3
|
+
import {getLanguageFromDocument} from '../utils'
|
|
4
|
+
import {Ti18nConfig} from '../types'
|
|
5
|
+
|
|
6
|
+
export function createLanguageBadge(pluginConfig: Ti18nConfig): DocumentBadgeComponent {
|
|
7
|
+
const LanguageBadge: DocumentBadgeComponent = (props) => {
|
|
8
|
+
const config = useConfig(pluginConfig, props.type)
|
|
9
|
+
const doc = props.draft || props.published
|
|
10
|
+
const lang = doc ? getLanguageFromDocument(doc, config) : null
|
|
11
|
+
if (lang) {
|
|
12
|
+
return {
|
|
13
|
+
label: lang,
|
|
14
|
+
color: 'success',
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return null
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return LanguageBadge
|
|
22
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const I18nDelimiter = '__i18n_'
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const I18nPrefix = 'i18n'
|