@tightknitai/block-kitchen 0.8.11 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -108,7 +108,7 @@ export function MyBuilderPage() {
108
108
  | `loadChannels` | `() => Promise<{ id: string; name: string }[]>` | yes | Returns channels available to send to. The package never makes Slack API calls itself. |
109
109
  | `loadSendAsUserStatus` | `() => Promise<{ canSendAsUser: boolean; oauthUrl?: string }>` | yes | Whether the current user has a Slack user-token and can post as themselves. If `canSendAsUser` is false, `oauthUrl` is shown as a "Sign in with Slack" link. |
110
110
  | `onSend` | `(payload) => Promise<{ ok: boolean; error?: string }>` | yes | Called when the user submits the send dialog. Payload is `{ channelId, blocks, sendAsUser }`. |
111
- | `editing` | `{ onLoadMessage, onUpdate, loadRecentMessages? }` | no | Opt-in edit mode. When present, the toolbar exposes "Edit message": the user pastes a Slack message link, `onLoadMessage({ link })` returns a host-computed [editability verdict](#editing-an-existing-message-opt-in), and a successful load flips the primary action to "Update message" wired to `onUpdate`. Pass `loadRecentMessages` to add a "recent messages from this app" picker beside the paste input. Omit `editing` to keep send-only behavior. |
111
+ | `editing` | `{ onLoadMessage, onUpdate, loadRecentMessages? }` | no | Opt-in edit mode. When present, the toolbar exposes "Edit message": the user pastes a Slack message link, `onLoadMessage({ link })` returns a host-computed [editability verdict](#editing-an-existing-message-opt-in), and a successful load flips the primary action to "Update message" wired to `onUpdate`. Pass `loadRecentMessages` to add a "recent messages from this app" picker beside the paste input; the user picks a channel first (reusing `loadChannels`) and the lookup is scoped to it. Omit `editing` to keep send-only behavior. |
112
112
  | `loadButtonLabel` | `string` | no | Label + accessible name for the toolbar button that opens the load-message dialog (the edit-mode entry point). Defaults to `'Load message'`. Only shown when `editing` is set and no message is loaded. |
113
113
  | `updateButtonLabel` | `string` | no | Label for the toolbar's primary button while a message is loaded for editing. It's a split button: clicking it updates the message in place; the menu beside it also offers "Send as a new message" (post the current blocks as new). Defaults to `'Review & update'`. |
114
114
  | `confirmUpdateLabel` | `string` | no | Label for the update dialog's final confirm button. Defaults to `'Update message'` (shows `'Updating…'` while in flight). |
@@ -162,10 +162,12 @@ nothing about who can edit; the host does both.
162
162
  return { ok: true };
163
163
  },
164
164
  // Optional: adds a "recent messages from this app" picker beside the paste
165
- // input. These are editable-by-construction (the app authored them), so
166
- // picking one loads it straight into edit mode (no verdict needed).
167
- loadRecentMessages: async () => {
168
- const msgs = await fetchRecentAppMessages(); // your code
165
+ // input. The user first picks a channel (reusing `loadChannels`), then this
166
+ // is called with that `channelId` so the lookup scans only one channel.
167
+ // These are editable-by-construction (the app authored them), so picking one
168
+ // loads it straight into edit mode (no verdict needed).
169
+ loadRecentMessages: async (channelId) => {
170
+ const msgs = await fetchRecentAppMessages(channelId); // your code
169
171
  return msgs.map((m) => ({
170
172
  channelId: m.channel,
171
173
  channelName: m.channelName,
@@ -1,5 +1,5 @@
1
- import { T as SupportedBlock } from './palette-Bup05opK.cjs';
2
- export { U as SupportedBlockType } from './palette-Bup05opK.cjs';
1
+ import { T as SupportedBlock } from './palette-DIbvAzRA.cjs';
2
+ export { U as SupportedBlockType } from './palette-DIbvAzRA.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-Bup05opK.js';
2
- export { U as SupportedBlockType } from './palette-Bup05opK.js';
1
+ import { T as SupportedBlock } from './palette-DIbvAzRA.js';
2
+ export { U as SupportedBlockType } from './palette-DIbvAzRA.js';
3
3
  import 'react';
4
4
  import 'slack-web-api-client';
5
5
 
package/dist/index.cjs CHANGED
@@ -2851,19 +2851,25 @@ function LoadMessageDialog({
2851
2851
  onOpenChange,
2852
2852
  onLoadMessage,
2853
2853
  loadRecentMessages,
2854
+ loadChannels,
2854
2855
  onLoaded,
2855
2856
  onOpenAsNew
2856
2857
  }) {
2857
2858
  const [link, setLink] = React.useState("");
2858
2859
  const [status, setStatus] = React.useState({ kind: "idle" });
2860
+ const [channels, setChannels] = React.useState(null);
2861
+ const [channelsError, setChannelsError] = React.useState(null);
2862
+ const [channelId, setChannelId] = React.useState("");
2859
2863
  const [recent, setRecent] = React.useState(null);
2860
2864
  const [recentError, setRecentError] = React.useState(null);
2861
2865
  const inputRef = React.useRef(null);
2862
2866
  const onLoadMessageRef = React.useRef(onLoadMessage);
2863
2867
  const loadRecentMessagesRef = React.useRef(loadRecentMessages);
2868
+ const loadChannelsRef = React.useRef(loadChannels);
2864
2869
  React.useEffect(() => {
2865
2870
  onLoadMessageRef.current = onLoadMessage;
2866
2871
  loadRecentMessagesRef.current = loadRecentMessages;
2872
+ loadChannelsRef.current = loadChannels;
2867
2873
  });
2868
2874
  const hasRecent = !!loadRecentMessages;
2869
2875
  React.useEffect(() => {
@@ -2872,15 +2878,38 @@ function LoadMessageDialog({
2872
2878
  }
2873
2879
  setLink("");
2874
2880
  setStatus({ kind: "idle" });
2881
+ setChannelId("");
2882
+ setRecent(null);
2883
+ setRecentError(null);
2875
2884
  if (!loadRecentMessagesRef.current) {
2876
- setRecent([]);
2877
- setRecentError(null);
2885
+ setChannels([]);
2886
+ setChannelsError(null);
2887
+ return;
2888
+ }
2889
+ setChannels(null);
2890
+ setChannelsError(null);
2891
+ let cancelled = false;
2892
+ loadChannelsRef.current().then((list) => {
2893
+ if (!cancelled) {
2894
+ setChannels(list);
2895
+ }
2896
+ }).catch((e) => {
2897
+ if (!cancelled) {
2898
+ setChannelsError(e instanceof Error ? e.message : "Failed to load channels");
2899
+ }
2900
+ });
2901
+ return () => {
2902
+ cancelled = true;
2903
+ };
2904
+ }, [open]);
2905
+ React.useEffect(() => {
2906
+ if (!open || !loadRecentMessagesRef.current || !channelId) {
2878
2907
  return;
2879
2908
  }
2880
2909
  setRecent(null);
2881
2910
  setRecentError(null);
2882
2911
  let cancelled = false;
2883
- loadRecentMessagesRef.current().then((list) => {
2912
+ loadRecentMessagesRef.current(channelId).then((list) => {
2884
2913
  if (!cancelled) {
2885
2914
  setRecent(list);
2886
2915
  }
@@ -2892,7 +2921,7 @@ function LoadMessageDialog({
2892
2921
  return () => {
2893
2922
  cancelled = true;
2894
2923
  };
2895
- }, [open]);
2924
+ }, [open, channelId]);
2896
2925
  const handleLoad = async () => {
2897
2926
  const trimmed = link.trim();
2898
2927
  if (!trimmed) {
@@ -2996,10 +3025,33 @@ function LoadMessageDialog({
2996
3025
  "or pick a recent message",
2997
3026
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "h-px flex-1 bg-border" })
2998
3027
  ] }),
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) => {
3028
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1.5", children: [
3029
+ /* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: "recent-channel-picker", children: "Channel" }),
3030
+ channels === null && !channelsError && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: "Loading channels\u2026" }),
3031
+ channelsError && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-destructive", children: channelsError }),
3032
+ channels && channels.length === 0 && !channelsError && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: "No public channels available." }),
3033
+ channels && channels.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(
3034
+ "select",
3035
+ {
3036
+ id: "recent-channel-picker",
3037
+ value: channelId,
3038
+ onChange: (e) => setChannelId(e.target.value),
3039
+ className: "flex h-9 w-full rounded-md border border-input bg-background px-3 py-1 text-sm shadow-sm focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring",
3040
+ children: [
3041
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", disabled: true, children: "Select a channel\u2026" }),
3042
+ channels.map((c) => /* @__PURE__ */ jsxRuntime.jsxs("option", { value: c.id, children: [
3043
+ "#",
3044
+ c.name
3045
+ ] }, c.id))
3046
+ ]
3047
+ }
3048
+ )
3049
+ ] }),
3050
+ !channelId && channels && channels.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: "Select a channel to see recent messages." }),
3051
+ channelId && recent === null && !recentError && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: "Loading recent messages\u2026" }),
3052
+ channelId && recentError && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-destructive", children: recentError }),
3053
+ 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." }),
3054
+ channelId && 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
3055
  const asUser = (m.editableVia ?? "bot") === "user";
3004
3056
  return /* @__PURE__ */ jsxRuntime.jsxs(
3005
3057
  "button",
@@ -9840,6 +9892,7 @@ function BlockKitchen(props) {
9840
9892
  onOpenChange: setLoadOpen,
9841
9893
  onLoadMessage: editing.onLoadMessage,
9842
9894
  loadRecentMessages: editing.loadRecentMessages,
9895
+ loadChannels,
9843
9896
  onLoaded: (result) => {
9844
9897
  replaceAll(result.blocks);
9845
9898
  setEditTarget({