@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.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { useSensors, useSensor, PointerSensor, TouchSensor, KeyboardSensor, pointerWithin, closestCenter, DndContext, DragOverlay, useDroppable, useDraggable } from '@dnd-kit/core';
2
2
  import { sortableKeyboardCoordinates, SortableContext, verticalListSortingStrategy, useSortable } from '@dnd-kit/sortable';
3
- import { X, Circle, Sparkles, FileText, AlignLeft, MousePointerClick, TextCursorInput, LayoutTemplate, Bell, Pilcrow, Image, Video, GalleryHorizontal, Table, ChartColumnBig, Container, Loader2, ExternalLink, AlertTriangle, GripVertical, Sun, Moon, MessageSquare, AppWindow, Home, MailSearch, Plus, ChevronDown, BookOpen, Undo2, Redo2, Trash2, Code2, Pencil, Send, Search, Check, Copy, Info, CheckCircle2, ChevronRight, LayoutGrid, ArrowUp, ArrowDown, Type, Bold, Italic, Strikethrough, Underline, Code, List, ListOrdered, Quote, Smile, Link as Link$1 } from 'lucide-react';
3
+ import { X, Circle, Sparkles, FileText, AlignLeft, MousePointerClick, TextCursorInput, LayoutTemplate, Bell, Pilcrow, Image, Video, GalleryHorizontal, Table, ChartColumnBig, Container, Loader2, ExternalLink, AlertTriangle, GripVertical, Sun, Moon, MessageSquare, AppWindow, Home, MailSearch, Plus, ChevronDown, BookOpen, Undo2, Redo2, Trash2, Code2, Pencil, Send, Search, Check, Copy, CheckCircle2, ChevronRight, LayoutGrid, ArrowUp, ArrowDown, Type, Bold, Italic, Strikethrough, Underline, Code, List, ListOrdered, Quote, Smile, Link as Link$1 } from 'lucide-react';
4
4
  import * as React from 'react';
5
5
  import { createContext, useState, useRef, useEffect, useCallback, useMemo, useId, useContext } from 'react';
6
6
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
@@ -13,14 +13,14 @@ import { nanoid } from 'nanoid';
13
13
  import { Slot } from '@radix-ui/react-slot';
14
14
  import { cva } from 'class-variance-authority';
15
15
  import * as LabelPrimitive from '@radix-ui/react-label';
16
+ import 'slack-blocks-to-jsx/dist/style.css';
17
+ import { Message } from 'slack-blocks-to-jsx';
16
18
  import { CSS } from '@dnd-kit/utilities';
17
19
  import * as PopoverPrimitive from '@radix-ui/react-popover';
18
20
  import * as RadioGroupPrimitive from '@radix-ui/react-radio-group';
19
21
  import Link from '@tiptap/extension-link';
20
22
  import { Node, ReactNodeViewRenderer, mergeAttributes, NodeViewWrapper, useEditor, EditorContent } from '@tiptap/react';
21
23
  import StarterKit from '@tiptap/starter-kit';
22
- import 'slack-blocks-to-jsx/dist/style.css';
23
- import { Message } from 'slack-blocks-to-jsx';
24
24
 
25
25
  // src/components/block-kitchen.tsx
26
26
 
@@ -3024,6 +3024,128 @@ var Label = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ j
3024
3024
  }
3025
3025
  ));
3026
3026
  Label.displayName = LabelPrimitive.Root.displayName;
