@tightknitai/block-kitchen 0.8.10 → 0.9.0

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,265 @@ 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
+ loadChannels,
2825
+ onLoaded,
2826
+ onOpenAsNew
2827
+ }) {
2828
+ const [link, setLink] = useState("");
2829
+ const [status, setStatus] = useState({ kind: "idle" });
2830
+ const [channels, setChannels] = useState(null);
2831
+ const [channelsError, setChannelsError] = useState(null);
2832
+ const [channelId, setChannelId] = useState("");
2833
+ const [recent, setRecent] = useState(null);
2834
+ const [recentError, setRecentError] = useState(null);
2835
+ const inputRef = useRef(null);
2836
+ const onLoadMessageRef = useRef(onLoadMessage);
2837
+ const loadRecentMessagesRef = useRef(loadRecentMessages);
2838
+ const loadChannelsRef = useRef(loadChannels);
2839
+ useEffect(() => {
2840
+ onLoadMessageRef.current = onLoadMessage;
2841
+ loadRecentMessagesRef.current = loadRecentMessages;
2842
+ loadChannelsRef.current = loadChannels;
2843
+ });
2844
+ const hasRecent = !!loadRecentMessages;
2845
+ useEffect(() => {
2846
+ if (!open) {
2847
+ return;
2848
+ }
2849
+ setLink("");
2850
+ setStatus({ kind: "idle" });
2851
+ setChannelId("");
2852
+ setRecent(null);
2853
+ setRecentError(null);
2854
+ if (!loadRecentMessagesRef.current) {
2855
+ setChannels([]);
2856
+ setChannelsError(null);
2857
+ return;
2858
+ }
2859
+ setChannels(null);
2860
+ setChannelsError(null);
2861
+ let cancelled = false;
2862
+ loadChannelsRef.current().then((list) => {
2863
+ if (!cancelled) {
2864
+ setChannels(list);
2865
+ }
2866
+ }).catch((e) => {
2867
+ if (!cancelled) {
2868
+ setChannelsError(e instanceof Error ? e.message : "Failed to load channels");
2869
+ }
2870
+ });
2871
+ return () => {
2872
+ cancelled = true;
2873
+ };
2874
+ }, [open]);
2875
+ useEffect(() => {
2876
+ if (!open || !loadRecentMessagesRef.current || !channelId) {
2877
+ return;
2878
+ }
2879
+ setRecent(null);
2880
+ setRecentError(null);
2881
+ let cancelled = false;
2882
+ loadRecentMessagesRef.current(channelId).then((list) => {
2883
+ if (!cancelled) {
2884
+ setRecent(list);
2885
+ }
2886
+ }).catch((e) => {
2887
+ if (!cancelled) {
2888
+ setRecentError(e instanceof Error ? e.message : "Failed to load recent messages.");
2889
+ }
2890
+ });
2891
+ return () => {
2892
+ cancelled = true;
2893
+ };
2894
+ }, [open, channelId]);
2895
+ const handleLoad = async () => {
2896
+ const trimmed = link.trim();
2897
+ if (!trimmed) {
2898
+ setStatus({ kind: "error", error: "Paste a Slack message link first." });
2899
+ return;
2900
+ }
2901
+ setStatus({ kind: "loading" });
2902
+ try {
2903
+ const result = await onLoadMessageRef.current({ link: trimmed });
2904
+ if (result.ok) {
2905
+ onLoaded(result);
2906
+ return;
2907
+ }
2908
+ setStatus({ kind: "not-editable", reason: result.reason, blocks: result.blocks });
2909
+ } catch (e) {
2910
+ setStatus({ kind: "error", error: e instanceof Error ? e.message : "Failed to load message." });
2911
+ }
2912
+ };
2913
+ const notEditable = status.kind === "not-editable" ? status : null;
2914
+ return /* @__PURE__ */ jsx(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxs(
2915
+ DialogContent,
2916
+ {
2917
+ className: "w-[calc(100vw-1.5rem)] max-w-lg rounded-lg",
2918
+ onOpenAutoFocus: (e) => {
2919
+ e.preventDefault();
2920
+ inputRef.current?.focus();
2921
+ },
2922
+ children: [
2923
+ /* @__PURE__ */ jsxs(DialogHeader, { children: [
2924
+ /* @__PURE__ */ jsx(DialogTitle, { children: "Edit an existing message" }),
2925
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-1.5", children: [
2926
+ /* @__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.` }),
2927
+ /* @__PURE__ */ jsxs(Tooltip, { children: [
2928
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
2929
+ "button",
2930
+ {
2931
+ type: "button",
2932
+ "aria-label": "How to copy a message link",
2933
+ 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",
2934
+ children: /* @__PURE__ */ jsx(Info, { className: "h-3.5 w-3.5" })
2935
+ }
2936
+ ) }),
2937
+ /* @__PURE__ */ jsxs(TooltipContent, { className: "max-w-xs leading-relaxed", children: [
2938
+ "In Slack, hover over the message and click the ",
2939
+ /* @__PURE__ */ jsx("strong", { children: "\u22EE More actions" }),
2940
+ " button (or right-click the message), then choose ",
2941
+ /* @__PURE__ */ jsx("strong", { children: "Copy link" }),
2942
+ "."
2943
+ ] })
2944
+ ] })
2945
+ ] })
2946
+ ] }),
2947
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4", children: [
2948
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5", children: [
2949
+ /* @__PURE__ */ jsx(Label, { htmlFor: "message-link", children: "Message link" }),
2950
+ /* @__PURE__ */ jsx(
2951
+ Input,
2952
+ {
2953
+ ref: inputRef,
2954
+ id: "message-link",
2955
+ value: link,
2956
+ onChange: (e) => {
2957
+ setLink(e.target.value);
2958
+ if (status.kind !== "idle" && status.kind !== "loading") {
2959
+ setStatus({ kind: "idle" });
2960
+ }
2961
+ },
2962
+ onKeyDown: (e) => {
2963
+ if (e.key === "Enter" && status.kind !== "loading") {
2964
+ e.preventDefault();
2965
+ handleLoad();
2966
+ }
2967
+ },
2968
+ placeholder: "https://your-workspace.slack.com/archives/C\u2026/p\u2026",
2969
+ autoComplete: "off",
2970
+ spellCheck: false
2971
+ }
2972
+ )
2973
+ ] }),
2974
+ 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 }),
2975
+ 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: [
2976
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2", children: [
2977
+ /* @__PURE__ */ jsx(AlertTriangle, { className: "mt-0.5 h-3.5 w-3.5 shrink-0" }),
2978
+ /* @__PURE__ */ jsx("span", { className: "flex-1", children: notEditable.reason })
2979
+ ] }),
2980
+ /* @__PURE__ */ jsx(
2981
+ Button,
2982
+ {
2983
+ type: "button",
2984
+ variant: "outline",
2985
+ size: "sm",
2986
+ className: "self-start",
2987
+ onClick: () => onOpenAsNew(notEditable.blocks),
2988
+ children: "Open as a new message instead"
2989
+ }
2990
+ )
2991
+ ] }),
2992
+ hasRecent && /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
2993
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-xs text-muted-foreground", children: [
2994
+ /* @__PURE__ */ jsx("span", { className: "h-px flex-1 bg-border" }),
2995
+ "or pick a recent message",
2996
+ /* @__PURE__ */ jsx("span", { className: "h-px flex-1 bg-border" })
2997
+ ] }),
2998
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5", children: [
2999
+ /* @__PURE__ */ jsx(Label, { htmlFor: "recent-channel-picker", children: "Channel" }),
3000
+ channels === null && !channelsError && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Loading channels\u2026" }),
3001
+ channelsError && /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive", children: channelsError }),
3002
+ channels && channels.length === 0 && !channelsError && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "No public channels available." }),
3003
+ channels && channels.length > 0 && /* @__PURE__ */ jsxs(
3004
+ "select",
3005
+ {
3006
+ id: "recent-channel-picker",
3007
+ value: channelId,
3008
+ onChange: (e) => setChannelId(e.target.value),
3009
+ 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",
3010
+ children: [
3011
+ /* @__PURE__ */ jsx("option", { value: "", disabled: true, children: "Select a channel\u2026" }),
3012
+ channels.map((c) => /* @__PURE__ */ jsxs("option", { value: c.id, children: [
3013
+ "#",
3014
+ c.name
3015
+ ] }, c.id))
3016
+ ]
3017
+ }
3018
+ )
3019
+ ] }),
3020
+ !channelId && channels && channels.length > 0 && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Select a channel to see recent messages." }),
3021
+ channelId && recent === null && !recentError && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Loading recent messages\u2026" }),
3022
+ channelId && recentError && /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive", children: recentError }),
3023
+ 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." }),
3024
+ channelId && recent && recent.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex max-h-48 flex-col gap-1 overflow-y-auto", children: recent.map((m) => {
3025
+ const asUser = (m.editableVia ?? "bot") === "user";
3026
+ return /* @__PURE__ */ jsxs(
3027
+ "button",
3028
+ {
3029
+ type: "button",
3030
+ onClick: () => onLoaded(recentToResult(m)),
3031
+ 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",
3032
+ children: [
3033
+ /* @__PURE__ */ jsxs("span", { className: "flex items-baseline justify-between gap-2", children: [
3034
+ /* @__PURE__ */ jsxs("span", { className: "flex min-w-0 items-baseline gap-1.5", children: [
3035
+ /* @__PURE__ */ jsx("span", { className: "truncate font-medium", children: m.channelName ? `#${m.channelName}` : m.channelId }),
3036
+ /* @__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" })
3037
+ ] }),
3038
+ /* @__PURE__ */ jsx("span", { className: "shrink-0 font-mono text-xs text-muted-foreground", children: m.ts })
3039
+ ] }),
3040
+ m.label && /* @__PURE__ */ jsx("span", { className: "truncate text-xs text-muted-foreground", children: m.label })
3041
+ ]
3042
+ },
3043
+ `${m.channelId}:${m.ts}`
3044
+ );
3045
+ }) })
3046
+ ] })
3047
+ ] }),
3048
+ /* @__PURE__ */ jsxs(DialogFooter, { children: [
3049
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", onClick: () => onOpenChange(false), children: "Cancel" }),
3050
+ /* @__PURE__ */ jsx(Button, { type: "button", onClick: handleLoad, disabled: status.kind === "loading" || !link.trim(), children: status.kind === "loading" ? "Loading\u2026" : "Load message" })
3051
+ ] })
3052
+ ]
3053
+ }
3054
+ ) });
3055
+ }
2712
3056
  var PALETTE_DRAG_PREFIX = "palette:";
