@tightknitai/block-kitchen 0.8.4 → 0.8.6

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,8 +1,7 @@
1
- import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { B as BlockKitchenProps, E as Template, q as PreviewSurface, r as PreviewTheme } from './palette-Dco-CVMi.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 ChannelOption, h as ContextActionsBlock, i as ContextActionsElement, j as CustomEmoji, F as FeedbackButtonSubobject, k as FeedbackButtonsElement, H as HeaderLevel, I as IconButtonElement, l as IconButtonIcon, m as InputBlock, M as MarkdownBlock, P as PaletteSection, n as PaletteVariant, o as PlanBlock, p as PreviewHooks, S as SendAsUserStatus, s as SendPayload, t as SendResult, u as SupportedBlock, v as SupportedBlockType, w as SupportedHeaderBlock, T as TableBlock, x as TableCell, y as TableColumnSetting, z as TaskCardBlock, D as TaskCardStatus, U as UrlSourceElement, V as VideoBlock, J as defaultPalette, K as extraAlertVariant, L as legacyInputVariants } from './palette-Dco-CVMi.cjs';
1
+ import * as react from 'react';
2
+ import { B as BlockKitchenProps, E as Template, q as PreviewSurface, r as PreviewTheme } from './palette-DToUE_3J.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 ChannelOption, h as ContextActionsBlock, i as ContextActionsElement, j as CustomEmoji, F as FeedbackButtonSubobject, k as FeedbackButtonsElement, H as HeaderLevel, I as IconButtonElement, l as IconButtonIcon, m as InputBlock, M as MarkdownBlock, P as PaletteSection, n as PaletteVariant, o as PlanBlock, p as PreviewHooks, S as SendAsUserStatus, s as SendPayload, t as SendResult, u as SupportedBlock, v as SupportedBlockType, w as SupportedHeaderBlock, T as TableBlock, x as TableCell, y as TableColumnSetting, z as TaskCardBlock, D as TaskCardStatus, U as UrlSourceElement, V as VideoBlock, J as defaultPalette, K as extraAlertVariant, L as legacyInputVariants } from './palette-DToUE_3J.cjs';
4
4
  export { decodeBlocksFromString, encodeBlocksToString, toSlackBlocks } from './helpers.cjs';
5
- import 'react';
6
5
  import 'slack-web-api-client';
7
6
 
8
7
  /**
@@ -13,7 +12,7 @@ import 'slack-web-api-client';
13
12
  * @param props - {@link BlockKitchenProps}
14
13
  * @returns the rendered Block Kit Builder
15
14
  */
16
- declare function BlockKitchen(props: BlockKitchenProps): react_jsx_runtime.JSX.Element;
15
+ declare function BlockKitchen(props: BlockKitchenProps): react.JSX.Element;
17
16
 
