@tightknitai/block-kitchen 0.10.20 → 0.10.21

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,7 +1,7 @@
1
1
  import * as react from 'react';
2
2
  import { ReactNode } from 'react';
3
- import { B as BlockKitchenProps, S as SupportedBlock, C as ChannelOption, a as SendAsUserStatus, b as SendPayload, c as SendExtrasContext, T as Template, P as PreviewSurface, d as PreviewTheme } from './palette-4BJw_nd7.cjs';
4
- export { A as AlertBlock, e as AlertLevel, f as BlockKitchenBaseProps, g as BlockKitchenComposeOnlyProps, h as BlockKitchenSendProps, i as BrandPreset, j as BrandTheme, k as BrandTokens, l as BuilderBlock, m as CardBlock, n as CarouselBlock, o as CartesianChart, p as Chart, q as ChartAxisConfig, r as ChartDataPoint, s as ChartSeries, t as ChartType, u as ContainerBlock, v as ContainerChildBlock, w as ContextActionsBlock, x as ContextActionsElement, y as CustomEmoji, D as DataTableActionCell, z as DataTableBlock, E as DataTableCell, F as DataTableRawNumberCell, G as DataVisualizationBlock, H as EditableVia, I as FeedbackButtonSubobject, J as FeedbackButtonsElement, K as HeaderLevel, L as IconButtonElement, M as IconButtonIcon, N as InputBlock, O as LoadMessageInput, Q as LoadResult, R as LoadedMessage, U as LoadingConfig, V as MarkdownBlock, W as MentionUser, X as MentionsConfig, Y as PaletteMode, Z as PaletteSection, _ as PaletteVariant, $ as PieChart, a0 as PieChartSegment, a1 as PlanBlock, a2 as PreviewHooks, a3 as PrimaryActionConfig, a4 as PrimaryActionContext, a5 as RecentMessage, a6 as SendResult, a7 as SupportedBlockType, a8 as SupportedHeaderBlock, a9 as TableBlock, aa as TableCell, ab as TableColumnSetting, ac as TableRawTextCell, ad as TaskCardBlock, ae as TaskCardStatus, af as UpdatePayload, ag as UpdateResult, ah as UrlSourceElement, ai as ValidationSummary, aj as VideoBlock, ak as defaultPalette, al as extraAlertVariant, am as legacyInputVariants } from './palette-4BJw_nd7.cjs';
3
+ import { B as BlockKitchenProps, S as SupportedBlock, C as ChannelOption, a as SendAsUserStatus, b as SendPayload, c as SendExtrasContext, T as Template, P as PreviewSurface, d as PreviewTheme } from './palette-CXXqlwIH.cjs';
4
+ export { A as AlertBlock, e as AlertLevel, f as BlockKitchenBaseProps, g as BlockKitchenComposeOnlyProps, h as BlockKitchenSendProps, i as BrandPreset, j as BrandTheme, k as BrandTokens, l as BuilderBlock, m as CardBlock, n as CarouselBlock, o as CartesianChart, p as Chart, q as ChartAxisConfig, r as ChartDataPoint, s as ChartSeries, t as ChartType, u as ContainerBlock, v as ContainerChildBlock, w as ContextActionsBlock, x as ContextActionsElement, y as CustomEmoji, D as DataTableActionCell, z as DataTableBlock, E as DataTableCell, F as DataTableRawNumberCell, G as DataVisualizationBlock, H as EditableVia, I as FeedbackButtonSubobject, J as FeedbackButtonsElement, K as HeaderLevel, L as IconButtonElement, M as IconButtonIcon, N as InputBlock, O as LoadMessageInput, Q as LoadResult, R as LoadedMessage, U as LoadingConfig, V as MarkdownBlock, W as MentionChannel, X as MentionUser, Y as MentionsConfig, Z as PaletteMode, _ as PaletteSection, $ as PaletteVariant, a0 as PieChart, a1 as PieChartSegment, a2 as PlanBlock, a3 as PreviewHooks, a4 as PrimaryActionConfig, a5 as PrimaryActionContext, a6 as RecentMessage, a7 as SendResult, a8 as SupportedBlockType, a9 as SupportedHeaderBlock, aa as TableBlock, ab as TableCell, ac as TableColumnSetting, ad as TableRawTextCell, ae as TaskCardBlock, af as TaskCardStatus, ag as UpdatePayload, ah as UpdateResult, ai as UrlSourceElement, aj as ValidationSummary, ak as VideoBlock, al as defaultPalette, am as extraAlertVariant, an as legacyInputVariants } from './palette-CXXqlwIH.cjs';
5
5
  export { decodeBlocksFromString, encodeBlocksToString, toSlackBlocks } from './helpers.cjs';
6
6
  import 'slack-web-api-client';
7
7
 
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as react from 'react';
2
2
  import { ReactNode } from 'react';
