@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.cjs CHANGED
@@ -2724,6 +2724,91 @@ function JsonDrawer({
2724
2724
  ] })
2725
2725
  ] }) });
2726
2726
  }
2727
+ var buttonVariants = classVarianceAuthority.cva(
2728
+ "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",
2729
+ {
2730
+ variants: {
2731
+ variant: {
2732
+ default: "bg-primary text-primary-foreground shadow hover:bg-primary/90",
2733
+ destructive: "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
2734
+ outline: "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
2735
+ secondary: "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
2736
+ ghost: "hover:bg-accent hover:text-accent-foreground",
2737
+ link: "text-primary underline-offset-4 hover:underline"
2738
+ },
2739
+ size: {
2740
+ default: "h-9 px-4 py-2",
2741
+ sm: "h-8 rounded-md px-3 text-xs",
2742
+ lg: "h-10 rounded-md px-8",
2743
+ icon: "h-9 w-9"
2744
+ }
2745
+ },
2746
+ defaultVariants: {
2747
+ variant: "default",
2748
+ size: "default"
2749
+ }
2750
+ }
2751
+ );
2752
+ var Button = React__namespace.forwardRef(
2753
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
2754
+ const Comp = asChild ? reactSlot.Slot : "button";
2755
+ return /* @__PURE__ */ jsxRuntime.jsx(Comp, { className: cn(buttonVariants({ variant, size, className })), ref, ...props });
2756
+ }
2757
+ );
2758
+ Button.displayName = "Button";
2759
+ var Dialog = DialogPrimitive__namespace.Root;
2760
+ var DialogPortal = DialogPrimitive__namespace.Portal;
2761
+ var DialogOverlay = React__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
2762
+ DialogPrimitive__namespace.Overlay,
2763
+ {
2764
+ ref,
2765
+ className: cn(
2766
+ "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",
2767
+ className
2768
+ ),
2769
+ ...props
2770
+ }
2771
+ ));
2772
+ DialogOverlay.displayName = DialogPrimitive__namespace.Overlay.displayName;
2773
+ var DialogContent = React__namespace.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsxs(DialogPortal, { children: [
2774
+ /* @__PURE__ */ jsxRuntime.jsx(DialogOverlay, {}),
2775
+ /* @__PURE__ */ jsxRuntime.jsxs(
2776
+ DialogPrimitive__namespace.Content,
2777
+ {
2778
+ ref,
2779
+ className: cn(
2780
+ "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",
2781
+ className
2782
+ ),
2783
+ ...props,
2784
+ children: [
2785
+ children,
2786
+ /* @__PURE__ */ jsxRuntime.jsxs(DialogPrimitive__namespace.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: [
2787
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "h-4 w-4" }),
2788
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "sr-only", children: "Close" })
2789
+ ] })
2790
+ ]
2791
+ }
2792
+ )
2793
+ ] }));
2794
+ DialogContent.displayName = DialogPrimitive__namespace.Content.displayName;
2795
+ function DialogHeader({ className, ...props }) {
2796
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("flex flex-col space-y-1.5 text-center sm:text-left", className), ...props });
2797
+ }
2798
+ function DialogFooter({ className, ...props }) {
2799
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className), ...props });
2800
+ }
2801
+ var DialogTitle = React__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
2802
+ DialogPrimitive__namespace.Title,
2803
+ {
2804
+ ref,
2805
+ className: cn("text-lg font-semibold leading-none tracking-tight", className),
2806
+ ...props
2807
+ }
2808
+ ));
2809
+ DialogTitle.displayName = DialogPrimitive__namespace.Title.displayName;
2810
+ var DialogDescription = React__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(DialogPrimitive__namespace.Description, { ref, className: cn("text-sm text-muted-foreground", className), ...props }));
2811
+ DialogDescription.displayName = DialogPrimitive__namespace.Description.displayName;
2727
2812
  var Input = React__namespace.forwardRef(
2728
2813
  ({ className, type, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
2729
2814
  "input",
@@ -2739,6 +2824,213 @@ var Input = React__namespace.forwardRef(
2739
2824
  )
2740
2825
  );
2741
2826
  Input.displayName = "Input";
2827
+ var Label = React__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
2828
+ LabelPrimitive__namespace.Root,
2829
+ {
2830
+ ref,
2831
+ className: cn("text-sm font-medium leading-none text-foreground", className),
2832
+ ...props
2833
+ }
2834
+ ));
2835
+ Label.displayName = LabelPrimitive__namespace.Root.displayName;
2836
+ function recentToResult(msg) {
2837
+ return {
2838
+ ok: true,
2839
+ channelId: msg.channelId,
2840
+ channelName: msg.channelName,
2841
+ ts: msg.ts,
2842
+ blocks: msg.blocks,
2843
+ editableVia: msg.editableVia ?? "bot",
2844
+ workspaceName: msg.workspaceName,
2845
+ username: msg.username,
2846
+ iconUrl: msg.iconUrl
2847
+ };
2848
+ }
2849
+ function LoadMessageDialog({
2850
+ open,
2851
+ onOpenChange,
2852
+ onLoadMessage,
2853
+ loadRecentMessages,
2854
+ onLoaded,
2855
+ onOpenAsNew
2856
+ }) {
2857
+ const [link, setLink] = React.useState("");
2858
+ const [status, setStatus] = React.useState({ kind: "idle" });
2859
+ const [recent, setRecent] = React.useState(null);
2860
+ const [recentError, setRecentError] = React.useState(null);
2861
+ const inputRef = React.useRef(null);
2862
+ const onLoadMessageRef = React.useRef(onLoadMessage);
2863
+ const loadRecentMessagesRef = React.useRef(loadRecentMessages);
2864
+ React.useEffect(() => {
2865
+ onLoadMessageRef.current = onLoadMessage;
2866
+ loadRecentMessagesRef.current = loadRecentMessages;
2867
+ });
2868
+ const hasRecent = !!loadRecentMessages;
2869
+ React.useEffect(() => {
2870
+ if (!open) {
2871
+ return;
2872
+ }
2873
+ setLink("");
2874
+ setStatus({ kind: "idle" });
2875
+ if (!loadRecentMessagesRef.current) {
2876
+ setRecent([]);
2877
+ setRecentError(null);
2878
+ return;
2879
+ }
2880
+ setRecent(null);
2881
+ setRecentError(null);
2882
+ let cancelled = false;
2883
+ loadRecentMessagesRef.current().then((list) => {
2884
+ if (!cancelled) {
2885
+ setRecent(list);
2886
+ }
2887
+ }).catch((e) => {
2888
+ if (!cancelled) {
2889
+ setRecentError(e instanceof Error ? e.message : "Failed to load recent messages.");
2890
+ }
2891
+ });
2892
+ return () => {
2893
+ cancelled = true;
2894
+ };
2895
+ }, [open]);
2896
+ const handleLoad = async () => {
2897
+ const trimmed = link.trim();
2898
+ if (!trimmed) {
2899
+ setStatus({ kind: "error", error: "Paste a Slack message link first." });
2900
+ return;
2901
+ }
2902
+ setStatus({ kind: "loading" });
2903
+ try {
2904
+ const result = await onLoadMessageRef.current({ link: trimmed });
2905
+ if (result.ok) {
2906
+ onLoaded(result);
2907
+ return;
2908
+ }
2909
+ setStatus({ kind: "not-editable", reason: result.reason, blocks: result.blocks });
2910
+ } catch (e) {
2911
+ setStatus({ kind: "error", error: e instanceof Error ? e.message : "Failed to load message." });
2912
+ }
2913
+ };
2914
+ const notEditable = status.kind === "not-editable" ? status : null;
2915
+ return /* @__PURE__ */ jsxRuntime.jsx(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxRuntime.jsxs(
2916
+ DialogContent,
2917
+ {
2918
+ className: "w-[calc(100vw-1.5rem)] max-w-lg rounded-lg",
2919
+ onOpenAutoFocus: (e) => {
2920
+ e.preventDefault();
2921
+ inputRef.current?.focus();
2922
+ },
2923
+ children: [
2924
+ /* @__PURE__ */ jsxRuntime.jsxs(DialogHeader, { children: [
2925
+ /* @__PURE__ */ jsxRuntime.jsx(DialogTitle, { children: "Edit an existing message" }),
2926
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start gap-1.5", children: [
2927
+ /* @__PURE__ */ jsxRuntime.jsx(DialogDescription, { children: hasRecent ? "Paste a Slack message link, or pick a recent message your app posted." : `Paste a Slack message link (Slack's "Copy link") to load its blocks for editing.` }),
2928
+ /* @__PURE__ */ jsxRuntime.jsxs(Tooltip, { children: [
2929
+ /* @__PURE__ */ jsxRuntime.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
2930
+ "button",
2931
+ {
2932
+ type: "button",
2933
+ "aria-label": "How to copy a message link",
2934
+ 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",
2935
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Info, { className: "h-3.5 w-3.5" })
2936
+ }
2937
+ ) }),
2938
+ /* @__PURE__ */ jsxRuntime.jsxs(TooltipContent, { className: "max-w-xs leading-relaxed", children: [
2939
+ "In Slack, hover over the message and click the ",
2940
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { children: "\u22EE More actions" }),
2941
+ " button (or right-click the message), then choose ",
2942
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { children: "Copy link" }),
2943
+ "."
2944
+ ] })
2945
+ ] })
2946
+ ] })
2947
+ ] }),
2948
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-4", children: [
2949
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1.5", children: [
2950
+ /* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: "message-link", children: "Message link" }),
2951
+ /* @__PURE__ */ jsxRuntime.jsx(
2952
+ Input,
2953
+ {
2954
+ ref: inputRef,
2955
+ id: "message-link",
2956
+ value: link,
2957
+ onChange: (e) => {
2958
+ setLink(e.target.value);
2959
+ if (status.kind !== "idle" && status.kind !== "loading") {
2960
+ setStatus({ kind: "idle" });
2961
+ }
2962
+ },
2963
+ onKeyDown: (e) => {
2964
+ if (e.key === "Enter" && status.kind !== "loading") {
2965
+ e.preventDefault();
2966
+ handleLoad();
2967
+ }
2968
+ },
2969
+ placeholder: "https://your-workspace.slack.com/archives/C\u2026/p\u2026",
2970
+ autoComplete: "off",
2971
+ spellCheck: false
2972
+ }
2973
+ )
2974
+ ] }),
2975
+ status.kind === "error" && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "rounded-md border border-destructive/30 bg-destructive/5 p-2 text-xs text-destructive", children: status.error }),
2976
+ notEditable && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2 rounded-md border border-amber-200! bg-amber-50 p-3 text-xs text-amber-800", children: [
2977
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start gap-2", children: [
2978
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertTriangle, { className: "mt-0.5 h-3.5 w-3.5 shrink-0" }),
2979
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1", children: notEditable.reason })
2980
+ ] }),
2981
+ /* @__PURE__ */ jsxRuntime.jsx(
2982
+ Button,
2983
+ {
2984
+ type: "button",
2985
+ variant: "outline",
2986
+ size: "sm",
2987
+ className: "self-start",
2988
+ onClick: () => onOpenAsNew(notEditable.blocks),
2989
+ children: "Open as a new message instead"
2990
+ }
2991
+ )
2992
+ ] }),
2993
+ hasRecent && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2", children: [
2994
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 text-xs text-muted-foreground", children: [
2995
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "h-px flex-1 bg-border" }),
2996
+ "or pick a recent message",
2997
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "h-px flex-1 bg-border" })
2998
+ ] }),
2999
+ recent === null && !recentError && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: "Loading recent messages\u2026" }),
3000
+ recentError && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-destructive", children: recentError }),
3001
+ recent && recent.length === 0 && !recentError && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: "No recent messages from this app." }),
3002
+ recent && recent.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex max-h-48 flex-col gap-1 overflow-y-auto", children: recent.map((m) => {
3003
+ const asUser = (m.editableVia ?? "bot") === "user";
3004
+ return /* @__PURE__ */ jsxRuntime.jsxs(
3005
+ "button",
3006
+ {
3007
+ type: "button",
3008
+ onClick: () => onLoaded(recentToResult(m)),
3009
+ 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",
3010
+ children: [
3011
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-baseline justify-between gap-2", children: [
3012
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex min-w-0 items-baseline gap-1.5", children: [
3013
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate font-medium", children: m.channelName ? `#${m.channelName}` : m.channelId }),
3014
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "shrink-0 rounded border px-1 py-px text-[10px] font-medium uppercase tracking-wide text-muted-foreground", children: asUser ? "You" : "Bot" })
3015
+ ] }),
3016
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "shrink-0 font-mono text-xs text-muted-foreground", children: m.ts })
3017
+ ] }),
3018
+ m.label && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate text-xs text-muted-foreground", children: m.label })
3019
+ ]
3020
+ },
3021
+ `${m.channelId}:${m.ts}`
3022
+ );
3023
+ }) })
3024
+ ] })
3025
+ ] }),
3026
+ /* @__PURE__ */ jsxRuntime.jsxs(DialogFooter, { children: [
3027
+ /* @__PURE__ */ jsxRuntime.jsx(Button, { type: "button", variant: "ghost", onClick: () => onOpenChange(false), children: "Cancel" }),
3028
+ /* @__PURE__ */ jsxRuntime.jsx(Button, { type: "button", onClick: handleLoad, disabled: status.kind === "loading" || !link.trim(), children: status.kind === "loading" ? "Loading\u2026" : "Load message" })
3029
+ ] })
3030
+ ]
3031
+ }
3032
+ ) });
3033
+ }
2742
3034
  var PALETTE_DRAG_PREFIX = "palette:";
