@tightknitai/block-kitchen 0.8.10 → 0.8.11

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, GripVertical, Sun, Moon, MessageSquare, AppWindow, Home, Plus, ChevronDown, BookOpen, ExternalLink, AlertTriangle, Trash2, Code2, MoreHorizontal, Send, Search, CheckCircle2, Check, ChevronRight, LayoutGrid, ArrowUp, ArrowDown, Pencil, Copy, Type, Bold, Italic, Strikethrough, Underline, Code, List, ListOrdered, Quote, Undo2, Redo2, 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, GripVertical, Sun, Moon, MessageSquare, AppWindow, Home, Pencil, Plus, ChevronDown, BookOpen, ExternalLink, AlertTriangle, Trash2, Code2, Send, Search, Info, CheckCircle2, Check, ChevronRight, LayoutGrid, ArrowUp, ArrowDown, Copy, Type, Bold, Italic, Strikethrough, Underline, Code, List, ListOrdered, Quote, Undo2, Redo2, Smile, Link as Link$1 } from 'lucide-react';
4
4
  import * as React from 'react';
5
5
  import { createContext, useMemo, useState, useCallback, useRef, useEffect, useId, useContext } from 'react';
6
6
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
@@ -2694,6 +2694,91 @@ function JsonDrawer({
2694
2694
  ] })
2695
2695
  ] }) });
2696
2696
  }