3027
+ function SlackBlockPreview({
3028
+ block,
3029
+ hooks,
3030
+ theme = "light"
3031
+ }) {
3032
+ const rootRef = useRef(null);
3033
+ const safeBlock = useMemo(() => sanitizeBlock(block), [block]);
3034
+ useEffect(() => {
3035
+ const root = rootRef.current;
3036
+ if (!root) return;
3037
+ for (const wrapper of root.querySelectorAll(
3038
+ ".slack_blocks_to_jsx__image_media_trigger, .slack_blocks_to_jsx__video_title"
3039
+ )) {
3040
+ for (const btn of wrapper.querySelectorAll("button:not([aria-label])")) {
3041
+ const title = wrapper.querySelector(".slack_blocks_to_jsx__image_title")?.textContent?.trim();
3042
+ btn.setAttribute("aria-label", title ? `Toggle ${title}` : "Toggle media");
3043
+ }
3044
+ }
3045
+ for (const a of root.querySelectorAll("a[href]")) {
3046
+ const href = a.getAttribute("href");
3047
+ if (!isSafeHref(href)) {
3048
+ a.setAttribute("href", "#");
3049
+ a.setAttribute("data-bk-blocked-href", "1");
3050
+ }
3051
+ }
3052
+ for (const img of root.querySelectorAll("img[src]")) {
3053
+ const src = img.getAttribute("src");
3054
+ if (!isSafeImageSrc(src)) {
3055
+ img.removeAttribute("src");
3056
+ img.setAttribute("data-bk-blocked-src", "1");
3057
+ }
3058
+ }
3059
+ });
3060
+ return (
3061
+ // The library scopes all of its CSS under `#slack_blocks_to_jsx` and
3062
+ // depends on the `slack_blocks_to_jsx styles_enabled` classes plus the
3063
+ // `data-theme` attribute for its button / mention / divider styling
3064
+ // (e.g. button reset + `bg-green-primary` for primary). `<Message
3065
+ // withoutWrapper>` skips that wrapper entirely, so we re-create it
3066
+ // here without the avatar / timestamp chrome.
3067
+ /* @__PURE__ */ jsx("div", { ref: rootRef, id: "slack_blocks_to_jsx", "data-theme": theme, className: "slack_blocks_to_jsx styles_enabled", children: /* @__PURE__ */ jsx(
3068
+ Message,
3069
+ {
3070
+ time: /* @__PURE__ */ new Date(),
3071
+ name: "",
3072
+ logo: "",
3073
+ withoutWrapper: true,
3074
+ theme,
3075
+ blocks: [safeBlock],
3076
+ hooks
3077
+ }
3078
+ ) })
3079
+ );
3080
+ }
3081
+ var PLACEHOLDER_TIME = "10:37 AM";
3082
+ function SlackMessageFrame({
3083
+ workspaceName,
3084
+ authorName,
3085
+ authorIcon,
3086
+ time = PLACEHOLDER_TIME,
3087
+ isDark,
3088
+ children
3089
+ }) {
3090
+ const displayName = authorName ?? workspaceName ?? "Your app";
3091
+ const initial = displayName.slice(0, 1).toUpperCase();
3092
+ const safeIcon = authorIcon && isSafeImageSrc(authorIcon) ? authorIcon : null;
3093
+ return /* @__PURE__ */ jsxs(
3094
+ "div",
3095
+ {
3096
+ className: cn(
3097
+ "flex flex-col overflow-hidden rounded-md border",
3098
+ isDark ? "border-[#2c2d30] bg-[#1a1d21]" : "border-[#e8e8e8] bg-white"
3099
+ ),
3100
+ children: [
3101
+ /* @__PURE__ */ jsxs(
3102
+ "div",
3103
+ {
3104
+ className: cn("flex items-center gap-2 px-5 pt-3 pb-1 text-xs", isDark ? "text-white/60" : "text-[#616061]"),
3105
+ children: [
3106
+ safeIcon ? /* @__PURE__ */ jsx("img", { src: safeIcon, alt: "", className: "h-7 w-7 shrink-0 rounded object-cover" }) : /* @__PURE__ */ jsx(
3107
+ "span",
3108
+ {
3109
+ className: cn(
3110
+ "inline-flex h-7 w-7 items-center justify-center rounded text-[12px] font-semibold",
3111
+ isDark ? "bg-white/10 text-white" : "bg-[#4a154b]/10 text-[#1d1c1d]"
3112
+ ),
3113
+ children: initial
3114
+ }
3115
+ ),
3116
+ /* @__PURE__ */ jsx("span", { className: cn("font-bold text-sm", isDark ? "text-white" : "text-[#1d1c1d]"), children: displayName }),
3117
+ time ? /* @__PURE__ */ jsx("span", { children: time }) : null
3118
+ ]
3119
+ }
3120
+ ),
3121
+ children
3122
+ ]
3123
+ }
3124
+ );
3125
+ }
3126
+ function SlackMessagePreview({
3127
+ blocks,
3128
+ hooks,
3129
+ theme = "light",
3130
+ workspaceName,
3131
+ authorName,
3132
+ authorIcon,
3133
+ time,
3134
+ emptyLabel = "This message has no blocks to preview."
3135
+ }) {
3136
+ const isDark = theme === "dark";
3137
+ return /* @__PURE__ */ jsx(
3138
+ SlackMessageFrame,
3139
+ {
3140
+ workspaceName,
3141
+ authorName,
3142
+ authorIcon,
3143
+ time,
3144
+ isDark,
3145
+ children: /* @__PURE__ */ 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__ */ jsx("p", { className: cn("py-1 text-sm", isDark ? "text-white/60" : "text-[#616061]"), children: emptyLabel }) : blocks.map((block, index) => /* @__PURE__ */ jsx(SlackBlockPreview, { block, hooks, theme }, `${block.type}-${index}`)) })
3146
+ }
3147
+ );
3148
+ }
3027
3149
  var POLL_INTERVAL_MS = 2500;
3028
3150
  var MAX_POLLS = 24;
3029
3151
  function useSlackSignIn(loadStatus, { open, enabled }) {
@@ -3097,6 +3219,12 @@ function SlackSignInButton({ onClick, polling }) {
3097
3219
  /* @__PURE__ */ jsx(ExternalLink, {})
3098
3220
  ] }) });
3099
3221
  }
