@tightknitai/block-kitchen 0.10.2 → 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
 
@@ -1957,14 +1957,7 @@ function sanitizeBlocks(blocks) {
1957
1957
 
1958
1958
  // src/lib/to-slack-blocks.ts
1959
1959
  function toSlackBlocks(blocks) {
1960
- return blocks.map((block) => {
1961
- const safe = sanitizeBlock(block);
1962
- if (safe.type === "header" && "level" in safe) {
1963
- const { level: _omit, ...rest } = safe;
1964
- return rest;
1965
- }
1966
- return safe;
1967
- });
1960
+ return blocks.map((block) => sanitizeBlock(block));
1968
1961
  }
1969
1962
 
1970
1963
  // src/lib/validation-sanitize.ts
@@ -3031,6 +3024,128 @@ var Label = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ j
3031
3024
  }
3032
3025
  ));
3033
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
+ }
3034
3149
  var POLL_INTERVAL_MS = 2500;
3035
3150
  var MAX_POLLS = 24;
3036
3151
  function useSlackSignIn(loadStatus, { open, enabled }) {
@@ -3104,6 +3219,12 @@ function SlackSignInButton({ onClick, polling }) {
3104
3219
  /* @__PURE__ */ jsx(ExternalLink, {})
3105
3220
  ] }) });
