@tightknitai/block-kitchen 0.9.3 → 0.9.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as react from 'react';
2
- import { B as BlockKitchenProps, $ as Template, K as PreviewSurface, N as PreviewTheme } from './palette-DIbvAzRA.cjs';
3
- export { A as AlertBlock, a as AlertLevel, b as BrandPreset, c as BrandTheme, d as BrandTokens, e as BuilderBlock, C as CardBlock, f as CarouselBlock, g as CartesianChart, h as ChannelOption, i as Chart, j as ChartAxisConfig, k as ChartDataPoint, l as ChartSeries, m as ChartType, n as ContainerBlock, o as ContainerChildBlock, p as ContextActionsBlock, q as ContextActionsElement, r as CustomEmoji, D as DataVisualizationBlock, E as EditableVia, s as EditingConfig, F as FeedbackButtonSubobject, t as FeedbackButtonsElement, H as HeaderLevel, I as IconButtonElement, u as IconButtonIcon, v as InputBlock, L as LoadMessageInput, w as LoadResult, M as MarkdownBlock, P as PaletteSection, x as PaletteVariant, y as PieChart, z as PieChartSegment, G as PlanBlock, J as PreviewHooks, R as RecentMessage, S as SendAsUserStatus, O as SendPayload, Q as SendResult, T as SupportedBlock, U as SupportedBlockType, V as SupportedHeaderBlock, W as TableBlock, X as TableCell, Y as TableColumnSetting, Z as TaskCardBlock, _ as TaskCardStatus, a0 as UpdatePayload, a1 as UpdateResult, a2 as UrlSourceElement, a3 as VideoBlock, a5 as defaultPalette, a6 as extraAlertVariant, a7 as legacyInputVariants } from './palette-DIbvAzRA.cjs';
2
+ import { B as BlockKitchenProps, $ as Template, K as PreviewSurface, N as PreviewTheme } from './palette-inX8F01o.cjs';
3
+ export { A as AlertBlock, a as AlertLevel, b as BrandPreset, c as BrandTheme, d as BrandTokens, e as BuilderBlock, C as CardBlock, f as CarouselBlock, g as CartesianChart, h as ChannelOption, i as Chart, j as ChartAxisConfig, k as ChartDataPoint, l as ChartSeries, m as ChartType, n as ContainerBlock, o as ContainerChildBlock, p as ContextActionsBlock, q as ContextActionsElement, r as CustomEmoji, D as DataVisualizationBlock, E as EditableVia, s as EditingConfig, F as FeedbackButtonSubobject, t as FeedbackButtonsElement, H as HeaderLevel, I as IconButtonElement, u as IconButtonIcon, v as InputBlock, L as LoadMessageInput, w as LoadResult, M as MarkdownBlock, P as PaletteSection, x as PaletteVariant, y as PieChart, z as PieChartSegment, G as PlanBlock, J as PreviewHooks, R as RecentMessage, S as SendAsUserStatus, O as SendPayload, Q as SendResult, T as SupportedBlock, U as SupportedBlockType, V as SupportedHeaderBlock, W as TableBlock, X as TableCell, Y as TableColumnSetting, Z as TaskCardBlock, _ as TaskCardStatus, a0 as UpdatePayload, a1 as UpdateResult, a2 as UrlSourceElement, a3 as VideoBlock, a5 as defaultPalette, a6 as extraAlertVariant, a7 as legacyInputVariants } from './palette-inX8F01o.cjs';
4
4
  export { decodeBlocksFromString, encodeBlocksToString, toSlackBlocks } from './helpers.cjs';
5
5
  import 'slack-web-api-client';