3222
+ var LINK_DEBOUNCE_MS = 400;
3223
+ var LINK_HELP_TEXT = 'Click the \u2807menu while hovering over the message in Slack (or right-click), and select "Copy Link".';
3224
+ var TABS = [
3225
+ { id: "recent", label: "Pick from Recent" },
3226
+ { id: "link", label: "Direct Link" }
3227
+ ];
3100
3228
  function recentToResult(msg) {
3101
3229
  return {
3102
3230
  ok: true,
@@ -3121,6 +3249,16 @@ function previewText(m) {
3121
3249
  }
3122
3250
  return `${m.blocks.length} block${m.blocks.length === 1 ? "" : "s"}`;
3123
3251
  }
3252
+ function formatTs(ts) {
3253
+ const seconds = Number(ts);
3254
+ if (!ts || !Number.isFinite(seconds) || seconds <= 0) {
3255
+ return "";
3256
+ }
3257
+ return new Date(seconds * 1e3).toLocaleTimeString(void 0, { hour: "numeric", minute: "2-digit" });
3258
+ }
3259
+ function editableLabel(editableVia) {
3260
+ return editableVia === "user" ? "You" : "Bot";
3261
+ }
3124
3262
  function LoadMessageDialog({
3125
3263
  open,
3126
3264
  onOpenChange,
@@ -3128,10 +3266,14 @@ function LoadMessageDialog({
3128
3266
  loadRecentMessages,
3129
3267
  loadChannels,
3130
3268
  onLoaded,
3131
- onOpenAsNew
3269
+ onOpenAsNew,
3270
+ previewHooks,
3271
+ previewTheme = "light"
3132
3272
  }) {
3133
3273
  const [link, setLink] = useState("");
3134
- const [status, setStatus] = useState({ kind: "idle" });
3274
+ const [linkStatus, setLinkStatus] = useState({ kind: "idle" });
3275
+ const [emptyLinkError, setEmptyLinkError] = useState(null);
3276
+ const [committing, setCommitting] = useState(false);
3135
3277
  const [channels, setChannels] = useState(null);
3136
3278
  const [channelsError, setChannelsError] = useState(null);
3137
3279
  const [channelId, setChannelId] = useState("");
@@ -3139,8 +3281,14 @@ function LoadMessageDialog({
3139
3281
  const [recentError, setRecentError] = useState(null);
3140
3282
  const [selectedRecent, setSelectedRecent] = useState(null);
3141
3283
  const [signInPolling, setSignInPolling] = useState(false);
3284
+ const [tab, setTab] = useState("recent");
3142
3285
  const inputRef = useRef(null);
3143
3286
  const signInPollRef = useRef(null);
3287
+ const resolvedLinkRef = useRef(null);
3288
+ const linkRequestRef = useRef(0);
3289
+ const domId = useId();
3290
+ const tabDomId = (id) => `${domId}-${id}-tab`;
3291
+ const panelDomId = (id) => `${domId}-${id}-panel`;
3144
3292
  const onLoadMessageRef = useRef(onLoadMessage);
3145
3293
  const loadRecentMessagesRef = useRef(loadRecentMessages);
3146
3294
  const loadChannelsRef = useRef(loadChannels);
@@ -3150,16 +3298,22 @@ function LoadMessageDialog({
3150
3298
  loadChannelsRef.current = loadChannels;
3151
3299
  });
3152
3300
  const hasRecent = !!loadRecentMessages && !!loadChannels;
3301
+ const activeTab = hasRecent ? tab : "link";
3153
3302
  useEffect(() => {
3154
3303
  if (!open) {
3155
3304
  return;
3156
3305
  }
3157
3306
  setLink("");
3158
- setStatus({ kind: "idle" });
3307
+ setLinkStatus({ kind: "idle" });
3308
+ setEmptyLinkError(null);
3309
+ setCommitting(false);
3159
3310
  setChannelId("");
3160
3311
  setRecent(null);
3161
3312
  setRecentError(null);
3162
3313
  setSelectedRecent(null);
3314
+ setTab("recent");
3315
+ resolvedLinkRef.current = null;
3316
+ linkRequestRef.current += 1;
3163
3317
  }, [open]);
3164
3318
  useEffect(() => {
3165
3319
  const load = loadChannelsRef.current;
@@ -3218,27 +3372,71 @@ function LoadMessageDialog({
3218
3372
  }
3219
3373
  return stopSignInPoll;
3220
3374
  }, [open, stopSignInPoll]);
3375
+ const resolveLink = useCallback(async (trimmed) => {
3376
+ const requestId = ++linkRequestRef.current;
3377
+ setLinkStatus({ kind: "loading" });
3378
+ try {
3379
+ const result = await onLoadMessageRef.current({ link: trimmed });
3380
+ if (linkRequestRef.current !== requestId) {
3381
+ return null;
3382
+ }
3383
+ resolvedLinkRef.current = trimmed;
3384
+ setLinkStatus(
3385
+ result.ok ? { kind: "ready", result } : { kind: "not-editable", reason: result.reason, blocks: result.blocks, oauthUrl: result.oauthUrl }
3386
+ );
3387
+ return result;
3388
+ } catch (e) {
3389
+ if (linkRequestRef.current !== requestId) {
3390
+ return null;
3391
+ }
3392
+ resolvedLinkRef.current = trimmed;
3393
+ setLinkStatus({ kind: "error", error: e instanceof Error ? e.message : "Failed to load message." });
3394
+ return null;
3395
+ }
3396
+ }, []);
3397
+ useEffect(() => {
3398
+ if (!open) {
3399
+ return;
3400
+ }
3401
+ const trimmed = link.trim();
3402
+ if (!trimmed) {
3403
+ linkRequestRef.current += 1;
3404
+ resolvedLinkRef.current = null;
3405
+ setLinkStatus({ kind: "idle" });
3406
+ return;
3407
+ }
3408
+ if (resolvedLinkRef.current === trimmed) {
3409
+ return;
3410
+ }
3411
+ const timer = setTimeout(() => {
3412
+ if (resolvedLinkRef.current !== trimmed) {
3413
+ void resolveLink(trimmed);
3414
+ }
3415
+ }, LINK_DEBOUNCE_MS);
3416
+ return () => clearTimeout(timer);
3417
+ }, [open, link, resolveLink]);
3221
3418
  const handleLoad = async () => {
3222
3419
  stopSignInPoll();
3223
- if (selectedRecent) {
3224
- onLoaded(recentToResult(selectedRecent));
3420
+ if (activeTab === "recent") {
3421
+ if (selectedRecent) {
3422
+ onLoaded(recentToResult(selectedRecent));
3423
+ }
3225
3424
  return;
3226
3425
  }
3227
3426
  const trimmed = link.trim();
3228
3427
  if (!trimmed) {
3229
- setStatus({ kind: "error", error: "Paste a Slack message link first." });
3428
+ setEmptyLinkError("Paste a Slack message link first.");
3230
3429
  return;
3231
3430
  }
3232
- setStatus({ kind: "loading" });
3233
- try {
3234
- const result = await onLoadMessageRef.current({ link: trimmed });
3235
- if (result.ok) {
3236
- onLoaded(result);
3237
- return;
3238
- }
3239
- setStatus({ kind: "not-editable", reason: result.reason, blocks: result.blocks, oauthUrl: result.oauthUrl });
3240
- } catch (e) {
3241
- setStatus({ kind: "error", error: e instanceof Error ? e.message : "Failed to load message." });
3431
+ if (linkStatus.kind === "ready" && resolvedLinkRef.current === trimmed) {
3432
+ onLoaded(linkStatus.result);
3433
+ return;
3434
+ }
3435
+ setCommitting(true);
3436
+ const result = await resolveLink(trimmed);
3437
+ setCommitting(false);
3438
+ if (result?.ok) {
3439
+ onLoaded(result);
3242
3440
  }
3243
3441
  };
3244
3442
  const startFindSignIn = (oauthUrl) => {
@@ -3270,191 +3468,288 @@ function LoadMessageDialog({
3270
3468
  });
3271
3469
  }, POLL_INTERVAL_MS2);
3272
3470
  };
3273
- const notEditable = status.kind === "not-editable" ? status : null;
3274
- const signInUrl = notEditable?.oauthUrl && isSafeHref(notEditable.oauthUrl) ? notEditable.oauthUrl : null;
3471
+ 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;
3472
+ const canLoad = activeTab === "recent" ? !!selectedRecent : !!link.trim();
3473
+ const panelProps = (id) => hasRecent ? { role: "tabpanel", id: panelDomId(id), "aria-labelledby": tabDomId(id) } : {};
3275
3474
  return /* @__PURE__ */ jsx(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxs(
3276
3475
  DialogContent,
3277
3476
  {
3278
- className: "w-[calc(100vw-1.5rem)] max-w-lg rounded-lg",
3477
+ className: "flex max-h-[85svh] w-[calc(100vw-1.5rem)] flex-col rounded-lg lg:max-w-4xl",
3279
3478
  onOpenAutoFocus: (e) => {
3479
+ if (hasRecent) {
3480
+ return;
3481
+ }
3280
3482
  e.preventDefault();
3281
3483
  inputRef.current?.focus();
3282
3484
  },
3283
3485
  children: [
3284
- /* @__PURE__ */ jsxs(DialogHeader, { children: [
3486
+ /* @__PURE__ */ jsxs(DialogHeader, { className: "shrink-0", children: [
3285
3487
  /* @__PURE__ */ jsx(DialogTitle, { children: "Find an existing message" }),
3286
- /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-1.5 text-left", children: [
3287
- /* @__PURE__ */ 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.` }),
3288
- /* @__PURE__ */ jsxs(Tooltip, { children: [
3289
- /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
3290
- "button",
3291
- {
3292
- type: "button",
3293
- "aria-label": "How to copy a message link",
3294
- 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",
3295
- children: /* @__PURE__ */ jsx(Info, { className: "h-3.5 w-3.5" })
3296
- }
3297
- ) }),
3298
- /* @__PURE__ */ jsxs(TooltipContent, { className: "max-w-xs leading-relaxed", children: [
3299
- "In Slack, hover over the message and click the ",
3300
- /* @__PURE__ */ jsx("strong", { children: "\u22EE More actions" }),
3301
- " button (or right-click the message), then choose ",
3302
- /* @__PURE__ */ jsx("strong", { children: "Copy link" }),
3303
- "."
3304
- ] })
3305
- ] })
3306
- ] })
3488
+ /* @__PURE__ */ 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.` })
3307
3489
  ] }),
3308
- /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-col gap-4", children: [
3309
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5", children: [
3310
- /* @__PURE__ */ jsx(Label, { htmlFor: "message-link", children: "Message link" }),
3311
- /* @__PURE__ */ jsx(
3312
- Input,
3490
+ /* @__PURE__ */ 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: [
3491
+ /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 shrink-0 flex-col gap-3 lg:min-h-0 lg:w-[19rem]", children: [
3492
+ hasRecent && /* @__PURE__ */ jsx(TabStrip, { activeTab, onSelect: setTab, tabDomId, panelDomId }),
3493
+ activeTab === "recent" ? /* @__PURE__ */ jsxs(
3494
+ "div",
3313
3495
  {
3314
- ref: inputRef,
3315
- id: "message-link",
3316
- value: link,
3317
- onChange: (e) => {
3318
- setLink(e.target.value);
3319
- setSelectedRecent(null);
3320
- stopSignInPoll();
3321
- if (status.kind !== "idle" && status.kind !== "loading") {
3322
- setStatus({ kind: "idle" });
3323
- }
3324
- },
3325
- onKeyDown: (e) => {
3326
- if (e.key === "Enter" && status.kind !== "loading") {
3327
- e.preventDefault();
3328
- handleLoad();
3329
- }
3330
- },
3331
- placeholder: "https://your-workspace.slack.com/archives/C\u2026/p\u2026",
3332
- autoComplete: "off",
3333
- spellCheck: false
3496
+ ...panelProps("recent"),
3497
+ className: "-mx-1 flex min-w-0 flex-col gap-2 px-1 lg:min-h-0 lg:flex-1 lg:overflow-hidden",
3498
+ children: [
3499
+ /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 flex-col gap-1.5", children: [
3500
+ /* @__PURE__ */ jsx(Label, { htmlFor: "recent-channel-picker", children: "Channel" }),
3501
+ channels === null && !channelsError && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Loading channels\u2026" }),
3502
+ channelsError && /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive", children: channelsError }),
3503
+ channels && channels.length === 0 && !channelsError && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "No public channels available." }),
3504
+ channels && channels.length > 0 && /* @__PURE__ */ jsxs(
3505
+ "select",
3506
+ {
3507
+ id: "recent-channel-picker",
3508
+ value: channelId,
3509
+ onChange: (e) => setChannelId(e.target.value),
3510
+ 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",
3511
+ children: [
3512
+ /* @__PURE__ */ jsx("option", { value: "", disabled: true, children: "Select a channel\u2026" }),
3513
+ channels.map((c) => /* @__PURE__ */ jsxs("option", { value: c.id, children: [
3514
+ "#",
3515
+ c.name
3516
+ ] }, c.id))
3517
+ ]
3518
+ }
3519
+ )
3520
+ ] }),
3521
+ !channelId && channels && channels.length > 0 && /* @__PURE__ */ jsx("p", { className: "shrink-0 text-xs text-muted-foreground", children: "Select a channel to see recent messages." }),
3522
+ channelId && recent === null && !recentError && /* @__PURE__ */ jsx("p", { className: "shrink-0 text-xs text-muted-foreground", children: "Loading recent messages\u2026" }),
3523
+ channelId && recentError && /* @__PURE__ */ jsx("p", { className: "shrink-0 text-xs text-destructive", children: recentError }),
3524
+ channelId && recent && recent.length === 0 && !recentError && /* @__PURE__ */ jsx("p", { className: "shrink-0 text-xs text-muted-foreground", children: "No recent messages from this app in this channel." }),
3525
+ channelId && recent && recent.length > 0 && /* @__PURE__ */ 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) => {
3526
+ const asUser = (m.editableVia ?? "bot") === "user";
3527
+ const authorName = m.username || (asUser ? "You" : "App bot");
3528
+ const safeIcon = isSafeImageSrc(m.iconUrl) ? m.iconUrl : void 0;
3529
+ const isSelected = selectedRecent?.channelId === m.channelId && selectedRecent?.ts === m.ts;
3530
+ return /* @__PURE__ */ jsxs(
3531
+ "button",
3532
+ {
3533
+ type: "button",
3534
+ "aria-pressed": isSelected,
3535
+ onClick: () => {
3536
+ setSelectedRecent(m);
3537
+ },
3538
+ className: cn(
3539
+ // Inset rings so the scroll container's overflow clip
3540
+ // can't shave the corners off the selected outline.
3541
+ "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",
3542
+ isSelected && "border-primary! bg-accent ring-1 ring-inset ring-primary"
3543
+ ),
3544
+ children: [
3545
+ safeIcon ? /* @__PURE__ */ jsx("img", { src: safeIcon, alt: "", className: "h-9 w-9 shrink-0 rounded-md object-cover" }) : /* @__PURE__ */ jsx(
3546
+ "span",
3547
+ {
3548
+ "aria-hidden": true,
3549
+ className: "flex h-9 w-9 shrink-0 items-center justify-center rounded-md bg-muted text-sm font-semibold text-muted-foreground",
3550
+ children: authorName.charAt(0).toUpperCase()
3551
+ }
3552
+ ),
3553
+ /* @__PURE__ */ jsxs("span", { className: "flex min-w-0 flex-col gap-0.5", children: [
3554
+ /* @__PURE__ */ jsxs("span", { className: "flex min-w-0 items-center gap-1.5", children: [
3555
+ /* @__PURE__ */ jsx("span", { className: "truncate text-sm font-semibold text-foreground", children: authorName }),
3556
+ /* @__PURE__ */ 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" })
3557
+ ] }),
3558
+ /* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground", children: [
3559
+ new Date(Number(m.ts) * 1e3).toLocaleString(),
3560
+ " ",
3561
+ /* @__PURE__ */ jsxs("span", { className: "font-mono", children: [
3562
+ "(",
3563
+ m.ts,
3564
+ ")"
3565
+ ] })
3566
+ ] }),
3567
+ /* @__PURE__ */ jsx("span", { className: "line-clamp-2 text-sm text-foreground", children: previewText(m) })
3568
+ ] })
3569
+ ]
3570
+ },
3571
+ `${m.channelId}:${m.ts}`
3572
+ );
3573
+ }) })
3574
+ ]
3575
+ }
3576
+ ) : /* @__PURE__ */ jsxs(
3577
+ "div",
3578
+ {
3579
+ ...panelProps("link"),
3580
+ className: "-mx-1 flex min-w-0 flex-col gap-2 px-1 lg:min-h-0 lg:flex-1 lg:overflow-y-auto",
3581
+ children: [
3582
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5", children: [
3583
+ /* @__PURE__ */ jsx(Label, { htmlFor: "message-link", children: "Message link" }),
3584
+ /* @__PURE__ */ jsx(
3585
+ Input,
3586
+ {
3587
+ ref: inputRef,
3588
+ id: "message-link",
3589
+ value: link,
3590
+ onChange: (e) => {
3591
+ setLink(e.target.value);
3592
+ setEmptyLinkError(null);
3593
+ stopSignInPoll();
3594
+ },
3595
+ onKeyDown: (e) => {
3596
+ if (e.key === "Enter" && !committing) {
3597
+ e.preventDefault();
3598
+ handleLoad();
3599
+ }
3600
+ },
3601
+ placeholder: "https://your-workspace.slack.com/archives/C\u2026/p\u2026",
3602
+ autoComplete: "off",
3603
+ spellCheck: false
3604
+ }
3605
+ ),
3606
+ /* @__PURE__ */ jsx("p", { className: "text-xs leading-relaxed text-muted-foreground", children: LINK_HELP_TEXT })
3607
+ ] }),
3608
+ emptyLinkError && /* @__PURE__ */ jsx("p", { className: "rounded-md border border-destructive/30 bg-destructive/5 p-2 text-xs text-destructive", children: emptyLinkError })
3609
+ ]
3334
3610
  }
