@tightknitai/block-kitchen 0.10.17 → 0.10.19
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 +27 -0
- package/dist/helpers.d.cts +2 -2
- package/dist/helpers.d.ts +2 -2
- package/dist/index.cjs +342 -10
- 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 +345 -13
- package/dist/index.js.map +1 -1
- package/dist/{palette-Cxh1yNzW.d.cts → palette-DNUiqoQK.d.cts} +46 -1
- package/dist/{palette-Cxh1yNzW.d.ts → palette-DNUiqoQK.d.ts} +46 -1
- package/dist/palette.d.cts +1 -1
- package/dist/palette.d.ts +1 -1
- package/dist/styles.css +1 -1
- package/package.json +14 -3
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-
|
|
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 DataVisualizationBlock, E as EditableVia, F as FeedbackButtonSubobject, z as FeedbackButtonsElement, H as HeaderLevel, I as IconButtonElement, G as IconButtonIcon, J as InputBlock, L as LoadMessageInput, K as LoadResult, M as LoadedMessage, N as LoadingConfig, O as MarkdownBlock, Q as
|
|
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-DNUiqoQK.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 DataVisualizationBlock, E as EditableVia, F as FeedbackButtonSubobject, z as FeedbackButtonsElement, H as HeaderLevel, I as IconButtonElement, G as IconButtonIcon, J as InputBlock, L as LoadMessageInput, K as LoadResult, M as LoadedMessage, N as LoadingConfig, O as MarkdownBlock, Q as MentionUser, R as MentionsConfig, U as PaletteMode, V as PaletteSection, W as PaletteVariant, X as PieChart, Y as PieChartSegment, Z as PlanBlock, _ as PreviewHooks, $ as PrimaryActionConfig, a0 as PrimaryActionContext, a1 as RecentMessage, a2 as SendResult, a3 as SupportedBlockType, a4 as SupportedHeaderBlock, a5 as TableBlock, a6 as TableCell, a7 as TableColumnSetting, a8 as TaskCardBlock, a9 as TaskCardStatus, aa as UpdatePayload, ab as UpdateResult, ac as UrlSourceElement, ad as ValidationSummary, ae as VideoBlock, af as defaultPalette, ag as extraAlertVariant, ah as legacyInputVariants } from './palette-DNUiqoQK.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-
|
|
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 DataVisualizationBlock, E as EditableVia, F as FeedbackButtonSubobject, z as FeedbackButtonsElement, H as HeaderLevel, I as IconButtonElement, G as IconButtonIcon, J as InputBlock, L as LoadMessageInput, K as LoadResult, M as LoadedMessage, N as LoadingConfig, O as MarkdownBlock, Q as
|
|
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-DNUiqoQK.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 DataVisualizationBlock, E as EditableVia, F as FeedbackButtonSubobject, z as FeedbackButtonsElement, H as HeaderLevel, I as IconButtonElement, G as IconButtonIcon, J as InputBlock, L as LoadMessageInput, K as LoadResult, M as LoadedMessage, N as LoadingConfig, O as MarkdownBlock, Q as MentionUser, R as MentionsConfig, U as PaletteMode, V as PaletteSection, W as PaletteVariant, X as PieChart, Y as PieChartSegment, Z as PlanBlock, _ as PreviewHooks, $ as PrimaryActionConfig, a0 as PrimaryActionContext, a1 as RecentMessage, a2 as SendResult, a3 as SupportedBlockType, a4 as SupportedHeaderBlock, a5 as TableBlock, a6 as TableCell, a7 as TableColumnSetting, a8 as TaskCardBlock, a9 as TaskCardStatus, aa as UpdatePayload, ab as UpdateResult, ac as UrlSourceElement, ad as ValidationSummary, ae as VideoBlock, af as defaultPalette, ag as extraAlertVariant, ah as legacyInputVariants } from './palette-DNUiqoQK.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,8 +1,8 @@
|
|
|
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, List, ListOrdered, Quote, 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, List, ListOrdered, Quote, Smile, Link as Link$1 } from 'lucide-react';
|
|
4
4
|
import * as React from 'react';
|
|
5
|
-
import { createContext, useState, useRef, useEffect, useCallback, useMemo, useId
|
|
5
|
+
import { createContext, useContext, useState, useRef, useEffect, useCallback, useMemo, useId } from 'react';
|
|
6
6
|
import { clsx } from 'clsx';
|
|
7
7
|
import { twMerge } from 'tailwind-merge';
|
|
8
8
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
@@ -19,8 +19,9 @@ import { Message } from 'slack-blocks-to-jsx';
|
|
|
19
19
|
import { CSS } from '@dnd-kit/utilities';
|
|
20
20
|
import * as RadioGroupPrimitive from '@radix-ui/react-radio-group';
|
|
21
21
|
import Link from '@tiptap/extension-link';
|
|
22
|
-
import { Node as Node$1, ReactNodeViewRenderer, mergeAttributes, NodeViewWrapper, useEditor, EditorContent } from '@tiptap/react';
|
|
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 { Suggestion, exitSuggestion } from '@tiptap/suggestion';
|
|
24
25
|
|
|
25
26
|
// src/components/block-kitchen.tsx
|
|
26
27
|
|
|
@@ -1833,6 +1834,13 @@ function CustomEmojiProvider({ customEmojis, children }) {
|
|
|
1833
1834
|
function useCustomEmojis() {
|
|
1834
1835
|
return useContext(CustomEmojiContext);
|
|
1835
1836
|
}
|
|
1837
|
+
var MentionsContext = createContext(null);
|
|
1838
|
+
function MentionsProvider({ mentions, children }) {
|
|
1839
|
+
return /* @__PURE__ */ jsx(MentionsContext.Provider, { value: mentions ?? null, children });
|
|
1840
|
+
}
|
|
1841
|
+
function useMentions() {
|
|
1842
|
+
return useContext(MentionsContext);
|
|
1843
|
+
}
|
|
1836
1844
|
|
|
1837
1845
|
// src/lib/error-grouping.ts
|
|
1838
1846
|
function toValidatorSurface(surface) {
|
|
@@ -3333,12 +3341,27 @@ function ChannelCombobox({
|
|
|
3333
3341
|
] });
|
|
3334
3342
|
}
|
|
3335
3343
|
var EMBED_SELECTOR = "iframe[src], embed[src], object[data], source[src], video[src], audio[src], track[src]";
|
|
3344
|
+
function useLatestHooks(hooks) {
|
|
3345
|
+
const latest = useRef(hooks);
|
|
3346
|
+
latest.current = hooks;
|
|
3347
|
+
const [live] = useState(
|
|
3348
|
+
() => new Proxy(
|
|
3349
|
+
{},
|
|
3350
|
+
{
|
|
3351
|
+
get: (_target, key) => typeof key === "string" ? latest.current?.[key] : void 0,
|
|
3352
|
+
has: (_target, key) => typeof key === "string" && latest.current !== void 0 && key in latest.current
|
|
3353
|
+
}
|
|
3354
|
+
)
|
|
3355
|
+
);
|
|
3356
|
+
return live;
|
|
3357
|
+
}
|
|
3336
3358
|
function SlackBlockPreview({
|
|
3337
3359
|
block,
|
|
3338
3360
|
hooks,
|
|
3339
3361
|
theme = "light"
|
|
3340
3362
|
}) {
|
|
3341
3363
|
const rootRef = useRef(null);
|
|
3364
|
+
const liveHooks = useLatestHooks(hooks);
|
|
3342
3365
|
const safeBlock = useMemo(() => sanitizeBlock(block), [block]);
|
|
3343
3366
|
useEffect(() => {
|
|
3344
3367
|
const root = rootRef.current;
|
|
@@ -3393,7 +3416,7 @@ function SlackBlockPreview({
|
|
|
3393
3416
|
withoutWrapper: true,
|
|
3394
3417
|
theme,
|
|
3395
3418
|
blocks: [safeBlock],
|
|
3396
|
-
hooks
|
|
3419
|
+
hooks: liveHooks
|
|
3397
3420
|
}
|
|
3398
3421
|
) })
|
|
3399
3422
|
);
|
|
@@ -5166,14 +5189,16 @@ function makeEmojiImportResolver(customByName, index = getEmojiIndex()) {
|
|
|
5166
5189
|
}
|
|
5167
5190
|
|
|
5168
5191
|
// src/lib/rich-text-tiptap.ts
|
|
5192
|
+
var USER_MENTION_NODE = "userMention";
|
|
5169
5193
|
var SUPPORTED_BLOCK_KINDS = /* @__PURE__ */ new Set([
|
|
5170
5194
|
"rich_text_section",
|
|
5171
5195
|
"rich_text_list",
|
|
5172
5196
|
"rich_text_quote",
|
|
5173
5197
|
"rich_text_preformatted"
|
|
5174
5198
|
]);
|
|
5175
|
-
var SUPPORTED_INLINE_KINDS = /* @__PURE__ */ new Set(["text", "link", "emoji"]);
|
|
5199
|
+
var SUPPORTED_INLINE_KINDS = /* @__PURE__ */ new Set(["text", "link", "emoji", "user"]);
|
|
5176
5200
|
var SUPPORTED_TEXT_STYLE_KEYS = /* @__PURE__ */ new Set(["bold", "italic", "strike", "underline", "code"]);
|
|
5201
|
+
var SUPPORTED_USER_STYLE_KEYS = /* @__PURE__ */ new Set(["bold", "italic", "strike", "underline"]);
|
|
5177
5202
|
var MAX_LIST_INDENT = 8;
|
|
5178
5203
|
function detectLossy(block) {
|
|
5179
5204
|
const out = [];
|
|
@@ -5196,6 +5221,17 @@ function detectLossy(block) {
|
|
|
5196
5221
|
if ("border" in el && (el.border ?? 0) > 0) {
|
|
5197
5222
|
out.push({ label: "Quote/preformatted border", where });
|
|
5198
5223
|
}
|
|
5224
|
+
if (el.type === "rich_text_preformatted") {
|
|
5225
|
+
el.elements?.forEach((inline, i) => {
|
|
5226
|
+
const sub = `${where}.elements[${i}]`;
|
|
5227
|
+
if (inline.type !== "text") {
|
|
5228
|
+
out.push({ label: `In code block: ${inline.type}`, where: sub });
|
|
5229
|
+
} else if (Object.values(inline.style ?? {}).some(Boolean)) {
|
|
5230
|
+
out.push({ label: "Styled text in code block", where: sub });
|
|
5231
|
+
}
|
|
5232
|
+
});
|
|
5233
|
+
return;
|
|
5234
|
+
}
|
|
5199
5235
|
scanInlines(el.elements ?? [], where, out);
|
|
5200
5236
|
});
|
|
5201
5237
|
return out;
|
|
@@ -5217,6 +5253,13 @@ function scanInlines(inlines, where, out) {
|
|
|
5217
5253
|
}
|
|
5218
5254
|
}
|
|
5219
5255
|
}
|
|
5256
|
+
if (inline.type === "user") {
|
|
5257
|
+
for (const k of Object.keys(inline.style ?? {})) {
|
|
5258
|
+
if (!SUPPORTED_USER_STYLE_KEYS.has(k)) {
|
|
5259
|
+
out.push({ label: `Mention style: ${k}`, where: sub });
|
|
5260
|
+
}
|
|
5261
|
+
}
|
|
5262
|
+
}
|
|
5220
5263
|
});
|
|
5221
5264
|
}
|
|
5222
5265
|
var defaultEmojiResolver = (el) => ({
|
|
@@ -5328,6 +5371,17 @@ function inlinesToPM(inlines, resolveEmoji) {
|
|
|
5328
5371
|
continue;
|
|
5329
5372
|
}
|
|
5330
5373
|
out.push({ type: "emoji", attrs });
|
|
5374
|
+
} else if (inline.type === "user") {
|
|
5375
|
+
const user = inline;
|
|
5376
|
+
if (!user.user_id) {
|
|
5377
|
+
continue;
|
|
5378
|
+
}
|
|
5379
|
+
const node = { type: USER_MENTION_NODE, attrs: { userId: user.user_id, name: null } };
|
|
5380
|
+
const marks = styleToMarks(user.style);
|
|
5381
|
+
if (marks.length > 0) {
|
|
5382
|
+
node.marks = marks;
|
|
5383
|
+
}
|
|
5384
|
+
out.push(node);
|
|
5331
5385
|
} else if (inline.type === "link") {
|
|
5332
5386
|
const link = inline;
|
|
5333
5387
|
const text = link.text || link.url || "";
|
|
@@ -5461,6 +5515,19 @@ function proseMirrorInlinesToRichTextElements(nodes) {
|
|
|
5461
5515
|
out.push(emoji);
|
|
5462
5516
|
continue;
|
|
5463
5517
|
}
|
|
5518
|
+
if (node.type === USER_MENTION_NODE) {
|
|
5519
|
+
const userId = String(node.attrs?.userId ?? "");
|
|
5520
|
+
if (!userId) {
|
|
5521
|
+
continue;
|
|
5522
|
+
}
|
|
5523
|
+
const user = { type: "user", user_id: userId };
|
|
5524
|
+
const style2 = userStyle(node.marks ?? []);
|
|
5525
|
+
if (Object.keys(style2).length > 0) {
|
|
5526
|
+
user.style = style2;
|
|
5527
|
+
}
|
|
5528
|
+
out.push(user);
|
|
5529
|
+
continue;
|
|
5530
|
+
}
|
|
5464
5531
|
if (node.type === "hardBreak") {
|
|
5465
5532
|
out.push({ type: "text", text: "\n" });
|
|
5466
5533
|
continue;
|
|
@@ -5515,6 +5582,15 @@ function marksToStyle(marks) {
|
|
|
5515
5582
|
}
|
|
5516
5583
|
return style;
|
|
5517
5584
|
}
|
|
5585
|
+
function userStyle(marks) {
|
|
5586
|
+
const style = {};
|
|
5587
|
+
for (const [key, value] of Object.entries(marksToStyle(marks))) {
|
|
5588
|
+
if (SUPPORTED_USER_STYLE_KEYS.has(key)) {
|
|
5589
|
+
style[key] = value;
|
|
5590
|
+
}
|
|
5591
|
+
}
|
|
5592
|
+
return style;
|
|
5593
|
+
}
|
|
5518
5594
|
function mergeAdjacentTextRuns(inlines) {
|
|
5519
5595
|
const out = [];
|
|
5520
5596
|
for (const inline of inlines) {
|
|
@@ -5581,6 +5657,91 @@ var EmojiNode = Node$1.create({
|
|
|
5581
5657
|
return ReactNodeViewRenderer(EmojiNodeView);
|
|
5582
5658
|
}
|
|
5583
5659
|
});
|
|
5660
|
+
var MENTION_SEARCH_DEBOUNCE_MS = 200;
|
|
5661
|
+
function insertUserMention(editor, range, user) {
|
|
5662
|
+
const to = editor.state.doc.textBetween(range.to, Math.min(range.to + 1, editor.state.doc.content.size)) === " " ? range.to + 1 : range.to;
|
|
5663
|
+
editor.chain().focus().insertContentAt({ from: range.from, to }, [
|
|
5664
|
+
{ type: USER_MENTION_NODE, attrs: { userId: user.id, name: user.name } },
|
|
5665
|
+
{ type: "text", text: " " }
|
|
5666
|
+
]).run();
|
|
5667
|
+
}
|
|
5668
|
+
function insertMentionTrigger(editor) {
|
|
5669
|
+
const { from } = editor.state.selection;
|
|
5670
|
+
const before = from > 1 ? editor.state.doc.textBetween(from - 1, from, void 0, "\uFFFC") : "";
|
|
5671
|
+
const needsSpace = before !== "" && !/\s/.test(before);
|
|
5672
|
+
editor.chain().focus().insertContent(needsSpace ? " @" : "@").run();
|
|
5673
|
+
}
|
|
5674
|
+
function dismissMentionSuggestion(editor) {
|
|
5675
|
+
exitSuggestion(editor.view);
|
|
5676
|
+
}
|
|
5677
|
+
var MentionSuggestion = Extension.create({
|
|
5678
|
+
name: "userMentionSuggestion",
|
|
5679
|
+
addOptions() {
|
|
5680
|
+
return {
|
|
5681
|
+
isEnabled: () => false,
|
|
5682
|
+
search: async () => [],
|
|
5683
|
+
render: () => ({})
|
|
5684
|
+
};
|
|
5685
|
+
},
|
|
5686
|
+
addProseMirrorPlugins() {
|
|
5687
|
+
const { isEnabled, search, render } = this.options;
|
|
5688
|
+
return [
|
|
5689
|
+
Suggestion({
|
|
5690
|
+
editor: this.editor,
|
|
5691
|
+
char: "@",
|
|
5692
|
+
debounce: MENTION_SEARCH_DEBOUNCE_MS,
|
|
5693
|
+
allow: () => isEnabled(),
|
|
5694
|
+
items: ({ query, signal }) => search(query, signal),
|
|
5695
|
+
command: ({ editor, range, props }) => insertUserMention(editor, range, props),
|
|
5696
|
+
render
|
|
5697
|
+
})
|
|
5698
|
+
];
|
|
5699
|
+
}
|
|
5700
|
+
});
|
|
5701
|
+
function userMentionLabel(attrs, getUserName) {
|
|
5702
|
+
return getUserName?.(attrs.userId) ?? attrs.name ?? attrs.userId;
|
|
5703
|
+
}
|
|
5704
|
+
function UserMentionNodeView({ node }) {
|
|
5705
|
+
const mentions = useMentions();
|
|
5706
|
+
const attrs = node.attrs;
|
|
5707
|
+
return /* @__PURE__ */ jsxs(NodeViewWrapper, { as: "span", className: "bk-user-mention", "data-user-mention": attrs.userId, children: [
|
|
5708
|
+
"@",
|
|
5709
|
+
userMentionLabel(attrs, mentions?.getUserName)
|
|
5710
|
+
] });
|
|
5711
|
+
}
|
|
5712
|
+
var UserMentionNode = Node$1.create({
|
|
5713
|
+
name: USER_MENTION_NODE,
|
|
5714
|
+
group: "inline",
|
|
5715
|
+
inline: true,
|
|
5716
|
+
atom: true,
|
|
5717
|
+
selectable: true,
|
|
5718
|
+
addAttributes() {
|
|
5719
|
+
return {
|
|
5720
|
+
userId: {
|
|
5721
|
+
default: "",
|
|
5722
|
+
parseHTML: (element) => element.getAttribute("data-user-mention") ?? "",
|
|
5723
|
+
renderHTML: (attributes) => ({ "data-user-mention": attributes.userId })
|
|
5724
|
+
},
|
|
5725
|
+
name: {
|
|
5726
|
+
default: null,
|
|
5727
|
+
parseHTML: (element) => element.getAttribute("data-user-name"),
|
|
5728
|
+
renderHTML: (attributes) => attributes.name ? { "data-user-name": attributes.name } : {}
|
|
5729
|
+
}
|
|
5730
|
+
};
|
|
5731
|
+
},
|
|
5732
|
+
parseHTML() {
|
|
5733
|
+
return [{ tag: "span[data-user-mention]" }];
|
|
5734
|
+
},
|
|
5735
|
+
renderHTML({ node, HTMLAttributes }) {
|
|
5736
|
+
return ["span", mergeAttributes(HTMLAttributes), `@${userMentionLabel(node.attrs)}`];
|
|
5737
|
+
},
|
|
5738
|
+
renderText({ node }) {
|
|
5739
|
+
return `@${userMentionLabel(node.attrs)}`;
|
|
5740
|
+
},
|
|
5741
|
+
addNodeView() {
|
|
5742
|
+
return ReactNodeViewRenderer(UserMentionNodeView);
|
|
5743
|
+
}
|
|
5744
|
+
});
|
|
5584
5745
|
|
|
5585
5746
|
// src/lib/emoji-recents.ts
|
|
5586
5747
|
var STORAGE_KEY = "bk:emoji-recents";
|
|
@@ -5862,6 +6023,133 @@ function EmojiPickerButton({
|
|
|
5862
6023
|
) })
|
|
5863
6024
|
] });
|
|
5864
6025
|
}
|
|
6026
|
+
function nextMentionPickerState(props, prev) {
|
|
6027
|
+
return {
|
|
6028
|
+
items: props.loading && prev ? prev.items : props.items,
|
|
6029
|
+
loading: props.loading,
|
|
6030
|
+
clientRect: props.clientRect ?? null,
|
|
6031
|
+
command: props.command
|
|
6032
|
+
};
|
|
6033
|
+
}
|
|
6034
|
+
function MentionPicker({
|
|
6035
|
+
editor,
|
|
6036
|
+
state,
|
|
6037
|
+
keyHandlerRef,
|
|
6038
|
+
onDismiss
|
|
6039
|
+
}) {
|
|
6040
|
+
const { items, loading, clientRect, command } = state;
|
|
6041
|
+
const [activeIndex, setActiveIndex] = useState(0);
|
|
6042
|
+
const [shownItems, setShownItems] = useState(items);
|
|
6043
|
+
if (shownItems !== items) {
|
|
6044
|
+
setShownItems(items);
|
|
6045
|
+
setActiveIndex(0);
|
|
6046
|
+
}
|
|
6047
|
+
const listRef = useRef(null);
|
|
6048
|
+
useEffect(() => {
|
|
6049
|
+
keyHandlerRef.current = (event) => {
|
|
6050
|
+
if (event.key === "ArrowDown" || event.key === "ArrowUp") {
|
|
6051
|
+
if (items.length > 0) {
|
|
6052
|
+
const step = event.key === "ArrowDown" ? 1 : -1;
|
|
6053
|
+
setActiveIndex((index) => (index + step + items.length) % items.length);
|
|
6054
|
+
}
|
|
6055
|
+
return true;
|
|
6056
|
+
}
|
|
6057
|
+
if (event.key === "Enter" || event.key === "Tab") {
|
|
6058
|
+
const user = items[activeIndex];
|
|
6059
|
+
if (!user) {
|
|
6060
|
+
return false;
|
|
6061
|
+
}
|
|
6062
|
+
command(user);
|
|
6063
|
+
return true;
|
|
6064
|
+
}
|
|
6065
|
+
return false;
|
|
6066
|
+
};
|
|
6067
|
+
return () => {
|
|
6068
|
+
keyHandlerRef.current = () => false;
|
|
6069
|
+
};
|
|
6070
|
+
}, [keyHandlerRef, items, activeIndex, command]);
|
|
6071
|
+
useEffect(() => {
|
|
6072
|
+
const row = listRef.current?.querySelectorAll('[role="option"]')[activeIndex];
|
|
6073
|
+
row?.scrollIntoView?.({ block: "nearest" });
|
|
6074
|
+
}, [activeIndex]);
|
|
6075
|
+
const anchorRef = useMemo(
|
|
6076
|
+
() => ({
|
|
6077
|
+
current: {
|
|
6078
|
+
getBoundingClientRect: () => clientRect?.() ?? editor.view.dom.getBoundingClientRect()
|
|
6079
|
+
}
|
|
6080
|
+
}),
|
|
6081
|
+
[clientRect, editor]
|
|
6082
|
+
);
|
|
6083
|
+
return /* @__PURE__ */ jsxs(
|
|
6084
|
+
Popover,
|
|
6085
|
+
{
|
|
6086
|
+
open: true,
|
|
6087
|
+
onOpenChange: (open) => {
|
|
6088
|
+
if (!open) {
|
|
6089
|
+
onDismiss();
|
|
6090
|
+
}
|
|
6091
|
+
},
|
|
6092
|
+
children: [
|
|
6093
|
+
/* @__PURE__ */ jsx(PopoverAnchor, { virtualRef: anchorRef }),
|
|
6094
|
+
/* @__PURE__ */ jsx(
|
|
6095
|
+
PopoverContent,
|
|
6096
|
+
{
|
|
6097
|
+
side: "bottom",
|
|
6098
|
+
align: "start",
|
|
6099
|
+
sideOffset: 4,
|
|
6100
|
+
className: "max-h-[min(15rem,var(--radix-popover-content-available-height))] w-64 p-1",
|
|
6101
|
+
onOpenAutoFocus: (e) => e.preventDefault(),
|
|
6102
|
+
onCloseAutoFocus: (e) => e.preventDefault(),
|
|
6103
|
+
onEscapeKeyDown: (e) => e.stopPropagation(),
|
|
6104
|
+
onInteractOutside: (e) => {
|
|
6105
|
+
if (e.target instanceof Node && editor.view.dom.contains(e.target)) {
|
|
6106
|
+
e.preventDefault();
|
|
6107
|
+
}
|
|
6108
|
+
},
|
|
6109
|
+
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) => (
|
|
6110
|
+
// biome-ignore lint/a11y/useFocusableInteractive: focus stays in the editor, which drives this list through the suggestion plugin's key handling.
|
|
6111
|
+
// biome-ignore lint/a11y/useKeyWithClickEvents: the keyboard path is the editor's, forwarded via keyHandlerRef.
|
|
6112
|
+
/* @__PURE__ */ jsxs(
|
|
6113
|
+
"div",
|
|
6114
|
+
{
|
|
6115
|
+
role: "option",
|
|
6116
|
+
"aria-selected": index === activeIndex,
|
|
6117
|
+
onPointerDown: (e) => e.preventDefault(),
|
|
6118
|
+
onClick: () => command(user),
|
|
6119
|
+
onMouseMove: () => setActiveIndex(index),
|
|
6120
|
+
className: cn(
|
|
6121
|
+
"flex cursor-pointer items-center gap-2 rounded-sm px-2 py-1.5 text-sm text-foreground",
|
|
6122
|
+
index === activeIndex && "bg-accent"
|
|
6123
|
+
),
|
|
6124
|
+
children: [
|
|
6125
|
+
/* @__PURE__ */ jsx(MentionAvatar, { user }),
|
|
6126
|
+
/* @__PURE__ */ jsx("span", { className: "truncate", children: user.name })
|
|
6127
|
+
]
|
|
6128
|
+
},
|
|
6129
|
+
user.id
|
|
6130
|
+
)
|
|
6131
|
+
)) })
|
|
6132
|
+
}
|
|
6133
|
+
)
|
|
6134
|
+
]
|
|
6135
|
+
}
|
|
6136
|
+
);
|
|
6137
|
+
}
|
|
6138
|
+
function MentionAvatar({ user }) {
|
|
6139
|
+
const [failed, setFailed] = useState(false);
|
|
6140
|
+
const src = user.avatarUrl && isSafeImageSrc(user.avatarUrl) ? user.avatarUrl : null;
|
|
6141
|
+
if (src && !failed) {
|
|
6142
|
+
return /* @__PURE__ */ jsx("img", { src, alt: "", className: "h-5 w-5 shrink-0 rounded object-cover", onError: () => setFailed(true) });
|
|
6143
|
+
}
|
|
6144
|
+
return /* @__PURE__ */ jsx(
|
|
6145
|
+
"span",
|
|
6146
|
+
{
|
|
6147
|
+
"aria-hidden": "true",
|
|
6148
|
+
className: "flex h-5 w-5 shrink-0 items-center justify-center rounded bg-muted text-[10px] font-medium text-muted-foreground",
|
|
6149
|
+
children: Array.from(user.name.trim())[0]?.toUpperCase() ?? "@"
|
|
6150
|
+
}
|
|
6151
|
+
);
|
|
6152
|
+
}
|
|
5865
6153
|
var BLOCK_ELEMENT_LABEL = {
|
|
5866
6154
|
rich_text_section: "Section",
|
|
5867
6155
|
rich_text_list: "List",
|
|
@@ -6437,7 +6725,7 @@ function LossyBanner({ reasons }) {
|
|
|
6437
6725
|
] });
|
|
6438
6726
|
}
|
|
6439
6727
|
var RICH_TEXT_INPUT_RULE_ALLOWLIST = ["bold", "italic", "strike", "code"];
|
|
6440
|
-
function buildRichTextExtensions() {
|
|
6728
|
+
function buildRichTextExtensions(mentionSuggestion) {
|
|
6441
6729
|
return [
|
|
6442
6730
|
StarterKit.configure({
|
|
6443
6731
|
heading: false,
|
|
@@ -6470,7 +6758,11 @@ function buildRichTextExtensions() {
|
|
|
6470
6758
|
// via the link button.
|
|
6471
6759
|
shouldAutoLink: hasExplicitSafeScheme,
|
|
6472
6760
|
HTMLAttributes: { rel: "noreferrer noopener", target: "_blank" }
|
|
6473
|
-
})
|
|
6761
|
+
}),
|
|
6762
|
+
// Always registered, so mentions already in the content round-trip with
|
|
6763
|
+
// or without the picker.
|
|
6764
|
+
UserMentionNode,
|
|
6765
|
+
...mentionSuggestion ? [MentionSuggestion.configure(mentionSuggestion)] : []
|
|
6474
6766
|
];
|
|
6475
6767
|
}
|
|
6476
6768
|
function RichTextWysiwygEditor({ block, onChange }) {
|
|
@@ -6497,8 +6789,25 @@ function RichTextWysiwygEditor({ block, onChange }) {
|
|
|
6497
6789
|
},
|
|
6498
6790
|
[onChange]
|
|
6499
6791
|
);
|
|
6792
|
+
const mentions = useMentions();
|
|
6793
|
+
const mentionsEnabled = mentions !== null;
|
|
6794
|
+
const mentionsRef = useRef(mentions);
|
|
6795
|
+
useEffect(() => {
|
|
6796
|
+
mentionsRef.current = mentions;
|
|
6797
|
+
}, [mentions]);
|
|
6798
|
+
const [mentionPicker, setMentionPicker] = useState(null);
|
|
6799
|
+
const mentionKeyHandlerRef = useRef(() => false);
|
|
6500
6800
|
const editor = useEditor({
|
|
6501
|
-
extensions: buildRichTextExtensions(
|
|
6801
|
+
extensions: buildRichTextExtensions({
|
|
6802
|
+
isEnabled: () => mentionsRef.current !== null,
|
|
6803
|
+
search: async (query, signal) => await mentionsRef.current?.searchUsers(query, { signal }) ?? [],
|
|
6804
|
+
render: () => ({
|
|
6805
|
+
onStart: (props) => setMentionPicker((prev) => nextMentionPickerState(props, prev)),
|
|
6806
|
+
onUpdate: (props) => setMentionPicker((prev) => nextMentionPickerState(props, prev)),
|
|
6807
|
+
onExit: () => setMentionPicker(null),
|
|
6808
|
+
onKeyDown: ({ event }) => mentionKeyHandlerRef.current(event)
|
|
6809
|
+
})
|
|
6810
|
+
}),
|
|
6502
6811
|
enableInputRules: RICH_TEXT_INPUT_RULE_ALLOWLIST,
|
|
6503
6812
|
content: richTextToProseMirror(block, resolveEmoji),
|
|
6504
6813
|
editorProps: {
|
|
@@ -6508,10 +6817,16 @@ function RichTextWysiwygEditor({ block, onChange }) {
|
|
|
6508
6817
|
},
|
|
6509
6818
|
onUpdate: ({ editor: ed }) => handleUpdate(ed)
|
|
6510
6819
|
});
|
|
6820
|
+
const seenBlockRef = useRef(block);
|
|
6511
6821
|
useEffect(() => {
|
|
6512
6822
|
if (!editor) {
|
|
6513
6823
|
return;
|
|
6514
6824
|
}
|
|
6825
|
+
const blockChanged = seenBlockRef.current !== block;
|
|
6826
|
+
seenBlockRef.current = block;
|
|
6827
|
+
if (!blockChanged && lastEmittedRef.current) {
|
|
6828
|
+
return;
|
|
6829
|
+
}
|
|
6515
6830
|
if (lastEmittedRef.current && JSON.stringify(lastEmittedRef.current) === JSON.stringify(block)) {
|
|
6516
6831
|
return;
|
|
6517
6832
|
}
|
|
@@ -6523,11 +6838,26 @@ function RichTextWysiwygEditor({ block, onChange }) {
|
|
|
6523
6838
|
return /* @__PURE__ */ jsx("div", { className: "min-h-[120px] rounded-md border border-input bg-background" });
|
|
6524
6839
|
}
|
|
6525
6840
|
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
6526
|
-
/* @__PURE__ */ jsx(
|
|
6527
|
-
|
|
6841
|
+
/* @__PURE__ */ jsx(
|
|
6842
|
+
Toolbar,
|
|
6843
|
+
{
|
|
6844
|
+
editor,
|
|
6845
|
+
mention: mentionsEnabled ? { active: mentionPicker !== null, insert: () => insertMentionTrigger(editor) } : null
|
|
6846
|
+
}
|
|
6847
|
+
),
|
|
6848
|
+
/* @__PURE__ */ jsx(EditorContent, { editor }),
|
|
6849
|
+
mentionPicker && /* @__PURE__ */ jsx(
|
|
6850
|
+
MentionPicker,
|
|
6851
|
+
{
|
|
6852
|
+
editor,
|
|
6853
|
+
state: mentionPicker,
|
|
6854
|
+
keyHandlerRef: mentionKeyHandlerRef,
|
|
6855
|
+
onDismiss: () => dismissMentionSuggestion(editor)
|
|
6856
|
+
}
|
|
6857
|
+
)
|
|
6528
6858
|
] });
|
|
6529
6859
|
}
|
|
6530
|
-
function Toolbar({ editor }) {
|
|
6860
|
+
function Toolbar({ editor, mention }) {
|
|
6531
6861
|
return /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-0.5 rounded-md border bg-muted/30 p-1", children: [
|
|
6532
6862
|
/* @__PURE__ */ jsx(
|
|
6533
6863
|
ToolbarButton,
|
|
@@ -6586,6 +6916,7 @@ function Toolbar({ editor }) {
|
|
|
6586
6916
|
}).run()
|
|
6587
6917
|
}
|
|
6588
6918
|
),
|
|
6919
|
+
mention && /* @__PURE__ */ jsx(ToolbarButton, { label: "Mention", active: mention.active, onClick: mention.insert, Icon: AtSign }),
|
|
6589
6920
|
/* @__PURE__ */ jsx(Divider, {}),
|
|
6590
6921
|
/* @__PURE__ */ jsx(
|
|
6591
6922
|
ToolbarButton,
|
|
@@ -10922,6 +11253,7 @@ function BlockKitchen(props) {
|
|
|
10922
11253
|
onValidationChange,
|
|
10923
11254
|
previewHooks,
|
|
10924
11255
|
customEmojis,
|
|
11256
|
+
mentions,
|
|
10925
11257
|
loadChannels,
|
|
10926
11258
|
loadSendAsUserStatus,
|
|
10927
11259
|
onSend,
|
|
@@ -11282,7 +11614,7 @@ function BlockKitchen(props) {
|
|
|
11282
11614
|
}
|
|
11283
11615
|
)
|
|
11284
11616
|
] }),
|
|
11285
|
-
/* @__PURE__ */ jsx(
|
|
11617
|
+
/* @__PURE__ */ jsx(MentionsProvider, { mentions, children: /* @__PURE__ */ jsx(
|
|
11286
11618
|
Surface,
|
|
11287
11619
|
{
|
|
11288
11620
|
blocks,
|
|
@@ -11302,7 +11634,7 @@ function BlockKitchen(props) {
|
|
|
11302
11634
|
isPaletteDrag: activePaletteVariant !== null,
|
|
11303
11635
|
onOpenPalette: () => setPaletteOpen(true)
|
|
11304
11636
|
}
|
|
11305
|
-
)
|
|
11637
|
+
) })
|
|
11306
11638
|
] })
|
|
11307
11639
|
]
|
|
11308
11640
|
}
|