@sanity/document-internationalization 2.0.0-studio-v3-plugin-v2.7 → 2.0.0-studio-v3-plugin-v2.8

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.
@@ -1,12 +1,21 @@
1
- import React, {useCallback} from 'react'
2
- import {useClient} from 'sanity'
3
- import {Button, Badge, Box, Flex, Text, useToast, Spinner} from '@sanity/ui'
1
+ import {AddIcon, CheckmarkIcon, SplitVerticalIcon} from '@sanity/icons'
2
+ import {
3
+ Badge,
4
+ Box,
5
+ Button,
6
+ Flex,
7
+ Spinner,
8
+ Text,
9
+ Tooltip,
10
+ useToast,
11
+ } from '@sanity/ui'
4
12
  import {uuid} from '@sanity/uuid'
5
- import {SplitVerticalIcon, AddIcon, CheckmarkIcon} from '@sanity/icons'
13
+ import {useCallback} from 'react'
14
+ import {useClient} from 'sanity'
6
15
 
7
- import {Language, Metadata, TranslationReference} from '../types'
8
16
  import {API_VERSION, METADATA_SCHEMA_NAME} from '../constants'
9
17
  import {useOpenInNewPane} from '../hooks/useOpenInNewPane'
18
+ import {Language, Metadata, TranslationReference} from '../types'
10
19
 