2713
3057
  function parsePaletteDragId(id) {
2714
3058
  if (typeof id !== "string" || !id.startsWith(PALETTE_DRAG_PREFIX)) {
@@ -2908,100 +3252,6 @@ function PaletteItem({
2908
3252
  }
2909
3253
  );
2910
3254
  }
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
3255
  function SendDialog({
3006
3256
  open,
3007
3257
  onOpenChange,
@@ -4142,7 +4392,7 @@ function TextItemCard({
4142
4392
  id: `ctx-text-${index}`,
4143
4393
  value: element.text,
4144
4394
  rows: 2,
4145
- placeholder: "e.g. Posted by @stephen",
4395
+ placeholder: "e.g. Posted by @riley",
4146
4396
  onChange: (e) => onChange({ ...element, text: e.target.value })
4147
4397
  }
4148
4398
  ) }),
@@ -4227,7 +4477,7 @@ function ImageItemCard({
4227
4477
  {
4228
4478
  id: `ctx-img-alt-${index}`,
4229
4479
  value: element.alt_text,
4230
- placeholder: "e.g. Stephen's avatar",
4480
+ placeholder: "e.g. Riley's avatar",
4231
4481
  onChange: (e) => onChange({ ...element, alt_text: e.target.value })
4232
4482
  }
4233
4483
  ) })
