@tightknitai/block-kitchen 0.10.3 → 0.10.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/dist/index.cjs CHANGED
@@ -14,14 +14,14 @@ var nanoid = require('nanoid');
14
14
  var reactSlot = require('@radix-ui/react-slot');
15
15
  var classVarianceAuthority = require('class-variance-authority');
16
16
  var LabelPrimitive = require('@radix-ui/react-label');
17
+ require('slack-blocks-to-jsx/dist/style.css');
18
+ var slackBlocksToJsx = require('slack-blocks-to-jsx');
17
19
  var utilities = require('@dnd-kit/utilities');
18
20
  var PopoverPrimitive = require('@radix-ui/react-popover');
19
21
  var RadioGroupPrimitive = require('@radix-ui/react-radio-group');
20
22
  var Link = require('@tiptap/extension-link');
21
23
  var react = require('@tiptap/react');
22
24
  var StarterKit = require('@tiptap/starter-kit');
23
- require('slack-blocks-to-jsx/dist/style.css');
24
- var slackBlocksToJsx = require('slack-blocks-to-jsx');
25
25
 
26
26
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
27
27
 
@@ -3054,6 +3054,128 @@ var Label = React__namespace.forwardRef(({ className, ...props }, ref) => /* @__
3054
3054
  }
3055
3055
  ));
3056
3056
  Label.displayName = LabelPrimitive__namespace.Root.displayName;
3057
+ function SlackBlockPreview({
3058
+ block,
3059
+ hooks,
3060
+ theme = "light"
3061
+ }) {
3062
+ const rootRef = React.useRef(null);
3063
+ const safeBlock = React.useMemo(() => sanitizeBlock(block), [block]);
3064
+ React.useEffect(() => {
3065
+ const root = rootRef.current;
3066
+ if (!root) return;
3067
+ for (const wrapper of root.querySelectorAll(
3068
+ ".slack_blocks_to_jsx__image_media_trigger, .slack_blocks_to_jsx__video_title"
3069
+ )) {
3070
+ for (const btn of wrapper.querySelectorAll("button:not([aria-label])")) {
3071
+ const title = wrapper.querySelector(".slack_blocks_to_jsx__image_title")?.textContent?.trim();
3072
+ btn.setAttribute("aria-label", title ? `Toggle ${title}` : "Toggle media");
3073
+ }
3074
+ }
3075
+ for (const a of root.querySelectorAll("a[href]")) {
3076
+ const href = a.getAttribute("href");
3077
+ if (!isSafeHref(href)) {
3078
+ a.setAttribute("href", "#");
3079
+ a.setAttribute("data-bk-blocked-href", "1");
3080
+ }
3081
+ }
3082
+ for (const img of root.querySelectorAll("img[src]")) {
3083
+ const src = img.getAttribute("src");
3084
+ if (!isSafeImageSrc(src)) {
3085
+ img.removeAttribute("src");
3086
+ img.setAttribute("data-bk-blocked-src", "1");
3087
+ }
3088
+ }
3089
+ });
3090
+ return (
3091
+ // The library scopes all of its CSS under `#slack_blocks_to_jsx` and
3092
+ // depends on the `slack_blocks_to_jsx styles_enabled` classes plus the
3093
+ // `data-theme` attribute for its button / mention / divider styling
3094
+ // (e.g. button reset + `bg-green-primary` for primary). `<Message
3095
+ // withoutWrapper>` skips that wrapper entirely, so we re-create it
3096
+ // here without the avatar / timestamp chrome.
3097
+ /* @__PURE__ */ jsxRuntime.jsx("div", { ref: rootRef, id: "slack_blocks_to_jsx", "data-theme": theme, className: "slack_blocks_to_jsx styles_enabled", children: /* @__PURE__ */ jsxRuntime.jsx(
3098
+ slackBlocksToJsx.Message,
3099
+ {
3100
+ time: /* @__PURE__ */ new Date(),
3101
+ name: "",
3102
+ logo: "",
3103
+ withoutWrapper: true,
3104
+ theme,
3105
+ blocks: [safeBlock],
3106
+ hooks
3107
+ }
3108
+ ) })
3109
+ );
3110
+ }
3111
+ var PLACEHOLDER_TIME = "10:37 AM";
3112
+ function SlackMessageFrame({
3113
+ workspaceName,
3114
+ authorName,
3115
+ authorIcon,
3116
+ time = PLACEHOLDER_TIME,
3117
+ isDark,
3118
+ children
3119
+ }) {
3120
+ const displayName = authorName ?? workspaceName ?? "Your app";
3121
+ const initial = displayName.slice(0, 1).toUpperCase();
3122
+ const safeIcon = authorIcon && isSafeImageSrc(authorIcon) ? authorIcon : null;
3123
+ return /* @__PURE__ */ jsxRuntime.jsxs(
3124
+ "div",
3125
+ {
3126
+ className: cn(
3127
+ "flex flex-col overflow-hidden rounded-md border",
3128
+ isDark ? "border-[#2c2d30] bg-[#1a1d21]" : "border-[#e8e8e8] bg-white"
3129
+ ),
3130
+ children: [
3131
+ /* @__PURE__ */ jsxRuntime.jsxs(
3132
+ "div",
3133
+ {
3134
+ className: cn("flex items-center gap-2 px-5 pt-3 pb-1 text-xs", isDark ? "text-white/60" : "text-[#616061]"),
3135
+ children: [
3136
+ safeIcon ? /* @__PURE__ */ jsxRuntime.jsx("img", { src: safeIcon, alt: "", className: "h-7 w-7 shrink-0 rounded object-cover" }) : /* @__PURE__ */ jsxRuntime.jsx(
3137
+ "span",
3138
+ {
3139
+ className: cn(
3140
+ "inline-flex h-7 w-7 items-center justify-center rounded text-[12px] font-semibold",
3141
+ isDark ? "bg-white/10 text-white" : "bg-[#4a154b]/10 text-[#1d1c1d]"
3142
+ ),
3143
+ children: initial
3144
+ }
3145
+ ),
3146
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("font-bold text-sm", isDark ? "text-white" : "text-[#1d1c1d]"), children: displayName }),
3147
+ time ? /* @__PURE__ */ jsxRuntime.jsx("span", { children: time }) : null
3148
+ ]
3149
+ }
3150
+ ),
3151
+ children
3152
+ ]
3153
+ }
3154
+ );
3155
+ }
3156
+ function SlackMessagePreview({
3157
+ blocks,
3158
+ hooks,
3159
+ theme = "light",
3160
+ workspaceName,
3161
+ authorName,
3162
+ authorIcon,
3163
+ time,
3164
+ emptyLabel = "This message has no blocks to preview."
3165
+ }) {
3166
+ const isDark = theme === "dark";
3167
+ return /* @__PURE__ */ jsxRuntime.jsx(
3168
+ SlackMessageFrame,
3169
+ {
3170
+ workspaceName,
3171
+ authorName,
3172
+ authorIcon,
3173
+ time,
3174
+ isDark,
3175
+ children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("flex min-w-0 flex-col gap-1 px-5 pb-3", isDark ? "bg-[#1a1d21]" : "bg-white"), children: blocks.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: cn("py-1 text-sm", isDark ? "text-white/60" : "text-[#616061]"), children: emptyLabel }) : blocks.map((block, index) => /* @__PURE__ */ jsxRuntime.jsx(SlackBlockPreview, { block, hooks, theme }, `${block.type}-${index}`)) })
3176
+ }
3177
+ );
3178
+ }
3057
3179
  var POLL_INTERVAL_MS = 2500;