3335
3611
  )
3336
3612
  ] }),
3337
- status.kind === "error" && /* @__PURE__ */ jsx("p", { className: "rounded-md border border-destructive/30 bg-destructive/5 p-2 text-xs text-destructive", children: status.error }),
3338
- notEditable && /* @__PURE__ */ 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: [
3613
+ /* @__PURE__ */ jsx(
3614
+ PreviewPane,
3615
+ {
3616
+ state: preview,
3617
+ hooks: previewHooks,
3618
+ theme: previewTheme,
3619
+ emptyHint: activeTab === "recent" ? "Select a recent message to preview it here." : "Paste a message link to preview it here.",
3620
+ signInPolling,
3621
+ onSignIn: startFindSignIn,
3622
+ onOpenAsNew
3623
+ }
3624
+ )
3625
+ ] }),
3626
+ /* @__PURE__ */ jsxs(DialogFooter, { className: "shrink-0", children: [
3627
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", onClick: () => onOpenChange(false), children: "Cancel" }),
3628
+ /* @__PURE__ */ jsx(Button, { type: "button", onClick: handleLoad, disabled: committing || !canLoad, children: committing ? "Loading\u2026" : "Load message" })
3629
+ ] })
3630
+ ]
3631
+ }
3632
+ ) });
3633
+ }
3634
+ function TabStrip({
3635
+ activeTab,
3636
+ onSelect,
3637
+ tabDomId,
3638
+ panelDomId
3639
+ }) {
3640
+ const tabRefs = useRef({});
3641
+ const moveTo = (next) => {
3642
+ onSelect(next);
3643
+ tabRefs.current[next]?.focus();
3644
+ };
3645
+ return /* @__PURE__ */ 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) => {
3646
+ const selected = entry.id === activeTab;
3647
+ return /* @__PURE__ */ jsx(
3648
+ "button",
3649
+ {
3650
+ ref: (node) => {
3651
+ tabRefs.current[entry.id] = node;
3652
+ },
3653
+ type: "button",
3654
+ role: "tab",
3655
+ id: tabDomId(entry.id),
3656
+ "aria-selected": selected,
3657
+ "aria-controls": panelDomId(entry.id),
3658
+ tabIndex: selected ? 0 : -1,
3659
+ onClick: () => onSelect(entry.id),
3660
+ onKeyDown: (e) => {
3661
+ if (e.key === "ArrowRight" || e.key === "ArrowLeft") {
3662
+ e.preventDefault();
3663
+ const delta = e.key === "ArrowRight" ? 1 : -1;
3664
+ moveTo(TABS[(index + delta + TABS.length) % TABS.length].id);
3665
+ } else if (e.key === "Home") {
3666
+ e.preventDefault();
3667
+ moveTo(TABS[0].id);
3668
+ } else if (e.key === "End") {
3669
+ e.preventDefault();
3670
+ moveTo(TABS[TABS.length - 1].id);
3671
+ }
3672
+ },
3673
+ className: cn(
3674
+ "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",
3675
+ selected ? "bg-background text-foreground shadow-sm" : "text-muted-foreground hover:text-foreground"
3676
+ ),
3677
+ children: entry.label
3678
+ },
3679
+ entry.id
3680
+ );
3681
+ }) });
3682
+ }
3683
+ function PreviewPane({
3684
+ state,
3685
+ hooks,
3686
+ theme,
3687
+ emptyHint,
3688
+ signInPolling,
3689
+ onSignIn,
3690
+ onOpenAsNew
3691
+ }) {
3692
+ const target = state.kind === "ready" ? state.target : null;
3693
+ const signInUrl = state.kind === "not-editable" && state.oauthUrl && isSafeHref(state.oauthUrl) ? state.oauthUrl : null;
3694
+ const verdictBlocks = state.kind === "not-editable" ? state.blocks : void 0;
3695
+ return /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 shrink-0 flex-col gap-2 lg:min-h-0 lg:flex-1", children: [
3696
+ /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 flex-wrap items-center gap-2", children: [
3697
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium uppercase tracking-wide text-muted-foreground", children: "Preview" }),
3698
+ target?.channelName || target?.channelId ? /* @__PURE__ */ 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,
3699
+ target?.editableVia ? /* @__PURE__ */ jsxs("span", { className: "rounded border px-1.5 py-px text-[10px] font-medium uppercase tracking-wide text-muted-foreground", children: [
3700
+ "Editable as ",
3701
+ editableLabel(target.editableVia)
3702
+ ] }) : null
3703
+ ] }),
3704
+ /* @__PURE__ */ jsxs(
3705
+ "div",
3706
+ {
3707
+ role: "region",
3708
+ "aria-label": "Message preview",
3709
+ tabIndex: 0,
3710
+ 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",
3711
+ children: [
3712
+ state.kind === "not-editable" && /* @__PURE__ */ 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: [
3339
3713
  /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2", children: [
3340
3714
  /* @__PURE__ */ jsx(AlertTriangle, { className: "mt-0.5 h-3.5 w-3.5 shrink-0" }),
3341
- /* @__PURE__ */ jsx("span", { className: "flex-1", children: notEditable.reason })
3715
+ /* @__PURE__ */ jsx("span", { className: "flex-1", children: state.reason })
3342
3716
  ] }),
3343
- signInUrl && /* @__PURE__ */ jsx(SlackSignInButton, { onClick: () => startFindSignIn(signInUrl), polling: signInPolling }),
3344
- notEditable.blocks && notEditable.blocks.length > 0 && /* @__PURE__ */ jsx(
3717
+ signInUrl && /* @__PURE__ */ jsx(SlackSignInButton, { onClick: () => onSignIn(signInUrl), polling: signInPolling }),
3718
+ verdictBlocks && verdictBlocks.length > 0 && /* @__PURE__ */ jsx(
3345
3719
  Button,
3346
3720
  {
3347
3721
  type: "button",
3348
3722
  variant: "outline",
3349
3723
  size: "sm",
3350
3724
  className: "self-start",
3351
- onClick: () => onOpenAsNew(notEditable.blocks),
3725
+ onClick: () => onOpenAsNew(verdictBlocks),
3352
3726
  children: "Open as a new message instead"
3353
3727
  }
3354
3728
  )
3355
3729
  ] }),
