@smartcat/sanity-plugin 1.3.0 → 1.4.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/dist/index.cjs +368 -132
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +371 -135
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/tool/AddItemsSearch.tsx +462 -0
- package/src/tool/DocTitle.tsx +23 -0
- package/src/tool/ProjectView.tsx +15 -237
- package/src/tool/itemActions.tsx +216 -0
- package/src/tool/processing.test.ts +11 -1
- package/src/tool/processing.ts +9 -3
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { defineType, defineField, defineArrayMember, useClient, prepareForPreview, useSchema, usePerspective, useRelativeTime, useValuePreview, useWorkspace, useFormValue, definePlugin } from "sanity";
|
|
2
|
-
import { TranslateIcon, CogIcon, AddIcon, LaunchIcon, CheckmarkIcon, CopyIcon, SyncIcon, ArrowLeftIcon, TrashIcon, PublishIcon,
|
|
2
|
+
import { TranslateIcon, CogIcon, AddIcon, LaunchIcon, ChevronDownIcon, JsonIcon, DownloadIcon, SearchIcon, CheckmarkIcon, CopyIcon, SyncIcon, ArrowLeftIcon, TrashIcon, PublishIcon, CloseIcon, RemoveCircleIcon } from "@sanity/icons";
|
|
3
3
|
import { TRANSLATION_PROJECT_TYPE, SETTINGS_TYPE, API_VERSION, SETTINGS_DOC_ID, requireSourceLanguage } from "./_chunks-es/constants.js";
|
|
4
4
|
import { jsxs, jsx, Fragment } from "react/jsx-runtime";
|
|
5
|
-
import { useState, useRef, useEffect, useCallback, useMemo,
|
|
6
|
-
import { Select, useToast, Flex, Spinner, Stack, Label, TextInput, Card, Text, Badge, Switch, Button, Heading, Dialog, Box, Inline, useTheme_v2,
|
|
5
|
+
import { useState, useRef, useEffect, useCallback, useMemo, Fragment as Fragment$1, useLayoutEffect } from "react";
|
|
6
|
+
import { Select, useToast, Flex, Spinner, Stack, Label, TextInput, Card, Text, Badge, Switch, Button, Heading, Dialog, Box, Inline, useTheme_v2, MenuButton, Menu, MenuItem, Checkbox, useTheme, Container } from "@sanity/ui";
|
|
7
7
|
import { uuid } from "@sanity/uuid";
|
|
8
8
|
import { TEMPLATE_PREFIX, STANDARD_WORKFLOW_OPTIONS, isWorkflowValid } from "./_chunks-es/workflow.js";
|
|
9
9
|
import { ITEM_ID, ITEM_ID_FROM_SUBQUERY, itemDocDeref, summarizeCompletion, buildLanguageMappings, findDuplicateSmartcatLanguages, itemDocDerefRespectingDraft, toSmartcatLanguage, targetPercent } from "./_chunks-es/index.js";
|
|
@@ -873,9 +873,12 @@ const ITEMS_QUERY = `*[_id == $id][0]{
|
|
|
873
873
|
sendExistingTranslations,
|
|
874
874
|
"doc": ${itemDocDerefRespectingDraft()}
|
|
875
875
|
}
|
|
876
|
-
}`, SIBLINGS_QUERY = `*[_type == "translation.metadata" && references($ids)]{
|
|
877
|
-
"variants": translations[]{ "language": _key, "doc": value-> }
|
|
878
876
|
}`;
|
|
877
|
+
function siblingsQuery$1(fieldLanguageKey) {
|
|
878
|
+
return `*[_type == "translation.metadata" && references($ids)]{
|
|
879
|
+
"variants": translations[]{ "language": coalesce(${fieldLanguageKey}, _key), "doc": value-> }
|
|
880
|
+
}`;
|
|
881
|
+
}
|
|
879
882
|
async function prepareExport({
|
|
880
883
|
client,
|
|
881
884
|
schema,
|
|
@@ -904,7 +907,7 @@ async function prepareExport({
|
|
|
904
907
|
).map((item) => publishedId(item.doc._id));
|
|
905
908
|
if (flaggedDocLevelIds.length > 0) {
|
|
906
909
|
const groups = await client.fetch(
|
|
907
|
-
|
|
910
|
+
siblingsQuery$1(fieldLanguageKey),
|
|
908
911
|
{ ids: flaggedDocLevelIds }
|
|
909
912
|
);
|
|
910
913
|
for (const group of groups ?? []) {
|
|
@@ -1259,6 +1262,14 @@ function sameValue(a, b) {
|
|
|
1259
1262
|
function stableString(value) {
|
|
1260
1263
|
return Array.isArray(value) ? `[${value.map(stableString).join(",")}]` : value && typeof value == "object" ? `{${Object.entries(value).filter(([k]) => k !== "_key").sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([k, v]) => `${JSON.stringify(k)}:${stableString(v)}`).join(",")}}` : JSON.stringify(value) ?? "undefined";
|
|
1261
1264
|
}
|
|
1265
|
+
function DocTitle({ doc }) {
|
|
1266
|
+
const schema = useSchema(), schemaType = doc ? schema.get(doc._type) : void 0, preview = useValuePreview({
|
|
1267
|
+
enabled: !!(doc && schemaType),
|
|
1268
|
+
schemaType,
|
|
1269
|
+
value: doc ? { _id: doc._id, _type: doc._type } : void 0
|
|
1270
|
+
}), title = typeof preview.value?.title == "string" ? preview.value.title : void 0;
|
|
1271
|
+
return /* @__PURE__ */ jsx(Fragment, { children: title ?? (preview.isLoading ? "\u2026" : UNTITLED) });
|
|
1272
|
+
}
|
|
1262
1273
|
function languageTitle(languages, id) {
|
|
1263
1274
|
return languages.find((l) => l.id === id)?.title || id;
|
|
1264
1275
|
}
|
|
@@ -1360,6 +1371,347 @@ function ItemProgress({
|
|
|
1360
1371
|
target._key
|
|
1361
1372
|
)) });
|
|
1362
1373
|
}
|
|
1374
|
+
function OpenInStudioButton({ id, type }) {
|
|
1375
|
+
const { onClick, href } = useIntentLink({ intent: "edit", params: { id, type } });
|
|
1376
|
+
return /* @__PURE__ */ jsx(
|
|
1377
|
+
Button,
|
|
1378
|
+
{
|
|
1379
|
+
as: "a",
|
|
1380
|
+
href,
|
|
1381
|
+
onClick,
|
|
1382
|
+
mode: "bleed",
|
|
1383
|
+
icon: LaunchIcon,
|
|
1384
|
+
text: "Open in Studio",
|
|
1385
|
+
fontSize: 0,
|
|
1386
|
+
padding: 2
|
|
1387
|
+
}
|
|
1388
|
+
);
|
|
1389
|
+
}
|
|
1390
|
+
const CodeArea = styled.textarea`
|
|
1391
|
+
width: 100%;
|
|
1392
|
+
height: 70vh;
|
|
1393
|
+
resize: none;
|
|
1394
|
+
border: none;
|
|
1395
|
+
outline: none;
|
|
1396
|
+
padding: 0;
|
|
1397
|
+
background: transparent;
|
|
1398
|
+
color: inherit;
|
|
1399
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
|
1400
|
+
font-size: 12px;
|
|
1401
|
+
line-height: 1.5;
|
|
1402
|
+
white-space: pre;
|
|
1403
|
+
tab-size: 2;
|
|
1404
|
+
`;
|
|
1405
|
+
function siblingsQuery(fieldLanguageKey) {
|
|
1406
|
+
return `*[_type == "translation.metadata" && references($id)][0].translations[]{"language": coalesce(${fieldLanguageKey}, _key), "doc": value->}`;
|
|
1407
|
+
}
|
|
1408
|
+
function ViewLocjsonButton(props) {
|
|
1409
|
+
const { docId, type, sourceIsPublished, client, schema, sourceLanguage, documentI18nLanguageField, fieldI18nLanguageField, targetLanguages, languages } = props, [open, setOpen] = useState(!1), [content, setContent] = useState(null), [filename, setFilename] = useState(null), [title, setTitle] = useState("Source only"), [error, setError] = useState(null), build = useCallback(
|
|
1410
|
+
async (targetLanguage) => {
|
|
1411
|
+
setOpen(!0), setContent(null), setFilename(null), setError(null), setTitle(targetLanguage ? `Source + ${languageTitle(languages, targetLanguage)}` : "Source only");
|
|
1412
|
+
try {
|
|
1413
|
+
const raw = await client.fetch(
|
|
1414
|
+
sourceIsPublished ? "*[_id == $id][0]" : 'coalesce(*[_id == "drafts." + $id][0], *[_id == $id][0])',
|
|
1415
|
+
{ id: docId }
|
|
1416
|
+
);
|
|
1417
|
+
if (!raw) throw new Error(`Document not found: ${docId}`);
|
|
1418
|
+
const isDraft = typeof raw._id == "string" && raw._id.startsWith("drafts."), doc = isDraft ? { ...raw, _id: docId } : raw, fields = getTranslatableFields(schema.get(type), { exclude: [documentI18nLanguageField] });
|
|
1419
|
+
let target;
|
|
1420
|
+
if (targetLanguage)
|
|
1421
|
+
if (localizationMode(schema.get(type)) === "document") {
|
|
1422
|
+
const sibling = (await client.fetch(siblingsQuery(fieldI18nLanguageField), { id: docId }) ?? []).find((v) => v.language === targetLanguage)?.doc ?? void 0;
|
|
1423
|
+
target = { language: targetLanguage, doc: sibling };
|
|
1424
|
+
} else
|
|
1425
|
+
target = { language: targetLanguage };
|
|
1426
|
+
const locjson = serializeToLocjson(doc, fields, {
|
|
1427
|
+
sourceLanguage,
|
|
1428
|
+
fieldLanguageKey: fieldI18nLanguageField,
|
|
1429
|
+
target
|
|
1430
|
+
}), smartcatCode = targetLanguage ? languages.find((l) => l.id === targetLanguage)?.smartcatLanguage || targetLanguage : void 0, smartcatName = buildLocjsonFilename(type, docId, resolveDocumentTitle(schema, doc), {
|
|
1431
|
+
draft: isDraft,
|
|
1432
|
+
language: smartcatCode
|
|
1433
|
+
});
|
|
1434
|
+
setFilename(smartcatName.split("/").pop() || smartcatName), setContent(JSON.stringify(locjson, null, 2));
|
|
1435
|
+
} catch (err) {
|
|
1436
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
1437
|
+
}
|
|
1438
|
+
},
|
|
1439
|
+
[client, schema, docId, type, sourceIsPublished, sourceLanguage, documentI18nLanguageField, fieldI18nLanguageField, languages]
|
|
1440
|
+
), download = useCallback(() => {
|
|
1441
|
+
if (content == null || !filename) return;
|
|
1442
|
+
const url = URL.createObjectURL(new Blob([content], { type: "application/json" })), anchor = document.createElement("a");
|
|
1443
|
+
anchor.href = url, anchor.download = filename, anchor.click(), URL.revokeObjectURL(url);
|
|
1444
|
+
}, [content, filename]);
|
|
1445
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1446
|
+
/* @__PURE__ */ jsx(
|
|
1447
|
+
MenuButton,
|
|
1448
|
+
{
|
|
1449
|
+
id: `locjson-menu-${docId}`,
|
|
1450
|
+
button: /* @__PURE__ */ jsx(Button, { mode: "bleed", icon: JsonIcon, iconRight: ChevronDownIcon, title: "View LocJSON", fontSize: 0, padding: 2 }),
|
|
1451
|
+
menu: /* @__PURE__ */ jsxs(Menu, { children: [
|
|
1452
|
+
/* @__PURE__ */ jsx(MenuItem, { text: "Source only", onClick: () => build() }),
|
|
1453
|
+
targetLanguages.map((lang) => /* @__PURE__ */ jsx(MenuItem, { text: `Source + ${languageTitle(languages, lang)}`, onClick: () => build(lang) }, lang))
|
|
1454
|
+
] }),
|
|
1455
|
+
popover: { portal: !0 }
|
|
1456
|
+
}
|
|
1457
|
+
),
|
|
1458
|
+
open && /* @__PURE__ */ jsx(
|
|
1459
|
+
Dialog,
|
|
1460
|
+
{
|
|
1461
|
+
id: `locjson-${docId}`,
|
|
1462
|
+
header: /* @__PURE__ */ jsxs(Flex, { align: "center", gap: 3, children: [
|
|
1463
|
+
/* @__PURE__ */ jsxs(Text, { weight: "semibold", children: [
|
|
1464
|
+
"LocJSON \xB7 ",
|
|
1465
|
+
type,
|
|
1466
|
+
" \xB7 ",
|
|
1467
|
+
title
|
|
1468
|
+
] }),
|
|
1469
|
+
/* @__PURE__ */ jsx(
|
|
1470
|
+
Button,
|
|
1471
|
+
{
|
|
1472
|
+
icon: DownloadIcon,
|
|
1473
|
+
text: "Download",
|
|
1474
|
+
mode: "ghost",
|
|
1475
|
+
fontSize: 1,
|
|
1476
|
+
padding: 2,
|
|
1477
|
+
disabled: content == null,
|
|
1478
|
+
onClick: download
|
|
1479
|
+
}
|
|
1480
|
+
)
|
|
1481
|
+
] }),
|
|
1482
|
+
onClose: () => setOpen(!1),
|
|
1483
|
+
width: 3,
|
|
1484
|
+
children: /* @__PURE__ */ jsx(Box, { padding: 4, children: error ? /* @__PURE__ */ jsx(Text, { size: 1, muted: !0, children: error }) : content === null ? /* @__PURE__ */ jsx(Flex, { align: "center", justify: "center", padding: 5, children: /* @__PURE__ */ jsx(Spinner, { muted: !0 }) }) : /* @__PURE__ */ jsx(
|
|
1485
|
+
CodeArea,
|
|
1486
|
+
{
|
|
1487
|
+
readOnly: !0,
|
|
1488
|
+
spellCheck: !1,
|
|
1489
|
+
value: content,
|
|
1490
|
+
onFocus: (e) => e.currentTarget.select()
|
|
1491
|
+
}
|
|
1492
|
+
) })
|
|
1493
|
+
}
|
|
1494
|
+
)
|
|
1495
|
+
] });
|
|
1496
|
+
}
|
|
1497
|
+
const QUERY_LIMIT = 50, DISPLAY_LIMIT = 25, MAX_TOKENS = 6, MIN_QUERY_LENGTH = 2, TITLE_FIELDS = "[title, name, heading, label, question, slug.current, _type, _id]", SCORE_FIELDS = "[title, name, heading, label, question, slug.current]", EXCLUDED_TYPES = ["smartcat.translationProject", "smartcat.settings", "translation.metadata"];
|
|
1498
|
+
function buildSearch(rawQuery, searchContent, translatableTypes) {
|
|
1499
|
+
if (rawQuery.trim().length < MIN_QUERY_LENGTH) return null;
|
|
1500
|
+
const tokens = rawQuery.trim().split(/\s+/).map((t) => t.replace(/["*]/g, "").replace(/^-+/, "").trim()).filter(Boolean).slice(0, MAX_TOKENS);
|
|
1501
|
+
if (tokens.length === 0) return null;
|
|
1502
|
+
const target = searchContent ? "@" : TITLE_FIELDS, params = {}, tokenClauses = [], scoreClauses = [];
|
|
1503
|
+
tokens.forEach((tok, i) => {
|
|
1504
|
+
params[`t${i}`] = `${tok}*`, tokenClauses.push(`${target} match text::query($t${i})`), scoreClauses.push(`boost(${SCORE_FIELDS} match text::query($t${i}), 2)`);
|
|
1505
|
+
});
|
|
1506
|
+
let typeFilter;
|
|
1507
|
+
return translatableTypes ? (params.types = translatableTypes, typeFilter = "_type in $types") : (params.excluded = EXCLUDED_TYPES, typeFilter = '!(_type in $excluded) && !(_type match "sanity.*")'), { query: `*[${[typeFilter, ...tokenClauses].join(" && ")}] | score(${scoreClauses.join(", ")}) | order(_score desc)[0...${QUERY_LIMIT}]{_id, _type, "hasDraft": _id in path("drafts.**") || count(*[_id == "drafts." + ^._id]) > 0}`, params };
|
|
1508
|
+
}
|
|
1509
|
+
function dedupeHits(rows) {
|
|
1510
|
+
const byDoc = /* @__PURE__ */ new Map();
|
|
1511
|
+
for (const row of rows) {
|
|
1512
|
+
const docId = row._id.replace(/^drafts\./, ""), existing = byDoc.get(docId);
|
|
1513
|
+
existing ? existing.hasDraft = existing.hasDraft || row.hasDraft : byDoc.set(docId, { docId, _type: row._type, hasDraft: row.hasDraft });
|
|
1514
|
+
}
|
|
1515
|
+
return [...byDoc.values()].slice(0, DISPLAY_LIMIT);
|
|
1516
|
+
}
|
|
1517
|
+
const MEMBERSHIP_QUERY = `*[_type == "${TRANSLATION_PROJECT_TYPE}" && count(items[${ITEM_ID} in $ids]) > 0] | order(name asc){
|
|
1518
|
+
_id,
|
|
1519
|
+
name,
|
|
1520
|
+
"docIds": items[${ITEM_ID} in $ids]{"id": ${ITEM_ID}}
|
|
1521
|
+
}`;
|
|
1522
|
+
function ProjectChip({ project, current }) {
|
|
1523
|
+
const router = useRouter(), onClick = useCallback(
|
|
1524
|
+
(e) => {
|
|
1525
|
+
e.preventDefault(), router.navigate({ projectId: project._id });
|
|
1526
|
+
},
|
|
1527
|
+
[router, project._id]
|
|
1528
|
+
);
|
|
1529
|
+
return /* @__PURE__ */ jsxs("a", { href: "#", onClick, children: [
|
|
1530
|
+
project.name || "Untitled project",
|
|
1531
|
+
current ? " (this project)" : ""
|
|
1532
|
+
] });
|
|
1533
|
+
}
|
|
1534
|
+
function AddItemsSearch({
|
|
1535
|
+
projectId,
|
|
1536
|
+
translatableTypes,
|
|
1537
|
+
existingDocIds,
|
|
1538
|
+
sourceLanguage,
|
|
1539
|
+
languages,
|
|
1540
|
+
targetLanguages,
|
|
1541
|
+
documentI18nLanguageField,
|
|
1542
|
+
fieldI18nLanguageField
|
|
1543
|
+
}) {
|
|
1544
|
+
const client = useClient({ apiVersion: API_VERSION }), schema = useSchema(), toast = useToast(), [query, setQuery] = useState(""), [searchContent, setSearchContent] = useState(!1), [results, setResults] = useState(null), [loading, setLoading] = useState(!1), [addingId, setAddingId] = useState(null), [addedIds, setAddedIds] = useState(/* @__PURE__ */ new Set()), [projectsByDoc, setProjectsByDoc] = useState(/* @__PURE__ */ new Map());
|
|
1545
|
+
useEffect(() => {
|
|
1546
|
+
const built = buildSearch(query, searchContent, translatableTypes);
|
|
1547
|
+
if (!built) {
|
|
1548
|
+
setResults(null), setLoading(!1);
|
|
1549
|
+
return;
|
|
1550
|
+
}
|
|
1551
|
+
setLoading(!0);
|
|
1552
|
+
let cancelled = !1;
|
|
1553
|
+
const handle = setTimeout(() => {
|
|
1554
|
+
client.fetch(built.query, built.params).then((res) => {
|
|
1555
|
+
cancelled || setResults(dedupeHits(res));
|
|
1556
|
+
}).catch((err) => {
|
|
1557
|
+
cancelled || (setResults([]), toast.push({ status: "error", title: "Search failed", description: String(err) }));
|
|
1558
|
+
}).finally(() => {
|
|
1559
|
+
cancelled || setLoading(!1);
|
|
1560
|
+
});
|
|
1561
|
+
}, 250);
|
|
1562
|
+
return () => {
|
|
1563
|
+
cancelled = !0, clearTimeout(handle);
|
|
1564
|
+
};
|
|
1565
|
+
}, [query, searchContent, translatableTypes, client, toast]);
|
|
1566
|
+
const docIdsKey = (results ?? []).map((r) => r.docId).join(",");
|
|
1567
|
+
useEffect(() => {
|
|
1568
|
+
const ids = docIdsKey ? docIdsKey.split(",") : [];
|
|
1569
|
+
if (ids.length === 0) {
|
|
1570
|
+
setProjectsByDoc(/* @__PURE__ */ new Map());
|
|
1571
|
+
return;
|
|
1572
|
+
}
|
|
1573
|
+
let cancelled = !1;
|
|
1574
|
+
const load = () => client.fetch(MEMBERSHIP_QUERY, { ids }).then((rows) => {
|
|
1575
|
+
if (cancelled) return;
|
|
1576
|
+
const map = /* @__PURE__ */ new Map();
|
|
1577
|
+
for (const row of rows)
|
|
1578
|
+
for (const { id } of row.docIds ?? []) {
|
|
1579
|
+
const list = map.get(id) ?? [];
|
|
1580
|
+
list.push({ _id: row._id, name: row.name }), map.set(id, list);
|
|
1581
|
+
}
|
|
1582
|
+
setProjectsByDoc(map);
|
|
1583
|
+
}).catch(() => !cancelled && setProjectsByDoc(/* @__PURE__ */ new Map()));
|
|
1584
|
+
load();
|
|
1585
|
+
const sub = client.listen(MEMBERSHIP_QUERY, { ids }, { visibility: "query" }).subscribe({ next: load, error: () => {
|
|
1586
|
+
} });
|
|
1587
|
+
return () => {
|
|
1588
|
+
cancelled = !0, sub.unsubscribe();
|
|
1589
|
+
};
|
|
1590
|
+
}, [docIdsKey, client]);
|
|
1591
|
+
const existingKey = [...existingDocIds].sort().join(",");
|
|
1592
|
+
useEffect(() => {
|
|
1593
|
+
setAddedIds((prev) => {
|
|
1594
|
+
if (prev.size === 0) return prev;
|
|
1595
|
+
const next = new Set([...prev].filter((id) => !existingDocIds.has(id)));
|
|
1596
|
+
return next.size === prev.size ? prev : next;
|
|
1597
|
+
});
|
|
1598
|
+
}, [existingKey]);
|
|
1599
|
+
const addItem = async (hit, sendExistingTranslations) => {
|
|
1600
|
+
setAddingId(hit.docId);
|
|
1601
|
+
try {
|
|
1602
|
+
const item = {
|
|
1603
|
+
_type: "smartcat.projectItem",
|
|
1604
|
+
_key: uuid(),
|
|
1605
|
+
docId: hit.docId,
|
|
1606
|
+
docType: hit._type,
|
|
1607
|
+
// Prefer the draft as source when one exists (the plugin translates the
|
|
1608
|
+
// draft version); the caller chose whether to also upload existing
|
|
1609
|
+
// translations — matching a fresh "Add to translation project".
|
|
1610
|
+
sourceIsPublished: !hit.hasDraft,
|
|
1611
|
+
sendExistingTranslations
|
|
1612
|
+
};
|
|
1613
|
+
await client.patch(projectId).setIfMissing({ items: [] }).insert("after", "items[-1]", [item]).commit(), setAddedIds((prev) => new Set(prev).add(hit.docId)), toast.push({ status: "success", title: "Added to project" });
|
|
1614
|
+
} catch (err) {
|
|
1615
|
+
toast.push({ status: "error", title: "Failed to add item", description: String(err) });
|
|
1616
|
+
} finally {
|
|
1617
|
+
setAddingId(null);
|
|
1618
|
+
}
|
|
1619
|
+
};
|
|
1620
|
+
return /* @__PURE__ */ jsxs(Stack, { space: 3, children: [
|
|
1621
|
+
/* @__PURE__ */ jsx(Card, { padding: 4, radius: 2, border: !0, children: /* @__PURE__ */ jsxs(Stack, { space: 4, children: [
|
|
1622
|
+
/* @__PURE__ */ jsxs(Stack, { space: 3, children: [
|
|
1623
|
+
/* @__PURE__ */ jsx(
|
|
1624
|
+
TextInput,
|
|
1625
|
+
{
|
|
1626
|
+
icon: SearchIcon,
|
|
1627
|
+
placeholder: "Search documents to add \u2014 by title, or schema type\u2026",
|
|
1628
|
+
value: query,
|
|
1629
|
+
onChange: (e) => setQuery(e.currentTarget.value),
|
|
1630
|
+
clearButton: query.length > 0,
|
|
1631
|
+
onClear: () => setQuery("")
|
|
1632
|
+
}
|
|
1633
|
+
),
|
|
1634
|
+
/* @__PURE__ */ jsxs(Flex, { as: "label", align: "center", gap: 2, style: { cursor: "pointer" }, children: [
|
|
1635
|
+
/* @__PURE__ */ jsx(Checkbox, { checked: searchContent, onChange: (e) => setSearchContent(e.currentTarget.checked) }),
|
|
1636
|
+
/* @__PURE__ */ jsx(Text, { size: 1, muted: !0, children: "Also search in content (slower; matches body text, not just titles)" })
|
|
1637
|
+
] })
|
|
1638
|
+
] }),
|
|
1639
|
+
loading ? /* @__PURE__ */ jsx(Flex, { align: "center", justify: "center", padding: 4, children: /* @__PURE__ */ jsx(Spinner, { muted: !0 }) }) : results === null ? null : results.length === 0 ? /* @__PURE__ */ jsxs(Text, { muted: !0, align: "center", size: 1, children: [
|
|
1640
|
+
"No documents match \u201C",
|
|
1641
|
+
query.trim(),
|
|
1642
|
+
"\u201D."
|
|
1643
|
+
] }) : /* @__PURE__ */ jsx(Stack, { space: 2, children: results.map((hit) => {
|
|
1644
|
+
const inThisProject = addedIds.has(hit.docId) || existingDocIds.has(hit.docId), memberships = projectsByDoc.get(hit.docId) ?? [], previewId = hit.hasDraft ? `drafts.${hit.docId}` : hit.docId;
|
|
1645
|
+
return /* @__PURE__ */ jsx(Card, { padding: 2, radius: 2, shadow: 1, children: /* @__PURE__ */ jsxs(Flex, { align: "center", gap: 3, children: [
|
|
1646
|
+
/* @__PURE__ */ jsx(Box, { flex: 1, children: /* @__PURE__ */ jsxs(Stack, { space: 2, children: [
|
|
1647
|
+
/* @__PURE__ */ jsxs(Flex, { align: "center", gap: 2, children: [
|
|
1648
|
+
/* @__PURE__ */ jsx(
|
|
1649
|
+
Badge,
|
|
1650
|
+
{
|
|
1651
|
+
tone: hit.hasDraft ? "caution" : "positive",
|
|
1652
|
+
paddingX: 2,
|
|
1653
|
+
title: hit.hasDraft ? "The draft version will be the translation source" : "The published version will be the translation source",
|
|
1654
|
+
children: hit.hasDraft ? "Draft" : "Published"
|
|
1655
|
+
}
|
|
1656
|
+
),
|
|
1657
|
+
/* @__PURE__ */ jsx(Text, { weight: "medium", textOverflow: "ellipsis", children: /* @__PURE__ */ jsx(DocTitle, { doc: { _id: previewId, _type: hit._type } }) }),
|
|
1658
|
+
/* @__PURE__ */ jsx(Badge, { tone: "primary", paddingX: 2, children: hit._type })
|
|
1659
|
+
] }),
|
|
1660
|
+
memberships.length > 0 && /* @__PURE__ */ jsxs(Text, { size: 0, muted: !0, children: [
|
|
1661
|
+
"In:",
|
|
1662
|
+
" ",
|
|
1663
|
+
memberships.map((p, i) => /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
1664
|
+
i > 0 && ", ",
|
|
1665
|
+
/* @__PURE__ */ jsx(ProjectChip, { project: p, current: p._id === projectId })
|
|
1666
|
+
] }, p._id))
|
|
1667
|
+
] })
|
|
1668
|
+
] }) }),
|
|
1669
|
+
/* @__PURE__ */ jsx(OpenInStudioButton, { id: hit.docId, type: hit._type }),
|
|
1670
|
+
/* @__PURE__ */ jsx(
|
|
1671
|
+
ViewLocjsonButton,
|
|
1672
|
+
{
|
|
1673
|
+
docId: hit.docId,
|
|
1674
|
+
type: hit._type,
|
|
1675
|
+
sourceIsPublished: !hit.hasDraft,
|
|
1676
|
+
client,
|
|
1677
|
+
schema,
|
|
1678
|
+
sourceLanguage,
|
|
1679
|
+
documentI18nLanguageField,
|
|
1680
|
+
fieldI18nLanguageField,
|
|
1681
|
+
targetLanguages,
|
|
1682
|
+
languages
|
|
1683
|
+
}
|
|
1684
|
+
),
|
|
1685
|
+
inThisProject ? /* @__PURE__ */ jsx(Button, { mode: "bleed", icon: CheckmarkIcon, text: "Added", fontSize: 1, padding: 2, disabled: !0 }) : /* @__PURE__ */ jsx(
|
|
1686
|
+
MenuButton,
|
|
1687
|
+
{
|
|
1688
|
+
id: `add-${hit.docId}`,
|
|
1689
|
+
button: /* @__PURE__ */ jsx(
|
|
1690
|
+
Button,
|
|
1691
|
+
{
|
|
1692
|
+
mode: "ghost",
|
|
1693
|
+
icon: AddIcon,
|
|
1694
|
+
iconRight: ChevronDownIcon,
|
|
1695
|
+
text: "Add",
|
|
1696
|
+
fontSize: 1,
|
|
1697
|
+
padding: 2,
|
|
1698
|
+
loading: addingId === hit.docId,
|
|
1699
|
+
disabled: addingId !== null
|
|
1700
|
+
}
|
|
1701
|
+
),
|
|
1702
|
+
menu: /* @__PURE__ */ jsxs(Menu, { children: [
|
|
1703
|
+
/* @__PURE__ */ jsx(MenuItem, { text: "Add source", onClick: () => addItem(hit, !1) }),
|
|
1704
|
+
/* @__PURE__ */ jsx(MenuItem, { text: "Add source + translations", onClick: () => addItem(hit, !0) })
|
|
1705
|
+
] }),
|
|
1706
|
+
popover: { portal: !0 }
|
|
1707
|
+
}
|
|
1708
|
+
)
|
|
1709
|
+
] }) }, hit.docId);
|
|
1710
|
+
}) })
|
|
1711
|
+
] }) }),
|
|
1712
|
+
/* @__PURE__ */ jsx(Text, { size: 1, muted: !0, children: "You can also add content from a document\u2019s \u201C\u22EF \u2192 Add to translation project\u201D menu." })
|
|
1713
|
+
] });
|
|
1714
|
+
}
|
|
1363
1715
|
function logsToText(logs) {
|
|
1364
1716
|
return logs.map((l) => {
|
|
1365
1717
|
const prefix = l.indent ? " " : "", detail = [
|
|
@@ -1502,14 +1854,6 @@ function languageMappingLabel(language) {
|
|
|
1502
1854
|
const code = language.smartcatLanguage || language.id;
|
|
1503
1855
|
return code === language.id ? language.id : `${language.id} \u2192 ${code}`;
|
|
1504
1856
|
}
|
|
1505
|
-
function DocTitle({ doc }) {
|
|
1506
|
-
const schema = useSchema(), schemaType = doc ? schema.get(doc._type) : void 0, preview = useValuePreview({
|
|
1507
|
-
enabled: !!(doc && schemaType),
|
|
1508
|
-
schemaType,
|
|
1509
|
-
value: doc ? { _id: doc._id, _type: doc._type } : void 0
|
|
1510
|
-
}), title = typeof preview.value?.title == "string" ? preview.value.title : void 0;
|
|
1511
|
-
return /* @__PURE__ */ jsx(Fragment, { children: title ?? (preview.isLoading ? "\u2026" : UNTITLED) });
|
|
1512
|
-
}
|
|
1513
1857
|
const IN_FLIGHT = ["queued", "importing", "downloaded"], CAN_IMPORT = ["sent", "translating", "completed"], rotate = keyframes`
|
|
1514
1858
|
to { transform: rotate(360deg); }
|
|
1515
1859
|
`, SpinningSyncIcon = styled(SyncIcon)`
|
|
@@ -1524,126 +1868,6 @@ function LastUpdate({ date }) {
|
|
|
1524
1868
|
relative
|
|
1525
1869
|
] });
|
|
1526
1870
|
}
|
|
1527
|
-
function OpenInStudioButton({ id, type }) {
|
|
1528
|
-
const { onClick, href } = useIntentLink({ intent: "edit", params: { id, type } });
|
|
1529
|
-
return /* @__PURE__ */ jsx(
|
|
1530
|
-
Button,
|
|
1531
|
-
{
|
|
1532
|
-
as: "a",
|
|
1533
|
-
href,
|
|
1534
|
-
onClick,
|
|
1535
|
-
mode: "bleed",
|
|
1536
|
-
icon: LaunchIcon,
|
|
1537
|
-
text: "Open in Studio",
|
|
1538
|
-
fontSize: 0,
|
|
1539
|
-
padding: 2
|
|
1540
|
-
}
|
|
1541
|
-
);
|
|
1542
|
-
}
|
|
1543
|
-
const CodeArea = styled.textarea`
|
|
1544
|
-
width: 100%;
|
|
1545
|
-
height: 70vh;
|
|
1546
|
-
resize: none;
|
|
1547
|
-
border: none;
|
|
1548
|
-
outline: none;
|
|
1549
|
-
padding: 0;
|
|
1550
|
-
background: transparent;
|
|
1551
|
-
color: inherit;
|
|
1552
|
-
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
|
1553
|
-
font-size: 12px;
|
|
1554
|
-
line-height: 1.5;
|
|
1555
|
-
white-space: pre;
|
|
1556
|
-
tab-size: 2;
|
|
1557
|
-
`, VIEW_SIBLINGS_QUERY = '*[_type == "translation.metadata" && references($id)][0].translations[]{"language": _key, "doc": value->}';
|
|
1558
|
-
function ViewLocjsonButton(props) {
|
|
1559
|
-
const { docId, type, sourceIsPublished, client, schema, sourceLanguage, documentI18nLanguageField, fieldI18nLanguageField, targetLanguages, languages } = props, [open, setOpen] = useState(!1), [content, setContent] = useState(null), [filename, setFilename] = useState(null), [title, setTitle] = useState("Source only"), [error, setError] = useState(null), build = useCallback(
|
|
1560
|
-
async (targetLanguage) => {
|
|
1561
|
-
setOpen(!0), setContent(null), setFilename(null), setError(null), setTitle(targetLanguage ? `Source + ${languageTitle(languages, targetLanguage)}` : "Source only");
|
|
1562
|
-
try {
|
|
1563
|
-
const raw = await client.fetch(
|
|
1564
|
-
sourceIsPublished ? "*[_id == $id][0]" : 'coalesce(*[_id == "drafts." + $id][0], *[_id == $id][0])',
|
|
1565
|
-
{ id: docId }
|
|
1566
|
-
);
|
|
1567
|
-
if (!raw) throw new Error(`Document not found: ${docId}`);
|
|
1568
|
-
const isDraft = typeof raw._id == "string" && raw._id.startsWith("drafts."), doc = isDraft ? { ...raw, _id: docId } : raw, fields = getTranslatableFields(schema.get(type), { exclude: [documentI18nLanguageField] });
|
|
1569
|
-
let target;
|
|
1570
|
-
if (targetLanguage)
|
|
1571
|
-
if (localizationMode(schema.get(type)) === "document") {
|
|
1572
|
-
const sibling = (await client.fetch(VIEW_SIBLINGS_QUERY, { id: docId }) ?? []).find((v) => v.language === targetLanguage)?.doc ?? void 0;
|
|
1573
|
-
target = { language: targetLanguage, doc: sibling };
|
|
1574
|
-
} else
|
|
1575
|
-
target = { language: targetLanguage };
|
|
1576
|
-
const locjson = serializeToLocjson(doc, fields, {
|
|
1577
|
-
sourceLanguage,
|
|
1578
|
-
fieldLanguageKey: fieldI18nLanguageField,
|
|
1579
|
-
target
|
|
1580
|
-
}), smartcatCode = targetLanguage ? languages.find((l) => l.id === targetLanguage)?.smartcatLanguage || targetLanguage : void 0, smartcatName = buildLocjsonFilename(type, docId, resolveDocumentTitle(schema, doc), {
|
|
1581
|
-
draft: isDraft,
|
|
1582
|
-
language: smartcatCode
|
|
1583
|
-
});
|
|
1584
|
-
setFilename(smartcatName.split("/").pop() || smartcatName), setContent(JSON.stringify(locjson, null, 2));
|
|
1585
|
-
} catch (err) {
|
|
1586
|
-
setError(err instanceof Error ? err.message : String(err));
|
|
1587
|
-
}
|
|
1588
|
-
},
|
|
1589
|
-
[client, schema, docId, type, sourceIsPublished, sourceLanguage, documentI18nLanguageField, fieldI18nLanguageField, languages]
|
|
1590
|
-
), download = useCallback(() => {
|
|
1591
|
-
if (content == null || !filename) return;
|
|
1592
|
-
const url = URL.createObjectURL(new Blob([content], { type: "application/json" })), anchor = document.createElement("a");
|
|
1593
|
-
anchor.href = url, anchor.download = filename, anchor.click(), URL.revokeObjectURL(url);
|
|
1594
|
-
}, [content, filename]);
|
|
1595
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1596
|
-
/* @__PURE__ */ jsx(
|
|
1597
|
-
MenuButton,
|
|
1598
|
-
{
|
|
1599
|
-
id: `locjson-menu-${docId}`,
|
|
1600
|
-
button: /* @__PURE__ */ jsx(Button, { mode: "bleed", icon: JsonIcon, iconRight: ChevronDownIcon, title: "View LocJSON", fontSize: 0, padding: 2 }),
|
|
1601
|
-
menu: /* @__PURE__ */ jsxs(Menu, { children: [
|
|
1602
|
-
/* @__PURE__ */ jsx(MenuItem, { text: "Source only", onClick: () => build() }),
|
|
1603
|
-
targetLanguages.map((lang) => /* @__PURE__ */ jsx(MenuItem, { text: `Source + ${languageTitle(languages, lang)}`, onClick: () => build(lang) }, lang))
|
|
1604
|
-
] }),
|
|
1605
|
-
popover: { portal: !0 }
|
|
1606
|
-
}
|
|
1607
|
-
),
|
|
1608
|
-
open && /* @__PURE__ */ jsx(
|
|
1609
|
-
Dialog,
|
|
1610
|
-
{
|
|
1611
|
-
id: `locjson-${docId}`,
|
|
1612
|
-
header: /* @__PURE__ */ jsxs(Flex, { align: "center", gap: 3, children: [
|
|
1613
|
-
/* @__PURE__ */ jsxs(Text, { weight: "semibold", children: [
|
|
1614
|
-
"LocJSON \xB7 ",
|
|
1615
|
-
type,
|
|
1616
|
-
" \xB7 ",
|
|
1617
|
-
title
|
|
1618
|
-
] }),
|
|
1619
|
-
/* @__PURE__ */ jsx(
|
|
1620
|
-
Button,
|
|
1621
|
-
{
|
|
1622
|
-
icon: DownloadIcon,
|
|
1623
|
-
text: "Download",
|
|
1624
|
-
mode: "ghost",
|
|
1625
|
-
fontSize: 1,
|
|
1626
|
-
padding: 2,
|
|
1627
|
-
disabled: content == null,
|
|
1628
|
-
onClick: download
|
|
1629
|
-
}
|
|
1630
|
-
)
|
|
1631
|
-
] }),
|
|
1632
|
-
onClose: () => setOpen(!1),
|
|
1633
|
-
width: 3,
|
|
1634
|
-
children: /* @__PURE__ */ jsx(Box, { padding: 4, children: error ? /* @__PURE__ */ jsx(Text, { size: 1, muted: !0, children: error }) : content === null ? /* @__PURE__ */ jsx(Flex, { align: "center", justify: "center", padding: 5, children: /* @__PURE__ */ jsx(Spinner, { muted: !0 }) }) : /* @__PURE__ */ jsx(
|
|
1635
|
-
CodeArea,
|
|
1636
|
-
{
|
|
1637
|
-
readOnly: !0,
|
|
1638
|
-
spellCheck: !1,
|
|
1639
|
-
value: content,
|
|
1640
|
-
onFocus: (e) => e.currentTarget.select()
|
|
1641
|
-
}
|
|
1642
|
-
) })
|
|
1643
|
-
}
|
|
1644
|
-
)
|
|
1645
|
-
] });
|
|
1646
|
-
}
|
|
1647
1871
|
function ProjectView({
|
|
1648
1872
|
projectId,
|
|
1649
1873
|
onBack,
|
|
@@ -2096,7 +2320,19 @@ Smartcat language code: '${l.smartcatLanguage || l.id}'`,
|
|
|
2096
2320
|
item._key
|
|
2097
2321
|
);
|
|
2098
2322
|
}) }),
|
|
2099
|
-
/* @__PURE__ */ jsx(
|
|
2323
|
+
/* @__PURE__ */ jsx(
|
|
2324
|
+
AddItemsSearch,
|
|
2325
|
+
{
|
|
2326
|
+
projectId,
|
|
2327
|
+
translatableTypes,
|
|
2328
|
+
existingDocIds: new Set(items.map((i) => i.docId)),
|
|
2329
|
+
sourceLanguage,
|
|
2330
|
+
languages,
|
|
2331
|
+
targetLanguages: targets,
|
|
2332
|
+
documentI18nLanguageField,
|
|
2333
|
+
fieldI18nLanguageField
|
|
2334
|
+
}
|
|
2335
|
+
)
|
|
2100
2336
|
] }),
|
|
2101
2337
|
confirmDelete && /* @__PURE__ */ jsx(
|
|
2102
2338
|
Dialog,
|