@@ -8560,6 +8810,8 @@ var SURFACE_DROPPABLE_ID = "builder-surface";
8560
8810
  function Surface({
8561
8811
  blocks,
8562
8812
  workspaceName,
8813
+ authorName,
8814
+ authorIcon,
8563
8815
  previewHooks,
8564
8816
  previewTheme,
8565
8817
  previewSurface = "message",
@@ -8635,16 +8887,20 @@ function Surface({
8635
8887
  {
8636
8888
  "aria-label": "Block preview",
8637
8889
  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 }) })
8890
+ 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
8891
  }
8640
8892
  );
8641
8893
  }
8642
8894
  function MessageFrame({
8643
8895
  workspaceName,
8896
+ authorName,
8897
+ authorIcon,
8644
8898
  isDark,
8645
8899
  children
8646
8900
  }) {
8647
- const initial = (workspaceName ?? "A").slice(0, 1).toUpperCase();
8901
+ const displayName = authorName ?? workspaceName ?? "Your app";
8902
+ const initial = displayName.slice(0, 1).toUpperCase();
8903
+ const safeIcon = authorIcon && isSafeImageSrc(authorIcon) ? authorIcon : null;
8648
8904
  return /* @__PURE__ */ jsxs(
8649
8905
  "div",
8650
8906
  {
@@ -8658,7 +8914,7 @@ function MessageFrame({
8658
8914
  {
8659
8915
  className: cn("flex items-center gap-2 px-5 pt-3 pb-1 text-xs", isDark ? "text-white/60" : "text-[#616061]"),
8660
8916
  children: [
8661
- /* @__PURE__ */ jsx(
8917
+ safeIcon ? /* @__PURE__ */ jsx("img", { src: safeIcon, alt: "", className: "h-7 w-7 shrink-0 rounded object-cover" }) : /* @__PURE__ */ jsx(
8662
8918
  "span",
8663
8919
  {
8664
8920
  className: cn(
@@ -8668,17 +8924,7 @@ function MessageFrame({
8668
8924
  children: initial
8669
8925
  }
8670
8926
  ),
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
- ),
8927
+ /* @__PURE__ */ jsx("span", { className: cn("font-bold text-sm", isDark ? "text-white" : "text-[#1d1c1d]"), children: displayName }),
8682
8928
  /* @__PURE__ */ jsx("span", { children: "10:37 AM" })
8683
8929
  ]
8684
8930
  }
@@ -8880,6 +9126,14 @@ var SURFACE_OPTIONS = [
8880
9126
  ];
8881
9127
  var DEFAULT_DOCS_HREF = "https://docs.slack.dev/reference/block-kit/blocks";
8882
9128
  var DEFAULT_DOCS_LABEL = "Docs";
9129
+ function formatMessageTs(ts) {
9130
+ const seconds = Number.parseFloat(ts);
9131
+ if (!Number.isFinite(seconds)) return ts;
9132
+ const date = new Date(seconds * 1e3);
9133
+ if (Number.isNaN(date.getTime())) return ts;
9134
+ return date.toLocaleString(void 0, { dateStyle: "medium", timeStyle: "short" });
9135
+ }
9136
+ 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
9137
  function Toolbar2({
8884
9138
  onClear,
8885
9139
  onOpenJson,
@@ -8896,194 +9150,227 @@ function Toolbar2({
8896
9150
  showThemeControl = true,
8897
9151
  docsLink,
8898
9152
  errorCount,
8899
- sendButtonLabel = "Send"
9153
+ sendButtonLabel = "Review & send",
9154
+ editingEnabled = false,
9155
+ editBadge,
9156
+ onOpenLoad,
9157
+ onOpenUpdate,
9158
+ onExitEdit,
9159
+ loadButtonLabel = "Load message",
9160
+ updateButtonLabel = "Review & update"
8900
9161
  }) {
8901
9162
  const activeTheme = THEME_OPTIONS.find((t) => t.value === previewTheme) ?? THEME_OPTIONS[0];
8902
- const activeSurface = SURFACE_OPTIONS.find((s) => s.value === previewSurface) ?? SURFACE_OPTIONS[0];
8903
- const surfaceOptions = SURFACE_OPTIONS.filter((s) => allowedSurfaces.includes(s.value));
8904
- const showSurfaceControl = surfaceOptions.length > 1;
8905
- const docsHref = docsLink === false ? null : docsLink?.href ?? DEFAULT_DOCS_HREF;
8906
- const docsLabel = docsLink === false ? null : docsLink?.label ?? DEFAULT_DOCS_LABEL;
8907
- const [surfaceMenuOpen, setSurfaceMenuOpen] = useState(false);
8908
- 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,
9163
+ const activeSurface = SURFACE_OPTIONS.find((s) => s.value === previewSurface) ?? SURFACE_OPTIONS[0];
9164
+ const surfaceOptions = SURFACE_OPTIONS.filter((s) => allowedSurfaces.includes(s.value));
9165
+ const showSurfaceControl = surfaceOptions.length > 1;
9166
+ const docsHref = docsLink === false ? null : docsLink?.href ?? DEFAULT_DOCS_HREF;
9167
+ const docsLabel = docsLink === false ? null : docsLink?.label ?? DEFAULT_DOCS_LABEL;
9168
+ const [surfaceMenuOpen, setSurfaceMenuOpen] = useState(false);
9169
+ const [themeMenuOpen, setThemeMenuOpen] = useState(false);
9170
+ const [sendMenuOpen, setSendMenuOpen] = useState(false);
9171
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
9172
+ /* @__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: [
9173
+ /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 items-center gap-1 sm:gap-2", children: [
9174
+ editingEnabled && !editBadge ? /* @__PURE__ */ jsxs(Button, { type: "button", size: "sm", onClick: onOpenLoad, "aria-label": loadButtonLabel, children: [
9175
+ /* @__PURE__ */ jsx(Pencil, { className: "h-3.5 w-3.5" }),
9176
+ /* @__PURE__ */ jsx("span", { className: "hidden md:inline", children: loadButtonLabel })
9177
+ ] }) : null,
9178
+ onOpenPalette ? /* @__PURE__ */ jsxs(
9179
+ Button,
8935
9180
  {
8936
- ariaLabel: "Preview surface",
8937
- options: surfaceOptions,
8938
- value: previewSurface,
8939
- onChange: (next) => {
8940
- onPreviewSurfaceChange(next);
8941
- setSurfaceMenuOpen(false);
9181
+ type: "button",
9182
+ variant: "ghost",
9183
+ size: "sm",
9184
+ onClick: onOpenPalette,
9185
+ className: "md:hidden",
9186
+ "aria-label": "Add a block",
9187
+ children: [
9188
+ /* @__PURE__ */ jsx(Plus, { className: "h-4 w-4" }),
9189
+ /* @__PURE__ */ jsx("span", { children: "Blocks" })
9190
+ ]
9191
+ }
9192
+ ) : null,
9193
+ showSurfaceControl ? /* @__PURE__ */ jsxs(Popover, { open: surfaceMenuOpen, onOpenChange: setSurfaceMenuOpen, children: [
9194
+ /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(Button, { type: "button", variant: "ghost", size: "sm", "aria-label": `Preview surface: ${activeSurface.label}`, children: [
9195
+ /* @__PURE__ */ jsx(activeSurface.Icon, { className: "h-3.5 w-3.5" }),
9196
+ /* @__PURE__ */ jsx("span", { className: "hidden md:inline", children: activeSurface.label }),
9197
+ /* @__PURE__ */ jsx(ChevronDown, { className: "hidden h-3.5 w-3.5 opacity-60 md:inline" })
9198
+ ] }) }),
9199
+ /* @__PURE__ */ jsx(PopoverContent, { align: "start", className: "w-36 p-1", children: /* @__PURE__ */ jsx(
9200
+ Menu,
9201
+ {
9202
+ ariaLabel: "Preview surface",
9203
+ options: surfaceOptions,
9204
+ value: previewSurface,
9205
+ onChange: (next) => {
9206
+ onPreviewSurfaceChange(next);
9207
+ setSurfaceMenuOpen(false);
9208
+ }
9209
+ }
9210
+ ) })
9211
+ ] }) : null,
9212
+ showThemeControl ? /* @__PURE__ */ jsxs(Popover, { open: themeMenuOpen, onOpenChange: setThemeMenuOpen, children: [
9213
+ /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(Button, { type: "button", variant: "ghost", size: "sm", "aria-label": `Preview theme: ${activeTheme.label}`, children: [
9214
+ /* @__PURE__ */ jsx(activeTheme.Icon, { className: "h-3.5 w-3.5" }),
9215
+ /* @__PURE__ */ jsx("span", { className: "hidden md:inline", children: activeTheme.label }),
9216
+ /* @__PURE__ */ jsx(ChevronDown, { className: "h-3.5 w-3.5 opacity-60" })
9217
+ ] }) }),
9218
+ /* @__PURE__ */ jsx(PopoverContent, { align: "start", className: "w-32 p-1", children: /* @__PURE__ */ jsx(
9219
+ Menu,
9220
+ {
9221
+ ariaLabel: "Preview theme",
9222
+ options: THEME_OPTIONS,
9223
+ value: previewTheme,
9224
+ onChange: (next) => {
9225
+ onPreviewThemeChange(next);
9226
+ setThemeMenuOpen(false);
9227
+ }
8942
9228
  }
9229
+ ) })
9230
+ ] }) : null,
9231
+ docsHref ? /* @__PURE__ */ jsxs(
9232
+ "a",
9233
+ {
9234
+ href: docsHref,
9235
+ target: "_blank",
9236
+ rel: "noreferrer noopener",
9237
+ 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",
9238
+ "aria-label": `${docsLabel} (opens in a new tab)`,
9239
+ children: [
9240
+ /* @__PURE__ */ jsx(BookOpen, { className: "h-3.5 w-3.5" }),
9241
+ /* @__PURE__ */ jsx("span", { className: "hidden md:inline", children: docsLabel }),
9242
+ /* @__PURE__ */ jsx(ExternalLink, { className: "hidden h-3 w-3 opacity-50 md:inline" })
9243
+ ]
8943
9244
  }
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,
9245
+ ) : null
9246
+ ] }),
9247
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 sm:gap-2", children: [
9248
+ errorCount > 0 ? /* @__PURE__ */ jsxs(
9249
+ Button,
8954
9250
  {
8955
- ariaLabel: "Preview theme",
8956
- options: THEME_OPTIONS,
8957
- value: previewTheme,
8958
- onChange: (next) => {
8959
- onPreviewThemeChange(next);
8960
- setThemeMenuOpen(false);
8961
- }
9251
+ type: "button",
9252
+ variant: "ghost",
9253
+ size: "sm",
9254
+ onClick: onOpenIssues,
9255
+ className: "border border-red-200! bg-red-50 text-red-700 hover:bg-red-100 hover:text-red-700",
9256
+ "aria-label": `${errorCount} ${errorCount === 1 ? "issue" : "issues"}`,
9257
+ children: [
9258
+ /* @__PURE__ */ jsx(AlertTriangle, { className: "h-3.5 w-3.5" }),
9259
+ /* @__PURE__ */ jsxs("span", { children: [
9260
+ errorCount,
9261
+ " ",
9262
+ /* @__PURE__ */ jsx("span", { className: "hidden md:inline", children: errorCount === 1 ? "issue" : "issues" })
9263
+ ] })
9264
+ ]
8962
9265
  }
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(
9266
+ ) : null,
9267
+ /* @__PURE__ */ jsxs(
9034
9268
  Button,
9035
9269
  {
9036
9270
  type: "button",
9037
9271
  variant: "ghost",
9038
9272
  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" })
9273
+ onClick: onClear,
9274
+ disabled: !canClear,
9275
+ className: "hover:bg-destructive/10 hover:text-destructive",
9276
+ "aria-label": "Clear all blocks",
9277
+ children: [
9278
+ /* @__PURE__ */ jsx(Trash2, { className: "h-3.5 w-3.5" }),
9279
+ /* @__PURE__ */ jsx("span", { className: "hidden md:inline", children: "Clear" })
9280
+ ]
9043
9281
  }
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
- ] }) })
9282
+ ),
9283
+ /* @__PURE__ */ jsxs(Button, { type: "button", variant: "ghost", size: "sm", onClick: onOpenJson, "aria-label": "View JSON", children: [
9284
+ /* @__PURE__ */ jsx(Code2, { className: "h-3.5 w-3.5" }),
9285
+ /* @__PURE__ */ jsx("span", { className: "hidden md:inline", children: "View JSON" })
9286
+ ] }),
9287
+ editBadge ? (
9288
+ // A message is loaded: split button. Main action updates it in
9289
+ // place; the menu also offers posting the blocks as a new message.
9290
+ /* @__PURE__ */ jsxs("div", { className: "flex items-stretch", children: [
9291
+ /* @__PURE__ */ jsxs(
9292
+ Button,
9293
+ {
9294
+ type: "button",
9295
+ size: "sm",
9296
+ onClick: onOpenUpdate,
9297
+ disabled: !canSend,
9298
+ "aria-label": updateButtonLabel,
9299
+ className: "rounded-r-none",
9300
+ children: [
9301
+ /* @__PURE__ */ jsx(Pencil, { className: "h-3.5 w-3.5" }),
9302
+ /* @__PURE__ */ jsx("span", { className: "hidden md:inline", children: updateButtonLabel })
9303
+ ]
9304
+ }
9305
+ ),
9306
+ /* @__PURE__ */ jsxs(Popover, { open: sendMenuOpen, onOpenChange: setSendMenuOpen, children: [
9307
+ /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
9308
+ Button,
9309
+ {
9310
+ type: "button",
9311
+ size: "sm",
9312
+ "aria-label": "More message options",
9313
+ "aria-haspopup": "menu",
9314
+ className: "rounded-l-none border-l border-l-primary-foreground/30! px-1.5",
9315
+ children: /* @__PURE__ */ jsx(ChevronDown, { className: "h-3.5 w-3.5" })
9316
+ }
9317
+ ) }),
9318
+ /* @__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: [
9319
+ /* @__PURE__ */ jsxs(
9320
+ "button",
9321
+ {
9322
+ type: "button",
9323
+ role: "menuitem",
9324
+ disabled: !canSend,
9325
+ onClick: () => {
9326
+ setSendMenuOpen(false);
9327
+ onOpenUpdate?.();
9328
+ },
9329
+ className: SEND_MENU_ITEM,
9330
+ children: [
9331
+ /* @__PURE__ */ jsx(Pencil, { className: "h-3.5 w-3.5" }),
9332
+ /* @__PURE__ */ jsx("span", { className: "flex-1", children: "Update message" })
9333
+ ]
9334
+ }
9335
+ ),
9336
+ /* @__PURE__ */ jsxs(
9337
+ "button",
9338
+ {
9339
+ type: "button",
9340
+ role: "menuitem",
9341
+ disabled: !canSend,
9342
+ onClick: () => {
9343
+ setSendMenuOpen(false);
9344
+ onOpenSend();
9345
+ },
9346
+ className: SEND_MENU_ITEM,
9347
+ children: [
9348
+ /* @__PURE__ */ jsx(Send, { className: "h-3.5 w-3.5" }),
9349
+ /* @__PURE__ */ jsx("span", { className: "flex-1", children: "Send as a new message" })
9350
+ ]
9351
+ }
9352
+ )
9353
+ ] }) })
9354
+ ] })
9355
+ ] })
9356
+ ) : /* @__PURE__ */ jsxs(Button, { type: "button", size: "sm", onClick: onOpenSend, disabled: !canSend, "aria-label": sendButtonLabel, children: [
9357
+ /* @__PURE__ */ jsx(Send, { className: "h-3.5 w-3.5" }),
9358
+ /* @__PURE__ */ jsx("span", { className: "hidden md:inline", children: sendButtonLabel })
9359
+ ] })
9360
+ ] })
9361
+ ] }),
9362
+ 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: [
9363
+ /* @__PURE__ */ jsx(Pencil, { className: "h-4 w-4 shrink-0 text-primary" }),
9364
+ /* @__PURE__ */ jsxs("span", { className: "min-w-0 flex-1 text-sm", children: [
9365
+ "References an existing message in ",
9366
+ /* @__PURE__ */ jsx("span", { className: "font-semibold", children: editBadge.channelLabel }),
9367
+ /* @__PURE__ */ jsx("span", { className: "ml-1 text-xs opacity-70", title: editBadge.ts, children: formatMessageTs(editBadge.ts) })
9081
9368
  ] }),
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 })
9369
+ /* @__PURE__ */ jsxs(Button, { type: "button", variant: "ghost", size: "sm", onClick: onExitEdit, className: "shrink-0", children: [
9370
+ /* @__PURE__ */ jsx(X, { className: "h-3.5 w-3.5" }),
9371
+ "Switch to a new message"
9085
9372
  ] })