18
17
  /**
19
18
  * Standalone, page-sized picker that displays {@link Template}s as a
@@ -50,6 +49,6 @@ declare function TemplatePicker({ templates, onSelect, surface, theme, heading,
50
49
  heading?: string;
51
50
  emptyLabel?: string;
52
51
  className?: string;
53
- }): react_jsx_runtime.JSX.Element;
52
+ }): react.JSX.Element;
54
53
 
55
54
  export { BlockKitchen, BlockKitchenProps, PreviewSurface, PreviewTheme, Template, TemplatePicker };
package/dist/index.d.ts CHANGED
@@ -1,8 +1,7 @@
1
- import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { B as BlockKitchenProps, E as Template, q as PreviewSurface, r as PreviewTheme } from './palette-Dco-CVMi.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 ChannelOption, h as ContextActionsBlock, i as ContextActionsElement, j as CustomEmoji, F as FeedbackButtonSubobject, k as FeedbackButtonsElement, H as HeaderLevel, I as IconButtonElement, l as IconButtonIcon, m as InputBlock, M as MarkdownBlock, P as PaletteSection, n as PaletteVariant, o as PlanBlock, p as PreviewHooks, S as SendAsUserStatus, s as SendPayload, t as SendResult, u as SupportedBlock, v as SupportedBlockType, w as SupportedHeaderBlock, T as TableBlock, x as TableCell, y as TableColumnSetting, z as TaskCardBlock, D as TaskCardStatus, U as UrlSourceElement, V as VideoBlock, J as defaultPalette, K as extraAlertVariant, L as legacyInputVariants } from './palette-Dco-CVMi.js';
1
+ import * as react from 'react';
2
+ import { B as BlockKitchenProps, E as Template, q as PreviewSurface, r as PreviewTheme } from './palette-DToUE_3J.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 ChannelOption, h as ContextActionsBlock, i as ContextActionsElement, j as CustomEmoji, F as FeedbackButtonSubobject, k as FeedbackButtonsElement, H as HeaderLevel, I as IconButtonElement, l as IconButtonIcon, m as InputBlock, M as MarkdownBlock, P as PaletteSection, n as PaletteVariant, o as PlanBlock, p as PreviewHooks, S as SendAsUserStatus, s as SendPayload, t as SendResult, u as SupportedBlock, v as SupportedBlockType, w as SupportedHeaderBlock, T as TableBlock, x as TableCell, y as TableColumnSetting, z as TaskCardBlock, D as TaskCardStatus, U as UrlSourceElement, V as VideoBlock, J as defaultPalette, K as extraAlertVariant, L as legacyInputVariants } from './palette-DToUE_3J.js';
4
4
  export { decodeBlocksFromString, encodeBlocksToString, toSlackBlocks } from './helpers.js';
5
- import 'react';
6
5
  import 'slack-web-api-client';
7
6
 
8
7
  /**
@@ -13,7 +12,7 @@ import 'slack-web-api-client';
13
12
  * @param props - {@link BlockKitchenProps}
14
13
  * @returns the rendered Block Kit Builder
15
14
  */
16
- declare function BlockKitchen(props: BlockKitchenProps): react_jsx_runtime.JSX.Element;
15
+ declare function BlockKitchen(props: BlockKitchenProps): react.JSX.Element;
17
16
 
