@sanity/language-filter 2.31.2-performance-opts.7 → 3.0.0-v3-studio.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/sanity.json CHANGED
@@ -1,16 +1,8 @@
1
1
  {
2
- "paths": {
3
- "source": "./src",
4
- "compiled": "./lib"
5
- },
6
2
  "parts": [
7
3
  {
8
- "implements": "part:@sanity/desk-tool/filter-fields-fn",
9
- "path": "filter-fields"
10
- },
11
- {
12
- "implements": "part:@sanity/desk-tool/language-select-component",
13
- "path": "SelectLanguageProvider"
4
+ "implements": "part:@sanity/base/sanity-root",
5
+ "path": "./v2-incompatible.js"
14
6
  }
15
7
  ]
16
8
  }
@@ -0,0 +1,30 @@
1
+ import React, {createContext, PropsWithChildren, useContext, useMemo} from 'react'
2
+ import {LanguageFilterConfig} from './types'
3
+
4
+ export interface LanguageFilterContextValue {
5
+ // eslint-disable-next-line react/require-default-props
6
+ options: LanguageFilterConfig
7
+ // eslint-disable-next-line react/require-default-props
8
+ enabled: boolean
9
+ }
10
+
11
+ const LanguageFilterContext = createContext<LanguageFilterContextValue | undefined>(undefined)
12
+
13
+ export function LanguageFilterProvider({
14
+ options,
15
+ enabled,
16
+ children,
17
+ }: PropsWithChildren<
18
+ Omit<LanguageFilterContextValue, 'selectedLanguageIds' | 'setSelectedLanguageIds'>
19
+ >) {
20
+ const value = useMemo(() => ({options, enabled}), [options, enabled])
21
+ return <LanguageFilterContext.Provider value={value}>{children}</LanguageFilterContext.Provider>
22
+ }
23
+
24
+ export function useLanguageFilterContext() {
25
+ const value = useContext(LanguageFilterContext)
26
+ if (!value) {
27
+ throw new Error('LanguageFilterContext is missing')
28
+ }
29
+ return value
30
+ }
@@ -0,0 +1,147 @@
1
+ import {Box, Button, Card, Checkbox, Flex, Popover, Stack, Text, useClickOutside} from '@sanity/ui'
2
+ import React, {FormEvent, useCallback, useState} from 'react'
3
+ import {usePaneLanguages} from './usePaneLanguages'
4
+ import {LanguageFilterConfig} from './types'
5
+
6
+ export interface LanguageFilterMenuButtonProps {
7
+ options: LanguageFilterConfig
8
+ onSelectedIdsChange: (ids: string[]) => void
9
+ }
10
+
11
+ export function LanguageFilterMenuButton(props: LanguageFilterMenuButtonProps) {
12
+ const {options, onSelectedIdsChange} = props
13
+
14
+ const defaultLanguages = options.supportedLanguages.filter((l) =>
15
+ options.defaultLanguages?.includes(l.id)
16
+ )
17
+
18
+ const languageOptions = options.supportedLanguages.filter(
19
+ (l) => !options.defaultLanguages?.includes(l.id)
20
+ )
21
+ const [open, setOpen] = useState(false)
22
+ const {activeLanguages, allSelected, selectAll, selectNone, toggleLanguage} = usePaneLanguages({
23
+ options,
24
+ onSelectedIdsChange,
25
+ })
26
+ const [button, setButton] = useState<HTMLElement | null>(null)
27
+ const [popover, setPopover] = useState<HTMLElement | null>(null)
28
+
29
+ const handleToggleAll = useCallback(
30
+ (event: FormEvent<HTMLInputElement>) => {
31
+ const checked = event.currentTarget.checked
32
+
33
+ if (checked) {
34
+ selectAll()
35
+ } else {
36
+ selectNone()
37
+ }
38
+ },
39
+ [selectAll, selectNone]
40
+ )
41
+
42
+ const handleClick = useCallback(() => setOpen((o) => !o), [])
43
+
44
+ const handleClickOutside = useCallback(() => setOpen(false), [])
45
+
46
+ useClickOutside(handleClickOutside, [button, popover])
47
+
48
+ const content = (
49
+ <Box overflow="auto" padding={1}>
50
+ {defaultLanguages.length > 0 && (
51
+ <Card radius={2}>
52
+ <Stack padding={2} space={3}>
53
+ <Box paddingBottom={2}>
54
+ <Text size={1} weight="semibold">
55
+ Default language{defaultLanguages.length > 1 && <>s</>}
56
+ </Text>
57
+ </Box>
58
+
59
+ {defaultLanguages.map((l) => (
60
+ <Text key={l.id}>{l.title}</Text>
61
+ ))}
62
+ </Stack>
63
+ </Card>
64
+ )}
65
+
66
+ <Stack marginTop={3} padding={2} space={2}>
67
+ <Box paddingBottom={2}>
68
+ <Text size={1} weight="semibold">
69
+ Show translations
70
+ </Text>
71
+ </Box>
72
+
73
+ <Card as="label">
74
+ <Flex align="center" gap={2}>
75
+ <Checkbox checked={allSelected} name="_allSelected" onChange={handleToggleAll} />
76
+ <Box flex={1}>
77
+ <Text muted={!allSelected} weight="semibold">
78
+ All translations
79
+ </Text>
80
+ </Box>
81
+ </Flex>
82
+ </Card>
83
+
84
+ {languageOptions.map((lang) => (
85
+ <LanguageFilterOption
86
+ id={lang.id}
87
+ key={lang.id}
88
+ onToggle={toggleLanguage}
89
+ selected={activeLanguages.includes(lang.id)}
90
+ title={lang.title}
91
+ />
92
+ ))}
93
+ </Stack>
94
+ </Box>
95
+ )
96
+
97
+ const langCount = options.supportedLanguages.length
98
+ return (
99
+ <Popover content={content} open={open} portal ref={setPopover}>
100
+ <Button
101
+ text={
102
+ <Flex gap={1}>
103
+ <Box>Filter languages:</Box>
104
+ <Flex gap={1} justify="space-around">
105
+ <Flex
106
+ style={{width: `${Math.floor(Math.log10(langCount) + 1)}ch`}}
107
+ justify="flex-end"
108
+ >
109
+ {activeLanguages.length}
110
+ </Flex>
111
+ <Box>/</Box>
112
+ <Box>{langCount}</Box>
113
+ </Flex>
114
+ </Flex>
115
+ }
116
+ mode="bleed"
117
+ onClick={handleClick}
118
+ ref={setButton}
119
+ selected={open}
120
+ />
121
+ </Popover>
122
+ )
123
+ }
124
+
125
+ function LanguageFilterOption(props: {
126
+ id: string
127
+ onToggle: (id: string) => void
128
+ selected: boolean
129
+ title: string
130
+ }) {
131
+ const {id, onToggle, selected, title} = props
132
+
133
+ const handleChange = useCallback(() => {
134
+ onToggle(id)
135
+ }, [id, onToggle])
136
+
137
+ return (
138
+ <Card as="label">
139
+ <Flex align="center" gap={2}>
140
+ <Checkbox checked={selected} name={`language-${id}`} onChange={handleChange} />
141
+ <Box flex={1}>
142
+ <Text muted={!selected}>{title}</Text>
143
+ </Box>
144
+ </Flex>
145
+ </Card>
146
+ )
147
+ }
@@ -0,0 +1,72 @@
1
+ import React, {useEffect, useMemo} from 'react'
2
+ import {ObjectInputProps, ObjectMember, RenderInputCallback} from 'sanity'
3
+ import {LanguageFilterConfig} from './types'
4
+ import {defaultFilterField} from './filterField'
5
+ import {useLanguageFilterContext} from './LanguageFilterContext'
6
+ import {useSelectedLanguageIds} from './useSelectedLanguageIds'
7
+
8
+ export type LanguageFilterObjectInputProps = {
9
+ options: LanguageFilterConfig
10
+ next: RenderInputCallback
11
+ /**
12
+ * We need a way to communicate state changes between the pane menu and input components.
13
+ * LanguageFilter button lives outside the input-render tree, so Context is out.
14
+ * This is a workaround for that.
15
+ */
16
+ subscribeSelectedIds: (callback: (ids: string[]) => void) => () => void
17
+ } & ObjectInputProps
18
+
19
+ export function LanguageFilterObjectInput(
20
+ props: ObjectInputProps & {
21
+ next: RenderInputCallback
22
+ subscribeSelectedIds: (callback: (ids: string[]) => void) => () => void
23
+ }
24
+ ) {
25
+ const {options, enabled} = useLanguageFilterContext()
26
+ const {next, subscribeSelectedIds, ...restProps} = props
27
+ if (!enabled || !options) {
28
+ return <>{next(restProps)}</>
29
+ }
30
+ return (
31
+ <FilteredObjectInput
32
+ {...restProps}
33
+ next={next}
34
+ options={options}
35
+ subscribeSelectedIds={subscribeSelectedIds}
36
+ />
37
+ )
38
+ }
39
+
40
+ function FilteredObjectInput(props: LanguageFilterObjectInputProps) {
41
+ const {
42
+ members: membersProp,
43
+ options,
44
+ schemaType,
45
+ next,
46
+ subscribeSelectedIds,
47
+ ...restProps
48
+ } = props
49
+ const [selectedIds, setSelectedIds] = useSelectedLanguageIds(options)
50
+
51
+ useEffect(() => {
52
+ const unsubscribe = subscribeSelectedIds(setSelectedIds)
53
+ return () => unsubscribe()
54
+ }, [subscribeSelectedIds, setSelectedIds])
55
+
56
+ const activeLanguages = useMemo(
57
+ () => [...(options.defaultLanguages ?? []), ...selectedIds],
58
+ [options.defaultLanguages, selectedIds]
59
+ )
60
+
61
+ const filterField = options.filterField ?? defaultFilterField
62
+
63
+ const members: ObjectMember[] = useMemo(() => {
64
+ return membersProp.filter(
65
+ (member) =>
66
+ (member.kind === 'field' && filterField(schemaType, member, activeLanguages)) ||
67
+ (member.kind === 'fieldSet' && filterField(schemaType, member.fieldSet, activeLanguages))
68
+ )
69
+ }, [schemaType, membersProp, filterField, activeLanguages])
70
+
71
+ return <>{next({...restProps, members, schemaType})}</>
72
+ }
@@ -0,0 +1,91 @@
1
+ import {defaultFilterField, isLanguageFilterEnabled} from './filterField'
2
+ import {FieldMember, ObjectSchemaType} from 'sanity'
3
+
4
+ describe('filterField', () => {
5
+ describe('isLanguageFilterEnabled', () => {
6
+ const docType: ObjectSchemaType = {
7
+ name: 'some-doc',
8
+ jsonType: 'object',
9
+ fields: [],
10
+ type: {
11
+ name: 'document',
12
+ jsonType: 'object',
13
+ fields: [],
14
+ },
15
+ }
16
+ it('should be enabled when documentTypes is missing', () => {
17
+ const enabled = isLanguageFilterEnabled(docType, {supportedLanguages: []})
18
+ expect(enabled).toBeTruthy()
19
+ })
20
+
21
+ it('should be disabled when documentTypes is missing and options.languageFilter: false', () => {
22
+ const enabled = isLanguageFilterEnabled(
23
+ {...docType, options: {languageFilter: false}},
24
+ {supportedLanguages: []}
25
+ )
26
+ expect(enabled).toBeFalsy()
27
+ })
28
+
29
+ it('should be enabled when documentTypes is contains doc-type name', () => {
30
+ const enabled = isLanguageFilterEnabled(
31
+ {...docType, options: {languageFilter: false}},
32
+ {supportedLanguages: [], documentTypes: [docType.name]}
33
+ )
34
+ expect(enabled).toBeTruthy()
35
+ })
36
+
37
+ it('should be enabled when documentTypes does not contain doc-type name, but options.languageFilter: true', () => {
38
+ const enabled = isLanguageFilterEnabled(
39
+ {...docType, options: {languageFilter: true}},
40
+ {supportedLanguages: [], documentTypes: []}
41
+ )
42
+ expect(enabled).toBeTruthy()
43
+ })
44
+ })
45
+
46
+ describe('defaultFilterField', () => {
47
+ const localePrefixedObject: ObjectSchemaType = {
48
+ name: 'locale_parent',
49
+ jsonType: 'object',
50
+ fields: [],
51
+ }
52
+ const member: FieldMember = {
53
+ name: 'nb',
54
+ key: 'nb',
55
+ collapsed: undefined,
56
+ collapsible: undefined,
57
+ kind: 'field',
58
+ open: true,
59
+ index: 0,
60
+ field: {
61
+ schemaType: {name: 'string', jsonType: 'string'},
62
+ level: 1,
63
+ id: 'nb',
64
+ path: [],
65
+ validation: [],
66
+ presence: [],
67
+ changed: false,
68
+ value: undefined,
69
+ },
70
+ }
71
+
72
+ it('should filter -> true for nb field inside local-prefixed object', () => {
73
+ const result = defaultFilterField(localePrefixedObject, member, ['nb'])
74
+ expect(result).toBeTruthy()
75
+ })
76
+
77
+ it('should filter -> false for unselected field inside local-prefixed object', () => {
78
+ const result = defaultFilterField(localePrefixedObject, member, ['other'])
79
+ expect(result).toBeFalsy()
80
+ })
81
+
82
+ it('should filter -> true for nb field inside non-prefixed object', () => {
83
+ const result = defaultFilterField(
84
+ {...localePrefixedObject, name: 'not-start-with-locale-field'},
85
+ member,
86
+ ['nb']
87
+ )
88
+ expect(result).toBeTruthy()
89
+ })
90
+ })
91
+ })
@@ -0,0 +1,34 @@
1
+ import type {SchemaType} from 'sanity'
2
+ import {FilterFieldFunction, LanguageFilterConfig, LanguageFilterSchema} from './types'
3
+
4
+ export const defaultFilterField: FilterFieldFunction = (
5
+ enclosingType,
6
+ field,
7
+ selectedLanguageIds
8
+ ) => !enclosingType.name.startsWith('locale') || selectedLanguageIds.includes(field.name)
9
+
10
+ export function isLanguageFilterEnabled(
11
+ schemaType: SchemaType | undefined,
12
+ options: LanguageFilterConfig
13
+ ): boolean {
14
+ const schemaFilter =
15
+ isDocument(schemaType) && (schemaType as LanguageFilterSchema)?.options?.languageFilter
16
+ const defaultEnabled = !options.documentTypes
17
+
18
+ return !!(
19
+ (defaultEnabled && schemaFilter !== false) ||
20
+ (!defaultEnabled && schemaFilter) ||
21
+ (schemaType && options.documentTypes?.includes(schemaType.name))
22
+ )
23
+ }
24
+
25
+ function isDocument(schemaType?: SchemaType) {
26
+ return schemaType?.jsonType === 'object' && getRootType(schemaType).name === 'document'
27
+ }
28
+
29
+ function getRootType(schema: SchemaType): SchemaType {
30
+ if (schema.type) {
31
+ return getRootType(schema.type)
32
+ }
33
+ return schema
34
+ }
package/src/index.ts ADDED
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Plugin function
3
+ */
4
+ export {languageFilter} from './plugin'
5
+
6
+ export {defaultFilterField, isLanguageFilterEnabled} from './filterField'
7
+
8
+ export type {
9
+ LanguageFilterConfig,
10
+ LanguageFilterSchema,
11
+ LanguageFilterOptions,
12
+ FilterFieldFunction,
13
+ Language,
14
+ } from './types'
@@ -0,0 +1,32 @@
1
+ export type LanguageSubscription = (ids: string[]) => void
2
+ export type Unsubscribe = () => void
3
+ export type LanguageSubscribe = (subscription: LanguageSubscription) => Unsubscribe
4
+
5
+ export interface SelectedLanguageIdsBus {
6
+ onSelectedIdsChange: (ids: string[]) => void
7
+ subscribeSelectedIds: LanguageSubscribe
8
+ }
9
+
10
+ /**
11
+ * We need a way to communicate state changes between the pane menu and input components.
12
+ * LanguageFilter button lives outside the input-render tree, so Context is out.
13
+ * This is a workaround for that.
14
+ */
15
+ export function createSelectedLanguageIdsBus(): SelectedLanguageIdsBus {
16
+ const subs: LanguageSubscription[] = []
17
+
18
+ const onSelectedIdsChange = (ids: string[]) => {
19
+ subs.forEach((s) => s(ids))
20
+ }
21
+ const subscribeSelectedIds = (subscription: LanguageSubscription) => {
22
+ subs.push(subscription)
23
+ return () => {
24
+ subs.splice(subs.indexOf(subscription), 1)
25
+ }
26
+ }
27
+
28
+ return {
29
+ onSelectedIdsChange,
30
+ subscribeSelectedIds,
31
+ }
32
+ }
package/src/plugin.tsx ADDED
@@ -0,0 +1,86 @@
1
+ import React from 'react'
2
+ import {_DocumentLanguageFilterComponent, createPlugin, ObjectInputProps} from 'sanity'
3
+ import {LanguageFilterObjectInput} from './LanguageFilterObjectInput'
4
+ import {LanguageFilterMenuButton} from './LanguageFilterMenuButton'
5
+ import {LanguageFilterConfig} from './types'
6
+ import {isLanguageFilterEnabled} from './filterField'
7
+ import {LanguageFilterProvider} from './LanguageFilterContext'
8
+ import {createSelectedLanguageIdsBus} from './languageSubscription'
9
+
10
+ /**
11
+ * ## Usage in sanity.config.ts (or .js)
12
+ *
13
+ * ```
14
+ * import {createConfig} from 'sanity'
15
+ * import {languageFilter} from '@sanity/language-filter'
16
+ *
17
+ * export const createConfig({
18
+ * /...
19
+ * plugins: [
20
+ * languageFilter({
21
+ * supportedLanguages: [
22
+ * {id: 'nb', title: 'Norwegian (Bokmål)'},
23
+ * {id: 'nn', title: 'Norwegian (Nynorsk)'},
24
+ * {id: 'en', title: 'English'},
25
+ * {id: 'es', title: 'Spanish'},
26
+ * {id: 'arb', title: 'Arabic'},
27
+ * {id: 'pt', title: 'Portuguese'},
28
+ * //...
29
+ * ],
30
+ * // Select Norwegian (Bokmål) by default
31
+ * defaultLanguages: ['nb'],
32
+ * // Only show language filter for document type `page` (schemaType.name)
33
+ * // Can also enable via document-options: options.languageFilter: true
34
+ * documentTypes: ['page'],
35
+ * // default filter function shown
36
+ * filterField: (enclosingType, field, selectedLanguageIds) =>
37
+ * !enclosingType.name.startsWith('locale') || selectedLanguageIds.includes(field.name),
38
+ * })
39
+ * ]
40
+ * })
41
+ * ```
42
+ */
43
+ export const languageFilter = createPlugin<LanguageFilterConfig>((options) => {
44
+ const {onSelectedIdsChange, subscribeSelectedIds} = createSelectedLanguageIdsBus()
45
+
46
+ const RenderLanguageFilter: _DocumentLanguageFilterComponent = () => {
47
+ return <LanguageFilterMenuButton options={options} onSelectedIdsChange={onSelectedIdsChange} />
48
+ }
49
+
50
+ return {
51
+ name: '@sanity/language-filter',
52
+ document: {
53
+ unstable_languageFilter: (prev, {schemaType, schema}) => {
54
+ if (isLanguageFilterEnabled(schema.get(schemaType), options)) {
55
+ return [...prev, RenderLanguageFilter]
56
+ }
57
+ return prev
58
+ },
59
+ },
60
+
61
+ form: {
62
+ renderInput(props, next) {
63
+ const enabled = isLanguageFilterEnabled(props.schemaType, options)
64
+ // will only be considered enabled for document, so this is only done once
65
+ if (enabled) {
66
+ return (
67
+ <LanguageFilterProvider enabled={enabled} options={options}>
68
+ {next(props)}
69
+ </LanguageFilterProvider>
70
+ )
71
+ }
72
+ if (props.schemaType.jsonType === 'object') {
73
+ return (
74
+ <LanguageFilterObjectInput
75
+ {...(props as ObjectInputProps)}
76
+ next={next}
77
+ subscribeSelectedIds={subscribeSelectedIds}
78
+ />
79
+ )
80
+ }
81
+
82
+ return undefined
83
+ },
84
+ },
85
+ }
86
+ })
package/src/types.ts ADDED
@@ -0,0 +1,27 @@
1
+ import {FieldMember, FieldsetState, ObjectSchemaType} from 'sanity'
2
+
3
+ export interface LanguageFilterOptions {
4
+ languageFilter?: boolean
5
+ }
6
+
7
+ export interface LanguageFilterSchema extends ObjectSchemaType {
8
+ options?: LanguageFilterOptions
9
+ }
10
+
11
+ export interface Language {
12
+ id: string
13
+ title: string
14
+ }
15
+
16
+ export type FilterFieldFunction = (
17
+ enclosingType: ObjectSchemaType,
18
+ field: FieldMember | FieldsetState,
19
+ selectedLanguageIds: string[]
20
+ ) => boolean
21
+
22
+ export interface LanguageFilterConfig {
23
+ supportedLanguages: Language[]
24
+ defaultLanguages?: string[]
25
+ documentTypes?: string[]
26
+ filterField?: FilterFieldFunction
27
+ }
@@ -0,0 +1,78 @@
1
+ import {useCallback, useMemo} from 'react'
2
+ import {LanguageFilterConfig} from './types'
3
+ import {
4
+ getSelectableLanguages,
5
+ persistLanguageIds,
6
+ useSelectedLanguageIds,
7
+ } from './useSelectedLanguageIds'
8
+
9
+ export interface UsePaneLanguagesParams {
10
+ options: LanguageFilterConfig
11
+ /**
12
+ * We need a way to communicate state changes between the pane menu and input components.
13
+ * LanguageFilter button lives outside the input-render tree, so Context is out.
14
+ * This is a workaround for that.
15
+ */
16
+ onSelectedIdsChange: (ids: string[]) => void
17
+ }
18
+
19
+ export function usePaneLanguages(props: UsePaneLanguagesParams): {
20
+ activeLanguages: string[]
21
+ allSelected: boolean
22
+ selectAll: () => void
23
+ selectNone: () => void
24
+ toggleLanguage: (languageId: string) => void
25
+ } {
26
+ const {options, onSelectedIdsChange} = props
27
+ const {defaultLanguages} = options
28
+
29
+ const [selectedIds, setSelectedIds] = useSelectedLanguageIds(options)
30
+
31
+ const selectableLanguages = useMemo(() => getSelectableLanguages(options), [options])
32
+
33
+ const updateSelectedIds = useCallback(
34
+ (ids: string[]) => {
35
+ setSelectedIds(ids)
36
+ persistLanguageIds(ids)
37
+ onSelectedIdsChange(ids)
38
+ },
39
+ [onSelectedIdsChange, setSelectedIds]
40
+ )
41
+
42
+ const selectAll = useCallback(
43
+ () => updateSelectedIds(selectableLanguages.map((l) => l.id)),
44
+ [updateSelectedIds, selectableLanguages]
45
+ )
46
+
47
+ const selectNone = useCallback(() => {
48
+ updateSelectedIds([])
49
+ }, [updateSelectedIds])
50
+
51
+ const toggleLanguage = useCallback(
52
+ (languageId: string) => {
53
+ let lang = selectedIds
54
+
55
+ if (lang.includes(languageId)) {
56
+ lang = lang.filter((l) => l !== languageId)
57
+ } else {
58
+ lang = [...lang, languageId]
59
+ }
60
+
61
+ updateSelectedIds(lang)
62
+ },
63
+ [updateSelectedIds, selectedIds]
64
+ )
65
+
66
+ const activeLanguages = useMemo(
67
+ () => [...(defaultLanguages ?? []), ...selectedIds],
68
+ [defaultLanguages, selectedIds]
69
+ )
70
+
71
+ return {
72
+ activeLanguages,
73
+ allSelected: selectedIds.length === selectableLanguages.length,
74
+ selectAll,
75
+ selectNone,
76
+ toggleLanguage,
77
+ }
78
+ }
@@ -0,0 +1,40 @@
1
+ import {Language, LanguageFilterConfig} from './types'
2
+ import {useState} from 'react'
3
+ const storageKey = '@sanity/plugin/language-filter/selected-languages'
4
+
5
+ export function getPersistedLanguageIds(options: LanguageFilterConfig): string[] {
6
+ const selectableLangs = getSelectableLanguages(options).map((l) => l.id)
7
+
8
+ let selected: string[] = selectableLangs
9
+ try {
10
+ const persistedValue = window.localStorage.getItem(storageKey)
11
+ if (persistedValue) {
12
+ selected = JSON.parse(persistedValue)
13
+ }
14
+ } catch (err) {} // eslint-disable-line no-empty
15
+
16
+ // constrain persisted/selected languages to the ones currently supported
17
+ selected = intersection(selected, selectableLangs)
18
+ return selected
19
+ }
20
+
21
+ export function persistLanguageIds(languageIds: string[]): void {
22
+ window.localStorage.setItem(storageKey, JSON.stringify(languageIds))
23
+ }
24
+
25
+ function intersection(array1: string[], array2: string[]) {
26
+ return array1.filter((value) => array2.includes(value))
27
+ }
28
+
29
+ export function getSelectableLanguages({
30
+ supportedLanguages,
31
+ defaultLanguages,
32
+ }: LanguageFilterConfig): Language[] {
33
+ return supportedLanguages.filter((lang) => !defaultLanguages?.includes(lang.id))
34
+ }
35
+
36
+ export function useSelectedLanguageIds(
37
+ options: LanguageFilterConfig
38
+ ): [string[], (ids: string[]) => void] {
39
+ return useState(() => getPersistedLanguageIds(options))
40
+ }
@@ -0,0 +1,11 @@
1
+ const {showIncompatiblePluginDialog} = require('@sanity/incompatible-plugin')
2
+ const {name, version, sanityExchangeUrl} = require('./package.json')
3
+
4
+ export default showIncompatiblePluginDialog({
5
+ name: name,
6
+ versions: {
7
+ v3: version,
8
+ v2: undefined,
9
+ },
10
+ sanityExchangeUrl,
11
+ })