6
6
 
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as react from 'react';
2
- import { B as BlockKitchenProps, $ as Template, K as PreviewSurface, N as PreviewTheme } from './palette-DIbvAzRA.js';
3
- export { A as AlertBlock, a as AlertLevel, b as BrandPreset, c as BrandTheme, d as BrandTokens, e as BuilderBlock, C as CardBlock, f as CarouselBlock, g as CartesianChart, h as ChannelOption, i as Chart, j as ChartAxisConfig, k as ChartDataPoint, l as ChartSeries, m as ChartType, n as ContainerBlock, o as ContainerChildBlock, p as ContextActionsBlock, q as ContextActionsElement, r as CustomEmoji, D as DataVisualizationBlock, E as EditableVia, s as EditingConfig, F as FeedbackButtonSubobject, t as FeedbackButtonsElement, H as HeaderLevel, I as IconButtonElement, u as IconButtonIcon, v as InputBlock, L as LoadMessageInput, w as LoadResult, M as MarkdownBlock, P as PaletteSection, x as PaletteVariant, y as PieChart, z as PieChartSegment, G as PlanBlock, J as PreviewHooks, R as RecentMessage, S as SendAsUserStatus, O as SendPayload, Q as SendResult, T as SupportedBlock, U as SupportedBlockType, V as SupportedHeaderBlock, W as TableBlock, X as TableCell, Y as TableColumnSetting, Z as TaskCardBlock, _ as TaskCardStatus, a0 as UpdatePayload, a1 as UpdateResult, a2 as UrlSourceElement, a3 as VideoBlock, a5 as defaultPalette, a6 as extraAlertVariant, a7 as legacyInputVariants } from './palette-DIbvAzRA.js';
2
+ import { B as BlockKitchenProps, $ as Template, K as PreviewSurface, N as PreviewTheme } from './palette-inX8F01o.js';
3
+ export { A as AlertBlock, a as AlertLevel, b as BrandPreset, c as BrandTheme, d as BrandTokens, e as BuilderBlock, C as CardBlock, f as CarouselBlock, g as CartesianChart, h as ChannelOption, i as Chart, j as ChartAxisConfig, k as ChartDataPoint, l as ChartSeries, m as ChartType, n as ContainerBlock, o as ContainerChildBlock, p as ContextActionsBlock, q as ContextActionsElement, r as CustomEmoji, D as DataVisualizationBlock, E as EditableVia, s as EditingConfig, F as FeedbackButtonSubobject, t as FeedbackButtonsElement, H as HeaderLevel, I as IconButtonElement, u as IconButtonIcon, v as InputBlock, L as LoadMessageInput, w as LoadResult, M as MarkdownBlock, P as PaletteSection, x as PaletteVariant, y as PieChart, z as PieChartSegment, G as PlanBlock, J as PreviewHooks, R as RecentMessage, S as SendAsUserStatus, O as SendPayload, Q as SendResult, T as SupportedBlock, U as SupportedBlockType, V as SupportedHeaderBlock, W as TableBlock, X as TableCell, Y as TableColumnSetting, Z as TaskCardBlock, _ as TaskCardStatus, a0 as UpdatePayload, a1 as UpdateResult, a2 as UrlSourceElement, a3 as VideoBlock, a5 as defaultPalette, a6 as extraAlertVariant, a7 as legacyInputVariants } from './palette-inX8F01o.js';
4
4
  export { decodeBlocksFromString, encodeBlocksToString, toSlackBlocks } from './helpers.js';
5
5
  import 'slack-web-api-client';