2697
+ var buttonVariants = cva(
2698
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium cursor-pointer transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
2699
+ {
2700
+ variants: {
2701
+ variant: {
2702
+ default: "bg-primary text-primary-foreground shadow hover:bg-primary/90",
2703
+ destructive: "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
2704
+ outline: "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
2705
+ secondary: "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
2706
+ ghost: "hover:bg-accent hover:text-accent-foreground",
2707
+ link: "text-primary underline-offset-4 hover:underline"
2708
+ },
2709
+ size: {
2710
+ default: "h-9 px-4 py-2",
2711
+ sm: "h-8 rounded-md px-3 text-xs",
2712
+ lg: "h-10 rounded-md px-8",
2713
+ icon: "h-9 w-9"
2714
+ }
2715
+ },
2716
+ defaultVariants: {
2717
+ variant: "default",
2718
+ size: "default"
2719
+ }
2720
+ }
2721
+ );
2722
+ var Button = React.forwardRef(
2723
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
2724
+ const Comp = asChild ? Slot : "button";
2725
+ return /* @__PURE__ */ jsx(Comp, { className: cn(buttonVariants({ variant, size, className })), ref, ...props });
2726
+ }
2727
+ );
2728
+ Button.displayName = "Button";
2729
+ var Dialog = DialogPrimitive.Root;
2730
+ var DialogPortal = DialogPrimitive.Portal;
2731
+ var DialogOverlay = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
2732
+ DialogPrimitive.Overlay,
2733
+ {
2734
+ ref,
2735
+ className: cn(
2736
+ "bk-portal-content fixed inset-0 z-50 bg-black/50 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
2737
+ className
2738
+ ),
2739
+ ...props
2740
+ }
2741
+ ));
2742
+ DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
2743
+ var DialogContent = React.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ jsxs(DialogPortal, { children: [
2744
+ /* @__PURE__ */ jsx(DialogOverlay, {}),
2745
+ /* @__PURE__ */ jsxs(
2746
+ DialogPrimitive.Content,
2747
+ {
2748
+ ref,
2749
+ className: cn(
2750
+ "bk-portal-content fixed left-1/2 top-1/2 z-50 grid w-full max-w-lg -translate-x-1/2 -translate-y-1/2 gap-4 border bg-background p-6 shadow-lg duration-200 sm:rounded-lg",
2751
+ className
2752
+ ),
2753
+ ...props,
2754
+ children: [
2755
+ children,
2756
+ /* @__PURE__ */ jsxs(DialogPrimitive.Close, { className: "absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-1 focus:ring-ring", children: [
2757
+ /* @__PURE__ */ jsx(X, { className: "h-4 w-4" }),
2758
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: "Close" })
2759
+ ] })
2760
+ ]
2761
+ }
2762
+ )
2763
+ ] }));
2764
+ DialogContent.displayName = DialogPrimitive.Content.displayName;
2765
+ function DialogHeader({ className, ...props }) {
2766
+ return /* @__PURE__ */ jsx("div", { className: cn("flex flex-col space-y-1.5 text-center sm:text-left", className), ...props });
2767
+ }
2768
+ function DialogFooter({ className, ...props }) {
2769
+ return /* @__PURE__ */ jsx("div", { className: cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className), ...props });
2770
+ }
2771
+ var DialogTitle = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
2772
+ DialogPrimitive.Title,
2773
+ {
2774
+ ref,
2775
+ className: cn("text-lg font-semibold leading-none tracking-tight", className),
2776
+ ...props
2777
+ }
2778
+ ));
2779
+ DialogTitle.displayName = DialogPrimitive.Title.displayName;
2780
+ var DialogDescription = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(DialogPrimitive.Description, { ref, className: cn("text-sm text-muted-foreground", className), ...props }));
2781
+ DialogDescription.displayName = DialogPrimitive.Description.displayName;
2697
2782
  var Input = React.forwardRef(
2698
2783
  ({ className, type, ...props }, ref) => /* @__PURE__ */ jsx(
2699
2784
  "input",
@@ -2709,6 +2794,213 @@ var Input = React.forwardRef(
2709
2794
  )
2710
2795
  );
2711
2796
  Input.displayName = "Input";
2797
+ var Label = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
2798
+ LabelPrimitive.Root,
2799
+ {
2800
+ ref,
2801
+ className: cn("text-sm font-medium leading-none text-foreground", className),
2802
+ ...props
2803
+ }
2804
+ ));
2805
+ Label.displayName = LabelPrimitive.Root.displayName;
2806
+ function recentToResult(msg) {
2807
+ return {
2808
+ ok: true,
2809
+ channelId: msg.channelId,
2810
+ channelName: msg.channelName,
2811
+ ts: msg.ts,
2812
+ blocks: msg.blocks,
2813
+ editableVia: msg.editableVia ?? "bot",
2814
+ workspaceName: msg.workspaceName,
2815
+ username: msg.username,
2816
+ iconUrl: msg.iconUrl
2817
+ };
2818
+ }
2819
+ function LoadMessageDialog({
2820
+ open,
2821
+ onOpenChange,
2822
+ onLoadMessage,
2823
+ loadRecentMessages,
2824
+ onLoaded,
2825
+ onOpenAsNew
2826
+ }) {
2827
+ const [link, setLink] = useState("");
2828
+ const [status, setStatus] = useState({ kind: "idle" });
2829
+ const [recent, setRecent] = useState(null);
2830
+ const [recentError, setRecentError] = useState(null);
2831
+ const inputRef = useRef(null);
2832
+ const onLoadMessageRef = useRef(onLoadMessage);
2833
+ const loadRecentMessagesRef = useRef(loadRecentMessages);
2834
+ useEffect(() => {
2835
+ onLoadMessageRef.current = onLoadMessage;
2836
+ loadRecentMessagesRef.current = loadRecentMessages;
2837
+ });
2838
+ const hasRecent = !!loadRecentMessages;
2839
+ useEffect(() => {
2840
+ if (!open) {
2841
+ return;
2842
+ }
2843
+ setLink("");
2844
+ setStatus({ kind: "idle" });
2845
+ if (!loadRecentMessagesRef.current) {
2846
+ setRecent([]);
2847
+ setRecentError(null);
2848
+ return;
2849
+ }
2850
+ setRecent(null);
2851
+ setRecentError(null);
2852
+ let cancelled = false;
2853
+ loadRecentMessagesRef.current().then((list) => {
2854
+ if (!cancelled) {
2855
+ setRecent(list);
2856
+ }
2857
+ }).catch((e) => {
2858
+ if (!cancelled) {
2859
+ setRecentError(e instanceof Error ? e.message : "Failed to load recent messages.");
2860
+ }
2861
+ });
2862
+ return () => {
2863
+ cancelled = true;
2864
+ };
2865
+ }, [open]);
2866
+ const handleLoad = async () => {
2867
+ const trimmed = link.trim();
2868
+ if (!trimmed) {
2869
+ setStatus({ kind: "error", error: "Paste a Slack message link first." });
2870
+ return;
2871
+ }
2872
+ setStatus({ kind: "loading" });
2873
+ try {
2874
+ const result = await onLoadMessageRef.current({ link: trimmed });
2875
+ if (result.ok) {
2876
+ onLoaded(result);
2877
+ return;
2878
+ }
2879
+ setStatus({ kind: "not-editable", reason: result.reason, blocks: result.blocks });
2880
+ } catch (e) {
2881
+ setStatus({ kind: "error", error: e instanceof Error ? e.message : "Failed to load message." });
2882
+ }
2883
+ };
2884
+ const notEditable = status.kind === "not-editable" ? status : null;
2885
+ return /* @__PURE__ */ jsx(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxs(
2886
+ DialogContent,
2887
+ {
2888
+ className: "w-[calc(100vw-1.5rem)] max-w-lg rounded-lg",
2889
+ onOpenAutoFocus: (e) => {
2890
+ e.preventDefault();
2891
+ inputRef.current?.focus();
2892
+ },
2893
+ children: [
2894
+ /* @__PURE__ */ jsxs(DialogHeader, { children: [
2895
+ /* @__PURE__ */ jsx(DialogTitle, { children: "Edit an existing message" }),
2896
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-1.5", children: [
2897
+ /* @__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.` }),
2898
+ /* @__PURE__ */ jsxs(Tooltip, { children: [
2899
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
2900
+ "button",
2901
+ {
2902
+ type: "button",
2903
+ "aria-label": "How to copy a message link",
2904
+ 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",
2905
+ children: /* @__PURE__ */ jsx(Info, { className: "h-3.5 w-3.5" })
2906
+ }
2907
+ ) }),
2908
+ /* @__PURE__ */ jsxs(TooltipContent, { className: "max-w-xs leading-relaxed", children: [
2909
+ "In Slack, hover over the message and click the ",
2910
+ /* @__PURE__ */ jsx("strong", { children: "\u22EE More actions" }),
2911
+ " button (or right-click the message), then choose ",
2912
+ /* @__PURE__ */ jsx("strong", { children: "Copy link" }),
2913
+ "."
2914
+ ] })
2915
+ ] })
2916
+ ] })
2917
+ ] }),
2918
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4", children: [
2919
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5", children: [
2920
+ /* @__PURE__ */ jsx(Label, { htmlFor: "message-link", children: "Message link" }),
2921
+ /* @__PURE__ */ jsx(
2922
+ Input,
2923
+ {
2924
+ ref: inputRef,
2925
+ id: "message-link",
2926
+ value: link,
2927
+ onChange: (e) => {
2928
+ setLink(e.target.value);
2929
+ if (status.kind !== "idle" && status.kind !== "loading") {
2930
+ setStatus({ kind: "idle" });
2931
+ }
2932
+ },
2933
+ onKeyDown: (e) => {
2934
+ if (e.key === "Enter" && status.kind !== "loading") {
2935
+ e.preventDefault();
2936
+ handleLoad();
2937
+ }
2938
+ },
2939
+ placeholder: "https://your-workspace.slack.com/archives/C\u2026/p\u2026",
2940
+ autoComplete: "off",
2941
+ spellCheck: false
2942
+ }
2943
+ )
2944
+ ] }),
2945
+ 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 }),
2946
+ 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: [
2947
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2", children: [
2948
+ /* @__PURE__ */ jsx(AlertTriangle, { className: "mt-0.5 h-3.5 w-3.5 shrink-0" }),
2949
+ /* @__PURE__ */ jsx("span", { className: "flex-1", children: notEditable.reason })
2950
+ ] }),
2951
+ /* @__PURE__ */ jsx(
2952
+ Button,
2953
+ {
2954
+ type: "button",
2955
+ variant: "outline",
2956
+ size: "sm",
2957
+ className: "self-start",
2958
+ onClick: () => onOpenAsNew(notEditable.blocks),
2959
+ children: "Open as a new message instead"
2960
+ }
2961
+ )
2962
+ ] }),
2963
+ hasRecent && /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
2964
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-xs text-muted-foreground", children: [
2965
+ /* @__PURE__ */ jsx("span", { className: "h-px flex-1 bg-border" }),
2966
+ "or pick a recent message",
2967
+ /* @__PURE__ */ jsx("span", { className: "h-px flex-1 bg-border" })
2968
+ ] }),
2969
+ recent === null && !recentError && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Loading recent messages\u2026" }),
2970
+ recentError && /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive", children: recentError }),
2971
+ recent && recent.length === 0 && !recentError && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "No recent messages from this app." }),
2972
+ recent && recent.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex max-h-48 flex-col gap-1 overflow-y-auto", children: recent.map((m) => {
2973
+ const asUser = (m.editableVia ?? "bot") === "user";
2974
+ return /* @__PURE__ */ jsxs(
2975
+ "button",
2976
+ {
2977
+ type: "button",
2978
+ onClick: () => onLoaded(recentToResult(m)),
2979
+ className: "flex flex-col gap-0.5 rounded-md border border-input bg-background px-3 py-2 text-left text-sm hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
2980
+ children: [
2981
+ /* @__PURE__ */ jsxs("span", { className: "flex items-baseline justify-between gap-2", children: [
2982
+ /* @__PURE__ */ jsxs("span", { className: "flex min-w-0 items-baseline gap-1.5", children: [
2983
+ /* @__PURE__ */ jsx("span", { className: "truncate font-medium", children: m.channelName ? `#${m.channelName}` : m.channelId }),
2984
+ /* @__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" })
2985
+ ] }),
2986
+ /* @__PURE__ */ jsx("span", { className: "shrink-0 font-mono text-xs text-muted-foreground", children: m.ts })
2987
+ ] }),
2988
+ m.label && /* @__PURE__ */ jsx("span", { className: "truncate text-xs text-muted-foreground", children: m.label })
2989
+ ]
2990
+ },
2991
+ `${m.channelId}:${m.ts}`
2992
+ );
2993
+ }) })
2994
+ ] })
2995
+ ] }),
2996
+ /* @__PURE__ */ jsxs(DialogFooter, { children: [
2997
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", onClick: () => onOpenChange(false), children: "Cancel" }),
2998
+ /* @__PURE__ */ jsx(Button, { type: "button", onClick: handleLoad, disabled: status.kind === "loading" || !link.trim(), children: status.kind === "loading" ? "Loading\u2026" : "Load message" })
2999
+ ] })
3000
+ ]
3001
+ }
3002
+ ) });
3003
+ }
2712
3004
  var PALETTE_DRAG_PREFIX = "palette:";
