@sanity/language-filter 3.1.1 → 3.2.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/README.md +37 -10
- package/lib/index.esm.js +1 -1
- package/lib/index.js +1 -1
- package/lib/src/index.d.ts +21 -6
- package/package.json +1 -1
- package/src/LanguageFilterMenuButton.tsx +10 -12
- package/src/LanguageFilterStudioContext.tsx +37 -9
- package/src/getSelectedValue.ts +29 -0
- package/src/plugin.tsx +1 -1
- package/src/types.ts +18 -4
- package/src/useSelectedLanguageIds.ts +3 -1
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
A Sanity plugin that supports filtering localized fields by language
|
|
9
9
|
|
|
10
|
-

|
|
11
11
|
|
|
12
12
|
## What this plugin solves
|
|
13
13
|
|
|
@@ -47,7 +47,7 @@ yarn add @sanity/language-filter
|
|
|
47
47
|
|
|
48
48
|
Add it as a plugin in sanity.config.ts (or .js), and configure it:
|
|
49
49
|
|
|
50
|
-
```
|
|
50
|
+
```ts
|
|
51
51
|
import {defineConfig} from 'sanity'
|
|
52
52
|
import {languageFilter} from '@sanity/language-filter'
|
|
53
53
|
|
|
@@ -77,12 +77,46 @@ Add it as a plugin in sanity.config.ts (or .js), and configure it:
|
|
|
77
77
|
|
|
78
78
|
Config properties:
|
|
79
79
|
|
|
80
|
-
- `supportedLanguages`
|
|
80
|
+
- `supportedLanguages` can be either:
|
|
81
|
+
-- An static array of language objects with `id` and `title`. If your localized fields are defined using our recommended way described here (https://www.sanity.io/docs/localization), you probably want to share this list of supported languages between this config and your schema.
|
|
82
|
+
-- A function that returns a promise resolving to an array of language objects with `id` and `title`. This is useful if you want to fetch the list of supported languages from an external source. See [Loading languages](#loading-languages) for more details.
|
|
81
83
|
- `defaultLanguages` (optional) is an array of strings where each entry must match an `id` from the `supportedLanguages` array. These languages will be listed by default and will not be possible to unselect. If no `defaultLanguages` is configured, all localized fields will be selected by default.
|
|
82
84
|
- `documentTypes` (optional) is an array of strings where each entry must match a `name` from your document schemas. If defined, this property will be used to conditionally show the language filter on specific document schema types. If undefined, the language filter will show on all document schema types.
|
|
83
85
|
- `filterField` (optional) is a function that must return true if the field should be displayed. It is passed the enclosing type (e.g the object type containing the localized fields, the field, and an array of the currently selected language ids.
|
|
84
86
|
This function is called for all fields and in objects for documents that have language filter enabled.
|
|
85
87
|
_Default:_ `!enclosingType.name.startsWith('locale') || selectedLanguageIds.includes(field.name)`
|
|
88
|
+
- `apiVersion` (optional) used for the Sanity Client when asynchronously loading languages.
|
|
89
|
+
|
|
90
|
+
## Loading languages
|
|
91
|
+
|
|
92
|
+
Languages must be an array of objects with an `id` and `title`.
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
languages: [
|
|
96
|
+
{id: 'en', title: 'English'},
|
|
97
|
+
{id: 'fr', title: 'French'}
|
|
98
|
+
],
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Or an asynchronous function that returns an array of objects with an `id` and `title`.
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
languages: async () => {
|
|
105
|
+
const response = await fetch('https://example.com/languages')
|
|
106
|
+
return response.json()
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
The async function contains a configured Sanity Client in the first parameter, allowing you to store Language options as documents. Your query should return an array of objects with an `id` and `title`.
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
languages: async (client) => {
|
|
114
|
+
const response = await client.fetch(`*[_type == "language"]{ id, title }`)
|
|
115
|
+
return response
|
|
116
|
+
},
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
`@sanity/language-filter`'s asynchronous language loading does not currently support modifying the query based on a value in the current document.
|
|
86
120
|
|
|
87
121
|
## Changes in V3
|
|
88
122
|
|
|
@@ -106,13 +140,6 @@ export const myDocumentSchema = {
|
|
|
106
140
|
}
|
|
107
141
|
```
|
|
108
142
|
|
|
109
|
-
### State management
|
|
110
|
-
|
|
111
|
-
Selected languages are now stored as `langs` url-param state; this allows users to copy paste
|
|
112
|
-
a url in the studio with the currently selected languages preselected.
|
|
113
|
-
|
|
114
|
-
Previously this state was stored in localstorage.
|
|
115
|
-
|
|
116
143
|
## License
|
|
117
144
|
|
|
118
145
|
MIT-licensed. See LICENSE.
|
package/lib/index.esm.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var e;const t=["members","schemaType","renderDefault"];function n(e,t){if(null==e)return{};var n,r,
|
|
1
|
+
var e;const t=["members","schemaType","renderDefault"];function n(e,t){if(null==e)return{};var n,r,o=function(e,t){if(null==e)return{};var n,r,o={},i=Object.keys(e);for(r=0;r<i.length;r++)n=i[r],t.indexOf(n)>=0||(o[n]=e[n]);return o}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r<i.length;r++)n=i[r],t.indexOf(n)>=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}function r(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function o(e){for(var t=1;t<arguments.length;t++){var n=null!=arguments[t]?arguments[t]:{};t%2?r(Object(n),!0).forEach((function(t){i(e,t,n[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(n)):r(Object(n)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(n,t))}))}return e}function i(e,t,n){return(t=function(e){var t=function(e,t){if("object"!=typeof e||null===e)return e;var n=e[Symbol.toPrimitive];if(void 0!==n){var r=n.call(e,t||"default");if("object"!=typeof r)return r;throw new TypeError("@@toPrimitive must return a primitive value.")}return("string"===t?String:Number)(e)}(e,"string");return"symbol"==typeof t?t:String(t)}(t))in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}import{jsx as l,jsxs as a,Fragment as s}from"react/jsx-runtime";import{useClient as u,useFormValue as c,TextWithTone as d,definePlugin as g,isObjectSchemaType as p}from"sanity";import{useState as f,createContext as m,useEffect as y,useMemo as h,useContext as b,useCallback as v}from"react";import{Box as L,useClickOutside as O,Stack as j,Card as S,Button as w,Flex as T,Text as P,TextInput as x,Popover as A,Badge as D}from"@sanity/ui";import I from"styled-components";import{EyeClosedIcon as k,EyeOpenIcon as C,TranslateIcon as N,CheckmarkCircleIcon as F,CircleIcon as z}from"@sanity/icons";const E=(e,t,n)=>!e.name.startsWith("locale")||n.includes(t.name);function J(e,t){var n,r;const o=function(e){return"object"===(null==e?void 0:e.jsonType)&&"document"===V(e).name}(e)&&(null==(n=null==e?void 0:e.options)?void 0:n.languageFilter),i=!t.documentTypes;return!!(i&&!1!==o||!i&&o||e&&(null==(r=t.documentTypes)?void 0:r.includes(e.name)))}function V(e){return e.type?V(e.type):e}const _="@sanity/plugin/language-filter/selected-languages";function H(e){const t=W(e).map((e=>e.id));let n=t;try{const e=window.localStorage.getItem(_);e&&(n=JSON.parse(e))}catch(e){}var r;return r=t,n=n.filter((e=>r.includes(e))),n}function W(e){let{supportedLanguages:t,defaultLanguages:n}=e;return Array.isArray(t)?t.filter((e=>!(null==n?void 0:n.includes(e.id)))):[]}const q={options:{apiVersion:"2022-11-27",supportedLanguages:[],defaultLanguages:[],documentTypes:[],filterField:E},selectedLanguageIds:[],setSelectedLanguageIds:()=>console.error("LanguageFilterStudioContext not initialized")},B=m(q);function G(e){const t=u({apiVersion:"2023-01-01"}),[n,r]=f(Array.isArray(e.options.supportedLanguages)?e.options.supportedLanguages:[]);y((()=>{let n=[];Array.isArray(e.options.supportedLanguages)||async function(e){n=await e(t,{}),r(n)}(e.options.supportedLanguages)}),[t,e.options.supportedLanguages]);const i=h((()=>o(o(o({},q.options),e.options),{},{supportedLanguages:n})),[e.options,n]),[a,s]=function(e){return f((()=>{var t;return[...null!=(t=e.defaultLanguages)?t:[],...H(e)]}))}(i);return l(B.Provider,{value:{options:i,selectedLanguageIds:a,setSelectedLanguageIds:s},children:e.renderDefault(e)})}function K(){return b(B)}function M(e){const{members:r,schemaType:i,renderDefault:l}=e,a=n(e,t),{selectedLanguageIds:s,options:u}=K(),{filterField:c}=u,d=h((()=>r.filter((e=>"field"===e.kind&&c(i,e,s)||"fieldSet"===e.kind)).map((e=>"fieldSet"===e.kind?o(o({},e),{},{fieldSet:o(o({},e.fieldSet),{},{members:e.fieldSet.members.filter((e=>"field"===e.kind&&c(i,e,s)))})}):e))),[i,r,c,s]);return l(o(o({},a),{},{members:d,schemaType:i,renderDefault:l}))}const Q=e=>Array.from(new Set(e));function R(){const{selectedLanguageIds:e,setSelectedLanguageIds:t,options:n}=K(),{defaultLanguages:r=[]}=n,o=h((()=>W(n)),[n]),i=v((e=>{var n;t(Q([...r,...e])),n=Q([...r,...e]),window.localStorage.setItem(_,JSON.stringify(n))}),[r,t]),l=v((()=>i(o.map((e=>e.id)))),[i,o]),a=v((()=>{i(r)}),[r,i]),s=v((t=>{let n=e;n=n.includes(t)?n.filter((e=>e!==t)):Q([...n,t]),i(n)}),[i,e]);return{activeLanguages:h((()=>Q([...null!=r?r:[],...e])),[r,e]),allSelected:e.length===o.length+r.length,selectAll:l,selectNone:a,toggleLanguage:s}}const U=I(L)(e||(X=["\n max-height: calc(100vh - 200px);\n"],Y||(Y=X.slice(0)),e=Object.freeze(Object.defineProperties(X,{raw:{value:Object.freeze(Y)}}))));var X,Y;function Z(){const{options:e}=K(),t=e.supportedLanguages.filter((t=>{var n;return null==(n=e.defaultLanguages)?void 0:n.includes(t.id)})),n=e.supportedLanguages.filter((t=>{var n;return!(null==(n=e.defaultLanguages)?void 0:n.includes(t.id))})),[r,o]=f(!1),{activeLanguages:i,allSelected:u,selectAll:c,selectNone:g,toggleLanguage:p}=R(),[m,y]=f(null),[h,b]=f(null),D=v((e=>{"ALL"===e.currentTarget.value?c():g()}),[c,g]),I=v((()=>o((e=>!e))),[]),F=v((()=>o(!1)),[]);O(F,[m,h]);const z=e.supportedLanguages.length,[E,J]=f(""),V=v((e=>{e.currentTarget.value?J(e.currentTarget.value):J("")}),[]),_=z>4,H=l(U,{overflow:"auto",children:a(j,{padding:1,space:1,children:[t.length>0&&a(s,{children:[t.map((e=>l($,{id:e.id,title:e.title,selected:!0},e.id))),l(S,{borderTop:!0})]}),l(w,{mode:"bleed",onClick:D,justify:"flex-start",value:u?"NONE":"ALL",disabled:!!E,children:a(T,{gap:3,align:"center",children:[l(P,{size:2,children:u?l(d,{tone:"primary",children:l(k,{})}):l(C,{})}),l(L,{flex:1,children:l(P,{children:u?"Hide all":"Show all"})})]})}),_?l(x,{onChange:V,value:E,placeholder:"Filter languages"}):l(S,{borderTop:!0}),n.filter((e=>!E||e.title.toLowerCase().includes(E.toLowerCase()))).map((e=>l($,{id:e.id,onToggle:p,selected:i.includes(e.id),title:e.title},e.id)))]})}),W=i.length===z?"Showing all":"Showing ".concat(i.length," / ").concat(z);return l(A,{content:H,open:r,portal:!0,ref:b,children:l(w,{text:W,icon:N,mode:"bleed",onClick:I,ref:y,selected:r})})}function $(e){const{id:t,onToggle:n,selected:r,title:o}=e,i=v((()=>{n&&n(t)}),[t,n]),s=!n;return l(w,{mode:"bleed",onClick:i,justify:"flex-start",disabled:s,children:a(T,{gap:3,align:"center",children:[l(P,{size:2,children:r?l(d,{tone:s?"default":"positive",children:l(F,{})}):l(z,{})}),l(L,{flex:1,children:l(P,{children:o})}),l(D,{children:t})]})})}const ee=g((e=>{const t=()=>l(Z,{}),n=o(o({},q.options),e);return{name:"@sanity/language-filter",studio:{components:{layout:e=>G(o(o({},e),{},{options:n}))}},document:{unstable_languageFilter:(n,r)=>{let{schemaType:o,schema:i}=r;return J(i.get(o),e)?[...n,t]:n}},form:{components:{input:e=>"root"!==e.id&&p(e.schemaType)?function(e){const{options:t}=K(),n=c(["_type"]),{documentTypes:r}=t;return"string"==typeof n&&r.includes(n)?l(M,o({},e)):e.renderDefault(e)}(e):e.renderDefault(e)}}}}));export{E as defaultFilterField,J as isLanguageFilterEnabled,ee as languageFilter,K as useLanguageFilterStudioContext};//# sourceMappingURL=index.esm.js.map
|
package/lib/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var e;const t=["members","schemaType","renderDefault"];function n(e,t){if(null==e)return{};var n,r,l=function(e,t){if(null==e)return{};var n,r,l={},
|
|
1
|
+
"use strict";var e;const t=["members","schemaType","renderDefault"];function n(e,t){if(null==e)return{};var n,r,l=function(e,t){if(null==e)return{};var n,r,l={},a=Object.keys(e);for(r=0;r<a.length;r++)n=a[r],t.indexOf(n)>=0||(l[n]=e[n]);return l}(e,t);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);for(r=0;r<a.length;r++)n=a[r],t.indexOf(n)>=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(l[n]=e[n])}return l}function r(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function l(e){for(var t=1;t<arguments.length;t++){var n=null!=arguments[t]?arguments[t]:{};t%2?r(Object(n),!0).forEach((function(t){a(e,t,n[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(n)):r(Object(n)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(n,t))}))}return e}function a(e,t,n){return(t=function(e){var t=function(e,t){if("object"!=typeof e||null===e)return e;var n=e[Symbol.toPrimitive];if(void 0!==n){var r=n.call(e,t||"default");if("object"!=typeof r)return r;throw new TypeError("@@toPrimitive must return a primitive value.")}return("string"===t?String:Number)(e)}(e,"string");return"symbol"==typeof t?t:String(t)}(t))in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}Object.defineProperty(exports,"__esModule",{value:!0});var o=require("react/jsx-runtime"),s=require("sanity"),i=require("react"),u=require("@sanity/ui"),c=require("styled-components"),d=require("@sanity/icons");function g(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var p=g(c);const f=(e,t,n)=>!e.name.startsWith("locale")||n.includes(t.name);function m(e,t){var n,r;const l=function(e){return"object"===(null==e?void 0:e.jsonType)&&"document"===y(e).name}(e)&&(null==(n=null==e?void 0:e.options)?void 0:n.languageFilter),a=!t.documentTypes;return!!(a&&!1!==l||!a&&l||e&&(null==(r=t.documentTypes)?void 0:r.includes(e.name)))}function y(e){return e.type?y(e.type):e}const x="@sanity/plugin/language-filter/selected-languages";function b(e){const t=j(e).map((e=>e.id));let n=t;try{const e=window.localStorage.getItem(x);e&&(n=JSON.parse(e))}catch(e){}var r;return r=t,n=n.filter((e=>r.includes(e))),n}function j(e){let{supportedLanguages:t,defaultLanguages:n}=e;return Array.isArray(t)?t.filter((e=>!(null==n?void 0:n.includes(e.id)))):[]}const h={options:{apiVersion:"2022-11-27",supportedLanguages:[],defaultLanguages:[],documentTypes:[],filterField:f},selectedLanguageIds:[],setSelectedLanguageIds:()=>console.error("LanguageFilterStudioContext not initialized")},v=i.createContext(h);function L(e){const t=s.useClient({apiVersion:"2023-01-01"}),[n,r]=i.useState(Array.isArray(e.options.supportedLanguages)?e.options.supportedLanguages:[]);i.useEffect((()=>{let n=[];Array.isArray(e.options.supportedLanguages)||async function(e){n=await e(t,{}),r(n)}(e.options.supportedLanguages)}),[t,e.options.supportedLanguages]);const a=i.useMemo((()=>l(l(l({},h.options),e.options),{},{supportedLanguages:n})),[e.options,n]),[u,c]=function(e){return i.useState((()=>{var t;return[...null!=(t=e.defaultLanguages)?t:[],...b(e)]}))}(a);return o.jsx(v.Provider,{value:{options:a,selectedLanguageIds:u,setSelectedLanguageIds:c},children:e.renderDefault(e)})}function O(){return i.useContext(v)}function S(e){const{members:r,schemaType:a,renderDefault:o}=e,s=n(e,t),{selectedLanguageIds:u,options:c}=O(),{filterField:d}=c,g=i.useMemo((()=>r.filter((e=>"field"===e.kind&&d(a,e,u)||"fieldSet"===e.kind)).map((e=>"fieldSet"===e.kind?l(l({},e),{},{fieldSet:l(l({},e.fieldSet),{},{members:e.fieldSet.members.filter((e=>"field"===e.kind&&d(a,e,u)))})}):e))),[a,r,d,u]);return o(l(l({},s),{},{members:g,schemaType:a,renderDefault:o}))}const T=e=>Array.from(new Set(e));function C(){const{selectedLanguageIds:e,setSelectedLanguageIds:t,options:n}=O(),{defaultLanguages:r=[]}=n,l=i.useMemo((()=>j(n)),[n]),a=i.useCallback((e=>{var n;t(T([...r,...e])),n=T([...r,...e]),window.localStorage.setItem(x,JSON.stringify(n))}),[r,t]),o=i.useCallback((()=>a(l.map((e=>e.id)))),[a,l]),s=i.useCallback((()=>{a(r)}),[r,a]),u=i.useCallback((t=>{let n=e;n=n.includes(t)?n.filter((e=>e!==t)):T([...n,t]),a(n)}),[a,e]);return{activeLanguages:i.useMemo((()=>T([...null!=r?r:[],...e])),[r,e]),allSelected:e.length===l.length+r.length,selectAll:o,selectNone:s,toggleLanguage:u}}const w=p.default(u.Box)(e||(k=["\n max-height: calc(100vh - 200px);\n"],P||(P=k.slice(0)),e=Object.freeze(Object.defineProperties(k,{raw:{value:Object.freeze(P)}}))));var k,P;function I(){const{options:e}=O(),t=e.supportedLanguages.filter((t=>{var n;return null==(n=e.defaultLanguages)?void 0:n.includes(t.id)})),n=e.supportedLanguages.filter((t=>{var n;return!(null==(n=e.defaultLanguages)?void 0:n.includes(t.id))})),[r,l]=i.useState(!1),{activeLanguages:a,allSelected:c,selectAll:g,selectNone:p,toggleLanguage:f}=C(),[m,y]=i.useState(null),[x,b]=i.useState(null),j=i.useCallback((e=>{"ALL"===e.currentTarget.value?g():p()}),[g,p]),h=i.useCallback((()=>l((e=>!e))),[]),v=i.useCallback((()=>l(!1)),[]);u.useClickOutside(v,[m,x]);const L=e.supportedLanguages.length,[S,T]=i.useState(""),k=i.useCallback((e=>{e.currentTarget.value?T(e.currentTarget.value):T("")}),[]),P=L>4,I=o.jsx(w,{overflow:"auto",children:o.jsxs(u.Stack,{padding:1,space:1,children:[t.length>0&&o.jsxs(o.Fragment,{children:[t.map((e=>o.jsx(F,{id:e.id,title:e.title,selected:!0},e.id))),o.jsx(u.Card,{borderTop:!0})]}),o.jsx(u.Button,{mode:"bleed",onClick:j,justify:"flex-start",value:c?"NONE":"ALL",disabled:!!S,children:o.jsxs(u.Flex,{gap:3,align:"center",children:[o.jsx(u.Text,{size:2,children:c?o.jsx(s.TextWithTone,{tone:"primary",children:o.jsx(d.EyeClosedIcon,{})}):o.jsx(d.EyeOpenIcon,{})}),o.jsx(u.Box,{flex:1,children:o.jsx(u.Text,{children:c?"Hide all":"Show all"})})]})}),P?o.jsx(u.TextInput,{onChange:k,value:S,placeholder:"Filter languages"}):o.jsx(u.Card,{borderTop:!0}),n.filter((e=>!S||e.title.toLowerCase().includes(S.toLowerCase()))).map((e=>o.jsx(F,{id:e.id,onToggle:f,selected:a.includes(e.id),title:e.title},e.id)))]})}),A=a.length===L?"Showing all":"Showing ".concat(a.length," / ").concat(L);return o.jsx(u.Popover,{content:I,open:r,portal:!0,ref:b,children:o.jsx(u.Button,{text:A,icon:d.TranslateIcon,mode:"bleed",onClick:h,ref:y,selected:r})})}function F(e){const{id:t,onToggle:n,selected:r,title:l}=e,a=i.useCallback((()=>{n&&n(t)}),[t,n]),c=!n;return o.jsx(u.Button,{mode:"bleed",onClick:a,justify:"flex-start",disabled:c,children:o.jsxs(u.Flex,{gap:3,align:"center",children:[o.jsx(u.Text,{size:2,children:r?o.jsx(s.TextWithTone,{tone:c?"default":"positive",children:o.jsx(d.CheckmarkCircleIcon,{})}):o.jsx(d.CircleIcon,{})}),o.jsx(u.Box,{flex:1,children:o.jsx(u.Text,{children:l})}),o.jsx(u.Badge,{children:t})]})})}const A=s.definePlugin((e=>{const t=()=>o.jsx(I,{}),n=l(l({},h.options),e);return{name:"@sanity/language-filter",studio:{components:{layout:e=>L(l(l({},e),{},{options:n}))}},document:{unstable_languageFilter:(n,r)=>{let{schemaType:l,schema:a}=r;return m(a.get(l),e)?[...n,t]:n}},form:{components:{input:e=>"root"!==e.id&&s.isObjectSchemaType(e.schemaType)?function(e){const{options:t}=O(),n=s.useFormValue(["_type"]),{documentTypes:r}=t;return"string"==typeof n&&r.includes(n)?o.jsx(S,l({},e)):e.renderDefault(e)}(e):e.renderDefault(e)}}}}));exports.defaultFilterField=f,exports.isLanguageFilterEnabled=m,exports.languageFilter=A,exports.useLanguageFilterStudioContext=O;//# sourceMappingURL=index.js.map
|
package/lib/src/index.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ import {FieldMember} from 'sanity'
|
|
|
4
4
|
import {FieldsetState} from 'sanity'
|
|
5
5
|
import {ObjectSchemaType} from 'sanity'
|
|
6
6
|
import {Plugin as Plugin_2} from 'sanity'
|
|
7
|
+
import {SanityClient} from 'sanity'
|
|
7
8
|
import type {SchemaType} from 'sanity'
|
|
8
9
|
|
|
9
10
|
export declare const defaultFilterField: FilterFieldFunction
|
|
@@ -19,11 +20,16 @@ export declare function isLanguageFilterEnabled(
|
|
|
19
20
|
options: LanguageFilterConfig
|
|
20
21
|
): boolean
|
|
21
22
|
|
|
22
|
-
export declare
|
|
23
|
-
id:
|
|
23
|
+
export declare type Language = {
|
|
24
|
+
id: Intl.UnicodeBCP47LocaleIdentifier
|
|
24
25
|
title: string
|
|
25
26
|
}
|
|
26
27
|
|
|
28
|
+
declare type LanguageCallback = (
|
|
29
|
+
client: SanityClient,
|
|
30
|
+
selectedValue: Record<string, unknown>
|
|
31
|
+
) => Promise<Language[]>
|
|
32
|
+
|
|
27
33
|
/**
|
|
28
34
|
* ## Usage in sanity.config.ts (or .js)
|
|
29
35
|
*
|
|
@@ -60,10 +66,19 @@ export declare interface Language {
|
|
|
60
66
|
export declare const languageFilter: Plugin_2<LanguageFilterConfig>
|
|
61
67
|
|
|
62
68
|
export declare interface LanguageFilterConfig {
|
|
63
|
-
supportedLanguages: Language[]
|
|
69
|
+
supportedLanguages: Language[] | LanguageCallback
|
|
64
70
|
defaultLanguages?: string[]
|
|
65
71
|
documentTypes?: string[]
|
|
66
72
|
filterField?: FilterFieldFunction
|
|
73
|
+
/**
|
|
74
|
+
* https://www.sanity.io/docs/api-versioning
|
|
75
|
+
* @defaultValue '2022-11-27'
|
|
76
|
+
*/
|
|
77
|
+
apiVersion?: string
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
declare interface LanguageFilterConfigProcessed extends LanguageFilterConfig {
|
|
81
|
+
supportedLanguages: Language[]
|
|
67
82
|
}
|
|
68
83
|
|
|
69
84
|
export declare interface LanguageFilterOptions {
|
|
@@ -74,11 +89,11 @@ export declare interface LanguageFilterSchema extends ObjectSchemaType {
|
|
|
74
89
|
options?: LanguageFilterOptions
|
|
75
90
|
}
|
|
76
91
|
|
|
77
|
-
declare interface
|
|
78
|
-
options: Required<
|
|
92
|
+
declare interface LanguageFilterStudioContextProcessed {
|
|
93
|
+
options: Required<LanguageFilterConfigProcessed>
|
|
79
94
|
}
|
|
80
95
|
|
|
81
|
-
declare interface LanguageFilterStudioContextValue extends
|
|
96
|
+
declare interface LanguageFilterStudioContextValue extends LanguageFilterStudioContextProcessed {
|
|
82
97
|
selectedLanguageIds: string[]
|
|
83
98
|
setSelectedLanguageIds: (ids: string[]) => void
|
|
84
99
|
}
|
package/package.json
CHANGED
|
@@ -12,7 +12,6 @@ import {
|
|
|
12
12
|
} from '@sanity/ui'
|
|
13
13
|
import React, {FormEvent, MouseEventHandler, useCallback, useState} from 'react'
|
|
14
14
|
import styled from 'styled-components'
|
|
15
|
-
import {LanguageFilterConfig} from './types'
|
|
16
15
|
import {usePaneLanguages} from './usePaneLanguages'
|
|
17
16
|
import {
|
|
18
17
|
CheckmarkCircleIcon,
|
|
@@ -22,17 +21,14 @@ import {
|
|
|
22
21
|
TranslateIcon,
|
|
23
22
|
} from '@sanity/icons'
|
|
24
23
|
import {TextWithTone} from 'sanity'
|
|
24
|
+
import {useLanguageFilterStudioContext} from './LanguageFilterStudioContext'
|
|
25
25
|
|
|
26
26
|
const StyledBox = styled(Box)`
|
|
27
27
|
max-height: calc(100vh - 200px);
|
|
28
28
|
`
|
|
29
29
|
|
|
30
|
-
export
|
|
31
|
-
options
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export function LanguageFilterMenuButton(props: LanguageFilterMenuButtonProps) {
|
|
35
|
-
const {options} = props
|
|
30
|
+
export function LanguageFilterMenuButton() {
|
|
31
|
+
const {options} = useLanguageFilterStudioContext()
|
|
36
32
|
|
|
37
33
|
const defaultLanguages = options.supportedLanguages.filter((l) =>
|
|
38
34
|
options.defaultLanguages?.includes(l.id)
|
|
@@ -41,7 +37,7 @@ export function LanguageFilterMenuButton(props: LanguageFilterMenuButtonProps) {
|
|
|
41
37
|
const languageOptions = options.supportedLanguages.filter(
|
|
42
38
|
(l) => !options.defaultLanguages?.includes(l.id)
|
|
43
39
|
)
|
|
44
|
-
const [open, setOpen] = useState(
|
|
40
|
+
const [open, setOpen] = useState(false)
|
|
45
41
|
const {activeLanguages, allSelected, selectAll, selectNone, toggleLanguage} = usePaneLanguages()
|
|
46
42
|
const [button, setButton] = useState<HTMLElement | null>(null)
|
|
47
43
|
const [popover, setPopover] = useState<HTMLElement | null>(null)
|
|
@@ -77,6 +73,8 @@ export function LanguageFilterMenuButton(props: LanguageFilterMenuButtonProps) {
|
|
|
77
73
|
}
|
|
78
74
|
}, [])
|
|
79
75
|
|
|
76
|
+
const showSearch = langCount > 4
|
|
77
|
+
|
|
80
78
|
const content = (
|
|
81
79
|
<StyledBox overflow="auto">
|
|
82
80
|
<Stack padding={1} space={1}>
|
|
@@ -112,11 +110,11 @@ export function LanguageFilterMenuButton(props: LanguageFilterMenuButtonProps) {
|
|
|
112
110
|
</Flex>
|
|
113
111
|
</Button>
|
|
114
112
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
{langCount > 4 ? (
|
|
113
|
+
{showSearch ? (
|
|
118
114
|
<TextInput onChange={handleQuery} value={query} placeholder="Filter languages" />
|
|
119
|
-
) :
|
|
115
|
+
) : (
|
|
116
|
+
<Card borderTop />
|
|
117
|
+
)}
|
|
120
118
|
|
|
121
119
|
{languageOptions
|
|
122
120
|
.filter((language) => {
|
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
import React, {createContext, useContext, useMemo} from 'react'
|
|
2
|
-
import {
|
|
3
|
-
|
|
1
|
+
import React, {createContext, useContext, useEffect, useMemo, useState} from 'react'
|
|
2
|
+
import {
|
|
3
|
+
Language,
|
|
4
|
+
LanguageCallback,
|
|
5
|
+
LanguageFilterConfig,
|
|
6
|
+
LanguageFilterConfigProcessed,
|
|
7
|
+
} from './types'
|
|
8
|
+
import {LayoutProps, useClient} from 'sanity'
|
|
4
9
|
import {defaultFilterField} from './filterField'
|
|
5
10
|
import {useSelectedLanguageIds} from './useSelectedLanguageIds'
|
|
6
11
|
|
|
@@ -9,13 +14,18 @@ export interface LanguageFilterStudioContextProps {
|
|
|
9
14
|
options: Required<LanguageFilterConfig>
|
|
10
15
|
}
|
|
11
16
|
|
|
12
|
-
export interface
|
|
17
|
+
export interface LanguageFilterStudioContextProcessed {
|
|
18
|
+
options: Required<LanguageFilterConfigProcessed>
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface LanguageFilterStudioContextValue extends LanguageFilterStudioContextProcessed {
|
|
13
22
|
selectedLanguageIds: string[]
|
|
14
23
|
setSelectedLanguageIds: (ids: string[]) => void
|
|
15
24
|
}
|
|
16
25
|
|
|
17
26
|
export const defaultContextValue: LanguageFilterStudioContextValue = {
|
|
18
27
|
options: {
|
|
28
|
+
apiVersion: '2022-11-27',
|
|
19
29
|
supportedLanguages: [],
|
|
20
30
|
defaultLanguages: [],
|
|
21
31
|
documentTypes: [],
|
|
@@ -36,13 +46,31 @@ const LanguageFilterStudioContext =
|
|
|
36
46
|
export function LanguageFilterStudioProvider(
|
|
37
47
|
props: LayoutProps & LanguageFilterStudioContextProps
|
|
38
48
|
) {
|
|
39
|
-
const
|
|
40
|
-
|
|
49
|
+
const client = useClient({apiVersion: '2023-01-01'})
|
|
50
|
+
const [languages, setLanguages] = useState<Language[]>(
|
|
51
|
+
Array.isArray(props.options.supportedLanguages) ? props.options.supportedLanguages : []
|
|
52
|
+
)
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
let asyncLanguages: Language[] = []
|
|
55
|
+
|
|
56
|
+
async function getLanguages(supportedLanguagesCallback: LanguageCallback) {
|
|
57
|
+
asyncLanguages = await supportedLanguagesCallback(client, {})
|
|
58
|
+
setLanguages(asyncLanguages)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (!Array.isArray(props.options.supportedLanguages)) {
|
|
62
|
+
getLanguages(props.options.supportedLanguages)
|
|
63
|
+
}
|
|
64
|
+
}, [client, props.options.supportedLanguages])
|
|
65
|
+
|
|
66
|
+
const options = useMemo<Required<LanguageFilterConfigProcessed>>(() => {
|
|
67
|
+
return {
|
|
41
68
|
...defaultContextValue.options,
|
|
42
69
|
...props.options,
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
)
|
|
70
|
+
supportedLanguages: languages,
|
|
71
|
+
}
|
|
72
|
+
}, [props.options, languages])
|
|
73
|
+
|
|
46
74
|
const [selectedLanguageIds, setSelectedLanguageIds] = useSelectedLanguageIds(options)
|
|
47
75
|
|
|
48
76
|
return (
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import {get} from 'lodash'
|
|
2
|
+
|
|
3
|
+
export const getSelectedValue = (
|
|
4
|
+
select: Record<string, string> | undefined,
|
|
5
|
+
document:
|
|
6
|
+
| {
|
|
7
|
+
[x: string]: unknown
|
|
8
|
+
}
|
|
9
|
+
| undefined
|
|
10
|
+
): Record<string, unknown> => {
|
|
11
|
+
if (!select || !document) {
|
|
12
|
+
return {}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const selection: Record<string, string> = select || {}
|
|
16
|
+
const selectedValue: Record<string, unknown> = {}
|
|
17
|
+
for (const [key, path] of Object.entries(selection)) {
|
|
18
|
+
let value = get(document, path)
|
|
19
|
+
if (Array.isArray(value)) {
|
|
20
|
+
// If there are references in the array, ensure they have `_ref` set, otherwise they are considered empty and can safely be ignored
|
|
21
|
+
value = value.filter((item) =>
|
|
22
|
+
typeof item === 'object' ? item?._type === 'reference' && '_ref' in item : true
|
|
23
|
+
)
|
|
24
|
+
}
|
|
25
|
+
selectedValue[key] = value
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return selectedValue
|
|
29
|
+
}
|
package/src/plugin.tsx
CHANGED
|
@@ -46,7 +46,7 @@ import {defaultContextValue, LanguageFilterStudioProvider} from './LanguageFilte
|
|
|
46
46
|
*/
|
|
47
47
|
export const languageFilter = definePlugin<LanguageFilterConfig>((options) => {
|
|
48
48
|
const RenderLanguageFilter: DocumentLanguageFilterComponent = () => {
|
|
49
|
-
return <LanguageFilterMenuButton
|
|
49
|
+
return <LanguageFilterMenuButton />
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
const pluginOptions = {
|
package/src/types.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {FieldMember, FieldsetState, ObjectSchemaType} from 'sanity'
|
|
1
|
+
import {FieldMember, FieldsetState, ObjectSchemaType, SanityClient} from 'sanity'
|
|
2
2
|
|
|
3
3
|
export interface LanguageFilterOptions {
|
|
4
4
|
languageFilter?: boolean
|
|
@@ -8,11 +8,16 @@ export interface LanguageFilterSchema extends ObjectSchemaType {
|
|
|
8
8
|
options?: LanguageFilterOptions
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
export
|
|
12
|
-
id:
|
|
11
|
+
export type Language = {
|
|
12
|
+
id: Intl.UnicodeBCP47LocaleIdentifier
|
|
13
13
|
title: string
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
export type LanguageCallback = (
|
|
17
|
+
client: SanityClient,
|
|
18
|
+
selectedValue: Record<string, unknown>
|
|
19
|
+
) => Promise<Language[]>
|
|
20
|
+
|
|
16
21
|
export type FilterFieldFunction = (
|
|
17
22
|
enclosingType: ObjectSchemaType,
|
|
18
23
|
field: FieldMember | FieldsetState,
|
|
@@ -20,8 +25,17 @@ export type FilterFieldFunction = (
|
|
|
20
25
|
) => boolean
|
|
21
26
|
|
|
22
27
|
export interface LanguageFilterConfig {
|
|
23
|
-
supportedLanguages: Language[]
|
|
28
|
+
supportedLanguages: Language[] | LanguageCallback
|
|
24
29
|
defaultLanguages?: string[]
|
|
25
30
|
documentTypes?: string[]
|
|
26
31
|
filterField?: FilterFieldFunction
|
|
32
|
+
/**
|
|
33
|
+
* https://www.sanity.io/docs/api-versioning
|
|
34
|
+
* @defaultValue '2022-11-27'
|
|
35
|
+
*/
|
|
36
|
+
apiVersion?: string
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface LanguageFilterConfigProcessed extends LanguageFilterConfig {
|
|
40
|
+
supportedLanguages: Language[]
|
|
27
41
|
}
|
|
@@ -30,7 +30,9 @@ export function getSelectableLanguages({
|
|
|
30
30
|
supportedLanguages,
|
|
31
31
|
defaultLanguages,
|
|
32
32
|
}: LanguageFilterConfig): Language[] {
|
|
33
|
-
return
|
|
33
|
+
return Array.isArray(supportedLanguages)
|
|
34
|
+
? supportedLanguages.filter((lang) => !defaultLanguages?.includes(lang.id))
|
|
35
|
+
: []
|
|
34
36
|
}
|
|
35
37
|
|
|
36
38
|
export function useSelectedLanguageIds(
|