18
17
  /**
19
18
  * Standalone, page-sized picker that displays {@link Template}s as a
@@ -50,6 +49,6 @@ declare function TemplatePicker({ templates, onSelect, surface, theme, heading,
50
49
  heading?: string;
51
50
  emptyLabel?: string;
52
51
  className?: string;
53
- }): react_jsx_runtime.JSX.Element;
52
+ }): react.JSX.Element;
54
53
 
55
54
  export { BlockKitchen, BlockKitchenProps, PreviewSurface, PreviewTheme, Template, TemplatePicker };
package/dist/index.js CHANGED
@@ -1640,6 +1640,12 @@ function isSafeHref(value) {
1640
1640
  }
1641
1641
  return SAFE_LINK_PROTOCOLS.test(scheme);
1642
1642
  }
1643
+ function hasExplicitSafeScheme(value) {
1644
+ if (typeof value !== "string" || value.length === 0) {
1645
+ return false;
1646
+ }
1647
+ return getScheme(value) !== null && isSafeHref(value);
1648
+ }
1643
1649
  function isSafeImageSrc(value) {
1644
1650
  if (value === null || value === void 0) {
1645
1651
  return false;
@@ -2238,6 +2244,18 @@ function IssuesSheet({
2238
2244
  ] })
2239
2245
  ] }) });
2240
2246
  }
2247
+
2248
+ // src/lib/parse-blocks-input.ts
2249
+ var BLOCKS_INPUT_SHAPE_ERROR = "Expected an array of blocks, or an object with a `blocks` array.";
2250
+ function unwrapBlocksInput(parsed) {
2251
+ if (Array.isArray(parsed)) {
2252
+ return parsed;
2253
+ }
2254
+ if (parsed && typeof parsed === "object" && Array.isArray(parsed.blocks)) {
2255
+ return parsed.blocks;
2256
+ }
2257
+ return null;
2258
+ }
2241
2259
  var MAX_JSON_BYTES = 1024 * 1024;
2242
2260
  function JsonDrawer({
2243
2261
  open,
@@ -2276,14 +2294,15 @@ function JsonDrawer({
2276
2294
  setValidationErrors([]);
2277
2295
  return;
2278
2296
  }
2279
- if (!Array.isArray(parsed)) {
2280
- setParseError("Top-level value must be an array of blocks.");
2297
+ const blocks2 = unwrapBlocksInput(parsed);
2298
+ if (!blocks2) {
2299
+ setParseError(BLOCKS_INPUT_SHAPE_ERROR);
2281
2300
  setValidationErrors([]);
2282
2301
  return;
2283
2302
  }
2284
2303
  setParseError(null);
2285
- onApply(parsed);
2286
- const result = validateBlockKit(parsed, {
2304
+ onApply(blocks2);
2305
+ const result = validateBlockKit(blocks2, {
2287
2306
  target: "blocks",
2288
2307
  surface: "message"
2289
2308
  });
@@ -2661,6 +2680,7 @@ function SendDialog({
2661
2680
  loadChannels,
2662
2681
  loadSendAsUserStatus,
2663
2682
  onSend,
2683
+ confirmSendLabel = "Send",
2664
2684
  errorCount,
2665
2685
  onShowIssues
2666
2686
  }) {
@@ -2834,7 +2854,7 @@ function SendDialog({
2834
2854
  type: "button",
2835
2855
  onClick: handleSubmit,
2836
2856
  disabled: status.kind === "sending" || !channelId || blocks.length === 0 || errorCount > 0,
2837
- children: status.kind === "sending" ? "Sending\u2026" : "Send"
2857
+ children: status.kind === "sending" ? "Sending\u2026" : confirmSendLabel
2838
2858
  }
2839
2859
  )
2840
2860
  ] })
@@ -5372,6 +5392,17 @@ function blockElementToPM(el, resolveEmoji) {
5372
5392
  }
5373
5393
  return null;
5374
5394
  }
5395
+ function pushTextWithHardBreaks(out, text, marks) {
5396
+ const segments = text.split("\n");
5397
+ segments.forEach((segment, i) => {
5398
+ if (i > 0) {
5399
+ out.push({ type: "hardBreak" });
5400
+ }
5401
+ if (segment) {
5402
+ out.push({ type: "text", text: segment, marks });
5403
+ }
5404
+ });
5405
+ }
5375
5406
  function inlinesToPM(inlines, resolveEmoji) {
5376
5407
  const out = [];
5377
5408
  for (const inline of inlines) {
@@ -5379,8 +5410,7 @@ function inlinesToPM(inlines, resolveEmoji) {
5379
5410
  if (!inline.text) {
5380
5411
  continue;
5381
5412
  }
5382
- const marks = styleToMarks(inline.style);
5383
- out.push({ type: "text", text: inline.text, marks });
5413
+ pushTextWithHardBreaks(out, inline.text, styleToMarks(inline.style));
5384
5414
  } else if (inline.type === "emoji") {
5385
5415
  const attrs = resolveEmoji(inline);
5386
5416
  if (!attrs.name) {
@@ -5517,6 +5547,10 @@ function proseMirrorInlinesToRichTextElements(nodes) {
5517
5547
  out.push(emoji);
5518
5548
  continue;
5519
5549
  }
5550
+ if (node.type === "hardBreak") {
5551
+ out.push({ type: "text", text: "\n" });
5552
+ continue;
5553
+ }
5520
5554
  if (node.type !== "text" || !node.text) {
5521
5555
  continue;
5522
5556
  }
@@ -6204,6 +6238,43 @@ function LossyBanner({ reasons }) {
6204
6238
  ] })
6205
6239
  ] });
6206
6240
  }
6241
+ var RICH_TEXT_INPUT_RULE_ALLOWLIST = ["bold", "italic", "strike", "code"];
6242
+ function buildRichTextExtensions() {
6243
+ return [
6244
+ StarterKit.configure({
6245
+ heading: false,
6246
+ horizontalRule: false,
6247
+ // StarterKit bundles its own Link extension (enabled by default),
6248
+ // which would register a second, unconfigured `link` mark and a
6249
+ // duplicate autolinker alongside the hardened one we add below.
6250
+ // Disable it so our single, security-configured Link is the only
6251
+ // link extension in the schema.
6252
+ link: false
6253
+ }),
6254
+ EmojiNode,
6255
+ Link.configure({
6256
+ openOnClick: false,
6257
+ autolink: true,
6258
+ defaultProtocol: "https",
6259
+ // Pin TipTap's link allowlist to the same set as our shared
6260
+ // `isSafeHref` helper. The upstream default also includes
6261
+ // ftp/cid/callto which we don't expect inside Slack content.
6262
+ protocols: ["http", "https", "mailto", "tel", "sms", "xmpp"],
6263
+ // Belt-and-suspenders: even if a downstream contributor widens
6264
+ // `protocols` later, our isSafeHref guard rejects everything
6265
+ // outside the safe-link set. setLink and toggleLink both gate
6266
+ // on this hook before applying the mark.
6267
+ isAllowedUri: (url) => isSafeHref(url),
6268
+ // Only auto-link tokens that carry an explicit, safe scheme. TipTap's
6269
+ // default would link any `host.tld` (e.g. `2.xyz`, `report.zip`,
6270
+ // `logo.png`) because the suffix is a real gTLD, which is wrong for
6271
+ // free-form Slack message text. Deliberate links can still be added
6272
+ // via the link button.
6273
+ shouldAutoLink: hasExplicitSafeScheme,
6274
+ HTMLAttributes: { rel: "noreferrer noopener", target: "_blank" }
6275
+ })
6276
+ ];
6277
+ }
6207
6278
  function RichTextWysiwygEditor({ block, onChange }) {
6208
6279
  const lastEmittedRef = useRef(null);
6209
6280
  const { byName: customByName } = useCustomEmojis();
@@ -6229,28 +6300,8 @@ function RichTextWysiwygEditor({ block, onChange }) {
6229
6300
  [onChange]
6230
6301
  );
6231
6302
  const editor = useEditor({
6232
- extensions: [
6233
- StarterKit.configure({
6234
- heading: false,
6235
- horizontalRule: false
6236
- }),
6237
- EmojiNode,
6238
- Link.configure({
6239
- openOnClick: false,
6240
- autolink: true,
6241
- defaultProtocol: "https",
6242
- // Pin TipTap's link allowlist to the same set as our shared
6243
- // `isSafeHref` helper. The upstream default also includes
6244
- // ftp/cid/callto which we don't expect inside Slack content.
6245
- protocols: ["http", "https", "mailto", "tel", "sms", "xmpp"],
6246
- // Belt-and-suspenders: even if a downstream contributor widens
6247
- // `protocols` later, our isSafeHref guard rejects everything
6248
- // outside the safe-link set. setLink and toggleLink both gate
6249
- // on this hook before applying the mark.
6250
- isAllowedUri: (url) => isSafeHref(url),
6251
- HTMLAttributes: { rel: "noreferrer noopener", target: "_blank" }
6252
- })
6253
- ],
6303
+ extensions: buildRichTextExtensions(),
6304
+ enableInputRules: RICH_TEXT_INPUT_RULE_ALLOWLIST,
6254
6305
  content: richTextToProseMirror(block, resolveEmoji),
6255
6306
  editorProps: {
6256
6307
  attributes: {
@@ -7877,7 +7928,8 @@ function Toolbar2({
7877
7928
  allowedSurfaces,
7878
7929
  showThemeControl = true,
7879
7930
  docsLink,
7880
- errorCount
7931
+ errorCount,
7932
+ sendButtonLabel = "Send"
7881
7933
  }) {
7882
7934
  const activeTheme = THEME_OPTIONS.find((t) => t.value === previewTheme) ?? THEME_OPTIONS[0];
7883
7935
  const activeSurface = SURFACE_OPTIONS.find((s) => s.value === previewSurface) ?? SURFACE_OPTIONS[0];
@@ -7967,7 +8019,7 @@ function Toolbar2({
7967
8019
  variant: "ghost",
7968
8020
  size: "sm",
7969
8021
  onClick: onOpenIssues,
7970
- className: "text-destructive hover:text-destructive",
8022
+ className: "border border-red-200! bg-red-50 text-red-700 hover:bg-red-100 hover:text-red-700",
7971
8023
  "aria-label": `${errorCount} ${errorCount === 1 ? "issue" : "issues"}`,
7972
8024
  children: [
7973
8025
  /* @__PURE__ */ jsx(AlertTriangle, { className: "h-3.5 w-3.5" }),