2713
3005
  function parsePaletteDragId(id) {
2714
3006
  if (typeof id !== "string" || !id.startsWith(PALETTE_DRAG_PREFIX)) {
@@ -2908,100 +3200,6 @@ function PaletteItem({
2908
3200
  }
2909
3201
  );
2910
3202
  }
2911
- var buttonVariants = cva(
2912
- "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium cursor-pointer transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
2913
- {
2914
- variants: {
2915
- variant: {
2916
- default: "bg-primary text-primary-foreground shadow hover:bg-primary/90",
2917
- destructive: "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
2918
- outline: "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
2919
- secondary: "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
2920
- ghost: "hover:bg-accent hover:text-accent-foreground",
2921
- link: "text-primary underline-offset-4 hover:underline"
2922
- },
2923
- size: {
2924
- default: "h-9 px-4 py-2",
2925
- sm: "h-8 rounded-md px-3 text-xs",
2926
- lg: "h-10 rounded-md px-8",
2927
- icon: "h-9 w-9"
2928
- }
2929
- },
2930
- defaultVariants: {
2931
- variant: "default",
2932
- size: "default"
2933
- }
2934
- }
2935
- );
2936
- var Button = React.forwardRef(
2937
- ({ className, variant, size, asChild = false, ...props }, ref) => {
2938
- const Comp = asChild ? Slot : "button";
2939
- return /* @__PURE__ */ jsx(Comp, { className: cn(buttonVariants({ variant, size, className })), ref, ...props });
2940
- }
2941
- );
2942
- Button.displayName = "Button";
2943
- var Dialog = DialogPrimitive.Root;
2944
- var DialogPortal = DialogPrimitive.Portal;
2945
- var DialogOverlay = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
2946
- DialogPrimitive.Overlay,
2947
- {
2948
- ref,
2949
- className: cn(
2950
- "bk-portal-content fixed inset-0 z-50 bg-black/50 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
2951
- className
2952
- ),
2953
- ...props
2954
- }
2955
- ));
2956
- DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
2957
- var DialogContent = React.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ jsxs(DialogPortal, { children: [
2958
- /* @__PURE__ */ jsx(DialogOverlay, {}),
2959
- /* @__PURE__ */ jsxs(
2960
- DialogPrimitive.Content,
2961
- {
2962
- ref,
2963
- className: cn(
2964
- "bk-portal-content fixed left-1/2 top-1/2 z-50 grid w-full max-w-lg -translate-x-1/2 -translate-y-1/2 gap-4 border bg-background p-6 shadow-lg duration-200 sm:rounded-lg",
2965
- className
2966
- ),
2967
- ...props,
2968
- children: [
2969
- children,
2970
- /* @__PURE__ */ jsxs(DialogPrimitive.Close, { className: "absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-1 focus:ring-ring", children: [
2971
- /* @__PURE__ */ jsx(X, { className: "h-4 w-4" }),
2972
- /* @__PURE__ */ jsx("span", { className: "sr-only", children: "Close" })
2973
- ] })
2974
- ]
2975
- }
2976
- )
2977
- ] }));
2978
- DialogContent.displayName = DialogPrimitive.Content.displayName;
2979
- function DialogHeader({ className, ...props }) {
2980
- return /* @__PURE__ */ jsx("div", { className: cn("flex flex-col space-y-1.5 text-center sm:text-left", className), ...props });
2981
- }
2982
- function DialogFooter({ className, ...props }) {
2983
- return /* @__PURE__ */ jsx("div", { className: cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className), ...props });
2984
- }
2985
- var DialogTitle = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
2986
- DialogPrimitive.Title,
2987
- {
2988
- ref,
2989
- className: cn("text-lg font-semibold leading-none tracking-tight", className),
2990
- ...props
2991
- }
2992
- ));
2993
- DialogTitle.displayName = DialogPrimitive.Title.displayName;
2994
- var DialogDescription = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(DialogPrimitive.Description, { ref, className: cn("text-sm text-muted-foreground", className), ...props }));
2995
- DialogDescription.displayName = DialogPrimitive.Description.displayName;
2996
- var Label = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
2997
- LabelPrimitive.Root,
2998
- {
2999
- ref,
3000
- className: cn("text-sm font-medium leading-none text-foreground", className),
3001
- ...props
3002
- }
3003
- ));
3004
- Label.displayName = LabelPrimitive.Root.displayName;
3005
3203
  function SendDialog({
3006
3204
  open,
3007
3205
  onOpenChange,
@@ -4142,7 +4340,7 @@ function TextItemCard({
4142
4340
  id: `ctx-text-${index}`,
4143
4341
  value: element.text,
4144
4342
  rows: 2,
4145
- placeholder: "e.g. Posted by @stephen",
4343
+ placeholder: "e.g. Posted by @riley",
4146
4344
  onChange: (e) => onChange({ ...element, text: e.target.value })
4147
4345
  }
4148
4346
  ) }),
@@ -4227,7 +4425,7 @@ function ImageItemCard({
4227
4425
  {
4228
4426
  id: `ctx-img-alt-${index}`,
4229
4427
  value: element.alt_text,
4230
- placeholder: "e.g. Stephen's avatar",
4428
+ placeholder: "e.g. Riley's avatar",
4231
4429
  onChange: (e) => onChange({ ...element, alt_text: e.target.value })
4232
4430
  }
4233
4431
  ) })
@@ -8560,6 +8758,8 @@ var SURFACE_DROPPABLE_ID = "builder-surface";
8560
8758
  function Surface({
8561
8759
  blocks,
8562
8760
  workspaceName,
8761
+ authorName,
8762
+ authorIcon,
8563
8763
  previewHooks,
8564
8764
  previewTheme,
8565
8765
  previewSurface = "message",
@@ -8635,16 +8835,20 @@ function Surface({
8635
8835
  {
8636
8836
  "aria-label": "Block preview",
8637
8837
  className: cn("flex min-w-0 flex-1 flex-col overflow-y-auto bg-muted p-3 sm:p-6"),
8638
- 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, isDark, children: blocksList }) })
8838
+ 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 }) })
8639
8839
  }
8640
8840
  );
8641
8841
  }