11
20
  type LanguageOptionProps = {
12
21
  language: Language
@@ -23,9 +32,14 @@ type LanguageOptionProps = {
23
32
  apiVersion?: string
24
33
  }
25
34
 
26
- function createReference(key: string, ref: string, type: string) {
35
+ function createReference(
36
+ key: string,
37
+ ref: string,
38
+ type: string
39
+ ): TranslationReference {
27
40
  return {
28
41
  _key: key,
42
+ _type: 'internationalizedArrayReferenceValue',
29
43
  value: {
30
44
  _type: 'reference',
31
45
  _ref: ref,
@@ -68,9 +82,12 @@ export default function LanguageOption(props: LanguageOptionProps) {
68
82
  const documentIds = documentId.startsWith(`drafts.`)
69
83
  ? [documentId, documentId.replace(`drafts.`, ``)]
70
84
  : [documentId, `drafts.${documentId}`]
71
- const latestDocument = await client.fetch(`*[_id in $ids]|order(_updatedAt desc)[0]`, {
72
- ids: documentIds,
73
- })
85
+ const latestDocument = await client.fetch(
86
+ `*[_id in $ids]|order(_updatedAt desc)[0]`,
87
+ {
88
+ ids: documentIds,
89
+ }
90
+ )
74
91
  const newTranslationDocument = {
75
92
  ...latestDocument,
76
93
  _id: `drafts.${uuid()}`,
@@ -105,7 +122,9 @@ export default function LanguageOption(props: LanguageOptionProps) {
105
122
  _id: metadataId,
106
123
  _type: METADATA_SCHEMA_NAME,
107
124
  schemaTypes: [schemaType],
108
- translations: [newTranslationReference, sourceReference].filter(Boolean),
125
+ translations: [newTranslationReference, sourceReference].filter(
126
+ Boolean
127
+ ),
109
128
  })
110
129
  }
111
130
 
@@ -143,26 +162,57 @@ export default function LanguageOption(props: LanguageOptionProps) {
143
162
  toast,
144
163
  ])
145
164
 
165
+ let message
166
+
167
+ if (current) {
168
+ message = `Current document`
169
+ } else if (translation) {
170
+ message = `Open ${language.title} translation`
171
+ } else if (!translation) {
172
+ message = `Create new ${language.title} translation`
173
+ }
174
+
146
175
  return (
147
- <Button
148
- onClick={translation ? handleOpen : handleCreate}
149
- mode={current ? `default` : `bleed`}
150
- disabled={disabled || current}
151
- >
152
- <Flex gap={3} align="center">
153
- {disabled ? (
154
- <Spinner />
155
- ) : (
156
- <Text size={2}>
157
- {/* eslint-disable-next-line no-nested-ternary */}
158
- {translation ? <SplitVerticalIcon /> : current ? <CheckmarkIcon /> : <AddIcon />}
176
+ <Tooltip
177
+ content={
178
+ <Box padding={2}>
179
+ <Text muted size={1}>
180
+ {message}
159
181
  </Text>
160
- )}
161
- <Box flex={1}>
162
- <Text>{language.title}</Text>
163
182
  </Box>
164
- <Badge tone={disabled || current ? `default` : `primary`}>{language.id}</Badge>
165
- </Flex>
166
- </Button>
183
+ }
184
+ fallbackPlacements={['right', 'left']}
185
+ placement="top"
186
+ portal
187
+ >
188
+ <Button
189
+ onClick={translation ? handleOpen : handleCreate}
190
+ mode={current && disabled ? `default` : `bleed`}
191
+ disabled={disabled || current}
192
+ >
193
+ <Flex gap={3} align="center">
194
+ {disabled ? (
195
+ <Spinner />
196
+ ) : (
197
+ <Text size={2}>
198
+ {/* eslint-disable-next-line no-nested-ternary */}
199
+ {translation ? (
200
+ <SplitVerticalIcon />
201
+ ) : current ? (
202
+ <CheckmarkIcon />
203
+ ) : (
204
+ <AddIcon />
205
+ )}
206
+ </Text>
207
+ )}
208
+ <Box flex={1}>
209
+ <Text>{language.title}</Text>
210
+ </Box>
211
+ <Badge tone={disabled || current ? `default` : `primary`}>
212
+ {language.id}
213
+ </Badge>
214
+ </Flex>
215
+ </Button>
216
+ </Tooltip>
167
217
  )
168
218
  }
@@ -1,10 +1,10 @@
1
+ import {EditIcon} from '@sanity/icons'
2
+ import {Badge, Box, Button, Flex, Text, useToast} from '@sanity/ui'
1
3
  import React from 'react'
2
- import {ChevronRightIcon} from '@sanity/icons'
3
- import {Button, useToast} from '@sanity/ui'
4
4
  import {SanityDocument, useClient} from 'sanity'
5
5
 
6
- import {Language} from '../types'
7
6
  import {API_VERSION} from '../constants'
7
+ import {Language} from '../types'
8
8
 
9
9
  type LanguagePatchProps = {
10
10
  language: Language
@@ -63,12 +63,20 @@ export default function LanguagePatch(props: LanguagePatchProps) {
63
63
 
64
64
  return (
65
65
  <Button
66
- mode="ghost"
67
- text={language.title}
68
- icon={ChevronRightIcon}
69
- onClick={() => handleClick()}
66
+ mode="bleed"
67
+ onClick={handleClick}
70
68
  disabled={disabled}
71
69
  justify="flex-start"
72
- />
70
+ >
71
+ <Flex gap={3} align="center">
72
+ <Text size={2}>
73
+ <EditIcon />
74
+ </Text>
75
+ <Box flex={1}>
76
+ <Text>{language.title}</Text>
77
+ </Box>
78
+ <Badge>{language.id}</Badge>
79
+ </Flex>
80
+ </Button>
73
81
  )
74
82
  }
@@ -1,15 +1,23 @@
1
- import {suspend} from 'suspend-react'
2
- import React, {useCallback, useState} from 'react'
3
- import {Text, Card, useClickOutside, Stack, Popover, Button, Box} from '@sanity/ui'
4
1
  import {TranslateIcon} from '@sanity/icons'
5
- import {useClient, useEditState, useWorkspace} from 'sanity'
2
+ import {
3
+ Box,
4
+ Button,
5
+ Card,
6
+ Popover,
7
+ Stack,
8
+ Text,
9
+ useClickOutside,
10
+ } from '@sanity/ui'
11
+ import React, {useCallback, useState} from 'react'
12
+ import {useClient, useEditState} from 'sanity'
13
+ import {suspend} from 'suspend-react'
6
14
 
7
- import {SupportedLanguages} from '../types'
8
- import LanguageOption from './LanguageOption'
15
+ import {API_VERSION} from '../constants'
9
16
  import {useTranslationMetadata} from '../hooks/useLanguageMetadata'
17
+ import {SupportedLanguages} from '../types'
10
18
  import LanguageManage from './LanguageManage'
19
+ import LanguageOption from './LanguageOption'
11
20
  import LanguagePatch from './LanguagePatch'
12
- import {API_VERSION} from '../constants'
13
21
 
14
22
  type MenuButtonProps = {
15
23
  supportedLanguages: SupportedLanguages
@@ -20,7 +28,12 @@ type MenuButtonProps = {
20
28
  }
21
29
 
22
30
  export default function MenuButton(props: MenuButtonProps) {
23
- const {apiVersion = API_VERSION, schemaType, documentId, languageField} = props
31
+ const {
32
+ apiVersion = API_VERSION,
33
+ schemaType,
34
+ documentId,
35
+ languageField,
36
+ } = props
24
37
 
25
38
  const client = useClient({apiVersion})
26
39
  const supportedLanguages = Array.isArray(props.supportedLanguages)
@@ -39,12 +52,18 @@ export default function MenuButton(props: MenuButtonProps) {
39
52
  const [popover, setPopover] = useState<HTMLElement | null>(null)
40
53
  const handleClickOutside = useCallback(() => setOpen(false), [])
41
54
  useClickOutside(handleClickOutside, [button, popover])
42
- const {data: metadata, loading, error} = useTranslationMetadata(documentId, schemaType)
55
+ const {
56
+ data: metadata,
57
+ loading,
58
+ error,
59
+ } = useTranslationMetadata(documentId, schemaType)
43
60
  const {draft, published} = useEditState(documentId, schemaType)
44
61
  const source = draft || published
45
62
 
46
63
  const sourceLanguageId = source?.[languageField] as string | undefined
47
- const sourceLanguageIsValid = supportedLanguages.some((l) => l.id === sourceLanguageId)
64
+ const sourceLanguageIsValid = supportedLanguages.some(
65
+ (l) => l.id === sourceLanguageId
66
+ )
48
67
  const allLanguagesAreValid = React.useMemo(() => {
49
68
  const valid = supportedLanguages.every((l) => l.id && l.title)
50
69
  if (!valid) {
@@ -67,6 +86,38 @@ export default function MenuButton(props: MenuButtonProps) {
67
86
  <Stack padding={1} space={1}>
68
87
  {supportedLanguages.length > 0 ? (
69
88
  <>
89
+ {/* Once metadata is loaded, there may be issues */}
90
+ {loading ? null : (
91
+ <>
92
+ {/* Not all languages are valid */}
93
+ {allLanguagesAreValid ? null : (
94
+ <Card tone="caution" padding={3}>
95
+ <Text size={1}>
96
+ Not all language objects are valid. See the console.
97
+ </Text>
98
+ </Card>
99
+ )}
100
+ {/* Current document has no language field */}
101
+ {sourceLanguageId ? null : (
102
+ <Card tone="caution" padding={3}>
103
+ <Text size={1}>
104
+ Choose a language to <br />
105
+ apply to <strong>this Document</strong>
106
+ </Text>
107
+ </Card>
108
+ )}
109
+ {/* Current document has an invalid language field */}
110
+ {sourceLanguageId && !sourceLanguageIsValid ? (
111
+ <Card tone="caution" padding={3}>
112
+ <Text size={1}>
113
+ Select a supported language.
114
+ <br />
115
+ Current language value: <code>{sourceLanguageId}</code>
116
+ </Text>
117
+ </Card>
118
+ ) : null}
119
+ </>
120
+ )}
70
121
  {supportedLanguages.map((language, langIndex) =>
71
122
  !loading && sourceLanguageId && sourceLanguageIsValid ? (
72
123
  // Button to duplicate this document to a new translation
@@ -83,7 +134,9 @@ export default function MenuButton(props: MenuButtonProps) {
83
134
  metadata={metadata}
84
135
  sourceId={documentId}
85
136
  sourceLanguageId={sourceLanguageId}
86
- translation={metadata?.translations.find((t) => t._key === language.id)}
137
+ translation={metadata?.translations.find(
138
+ (t) => t._key === language.id
139
+ )}
87
140
  />
88
141
  ) : (
89
142
  // Button to set a language field on *this* document
@@ -107,36 +160,6 @@ export default function MenuButton(props: MenuButtonProps) {
107
160
  />
108
161
  )
109
162
  )}
110
- {/* Once metadata is loaded, there may be issues */}
111
- {loading ? null : (
112
- <>
113
- {/* Not all languages are valid */}
114
- {allLanguagesAreValid ? null : (
115
- <Card tone="caution" padding={3}>
116
- <Text size={1}>Not all language objects are valid. See the console.</Text>
117
- </Card>
118
- )}
119
- {/* Current document has no language field */}
120
- {sourceLanguageId ? null : (
121
- <Card tone="caution" padding={3}>
122
- <Text size={1}>
123
- Choose a language to <br />
124
- apply to <strong>this Document</strong>
125
- </Text>
126
- </Card>
127
- )}
128
- {/* Current document has an invalid language field */}
129
- {sourceLanguageId && !sourceLanguageIsValid ? (
130
- <Card tone="caution" padding={3}>
131
- <Text size={1}>
132
- Change the current language value <code>{sourceLanguageId}</code>
133
- <br />
134
- to one of the supported languages
135
- </Text>
136
- </Card>
137
- ) : null}
138
- </>
139
- )}
140
163
  </>
141
164
  ) : null}
142
165
  <LanguageManage id={metadata?._id} />
@@ -145,14 +168,24 @@ export default function MenuButton(props: MenuButtonProps) {
145
168
  </Box>
146
169
  )
147
170
 
171
+ const issueWithTranslations =
172
+ !loading && sourceLanguageId && !sourceLanguageIsValid
173
+
148
174
  return (
149
- <Popover constrainSize content={content} open={open} portal ref={setPopover} overflow="auto">
175
+ <Popover
176
+ constrainSize
177
+ content={content}
178
+ open={open}
179
+ portal
180
+ ref={setPopover}
181
+ overflow="auto"
182
+ >
150
183
  <Button
151
184
  text="Translations"
152
185
  mode="bleed"
153
186
  disabled={!source}
154
187
  tone={
155
- !source || (!loading && sourceLanguageId && sourceLanguageIsValid) ? undefined : `caution`
188
+ !source || loading || !issueWithTranslations ? undefined : `caution`
156
189
  }
157
190
  icon={TranslateIcon}
158
191
  onClick={handleClick}
@@ -0,0 +1,49 @@
1
+ import React from 'react'
2
+ import {PatchEvent, unset, useClient, useEditState} from 'sanity'
3
+ import {useDocumentPane} from 'sanity/desk'
4
+
5
+ import {API_VERSION} from '../../constants'
6
+ import {TranslationReference} from '../../types'
7
+
8
+ type ReferencePatcherProps = {
9
+ translation: TranslationReference
10
+ documentType: string
11
+ metadataId: string
12
+ }
13
+
14
+ // For every reference, check if it is published, and if so, strengthen the reference
15
+ export default function ReferencePatcher(props: ReferencePatcherProps) {
16
+ const {translation, documentType, metadataId} = props
17
+ const editState = useEditState(translation.value._ref, documentType)
18
+ const client = useClient({apiVersion: API_VERSION})
19
+ const {onChange} = useDocumentPane()
20
+
21
+ React.useEffect(() => {
22
+ if (
23
+ // We have a reference
24
+ translation.value._ref &&
25
+ // It's still weak and not-yet-strengthened
26
+ translation.value._weak &&
27
+ translation.value._strengthenOnPublish &&
28
+ // The referenced document has just been published
29
+ !editState.draft &&
30
+ editState.published &&
31
+ editState.ready
32
+ ) {
33
+ const referencePathBase = [
34
+ 'translations',
35
+ {_key: translation._key},
36
+ 'value',
37
+ ]
38
+
39
+ onChange(
40
+ new PatchEvent([
41
+ unset([...referencePathBase, '_weak']),
42
+ unset([...referencePathBase, '_strengthenOnPublish']),
43
+ ])
44
+ )
45
+ }
46
+ }, [translation, editState, metadataId, client, onChange])
47
+
48
+ return null
49
+ }
@@ -0,0 +1,34 @@
1
+ import {TranslationReference} from '../../types'
2
+ import ReferencePatcher from './ReferencePatcher'
3
+
4
+ type OptimisticallyStrengthenProps = {
5
+ translations: TranslationReference[]
6
+ metadataId: string
7
+ }
8
+
9
+ // There's no good reason to leave published references as weak
10
+ // So this component will run on every render and strengthen them
11
+ export default function OptimisticallyStrengthen(
12
+ props: OptimisticallyStrengthenProps
13
+ ) {
14
+ const {translations = [], metadataId} = props
15
+
16
+ if (!translations.length) {
17
+ return null
18
+ }
19
+
20
+ return (
21
+ <>
22
+ {translations.map((translation) =>
23
+ translation.value._strengthenOnPublish?.type ? (
24
+ <ReferencePatcher
25
+ key={translation._key}
26
+ translation={translation}
27
+ documentType={translation.value._strengthenOnPublish.type}
28
+ metadataId={metadataId}
29
+ />
30
+ ) : null
31
+ )}
32
+ </>
33
+ )
34
+ }