@@ -8060,9 +8112,9 @@ function Toolbar2({
8060
8112
  ) : null
8061
8113
  ] }) })
8062
8114
  ] }),
8063
- /* @__PURE__ */ jsxs(Button, { type: "button", size: "sm", onClick: onOpenSend, disabled: !canSend, "aria-label": "Send", children: [
8115
+ /* @__PURE__ */ jsxs(Button, { type: "button", size: "sm", onClick: onOpenSend, disabled: !canSend, "aria-label": sendButtonLabel, children: [
8064
8116
  /* @__PURE__ */ jsx(Send, { className: "h-3.5 w-3.5" }),
8065
- /* @__PURE__ */ jsx("span", { className: "hidden sm:inline", children: "Send" })
8117
+ /* @__PURE__ */ jsx("span", { className: "hidden sm:inline", children: sendButtonLabel })
8066
8118
  ] })
8067
8119
  ] })
8068
8120
  ] });
@@ -8188,6 +8240,9 @@ function BlockKitchen(props) {
8188
8240
  showThemeControl = true,
8189
8241
  docsLink,
8190
8242
  defaultPreviewTheme = "light",
8243
+ previewTheme: controlledPreviewTheme,
8244
+ sendButtonLabel,
8245
+ confirmSendLabel,
8191
8246
  theme
8192
8247
  } = props;