3058
3180
  var MAX_POLLS = 24;
3059
3181
  function useSlackSignIn(loadStatus, { open, enabled }) {
@@ -3127,6 +3249,12 @@ function SlackSignInButton({ onClick, polling }) {
3127
3249
  /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ExternalLink, {})
3128
3250
  ] }) });
3129
3251
  }
3252
+ var LINK_DEBOUNCE_MS = 400;
3253
+ var LINK_HELP_TEXT = 'Click the \u2807menu while hovering over the message in Slack (or right-click), and select "Copy Link".';
3254
+ var TABS = [
3255
+ { id: "recent", label: "Pick from Recent" },
3256
+ { id: "link", label: "Direct Link" }
3257
+ ];
3130
3258
  function recentToResult(msg) {
3131
3259
  return {
3132
3260
  ok: true,
@@ -3151,6 +3279,16 @@ function previewText(m) {
3151
3279
  }
3152
3280
  return `${m.blocks.length} block${m.blocks.length === 1 ? "" : "s"}`;
3153
3281
  }
3282
+ function formatTs(ts) {
3283
+ const seconds = Number(ts);
3284
+ if (!ts || !Number.isFinite(seconds) || seconds <= 0) {
3285
+ return "";
3286
+ }
3287
+ return new Date(seconds * 1e3).toLocaleTimeString(void 0, { hour: "numeric", minute: "2-digit" });
3288
+ }
3289
+ function editableLabel(editableVia) {
3290
+ return editableVia === "user" ? "You" : "Bot";
3291
+ }
3154
3292
  function LoadMessageDialog({
3155
3293
  open,
3156
3294
  onOpenChange,
@@ -3158,10 +3296,14 @@ function LoadMessageDialog({
3158
3296
  loadRecentMessages,
3159
3297
  loadChannels,
3160
3298
  onLoaded,
3161
- onOpenAsNew
3299
+ onOpenAsNew,
3300
+ previewHooks,
3301
+ previewTheme = "light"
3162
3302
  }) {
3163
3303
  const [link, setLink] = React.useState("");
3164
- const [status, setStatus] = React.useState({ kind: "idle" });
3304
+ const [linkStatus, setLinkStatus] = React.useState({ kind: "idle" });
3305
+ const [emptyLinkError, setEmptyLinkError] = React.useState(null);
3306
+ const [committing, setCommitting] = React.useState(false);
3165
3307
  const [channels, setChannels] = React.useState(null);
3166
3308
  const [channelsError, setChannelsError] = React.useState(null);
3167
3309
  const [channelId, setChannelId] = React.useState("");
@@ -3169,8 +3311,14 @@ function LoadMessageDialog({
3169
3311
  const [recentError, setRecentError] = React.useState(null);
3170
3312
  const [selectedRecent, setSelectedRecent] = React.useState(null);
3171
3313
  const [signInPolling, setSignInPolling] = React.useState(false);
3314
+ const [tab, setTab] = React.useState("recent");
3172
3315
  const inputRef = React.useRef(null);
3173
3316
  const signInPollRef = React.useRef(null);
3317
+ const resolvedLinkRef = React.useRef(null);
3318
+ const linkRequestRef = React.useRef(0);
3319
+ const domId = React.useId();
3320
+ const tabDomId = (id) => `${domId}-${id}-tab`;
3321
+ const panelDomId = (id) => `${domId}-${id}-panel`;
3174
3322
  const onLoadMessageRef = React.useRef(onLoadMessage);
3175
3323
  const loadRecentMessagesRef = React.useRef(loadRecentMessages);
3176
3324
  const loadChannelsRef = React.useRef(loadChannels);
@@ -3180,16 +3328,22 @@ function LoadMessageDialog({
3180
3328
  loadChannelsRef.current = loadChannels;
3181
3329
  });
3182
3330
  const hasRecent = !!loadRecentMessages && !!loadChannels;
3331
+ const activeTab = hasRecent ? tab : "link";
3183
3332
  React.useEffect(() => {
3184
3333
  if (!open) {
3185
3334
  return;
3186
3335
  }
3187
3336
  setLink("");
3188
- setStatus({ kind: "idle" });
3337
+ setLinkStatus({ kind: "idle" });
3338
+ setEmptyLinkError(null);
3339
+ setCommitting(false);
3189
3340
  setChannelId("");
3190
3341
  setRecent(null);
3191
3342
  setRecentError(null);
3192
3343
  setSelectedRecent(null);
3344
+ setTab("recent");
3345
+ resolvedLinkRef.current = null;
3346
+ linkRequestRef.current += 1;
3193
3347
  }, [open]);
3194
3348
  React.useEffect(() => {
3195
3349
  const load = loadChannelsRef.current;
@@ -3248,27 +3402,71 @@ function LoadMessageDialog({
3248
3402
  }
3249
3403
  return stopSignInPoll;
3250
3404
  }, [open, stopSignInPoll]);
3405
+ const resolveLink = React.useCallback(async (trimmed) => {
3406
+ const requestId = ++linkRequestRef.current;
3407
+ setLinkStatus({ kind: "loading" });
3408
+ try {
3409
+ const result = await onLoadMessageRef.current({ link: trimmed });
3410
+ if (linkRequestRef.current !== requestId) {
3411
+ return null;
3412
+ }
3413
+ resolvedLinkRef.current = trimmed;
3414
+ setLinkStatus(
3415
+ result.ok ? { kind: "ready", result } : { kind: "not-editable", reason: result.reason, blocks: result.blocks, oauthUrl: result.oauthUrl }
3416
+ );
3417
+ return result;
3418
+ } catch (e) {
3419
+ if (linkRequestRef.current !== requestId) {
3420
+ return null;
3421
+ }
3422
+ resolvedLinkRef.current = trimmed;
3423
+ setLinkStatus({ kind: "error", error: e instanceof Error ? e.message : "Failed to load message." });
3424
+ return null;
3425
+ }
3426
+ }, []);
3427
+ React.useEffect(() => {
3428
+ if (!open) {
3429
+ return;
3430
+ }
3431
+ const trimmed = link.trim();
3432
+ if (!trimmed) {
3433
+ linkRequestRef.current += 1;
3434
+ resolvedLinkRef.current = null;
3435
+ setLinkStatus({ kind: "idle" });
3436
+ return;
3437
+ }
3438
+ if (resolvedLinkRef.current === trimmed) {
3439
+ return;
3440
+ }
3441
+ const timer = setTimeout(() => {
3442
+ if (resolvedLinkRef.current !== trimmed) {
3443
+ void resolveLink(trimmed);
3444
+ }
3445
+ }, LINK_DEBOUNCE_MS);
3446
+ return () => clearTimeout(timer);
3447
+ }, [open, link, resolveLink]);
3251
3448
  const handleLoad = async () => {
3252
3449
  stopSignInPoll();
3253
- if (selectedRecent) {
3254
- onLoaded(recentToResult(selectedRecent));
3450
+ if (activeTab === "recent") {
3451
+ if (selectedRecent) {
3452
+ onLoaded(recentToResult(selectedRecent));
3453
+ }
3255
3454
  return;
3256
3455
  }
3257
3456
  const trimmed = link.trim();
3258
3457
  if (!trimmed) {
3259
- setStatus({ kind: "error", error: "Paste a Slack message link first." });
3458
+ setEmptyLinkError("Paste a Slack message link first.");
3260
3459
  return;
3261
3460
  }
3262
- setStatus({ kind: "loading" });
3263
- try {
3264
- const result = await onLoadMessageRef.current({ link: trimmed });
3265
- if (result.ok) {
3266
- onLoaded(result);
3267
- return;
3268
- }
3269
- setStatus({ kind: "not-editable", reason: result.reason, blocks: result.blocks, oauthUrl: result.oauthUrl });
3270
- } catch (e) {
3271
- setStatus({ kind: "error", error: e instanceof Error ? e.message : "Failed to load message." });
3461
+ if (linkStatus.kind === "ready" && resolvedLinkRef.current === trimmed) {
3462
+ onLoaded(linkStatus.result);
3463
+ return;
3464
+ }
3465
+ setCommitting(true);
3466
+ const result = await resolveLink(trimmed);
3467
+ setCommitting(false);
3468
+ if (result?.ok) {
3469
+ onLoaded(result);
3272
3470
  }
3273
3471
  };
3274
3472
  const startFindSignIn = (oauthUrl) => {
@@ -3300,191 +3498,288 @@ function LoadMessageDialog({
3300
3498
  });
3301
3499
  }, POLL_INTERVAL_MS2);
3302
3500
  };
3303
- const notEditable = status.kind === "not-editable" ? status : null;
3304
- const signInUrl = notEditable?.oauthUrl && isSafeHref(notEditable.oauthUrl) ? notEditable.oauthUrl : null;
3501
+ const preview = activeTab === "recent" ? selectedRecent ? { kind: "ready", target: { ...selectedRecent, editableVia: selectedRecent.editableVia ?? "bot" } } : { kind: "empty" } : linkStatus.kind === "idle" ? { kind: "empty" } : linkStatus.kind === "ready" ? { kind: "ready", target: linkStatus.result } : linkStatus;
3502
+ const canLoad = activeTab === "recent" ? !!selectedRecent : !!link.trim();
3503
+ const panelProps = (id) => hasRecent ? { role: "tabpanel", id: panelDomId(id), "aria-labelledby": tabDomId(id) } : {};
3305
3504
  return /* @__PURE__ */ jsxRuntime.jsx(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxRuntime.jsxs(
3306
3505
  DialogContent,
3307
3506
  {
3308
- className: "w-[calc(100vw-1.5rem)] max-w-lg rounded-lg",
3507
+ className: "flex max-h-[85svh] w-[calc(100vw-1.5rem)] flex-col rounded-lg lg:max-w-4xl",
3309
3508
  onOpenAutoFocus: (e) => {
3509
+ if (hasRecent) {
3510
+ return;
3511
+ }
3310
3512
  e.preventDefault();
3311
3513
  inputRef.current?.focus();
3312
3514
  },
3313
3515
  children: [
3314
- /* @__PURE__ */ jsxRuntime.jsxs(DialogHeader, { children: [
3516
+ /* @__PURE__ */ jsxRuntime.jsxs(DialogHeader, { className: "shrink-0", children: [
3315
3517
  /* @__PURE__ */ jsxRuntime.jsx(DialogTitle, { children: "Find an existing message" }),
3316
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start gap-1.5 text-left", children: [
3317
- /* @__PURE__ */ jsxRuntime.jsx(DialogDescription, { children: hasRecent ? "Paste a Slack message link, or pick a recent message your app posted." : `Paste a Slack message link (Slack's "Copy link") to load its blocks for editing.` }),
3318
- /* @__PURE__ */ jsxRuntime.jsxs(Tooltip, { children: [
3319
- /* @__PURE__ */ jsxRuntime.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
3320
- "button",
3321
- {
3322
- type: "button",
3323
- "aria-label": "How to copy a message link",
3324
- className: "mt-0.5 shrink-0 rounded text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
3325
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Info, { className: "h-3.5 w-3.5" })
3326
- }
3327
- ) }),
3328
- /* @__PURE__ */ jsxRuntime.jsxs(TooltipContent, { className: "max-w-xs leading-relaxed", children: [
3329
- "In Slack, hover over the message and click the ",
3330
- /* @__PURE__ */ jsxRuntime.jsx("strong", { children: "\u22EE More actions" }),
3331
- " button (or right-click the message), then choose ",
3332
- /* @__PURE__ */ jsxRuntime.jsx("strong", { children: "Copy link" }),
3333
- "."
3334
- ] })
3335
- ] })
3336
- ] })
3518
+ /* @__PURE__ */ jsxRuntime.jsx(DialogDescription, { children: hasRecent ? "Pick a recent message your app posted, or paste a Slack message link. Preview it before loading." : `Paste a Slack message link (Slack's "Copy link") to preview it and load its blocks for editing.` })
3337
3519
  ] }),
3338
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 flex-col gap-4", children: [
3339
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1.5", children: [
3340
- /* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: "message-link", children: "Message link" }),
3341
- /* @__PURE__ */ jsxRuntime.jsx(
3342
- Input,
3520
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "-mx-1 flex min-h-0 min-w-0 flex-1 flex-col gap-4 overflow-y-auto px-1 lg:flex-row lg:gap-5 lg:overflow-hidden", children: [
3521
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 shrink-0 flex-col gap-3 lg:min-h-0 lg:w-[19rem]", children: [
3522
+ hasRecent && /* @__PURE__ */ jsxRuntime.jsx(TabStrip, { activeTab, onSelect: setTab, tabDomId, panelDomId }),
3523
+ activeTab === "recent" ? /* @__PURE__ */ jsxRuntime.jsxs(
3524
+ "div",
3343
3525
  {
3344
- ref: inputRef,
3345
- id: "message-link",
3346
- value: link,
3347
- onChange: (e) => {
3348
- setLink(e.target.value);
3349
- setSelectedRecent(null);
3350
- stopSignInPoll();
3351
- if (status.kind !== "idle" && status.kind !== "loading") {
3352
- setStatus({ kind: "idle" });
3353
- }
3354
- },
3355
- onKeyDown: (e) => {
3356
- if (e.key === "Enter" && status.kind !== "loading") {
3357
- e.preventDefault();
3358
- handleLoad();
3359
- }
3360
- },
3361
- placeholder: "https://your-workspace.slack.com/archives/C\u2026/p\u2026",
3362
- autoComplete: "off",
3363
- spellCheck: false
3526
+ ...panelProps("recent"),
3527
+ className: "-mx-1 flex min-w-0 flex-col gap-2 px-1 lg:min-h-0 lg:flex-1 lg:overflow-hidden",
3528
+ children: [
3529
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex shrink-0 flex-col gap-1.5", children: [
3530
+ /* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: "recent-channel-picker", children: "Channel" }),
3531
+ channels === null && !channelsError && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: "Loading channels\u2026" }),
3532
+ channelsError && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-destructive", children: channelsError }),
3533
+ channels && channels.length === 0 && !channelsError && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: "No public channels available." }),
3534
+ channels && channels.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(
3535
+ "select",
3536
+ {
3537
+ id: "recent-channel-picker",
3538
+ value: channelId,
3539
+ onChange: (e) => setChannelId(e.target.value),
3540
+ className: "flex h-9 w-full rounded-md border border-input bg-background px-3 py-1 text-sm shadow-sm focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
3541
+ children: [
3542
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", disabled: true, children: "Select a channel\u2026" }),
3543
+ channels.map((c) => /* @__PURE__ */ jsxRuntime.jsxs("option", { value: c.id, children: [
3544
+ "#",
3545
+ c.name
3546
+ ] }, c.id))
3547
+ ]
3548
+ }
3549
+ )
3550
+ ] }),
3551
+ !channelId && channels && channels.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "shrink-0 text-xs text-muted-foreground", children: "Select a channel to see recent messages." }),
3552
+ channelId && recent === null && !recentError && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "shrink-0 text-xs text-muted-foreground", children: "Loading recent messages\u2026" }),
3553
+ channelId && recentError && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "shrink-0 text-xs text-destructive", children: recentError }),
3554
+ channelId && recent && recent.length === 0 && !recentError && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "shrink-0 text-xs text-muted-foreground", children: "No recent messages from this app in this channel." }),
3555
+ channelId && recent && recent.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex max-h-60 min-h-0 min-w-0 flex-col gap-1 overflow-y-auto lg:max-h-none lg:flex-1", children: recent.map((m) => {
3556
+ const asUser = (m.editableVia ?? "bot") === "user";
3557
+ const authorName = m.username || (asUser ? "You" : "App bot");
3558
+ const safeIcon = isSafeImageSrc(m.iconUrl) ? m.iconUrl : void 0;
3559
+ const isSelected = selectedRecent?.channelId === m.channelId && selectedRecent?.ts === m.ts;
3560
+ return /* @__PURE__ */ jsxRuntime.jsxs(
3561
+ "button",
3562
+ {
3563
+ type: "button",
3564
+ "aria-pressed": isSelected,
3565
+ onClick: () => {
3566
+ setSelectedRecent(m);
3567
+ },
3568
+ className: cn(
3569
+ // Inset rings so the scroll container's overflow clip
3570
+ // can't shave the corners off the selected outline.
3571
+ "flex min-w-0 items-start gap-2.5 rounded-md border border-input bg-background px-3 py-2 text-left hover:bg-accent focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-inset focus-visible:ring-ring",
3572
+ isSelected && "border-primary! bg-accent ring-1 ring-inset ring-primary"
3573
+ ),
3574
+ children: [
3575
+ safeIcon ? /* @__PURE__ */ jsxRuntime.jsx("img", { src: safeIcon, alt: "", className: "h-9 w-9 shrink-0 rounded-md object-cover" }) : /* @__PURE__ */ jsxRuntime.jsx(
3576
+ "span",
3577
+ {
3578
+ "aria-hidden": true,
3579
+ className: "flex h-9 w-9 shrink-0 items-center justify-center rounded-md bg-muted text-sm font-semibold text-muted-foreground",
3580
+ children: authorName.charAt(0).toUpperCase()
3581
+ }
3582
+ ),
3583
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex min-w-0 flex-col gap-0.5", children: [
3584
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex min-w-0 items-center gap-1.5", children: [
3585
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate text-sm font-semibold text-foreground", children: authorName }),
3586
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "shrink-0 rounded border px-1 py-px text-[10px] font-medium uppercase tracking-wide text-muted-foreground", children: asUser ? "You" : "Bot" })
3587
+ ] }),
3588
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs text-muted-foreground", children: [
3589
+ new Date(Number(m.ts) * 1e3).toLocaleString(),
3590
+ " ",
3591
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "font-mono", children: [
3592
+ "(",
3593
+ m.ts,
3594
+ ")"
3595
+ ] })
3596
+ ] }),
3597
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "line-clamp-2 text-sm text-foreground", children: previewText(m) })
3598
+ ] })
3599
+ ]
3600
+ },
3601
+ `${m.channelId}:${m.ts}`
3602
+ );
3603
+ }) })
3604
+ ]
3605
+ }
3606
+ ) : /* @__PURE__ */ jsxRuntime.jsxs(
3607
+ "div",
3608
+ {
3609
+ ...panelProps("link"),
3610
+ className: "-mx-1 flex min-w-0 flex-col gap-2 px-1 lg:min-h-0 lg:flex-1 lg:overflow-y-auto",
3611
+ children: [
3612
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1.5", children: [
3613
+ /* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: "message-link", children: "Message link" }),
3614
+ /* @__PURE__ */ jsxRuntime.jsx(
3615
+ Input,
3616
+ {
3617
+ ref: inputRef,
3618
+ id: "message-link",
3619
+ value: link,
3620
+ onChange: (e) => {
3621
+ setLink(e.target.value);
3622
+ setEmptyLinkError(null);
3623
+ stopSignInPoll();
3624
+ },
3625
+ onKeyDown: (e) => {
3626
+ if (e.key === "Enter" && !committing) {
3627
+ e.preventDefault();
3628
+ handleLoad();
3629
+ }
3630
+ },
3631
+ placeholder: "https://your-workspace.slack.com/archives/C\u2026/p\u2026",
3632
+ autoComplete: "off",
3633
+ spellCheck: false
3634
+ }
3635
+ ),
3636
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs leading-relaxed text-muted-foreground", children: LINK_HELP_TEXT })
3637
+ ] }),
3638
+ emptyLinkError && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "rounded-md border border-destructive/30 bg-destructive/5 p-2 text-xs text-destructive", children: emptyLinkError })
3639
+ ]
3364
3640
  }
