@tightknitai/block-kitchen 0.9.2 → 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/helpers.cjs +10 -0
- package/dist/helpers.cjs.map +1 -1
- package/dist/helpers.d.cts +2 -2
- package/dist/helpers.d.ts +2 -2
- package/dist/helpers.js +10 -0
- package/dist/helpers.js.map +1 -1
- package/dist/index.cjs +255 -89
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +256 -90
- package/dist/index.js.map +1 -1
- package/dist/{palette-DIbvAzRA.d.cts → palette-inX8F01o.d.cts} +9 -1
- package/dist/{palette-DIbvAzRA.d.ts → palette-inX8F01o.d.ts} +9 -1
- package/dist/palette.d.cts +1 -1
- package/dist/palette.d.ts +1 -1
- package/dist/styles.css +1 -1
- package/package.json +1 -1
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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,
|
|
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';
|
|
@@ -1887,6 +1887,10 @@ function sanitizeImageSrc(value) {
|
|
|
1887
1887
|
// src/lib/sanitize-blocks.ts
|
|
1888
1888
|
var HREF_KEYS = /* @__PURE__ */ new Set(["url"]);
|
|
1889
1889
|
var IMAGE_KEYS = /* @__PURE__ */ new Set(["image_url"]);
|
|
1890
|
+
var RETRIEVAL_ONLY_KEYS = /* @__PURE__ */ new Map([
|
|
1891
|
+
// Covers both the image block and the image element — both `type: 'image'`.
|
|
1892
|
+
["image", /* @__PURE__ */ new Set(["image_width", "image_height", "image_bytes", "fallback", "is_animated"])]
|
|
1893
|
+
]);
|
|
1890
1894
|
function sanitizeValue(value) {
|
|
1891
1895
|
if (value === null || typeof value !== "object") {
|
|
1892
1896
|
return value;
|
|
@@ -1904,8 +1908,14 @@ function sanitizeValue(value) {
|
|
|
1904
1908
|
return changed ? out : value;
|
|
1905
1909
|
}
|
|
1906
1910
|
const src = value;
|
|
1911
|
+
const dropKeys = typeof src.type === "string" ? RETRIEVAL_ONLY_KEYS.get(src.type) : void 0;
|
|
1907
1912
|
let copy = null;
|
|
1908
1913
|
for (const key of Object.keys(src)) {
|
|
1914
|
+
if (dropKeys?.has(key)) {
|
|
1915
|
+
copy ??= { ...src };
|
|
1916
|
+
delete copy[key];
|
|
1917
|
+
continue;
|
|
1918
|
+
}
|
|
1909
1919
|
const original = src[key];
|
|
1910
1920
|
let next = original;
|
|
1911
1921
|
if (typeof original === "string") {
|
|
@@ -1929,6 +1939,18 @@ function sanitizeValue(value) {
|
|
|
1929
1939
|
function sanitizeBlock(block) {
|
|
1930
1940
|
return sanitizeValue(block);
|
|
1931
1941
|
}
|
|
1942
|
+
function sanitizeBlocks(blocks) {
|
|
1943
|
+
let changed = false;
|
|
1944
|
+
const out = new Array(blocks.length);
|
|
1945
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
1946
|
+
const next = sanitizeBlock(blocks[i]);
|
|
1947
|
+
if (next !== blocks[i]) {
|
|
1948
|
+
changed = true;
|
|
1949
|
+
}
|
|
1950
|
+
out[i] = next;
|
|
1951
|
+
}
|
|
1952
|
+
return changed ? out : blocks;
|
|
1953
|
+
}
|
|
1932
1954
|
|
|
1933
1955
|
// src/lib/to-slack-blocks.ts
|
|
1934
1956
|
function toSlackBlocks(blocks) {
|
|
@@ -2186,7 +2208,7 @@ function useBlockKitchenState({
|
|
|
2186
2208
|
});
|
|
2187
2209
|
}, []);
|
|
2188
2210
|
const replaceAll = useCallback((newBlocks) => {
|
|
2189
|
-
setBlocks(newBlocks.map(wrap));
|
|
2211
|
+
setBlocks(sanitizeBlocks(newBlocks).map(wrap));
|
|
2190
2212
|
}, []);
|
|
2191
2213
|
return {
|
|
2192
2214
|
blocks,
|
|
@@ -2595,8 +2617,10 @@ function JsonDrawer({
|
|
|
2595
2617
|
const [value, setValue] = useState("");
|
|
2596
2618
|
const [parseError, setParseError] = useState(null);
|
|
2597
2619
|
const [validationErrors, setValidationErrors] = useState([]);
|
|
2620
|
+
const [copied, setCopied] = useState(false);
|
|
2598
2621
|
const textareaRef = useRef(null);
|
|
2599
2622
|
const gutterRef = useRef(null);
|
|
2623
|
+
const copyResetRef = useRef(null);
|
|
2600
2624
|
const blocksRef = useRef(blocks);
|
|
2601
2625
|
useEffect(() => {
|
|
2602
2626
|
blocksRef.current = blocks;
|
|
@@ -2606,8 +2630,27 @@ function JsonDrawer({
|
|
|
2606
2630
|
setValue(JSON.stringify(blocksRef.current, null, 2));
|
|
2607
2631
|
setParseError(null);
|
|
2608
2632
|
setValidationErrors([]);
|
|
2633
|
+
setCopied(false);
|
|
2609
2634
|
}
|
|
2610
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
|
+
};
|
|
2611
2654
|
const handleChange = (next) => {
|
|
2612
2655
|
setValue(next);
|
|
2613
2656
|
if (next.length > MAX_JSON_BYTES) {
|
|
@@ -2654,7 +2697,17 @@ function JsonDrawer({
|
|
|
2654
2697
|
/* @__PURE__ */ jsx(SheetTitle, { children: "Block Kit JSON" }),
|
|
2655
2698
|
/* @__PURE__ */ jsx(SheetDescription, { children: "Edits update the preview as you type. Parse errors show below." })
|
|
2656
2699
|
] }),
|
|
2657
|
-
/* @__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
|
+
),
|
|
2658
2711
|
/* @__PURE__ */ jsx(
|
|
2659
2712
|
"div",
|
|
2660
2713
|
{
|
|
@@ -2803,6 +2856,79 @@ var Label = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ j
|
|
|
2803
2856
|
}
|
|
2804
2857
|
));
|
|
2805
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
|
+
}
|
|
2806
2932
|
function recentToResult(msg) {
|
|
2807
2933
|
return {
|
|
2808
2934
|
ok: true,
|
|
@@ -2816,6 +2942,17 @@ function recentToResult(msg) {
|
|
|
2816
2942
|
iconUrl: msg.iconUrl
|
|
2817
2943
|
};
|
|
2818
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
|
+
}
|
|
2819
2956
|
function LoadMessageDialog({
|
|
2820
2957
|
open,
|
|
2821
2958
|
onOpenChange,
|
|
@@ -2832,7 +2969,10 @@ function LoadMessageDialog({
|
|
|
2832
2969
|
const [channelId, setChannelId] = useState("");
|
|
2833
2970
|
const [recent, setRecent] = useState(null);
|
|
2834
2971
|
const [recentError, setRecentError] = useState(null);
|
|
2972
|
+
const [selectedRecent, setSelectedRecent] = useState(null);
|
|
2973
|
+
const [signInPolling, setSignInPolling] = useState(false);
|
|
2835
2974
|
const inputRef = useRef(null);
|
|
2975
|
+
const signInPollRef = useRef(null);
|
|
2836
2976
|
const onLoadMessageRef = useRef(onLoadMessage);
|
|
2837
2977
|
const loadRecentMessagesRef = useRef(loadRecentMessages);
|
|
2838
2978
|
const loadChannelsRef = useRef(loadChannels);
|
|
@@ -2851,6 +2991,7 @@ function LoadMessageDialog({
|
|
|
2851
2991
|
setChannelId("");
|
|
2852
2992
|
setRecent(null);
|
|
2853
2993
|
setRecentError(null);
|
|
2994
|
+
setSelectedRecent(null);
|
|
2854
2995
|
if (!loadRecentMessagesRef.current) {
|
|
2855
2996
|
setChannels([]);
|
|
2856
2997
|
setChannelsError(null);
|
|
@@ -2878,6 +3019,7 @@ function LoadMessageDialog({
|
|
|
2878
3019
|
}
|
|
2879
3020
|
setRecent(null);
|
|
2880
3021
|
setRecentError(null);
|
|
3022
|
+
setSelectedRecent(null);
|
|
2881
3023
|
let cancelled = false;
|
|
2882
3024
|
loadRecentMessagesRef.current(channelId).then((list) => {
|
|
2883
3025
|
if (!cancelled) {
|
|
@@ -2892,7 +3034,25 @@ function LoadMessageDialog({
|
|
|
2892
3034
|
cancelled = true;
|
|
2893
3035
|
};
|
|
2894
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]);
|
|
2895
3050
|
const handleLoad = async () => {
|
|
3051
|
+
stopSignInPoll();
|
|
3052
|
+
if (selectedRecent) {
|
|
3053
|
+
onLoaded(recentToResult(selectedRecent));
|
|
3054
|
+
return;
|
|
3055
|
+
}
|
|
2896
3056
|
const trimmed = link.trim();
|
|
2897
3057
|
if (!trimmed) {
|
|
2898
3058
|
setStatus({ kind: "error", error: "Paste a Slack message link first." });
|
|
@@ -2905,12 +3065,42 @@ function LoadMessageDialog({
|
|
|
2905
3065
|
onLoaded(result);
|
|
2906
3066
|
return;
|
|
2907
3067
|
}
|
|
2908
|
-
setStatus({ kind: "not-editable", reason: result.reason, blocks: result.blocks });
|
|
3068
|
+
setStatus({ kind: "not-editable", reason: result.reason, blocks: result.blocks, oauthUrl: result.oauthUrl });
|
|
2909
3069
|
} catch (e) {
|
|
2910
3070
|
setStatus({ kind: "error", error: e instanceof Error ? e.message : "Failed to load message." });
|
|
2911
3071
|
}
|
|
2912
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
|
+
};
|
|
2913
3102
|
const notEditable = status.kind === "not-editable" ? status : null;
|
|
3103
|
+
const signInUrl = notEditable?.oauthUrl && isSafeHref(notEditable.oauthUrl) ? notEditable.oauthUrl : null;
|
|
2914
3104
|
return /* @__PURE__ */ jsx(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxs(
|
|
2915
3105
|
DialogContent,
|
|
2916
3106
|
{
|
|
@@ -2921,7 +3111,7 @@ function LoadMessageDialog({
|
|
|
2921
3111
|
},
|
|
2922
3112
|
children: [
|
|
2923
3113
|
/* @__PURE__ */ jsxs(DialogHeader, { children: [
|
|
2924
|
-
/* @__PURE__ */ jsx(DialogTitle, { children: "
|
|
3114
|
+
/* @__PURE__ */ jsx(DialogTitle, { children: "Find an existing message" }),
|
|
2925
3115
|
/* @__PURE__ */ jsxs("div", { className: "flex items-start gap-1.5 text-left", children: [
|
|
2926
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.` }),
|
|
2927
3117
|
/* @__PURE__ */ jsxs(Tooltip, { children: [
|
|
@@ -2955,6 +3145,8 @@ function LoadMessageDialog({
|
|
|
2955
3145
|
value: link,
|
|
2956
3146
|
onChange: (e) => {
|
|
2957
3147
|
setLink(e.target.value);
|
|
3148
|
+
setSelectedRecent(null);
|
|
3149
|
+
stopSignInPoll();
|
|
2958
3150
|
if (status.kind !== "idle" && status.kind !== "loading") {
|
|
2959
3151
|
setStatus({ kind: "idle" });
|
|
2960
3152
|
}
|
|
@@ -2977,7 +3169,8 @@ function LoadMessageDialog({
|
|
|
2977
3169
|
/* @__PURE__ */ jsx(AlertTriangle, { className: "mt-0.5 h-3.5 w-3.5 shrink-0" }),
|
|
2978
3170
|
/* @__PURE__ */ jsx("span", { className: "flex-1", children: notEditable.reason })
|
|
2979
3171
|
] }),
|
|
2980
|
-
/* @__PURE__ */ jsx(
|
|
3172
|
+
signInUrl && /* @__PURE__ */ jsx(SlackSignInButton, { onClick: () => startFindSignIn(signInUrl), polling: signInPolling }),
|
|
3173
|
+
notEditable.blocks && notEditable.blocks.length > 0 && /* @__PURE__ */ jsx(
|
|
2981
3174
|
Button,
|
|
2982
3175
|
{
|
|
2983
3176
|
type: "button",
|
|
@@ -3023,26 +3216,52 @@ function LoadMessageDialog({
|
|
|
3023
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." }),
|
|
3024
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) => {
|
|
3025
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;
|
|
3026
3222
|
return /* @__PURE__ */ jsxs(
|
|
3027
3223
|
"button",
|
|
3028
3224
|
{
|
|
3029
3225
|
type: "button",
|
|
3030
|
-
|
|
3031
|
-
|
|
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
|
+
),
|
|
3032
3240
|
children: [
|
|
3033
|
-
/* @__PURE__ */
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
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(),
|
|
3037
3256
|
" ",
|
|
3038
3257
|
/* @__PURE__ */ jsxs("span", { className: "font-mono", children: [
|
|
3039
3258
|
"(",
|
|
3040
3259
|
m.ts,
|
|
3041
3260
|
")"
|
|
3042
3261
|
] })
|
|
3043
|
-
] })
|
|
3044
|
-
|
|
3045
|
-
|
|
3262
|
+
] }),
|
|
3263
|
+
/* @__PURE__ */ jsx("span", { className: "line-clamp-2 text-sm text-foreground", children: previewText(m) })
|
|
3264
|
+
] })
|
|
3046
3265
|
]
|
|
3047
3266
|
},
|
|
3048
3267
|
`${m.channelId}:${m.ts}`
|
|
@@ -3052,7 +3271,15 @@ function LoadMessageDialog({
|
|
|
3052
3271
|
] }),
|
|
3053
3272
|
/* @__PURE__ */ jsxs(DialogFooter, { children: [
|
|
3054
3273
|
/* @__PURE__ */ jsx(Button, { type: "button", variant: "ghost", onClick: () => onOpenChange(false), children: "Cancel" }),
|
|
3055
|
-
/* @__PURE__ */ jsx(
|
|
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
|
+
)
|
|
3056
3283
|
] })
|
|
3057
3284
|
]
|
|
3058
3285
|
}
|
|
@@ -3271,18 +3498,13 @@ function SendDialog({
|
|
|
3271
3498
|
const [channels, setChannels] = useState(null);
|
|
3272
3499
|
const [channelsError, setChannelsError] = useState(null);
|
|
3273
3500
|
const [channelId, setChannelId] = useState("");
|
|
3274
|
-
const [userStatus, setUserStatus] = useState(null);
|
|
3275
3501
|
const [sendAs, setSendAs] = useState("bot");
|
|
3276
3502
|
const [status, setStatus] = useState({ kind: "idle" });
|
|
3503
|
+
const { userStatus, polling, startSignIn } = useSlackSignIn(loadSendAsUserStatus, { open, enabled: true });
|
|
3277
3504
|
const loadChannelsRef = useRef(loadChannels);
|
|
3278
|
-
const loadSendAsUserStatusRef = useRef(loadSendAsUserStatus);
|
|
3279
3505
|
useEffect(() => {
|
|
3280
3506
|
loadChannelsRef.current = loadChannels;
|
|
3281
|
-
loadSendAsUserStatusRef.current = loadSendAsUserStatus;
|
|
3282
3507
|
});
|
|
3283
|
-
const refreshSendAsUser = useCallback(() => {
|
|
3284
|
-
loadSendAsUserStatusRef.current().then(setUserStatus).catch(() => setUserStatus({ canSendAsUser: false }));
|
|
3285
|
-
}, []);
|
|
3286
3508
|
useEffect(() => {
|
|
3287
3509
|
if (!open) {
|
|
3288
3510
|
return;
|
|
@@ -3290,7 +3512,6 @@ function SendDialog({
|
|
|
3290
3512
|
setStatus({ kind: "idle" });
|
|
3291
3513
|
setChannels(null);
|
|
3292
3514
|
setChannelsError(null);
|
|
3293
|
-
setUserStatus(null);
|
|
3294
3515
|
setChannelId("");
|
|
3295
3516
|
setSendAs("bot");
|
|
3296
3517
|
let cancelled = false;
|
|
@@ -3306,19 +3527,10 @@ function SendDialog({
|
|
|
3306
3527
|
}
|
|
3307
3528
|
setChannelsError(e instanceof Error ? e.message : "Failed to load channels");
|
|
3308
3529
|
});
|
|
3309
|
-
refreshSendAsUser();
|
|
3310
3530
|
return () => {
|
|
3311
3531
|
cancelled = true;
|
|
3312
3532
|
};
|
|
3313
|
-
}, [open
|
|
3314
|
-
useEffect(() => {
|
|
3315
|
-
if (!open) {
|
|
3316
|
-
return;
|
|
3317
|
-
}
|
|
3318
|
-
const handler = () => refreshSendAsUser();
|
|
3319
|
-
window.addEventListener("focus", handler);
|
|
3320
|
-
return () => window.removeEventListener("focus", handler);
|
|
3321
|
-
}, [open, refreshSendAsUser]);
|
|
3533
|
+
}, [open]);
|
|
3322
3534
|
const handleSubmit = async () => {
|
|
3323
3535
|
if (!channelId) {
|
|
3324
3536
|
setStatus({ kind: "error", error: "Please pick a channel." });
|
|
@@ -3372,22 +3584,9 @@ function SendDialog({
|
|
|
3372
3584
|
]
|
|
3373
3585
|
}
|
|
3374
3586
|
),
|
|
3375
|
-
userStatus && !userStatus.canSendAsUser && userStatus.oauthUrl && isSafeHref(userStatus.oauthUrl) && /* @__PURE__ */ jsxs(
|
|
3376
|
-
/* @__PURE__ */
|
|
3377
|
-
|
|
3378
|
-
{
|
|
3379
|
-
href: userStatus.oauthUrl,
|
|
3380
|
-
target: "_blank",
|
|
3381
|
-
rel: "noopener noreferrer",
|
|
3382
|
-
className: "inline-flex items-center gap-1 text-primary hover:underline",
|
|
3383
|
-
children: [
|
|
3384
|
-
"Sign in with Slack ",
|
|
3385
|
-
/* @__PURE__ */ jsx(ExternalLink, { className: "h-3 w-3" })
|
|
3386
|
-
]
|
|
3387
|
-
}
|
|
3388
|
-
),
|
|
3389
|
-
" ",
|
|
3390
|
-
"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 })
|
|
3391
3590
|
] })
|
|
3392
3591
|
] }),
|
|
3393
3592
|
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5", children: [
|
|
@@ -9161,7 +9360,7 @@ function Toolbar2({
|
|
|
9161
9360
|
onOpenLoad,
|
|
9162
9361
|
onOpenUpdate,
|
|
9163
9362
|
onExitEdit,
|
|
9164
|
-
loadButtonLabel = "
|
|
9363
|
+
loadButtonLabel = "Find message",
|
|
9165
9364
|
updateButtonLabel = "Review & update"
|
|
9166
9365
|
}) {
|
|
9167
9366
|
const activeTheme = THEME_OPTIONS.find((t) => t.value === previewTheme) ?? THEME_OPTIONS[0];
|
|
@@ -9445,33 +9644,13 @@ function UpdateDialog({
|
|
|
9445
9644
|
onShowIssues
|
|
9446
9645
|
}) {
|
|
9447
9646
|
const asUser = target.editableVia === "user";
|
|
9448
|
-
const [userStatus, setUserStatus] = useState(null);
|
|
9449
9647
|
const [status, setStatus] = useState({ kind: "idle" });
|
|
9450
|
-
const
|
|
9648
|
+
const { userStatus, polling, startSignIn } = useSlackSignIn(loadSendAsUserStatus, { open, enabled: asUser });
|
|
9451
9649
|
useEffect(() => {
|
|
9452
|
-
|
|
9453
|
-
|
|
9454
|
-
const refreshSendAsUser = useCallback(() => {
|
|
9455
|
-
loadSendAsUserStatusRef.current().then(setUserStatus).catch(() => setUserStatus({ canSendAsUser: false }));
|
|
9456
|
-
}, []);
|
|
9457
|
-
useEffect(() => {
|
|
9458
|
-
if (!open) {
|
|
9459
|
-
return;
|
|
9460
|
-
}
|
|
9461
|
-
setStatus({ kind: "idle" });
|
|
9462
|
-
if (asUser) {
|
|
9463
|
-
setUserStatus(null);
|
|
9464
|
-
refreshSendAsUser();
|
|
9465
|
-
}
|
|
9466
|
-
}, [open, asUser, refreshSendAsUser]);
|
|
9467
|
-
useEffect(() => {
|
|
9468
|
-
if (!open || !asUser) {
|
|
9469
|
-
return;
|
|
9650
|
+
if (open) {
|
|
9651
|
+
setStatus({ kind: "idle" });
|
|
9470
9652
|
}
|
|
9471
|
-
|
|
9472
|
-
window.addEventListener("focus", handler);
|
|
9473
|
-
return () => window.removeEventListener("focus", handler);
|
|
9474
|
-
}, [open, asUser, refreshSendAsUser]);
|
|
9653
|
+
}, [open]);
|
|
9475
9654
|
const needsSignIn = asUser && userStatus !== null && !userStatus.canSendAsUser;
|
|
9476
9655
|
const handleSubmit = async () => {
|
|
9477
9656
|
setStatus({ kind: "updating" });
|
|
@@ -9506,22 +9685,9 @@ function UpdateDialog({
|
|
|
9506
9685
|
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5", children: [
|
|
9507
9686
|
/* @__PURE__ */ jsx(Label, { children: "Posting as" }),
|
|
9508
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" }),
|
|
9509
|
-
needsSignIn && userStatus?.oauthUrl && isSafeHref(userStatus.oauthUrl) && /* @__PURE__ */ jsxs(
|
|
9510
|
-
/* @__PURE__ */
|
|
9511
|
-
|
|
9512
|
-
{
|
|
9513
|
-
href: userStatus.oauthUrl,
|
|
9514
|
-
target: "_blank",
|
|
9515
|
-
rel: "noopener noreferrer",
|
|
9516
|
-
className: "inline-flex items-center gap-1 text-primary hover:underline",
|
|
9517
|
-
children: [
|
|
9518
|
-
"Sign in with Slack ",
|
|
9519
|
-
/* @__PURE__ */ jsx(ExternalLink, { className: "h-3 w-3" })
|
|
9520
|
-
]
|
|
9521
|
-
}
|
|
9522
|
-
),
|
|
9523
|
-
" ",
|
|
9524
|
-
"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 })
|
|
9525
9691
|
] }),
|
|
9526
9692
|
needsSignIn && !userStatus?.oauthUrl && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Sign in with Slack to update your own message." })
|
|
9527
9693
|
] }),
|