3
- import { B as BlockKitchenProps, S as SupportedBlock, C as ChannelOption, a as SendAsUserStatus, b as SendPayload, c as SendExtrasContext, T as Template, P as PreviewSurface, d as PreviewTheme } from './palette-4BJw_nd7.js';
4
- export { A as AlertBlock, e as AlertLevel, f as BlockKitchenBaseProps, g as BlockKitchenComposeOnlyProps, h as BlockKitchenSendProps, i as BrandPreset, j as BrandTheme, k as BrandTokens, l as BuilderBlock, m as CardBlock, n as CarouselBlock, o as CartesianChart, p as Chart, q as ChartAxisConfig, r as ChartDataPoint, s as ChartSeries, t as ChartType, u as ContainerBlock, v as ContainerChildBlock, w as ContextActionsBlock, x as ContextActionsElement, y as CustomEmoji, D as DataTableActionCell, z as DataTableBlock, E as DataTableCell, F as DataTableRawNumberCell, G as DataVisualizationBlock, H as EditableVia, I as FeedbackButtonSubobject, J as FeedbackButtonsElement, K as HeaderLevel, L as IconButtonElement, M as IconButtonIcon, N as InputBlock, O as LoadMessageInput, Q as LoadResult, R as LoadedMessage, U as LoadingConfig, V as MarkdownBlock, W as MentionUser, X as MentionsConfig, Y as PaletteMode, Z as PaletteSection, _ as PaletteVariant, $ as PieChart, a0 as PieChartSegment, a1 as PlanBlock, a2 as PreviewHooks, a3 as PrimaryActionConfig, a4 as PrimaryActionContext, a5 as RecentMessage, a6 as SendResult, a7 as SupportedBlockType, a8 as SupportedHeaderBlock, a9 as TableBlock, aa as TableCell, ab as TableColumnSetting, ac as TableRawTextCell, ad as TaskCardBlock, ae as TaskCardStatus, af as UpdatePayload, ag as UpdateResult, ah as UrlSourceElement, ai as ValidationSummary, aj as VideoBlock, ak as defaultPalette, al as extraAlertVariant, am as legacyInputVariants } from './palette-4BJw_nd7.js';
3
+ import { B as BlockKitchenProps, S as SupportedBlock, C as ChannelOption, a as SendAsUserStatus, b as SendPayload, c as SendExtrasContext, T as Template, P as PreviewSurface, d as PreviewTheme } from './palette-CXXqlwIH.js';
4
+ export { A as AlertBlock, e as AlertLevel, f as BlockKitchenBaseProps, g as BlockKitchenComposeOnlyProps, h as BlockKitchenSendProps, i as BrandPreset, j as BrandTheme, k as BrandTokens, l as BuilderBlock, m as CardBlock, n as CarouselBlock, o as CartesianChart, p as Chart, q as ChartAxisConfig, r as ChartDataPoint, s as ChartSeries, t as ChartType, u as ContainerBlock, v as ContainerChildBlock, w as ContextActionsBlock, x as ContextActionsElement, y as CustomEmoji, D as DataTableActionCell, z as DataTableBlock, E as DataTableCell, F as DataTableRawNumberCell, G as DataVisualizationBlock, H as EditableVia, I as FeedbackButtonSubobject, J as FeedbackButtonsElement, K as HeaderLevel, L as IconButtonElement, M as IconButtonIcon, N as InputBlock, O as LoadMessageInput, Q as LoadResult, R as LoadedMessage, U as LoadingConfig, V as MarkdownBlock, W as MentionChannel, X as MentionUser, Y as MentionsConfig, Z as PaletteMode, _ as PaletteSection, $ as PaletteVariant, a0 as PieChart, a1 as PieChartSegment, a2 as PlanBlock, a3 as PreviewHooks, a4 as PrimaryActionConfig, a5 as PrimaryActionContext, a6 as RecentMessage, a7 as SendResult, a8 as SupportedBlockType, a9 as SupportedHeaderBlock, aa as TableBlock, ab as TableCell, ac as TableColumnSetting, ad as TableRawTextCell, ae as TaskCardBlock, af as TaskCardStatus, ag as UpdatePayload, ah as UpdateResult, ai as UrlSourceElement, aj as ValidationSummary, ak as VideoBlock, al as defaultPalette, am as extraAlertVariant, an as legacyInputVariants } from './palette-CXXqlwIH.js';
5
5
  export { decodeBlocksFromString, encodeBlocksToString, toSlackBlocks } from './helpers.js';
6
6
  import 'slack-web-api-client';
7
7
 
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, Loader2, ExternalLink, AlertTriangle, ChevronRight, ChevronLeft, GripVertical, ChevronsUpDown, Check, Sun, Moon, MessageSquare, AppWindow, Home, Plus, ChevronDown, BookOpen, Undo2, Redo2, Trash2, Code2, Pencil, Send, Search, Copy, Info, CheckCircle2, LayoutGrid, ArrowUp, ArrowDown, Type, Bold, Italic, Strikethrough, Underline, Code, AtSign, List, ListOrdered, Quote, Hash, 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, Loader2, ExternalLink, AlertTriangle, ChevronRight, ChevronLeft, GripVertical, ChevronsUpDown, Check, Sun, Moon, MessageSquare, AppWindow, Home, Plus, ChevronDown, BookOpen, Undo2, Redo2, Trash2, Code2, Pencil, Send, Search, Copy, Info, CheckCircle2, LayoutGrid, ArrowUp, ArrowDown, Type, Bold, Italic, Strikethrough, Underline, Code, AtSign, Hash, List, ListOrdered, Quote, Smile, Link as Link$1 } from 'lucide-react';
4
4
  import * as React from 'react';