3365
3641
  )
3366
3642
  ] }),
3367
- status.kind === "error" && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "rounded-md border border-destructive/30 bg-destructive/5 p-2 text-xs text-destructive", children: status.error }),
3368
- notEditable && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2 rounded-md border border-amber-200! bg-amber-50 p-3 text-xs text-amber-800", children: [
3643
+ /* @__PURE__ */ jsxRuntime.jsx(
3644
+ PreviewPane,
3645
+ {
3646
+ state: preview,
3647
+ hooks: previewHooks,
3648
+ theme: previewTheme,
3649
+ emptyHint: activeTab === "recent" ? "Select a recent message to preview it here." : "Paste a message link to preview it here.",
3650
+ signInPolling,
3651
+ onSignIn: startFindSignIn,
3652
+ onOpenAsNew
3653
+ }
3654
+ )
3655
+ ] }),
3656
+ /* @__PURE__ */ jsxRuntime.jsxs(DialogFooter, { className: "shrink-0", children: [
3657
+ /* @__PURE__ */ jsxRuntime.jsx(Button, { type: "button", variant: "ghost", onClick: () => onOpenChange(false), children: "Cancel" }),
3658
+ /* @__PURE__ */ jsxRuntime.jsx(Button, { type: "button", onClick: handleLoad, disabled: committing || !canLoad, children: committing ? "Loading\u2026" : "Load message" })
3659
+ ] })
3660
+ ]
3661
+ }
3662
+ ) });
3663
+ }
3664
+ function TabStrip({
3665
+ activeTab,
3666
+ onSelect,
3667
+ tabDomId,
3668
+ panelDomId
3669
+ }) {
3670
+ const tabRefs = React.useRef({});
3671
+ const moveTo = (next) => {
3672
+ onSelect(next);
3673
+ tabRefs.current[next]?.focus();
3674
+ };
3675
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { role: "tablist", "aria-label": "Message source", className: "flex shrink-0 gap-1 rounded-md bg-muted p-1", children: TABS.map((entry, index) => {
3676
+ const selected = entry.id === activeTab;
3677
+ return /* @__PURE__ */ jsxRuntime.jsx(
3678
+ "button",
3679
+ {
3680
+ ref: (node) => {
3681
+ tabRefs.current[entry.id] = node;
3682
+ },
3683
+ type: "button",
3684
+ role: "tab",
3685
+ id: tabDomId(entry.id),
3686
+ "aria-selected": selected,
3687
+ "aria-controls": panelDomId(entry.id),
3688
+ tabIndex: selected ? 0 : -1,
3689
+ onClick: () => onSelect(entry.id),
3690
+ onKeyDown: (e) => {
3691
+ if (e.key === "ArrowRight" || e.key === "ArrowLeft") {
3692
+ e.preventDefault();
3693
+ const delta = e.key === "ArrowRight" ? 1 : -1;
3694
+ moveTo(TABS[(index + delta + TABS.length) % TABS.length].id);
3695
+ } else if (e.key === "Home") {
3696
+ e.preventDefault();
3697
+ moveTo(TABS[0].id);
3698
+ } else if (e.key === "End") {
3699
+ e.preventDefault();
3700
+ moveTo(TABS[TABS.length - 1].id);
3701
+ }
3702
+ },
3703
+ className: cn(
3704
+ "flex-1 rounded-sm px-3 py-1.5 text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
3705
+ selected ? "bg-background text-foreground shadow-sm" : "text-muted-foreground hover:text-foreground"
3706
+ ),
3707
+ children: entry.label
3708
+ },
3709
+ entry.id
3710
+ );
3711
+ }) });
3712
+ }
3713
+ function PreviewPane({
3714
+ state,
3715
+ hooks,
3716
+ theme,
3717
+ emptyHint,
3718
+ signInPolling,
3719
+ onSignIn,
3720
+ onOpenAsNew
3721
+ }) {
3722
+ const target = state.kind === "ready" ? state.target : null;
3723
+ const signInUrl = state.kind === "not-editable" && state.oauthUrl && isSafeHref(state.oauthUrl) ? state.oauthUrl : null;
3724
+ const verdictBlocks = state.kind === "not-editable" ? state.blocks : void 0;
3725
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 shrink-0 flex-col gap-2 lg:min-h-0 lg:flex-1", children: [
3726
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex shrink-0 flex-wrap items-center gap-2", children: [
3727
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-medium uppercase tracking-wide text-muted-foreground", children: "Preview" }),
3728
+ target?.channelName || target?.channelId ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "rounded border px-1.5 py-px text-[10px] font-medium text-muted-foreground", children: target.channelName ? `#${target.channelName}` : target.channelId }) : null,
3729
+ target?.editableVia ? /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "rounded border px-1.5 py-px text-[10px] font-medium uppercase tracking-wide text-muted-foreground", children: [
3730
+ "Editable as ",
3731
+ editableLabel(target.editableVia)
3732
+ ] }) : null
3733
+ ] }),
3734
+ /* @__PURE__ */ jsxRuntime.jsxs(
3735
+ "div",
3736
+ {
3737
+ role: "region",
3738
+ "aria-label": "Message preview",
3739
+ tabIndex: 0,
3740
+ className: "flex max-h-72 min-h-32 flex-1 flex-col gap-3 overflow-y-auto rounded-md border bg-muted/40 p-3 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring lg:max-h-none lg:min-h-0",
3741
+ children: [
3742
+ state.kind === "not-editable" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex shrink-0 flex-col gap-2 rounded-md border border-amber-200! bg-amber-50 p-3 text-xs text-amber-800", children: [
3369
3743
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start gap-2", children: [
3370
3744
  /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertTriangle, { className: "mt-0.5 h-3.5 w-3.5 shrink-0" }),
3371
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1", children: notEditable.reason })
3745
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1", children: state.reason })
3372
3746
  ] }),