8642
8842
  function MessageFrame({
8643
8843
  workspaceName,
8844
+ authorName,
8845
+ authorIcon,
8644
8846
  isDark,
8645
8847
  children
8646
8848
  }) {
8647
- const initial = (workspaceName ?? "A").slice(0, 1).toUpperCase();
8849
+ const displayName = authorName ?? workspaceName ?? "Your app";
8850
+ const initial = displayName.slice(0, 1).toUpperCase();
8851
+ const safeIcon = authorIcon && isSafeImageSrc(authorIcon) ? authorIcon : null;
8648
8852
  return /* @__PURE__ */ jsxs(
8649
8853
  "div",
8650
8854
  {
@@ -8658,7 +8862,7 @@ function MessageFrame({
8658
8862
  {
8659
8863
  className: cn("flex items-center gap-2 px-5 pt-3 pb-1 text-xs", isDark ? "text-white/60" : "text-[#616061]"),
8660
8864
  children: [
8661
- /* @__PURE__ */ jsx(
8865
+ safeIcon ? /* @__PURE__ */ jsx("img", { src: safeIcon, alt: "", className: "h-7 w-7 shrink-0 rounded object-cover" }) : /* @__PURE__ */ jsx(
8662
8866
  "span",
8663
8867
  {
8664
8868
  className: cn(
@@ -8668,17 +8872,7 @@ function MessageFrame({
8668
8872
  children: initial
8669
8873
  }
8670
8874
  ),
8671
- /* @__PURE__ */ jsx("span", { className: cn("font-bold text-sm", isDark ? "text-white" : "text-[#1d1c1d]"), children: workspaceName ?? "Your app" }),
8672
- /* @__PURE__ */ jsx(
8673
- "span",
8674
- {
8675
- className: cn(
8676
- "rounded px-1 text-[10px] font-semibold",
8677
- isDark ? "bg-white/10 text-white/70" : "bg-[#f3f3f3] text-[#616061]"
8678
- ),
8679
- children: "APP"
8680
- }
8681
- ),
8875
+ /* @__PURE__ */ jsx("span", { className: cn("font-bold text-sm", isDark ? "text-white" : "text-[#1d1c1d]"), children: displayName }),
8682
8876
  /* @__PURE__ */ jsx("span", { children: "10:37 AM" })
8683
8877
  ]
8684
8878
  }
@@ -8880,6 +9074,14 @@ var SURFACE_OPTIONS = [
8880
9074
  ];
8881
9075
  var DEFAULT_DOCS_HREF = "https://docs.slack.dev/reference/block-kit/blocks";
8882
9076
  var DEFAULT_DOCS_LABEL = "Docs";
9077
+ function formatMessageTs(ts) {
9078
+ const seconds = Number.parseFloat(ts);
9079
+ if (!Number.isFinite(seconds)) return ts;
9080
+ const date = new Date(seconds * 1e3);
9081
+ if (Number.isNaN(date.getTime())) return ts;
9082
+ return date.toLocaleString(void 0, { dateStyle: "medium", timeStyle: "short" });
9083
+ }
9084
+ var SEND_MENU_ITEM = "flex w-full cursor-pointer items-center gap-2 rounded px-2 py-1.5 text-left text-sm text-foreground transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:bg-accent focus-visible:text-accent-foreground focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50";
8883
9085
  function Toolbar2({
8884
9086
  onClear,
8885
9087
  onOpenJson,
@@ -8896,7 +9098,14 @@ function Toolbar2({
8896
9098
  showThemeControl = true,
8897
9099
  docsLink,
8898
9100
  errorCount,
8899
- sendButtonLabel = "Send"
9101
+ sendButtonLabel = "Review & send",
9102
+ editingEnabled = false,
9103
+ editBadge,
9104
+ onOpenLoad,
9105
+ onOpenUpdate,
9106
+ onExitEdit,
9107
+ loadButtonLabel = "Load message",
9108
+ updateButtonLabel = "Review & update"
8900
9109
  }) {
8901
9110
  const activeTheme = THEME_OPTIONS.find((t) => t.value === previewTheme) ?? THEME_OPTIONS[0];
8902
9111
  const activeSurface = SURFACE_OPTIONS.find((s) => s.value === previewSurface) ?? SURFACE_OPTIONS[0];
@@ -8906,184 +9115,210 @@ function Toolbar2({
8906
9115
  const docsLabel = docsLink === false ? null : docsLink?.label ?? DEFAULT_DOCS_LABEL;
8907
9116
  const [surfaceMenuOpen, setSurfaceMenuOpen] = useState(false);
8908
9117
  const [themeMenuOpen, setThemeMenuOpen] = useState(false);
8909
- const [moreMenuOpen, setMoreMenuOpen] = useState(false);
8910
- return /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-between gap-x-2 gap-y-1 border-b bg-background px-2 py-1.5 sm:px-3 sm:py-2", children: [
8911
- /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 items-center gap-1 sm:gap-2", children: [
8912
- onOpenPalette ? /* @__PURE__ */ jsxs(
8913
- Button,
8914
- {
8915
- type: "button",
8916
- variant: "ghost",
8917
- size: "sm",
8918
- onClick: onOpenPalette,
8919
- className: "md:hidden",
8920
- "aria-label": "Add a block",
8921
- children: [
8922
- /* @__PURE__ */ jsx(Plus, { className: "h-4 w-4" }),
8923
- /* @__PURE__ */ jsx("span", { children: "Blocks" })
8924
- ]
8925
- }
8926
- ) : null,
8927
- showSurfaceControl ? /* @__PURE__ */ jsxs(Popover, { open: surfaceMenuOpen, onOpenChange: setSurfaceMenuOpen, children: [
8928
- /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(Button, { type: "button", variant: "ghost", size: "sm", "aria-label": `Preview surface: ${activeSurface.label}`, children: [
8929
- /* @__PURE__ */ jsx(activeSurface.Icon, { className: "h-3.5 w-3.5" }),
8930
- /* @__PURE__ */ jsx("span", { className: "hidden sm:inline", children: activeSurface.label }),
8931
- /* @__PURE__ */ jsx(ChevronDown, { className: "h-3.5 w-3.5 opacity-60" })
8932
- ] }) }),
8933
- /* @__PURE__ */ jsx(PopoverContent, { align: "start", className: "w-36 p-1", children: /* @__PURE__ */ jsx(
8934
- Menu,
9118
+ const [sendMenuOpen, setSendMenuOpen] = useState(false);
9119
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
9120
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-between gap-x-2 gap-y-1 border-b bg-background px-2 py-1.5 sm:px-3 sm:py-2", children: [
9121
+ /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 items-center gap-1 sm:gap-2", children: [
9122
+ editingEnabled && !editBadge ? /* @__PURE__ */ jsxs(Button, { type: "button", size: "sm", onClick: onOpenLoad, "aria-label": loadButtonLabel, children: [
9123
+ /* @__PURE__ */ jsx(Pencil, { className: "h-3.5 w-3.5" }),
9124
+ /* @__PURE__ */ jsx("span", { className: "hidden md:inline", children: loadButtonLabel })
9125
+ ] }) : null,
9126
+ onOpenPalette ? /* @__PURE__ */ jsxs(
9127
+ Button,
8935
9128
  {
8936
- ariaLabel: "Preview surface",
8937
- options: surfaceOptions,
8938
- value: previewSurface,
8939
- onChange: (next) => {
8940
- onPreviewSurfaceChange(next);
8941
- setSurfaceMenuOpen(false);
9129
+ type: "button",
9130
+ variant: "ghost",
9131
+ size: "sm",
9132
+ onClick: onOpenPalette,
9133
+ className: "md:hidden",
9134
+ "aria-label": "Add a block",
9135
+ children: [
9136
+ /* @__PURE__ */ jsx(Plus, { className: "h-4 w-4" }),
9137
+ /* @__PURE__ */ jsx("span", { children: "Blocks" })
9138
+ ]
9139
+ }
9140
+ ) : null,
9141
+ showSurfaceControl ? /* @__PURE__ */ jsxs(Popover, { open: surfaceMenuOpen, onOpenChange: setSurfaceMenuOpen, children: [
9142
+ /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(Button, { type: "button", variant: "ghost", size: "sm", "aria-label": `Preview surface: ${activeSurface.label}`, children: [
9143
+ /* @__PURE__ */ jsx(activeSurface.Icon, { className: "h-3.5 w-3.5" }),
9144
+ /* @__PURE__ */ jsx("span", { className: "hidden md:inline", children: activeSurface.label }),
9145
+ /* @__PURE__ */ jsx(ChevronDown, { className: "hidden h-3.5 w-3.5 opacity-60 md:inline" })
9146
+ ] }) }),
9147
+ /* @__PURE__ */ jsx(PopoverContent, { align: "start", className: "w-36 p-1", children: /* @__PURE__ */ jsx(
9148
+ Menu,
9149
+ {
9150
+ ariaLabel: "Preview surface",
9151
+ options: surfaceOptions,
9152
+ value: previewSurface,
9153
+ onChange: (next) => {
9154
+ onPreviewSurfaceChange(next);
9155
+ setSurfaceMenuOpen(false);
9156
+ }
9157
+ }
9158
+ ) })
9159
+ ] }) : null,
9160
+ showThemeControl ? /* @__PURE__ */ jsxs(Popover, { open: themeMenuOpen, onOpenChange: setThemeMenuOpen, children: [
9161
+ /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(Button, { type: "button", variant: "ghost", size: "sm", "aria-label": `Preview theme: ${activeTheme.label}`, children: [
9162
+ /* @__PURE__ */ jsx(activeTheme.Icon, { className: "h-3.5 w-3.5" }),
9163
+ /* @__PURE__ */ jsx("span", { className: "hidden md:inline", children: activeTheme.label }),
9164
+ /* @__PURE__ */ jsx(ChevronDown, { className: "h-3.5 w-3.5 opacity-60" })
9165
+ ] }) }),
9166
+ /* @__PURE__ */ jsx(PopoverContent, { align: "start", className: "w-32 p-1", children: /* @__PURE__ */ jsx(
9167
+ Menu,
9168
+ {
9169
+ ariaLabel: "Preview theme",
9170
+ options: THEME_OPTIONS,
9171
+ value: previewTheme,
9172
+ onChange: (next) => {
9173
+ onPreviewThemeChange(next);
9174
+ setThemeMenuOpen(false);
9175
+ }
8942
9176
  }
9177
+ ) })
9178
+ ] }) : null,
9179
+ docsHref ? /* @__PURE__ */ jsxs(
9180
+ "a",
9181
+ {
9182
+ href: docsHref,
9183
+ target: "_blank",
9184
+ rel: "noreferrer noopener",
9185
+ className: "inline-flex h-8 items-center gap-1.5 rounded-md px-2 text-xs font-medium text-muted-foreground no-underline transition-colors hover:bg-accent hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
9186
+ "aria-label": `${docsLabel} (opens in a new tab)`,
9187
+ children: [
9188
+ /* @__PURE__ */ jsx(BookOpen, { className: "h-3.5 w-3.5" }),
9189
+ /* @__PURE__ */ jsx("span", { className: "hidden md:inline", children: docsLabel }),
9190
+ /* @__PURE__ */ jsx(ExternalLink, { className: "hidden h-3 w-3 opacity-50 md:inline" })
9191
+ ]
8943
9192
  }
8944
- ) })
8945
- ] }) : null,
8946
- showThemeControl ? /* @__PURE__ */ jsxs(Popover, { open: themeMenuOpen, onOpenChange: setThemeMenuOpen, children: [
8947
- /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(Button, { type: "button", variant: "ghost", size: "sm", "aria-label": `Preview theme: ${activeTheme.label}`, children: [
8948
- /* @__PURE__ */ jsx(activeTheme.Icon, { className: "h-3.5 w-3.5" }),
8949
- /* @__PURE__ */ jsx("span", { className: "hidden sm:inline", children: activeTheme.label }),
8950
- /* @__PURE__ */ jsx(ChevronDown, { className: "h-3.5 w-3.5 opacity-60" })
8951
- ] }) }),
8952
- /* @__PURE__ */ jsx(PopoverContent, { align: "start", className: "w-32 p-1", children: /* @__PURE__ */ jsx(
8953
- Menu,
9193
+ ) : null
9194
+ ] }),
9195
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 sm:gap-2", children: [
9196
+ errorCount > 0 ? /* @__PURE__ */ jsxs(
9197
+ Button,
8954
9198
  {
8955
- ariaLabel: "Preview theme",
8956
- options: THEME_OPTIONS,
8957
- value: previewTheme,
8958
- onChange: (next) => {
8959
- onPreviewThemeChange(next);
8960
- setThemeMenuOpen(false);
8961
- }
9199
+ type: "button",
9200
+ variant: "ghost",
9201
+ size: "sm",
9202
+ onClick: onOpenIssues,
9203
+ className: "border border-red-200! bg-red-50 text-red-700 hover:bg-red-100 hover:text-red-700",
9204
+ "aria-label": `${errorCount} ${errorCount === 1 ? "issue" : "issues"}`,
9205
+ children: [
9206
+ /* @__PURE__ */ jsx(AlertTriangle, { className: "h-3.5 w-3.5" }),
9207
+ /* @__PURE__ */ jsxs("span", { children: [
9208
+ errorCount,
9209
+ " ",
9210
+ /* @__PURE__ */ jsx("span", { className: "hidden md:inline", children: errorCount === 1 ? "issue" : "issues" })
9211
+ ] })
9212
+ ]
8962
9213
  }
8963
- ) })
8964
- ] }) : null,
8965
- docsHref ? /* @__PURE__ */ jsxs(
8966
- "a",
8967
- {
8968
- href: docsHref,
8969
- target: "_blank",
8970
- rel: "noreferrer noopener",
8971
- className: "hidden h-8 items-center gap-1.5 rounded-md px-2 py-1 text-sm text-muted-foreground no-underline transition-colors hover:bg-accent hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring sm:inline-flex",
8972
- "aria-label": `${docsLabel} (opens in a new tab)`,
8973
- children: [
8974
- /* @__PURE__ */ jsx(BookOpen, { className: "h-3.5 w-3.5" }),
8975
- docsLabel,
8976
- /* @__PURE__ */ jsx(ExternalLink, { className: "h-3 w-3 opacity-50" })
8977
- ]
8978
- }
8979
- ) : null
8980
- ] }),
8981
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 sm:gap-2", children: [
8982
- errorCount > 0 ? /* @__PURE__ */ jsxs(
8983
- Button,
8984
- {
8985
- type: "button",
8986
- variant: "ghost",
8987
- size: "sm",
8988
- onClick: onOpenIssues,
8989
- className: "border border-red-200! bg-red-50 text-red-700 hover:bg-red-100 hover:text-red-700",
8990
- "aria-label": `${errorCount} ${errorCount === 1 ? "issue" : "issues"}`,
8991
- children: [
8992
- /* @__PURE__ */ jsx(AlertTriangle, { className: "h-3.5 w-3.5" }),
8993
- /* @__PURE__ */ jsxs("span", { children: [
8994
- errorCount,
8995
- " ",
8996
- /* @__PURE__ */ jsx("span", { className: "hidden sm:inline", children: errorCount === 1 ? "issue" : "issues" })
8997
- ] })
8998
- ]
8999
- }
9000
- ) : null,
9001
- /* @__PURE__ */ jsxs(
9002
- Button,
9003
- {
9004
- type: "button",
9005
- variant: "ghost",
9006
- size: "sm",
9007
- onClick: onClear,
9008
- disabled: !canClear,
9009
- className: "hidden hover:bg-destructive/10 hover:text-destructive sm:inline-flex",
9010
- "aria-label": "Clear all blocks",
9011
- children: [
9012
- /* @__PURE__ */ jsx(Trash2, { className: "h-3.5 w-3.5" }),
9013
- "Clear"
9014
- ]
9015
- }
9016
- ),
9017
- /* @__PURE__ */ jsxs(
9018
- Button,
9019
- {
9020
- type: "button",
9021
- variant: "ghost",
9022
- size: "sm",
9023
- onClick: onOpenJson,
9024
- "aria-label": "View JSON",
9025
- className: "hidden sm:inline-flex",
9026
- children: [
9027
- /* @__PURE__ */ jsx(Code2, { className: "h-3.5 w-3.5" }),
9028
- "View JSON"
9029
- ]
9030
- }
9031
- ),
9032
- /* @__PURE__ */ jsxs(Popover, { open: moreMenuOpen, onOpenChange: setMoreMenuOpen, children: [
9033
- /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
9214
+ ) : null,
9215
+ /* @__PURE__ */ jsxs(
9034
9216
  Button,
9035
9217
  {
9036
9218
  type: "button",
9037
9219
  variant: "ghost",
9038
9220
  size: "sm",
9039
- "aria-label": "More actions",
9040
- "aria-haspopup": "menu",
9041
- className: "px-2 sm:hidden",
9042
- children: /* @__PURE__ */ jsx(MoreHorizontal, { className: "h-3.5 w-3.5" })
9221
+ onClick: onClear,
9222
+ disabled: !canClear,
9223
+ className: "hover:bg-destructive/10 hover:text-destructive",
9224
+ "aria-label": "Clear all blocks",
9225
+ children: [
9226
+ /* @__PURE__ */ jsx(Trash2, { className: "h-3.5 w-3.5" }),
9227
+ /* @__PURE__ */ jsx("span", { className: "hidden md:inline", children: "Clear" })
9228
+ ]
9043
9229
  }
9044
- ) }),
9045
- /* @__PURE__ */ jsx(PopoverContent, { align: "end", className: "w-44 p-1", children: /* @__PURE__ */ jsxs("div", { role: "menu", "aria-label": "More actions", className: "flex flex-col", children: [
9046
- /* @__PURE__ */ jsx(
9047
- ActionMenuItem,
9048
- {
9049
- icon: /* @__PURE__ */ jsx(Trash2, { className: "h-3.5 w-3.5" }),
9050
- onSelect: () => {
9051
- setMoreMenuOpen(false);
9052
- onClear();
9053
- },
9054
- disabled: !canClear,
9055
- tone: "destructive",
9056
- children: "Clear"
9057
- }
9058
- ),
9059
- /* @__PURE__ */ jsx(
9060
- ActionMenuItem,
9061
- {
9062
- icon: /* @__PURE__ */ jsx(Code2, { className: "h-3.5 w-3.5" }),
9063
- onSelect: () => {
9064
- setMoreMenuOpen(false);
9065
- onOpenJson();
9066
- },
9067
- children: "View JSON"
9068
- }
9069
- ),
9070
- docsHref ? /* @__PURE__ */ jsx(
9071
- ActionMenuLink,
9072
- {
9073
- href: docsHref,
9074
- icon: /* @__PURE__ */ jsx(BookOpen, { className: "h-3.5 w-3.5" }),
9075
- trailingIcon: /* @__PURE__ */ jsx(ExternalLink, { className: "h-3 w-3 opacity-50" }),
9076
- onSelect: () => setMoreMenuOpen(false),
9077
- children: docsLabel
9078
- }
9079
- ) : null
9080
- ] }) })
9230
+ ),
9231
+ /* @__PURE__ */ jsxs(Button, { type: "button", variant: "ghost", size: "sm", onClick: onOpenJson, "aria-label": "View JSON", children: [
9232
+ /* @__PURE__ */ jsx(Code2, { className: "h-3.5 w-3.5" }),
9233
+ /* @__PURE__ */ jsx("span", { className: "hidden md:inline", children: "View JSON" })
9234
+ ] }),
9235
+ editBadge ? (
9236
+ // A message is loaded: split button. Main action updates it in
9237
+ // place; the menu also offers posting the blocks as a new message.
9238
+ /* @__PURE__ */ jsxs("div", { className: "flex items-stretch", children: [
9239
+ /* @__PURE__ */ jsxs(
9240
+ Button,
9241
+ {
9242
+ type: "button",
9243
+ size: "sm",
9244
+ onClick: onOpenUpdate,
9245
+ disabled: !canSend,
9246
+ "aria-label": updateButtonLabel,
9247
+ className: "rounded-r-none",
9248
+ children: [
9249
+ /* @__PURE__ */ jsx(Pencil, { className: "h-3.5 w-3.5" }),
9250
+ /* @__PURE__ */ jsx("span", { className: "hidden md:inline", children: updateButtonLabel })
9251
+ ]
9252
+ }
9253
+ ),
9254
+ /* @__PURE__ */ jsxs(Popover, { open: sendMenuOpen, onOpenChange: setSendMenuOpen, children: [
9255
+ /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
9256
+ Button,
9257
+ {
9258
+ type: "button",
9259
+ size: "sm",
9260
+ "aria-label": "More message options",
9261
+ "aria-haspopup": "menu",
9262
+ className: "rounded-l-none border-l border-l-primary-foreground/30! px-1.5",
9263
+ children: /* @__PURE__ */ jsx(ChevronDown, { className: "h-3.5 w-3.5" })
9264
+ }
9265
+ ) }),
9266
+ /* @__PURE__ */ jsx(PopoverContent, { align: "end", className: "w-56 p-1", children: /* @__PURE__ */ jsxs("div", { role: "menu", "aria-label": "Message options", className: "flex flex-col", children: [
9267
+ /* @__PURE__ */ jsxs(
9268
+ "button",
9269
+ {
9270
+ type: "button",
9271
+ role: "menuitem",
9272
+ disabled: !canSend,
9273
+ onClick: () => {
9274
+ setSendMenuOpen(false);
9275
+ onOpenUpdate?.();
9276
+ },
9277
+ className: SEND_MENU_ITEM,
9278
+ children: [
9279
+ /* @__PURE__ */ jsx(Pencil, { className: "h-3.5 w-3.5" }),
9280
+ /* @__PURE__ */ jsx("span", { className: "flex-1", children: "Update message" })
9281
+ ]
9282
+ }
9283
+ ),
9284
+ /* @__PURE__ */ jsxs(
9285
+ "button",
9286
+ {
9287
+ type: "button",
9288
+ role: "menuitem",
9289
+ disabled: !canSend,
9290
+ onClick: () => {
9291
+ setSendMenuOpen(false);
9292
+ onOpenSend();
9293
+ },
9294
+ className: SEND_MENU_ITEM,
9295
+ children: [
9296
+ /* @__PURE__ */ jsx(Send, { className: "h-3.5 w-3.5" }),
9297
+ /* @__PURE__ */ jsx("span", { className: "flex-1", children: "Send as a new message" })
9298
+ ]
9299
+ }
9300
+ )
9301
+ ] }) })
9302
+ ] })
9303
+ ] })
9304
+ ) : /* @__PURE__ */ jsxs(Button, { type: "button", size: "sm", onClick: onOpenSend, disabled: !canSend, "aria-label": sendButtonLabel, children: [
9305
+ /* @__PURE__ */ jsx(Send, { className: "h-3.5 w-3.5" }),
9306
+ /* @__PURE__ */ jsx("span", { className: "hidden md:inline", children: sendButtonLabel })
9307
+ ] })
9308
+ ] })
9309
+ ] }),
9310
+ editingEnabled && editBadge ? /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-x-3 gap-y-1 border-b bg-muted px-2 py-2 text-foreground sm:px-3", children: [
9311
+ /* @__PURE__ */ jsx(Pencil, { className: "h-4 w-4 shrink-0 text-primary" }),
9312
+ /* @__PURE__ */ jsxs("span", { className: "min-w-0 flex-1 text-sm", children: [
9313
+ "References an existing message in ",
9314
+ /* @__PURE__ */ jsx("span", { className: "font-semibold", children: editBadge.channelLabel }),
9315
+ /* @__PURE__ */ jsx("span", { className: "ml-1 text-xs opacity-70", title: editBadge.ts, children: formatMessageTs(editBadge.ts) })
9081
9316
  ] }),