9086
- ] })
9373
+ ] }) : null
9087
9374
  ] });
9088
9375
  }
9089
9376
  function Menu({
@@ -9141,52 +9428,132 @@ function Menu({
9141
9428
  );
9142
9429
  }) });
9143
9430
  }
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
9431
+ function UpdateDialog({
9432
+ open,
9433
+ onOpenChange,
9434
+ target,
9435
+ blocks,
9436
+ loadSendAsUserStatus,
9437
+ onUpdate,
9438
+ confirmUpdateLabel = "Update message",
9439
+ errorCount,
9440
+ onShowIssues
9151
9441
  }) {
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
- ]
9442
+ const asUser = target.editableVia === "user";
9443
+ const [userStatus, setUserStatus] = useState(null);
9444
+ const [status, setStatus] = useState({ kind: "idle" });
9445
+ const loadSendAsUserStatusRef = useRef(loadSendAsUserStatus);
9446
+ useEffect(() => {
9447
+ loadSendAsUserStatusRef.current = loadSendAsUserStatus;
9448
+ });
9449
+ const refreshSendAsUser = useCallback(() => {
9450
+ loadSendAsUserStatusRef.current().then(setUserStatus).catch(() => setUserStatus({ canSendAsUser: false }));
9451
+ }, []);
9452
+ useEffect(() => {
9453
+ if (!open) {
9454
+ return;
9164
9455
  }
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
- ]
9456
+ setStatus({ kind: "idle" });
9457
+ if (asUser) {
9458
+ setUserStatus(null);
9459
+ refreshSendAsUser();
9188
9460
  }
9189
- );
9461
+ }, [open, asUser, refreshSendAsUser]);
9462
+ useEffect(() => {
9463
+ if (!open || !asUser) {
9464
+ return;
9465
+ }
9466
+ const handler = () => refreshSendAsUser();
9467
+ window.addEventListener("focus", handler);
9468
+ return () => window.removeEventListener("focus", handler);
9469
+ }, [open, asUser, refreshSendAsUser]);
9470
+ const needsSignIn = asUser && userStatus !== null && !userStatus.canSendAsUser;
9471
+ const handleSubmit = async () => {
9472
+ setStatus({ kind: "updating" });
9473
+ try {
9474
+ const result = await onUpdate({
9475
+ channelId: target.channelId,
9476
+ ts: target.ts,
9477
+ blocks: toSlackBlocks(blocks),
9478
+ asUser
9479
+ });
9480
+ if (result.ok) {
9481
+ setStatus({ kind: "idle" });
9482
+ onOpenChange(false);
9483
+ return;
9484
+ }
9485
+ setStatus({ kind: "error", error: result.error ?? "Update failed." });
9486
+ } catch (e) {
9487
+ setStatus({ kind: "error", error: e instanceof Error ? e.message : "Update failed." });
9488
+ }
9489
+ };
9490
+ const channelLabel = target.channelName ? `#${target.channelName}` : target.channelId;
9491
+ return /* @__PURE__ */ jsx(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxs(DialogContent, { className: "w-[calc(100vw-1.5rem)] max-w-lg rounded-lg", children: [
9492
+ /* @__PURE__ */ jsxs(DialogHeader, { children: [
9493
+ /* @__PURE__ */ jsx(DialogTitle, { children: "Update message" }),
9494
+ /* @__PURE__ */ jsx(DialogDescription, { children: "This replaces the message that's already posted. It stays in the same channel and can't be moved." })
9495
+ ] }),
9496
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4", children: [
9497
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5", children: [
9498
+ /* @__PURE__ */ jsx(Label, { children: "Channel" }),
9499
+ /* @__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 })
9500
+ ] }),
9501
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5", children: [
9502
+ /* @__PURE__ */ jsx(Label, { children: "Posting as" }),
9503
+ /* @__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" }),
9504
+ needsSignIn && userStatus?.oauthUrl && isSafeHref(userStatus.oauthUrl) && /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
9505
+ /* @__PURE__ */ jsxs(
9506
+ "a",
9507
+ {
9508
+ href: userStatus.oauthUrl,
9509
+ target: "_blank",
9510
+ rel: "noopener noreferrer",
9511
+ className: "inline-flex items-center gap-1 text-primary hover:underline",
9512
+ children: [
9513
+ "Sign in with Slack ",
9514
+ /* @__PURE__ */ jsx(ExternalLink, { className: "h-3 w-3" })
9515
+ ]
9516
+ }
9517
+ ),
9518
+ " ",
9519
+ "to update your own message."
9520
+ ] }),
9521
+ needsSignIn && !userStatus?.oauthUrl && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Sign in with Slack to update your own message." })
9522
+ ] }),
9523
+ errorCount > 0 ? /* @__PURE__ */ jsxs(
9524
+ "button",
9525
+ {
9526
+ type: "button",
9527
+ onClick: onShowIssues,
9528
+ 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",
9529
+ children: [
9530
+ /* @__PURE__ */ jsx(AlertTriangle, { className: "h-3.5 w-3.5 shrink-0" }),
9531
+ /* @__PURE__ */ jsxs("span", { className: "flex-1", children: [
9532
+ "Fix ",
9533
+ errorCount,
9534
+ " ",
9535
+ errorCount === 1 ? "issue" : "issues",
9536
+ " before updating."
9537
+ ] }),
9538
+ /* @__PURE__ */ jsx("span", { className: "shrink-0 underline", children: "Show issues" })
9539
+ ]
9540
+ }
9541
+ ) : null,
9542
+ 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 })
9543
+ ] }),
9544
+ /* @__PURE__ */ jsxs(DialogFooter, { children: [
9545
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", onClick: () => onOpenChange(false), children: "Cancel" }),
9546
+ /* @__PURE__ */ jsx(
9547
+ Button,
9548
+ {
9549
+ type: "button",
9550
+ onClick: handleSubmit,
9551
+ disabled: status.kind === "updating" || blocks.length === 0 || errorCount > 0 || needsSignIn,
9552
+ children: status.kind === "updating" ? "Updating\u2026" : confirmUpdateLabel
9553
+ }
9554
+ )
9555
+ ] })
9556
+ ] }) });
9190
9557
  }