8193
8248
  const paletteSections = useMemo(() => {
@@ -8215,7 +8270,9 @@ function BlockKitchen(props) {
8215
8270
  const [issuesOpen, setIssuesOpen] = useState(false);
8216
8271
  const [paletteOpen, setPaletteOpen] = useState(false);
8217
8272
  const [openBlockId, setOpenBlockId] = useState(null);
8218
- const [previewTheme, setPreviewTheme] = useState(defaultPreviewTheme);
8273
+ const [uncontrolledPreviewTheme, setUncontrolledPreviewTheme] = useState(defaultPreviewTheme);
8274
+ const isPreviewThemeControlled = controlledPreviewTheme !== void 0;
8275
+ const previewTheme = controlledPreviewTheme ?? uncontrolledPreviewTheme;
8219
8276
  const [previewSurface, setPreviewSurface] = useState(allowedSurfaces[0]);
8220
8277
  const [activePaletteVariantId, setActivePaletteVariantId] = useState(null);
8221
8278
  const isMobile = useIsMobile();
@@ -8297,12 +8354,13 @@ function BlockKitchen(props) {
8297
8354
  canClear: blocks.length > 0,
8298
8355
  errorCount: validation.total,
8299
8356
  previewTheme,
8300
- onPreviewThemeChange: setPreviewTheme,
8357
+ onPreviewThemeChange: setUncontrolledPreviewTheme,
8301
8358
  previewSurface,
8302
8359
  onPreviewSurfaceChange: setPreviewSurface,
8303
8360
  allowedSurfaces,
8304
- showThemeControl,
8305
- docsLink
8361
+ showThemeControl: isPreviewThemeControlled ? false : showThemeControl,
8362
+ docsLink,
8363
+ sendButtonLabel
8306
8364
  }
8307
8365
  ),
8308
8366
  /* @__PURE__ */ jsxs("div", { className: "flex min-h-0 flex-1 items-stretch", children: [
@@ -8378,6 +8436,7 @@ function BlockKitchen(props) {
8378
8436
  loadChannels,
8379
8437
  loadSendAsUserStatus,
8380
8438
  onSend,
8439
+ confirmSendLabel,
8381
8440
  errorCount: validation.total,
8382
8441
  onShowIssues: () => {
8383
8442
  setSendOpen(false);