2743
3035
  function parsePaletteDragId(id) {
2744
3036
  if (typeof id !== "string" || !id.startsWith(PALETTE_DRAG_PREFIX)) {
@@ -2938,100 +3230,6 @@ function PaletteItem({
2938
3230
  }
2939
3231
  );
2940
3232
  }
2941
- var buttonVariants = classVarianceAuthority.cva(
2942
- "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",
2943
- {
2944
- variants: {
2945
- variant: {
2946
- default: "bg-primary text-primary-foreground shadow hover:bg-primary/90",
2947
- destructive: "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
2948
- outline: "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
2949
- secondary: "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
2950
- ghost: "hover:bg-accent hover:text-accent-foreground",
2951
- link: "text-primary underline-offset-4 hover:underline"
2952
- },
2953
- size: {
2954
- default: "h-9 px-4 py-2",
2955
- sm: "h-8 rounded-md px-3 text-xs",
2956
- lg: "h-10 rounded-md px-8",
2957
- icon: "h-9 w-9"
2958
- }
2959
- },
2960
- defaultVariants: {
2961
- variant: "default",
2962
- size: "default"
2963
- }
2964
- }
2965
- );
2966
- var Button = React__namespace.forwardRef(
2967
- ({ className, variant, size, asChild = false, ...props }, ref) => {
2968
- const Comp = asChild ? reactSlot.Slot : "button";
2969
- return /* @__PURE__ */ jsxRuntime.jsx(Comp, { className: cn(buttonVariants({ variant, size, className })), ref, ...props });
2970
- }
2971
- );
2972
- Button.displayName = "Button";
2973
- var Dialog = DialogPrimitive__namespace.Root;
2974
- var DialogPortal = DialogPrimitive__namespace.Portal;
2975
- var DialogOverlay = React__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
2976
- DialogPrimitive__namespace.Overlay,
2977
- {
2978
- ref,
2979
- className: cn(
2980
- "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",
2981
- className
2982
- ),
2983
- ...props
2984
- }
2985
- ));
2986
- DialogOverlay.displayName = DialogPrimitive__namespace.Overlay.displayName;
2987
- var DialogContent = React__namespace.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsxs(DialogPortal, { children: [
2988
- /* @__PURE__ */ jsxRuntime.jsx(DialogOverlay, {}),
2989
- /* @__PURE__ */ jsxRuntime.jsxs(
2990
- DialogPrimitive__namespace.Content,
2991
- {
2992
- ref,
2993
- className: cn(
2994
- "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",
2995
- className
2996
- ),
2997
- ...props,
2998
- children: [
2999
- children,
3000
- /* @__PURE__ */ jsxRuntime.jsxs(DialogPrimitive__namespace.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: [
3001
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "h-4 w-4" }),
3002
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "sr-only", children: "Close" })
3003
- ] })
3004
- ]
3005
- }
3006
- )
3007
- ] }));
3008
- DialogContent.displayName = DialogPrimitive__namespace.Content.displayName;
3009
- function DialogHeader({ className, ...props }) {
3010
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("flex flex-col space-y-1.5 text-center sm:text-left", className), ...props });
3011
- }
3012
- function DialogFooter({ className, ...props }) {
3013
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className), ...props });
3014
- }
3015
- var DialogTitle = React__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
3016
- DialogPrimitive__namespace.Title,
3017
- {
3018
- ref,
3019
- className: cn("text-lg font-semibold leading-none tracking-tight", className),
3020
- ...props
3021
- }
3022
- ));
3023
- DialogTitle.displayName = DialogPrimitive__namespace.Title.displayName;
3024
- var DialogDescription = React__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(DialogPrimitive__namespace.Description, { ref, className: cn("text-sm text-muted-foreground", className), ...props }));
3025
- DialogDescription.displayName = DialogPrimitive__namespace.Description.displayName;
3026
- var Label = React__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
3027
- LabelPrimitive__namespace.Root,
3028
- {
3029
- ref,
3030
- className: cn("text-sm font-medium leading-none text-foreground", className),
3031
- ...props
3032
- }
3033
- ));
3034
- Label.displayName = LabelPrimitive__namespace.Root.displayName;
3035
3233
  function SendDialog({
3036
3234
  open,
3037
3235
  onOpenChange,
@@ -4172,7 +4370,7 @@ function TextItemCard({
4172
4370
  id: `ctx-text-${index}`,
4173
4371
  value: element.text,
4174
4372
  rows: 2,
4175
- placeholder: "e.g. Posted by @stephen",
4373
+ placeholder: "e.g. Posted by @riley",
4176
4374
  onChange: (e) => onChange({ ...element, text: e.target.value })
4177
4375
  }
4178
4376
  ) }),
