@tightknitai/block-kitchen 0.8.3 → 0.8.4
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/README.md +7 -0
- package/dist/helpers.d.cts +2 -2
- package/dist/helpers.d.ts +2 -2
- package/dist/index.cjs +785 -142
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +788 -145
- package/dist/index.js.map +1 -1
- package/dist/{palette-CTUROcUC.d.cts → palette-Dco-CVMi.d.cts} +32 -1
- package/dist/{palette-CTUROcUC.d.ts → palette-Dco-CVMi.d.ts} +32 -1
- package/dist/palette.d.cts +1 -1
- package/dist/palette.d.ts +1 -1
- package/dist/styles.css +1 -1
- package/package.json +3 -2
package/dist/index.cjs
CHANGED
|
@@ -4,10 +4,10 @@ var core = require('@dnd-kit/core');
|
|
|
4
4
|
var sortable = require('@dnd-kit/sortable');
|
|
5
5
|
var lucideReact = require('lucide-react');
|
|
6
6
|
var React = require('react');
|
|
7
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
7
8
|
var DialogPrimitive = require('@radix-ui/react-dialog');
|
|
8
9
|
var clsx = require('clsx');
|
|
9
10
|
var tailwindMerge = require('tailwind-merge');
|
|
10
|
-
var jsxRuntime = require('react/jsx-runtime');
|
|
11
11
|
var TooltipPrimitive = require('@radix-ui/react-tooltip');
|
|
12
12
|
var slackBlockKitValidator = require('@tightknitai/slack-block-kit-validator');
|
|
13
13
|
var nanoid = require('nanoid');
|
|
@@ -53,6 +53,35 @@ var Link__default = /*#__PURE__*/_interopDefault(Link);
|
|
|
53
53
|
var StarterKit__default = /*#__PURE__*/_interopDefault(StarterKit);
|
|
54
54
|
|
|
55
55
|
// src/components/block-kitchen.tsx
|
|
56
|
+
function makeEmojiHook(byName) {
|
|
57
|
+
return (data, parse) => {
|
|
58
|
+
const entry = byName.get(data.name);
|
|
59
|
+
if (entry) {
|
|
60
|
+
if (entry.url) {
|
|
61
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
62
|
+
"img",
|
|
63
|
+
{
|
|
64
|
+
src: entry.url,
|
|
65
|
+
alt: `:${data.name}:`,
|
|
66
|
+
title: `:${data.name}:`,
|
|
67
|
+
className: "bk-custom-emoji",
|
|
68
|
+
style: {
|
|
69
|
+
display: "inline-block",
|
|
70
|
+
width: "1.25em",
|
|
71
|
+
height: "1.25em",
|
|
72
|
+
verticalAlign: "-0.25em",
|
|
73
|
+
objectFit: "contain"
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
if (entry.alias) {
|
|
79
|
+
return parse({ name: entry.alias, unicode: data.unicode, skin_tone: data.skin_tone });
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return parse(data);
|
|
83
|
+
};
|
|
84
|
+
}
|
|
56
85
|
var defaultPalette = [
|
|
57
86
|
{
|
|
58
87
|
name: "Agents",
|
|
@@ -1549,50 +1578,63 @@ function useIsMobile() {
|
|
|
1549
1578
|
}, []);
|
|
1550
1579
|
return isMobile;
|
|
1551
1580
|
}
|
|
1581
|
+
var EMPTY = {
|
|
1582
|
+
customEmojis: [],
|
|
1583
|
+
byName: /* @__PURE__ */ new Map()
|
|
1584
|
+
};
|
|
1585
|
+
var CustomEmojiContext = React.createContext(EMPTY);
|
|
1586
|
+
function CustomEmojiProvider({ customEmojis, children }) {
|
|
1587
|
+
const value = React.useMemo(() => {
|
|
1588
|
+
if (!customEmojis || customEmojis.length === 0) {
|
|
1589
|
+
return EMPTY;
|
|
1590
|
+
}
|
|
1591
|
+
const byName = /* @__PURE__ */ new Map();
|
|
1592
|
+
for (const emoji of customEmojis) {
|
|
1593
|
+
byName.set(emoji.name, emoji);
|
|
1594
|
+
}
|
|
1595
|
+
return { customEmojis, byName };
|
|
1596
|
+
}, [customEmojis]);
|
|
1597
|
+
return /* @__PURE__ */ jsxRuntime.jsx(CustomEmojiContext.Provider, { value, children });
|
|
1598
|
+
}
|
|
1599
|
+
function useCustomEmojis() {
|
|
1600
|
+
return React.useContext(CustomEmojiContext);
|
|
1601
|
+
}
|
|
1552
1602
|
|
|
1553
1603
|
// src/lib/error-grouping.ts
|
|
1554
1604
|
function toValidatorSurface(surface) {
|
|
1555
1605
|
return surface === "app_home" ? "home" : surface;
|
|
1556
1606
|
}
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1607
|
+
var BLOCK_PREFIX = /^blocks\[(\d+)\]/;
|
|
1608
|
+
function extractBlockIndex(raw) {
|
|
1609
|
+
const match = BLOCK_PREFIX.exec(raw);
|
|
1610
|
+
return match ? Number.parseInt(match[1], 10) : null;
|
|
1611
|
+
}
|
|
1612
|
+
function toBlockMessage(raw) {
|
|
1613
|
+
const rest = raw.replace(BLOCK_PREFIX, "");
|
|
1614
|
+
if (rest.startsWith(":")) {
|
|
1615
|
+
return rest.replace(/^:\s*/, "");
|
|
1564
1616
|
}
|
|
1565
|
-
return
|
|
1617
|
+
return rest.replace(/^\./, "");
|
|
1566
1618
|
}
|
|
1567
|
-
function
|
|
1568
|
-
return
|
|
1619
|
+
function toGeneralMessage(raw) {
|
|
1620
|
+
return raw.startsWith("(root)") ? raw.replace(/^\(root\):?\s*/, "") : raw;
|
|
1569
1621
|
}
|
|
1570
1622
|
function groupValidatorErrors(errors, blocks) {
|
|
1571
1623
|
const byBlockId = /* @__PURE__ */ new Map();
|
|
1572
1624
|
const general = [];
|
|
1573
1625
|
for (const raw of errors) {
|
|
1574
|
-
const
|
|
1575
|
-
const path = space === -1 ? "(root)" : raw.slice(0, space);
|
|
1576
|
-
const message = space === -1 ? raw : raw.slice(space + 1);
|
|
1577
|
-
const idx = extractBlockIndex(path);
|
|
1626
|
+
const idx = extractBlockIndex(raw);
|
|
1578
1627
|
if (idx === null || idx >= blocks.length) {
|
|
1579
|
-
general.push(raw);
|
|
1628
|
+
general.push(toGeneralMessage(raw));
|
|
1580
1629
|
continue;
|
|
1581
1630
|
}
|
|
1582
1631
|
const id = blocks[idx].id;
|
|
1583
|
-
const friendly = formatMessage(stripBlockPrefix(path), message);
|
|
1584
1632
|
const bucket = byBlockId.get(id) ?? [];
|
|
1585
|
-
bucket.push(
|
|
1633
|
+
bucket.push(toBlockMessage(raw));
|
|
1586
1634
|
byBlockId.set(id, bucket);
|
|
1587
1635
|
}
|
|
1588
1636
|
return { byBlockId, general, total: errors.length };
|
|
1589
1637
|
}
|
|
1590
|
-
function formatMessage(relativePath, message) {
|
|
1591
|
-
if (relativePath === "" || relativePath === "(root)") {
|
|
1592
|
-
return message;
|
|
1593
|
-
}
|
|
1594
|
-
return `${relativePath}: ${message}`;
|
|
1595
|
-
}
|
|
1596
1638
|
|
|
1597
1639
|
// src/lib/url-safety.ts
|
|
1598
1640
|
var SAFE_LINK_PROTOCOLS = /^(https?|ircs?|mailto|tel|sms|xmpp)$/i;
|
|
@@ -1712,10 +1754,47 @@ function toSlackBlocks(blocks) {
|
|
|
1712
1754
|
});
|
|
1713
1755
|
}
|
|
1714
1756
|
|
|
1757
|
+
// src/lib/validation-sanitize.ts
|
|
1758
|
+
var VALIDATION_EMOJI_PLACEHOLDER = "white_check_mark";
|
|
1759
|
+
function rewriteEmojiNames(value) {
|
|
1760
|
+
if (Array.isArray(value)) {
|
|
1761
|
+
let changed = false;
|
|
1762
|
+
const next = value.map((item) => {
|
|
1763
|
+
const rewritten = rewriteEmojiNames(item);
|
|
1764
|
+
if (rewritten !== item) {
|
|
1765
|
+
changed = true;
|
|
1766
|
+
}
|
|
1767
|
+
return rewritten;
|
|
1768
|
+
});
|
|
1769
|
+
return changed ? next : value;
|
|
1770
|
+
}
|
|
1771
|
+
if (value !== null && typeof value === "object") {
|
|
1772
|
+
const source = value;
|
|
1773
|
+
let changed = false;
|
|
1774
|
+
const next = {};
|
|
1775
|
+
for (const key of Object.keys(source)) {
|
|
1776
|
+
const rewritten = rewriteEmojiNames(source[key]);
|
|
1777
|
+
next[key] = rewritten;
|
|
1778
|
+
if (rewritten !== source[key]) {
|
|
1779
|
+
changed = true;
|
|
1780
|
+
}
|
|
1781
|
+
}
|
|
1782
|
+
if (next.type === "emoji" && typeof next.name === "string" && next.name !== VALIDATION_EMOJI_PLACEHOLDER) {
|
|
1783
|
+
next.name = VALIDATION_EMOJI_PLACEHOLDER;
|
|
1784
|
+
changed = true;
|
|
1785
|
+
}
|
|
1786
|
+
return changed ? next : value;
|
|
1787
|
+
}
|
|
1788
|
+
return value;
|
|
1789
|
+
}
|
|
1790
|
+
function stripCustomEmojiForValidation(blocks) {
|
|
1791
|
+
return blocks.map((block) => rewriteEmojiNames(block));
|
|
1792
|
+
}
|
|
1793
|
+
|
|
1715
1794
|
// src/state/use-block-kit-validation.ts
|
|
1716
1795
|
var DEBOUNCE_MS = 150;
|
|
1717
1796
|
function computeValidation(blocks, surface) {
|
|
1718
|
-
const payload = toSlackBlocks(blocks.map((b) => b.block));
|
|
1797
|
+
const payload = stripCustomEmojiForValidation(toSlackBlocks(blocks.map((b) => b.block)));
|
|
1719
1798
|
const result = slackBlockKitValidator.validateBlockKit(payload, {
|
|
1720
1799
|
target: "blocks",
|
|
1721
1800
|
surface: toValidatorSurface(surface)
|
|
@@ -3561,52 +3640,55 @@ function ContextEditor({ block, onChange }) {
|
|
|
3561
3640
|
{
|
|
3562
3641
|
label: "Items",
|
|
3563
3642
|
help: "Shown in a horizontal row, usually as footnote-style content. Slack supports up to 10 items.",
|
|
3564
|
-
children: /* @__PURE__ */ jsxRuntime.
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3643
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
3644
|
+
elements.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "rounded-md border border-dashed bg-muted/20 px-3 py-2 text-xs text-muted-foreground", children: "No items yet \u2014 a context block needs at least one. Add text or an image below, or delete the block if you don't need it." }) : null,
|
|
3645
|
+
elements.map((el, idx) => {
|
|
3646
|
+
if (el.type === "mrkdwn" || el.type === "plain_text") {
|
|
3647
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3648
|
+
TextItemCard,
|
|
3649
|
+
{
|
|
3650
|
+
index: idx,
|
|
3651
|
+
element: el,
|
|
3652
|
+
onChange: (next) => updateAt(idx, () => next),
|
|
3653
|
+
onRemove: () => removeAt(idx)
|
|
3654
|
+
},
|
|
3655
|
+
idx
|
|
3656
|
+
);
|
|
3657
|
+
}
|
|
3658
|
+
if (el.type === "image" && "image_url" in el) {
|
|
3659
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3660
|
+
ImageItemCard,
|
|
3661
|
+
{
|
|
3662
|
+
index: idx,
|
|
3663
|
+
element: el,
|
|
3664
|
+
onChange: (next) => updateAt(idx, () => next),
|
|
3665
|
+
onRemove: () => removeAt(idx)
|
|
3666
|
+
},
|
|
3667
|
+
idx
|
|
3668
|
+
);
|
|
3669
|
+
}
|
|
3670
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3671
|
+
"div",
|
|
3580
3672
|
{
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
3673
|
+
className: "flex items-center justify-between rounded border bg-muted/30 px-2 py-1.5 text-xs text-muted-foreground",
|
|
3674
|
+
children: [
|
|
3675
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "Item not editable in the visual builder" }),
|
|
3676
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3677
|
+
"button",
|
|
3678
|
+
{
|
|
3679
|
+
type: "button",
|
|
3680
|
+
"aria-label": "Remove item",
|
|
3681
|
+
onClick: () => removeAt(idx),
|
|
3682
|
+
className: "text-muted-foreground hover:text-destructive",
|
|
3683
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { className: "h-3.5 w-3.5" })
|
|
3684
|
+
}
|
|
3685
|
+
)
|
|
3686
|
+
]
|
|
3585
3687
|
},
|
|
3586
3688
|
idx
|
|
3587
3689
|
);
|
|
3588
|
-
}
|
|
3589
|
-
|
|
3590
|
-
"div",
|
|
3591
|
-
{
|
|
3592
|
-
className: "flex items-center justify-between rounded border bg-muted/30 px-2 py-1.5 text-xs text-muted-foreground",
|
|
3593
|
-
children: [
|
|
3594
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "Item not editable in the visual builder" }),
|
|
3595
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3596
|
-
"button",
|
|
3597
|
-
{
|
|
3598
|
-
type: "button",
|
|
3599
|
-
"aria-label": "Remove item",
|
|
3600
|
-
onClick: () => removeAt(idx),
|
|
3601
|
-
className: "text-muted-foreground hover:text-destructive",
|
|
3602
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { className: "h-3.5 w-3.5" })
|
|
3603
|
-
}
|
|
3604
|
-
)
|
|
3605
|
-
]
|
|
3606
|
-
},
|
|
3607
|
-
idx
|
|
3608
|
-
);
|
|
3609
|
-
}) })
|
|
3690
|
+
})
|
|
3691
|
+
] })
|
|
3610
3692
|
}
|
|
3611
3693
|
),
|
|
3612
3694
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2", children: [
|
|
@@ -3745,12 +3827,423 @@ function ImageItemCard({
|
|
|
3745
3827
|
function DividerEditor() {
|
|
3746
3828
|
return /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: "A horizontal line that separates content. This block has no options to configure." });
|
|
3747
3829
|
}
|
|
3830
|
+
|
|
3831
|
+
// src/lib/emoji-data.ts
|
|
3832
|
+
var TONE_CODEPOINT_TO_SKIN_TONE = {
|
|
3833
|
+
"1F3FB": 2,
|
|
3834
|
+
"1F3FC": 3,
|
|
3835
|
+
"1F3FD": 4,
|
|
3836
|
+
"1F3FE": 5,
|
|
3837
|
+
"1F3FF": 6
|
|
3838
|
+
};
|
|
3839
|
+
var CATEGORY_ORDER = [
|
|
3840
|
+
"Smileys & Emotion",
|
|
3841
|
+
"People & Body",
|
|
3842
|
+
"Animals & Nature",
|
|
3843
|
+
"Food & Drink",
|
|
3844
|
+
"Travel & Places",
|
|
3845
|
+
"Activities",
|
|
3846
|
+
"Objects",
|
|
3847
|
+
"Symbols",
|
|
3848
|
+
"Flags"
|
|
3849
|
+
];
|
|
3850
|
+
function codepointsToGlyph(unified) {
|
|
3851
|
+
return unified.split("-").map((cp) => {
|
|
3852
|
+
const n = Number.parseInt(cp, 16);
|
|
3853
|
+
return Number.isFinite(n) ? String.fromCodePoint(n) : "";
|
|
3854
|
+
}).join("");
|
|
3855
|
+
}
|
|
3856
|
+
function unifiedToUnicode(unified) {
|
|
3857
|
+
return unified.toLowerCase();
|
|
3858
|
+
}
|
|
3859
|
+
function composeTextEmoji(name, skinTone) {
|
|
3860
|
+
if (skinTone && skinTone >= 2 && skinTone <= 6) {
|
|
3861
|
+
return `:${name}::skin-tone-${skinTone}:`;
|
|
3862
|
+
}
|
|
3863
|
+
return `:${name}:`;
|
|
3864
|
+
}
|
|
3865
|
+
function buildEmojiIndex(raw) {
|
|
3866
|
+
const all = [];
|
|
3867
|
+
const byName = /* @__PURE__ */ new Map();
|
|
3868
|
+
const byUnified = /* @__PURE__ */ new Map();
|
|
3869
|
+
for (const r of raw) {
|
|
3870
|
+
if (!CATEGORY_ORDER.includes(r.category)) {
|
|
3871
|
+
continue;
|
|
3872
|
+
}
|
|
3873
|
+
const shortNames = r.short_names?.length ? r.short_names : [r.short_name];
|
|
3874
|
+
const skinUnified = {};
|
|
3875
|
+
if (r.skin_variations) {
|
|
3876
|
+
for (const [toneKey, variation] of Object.entries(r.skin_variations)) {
|
|
3877
|
+
const skinTone = TONE_CODEPOINT_TO_SKIN_TONE[toneKey];
|
|
3878
|
+
if (skinTone && variation?.unified) {
|
|
3879
|
+
skinUnified[skinTone] = variation.unified;
|
|
3880
|
+
}
|
|
3881
|
+
}
|
|
3882
|
+
}
|
|
3883
|
+
const datum = {
|
|
3884
|
+
name: shortNames[0],
|
|
3885
|
+
shortNames,
|
|
3886
|
+
unified: r.unified,
|
|
3887
|
+
category: r.category,
|
|
3888
|
+
sortOrder: r.sort_order,
|
|
3889
|
+
skinUnified
|
|
3890
|
+
};
|
|
3891
|
+
all.push(datum);
|
|
3892
|
+
byUnified.set(r.unified.toUpperCase(), datum);
|
|
3893
|
+
for (const sn of shortNames) {
|
|
3894
|
+
if (!byName.has(sn)) {
|
|
3895
|
+
byName.set(sn, datum);
|
|
3896
|
+
}
|
|
3897
|
+
}
|
|
3898
|
+
}
|
|
3899
|
+
all.sort((a, b) => a.sortOrder - b.sortOrder);
|
|
3900
|
+
const byCategory = CATEGORY_ORDER.map((category) => ({
|
|
3901
|
+
category,
|
|
3902
|
+
emoji: all.filter((e) => e.category === category)
|
|
3903
|
+
})).filter((group) => group.emoji.length > 0);
|
|
3904
|
+
return { all, byName, byUnified, byCategory };
|
|
3905
|
+
}
|
|
3906
|
+
var indexPromise = null;
|
|
3907
|
+
var cachedIndex = null;
|
|
3908
|
+
async function loadEmojiIndex() {
|
|
3909
|
+
if (cachedIndex) {
|
|
3910
|
+
return cachedIndex;
|
|
3911
|
+
}
|
|
3912
|
+
if (!indexPromise) {
|
|
3913
|
+
indexPromise = import('emoji-datasource/emoji.json').then((mod) => {
|
|
3914
|
+
const raw = mod.default ?? mod;
|
|
3915
|
+
cachedIndex = buildEmojiIndex(raw);
|
|
3916
|
+
return cachedIndex;
|
|
3917
|
+
}).catch((err) => {
|
|
3918
|
+
indexPromise = null;
|
|
3919
|
+
throw err;
|
|
3920
|
+
});
|
|
3921
|
+
}
|
|
3922
|
+
return indexPromise;
|
|
3923
|
+
}
|
|
3924
|
+
function getEmojiIndex() {
|
|
3925
|
+
return cachedIndex;
|
|
3926
|
+
}
|
|
3927
|
+
|
|
3928
|
+
// src/lib/emoji-recents.ts
|
|
3929
|
+
var STORAGE_KEY = "bk:emoji-recents";
|
|
3930
|
+
var SKIN_TONE_KEY = "bk:emoji-skin-tone";
|
|
3931
|
+
var MAX_RECENTS = 24;
|
|
3932
|
+
function readEmojiSkinTone() {
|
|
3933
|
+
try {
|
|
3934
|
+
const raw = globalThis.localStorage?.getItem(SKIN_TONE_KEY);
|
|
3935
|
+
if (!raw) {
|
|
3936
|
+
return null;
|
|
3937
|
+
}
|
|
3938
|
+
const n = Number.parseInt(raw, 10);
|
|
3939
|
+
return n >= 2 && n <= 6 ? n : null;
|
|
3940
|
+
} catch {
|
|
3941
|
+
return null;
|
|
3942
|
+
}
|
|
3943
|
+
}
|
|
3944
|
+
function writeEmojiSkinTone(skinTone) {
|
|
3945
|
+
try {
|
|
3946
|
+
if (skinTone && skinTone >= 2 && skinTone <= 6) {
|
|
3947
|
+
globalThis.localStorage?.setItem(SKIN_TONE_KEY, String(skinTone));
|
|
3948
|
+
} else {
|
|
3949
|
+
globalThis.localStorage?.removeItem(SKIN_TONE_KEY);
|
|
3950
|
+
}
|
|
3951
|
+
} catch {
|
|
3952
|
+
}
|
|
3953
|
+
}
|
|
3954
|
+
function readEmojiRecents() {
|
|
3955
|
+
try {
|
|
3956
|
+
const raw = globalThis.localStorage?.getItem(STORAGE_KEY);
|
|
3957
|
+
if (!raw) {
|
|
3958
|
+
return [];
|
|
3959
|
+
}
|
|
3960
|
+
const parsed = JSON.parse(raw);
|
|
3961
|
+
return Array.isArray(parsed) ? parsed.filter((n) => typeof n === "string") : [];
|
|
3962
|
+
} catch {
|
|
3963
|
+
return [];
|
|
3964
|
+
}
|
|
3965
|
+
}
|
|
3966
|
+
function pushEmojiRecent(name) {
|
|
3967
|
+
const next = [name, ...readEmojiRecents().filter((n) => n !== name)].slice(0, MAX_RECENTS);
|
|
3968
|
+
try {
|
|
3969
|
+
globalThis.localStorage?.setItem(STORAGE_KEY, JSON.stringify(next));
|
|
3970
|
+
} catch {
|
|
3971
|
+
}
|
|
3972
|
+
return next;
|
|
3973
|
+
}
|
|
3974
|
+
var SKIN_TONES = [
|
|
3975
|
+
{ tone: null, label: "Default", swatch: "\u270B" },
|
|
3976
|
+
{ tone: 2, label: "Light", swatch: "\u270B\u{1F3FB}" },
|
|
3977
|
+
{ tone: 3, label: "Medium-light", swatch: "\u270B\u{1F3FC}" },
|
|
3978
|
+
{ tone: 4, label: "Medium", swatch: "\u270B\u{1F3FD}" },
|
|
3979
|
+
{ tone: 5, label: "Medium-dark", swatch: "\u270B\u{1F3FE}" },
|
|
3980
|
+
{ tone: 6, label: "Dark", swatch: "\u270B\u{1F3FF}" }
|
|
3981
|
+
];
|
|
3982
|
+
function EmojiPicker({
|
|
3983
|
+
onSelect,
|
|
3984
|
+
autoFocus = true
|
|
3985
|
+
}) {
|
|
3986
|
+
const { customEmojis } = useCustomEmojis();
|
|
3987
|
+
const [index, setIndex] = React.useState(null);
|
|
3988
|
+
const [loadError, setLoadError] = React.useState(false);
|
|
3989
|
+
const [query, setQuery] = React.useState("");
|
|
3990
|
+
const [skinTone, setSkinTone] = React.useState(() => readEmojiSkinTone());
|
|
3991
|
+
const [recents, setRecents] = React.useState(() => readEmojiRecents());
|
|
3992
|
+
const searchRef = React.useRef(null);
|
|
3993
|
+
React.useEffect(() => {
|
|
3994
|
+
let active = true;
|
|
3995
|
+
loadEmojiIndex().then((idx) => {
|
|
3996
|
+
if (active) {
|
|
3997
|
+
setIndex(idx);
|
|
3998
|
+
}
|
|
3999
|
+
}).catch(() => {
|
|
4000
|
+
if (active) {
|
|
4001
|
+
setLoadError(true);
|
|
4002
|
+
}
|
|
4003
|
+
});
|
|
4004
|
+
return () => {
|
|
4005
|
+
active = false;
|
|
4006
|
+
};
|
|
4007
|
+
}, []);
|
|
4008
|
+
React.useEffect(() => {
|
|
4009
|
+
if (autoFocus) {
|
|
4010
|
+
searchRef.current?.focus();
|
|
4011
|
+
}
|
|
4012
|
+
}, [autoFocus]);
|
|
4013
|
+
const customByName = React.useMemo(() => {
|
|
4014
|
+
const map = /* @__PURE__ */ new Map();
|
|
4015
|
+
for (const e of customEmojis) {
|
|
4016
|
+
map.set(e.name, e);
|
|
4017
|
+
}
|
|
4018
|
+
return map;
|
|
4019
|
+
}, [customEmojis]);
|
|
4020
|
+
const customImageEmoji = React.useMemo(() => customEmojis.filter((e) => e.url), [customEmojis]);
|
|
4021
|
+
const handlePick = (selection) => {
|
|
4022
|
+
setRecents(pushEmojiRecent(selection.name));
|
|
4023
|
+
onSelect(selection);
|
|
4024
|
+
};
|
|
4025
|
+
const handleSkinTone = (tone) => {
|
|
4026
|
+
setSkinTone(tone);
|
|
4027
|
+
writeEmojiSkinTone(tone);
|
|
4028
|
+
};
|
|
4029
|
+
const sections = React.useMemo(() => {
|
|
4030
|
+
if (!index) {
|
|
4031
|
+
return [];
|
|
4032
|
+
}
|
|
4033
|
+
const trimmed = query.trim().toLowerCase();
|
|
4034
|
+
if (trimmed) {
|
|
4035
|
+
const standard = index.all.filter((d) => d.shortNames.some((sn) => sn.includes(trimmed))).slice(0, 120).map((datum) => ({ datum }));
|
|
4036
|
+
const custom = customImageEmoji.filter((e) => e.name.toLowerCase().includes(trimmed)).map((custom2) => ({ custom: custom2 }));
|
|
4037
|
+
return [{ key: "search", title: "Search results", emoji: [...custom, ...standard] }];
|
|
4038
|
+
}
|
|
4039
|
+
const out = [];
|
|
4040
|
+
if (recents.length > 0) {
|
|
4041
|
+
const recentEmoji = recents.map((name) => {
|
|
4042
|
+
const custom = customByName.get(name);
|
|
4043
|
+
if (custom?.url) {
|
|
4044
|
+
return { custom };
|
|
4045
|
+
}
|
|
4046
|
+
const datum = index.byName.get(name);
|
|
4047
|
+
return datum ? { datum } : null;
|
|
4048
|
+
}).filter((e) => e !== null);
|
|
4049
|
+
if (recentEmoji.length > 0) {
|
|
4050
|
+
out.push({ key: "recents", title: "Frequently used", emoji: recentEmoji });
|
|
4051
|
+
}
|
|
4052
|
+
}
|
|
4053
|
+
if (customImageEmoji.length > 0) {
|
|
4054
|
+
out.push({ key: "custom", title: "Custom", emoji: customImageEmoji.map((custom) => ({ custom })) });
|
|
4055
|
+
}
|
|
4056
|
+
for (const group of index.byCategory) {
|
|
4057
|
+
out.push({ key: group.category, title: group.category, emoji: group.emoji.map((datum) => ({ datum })) });
|
|
4058
|
+
}
|
|
4059
|
+
return out;
|
|
4060
|
+
}, [index, query, recents, customByName, customImageEmoji]);
|
|
4061
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bk-emoji-picker flex w-[320px] max-w-[90vw] flex-col gap-2", children: [
|
|
4062
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
4063
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4064
|
+
Input,
|
|
4065
|
+
{
|
|
4066
|
+
ref: searchRef,
|
|
4067
|
+
value: query,
|
|
4068
|
+
placeholder: "Search emoji\u2026",
|
|
4069
|
+
"aria-label": "Search emoji",
|
|
4070
|
+
onChange: (e) => setQuery(e.target.value),
|
|
4071
|
+
className: "h-8"
|
|
4072
|
+
}
|
|
4073
|
+
),
|
|
4074
|
+
/* @__PURE__ */ jsxRuntime.jsx(SkinToneSelector, { value: skinTone, onChange: handleSkinTone })
|
|
4075
|
+
] }),
|
|
4076
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-64 overflow-y-auto pr-1", children: loadError ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "p-4 text-center text-xs text-muted-foreground", children: "Couldn't load emoji." }) : !index ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "p-4 text-center text-xs text-muted-foreground", children: "Loading emoji\u2026" }) : sections.every((s) => s.emoji.length === 0) ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "p-4 text-center text-xs text-muted-foreground", children: "No emoji found." }) : sections.map((section) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-2", children: [
|
|
4077
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "sticky top-0 z-10 bg-background px-1 py-1 text-[11px] font-medium text-muted-foreground", children: section.title }),
|
|
4078
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-8 gap-0.5", children: section.emoji.map(
|
|
4079
|
+
(entry, i) => entry.custom ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
4080
|
+
CustomEmojiButton,
|
|
4081
|
+
{
|
|
4082
|
+
emoji: entry.custom,
|
|
4083
|
+
onPick: handlePick
|
|
4084
|
+
},
|
|
4085
|
+
`${section.key}-${entry.custom.name}-${i}`
|
|
4086
|
+
) : entry.datum ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
4087
|
+
StandardEmojiButton,
|
|
4088
|
+
{
|
|
4089
|
+
datum: entry.datum,
|
|
4090
|
+
skinTone,
|
|
4091
|
+
onPick: handlePick
|
|
4092
|
+
},
|
|
4093
|
+
`${section.key}-${entry.datum.name}-${i}`
|
|
4094
|
+
) : null
|
|
4095
|
+
) })
|
|
4096
|
+
] }, section.key)) })
|
|
4097
|
+
] });
|
|
4098
|
+
}
|
|
4099
|
+
function resolveStandard(datum, skinTone) {
|
|
4100
|
+
const tonedUnified = skinTone ? datum.skinUnified[skinTone] : void 0;
|
|
4101
|
+
return {
|
|
4102
|
+
name: datum.name,
|
|
4103
|
+
unicode: unifiedToUnicode(tonedUnified ?? datum.unified),
|
|
4104
|
+
src: null,
|
|
4105
|
+
skinTone: tonedUnified ? skinTone : null
|
|
4106
|
+
};
|
|
4107
|
+
}
|
|
4108
|
+
var emojiButtonClass = "flex h-8 w-8 cursor-pointer items-center justify-center rounded text-lg leading-none hover:bg-accent";
|
|
4109
|
+
function StandardEmojiButton({
|
|
4110
|
+
datum,
|
|
4111
|
+
skinTone,
|
|
4112
|
+
onPick
|
|
4113
|
+
}) {
|
|
4114
|
+
const tonedUnified = skinTone && datum.skinUnified[skinTone] || datum.unified;
|
|
4115
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
4116
|
+
"button",
|
|
4117
|
+
{
|
|
4118
|
+
type: "button",
|
|
4119
|
+
className: emojiButtonClass,
|
|
4120
|
+
title: `:${datum.name}:`,
|
|
4121
|
+
"aria-label": datum.name,
|
|
4122
|
+
onClick: () => onPick(resolveStandard(datum, skinTone)),
|
|
4123
|
+
children: codepointsToGlyph(tonedUnified)
|
|
4124
|
+
}
|
|
4125
|
+
);
|
|
4126
|
+
}
|
|
4127
|
+
function CustomEmojiButton({ emoji, onPick }) {
|
|
4128
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
4129
|
+
"button",
|
|
4130
|
+
{
|
|
4131
|
+
type: "button",
|
|
4132
|
+
className: emojiButtonClass,
|
|
4133
|
+
title: `:${emoji.name}:`,
|
|
4134
|
+
"aria-label": emoji.name,
|
|
4135
|
+
onClick: () => onPick({ name: emoji.name, unicode: null, src: emoji.url, skinTone: null }),
|
|
4136
|
+
children: emoji.url ? /* @__PURE__ */ jsxRuntime.jsx("img", { src: emoji.url, alt: `:${emoji.name}:`, className: "h-5 w-5 object-contain" }) : /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-[10px]", children: [
|
|
4137
|
+
":",
|
|
4138
|
+
emoji.name,
|
|
4139
|
+
":"
|
|
4140
|
+
] })
|
|
4141
|
+
}
|
|
4142
|
+
);
|
|
4143
|
+
}
|
|
4144
|
+
function SkinToneSelector({ value, onChange }) {
|
|
4145
|
+
const [open, setOpen] = React.useState(false);
|
|
4146
|
+
const active = SKIN_TONES.find((t) => t.tone === value) ?? SKIN_TONES[0];
|
|
4147
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative shrink-0", children: [
|
|
4148
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4149
|
+
"button",
|
|
4150
|
+
{
|
|
4151
|
+
type: "button",
|
|
4152
|
+
"aria-label": `Skin tone: ${active.label}`,
|
|
4153
|
+
title: `Skin tone: ${active.label}`,
|
|
4154
|
+
className: "flex h-8 w-8 items-center justify-center rounded border text-lg leading-none hover:bg-accent",
|
|
4155
|
+
onClick: () => setOpen((o) => !o),
|
|
4156
|
+
children: active.swatch
|
|
4157
|
+
}
|
|
4158
|
+
),
|
|
4159
|
+
open && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute right-0 z-20 mt-1 flex gap-0.5 rounded-md border bg-background p-1 shadow-md", children: SKIN_TONES.map((t) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
4160
|
+
"button",
|
|
4161
|
+
{
|
|
4162
|
+
type: "button",
|
|
4163
|
+
"aria-label": t.label,
|
|
4164
|
+
title: t.label,
|
|
4165
|
+
className: cn(emojiButtonClass, value === t.tone && "bg-accent"),
|
|
4166
|
+
onClick: () => {
|
|
4167
|
+
onChange(t.tone);
|
|
4168
|
+
setOpen(false);
|
|
4169
|
+
},
|
|
4170
|
+
children: t.swatch
|
|
4171
|
+
},
|
|
4172
|
+
t.label
|
|
4173
|
+
)) })
|
|
4174
|
+
] });
|
|
4175
|
+
}
|
|
4176
|
+
function EmojiPickerButton({
|
|
4177
|
+
onSelect,
|
|
4178
|
+
className,
|
|
4179
|
+
label = "Insert emoji",
|
|
4180
|
+
align = "start"
|
|
4181
|
+
}) {
|
|
4182
|
+
const [open, setOpen] = React.useState(false);
|
|
4183
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(Popover, { open, onOpenChange: setOpen, children: [
|
|
4184
|
+
/* @__PURE__ */ jsxRuntime.jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
4185
|
+
"button",
|
|
4186
|
+
{
|
|
4187
|
+
type: "button",
|
|
4188
|
+
"aria-label": label,
|
|
4189
|
+
title: label,
|
|
4190
|
+
className: cn(
|
|
4191
|
+
"flex h-7 w-7 cursor-pointer items-center justify-center rounded text-muted-foreground transition-colors hover:bg-accent hover:text-foreground",
|
|
4192
|
+
className
|
|
4193
|
+
),
|
|
4194
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Smile, { className: "h-3.5 w-3.5" })
|
|
4195
|
+
}
|
|
4196
|
+
) }),
|
|
4197
|
+
/* @__PURE__ */ jsxRuntime.jsx(PopoverContent, { align, className: "w-auto p-2", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
4198
|
+
EmojiPicker,
|
|
4199
|
+
{
|
|
4200
|
+
onSelect: (selection) => {
|
|
4201
|
+
onSelect(selection);
|
|
4202
|
+
setOpen(false);
|
|
4203
|
+
}
|
|
4204
|
+
}
|
|
4205
|
+
) })
|
|
4206
|
+
] });
|
|
4207
|
+
}
|
|
4208
|
+
function EmojiTextInsertButton({
|
|
4209
|
+
targetRef,
|
|
4210
|
+
value,
|
|
4211
|
+
onChange,
|
|
4212
|
+
className,
|
|
4213
|
+
align = "end"
|
|
4214
|
+
}) {
|
|
4215
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
4216
|
+
EmojiPickerButton,
|
|
4217
|
+
{
|
|
4218
|
+
align,
|
|
4219
|
+
label: "Insert emoji",
|
|
4220
|
+
className,
|
|
4221
|
+
onSelect: (sel) => {
|
|
4222
|
+
const el = targetRef.current;
|
|
4223
|
+
const insert = composeTextEmoji(sel.name, sel.skinTone ?? void 0);
|
|
4224
|
+
const start = el?.selectionStart ?? value.length;
|
|
4225
|
+
const end = el?.selectionEnd ?? value.length;
|
|
4226
|
+
const next = value.slice(0, start) + insert + value.slice(end);
|
|
4227
|
+
onChange(next);
|
|
4228
|
+
requestAnimationFrame(() => {
|
|
4229
|
+
if (el) {
|
|
4230
|
+
const caret = start + insert.length;
|
|
4231
|
+
el.focus();
|
|
4232
|
+
el.setSelectionRange(caret, caret);
|
|
4233
|
+
}
|
|
4234
|
+
});
|
|
4235
|
+
}
|
|
4236
|
+
}
|
|
4237
|
+
);
|
|
4238
|
+
}
|
|
3748
4239
|
var HEADER_MAX = 150;
|
|
3749
4240
|
var HEADER_LEVELS = [1, 2, 3, 4];
|
|
3750
4241
|
function HeaderEditor({ block, onChange }) {
|
|
3751
4242
|
const text = block.text?.text ?? "";
|
|
3752
4243
|
const remaining = HEADER_MAX - text.length;
|
|
3753
4244
|
const level = block.level;
|
|
4245
|
+
const inputRef = React.useRef(null);
|
|
4246
|
+
const setText = (next) => onChange({ ...block, text: { type: "plain_text", text: next, emoji: true } });
|
|
3754
4247
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-3", children: [
|
|
3755
4248
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3756
4249
|
EditorField,
|
|
@@ -3758,23 +4251,20 @@ function HeaderEditor({ block, onChange }) {
|
|
|
3758
4251
|
label: "Heading text",
|
|
3759
4252
|
help: `Plain text only. ${remaining} characters remaining.`,
|
|
3760
4253
|
htmlFor: "header-text",
|
|
3761
|
-
children: /* @__PURE__ */ jsxRuntime.
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
})
|
|
3776
|
-
}
|
|
3777
|
-
)
|
|
4254
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5", children: [
|
|
4255
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4256
|
+
Input,
|
|
4257
|
+
{
|
|
4258
|
+
ref: inputRef,
|
|
4259
|
+
id: "header-text",
|
|
4260
|
+
value: text,
|
|
4261
|
+
maxLength: HEADER_MAX,
|
|
4262
|
+
placeholder: "e.g. Weekly roundup",
|
|
4263
|
+
onChange: (e) => setText(e.target.value)
|
|
4264
|
+
}
|
|
4265
|
+
),
|
|
4266
|
+
/* @__PURE__ */ jsxRuntime.jsx(EmojiTextInsertButton, { targetRef: inputRef, value: text, onChange: setText, className: "shrink-0 border" })
|
|
4267
|
+
] })
|
|
3778
4268
|
}
|
|
3779
4269
|
),
|
|
3780
4270
|
/* @__PURE__ */ jsxRuntime.jsx(EditorField, { label: "Level", help: "Optional. Builder-only extension; Slack's API ignores this value.", children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
@@ -4637,22 +5127,29 @@ function CheckboxField({
|
|
|
4637
5127
|
] });
|
|
4638
5128
|
}
|
|
4639
5129
|
function MarkdownEditor({ block, onChange }) {
|
|
5130
|
+
const textRef = React.useRef(null);
|
|
5131
|
+
const text = block.text ?? "";
|
|
5132
|
+
const setText = (next) => onChange({ ...block, text: next });
|
|
4640
5133
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
4641
5134
|
EditorField,
|
|
4642
5135
|
{
|
|
4643
5136
|
label: "Markdown",
|
|
4644
5137
|
help: "Standard markdown with GFM: **bold**, _italic_, lists, tables, ```code```, [links](url).",
|
|
4645
5138
|
htmlFor: "markdown-text",
|
|
4646
|
-
children: /* @__PURE__ */ jsxRuntime.
|
|
4647
|
-
|
|
4648
|
-
|
|
4649
|
-
|
|
4650
|
-
|
|
4651
|
-
|
|
4652
|
-
|
|
4653
|
-
|
|
4654
|
-
|
|
4655
|
-
|
|
5139
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start gap-1.5", children: [
|
|
5140
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5141
|
+
Textarea,
|
|
5142
|
+
{
|
|
5143
|
+
ref: textRef,
|
|
5144
|
+
id: "markdown-text",
|
|
5145
|
+
value: text,
|
|
5146
|
+
rows: 8,
|
|
5147
|
+
placeholder: "e.g. **Roadmap**\\n\\n- Item one\\n- Item two",
|
|
5148
|
+
onChange: (e) => setText(e.target.value)
|
|
5149
|
+
}
|
|
5150
|
+
),
|
|
5151
|
+
/* @__PURE__ */ jsxRuntime.jsx(EmojiTextInsertButton, { targetRef: textRef, value: text, onChange: setText, className: "mt-1 shrink-0 border" })
|
|
5152
|
+
] })
|
|
4656
5153
|
}
|
|
4657
5154
|
);
|
|
4658
5155
|
}
|
|
@@ -4747,6 +5244,26 @@ function PlanEditor({ block, onChange }) {
|
|
|
4747
5244
|
] });
|
|
4748
5245
|
}
|
|
4749
5246
|
|
|
5247
|
+
// src/lib/emoji-resolve.ts
|
|
5248
|
+
function makeEmojiImportResolver(customByName, index = getEmojiIndex()) {
|
|
5249
|
+
return (el) => {
|
|
5250
|
+
const name = el.name ?? "";
|
|
5251
|
+
const skinTone = el.skin_tone ?? null;
|
|
5252
|
+
const custom = customByName.get(name);
|
|
5253
|
+
if (custom?.url) {
|
|
5254
|
+
return { name, src: custom.url, unicode: null, skinTone };
|
|
5255
|
+
}
|
|
5256
|
+
const lookupName = custom?.alias ?? name;
|
|
5257
|
+
const datum = index?.byName.get(lookupName);
|
|
5258
|
+
let unicode = el.unicode ?? null;
|
|
5259
|
+
if (!unicode && datum) {
|
|
5260
|
+
const toned = skinTone ? datum.skinUnified[skinTone] : void 0;
|
|
5261
|
+
unicode = unifiedToUnicode(toned ?? datum.unified);
|
|
5262
|
+
}
|
|
5263
|
+
return { name, src: null, unicode, skinTone };
|
|
5264
|
+
};
|
|
5265
|
+
}
|
|
5266
|
+
|
|
4750
5267
|
// src/lib/rich-text-tiptap.ts
|
|
4751
5268
|
var SUPPORTED_BLOCK_KINDS = /* @__PURE__ */ new Set([
|
|
4752
5269
|
"rich_text_section",
|
|
@@ -4754,7 +5271,7 @@ var SUPPORTED_BLOCK_KINDS = /* @__PURE__ */ new Set([
|
|
|
4754
5271
|
"rich_text_quote",
|
|
4755
5272
|
"rich_text_preformatted"
|
|
4756
5273
|
]);
|
|
4757
|
-
var SUPPORTED_INLINE_KINDS = /* @__PURE__ */ new Set(["text", "link"]);
|
|
5274
|
+
var SUPPORTED_INLINE_KINDS = /* @__PURE__ */ new Set(["text", "link", "emoji"]);
|
|
4758
5275
|
var SUPPORTED_TEXT_STYLE_KEYS = /* @__PURE__ */ new Set(["bold", "italic", "strike", "code"]);
|
|
4759
5276
|
var MAX_LIST_INDENT = 8;
|
|
4760
5277
|
function detectLossy(block) {
|
|
@@ -4801,16 +5318,22 @@ function scanInlines(inlines, where, out) {
|
|
|
4801
5318
|
}
|
|
4802
5319
|
});
|
|
4803
5320
|
}
|
|
4804
|
-
|
|
5321
|
+
var defaultEmojiResolver = (el) => ({
|
|
5322
|
+
name: el.name ?? "",
|
|
5323
|
+
src: null,
|
|
5324
|
+
unicode: el.unicode ?? null,
|
|
5325
|
+
skinTone: el.skin_tone ?? null
|
|
5326
|
+
});
|
|
5327
|
+
function richTextToProseMirror(block, resolveEmoji = defaultEmojiResolver) {
|
|
4805
5328
|
const content = [];
|
|
4806
5329
|
const listsByIndent = /* @__PURE__ */ new Map();
|
|
4807
5330
|
for (const el of block.elements ?? []) {
|
|
4808
5331
|
if (el.type === "rich_text_list") {
|
|
4809
|
-
appendRichTextList(el, content, listsByIndent);
|
|
5332
|
+
appendRichTextList(el, content, listsByIndent, resolveEmoji);
|
|
4810
5333
|
continue;
|
|
4811
5334
|
}
|
|
4812
5335
|
listsByIndent.clear();
|
|
4813
|
-
const node = blockElementToPM(el);
|
|
5336
|
+
const node = blockElementToPM(el, resolveEmoji);
|
|
4814
5337
|
if (node) {
|
|
4815
5338
|
content.push(node);
|
|
4816
5339
|
}
|
|
@@ -4820,12 +5343,12 @@ function richTextToProseMirror(block) {
|
|
|
4820
5343
|
content: content.length > 0 ? content : [{ type: "paragraph" }]
|
|
4821
5344
|
};
|
|
4822
5345
|
}
|
|
4823
|
-
function appendRichTextList(el, topLevel, listsByIndent) {
|
|
5346
|
+
function appendRichTextList(el, topLevel, listsByIndent, resolveEmoji) {
|
|
4824
5347
|
const indent = el.indent ?? 0;
|
|
4825
5348
|
const listType = el.style === "ordered" ? "orderedList" : "bulletList";
|
|
4826
5349
|
const items = (el.elements ?? []).map((section) => ({
|
|
4827
5350
|
type: "listItem",
|
|
4828
|
-
content: [{ type: "paragraph", content: inlinesToPM(section.elements ?? []) }]
|
|
5351
|
+
content: [{ type: "paragraph", content: inlinesToPM(section.elements ?? [], resolveEmoji) }]
|
|
4829
5352
|
}));
|
|
4830
5353
|
for (const k of Array.from(listsByIndent.keys())) {
|
|
4831
5354
|
if (k > indent) {
|
|
@@ -4851,11 +5374,11 @@ function appendRichTextList(el, topLevel, listsByIndent) {
|
|
|
4851
5374
|
const parentLastItem = parent.content[parent.content.length - 1];
|
|
4852
5375
|
parentLastItem.content = [...parentLastItem.content ?? [], list];
|
|
4853
5376
|
}
|
|
4854
|
-
function blockElementToPM(el) {
|
|
5377
|
+
function blockElementToPM(el, resolveEmoji) {
|
|
4855
5378
|
if (el.type === "rich_text_section") {
|
|
4856
5379
|
return {
|
|
4857
5380
|
type: "paragraph",
|
|
4858
|
-
content: inlinesToPM(el.elements ?? [])
|
|
5381
|
+
content: inlinesToPM(el.elements ?? [], resolveEmoji)
|
|
4859
5382
|
};
|
|
4860
5383
|
}
|
|
4861
5384
|
if (el.type === "rich_text_quote") {
|
|
@@ -4864,7 +5387,7 @@ function blockElementToPM(el) {
|
|
|
4864
5387
|
content: [
|
|
4865
5388
|
{
|
|
4866
5389
|
type: "paragraph",
|
|
4867
|
-
content: inlinesToPM(el.elements ?? [])
|
|
5390
|
+
content: inlinesToPM(el.elements ?? [], resolveEmoji)
|
|
4868
5391
|
}
|
|
4869
5392
|
]
|
|
4870
5393
|
};
|
|
@@ -4879,7 +5402,7 @@ function blockElementToPM(el) {
|
|
|
4879
5402
|
}
|
|
4880
5403
|
return null;
|
|
4881
5404
|
}
|
|
4882
|
-
function inlinesToPM(inlines) {
|
|
5405
|
+
function inlinesToPM(inlines, resolveEmoji) {
|
|
4883
5406
|
const out = [];
|
|
4884
5407
|
for (const inline of inlines) {
|
|
4885
5408
|
if (inline.type === "text") {
|
|
@@ -4888,6 +5411,12 @@ function inlinesToPM(inlines) {
|
|
|
4888
5411
|
}
|
|
4889
5412
|
const marks = styleToMarks(inline.style);
|
|
4890
5413
|
out.push({ type: "text", text: inline.text, marks });
|
|
5414
|
+
} else if (inline.type === "emoji") {
|
|
5415
|
+
const attrs = resolveEmoji(inline);
|
|
5416
|
+
if (!attrs.name) {
|
|
5417
|
+
continue;
|
|
5418
|
+
}
|
|
5419
|
+
out.push({ type: "emoji", attrs });
|
|
4891
5420
|
} else if (inline.type === "link") {
|
|
4892
5421
|
const link = inline;
|
|
4893
5422
|
const text = link.text || link.url || "";
|
|
@@ -5005,6 +5534,19 @@ function flattenList(listNode, indent, out) {
|
|
|
5005
5534
|
function proseMirrorInlinesToRichTextElements(nodes) {
|
|
5006
5535
|
const out = [];
|
|
5007
5536
|
for (const node of nodes) {
|
|
5537
|
+
if (node.type === "emoji") {
|
|
5538
|
+
const name = String(node.attrs?.name ?? "");
|
|
5539
|
+
if (!name) {
|
|
5540
|
+
continue;
|
|
5541
|
+
}
|
|
5542
|
+
const emoji = { type: "emoji", name };
|
|
5543
|
+
const skinTone = node.attrs?.skinTone;
|
|
5544
|
+
if (typeof skinTone === "number" && skinTone >= 2 && skinTone <= 6) {
|
|
5545
|
+
emoji.skin_tone = skinTone;
|
|
5546
|
+
}
|
|
5547
|
+
out.push(emoji);
|
|
5548
|
+
continue;
|
|
5549
|
+
}
|
|
5008
5550
|
if (node.type !== "text" || !node.text) {
|
|
5009
5551
|
continue;
|
|
5010
5552
|
}
|
|
@@ -5075,6 +5617,49 @@ function sameStyle(a, b) {
|
|
|
5075
5617
|
}
|
|
5076
5618
|
return ak.every((k, i) => k === bk[i]);
|
|
5077
5619
|
}
|
|
5620
|
+
function EmojiNodeView({ node }) {
|
|
5621
|
+
const { name, src, unicode } = node.attrs;
|
|
5622
|
+
const label = `:${name}:`;
|
|
5623
|
+
return /* @__PURE__ */ jsxRuntime.jsx(react.NodeViewWrapper, { as: "span", className: "bk-emoji-node", "data-emoji": name, title: label, children: src ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
5624
|
+
"img",
|
|
5625
|
+
{
|
|
5626
|
+
src,
|
|
5627
|
+
alt: label,
|
|
5628
|
+
style: {
|
|
5629
|
+
display: "inline-block",
|
|
5630
|
+
width: "1.25em",
|
|
5631
|
+
height: "1.25em",
|
|
5632
|
+
verticalAlign: "-0.25em",
|
|
5633
|
+
objectFit: "contain"
|
|
5634
|
+
}
|
|
5635
|
+
}
|
|
5636
|
+
) : /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-label": label, role: "img", children: unicode ? codepointsToGlyph(unicode) : label }) });
|
|
5637
|
+
}
|
|
5638
|
+
var EmojiNode = react.Node.create({
|
|
5639
|
+
name: "emoji",
|
|
5640
|
+
group: "inline",
|
|
5641
|
+
inline: true,
|
|
5642
|
+
atom: true,
|
|
5643
|
+
selectable: true,
|
|
5644
|
+
addAttributes() {
|
|
5645
|
+
return {
|
|
5646
|
+
name: { default: "" },
|
|
5647
|
+
src: { default: null },
|
|
5648
|
+
unicode: { default: null },
|
|
5649
|
+
skinTone: { default: null }
|
|
5650
|
+
};
|
|
5651
|
+
},
|
|
5652
|
+
parseHTML() {
|
|
5653
|
+
return [{ tag: "span[data-emoji]" }];
|
|
5654
|
+
},
|
|
5655
|
+
renderHTML({ HTMLAttributes }) {
|
|
5656
|
+
const name = HTMLAttributes.name ?? "";
|
|
5657
|
+
return ["span", react.mergeAttributes({ "data-emoji": name }, HTMLAttributes), `:${name}:`];
|
|
5658
|
+
},
|
|
5659
|
+
addNodeView() {
|
|
5660
|
+
return react.ReactNodeViewRenderer(EmojiNodeView);
|
|
5661
|
+
}
|
|
5662
|
+
});
|
|
5078
5663
|
var BLOCK_ELEMENT_LABEL = {
|
|
5079
5664
|
rich_text_section: "Section",
|
|
5080
5665
|
rich_text_list: "List",
|
|
@@ -5413,15 +5998,34 @@ function InlineFields({
|
|
|
5413
5998
|
}
|
|
5414
5999
|
if (kind === "emoji") {
|
|
5415
6000
|
const emoji = element;
|
|
5416
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
5417
|
-
|
|
6001
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
6002
|
+
EditorField,
|
|
5418
6003
|
{
|
|
5419
|
-
|
|
5420
|
-
|
|
5421
|
-
|
|
5422
|
-
|
|
6004
|
+
label: "Name",
|
|
6005
|
+
help: "Pick from the emoji list, or type a shortcode (without colons).",
|
|
6006
|
+
htmlFor: `${idPrefix}-emoji`,
|
|
6007
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5", children: [
|
|
6008
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
6009
|
+
Input,
|
|
6010
|
+
{
|
|
6011
|
+
id: `${idPrefix}-emoji`,
|
|
6012
|
+
value: emoji.name ?? "",
|
|
6013
|
+
placeholder: "e.g. wave",
|
|
6014
|
+
onChange: (e) => onChange({ ...emoji, name: e.target.value })
|
|
6015
|
+
}
|
|
6016
|
+
),
|
|
6017
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
6018
|
+
EmojiPickerButton,
|
|
6019
|
+
{
|
|
6020
|
+
align: "end",
|
|
6021
|
+
label: "Pick emoji",
|
|
6022
|
+
className: "shrink-0 border",
|
|
6023
|
+
onSelect: (sel) => onChange({ ...emoji, name: sel.name, skin_tone: sel.skinTone ?? void 0, unicode: void 0 })
|
|
6024
|
+
}
|
|
6025
|
+
)
|
|
6026
|
+
] })
|
|
5423
6027
|
}
|
|
5424
|
-
)
|
|
6028
|
+
);
|
|
5425
6029
|
}
|
|
5426
6030
|
if (kind === "user") {
|
|
5427
6031
|
const user = element;
|
|
@@ -5632,6 +6236,20 @@ function LossyBanner({ reasons }) {
|
|
|
5632
6236
|
}
|
|
5633
6237
|
function RichTextWysiwygEditor({ block, onChange }) {
|
|
5634
6238
|
const lastEmittedRef = React.useRef(null);
|
|
6239
|
+
const { byName: customByName } = useCustomEmojis();
|
|
6240
|
+
const [emojiIndex, setEmojiIndex] = React.useState(null);
|
|
6241
|
+
React.useEffect(() => {
|
|
6242
|
+
let active = true;
|
|
6243
|
+
loadEmojiIndex().then((idx) => active && setEmojiIndex(idx)).catch(() => {
|
|
6244
|
+
});
|
|
6245
|
+
return () => {
|
|
6246
|
+
active = false;
|
|
6247
|
+
};
|
|
6248
|
+
}, []);
|
|
6249
|
+
const resolveEmoji = React.useMemo(
|
|
6250
|
+
() => makeEmojiImportResolver(customByName, emojiIndex),
|
|
6251
|
+
[customByName, emojiIndex]
|
|
6252
|
+
);
|
|
5635
6253
|
const handleUpdate = React.useCallback(
|
|
5636
6254
|
(editor2) => {
|
|
5637
6255
|
const next = proseMirrorToRichText(editor2.getJSON());
|
|
@@ -5646,6 +6264,7 @@ function RichTextWysiwygEditor({ block, onChange }) {
|
|
|
5646
6264
|
heading: false,
|
|
5647
6265
|
horizontalRule: false
|
|
5648
6266
|
}),
|
|
6267
|
+
EmojiNode,
|
|
5649
6268
|
Link__default.default.configure({
|
|
5650
6269
|
openOnClick: false,
|
|
5651
6270
|
autolink: true,
|
|
@@ -5662,7 +6281,7 @@ function RichTextWysiwygEditor({ block, onChange }) {
|
|
|
5662
6281
|
HTMLAttributes: { rel: "noreferrer noopener", target: "_blank" }
|
|
5663
6282
|
})
|
|
5664
6283
|
],
|
|
5665
|
-
content: richTextToProseMirror(block),
|
|
6284
|
+
content: richTextToProseMirror(block, resolveEmoji),
|
|
5666
6285
|
editorProps: {
|
|
5667
6286
|
attributes: {
|
|
5668
6287
|
class: "bk-rich-editor min-h-[120px] rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus-visible:ring-1 focus-visible:ring-ring [&_p]:my-1 [&_ul]:my-1 [&_ul]:list-disc [&_ul]:pl-5 [&_ol]:my-1 [&_ol]:pl-5 [&_li]:my-0 [&_pre]:bg-muted [&_pre]:rounded [&_pre]:p-2 [&_code]:rounded [&_code]:bg-muted [&_code]:px-1 [&_blockquote]:border-l-2 [&_blockquote]:border-border [&_blockquote]:pl-3 [&_blockquote]:text-muted-foreground"
|
|
@@ -5677,10 +6296,10 @@ function RichTextWysiwygEditor({ block, onChange }) {
|
|
|
5677
6296
|
if (lastEmittedRef.current && JSON.stringify(lastEmittedRef.current) === JSON.stringify(block)) {
|
|
5678
6297
|
return;
|
|
5679
6298
|
}
|
|
5680
|
-
editor.commands.setContent(richTextToProseMirror(block), {
|
|
6299
|
+
editor.commands.setContent(richTextToProseMirror(block, resolveEmoji), {
|
|
5681
6300
|
emitUpdate: false
|
|
5682
6301
|
});
|
|
5683
|
-
}, [block, editor]);
|
|
6302
|
+
}, [block, editor, resolveEmoji]);
|
|
5684
6303
|
if (!editor) {
|
|
5685
6304
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-h-[120px] rounded-md border border-input bg-background" });
|
|
5686
6305
|
}
|
|
@@ -5729,6 +6348,16 @@ function Toolbar({ editor }) {
|
|
|
5729
6348
|
),
|
|
5730
6349
|
/* @__PURE__ */ jsxRuntime.jsx(Divider, {}),
|
|
5731
6350
|
/* @__PURE__ */ jsxRuntime.jsx(LinkPopover, { editor }),
|
|
6351
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
6352
|
+
EmojiPickerButton,
|
|
6353
|
+
{
|
|
6354
|
+
align: "start",
|
|
6355
|
+
onSelect: (sel) => editor.chain().focus().insertContent({
|
|
6356
|
+
type: "emoji",
|
|
6357
|
+
attrs: { name: sel.name, src: sel.src, unicode: sel.unicode, skinTone: sel.skinTone }
|
|
6358
|
+
}).run()
|
|
6359
|
+
}
|
|
6360
|
+
),
|
|
5732
6361
|
/* @__PURE__ */ jsxRuntime.jsx(Divider, {}),
|
|
5733
6362
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5734
6363
|
ToolbarButton,
|
|
@@ -5927,6 +6556,8 @@ function defaultImage() {
|
|
|
5927
6556
|
function SectionEditor({ block, onChange }) {
|
|
5928
6557
|
const text = block.text?.text ?? "";
|
|
5929
6558
|
const accessoryKind = detectAccessory(block.accessory);
|
|
6559
|
+
const textRef = React.useRef(null);
|
|
6560
|
+
const setText = (next) => onChange({ ...block, text: { type: "mrkdwn", text: next } });
|
|
5930
6561
|
const setAccessoryKind = (next) => {
|
|
5931
6562
|
if (next === accessoryKind) {
|
|
5932
6563
|
return;
|
|
@@ -5950,19 +6581,20 @@ function SectionEditor({ block, onChange }) {
|
|
|
5950
6581
|
label: "Text",
|
|
5951
6582
|
help: "Supports *bold*, _italic_, ~strike~, `code`, and <url|link> formatting.",
|
|
5952
6583
|
htmlFor: "section-text",
|
|
5953
|
-
children: /* @__PURE__ */ jsxRuntime.
|
|
5954
|
-
|
|
5955
|
-
|
|
5956
|
-
|
|
5957
|
-
|
|
5958
|
-
|
|
5959
|
-
|
|
5960
|
-
|
|
5961
|
-
|
|
5962
|
-
|
|
5963
|
-
}
|
|
5964
|
-
|
|
5965
|
-
|
|
6584
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start gap-1.5", children: [
|
|
6585
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
6586
|
+
Textarea,
|
|
6587
|
+
{
|
|
6588
|
+
ref: textRef,
|
|
6589
|
+
id: "section-text",
|
|
6590
|
+
value: text,
|
|
6591
|
+
rows: 4,
|
|
6592
|
+
placeholder: "e.g. Welcome to the channel! Ping <@U123> with any questions.",
|
|
6593
|
+
onChange: (e) => setText(e.target.value)
|
|
6594
|
+
}
|
|
6595
|
+
),
|
|
6596
|
+
/* @__PURE__ */ jsxRuntime.jsx(EmojiTextInsertButton, { targetRef: textRef, value: text, onChange: setText, className: "mt-1 shrink-0 border" })
|
|
6597
|
+
] })
|
|
5966
6598
|
}
|
|
5967
6599
|
),
|
|
5968
6600
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-3 rounded-md border bg-muted/20 p-3", children: [
|
|
@@ -6003,19 +6635,22 @@ function ButtonAccessoryFields({ button, onChange }) {
|
|
|
6003
6635
|
const url = button.url ?? "";
|
|
6004
6636
|
const value = button.value ?? "";
|
|
6005
6637
|
const style = button.style === "primary" || button.style === "danger" ? button.style : "default";
|
|
6638
|
+
const labelRef = React.useRef(null);
|
|
6639
|
+
const setLabel = (next) => onChange({ ...button, text: { type: "plain_text", text: next, emoji: true } });
|
|
6006
6640
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-3", children: [
|
|
6007
|
-
/* @__PURE__ */ jsxRuntime.jsx(EditorField, { label: "Label", htmlFor: "section-acc-btn-label", children: /* @__PURE__ */ jsxRuntime.
|
|
6008
|
-
|
|
6009
|
-
|
|
6010
|
-
|
|
6011
|
-
|
|
6012
|
-
|
|
6013
|
-
|
|
6014
|
-
|
|
6015
|
-
|
|
6016
|
-
}
|
|
6017
|
-
|
|
6018
|
-
|
|
6641
|
+
/* @__PURE__ */ jsxRuntime.jsx(EditorField, { label: "Label", htmlFor: "section-acc-btn-label", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5", children: [
|
|
6642
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
6643
|
+
Input,
|
|
6644
|
+
{
|
|
6645
|
+
ref: labelRef,
|
|
6646
|
+
id: "section-acc-btn-label",
|
|
6647
|
+
value: label,
|
|
6648
|
+
placeholder: "e.g. Learn more",
|
|
6649
|
+
onChange: (e) => setLabel(e.target.value)
|
|
6650
|
+
}
|
|
6651
|
+
),
|
|
6652
|
+
/* @__PURE__ */ jsxRuntime.jsx(EmojiTextInsertButton, { targetRef: labelRef, value: label, onChange: setLabel, className: "shrink-0 border" })
|
|
6653
|
+
] }) }),
|
|
6019
6654
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
6020
6655
|
EditorField,
|
|
6021
6656
|
{
|
|
@@ -7570,6 +8205,7 @@ function BlockKitchen(props) {
|
|
|
7570
8205
|
initialBlocks,
|
|
7571
8206
|
onChange,
|
|
7572
8207
|
previewHooks,
|
|
8208
|
+
customEmojis,
|
|
7573
8209
|
loadChannels,
|
|
7574
8210
|
loadSendAsUserStatus,
|
|
7575
8211
|
onSend,
|
|
@@ -7613,6 +8249,13 @@ function BlockKitchen(props) {
|
|
|
7613
8249
|
const [previewSurface, setPreviewSurface] = React.useState(allowedSurfaces[0]);
|
|
7614
8250
|
const [activePaletteVariantId, setActivePaletteVariantId] = React.useState(null);
|
|
7615
8251
|
const isMobile = useIsMobile();
|
|
8252
|
+
const mergedPreviewHooks = React.useMemo(() => {
|
|
8253
|
+
if (!customEmojis || customEmojis.length === 0) {
|
|
8254
|
+
return previewHooks;
|
|
8255
|
+
}
|
|
8256
|
+
const byName = new Map(customEmojis.map((emoji) => [emoji.name, emoji]));
|
|
8257
|
+
return { emoji: makeEmojiHook(byName), ...previewHooks };
|
|
8258
|
+
}, [customEmojis, previewHooks]);
|
|
7616
8259
|
const validation = useBlockKitValidation(blocks, "message");
|
|
7617
8260
|
const sensors = core.useSensors(
|
|
7618
8261
|
core.useSensor(core.PointerSensor, { activationConstraint: { distance: 4 } }),
|
|
@@ -7662,7 +8305,7 @@ function BlockKitchen(props) {
|
|
|
7662
8305
|
}, []);
|
|
7663
8306
|
const activePaletteVariant = activePaletteVariantId ? variantById.get(activePaletteVariantId) : null;
|
|
7664
8307
|
const blockPayloads = blocks.map((b) => b.block);
|
|
7665
|
-
return /* @__PURE__ */ jsxRuntime.jsx(BrandThemeScope, { theme, children: /* @__PURE__ */ jsxRuntime.jsx(TooltipProvider, { delayDuration: 200, children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
8308
|
+
return /* @__PURE__ */ jsxRuntime.jsx(BrandThemeScope, { theme, children: /* @__PURE__ */ jsxRuntime.jsx(CustomEmojiProvider, { customEmojis, children: /* @__PURE__ */ jsxRuntime.jsx(TooltipProvider, { delayDuration: 200, children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
7666
8309
|
core.DndContext,
|
|
7667
8310
|
{
|
|
7668
8311
|
sensors,
|
|
@@ -7708,7 +8351,7 @@ function BlockKitchen(props) {
|
|
|
7708
8351
|
{
|
|
7709
8352
|
blocks,
|
|
7710
8353
|
workspaceName,
|
|
7711
|
-
previewHooks,
|
|
8354
|
+
previewHooks: mergedPreviewHooks,
|
|
7712
8355
|
previewTheme,
|
|
7713
8356
|
previewSurface,
|
|
7714
8357
|
errorsByBlockId: validation.byBlockId,
|
|
@@ -7784,7 +8427,7 @@ function BlockKitchen(props) {
|
|
|
7784
8427
|
)
|
|
7785
8428
|
]
|
|
7786
8429
|
}
|
|
7787
|
-
) }) });
|
|
8430
|
+
) }) }) });
|
|
7788
8431
|
}
|
|
7789
8432
|
var UNCATEGORIZED = "Other";
|
|
7790
8433
|
function TemplateThumbnail({ blocks, theme }) {
|