9191
9558
  function BlockKitchen(props) {
9192
9559
  const {
@@ -9198,6 +9565,10 @@ function BlockKitchen(props) {
9198
9565
  loadChannels,
9199
9566
  loadSendAsUserStatus,
9200
9567
  onSend,
9568
+ editing,
9569
+ loadButtonLabel,
9570
+ updateButtonLabel,
9571
+ confirmUpdateLabel,
9201
9572
  palette,
9202
9573
  disabledBlockTypes,
9203
9574
  showPaletteSearch,
@@ -9243,6 +9614,10 @@ function BlockKitchen(props) {
9243
9614
  }, [blocks]);
9244
9615
  const [jsonOpen, setJsonOpen] = useState(false);
9245
9616
  const [sendOpen, setSendOpen] = useState(false);
9617
+ const [updateOpen, setUpdateOpen] = useState(false);
9618
+ const [loadOpen, setLoadOpen] = useState(false);
9619
+ const [editTarget, setEditTarget] = useState(null);
9620
+ const activeEditTarget = editing ? editTarget : null;
9246
9621
  const [issuesOpen, setIssuesOpen] = useState(false);
9247
9622
  const [paletteOpen, setPaletteOpen] = useState(false);
9248
9623
  const [openBlockId, setOpenBlockId] = useState(null);
@@ -9363,7 +9738,21 @@ function BlockKitchen(props) {
9363
9738
  allowedSurfaces,
9364
9739
  showThemeControl: isPreviewThemeControlled ? false : showThemeControl,
9365
9740
  docsLink,
9366
- sendButtonLabel
9741
+ sendButtonLabel,
9742
+ editingEnabled: !!editing,
9743
+ editBadge: activeEditTarget ? {
9744
+ channelLabel: activeEditTarget.channelName ? `#${activeEditTarget.channelName}` : activeEditTarget.channelId,
9745
+ ts: activeEditTarget.ts
9746
+ } : null,
9747
+ onOpenLoad: () => setLoadOpen(true),
9748
+ onOpenUpdate: () => setUpdateOpen(true),
9749
+ onExitEdit: () => {
9750
+ setEditTarget(null);
9751
+ replaceAll([]);
9752
+ setLoadOpen(true);
9753
+ },
9754
+ loadButtonLabel,
9755
+ updateButtonLabel
9367
9756
  }
9368
9757
  ),
9369
9758
  /* @__PURE__ */ jsxs("div", { className: "flex min-h-0 flex-1 items-stretch", children: [
@@ -9382,6 +9771,8 @@ function BlockKitchen(props) {
9382
9771
  {
9383
9772
  blocks,
9384
9773
  workspaceName,
9774
+ authorName: activeEditTarget?.username,
9775
+ authorIcon: activeEditTarget?.iconUrl,
9385
9776
  previewHooks: mergedPreviewHooks,
9386
9777
  previewTheme,
9387
9778
  previewSurface,
@@ -9447,6 +9838,53 @@ function BlockKitchen(props) {
9447
9838
  }
9448
9839
  }
9449
9840
  ),
9841
+ activeEditTarget && editing ? /* @__PURE__ */ jsx(
9842
+ UpdateDialog,
9843
+ {
9844
+ open: updateOpen,
9845
+ onOpenChange: setUpdateOpen,
9846
+ target: activeEditTarget,
9847
+ blocks: blockPayloads,
9848
+ loadSendAsUserStatus,
9849
+ onUpdate: editing.onUpdate,
9850
+ confirmUpdateLabel,
9851
+ errorCount: validation.total,
9852
+ onShowIssues: () => {
9853
+ setUpdateOpen(false);
9854
+ setIssuesOpen(true);
9855
+ }
9856
+ }
9857
+ ) : null,
9858
+ editing ? /* @__PURE__ */ jsx(
9859
+ LoadMessageDialog,
9860
+ {
9861
+ open: loadOpen,
9862
+ onOpenChange: setLoadOpen,
9863
+ onLoadMessage: editing.onLoadMessage,
9864
+ loadRecentMessages: editing.loadRecentMessages,
9865
+ loadChannels,
9866
+ onLoaded: (result) => {
9867
+ replaceAll(result.blocks);
9868
+ setEditTarget({
9869
+ channelId: result.channelId,
9870
+ channelName: result.channelName,
9871
+ ts: result.ts,
9872
+ editableVia: result.editableVia,
9873
+ workspaceName: result.workspaceName,
9874
+ username: result.username,
9875
+ iconUrl: result.iconUrl
9876
+ });
9877
+ setLoadOpen(false);
9878
+ },
9879
+ onOpenAsNew: (loadedBlocks) => {
9880
+ if (loadedBlocks) {
9881
+ replaceAll(loadedBlocks);
9882
+ }
9883
+ setEditTarget(null);
9884
+ setLoadOpen(false);
9885
+ }
9886
+ }
9887
+ ) : null,
9450
9888
  /* @__PURE__ */ jsx(
9451
9889
  IssuesSheet,
9452
9890
  {