@tightknitai/block-kitchen 0.8.3 → 0.8.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/README.md +7 -0
- package/dist/helpers.cjs.map +1 -1
- package/dist/helpers.d.cts +2 -2
- package/dist/helpers.d.ts +2 -2
- package/dist/helpers.js.map +1 -1
- package/dist/index.cjs +845 -165
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -6
- package/dist/index.d.ts +5 -6
- package/dist/index.js +848 -168
- 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;
|
|
@@ -1628,6 +1670,12 @@ function isSafeHref(value) {
|
|
|
1628
1670
|
}
|
|
1629
1671
|
return SAFE_LINK_PROTOCOLS.test(scheme);
|
|
1630
1672
|
}
|
|
1673
|
+
function hasExplicitSafeScheme(value) {
|
|
1674
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
1675
|
+
return false;
|
|
1676
|
+
}
|
|
1677
|
+
return getScheme(value) !== null && isSafeHref(value);
|
|
1678
|
+
}
|
|
1631
1679
|
function isSafeImageSrc(value) {
|
|
1632
1680
|
if (value === null || value === void 0) {
|
|
1633
1681
|
return false;
|
|
@@ -1712,10 +1760,47 @@ function toSlackBlocks(blocks) {
|
|
|
1712
1760
|
});
|
|
1713
1761
|
}
|
|
1714
1762
|
|
|
1763
|
+
// src/lib/validation-sanitize.ts
|
|
1764
|
+
var VALIDATION_EMOJI_PLACEHOLDER = "white_check_mark";
|
|
1765
|
+
function rewriteEmojiNames(value) {
|
|
1766
|
+
if (Array.isArray(value)) {
|
|
1767
|
+
let changed = false;
|
|
1768
|
+
const next = value.map((item) => {
|
|
1769
|
+
const rewritten = rewriteEmojiNames(item);
|
|
1770
|
+
if (rewritten !== item) {
|
|
1771
|
+
changed = true;
|
|
1772
|
+
}
|
|
1773
|
+
return rewritten;
|
|
1774
|
+
});
|
|
1775
|
+
return changed ? next : value;
|
|
1776
|
+
}
|
|
1777
|
+
if (value !== null && typeof value === "object") {
|
|
1778
|
+
const source = value;
|
|
1779
|
+
let changed = false;
|
|
1780
|
+
const next = {};
|
|
1781
|
+
for (const key of Object.keys(source)) {
|
|
1782
|
+
const rewritten = rewriteEmojiNames(source[key]);
|
|
1783
|
+
next[key] = rewritten;
|
|
1784
|
+
if (rewritten !== source[key]) {
|
|
1785
|
+
changed = true;
|
|
1786
|
+
}
|
|
1787
|
+
}
|
|
1788
|
+
if (next.type === "emoji" && typeof next.name === "string" && next.name !== VALIDATION_EMOJI_PLACEHOLDER) {
|
|
1789
|
+
next.name = VALIDATION_EMOJI_PLACEHOLDER;
|
|
1790
|
+
changed = true;
|
|
1791
|
+
}
|
|
1792
|
+
return changed ? next : value;
|
|
1793
|
+
}
|
|
1794
|
+
return value;
|
|
1795
|
+
}
|
|
1796
|
+
function stripCustomEmojiForValidation(blocks) {
|
|
1797
|
+
return blocks.map((block) => rewriteEmojiNames(block));
|
|
1798
|
+
}
|
|
1799
|
+
|
|
1715
1800
|
// src/state/use-block-kit-validation.ts
|
|
1716
1801
|
var DEBOUNCE_MS = 150;
|
|
1717
1802
|
function computeValidation(blocks, surface) {
|
|
1718
|
-
const payload = toSlackBlocks(blocks.map((b) => b.block));
|
|
1803
|
+
const payload = stripCustomEmojiForValidation(toSlackBlocks(blocks.map((b) => b.block)));
|
|
1719
1804
|
const result = slackBlockKitValidator.validateBlockKit(payload, {
|
|
1720
1805
|
target: "blocks",
|
|
1721
1806
|
surface: toValidatorSurface(surface)
|
|
@@ -3561,52 +3646,55 @@ function ContextEditor({ block, onChange }) {
|
|
|
3561
3646
|
{
|
|
3562
3647
|
label: "Items",
|
|
3563
3648
|
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
|
-
|
|
3649
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
3650
|
+
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,
|
|
3651
|
+
elements.map((el, idx) => {
|
|
3652
|
+
if (el.type === "mrkdwn" || el.type === "plain_text") {
|
|
3653
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3654
|
+
TextItemCard,
|
|
3655
|
+
{
|
|
3656
|
+
index: idx,
|
|
3657
|
+
element: el,
|
|
3658
|
+
onChange: (next) => updateAt(idx, () => next),
|
|
3659
|
+
onRemove: () => removeAt(idx)
|
|
3660
|
+
},
|
|
3661
|
+
idx
|
|
3662
|
+
);
|
|
3663
|
+
}
|
|
3664
|
+
if (el.type === "image" && "image_url" in el) {
|
|
3665
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3666
|
+
ImageItemCard,
|
|
3667
|
+
{
|
|
3668
|
+
index: idx,
|
|
3669
|
+
element: el,
|
|
3670
|
+
onChange: (next) => updateAt(idx, () => next),
|
|
3671
|
+
onRemove: () => removeAt(idx)
|
|
3672
|
+
},
|
|
3673
|
+
idx
|
|
3674
|
+
);
|
|
3675
|
+
}
|
|
3676
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3677
|
+
"div",
|
|
3580
3678
|
{
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
3679
|
+
className: "flex items-center justify-between rounded border bg-muted/30 px-2 py-1.5 text-xs text-muted-foreground",
|
|
3680
|
+
children: [
|
|
3681
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "Item not editable in the visual builder" }),
|
|
3682
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3683
|
+
"button",
|
|
3684
|
+
{
|
|
3685
|
+
type: "button",
|
|
3686
|
+
"aria-label": "Remove item",
|
|
3687
|
+
onClick: () => removeAt(idx),
|
|
3688
|
+
className: "text-muted-foreground hover:text-destructive",
|
|
3689
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { className: "h-3.5 w-3.5" })
|
|
3690
|
+
}
|
|
3691
|
+
)
|
|
3692
|
+
]
|
|
3585
3693
|
},
|
|
3586
3694
|
idx
|
|
3587
3695
|
);
|
|
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
|
-
}) })
|
|
3696
|
+
})
|
|
3697
|
+
] })
|
|
3610
3698
|
}
|
|
3611
3699
|
),
|
|
3612
3700
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2", children: [
|
|
@@ -3745,12 +3833,423 @@ function ImageItemCard({
|
|
|
3745
3833
|
function DividerEditor() {
|
|
3746
3834
|
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
3835
|
}
|
|
3836
|
+
|
|
3837
|
+
// src/lib/emoji-data.ts
|
|
3838
|
+
var TONE_CODEPOINT_TO_SKIN_TONE = {
|
|
3839
|
+
"1F3FB": 2,
|
|
3840
|
+
"1F3FC": 3,
|
|
3841
|
+
"1F3FD": 4,
|
|
3842
|
+
"1F3FE": 5,
|
|
3843
|
+
"1F3FF": 6
|
|
3844
|
+
};
|
|
3845
|
+
var CATEGORY_ORDER = [
|
|
3846
|
+
"Smileys & Emotion",
|
|
3847
|
+
"People & Body",
|
|
3848
|
+
"Animals & Nature",
|
|
3849
|
+
"Food & Drink",
|
|
3850
|
+
"Travel & Places",
|
|
3851
|
+
"Activities",
|
|
3852
|
+
"Objects",
|
|
3853
|
+
"Symbols",
|
|
3854
|
+
"Flags"
|
|
3855
|
+
];
|
|
3856
|
+
function codepointsToGlyph(unified) {
|
|
3857
|
+
return unified.split("-").map((cp) => {
|
|
3858
|
+
const n = Number.parseInt(cp, 16);
|
|
3859
|
+
return Number.isFinite(n) ? String.fromCodePoint(n) : "";
|
|
3860
|
+
}).join("");
|
|
3861
|
+
}
|
|
3862
|
+
function unifiedToUnicode(unified) {
|
|
3863
|
+
return unified.toLowerCase();
|
|
3864
|
+
}
|
|
3865
|
+
function composeTextEmoji(name, skinTone) {
|
|
3866
|
+
if (skinTone && skinTone >= 2 && skinTone <= 6) {
|
|
3867
|
+
return `:${name}::skin-tone-${skinTone}:`;
|
|
3868
|
+
}
|
|
3869
|
+
return `:${name}:`;
|
|
3870
|
+
}
|
|
3871
|
+
function buildEmojiIndex(raw) {
|
|
3872
|
+
const all = [];
|
|
3873
|
+
const byName = /* @__PURE__ */ new Map();
|
|
3874
|
+
const byUnified = /* @__PURE__ */ new Map();
|
|
3875
|
+
for (const r of raw) {
|
|
3876
|
+
if (!CATEGORY_ORDER.includes(r.category)) {
|
|
3877
|
+
continue;
|
|
3878
|
+
}
|
|
3879
|
+
const shortNames = r.short_names?.length ? r.short_names : [r.short_name];
|
|
3880
|
+
const skinUnified = {};
|
|
3881
|
+
if (r.skin_variations) {
|
|
3882
|
+
for (const [toneKey, variation] of Object.entries(r.skin_variations)) {
|
|
3883
|
+
const skinTone = TONE_CODEPOINT_TO_SKIN_TONE[toneKey];
|
|
3884
|
+
if (skinTone && variation?.unified) {
|
|
3885
|
+
skinUnified[skinTone] = variation.unified;
|
|
3886
|
+
}
|
|
3887
|
+
}
|
|
3888
|
+
}
|
|
3889
|
+
const datum = {
|
|
3890
|
+
name: shortNames[0],
|
|
3891
|
+
shortNames,
|
|
3892
|
+
unified: r.unified,
|
|
3893
|
+
category: r.category,
|
|
3894
|
+
sortOrder: r.sort_order,
|
|
3895
|
+
skinUnified
|
|
3896
|
+
};
|
|
3897
|
+
all.push(datum);
|
|
3898
|
+
byUnified.set(r.unified.toUpperCase(), datum);
|
|
3899
|
+
for (const sn of shortNames) {
|
|
3900
|
+
if (!byName.has(sn)) {
|
|
3901
|
+
byName.set(sn, datum);
|
|
3902
|
+
}
|
|
3903
|
+
}
|
|
3904
|
+
}
|
|
3905
|
+
all.sort((a, b) => a.sortOrder - b.sortOrder);
|
|
3906
|
+
const byCategory = CATEGORY_ORDER.map((category) => ({
|
|
3907
|
+
category,
|
|
3908
|
+
emoji: all.filter((e) => e.category === category)
|
|
3909
|
+
})).filter((group) => group.emoji.length > 0);
|
|
3910
|
+
return { all, byName, byUnified, byCategory };
|
|
3911
|
+
}
|
|
3912
|
+
var indexPromise = null;
|
|
3913
|
+
var cachedIndex = null;
|
|
3914
|
+
async function loadEmojiIndex() {
|
|
3915
|
+
if (cachedIndex) {
|
|
3916
|
+
return cachedIndex;
|
|
3917
|
+
}
|
|
3918
|
+
if (!indexPromise) {
|
|
3919
|
+
indexPromise = import('emoji-datasource/emoji.json').then((mod) => {
|
|
3920
|
+
const raw = mod.default ?? mod;
|
|
3921
|
+
cachedIndex = buildEmojiIndex(raw);
|
|
3922
|
+
return cachedIndex;
|
|
3923
|
+
}).catch((err) => {
|
|
3924
|
+
indexPromise = null;
|
|
3925
|
+
throw err;
|
|
3926
|
+
});
|
|
3927
|
+
}
|
|
3928
|
+
return indexPromise;
|
|
3929
|
+
}
|
|
3930
|
+
function getEmojiIndex() {
|
|
3931
|
+
return cachedIndex;
|
|
3932
|
+
}
|
|
3933
|
+
|
|
3934
|
+
// src/lib/emoji-recents.ts
|
|
3935
|
+
var STORAGE_KEY = "bk:emoji-recents";
|
|
3936
|
+
var SKIN_TONE_KEY = "bk:emoji-skin-tone";
|
|
3937
|
+
var MAX_RECENTS = 24;
|
|
3938
|
+
function readEmojiSkinTone() {
|
|
3939
|
+
try {
|
|
3940
|
+
const raw = globalThis.localStorage?.getItem(SKIN_TONE_KEY);
|
|
3941
|
+
if (!raw) {
|
|
3942
|
+
return null;
|
|
3943
|
+
}
|
|
3944
|
+
const n = Number.parseInt(raw, 10);
|
|
3945
|
+
return n >= 2 && n <= 6 ? n : null;
|
|
3946
|
+
} catch {
|
|
3947
|
+
return null;
|
|
3948
|
+
}
|
|
3949
|
+
}
|
|
3950
|
+
function writeEmojiSkinTone(skinTone) {
|
|
3951
|
+
try {
|
|
3952
|
+
if (skinTone && skinTone >= 2 && skinTone <= 6) {
|
|
3953
|
+
globalThis.localStorage?.setItem(SKIN_TONE_KEY, String(skinTone));
|
|
3954
|
+
} else {
|
|
3955
|
+
globalThis.localStorage?.removeItem(SKIN_TONE_KEY);
|
|
3956
|
+
}
|
|
3957
|
+
} catch {
|
|
3958
|
+
}
|
|
3959
|
+
}
|
|
3960
|
+
function readEmojiRecents() {
|
|
3961
|
+
try {
|
|
3962
|
+
const raw = globalThis.localStorage?.getItem(STORAGE_KEY);
|
|
3963
|
+
if (!raw) {
|
|
3964
|
+
return [];
|
|
3965
|
+
}
|
|
3966
|
+
const parsed = JSON.parse(raw);
|
|
3967
|
+
return Array.isArray(parsed) ? parsed.filter((n) => typeof n === "string") : [];
|
|
3968
|
+
} catch {
|
|
3969
|
+
return [];
|
|
3970
|
+
}
|
|
3971
|
+
}
|
|
3972
|
+
function pushEmojiRecent(name) {
|
|
3973
|
+
const next = [name, ...readEmojiRecents().filter((n) => n !== name)].slice(0, MAX_RECENTS);
|
|
3974
|
+
try {
|
|
3975
|
+
globalThis.localStorage?.setItem(STORAGE_KEY, JSON.stringify(next));
|
|
3976
|
+
} catch {
|
|
3977
|
+
}
|
|
3978
|
+
return next;
|
|
3979
|
+
}
|
|
3980
|
+
var SKIN_TONES = [
|
|
3981
|
+
{ tone: null, label: "Default", swatch: "\u270B" },
|
|
3982
|
+
{ tone: 2, label: "Light", swatch: "\u270B\u{1F3FB}" },
|
|
3983
|
+
{ tone: 3, label: "Medium-light", swatch: "\u270B\u{1F3FC}" },
|
|
3984
|
+
{ tone: 4, label: "Medium", swatch: "\u270B\u{1F3FD}" },
|
|
3985
|
+
{ tone: 5, label: "Medium-dark", swatch: "\u270B\u{1F3FE}" },
|
|
3986
|
+
{ tone: 6, label: "Dark", swatch: "\u270B\u{1F3FF}" }
|
|
3987
|
+
];
|
|
3988
|
+
function EmojiPicker({
|
|
3989
|
+
onSelect,
|
|
3990
|
+
autoFocus = true
|
|
3991
|
+
}) {
|
|
3992
|
+
const { customEmojis } = useCustomEmojis();
|
|
3993
|
+
const [index, setIndex] = React.useState(null);
|
|
3994
|
+
const [loadError, setLoadError] = React.useState(false);
|
|
3995
|
+
const [query, setQuery] = React.useState("");
|
|
3996
|
+
const [skinTone, setSkinTone] = React.useState(() => readEmojiSkinTone());
|
|
3997
|
+
const [recents, setRecents] = React.useState(() => readEmojiRecents());
|
|
3998
|
+
const searchRef = React.useRef(null);
|
|
3999
|
+
React.useEffect(() => {
|
|
4000
|
+
let active = true;
|
|
4001
|
+
loadEmojiIndex().then((idx) => {
|
|
4002
|
+
if (active) {
|
|
4003
|
+
setIndex(idx);
|
|
4004
|
+
}
|
|
4005
|
+
}).catch(() => {
|
|
4006
|
+
if (active) {
|
|
4007
|
+
setLoadError(true);
|
|
4008
|
+
}
|
|
4009
|
+
});
|
|
4010
|
+
return () => {
|
|
4011
|
+
active = false;
|
|
4012
|
+
};
|
|
4013
|
+
}, []);
|
|
4014
|
+
React.useEffect(() => {
|
|
4015
|
+
if (autoFocus) {
|
|
4016
|
+
searchRef.current?.focus();
|
|
4017
|
+
}
|
|
4018
|
+
}, [autoFocus]);
|
|
4019
|
+
const customByName = React.useMemo(() => {
|
|
4020
|
+
const map = /* @__PURE__ */ new Map();
|
|
4021
|
+
for (const e of customEmojis) {
|
|
4022
|
+
map.set(e.name, e);
|
|
4023
|
+
}
|
|
4024
|
+
return map;
|
|
4025
|
+
}, [customEmojis]);
|
|
4026
|
+
const customImageEmoji = React.useMemo(() => customEmojis.filter((e) => e.url), [customEmojis]);
|
|
4027
|
+
const handlePick = (selection) => {
|
|
4028
|
+
setRecents(pushEmojiRecent(selection.name));
|
|
4029
|
+
onSelect(selection);
|
|
4030
|
+
};
|
|
4031
|
+
const handleSkinTone = (tone) => {
|
|
4032
|
+
setSkinTone(tone);
|
|
4033
|
+
writeEmojiSkinTone(tone);
|
|
4034
|
+
};
|
|
4035
|
+
const sections = React.useMemo(() => {
|
|
4036
|
+
if (!index) {
|
|
4037
|
+
return [];
|
|
4038
|
+
}
|
|
4039
|
+
const trimmed = query.trim().toLowerCase();
|
|
4040
|
+
if (trimmed) {
|
|
4041
|
+
const standard = index.all.filter((d) => d.shortNames.some((sn) => sn.includes(trimmed))).slice(0, 120).map((datum) => ({ datum }));
|
|
4042
|
+
const custom = customImageEmoji.filter((e) => e.name.toLowerCase().includes(trimmed)).map((custom2) => ({ custom: custom2 }));
|
|
4043
|
+
return [{ key: "search", title: "Search results", emoji: [...custom, ...standard] }];
|
|
4044
|
+
}
|
|
4045
|
+
const out = [];
|
|
4046
|
+
if (recents.length > 0) {
|
|
4047
|
+
const recentEmoji = recents.map((name) => {
|
|
4048
|
+
const custom = customByName.get(name);
|
|
4049
|
+
if (custom?.url) {
|
|
4050
|
+
return { custom };
|
|
4051
|
+
}
|
|
4052
|
+
const datum = index.byName.get(name);
|
|
4053
|
+
return datum ? { datum } : null;
|
|
4054
|
+
}).filter((e) => e !== null);
|
|
4055
|
+
if (recentEmoji.length > 0) {
|
|
4056
|
+
out.push({ key: "recents", title: "Frequently used", emoji: recentEmoji });
|
|
4057
|
+
}
|
|
4058
|
+
}
|
|
4059
|
+
if (customImageEmoji.length > 0) {
|
|
4060
|
+
out.push({ key: "custom", title: "Custom", emoji: customImageEmoji.map((custom) => ({ custom })) });
|
|
4061
|
+
}
|
|
4062
|
+
for (const group of index.byCategory) {
|
|
4063
|
+
out.push({ key: group.category, title: group.category, emoji: group.emoji.map((datum) => ({ datum })) });
|
|
4064
|
+
}
|
|
4065
|
+
return out;
|
|
4066
|
+
}, [index, query, recents, customByName, customImageEmoji]);
|
|
4067
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bk-emoji-picker flex w-[320px] max-w-[90vw] flex-col gap-2", children: [
|
|
4068
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
4069
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4070
|
+
Input,
|
|
4071
|
+
{
|
|
4072
|
+
ref: searchRef,
|
|
4073
|
+
value: query,
|
|
4074
|
+
placeholder: "Search emoji\u2026",
|
|
4075
|
+
"aria-label": "Search emoji",
|
|
4076
|
+
onChange: (e) => setQuery(e.target.value),
|
|
4077
|
+
className: "h-8"
|
|
4078
|
+
}
|
|
4079
|
+
),
|
|
4080
|
+
/* @__PURE__ */ jsxRuntime.jsx(SkinToneSelector, { value: skinTone, onChange: handleSkinTone })
|
|
4081
|
+
] }),
|
|
4082
|
+
/* @__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: [
|
|
4083
|
+
/* @__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 }),
|
|
4084
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-8 gap-0.5", children: section.emoji.map(
|
|
4085
|
+
(entry, i) => entry.custom ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
4086
|
+
CustomEmojiButton,
|
|
4087
|
+
{
|
|
4088
|
+
emoji: entry.custom,
|
|
4089
|
+
onPick: handlePick
|
|
4090
|
+
},
|
|
4091
|
+
`${section.key}-${entry.custom.name}-${i}`
|
|
4092
|
+
) : entry.datum ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
4093
|
+
StandardEmojiButton,
|
|
4094
|
+
{
|
|
4095
|
+
datum: entry.datum,
|
|
4096
|
+
skinTone,
|
|
4097
|
+
onPick: handlePick
|
|
4098
|
+
},
|
|
4099
|
+
`${section.key}-${entry.datum.name}-${i}`
|
|
4100
|
+
) : null
|
|
4101
|
+
) })
|
|
4102
|
+
] }, section.key)) })
|
|
4103
|
+
] });
|
|
4104
|
+
}
|
|
4105
|
+
function resolveStandard(datum, skinTone) {
|
|
4106
|
+
const tonedUnified = skinTone ? datum.skinUnified[skinTone] : void 0;
|
|
4107
|
+
return {
|
|
4108
|
+
name: datum.name,
|
|
4109
|
+
unicode: unifiedToUnicode(tonedUnified ?? datum.unified),
|
|
4110
|
+
src: null,
|
|
4111
|
+
skinTone: tonedUnified ? skinTone : null
|
|
4112
|
+
};
|
|
4113
|
+
}
|
|
4114
|
+
var emojiButtonClass = "flex h-8 w-8 cursor-pointer items-center justify-center rounded text-lg leading-none hover:bg-accent";
|
|
4115
|
+
function StandardEmojiButton({
|
|
4116
|
+
datum,
|
|
4117
|
+
skinTone,
|
|
4118
|
+
onPick
|
|
4119
|
+
}) {
|
|
4120
|
+
const tonedUnified = skinTone && datum.skinUnified[skinTone] || datum.unified;
|
|
4121
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
4122
|
+
"button",
|
|
4123
|
+
{
|
|
4124
|
+
type: "button",
|
|
4125
|
+
className: emojiButtonClass,
|
|
4126
|
+
title: `:${datum.name}:`,
|
|
4127
|
+
"aria-label": datum.name,
|
|
4128
|
+
onClick: () => onPick(resolveStandard(datum, skinTone)),
|
|
4129
|
+
children: codepointsToGlyph(tonedUnified)
|
|
4130
|
+
}
|
|
4131
|
+
);
|
|
4132
|
+
}
|
|
4133
|
+
function CustomEmojiButton({ emoji, onPick }) {
|
|
4134
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
4135
|
+
"button",
|
|
4136
|
+
{
|
|
4137
|
+
type: "button",
|
|
4138
|
+
className: emojiButtonClass,
|
|
4139
|
+
title: `:${emoji.name}:`,
|
|
4140
|
+
"aria-label": emoji.name,
|
|
4141
|
+
onClick: () => onPick({ name: emoji.name, unicode: null, src: emoji.url, skinTone: null }),
|
|
4142
|
+
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: [
|
|
4143
|
+
":",
|
|
4144
|
+
emoji.name,
|
|
4145
|
+
":"
|
|
4146
|
+
] })
|
|
4147
|
+
}
|
|
4148
|
+
);
|
|
4149
|
+
}
|
|
4150
|
+
function SkinToneSelector({ value, onChange }) {
|
|
4151
|
+
const [open, setOpen] = React.useState(false);
|
|
4152
|
+
const active = SKIN_TONES.find((t) => t.tone === value) ?? SKIN_TONES[0];
|
|
4153
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative shrink-0", children: [
|
|
4154
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4155
|
+
"button",
|
|
4156
|
+
{
|
|
4157
|
+
type: "button",
|
|
4158
|
+
"aria-label": `Skin tone: ${active.label}`,
|
|
4159
|
+
title: `Skin tone: ${active.label}`,
|
|
4160
|
+
className: "flex h-8 w-8 items-center justify-center rounded border text-lg leading-none hover:bg-accent",
|
|
4161
|
+
onClick: () => setOpen((o) => !o),
|
|
4162
|
+
children: active.swatch
|
|
4163
|
+
}
|
|
4164
|
+
),
|
|
4165
|
+
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(
|
|
4166
|
+
"button",
|
|
4167
|
+
{
|
|
4168
|
+
type: "button",
|
|
4169
|
+
"aria-label": t.label,
|
|
4170
|
+
title: t.label,
|
|
4171
|
+
className: cn(emojiButtonClass, value === t.tone && "bg-accent"),
|
|
4172
|
+
onClick: () => {
|
|
4173
|
+
onChange(t.tone);
|
|
4174
|
+
setOpen(false);
|
|
4175
|
+
},
|
|
4176
|
+
children: t.swatch
|
|
4177
|
+
},
|
|
4178
|
+
t.label
|
|
4179
|
+
)) })
|
|
4180
|
+
] });
|
|
4181
|
+
}
|
|
4182
|
+
function EmojiPickerButton({
|
|
4183
|
+
onSelect,
|
|
4184
|
+
className,
|
|
4185
|
+
label = "Insert emoji",
|
|
4186
|
+
align = "start"
|
|
4187
|
+
}) {
|
|
4188
|
+
const [open, setOpen] = React.useState(false);
|
|
4189
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(Popover, { open, onOpenChange: setOpen, children: [
|
|
4190
|
+
/* @__PURE__ */ jsxRuntime.jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
4191
|
+
"button",
|
|
4192
|
+
{
|
|
4193
|
+
type: "button",
|
|
4194
|
+
"aria-label": label,
|
|
4195
|
+
title: label,
|
|
4196
|
+
className: cn(
|
|
4197
|
+
"flex h-7 w-7 cursor-pointer items-center justify-center rounded text-muted-foreground transition-colors hover:bg-accent hover:text-foreground",
|
|
4198
|
+
className
|
|
4199
|
+
),
|
|
4200
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Smile, { className: "h-3.5 w-3.5" })
|
|
4201
|
+
}
|
|
4202
|
+
) }),
|
|
4203
|
+
/* @__PURE__ */ jsxRuntime.jsx(PopoverContent, { align, className: "w-auto p-2", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
4204
|
+
EmojiPicker,
|
|
4205
|
+
{
|
|
4206
|
+
onSelect: (selection) => {
|
|
4207
|
+
onSelect(selection);
|
|
4208
|
+
setOpen(false);
|
|
4209
|
+
}
|
|
4210
|
+
}
|
|
4211
|
+
) })
|
|
4212
|
+
] });
|
|
4213
|
+
}
|
|
4214
|
+
function EmojiTextInsertButton({
|
|
4215
|
+
targetRef,
|
|
4216
|
+
value,
|
|
4217
|
+
onChange,
|
|
4218
|
+
className,
|
|
4219
|
+
align = "end"
|
|
4220
|
+
}) {
|
|
4221
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
4222
|
+
EmojiPickerButton,
|
|
4223
|
+
{
|
|
4224
|
+
align,
|
|
4225
|
+
label: "Insert emoji",
|
|
4226
|
+
className,
|
|
4227
|
+
onSelect: (sel) => {
|
|
4228
|
+
const el = targetRef.current;
|
|
4229
|
+
const insert = composeTextEmoji(sel.name, sel.skinTone ?? void 0);
|
|
4230
|
+
const start = el?.selectionStart ?? value.length;
|
|
4231
|
+
const end = el?.selectionEnd ?? value.length;
|
|
4232
|
+
const next = value.slice(0, start) + insert + value.slice(end);
|
|
4233
|
+
onChange(next);
|
|
4234
|
+
requestAnimationFrame(() => {
|
|
4235
|
+
if (el) {
|
|
4236
|
+
const caret = start + insert.length;
|
|
4237
|
+
el.focus();
|
|
4238
|
+
el.setSelectionRange(caret, caret);
|
|
4239
|
+
}
|
|
4240
|
+
});
|
|
4241
|
+
}
|
|
4242
|
+
}
|
|
4243
|
+
);
|
|
4244
|
+
}
|
|
3748
4245
|
var HEADER_MAX = 150;
|
|
3749
4246
|
var HEADER_LEVELS = [1, 2, 3, 4];
|
|
3750
4247
|
function HeaderEditor({ block, onChange }) {
|
|
3751
4248
|
const text = block.text?.text ?? "";
|
|
3752
4249
|
const remaining = HEADER_MAX - text.length;
|
|
3753
4250
|
const level = block.level;
|
|
4251
|
+
const inputRef = React.useRef(null);
|
|
4252
|
+
const setText = (next) => onChange({ ...block, text: { type: "plain_text", text: next, emoji: true } });
|
|
3754
4253
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-3", children: [
|
|
3755
4254
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3756
4255
|
EditorField,
|
|
@@ -3758,23 +4257,20 @@ function HeaderEditor({ block, onChange }) {
|
|
|
3758
4257
|
label: "Heading text",
|
|
3759
4258
|
help: `Plain text only. ${remaining} characters remaining.`,
|
|
3760
4259
|
htmlFor: "header-text",
|
|
3761
|
-
children: /* @__PURE__ */ jsxRuntime.
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
})
|
|
3776
|
-
}
|
|
3777
|
-
)
|
|
4260
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5", children: [
|
|
4261
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4262
|
+
Input,
|
|
4263
|
+
{
|
|
4264
|
+
ref: inputRef,
|
|
4265
|
+
id: "header-text",
|
|
4266
|
+
value: text,
|
|
4267
|
+
maxLength: HEADER_MAX,
|
|
4268
|
+
placeholder: "e.g. Weekly roundup",
|
|
4269
|
+
onChange: (e) => setText(e.target.value)
|
|
4270
|
+
}
|
|
4271
|
+
),
|
|
4272
|
+
/* @__PURE__ */ jsxRuntime.jsx(EmojiTextInsertButton, { targetRef: inputRef, value: text, onChange: setText, className: "shrink-0 border" })
|
|
4273
|
+
] })
|
|
3778
4274
|
}
|
|
3779
4275
|
),
|
|
3780
4276
|
/* @__PURE__ */ jsxRuntime.jsx(EditorField, { label: "Level", help: "Optional. Builder-only extension; Slack's API ignores this value.", children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
@@ -4637,22 +5133,29 @@ function CheckboxField({
|
|
|
4637
5133
|
] });
|
|
4638
5134
|
}
|
|
4639
5135
|
function MarkdownEditor({ block, onChange }) {
|
|
5136
|
+
const textRef = React.useRef(null);
|
|
5137
|
+
const text = block.text ?? "";
|
|
5138
|
+
const setText = (next) => onChange({ ...block, text: next });
|
|
4640
5139
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
4641
5140
|
EditorField,
|
|
4642
5141
|
{
|
|
4643
5142
|
label: "Markdown",
|
|
4644
5143
|
help: "Standard markdown with GFM: **bold**, _italic_, lists, tables, ```code```, [links](url).",
|
|
4645
5144
|
htmlFor: "markdown-text",
|
|
4646
|
-
children: /* @__PURE__ */ jsxRuntime.
|
|
4647
|
-
|
|
4648
|
-
|
|
4649
|
-
|
|
4650
|
-
|
|
4651
|
-
|
|
4652
|
-
|
|
4653
|
-
|
|
4654
|
-
|
|
4655
|
-
|
|
5145
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start gap-1.5", children: [
|
|
5146
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5147
|
+
Textarea,
|
|
5148
|
+
{
|
|
5149
|
+
ref: textRef,
|
|
5150
|
+
id: "markdown-text",
|
|
5151
|
+
value: text,
|
|
5152
|
+
rows: 8,
|
|
5153
|
+
placeholder: "e.g. **Roadmap**\\n\\n- Item one\\n- Item two",
|
|
5154
|
+
onChange: (e) => setText(e.target.value)
|
|
5155
|
+
}
|
|
5156
|
+
),
|
|
5157
|
+
/* @__PURE__ */ jsxRuntime.jsx(EmojiTextInsertButton, { targetRef: textRef, value: text, onChange: setText, className: "mt-1 shrink-0 border" })
|
|
5158
|
+
] })
|
|
4656
5159
|
}
|
|
4657
5160
|
);
|
|
4658
5161
|
}
|
|
@@ -4747,6 +5250,26 @@ function PlanEditor({ block, onChange }) {
|
|
|
4747
5250
|
] });
|
|
4748
5251
|
}
|
|
4749
5252
|
|
|
5253
|
+
// src/lib/emoji-resolve.ts
|
|
5254
|
+
function makeEmojiImportResolver(customByName, index = getEmojiIndex()) {
|
|
5255
|
+
return (el) => {
|
|
5256
|
+
const name = el.name ?? "";
|
|
5257
|
+
const skinTone = el.skin_tone ?? null;
|
|
5258
|
+
const custom = customByName.get(name);
|
|
5259
|
+
if (custom?.url) {
|
|
5260
|
+
return { name, src: custom.url, unicode: null, skinTone };
|
|
5261
|
+
}
|
|
5262
|
+
const lookupName = custom?.alias ?? name;
|
|
5263
|
+
const datum = index?.byName.get(lookupName);
|
|
5264
|
+
let unicode = el.unicode ?? null;
|
|
5265
|
+
if (!unicode && datum) {
|
|
5266
|
+
const toned = skinTone ? datum.skinUnified[skinTone] : void 0;
|
|
5267
|
+
unicode = unifiedToUnicode(toned ?? datum.unified);
|
|
5268
|
+
}
|
|
5269
|
+
return { name, src: null, unicode, skinTone };
|
|
5270
|
+
};
|
|
5271
|
+
}
|
|
5272
|
+
|
|
4750
5273
|
// src/lib/rich-text-tiptap.ts
|
|
4751
5274
|
var SUPPORTED_BLOCK_KINDS = /* @__PURE__ */ new Set([
|
|
4752
5275
|
"rich_text_section",
|
|
@@ -4754,7 +5277,7 @@ var SUPPORTED_BLOCK_KINDS = /* @__PURE__ */ new Set([
|
|
|
4754
5277
|
"rich_text_quote",
|
|
4755
5278
|
"rich_text_preformatted"
|
|
4756
5279
|
]);
|
|
4757
|
-
var SUPPORTED_INLINE_KINDS = /* @__PURE__ */ new Set(["text", "link"]);
|
|
5280
|
+
var SUPPORTED_INLINE_KINDS = /* @__PURE__ */ new Set(["text", "link", "emoji"]);
|
|
4758
5281
|
var SUPPORTED_TEXT_STYLE_KEYS = /* @__PURE__ */ new Set(["bold", "italic", "strike", "code"]);
|
|
4759
5282
|
var MAX_LIST_INDENT = 8;
|
|
4760
5283
|
function detectLossy(block) {
|
|
@@ -4801,16 +5324,22 @@ function scanInlines(inlines, where, out) {
|
|
|
4801
5324
|
}
|
|
4802
5325
|
});
|
|
4803
5326
|
}
|
|
4804
|
-
|
|
5327
|
+
var defaultEmojiResolver = (el) => ({
|
|
5328
|
+
name: el.name ?? "",
|
|
5329
|
+
src: null,
|
|
5330
|
+
unicode: el.unicode ?? null,
|
|
5331
|
+
skinTone: el.skin_tone ?? null
|
|
5332
|
+
});
|
|
5333
|
+
function richTextToProseMirror(block, resolveEmoji = defaultEmojiResolver) {
|
|
4805
5334
|
const content = [];
|
|
4806
5335
|
const listsByIndent = /* @__PURE__ */ new Map();
|
|
4807
5336
|
for (const el of block.elements ?? []) {
|
|
4808
5337
|
if (el.type === "rich_text_list") {
|
|
4809
|
-
appendRichTextList(el, content, listsByIndent);
|
|
5338
|
+
appendRichTextList(el, content, listsByIndent, resolveEmoji);
|
|
4810
5339
|
continue;
|
|
4811
5340
|
}
|
|
4812
5341
|
listsByIndent.clear();
|
|
4813
|
-
const node = blockElementToPM(el);
|
|
5342
|
+
const node = blockElementToPM(el, resolveEmoji);
|
|
4814
5343
|
if (node) {
|
|
4815
5344
|
content.push(node);
|
|
4816
5345
|
}
|
|
@@ -4820,12 +5349,12 @@ function richTextToProseMirror(block) {
|
|
|
4820
5349
|
content: content.length > 0 ? content : [{ type: "paragraph" }]
|
|
4821
5350
|
};
|
|
4822
5351
|
}
|
|
4823
|
-
function appendRichTextList(el, topLevel, listsByIndent) {
|
|
5352
|
+
function appendRichTextList(el, topLevel, listsByIndent, resolveEmoji) {
|
|
4824
5353
|
const indent = el.indent ?? 0;
|
|
4825
5354
|
const listType = el.style === "ordered" ? "orderedList" : "bulletList";
|
|
4826
5355
|
const items = (el.elements ?? []).map((section) => ({
|
|
4827
5356
|
type: "listItem",
|
|
4828
|
-
content: [{ type: "paragraph", content: inlinesToPM(section.elements ?? []) }]
|
|
5357
|
+
content: [{ type: "paragraph", content: inlinesToPM(section.elements ?? [], resolveEmoji) }]
|
|
4829
5358
|
}));
|
|
4830
5359
|
for (const k of Array.from(listsByIndent.keys())) {
|
|
4831
5360
|
if (k > indent) {
|
|
@@ -4851,11 +5380,11 @@ function appendRichTextList(el, topLevel, listsByIndent) {
|
|
|
4851
5380
|
const parentLastItem = parent.content[parent.content.length - 1];
|
|
4852
5381
|
parentLastItem.content = [...parentLastItem.content ?? [], list];
|
|
4853
5382
|
}
|
|
4854
|
-
function blockElementToPM(el) {
|
|
5383
|
+
function blockElementToPM(el, resolveEmoji) {
|
|
4855
5384
|
if (el.type === "rich_text_section") {
|
|
4856
5385
|
return {
|
|
4857
5386
|
type: "paragraph",
|
|
4858
|
-
content: inlinesToPM(el.elements ?? [])
|
|
5387
|
+
content: inlinesToPM(el.elements ?? [], resolveEmoji)
|
|
4859
5388
|
};
|
|
4860
5389
|
}
|
|
4861
5390
|
if (el.type === "rich_text_quote") {
|
|
@@ -4864,7 +5393,7 @@ function blockElementToPM(el) {
|
|
|
4864
5393
|
content: [
|
|
4865
5394
|
{
|
|
4866
5395
|
type: "paragraph",
|
|
4867
|
-
content: inlinesToPM(el.elements ?? [])
|
|
5396
|
+
content: inlinesToPM(el.elements ?? [], resolveEmoji)
|
|
4868
5397
|
}
|
|
4869
5398
|
]
|
|
4870
5399
|
};
|
|
@@ -4879,15 +5408,31 @@ function blockElementToPM(el) {
|
|
|
4879
5408
|
}
|
|
4880
5409
|
return null;
|
|
4881
5410
|
}
|
|
4882
|
-
function
|
|
5411
|
+
function pushTextWithHardBreaks(out, text, marks) {
|
|
5412
|
+
const segments = text.split("\n");
|
|
5413
|
+
segments.forEach((segment, i) => {
|
|
5414
|
+
if (i > 0) {
|
|
5415
|
+
out.push({ type: "hardBreak" });
|
|
5416
|
+
}
|
|
5417
|
+
if (segment) {
|
|
5418
|
+
out.push({ type: "text", text: segment, marks });
|
|
5419
|
+
}
|
|
5420
|
+
});
|
|
5421
|
+
}
|
|
5422
|
+
function inlinesToPM(inlines, resolveEmoji) {
|
|
4883
5423
|
const out = [];
|
|
4884
5424
|
for (const inline of inlines) {
|
|
4885
5425
|
if (inline.type === "text") {
|
|
4886
5426
|
if (!inline.text) {
|
|
4887
5427
|
continue;
|
|
4888
5428
|
}
|
|
4889
|
-
|
|
4890
|
-
|
|
5429
|
+
pushTextWithHardBreaks(out, inline.text, styleToMarks(inline.style));
|
|
5430
|
+
} else if (inline.type === "emoji") {
|
|
5431
|
+
const attrs = resolveEmoji(inline);
|
|
5432
|
+
if (!attrs.name) {
|
|
5433
|
+
continue;
|
|
5434
|
+
}
|
|
5435
|
+
out.push({ type: "emoji", attrs });
|
|
4891
5436
|
} else if (inline.type === "link") {
|
|
4892
5437
|
const link = inline;
|
|
4893
5438
|
const text = link.text || link.url || "";
|
|
@@ -5005,6 +5550,23 @@ function flattenList(listNode, indent, out) {
|
|
|
5005
5550
|
function proseMirrorInlinesToRichTextElements(nodes) {
|
|
5006
5551
|
const out = [];
|
|
5007
5552
|
for (const node of nodes) {
|
|
5553
|
+
if (node.type === "emoji") {
|
|
5554
|
+
const name = String(node.attrs?.name ?? "");
|
|
5555
|
+
if (!name) {
|
|
5556
|
+
continue;
|
|
5557
|
+
}
|
|
5558
|
+
const emoji = { type: "emoji", name };
|
|
5559
|
+
const skinTone = node.attrs?.skinTone;
|
|
5560
|
+
if (typeof skinTone === "number" && skinTone >= 2 && skinTone <= 6) {
|
|
5561
|
+
emoji.skin_tone = skinTone;
|
|
5562
|
+
}
|
|
5563
|
+
out.push(emoji);
|
|
5564
|
+
continue;
|
|
5565
|
+
}
|
|
5566
|
+
if (node.type === "hardBreak") {
|
|
5567
|
+
out.push({ type: "text", text: "\n" });
|
|
5568
|
+
continue;
|
|
5569
|
+
}
|
|
5008
5570
|
if (node.type !== "text" || !node.text) {
|
|
5009
5571
|
continue;
|
|
5010
5572
|
}
|
|
@@ -5075,6 +5637,49 @@ function sameStyle(a, b) {
|
|
|
5075
5637
|
}
|
|
5076
5638
|
return ak.every((k, i) => k === bk[i]);
|
|
5077
5639
|
}
|
|
5640
|
+
function EmojiNodeView({ node }) {
|
|
5641
|
+
const { name, src, unicode } = node.attrs;
|
|
5642
|
+
const label = `:${name}:`;
|
|
5643
|
+
return /* @__PURE__ */ jsxRuntime.jsx(react.NodeViewWrapper, { as: "span", className: "bk-emoji-node", "data-emoji": name, title: label, children: src ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
5644
|
+
"img",
|
|
5645
|
+
{
|
|
5646
|
+
src,
|
|
5647
|
+
alt: label,
|
|
5648
|
+
style: {
|
|
5649
|
+
display: "inline-block",
|
|
5650
|
+
width: "1.25em",
|
|
5651
|
+
height: "1.25em",
|
|
5652
|
+
verticalAlign: "-0.25em",
|
|
5653
|
+
objectFit: "contain"
|
|
5654
|
+
}
|
|
5655
|
+
}
|
|
5656
|
+
) : /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-label": label, role: "img", children: unicode ? codepointsToGlyph(unicode) : label }) });
|
|
5657
|
+
}
|
|
5658
|
+
var EmojiNode = react.Node.create({
|
|
5659
|
+
name: "emoji",
|
|
5660
|
+
group: "inline",
|
|
5661
|
+
inline: true,
|
|
5662
|
+
atom: true,
|
|
5663
|
+
selectable: true,
|
|
5664
|
+
addAttributes() {
|
|
5665
|
+
return {
|
|
5666
|
+
name: { default: "" },
|
|
5667
|
+
src: { default: null },
|
|
5668
|
+
unicode: { default: null },
|
|
5669
|
+
skinTone: { default: null }
|
|
5670
|
+
};
|
|
5671
|
+
},
|
|
5672
|
+
parseHTML() {
|
|
5673
|
+
return [{ tag: "span[data-emoji]" }];
|
|
5674
|
+
},
|
|
5675
|
+
renderHTML({ HTMLAttributes }) {
|
|
5676
|
+
const name = HTMLAttributes.name ?? "";
|
|
5677
|
+
return ["span", react.mergeAttributes({ "data-emoji": name }, HTMLAttributes), `:${name}:`];
|
|
5678
|
+
},
|
|
5679
|
+
addNodeView() {
|
|
5680
|
+
return react.ReactNodeViewRenderer(EmojiNodeView);
|
|
5681
|
+
}
|
|
5682
|
+
});
|
|
5078
5683
|
var BLOCK_ELEMENT_LABEL = {
|
|
5079
5684
|
rich_text_section: "Section",
|
|
5080
5685
|
rich_text_list: "List",
|
|
@@ -5413,15 +6018,34 @@ function InlineFields({
|
|
|
5413
6018
|
}
|
|
5414
6019
|
if (kind === "emoji") {
|
|
5415
6020
|
const emoji = element;
|
|
5416
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
5417
|
-
|
|
6021
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
6022
|
+
EditorField,
|
|
5418
6023
|
{
|
|
5419
|
-
|
|
5420
|
-
|
|
5421
|
-
|
|
5422
|
-
|
|
6024
|
+
label: "Name",
|
|
6025
|
+
help: "Pick from the emoji list, or type a shortcode (without colons).",
|
|
6026
|
+
htmlFor: `${idPrefix}-emoji`,
|
|
6027
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5", children: [
|
|
6028
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
6029
|
+
Input,
|
|
6030
|
+
{
|
|
6031
|
+
id: `${idPrefix}-emoji`,
|
|
6032
|
+
value: emoji.name ?? "",
|
|
6033
|
+
placeholder: "e.g. wave",
|
|
6034
|
+
onChange: (e) => onChange({ ...emoji, name: e.target.value })
|
|
6035
|
+
}
|
|
6036
|
+
),
|
|
6037
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
6038
|
+
EmojiPickerButton,
|
|
6039
|
+
{
|
|
6040
|
+
align: "end",
|
|
6041
|
+
label: "Pick emoji",
|
|
6042
|
+
className: "shrink-0 border",
|
|
6043
|
+
onSelect: (sel) => onChange({ ...emoji, name: sel.name, skin_tone: sel.skinTone ?? void 0, unicode: void 0 })
|
|
6044
|
+
}
|
|
6045
|
+
)
|
|
6046
|
+
] })
|
|
5423
6047
|
}
|
|
5424
|
-
)
|
|
6048
|
+
);
|
|
5425
6049
|
}
|
|
5426
6050
|
if (kind === "user") {
|
|
5427
6051
|
const user = element;
|
|
@@ -5630,8 +6254,59 @@ function LossyBanner({ reasons }) {
|
|
|
5630
6254
|
] })
|
|
5631
6255
|
] });
|
|
5632
6256
|
}
|
|
6257
|
+
var RICH_TEXT_INPUT_RULE_ALLOWLIST = ["bold", "italic", "strike", "code"];
|
|
6258
|
+
function buildRichTextExtensions() {
|
|
6259
|
+
return [
|
|
6260
|
+
StarterKit__default.default.configure({
|
|
6261
|
+
heading: false,
|
|
6262
|
+
horizontalRule: false,
|
|
6263
|
+
// StarterKit bundles its own Link extension (enabled by default),
|
|
6264
|
+
// which would register a second, unconfigured `link` mark and a
|
|
6265
|
+
// duplicate autolinker alongside the hardened one we add below.
|
|
6266
|
+
// Disable it so our single, security-configured Link is the only
|
|
6267
|
+
// link extension in the schema.
|
|
6268
|
+
link: false
|
|
6269
|
+
}),
|
|
6270
|
+
EmojiNode,
|
|
6271
|
+
Link__default.default.configure({
|
|
6272
|
+
openOnClick: false,
|
|
6273
|
+
autolink: true,
|
|
6274
|
+
defaultProtocol: "https",
|
|
6275
|
+
// Pin TipTap's link allowlist to the same set as our shared
|
|
6276
|
+
// `isSafeHref` helper. The upstream default also includes
|
|
6277
|
+
// ftp/cid/callto which we don't expect inside Slack content.
|
|
6278
|
+
protocols: ["http", "https", "mailto", "tel", "sms", "xmpp"],
|
|
6279
|
+
// Belt-and-suspenders: even if a downstream contributor widens
|
|
6280
|
+
// `protocols` later, our isSafeHref guard rejects everything
|
|
6281
|
+
// outside the safe-link set. setLink and toggleLink both gate
|
|
6282
|
+
// on this hook before applying the mark.
|
|
6283
|
+
isAllowedUri: (url) => isSafeHref(url),
|
|
6284
|
+
// Only auto-link tokens that carry an explicit, safe scheme. TipTap's
|
|
6285
|
+
// default would link any `host.tld` (e.g. `2.xyz`, `report.zip`,
|
|
6286
|
+
// `logo.png`) because the suffix is a real gTLD, which is wrong for
|
|
6287
|
+
// free-form Slack message text. Deliberate links can still be added
|
|
6288
|
+
// via the link button.
|
|
6289
|
+
shouldAutoLink: hasExplicitSafeScheme,
|
|
6290
|
+
HTMLAttributes: { rel: "noreferrer noopener", target: "_blank" }
|
|
6291
|
+
})
|
|
6292
|
+
];
|
|
6293
|
+
}
|
|
5633
6294
|
function RichTextWysiwygEditor({ block, onChange }) {
|
|
5634
6295
|
const lastEmittedRef = React.useRef(null);
|
|
6296
|
+
const { byName: customByName } = useCustomEmojis();
|
|
6297
|
+
const [emojiIndex, setEmojiIndex] = React.useState(null);
|
|
6298
|
+
React.useEffect(() => {
|
|
6299
|
+
let active = true;
|
|
6300
|
+
loadEmojiIndex().then((idx) => active && setEmojiIndex(idx)).catch(() => {
|
|
6301
|
+
});
|
|
6302
|
+
return () => {
|
|
6303
|
+
active = false;
|
|
6304
|
+
};
|
|
6305
|
+
}, []);
|
|
6306
|
+
const resolveEmoji = React.useMemo(
|
|
6307
|
+
() => makeEmojiImportResolver(customByName, emojiIndex),
|
|
6308
|
+
[customByName, emojiIndex]
|
|
6309
|
+
);
|
|
5635
6310
|
const handleUpdate = React.useCallback(
|
|
5636
6311
|
(editor2) => {
|
|
5637
6312
|
const next = proseMirrorToRichText(editor2.getJSON());
|
|
@@ -5641,28 +6316,9 @@ function RichTextWysiwygEditor({ block, onChange }) {
|
|
|
5641
6316
|
[onChange]
|
|
5642
6317
|
);
|
|
5643
6318
|
const editor = react.useEditor({
|
|
5644
|
-
extensions:
|
|
5645
|
-
|
|
5646
|
-
|
|
5647
|
-
horizontalRule: false
|
|
5648
|
-
}),
|
|
5649
|
-
Link__default.default.configure({
|
|
5650
|
-
openOnClick: false,
|
|
5651
|
-
autolink: true,
|
|
5652
|
-
defaultProtocol: "https",
|
|
5653
|
-
// Pin TipTap's link allowlist to the same set as our shared
|
|
5654
|
-
// `isSafeHref` helper. The upstream default also includes
|
|
5655
|
-
// ftp/cid/callto which we don't expect inside Slack content.
|
|
5656
|
-
protocols: ["http", "https", "mailto", "tel", "sms", "xmpp"],
|
|
5657
|
-
// Belt-and-suspenders: even if a downstream contributor widens
|
|
5658
|
-
// `protocols` later, our isSafeHref guard rejects everything
|
|
5659
|
-
// outside the safe-link set. setLink and toggleLink both gate
|
|
5660
|
-
// on this hook before applying the mark.
|
|
5661
|
-
isAllowedUri: (url) => isSafeHref(url),
|
|
5662
|
-
HTMLAttributes: { rel: "noreferrer noopener", target: "_blank" }
|
|
5663
|
-
})
|
|
5664
|
-
],
|
|
5665
|
-
content: richTextToProseMirror(block),
|
|
6319
|
+
extensions: buildRichTextExtensions(),
|
|
6320
|
+
enableInputRules: RICH_TEXT_INPUT_RULE_ALLOWLIST,
|
|
6321
|
+
content: richTextToProseMirror(block, resolveEmoji),
|
|
5666
6322
|
editorProps: {
|
|
5667
6323
|
attributes: {
|
|
5668
6324
|
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 +6333,10 @@ function RichTextWysiwygEditor({ block, onChange }) {
|
|
|
5677
6333
|
if (lastEmittedRef.current && JSON.stringify(lastEmittedRef.current) === JSON.stringify(block)) {
|
|
5678
6334
|
return;
|
|
5679
6335
|
}
|
|
5680
|
-
editor.commands.setContent(richTextToProseMirror(block), {
|
|
6336
|
+
editor.commands.setContent(richTextToProseMirror(block, resolveEmoji), {
|
|
5681
6337
|
emitUpdate: false
|
|
5682
6338
|
});
|
|
5683
|
-
}, [block, editor]);
|
|
6339
|
+
}, [block, editor, resolveEmoji]);
|
|
5684
6340
|
if (!editor) {
|
|
5685
6341
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-h-[120px] rounded-md border border-input bg-background" });
|
|
5686
6342
|
}
|
|
@@ -5729,6 +6385,16 @@ function Toolbar({ editor }) {
|
|
|
5729
6385
|
),
|
|
5730
6386
|
/* @__PURE__ */ jsxRuntime.jsx(Divider, {}),
|
|
5731
6387
|
/* @__PURE__ */ jsxRuntime.jsx(LinkPopover, { editor }),
|
|
6388
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
6389
|
+
EmojiPickerButton,
|
|
6390
|
+
{
|
|
6391
|
+
align: "start",
|
|
6392
|
+
onSelect: (sel) => editor.chain().focus().insertContent({
|
|
6393
|
+
type: "emoji",
|
|
6394
|
+
attrs: { name: sel.name, src: sel.src, unicode: sel.unicode, skinTone: sel.skinTone }
|
|
6395
|
+
}).run()
|
|
6396
|
+
}
|
|
6397
|
+
),
|
|
5732
6398
|
/* @__PURE__ */ jsxRuntime.jsx(Divider, {}),
|
|
5733
6399
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5734
6400
|
ToolbarButton,
|
|
@@ -5927,6 +6593,8 @@ function defaultImage() {
|
|
|
5927
6593
|
function SectionEditor({ block, onChange }) {
|
|
5928
6594
|
const text = block.text?.text ?? "";
|
|
5929
6595
|
const accessoryKind = detectAccessory(block.accessory);
|
|
6596
|
+
const textRef = React.useRef(null);
|
|
6597
|
+
const setText = (next) => onChange({ ...block, text: { type: "mrkdwn", text: next } });
|
|
5930
6598
|
const setAccessoryKind = (next) => {
|
|
5931
6599
|
if (next === accessoryKind) {
|
|
5932
6600
|
return;
|
|
@@ -5950,19 +6618,20 @@ function SectionEditor({ block, onChange }) {
|
|
|
5950
6618
|
label: "Text",
|
|
5951
6619
|
help: "Supports *bold*, _italic_, ~strike~, `code`, and <url|link> formatting.",
|
|
5952
6620
|
htmlFor: "section-text",
|
|
5953
|
-
children: /* @__PURE__ */ jsxRuntime.
|
|
5954
|
-
|
|
5955
|
-
|
|
5956
|
-
|
|
5957
|
-
|
|
5958
|
-
|
|
5959
|
-
|
|
5960
|
-
|
|
5961
|
-
|
|
5962
|
-
|
|
5963
|
-
}
|
|
5964
|
-
|
|
5965
|
-
|
|
6621
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start gap-1.5", children: [
|
|
6622
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
6623
|
+
Textarea,
|
|
6624
|
+
{
|
|
6625
|
+
ref: textRef,
|
|
6626
|
+
id: "section-text",
|
|
6627
|
+
value: text,
|
|
6628
|
+
rows: 4,
|
|
6629
|
+
placeholder: "e.g. Welcome to the channel! Ping <@U123> with any questions.",
|
|
6630
|
+
onChange: (e) => setText(e.target.value)
|
|
6631
|
+
}
|
|
6632
|
+
),
|
|
6633
|
+
/* @__PURE__ */ jsxRuntime.jsx(EmojiTextInsertButton, { targetRef: textRef, value: text, onChange: setText, className: "mt-1 shrink-0 border" })
|
|
6634
|
+
] })
|
|
5966
6635
|
}
|
|
5967
6636
|
),
|
|
5968
6637
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-3 rounded-md border bg-muted/20 p-3", children: [
|
|
@@ -6003,19 +6672,22 @@ function ButtonAccessoryFields({ button, onChange }) {
|
|
|
6003
6672
|
const url = button.url ?? "";
|
|
6004
6673
|
const value = button.value ?? "";
|
|
6005
6674
|
const style = button.style === "primary" || button.style === "danger" ? button.style : "default";
|
|
6675
|
+
const labelRef = React.useRef(null);
|
|
6676
|
+
const setLabel = (next) => onChange({ ...button, text: { type: "plain_text", text: next, emoji: true } });
|
|
6006
6677
|
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
|
-
|
|
6678
|
+
/* @__PURE__ */ jsxRuntime.jsx(EditorField, { label: "Label", htmlFor: "section-acc-btn-label", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5", children: [
|
|
6679
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
6680
|
+
Input,
|
|
6681
|
+
{
|
|
6682
|
+
ref: labelRef,
|
|
6683
|
+
id: "section-acc-btn-label",
|
|
6684
|
+
value: label,
|
|
6685
|
+
placeholder: "e.g. Learn more",
|
|
6686
|
+
onChange: (e) => setLabel(e.target.value)
|
|
6687
|
+
}
|
|
6688
|
+
),
|
|
6689
|
+
/* @__PURE__ */ jsxRuntime.jsx(EmojiTextInsertButton, { targetRef: labelRef, value: label, onChange: setLabel, className: "shrink-0 border" })
|
|
6690
|
+
] }) }),
|
|
6019
6691
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
6020
6692
|
EditorField,
|
|
6021
6693
|
{
|
|
@@ -7570,6 +8242,7 @@ function BlockKitchen(props) {
|
|
|
7570
8242
|
initialBlocks,
|
|
7571
8243
|
onChange,
|
|
7572
8244
|
previewHooks,
|
|
8245
|
+
customEmojis,
|
|
7573
8246
|
loadChannels,
|
|
7574
8247
|
loadSendAsUserStatus,
|
|
7575
8248
|
onSend,
|
|
@@ -7613,6 +8286,13 @@ function BlockKitchen(props) {
|
|
|
7613
8286
|
const [previewSurface, setPreviewSurface] = React.useState(allowedSurfaces[0]);
|
|
7614
8287
|
const [activePaletteVariantId, setActivePaletteVariantId] = React.useState(null);
|
|
7615
8288
|
const isMobile = useIsMobile();
|
|
8289
|
+
const mergedPreviewHooks = React.useMemo(() => {
|
|
8290
|
+
if (!customEmojis || customEmojis.length === 0) {
|
|
8291
|
+
return previewHooks;
|
|
8292
|
+
}
|
|
8293
|
+
const byName = new Map(customEmojis.map((emoji) => [emoji.name, emoji]));
|
|
8294
|
+
return { emoji: makeEmojiHook(byName), ...previewHooks };
|
|
8295
|
+
}, [customEmojis, previewHooks]);
|
|
7616
8296
|
const validation = useBlockKitValidation(blocks, "message");
|
|
7617
8297
|
const sensors = core.useSensors(
|
|
7618
8298
|
core.useSensor(core.PointerSensor, { activationConstraint: { distance: 4 } }),
|
|
@@ -7662,7 +8342,7 @@ function BlockKitchen(props) {
|
|
|
7662
8342
|
}, []);
|
|
7663
8343
|
const activePaletteVariant = activePaletteVariantId ? variantById.get(activePaletteVariantId) : null;
|
|
7664
8344
|
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(
|
|
8345
|
+
return /* @__PURE__ */ jsxRuntime.jsx(BrandThemeScope, { theme, children: /* @__PURE__ */ jsxRuntime.jsx(CustomEmojiProvider, { customEmojis, children: /* @__PURE__ */ jsxRuntime.jsx(TooltipProvider, { delayDuration: 200, children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
7666
8346
|
core.DndContext,
|
|
7667
8347
|
{
|
|
7668
8348
|
sensors,
|
|
@@ -7708,7 +8388,7 @@ function BlockKitchen(props) {
|
|
|
7708
8388
|
{
|
|
7709
8389
|
blocks,
|
|
7710
8390
|
workspaceName,
|
|
7711
|
-
previewHooks,
|
|
8391
|
+
previewHooks: mergedPreviewHooks,
|
|
7712
8392
|
previewTheme,
|
|
7713
8393
|
previewSurface,
|
|
7714
8394
|
errorsByBlockId: validation.byBlockId,
|
|
@@ -7784,7 +8464,7 @@ function BlockKitchen(props) {
|
|
|
7784
8464
|
)
|
|
7785
8465
|
]
|
|
7786
8466
|
}
|
|
7787
|
-
) }) });
|
|
8467
|
+
) }) }) });
|
|
7788
8468
|
}
|
|
7789
8469
|
var UNCATEGORIZED = "Other";
|
|
7790
8470
|
function TemplateThumbnail({ blocks, theme }) {
|