sanity-plugin-internationalized-array 1.7.0 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/plugin.tsx CHANGED
@@ -1,49 +1,82 @@
1
- import {definePlugin} from 'sanity'
1
+ import {definePlugin, isObjectInputProps} from 'sanity'
2
2
 
3
+ import {InternationalizedArrayProvider} from './components/InternationalizedArrayContext'
3
4
  import Preload from './components/Preload'
5
+ import {CONFIG_DEFAULT} from './constants'
6
+ import {internationalizedArrayFieldAction} from './fieldActions'
4
7
  import array from './schema/array'
5
8
  import object from './schema/object'
6
9
  import {PluginConfig} from './types'
7
10
 
8
- const CONFIG_DEFAULT: PluginConfig = {
9
- languages: [],
10
- defaultLanguages: [],
11
- fieldTypes: [],
12
- }
13
-
14
- export const internationalizedArray = definePlugin<PluginConfig>(
15
- (config = CONFIG_DEFAULT) => {
16
- const {
17
- apiVersion = '2022-11-27',
18
- select,
19
- languages,
20
- fieldTypes,
21
- defaultLanguages,
22
- } = {...CONFIG_DEFAULT, ...config}
23
-
24
- return {
25
- name: 'sanity-plugin-internationalized-array',
26
- // If `languages` is a callback then let's preload it
27
- studio: Array.isArray(languages)
28
- ? undefined
29
- : {
30
- components: {
31
- layout: (props) => (
32
- <>
33
- <Preload apiVersion={apiVersion} languages={languages} />
34
- {props.renderDefault(props)}
35
- </>
36
- ),
37
- },
11
+ export const internationalizedArray = definePlugin<PluginConfig>((config) => {
12
+ const pluginConfig = {...CONFIG_DEFAULT, ...config}
13
+ const {
14
+ apiVersion = '2022-11-27',
15
+ select,
16
+ languages,
17
+ fieldTypes,
18
+ defaultLanguages,
19
+ buttonLocations,
20
+ } = pluginConfig
21
+
22
+ return {
23
+ name: 'sanity-plugin-internationalized-array',
24
+ // Preload languages for use throughout the Studio
25
+ studio: Array.isArray(languages)
26
+ ? undefined
27
+ : {
28
+ components: {
29
+ layout: (props) => (
30
+ <>
31
+ <Preload apiVersion={apiVersion} languages={languages} />
32
+ {props.renderDefault(props)}
33
+ </>
34
+ ),
38
35
  },
39
- schema: {
40
- types: [
41
- ...fieldTypes.map((type) =>
42
- array({type, apiVersion, select, languages, defaultLanguages})
43
- ),
44
- ...fieldTypes.map((type) => object({type})),
45
- ],
36
+ },
37
+ // Optional: render "add language" buttons as field actions
38
+ document: {
39
+ unstable_fieldActions: buttonLocations.includes('unstable__fieldAction')
40
+ ? (prev) => [...prev, internationalizedArrayFieldAction]
41
+ : undefined,
42
+ },
43
+ // Wrap document editor with a language provider
44
+ form: {
45
+ components: {
46
+ input: (props) => {
47
+ const isRootInput = props.id === 'root' && isObjectInputProps(props)
48
+
49
+ if (!isRootInput) {
50
+ return props.renderDefault(props)
51
+ }
52
+
53
+ const rootFieldTypeNames = props.schemaType.fields.map(
54
+ (field) => field.type.name
55
+ )
56
+
57
+ const hasInternationalizedArray = rootFieldTypeNames.some((name) =>
58
+ name.startsWith('internationalizedArray')
59
+ )
60
+
61
+ if (!hasInternationalizedArray) {
62
+ return props.renderDefault(props)
63
+ }
64
+
65
+ return InternationalizedArrayProvider({
66
+ ...props,
67
+ internationalizedArray: pluginConfig,
68
+ })
69
+ },
46
70
  },
47
- }
71
+ },
72
+ // Register custom schema types for the outer array and the inner object
73
+ schema: {
74
+ types: [
75
+ ...fieldTypes.map((type) =>
76
+ array({type, apiVersion, select, languages, defaultLanguages})
77
+ ),
78
+ ...fieldTypes.map((type) => object({type})),
79
+ ],
80
+ },
48
81
  }
49
- )
82
+ })
@@ -16,7 +16,7 @@ type ArrayFactoryConfig = {
16
16
  }
17
17
 
18
18
  export default (config: ArrayFactoryConfig): FieldDefinition<'array'> => {
19
- const {apiVersion, select, languages, defaultLanguages, type} = config
19
+ const {apiVersion, select, languages, type} = config
20
20
  const typeName = typeof type === `string` ? type : type.name
21
21
  const arrayName = createFieldName(typeName)
22
22
  const objectName = createFieldName(typeName, true)
@@ -28,7 +28,8 @@ export default (config: ArrayFactoryConfig): FieldDefinition<'array'> => {
28
28
  components: {
29
29
  input: InternationalizedArray,
30
30
  },
31
- options: {apiVersion, select, languages, defaultLanguages},
31
+ // These options are required for validation rules – not the custom input component
32
+ options: {apiVersion, select, languages},
32
33
  // TODO: Resolve this typing issue with the inner object
33
34
  // @ts-expect-error
34
35
  of: [
package/src/types.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  import type {
2
- ArraySchemaType,
3
2
  FieldDefinition,
4
3
  Rule,
5
4
  RuleTypeConstraint,
@@ -115,13 +114,14 @@ export type PluginConfig = {
115
114
  * ```
116
115
  */
117
116
  fieldTypes: (string | RuleTypeConstraint | FieldDefinition)[]
118
- }
119
-
120
- export type ArraySchemaWithLanguageOptions = ArraySchemaType & {
121
- options: {
122
- select?: Record<string, string>
123
- languages: Language[] | LanguageCallback
124
- apiVersion: string
125
- defaultLanguages?: string[]
126
- }
117
+ /**
118
+ * Locations where the "+ EN" add language buttons are visible
119
+ * @defaultValue ['field']
120
+ * */
121
+ buttonLocations: ('field' | 'unstable__fieldAction')[]
122
+ /**
123
+ * Show or hide the "Add missing languages" button
124
+ * @defaultValue true
125
+ * */
126
+ buttonAddAll: boolean
127
127
  }
@@ -0,0 +1,14 @@
1
+ import {Language, Value} from '../types'
2
+
3
+ export function checkAllLanguagesArePresent(
4
+ languages: Language[],
5
+ value: Value[] | undefined
6
+ ): boolean {
7
+ const filteredLanguageIds = languages.map((l) => l.id)
8
+ const languagesInUseIds = value ? value.map((v) => v._key) : []
9
+
10
+ return (
11
+ languagesInUseIds.length === filteredLanguageIds.length &&
12
+ languagesInUseIds.every((l) => filteredLanguageIds.includes(l))
13
+ )
14
+ }
@@ -0,0 +1,16 @@
1
+ import {Language, Value} from '../types'
2
+
3
+ export function createAddAllTitle(
4
+ value: Value[] | undefined,
5
+ languages: Language[]
6
+ ): string {
7
+ if (value?.length) {
8
+ return `Add missing ${
9
+ languages.length - value.length === 1 ? `language` : `languages`
10
+ }`
11
+ }
12
+
13
+ return languages.length === 1
14
+ ? `Add ${languages[0].title} Field`
15
+ : `Add all languages`
16
+ }
@@ -0,0 +1,75 @@
1
+ import {FormInsertPatch, insert, Path, SchemaType} from 'sanity'
2
+
3
+ import {Language, Value} from '../types'
4
+
5
+ type AddConfig = {
6
+ // New keys to add to the field
7
+ addLanguageKeys: string[]
8
+ // Schema of the current field
9
+ schemaType: SchemaType
10
+ // All languages registered in the plugin
11
+ languages: Language[]
12
+ // Languages that are currently visible
13
+ filteredLanguages: Language[]
14
+ // Current value of the internationalizedArray field
15
+ value?: Value[]
16
+ // Path to this item
17
+ path?: Path
18
+ }
19
+
20
+ export function createAddLanguagePatches(config: AddConfig): FormInsertPatch[] {
21
+ const {
22
+ addLanguageKeys,
23
+ schemaType,
24
+ languages,
25
+ filteredLanguages,
26
+ value,
27
+ path = [],
28
+ } = config
29
+
30
+ const itemBase = {_type: `${schemaType.name}Value`}
31
+
32
+ // Create new items
33
+ const newItems =
34
+ Array.isArray(addLanguageKeys) && addLanguageKeys.length > 0
35
+ ? // Just one for this language
36
+ addLanguageKeys.map((id) => ({...itemBase, _key: id}))
37
+ : // Or one for every missing language
38
+ filteredLanguages
39
+ .filter((language) =>
40
+ value?.length ? !value.find((v) => v._key === language.id) : true
41
+ )
42
+ .map((language) => ({...itemBase, _key: language.id}))
43
+
44
+ // Insert new items in the correct order
45
+ const languagesInUse = value?.length ? value.map((v) => v) : []
46
+
47
+ const insertions = newItems.map((item) => {
48
+ // What's the original index of this language?
49
+ const languageIndex = languages.findIndex((l) => item._key === l.id)
50
+
51
+ // What languages are there beyond that index?
52
+ const remainingLanguages = languages.slice(languageIndex + 1)
53
+
54
+ // So what is the index in the current value array of the next language in the language array?
55
+ const nextLanguageIndex = languagesInUse.findIndex((l) =>
56
+ // eslint-disable-next-line max-nested-callbacks
57
+ remainingLanguages.find((r) => r.id === l._key)
58
+ )
59
+
60
+ // Keep local state up to date incase multiple insertions are being made
61
+ if (nextLanguageIndex < 0) {
62
+ languagesInUse.push(item)
63
+ } else {
64
+ languagesInUse.splice(nextLanguageIndex, 0, item)
65
+ }
66
+
67
+ return nextLanguageIndex < 0
68
+ ? // No next language (-1), add to end of array
69
+ insert([item], 'after', [...path, nextLanguageIndex])
70
+ : // Next language found, insert before that
71
+ insert([item], 'before', [...path, nextLanguageIndex])
72
+ })
73
+
74
+ return insertions
75
+ }
@@ -1,9 +0,0 @@
1
- import React from 'react'
2
-
3
- import {Language} from '../types'
4
-
5
- export const LanguageContext = React.createContext<{languages: Language[]}>({
6
- languages: [],
7
- })
8
-
9
- export const LanguageProvider = LanguageContext.Provider