sanity-plugin-internationalized-array 3.1.4 → 3.1.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.esm.js +161 -22
- package/lib/index.esm.js.map +1 -1
- package/lib/index.js +160 -21
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +161 -22
- package/lib/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/cache.ts +129 -1
- package/src/components/DocumentAddButtons.tsx +71 -3
- package/src/components/Feedback.tsx +2 -1
- package/src/components/InternationalizedArray.tsx +2 -2
- package/src/components/InternationalizedArrayContext.tsx +38 -5
- package/src/components/InternationalizedInput.tsx +93 -1
- package/src/components/Preload.tsx +16 -6
- package/src/schema/array.ts +53 -28
package/lib/index.esm.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as suspend from "suspend-react";
|
|
2
2
|
import { suspend as suspend$1 } from "suspend-react";
|
|
3
3
|
import { jsx, jsxs, Fragment } from "react/jsx-runtime";
|
|
4
|
-
import { isSanityDocument, setIfMissing, insert, PatchEvent, useClient, useWorkspace, defineDocumentFieldAction, useFormValue, set, ArrayOfObjectsItem, MemberItemError, defineField, unset, isDocumentSchemaType, definePlugin, isObjectInputProps } from "sanity";
|
|
4
|
+
import { isSanityDocument, useSchema, setIfMissing, insert, PatchEvent, useClient, useWorkspace, defineDocumentFieldAction, useFormValue, set, ArrayOfObjectsItem, MemberItemError, defineField, unset, isDocumentSchemaType, definePlugin, isObjectInputProps } from "sanity";
|
|
5
5
|
import { useLanguageFilterStudioContext } from "@sanity/language-filter";
|
|
6
6
|
import { Grid, Button, useToast, Stack, Box, Text, Card, Code, Spinner, Label, MenuButton, Menu, MenuItem, Flex, Tooltip } from "@sanity/ui";
|
|
7
7
|
import equal from "fast-deep-equal";
|
|
@@ -9,7 +9,32 @@ import { memo, useCallback, createContext, useContext, useDeferredValue, useMemo
|
|
|
9
9
|
import { useDocumentPane } from "sanity/structure";
|
|
10
10
|
import { AddIcon, TranslateIcon, RemoveCircleIcon } from "@sanity/icons";
|
|
11
11
|
import get from "lodash/get.js";
|
|
12
|
-
const namespace = "sanity-plugin-internationalized-array", version = "v1",
|
|
12
|
+
const namespace = "sanity-plugin-internationalized-array", version = "v1", functionCache = /* @__PURE__ */ new Map(), functionKeyCache = /* @__PURE__ */ new WeakMap(), preloadWithKey = (fn, key) => suspend.preload(() => fn(), key), clear = () => suspend.clear([version, namespace]), peek = (selectedValue) => suspend.peek([version, namespace, selectedValue]), createCacheKey = (selectedValue, workspaceId) => {
|
|
13
|
+
const selectedValueHash = JSON.stringify(selectedValue);
|
|
14
|
+
return workspaceId ? [version, namespace, selectedValueHash, workspaceId] : [version, namespace, selectedValueHash];
|
|
15
|
+
}, getFunctionKey = (fn) => {
|
|
16
|
+
const cachedKey = functionKeyCache.get(fn);
|
|
17
|
+
if (cachedKey)
|
|
18
|
+
return cachedKey;
|
|
19
|
+
const fnStr = fn.toString();
|
|
20
|
+
let hash = 0;
|
|
21
|
+
const maxLength = Math.min(fnStr.length, 100);
|
|
22
|
+
for (let i = 0; i < maxLength; i++) {
|
|
23
|
+
const char = fnStr.charCodeAt(i);
|
|
24
|
+
hash = (hash << 5) - hash + char, hash &= hash;
|
|
25
|
+
}
|
|
26
|
+
const key = `anonymous_${Math.abs(hash)}`;
|
|
27
|
+
return functionKeyCache.set(fn, key), key;
|
|
28
|
+
}, createFunctionCacheKey = (fn, selectedValue, workspaceId) => {
|
|
29
|
+
const functionKey = getFunctionKey(fn), selectedValueHash = JSON.stringify(selectedValue);
|
|
30
|
+
return workspaceId ? `${functionKey}:${selectedValueHash}:${workspaceId}` : `${functionKey}:${selectedValueHash}`;
|
|
31
|
+
}, getFunctionCache = (fn, selectedValue, workspaceId) => {
|
|
32
|
+
const key = createFunctionCacheKey(fn, selectedValue, workspaceId);
|
|
33
|
+
return functionCache.get(key);
|
|
34
|
+
}, setFunctionCache = (fn, selectedValue, languages, workspaceId) => {
|
|
35
|
+
const key = createFunctionCacheKey(fn, selectedValue, workspaceId);
|
|
36
|
+
functionCache.set(key, languages);
|
|
37
|
+
}, MAX_COLUMNS = {
|
|
13
38
|
codeOnly: 5,
|
|
14
39
|
titleOnly: 4,
|
|
15
40
|
titleAndCode: 3
|
|
@@ -95,7 +120,43 @@ function AddButtons(props) {
|
|
|
95
120
|
}
|
|
96
121
|
var AddButtons$1 = memo(AddButtons);
|
|
97
122
|
function DocumentAddButtons(props) {
|
|
98
|
-
const { filteredLanguages } = useInternationalizedArrayContext(), value = isSanityDocument(props.value) ? props.value : void 0, toast = useToast(), { onChange } = useDocumentPane(), documentsToTranslation = getDocumentsToTranslate(value, []),
|
|
123
|
+
const { filteredLanguages } = useInternationalizedArrayContext(), value = isSanityDocument(props.value) ? props.value : void 0, toast = useToast(), { onChange } = useDocumentPane(), schema = useSchema(), documentsToTranslation = getDocumentsToTranslate(value, []), getInitialValueForType = useCallback(
|
|
124
|
+
(typeName) => {
|
|
125
|
+
var _a;
|
|
126
|
+
if (!typeName) return;
|
|
127
|
+
const match = typeName.match(/^internationalizedArray(.+)Value$/);
|
|
128
|
+
if (!match) return;
|
|
129
|
+
const baseTypeName = match[1].charAt(0).toLowerCase() + match[1].slice(1), arrayBasedTypes = [
|
|
130
|
+
"body",
|
|
131
|
+
"htmlContent",
|
|
132
|
+
"blockContent",
|
|
133
|
+
"portableText"
|
|
134
|
+
];
|
|
135
|
+
if (arrayBasedTypes.includes(baseTypeName))
|
|
136
|
+
return [];
|
|
137
|
+
try {
|
|
138
|
+
const schemaType = schema.get(typeName);
|
|
139
|
+
if (schemaType) {
|
|
140
|
+
const valueField = (_a = schemaType == null ? void 0 : schemaType.fields) == null ? void 0 : _a.find(
|
|
141
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
142
|
+
(f) => f.name === "value"
|
|
143
|
+
);
|
|
144
|
+
if (valueField) {
|
|
145
|
+
const fieldType = valueField.type;
|
|
146
|
+
if ((fieldType == null ? void 0 : fieldType.jsonType) === "array" || (fieldType == null ? void 0 : fieldType.name) === "array" || (fieldType == null ? void 0 : fieldType.type) === "array" || (fieldType == null ? void 0 : fieldType.of) !== void 0 || arrayBasedTypes.includes(fieldType == null ? void 0 : fieldType.name))
|
|
147
|
+
return [];
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
} catch (error) {
|
|
151
|
+
console.warn(
|
|
152
|
+
"Could not determine field type from schema:",
|
|
153
|
+
typeName,
|
|
154
|
+
error
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
[schema]
|
|
159
|
+
), handleDocumentButtonClick = useCallback(
|
|
99
160
|
async (event) => {
|
|
100
161
|
const languageId = event.currentTarget.value;
|
|
101
162
|
if (!languageId) {
|
|
@@ -121,12 +182,13 @@ function DocumentAddButtons(props) {
|
|
|
121
182
|
}
|
|
122
183
|
const patches = [];
|
|
123
184
|
for (const toTranslate of removeDuplicates) {
|
|
124
|
-
const path = toTranslate.path, ifMissing = setIfMissing([], path), insertValue = insert(
|
|
185
|
+
const path = toTranslate.path, initialValue = getInitialValueForType(toTranslate._type), ifMissing = setIfMissing([], path), insertValue = insert(
|
|
125
186
|
[
|
|
126
187
|
{
|
|
127
188
|
_key: languageId,
|
|
128
189
|
_type: toTranslate._type,
|
|
129
|
-
value:
|
|
190
|
+
value: initialValue
|
|
191
|
+
// Use the determined initial value instead of undefined
|
|
130
192
|
}
|
|
131
193
|
],
|
|
132
194
|
"after",
|
|
@@ -136,7 +198,7 @@ function DocumentAddButtons(props) {
|
|
|
136
198
|
}
|
|
137
199
|
onChange(PatchEvent.from(patches.flat()));
|
|
138
200
|
},
|
|
139
|
-
[documentsToTranslation, onChange, toast]
|
|
201
|
+
[documentsToTranslation, getInitialValueForType, onChange, toast]
|
|
140
202
|
);
|
|
141
203
|
return /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
|
|
142
204
|
/* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(Text, { size: 1, weight: "semibold", children: "Add translation to internationalized fields" }) }),
|
|
@@ -182,10 +244,36 @@ function InternationalizedArrayProvider(props) {
|
|
|
182
244
|
const { internationalizedArray: internationalizedArray2 } = props, client = useClient({ apiVersion: internationalizedArray2.apiVersion }), workspace = useWorkspace(), { formState } = useDocumentPane(), deferredDocument = useDeferredValue(formState == null ? void 0 : formState.value), selectedValue = useMemo(
|
|
183
245
|
() => getSelectedValue(internationalizedArray2.select, deferredDocument),
|
|
184
246
|
[internationalizedArray2.select, deferredDocument]
|
|
247
|
+
), workspaceId = useMemo(() => {
|
|
248
|
+
if (workspace != null && workspace.name)
|
|
249
|
+
return workspace.name;
|
|
250
|
+
const workspaceKey = {
|
|
251
|
+
name: workspace == null ? void 0 : workspace.name,
|
|
252
|
+
title: workspace == null ? void 0 : workspace.title
|
|
253
|
+
// Add other stable properties as needed
|
|
254
|
+
};
|
|
255
|
+
return JSON.stringify(workspaceKey);
|
|
256
|
+
}, [workspace]), cacheKey = useMemo(
|
|
257
|
+
() => createCacheKey(selectedValue, workspaceId),
|
|
258
|
+
[selectedValue, workspaceId]
|
|
185
259
|
), languages = Array.isArray(internationalizedArray2.languages) ? internationalizedArray2.languages : suspend$1(
|
|
186
260
|
// eslint-disable-next-line require-await
|
|
187
|
-
async () =>
|
|
188
|
-
|
|
261
|
+
async () => {
|
|
262
|
+
if (typeof internationalizedArray2.languages == "function") {
|
|
263
|
+
const result = await internationalizedArray2.languages(
|
|
264
|
+
client,
|
|
265
|
+
selectedValue
|
|
266
|
+
);
|
|
267
|
+
return setFunctionCache(
|
|
268
|
+
internationalizedArray2.languages,
|
|
269
|
+
selectedValue,
|
|
270
|
+
result,
|
|
271
|
+
workspaceId
|
|
272
|
+
), result;
|
|
273
|
+
}
|
|
274
|
+
return internationalizedArray2.languages;
|
|
275
|
+
},
|
|
276
|
+
cacheKey,
|
|
189
277
|
{ equal }
|
|
190
278
|
), { selectedLanguageIds, options: languageFilterOptions } = useLanguageFilterStudioContext(), filteredLanguages = useMemo(() => {
|
|
191
279
|
const documentType = deferredDocument ? deferredDocument._type : void 0;
|
|
@@ -227,10 +315,13 @@ function InternationalizedField(props) {
|
|
|
227
315
|
})) : customProps.renderDefault(customProps);
|
|
228
316
|
}
|
|
229
317
|
var Preload = memo(function(props) {
|
|
230
|
-
const client = useClient({ apiVersion: props.apiVersion });
|
|
231
|
-
return Array.isArray(peek({})) ||
|
|
232
|
-
|
|
233
|
-
|
|
318
|
+
const client = useClient({ apiVersion: props.apiVersion }), cacheKey = createCacheKey({});
|
|
319
|
+
return Array.isArray(peek({})) || preloadWithKey(async () => {
|
|
320
|
+
if (Array.isArray(props.languages))
|
|
321
|
+
return props.languages;
|
|
322
|
+
const result = await props.languages(client, {});
|
|
323
|
+
return setFunctionCache(props.languages, {}, result), result;
|
|
324
|
+
}, cacheKey), null;
|
|
234
325
|
});
|
|
235
326
|
function checkAllLanguagesArePresent(languages, value) {
|
|
236
327
|
const filteredLanguageIds = languages.map((l) => l.id), languagesInUseIds = value ? value.map((v) => v._key) : [];
|
|
@@ -539,24 +630,48 @@ var __defProp$4 = Object.defineProperty, __defProps$3 = Object.defineProperties,
|
|
|
539
630
|
],
|
|
540
631
|
// @ts-expect-error - fix typings
|
|
541
632
|
validation: (rule) => rule.custom(async (value, context) => {
|
|
542
|
-
|
|
633
|
+
var _a;
|
|
634
|
+
if (!value || value.length === 0 || value.length === 1 && !((_a = value[0]) != null && _a._key))
|
|
543
635
|
return !0;
|
|
544
636
|
const selectedValue = getSelectedValue(select, context.document), client = context.getClient({ apiVersion });
|
|
545
637
|
let contextLanguages = [];
|
|
546
638
|
const languagesFieldOption = getLanguagesFieldOption(context == null ? void 0 : context.type);
|
|
547
|
-
if (Array.isArray(languagesFieldOption)
|
|
639
|
+
if (Array.isArray(languagesFieldOption))
|
|
640
|
+
contextLanguages = languagesFieldOption;
|
|
641
|
+
else if (Array.isArray(peek(selectedValue)))
|
|
642
|
+
contextLanguages = peek(selectedValue) || [];
|
|
643
|
+
else if (typeof languagesFieldOption == "function") {
|
|
644
|
+
const cachedLanguages = getFunctionCache(
|
|
645
|
+
languagesFieldOption,
|
|
646
|
+
selectedValue
|
|
647
|
+
);
|
|
648
|
+
if (Array.isArray(cachedLanguages))
|
|
649
|
+
contextLanguages = cachedLanguages;
|
|
650
|
+
else {
|
|
651
|
+
const suspendCachedLanguages = peek(selectedValue);
|
|
652
|
+
Array.isArray(suspendCachedLanguages) ? contextLanguages = suspendCachedLanguages : (contextLanguages = await languagesFieldOption(
|
|
653
|
+
client,
|
|
654
|
+
selectedValue
|
|
655
|
+
), setFunctionCache(
|
|
656
|
+
languagesFieldOption,
|
|
657
|
+
selectedValue,
|
|
658
|
+
contextLanguages
|
|
659
|
+
));
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
if (value && value.length > contextLanguages.length)
|
|
548
663
|
return `Cannot be more than ${contextLanguages.length === 1 ? "1 item" : `${contextLanguages.length} items`}`;
|
|
549
|
-
const
|
|
550
|
-
(item) =>
|
|
551
|
-
)
|
|
664
|
+
const languageIds = new Set(contextLanguages.map((lang) => lang.id)), nonLanguageKeys = value.filter(
|
|
665
|
+
(item) => (item == null ? void 0 : item._key) && !languageIds.has(item._key)
|
|
666
|
+
);
|
|
552
667
|
if (nonLanguageKeys.length)
|
|
553
668
|
return {
|
|
554
669
|
message: "Array item keys must be valid languages registered to the field type",
|
|
555
670
|
paths: nonLanguageKeys.map((item) => [{ _key: item._key }])
|
|
556
671
|
};
|
|
557
|
-
const
|
|
558
|
-
|
|
559
|
-
|
|
672
|
+
const seenKeys = /* @__PURE__ */ new Set(), duplicateValues = [];
|
|
673
|
+
for (const item of value)
|
|
674
|
+
item != null && item._key && (seenKeys.has(item._key) ? duplicateValues.push(item) : seenKeys.add(item._key));
|
|
560
675
|
return duplicateValues.length ? {
|
|
561
676
|
message: "There can only be one field per language",
|
|
562
677
|
paths: duplicateValues.map((item) => [{ _key: item._key }])
|
|
@@ -584,6 +699,28 @@ var __defProp$3 = Object.defineProperty, __defProps$2 = Object.defineProperties,
|
|
|
584
699
|
function InternationalizedInput(props) {
|
|
585
700
|
const parentValue = useFormValue(
|
|
586
701
|
props.path.slice(0, -1)
|
|
702
|
+
), originalOnChange = props.inputProps.onChange, wrappedOnChange = useCallback(
|
|
703
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
704
|
+
(patches) => {
|
|
705
|
+
var _a;
|
|
706
|
+
if (!Array.isArray(patches))
|
|
707
|
+
return originalOnChange(patches);
|
|
708
|
+
const valueField = (_a = props.value) == null ? void 0 : _a.value;
|
|
709
|
+
if ((valueField == null || Array.isArray(valueField) && valueField.length === 0) && patches.some((patch) => !patch || typeof patch != "object" ? !1 : patch.type === "insert" && patch.path && Array.isArray(patch.path) && patch.path.length > 0 ? patch.path[0] === "value" || typeof patch.path[0] == "number" : !1)) {
|
|
710
|
+
const initPatch = valueField === void 0 ? { type: "setIfMissing", path: ["value"], value: [] } : null, fixedPatches = patches.map((patch) => {
|
|
711
|
+
if (!patch || typeof patch != "object")
|
|
712
|
+
return patch;
|
|
713
|
+
if (patch.type === "insert" && patch.path && Array.isArray(patch.path)) {
|
|
714
|
+
const fixedPath = patch.path[0] === "value" ? patch.path : ["value", ...patch.path];
|
|
715
|
+
return __spreadProps$2(__spreadValues$3({}, patch), { path: fixedPath });
|
|
716
|
+
}
|
|
717
|
+
return patch;
|
|
718
|
+
}), allPatches = initPatch ? [initPatch, ...fixedPatches] : fixedPatches;
|
|
719
|
+
return originalOnChange(allPatches);
|
|
720
|
+
}
|
|
721
|
+
return originalOnChange(patches);
|
|
722
|
+
},
|
|
723
|
+
[props.value, originalOnChange]
|
|
587
724
|
), inlineProps = __spreadProps$2(__spreadValues$3({}, props.inputProps), {
|
|
588
725
|
// This is the magic that makes inline editing work?
|
|
589
726
|
members: props.inputProps.members.filter(
|
|
@@ -591,7 +728,9 @@ function InternationalizedInput(props) {
|
|
|
591
728
|
),
|
|
592
729
|
// This just overrides the type
|
|
593
730
|
// Remove this as it shouldn't be necessary?
|
|
594
|
-
value: props.value
|
|
731
|
+
value: props.value,
|
|
732
|
+
// Use our wrapped onChange handler
|
|
733
|
+
onChange: wrappedOnChange
|
|
595
734
|
}), { validation, value, onChange, readOnly } = inlineProps, { languages, languageDisplay, defaultLanguages } = useInternationalizedArrayContext(), languageKeysInUse = useMemo(
|
|
596
735
|
() => {
|
|
597
736
|
var _a;
|
|
@@ -641,7 +780,7 @@ function InternationalizedInput(props) {
|
|
|
641
780
|
}
|
|
642
781
|
) }),
|
|
643
782
|
/* @__PURE__ */ jsxs(Flex, { align: "center", gap: 2, children: [
|
|
644
|
-
/* @__PURE__ */ jsx(Card, { flex: 1, tone: "inherit", children: props.inputProps.renderInput(
|
|
783
|
+
/* @__PURE__ */ jsx(Card, { flex: 1, tone: "inherit", children: props.inputProps.renderInput(inlineProps) }),
|
|
645
784
|
/* @__PURE__ */ jsx(Card, { tone: "inherit", children: isDefault ? /* @__PURE__ */ jsx(
|
|
646
785
|
Tooltip,
|
|
647
786
|
{
|