@sanity/document-internationalization 2.0.0-studio-v3-plugin-v2.4 → 2.0.0-studio-v3-plugin-v2.6

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/lib/index.js CHANGED
@@ -1 +1,654 @@
1
- "use strict";function e(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function t(t){for(var a=1;a<arguments.length;a++){var r=null!=arguments[a]?arguments[a]:{};a%2?e(Object(r),!0).forEach((function(e){n(t,e,r[e])})):Object.getOwnPropertyDescriptors?Object.defineProperties(t,Object.getOwnPropertyDescriptors(r)):e(Object(r)).forEach((function(e){Object.defineProperty(t,e,Object.getOwnPropertyDescriptor(r,e))}))}return t}function n(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 a=n.call(e,t||"default");if("object"!=typeof a)return a;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 a=require("react/jsx-runtime"),r=require("sanity"),i=require("sanity-plugin-internationalized-array"),s=require("@sanity/ui"),l=require("@sanity/icons"),o=require("react"),u=require("@sanity/uuid"),c=require("sanity/desk"),d=require("sanity/router"),p=require("sanity-plugin-utils");function g(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var f=g(o);const h="translation.metadata",y="2022-11-27";var m=e=>r.defineType({type:"document",name:h,title:"Translation metadata",icon:l.TranslateIcon,liveEdit:!0,fields:[r.defineField({name:"translations",type:"internationalizedArrayReference"}),r.defineField({name:"schemaTypes",description:"Used to filter the reference fields above so all translations share the same types.",type:"array",of:[{type:"string"}],options:{list:e},readOnly:e=>{let{value:t}=e;return Boolean(t)}})],preview:{select:{translations:"translations",documentSchemaTypes:"schemaTypes"},prepare(e){const{translations:t,documentSchemaTypes:n}=e,a=1===t.length?"1 Translation":"".concat(t.length," Translations"),r=t.length?t.map((e=>e._key.toUpperCase())).join(", "):"";return{title:a,subtitle:[r?"(".concat(r,")"):null,(null==n?void 0:n.length)?n.map((e=>e.toUpperCase())).join(", "):"No Schemas Defined"].filter(Boolean).join(" ")}}}});function v(e,t){let n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:(e,t)=>e===t;if(e===t)return!0;if(!e||!t)return!1;const a=e.length;if(t.length!==a)return!1;for(let r=0;r<a;r++)if(!n(e[r],t[r]))return!1;return!0}const b=[];const j=(e,t,n)=>function(e,t){let n=arguments.length>2&&void 0!==arguments[2]&&arguments[2],a=arguments.length>3&&void 0!==arguments[3]?arguments[3]:{};for(const e of b)if(v(t,e.keys,e.equal)){if(n)return;if(Object.prototype.hasOwnProperty.call(e,"error"))throw e.error;if(Object.prototype.hasOwnProperty.call(e,"response"))return e.response;if(!n)throw e.promise}const r={keys:t,equal:a.equal,promise:e(...t).then((e=>r.response=e)).then((()=>{a.lifespan&&a.lifespan>0&&setTimeout((()=>{const e=b.indexOf(r);-1!==e&&b.splice(e,1)}),a.lifespan)})).catch((e=>r.error=e))};if(b.push(r),!n)throw r.promise}(e,t,!1,n);function x(e,t){const n=f.default.useContext(d.RouterContext),{routerPanesState:a,groupIndex:r}=c.usePaneRouter();return f.default.useCallback((()=>{if(!n||!e||!t)return;const i=[...a];i.splice(r+1,0,[{id:e,params:{type:t}}]);const s=n.resolvePathFromState({panes:i});n.navigateUrl({path:s})}),[e,t,n,a,r])}function T(e,t,n){return{_key:e,value:{_type:"reference",_ref:t,_weak:!0,_strengthenOnPublish:{type:n}}}}function _(e){var n;const{apiVersion:i=y,index:c,language:d,languageField:p,schemaType:g,documentId:f,disabled:m,current:v,sourceId:b,sourceLanguageId:j,metadata:_,translation:k}=e,C=r.useClient({apiVersion:i}),O=s.useToast(),I=x(null==(n=null==k?void 0:k.value)?void 0:n._ref,g),S=o.useCallback((()=>I()),[I]),w=o.useCallback((async()=>{var e;const n=Boolean(null==_?void 0:_._id),a=C.transaction(),r=f.startsWith("drafts.")?[f,f.replace("drafts.","")]:[f,"drafts.".concat(f)],i=t(t({},await C.fetch("*[_id in $ids]|order(_updatedAt desc)[0]",{ids:r})),{},{_id:"drafts.".concat(u.uuid()),[p]:d.id});a.create(i);const s=null!=(e=null==_?void 0:_._id)?e:u.uuid(),l=T(d.id,i._id.replace("drafts.",""),g);if(n){const e="translations[".concat(c-1,"]"),t=C.patch(s).setIfMissing({translations:[]}).insert("before",e,[l]);a.patch(t)}else{const e=j?T(j,b,g):null;a.createIfNotExists({_id:s,_type:h,schemaTypes:[g],translations:[l,e].filter(Boolean)})}a.commit().then((()=>O.push({status:"success",title:"Created ".concat(d.title," translation"),description:n?"Updated Translations Metadata":"Created Translations Metadata"}))).catch((e=>(console.error(e),O.push({status:"error",title:"Error creating translation",description:e.message}))))}),[C,f,c,d,p,null==_?void 0:_._id,g,b,j,O]);return a.jsx(s.Button,{onClick:k?S:w,mode:v?"default":"bleed",disabled:m||v,children:a.jsxs(s.Flex,{gap:3,align:"center",children:[m?a.jsx(s.Spinner,{}):a.jsx(s.Text,{size:2,children:k?a.jsx(l.SplitVerticalIcon,{}):v?a.jsx(l.CheckmarkIcon,{}):a.jsx(l.AddIcon,{})}),a.jsx(s.Box,{flex:1,children:a.jsx(s.Text,{children:d.title})}),a.jsx(s.Badge,{tone:m||v?"default":"primary",children:d.id})]})})}function k(e){const{id:t}=e,n=x(t,h);return a.jsx(s.Button,{disabled:!t,mode:"ghost",text:"Manage Translations",icon:l.CogIcon,onClick:()=>n()})}function C(e){const{apiVersion:t=y,language:n,languageField:i,documentId:o,schemaType:u,source:c,disabled:d=!1}=e,p=r.useClient({apiVersion:t}),g=s.useToast(),h=f.default.useCallback((()=>{const e=c?c._id:"draft.".concat(o),t=p.transaction();c||t.createIfNotExists({_id:e,_type:u});const a=p.patch(e).set({[i]:n.id});t.patch(a),t.commit().then((()=>{g.push({title:"Set document language to ".concat(n.title),status:"success"})})).catch((e=>(console.error(e),g.push({title:"Failed to set document language to ".concat(n.title),status:"error"}))))}),[c,o,p,i,n,u,g]);return a.jsx(s.Button,{mode:"ghost",text:n.title,icon:l.ChevronRightIcon,onClick:()=>h(),disabled:d,justify:"flex-start"})}function O(e){const{apiVersion:t=y,schemaType:n,documentId:i,languageField:u}=e,c=r.useClient({apiVersion:t}),d=Array.isArray(e.supportedLanguages)?e.supportedLanguages:j((async()=>"function"==typeof e.supportedLanguages?e.supportedLanguages(c):e.supportedLanguages),[]),[g,m]=o.useState(!1),v=o.useCallback((()=>m((e=>!e))),[]),[b,x]=o.useState(null),[T,O]=o.useState(null),I=o.useCallback((()=>m(!1)),[]);s.useClickOutside(I,[b,T]);const{data:S,loading:w,error:P}=function(e,t){const{data:n,loading:a,error:r}=p.useListeningQuery("*[_type == $translationSchema && $id in translations[].value._ref][0]",{params:{id:e,translationSchema:h}});return{data:n,loading:a,error:r}}(i),{draft:F,published:q}=r.useEditState(i,n),L=F||q,A=null==L?void 0:L[u],z=d.some((e=>e.id===A)),B=f.default.useMemo((()=>{const e=d.every((e=>e.id&&e.title));return e||console.warn('Not all languages are valid. It should be an array of objects with an "id" and "title" property. Or a function that returns an array of objects with an "id" and "title" property.',d),e}),[d]),D=a.jsx(s.Box,{overflow:"auto",children:P?a.jsx(s.Card,{tone:"critical",padding:2,children:a.jsxs(s.Text,{children:["Error: ",P]})}):a.jsxs(s.Stack,{padding:1,space:1,children:[d.length>0?a.jsxs(a.Fragment,{children:[d.map(((e,t)=>{var r;return!w&&A&&z?a.jsx(_,{index:t,language:e,languageField:u,schemaType:n,documentId:i,disabled:w||!B,current:e.id===A,metadata:S,sourceId:i,sourceLanguageId:A,translation:null==S?void 0:S.translations.find((t=>t._key===e.id))},e.id||e.title||"lang-".concat(t)):a.jsx(C,{languageField:u,source:L,documentId:i,schemaType:n,language:e,disabled:null!=(r=!B||(null==S?void 0:S.translations.filter((e=>{var t;return(null==(t=null==e?void 0:e.value)?void 0:t._ref)!==i})).some((t=>t._key===e.id))))&&r},e.id||e.title||"lang-".concat(t))})),w?null:a.jsxs(a.Fragment,{children:[B?null:a.jsx(s.Card,{tone:"caution",padding:3,children:a.jsx(s.Text,{size:1,children:"Not all language objects are valid. See the console."})}),A?null:a.jsx(s.Card,{tone:"caution",padding:3,children:a.jsxs(s.Text,{size:1,children:["Choose a language to ",a.jsx("br",{}),"apply to ",a.jsx("strong",{children:"this Document"})]})}),A&&!z?a.jsx(s.Card,{tone:"caution",padding:3,children:a.jsxs(s.Text,{size:1,children:["Change the current language value ",a.jsx("code",{children:A}),a.jsx("br",{}),"to one of the supported languages"]})}):null]})]}):null,a.jsx(k,{id:null==S?void 0:S._id})]})});return a.jsx(s.Popover,{constrainSize:!0,content:D,open:g,portal:!0,ref:O,children:a.jsx(s.Button,{text:"Translations",mode:"bleed",disabled:!L,tone:!L||!w&&A&&z?void 0:"caution",icon:l.TranslateIcon,onClick:v,ref:x,selected:g})})}const I={supportedLanguages:[],schemaTypes:[],languageField:"language",bulkPublish:!1},S=r.definePlugin((e=>{const{supportedLanguages:n,schemaTypes:l,languageField:o,bulkPublish:u}=t(t({},I),e);return{name:"@sanity/document-internationalization",form:{components:{input:e=>u&&"root"===e.id&&e.schemaType.name===h?a.jsx(s.Stack,{space:5,children:e.renderDefault(e)}):e.renderDefault(e)}},document:{unstable_languageFilter:(e,t)=>{const{schemaType:r,documentId:i}=t;return l.includes(r)?[...e,()=>((e,t)=>a.jsx(O,{supportedLanguages:n,schemaType:e,documentId:null!=t?t:"",languageField:o}))(r,i)]:e},badges:(e,t)=>{let{schemaType:a}=t;return l.includes(a)?[e=>function(e,t,n){var a,r;const i=(null==e?void 0:e.draft)||(null==e?void 0:e.published),s=null==i?void 0:i[n];if(!s)return null;const l=Array.isArray(t)?t.find((e=>e.id===s)):null;return{label:null!=(a=null==l?void 0:l.id)?a:String(s),title:null!=(r=null==l?void 0:l.title)?r:void 0,color:"primary"}}(e,n,o),...e]:e}},schema:{types:[m(l)],templates:(e,t)=>{let{schema:a}=t;if(!Array.isArray(n))return e;const r=l.map((e=>{var t,n;return{id:"".concat(e,"-parameterized"),title:"".concat(null!=(n=null==(t=null==a?void 0:a.get(e))?void 0:t.title)?n:e,": with Language"),schemaType:e,parameters:[{name:"languageId",title:"Language ID",type:"string"}],value:e=>{let{languageId:t}=e;return{[o]:t}}}})),i=l.flatMap((e=>n.map((t=>{var n,r;return{id:"".concat(e,"-").concat(t.id),title:"".concat(t.title," ").concat(null!=(r=null==(n=null==a?void 0:a.get(e))?void 0:n.title)?r:e),schemaType:e,value:{[o]:t.id}}}))));return[...e,...r,...i]}},plugins:[i.internationalizedArray({languages:n,fieldTypes:[r.defineField({name:"reference",type:"reference",to:l.map((e=>({type:e}))),options:{collapsed:!1,filter:e=>{let{parent:t,document:n}=e;if(!t)return null;const a=(Array.isArray(t)?t:[t]).find((e=>e._key));return(null==a?void 0:a._key)?n.schemaTypes?{filter:"_type in $schemaTypes && ".concat(o," == $language"),params:{schemaTypes:n.schemaTypes,language:a._key}}:{filter:"".concat(o," == $language"),params:{language:a._key}}:null}}},{strict:!1})]})]}}));exports.documentInternationalization=S;//# sourceMappingURL=index.js.map
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', {
4
+ value: true
5
+ });
6
+ var jsxRuntime = require('react/jsx-runtime');
7
+ var sanity = require('sanity');
8
+ var sanityPluginInternationalizedArray = require('sanity-plugin-internationalized-array');
9
+ var ui = require('@sanity/ui');
10
+ var icons = require('@sanity/icons');
11
+ var React = require('react');
12
+ var uuid = require('@sanity/uuid');
13
+ var desk = require('sanity/desk');
14
+ var router = require('sanity/router');
15
+ var sanityPluginUtils = require('sanity-plugin-utils');
16
+ function _interopDefaultCompat(e) {
17
+ return e && typeof e === 'object' && 'default' in e ? e : {
18
+ default: e
19
+ };
20
+ }
21
+ var React__default = /*#__PURE__*/_interopDefaultCompat(React);
22
+ const METADATA_SCHEMA_NAME = "translation.metadata";
23
+ const API_VERSION = "2022-11-27";
24
+ var metadata = schemaTypes => sanity.defineType({
25
+ type: "document",
26
+ name: METADATA_SCHEMA_NAME,
27
+ title: "Translation metadata",
28
+ icon: icons.TranslateIcon,
29
+ liveEdit: true,
30
+ fields: [sanity.defineField({
31
+ name: "translations",
32
+ type: "internationalizedArrayReference"
33
+ }), sanity.defineField({
34
+ name: "schemaTypes",
35
+ description: "Used to filter the reference fields above so all translations share the same types.",
36
+ type: "array",
37
+ // For some reason TS dislikes this line because of the DocumentDefinition return type
38
+ // @ts-expect-error
39
+ of: [{
40
+ type: "string"
41
+ }],
42
+ options: {
43
+ list: schemaTypes
44
+ },
45
+ readOnly: _ref => {
46
+ let {
47
+ value
48
+ } = _ref;
49
+ return Boolean(value);
50
+ }
51
+ })],
52
+ preview: {
53
+ select: {
54
+ translations: "translations",
55
+ documentSchemaTypes: "schemaTypes"
56
+ },
57
+ prepare(selection) {
58
+ const {
59
+ translations,
60
+ documentSchemaTypes
61
+ } = selection;
62
+ const title = translations.length === 1 ? "1 Translation" : "".concat(translations.length, " Translations");
63
+ const languageKeys = translations.length ? translations.map(t => t._key.toUpperCase()).join(", ") : "";
64
+ const subtitle = [languageKeys ? "(".concat(languageKeys, ")") : null, (documentSchemaTypes == null ? void 0 : documentSchemaTypes.length) ? documentSchemaTypes.map(s => s.toUpperCase()).join(", ") : "No Schemas Defined"].filter(Boolean).join(" ");
65
+ return {
66
+ title,
67
+ subtitle
68
+ };
69
+ }
70
+ }
71
+ });
72
+ function shallowEqualArrays(arrA, arrB) {
73
+ let equal = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : (a, b) => a === b;
74
+ if (arrA === arrB) return true;
75
+ if (!arrA || !arrB) return false;
76
+ const len = arrA.length;
77
+ if (arrB.length !== len) return false;
78
+ for (let i = 0; i < len; i++) if (!equal(arrA[i], arrB[i])) return false;
79
+ return true;
80
+ }
81
+ const globalCache = [];
82
+ function query(fn, keys) {
83
+ let preload = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
84
+ let config = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
85
+ for (const entry of globalCache) {
86
+ // Find a match
87
+ if (shallowEqualArrays(keys, entry.keys, entry.equal)) {
88
+ // If we're pre-loading and the element is present, just return
89
+ if (preload) return undefined; // If an error occurred, throw
90
+
91
+ if (Object.prototype.hasOwnProperty.call(entry, 'error')) throw entry.error; // If a response was successful, return
92
+
93
+ if (Object.prototype.hasOwnProperty.call(entry, 'response')) return entry.response; // If the promise is still unresolved, throw
94
+
95
+ if (!preload) throw entry.promise;
96
+ }
97
+ } // The request is new or has changed.
98
+
99
+ const entry = {
100
+ keys,
101
+ equal: config.equal,
102
+ promise:
103
+ // Execute the promise
104
+ fn(...keys) // When it resolves, store its value
105
+ .then(response => entry.response = response) // Remove the entry if a lifespan was given
106
+ .then(() => {
107
+ if (config.lifespan && config.lifespan > 0) {
108
+ setTimeout(() => {
109
+ const index = globalCache.indexOf(entry);
110
+ if (index !== -1) globalCache.splice(index, 1);
111
+ }, config.lifespan);
112
+ }
113
+ }) // Store caught errors, they will be thrown in the render-phase to bubble into an error-bound
114
+ .catch(error => entry.error = error)
115
+ }; // Register the entry
116
+
117
+ globalCache.push(entry); // And throw the promise, this yields control back to React
118
+
119
+ if (!preload) throw entry.promise;
120
+ return undefined;
121
+ }
122
+ const suspend = (fn, keys, config) => query(fn, keys, false, config);
123
+ function useOpenInNewPane(id, type) {
124
+ const routerContext = React__default.default.useContext(router.RouterContext);
125
+ const {
126
+ routerPanesState,
127
+ groupIndex
128
+ } = desk.usePaneRouter();
129
+ const openInNewPane = React__default.default.useCallback(() => {
130
+ if (!routerContext || !id || !type) {
131
+ return;
132
+ }
133
+ const panes = [...routerPanesState];
134
+ panes.splice(groupIndex + 1, 0, [{
135
+ id,
136
+ params: {
137
+ type
138
+ }
139
+ }]);
140
+ const href = routerContext.resolvePathFromState({
141
+ panes
142
+ });
143
+ routerContext.navigateUrl({
144
+ path: href
145
+ });
146
+ }, [id, type, routerContext, routerPanesState, groupIndex]);
147
+ return openInNewPane;
148
+ }
149
+ function createReference(key, ref, type) {
150
+ return {
151
+ _key: key,
152
+ value: {
153
+ _type: "reference",
154
+ _ref: ref,
155
+ _weak: true,
156
+ _strengthenOnPublish: {
157
+ type
158
+ }
159
+ }
160
+ };
161
+ }
162
+ function LanguageOption(props) {
163
+ var _a;
164
+ const {
165
+ apiVersion = API_VERSION,
166
+ index,
167
+ language,
168
+ languageField,
169
+ schemaType,
170
+ documentId,
171
+ disabled,
172
+ current,
173
+ sourceId,
174
+ sourceLanguageId,
175
+ metadata,
176
+ translation
177
+ } = props;
178
+ const client = sanity.useClient({
179
+ apiVersion
180
+ });
181
+ const toast = ui.useToast();
182
+ const open = useOpenInNewPane((_a = translation == null ? void 0 : translation.value) == null ? void 0 : _a._ref, schemaType);
183
+ const handleOpen = React.useCallback(() => open(), [open]);
184
+ const handleCreate = React.useCallback(async () => {
185
+ var _a2;
186
+ const metadataExists = Boolean(metadata == null ? void 0 : metadata._id);
187
+ const transaction = client.transaction();
188
+ const documentIds = documentId.startsWith("drafts.") ? [documentId, documentId.replace("drafts.", "")] : [documentId, "drafts.".concat(documentId)];
189
+ const latestDocument = await client.fetch("*[_id in $ids]|order(_updatedAt desc)[0]", {
190
+ ids: documentIds
191
+ });
192
+ const newTranslationDocument = {
193
+ ...latestDocument,
194
+ _id: "drafts.".concat(uuid.uuid()),
195
+ [languageField]: language.id
196
+ };
197
+ transaction.create(newTranslationDocument);
198
+ const metadataId = (_a2 = metadata == null ? void 0 : metadata._id) != null ? _a2 : uuid.uuid();
199
+ const newTranslationReference = createReference(language.id, newTranslationDocument._id.replace("drafts.", ""), schemaType);
200
+ if (metadataExists) {
201
+ const path = "translations[".concat(index - 1, "]");
202
+ const metadataPatch = client.patch(metadataId).setIfMissing({
203
+ translations: []
204
+ }).insert("before", path, [newTranslationReference]);
205
+ transaction.patch(metadataPatch);
206
+ } else {
207
+ const sourceReference = sourceLanguageId ? createReference(sourceLanguageId, sourceId, schemaType) : null;
208
+ transaction.createIfNotExists({
209
+ _id: metadataId,
210
+ _type: METADATA_SCHEMA_NAME,
211
+ schemaTypes: [schemaType],
212
+ translations: [newTranslationReference, sourceReference].filter(Boolean)
213
+ });
214
+ }
215
+ transaction.commit().then(() => {
216
+ return toast.push({
217
+ status: "success",
218
+ title: "Created ".concat(language.title, " translation"),
219
+ description: metadataExists ? "Updated Translations Metadata" : "Created Translations Metadata"
220
+ });
221
+ }).catch(err => {
222
+ console.error(err);
223
+ return toast.push({
224
+ status: "error",
225
+ title: "Error creating translation",
226
+ description: err.message
227
+ });
228
+ });
229
+ }, [client, documentId, index, language, languageField, metadata == null ? void 0 : metadata._id, schemaType, sourceId, sourceLanguageId, toast]);
230
+ return /* @__PURE__ */jsxRuntime.jsx(ui.Button, {
231
+ onClick: translation ? handleOpen : handleCreate,
232
+ mode: current ? "default" : "bleed",
233
+ disabled: disabled || current,
234
+ children: /* @__PURE__ */jsxRuntime.jsxs(ui.Flex, {
235
+ gap: 3,
236
+ align: "center",
237
+ children: [disabled ? /* @__PURE__ */jsxRuntime.jsx(ui.Spinner, {}) : /* @__PURE__ */jsxRuntime.jsx(ui.Text, {
238
+ size: 2,
239
+ children: translation ? /* @__PURE__ */jsxRuntime.jsx(icons.SplitVerticalIcon, {}) : current ? /* @__PURE__ */jsxRuntime.jsx(icons.CheckmarkIcon, {}) : /* @__PURE__ */jsxRuntime.jsx(icons.AddIcon, {})
240
+ }), /* @__PURE__ */jsxRuntime.jsx(ui.Box, {
241
+ flex: 1,
242
+ children: /* @__PURE__ */jsxRuntime.jsx(ui.Text, {
243
+ children: language.title
244
+ })
245
+ }), /* @__PURE__ */jsxRuntime.jsx(ui.Badge, {
246
+ tone: disabled || current ? "default" : "primary",
247
+ children: language.id
248
+ })]
249
+ })
250
+ });
251
+ }
252
+ function useTranslationMetadata(id, schemaType) {
253
+ const query = "*[_type == $translationSchema && $id in translations[].value._ref][0]";
254
+ const {
255
+ data,
256
+ loading,
257
+ error
258
+ } = sanityPluginUtils.useListeningQuery(query, {
259
+ params: {
260
+ id,
261
+ translationSchema: METADATA_SCHEMA_NAME
262
+ }
263
+ });
264
+ return {
265
+ data,
266
+ loading,
267
+ error
268
+ };
269
+ }
270
+ function LanguageManage(props) {
271
+ const {
272
+ id
273
+ } = props;
274
+ const open = useOpenInNewPane(id, METADATA_SCHEMA_NAME);
275
+ return /* @__PURE__ */jsxRuntime.jsx(ui.Button, {
276
+ disabled: !id,
277
+ mode: "ghost",
278
+ text: "Manage Translations",
279
+ icon: icons.CogIcon,
280
+ onClick: () => open()
281
+ });
282
+ }
283
+ function LanguagePatch(props) {
284
+ const {
285
+ apiVersion = API_VERSION,
286
+ language,
287
+ languageField,
288
+ documentId,
289
+ schemaType,
290
+ source,
291
+ disabled = false
292
+ } = props;
293
+ const client = sanity.useClient({
294
+ apiVersion
295
+ });
296
+ const toast = ui.useToast();
297
+ const handleClick = React__default.default.useCallback(() => {
298
+ const currentId = source ? source._id : "draft.".concat(documentId);
299
+ const transaction = client.transaction();
300
+ if (!source) {
301
+ transaction.createIfNotExists({
302
+ _id: currentId,
303
+ _type: schemaType
304
+ });
305
+ }
306
+ const patch = client.patch(currentId).set({
307
+ [languageField]: language.id
308
+ });
309
+ transaction.patch(patch);
310
+ transaction.commit().then(() => {
311
+ toast.push({
312
+ title: "Set document language to ".concat(language.title),
313
+ status: "success"
314
+ });
315
+ }).catch(err => {
316
+ console.error(err);
317
+ return toast.push({
318
+ title: "Failed to set document language to ".concat(language.title),
319
+ status: "error"
320
+ });
321
+ });
322
+ }, [source, documentId, client, languageField, language, schemaType, toast]);
323
+ return /* @__PURE__ */jsxRuntime.jsx(ui.Button, {
324
+ mode: "ghost",
325
+ text: language.title,
326
+ icon: icons.ChevronRightIcon,
327
+ onClick: () => handleClick(),
328
+ disabled,
329
+ justify: "flex-start"
330
+ });
331
+ }
332
+ function MenuButton(props) {
333
+ const {
334
+ apiVersion = API_VERSION,
335
+ schemaType,
336
+ documentId,
337
+ languageField
338
+ } = props;
339
+ const client = sanity.useClient({
340
+ apiVersion
341
+ });
342
+ const supportedLanguages = Array.isArray(props.supportedLanguages) ? props.supportedLanguages :
343
+ // eslint-disable-next-line require-await
344
+ suspend(async () => {
345
+ if (typeof props.supportedLanguages === "function") {
346
+ return props.supportedLanguages(client);
347
+ }
348
+ return props.supportedLanguages;
349
+ }, []);
350
+ const [open, setOpen] = React.useState(false);
351
+ const handleClick = React.useCallback(() => setOpen(o => !o), []);
352
+ const [button, setButton] = React.useState(null);
353
+ const [popover, setPopover] = React.useState(null);
354
+ const handleClickOutside = React.useCallback(() => setOpen(false), []);
355
+ ui.useClickOutside(handleClickOutside, [button, popover]);
356
+ const {
357
+ data: metadata,
358
+ loading,
359
+ error
360
+ } = useTranslationMetadata(documentId);
361
+ const {
362
+ draft,
363
+ published
364
+ } = sanity.useEditState(documentId, schemaType);
365
+ const source = draft || published;
366
+ const sourceLanguageId = source == null ? void 0 : source[languageField];
367
+ const sourceLanguageIsValid = supportedLanguages.some(l => l.id === sourceLanguageId);
368
+ const allLanguagesAreValid = React__default.default.useMemo(() => {
369
+ const valid = supportedLanguages.every(l => l.id && l.title);
370
+ if (!valid) {
371
+ console.warn("Not all languages are valid. It should be an array of objects with an \"id\" and \"title\" property. Or a function that returns an array of objects with an \"id\" and \"title\" property.", supportedLanguages);
372
+ }
373
+ return valid;
374
+ }, [supportedLanguages]);
375
+ const content = /* @__PURE__ */jsxRuntime.jsx(ui.Box, {
376
+ children: error ? /* @__PURE__ */jsxRuntime.jsx(ui.Card, {
377
+ tone: "critical",
378
+ padding: 2,
379
+ children: /* @__PURE__ */jsxRuntime.jsxs(ui.Text, {
380
+ children: ["Error: ", error]
381
+ })
382
+ }) : /* @__PURE__ */jsxRuntime.jsxs(ui.Stack, {
383
+ padding: 1,
384
+ space: 1,
385
+ children: [supportedLanguages.length > 0 ? /* @__PURE__ */jsxRuntime.jsxs(jsxRuntime.Fragment, {
386
+ children: [supportedLanguages.map((language, langIndex) => {
387
+ var _a;
388
+ return !loading && sourceLanguageId && sourceLanguageIsValid ?
389
+ // Button to duplicate this document to a new translation
390
+ // And either create or update the metadata document
391
+ /* @__PURE__ */
392
+ jsxRuntime.jsx(LanguageOption, {
393
+ index: langIndex,
394
+ language,
395
+ languageField,
396
+ schemaType,
397
+ documentId,
398
+ disabled: loading || !allLanguagesAreValid,
399
+ current: language.id === sourceLanguageId,
400
+ metadata,
401
+ sourceId: documentId,
402
+ sourceLanguageId,
403
+ translation: metadata == null ? void 0 : metadata.translations.find(t => t._key === language.id)
404
+ }, language.id || language.title || "lang-".concat(langIndex)) :
405
+ // Button to set a language field on *this* document
406
+ /* @__PURE__ */
407
+ jsxRuntime.jsx(LanguagePatch, {
408
+ languageField,
409
+ source,
410
+ documentId,
411
+ schemaType,
412
+ language,
413
+ disabled: (_a = !allLanguagesAreValid || (metadata == null ? void 0 : metadata.translations.filter(t => {
414
+ var _a2;
415
+ return ((_a2 = t == null ? void 0 : t.value) == null ? void 0 : _a2._ref) !== documentId;
416
+ }).some(t => t._key === language.id))) != null ? _a : false
417
+ }, language.id || language.title || "lang-".concat(langIndex));
418
+ }), loading ? null : /* @__PURE__ */jsxRuntime.jsxs(jsxRuntime.Fragment, {
419
+ children: [allLanguagesAreValid ? null : /* @__PURE__ */jsxRuntime.jsx(ui.Card, {
420
+ tone: "caution",
421
+ padding: 3,
422
+ children: /* @__PURE__ */jsxRuntime.jsx(ui.Text, {
423
+ size: 1,
424
+ children: "Not all language objects are valid. See the console."
425
+ })
426
+ }), sourceLanguageId ? null : /* @__PURE__ */jsxRuntime.jsx(ui.Card, {
427
+ tone: "caution",
428
+ padding: 3,
429
+ children: /* @__PURE__ */jsxRuntime.jsxs(ui.Text, {
430
+ size: 1,
431
+ children: ["Choose a language to ", /* @__PURE__ */jsxRuntime.jsx("br", {}), "apply to ", /* @__PURE__ */jsxRuntime.jsx("strong", {
432
+ children: "this Document"
433
+ })]
434
+ })
435
+ }), sourceLanguageId && !sourceLanguageIsValid ? /* @__PURE__ */jsxRuntime.jsx(ui.Card, {
436
+ tone: "caution",
437
+ padding: 3,
438
+ children: /* @__PURE__ */jsxRuntime.jsxs(ui.Text, {
439
+ size: 1,
440
+ children: ["Change the current language value ", /* @__PURE__ */jsxRuntime.jsx("code", {
441
+ children: sourceLanguageId
442
+ }), /* @__PURE__ */jsxRuntime.jsx("br", {}), "to one of the supported languages"]
443
+ })
444
+ }) : null]
445
+ })]
446
+ }) : null, /* @__PURE__ */jsxRuntime.jsx(LanguageManage, {
447
+ id: metadata == null ? void 0 : metadata._id
448
+ })]
449
+ })
450
+ });
451
+ return /* @__PURE__ */jsxRuntime.jsx(ui.Popover, {
452
+ constrainSize: true,
453
+ content,
454
+ open,
455
+ portal: true,
456
+ ref: setPopover,
457
+ overflow: "auto",
458
+ children: /* @__PURE__ */jsxRuntime.jsx(ui.Button, {
459
+ text: "Translations",
460
+ mode: "bleed",
461
+ disabled: !source,
462
+ tone: !source || !loading && sourceLanguageId && sourceLanguageIsValid ? void 0 : "caution",
463
+ icon: icons.TranslateIcon,
464
+ onClick: handleClick,
465
+ ref: setButton,
466
+ selected: open
467
+ })
468
+ });
469
+ }
470
+ function LanguageBadge(props, supportedLanguages, languageField) {
471
+ var _a, _b;
472
+ const source = (props == null ? void 0 : props.draft) || (props == null ? void 0 : props.published);
473
+ const languageId = source == null ? void 0 : source[languageField];
474
+ if (!languageId) {
475
+ return null;
476
+ }
477
+ const language = Array.isArray(supportedLanguages) ? supportedLanguages.find(l => l.id === languageId) : null;
478
+ return {
479
+ label: (_a = language == null ? void 0 : language.id) != null ? _a : String(languageId),
480
+ title: (_b = language == null ? void 0 : language.title) != null ? _b : void 0,
481
+ color: "primary"
482
+ };
483
+ }
484
+ const DEFAULT_CONFIG = {
485
+ supportedLanguages: [],
486
+ schemaTypes: [],
487
+ languageField: "language",
488
+ bulkPublish: false
489
+ };
490
+ const documentInternationalization = sanity.definePlugin(config => {
491
+ const {
492
+ supportedLanguages,
493
+ schemaTypes,
494
+ languageField,
495
+ bulkPublish
496
+ } = {
497
+ ...DEFAULT_CONFIG,
498
+ ...config
499
+ };
500
+ const renderLanguageFilter = (schemaType, documentId) => {
501
+ return /* @__PURE__ */jsxRuntime.jsx(MenuButton, {
502
+ supportedLanguages,
503
+ schemaType,
504
+ documentId: documentId != null ? documentId : "",
505
+ languageField
506
+ });
507
+ };
508
+ return {
509
+ name: "@sanity/document-internationalization",
510
+ // Adds:
511
+ // - A bulk-publishing UI component to the form
512
+ // - Will only work for projects on a compatible plan
513
+ form: {
514
+ components: {
515
+ input: props => {
516
+ if (bulkPublish && props.id === "root" && props.schemaType.name === METADATA_SCHEMA_NAME) {
517
+ return /* @__PURE__ */jsxRuntime.jsx(ui.Stack, {
518
+ space: 5,
519
+ children: props.renderDefault(props)
520
+ });
521
+ }
522
+ return props.renderDefault(props);
523
+ }
524
+ }
525
+ },
526
+ // Adds:
527
+ // - The `Translations` dropdown to the editing form
528
+ // - `Badges` to documents with a language value
529
+ document: {
530
+ unstable_languageFilter: (prev, ctx) => {
531
+ const {
532
+ schemaType,
533
+ documentId
534
+ } = ctx;
535
+ return schemaTypes.includes(schemaType) ? [...prev, () => renderLanguageFilter(schemaType, documentId)] : prev;
536
+ },
537
+ badges: (prev, _ref2) => {
538
+ let {
539
+ schemaType
540
+ } = _ref2;
541
+ if (!schemaTypes.includes(schemaType)) {
542
+ return prev;
543
+ }
544
+ return [props => LanguageBadge(props, supportedLanguages, languageField), ...prev];
545
+ }
546
+ },
547
+ // Adds:
548
+ // - The `Translations metadata` document type to the schema
549
+ schema: {
550
+ // Create the metadata document type
551
+ types: [metadata(schemaTypes)],
552
+ // For every schema type this plugin is enabled on
553
+ // Create an initial value template to set the language
554
+ templates: (prev, _ref3) => {
555
+ let {
556
+ schema
557
+ } = _ref3;
558
+ if (!Array.isArray(supportedLanguages)) {
559
+ return prev;
560
+ }
561
+ const parameterizedTemplates = schemaTypes.map(schemaType => {
562
+ var _a, _b;
563
+ return {
564
+ id: "".concat(schemaType, "-parameterized"),
565
+ title: "".concat((_b = (_a = schema == null ? void 0 : schema.get(schemaType)) == null ? void 0 : _a.title) != null ? _b : schemaType, ": with Language"),
566
+ schemaType,
567
+ parameters: [{
568
+ name: "languageId",
569
+ title: "Language ID",
570
+ type: "string"
571
+ }],
572
+ value: _ref4 => {
573
+ let {
574
+ languageId
575
+ } = _ref4;
576
+ return {
577
+ [languageField]: languageId
578
+ };
579
+ }
580
+ };
581
+ });
582
+ const staticTemplates = schemaTypes.flatMap(schemaType => {
583
+ return supportedLanguages.map(language => {
584
+ var _a, _b;
585
+ return {
586
+ id: "".concat(schemaType, "-").concat(language.id),
587
+ title: "".concat(language.title, " ").concat((_b = (_a = schema == null ? void 0 : schema.get(schemaType)) == null ? void 0 : _a.title) != null ? _b : schemaType),
588
+ schemaType,
589
+ value: {
590
+ [languageField]: language.id
591
+ }
592
+ };
593
+ });
594
+ });
595
+ return [...prev, ...parameterizedTemplates, ...staticTemplates];
596
+ }
597
+ },
598
+ // Uses:
599
+ // - `sanity-plugin-internationalized-array` to maintain the translations array
600
+ plugins: [
601
+ // Translation metadata stores its references using this plugin
602
+ // It cuts down on attribute usage and gives UI conveniences to add new translations
603
+ sanityPluginInternationalizedArray.internationalizedArray({
604
+ languages: supportedLanguages,
605
+ fieldTypes: [
606
+ // TODO: The plugin should allow this kind of input
607
+ // @ts-ignore
608
+ sanity.defineField({
609
+ name: "reference",
610
+ type: "reference",
611
+ to: schemaTypes.map(type => ({
612
+ type
613
+ })),
614
+ // TODO: Add a validation rule to *ensure* the document's language matches the array key
615
+ // Reference filters don't actually enforce validation!
616
+ // validation: (Rule) => Rule.custom(),
617
+ options: {
618
+ collapsed: false,
619
+ // TODO: Update type once it knows the values of this filter
620
+ // @ts-ignore
621
+ filter: _ref5 => {
622
+ let {
623
+ parent,
624
+ document
625
+ } = _ref5;
626
+ if (!parent) return null;
627
+ const parentArray = Array.isArray(parent) ? parent : [parent];
628
+ const language = parentArray.find(p => p._key);
629
+ if (!(language == null ? void 0 : language._key)) return null;
630
+ if (document.schemaTypes) {
631
+ return {
632
+ filter: "_type in $schemaTypes && ".concat(languageField, " == $language"),
633
+ params: {
634
+ schemaTypes: document.schemaTypes,
635
+ language: language._key
636
+ }
637
+ };
638
+ }
639
+ return {
640
+ filter: "".concat(languageField, " == $language"),
641
+ params: {
642
+ language: language._key
643
+ }
644
+ };
645
+ }
646
+ }
647
+ }, {
648
+ strict: false
649
+ })]
650
+ })]
651
+ };
652
+ });
653
+ exports.documentInternationalization = documentInternationalization;
654
+ //# sourceMappingURL=index.js.map