@tightknitai/block-kitchen 0.9.3 → 0.9.5

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.
@@ -1,5 +1,5 @@
1
- import { T as SupportedBlock } from './palette-DIbvAzRA.cjs';
2
- export { U as SupportedBlockType } from './palette-DIbvAzRA.cjs';
1
+ import { T as SupportedBlock } from './palette-inX8F01o.cjs';
2
+ export { U as SupportedBlockType } from './palette-inX8F01o.cjs';
3
3
  import 'react';
4
4
  import 'slack-web-api-client';
5
5
 
package/dist/helpers.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { T as SupportedBlock } from './palette-DIbvAzRA.js';
2
- export { U as SupportedBlockType } from './palette-DIbvAzRA.js';
1
+ import { T as SupportedBlock } from './palette-inX8F01o.js';
2
+ export { U as SupportedBlockType } from './palette-inX8F01o.js';
3
3
  import 'react';
4
4
  import 'slack-web-api-client';
5
5
 
package/dist/index.cjs CHANGED
@@ -2647,8 +2647,10 @@ function JsonDrawer({
2647
2647
  const [value, setValue] = React.useState("");
2648
2648
  const [parseError, setParseError] = React.useState(null);
2649
2649
  const [validationErrors, setValidationErrors] = React.useState([]);
2650
+ const [copied, setCopied] = React.useState(false);
2650
2651
  const textareaRef = React.useRef(null);
2651
2652
  const gutterRef = React.useRef(null);
2653
+ const copyResetRef = React.useRef(null);
2652
2654
  const blocksRef = React.useRef(blocks);
2653
2655
  React.useEffect(() => {
2654
2656
  blocksRef.current = blocks;
@@ -2658,8 +2660,27 @@ function JsonDrawer({
2658
2660
  setValue(JSON.stringify(blocksRef.current, null, 2));
2659
2661
  setParseError(null);
2660
2662
  setValidationErrors([]);
2663
+ setCopied(false);
2661
2664
  }
2662
2665
  }, [open]);
2666
+ React.useEffect(
2667
+ () => () => {
2668
+ if (copyResetRef.current) {
2669
+ clearTimeout(copyResetRef.current);
2670
+ }
2671
+ },
2672
+ []
2673
+ );
2674
+ const handleCopy = () => {
2675
+ navigator.clipboard?.writeText(value).then(() => {
2676
+ setCopied(true);
2677
+ if (copyResetRef.current) {
2678
+ clearTimeout(copyResetRef.current);
2679
+ }
2680
+ copyResetRef.current = setTimeout(() => setCopied(false), 2e3);
2681
+ }).catch(() => {
2682
+ });
2683
+ };
2663
2684
  const handleChange = (next) => {
2664
2685
  setValue(next);
2665
2686
  if (next.length > MAX_JSON_BYTES) {
@@ -2706,7 +2727,17 @@ function JsonDrawer({
2706
2727
  /* @__PURE__ */ jsxRuntime.jsx(SheetTitle, { children: "Block Kit JSON" }),
2707
2728
  /* @__PURE__ */ jsxRuntime.jsx(SheetDescription, { children: "Edits update the preview as you type. Parse errors show below." })
2708
2729
  ] }),
2709
- /* @__PURE__ */ jsxRuntime.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: [
2730
+ /* @__PURE__ */ jsxRuntime.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: [
2731
+ /* @__PURE__ */ jsxRuntime.jsx(
2732
+ "button",
2733
+ {
2734
+ type: "button",
2735
+ onClick: handleCopy,
2736
+ "aria-label": copied ? "Copied to clipboard" : "Copy JSON",
2737
+ 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",
2738
+ children: copied ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "h-3.5 w-3.5 text-emerald-600" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Copy, { className: "h-3.5 w-3.5" })
2739
+ }
2740
+ ),
2710
2741
  /* @__PURE__ */ jsxRuntime.jsx(
2711
2742
  "div",
2712
2743
  {
@@ -2855,6 +2886,79 @@ var Label = React__namespace.forwardRef(({ className, ...props }, ref) => /* @__
2855
2886
  }
2856
2887
  ));
2857
2888
  Label.displayName = LabelPrimitive__namespace.Root.displayName;
2889
+ var POLL_INTERVAL_MS = 2500;
2890
+ var MAX_POLLS = 24;
2891
+ function useSlackSignIn(loadStatus, { open, enabled }) {
2892
+ const [userStatus, setUserStatus] = React.useState(null);
2893
+ const [polling, setPolling] = React.useState(false);
2894
+ const loadStatusRef = React.useRef(loadStatus);
2895
+ React.useEffect(() => {
2896
+ loadStatusRef.current = loadStatus;
2897
+ });
2898
+ const refresh = React.useCallback(() => {
2899
+ loadStatusRef.current().then(setUserStatus).catch(() => setUserStatus({ canSendAsUser: false }));
2900
+ }, []);
2901
+ const pollRef = React.useRef(null);
2902
+ const stopPolling = React.useCallback(() => {
2903
+ if (pollRef.current) {
2904
+ clearInterval(pollRef.current);
2905
+ pollRef.current = null;
2906
+ }
2907
+ setPolling(false);
2908
+ }, []);
2909
+ const startSignIn = React.useCallback(() => {
2910
+ const url = userStatus?.oauthUrl;
2911
+ if (!url || !isSafeHref(url)) {
2912
+ return;
2913
+ }
2914
+ window.open(url, "_blank", "noopener,noreferrer");
2915
+ stopPolling();
2916
+ setPolling(true);
2917
+ let tries = 0;
2918
+ pollRef.current = setInterval(() => {
2919
+ tries += 1;
2920
+ loadStatusRef.current().then((next) => {
2921
+ setUserStatus(next);
2922
+ if (next.canSendAsUser) {
2923
+ stopPolling();
2924
+ }
2925
+ }).catch(() => {
2926
+ }).finally(() => {
2927
+ if (tries >= MAX_POLLS) {
2928
+ stopPolling();
2929
+ }
2930
+ });
2931
+ }, POLL_INTERVAL_MS);
2932
+ }, [userStatus, stopPolling]);
2933
+ React.useEffect(() => {
2934
+ if (!open || !enabled) {
2935
+ setUserStatus(null);
2936
+ stopPolling();
2937
+ return;
2938
+ }
2939
+ setUserStatus(null);
2940
+ refresh();
2941
+ return stopPolling;
2942
+ }, [open, enabled, refresh, stopPolling]);
2943
+ React.useEffect(() => {
2944
+ if (!open || !enabled) {
2945
+ return;
2946
+ }
2947
+ const handler = () => refresh();
2948
+ window.addEventListener("focus", handler);
2949
+ return () => window.removeEventListener("focus", handler);
2950
+ }, [open, enabled, refresh]);
2951
+ return { userStatus, polling, startSignIn };
2952
+ }
2953
+ function SlackSignInButton({ onClick, polling }) {
2954
+ return /* @__PURE__ */ jsxRuntime.jsx(Button, { type: "button", variant: "outline", size: "sm", className: "w-fit", onClick, disabled: polling, children: polling ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2955
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "animate-spin" }),
2956
+ " Waiting for Slack\u2026"
2957
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2958
+ "Sign in with Slack ",
2959
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ExternalLink, {})
2960
+ ] }) });
2961
+ }
2858
2962
  function recentToResult(msg) {
2859
2963
  return {
2860
2964
  ok: true,
@@ -2868,6 +2972,17 @@ function recentToResult(msg) {
2868
2972
  iconUrl: msg.iconUrl
2869
2973
  };
2870
2974
  }
2975
+ function previewText(m) {
2976
+ if (m.label) {
2977
+ return m.label;
2978
+ }
2979
+ for (const b of m.blocks) {
2980
+ if ((b.type === "header" || b.type === "section") && "text" in b && b.text && "text" in b.text) {
2981
+ return b.text.text;
2982
+ }
2983
+ }
2984
+ return `${m.blocks.length} block${m.blocks.length === 1 ? "" : "s"}`;
2985
+ }
2871
2986
  function LoadMessageDialog({
2872
2987
  open,
2873
2988
  onOpenChange,
@@ -2884,7 +2999,10 @@ function LoadMessageDialog({
2884
2999
  const [channelId, setChannelId] = React.useState("");
2885
3000
  const [recent, setRecent] = React.useState(null);
2886
3001
  const [recentError, setRecentError] = React.useState(null);
3002
+ const [selectedRecent, setSelectedRecent] = React.useState(null);
3003
+ const [signInPolling, setSignInPolling] = React.useState(false);
2887
3004
  const inputRef = React.useRef(null);
3005
+ const signInPollRef = React.useRef(null);
2888
3006
  const onLoadMessageRef = React.useRef(onLoadMessage);
2889
3007
  const loadRecentMessagesRef = React.useRef(loadRecentMessages);
2890
3008
  const loadChannelsRef = React.useRef(loadChannels);
@@ -2903,6 +3021,7 @@ function LoadMessageDialog({
2903
3021
  setChannelId("");
2904
3022
  setRecent(null);
2905
3023
  setRecentError(null);
3024
+ setSelectedRecent(null);
2906
3025
  if (!loadRecentMessagesRef.current) {
2907
3026
  setChannels([]);
2908
3027
  setChannelsError(null);
@@ -2930,6 +3049,7 @@ function LoadMessageDialog({
2930
3049
  }
2931
3050
  setRecent(null);
2932
3051
  setRecentError(null);
3052
+ setSelectedRecent(null);
2933
3053
  let cancelled = false;
2934
3054
  loadRecentMessagesRef.current(channelId).then((list) => {
2935
3055
  if (!cancelled) {
@@ -2944,7 +3064,25 @@ function LoadMessageDialog({
2944
3064
  cancelled = true;
2945
3065
  };
2946
3066
  }, [open, channelId]);
3067
+ const stopSignInPoll = React.useCallback(() => {
3068
+ if (signInPollRef.current) {
3069
+ clearInterval(signInPollRef.current);
3070
+ signInPollRef.current = null;
3071
+ }
3072
+ setSignInPolling(false);
3073
+ }, []);
3074
+ React.useEffect(() => {
3075
+ if (!open) {
3076
+ stopSignInPoll();
3077
+ }
3078
+ return stopSignInPoll;
3079
+ }, [open, stopSignInPoll]);
2947
3080
  const handleLoad = async () => {
3081
+ stopSignInPoll();
3082
+ if (selectedRecent) {
3083
+ onLoaded(recentToResult(selectedRecent));
3084
+ return;
3085
+ }
2948
3086
  const trimmed = link.trim();
2949
3087
  if (!trimmed) {
2950
3088
  setStatus({ kind: "error", error: "Paste a Slack message link first." });
@@ -2957,12 +3095,42 @@ function LoadMessageDialog({
2957
3095
  onLoaded(result);
2958
3096
  return;
2959
3097
  }
2960
- setStatus({ kind: "not-editable", reason: result.reason, blocks: result.blocks });
3098
+ setStatus({ kind: "not-editable", reason: result.reason, blocks: result.blocks, oauthUrl: result.oauthUrl });
2961
3099
  } catch (e) {
2962
3100
  setStatus({ kind: "error", error: e instanceof Error ? e.message : "Failed to load message." });
2963
3101
  }
2964
3102
  };
3103
+ const startFindSignIn = (oauthUrl) => {
3104
+ if (!isSafeHref(oauthUrl)) {
3105
+ return;
3106
+ }
3107
+ const trimmed = link.trim();
3108
+ if (!trimmed) {
3109
+ return;
3110
+ }
3111
+ window.open(oauthUrl, "_blank", "noopener,noreferrer");
3112
+ stopSignInPoll();
3113
+ setSignInPolling(true);
3114
+ let tries = 0;
3115
+ const POLL_INTERVAL_MS2 = 2500;
3116
+ const MAX_POLLS2 = 24;
3117
+ signInPollRef.current = setInterval(() => {
3118
+ tries += 1;
3119
+ onLoadMessageRef.current({ link: trimmed }).then((result) => {
3120
+ if (result.ok) {
3121
+ stopSignInPoll();
3122
+ onLoaded(result);
3123
+ }
3124
+ }).catch(() => {
3125
+ }).finally(() => {
3126
+ if (tries >= MAX_POLLS2) {
3127
+ stopSignInPoll();
3128
+ }
3129
+ });
3130
+ }, POLL_INTERVAL_MS2);
3131
+ };
2965
3132
  const notEditable = status.kind === "not-editable" ? status : null;
3133
+ const signInUrl = notEditable?.oauthUrl && isSafeHref(notEditable.oauthUrl) ? notEditable.oauthUrl : null;
2966
3134
  return /* @__PURE__ */ jsxRuntime.jsx(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxRuntime.jsxs(
2967
3135
  DialogContent,
2968
3136
  {
@@ -2973,7 +3141,7 @@ function LoadMessageDialog({
2973
3141
  },
2974
3142
  children: [
2975
3143
  /* @__PURE__ */ jsxRuntime.jsxs(DialogHeader, { children: [
2976
- /* @__PURE__ */ jsxRuntime.jsx(DialogTitle, { children: "Edit an existing message" }),
3144
+ /* @__PURE__ */ jsxRuntime.jsx(DialogTitle, { children: "Find an existing message" }),
2977
3145
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start gap-1.5 text-left", children: [
2978
3146
  /* @__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.` }),
2979
3147
  /* @__PURE__ */ jsxRuntime.jsxs(Tooltip, { children: [
@@ -3007,6 +3175,8 @@ function LoadMessageDialog({
3007
3175
  value: link,
3008
3176
  onChange: (e) => {
3009
3177
  setLink(e.target.value);
3178
+ setSelectedRecent(null);
3179
+ stopSignInPoll();
3010
3180
  if (status.kind !== "idle" && status.kind !== "loading") {
3011
3181
  setStatus({ kind: "idle" });
3012
3182
  }
@@ -3029,7 +3199,8 @@ function LoadMessageDialog({
3029
3199
  /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertTriangle, { className: "mt-0.5 h-3.5 w-3.5 shrink-0" }),
3030
3200
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1", children: notEditable.reason })
3031
3201
  ] }),
3032
- /* @__PURE__ */ jsxRuntime.jsx(
3202
+ signInUrl && /* @__PURE__ */ jsxRuntime.jsx(SlackSignInButton, { onClick: () => startFindSignIn(signInUrl), polling: signInPolling }),
3203
+ notEditable.blocks && notEditable.blocks.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
3033
3204
  Button,
3034
3205
  {
3035
3206
  type: "button",
@@ -3075,26 +3246,52 @@ function LoadMessageDialog({
3075
3246
  channelId && recent && recent.length === 0 && !recentError && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: "No recent messages from this app in this channel." }),
3076
3247
  channelId && recent && recent.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex max-h-48 min-w-0 flex-col gap-1 overflow-y-auto", children: recent.map((m) => {
3077
3248
  const asUser = (m.editableVia ?? "bot") === "user";
3249
+ const authorName = m.username || (asUser ? "You" : "App bot");
3250
+ const safeIcon = isSafeImageSrc(m.iconUrl) ? m.iconUrl : void 0;
3251
+ const isSelected = selectedRecent?.channelId === m.channelId && selectedRecent?.ts === m.ts;
3078
3252
  return /* @__PURE__ */ jsxRuntime.jsxs(
3079
3253
  "button",
3080
3254
  {
3081
3255
  type: "button",
3082
- onClick: () => onLoaded(recentToResult(m)),
3083
- 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",
3256
+ "aria-pressed": isSelected,
3257
+ onClick: () => {
3258
+ setSelectedRecent(m);
3259
+ setLink("");
3260
+ if (status.kind !== "idle" && status.kind !== "loading") {
3261
+ setStatus({ kind: "idle" });
3262
+ }
3263
+ },
3264
+ className: cn(
3265
+ // Inset rings so the scroll container's overflow clip
3266
+ // can't shave the corners off the selected outline.
3267
+ "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",
3268
+ isSelected && "border-primary! bg-accent ring-1 ring-inset ring-primary"
3269
+ ),
3084
3270
  children: [
3085
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex min-w-0 items-baseline justify-between gap-2", children: [
3086
- /* @__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" }),
3087
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "shrink-0 text-xs text-muted-foreground", children: [
3088
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-semibold text-foreground", children: new Date(Number(m.ts) * 1e3).toLocaleString() }),
3271
+ safeIcon ? /* @__PURE__ */ jsxRuntime.jsx("img", { src: safeIcon, alt: "", className: "h-9 w-9 shrink-0 rounded-md object-cover" }) : /* @__PURE__ */ jsxRuntime.jsx(
3272
+ "span",
3273
+ {
3274
+ "aria-hidden": true,
3275
+ className: "flex h-9 w-9 shrink-0 items-center justify-center rounded-md bg-muted text-sm font-semibold text-muted-foreground",
3276
+ children: authorName.charAt(0).toUpperCase()
3277
+ }
3278
+ ),
3279
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex min-w-0 flex-col gap-0.5", children: [
3280
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex min-w-0 items-center gap-1.5", children: [
3281
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate text-sm font-semibold text-foreground", children: authorName }),
3282
+ /* @__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" })
3283
+ ] }),
3284
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs text-muted-foreground", children: [
3285
+ new Date(Number(m.ts) * 1e3).toLocaleString(),
3089
3286
  " ",
3090
3287
  /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "font-mono", children: [
3091
3288
  "(",
3092
3289
  m.ts,
3093
3290
  ")"
3094
3291
  ] })
3095
- ] })
3096
- ] }),
3097
- m.label && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate text-sm text-muted-foreground", children: m.label })
3292
+ ] }),
3293
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "line-clamp-2 text-sm text-foreground", children: previewText(m) })
3294
+ ] })
3098
3295
  ]
3099
3296
  },
3100
3297
  `${m.channelId}:${m.ts}`
@@ -3104,7 +3301,15 @@ function LoadMessageDialog({
3104
3301
  ] }),
3105
3302
  /* @__PURE__ */ jsxRuntime.jsxs(DialogFooter, { children: [
3106
3303
  /* @__PURE__ */ jsxRuntime.jsx(Button, { type: "button", variant: "ghost", onClick: () => onOpenChange(false), children: "Cancel" }),
3107
- /* @__PURE__ */ jsxRuntime.jsx(Button, { type: "button", onClick: handleLoad, disabled: status.kind === "loading" || !link.trim(), children: status.kind === "loading" ? "Loading\u2026" : "Load message" })
3304
+ /* @__PURE__ */ jsxRuntime.jsx(
3305
+ Button,
3306
+ {
3307
+ type: "button",
3308
+ onClick: handleLoad,
3309
+ disabled: status.kind === "loading" || !selectedRecent && !link.trim(),
3310
+ children: status.kind === "loading" ? "Loading\u2026" : "Load message"
3311
+ }
3312
+ )
3108
3313
  ] })
3109
3314
  ]
3110
3315
  }
@@ -3323,18 +3528,13 @@ function SendDialog({
3323
3528
  const [channels, setChannels] = React.useState(null);
3324
3529
  const [channelsError, setChannelsError] = React.useState(null);
3325
3530
  const [channelId, setChannelId] = React.useState("");
3326
- const [userStatus, setUserStatus] = React.useState(null);
3327
3531
  const [sendAs, setSendAs] = React.useState("bot");
3328
3532
  const [status, setStatus] = React.useState({ kind: "idle" });
3533
+ const { userStatus, polling, startSignIn } = useSlackSignIn(loadSendAsUserStatus, { open, enabled: true });
3329
3534
  const loadChannelsRef = React.useRef(loadChannels);
3330
- const loadSendAsUserStatusRef = React.useRef(loadSendAsUserStatus);
3331
3535
  React.useEffect(() => {
3332
3536
  loadChannelsRef.current = loadChannels;
3333
- loadSendAsUserStatusRef.current = loadSendAsUserStatus;
3334
3537
  });
3335
- const refreshSendAsUser = React.useCallback(() => {
3336
- loadSendAsUserStatusRef.current().then(setUserStatus).catch(() => setUserStatus({ canSendAsUser: false }));
3337
- }, []);
3338
3538
  React.useEffect(() => {
3339
3539
  if (!open) {
3340
3540
  return;
@@ -3342,7 +3542,6 @@ function SendDialog({
3342
3542
  setStatus({ kind: "idle" });
3343
3543
  setChannels(null);
3344
3544
  setChannelsError(null);
3345
- setUserStatus(null);
3346
3545
  setChannelId("");
3347
3546
  setSendAs("bot");
3348
3547
  let cancelled = false;
@@ -3358,19 +3557,10 @@ function SendDialog({
3358
3557
  }
3359
3558
  setChannelsError(e instanceof Error ? e.message : "Failed to load channels");
3360
3559
  });
3361
- refreshSendAsUser();
3362
3560
  return () => {
3363
3561
  cancelled = true;
3364
3562
  };
3365
- }, [open, refreshSendAsUser]);
3366
- React.useEffect(() => {
3367
- if (!open) {
3368
- return;
3369
- }
3370
- const handler = () => refreshSendAsUser();
3371
- window.addEventListener("focus", handler);
3372
- return () => window.removeEventListener("focus", handler);
3373
- }, [open, refreshSendAsUser]);
3563
+ }, [open]);
3374
3564
  const handleSubmit = async () => {
3375
3565
  if (!channelId) {
3376
3566
  setStatus({ kind: "error", error: "Please pick a channel." });
@@ -3424,22 +3614,9 @@ function SendDialog({
3424
3614
  ]
3425
3615
  }
3426
3616
  ),
3427
- userStatus && !userStatus.canSendAsUser && userStatus.oauthUrl && isSafeHref(userStatus.oauthUrl) && /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-xs text-muted-foreground", children: [
3428
- /* @__PURE__ */ jsxRuntime.jsxs(
3429
- "a",
3430
- {
3431
- href: userStatus.oauthUrl,
3432
- target: "_blank",
3433
- rel: "noopener noreferrer",
3434
- className: "inline-flex items-center gap-1 text-primary hover:underline",
3435
- children: [
3436
- "Sign in with Slack ",
3437
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ExternalLink, { className: "h-3 w-3" })
3438
- ]
3439
- }
3440
- ),
3441
- " ",
3442
- "to post as yourself."
3617
+ userStatus && !userStatus.canSendAsUser && userStatus.oauthUrl && isSafeHref(userStatus.oauthUrl) && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3618
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: "Connect your Slack account to post as yourself." }),
3619
+ /* @__PURE__ */ jsxRuntime.jsx(SlackSignInButton, { onClick: startSignIn, polling })
3443
3620
  ] })
3444
3621
  ] }),
3445
3622
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1.5", children: [
@@ -9213,7 +9390,7 @@ function Toolbar2({
9213
9390
  onOpenLoad,
9214
9391
  onOpenUpdate,
9215
9392
  onExitEdit,
9216
- loadButtonLabel = "Load message",
9393
+ loadButtonLabel = "Find message",
9217
9394
  updateButtonLabel = "Review & update"
9218
9395
  }) {
9219
9396
  const activeTheme = THEME_OPTIONS.find((t) => t.value === previewTheme) ?? THEME_OPTIONS[0];
@@ -9497,33 +9674,13 @@ function UpdateDialog({
9497
9674
  onShowIssues
9498
9675
  }) {
9499
9676
  const asUser = target.editableVia === "user";
9500
- const [userStatus, setUserStatus] = React.useState(null);
9501
9677
  const [status, setStatus] = React.useState({ kind: "idle" });
9502
- const loadSendAsUserStatusRef = React.useRef(loadSendAsUserStatus);
9503
- React.useEffect(() => {
9504
- loadSendAsUserStatusRef.current = loadSendAsUserStatus;
9505
- });
9506
- const refreshSendAsUser = React.useCallback(() => {
9507
- loadSendAsUserStatusRef.current().then(setUserStatus).catch(() => setUserStatus({ canSendAsUser: false }));
9508
- }, []);
9678
+ const { userStatus, polling, startSignIn } = useSlackSignIn(loadSendAsUserStatus, { open, enabled: asUser });
9509
9679
  React.useEffect(() => {
9510
- if (!open) {
9511
- return;
9512
- }
9513
- setStatus({ kind: "idle" });
9514
- if (asUser) {
9515
- setUserStatus(null);
9516
- refreshSendAsUser();
9517
- }
9518
- }, [open, asUser, refreshSendAsUser]);
9519
- React.useEffect(() => {
9520
- if (!open || !asUser) {
9521
- return;
9680
+ if (open) {
9681
+ setStatus({ kind: "idle" });
9522
9682
  }
9523
- const handler = () => refreshSendAsUser();
9524
- window.addEventListener("focus", handler);
9525
- return () => window.removeEventListener("focus", handler);
9526
- }, [open, asUser, refreshSendAsUser]);
9683
+ }, [open]);
9527
9684
  const needsSignIn = asUser && userStatus !== null && !userStatus.canSendAsUser;
9528
9685
  const handleSubmit = async () => {
9529
9686
  setStatus({ kind: "updating" });
@@ -9558,22 +9715,9 @@ function UpdateDialog({
9558
9715
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1.5", children: [
9559
9716
  /* @__PURE__ */ jsxRuntime.jsx(Label, { children: "Posting as" }),
9560
9717
  /* @__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" }),
9561
- needsSignIn && userStatus?.oauthUrl && isSafeHref(userStatus.oauthUrl) && /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-xs text-muted-foreground", children: [
9562
- /* @__PURE__ */ jsxRuntime.jsxs(
9563
- "a",
9564
- {
9565
- href: userStatus.oauthUrl,
9566
- target: "_blank",
9567
- rel: "noopener noreferrer",
9568
- className: "inline-flex items-center gap-1 text-primary hover:underline",
9569
- children: [
9570
- "Sign in with Slack ",
9571
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ExternalLink, { className: "h-3 w-3" })
9572
- ]
9573
- }
9574
- ),
9575
- " ",
9576
- "to update your own message."
9718
+ needsSignIn && userStatus?.oauthUrl && isSafeHref(userStatus.oauthUrl) && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
9719
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: "Connect your Slack account to edit your own messages." }),
9720
+ /* @__PURE__ */ jsxRuntime.jsx(SlackSignInButton, { onClick: startSignIn, polling })
9577
9721
  ] }),
9578
9722
  needsSignIn && !userStatus?.oauthUrl && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: "Sign in with Slack to update your own message." })
9579
9723
  ] }),