3356
- hasRecent && /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-col gap-2", children: [
3357
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-xs text-muted-foreground", children: [
3358
- /* @__PURE__ */ jsx("span", { className: "h-px flex-1 bg-border" }),
3359
- "or pick a recent message",
3360
- /* @__PURE__ */ jsx("span", { className: "h-px flex-1 bg-border" })
3361
- ] }),
3362
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5", children: [
3363
- /* @__PURE__ */ jsx(Label, { htmlFor: "recent-channel-picker", children: "Channel" }),
3364
- channels === null && !channelsError && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Loading channels\u2026" }),
3365
- channelsError && /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive", children: channelsError }),
3366
- channels && channels.length === 0 && !channelsError && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "No public channels available." }),
3367
- channels && channels.length > 0 && /* @__PURE__ */ jsxs(
3368
- "select",
3369
- {
3370
- id: "recent-channel-picker",
3371
- value: channelId,
3372
- onChange: (e) => setChannelId(e.target.value),
3373
- 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",
3374
- children: [
3375
- /* @__PURE__ */ jsx("option", { value: "", disabled: true, children: "Select a channel\u2026" }),
3376
- channels.map((c) => /* @__PURE__ */ jsxs("option", { value: c.id, children: [
3377
- "#",
3378
- c.name
3379
- ] }, c.id))
3380
- ]
3381
- }
3382
- )
3383
- ] }),
3384
- !channelId && channels && channels.length > 0 && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Select a channel to see recent messages." }),
3385
- channelId && recent === null && !recentError && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Loading recent messages\u2026" }),
3386
- channelId && recentError && /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive", children: recentError }),
3387
- channelId && recent && recent.length === 0 && !recentError && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "No recent messages from this app in this channel." }),
3388
- channelId && recent && recent.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex max-h-48 min-w-0 flex-col gap-1 overflow-y-auto", children: recent.map((m) => {
3389
- const asUser = (m.editableVia ?? "bot") === "user";
3390
- const authorName = m.username || (asUser ? "You" : "App bot");
3391
- const safeIcon = isSafeImageSrc(m.iconUrl) ? m.iconUrl : void 0;
3392
- const isSelected = selectedRecent?.channelId === m.channelId && selectedRecent?.ts === m.ts;
3393
- return /* @__PURE__ */ jsxs(
3394
- "button",
3395
- {
3396
- type: "button",
3397
- "aria-pressed": isSelected,
3398
- onClick: () => {
3399
- setSelectedRecent(m);
3400
- setLink("");
3401
- if (status.kind !== "idle" && status.kind !== "loading") {
3402
- setStatus({ kind: "idle" });
3403
- }
3404
- },
3405
- className: cn(
3406
- // Inset rings so the scroll container's overflow clip
3407
- // can't shave the corners off the selected outline.
3408
- "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",
3409
- isSelected && "border-primary! bg-accent ring-1 ring-inset ring-primary"
3410
- ),
3411
- children: [
3412
- safeIcon ? /* @__PURE__ */ jsx("img", { src: safeIcon, alt: "", className: "h-9 w-9 shrink-0 rounded-md object-cover" }) : /* @__PURE__ */ jsx(
3413
- "span",
3414
- {
3415
- "aria-hidden": true,
3416
- className: "flex h-9 w-9 shrink-0 items-center justify-center rounded-md bg-muted text-sm font-semibold text-muted-foreground",
3417
- children: authorName.charAt(0).toUpperCase()
3418
- }
3419
- ),
3420
- /* @__PURE__ */ jsxs("span", { className: "flex min-w-0 flex-col gap-0.5", children: [
3421
- /* @__PURE__ */ jsxs("span", { className: "flex min-w-0 items-center gap-1.5", children: [
3422
- /* @__PURE__ */ jsx("span", { className: "truncate text-sm font-semibold text-foreground", children: authorName }),
3423
- /* @__PURE__ */ 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" })
3424
- ] }),
3425
- /* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground", children: [
3426
- new Date(Number(m.ts) * 1e3).toLocaleString(),
3427
- " ",
3428
- /* @__PURE__ */ jsxs("span", { className: "font-mono", children: [
3429
- "(",
3430
- m.ts,
3431
- ")"
3432
- ] })
3433
- ] }),
3434
- /* @__PURE__ */ jsx("span", { className: "line-clamp-2 text-sm text-foreground", children: previewText(m) })
3435
- ] })
3436
- ]
3437
- },
3438
- `${m.channelId}:${m.ts}`
3439
- );
3440
- }) })
3441
- ] })
3442
- ] }),
3443
- /* @__PURE__ */ jsxs(DialogFooter, { children: [
3444
- /* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", onClick: () => onOpenChange(false), children: "Cancel" }),
3445
- /* @__PURE__ */ jsx(
3446
- Button,
3730
+ state.kind === "error" && /* @__PURE__ */ jsx("p", { className: "shrink-0 rounded-md border border-destructive/30 bg-destructive/5 p-2 text-xs text-destructive", children: state.error }),
3731
+ state.kind === "loading" && /* @__PURE__ */ jsxs("p", { className: "m-auto flex items-center gap-2 text-sm text-muted-foreground", children: [
3732
+ /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin" }),
3733
+ "Looking up that message\u2026"
3734
+ ] }),
3735
+ state.kind === "empty" && /* @__PURE__ */ jsx("p", { className: "m-auto px-4 text-center text-sm text-muted-foreground", children: emptyHint }),
3736
+ target && /* @__PURE__ */ jsx("div", { className: "shrink-0", children: /* @__PURE__ */ jsx(
3737
+ SlackMessagePreview,
3447
3738
  {
3448
- type: "button",
3449
- onClick: handleLoad,
3450
- disabled: status.kind === "loading" || !selectedRecent && !link.trim(),
3451
- children: status.kind === "loading" ? "Loading\u2026" : "Load message"
3739
+ blocks: target.blocks,
3740
+ hooks,
3741
+ theme,
3742
+ workspaceName: target.workspaceName,
3743
+ authorName: target.username,
3744
+ authorIcon: target.iconUrl,
3745
+ time: formatTs(target.ts)
3452
3746
  }
3453
- )
3454
- ] })
3455
- ]
3456
- }
3457
- ) });
3747
+ ) }),
3748
+ verdictBlocks && verdictBlocks.length > 0 && /* @__PURE__ */ jsx("div", { className: "shrink-0", children: /* @__PURE__ */ jsx(SlackMessagePreview, { blocks: verdictBlocks, hooks, theme, time: "" }) })
3749
+ ]
3750
+ }
3751
+ )
3752
+ ] });
3458
3753
  }