3373
- signInUrl && /* @__PURE__ */ jsxRuntime.jsx(SlackSignInButton, { onClick: () => startFindSignIn(signInUrl), polling: signInPolling }),
3374
- notEditable.blocks && notEditable.blocks.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
3747
+ signInUrl && /* @__PURE__ */ jsxRuntime.jsx(SlackSignInButton, { onClick: () => onSignIn(signInUrl), polling: signInPolling }),
3748
+ verdictBlocks && verdictBlocks.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
3375
3749
  Button,
3376
3750
  {
3377
3751
  type: "button",
3378
3752
  variant: "outline",
3379
3753
  size: "sm",
3380
3754
  className: "self-start",
3381
- onClick: () => onOpenAsNew(notEditable.blocks),
3755
+ onClick: () => onOpenAsNew(verdictBlocks),
3382
3756
  children: "Open as a new message instead"
3383
3757
  }
3384
3758
  )
3385
3759
  ] }),
3386
- hasRecent && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 flex-col gap-2", children: [
3387
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 text-xs text-muted-foreground", children: [
3388
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "h-px flex-1 bg-border" }),
3389
- "or pick a recent message",
3390
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "h-px flex-1 bg-border" })
3391
- ] }),
3392
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1.5", children: [
3393
- /* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: "recent-channel-picker", children: "Channel" }),
3394
- channels === null && !channelsError && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: "Loading channels\u2026" }),
3395
- channelsError && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-destructive", children: channelsError }),
3396
- channels && channels.length === 0 && !channelsError && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: "No public channels available." }),
3397
- channels && channels.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(
3398
- "select",
3399
- {
3400
- id: "recent-channel-picker",
3401
- value: channelId,
3402
- onChange: (e) => setChannelId(e.target.value),
3403
- className: "flex h-9 w-full rounded-md border border-input bg-background px-3 py-1 text-sm shadow-sm focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
3404
- children: [
3405
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", disabled: true, children: "Select a channel\u2026" }),
3406
- channels.map((c) => /* @__PURE__ */ jsxRuntime.jsxs("option", { value: c.id, children: [
3407
- "#",
3408
- c.name
3409
- ] }, c.id))
3410
- ]
3411
- }
3412
- )
3413
- ] }),
3414
- !channelId && channels && channels.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: "Select a channel to see recent messages." }),
3415
- channelId && recent === null && !recentError && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: "Loading recent messages\u2026" }),
3416
- channelId && recentError && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-destructive", children: recentError }),
3417
- channelId && recent && recent.length === 0 && !recentError && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: "No recent messages from this app in this channel." }),
3418
- channelId && recent && recent.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex max-h-48 min-w-0 flex-col gap-1 overflow-y-auto", children: recent.map((m) => {
3419
- const asUser = (m.editableVia ?? "bot") === "user";
3420
- const authorName = m.username || (asUser ? "You" : "App bot");
3421
- const safeIcon = isSafeImageSrc(m.iconUrl) ? m.iconUrl : void 0;
3422
- const isSelected = selectedRecent?.channelId === m.channelId && selectedRecent?.ts === m.ts;
3423
- return /* @__PURE__ */ jsxRuntime.jsxs(
3424
- "button",
3425
- {
3426
- type: "button",
3427
- "aria-pressed": isSelected,
3428
- onClick: () => {
3429
- setSelectedRecent(m);
3430
- setLink("");
3431
- if (status.kind !== "idle" && status.kind !== "loading") {
3432
- setStatus({ kind: "idle" });
3433
- }
3434
- },
3435
- className: cn(
3436
- // Inset rings so the scroll container's overflow clip
3437
- // can't shave the corners off the selected outline.
3438
- "flex min-w-0 items-start gap-2.5 rounded-md border border-input bg-background px-3 py-2 text-left hover:bg-accent focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-inset focus-visible:ring-ring",
3439
- isSelected && "border-primary! bg-accent ring-1 ring-inset ring-primary"
3440
- ),
3441
- children: [
3442
- safeIcon ? /* @__PURE__ */ jsxRuntime.jsx("img", { src: safeIcon, alt: "", className: "h-9 w-9 shrink-0 rounded-md object-cover" }) : /* @__PURE__ */ jsxRuntime.jsx(
3443
- "span",
3444
- {
3445
- "aria-hidden": true,
3446
- className: "flex h-9 w-9 shrink-0 items-center justify-center rounded-md bg-muted text-sm font-semibold text-muted-foreground",
3447
- children: authorName.charAt(0).toUpperCase()
3448
- }
3449
- ),
3450
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex min-w-0 flex-col gap-0.5", children: [
3451
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex min-w-0 items-center gap-1.5", children: [
3452
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate text-sm font-semibold text-foreground", children: authorName }),
3453
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "shrink-0 rounded border px-1 py-px text-[10px] font-medium uppercase tracking-wide text-muted-foreground", children: asUser ? "You" : "Bot" })
3454
- ] }),
3455
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs text-muted-foreground", children: [
3456
- new Date(Number(m.ts) * 1e3).toLocaleString(),
3457
- " ",
3458
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "font-mono", children: [
3459
- "(",
3460
- m.ts,
3461
- ")"
3462
- ] })
3463
- ] }),
3464
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "line-clamp-2 text-sm text-foreground", children: previewText(m) })
3465
- ] })
3466
- ]
3467
- },
3468
- `${m.channelId}:${m.ts}`
3469
- );
3470
- }) })
3471
- ] })
3472
- ] }),
3473
- /* @__PURE__ */ jsxRuntime.jsxs(DialogFooter, { children: [
3474
- /* @__PURE__ */ jsxRuntime.jsx(Button, { type: "button", variant: "ghost", onClick: () => onOpenChange(false), children: "Cancel" }),
3475
- /* @__PURE__ */ jsxRuntime.jsx(
3476
- Button,
3760
+ state.kind === "error" && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "shrink-0 rounded-md border border-destructive/30 bg-destructive/5 p-2 text-xs text-destructive", children: state.error }),
3761
+ state.kind === "loading" && /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "m-auto flex items-center gap-2 text-sm text-muted-foreground", children: [
3762
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "h-4 w-4 animate-spin" }),
3763
+ "Looking up that message\u2026"
3764
+ ] }),
3765
+ state.kind === "empty" && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "m-auto px-4 text-center text-sm text-muted-foreground", children: emptyHint }),
3766
+ target && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx(
3767
+ SlackMessagePreview,
3477
3768
  {
3478
- type: "button",
3479
- onClick: handleLoad,
3480
- disabled: status.kind === "loading" || !selectedRecent && !link.trim(),
3481
- children: status.kind === "loading" ? "Loading\u2026" : "Load message"
3769
+ blocks: target.blocks,
3770
+ hooks,
3771
+ theme,
3772
+ workspaceName: target.workspaceName,
3773
+ authorName: target.username,
3774
+ authorIcon: target.iconUrl,
3775
+ time: formatTs(target.ts)
3482
3776
  }
3483
- )
3484
- ] })
3485
- ]
3486
- }
3487
- ) });
3777
+ ) }),
3778
+ verdictBlocks && verdictBlocks.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx(SlackMessagePreview, { blocks: verdictBlocks, hooks, theme, time: "" }) })
3779
+ ]
3780
+ }
3781
+ )
3782
+ ] });
3488
3783
  }
