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