@@ -4257,7 +4455,7 @@ function ImageItemCard({
4257
4455
  {
4258
4456
  id: `ctx-img-alt-${index}`,
4259
4457
  value: element.alt_text,
4260
- placeholder: "e.g. Stephen's avatar",
4458
+ placeholder: "e.g. Riley's avatar",
4261
4459
  onChange: (e) => onChange({ ...element, alt_text: e.target.value })
4262
4460
  }
4263
4461
  ) })
@@ -8590,6 +8788,8 @@ var SURFACE_DROPPABLE_ID = "builder-surface";
8590
8788
  function Surface({
8591
8789
  blocks,
8592
8790
  workspaceName,
8791
+ authorName,
8792
+ authorIcon,
8593
8793
  previewHooks,
8594
8794
  previewTheme,
8595
8795
  previewSurface = "message",
@@ -8665,16 +8865,20 @@ function Surface({
8665
8865
  {
8666
8866
  "aria-label": "Block preview",
8667
8867
  className: cn("flex min-w-0 flex-1 flex-col overflow-y-auto bg-muted p-3 sm:p-6"),
8668
- children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mx-auto w-full max-w-2xl", children: previewSurface === "modal" ? /* @__PURE__ */ jsxRuntime.jsx(ModalFrame, { isDark, children: blocksList }) : previewSurface === "app_home" ? /* @__PURE__ */ jsxRuntime.jsx(AppHomeFrame, { workspaceName, isDark, children: blocksList }) : /* @__PURE__ */ jsxRuntime.jsx(MessageFrame, { workspaceName, isDark, children: blocksList }) })
8868
+ children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mx-auto w-full max-w-2xl", children: previewSurface === "modal" ? /* @__PURE__ */ jsxRuntime.jsx(ModalFrame, { isDark, children: blocksList }) : previewSurface === "app_home" ? /* @__PURE__ */ jsxRuntime.jsx(AppHomeFrame, { workspaceName, isDark, children: blocksList }) : /* @__PURE__ */ jsxRuntime.jsx(MessageFrame, { workspaceName, authorName, authorIcon, isDark, children: blocksList }) })
8669
8869
  }
8670
8870
  );
8671
8871
  }
8672
8872
  function MessageFrame({
8673
8873
  workspaceName,
8874
+ authorName,
8875
+ authorIcon,
8674
8876
  isDark,
8675
8877
  children
8676
8878
  }) {
8677
- const initial = (workspaceName ?? "A").slice(0, 1).toUpperCase();
8879
+ const displayName = authorName ?? workspaceName ?? "Your app";
8880
+ const initial = displayName.slice(0, 1).toUpperCase();
8881
+ const safeIcon = authorIcon && isSafeImageSrc(authorIcon) ? authorIcon : null;
8678
8882
  return /* @__PURE__ */ jsxRuntime.jsxs(
8679
8883
  "div",
8680
8884
  {
@@ -8688,7 +8892,7 @@ function MessageFrame({
8688
8892
  {
8689
8893
  className: cn("flex items-center gap-2 px-5 pt-3 pb-1 text-xs", isDark ? "text-white/60" : "text-[#616061]"),
8690
8894
  children: [
8691
- /* @__PURE__ */ jsxRuntime.jsx(
8895
+ safeIcon ? /* @__PURE__ */ jsxRuntime.jsx("img", { src: safeIcon, alt: "", className: "h-7 w-7 shrink-0 rounded object-cover" }) : /* @__PURE__ */ jsxRuntime.jsx(
8692
8896
  "span",
8693
8897
  {
8694
8898
  className: cn(
@@ -8698,17 +8902,7 @@ function MessageFrame({
8698
8902
  children: initial
8699
8903
  }
8700
8904
  ),
8701
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("font-bold text-sm", isDark ? "text-white" : "text-[#1d1c1d]"), children: workspaceName ?? "Your app" }),
8702
- /* @__PURE__ */ jsxRuntime.jsx(
8703
- "span",
8704
- {
8705
- className: cn(
8706
- "rounded px-1 text-[10px] font-semibold",
8707
- isDark ? "bg-white/10 text-white/70" : "bg-[#f3f3f3] text-[#616061]"
8708
- ),
8709
- children: "APP"
8710
- }
8711
- ),
8905
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("font-bold text-sm", isDark ? "text-white" : "text-[#1d1c1d]"), children: displayName }),
8712
8906
  /* @__PURE__ */ jsxRuntime.jsx("span", { children: "10:37 AM" })
8713
8907
  ]
8714
8908
  }
@@ -8910,6 +9104,14 @@ var SURFACE_OPTIONS = [
8910
9104
  ];
8911
9105
  var DEFAULT_DOCS_HREF = "https://docs.slack.dev/reference/block-kit/blocks";
8912
9106
  var DEFAULT_DOCS_LABEL = "Docs";
9107
+ function formatMessageTs(ts) {
9108
+ const seconds = Number.parseFloat(ts);
9109
+ if (!Number.isFinite(seconds)) return ts;
9110
+ const date = new Date(seconds * 1e3);
9111
+ if (Number.isNaN(date.getTime())) return ts;
9112
+ return date.toLocaleString(void 0, { dateStyle: "medium", timeStyle: "short" });
9113
+ }
9114
+ 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";
8913
9115
  function Toolbar2({
8914
9116
  onClear,
8915
9117
  onOpenJson,
@@ -8926,7 +9128,14 @@ function Toolbar2({
8926
9128
  showThemeControl = true,
8927
9129
  docsLink,
8928
9130
  errorCount,
8929
- sendButtonLabel = "Send"
9131
+ sendButtonLabel = "Review & send",
9132
+ editingEnabled = false,
9133
+ editBadge,
9134
+ onOpenLoad,
9135
+ onOpenUpdate,
9136
+ onExitEdit,
9137
+ loadButtonLabel = "Load message",
9138
+ updateButtonLabel = "Review & update"
8930
9139
  }) {
8931
9140
  const activeTheme = THEME_OPTIONS.find((t) => t.value === previewTheme) ?? THEME_OPTIONS[0];
8932
9141
  const activeSurface = SURFACE_OPTIONS.find((s) => s.value === previewSurface) ?? SURFACE_OPTIONS[0];
@@ -8936,184 +9145,210 @@ function Toolbar2({
8936
9145
  const docsLabel = docsLink === false ? null : docsLink?.label ?? DEFAULT_DOCS_LABEL;
8937
9146
  const [surfaceMenuOpen, setSurfaceMenuOpen] = React.useState(false);
8938
9147
  const [themeMenuOpen, setThemeMenuOpen] = React.useState(false);
8939
- const [moreMenuOpen, setMoreMenuOpen] = React.useState(false);
8940
- return /* @__PURE__ */ jsxRuntime.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: [
8941
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 flex-1 items-center gap-1 sm:gap-2", children: [
8942
- onOpenPalette ? /* @__PURE__ */ jsxRuntime.jsxs(
8943
- Button,
8944
- {
8945
- type: "button",
8946
- variant: "ghost",
8947
- size: "sm",
8948
- onClick: onOpenPalette,
8949
- className: "md:hidden",
8950
- "aria-label": "Add a block",
8951
- children: [
8952
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, { className: "h-4 w-4" }),
8953
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Blocks" })
8954
- ]
8955
- }
8956
- ) : null,
8957
- showSurfaceControl ? /* @__PURE__ */ jsxRuntime.jsxs(Popover, { open: surfaceMenuOpen, onOpenChange: setSurfaceMenuOpen, children: [
8958
- /* @__PURE__ */ jsxRuntime.jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(Button, { type: "button", variant: "ghost", size: "sm", "aria-label": `Preview surface: ${activeSurface.label}`, children: [
8959
- /* @__PURE__ */ jsxRuntime.jsx(activeSurface.Icon, { className: "h-3.5 w-3.5" }),
8960
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "hidden sm:inline", children: activeSurface.label }),
8961
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: "h-3.5 w-3.5 opacity-60" })
8962
- ] }) }),
8963
- /* @__PURE__ */ jsxRuntime.jsx(PopoverContent, { align: "start", className: "w-36 p-1", children: /* @__PURE__ */ jsxRuntime.jsx(
8964
- Menu,
9148
+ const [sendMenuOpen, setSendMenuOpen] = React.useState(false);
9149
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
9150
+ /* @__PURE__ */ jsxRuntime.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: [
9151
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 flex-1 items-center gap-1 sm:gap-2", children: [
9152
+ editingEnabled && !editBadge ? /* @__PURE__ */ jsxRuntime.jsxs(Button, { type: "button", size: "sm", onClick: onOpenLoad, "aria-label": loadButtonLabel, children: [
9153
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Pencil, { className: "h-3.5 w-3.5" }),
9154
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "hidden md:inline", children: loadButtonLabel })
9155
+ ] }) : null,
9156
+ onOpenPalette ? /* @__PURE__ */ jsxRuntime.jsxs(
9157
+ Button,
8965
9158
  {
8966
- ariaLabel: "Preview surface",
8967
- options: surfaceOptions,
8968
- value: previewSurface,
8969
- onChange: (next) => {
8970
- onPreviewSurfaceChange(next);
8971
- setSurfaceMenuOpen(false);
9159
+ type: "button",
9160
+ variant: "ghost",
9161
+ size: "sm",
9162
+ onClick: onOpenPalette,
9163
+ className: "md:hidden",
9164
+ "aria-label": "Add a block",
9165
+ children: [
9166
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, { className: "h-4 w-4" }),
9167
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Blocks" })
9168
+ ]
9169
+ }
9170
+ ) : null,
9171
+ showSurfaceControl ? /* @__PURE__ */ jsxRuntime.jsxs(Popover, { open: surfaceMenuOpen, onOpenChange: setSurfaceMenuOpen, children: [
9172
+ /* @__PURE__ */ jsxRuntime.jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(Button, { type: "button", variant: "ghost", size: "sm", "aria-label": `Preview surface: ${activeSurface.label}`, children: [
9173
+ /* @__PURE__ */ jsxRuntime.jsx(activeSurface.Icon, { className: "h-3.5 w-3.5" }),
9174
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "hidden md:inline", children: activeSurface.label }),
9175
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: "hidden h-3.5 w-3.5 opacity-60 md:inline" })
9176
+ ] }) }),
9177
+ /* @__PURE__ */ jsxRuntime.jsx(PopoverContent, { align: "start", className: "w-36 p-1", children: /* @__PURE__ */ jsxRuntime.jsx(
9178
+ Menu,
9179
+ {
9180
+ ariaLabel: "Preview surface",
9181
+ options: surfaceOptions,
9182
+ value: previewSurface,
9183
+ onChange: (next) => {
9184
+ onPreviewSurfaceChange(next);
9185
+ setSurfaceMenuOpen(false);
9186
+ }
9187
+ }
9188
+ ) })
9189
+ ] }) : null,
9190
+ showThemeControl ? /* @__PURE__ */ jsxRuntime.jsxs(Popover, { open: themeMenuOpen, onOpenChange: setThemeMenuOpen, children: [
9191
+ /* @__PURE__ */ jsxRuntime.jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(Button, { type: "button", variant: "ghost", size: "sm", "aria-label": `Preview theme: ${activeTheme.label}`, children: [
9192
+ /* @__PURE__ */ jsxRuntime.jsx(activeTheme.Icon, { className: "h-3.5 w-3.5" }),
9193
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "hidden md:inline", children: activeTheme.label }),
9194
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: "h-3.5 w-3.5 opacity-60" })
9195
+ ] }) }),
9196
+ /* @__PURE__ */ jsxRuntime.jsx(PopoverContent, { align: "start", className: "w-32 p-1", children: /* @__PURE__ */ jsxRuntime.jsx(
9197
+ Menu,
9198
+ {
9199
+ ariaLabel: "Preview theme",
9200
+ options: THEME_OPTIONS,
9201
+ value: previewTheme,
9202
+ onChange: (next) => {
9203
+ onPreviewThemeChange(next);
9204
+ setThemeMenuOpen(false);
9205
+ }
8972
9206
  }
9207
+ ) })
9208
+ ] }) : null,
9209
+ docsHref ? /* @__PURE__ */ jsxRuntime.jsxs(
9210
+ "a",
9211
+ {
9212
+ href: docsHref,
9213
+ target: "_blank",
9214
+ rel: "noreferrer noopener",
9215
+ 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",
9216
+ "aria-label": `${docsLabel} (opens in a new tab)`,
9217
+ children: [
9218
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.BookOpen, { className: "h-3.5 w-3.5" }),
9219
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "hidden md:inline", children: docsLabel }),
9220
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ExternalLink, { className: "hidden h-3 w-3 opacity-50 md:inline" })
9221
+ ]
8973
9222
  }