3459
3754
  var PALETTE_DRAG_PREFIX = "palette:";
3460
3755
  function parsePaletteDragId(id) {
@@ -8677,60 +8972,6 @@ function dispatch(block, onChange) {
8677
8972
  return null;
8678
8973
  }
8679
8974
  }
8680
- function SlackBlockPreview({
8681
- block,
8682
- hooks,
8683
- theme = "light"
8684
- }) {
8685
- const rootRef = useRef(null);
8686
- const safeBlock = useMemo(() => sanitizeBlock(block), [block]);
8687
- useEffect(() => {
8688
- const root = rootRef.current;
8689
- if (!root) return;
8690
- for (const wrapper of root.querySelectorAll(
8691
- ".slack_blocks_to_jsx__image_media_trigger, .slack_blocks_to_jsx__video_title"
8692
- )) {
8693
- for (const btn of wrapper.querySelectorAll("button:not([aria-label])")) {
8694
- const title = wrapper.querySelector(".slack_blocks_to_jsx__image_title")?.textContent?.trim();
8695
- btn.setAttribute("aria-label", title ? `Toggle ${title}` : "Toggle media");
8696
- }
8697
- }
8698
- for (const a of root.querySelectorAll("a[href]")) {
8699
- const href = a.getAttribute("href");
8700
- if (!isSafeHref(href)) {
8701
- a.setAttribute("href", "#");
8702
- a.setAttribute("data-bk-blocked-href", "1");
8703
- }
8704
- }
8705
- for (const img of root.querySelectorAll("img[src]")) {
8706
- const src = img.getAttribute("src");
8707
- if (!isSafeImageSrc(src)) {
8708
- img.removeAttribute("src");
8709
- img.setAttribute("data-bk-blocked-src", "1");
8710
- }
8711
- }
8712
- });
8713
- return (
8714
- // The library scopes all of its CSS under `#slack_blocks_to_jsx` and
8715
- // depends on the `slack_blocks_to_jsx styles_enabled` classes plus the
8716
- // `data-theme` attribute for its button / mention / divider styling
8717
- // (e.g. button reset + `bg-green-primary` for primary). `<Message
8718
- // withoutWrapper>` skips that wrapper entirely, so we re-create it
8719
- // here without the avatar / timestamp chrome.
8720
- /* @__PURE__ */ jsx("div", { ref: rootRef, id: "slack_blocks_to_jsx", "data-theme": theme, className: "slack_blocks_to_jsx styles_enabled", children: /* @__PURE__ */ jsx(
8721
- Message,
8722
- {
8723
- time: /* @__PURE__ */ new Date(),
8724
- name: "",
8725
- logo: "",
8726
- withoutWrapper: true,
8727
- theme,
8728
- blocks: [safeBlock],
8729
- hooks
8730
- }
8731
- ) })
8732
- );
8733
- }
8734
8975
  var BLOCK_TYPE_LABELS = {
8735
8976
  section: "Section",
8736
8977
  header: "Header",
@@ -9353,50 +9594,16 @@ function Surface({
9353
9594
  {
9354
9595
  "aria-label": "Block preview",
9355
9596
  className: cn("flex min-w-0 flex-1 flex-col overflow-y-auto bg-muted p-3 sm:p-6"),
9356
- children: /* @__PURE__ */ jsx("div", { className: "mx-auto w-full max-w-2xl", children: previewSurface === "modal" ? /* @__PURE__ */ jsx(ModalFrame, { isDark, children: blocksList }) : previewSurface === "app_home" ? /* @__PURE__ */ jsx(AppHomeFrame, { workspaceName, isDark, children: blocksList }) : /* @__PURE__ */ jsx(MessageFrame, { workspaceName, authorName, authorIcon, isDark, children: blocksList }) })
9357
- }
9358
- );
9359
- }
9360
- function MessageFrame({
9361
- workspaceName,
9362
- authorName,
9363
- authorIcon,
9364
- isDark,
9365
- children
9366
- }) {
9367
- const displayName = authorName ?? workspaceName ?? "Your app";
9368
- const initial = displayName.slice(0, 1).toUpperCase();
9369
- const safeIcon = authorIcon && isSafeImageSrc(authorIcon) ? authorIcon : null;
9370
- return /* @__PURE__ */ jsxs(
9371
- "div",
9372
- {
9373
- className: cn(
9374
- "flex flex-col overflow-hidden rounded-md border",
9375
- isDark ? "border-[#2c2d30] bg-[#1a1d21]" : "border-[#e8e8e8] bg-white"
9376
- ),
9377
- children: [
9378
- /* @__PURE__ */ jsxs(
9379
- "div",
9380
- {
9381
- className: cn("flex items-center gap-2 px-5 pt-3 pb-1 text-xs", isDark ? "text-white/60" : "text-[#616061]"),
9382
- children: [
9383
- safeIcon ? /* @__PURE__ */ jsx("img", { src: safeIcon, alt: "", className: "h-7 w-7 shrink-0 rounded object-cover" }) : /* @__PURE__ */ jsx(
9384
- "span",
9385
- {
9386
- className: cn(
9387
- "inline-flex h-7 w-7 items-center justify-center rounded text-[12px] font-semibold",
9388
- isDark ? "bg-white/10 text-white" : "bg-[#4a154b]/10 text-[#1d1c1d]"
9389
- ),
9390
- children: initial
9391
- }
9392
- ),
9393
- /* @__PURE__ */ jsx("span", { className: cn("font-bold text-sm", isDark ? "text-white" : "text-[#1d1c1d]"), children: displayName }),
9394
- /* @__PURE__ */ jsx("span", { children: "10:37 AM" })
9395
- ]
9396
- }
9397
- ),
9398
- children
9399
- ]
9597
+ children: /* @__PURE__ */ jsx("div", { className: "mx-auto w-full max-w-2xl", children: previewSurface === "modal" ? /* @__PURE__ */ jsx(ModalFrame, { isDark, children: blocksList }) : previewSurface === "app_home" ? /* @__PURE__ */ jsx(AppHomeFrame, { workspaceName, isDark, children: blocksList }) : /* @__PURE__ */ jsx(
9598
+ SlackMessageFrame,
9599
+ {
9600
+ workspaceName,
9601
+ authorName,
9602
+ authorIcon,
9603
+ isDark,
9604
+ children: blocksList
9605
+ }
9606
+ ) })
9400
9607
  }
9401
9608
  );
9402
9609
  }
@@ -10485,6 +10692,8 @@ function BlockKitchen(props) {
10485
10692
  onLoadMessage: loading.onLoadMessage,
10486
10693
  loadRecentMessages: loading.loadRecentMessages,
10487
10694
  loadChannels: recentChannelSource,
10695
+ previewHooks: mergedPreviewHooks,
10696
+ previewTheme,
10488
10697
  onLoaded: (result) => {
10489
10698
  resetAll(result.blocks);
10490
10699
  setEditTarget(toLoadedMessage(result));