5
5
  import { createContext, useContext, useState, useRef, useEffect, useCallback, useMemo, useId } from 'react';
6
6
  import { clsx } from 'clsx';
@@ -21,6 +21,7 @@ import * as RadioGroupPrimitive from '@radix-ui/react-radio-group';
21
21
  import Link from '@tiptap/extension-link';
22
22
  import { Node as Node$1, ReactNodeViewRenderer, mergeAttributes, Extension, NodeViewWrapper, useEditor, EditorContent } from '@tiptap/react';
23
23
  import StarterKit from '@tiptap/starter-kit';
24
+ import { PluginKey } from '@tiptap/pm/state';
24
25
  import { Suggestion, exitSuggestion } from '@tiptap/suggestion';
25
26
 
26
27
  // src/components/block-kitchen.tsx
@@ -5244,15 +5245,16 @@ function makeEmojiImportResolver(customByName, index = getEmojiIndex()) {
5244
5245
 
5245
5246
  // src/lib/rich-text-tiptap.ts
5246
5247
  var USER_MENTION_NODE = "userMention";
5248
+ var CHANNEL_MENTION_NODE = "channelMention";
5247
5249
  var SUPPORTED_BLOCK_KINDS = /* @__PURE__ */ new Set([
5248
5250
  "rich_text_section",
5249
5251
  "rich_text_list",
5250
5252
  "rich_text_quote",
5251
5253
  "rich_text_preformatted"
5252
5254
  ]);
5253
- var SUPPORTED_INLINE_KINDS = /* @__PURE__ */ new Set(["text", "link", "emoji", "user"]);
5255
+ var SUPPORTED_INLINE_KINDS = /* @__PURE__ */ new Set(["text", "link", "emoji", "user", "channel"]);
5254
5256
  var SUPPORTED_TEXT_STYLE_KEYS = /* @__PURE__ */ new Set(["bold", "italic", "strike", "underline", "code"]);
5255
- var SUPPORTED_USER_STYLE_KEYS = /* @__PURE__ */ new Set(["bold", "italic", "strike", "underline"]);
5257
+ var SUPPORTED_MENTION_STYLE_KEYS = /* @__PURE__ */ new Set(["bold", "italic", "strike", "underline"]);
5256
5258
  var MAX_LIST_INDENT = 8;
5257
5259
  function detectLossy(block) {
5258
5260
  const out = [];
@@ -5307,10 +5309,13 @@ function scanInlines(inlines, where, out) {
5307
5309
  }
5308
5310
  }
5309
5311
  }