3489
3784
  var PALETTE_DRAG_PREFIX = "palette:";
3490
3785
  function parsePaletteDragId(id) {
@@ -8707,60 +9002,6 @@ function dispatch(block, onChange) {
8707
9002
  return null;
8708
9003
  }
8709
9004
  }
8710
- function SlackBlockPreview({
8711
- block,
8712
- hooks,
8713
- theme = "light"
8714
- }) {
8715
- const rootRef = React.useRef(null);
8716
- const safeBlock = React.useMemo(() => sanitizeBlock(block), [block]);
8717
- React.useEffect(() => {
8718
- const root = rootRef.current;
8719
- if (!root) return;
8720
- for (const wrapper of root.querySelectorAll(
8721
- ".slack_blocks_to_jsx__image_media_trigger, .slack_blocks_to_jsx__video_title"
8722
- )) {
8723
- for (const btn of wrapper.querySelectorAll("button:not([aria-label])")) {
8724
- const title = wrapper.querySelector(".slack_blocks_to_jsx__image_title")?.textContent?.trim();
8725
- btn.setAttribute("aria-label", title ? `Toggle ${title}` : "Toggle media");
8726
- }
8727
- }
8728
- for (const a of root.querySelectorAll("a[href]")) {
8729
- const href = a.getAttribute("href");
8730
- if (!isSafeHref(href)) {
8731
- a.setAttribute("href", "#");
8732
- a.setAttribute("data-bk-blocked-href", "1");
8733
- }
8734
- }
8735
- for (const img of root.querySelectorAll("img[src]")) {
8736
- const src = img.getAttribute("src");
8737
- if (!isSafeImageSrc(src)) {
8738
- img.removeAttribute("src");
8739
- img.setAttribute("data-bk-blocked-src", "1");
8740
- }
8741
- }
8742
- });
8743
- return (
8744
- // The library scopes all of its CSS under `#slack_blocks_to_jsx` and
8745
- // depends on the `slack_blocks_to_jsx styles_enabled` classes plus the
8746
- // `data-theme` attribute for its button / mention / divider styling
8747
- // (e.g. button reset + `bg-green-primary` for primary). `<Message
8748
- // withoutWrapper>` skips that wrapper entirely, so we re-create it
8749
- // here without the avatar / timestamp chrome.
8750
- /* @__PURE__ */ jsxRuntime.jsx("div", { ref: rootRef, id: "slack_blocks_to_jsx", "data-theme": theme, className: "slack_blocks_to_jsx styles_enabled", children: /* @__PURE__ */ jsxRuntime.jsx(
8751
- slackBlocksToJsx.Message,
8752
- {
8753
- time: /* @__PURE__ */ new Date(),
8754
- name: "",
8755
- logo: "",
8756
- withoutWrapper: true,
8757
- theme,
8758
- blocks: [safeBlock],
8759
- hooks
8760
- }
8761
- ) })
8762
- );
8763
- }
8764
9005
  var BLOCK_TYPE_LABELS = {
8765
9006
  section: "Section",
8766
9007
  header: "Header",
@@ -9383,50 +9624,16 @@ function Surface({
9383
9624
  {
9384
9625
  "aria-label": "Block preview",
9385
9626
  className: cn("flex min-w-0 flex-1 flex-col overflow-y-auto bg-muted p-3 sm:p-6"),
9386
- children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mx-auto w-full max-w-2xl", children: previewSurface === "modal" ? /* @__PURE__ */ jsxRuntime.jsx(ModalFrame, { isDark, children: blocksList }) : previewSurface === "app_home" ? /* @__PURE__ */ jsxRuntime.jsx(AppHomeFrame, { workspaceName, isDark, children: blocksList }) : /* @__PURE__ */ jsxRuntime.jsx(MessageFrame, { workspaceName, authorName, authorIcon, isDark, children: blocksList }) })
9387
- }
9388
- );
9389
- }
9390
- function MessageFrame({
9391
- workspaceName,
9392
- authorName,
9393
- authorIcon,
9394
- isDark,
9395
- children
9396
- }) {
9397
- const displayName = authorName ?? workspaceName ?? "Your app";
9398
- const initial = displayName.slice(0, 1).toUpperCase();
9399
- const safeIcon = authorIcon && isSafeImageSrc(authorIcon) ? authorIcon : null;
9400
- return /* @__PURE__ */ jsxRuntime.jsxs(
9401
- "div",
9402
- {
9403
- className: cn(
9404
- "flex flex-col overflow-hidden rounded-md border",
9405
- isDark ? "border-[#2c2d30] bg-[#1a1d21]" : "border-[#e8e8e8] bg-white"
9406
- ),
9407
- children: [
9408
- /* @__PURE__ */ jsxRuntime.jsxs(
9409
- "div",
9410
- {
9411
- className: cn("flex items-center gap-2 px-5 pt-3 pb-1 text-xs", isDark ? "text-white/60" : "text-[#616061]"),
9412
- children: [
9413
- safeIcon ? /* @__PURE__ */ jsxRuntime.jsx("img", { src: safeIcon, alt: "", className: "h-7 w-7 shrink-0 rounded object-cover" }) : /* @__PURE__ */ jsxRuntime.jsx(
9414
- "span",
9415
- {
9416
- className: cn(
9417
- "inline-flex h-7 w-7 items-center justify-center rounded text-[12px] font-semibold",
9418
- isDark ? "bg-white/10 text-white" : "bg-[#4a154b]/10 text-[#1d1c1d]"
9419
- ),
9420
- children: initial
9421
- }
9422
- ),
9423
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("font-bold text-sm", isDark ? "text-white" : "text-[#1d1c1d]"), children: displayName }),
9424
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: "10:37 AM" })
9425
- ]
9426
- }
9427
- ),
9428
- children
9429
- ]
9627
+ children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mx-auto w-full max-w-2xl", children: previewSurface === "modal" ? /* @__PURE__ */ jsxRuntime.jsx(ModalFrame, { isDark, children: blocksList }) : previewSurface === "app_home" ? /* @__PURE__ */ jsxRuntime.jsx(AppHomeFrame, { workspaceName, isDark, children: blocksList }) : /* @__PURE__ */ jsxRuntime.jsx(
9628
+ SlackMessageFrame,
9629
+ {
9630
+ workspaceName,
9631
+ authorName,
9632
+ authorIcon,
9633
+ isDark,
9634
+ children: blocksList
9635
+ }
9636
+ ) })
9430
9637
  }
9431
9638
  );
9432
9639
  }
@@ -10515,6 +10722,8 @@ function BlockKitchen(props) {
10515
10722
  onLoadMessage: loading.onLoadMessage,
10516
10723
  loadRecentMessages: loading.loadRecentMessages,
10517
10724
  loadChannels: recentChannelSource,
10725
+ previewHooks: mergedPreviewHooks,
10726
+ previewTheme,
10518
10727
  onLoaded: (result) => {
10519
10728
  resetAll(result.blocks);
10520
10729
  setEditTarget(toLoadedMessage(result));