8974
- ) })
8975
- ] }) : null,
8976
- showThemeControl ? /* @__PURE__ */ jsxRuntime.jsxs(Popover, { open: themeMenuOpen, onOpenChange: setThemeMenuOpen, children: [
8977
- /* @__PURE__ */ jsxRuntime.jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(Button, { type: "button", variant: "ghost", size: "sm", "aria-label": `Preview theme: ${activeTheme.label}`, children: [
8978
- /* @__PURE__ */ jsxRuntime.jsx(activeTheme.Icon, { className: "h-3.5 w-3.5" }),
8979
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "hidden sm:inline", children: activeTheme.label }),
8980
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: "h-3.5 w-3.5 opacity-60" })
8981
- ] }) }),
8982
- /* @__PURE__ */ jsxRuntime.jsx(PopoverContent, { align: "start", className: "w-32 p-1", children: /* @__PURE__ */ jsxRuntime.jsx(
8983
- Menu,
9223
+ ) : null
9224
+ ] }),
9225
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1 sm:gap-2", children: [
9226
+ errorCount > 0 ? /* @__PURE__ */ jsxRuntime.jsxs(
9227
+ Button,
8984
9228
  {
8985
- ariaLabel: "Preview theme",
8986
- options: THEME_OPTIONS,
8987
- value: previewTheme,
8988
- onChange: (next) => {
8989
- onPreviewThemeChange(next);
8990
- setThemeMenuOpen(false);
8991
- }
9229
+ type: "button",
9230
+ variant: "ghost",
9231
+ size: "sm",
9232
+ onClick: onOpenIssues,
9233
+ className: "border border-red-200! bg-red-50 text-red-700 hover:bg-red-100 hover:text-red-700",
9234
+ "aria-label": `${errorCount} ${errorCount === 1 ? "issue" : "issues"}`,
9235
+ children: [
9236
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertTriangle, { className: "h-3.5 w-3.5" }),
9237
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
9238
+ errorCount,
9239
+ " ",
9240
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "hidden md:inline", children: errorCount === 1 ? "issue" : "issues" })
9241
+ ] })
9242
+ ]
8992
9243
  }