6
6
 
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, MailSearch, Plus, ChevronDown, BookOpen, ExternalLink, AlertTriangle, Trash2, Code2, Pencil, 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';
3
+ import { X, Circle, Sparkles, FileText, AlignLeft, MousePointerClick, TextCursorInput, LayoutTemplate, Bell, Pilcrow, Image, Video, GalleryHorizontal, Table, ChartColumnBig, Container, GripVertical, Sun, Moon, MessageSquare, AppWindow, Home, MailSearch, Plus, ChevronDown, BookOpen, ExternalLink, AlertTriangle, Trash2, Code2, Pencil, Send, Search, Check, Copy, Info, CheckCircle2, ChevronRight, LayoutGrid, ArrowUp, ArrowDown, Loader2, 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';
@@ -2617,8 +2617,10 @@ function JsonDrawer({
2617
2617
  const [value, setValue] = useState("");
2618
2618
  const [parseError, setParseError] = useState(null);
2619
2619
  const [validationErrors, setValidationErrors] = useState([]);
2620
+ const [copied, setCopied] = useState(false);
2620
2621
  const textareaRef = useRef(null);
2621
2622
  const gutterRef = useRef(null);
2623
+ const copyResetRef = useRef(null);
2622
2624
  const blocksRef = useRef(blocks);
2623
2625
  useEffect(() => {
2624
2626
  blocksRef.current = blocks;
@@ -2628,8 +2630,27 @@ function JsonDrawer({
2628
2630
  setValue(JSON.stringify(blocksRef.current, null, 2));
2629
2631
  setParseError(null);
2630
2632
  setValidationErrors([]);
2633
+ setCopied(false);
2631
2634
  }
2632
2635
  }, [open]);
2636
+ useEffect(
2637
+ () => () => {
2638
+ if (copyResetRef.current) {
2639
+ clearTimeout(copyResetRef.current);
2640
+ }
2641
+ },
2642
+ []
2643
+ );
2644
+ const handleCopy = () => {
2645
+ navigator.clipboard?.writeText(value).then(() => {
2646
+ setCopied(true);
2647
+ if (copyResetRef.current) {
2648
+ clearTimeout(copyResetRef.current);
2649
+ }
2650
+ copyResetRef.current = setTimeout(() => setCopied(false), 2e3);
2651
+ }).catch(() => {
2652
+ });
2653
+ };
2633
2654
  const handleChange = (next) => {
2634
2655
  setValue(next);
2635
2656
  if (next.length > MAX_JSON_BYTES) {
@@ -2676,7 +2697,17 @@ function JsonDrawer({
2676
2697
  /* @__PURE__ */ jsx(SheetTitle, { children: "Block Kit JSON" }),
2677
2698
  /* @__PURE__ */ jsx(SheetDescription, { children: "Edits update the preview as you type. Parse errors show below." })
2678
2699
  ] }),
2679
- /* @__PURE__ */ jsxs("div", { className: "flex flex-1 overflow-hidden rounded-md border border-input bg-muted/30 shadow-sm focus-within:ring-1 focus-within:ring-ring", children: [
2700
+ /* @__PURE__ */ jsxs("div", { className: "relative flex flex-1 overflow-hidden rounded-md border border-input bg-muted/30 shadow-sm focus-within:ring-1 focus-within:ring-ring", children: [
2701
+ /* @__PURE__ */ jsx(
2702
+ "button",
2703
+ {
2704
+ type: "button",
2705
+ onClick: handleCopy,
2706
+ "aria-label": copied ? "Copied to clipboard" : "Copy JSON",
2707
+ className: "absolute right-2 top-2 z-10 inline-flex items-center justify-center rounded-md border border-input bg-background/80 p-1.5 text-muted-foreground shadow-sm backdrop-blur transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
2708
+ children: copied ? /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5 text-emerald-600" }) : /* @__PURE__ */ jsx(Copy, { className: "h-3.5 w-3.5" })
2709
+ }
2710
+ ),
2680
2711
  /* @__PURE__ */ jsx(
2681
2712
  "div",
2682
2713
  {
@@ -2825,6 +2856,79 @@ var Label = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ j
2825
2856
  }
2826
2857
  ));
2827
2858
  Label.displayName = LabelPrimitive.Root.displayName;
2859
+ var POLL_INTERVAL_MS = 2500;
2860
+ var MAX_POLLS = 24;
2861
+ function useSlackSignIn(loadStatus, { open, enabled }) {
2862
+ const [userStatus, setUserStatus] = useState(null);
2863
+ const [polling, setPolling] = useState(false);
2864
+ const loadStatusRef = useRef(loadStatus);
2865
+ useEffect(() => {
2866
+ loadStatusRef.current = loadStatus;
2867
+ });
2868
+ const refresh = useCallback(() => {
2869
+ loadStatusRef.current().then(setUserStatus).catch(() => setUserStatus({ canSendAsUser: false }));
2870
+ }, []);
2871
+ const pollRef = useRef(null);
2872
+ const stopPolling = useCallback(() => {
2873
+ if (pollRef.current) {
2874
+ clearInterval(pollRef.current);
2875
+ pollRef.current = null;
2876
+ }
2877
+ setPolling(false);
2878
+ }, []);
2879
+ const startSignIn = useCallback(() => {
2880
+ const url = userStatus?.oauthUrl;
2881
+ if (!url || !isSafeHref(url)) {
2882
+ return;
2883
+ }
2884
+ window.open(url, "_blank", "noopener,noreferrer");
2885
+ stopPolling();
2886
+ setPolling(true);
2887
+ let tries = 0;
2888
+ pollRef.current = setInterval(() => {
2889
+ tries += 1;
2890
+ loadStatusRef.current().then((next) => {
2891
+ setUserStatus(next);
2892
+ if (next.canSendAsUser) {
2893
+ stopPolling();
2894
+ }
2895
+ }).catch(() => {
2896
+ }).finally(() => {
2897
+ if (tries >= MAX_POLLS) {
2898
+ stopPolling();
2899
+ }
2900
+ });
2901
+ }, POLL_INTERVAL_MS);
2902
+ }, [userStatus, stopPolling]);
2903
+ useEffect(() => {
2904
+ if (!open || !enabled) {
2905
+ setUserStatus(null);
2906
+ stopPolling();
2907
+ return;
2908
+ }
2909
+ setUserStatus(null);
2910
+ refresh();
2911
+ return stopPolling;
2912
+ }, [open, enabled, refresh, stopPolling]);
2913
+ useEffect(() => {
2914
+ if (!open || !enabled) {
2915
+ return;
2916
+ }
2917
+ const handler = () => refresh();
2918
+ window.addEventListener("focus", handler);
2919
+ return () => window.removeEventListener("focus", handler);
2920
+ }, [open, enabled, refresh]);
2921
+ return { userStatus, polling, startSignIn };
2922
+ }
2923
+ function SlackSignInButton({ onClick, polling }) {
2924
+ return /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", size: "sm", className: "w-fit", onClick, disabled: polling, children: polling ? /* @__PURE__ */ jsxs(Fragment, { children: [
2925
+ /* @__PURE__ */ jsx(Loader2, { className: "animate-spin" }),
2926
+ " Waiting for Slack\u2026"
2927
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
2928
+ "Sign in with Slack ",
2929
+ /* @__PURE__ */ jsx(ExternalLink, {})
2930
+ ] }) });
2931
+ }
2828
2932
  function recentToResult(msg) {
2829
2933
  return {
2830
2934
  ok: true,
@@ -2838,6 +2942,17 @@ function recentToResult(msg) {
2838
2942
  iconUrl: msg.iconUrl
2839
2943
  };
2840
2944
  }
2945
+ function previewText(m) {
2946
+ if (m.label) {
2947
+ return m.label;
2948
+ }
2949
+ for (const b of m.blocks) {
2950
+ if ((b.type === "header" || b.type === "section") && "text" in b && b.text && "text" in b.text) {
2951
+ return b.text.text;
2952
+ }
2953
+ }
2954
+ return `${m.blocks.length} block${m.blocks.length === 1 ? "" : "s"}`;
2955
+ }
2841
2956
  function LoadMessageDialog({
2842
2957
  open,
2843
2958
  onOpenChange,
@@ -2854,7 +2969,10 @@ function LoadMessageDialog({
2854
2969
  const [channelId, setChannelId] = useState("");
2855
2970
  const [recent, setRecent] = useState(null);
2856
2971
  const [recentError, setRecentError] = useState(null);
2972
+ const [selectedRecent, setSelectedRecent] = useState(null);
2973
+ const [signInPolling, setSignInPolling] = useState(false);
2857
2974
  const inputRef = useRef(null);
2975
+ const signInPollRef = useRef(null);
2858
2976
  const onLoadMessageRef = useRef(onLoadMessage);
2859
2977
  const loadRecentMessagesRef = useRef(loadRecentMessages);
2860
2978
  const loadChannelsRef = useRef(loadChannels);
@@ -2873,6 +2991,7 @@ function LoadMessageDialog({
2873
2991
  setChannelId("");
2874
2992
  setRecent(null);
2875
2993
  setRecentError(null);
2994
+ setSelectedRecent(null);
2876
2995
  if (!loadRecentMessagesRef.current) {
2877
2996
  setChannels([]);
2878
2997
  setChannelsError(null);
@@ -2900,6 +3019,7 @@ function LoadMessageDialog({
2900
3019
  }
2901
3020
  setRecent(null);
2902
3021
  setRecentError(null);
3022
+ setSelectedRecent(null);
2903
3023
  let cancelled = false;
2904
3024
  loadRecentMessagesRef.current(channelId).then((list) => {
2905
3025
  if (!cancelled) {
@@ -2914,7 +3034,25 @@ function LoadMessageDialog({
2914
3034
  cancelled = true;
2915
3035
  };
2916
3036
  }, [open, channelId]);
3037
+ const stopSignInPoll = useCallback(() => {
3038
+ if (signInPollRef.current) {
3039
+ clearInterval(signInPollRef.current);
3040
+ signInPollRef.current = null;
3041
+ }
3042
+ setSignInPolling(false);
3043
+ }, []);
3044
+ useEffect(() => {
3045
+ if (!open) {
3046
+ stopSignInPoll();
3047
+ }
3048
+ return stopSignInPoll;
3049
+ }, [open, stopSignInPoll]);
2917
3050
  const handleLoad = async () => {
3051
+ stopSignInPoll();
3052
+ if (selectedRecent) {
3053
+ onLoaded(recentToResult(selectedRecent));
3054
+ return;
3055
+ }
2918
3056
  const trimmed = link.trim();
2919
3057
  if (!trimmed) {
2920
3058
  setStatus({ kind: "error", error: "Paste a Slack message link first." });
@@ -2927,12 +3065,42 @@ function LoadMessageDialog({
2927
3065
  onLoaded(result);
2928
3066
  return;
2929
3067
  }
2930
- setStatus({ kind: "not-editable", reason: result.reason, blocks: result.blocks });
3068
+ setStatus({ kind: "not-editable", reason: result.reason, blocks: result.blocks, oauthUrl: result.oauthUrl });
2931
3069
  } catch (e) {
2932
3070
  setStatus({ kind: "error", error: e instanceof Error ? e.message : "Failed to load message." });
2933
3071
  }
2934
3072
  };
3073
+ const startFindSignIn = (oauthUrl) => {
3074
+ if (!isSafeHref(oauthUrl)) {
3075
+ return;
3076
+ }
3077
+ const trimmed = link.trim();
3078
+ if (!trimmed) {
3079
+ return;
3080
+ }
3081
+ window.open(oauthUrl, "_blank", "noopener,noreferrer");
3082
+ stopSignInPoll();
3083
+ setSignInPolling(true);
3084
+ let tries = 0;
3085
+ const POLL_INTERVAL_MS2 = 2500;
3086
+ const MAX_POLLS2 = 24;
3087
+ signInPollRef.current = setInterval(() => {
3088
+ tries += 1;
3089
+ onLoadMessageRef.current({ link: trimmed }).then((result) => {
3090
+ if (result.ok) {
3091
+ stopSignInPoll();
3092
+ onLoaded(result);
3093
+ }
3094
+ }).catch(() => {
3095
+ }).finally(() => {
3096
+ if (tries >= MAX_POLLS2) {
3097
+ stopSignInPoll();
3098
+ }
3099
+ });
3100
+ }, POLL_INTERVAL_MS2);
3101
+ };
2935
3102
  const notEditable = status.kind === "not-editable" ? status : null;
3103
+ const signInUrl = notEditable?.oauthUrl && isSafeHref(notEditable.oauthUrl) ? notEditable.oauthUrl : null;
2936
3104
  return /* @__PURE__ */ jsx(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxs(
2937
3105
  DialogContent,
2938
3106
  {
@@ -2943,7 +3111,7 @@ function LoadMessageDialog({
2943
3111
  },
2944
3112
  children: [
2945
3113
  /* @__PURE__ */ jsxs(DialogHeader, { children: [
2946
- /* @__PURE__ */ jsx(DialogTitle, { children: "Edit an existing message" }),
3114
+ /* @__PURE__ */ jsx(DialogTitle, { children: "Find an existing message" }),
2947
3115
  /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-1.5 text-left", children: [
2948
3116
  /* @__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.` }),
2949
3117
  /* @__PURE__ */ jsxs(Tooltip, { children: [
@@ -2977,6 +3145,8 @@ function LoadMessageDialog({
2977
3145
  value: link,
2978
3146
  onChange: (e) => {
2979
3147
  setLink(e.target.value);
3148
+ setSelectedRecent(null);
3149
+ stopSignInPoll();
2980
3150
  if (status.kind !== "idle" && status.kind !== "loading") {
2981
3151
  setStatus({ kind: "idle" });
2982
3152
  }
@@ -2999,7 +3169,8 @@ function LoadMessageDialog({
2999
3169
  /* @__PURE__ */ jsx(AlertTriangle, { className: "mt-0.5 h-3.5 w-3.5 shrink-0" }),
3000
3170
  /* @__PURE__ */ jsx("span", { className: "flex-1", children: notEditable.reason })
3001
3171
  ] }),
3002
- /* @__PURE__ */ jsx(
3172
+ signInUrl && /* @__PURE__ */ jsx(SlackSignInButton, { onClick: () => startFindSignIn(signInUrl), polling: signInPolling }),
3173
+ notEditable.blocks && notEditable.blocks.length > 0 && /* @__PURE__ */ jsx(
3003
3174
  Button,
3004
3175
  {
3005
3176
  type: "button",
@@ -3045,26 +3216,52 @@ function LoadMessageDialog({
3045
3216
  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." }),
3046
3217
  channelId && recent && recent.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex max-h-48 min-w-0 flex-col gap-1 overflow-y-auto", children: recent.map((m) => {
3047
3218
  const asUser = (m.editableVia ?? "bot") === "user";
3219
+ const authorName = m.username || (asUser ? "You" : "App bot");
3220
+ const safeIcon = isSafeImageSrc(m.iconUrl) ? m.iconUrl : void 0;
3221
+ const isSelected = selectedRecent?.channelId === m.channelId && selectedRecent?.ts === m.ts;
3048
3222
  return /* @__PURE__ */ jsxs(
3049
3223
  "button",
3050
3224
  {
3051
3225
  type: "button",
3052
- onClick: () => onLoaded(recentToResult(m)),
3053
- className: "flex min-w-0 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",
3226
+ "aria-pressed": isSelected,
3227
+ onClick: () => {
3228
+ setSelectedRecent(m);
3229
+ setLink("");
3230
+ if (status.kind !== "idle" && status.kind !== "loading") {
3231
+ setStatus({ kind: "idle" });
3232
+ }
3233
+ },
3234
+ className: cn(
3235
+ // Inset rings so the scroll container's overflow clip
3236
+ // can't shave the corners off the selected outline.
3237
+ "flex min-w-0 items-start gap-2.5 rounded-md border border-input bg-background px-3 py-2 text-left hover:bg-accent focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-inset focus-visible:ring-ring",
3238
+ isSelected && "border-primary! bg-accent ring-1 ring-inset ring-primary"
3239
+ ),
3054
3240
  children: [
3055
- /* @__PURE__ */ jsxs("span", { className: "flex min-w-0 items-baseline justify-between gap-2", children: [
3056
- /* @__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" }),
3057
- /* @__PURE__ */ jsxs("span", { className: "shrink-0 text-xs text-muted-foreground", children: [
3058
- /* @__PURE__ */ jsx("span", { className: "font-semibold text-foreground", children: new Date(Number(m.ts) * 1e3).toLocaleString() }),
3241
+ safeIcon ? /* @__PURE__ */ jsx("img", { src: safeIcon, alt: "", className: "h-9 w-9 shrink-0 rounded-md object-cover" }) : /* @__PURE__ */ jsx(
3242
+ "span",
3243
+ {
3244
+ "aria-hidden": true,
3245
+ className: "flex h-9 w-9 shrink-0 items-center justify-center rounded-md bg-muted text-sm font-semibold text-muted-foreground",
3246
+ children: authorName.charAt(0).toUpperCase()
3247
+ }
3248
+ ),
3249
+ /* @__PURE__ */ jsxs("span", { className: "flex min-w-0 flex-col gap-0.5", children: [
3250
+ /* @__PURE__ */ jsxs("span", { className: "flex min-w-0 items-center gap-1.5", children: [
3251
+ /* @__PURE__ */ jsx("span", { className: "truncate text-sm font-semibold text-foreground", children: authorName }),
3252
+ /* @__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" })
3253
+ ] }),
3254
+ /* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground", children: [
3255
+ new Date(Number(m.ts) * 1e3).toLocaleString(),
3059
3256
  " ",
3060
3257
  /* @__PURE__ */ jsxs("span", { className: "font-mono", children: [
3061
3258
  "(",
3062
3259
  m.ts,
3063
3260
  ")"
3064
3261
  ] })
3065
- ] })
3066
- ] }),
3067
- m.label && /* @__PURE__ */ jsx("span", { className: "truncate text-sm text-muted-foreground", children: m.label })
3262
+ ] }),
3263
+ /* @__PURE__ */ jsx("span", { className: "line-clamp-2 text-sm text-foreground", children: previewText(m) })
3264
+ ] })
3068
3265
  ]
3069
3266
  },
3070
3267
  `${m.channelId}:${m.ts}`
@@ -3074,7 +3271,15 @@ function LoadMessageDialog({
3074
3271
  ] }),
3075
3272
  /* @__PURE__ */ jsxs(DialogFooter, { children: [
3076
3273
  /* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", onClick: () => onOpenChange(false), children: "Cancel" }),
3077
- /* @__PURE__ */ jsx(Button, { type: "button", onClick: handleLoad, disabled: status.kind === "loading" || !link.trim(), children: status.kind === "loading" ? "Loading\u2026" : "Load message" })
3274
+ /* @__PURE__ */ jsx(
3275
+ Button,
3276
+ {
3277
+ type: "button",
3278
+ onClick: handleLoad,
3279
+ disabled: status.kind === "loading" || !selectedRecent && !link.trim(),
3280
+ children: status.kind === "loading" ? "Loading\u2026" : "Find message"
3281
+ }
3282
+ )
3078
3283
  ] })
3079
3284
  ]
3080
3285
  }
@@ -3293,18 +3498,13 @@ function SendDialog({
3293
3498
  const [channels, setChannels] = useState(null);
3294
3499
  const [channelsError, setChannelsError] = useState(null);
3295
3500
  const [channelId, setChannelId] = useState("");
3296
- const [userStatus, setUserStatus] = useState(null);
3297
3501
  const [sendAs, setSendAs] = useState("bot");
3298
3502
  const [status, setStatus] = useState({ kind: "idle" });
3503
+ const { userStatus, polling, startSignIn } = useSlackSignIn(loadSendAsUserStatus, { open, enabled: true });
3299
3504
  const loadChannelsRef = useRef(loadChannels);
3300
- const loadSendAsUserStatusRef = useRef(loadSendAsUserStatus);
3301
3505
  useEffect(() => {
3302
3506
  loadChannelsRef.current = loadChannels;
3303
- loadSendAsUserStatusRef.current = loadSendAsUserStatus;
3304
3507
  });
3305
- const refreshSendAsUser = useCallback(() => {
3306
- loadSendAsUserStatusRef.current().then(setUserStatus).catch(() => setUserStatus({ canSendAsUser: false }));
3307
- }, []);
3308
3508
  useEffect(() => {
3309
3509
  if (!open) {
3310
3510
  return;
@@ -3312,7 +3512,6 @@ function SendDialog({
3312
3512
  setStatus({ kind: "idle" });
3313
3513
  setChannels(null);
3314
3514
  setChannelsError(null);
3315
- setUserStatus(null);
3316
3515
  setChannelId("");
3317
3516
  setSendAs("bot");
3318
3517
  let cancelled = false;
@@ -3328,19 +3527,10 @@ function SendDialog({
3328
3527
  }
3329
3528
  setChannelsError(e instanceof Error ? e.message : "Failed to load channels");
3330
3529
  });
3331
- refreshSendAsUser();
3332
3530
  return () => {
3333
3531
  cancelled = true;
3334
3532
  };
3335
- }, [open, refreshSendAsUser]);
3336
- useEffect(() => {
3337
- if (!open) {
3338
- return;
3339
- }
3340
- const handler = () => refreshSendAsUser();
3341
- window.addEventListener("focus", handler);
3342
- return () => window.removeEventListener("focus", handler);
3343
- }, [open, refreshSendAsUser]);
3533
+ }, [open]);
3344
3534
  const handleSubmit = async () => {
3345
3535
  if (!channelId) {
3346
3536
  setStatus({ kind: "error", error: "Please pick a channel." });
@@ -3394,22 +3584,9 @@ function SendDialog({
3394
3584
  ]
3395
3585
  }
3396
3586
  ),
3397
- userStatus && !userStatus.canSendAsUser && userStatus.oauthUrl && isSafeHref(userStatus.oauthUrl) && /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
3398
- /* @__PURE__ */ jsxs(
3399
- "a",
3400
- {
3401
- href: userStatus.oauthUrl,
3402
- target: "_blank",
3403
- rel: "noopener noreferrer",
3404
- className: "inline-flex items-center gap-1 text-primary hover:underline",
3405
- children: [
3406
- "Sign in with Slack ",
3407
- /* @__PURE__ */ jsx(ExternalLink, { className: "h-3 w-3" })
3408
- ]
3409
- }
3410
- ),
3411
- " ",
3412
- "to post as yourself."
3587
+ userStatus && !userStatus.canSendAsUser && userStatus.oauthUrl && isSafeHref(userStatus.oauthUrl) && /* @__PURE__ */ jsxs(Fragment, { children: [
3588
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Connect your Slack account to post as yourself." }),
3589
+ /* @__PURE__ */ jsx(SlackSignInButton, { onClick: startSignIn, polling })
3413
3590
  ] })
3414
3591
  ] }),
3415
3592
  /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5", children: [
@@ -9183,7 +9360,7 @@ function Toolbar2({
9183
9360
  onOpenLoad,
9184
9361
  onOpenUpdate,
9185
9362
  onExitEdit,
9186
- loadButtonLabel = "Load message",
9363
+ loadButtonLabel = "Find message",
9187
9364
  updateButtonLabel = "Review & update"
9188
9365
  }) {
9189
9366
  const activeTheme = THEME_OPTIONS.find((t) => t.value === previewTheme) ?? THEME_OPTIONS[0];
@@ -9467,33 +9644,13 @@ function UpdateDialog({
9467
9644
  onShowIssues
9468
9645
  }) {
9469
9646
  const asUser = target.editableVia === "user";
9470
- const [userStatus, setUserStatus] = useState(null);
9471
9647
  const [status, setStatus] = useState({ kind: "idle" });
9472
- const loadSendAsUserStatusRef = useRef(loadSendAsUserStatus);
9473
- useEffect(() => {
9474
- loadSendAsUserStatusRef.current = loadSendAsUserStatus;
9475
- });
9476
- const refreshSendAsUser = useCallback(() => {
9477
- loadSendAsUserStatusRef.current().then(setUserStatus).catch(() => setUserStatus({ canSendAsUser: false }));
9478
- }, []);
9648
+ const { userStatus, polling, startSignIn } = useSlackSignIn(loadSendAsUserStatus, { open, enabled: asUser });
9479
9649
  useEffect(() => {
9480
- if (!open) {
9481
- return;
9482
- }
9483
- setStatus({ kind: "idle" });
9484
- if (asUser) {
9485
- setUserStatus(null);
9486
- refreshSendAsUser();
9487
- }
9488
- }, [open, asUser, refreshSendAsUser]);
9489
- useEffect(() => {
9490
- if (!open || !asUser) {
9491
- return;
9650
+ if (open) {
9651
+ setStatus({ kind: "idle" });
9492
9652
  }
9493
- const handler = () => refreshSendAsUser();
9494
- window.addEventListener("focus", handler);
9495
- return () => window.removeEventListener("focus", handler);
9496
- }, [open, asUser, refreshSendAsUser]);
9653
+ }, [open]);
9497
9654
  const needsSignIn = asUser && userStatus !== null && !userStatus.canSendAsUser;
9498
9655
  const handleSubmit = async () => {
9499
9656
  setStatus({ kind: "updating" });
@@ -9528,22 +9685,9 @@ function UpdateDialog({
9528
9685
  /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5", children: [
9529
9686
  /* @__PURE__ */ jsx(Label, { children: "Posting as" }),
9530
9687
  /* @__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" }),
9531
- needsSignIn && userStatus?.oauthUrl && isSafeHref(userStatus.oauthUrl) && /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
9532
- /* @__PURE__ */ jsxs(
9533
- "a",
9534
- {
9535
- href: userStatus.oauthUrl,
9536
- target: "_blank",
9537
- rel: "noopener noreferrer",
9538
- className: "inline-flex items-center gap-1 text-primary hover:underline",
9539
- children: [
9540
- "Sign in with Slack ",
9541
- /* @__PURE__ */ jsx(ExternalLink, { className: "h-3 w-3" })
9542
- ]
9543
- }
9544
- ),
9545
- " ",
9546
- "to update your own message."
9688
+ needsSignIn && userStatus?.oauthUrl && isSafeHref(userStatus.oauthUrl) && /* @__PURE__ */ jsxs(Fragment, { children: [
9689
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Connect your Slack account to edit your own messages." }),
9690
+ /* @__PURE__ */ jsx(SlackSignInButton, { onClick: startSignIn, polling })
9547
9691
  ] }),
9548
9692
  needsSignIn && !userStatus?.oauthUrl && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Sign in with Slack to update your own message." })
9549
9693
  ] }),