3106
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
+ ];
3107
3228
  function recentToResult(msg) {
3108
3229
  return {
3109
3230
  ok: true,
@@ -3128,6 +3249,16 @@ function previewText(m) {
3128
3249
  }
3129
3250
  return `${m.blocks.length} block${m.blocks.length === 1 ? "" : "s"}`;
3130
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
+ }
3131
3262
  function LoadMessageDialog({
3132
3263
  open,
3133
3264
  onOpenChange,
@@ -3135,10 +3266,14 @@ function LoadMessageDialog({
3135
3266
  loadRecentMessages,
3136
3267
  loadChannels,
3137
3268
  onLoaded,
3138
- onOpenAsNew
3269
+ onOpenAsNew,
3270
+ previewHooks,
3271
+ previewTheme = "light"
3139
3272
  }) {
3140
3273
  const [link, setLink] = useState("");
3141
- 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);
3142
3277
  const [channels, setChannels] = useState(null);
3143
3278
  const [channelsError, setChannelsError] = useState(null);
3144
3279
  const [channelId, setChannelId] = useState("");
@@ -3146,8 +3281,14 @@ function LoadMessageDialog({
3146
3281
  const [recentError, setRecentError] = useState(null);
3147
3282
  const [selectedRecent, setSelectedRecent] = useState(null);
3148
3283
  const [signInPolling, setSignInPolling] = useState(false);
3284
+ const [tab, setTab] = useState("recent");
3149
3285
  const inputRef = useRef(null);
3150
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`;
3151
3292
  const onLoadMessageRef = useRef(onLoadMessage);
3152
3293
  const loadRecentMessagesRef = useRef(loadRecentMessages);
3153
3294
  const loadChannelsRef = useRef(loadChannels);
@@ -3157,16 +3298,22 @@ function LoadMessageDialog({
3157
3298
  loadChannelsRef.current = loadChannels;
3158
3299
  });
3159
3300
  const hasRecent = !!loadRecentMessages && !!loadChannels;
3301
+ const activeTab = hasRecent ? tab : "link";
3160
3302
  useEffect(() => {
3161
3303
  if (!open) {
3162
3304
  return;
3163
3305
  }
3164
3306
  setLink("");
3165
- setStatus({ kind: "idle" });
3307
+ setLinkStatus({ kind: "idle" });
3308
+ setEmptyLinkError(null);
3309
+ setCommitting(false);
3166
3310
  setChannelId("");
3167
3311
  setRecent(null);
3168
3312
  setRecentError(null);
3169
3313
  setSelectedRecent(null);
3314
+ setTab("recent");
3315
+ resolvedLinkRef.current = null;
3316
+ linkRequestRef.current += 1;
3170
3317
  }, [open]);
3171
3318
  useEffect(() => {
3172
3319
  const load = loadChannelsRef.current;
@@ -3225,27 +3372,71 @@ function LoadMessageDialog({
3225
3372
  }
3226
3373
  return stopSignInPoll;
3227
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]);
3228
3418
  const handleLoad = async () => {
3229
3419
  stopSignInPoll();
3230
- if (selectedRecent) {
3231
- onLoaded(recentToResult(selectedRecent));
3420
+ if (activeTab === "recent") {
3421
+ if (selectedRecent) {
3422
+ onLoaded(recentToResult(selectedRecent));
3423
+ }
3232
3424
  return;
3233
3425
  }
3234
3426
  const trimmed = link.trim();
3235
3427
  if (!trimmed) {
3236
- setStatus({ kind: "error", error: "Paste a Slack message link first." });
3428
+ setEmptyLinkError("Paste a Slack message link first.");
3237
3429
  return;
3238
3430
  }
3239
- setStatus({ kind: "loading" });
3240
- try {
3241
- const result = await onLoadMessageRef.current({ link: trimmed });
3242
- if (result.ok) {
3243
- onLoaded(result);
3244
- return;
3245
- }
3246
- setStatus({ kind: "not-editable", reason: result.reason, blocks: result.blocks, oauthUrl: result.oauthUrl });
3247
- } catch (e) {
3248
- 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);
3249
3440
  }
3250
3441
  };
3251
3442
  const startFindSignIn = (oauthUrl) => {
@@ -3277,191 +3468,288 @@ function LoadMessageDialog({
3277
3468
  });
3278
3469
  }, POLL_INTERVAL_MS2);
3279
3470
  };
3280
- const notEditable = status.kind === "not-editable" ? status : null;
3281
- 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) } : {};
3282
3474
  return /* @__PURE__ */ jsx(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxs(
3283
3475
  DialogContent,
3284
3476
  {
3285
- 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",
3286
3478
  onOpenAutoFocus: (e) => {
3479
+ if (hasRecent) {
3480
+ return;
3481
+ }
3287
3482
  e.preventDefault();
3288
3483
  inputRef.current?.focus();
3289
3484
  },
3290
3485
  children: [
3291
- /* @__PURE__ */ jsxs(DialogHeader, { children: [
3486
+ /* @__PURE__ */ jsxs(DialogHeader, { className: "shrink-0", children: [
3292
3487
  /* @__PURE__ */ jsx(DialogTitle, { children: "Find an existing message" }),
3293
- /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-1.5 text-left", children: [
3294
- /* @__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.` }),
3295
- /* @__PURE__ */ jsxs(Tooltip, { children: [
3296
- /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
3297
- "button",
3298
- {
3299
- type: "button",
3300
- "aria-label": "How to copy a message link",
3301
- 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",
3302
- children: /* @__PURE__ */ jsx(Info, { className: "h-3.5 w-3.5" })
3303
- }
3304
- ) }),
3305
- /* @__PURE__ */ jsxs(TooltipContent, { className: "max-w-xs leading-relaxed", children: [
3306
- "In Slack, hover over the message and click the ",
3307
- /* @__PURE__ */ jsx("strong", { children: "\u22EE More actions" }),
3308
- " button (or right-click the message), then choose ",
3309
- /* @__PURE__ */ jsx("strong", { children: "Copy link" }),
3310
- "."
3311
- ] })
3312
- ] })
3313
- ] })
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.` })
3314
3489
  ] }),
3315
- /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-col gap-4", children: [
3316
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5", children: [
3317
- /* @__PURE__ */ jsx(Label, { htmlFor: "message-link", children: "Message link" }),
3318
- /* @__PURE__ */ jsx(
3319
- 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",
3320
3495
  {
3321
- ref: inputRef,
3322
- id: "message-link",
3323
- value: link,
3324
- onChange: (e) => {
3325
- setLink(e.target.value);
3326
- setSelectedRecent(null);
3327
- stopSignInPoll();
3328
- if (status.kind !== "idle" && status.kind !== "loading") {
3329
- setStatus({ kind: "idle" });
3330
- }
3331
- },
3332
- onKeyDown: (e) => {
3333
- if (e.key === "Enter" && status.kind !== "loading") {
3334
- e.preventDefault();
3335
- handleLoad();
3336
- }
3337
- },
3338
- placeholder: "https://your-workspace.slack.com/archives/C\u2026/p\u2026",
3339
- autoComplete: "off",
3340
- 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
+ ]
3341
3610
  }
3342
3611
  )
3343
3612
  ] }),
3344
- 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 }),
3345
- 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: [
3346
3713
  /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2", children: [
3347
3714
  /* @__PURE__ */ jsx(AlertTriangle, { className: "mt-0.5 h-3.5 w-3.5 shrink-0" }),
3348
- /* @__PURE__ */ jsx("span", { className: "flex-1", children: notEditable.reason })
3715
+ /* @__PURE__ */ jsx("span", { className: "flex-1", children: state.reason })
3349
3716
  ] }),
3350
- signInUrl && /* @__PURE__ */ jsx(SlackSignInButton, { onClick: () => startFindSignIn(signInUrl), polling: signInPolling }),
3351
- 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(
3352
3719
  Button,
3353
3720
  {
3354
3721
  type: "button",
3355
3722
  variant: "outline",
3356
3723
  size: "sm",
3357
3724
  className: "self-start",
3358
- onClick: () => onOpenAsNew(notEditable.blocks),
3725
+ onClick: () => onOpenAsNew(verdictBlocks),
3359
3726
  children: "Open as a new message instead"
3360
3727
  }
3361
3728
  )
3362
3729
  ] }),
3363
- hasRecent && /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-col gap-2", children: [
3364
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-xs text-muted-foreground", children: [
3365
- /* @__PURE__ */ jsx("span", { className: "h-px flex-1 bg-border" }),
3366
- "or pick a recent message",
3367
- /* @__PURE__ */ jsx("span", { className: "h-px flex-1 bg-border" })
3368
- ] }),
3369
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5", children: [
3370
- /* @__PURE__ */ jsx(Label, { htmlFor: "recent-channel-picker", children: "Channel" }),
3371
- channels === null && !channelsError && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Loading channels\u2026" }),
3372
- channelsError && /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive", children: channelsError }),
3373
- channels && channels.length === 0 && !channelsError && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "No public channels available." }),
3374
- channels && channels.length > 0 && /* @__PURE__ */ jsxs(
3375
- "select",
3376
- {
3377
- id: "recent-channel-picker",
3378
- value: channelId,
3379
- onChange: (e) => setChannelId(e.target.value),
3380
- 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",
3381
- children: [
3382
- /* @__PURE__ */ jsx("option", { value: "", disabled: true, children: "Select a channel\u2026" }),
3383
- channels.map((c) => /* @__PURE__ */ jsxs("option", { value: c.id, children: [
3384
- "#",
3385
- c.name
3386
- ] }, c.id))
3387
- ]
3388
- }
3389
- )
3390
- ] }),
3391
- !channelId && channels && channels.length > 0 && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Select a channel to see recent messages." }),
3392
- channelId && recent === null && !recentError && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Loading recent messages\u2026" }),
3393
- channelId && recentError && /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive", children: recentError }),
3394
- 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." }),
3395
- 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) => {
3396
- const asUser = (m.editableVia ?? "bot") === "user";
3397
- const authorName = m.username || (asUser ? "You" : "App bot");
3398
- const safeIcon = isSafeImageSrc(m.iconUrl) ? m.iconUrl : void 0;
3399
- const isSelected = selectedRecent?.channelId === m.channelId && selectedRecent?.ts === m.ts;
3400
- return /* @__PURE__ */ jsxs(
3401
- "button",
3402
- {
3403
- type: "button",
3404
- "aria-pressed": isSelected,
3405
- onClick: () => {
3406
- setSelectedRecent(m);
3407
- setLink("");
3408
- if (status.kind !== "idle" && status.kind !== "loading") {
3409
- setStatus({ kind: "idle" });
3410
- }
3411
- },
3412
- className: cn(
3413
- // Inset rings so the scroll container's overflow clip
3414
- // can't shave the corners off the selected outline.
3415
- "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",
3416
- isSelected && "border-primary! bg-accent ring-1 ring-inset ring-primary"
3417
- ),
3418
- children: [
3419
- safeIcon ? /* @__PURE__ */ jsx("img", { src: safeIcon, alt: "", className: "h-9 w-9 shrink-0 rounded-md object-cover" }) : /* @__PURE__ */ jsx(
3420
- "span",
3421
- {
3422
- "aria-hidden": true,
3423
- className: "flex h-9 w-9 shrink-0 items-center justify-center rounded-md bg-muted text-sm font-semibold text-muted-foreground",
3424
- children: authorName.charAt(0).toUpperCase()
3425
- }
3426
- ),
3427
- /* @__PURE__ */ jsxs("span", { className: "flex min-w-0 flex-col gap-0.5", children: [
3428
- /* @__PURE__ */ jsxs("span", { className: "flex min-w-0 items-center gap-1.5", children: [
3429
- /* @__PURE__ */ jsx("span", { className: "truncate text-sm font-semibold text-foreground", children: authorName }),
3430
- /* @__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" })
3431
- ] }),
3432
- /* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground", children: [
3433
- new Date(Number(m.ts) * 1e3).toLocaleString(),
3434
- " ",
3435
- /* @__PURE__ */ jsxs("span", { className: "font-mono", children: [
3436
- "(",
3437
- m.ts,
3438
- ")"
3439
- ] })
3440
- ] }),
3441
- /* @__PURE__ */ jsx("span", { className: "line-clamp-2 text-sm text-foreground", children: previewText(m) })
3442
- ] })
3443
- ]
3444
- },
3445
- `${m.channelId}:${m.ts}`
3446
- );
3447
- }) })
3448
- ] })
3449
- ] }),
3450
- /* @__PURE__ */ jsxs(DialogFooter, { children: [
3451
- /* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", onClick: () => onOpenChange(false), children: "Cancel" }),
3452
- /* @__PURE__ */ jsx(
3453
- 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,
3454
3738
  {
3455
- type: "button",
3456
- onClick: handleLoad,
3457
- disabled: status.kind === "loading" || !selectedRecent && !link.trim(),
3458
- 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)
3459
3746
  }
3460
- )
3461
- ] })
3462
- ]
3463
- }
3464
- ) });
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
+ ] });
3465
3753
  }
3466
3754
  var PALETTE_DRAG_PREFIX = "palette:";
3467
3755
  function parsePaletteDragId(id) {
@@ -5654,7 +5942,7 @@ function HeaderEditor({ block, onChange }) {
5654
5942
  ] })
5655
5943
  }
5656
5944
  ),
5657
- /* @__PURE__ */ jsx(EditorField, { label: "Level", help: "Optional. Builder-only extension; Slack's API ignores this value.", children: /* @__PURE__ */ jsxs(
5945
+ /* @__PURE__ */ jsx(EditorField, { label: "Level", help: "Optional. Sets the heading size Slack renders, from H1 (largest) to H4.", children: /* @__PURE__ */ jsxs(
5658
5946
  RadioGroup,
5659
5947
  {
5660
5948
  value: level === void 0 ? "default" : String(level),
@@ -8684,60 +8972,6 @@ function dispatch(block, onChange) {
8684
8972
  return null;
8685
8973
  }
8686
8974
  }
8687
- function SlackBlockPreview({
8688
- block,
8689
- hooks,
8690
- theme = "light"
8691
- }) {
8692
- const rootRef = useRef(null);
8693
- const safeBlock = useMemo(() => sanitizeBlock(block), [block]);
8694
- useEffect(() => {
8695
- const root = rootRef.current;
8696
- if (!root) return;
8697
- for (const wrapper of root.querySelectorAll(
8698
- ".slack_blocks_to_jsx__image_media_trigger, .slack_blocks_to_jsx__video_title"
8699
- )) {
8700
- for (const btn of wrapper.querySelectorAll("button:not([aria-label])")) {
8701
- const title = wrapper.querySelector(".slack_blocks_to_jsx__image_title")?.textContent?.trim();
8702
- btn.setAttribute("aria-label", title ? `Toggle ${title}` : "Toggle media");
8703
- }
8704
- }
8705
- for (const a of root.querySelectorAll("a[href]")) {
8706
- const href = a.getAttribute("href");
8707
- if (!isSafeHref(href)) {
8708
- a.setAttribute("href", "#");
8709
- a.setAttribute("data-bk-blocked-href", "1");
8710
- }
8711
- }
8712
- for (const img of root.querySelectorAll("img[src]")) {
8713
- const src = img.getAttribute("src");
8714
- if (!isSafeImageSrc(src)) {
8715
- img.removeAttribute("src");
8716
- img.setAttribute("data-bk-blocked-src", "1");
8717
- }
8718
- }
8719
- });
8720
- return (
8721
- // The library scopes all of its CSS under `#slack_blocks_to_jsx` and
8722
- // depends on the `slack_blocks_to_jsx styles_enabled` classes plus the
8723
- // `data-theme` attribute for its button / mention / divider styling
8724
- // (e.g. button reset + `bg-green-primary` for primary). `<Message
8725
- // withoutWrapper>` skips that wrapper entirely, so we re-create it
8726
- // here without the avatar / timestamp chrome.
8727
- /* @__PURE__ */ jsx("div", { ref: rootRef, id: "slack_blocks_to_jsx", "data-theme": theme, className: "slack_blocks_to_jsx styles_enabled", children: /* @__PURE__ */ jsx(
8728
- Message,
8729
- {
8730
- time: /* @__PURE__ */ new Date(),
8731
- name: "",
8732
- logo: "",
8733
- withoutWrapper: true,
8734
- theme,
8735
- blocks: [safeBlock],
8736
- hooks
8737
- }
8738
- ) })
8739
- );
8740
- }
8741
8975
  var BLOCK_TYPE_LABELS = {
8742
8976
  section: "Section",
8743
8977
  header: "Header",
@@ -9360,50 +9594,16 @@ function Surface({
9360
9594
  {
9361
9595
  "aria-label": "Block preview",
9362
9596
  className: cn("flex min-w-0 flex-1 flex-col overflow-y-auto bg-muted p-3 sm:p-6"),
9363
- 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 }) })
9364
- }
9365
- );
9366
- }
9367
- function MessageFrame({
9368
- workspaceName,
9369
- authorName,
9370
- authorIcon,
9371
- isDark,
9372
- children
9373
- }) {
9374
- const displayName = authorName ?? workspaceName ?? "Your app";
9375
- const initial = displayName.slice(0, 1).toUpperCase();
9376
- const safeIcon = authorIcon && isSafeImageSrc(authorIcon) ? authorIcon : null;
9377
- return /* @__PURE__ */ jsxs(
9378
- "div",
9379
- {
9380
- className: cn(
9381
- "flex flex-col overflow-hidden rounded-md border",
9382
- isDark ? "border-[#2c2d30] bg-[#1a1d21]" : "border-[#e8e8e8] bg-white"
9383
- ),
9384
- children: [
9385
- /* @__PURE__ */ jsxs(
9386
- "div",
9387
- {
9388
- className: cn("flex items-center gap-2 px-5 pt-3 pb-1 text-xs", isDark ? "text-white/60" : "text-[#616061]"),
9389
- children: [
9390
- safeIcon ? /* @__PURE__ */ jsx("img", { src: safeIcon, alt: "", className: "h-7 w-7 shrink-0 rounded object-cover" }) : /* @__PURE__ */ jsx(
9391
- "span",
9392
- {
9393
- className: cn(
9394
- "inline-flex h-7 w-7 items-center justify-center rounded text-[12px] font-semibold",
9395
- isDark ? "bg-white/10 text-white" : "bg-[#4a154b]/10 text-[#1d1c1d]"
9396
- ),
9397
- children: initial
9398
- }
9399
- ),
9400
- /* @__PURE__ */ jsx("span", { className: cn("font-bold text-sm", isDark ? "text-white" : "text-[#1d1c1d]"), children: displayName }),
9401
- /* @__PURE__ */ jsx("span", { children: "10:37 AM" })
9402
- ]
9403
- }
9404
- ),
9405
- children
9406
- ]
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
+ ) })
9407
9607
  }
9408
9608
  );
9409
9609
  }
@@ -10492,6 +10692,8 @@ function BlockKitchen(props) {
10492
10692
  onLoadMessage: loading.onLoadMessage,
10493
10693
  loadRecentMessages: loading.loadRecentMessages,
10494
10694
  loadChannels: recentChannelSource,
10695
+ previewHooks: mergedPreviewHooks,
10696
+ previewTheme,
10495
10697
  onLoaded: (result) => {
10496
10698
  resetAll(result.blocks);
10497
10699
  setEditTarget(toLoadedMessage(result));