8993
- ) })
8994
- ] }) : null,
8995
- docsHref ? /* @__PURE__ */ jsxRuntime.jsxs(
8996
- "a",
8997
- {
8998
- href: docsHref,
8999
- target: "_blank",
9000
- rel: "noreferrer noopener",
9001
- 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",
9002
- "aria-label": `${docsLabel} (opens in a new tab)`,
9003
- children: [
9004
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.BookOpen, { className: "h-3.5 w-3.5" }),
9005
- docsLabel,
9006
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ExternalLink, { className: "h-3 w-3 opacity-50" })
9007
- ]
9008
- }
9009
- ) : null
9010
- ] }),
9011
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1 sm:gap-2", children: [
9012
- errorCount > 0 ? /* @__PURE__ */ jsxRuntime.jsxs(
9013
- Button,
9014
- {
9015
- type: "button",
9016
- variant: "ghost",
9017
- size: "sm",
9018
- onClick: onOpenIssues,
9019
- className: "border border-red-200! bg-red-50 text-red-700 hover:bg-red-100 hover:text-red-700",
9020
- "aria-label": `${errorCount} ${errorCount === 1 ? "issue" : "issues"}`,
9021
- children: [
9022
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertTriangle, { className: "h-3.5 w-3.5" }),
9023
- /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
9024
- errorCount,
9025
- " ",
9026
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "hidden sm:inline", children: errorCount === 1 ? "issue" : "issues" })
9027
- ] })
9028
- ]
9029
- }
9030
- ) : null,
9031
- /* @__PURE__ */ jsxRuntime.jsxs(
9032
- Button,
9033
- {
9034
- type: "button",
9035
- variant: "ghost",
9036
- size: "sm",
9037
- onClick: onClear,
9038
- disabled: !canClear,
9039
- className: "hidden hover:bg-destructive/10 hover:text-destructive sm:inline-flex",
9040
- "aria-label": "Clear all blocks",
9041
- children: [
9042
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { className: "h-3.5 w-3.5" }),
9043
- "Clear"
9044
- ]
9045
- }
9046
- ),
9047
- /* @__PURE__ */ jsxRuntime.jsxs(
9048
- Button,
9049
- {
9050
- type: "button",
9051
- variant: "ghost",
9052
- size: "sm",
9053
- onClick: onOpenJson,
9054
- "aria-label": "View JSON",
9055
- className: "hidden sm:inline-flex",
9056
- children: [
9057
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Code2, { className: "h-3.5 w-3.5" }),
9058
- "View JSON"
9059
- ]
9060
- }
9061
- ),
9062
- /* @__PURE__ */ jsxRuntime.jsxs(Popover, { open: moreMenuOpen, onOpenChange: setMoreMenuOpen, children: [
9063
- /* @__PURE__ */ jsxRuntime.jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
9244
+ ) : null,
9245
+ /* @__PURE__ */ jsxRuntime.jsxs(
9064
9246
  Button,
9065
9247
  {
9066
9248
  type: "button",
9067
9249
  variant: "ghost",
9068
9250
  size: "sm",
9069
- "aria-label": "More actions",
9070
- "aria-haspopup": "menu",
9071
- className: "px-2 sm:hidden",
9072
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MoreHorizontal, { className: "h-3.5 w-3.5" })
9251
+ onClick: onClear,
9252
+ disabled: !canClear,
9253
+ className: "hover:bg-destructive/10 hover:text-destructive",
9254
+ "aria-label": "Clear all blocks",
9255
+ children: [
9256
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { className: "h-3.5 w-3.5" }),
9257
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "hidden md:inline", children: "Clear" })
9258
+ ]
9073
9259
  }