9082
- /* @__PURE__ */ jsxs(Button, { type: "button", size: "sm", onClick: onOpenSend, disabled: !canSend, "aria-label": sendButtonLabel, children: [
9083
- /* @__PURE__ */ jsx(Send, { className: "h-3.5 w-3.5" }),
9084
- /* @__PURE__ */ jsx("span", { className: "hidden sm:inline", children: sendButtonLabel })
9317
+ /* @__PURE__ */ jsxs(Button, { type: "button", variant: "ghost", size: "sm", onClick: onExitEdit, className: "shrink-0", children: [
9318
+ /* @__PURE__ */ jsx(X, { className: "h-3.5 w-3.5" }),
9319
+ "Switch to a new message"
9085
9320
  ] })
9086
- ] })
9321
+ ] }) : null
9087
9322
  ] });
9088
9323
  }
9089
9324
  function Menu({
@@ -9141,52 +9376,132 @@ function Menu({
9141
9376
  );
9142
9377
  }) });
9143
9378
  }
9144
- var ACTION_ITEM_BASE = "flex w-full cursor-pointer items-center gap-2 rounded px-2 py-1.5 text-left text-sm text-muted-foreground no-underline transition-colors hover:bg-accent hover:text-foreground focus-visible:bg-accent focus-visible:text-foreground focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50";
9145
- function ActionMenuItem({
9146
- icon,
9147
- children,
9148
- onSelect,
9149
- disabled,
9150
- tone
9379
+ function UpdateDialog({
9380
+ open,
9381
+ onOpenChange,
9382
+ target,
9383
+ blocks,
9384
+ loadSendAsUserStatus,
9385
+ onUpdate,
9386
+ confirmUpdateLabel = "Update message",
9387
+ errorCount,
9388
+ onShowIssues
9151
9389
  }) {
9152
- return /* @__PURE__ */ jsxs(
9153
- "button",
9154
- {
9155
- type: "button",
9156
- role: "menuitem",
9157
- onClick: onSelect,
9158
- disabled,
9159
- className: cn(ACTION_ITEM_BASE, tone === "destructive" && "hover:bg-destructive/10 hover:text-destructive"),
9160
- children: [
9161
- icon,
9162
- /* @__PURE__ */ jsx("span", { className: "flex-1", children })
9163
- ]
9390
+ const asUser = target.editableVia === "user";
9391
+ const [userStatus, setUserStatus] = useState(null);
9392
+ const [status, setStatus] = useState({ kind: "idle" });
9393
+ const loadSendAsUserStatusRef = useRef(loadSendAsUserStatus);
9394
+ useEffect(() => {
9395
+ loadSendAsUserStatusRef.current = loadSendAsUserStatus;
9396
+ });
9397
+ const refreshSendAsUser = useCallback(() => {
9398
+ loadSendAsUserStatusRef.current().then(setUserStatus).catch(() => setUserStatus({ canSendAsUser: false }));
9399
+ }, []);
9400
+ useEffect(() => {
9401
+ if (!open) {
9402
+ return;
9164
9403
  }
9165
- );
9166
- }
9167
- function ActionMenuLink({
9168
- href,
9169
- icon,
9170
- trailingIcon,
9171
- children,
9172
- onSelect
9173
- }) {
9174
- return /* @__PURE__ */ jsxs(
9175
- "a",
9176
- {
9177
- role: "menuitem",
9178
- href,
9179
- target: "_blank",
9180
- rel: "noreferrer noopener",
9181
- onClick: onSelect,
9182
- className: ACTION_ITEM_BASE,
9183
- children: [
9184
- icon,
9185
- /* @__PURE__ */ jsx("span", { className: "flex-1", children }),
9186
- trailingIcon
9187
- ]
9404
+ setStatus({ kind: "idle" });
9405
+ if (asUser) {
9406
+ setUserStatus(null);
9407
+ refreshSendAsUser();
9188
9408
  }
9189
- );
9409
+ }, [open, asUser, refreshSendAsUser]);
9410
+ useEffect(() => {
9411
+ if (!open || !asUser) {
9412
+ return;
9413
+ }
9414
+ const handler = () => refreshSendAsUser();
9415
+ window.addEventListener("focus", handler);
9416
+ return () => window.removeEventListener("focus", handler);
9417
+ }, [open, asUser, refreshSendAsUser]);
9418
+ const needsSignIn = asUser && userStatus !== null && !userStatus.canSendAsUser;
9419
+ const handleSubmit = async () => {
9420
+ setStatus({ kind: "updating" });
9421
+ try {
9422
+ const result = await onUpdate({
9423
+ channelId: target.channelId,
9424
+ ts: target.ts,
9425
+ blocks: toSlackBlocks(blocks),
9426
+ asUser
9427
+ });
9428
+ if (result.ok) {
9429
+ setStatus({ kind: "idle" });
9430
+ onOpenChange(false);
9431
+ return;
9432
+ }
9433
+ setStatus({ kind: "error", error: result.error ?? "Update failed." });
9434
+ } catch (e) {
9435
+ setStatus({ kind: "error", error: e instanceof Error ? e.message : "Update failed." });
9436
+ }
9437
+ };
9438
+ const channelLabel = target.channelName ? `#${target.channelName}` : target.channelId;
9439
+ return /* @__PURE__ */ jsx(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxs(DialogContent, { className: "w-[calc(100vw-1.5rem)] max-w-lg rounded-lg", children: [
9440
+ /* @__PURE__ */ jsxs(DialogHeader, { children: [
9441
+ /* @__PURE__ */ jsx(DialogTitle, { children: "Update message" }),
9442
+ /* @__PURE__ */ jsx(DialogDescription, { children: "This replaces the message that's already posted. It stays in the same channel and can't be moved." })
9443
+ ] }),
9444
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4", children: [
9445
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5", children: [
9446
+ /* @__PURE__ */ jsx(Label, { children: "Channel" }),
9447
+ /* @__PURE__ */ jsx("p", { className: "flex h-9 items-center rounded-md border border-input bg-muted px-3 text-sm text-muted-foreground", children: channelLabel })
9448
+ ] }),
9449
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5", children: [
9450
+ /* @__PURE__ */ jsx(Label, { children: "Posting as" }),
9451
+ /* @__PURE__ */ jsx("p", { className: "flex h-9 items-center rounded-md border border-input bg-muted px-3 text-sm text-muted-foreground", children: asUser ? "Your account" : "App bot" }),
9452
+ needsSignIn && userStatus?.oauthUrl && isSafeHref(userStatus.oauthUrl) && /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
9453
+ /* @__PURE__ */ jsxs(
9454
+ "a",
9455
+ {
9456
+ href: userStatus.oauthUrl,
9457
+ target: "_blank",
9458
+ rel: "noopener noreferrer",
9459
+ className: "inline-flex items-center gap-1 text-primary hover:underline",
9460
+ children: [
9461
+ "Sign in with Slack ",
9462
+ /* @__PURE__ */ jsx(ExternalLink, { className: "h-3 w-3" })
9463
+ ]
9464
+ }
9465
+ ),
9466
+ " ",
9467
+ "to update your own message."
9468
+ ] }),
9469
+ needsSignIn && !userStatus?.oauthUrl && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Sign in with Slack to update your own message." })
9470
+ ] }),
9471
+ errorCount > 0 ? /* @__PURE__ */ jsxs(
9472
+ "button",
9473
+ {
9474
+ type: "button",
9475
+ onClick: onShowIssues,
9476
+ className: "flex items-center gap-2 rounded-md border border-destructive/30 bg-destructive/5 p-2 text-left text-xs text-destructive hover:bg-destructive/10",
9477
+ children: [
9478
+ /* @__PURE__ */ jsx(AlertTriangle, { className: "h-3.5 w-3.5 shrink-0" }),
9479
+ /* @__PURE__ */ jsxs("span", { className: "flex-1", children: [
9480
+ "Fix ",
9481
+ errorCount,
9482
+ " ",
9483
+ errorCount === 1 ? "issue" : "issues",
9484
+ " before updating."
9485
+ ] }),
9486
+ /* @__PURE__ */ jsx("span", { className: "shrink-0 underline", children: "Show issues" })
9487
+ ]
9488
+ }
9489
+ ) : null,
9490
+ 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 })
9491
+ ] }),
9492
+ /* @__PURE__ */ jsxs(DialogFooter, { children: [
9493
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", onClick: () => onOpenChange(false), children: "Cancel" }),
9494
+ /* @__PURE__ */ jsx(
9495
+ Button,
9496
+ {
9497
+ type: "button",
9498
+ onClick: handleSubmit,
9499
+ disabled: status.kind === "updating" || blocks.length === 0 || errorCount > 0 || needsSignIn,
9500
+ children: status.kind === "updating" ? "Updating\u2026" : confirmUpdateLabel
9501
+ }
9502
+ )
9503
+ ] })
9504
+ ] }) });
9190
9505
  }