5310
- if (inline.type === "user") {
5312
+ if (inline.type === "user" || inline.type === "channel") {
5313
+ if (inline.type === "channel" && !inline.channel_id) {
5314
+ out.push({ label: "Channel without id", where: sub });
5315
+ }
5311
5316
  for (const k of Object.keys(inline.style ?? {})) {
5312
- if (!SUPPORTED_USER_STYLE_KEYS.has(k)) {
5313
- out.push({ label: `Mention style: ${k}`, where: sub });
5317
+ if (!SUPPORTED_MENTION_STYLE_KEYS.has(k)) {
5318
+ out.push({ label: `${inline.type === "channel" ? "Channel" : "Mention"} style: ${k}`, where: sub });
5314
5319
  }
5315
5320
  }
5316
5321
  }
@@ -5436,6 +5441,20 @@ function inlinesToPM(inlines, resolveEmoji) {
5436
5441
  node.marks = marks;
5437
5442
  }
5438
5443
  out.push(node);
5444
+ } else if (inline.type === "channel") {
5445
+ const channel = inline;
5446
+ if (!channel.channel_id) {
5447
+ continue;
5448
+ }
5449
+ const node = {
5450
+ type: CHANNEL_MENTION_NODE,
5451
+ attrs: { channelId: channel.channel_id, name: null }
5452
+ };
5453
+ const marks = styleToMarks(channel.style);
5454
+ if (marks.length > 0) {
5455
+ node.marks = marks;
5456
+ }
5457
+ out.push(node);
5439
5458
  } else if (inline.type === "link") {
5440
5459
  const link = inline;
5441
5460
  const text = link.text || link.url || "";
@@ -5575,13 +5594,26 @@ function proseMirrorInlinesToRichTextElements(nodes) {
5575
5594
  continue;
5576
5595
  }
5577
5596
  const user = { type: "user", user_id: userId };
5578
- const style2 = userStyle(node.marks ?? []);
5597
+ const style2 = mentionStyle(node.marks ?? []);
5579
5598
  if (Object.keys(style2).length > 0) {
5580
5599
  user.style = style2;
5581
5600
  }
5582
5601
  out.push(user);
5583
5602
  continue;
5584
5603
  }
5604
+ if (node.type === CHANNEL_MENTION_NODE) {
5605
+ const channelId = String(node.attrs?.channelId ?? "");
5606
+ if (!channelId) {
5607
+ continue;
5608
+ }
5609
+ const channel = { type: "channel", channel_id: channelId };
5610
+ const style2 = mentionStyle(node.marks ?? []);
5611
+ if (Object.keys(style2).length > 0) {
5612
+ channel.style = style2;
5613
+ }
5614
+ out.push(channel);
5615
+ continue;
5616
+ }
5585
5617
  if (node.type === "hardBreak") {
5586
5618
  out.push({ type: "text", text: "\n" });
5587
5619
  continue;
@@ -5636,10 +5668,10 @@ function marksToStyle(marks) {
5636
5668
  }
5637
5669
  return style;
5638
5670
  }
5639
- function userStyle(marks) {
5671
+ function mentionStyle(marks) {
5640
5672
  const style = {};
5641
5673
  for (const [key, value] of Object.entries(marksToStyle(marks))) {
5642
- if (SUPPORTED_USER_STYLE_KEYS.has(key)) {
5674
+ if (SUPPORTED_MENTION_STYLE_KEYS.has(key)) {
5643
5675
  style[key] = value;
5644
5676
  }
5645
5677
  }
@@ -5668,50 +5700,52 @@ function sameStyle(a, b) {
5668
5700
  }
5669
5701
  return ak.every((k, i) => k === bk[i]);
5670
5702
  }
5671
- function EmojiNodeView({ node }) {
5672
- const { name, src, unicode } = node.attrs;
5673
- const label = `:${name}:`;
5674
- return /* @__PURE__ */ jsx(NodeViewWrapper, { as: "span", className: "bk-emoji-node", "data-emoji": name, title: label, children: src ? /* @__PURE__ */ jsx(
5675
- "img",
5676
- {
5677
- src,
5678
- alt: label,
5679
- style: {
5680
- display: "inline-block",
5681
- width: "1.25em",
5682
- height: "1.25em",
5683
- verticalAlign: "-0.25em",
5684
- objectFit: "contain"
5685
- }
5686
- }
5687
- ) : /* @__PURE__ */ jsx("span", { "aria-label": label, role: "img", children: unicode ? codepointsToGlyph(unicode) : label }) });
5703
+ function channelMentionLabel(attrs, getChannelName) {
5704
+ return getChannelName?.(attrs.channelId) ?? attrs.name ?? attrs.channelId;
5688
5705
  }
5689
- var EmojiNode = Node$1.create({
5690
- name: "emoji",
5706
+ function ChannelMentionNodeView({ node }) {
5707
+ const mentions = useMentions();
5708
+ const attrs = node.attrs;
5709
+ return /* @__PURE__ */ jsxs(NodeViewWrapper, { as: "span", className: "bk-channel-mention", "data-channel-mention": attrs.channelId, children: [
5710
+ "#",
5711
+ channelMentionLabel(attrs, mentions?.getChannelName)
5712
+ ] });
5713
+ }
5714
+ var ChannelMentionNode = Node$1.create({
5715
+ name: CHANNEL_MENTION_NODE,
5691
5716
  group: "inline",
5692
5717
  inline: true,
5693
5718
  atom: true,
5694
5719
  selectable: true,
5695
5720
  addAttributes() {
5696
5721
  return {
5697
- name: { default: "" },
5698
- src: { default: null },
5699
- unicode: { default: null },
5700
- skinTone: { default: null }
5722
+ channelId: {
5723
+ default: "",
5724
+ parseHTML: (element) => element.getAttribute("data-channel-mention") ?? "",
5725
+ renderHTML: (attributes) => ({ "data-channel-mention": attributes.channelId })
5726
+ },
5727
+ name: {
5728
+ default: null,
5729
+ parseHTML: (element) => element.getAttribute("data-channel-name"),
5730
+ renderHTML: (attributes) => attributes.name ? { "data-channel-name": attributes.name } : {}
5731
+ }
5701
5732
  };
5702
5733
  },
5703
5734
  parseHTML() {
5704
- return [{ tag: "span[data-emoji]" }];
5735
+ return [{ tag: "span[data-channel-mention]" }];
5705
5736
  },
5706
- renderHTML({ HTMLAttributes }) {
5707
- const name = HTMLAttributes.name ?? "";
5708
- return ["span", mergeAttributes({ "data-emoji": name }, HTMLAttributes), `:${name}:`];
5737
+ renderHTML({ node, HTMLAttributes }) {
5738
+ return ["span", mergeAttributes(HTMLAttributes), `#${channelMentionLabel(node.attrs)}`];
5739
+ },
5740
+ renderText({ node }) {
5741
+ return `#${channelMentionLabel(node.attrs)}`;
5709
5742
  },
5710
5743
  addNodeView() {
5711
- return ReactNodeViewRenderer(EmojiNodeView);
5744
+ return ReactNodeViewRenderer(ChannelMentionNodeView);
5712
5745
  }
5713
5746
  });
5714
5747
  var MENTION_SEARCH_DEBOUNCE_MS = 200;
5748
+ var USER_MENTION_SUGGESTION_PLUGIN_KEY = new PluginKey("userMentionSuggestion");
5715
5749
  function insertUserMention(editor, range, user) {
5716
5750
  const to = editor.state.doc.textBetween(range.to, Math.min(range.to + 1, editor.state.doc.content.size)) === " " ? range.to + 1 : range.to;
5717
5751
  editor.chain().focus().insertContentAt({ from: range.from, to }, [
@@ -5720,13 +5754,16 @@ function insertUserMention(editor, range, user) {
5720
5754
  ]).run();
5721
5755
  }
5722
5756
  function insertMentionTrigger(editor) {
5757
+ insertSuggestionTrigger(editor, "@");
5758
+ }
5759
+ function insertSuggestionTrigger(editor, char) {
5723
5760
  const { from } = editor.state.selection;
5724
5761
  const before = from > 1 ? editor.state.doc.textBetween(from - 1, from, void 0, "\uFFFC") : "";
5725
5762
  const needsSpace = before !== "" && !/\s/.test(before);
5726
- editor.chain().focus().insertContent(needsSpace ? " @" : "@").run();
5763
+ editor.chain().focus().insertContent(needsSpace ? ` ${char}` : char).run();
5727
5764
  }
5728
5765
  function dismissMentionSuggestion(editor) {
5729
- exitSuggestion(editor.view);
5766
+ exitSuggestion(editor.view, USER_MENTION_SUGGESTION_PLUGIN_KEY);
5730
5767
  }
5731
5768
  var MentionSuggestion = Extension.create({
5732
5769
  name: "userMentionSuggestion",
@@ -5742,6 +5779,7 @@ var MentionSuggestion = Extension.create({
5742
5779
  return [
5743
5780
  Suggestion({
5744
5781
  editor: this.editor,
5782
+ pluginKey: USER_MENTION_SUGGESTION_PLUGIN_KEY,
5745
5783
  char: "@",
5746
5784
  debounce: MENTION_SEARCH_DEBOUNCE_MS,
5747
5785
  allow: () => isEnabled(),
@@ -5752,6 +5790,90 @@ var MentionSuggestion = Extension.create({
5752
5790
  ];
5753
5791
  }
5754
5792
  });
5793
+
5794
+ // src/lib/tiptap-channel-suggestion.ts
5795
+ var CHANNEL_MENTION_SUGGESTION_PLUGIN_KEY = new PluginKey("channelMentionSuggestion");
5796
+ function insertChannelMention(editor, range, channel) {
5797
+ const to = editor.state.doc.textBetween(range.to, Math.min(range.to + 1, editor.state.doc.content.size)) === " " ? range.to + 1 : range.to;
5798
+ editor.chain().focus().insertContentAt({ from: range.from, to }, [
5799
+ { type: CHANNEL_MENTION_NODE, attrs: { channelId: channel.id, name: channel.name } },
5800
+ { type: "text", text: " " }
5801
+ ]).run();
5802
+ }
5803
+ function insertChannelTrigger(editor) {
5804
+ insertSuggestionTrigger(editor, "#");
5805
+ }
5806
+ function dismissChannelSuggestion(editor) {
5807
+ exitSuggestion(editor.view, CHANNEL_MENTION_SUGGESTION_PLUGIN_KEY);
5808
+ }
5809
+ var ChannelSuggestion = Extension.create({
5810
+ name: "channelMentionSuggestion",
5811
+ addOptions() {
5812
+ return {
5813
+ isEnabled: () => false,
5814
+ search: async () => [],
5815
+ render: () => ({})
5816
+ };
5817
+ },
5818
+ addProseMirrorPlugins() {
5819
+ const { isEnabled, search, render } = this.options;
5820
+ return [
5821
+ Suggestion({
5822
+ editor: this.editor,
5823
+ pluginKey: CHANNEL_MENTION_SUGGESTION_PLUGIN_KEY,
5824
+ char: "#",
5825
+ debounce: MENTION_SEARCH_DEBOUNCE_MS,
5826
+ allow: () => isEnabled(),
5827
+ items: ({ query, signal }) => search(query, signal),
5828
+ command: ({ editor, range, props }) => insertChannelMention(editor, range, props),
5829
+ render
5830
+ })
5831
+ ];
5832
+ }
5833
+ });
5834
+ function EmojiNodeView({ node }) {
5835
+ const { name, src, unicode } = node.attrs;
5836
+ const label = `:${name}:`;
5837
+ return /* @__PURE__ */ jsx(NodeViewWrapper, { as: "span", className: "bk-emoji-node", "data-emoji": name, title: label, children: src ? /* @__PURE__ */ jsx(
5838
+ "img",
5839
+ {
5840
+ src,
5841
+ alt: label,
5842
+ style: {
5843
+ display: "inline-block",
5844
+ width: "1.25em",
5845
+ height: "1.25em",
5846
+ verticalAlign: "-0.25em",
5847
+ objectFit: "contain"
5848
+ }
5849
+ }
5850
+ ) : /* @__PURE__ */ jsx("span", { "aria-label": label, role: "img", children: unicode ? codepointsToGlyph(unicode) : label }) });
5851
+ }
5852
+ var EmojiNode = Node$1.create({
5853
+ name: "emoji",
5854
+ group: "inline",
5855
+ inline: true,
5856
+ atom: true,
5857
+ selectable: true,
5858
+ addAttributes() {
5859
+ return {
5860
+ name: { default: "" },
5861
+ src: { default: null },
5862
+ unicode: { default: null },
5863
+ skinTone: { default: null }
5864
+ };
5865
+ },
5866
+ parseHTML() {
5867
+ return [{ tag: "span[data-emoji]" }];
5868
+ },
5869
+ renderHTML({ HTMLAttributes }) {
5870
+ const name = HTMLAttributes.name ?? "";
5871
+ return ["span", mergeAttributes({ "data-emoji": name }, HTMLAttributes), `:${name}:`];
5872
+ },
5873
+ addNodeView() {
5874
+ return ReactNodeViewRenderer(EmojiNodeView);
5875
+ }
5876
+ });
5755
5877
  function userMentionLabel(attrs, getUserName) {
5756
5878
  return getUserName?.(attrs.userId) ?? attrs.name ?? attrs.userId;
5757
5879
  }
@@ -6087,6 +6209,7 @@ function nextMentionPickerState(props, prev) {
6087
6209
  }
6088
6210
  function MentionPicker({
6089
6211
  editor,
6212
+ kind,
6090
6213
  state,
6091
6214
  keyHandlerRef,
6092
6215
  onDismiss
@@ -6099,6 +6222,27 @@ function MentionPicker({
6099
6222
  setActiveIndex(0);
6100
6223
  }
6101
6224
  const listRef = useRef(null);
6225
+ const listboxId = useId();
6226
+ const activeOptionId = items[activeIndex] ? `${listboxId}-${activeIndex}` : void 0;
6227
+ useEffect(() => {
6228
+ const root = editor.view.dom;
6229
+ root.setAttribute("role", "combobox");
6230
+ root.setAttribute("aria-autocomplete", "list");
6231
+ root.setAttribute("aria-expanded", "true");
6232
+ root.setAttribute("aria-controls", listboxId);
6233
+ if (activeOptionId) {
6234
+ root.setAttribute("aria-activedescendant", activeOptionId);
6235
+ } else {
6236
+ root.removeAttribute("aria-activedescendant");
6237
+ }
6238
+ return () => {
6239
+ root.removeAttribute("role");
6240
+ root.removeAttribute("aria-autocomplete");
6241
+ root.removeAttribute("aria-expanded");
6242
+ root.removeAttribute("aria-controls");
6243
+ root.removeAttribute("aria-activedescendant");
6244
+ };
6245
+ }, [activeOptionId, editor, listboxId]);
6102
6246
  useEffect(() => {
6103
6247
  keyHandlerRef.current = (event) => {
6104
6248
  if (event.key === "ArrowDown" || event.key === "ArrowUp") {
@@ -6109,11 +6253,11 @@ function MentionPicker({
6109
6253
  return true;
6110
6254
  }
6111
6255
  if (event.key === "Enter" || event.key === "Tab") {
6112
- const user = items[activeIndex];
6113
- if (!user) {
6256
+ const item = items[activeIndex];
6257
+ if (!item) {
6114
6258
  return false;
6115
6259
  }
6116
- command(user);
6260
+ command(item);
6117
6261
  return true;
6118
6262
  }
6119
6263
  return false;
@@ -6160,29 +6304,39 @@ function MentionPicker({
6160
6304
  e.preventDefault();
6161
6305
  }
6162
6306
  },
6163
- children: /* @__PURE__ */ jsx("div", { ref: listRef, role: "listbox", "aria-label": "People to mention", children: items.length === 0 ? /* @__PURE__ */ jsx("p", { className: "px-2 py-3 text-center text-xs text-muted-foreground", children: loading ? "Searching\u2026" : "No people match that search." }) : items.map((user, index) => (
6164
- // biome-ignore lint/a11y/useFocusableInteractive: focus stays in the editor, which drives this list through the suggestion plugin's key handling.
6165
- // biome-ignore lint/a11y/useKeyWithClickEvents: the keyboard path is the editor's, forwarded via keyHandlerRef.
6166
- /* @__PURE__ */ jsxs(
6167
- "div",
6168
- {
6169
- role: "option",
6170
- "aria-selected": index === activeIndex,
6171
- onPointerDown: (e) => e.preventDefault(),
6172
- onClick: () => command(user),
6173
- onMouseMove: () => setActiveIndex(index),
6174
- className: cn(
6175
- "flex cursor-pointer items-center gap-2 rounded-sm px-2 py-1.5 text-sm text-foreground",
6176
- index === activeIndex && "bg-accent"
6177
- ),
6178
- children: [
6179
- /* @__PURE__ */ jsx(MentionAvatar, { user }),
6180
- /* @__PURE__ */ jsx("span", { className: "truncate", children: user.name })
6181
- ]
6182
- },
6183
- user.id
6184
- )
6185
- )) })
6307
+ children: /* @__PURE__ */ jsx(
6308
+ "div",
6309
+ {
6310
+ ref: listRef,
6311
+ id: listboxId,
6312
+ role: "listbox",
6313
+ "aria-label": kind === "channel" ? "Channels to mention" : "People to mention",
6314
+ children: items.length === 0 ? /* @__PURE__ */ jsx("p", { className: "px-2 py-3 text-center text-xs text-muted-foreground", children: loading ? "Searching\u2026" : `No ${kind === "channel" ? "channels" : "people"} match that search.` }) : items.map((item, index) => (
6315
+ // biome-ignore lint/a11y/useFocusableInteractive: focus stays in the editor, which drives this list through the suggestion plugin's key handling.
6316
+ // biome-ignore lint/a11y/useKeyWithClickEvents: the keyboard path is the editor's, forwarded via keyHandlerRef.
6317
+ /* @__PURE__ */ jsxs(
6318
+ "div",
6319
+ {
6320
+ id: `${listboxId}-${index}`,
6321
+ role: "option",
6322
+ "aria-selected": index === activeIndex,
6323
+ onPointerDown: (e) => e.preventDefault(),
6324
+ onClick: () => command(item),
6325
+ onMouseMove: () => setActiveIndex(index),
6326
+ className: cn(
6327
+ "flex cursor-pointer items-center gap-2 rounded-sm px-2 py-1.5 text-sm text-foreground",
6328
+ index === activeIndex && "bg-accent"
6329
+ ),
6330
+ children: [
6331
+ kind === "channel" ? /* @__PURE__ */ jsx("span", { className: "flex h-5 w-5 shrink-0 items-center justify-center rounded bg-muted text-muted-foreground", children: /* @__PURE__ */ jsx(Hash, { className: "h-3.5 w-3.5" }) }) : /* @__PURE__ */ jsx(MentionAvatar, { user: item }),
6332
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: kind === "channel" ? `#${item.name}` : item.name })
6333
+ ]
6334
+ },
6335
+ item.id
6336
+ )
6337
+ ))
6338
+ }
6339
+ )
6186
6340
  }
6187
6341
  )
6188
6342
  ]
@@ -6779,7 +6933,7 @@ function LossyBanner({ reasons }) {
6779
6933
  ] });
6780
6934
  }
6781
6935
  var RICH_TEXT_INPUT_RULE_ALLOWLIST = ["bold", "italic", "strike", "code"];
6782
- function buildRichTextExtensions(mentionSuggestion) {
6936
+ function buildRichTextExtensions(mentionSuggestion, channelSuggestion) {
6783
6937
  return [
6784
6938
  StarterKit.configure({
6785
6939
  heading: false,
@@ -6813,10 +6967,11 @@ function buildRichTextExtensions(mentionSuggestion) {
6813
6967
  shouldAutoLink: hasExplicitSafeScheme,
6814
6968
  HTMLAttributes: { rel: "noreferrer noopener", target: "_blank" }
6815
6969
  }),
6816
- // Always registered, so mentions already in the content round-trip with
6817
- // or without the picker.
6970
+ // Always registered, so mentions already in the content round-trip with or without pickers.
6818
6971
  UserMentionNode,
6819
- ...mentionSuggestion ? [MentionSuggestion.configure(mentionSuggestion)] : []
6972
+ ChannelMentionNode,
6973
+ ...mentionSuggestion ? [MentionSuggestion.configure(mentionSuggestion)] : [],
6974
+ ...channelSuggestion ? [ChannelSuggestion.configure(channelSuggestion)] : []
6820
6975
  ];
6821
6976
  }
6822
6977
  function RichTextWysiwygEditor({ block, onChange }) {
@@ -6844,24 +6999,39 @@ function RichTextWysiwygEditor({ block, onChange }) {
6844
6999
  [onChange]
6845
7000
  );
6846
7001
  const mentions = useMentions();
6847
- const mentionsEnabled = mentions !== null;
7002
+ const userMentionsEnabled = mentions !== null;
7003
+ const channelMentionsEnabled = mentions?.searchChannels !== void 0;
6848
7004
  const mentionsRef = useRef(mentions);
6849
7005
  useEffect(() => {
6850
7006
  mentionsRef.current = mentions;
6851
7007
  }, [mentions]);
6852
7008
  const [mentionPicker, setMentionPicker] = useState(null);
6853
7009
  const mentionKeyHandlerRef = useRef(() => false);
7010
+ const [channelPicker, setChannelPicker] = useState(null);
7011
+ const channelKeyHandlerRef = useRef(() => false);
6854
7012
  const editor = useEditor({
6855
- extensions: buildRichTextExtensions({
6856
- isEnabled: () => mentionsRef.current !== null,
6857
- search: async (query, signal) => await mentionsRef.current?.searchUsers(query, { signal }) ?? [],
6858
- render: () => ({
6859
- onStart: (props) => setMentionPicker((prev) => nextMentionPickerState(props, prev)),
6860
- onUpdate: (props) => setMentionPicker((prev) => nextMentionPickerState(props, prev)),
6861
- onExit: () => setMentionPicker(null),
6862
- onKeyDown: ({ event }) => mentionKeyHandlerRef.current(event)
6863
- })
6864
- }),
7013
+ extensions: buildRichTextExtensions(
7014
+ {
7015
+ isEnabled: () => mentionsRef.current !== null,
7016
+ search: async (query, signal) => await mentionsRef.current?.searchUsers(query, { signal }) ?? [],
7017
+ render: () => ({
7018
+ onStart: (props) => setMentionPicker((prev) => nextMentionPickerState(props, prev)),
7019
+ onUpdate: (props) => setMentionPicker((prev) => nextMentionPickerState(props, prev)),
7020
+ onExit: () => setMentionPicker(null),
7021
+ onKeyDown: ({ event }) => mentionKeyHandlerRef.current(event)
7022
+ })
7023
+ },
7024
+ {
7025
+ isEnabled: () => mentionsRef.current?.searchChannels !== void 0,
7026
+ search: async (query, signal) => await mentionsRef.current?.searchChannels?.(query, { signal }) ?? [],
7027
+ render: () => ({
7028
+ onStart: (props) => setChannelPicker((prev) => nextMentionPickerState(props, prev)),
7029
+ onUpdate: (props) => setChannelPicker((prev) => nextMentionPickerState(props, prev)),
7030
+ onExit: () => setChannelPicker(null),
7031
+ onKeyDown: ({ event }) => channelKeyHandlerRef.current(event)
7032
+ })
7033
+ }
7034
+ ),
6865
7035
  enableInputRules: RICH_TEXT_INPUT_RULE_ALLOWLIST,
6866
7036
  content: richTextToProseMirror(block, resolveEmoji),
6867
7037
  editorProps: {
@@ -6896,7 +7066,8 @@ function RichTextWysiwygEditor({ block, onChange }) {
6896
7066
  Toolbar,
6897
7067
  {
6898
7068
  editor,
6899
- mention: mentionsEnabled ? { active: mentionPicker !== null, insert: () => insertMentionTrigger(editor) } : null
7069
+ mention: userMentionsEnabled ? { active: mentionPicker !== null, insert: () => insertMentionTrigger(editor) } : null,
7070
+ channel: channelMentionsEnabled ? { active: channelPicker !== null, insert: () => insertChannelTrigger(editor) } : null
6900
7071
  }
6901
7072
  ),
6902
7073
  /* @__PURE__ */ jsx(EditorContent, { editor }),
@@ -6904,14 +7075,29 @@ function RichTextWysiwygEditor({ block, onChange }) {
6904
7075
  MentionPicker,
6905
7076
  {
6906
7077
  editor,
7078
+ kind: "user",
6907
7079
  state: mentionPicker,
6908
7080
  keyHandlerRef: mentionKeyHandlerRef,
6909
7081
  onDismiss: () => dismissMentionSuggestion(editor)
6910
7082
  }
7083
+ ),
7084
+ channelPicker && /* @__PURE__ */ jsx(
7085
+ MentionPicker,
7086
+ {
7087
+ editor,
7088
+ kind: "channel",
7089
+ state: channelPicker,
7090
+ keyHandlerRef: channelKeyHandlerRef,
7091
+ onDismiss: () => dismissChannelSuggestion(editor)
7092
+ }
6911
7093
  )
6912
7094
  ] });
6913
7095
  }
6914
- function Toolbar({ editor, mention }) {
7096
+ function Toolbar({
7097
+ editor,
7098
+ mention,
7099
+ channel
7100
+ }) {
6915
7101
  return /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-0.5 rounded-md border bg-muted/30 p-1", children: [
6916
7102
  /* @__PURE__ */ jsx(
6917
7103
  ToolbarButton,
@@ -6971,6 +7157,7 @@ function Toolbar({ editor, mention }) {
6971
7157
  }
6972
7158
  ),
6973
7159
  mention && /* @__PURE__ */ jsx(ToolbarButton, { label: "Mention", active: mention.active, onClick: mention.insert, Icon: AtSign }),
7160
+ channel && /* @__PURE__ */ jsx(ToolbarButton, { label: "Channel", active: channel.active, onClick: channel.insert, Icon: Hash }),
6974
7161
  /* @__PURE__ */ jsx(Divider, {}),
6975
7162
  /* @__PURE__ */ jsx(
6976
7163
  ToolbarButton,