9074
- ) }),
9075
- /* @__PURE__ */ jsxRuntime.jsx(PopoverContent, { align: "end", className: "w-44 p-1", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { role: "menu", "aria-label": "More actions", className: "flex flex-col", children: [
9076
- /* @__PURE__ */ jsxRuntime.jsx(
9077
- ActionMenuItem,
9078
- {
9079
- icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { className: "h-3.5 w-3.5" }),
9080
- onSelect: () => {
9081
- setMoreMenuOpen(false);
9082
- onClear();
9083
- },
9084
- disabled: !canClear,
9085
- tone: "destructive",
9086
- children: "Clear"
9087
- }
9088
- ),
9089
- /* @__PURE__ */ jsxRuntime.jsx(
9090
- ActionMenuItem,
9091
- {
9092
- icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Code2, { className: "h-3.5 w-3.5" }),
9093
- onSelect: () => {
9094
- setMoreMenuOpen(false);
9095
- onOpenJson();
9096
- },
9097
- children: "View JSON"
9098
- }
9099
- ),
9100
- docsHref ? /* @__PURE__ */ jsxRuntime.jsx(
9101
- ActionMenuLink,
9102
- {
9103
- href: docsHref,
9104
- icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.BookOpen, { className: "h-3.5 w-3.5" }),
9105
- trailingIcon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ExternalLink, { className: "h-3 w-3 opacity-50" }),
9106
- onSelect: () => setMoreMenuOpen(false),
9107
- children: docsLabel
9108
- }
9109
- ) : null
9110
- ] }) })
9260
+ ),
9261
+ /* @__PURE__ */ jsxRuntime.jsxs(Button, { type: "button", variant: "ghost", size: "sm", onClick: onOpenJson, "aria-label": "View JSON", children: [
9262
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Code2, { className: "h-3.5 w-3.5" }),
9263
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "hidden md:inline", children: "View JSON" })
9264
+ ] }),
9265
+ editBadge ? (
9266
+ // A message is loaded: split button. Main action updates it in
9267
+ // place; the menu also offers posting the blocks as a new message.
9268
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-stretch", children: [
9269
+ /* @__PURE__ */ jsxRuntime.jsxs(
9270
+ Button,
9271
+ {
9272
+ type: "button",
9273
+ size: "sm",
9274
+ onClick: onOpenUpdate,
9275
+ disabled: !canSend,
9276
+ "aria-label": updateButtonLabel,
9277
+ className: "rounded-r-none",
9278
+ children: [
9279
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Pencil, { className: "h-3.5 w-3.5" }),
9280
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "hidden md:inline", children: updateButtonLabel })
9281
+ ]
9282
+ }
9283
+ ),
9284
+ /* @__PURE__ */ jsxRuntime.jsxs(Popover, { open: sendMenuOpen, onOpenChange: setSendMenuOpen, children: [
9285
+ /* @__PURE__ */ jsxRuntime.jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
9286
+ Button,
9287
+ {
9288
+ type: "button",
9289
+ size: "sm",
9290
+ "aria-label": "More message options",
9291
+ "aria-haspopup": "menu",
9292
+ className: "rounded-l-none border-l border-l-primary-foreground/30! px-1.5",
9293
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: "h-3.5 w-3.5" })
9294
+ }
9295
+ ) }),
9296
+ /* @__PURE__ */ jsxRuntime.jsx(PopoverContent, { align: "end", className: "w-56 p-1", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { role: "menu", "aria-label": "Message options", className: "flex flex-col", children: [
9297
+ /* @__PURE__ */ jsxRuntime.jsxs(
9298
+ "button",
9299
+ {
9300
+ type: "button",
9301
+ role: "menuitem",
9302
+ disabled: !canSend,
9303
+ onClick: () => {
9304
+ setSendMenuOpen(false);
9305
+ onOpenUpdate?.();
9306
+ },
9307
+ className: SEND_MENU_ITEM,
9308
+ children: [
9309
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Pencil, { className: "h-3.5 w-3.5" }),
9310
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1", children: "Update message" })
9311
+ ]
9312
+ }
9313
+ ),
9314
+ /* @__PURE__ */ jsxRuntime.jsxs(
9315
+ "button",
9316
+ {
9317
+ type: "button",
9318
+ role: "menuitem",
9319
+ disabled: !canSend,
9320
+ onClick: () => {
9321
+ setSendMenuOpen(false);
9322
+ onOpenSend();
9323
+ },
9324
+ className: SEND_MENU_ITEM,
9325
+ children: [
9326
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Send, { className: "h-3.5 w-3.5" }),
9327
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1", children: "Send as a new message" })
9328
+ ]
9329
+ }
9330
+ )
9331
+ ] }) })
9332
+ ] })
9333
+ ] })
9334
+ ) : /* @__PURE__ */ jsxRuntime.jsxs(Button, { type: "button", size: "sm", onClick: onOpenSend, disabled: !canSend, "aria-label": sendButtonLabel, children: [
9335
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Send, { className: "h-3.5 w-3.5" }),
9336
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "hidden md:inline", children: sendButtonLabel })
9337
+ ] })
9338
+ ] })
9339
+ ] }),
9340
+ editingEnabled && editBadge ? /* @__PURE__ */ jsxRuntime.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: [
9341
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Pencil, { className: "h-4 w-4 shrink-0 text-primary" }),
9342
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "min-w-0 flex-1 text-sm", children: [
9343
+ "References an existing message in ",
9344
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-semibold", children: editBadge.channelLabel }),
9345
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ml-1 text-xs opacity-70", title: editBadge.ts, children: formatMessageTs(editBadge.ts) })
9111
9346
  ] }),