9191
9506
  function BlockKitchen(props) {
9192
9507
  const {
@@ -9198,6 +9513,10 @@ function BlockKitchen(props) {
9198
9513
  loadChannels,
9199
9514
  loadSendAsUserStatus,
9200
9515
  onSend,
9516
+ editing,
9517
+ loadButtonLabel,
9518
+ updateButtonLabel,
9519
+ confirmUpdateLabel,
9201
9520
  palette,
9202
9521
  disabledBlockTypes,
9203
9522
  showPaletteSearch,
@@ -9243,6 +9562,10 @@ function BlockKitchen(props) {
9243
9562
  }, [blocks]);
9244
9563
  const [jsonOpen, setJsonOpen] = useState(false);
9245
9564
  const [sendOpen, setSendOpen] = useState(false);
9565
+ const [updateOpen, setUpdateOpen] = useState(false);
9566
+ const [loadOpen, setLoadOpen] = useState(false);
9567
+ const [editTarget, setEditTarget] = useState(null);
9568
+ const activeEditTarget = editing ? editTarget : null;
9246
9569
  const [issuesOpen, setIssuesOpen] = useState(false);
9247
9570
  const [paletteOpen, setPaletteOpen] = useState(false);
9248
9571
  const [openBlockId, setOpenBlockId] = useState(null);
@@ -9363,7 +9686,21 @@ function BlockKitchen(props) {
9363
9686
  allowedSurfaces,
9364
9687
  showThemeControl: isPreviewThemeControlled ? false : showThemeControl,
9365
9688
  docsLink,
9366
- sendButtonLabel
9689
+ sendButtonLabel,
9690
+ editingEnabled: !!editing,
9691
+ editBadge: activeEditTarget ? {
9692
+ channelLabel: activeEditTarget.channelName ? `#${activeEditTarget.channelName}` : activeEditTarget.channelId,
9693
+ ts: activeEditTarget.ts
9694
+ } : null,
9695
+ onOpenLoad: () => setLoadOpen(true),
9696
+ onOpenUpdate: () => setUpdateOpen(true),
9697
+ onExitEdit: () => {
9698
+ setEditTarget(null);
9699
+ replaceAll([]);
9700
+ setLoadOpen(true);
9701
+ },
9702
+ loadButtonLabel,
9703
+ updateButtonLabel
9367
9704
  }
9368
9705
  ),
9369
9706
  /* @__PURE__ */ jsxs("div", { className: "flex min-h-0 flex-1 items-stretch", children: [
@@ -9382,6 +9719,8 @@ function BlockKitchen(props) {
9382
9719
  {
9383
9720
  blocks,
9384
9721
  workspaceName,
9722
+ authorName: activeEditTarget?.username,
9723
+ authorIcon: activeEditTarget?.iconUrl,
9385
9724
  previewHooks: mergedPreviewHooks,
9386
9725
  previewTheme,
9387
9726
  previewSurface,
@@ -9447,6 +9786,52 @@ function BlockKitchen(props) {
9447
9786
  }
9448
9787
  }
9449
9788
  ),
9789
+ activeEditTarget && editing ? /* @__PURE__ */ jsx(
9790
+ UpdateDialog,
9791
+ {
9792
+ open: updateOpen,
9793
+ onOpenChange: setUpdateOpen,
9794
+ target: activeEditTarget,
9795
+ blocks: blockPayloads,
9796
+ loadSendAsUserStatus,
9797
+ onUpdate: editing.onUpdate,
9798
+ confirmUpdateLabel,
9799
+ errorCount: validation.total,
9800
+ onShowIssues: () => {
9801
+ setUpdateOpen(false);
9802
+ setIssuesOpen(true);
9803
+ }
9804
+ }
9805
+ ) : null,
9806
+ editing ? /* @__PURE__ */ jsx(
9807
+ LoadMessageDialog,
9808
+ {
9809
+ open: loadOpen,
9810
+ onOpenChange: setLoadOpen,
9811
+ onLoadMessage: editing.onLoadMessage,
9812
+ loadRecentMessages: editing.loadRecentMessages,
9813
+ onLoaded: (result) => {
9814
+ replaceAll(result.blocks);
9815
+ setEditTarget({
9816
+ channelId: result.channelId,
9817
+ channelName: result.channelName,
9818
+ ts: result.ts,
9819
+ editableVia: result.editableVia,
9820
+ workspaceName: result.workspaceName,
9821
+ username: result.username,
9822
+ iconUrl: result.iconUrl
9823
+ });
9824
+ setLoadOpen(false);
9825
+ },
9826
+ onOpenAsNew: (loadedBlocks) => {
9827
+ if (loadedBlocks) {
9828
+ replaceAll(loadedBlocks);
9829
+ }
9830
+ setEditTarget(null);
9831
+ setLoadOpen(false);
9832
+ }
9833
+ }
9834
+ ) : null,
9450
9835
  /* @__PURE__ */ jsx(
9451
9836
  IssuesSheet,
9452
9837
  {