@sanity/language-filter 1.0.0-firstpass-v3-impl.1 → 1.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/README.md +4 -0
- package/lib/cjs/index.js +217 -124
- package/lib/cjs/index.js.map +1 -1
- package/lib/esm/index.js +219 -126
- package/lib/esm/index.js.map +1 -1
- package/lib/types/index.d.ts +28 -10
- package/lib/types/index.d.ts.map +1 -1
- package/package.json +9 -8
- package/src/LanguageFilterContext.tsx +30 -0
- package/src/LanguageFilterMenuButton.tsx +31 -12
- package/src/LanguageFilterObjectInput.tsx +59 -20
- package/src/languageSubscription.ts +32 -0
- package/src/plugin.tsx +23 -4
- package/src/usePaneLanguages.ts +42 -45
- package/src/useSelectedLanguageIds.ts +40 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sanity/language-filter",
|
|
3
|
-
"version": "1.0.0-
|
|
3
|
+
"version": "1.0.0-v3-studio.1",
|
|
4
4
|
"description": "A Sanity plugin that supports filtering localized fields by language",
|
|
5
5
|
"author": "Sanity.io <hello@sanity.io>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -28,7 +28,8 @@
|
|
|
28
28
|
"watch": "parcel watch",
|
|
29
29
|
"test": "jest",
|
|
30
30
|
"link-watch": "plugin-kit link-watch",
|
|
31
|
-
"prepublishOnly": "npm run build"
|
|
31
|
+
"prepublishOnly": "npm run build",
|
|
32
|
+
"prepare": "husky install"
|
|
32
33
|
},
|
|
33
34
|
"repository": {
|
|
34
35
|
"type": "git",
|
|
@@ -40,14 +41,14 @@
|
|
|
40
41
|
"dependencies": {
|
|
41
42
|
"@sanity/icons": "^1.3.4",
|
|
42
43
|
"@sanity/incompatible-plugin": "^1.0.0",
|
|
43
|
-
"@sanity/ui": "^0.
|
|
44
|
+
"@sanity/ui": "^0.38.0",
|
|
44
45
|
"@sanity/util": "3.0.0-dev-preview.15"
|
|
45
46
|
},
|
|
46
47
|
"devDependencies": {
|
|
47
48
|
"@babel/preset-env": "^7.18.10",
|
|
48
49
|
"@babel/preset-react": "^7.18.6",
|
|
49
|
-
"@commitlint/cli": "^17.
|
|
50
|
-
"@commitlint/config-conventional": "^17.0
|
|
50
|
+
"@commitlint/cli": "^17.1.2",
|
|
51
|
+
"@commitlint/config-conventional": "^17.1.0",
|
|
51
52
|
"@parcel/packager-ts": "^2.7.0",
|
|
52
53
|
"@parcel/transformer-typescript-types": "^2.7.0",
|
|
53
54
|
"@sanity/plugin-kit": "^1.0.1",
|
|
@@ -55,11 +56,11 @@
|
|
|
55
56
|
"@types/jest": "^28.1.8",
|
|
56
57
|
"@typescript-eslint/eslint-plugin": "^5.35.1",
|
|
57
58
|
"@typescript-eslint/parser": "^5.35.1",
|
|
58
|
-
"eslint": "
|
|
59
|
+
"eslint": "8.22.0",
|
|
59
60
|
"eslint-config-prettier": "^8.5.0",
|
|
60
61
|
"eslint-config-sanity": "^6.0.0",
|
|
61
62
|
"eslint-plugin-prettier": "^4.2.1",
|
|
62
|
-
"eslint-plugin-react": "^7.31.
|
|
63
|
+
"eslint-plugin-react": "^7.31.1",
|
|
63
64
|
"eslint-plugin-react-hooks": "^4.6.0",
|
|
64
65
|
"husky": "^8.0.1",
|
|
65
66
|
"jest": "^28.1.3",
|
|
@@ -70,7 +71,7 @@
|
|
|
70
71
|
"rimraf": "^3.0.2",
|
|
71
72
|
"sanity": "3.0.0-dev-preview.15",
|
|
72
73
|
"ts-jest": "^28.0.8",
|
|
73
|
-
"typescript": "
|
|
74
|
+
"typescript": "4.7.4"
|
|
74
75
|
},
|
|
75
76
|
"peerDependencies": {
|
|
76
77
|
"react": "^17.0.0 || ^18.0.0",
|
|
@@ -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
|
+
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import {TranslateIcon} from '@sanity/icons'
|
|
2
1
|
import {Box, Button, Card, Checkbox, Flex, Popover, Stack, Text, useClickOutside} from '@sanity/ui'
|
|
3
2
|
import React, {FormEvent, useCallback, useState} from 'react'
|
|
4
3
|
import {usePaneLanguages} from './usePaneLanguages'
|
|
@@ -6,19 +5,23 @@ import {LanguageFilterConfig} from './types'
|
|
|
6
5
|
|
|
7
6
|
export interface LanguageFilterMenuButtonProps {
|
|
8
7
|
options: LanguageFilterConfig
|
|
8
|
+
onSelectedIdsChange: (ids: string[]) => void
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
export function LanguageFilterMenuButton(props: LanguageFilterMenuButtonProps) {
|
|
12
|
-
const {options} = props
|
|
12
|
+
const {options, onSelectedIdsChange} = props
|
|
13
|
+
|
|
13
14
|
const defaultLanguages = options.supportedLanguages.filter((l) =>
|
|
14
15
|
options.defaultLanguages?.includes(l.id)
|
|
15
16
|
)
|
|
17
|
+
|
|
16
18
|
const languageOptions = options.supportedLanguages.filter(
|
|
17
19
|
(l) => !options.defaultLanguages?.includes(l.id)
|
|
18
20
|
)
|
|
19
21
|
const [open, setOpen] = useState(false)
|
|
20
|
-
const {
|
|
22
|
+
const {activeLanguages, allSelected, selectAll, selectNone, toggleLanguage} = usePaneLanguages({
|
|
21
23
|
options,
|
|
24
|
+
onSelectedIdsChange,
|
|
22
25
|
})
|
|
23
26
|
const [button, setButton] = useState<HTMLElement | null>(null)
|
|
24
27
|
const [popover, setPopover] = useState<HTMLElement | null>(null)
|
|
@@ -45,11 +48,13 @@ export function LanguageFilterMenuButton(props: LanguageFilterMenuButtonProps) {
|
|
|
45
48
|
const content = (
|
|
46
49
|
<Box overflow="auto" padding={1}>
|
|
47
50
|
{defaultLanguages.length > 0 && (
|
|
48
|
-
<Card radius={2}
|
|
51
|
+
<Card radius={2}>
|
|
49
52
|
<Stack padding={2} space={3}>
|
|
50
|
-
<
|
|
51
|
-
|
|
52
|
-
|
|
53
|
+
<Box paddingBottom={2}>
|
|
54
|
+
<Text size={1} weight="semibold">
|
|
55
|
+
Default language{defaultLanguages.length > 1 && <>s</>}
|
|
56
|
+
</Text>
|
|
57
|
+
</Box>
|
|
53
58
|
|
|
54
59
|
{defaultLanguages.map((l) => (
|
|
55
60
|
<Text key={l.id}>{l.title}</Text>
|
|
@@ -59,7 +64,7 @@ export function LanguageFilterMenuButton(props: LanguageFilterMenuButtonProps) {
|
|
|
59
64
|
)}
|
|
60
65
|
|
|
61
66
|
<Stack marginTop={3} padding={2} space={2}>
|
|
62
|
-
<Box paddingBottom={
|
|
67
|
+
<Box paddingBottom={2}>
|
|
63
68
|
<Text size={1} weight="semibold">
|
|
64
69
|
Show translations
|
|
65
70
|
</Text>
|
|
@@ -81,7 +86,7 @@ export function LanguageFilterMenuButton(props: LanguageFilterMenuButtonProps) {
|
|
|
81
86
|
id={lang.id}
|
|
82
87
|
key={lang.id}
|
|
83
88
|
onToggle={toggleLanguage}
|
|
84
|
-
selected={
|
|
89
|
+
selected={activeLanguages.includes(lang.id)}
|
|
85
90
|
title={lang.title}
|
|
86
91
|
/>
|
|
87
92
|
))}
|
|
@@ -89,11 +94,25 @@ export function LanguageFilterMenuButton(props: LanguageFilterMenuButtonProps) {
|
|
|
89
94
|
</Box>
|
|
90
95
|
)
|
|
91
96
|
|
|
97
|
+
const langCount = options.supportedLanguages.length
|
|
92
98
|
return (
|
|
93
|
-
<Popover
|
|
99
|
+
<Popover content={content} open={open} portal ref={setPopover}>
|
|
94
100
|
<Button
|
|
95
|
-
|
|
96
|
-
|
|
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
|
+
}
|
|
97
116
|
mode="bleed"
|
|
98
117
|
onClick={handleClick}
|
|
99
118
|
ref={setButton}
|
|
@@ -1,33 +1,72 @@
|
|
|
1
|
-
import React, {useMemo} from 'react'
|
|
2
|
-
import {ObjectInputProps, ObjectMember} from 'sanity'
|
|
3
|
-
import {ObjectInput} from 'sanity/form'
|
|
4
|
-
import {usePaneLanguages} from './usePaneLanguages'
|
|
1
|
+
import React, {useEffect, useMemo} from 'react'
|
|
2
|
+
import {ObjectInputProps, ObjectMember, RenderInputCallback} from 'sanity'
|
|
5
3
|
import {LanguageFilterConfig} from './types'
|
|
6
4
|
import {defaultFilterField} from './filterField'
|
|
5
|
+
import {useLanguageFilterContext} from './LanguageFilterContext'
|
|
6
|
+
import {useSelectedLanguageIds} from './useSelectedLanguageIds'
|
|
7
7
|
|
|
8
|
-
export type LanguageFilterObjectInputProps = {
|
|
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
|
|
9
18
|
|
|
10
|
-
export function LanguageFilterObjectInput(
|
|
11
|
-
|
|
12
|
-
|
|
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
|
+
)
|
|
13
60
|
|
|
14
61
|
const filterField = options.filterField ?? defaultFilterField
|
|
15
62
|
|
|
16
63
|
const members: ObjectMember[] = useMemo(() => {
|
|
17
64
|
return membersProp.filter(
|
|
18
65
|
(member) =>
|
|
19
|
-
(member.kind === 'field' && filterField(schemaType, member,
|
|
20
|
-
(member.kind === 'fieldSet' && filterField(schemaType, member.fieldSet,
|
|
66
|
+
(member.kind === 'field' && filterField(schemaType, member, activeLanguages)) ||
|
|
67
|
+
(member.kind === 'fieldSet' && filterField(schemaType, member.fieldSet, activeLanguages))
|
|
21
68
|
)
|
|
22
|
-
}, [schemaType, membersProp, filterField,
|
|
69
|
+
}, [schemaType, membersProp, filterField, activeLanguages])
|
|
23
70
|
|
|
24
|
-
return (
|
|
25
|
-
<ObjectInput
|
|
26
|
-
{...restProps}
|
|
27
|
-
level={level}
|
|
28
|
-
members={members}
|
|
29
|
-
path={path}
|
|
30
|
-
schemaType={schemaType}
|
|
31
|
-
/>
|
|
32
|
-
)
|
|
71
|
+
return <>{next({...restProps, members, schemaType})}</>
|
|
33
72
|
}
|
|
@@ -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
CHANGED
|
@@ -4,6 +4,8 @@ import {LanguageFilterObjectInput} from './LanguageFilterObjectInput'
|
|
|
4
4
|
import {LanguageFilterMenuButton} from './LanguageFilterMenuButton'
|
|
5
5
|
import {LanguageFilterConfig} from './types'
|
|
6
6
|
import {isLanguageFilterEnabled} from './filterField'
|
|
7
|
+
import {LanguageFilterProvider} from './LanguageFilterContext'
|
|
8
|
+
import {createSelectedLanguageIdsBus} from './languageSubscription'
|
|
7
9
|
|
|
8
10
|
/**
|
|
9
11
|
* ## Usage in sanity.config.ts (or .js)
|
|
@@ -39,8 +41,10 @@ import {isLanguageFilterEnabled} from './filterField'
|
|
|
39
41
|
* ```
|
|
40
42
|
*/
|
|
41
43
|
export const languageFilter = createPlugin<LanguageFilterConfig>((options) => {
|
|
44
|
+
const {onSelectedIdsChange, subscribeSelectedIds} = createSelectedLanguageIdsBus()
|
|
45
|
+
|
|
42
46
|
const RenderLanguageFilter: _DocumentLanguageFilterComponent = () => {
|
|
43
|
-
return <LanguageFilterMenuButton options={options} />
|
|
47
|
+
return <LanguageFilterMenuButton options={options} onSelectedIdsChange={onSelectedIdsChange} />
|
|
44
48
|
}
|
|
45
49
|
|
|
46
50
|
return {
|
|
@@ -57,10 +61,25 @@ export const languageFilter = createPlugin<LanguageFilterConfig>((options) => {
|
|
|
57
61
|
form: {
|
|
58
62
|
renderInput(props, next) {
|
|
59
63
|
const enabled = isLanguageFilterEnabled(props.schemaType, options)
|
|
60
|
-
|
|
61
|
-
|
|
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
|
+
)
|
|
62
80
|
}
|
|
63
|
-
|
|
81
|
+
|
|
82
|
+
return undefined
|
|
64
83
|
},
|
|
65
84
|
},
|
|
66
85
|
}
|
package/src/usePaneLanguages.ts
CHANGED
|
@@ -1,49 +1,56 @@
|
|
|
1
1
|
import {useCallback, useMemo} from 'react'
|
|
2
|
-
import {usePaneRouter} from 'sanity/desk'
|
|
3
2
|
import {LanguageFilterConfig} from './types'
|
|
3
|
+
import {
|
|
4
|
+
getSelectableLanguages,
|
|
5
|
+
persistLanguageIds,
|
|
6
|
+
useSelectedLanguageIds,
|
|
7
|
+
} from './useSelectedLanguageIds'
|
|
4
8
|
|
|
5
|
-
|
|
6
|
-
|
|
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
|
+
}
|
|
7
18
|
|
|
8
|
-
export function usePaneLanguages(props:
|
|
9
|
-
|
|
19
|
+
export function usePaneLanguages(props: UsePaneLanguagesParams): {
|
|
20
|
+
activeLanguages: string[]
|
|
10
21
|
allSelected: boolean
|
|
11
22
|
selectAll: () => void
|
|
12
23
|
selectNone: () => void
|
|
13
24
|
toggleLanguage: (languageId: string) => void
|
|
14
25
|
} {
|
|
15
|
-
const {options} = props
|
|
16
|
-
const {
|
|
17
|
-
const {supportedLanguages, defaultLanguages} = options
|
|
18
|
-
|
|
19
|
-
const selectableLanguages = useMemo(
|
|
20
|
-
() => supportedLanguages.filter((lang) => !defaultLanguages?.includes(lang.id)),
|
|
21
|
-
[supportedLanguages, defaultLanguages]
|
|
22
|
-
)
|
|
26
|
+
const {options, onSelectedIdsChange} = props
|
|
27
|
+
const {defaultLanguages} = options
|
|
23
28
|
|
|
24
|
-
const
|
|
25
|
-
if (params?.langs === '$none') {
|
|
26
|
-
return []
|
|
27
|
-
}
|
|
29
|
+
const [selectedIds, setSelectedIds] = useSelectedLanguageIds(options)
|
|
28
30
|
|
|
29
|
-
|
|
30
|
-
return selectableLanguages.map((lang) => lang.id)
|
|
31
|
-
}
|
|
31
|
+
const selectableLanguages = useMemo(() => getSelectableLanguages(options), [options])
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
const updateSelectedIds = useCallback(
|
|
34
|
+
(ids: string[]) => {
|
|
35
|
+
setSelectedIds(ids)
|
|
36
|
+
persistLanguageIds(ids)
|
|
37
|
+
onSelectedIdsChange(ids)
|
|
38
|
+
},
|
|
39
|
+
[onSelectedIdsChange, setSelectedIds]
|
|
40
|
+
)
|
|
35
41
|
|
|
36
|
-
const selectAll = useCallback(
|
|
37
|
-
|
|
38
|
-
|
|
42
|
+
const selectAll = useCallback(
|
|
43
|
+
() => updateSelectedIds(selectableLanguages.map((l) => l.id)),
|
|
44
|
+
[updateSelectedIds, selectableLanguages]
|
|
45
|
+
)
|
|
39
46
|
|
|
40
47
|
const selectNone = useCallback(() => {
|
|
41
|
-
|
|
42
|
-
}, [
|
|
48
|
+
updateSelectedIds([])
|
|
49
|
+
}, [updateSelectedIds])
|
|
43
50
|
|
|
44
51
|
const toggleLanguage = useCallback(
|
|
45
52
|
(languageId: string) => {
|
|
46
|
-
let lang =
|
|
53
|
+
let lang = selectedIds
|
|
47
54
|
|
|
48
55
|
if (lang.includes(languageId)) {
|
|
49
56
|
lang = lang.filter((l) => l !== languageId)
|
|
@@ -51,29 +58,19 @@ export function usePaneLanguages(props: {options: LanguageFilterConfig}): {
|
|
|
51
58
|
lang = [...lang, languageId]
|
|
52
59
|
}
|
|
53
60
|
|
|
54
|
-
|
|
55
|
-
setParams({...params, langs: '$none'}) // none
|
|
56
|
-
return
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
if (lang.length === selectableLanguages.length) {
|
|
60
|
-
setParams({...params, langs: '$all'})
|
|
61
|
-
return
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
setParams({...params, langs: lang.join(LANG_ID_SEPARATOR)})
|
|
61
|
+
updateSelectedIds(lang)
|
|
65
62
|
},
|
|
66
|
-
[
|
|
63
|
+
[updateSelectedIds, selectedIds]
|
|
67
64
|
)
|
|
68
65
|
|
|
69
|
-
const
|
|
70
|
-
() => [...(defaultLanguages ?? []), ...
|
|
71
|
-
[defaultLanguages,
|
|
66
|
+
const activeLanguages = useMemo(
|
|
67
|
+
() => [...(defaultLanguages ?? []), ...selectedIds],
|
|
68
|
+
[defaultLanguages, selectedIds]
|
|
72
69
|
)
|
|
73
70
|
|
|
74
71
|
return {
|
|
75
|
-
|
|
76
|
-
allSelected:
|
|
72
|
+
activeLanguages,
|
|
73
|
+
allSelected: selectedIds.length === selectableLanguages.length,
|
|
77
74
|
selectAll,
|
|
78
75
|
selectNone,
|
|
79
76
|
toggleLanguage,
|
|
@@ -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
|
+
}
|