9112
- /* @__PURE__ */ jsxRuntime.jsxs(Button, { type: "button", size: "sm", onClick: onOpenSend, disabled: !canSend, "aria-label": sendButtonLabel, children: [
9113
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Send, { className: "h-3.5 w-3.5" }),
9114
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "hidden sm:inline", children: sendButtonLabel })
9347
+ /* @__PURE__ */ jsxRuntime.jsxs(Button, { type: "button", variant: "ghost", size: "sm", onClick: onExitEdit, className: "shrink-0", children: [
9348
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "h-3.5 w-3.5" }),
9349
+ "Switch to a new message"
9115
9350
  ] })
9116
- ] })
9351
+ ] }) : null
9117
9352
  ] });
9118
9353
  }
9119
9354
  function Menu({
@@ -9171,52 +9406,132 @@ function Menu({
9171
9406
  );
9172
9407
  }) });
9173
9408
  }
9174
- 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";
9175
- function ActionMenuItem({
9176
- icon,
9177
- children,
9178
- onSelect,
9179
- disabled,
9180
- tone
9409
+ function UpdateDialog({
9410
+ open,
9411
+ onOpenChange,
9412
+ target,
9413
+ blocks,
9414
+ loadSendAsUserStatus,
9415
+ onUpdate,
9416
+ confirmUpdateLabel = "Update message",
9417
+ errorCount,
9418
+ onShowIssues
9181
9419
  }) {
9182
- return /* @__PURE__ */ jsxRuntime.jsxs(
9183
- "button",
9184
- {
9185
- type: "button",
9186
- role: "menuitem",
9187
- onClick: onSelect,
9188
- disabled,
9189
- className: cn(ACTION_ITEM_BASE, tone === "destructive" && "hover:bg-destructive/10 hover:text-destructive"),
9190
- children: [
9191
- icon,
9192
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1", children })
9193
- ]
9420
+ const asUser = target.editableVia === "user";
9421
+ const [userStatus, setUserStatus] = React.useState(null);
9422
+ const [status, setStatus] = React.useState({ kind: "idle" });
9423
+ const loadSendAsUserStatusRef = React.useRef(loadSendAsUserStatus);
9424
+ React.useEffect(() => {
9425
+ loadSendAsUserStatusRef.current = loadSendAsUserStatus;
9426
+ });
9427
+ const refreshSendAsUser = React.useCallback(() => {
9428
+ loadSendAsUserStatusRef.current().then(setUserStatus).catch(() => setUserStatus({ canSendAsUser: false }));
9429
+ }, []);
9430
+ React.useEffect(() => {
9431
+ if (!open) {
9432
+ return;
9194
9433
  }
9195
- );
9196
- }
9197
- function ActionMenuLink({
9198
- href,
9199
- icon,
9200
- trailingIcon,
9201
- children,
9202
- onSelect
9203
- }) {
9204
- return /* @__PURE__ */ jsxRuntime.jsxs(
9205
- "a",
9206
- {
9207
- role: "menuitem",
9208
- href,
9209
- target: "_blank",
9210
- rel: "noreferrer noopener",
9211
- onClick: onSelect,
9212
- className: ACTION_ITEM_BASE,
9213
- children: [
9214
- icon,
9215
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1", children }),
9216
- trailingIcon
9217
- ]
9434
+ setStatus({ kind: "idle" });
9435
+ if (asUser) {
9436
+ setUserStatus(null);
9437
+ refreshSendAsUser();
9218
9438
  }
9219
- );
9439
+ }, [open, asUser, refreshSendAsUser]);
9440
+ React.useEffect(() => {
9441
+ if (!open || !asUser) {
9442
+ return;
9443
+ }
9444
+ const handler = () => refreshSendAsUser();
9445
+ window.addEventListener("focus", handler);
9446
+ return () => window.removeEventListener("focus", handler);
9447
+ }, [open, asUser, refreshSendAsUser]);
9448
+ const needsSignIn = asUser && userStatus !== null && !userStatus.canSendAsUser;
9449
+ const handleSubmit = async () => {
9450
+ setStatus({ kind: "updating" });
9451
+ try {
9452
+ const result = await onUpdate({
9453
+ channelId: target.channelId,
9454
+ ts: target.ts,
9455
+ blocks: toSlackBlocks(blocks),
9456
+ asUser
9457
+ });
9458
+ if (result.ok) {
9459
+ setStatus({ kind: "idle" });
9460
+ onOpenChange(false);
9461
+ return;
9462
+ }
9463
+ setStatus({ kind: "error", error: result.error ?? "Update failed." });
9464
+ } catch (e) {
9465
+ setStatus({ kind: "error", error: e instanceof Error ? e.message : "Update failed." });
9466
+ }
9467
+ };
9468
+ const channelLabel = target.channelName ? `#${target.channelName}` : target.channelId;
9469
+ return /* @__PURE__ */ jsxRuntime.jsx(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxRuntime.jsxs(DialogContent, { className: "w-[calc(100vw-1.5rem)] max-w-lg rounded-lg", children: [
9470
+ /* @__PURE__ */ jsxRuntime.jsxs(DialogHeader, { children: [
9471
+ /* @__PURE__ */ jsxRuntime.jsx(DialogTitle, { children: "Update message" }),
9472
+ /* @__PURE__ */ jsxRuntime.jsx(DialogDescription, { children: "This replaces the message that's already posted. It stays in the same channel and can't be moved." })
9473
+ ] }),
9474
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-4", children: [
9475
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1.5", children: [
9476
+ /* @__PURE__ */ jsxRuntime.jsx(Label, { children: "Channel" }),
9477
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "flex h-9 items-center rounded-md border border-input bg-muted px-3 text-sm text-muted-foreground", children: channelLabel })
9478
+ ] }),
9479
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1.5", children: [
9480
+ /* @__PURE__ */ jsxRuntime.jsx(Label, { children: "Posting as" }),
9481
+ /* @__PURE__ */ jsxRuntime.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" }),
9482
+ needsSignIn && userStatus?.oauthUrl && isSafeHref(userStatus.oauthUrl) && /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-xs text-muted-foreground", children: [
9483
+ /* @__PURE__ */ jsxRuntime.jsxs(
9484
+ "a",
9485
+ {
9486
+ href: userStatus.oauthUrl,
9487
+ target: "_blank",
9488
+ rel: "noopener noreferrer",
9489
+ className: "inline-flex items-center gap-1 text-primary hover:underline",
9490
+ children: [
9491
+ "Sign in with Slack ",
9492
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ExternalLink, { className: "h-3 w-3" })
9493
+ ]
9494
+ }
9495
+ ),
9496
+ " ",
9497
+ "to update your own message."
9498
+ ] }),
9499
+ needsSignIn && !userStatus?.oauthUrl && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: "Sign in with Slack to update your own message." })
9500
+ ] }),
9501
+ errorCount > 0 ? /* @__PURE__ */ jsxRuntime.jsxs(
9502
+ "button",
9503
+ {
9504
+ type: "button",
9505
+ onClick: onShowIssues,
9506
+ 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",
9507
+ children: [
9508
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertTriangle, { className: "h-3.5 w-3.5 shrink-0" }),
9509
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex-1", children: [
9510
+ "Fix ",
9511
+ errorCount,
9512
+ " ",
9513
+ errorCount === 1 ? "issue" : "issues",
9514
+ " before updating."
9515
+ ] }),
9516
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "shrink-0 underline", children: "Show issues" })
9517
+ ]
9518
+ }
9519
+ ) : null,
9520
+ status.kind === "error" && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "rounded-md border border-destructive/30 bg-destructive/5 p-2 text-xs text-destructive", children: status.error })
9521
+ ] }),
9522
+ /* @__PURE__ */ jsxRuntime.jsxs(DialogFooter, { children: [
9523
+ /* @__PURE__ */ jsxRuntime.jsx(Button, { type: "button", variant: "ghost", onClick: () => onOpenChange(false), children: "Cancel" }),
9524
+ /* @__PURE__ */ jsxRuntime.jsx(
9525
+ Button,
9526
+ {
9527
+ type: "button",
9528
+ onClick: handleSubmit,
9529
+ disabled: status.kind === "updating" || blocks.length === 0 || errorCount > 0 || needsSignIn,
9530
+ children: status.kind === "updating" ? "Updating\u2026" : confirmUpdateLabel
9531
+ }
9532
+ )
9533
+ ] })
9534
+ ] }) });
9220
9535
  }
