@proveanything/smartlinks-utils-ui 0.12.4 → 0.12.5
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/components/AssetPicker/index.css +15 -0
- package/dist/components/AssetPicker/index.css.map +1 -1
- package/dist/components/ConditionsEditor/index.css +15 -0
- package/dist/components/ConditionsEditor/index.css.map +1 -1
- package/dist/components/FontPicker/index.css +15 -0
- package/dist/components/FontPicker/index.css.map +1 -1
- package/dist/components/IconPicker/index.css +15 -0
- package/dist/components/IconPicker/index.css.map +1 -1
- package/dist/components/RecordsAdmin/index.css +15 -0
- package/dist/components/RecordsAdmin/index.css.map +1 -1
- package/dist/components/RecordsAdmin/index.d.ts +8 -0
- package/dist/components/RecordsAdmin/index.js +370 -32
- package/dist/components/RecordsAdmin/index.js.map +1 -1
- package/dist/index.css +15 -0
- package/dist/index.css.map +1 -1
- package/package.json +1 -1
|
@@ -455,6 +455,14 @@ interface RecordsAdminI18n {
|
|
|
455
455
|
duplicateAction: string;
|
|
456
456
|
/** Toast shown after a successful Duplicate. `{name}` = source label. */
|
|
457
457
|
duplicateToast: string;
|
|
458
|
+
/** Duplicate-to picker labels. */
|
|
459
|
+
duplicateDialogTitle: string;
|
|
460
|
+
duplicateTargetGlobal: string;
|
|
461
|
+
duplicateTargetSameRule: string;
|
|
462
|
+
duplicateTargetExistingRules: string;
|
|
463
|
+
duplicateTargetNewRule: string;
|
|
464
|
+
duplicateConfirm: string;
|
|
465
|
+
duplicateCancel: string;
|
|
458
466
|
/**
|
|
459
467
|
* Row-menu label for the "Copy and start new rule" action — shown on
|
|
460
468
|
* the rules tab in singleton mode. One-click flow: copies the row to
|
|
@@ -7,7 +7,7 @@ import { cn } from '../../chunk-L7FQ52F5.js';
|
|
|
7
7
|
import { parsedRefToTarget, parsedRefToScope, matchRecords, scopesEqual, getRecordById, listRecords, upsertRecord, updateRecord, createRecord, removeRecord } from '../../chunk-KA4MKRHL.js';
|
|
8
8
|
export { bulkDelete, bulkUpsert, createRecord, getRecordById, listRecords, matchRecords, parsedRefToScope, parsedRefToTarget, removeRecord, restoreRecord, scopesEqual, upsertRecord } from '../../chunk-KA4MKRHL.js';
|
|
9
9
|
import { createContext, useMemo, useState, useEffect, useCallback, useRef, isValidElement, useLayoutEffect, useContext, useSyncExternalStore, createElement } from 'react';
|
|
10
|
-
import { ChevronDown, Database, Lightbulb, SearchX, Inbox, LayoutGrid, Eye, MoreHorizontal, Download, Upload, Trash2, Copy, Pencil, Plus, CircleDashed, ArrowDownLeft, CheckCircle2, List, SlidersHorizontal, Globe, Tag, Boxes, Layers, Package, Target, Check, Rows3, ChevronRight, Eraser, FilePlus2, CopyPlus, ClipboardPaste, Box, X, Search, Image, Table, ArrowLeft, ChevronLeft, AlertTriangle, Info, HelpCircle, CornerDownLeft, Circle, ArrowUpDown, ArrowUp, ArrowDown, MinusCircle, XCircle, AlertCircle, Undo2, Save, Loader2, Archive, ArrowRight, Globe2, Settings2 } from 'lucide-react';
|
|
10
|
+
import { ChevronDown, Database, Lightbulb, SearchX, Inbox, LayoutGrid, Eye, MoreHorizontal, Download, Upload, Trash2, Copy, Pencil, Plus, CircleDashed, ArrowDownLeft, CheckCircle2, List, SlidersHorizontal, Globe, Tag, Boxes, Layers, Package, Target, Check, Rows3, ChevronRight, Eraser, FilePlus2, CopyPlus, ClipboardPaste, Box, X, Search, Image, Table, ArrowLeft, ChevronLeft, AlertTriangle, Info, HelpCircle, CornerDownLeft, Circle, ArrowUpDown, ArrowUp, ArrowDown, MinusCircle, XCircle, AlertCircle, Undo2, Save, Loader2, Archive, ArrowRight, Globe2, Sparkles, Settings2 } from 'lucide-react';
|
|
11
11
|
import { useQuery, useQueryClient, useInfiniteQuery } from '@tanstack/react-query';
|
|
12
12
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
13
13
|
import { createPortal } from 'react-dom';
|
|
@@ -240,6 +240,13 @@ var DEFAULT_I18N = {
|
|
|
240
240
|
pasteWarnContinue: "Continue",
|
|
241
241
|
duplicateAction: "Duplicate",
|
|
242
242
|
duplicateToast: "Duplicated {name}. Review and Save.",
|
|
243
|
+
duplicateDialogTitle: 'Duplicate "{name}" to:',
|
|
244
|
+
duplicateTargetGlobal: "Global",
|
|
245
|
+
duplicateTargetSameRule: "Same rule as source",
|
|
246
|
+
duplicateTargetExistingRules: "Existing rules",
|
|
247
|
+
duplicateTargetNewRule: "New rule\u2026",
|
|
248
|
+
duplicateConfirm: "Duplicate",
|
|
249
|
+
duplicateCancel: "Cancel",
|
|
243
250
|
copyAndNewRuleAction: "Copy and start new rule",
|
|
244
251
|
itemsAllLabel: "All items",
|
|
245
252
|
subtitleEmpty: "Not set",
|
|
@@ -1621,6 +1628,199 @@ var ClipboardToast = ({ message, variant = "copy", onDismiss }) => {
|
|
|
1621
1628
|
}
|
|
1622
1629
|
);
|
|
1623
1630
|
};
|
|
1631
|
+
var DEFAULT_I18N2 = {
|
|
1632
|
+
title: 'Duplicate "{name}" to:',
|
|
1633
|
+
global: "Global",
|
|
1634
|
+
sameRule: "Same rule as source",
|
|
1635
|
+
existingRules: "Existing rules",
|
|
1636
|
+
newRule: "New rule\u2026",
|
|
1637
|
+
confirm: "Duplicate",
|
|
1638
|
+
cancel: "Cancel"
|
|
1639
|
+
};
|
|
1640
|
+
function DuplicateTargetPicker({
|
|
1641
|
+
open,
|
|
1642
|
+
sourceLabel,
|
|
1643
|
+
sourceHasRule,
|
|
1644
|
+
rules,
|
|
1645
|
+
supportsRules,
|
|
1646
|
+
supportsGlobal = true,
|
|
1647
|
+
onCancel,
|
|
1648
|
+
onConfirm,
|
|
1649
|
+
i18n
|
|
1650
|
+
}) {
|
|
1651
|
+
const t = { ...DEFAULT_I18N2, ...i18n ?? {} };
|
|
1652
|
+
const [picked, setPicked] = useState(null);
|
|
1653
|
+
const containerRef = useRef(null);
|
|
1654
|
+
useEffect(() => {
|
|
1655
|
+
if (!open) {
|
|
1656
|
+
setPicked(null);
|
|
1657
|
+
return;
|
|
1658
|
+
}
|
|
1659
|
+
setPicked(sourceHasRule && supportsRules ? { kind: "sameRule" } : supportsGlobal ? { kind: "global" } : null);
|
|
1660
|
+
}, [open, sourceHasRule, supportsRules, supportsGlobal]);
|
|
1661
|
+
useEffect(() => {
|
|
1662
|
+
if (!open) return;
|
|
1663
|
+
const onKey = (e) => {
|
|
1664
|
+
if (e.key === "Escape") {
|
|
1665
|
+
e.preventDefault();
|
|
1666
|
+
onCancel();
|
|
1667
|
+
}
|
|
1668
|
+
};
|
|
1669
|
+
window.addEventListener("keydown", onKey);
|
|
1670
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
1671
|
+
}, [open, onCancel]);
|
|
1672
|
+
const sortedRules = useMemo(() => rules.slice().sort((a, b) => {
|
|
1673
|
+
if (b.count !== a.count) return b.count - a.count;
|
|
1674
|
+
return a.summary.localeCompare(b.summary);
|
|
1675
|
+
}), [rules]);
|
|
1676
|
+
if (!open || typeof document === "undefined") return null;
|
|
1677
|
+
const stop = (e) => {
|
|
1678
|
+
e.stopPropagation();
|
|
1679
|
+
};
|
|
1680
|
+
const isSame = (a, b) => {
|
|
1681
|
+
if (!a || a.kind !== b.kind) return false;
|
|
1682
|
+
if (a.kind === "existingRule" && b.kind === "existingRule") {
|
|
1683
|
+
return a.ruleHash === b.ruleHash;
|
|
1684
|
+
}
|
|
1685
|
+
return true;
|
|
1686
|
+
};
|
|
1687
|
+
const RadioRow = ({
|
|
1688
|
+
target,
|
|
1689
|
+
label,
|
|
1690
|
+
sublabel,
|
|
1691
|
+
icon: Icon
|
|
1692
|
+
}) => {
|
|
1693
|
+
const checked = isSame(picked, target);
|
|
1694
|
+
return /* @__PURE__ */ jsxs(
|
|
1695
|
+
"button",
|
|
1696
|
+
{
|
|
1697
|
+
type: "button",
|
|
1698
|
+
onClick: () => setPicked(target),
|
|
1699
|
+
className: "w-full text-left px-3 py-2 rounded-md border transition-colors flex items-start gap-2.5",
|
|
1700
|
+
style: {
|
|
1701
|
+
borderColor: checked ? "hsl(var(--ra-accent))" : "hsl(var(--ra-border))",
|
|
1702
|
+
background: checked ? "hsl(var(--ra-accent) / 0.08)" : "transparent",
|
|
1703
|
+
color: "hsl(var(--ra-text))"
|
|
1704
|
+
},
|
|
1705
|
+
children: [
|
|
1706
|
+
/* @__PURE__ */ jsx(
|
|
1707
|
+
"span",
|
|
1708
|
+
{
|
|
1709
|
+
className: "mt-0.5 inline-flex items-center justify-center h-4 w-4 rounded-full shrink-0",
|
|
1710
|
+
style: {
|
|
1711
|
+
border: `2px solid ${checked ? "hsl(var(--ra-accent))" : "hsl(var(--ra-border))"}`
|
|
1712
|
+
},
|
|
1713
|
+
children: checked && /* @__PURE__ */ jsx(
|
|
1714
|
+
"span",
|
|
1715
|
+
{
|
|
1716
|
+
className: "h-2 w-2 rounded-full",
|
|
1717
|
+
style: { background: "hsl(var(--ra-accent))" }
|
|
1718
|
+
}
|
|
1719
|
+
)
|
|
1720
|
+
}
|
|
1721
|
+
),
|
|
1722
|
+
Icon && /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4 mt-0.5 shrink-0" }),
|
|
1723
|
+
/* @__PURE__ */ jsxs("span", { className: "min-w-0 flex-1", children: [
|
|
1724
|
+
/* @__PURE__ */ jsx("span", { className: "block text-sm", children: label }),
|
|
1725
|
+
sublabel && /* @__PURE__ */ jsx("span", { className: "block text-xs truncate", style: { color: "hsl(var(--ra-muted-text))" }, children: sublabel })
|
|
1726
|
+
] })
|
|
1727
|
+
]
|
|
1728
|
+
}
|
|
1729
|
+
);
|
|
1730
|
+
};
|
|
1731
|
+
const showRuleSection = supportsRules && (sortedRules.length > 0 || sourceHasRule);
|
|
1732
|
+
return createPortal(
|
|
1733
|
+
/* @__PURE__ */ jsx(
|
|
1734
|
+
"div",
|
|
1735
|
+
{
|
|
1736
|
+
className: "fixed inset-0 z-[1000] flex items-center justify-center",
|
|
1737
|
+
style: { background: "hsl(0 0% 0% / 0.4)" },
|
|
1738
|
+
onClick: onCancel,
|
|
1739
|
+
onMouseDown: stop,
|
|
1740
|
+
onTouchStart: stop,
|
|
1741
|
+
children: /* @__PURE__ */ jsxs(
|
|
1742
|
+
"div",
|
|
1743
|
+
{
|
|
1744
|
+
ref: containerRef,
|
|
1745
|
+
role: "dialog",
|
|
1746
|
+
"aria-modal": "true",
|
|
1747
|
+
className: "w-[min(28rem,calc(100vw-2rem))] max-h-[min(36rem,calc(100vh-2rem))] flex flex-col rounded-lg shadow-xl",
|
|
1748
|
+
style: { background: "hsl(var(--ra-surface))", border: "1px solid hsl(var(--ra-border))" },
|
|
1749
|
+
onClick: stop,
|
|
1750
|
+
onMouseDown: stop,
|
|
1751
|
+
onTouchStart: stop,
|
|
1752
|
+
children: [
|
|
1753
|
+
/* @__PURE__ */ jsx("header", { className: "px-4 py-3 border-b", style: { borderColor: "hsl(var(--ra-border))" }, children: /* @__PURE__ */ jsx("h2", { className: "text-sm font-semibold", style: { color: "hsl(var(--ra-text))" }, children: t.title.replace("{name}", sourceLabel) }) }),
|
|
1754
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 min-h-0 overflow-y-auto px-4 py-3 space-y-3", children: [
|
|
1755
|
+
supportsGlobal && /* @__PURE__ */ jsx(RadioRow, { target: { kind: "global" }, label: t.global, icon: Globe2 }),
|
|
1756
|
+
showRuleSection && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1757
|
+
sourceHasRule && /* @__PURE__ */ jsx(RadioRow, { target: { kind: "sameRule" }, label: t.sameRule, icon: Target }),
|
|
1758
|
+
sortedRules.length > 0 && /* @__PURE__ */ jsxs("div", { children: [
|
|
1759
|
+
/* @__PURE__ */ jsx(
|
|
1760
|
+
"div",
|
|
1761
|
+
{
|
|
1762
|
+
className: "text-[10px] uppercase tracking-wide mb-1.5 px-1",
|
|
1763
|
+
style: { color: "hsl(var(--ra-muted-text))" },
|
|
1764
|
+
children: t.existingRules
|
|
1765
|
+
}
|
|
1766
|
+
),
|
|
1767
|
+
/* @__PURE__ */ jsx("div", { className: "space-y-1.5", children: sortedRules.map((r) => /* @__PURE__ */ jsx(
|
|
1768
|
+
RadioRow,
|
|
1769
|
+
{
|
|
1770
|
+
target: { kind: "existingRule", ruleHash: r.hash },
|
|
1771
|
+
label: r.summary,
|
|
1772
|
+
sublabel: `${r.count} record${r.count === 1 ? "" : "s"}`,
|
|
1773
|
+
icon: Target
|
|
1774
|
+
},
|
|
1775
|
+
r.hash
|
|
1776
|
+
)) })
|
|
1777
|
+
] }),
|
|
1778
|
+
/* @__PURE__ */ jsx(RadioRow, { target: { kind: "newRule" }, label: t.newRule, icon: Sparkles })
|
|
1779
|
+
] })
|
|
1780
|
+
] }),
|
|
1781
|
+
/* @__PURE__ */ jsxs(
|
|
1782
|
+
"footer",
|
|
1783
|
+
{
|
|
1784
|
+
className: "px-4 py-3 border-t flex items-center justify-end gap-2",
|
|
1785
|
+
style: { borderColor: "hsl(var(--ra-border))" },
|
|
1786
|
+
children: [
|
|
1787
|
+
/* @__PURE__ */ jsx(
|
|
1788
|
+
"button",
|
|
1789
|
+
{
|
|
1790
|
+
type: "button",
|
|
1791
|
+
onClick: onCancel,
|
|
1792
|
+
className: "text-xs px-3 py-1.5 rounded-md border hover:bg-[hsl(var(--ra-muted))]",
|
|
1793
|
+
style: { borderColor: "hsl(var(--ra-border))", color: "hsl(var(--ra-text))" },
|
|
1794
|
+
children: t.cancel
|
|
1795
|
+
}
|
|
1796
|
+
),
|
|
1797
|
+
/* @__PURE__ */ jsxs(
|
|
1798
|
+
"button",
|
|
1799
|
+
{
|
|
1800
|
+
type: "button",
|
|
1801
|
+
disabled: !picked,
|
|
1802
|
+
onClick: () => {
|
|
1803
|
+
if (picked) onConfirm(picked);
|
|
1804
|
+
},
|
|
1805
|
+
className: "text-xs px-3 py-1.5 rounded-md font-medium transition-opacity disabled:opacity-40 inline-flex items-center gap-1.5",
|
|
1806
|
+
style: { background: "hsl(var(--ra-accent))", color: "hsl(var(--ra-surface))" },
|
|
1807
|
+
children: [
|
|
1808
|
+
/* @__PURE__ */ jsx(Plus, { className: "h-3.5 w-3.5" }),
|
|
1809
|
+
t.confirm
|
|
1810
|
+
]
|
|
1811
|
+
}
|
|
1812
|
+
)
|
|
1813
|
+
]
|
|
1814
|
+
}
|
|
1815
|
+
)
|
|
1816
|
+
]
|
|
1817
|
+
}
|
|
1818
|
+
)
|
|
1819
|
+
}
|
|
1820
|
+
),
|
|
1821
|
+
document.body
|
|
1822
|
+
);
|
|
1823
|
+
}
|
|
1624
1824
|
function useShellClipboard(args) {
|
|
1625
1825
|
const {
|
|
1626
1826
|
enabled,
|
|
@@ -1640,7 +1840,11 @@ function useShellClipboard(args) {
|
|
|
1640
1840
|
onLeftSelectRef,
|
|
1641
1841
|
onCreateItemDraftRef,
|
|
1642
1842
|
onCreateRuleFromClipboardRef,
|
|
1643
|
-
isRuleTab
|
|
1843
|
+
isRuleTab,
|
|
1844
|
+
ruleCatalogue,
|
|
1845
|
+
ruleCatalogueRef,
|
|
1846
|
+
supportsRules = true,
|
|
1847
|
+
onCreateItemDraftAtScopeRef
|
|
1644
1848
|
} = args;
|
|
1645
1849
|
const clipboard = useRecordClipboard({
|
|
1646
1850
|
appId,
|
|
@@ -1770,17 +1974,98 @@ function useShellClipboard(args) {
|
|
|
1770
1974
|
// wired item-draft creation. Pushes the editor value to the clipboard
|
|
1771
1975
|
// (so the existing pendingPasteTarget effect lands it on the new
|
|
1772
1976
|
// draft) and mints a fresh draft.
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1977
|
+
// Collection-cardinality clone affordance — opens the Duplicate-To
|
|
1978
|
+
// picker so the admin chooses the destination scope (Global, an
|
|
1979
|
+
// existing rule, a new rule, or the source's own rule). We only show
|
|
1980
|
+
// this when the host wired the at-scope draft creator, since without
|
|
1981
|
+
// it we have nowhere to mint the duplicate.
|
|
1982
|
+
onDuplicate: isCollection && !!onCreateItemDraftAtScopeRef ? () => {
|
|
1983
|
+
if (!editingScope) return;
|
|
1984
|
+
openDuplicatePicker({
|
|
1985
|
+
value: editorCtx.value,
|
|
1986
|
+
scope: editingScope,
|
|
1987
|
+
label: editorHeaderLabel ?? editingScope.raw,
|
|
1988
|
+
facetRule: resolved.facetRule ?? null,
|
|
1989
|
+
recordId: resolved.recordId
|
|
1990
|
+
});
|
|
1781
1991
|
} : void 0
|
|
1782
1992
|
} : void 0;
|
|
1783
1993
|
const [pendingPasteTarget, setPendingPasteTarget] = useState(null);
|
|
1994
|
+
const [pendingSeed, setPendingSeed] = useState(null);
|
|
1995
|
+
const [pickerOpen, setPickerOpen] = useState(false);
|
|
1996
|
+
const pickerSourceRef = useRef(null);
|
|
1997
|
+
const openDuplicatePicker = useCallback((source) => {
|
|
1998
|
+
pickerSourceRef.current = {
|
|
1999
|
+
value: source.value,
|
|
2000
|
+
scope: source.scope,
|
|
2001
|
+
label: source.label,
|
|
2002
|
+
facetRule: source.facetRule ?? null,
|
|
2003
|
+
recordId: source.recordId
|
|
2004
|
+
};
|
|
2005
|
+
setPickerOpen(true);
|
|
2006
|
+
}, []);
|
|
2007
|
+
const onDuplicatePickerCancel = useCallback(() => {
|
|
2008
|
+
setPickerOpen(false);
|
|
2009
|
+
pickerSourceRef.current = null;
|
|
2010
|
+
}, []);
|
|
2011
|
+
const onDuplicatePickerConfirm = useCallback((target) => {
|
|
2012
|
+
const source = pickerSourceRef.current;
|
|
2013
|
+
setPickerOpen(false);
|
|
2014
|
+
pickerSourceRef.current = null;
|
|
2015
|
+
if (!source) return;
|
|
2016
|
+
const transformed = onCopyOverride ? onCopyOverride({ value: source.value, scope: source.scope }) : cloneValue(source.value);
|
|
2017
|
+
if (target.kind === "newRule") {
|
|
2018
|
+
clipboard.set({
|
|
2019
|
+
value: transformed,
|
|
2020
|
+
sourceScope: source.scope,
|
|
2021
|
+
sourceRecordId: source.recordId,
|
|
2022
|
+
sourceLabel: source.label
|
|
2023
|
+
});
|
|
2024
|
+
onTelemetry?.({
|
|
2025
|
+
type: "clipboard.copy",
|
|
2026
|
+
recordType,
|
|
2027
|
+
sourceRef: anchorKey(source.scope)
|
|
2028
|
+
});
|
|
2029
|
+
window.setTimeout(() => onCreateRuleFromClipboardRef?.current?.(), 0);
|
|
2030
|
+
return;
|
|
2031
|
+
}
|
|
2032
|
+
const create = onCreateItemDraftAtScopeRef?.current;
|
|
2033
|
+
if (!create) return;
|
|
2034
|
+
let scopeArg;
|
|
2035
|
+
if (target.kind === "global") {
|
|
2036
|
+
scopeArg = { kind: "global" };
|
|
2037
|
+
} else if (target.kind === "sameRule") {
|
|
2038
|
+
if (!source.facetRule) return;
|
|
2039
|
+
scopeArg = { kind: "rule", rule: source.facetRule };
|
|
2040
|
+
} else {
|
|
2041
|
+
const list = ruleCatalogueRef?.current ?? ruleCatalogue ?? [];
|
|
2042
|
+
const hit = list.find((r) => r.hash === target.ruleHash);
|
|
2043
|
+
if (!hit) return;
|
|
2044
|
+
scopeArg = { kind: "rule", rule: hit.rule };
|
|
2045
|
+
}
|
|
2046
|
+
const newId = create(scopeArg);
|
|
2047
|
+
if (!newId) return;
|
|
2048
|
+
setPendingPasteTarget({ kind: "record", recordId: newId });
|
|
2049
|
+
setPendingSeed({ value: transformed, sourceLabel: source.label });
|
|
2050
|
+
onTelemetry?.({
|
|
2051
|
+
type: "clipboard.copy",
|
|
2052
|
+
recordType,
|
|
2053
|
+
sourceRef: anchorKey(source.scope)
|
|
2054
|
+
});
|
|
2055
|
+
setNotice({
|
|
2056
|
+
message: i18n.duplicateToast.replace("{name}", source.label),
|
|
2057
|
+
variant: "copy"
|
|
2058
|
+
});
|
|
2059
|
+
}, [
|
|
2060
|
+
onCopyOverride,
|
|
2061
|
+
clipboard,
|
|
2062
|
+
onTelemetry,
|
|
2063
|
+
recordType,
|
|
2064
|
+
onCreateRuleFromClipboardRef,
|
|
2065
|
+
onCreateItemDraftAtScopeRef,
|
|
2066
|
+
ruleCatalogue,
|
|
2067
|
+
i18n.duplicateToast
|
|
2068
|
+
]);
|
|
1784
2069
|
useEffect(() => {
|
|
1785
2070
|
if (!pendingPasteTarget) return;
|
|
1786
2071
|
if (!editingScope) return;
|
|
@@ -1788,14 +2073,27 @@ function useShellClipboard(args) {
|
|
|
1788
2073
|
if (!matched) return;
|
|
1789
2074
|
const t = window.setTimeout(() => {
|
|
1790
2075
|
setPendingPasteTarget(null);
|
|
2076
|
+
if (pendingSeed) {
|
|
2077
|
+
const seed = pendingSeed;
|
|
2078
|
+
setPendingSeed(null);
|
|
2079
|
+
editorCtx.onChange(seed.value);
|
|
2080
|
+
onTelemetry?.({
|
|
2081
|
+
type: "clipboard.paste",
|
|
2082
|
+
recordType,
|
|
2083
|
+
sourceRef: anchorKey(editingScope),
|
|
2084
|
+
destinationRef: editingScope.raw,
|
|
2085
|
+
replaced: false
|
|
2086
|
+
});
|
|
2087
|
+
return;
|
|
2088
|
+
}
|
|
1791
2089
|
void pasteCurrent();
|
|
1792
2090
|
}, 0);
|
|
1793
2091
|
return () => window.clearTimeout(t);
|
|
1794
|
-
}, [pendingPasteTarget, editingScope, isCollection, selectedItemId, selectedRecordId, pasteCurrent]);
|
|
2092
|
+
}, [pendingPasteTarget, editingScope, isCollection, selectedItemId, selectedRecordId, pasteCurrent, pendingSeed, editorCtx, onTelemetry, recordType]);
|
|
1795
2093
|
const rowClipboard = enabled ? (record) => {
|
|
1796
2094
|
const summaryHasData = record.data != null;
|
|
1797
2095
|
const sourceParsed = record.scope;
|
|
1798
|
-
const canDuplicate = summaryHasData && isCollection;
|
|
2096
|
+
const canDuplicate = summaryHasData && isCollection && !!onCreateItemDraftAtScopeRef;
|
|
1799
2097
|
const canCopyAndNewRule = summaryHasData && !isCollection && !!isRuleTab && !!onCreateRuleFromClipboardRef;
|
|
1800
2098
|
return {
|
|
1801
2099
|
onCopy: summaryHasData ? () => {
|
|
@@ -1826,24 +2124,12 @@ function useShellClipboard(args) {
|
|
|
1826
2124
|
}, 0);
|
|
1827
2125
|
} : void 0,
|
|
1828
2126
|
onDuplicate: canDuplicate ? () => {
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
});
|
|
1836
|
-
onTelemetry?.({ type: "clipboard.copy", recordType, sourceRef: anchorKey(record.scope) });
|
|
1837
|
-
onLeftSelectRef.current?.(record);
|
|
1838
|
-
const create = onCreateItemDraftRef?.current;
|
|
1839
|
-
if (!create) return;
|
|
1840
|
-
window.setTimeout(() => {
|
|
1841
|
-
const newId = create();
|
|
1842
|
-
if (newId) setPendingPasteTarget({ kind: "record", recordId: newId });
|
|
1843
|
-
}, 0);
|
|
1844
|
-
setNotice({
|
|
1845
|
-
message: i18n.duplicateToast.replace("{name}", record.label),
|
|
1846
|
-
variant: "copy"
|
|
2127
|
+
openDuplicatePicker({
|
|
2128
|
+
value: record.data,
|
|
2129
|
+
scope: sourceParsed,
|
|
2130
|
+
label: record.label,
|
|
2131
|
+
facetRule: record.facetRule ?? null,
|
|
2132
|
+
recordId: record.id ?? void 0
|
|
1847
2133
|
});
|
|
1848
2134
|
} : void 0
|
|
1849
2135
|
};
|
|
@@ -1856,10 +2142,36 @@ function useShellClipboard(args) {
|
|
|
1856
2142
|
onDismiss: () => setNotice(null)
|
|
1857
2143
|
}
|
|
1858
2144
|
) : null;
|
|
2145
|
+
const duplicatePicker = enabled ? /* @__PURE__ */ jsx(
|
|
2146
|
+
DuplicateTargetPicker,
|
|
2147
|
+
{
|
|
2148
|
+
open: pickerOpen,
|
|
2149
|
+
sourceLabel: pickerSourceRef.current?.label ?? "",
|
|
2150
|
+
sourceHasRule: !!pickerSourceRef.current?.facetRule,
|
|
2151
|
+
rules: (ruleCatalogueRef?.current ?? ruleCatalogue ?? []).map((r) => ({
|
|
2152
|
+
hash: r.hash,
|
|
2153
|
+
summary: r.summary,
|
|
2154
|
+
count: r.count
|
|
2155
|
+
})),
|
|
2156
|
+
supportsRules,
|
|
2157
|
+
onCancel: onDuplicatePickerCancel,
|
|
2158
|
+
onConfirm: onDuplicatePickerConfirm,
|
|
2159
|
+
i18n: {
|
|
2160
|
+
title: i18n.duplicateDialogTitle,
|
|
2161
|
+
global: i18n.duplicateTargetGlobal,
|
|
2162
|
+
sameRule: i18n.duplicateTargetSameRule,
|
|
2163
|
+
existingRules: i18n.duplicateTargetExistingRules,
|
|
2164
|
+
newRule: i18n.duplicateTargetNewRule,
|
|
2165
|
+
confirm: i18n.duplicateConfirm,
|
|
2166
|
+
cancel: i18n.duplicateCancel
|
|
2167
|
+
}
|
|
2168
|
+
}
|
|
2169
|
+
) : null;
|
|
1859
2170
|
return {
|
|
1860
2171
|
editorClipboard,
|
|
1861
2172
|
rowClipboard,
|
|
1862
2173
|
confirmDialog: pasteConfirm.dialog,
|
|
2174
|
+
duplicatePicker,
|
|
1863
2175
|
toast,
|
|
1864
2176
|
store: clipboard
|
|
1865
2177
|
};
|
|
@@ -9178,6 +9490,8 @@ function RecordsAdminShellInner(props) {
|
|
|
9178
9490
|
]);
|
|
9179
9491
|
const onLeftSelectRef = useRef(null);
|
|
9180
9492
|
const onCreateItemDraftRef = useRef(null);
|
|
9493
|
+
const onCreateItemDraftAtScopeRef = useRef(null);
|
|
9494
|
+
const ruleCatalogueRef = useRef([]);
|
|
9181
9495
|
const onCreateRuleFromClipboardRef = useRef(null);
|
|
9182
9496
|
const previewReopenAnchorRef = useRef(null);
|
|
9183
9497
|
const { runWithGuard } = useDirtyNavigation({
|
|
@@ -9230,14 +9544,17 @@ function RecordsAdminShellInner(props) {
|
|
|
9230
9544
|
isCollection,
|
|
9231
9545
|
selectedItemId,
|
|
9232
9546
|
selectedRecordId,
|
|
9233
|
-
resolved: { source: resolved.source, recordId: resolved.recordId },
|
|
9547
|
+
resolved: { source: resolved.source, recordId: resolved.recordId, facetRule: resolved.facetRule ?? null },
|
|
9234
9548
|
onTelemetry,
|
|
9235
9549
|
onCopyOverride,
|
|
9236
9550
|
onPasteOverride,
|
|
9237
9551
|
onLeftSelectRef,
|
|
9238
9552
|
onCreateItemDraftRef,
|
|
9239
9553
|
onCreateRuleFromClipboardRef,
|
|
9240
|
-
isRuleTab: activeScope === "rule" || activeScope === "collection"
|
|
9554
|
+
isRuleTab: activeScope === "rule" || activeScope === "collection",
|
|
9555
|
+
ruleCatalogueRef,
|
|
9556
|
+
supportsRules: effectiveTopLevelScopes.includes("rule"),
|
|
9557
|
+
onCreateItemDraftAtScopeRef
|
|
9241
9558
|
});
|
|
9242
9559
|
const editorClipboard = shellClipboard.editorClipboard;
|
|
9243
9560
|
const rowClipboard = shellClipboard.rowClipboard;
|
|
@@ -10019,6 +10336,26 @@ function RecordsAdminShellInner(props) {
|
|
|
10019
10336
|
onLeftSelectRef.current = onLeftSelect;
|
|
10020
10337
|
onCreateItemDraftRef.current = onItemCreate;
|
|
10021
10338
|
onCreateRuleFromClipboardRef.current = () => onCreateRule("paste");
|
|
10339
|
+
ruleCatalogueRef.current = ruleCatalogue;
|
|
10340
|
+
onCreateItemDraftAtScopeRef.current = (target) => {
|
|
10341
|
+
if (!isCollection) return null;
|
|
10342
|
+
if (target.kind === "global") {
|
|
10343
|
+
if (activeScope !== "collection") setActiveScope("collection");
|
|
10344
|
+
setSelectedRecordId(null);
|
|
10345
|
+
setDraftKind(null);
|
|
10346
|
+
const id2 = onItemCreate();
|
|
10347
|
+
return id2 ?? null;
|
|
10348
|
+
}
|
|
10349
|
+
if (activeScope !== "rule") setActiveScope("rule");
|
|
10350
|
+
setRuleWizardRule(target.rule);
|
|
10351
|
+
setRuleWizardSeedMode(null);
|
|
10352
|
+
setRuleWizardDraftKey(null);
|
|
10353
|
+
setRuleWizardStep(2);
|
|
10354
|
+
setDraftKind("rule");
|
|
10355
|
+
setSelectedRecordId(null);
|
|
10356
|
+
const id = onItemCreate();
|
|
10357
|
+
return id ?? null;
|
|
10358
|
+
};
|
|
10022
10359
|
return /* @__PURE__ */ jsx(RuleLabelLookupProvider, { value: ruleLabelLookup, children: /* @__PURE__ */ jsxs(
|
|
10023
10360
|
"div",
|
|
10024
10361
|
{
|
|
@@ -10027,6 +10364,7 @@ function RecordsAdminShellInner(props) {
|
|
|
10027
10364
|
children: [
|
|
10028
10365
|
dirtyConfirm.dialog,
|
|
10029
10366
|
shellClipboard.confirmDialog,
|
|
10367
|
+
shellClipboard.duplicatePicker,
|
|
10030
10368
|
shellClipboard.toast,
|
|
10031
10369
|
(() => {
|
|
10032
10370
|
const showFloatHelp = !!intro && dismissed && resolvedReopenAffordance === "footer" && !headerWillRender;
|