9221
9536
  function BlockKitchen(props) {
9222
9537
  const {
@@ -9228,6 +9543,10 @@ function BlockKitchen(props) {
9228
9543
  loadChannels,
9229
9544
  loadSendAsUserStatus,
9230
9545
  onSend,
9546
+ editing,
9547
+ loadButtonLabel,
9548
+ updateButtonLabel,
9549
+ confirmUpdateLabel,
9231
9550
  palette,
9232
9551
  disabledBlockTypes,
9233
9552
  showPaletteSearch,
@@ -9273,6 +9592,10 @@ function BlockKitchen(props) {
9273
9592
  }, [blocks]);
9274
9593
  const [jsonOpen, setJsonOpen] = React.useState(false);
9275
9594
  const [sendOpen, setSendOpen] = React.useState(false);
9595
+ const [updateOpen, setUpdateOpen] = React.useState(false);
9596
+ const [loadOpen, setLoadOpen] = React.useState(false);
9597
+ const [editTarget, setEditTarget] = React.useState(null);
9598
+ const activeEditTarget = editing ? editTarget : null;
9276
9599
  const [issuesOpen, setIssuesOpen] = React.useState(false);
9277
9600
  const [paletteOpen, setPaletteOpen] = React.useState(false);
9278
9601
  const [openBlockId, setOpenBlockId] = React.useState(null);
@@ -9393,7 +9716,21 @@ function BlockKitchen(props) {
9393
9716
  allowedSurfaces,
9394
9717
  showThemeControl: isPreviewThemeControlled ? false : showThemeControl,
9395
9718
  docsLink,
9396
- sendButtonLabel
9719
+ sendButtonLabel,
9720
+ editingEnabled: !!editing,
9721
+ editBadge: activeEditTarget ? {
9722
+ channelLabel: activeEditTarget.channelName ? `#${activeEditTarget.channelName}` : activeEditTarget.channelId,
9723
+ ts: activeEditTarget.ts
9724
+ } : null,
9725
+ onOpenLoad: () => setLoadOpen(true),
9726
+ onOpenUpdate: () => setUpdateOpen(true),
9727
+ onExitEdit: () => {
9728
+ setEditTarget(null);
9729
+ replaceAll([]);
9730
+ setLoadOpen(true);
9731
+ },
9732
+ loadButtonLabel,
9733
+ updateButtonLabel
9397
9734
  }
9398
9735
  ),
9399
9736
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-h-0 flex-1 items-stretch", children: [
@@ -9412,6 +9749,8 @@ function BlockKitchen(props) {
9412
9749
  {
9413
9750
  blocks,
9414
9751
  workspaceName,
9752
+ authorName: activeEditTarget?.username,
9753
+ authorIcon: activeEditTarget?.iconUrl,
9415
9754
  previewHooks: mergedPreviewHooks,
9416
9755
  previewTheme,
9417
9756
  previewSurface,
@@ -9477,6 +9816,52 @@ function BlockKitchen(props) {
9477
9816
  }
9478
9817
  }
9479
9818
  ),
9819
+ activeEditTarget && editing ? /* @__PURE__ */ jsxRuntime.jsx(
9820
+ UpdateDialog,
9821
+ {
9822
+ open: updateOpen,
9823
+ onOpenChange: setUpdateOpen,
9824
+ target: activeEditTarget,
9825
+ blocks: blockPayloads,
9826
+ loadSendAsUserStatus,
9827
+ onUpdate: editing.onUpdate,
9828
+ confirmUpdateLabel,
9829
+ errorCount: validation.total,
9830
+ onShowIssues: () => {
9831
+ setUpdateOpen(false);
9832
+ setIssuesOpen(true);
9833
+ }
9834
+ }
9835
+ ) : null,
9836
+ editing ? /* @__PURE__ */ jsxRuntime.jsx(
9837
+ LoadMessageDialog,
9838
+ {
9839
+ open: loadOpen,
9840
+ onOpenChange: setLoadOpen,
9841
+ onLoadMessage: editing.onLoadMessage,
9842
+ loadRecentMessages: editing.loadRecentMessages,
9843
+ onLoaded: (result) => {
9844
+ replaceAll(result.blocks);
9845
+ setEditTarget({
9846
+ channelId: result.channelId,
9847
+ channelName: result.channelName,
9848
+ ts: result.ts,
9849
+ editableVia: result.editableVia,
9850
+ workspaceName: result.workspaceName,
9851
+ username: result.username,
9852
+ iconUrl: result.iconUrl
9853
+ });
9854
+ setLoadOpen(false);
9855
+ },
9856
+ onOpenAsNew: (loadedBlocks) => {
9857
+ if (loadedBlocks) {
9858
+ replaceAll(loadedBlocks);
9859
+ }
9860
+ setEditTarget(null);
9861
+ setLoadOpen(false);
9862
+ }
9863
+ }
9864
+ ) : null,
9480
9865
  /* @__PURE__ */ jsxRuntime.jsx(
9481
9866
  IssuesSheet,
9482
9867
  {