@tensor_1/react 2.39.0 → 2.40.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -3220,12 +3220,19 @@ interface RelatedLinksProps extends Omit<HTMLAttributes<HTMLElement>, 'title' |
3220
3220
  declare function RelatedLinks({ title, items, className, ...props }: RelatedLinksProps): react.JSX.Element;
3221
3221
 
3222
3222
  interface ToggleRowProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onChange'> {
3223
+ /** The row's visible label. It is the switch's `<label>`: clicking it flips the switch, and it names the switch. */
3223
3224
  label: ReactNode;
3225
+ /** Shown under the label and announced as the switch's description (`aria-describedby`). */
3224
3226
  description?: ReactNode;
3225
3227
  checked: boolean;
3226
3228
  onCheckedChange: (checked: boolean) => void;
3227
3229
  disabled?: boolean;
3228
- accessibleLabel: string;
3230
+ /**
3231
+ * The switch's name when the visible label is not text (an icon, an image).
3232
+ * Leave it out for a text label: the label already names the switch, and a
3233
+ * repeated `aria-label` would replace it.
3234
+ */
3235
+ accessibleLabel?: string;
3229
3236
  }
3230
3237
  declare function ToggleRow({ label, description, checked, onCheckedChange, disabled, accessibleLabel, className, ...props }: ToggleRowProps): react.JSX.Element;
3231
3238
 
@@ -4454,7 +4461,10 @@ interface CommentsProps extends HTMLAttributes<HTMLDivElement> {
4454
4461
  */
4455
4462
  declare function Comments({ children, className, ...props }: CommentsProps): react.JSX.Element;
4456
4463
  interface CommentProps extends HTMLAttributes<HTMLDivElement> {
4457
- /** Usually an `<Avatar />` (the grid reserves the leading column). */
4464
+ /**
4465
+ * Usually an `<Avatar />` (the grid reserves the leading column). `false` drops the
4466
+ * column, for a comment that sits on a rail which already marks each row (a timeline).
4467
+ */
4458
4468
  avatar?: ReactNode;
4459
4469
  author?: ReactNode;
4460
4470
  meta?: ReactNode;
@@ -0,0 +1,110 @@
1
+ type MarkdownInline = {
2
+ type: 'text';
3
+ value: string;
4
+ } | {
5
+ type: 'code';
6
+ value: string;
7
+ } | {
8
+ type: 'strong';
9
+ children: MarkdownInline[];
10
+ } | {
11
+ type: 'em';
12
+ children: MarkdownInline[];
13
+ } | {
14
+ type: 'del';
15
+ children: MarkdownInline[];
16
+ } | {
17
+ type: 'link';
18
+ href: string;
19
+ title?: string;
20
+ children: MarkdownInline[];
21
+ bare?: boolean;
22
+ } | {
23
+ type: 'image';
24
+ src: string;
25
+ alt: string;
26
+ title?: string;
27
+ }
28
+ /** `@[Display name](mention:<id>)` — a person mentioned by id; `label` is the stored name. */
29
+ | {
30
+ type: 'mention';
31
+ id: string;
32
+ label: string;
33
+ } | {
34
+ type: 'br';
35
+ };
36
+ /**
37
+ * The one stored form of a mention: `@[Display name](mention:<id>)`. The id is the
38
+ * product's (a user id); the name is what the author saw, kept so the text still reads
39
+ * without the product. `[`, `]` and `\` in the name are backslash-escaped. Ids are
40
+ * limited to letters, digits and `_ . -`, so a mention can never carry a URL.
41
+ */
42
+ declare const MENTION_SCHEME = "mention:";
43
+ /** True when `id` can be stored in a mention. */
44
+ declare function isMentionId(id: string): boolean;
45
+ /** The markdown for one mention, or `null` when the id cannot be stored or the name is empty. */
46
+ declare function formatMention(mention: {
47
+ id: string;
48
+ label: string;
49
+ }): string | null;
50
+ /**
51
+ * The mention at the start of `text`, with the number of characters it spans, or `null`.
52
+ * Used by the viewer, the editor and products that read stored text on the server.
53
+ */
54
+ declare function readMention(text: string): {
55
+ id: string;
56
+ label: string;
57
+ length: number;
58
+ } | null;
59
+ type TableAlign = 'left' | 'center' | 'right' | null;
60
+ interface MarkdownListItem {
61
+ /** `null` for a plain item, otherwise the task state. */
62
+ checked: boolean | null;
63
+ children: MarkdownInline[];
64
+ /** Nested lists. */
65
+ lists: MarkdownList[];
66
+ }
67
+ interface MarkdownList {
68
+ type: 'list';
69
+ ordered: boolean;
70
+ start: number;
71
+ items: MarkdownListItem[];
72
+ }
73
+ type MarkdownBlock = {
74
+ type: 'paragraph';
75
+ children: MarkdownInline[];
76
+ } | {
77
+ type: 'heading';
78
+ level: number;
79
+ children: MarkdownInline[];
80
+ } | {
81
+ type: 'code';
82
+ lang: string;
83
+ value: string;
84
+ } | {
85
+ type: 'quote';
86
+ children: MarkdownInline[];
87
+ } | {
88
+ type: 'rule';
89
+ } | MarkdownList | {
90
+ type: 'table';
91
+ align: TableAlign[];
92
+ head: MarkdownInline[][];
93
+ rows: MarkdownInline[][][];
94
+ };
95
+ /** Inline markdown to nodes. Newlines become line breaks. */
96
+ declare function parseInline(text: string, depth?: number): MarkdownInline[];
97
+ /** The text a reader sees, for accessible names and image alt text. */
98
+ declare function plainText(nodes: ReadonlyArray<MarkdownInline>): string;
99
+ /**
100
+ * Every mention in a markdown text, in reading order (an id may repeat). Code spans and
101
+ * code blocks hold no mentions. For products that act on mentions when text is saved.
102
+ */
103
+ declare function findMentions(markdown: string): Array<{
104
+ id: string;
105
+ label: string;
106
+ }>;
107
+ /** Markdown source to blocks. Empty or whitespace-only input yields no blocks. */
108
+ declare function parseMarkdown(source: string): MarkdownBlock[];
109
+
110
+ export { MENTION_SCHEME as M, type TableAlign as T, type MarkdownBlock as a, type MarkdownInline as b, type MarkdownList as c, type MarkdownListItem as d, formatMention as e, findMentions as f, parseMarkdown as g, plainText as h, isMentionId as i, parseInline as p, readMention as r };
@@ -24,9 +24,38 @@ type MarkdownInline = {
24
24
  src: string;
25
25
  alt: string;
26
26
  title?: string;
27
+ }
28
+ /** `@[Display name](mention:<id>)` — a person mentioned by id; `label` is the stored name. */
29
+ | {
30
+ type: 'mention';
31
+ id: string;
32
+ label: string;
27
33
  } | {
28
34
  type: 'br';
29
35
  };
36
+ /**
37
+ * The one stored form of a mention: `@[Display name](mention:<id>)`. The id is the
38
+ * product's (a user id); the name is what the author saw, kept so the text still reads
39
+ * without the product. `[`, `]` and `\` in the name are backslash-escaped. Ids are
40
+ * limited to letters, digits and `_ . -`, so a mention can never carry a URL.
41
+ */
42
+ declare const MENTION_SCHEME = "mention:";
43
+ /** True when `id` can be stored in a mention. */
44
+ declare function isMentionId(id: string): boolean;
45
+ /** The markdown for one mention, or `null` when the id cannot be stored or the name is empty. */
46
+ declare function formatMention(mention: {
47
+ id: string;
48
+ label: string;
49
+ }): string | null;
50
+ /**
51
+ * The mention at the start of `text`, with the number of characters it spans, or `null`.
52
+ * Used by the viewer, the editor and products that read stored text on the server.
53
+ */
54
+ declare function readMention(text: string): {
55
+ id: string;
56
+ label: string;
57
+ length: number;
58
+ } | null;
30
59
  type TableAlign = 'left' | 'center' | 'right' | null;
31
60
  interface MarkdownListItem {
32
61
  /** `null` for a plain item, otherwise the task state. */
@@ -67,9 +96,17 @@ type MarkdownBlock = {
67
96
  declare function parseInline(text: string, depth?: number): MarkdownInline[];
68
97
  /** The text a reader sees, for accessible names and image alt text. */
69
98
  declare function plainText(nodes: ReadonlyArray<MarkdownInline>): string;
99
+ /**
100
+ * Every mention in a markdown text, in reading order (an id may repeat). Code spans and
101
+ * code blocks hold no mentions. For products that act on mentions when text is saved.
102
+ */
103
+ declare function findMentions(markdown: string): Array<{
104
+ id: string;
105
+ label: string;
106
+ }>;
70
107
  /** Split a table row on unescaped pipes outside code spans. */
71
108
  declare function splitTableRow(line: string): string[];
72
109
  /** Markdown source to blocks. Empty or whitespace-only input yields no blocks. */
73
110
  declare function parseMarkdown(source: string): MarkdownBlock[];
74
111
 
75
- export { type MarkdownBlock, type MarkdownInline, type MarkdownList, type MarkdownListItem, type TableAlign, parseInline, parseMarkdown, plainText, splitTableRow };
112
+ export { MENTION_SCHEME, type MarkdownBlock, type MarkdownInline, type MarkdownList, type MarkdownListItem, type TableAlign, findMentions, formatMention, isMentionId, parseInline, parseMarkdown, plainText, readMention, splitTableRow };
@@ -1,3 +1,21 @@
1
+ const MENTION_SCHEME = "mention:";
2
+ const MENTION_ID = /^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$/;
3
+ const MENTION_RE = /^@\[((?:\\[\\[\]]|[^\\[\]\n]){1,200})\]\(mention:([A-Za-z0-9][A-Za-z0-9_.-]{0,127})\)/;
4
+ const cleanMentionLabel = (label) => label.replace(/\s+/gu, " ").trim().slice(0, 200);
5
+ function isMentionId(id) {
6
+ return MENTION_ID.test(id);
7
+ }
8
+ function formatMention(mention) {
9
+ const label = cleanMentionLabel(mention.label);
10
+ if (!isMentionId(mention.id) || !label) return null;
11
+ return `@[${label.replace(/[\\[\]]/g, (ch) => `\\${ch}`)}](${MENTION_SCHEME}${mention.id})`;
12
+ }
13
+ function readMention(text) {
14
+ const match = MENTION_RE.exec(text);
15
+ if (!match) return null;
16
+ const label = cleanMentionLabel(match[1].replace(/\\([\\[\]])/g, "$1"));
17
+ return label ? { id: match[2], label, length: match[0].length } : null;
18
+ }
1
19
  const ASCII_PUNCT = /[!-/:-@[-`{-~]/;
2
20
  const WORD = /[\p{L}\p{N}]/u;
3
21
  const NAMED_ENTITIES = {
@@ -152,6 +170,14 @@ function parseInline(text, depth = 0) {
152
170
  i += run.length;
153
171
  continue;
154
172
  }
173
+ if (ch === "@" && text[i + 1] === "[") {
174
+ const mention = readMention(rest);
175
+ if (mention) {
176
+ out.push({ type: "mention", id: mention.id, label: mention.label });
177
+ i += mention.length;
178
+ continue;
179
+ }
180
+ }
155
181
  if (ch === "!" && text[i + 1] === "[") {
156
182
  const end = findBracketEnd(text, i + 1);
157
183
  const dest = end > 0 ? readDestination(text, end + 1) : null;
@@ -246,11 +272,47 @@ function plainText(nodes) {
246
272
  return " ";
247
273
  case "image":
248
274
  return n.alt;
275
+ case "mention":
276
+ return `@${n.label}`;
249
277
  default:
250
278
  return plainText(n.children);
251
279
  }
252
280
  }).join("");
253
281
  }
282
+ const collectMentions = (nodes, out) => {
283
+ for (const n of nodes) {
284
+ if (n.type === "mention") out.push({ id: n.id, label: n.label });
285
+ else if ("children" in n) collectMentions(n.children, out);
286
+ }
287
+ };
288
+ function findMentions(markdown) {
289
+ const out = [];
290
+ const walkList = (list) => {
291
+ for (const item of list.items) {
292
+ collectMentions(item.children, out);
293
+ item.lists.forEach(walkList);
294
+ }
295
+ };
296
+ for (const block of parseMarkdown(markdown)) {
297
+ switch (block.type) {
298
+ case "paragraph":
299
+ case "heading":
300
+ case "quote":
301
+ collectMentions(block.children, out);
302
+ break;
303
+ case "list":
304
+ walkList(block);
305
+ break;
306
+ case "table":
307
+ block.head.forEach((cell) => collectMentions(cell, out));
308
+ block.rows.forEach((row) => row.forEach((cell) => collectMentions(cell, out)));
309
+ break;
310
+ default:
311
+ break;
312
+ }
313
+ }
314
+ return out;
315
+ }
254
316
  const FENCE_RE = /^(`{3,}|~{3,})\s*([^`\s]*)/;
255
317
  const HEADING_RE = /^(#{1,6})\s+(.*?)(?:\s+#+)?$/;
256
318
  const RULE_RE = /^(?:(?:\*\s*){3,}|(?:-\s*){3,}|(?:_\s*){3,})$/;
@@ -431,8 +493,13 @@ ${current.trim()}`;
431
493
  return blocks;
432
494
  }
433
495
  export {
496
+ MENTION_SCHEME,
497
+ findMentions,
498
+ formatMention,
499
+ isMentionId,
434
500
  parseInline,
435
501
  parseMarkdown,
436
502
  plainText,
503
+ readMention,
437
504
  splitTableRow
438
505
  };
package/dist/markdown.cjs CHANGED
@@ -20,11 +20,16 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/markdown.ts
21
21
  var markdown_exports = {};
22
22
  __export(markdown_exports, {
23
+ MENTION_SCHEME: () => MENTION_SCHEME,
23
24
  Markdown: () => Markdown,
24
25
  defaultMarkdownIsSafeUrl: () => defaultMarkdownIsSafeUrl,
26
+ findMentions: () => findMentions,
27
+ formatMention: () => formatMention,
28
+ isMentionId: () => isMentionId,
25
29
  parseInline: () => parseInline,
26
30
  parseMarkdown: () => parseMarkdown,
27
- plainText: () => plainText
31
+ plainText: () => plainText,
32
+ readMention: () => readMention
28
33
  });
29
34
  module.exports = __toCommonJS(markdown_exports);
30
35
 
@@ -34,6 +39,24 @@ function cx(...args) {
34
39
  }
35
40
 
36
41
  // src/markdown-model.ts
42
+ var MENTION_SCHEME = "mention:";
43
+ var MENTION_ID = /^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$/;
44
+ var MENTION_RE = /^@\[((?:\\[\\[\]]|[^\\[\]\n]){1,200})\]\(mention:([A-Za-z0-9][A-Za-z0-9_.-]{0,127})\)/;
45
+ var cleanMentionLabel = (label) => label.replace(/\s+/gu, " ").trim().slice(0, 200);
46
+ function isMentionId(id) {
47
+ return MENTION_ID.test(id);
48
+ }
49
+ function formatMention(mention) {
50
+ const label = cleanMentionLabel(mention.label);
51
+ if (!isMentionId(mention.id) || !label) return null;
52
+ return `@[${label.replace(/[\\[\]]/g, (ch) => `\\${ch}`)}](${MENTION_SCHEME}${mention.id})`;
53
+ }
54
+ function readMention(text) {
55
+ const match = MENTION_RE.exec(text);
56
+ if (!match) return null;
57
+ const label = cleanMentionLabel(match[1].replace(/\\([\\[\]])/g, "$1"));
58
+ return label ? { id: match[2], label, length: match[0].length } : null;
59
+ }
37
60
  var ASCII_PUNCT = /[!-/:-@[-`{-~]/;
38
61
  var WORD = /[\p{L}\p{N}]/u;
39
62
  var NAMED_ENTITIES = {
@@ -188,6 +211,14 @@ function parseInline(text, depth = 0) {
188
211
  i += run.length;
189
212
  continue;
190
213
  }
214
+ if (ch === "@" && text[i + 1] === "[") {
215
+ const mention = readMention(rest);
216
+ if (mention) {
217
+ out.push({ type: "mention", id: mention.id, label: mention.label });
218
+ i += mention.length;
219
+ continue;
220
+ }
221
+ }
191
222
  if (ch === "!" && text[i + 1] === "[") {
192
223
  const end = findBracketEnd(text, i + 1);
193
224
  const dest = end > 0 ? readDestination(text, end + 1) : null;
@@ -282,11 +313,47 @@ function plainText(nodes) {
282
313
  return " ";
283
314
  case "image":
284
315
  return n.alt;
316
+ case "mention":
317
+ return `@${n.label}`;
285
318
  default:
286
319
  return plainText(n.children);
287
320
  }
288
321
  }).join("");
289
322
  }
323
+ var collectMentions = (nodes, out) => {
324
+ for (const n of nodes) {
325
+ if (n.type === "mention") out.push({ id: n.id, label: n.label });
326
+ else if ("children" in n) collectMentions(n.children, out);
327
+ }
328
+ };
329
+ function findMentions(markdown) {
330
+ const out = [];
331
+ const walkList = (list) => {
332
+ for (const item of list.items) {
333
+ collectMentions(item.children, out);
334
+ item.lists.forEach(walkList);
335
+ }
336
+ };
337
+ for (const block of parseMarkdown(markdown)) {
338
+ switch (block.type) {
339
+ case "paragraph":
340
+ case "heading":
341
+ case "quote":
342
+ collectMentions(block.children, out);
343
+ break;
344
+ case "list":
345
+ walkList(block);
346
+ break;
347
+ case "table":
348
+ block.head.forEach((cell) => collectMentions(cell, out));
349
+ block.rows.forEach((row) => row.forEach((cell) => collectMentions(cell, out)));
350
+ break;
351
+ default:
352
+ break;
353
+ }
354
+ }
355
+ return out;
356
+ }
290
357
  var FENCE_RE = /^(`{3,}|~{3,})\s*([^`\s]*)/;
291
358
  var HEADING_RE = /^(#{1,6})\s+(.*?)(?:\s+#+)?$/;
292
359
  var RULE_RE = /^(?:(?:\*\s*){3,}|(?:-\s*){3,}|(?:_\s*){3,})$/;
@@ -510,6 +577,13 @@ function renderInlines(nodes, policy) {
510
577
  i
511
578
  );
512
579
  }
580
+ case "mention": {
581
+ const name = policy.resolveMention?.(node.id, node.label) ?? node.label;
582
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "uix-mention", "data-mention-id": node.id, children: [
583
+ "@",
584
+ name
585
+ ] }, i);
586
+ }
513
587
  case "image": {
514
588
  const resolved = policy.resolveImageSrc?.(node.src) ?? null;
515
589
  if (resolved) {
@@ -568,18 +642,30 @@ function renderBlock(block, policy, headingOffset, key) {
568
642
  );
569
643
  }
570
644
  }
571
- function Markdown({ children, className, isSafeUrl = defaultMarkdownIsSafeUrl, resolveImageSrc, headingOffset = 2 }) {
645
+ function Markdown({
646
+ children,
647
+ className,
648
+ isSafeUrl = defaultMarkdownIsSafeUrl,
649
+ resolveImageSrc,
650
+ headingOffset = 2,
651
+ resolveMention
652
+ }) {
572
653
  const source = typeof children === "string" ? children : "";
573
654
  if (source.trim() === "") return null;
574
- const policy = { isSafeUrl, resolveImageSrc };
655
+ const policy = { isSafeUrl, resolveImageSrc, resolveMention };
575
656
  const blocks = parseMarkdown(source);
576
657
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: cx("uix-prose", "uix-markdown", className), "data-component": "markdown", children: blocks.map((block, i) => renderBlock(block, policy, headingOffset, i)) });
577
658
  }
578
659
  // Annotate the CommonJS export names for ESM import in node:
579
660
  0 && (module.exports = {
661
+ MENTION_SCHEME,
580
662
  Markdown,
581
663
  defaultMarkdownIsSafeUrl,
664
+ findMentions,
665
+ formatMention,
666
+ isMentionId,
582
667
  parseInline,
583
668
  parseMarkdown,
584
- plainText
669
+ plainText,
670
+ readMention
585
671
  });
@@ -1,4 +1,5 @@
1
1
  import * as react from 'react';
2
+ export { M as MENTION_SCHEME, a as MarkdownBlock, b as MarkdownInline, c as MarkdownList, d as MarkdownListItem, T as TableAlign, f as findMentions, e as formatMention, i as isMentionId, p as parseInline, g as parseMarkdown, h as plainText, r as readMention } from './markdown-model-Cuwi6pIk.cjs';
2
3
 
3
4
  /**
4
5
  * Allows http(s), mailto and same-app references (`/path`, `#anchor`, `./x`, `../x`).
@@ -26,6 +27,12 @@ interface MarkdownProps {
26
27
  * (`#` → `<h3>` by default, as in TENSOR's original renderer). Capped at `<h6>`.
27
28
  */
28
29
  headingOffset?: number;
30
+ /**
31
+ * The name to show for a mention (`@[Name](mention:<id>)`), e.g. the person's current
32
+ * or masked name. Return `undefined` to show the name stored in the text. Without it
33
+ * every mention shows its stored name.
34
+ */
35
+ resolveMention?: (id: string, storedLabel: string) => string | undefined;
29
36
  }
30
37
  /**
31
38
  * Safe markdown viewer over `.uix-prose`. It never produces an HTML string and never
@@ -35,81 +42,10 @@ interface MarkdownProps {
35
42
  * Supports headings, paragraphs (single newline = line break), bold, italic,
36
43
  * strikethrough, inline and fenced code, links and bare http(s) URLs, blockquotes,
37
44
  * horizontal rules, nested bulleted/numbered lists, read-only task lists, GFM tables
38
- * (in a keyboard-scrollable wrapper) and images gated by `resolveImageSrc`.
45
+ * (in a keyboard-scrollable wrapper), images gated by `resolveImageSrc` and mentions
46
+ * (`@[Name](mention:<id>)`, drawn as `.uix-mention`, named by `resolveMention`).
39
47
  * Server-component safe.
40
48
  */
41
- declare function Markdown({ children, className, isSafeUrl, resolveImageSrc, headingOffset }: MarkdownProps): react.JSX.Element | null;
42
-
43
- type MarkdownInline = {
44
- type: 'text';
45
- value: string;
46
- } | {
47
- type: 'code';
48
- value: string;
49
- } | {
50
- type: 'strong';
51
- children: MarkdownInline[];
52
- } | {
53
- type: 'em';
54
- children: MarkdownInline[];
55
- } | {
56
- type: 'del';
57
- children: MarkdownInline[];
58
- } | {
59
- type: 'link';
60
- href: string;
61
- title?: string;
62
- children: MarkdownInline[];
63
- bare?: boolean;
64
- } | {
65
- type: 'image';
66
- src: string;
67
- alt: string;
68
- title?: string;
69
- } | {
70
- type: 'br';
71
- };
72
- type TableAlign = 'left' | 'center' | 'right' | null;
73
- interface MarkdownListItem {
74
- /** `null` for a plain item, otherwise the task state. */
75
- checked: boolean | null;
76
- children: MarkdownInline[];
77
- /** Nested lists. */
78
- lists: MarkdownList[];
79
- }
80
- interface MarkdownList {
81
- type: 'list';
82
- ordered: boolean;
83
- start: number;
84
- items: MarkdownListItem[];
85
- }
86
- type MarkdownBlock = {
87
- type: 'paragraph';
88
- children: MarkdownInline[];
89
- } | {
90
- type: 'heading';
91
- level: number;
92
- children: MarkdownInline[];
93
- } | {
94
- type: 'code';
95
- lang: string;
96
- value: string;
97
- } | {
98
- type: 'quote';
99
- children: MarkdownInline[];
100
- } | {
101
- type: 'rule';
102
- } | MarkdownList | {
103
- type: 'table';
104
- align: TableAlign[];
105
- head: MarkdownInline[][];
106
- rows: MarkdownInline[][][];
107
- };
108
- /** Inline markdown to nodes. Newlines become line breaks. */
109
- declare function parseInline(text: string, depth?: number): MarkdownInline[];
110
- /** The text a reader sees, for accessible names and image alt text. */
111
- declare function plainText(nodes: ReadonlyArray<MarkdownInline>): string;
112
- /** Markdown source to blocks. Empty or whitespace-only input yields no blocks. */
113
- declare function parseMarkdown(source: string): MarkdownBlock[];
49
+ declare function Markdown({ children, className, isSafeUrl, resolveImageSrc, headingOffset, resolveMention, }: MarkdownProps): react.JSX.Element | null;
114
50
 
115
- export { Markdown, type MarkdownBlock, type MarkdownInline, type MarkdownList, type MarkdownListItem, type MarkdownProps, type TableAlign, defaultMarkdownIsSafeUrl, parseInline, parseMarkdown, plainText };
51
+ export { Markdown, type MarkdownProps, defaultMarkdownIsSafeUrl };
@@ -1,4 +1,4 @@
1
1
  export { Markdown, MarkdownProps, defaultMarkdownIsSafeUrl } from './components/Markdown.js';
2
- export { MarkdownBlock, MarkdownInline, MarkdownList, MarkdownListItem, TableAlign, parseInline, parseMarkdown, plainText } from './markdown-model.js';
2
+ export { MENTION_SCHEME, MarkdownBlock, MarkdownInline, MarkdownList, MarkdownListItem, TableAlign, findMentions, formatMention, isMentionId, parseInline, parseMarkdown, plainText, readMention } from './markdown-model.js';
3
3
  import 'react';
4
4
  import './url-policy.js';
package/dist/markdown.js CHANGED
@@ -1,9 +1,23 @@
1
1
  import { Markdown, defaultMarkdownIsSafeUrl } from "./components/Markdown.js";
2
- import { parseMarkdown, parseInline, plainText } from "./markdown-model.js";
2
+ import {
3
+ parseMarkdown,
4
+ parseInline,
5
+ plainText,
6
+ findMentions,
7
+ formatMention,
8
+ readMention,
9
+ isMentionId,
10
+ MENTION_SCHEME
11
+ } from "./markdown-model.js";
3
12
  export {
13
+ MENTION_SCHEME,
4
14
  Markdown,
5
15
  defaultMarkdownIsSafeUrl,
16
+ findMentions,
17
+ formatMention,
18
+ isMentionId,
6
19
  parseInline,
7
20
  parseMarkdown,
8
- plainText
21
+ plainText,
22
+ readMention
9
23
  };
@@ -0,0 +1,14 @@
1
+ import { Node } from '@tiptap/core';
2
+
3
+ /** A person the consumer offers in the `@` listbox. */
4
+ interface MentionCandidate {
5
+ /** Stored in the text; letters, digits and `_ . -` only (see `isMentionId`). */
6
+ id: string;
7
+ /** The name inserted and shown. */
8
+ label: string;
9
+ /** A second line in the listbox (e.g. team or e-mail), never stored. */
10
+ description?: string;
11
+ }
12
+ declare const Mention: Node<any, any>;
13
+
14
+ export { Mention, type MentionCandidate };
@@ -0,0 +1,57 @@
1
+ import { mergeAttributes, Node } from "@tiptap/core";
2
+ import { formatMention, readMention } from "../markdown-model.js";
3
+ const Mention = Node.create({
4
+ name: "mention",
5
+ group: "inline",
6
+ inline: true,
7
+ atom: true,
8
+ selectable: false,
9
+ addAttributes() {
10
+ return {
11
+ id: { default: null },
12
+ label: { default: null }
13
+ };
14
+ },
15
+ parseHTML() {
16
+ return [{
17
+ tag: "span[data-mention-id]",
18
+ getAttrs: (el) => {
19
+ const node = el;
20
+ const id = node.getAttribute("data-mention-id") ?? "";
21
+ const label = node.getAttribute("data-mention-label") ?? node.textContent?.replace(/^@/, "") ?? "";
22
+ return formatMention({ id, label }) ? { id, label } : false;
23
+ }
24
+ }];
25
+ },
26
+ renderHTML({ node, HTMLAttributes }) {
27
+ const label = String(node.attrs.label ?? "");
28
+ return [
29
+ "span",
30
+ mergeAttributes(HTMLAttributes, {
31
+ class: "uix-mention",
32
+ "data-mention-id": String(node.attrs.id ?? ""),
33
+ "data-mention-label": label,
34
+ contenteditable: "false"
35
+ }),
36
+ `@${label}`
37
+ ];
38
+ },
39
+ renderText({ node }) {
40
+ return `@${String(node.attrs.label ?? "")}`;
41
+ },
42
+ markdownTokenizer: {
43
+ name: "mention",
44
+ level: "inline",
45
+ start: (src) => src.indexOf("@["),
46
+ tokenize: (src) => {
47
+ const found = readMention(src);
48
+ if (!found) return void 0;
49
+ return { type: "mention", raw: src.slice(0, found.length), id: found.id, label: found.label };
50
+ }
51
+ },
52
+ parseMarkdown: (token, helpers) => helpers.createNode("mention", { id: String(token.id ?? ""), label: String(token.label ?? "") }),
53
+ renderMarkdown: (node) => formatMention({ id: String(node.attrs?.id ?? ""), label: String(node.attrs?.label ?? "") }) ?? `@${String(node.attrs?.label ?? "")}`
54
+ });
55
+ export {
56
+ Mention
57
+ };
@@ -6,6 +6,7 @@ import { Image } from "@tiptap/extension-image";
6
6
  import { StarterKit } from "@tiptap/starter-kit";
7
7
  import { Marked } from "marked";
8
8
  import { defaultIsSafeUrl } from "../url-policy.js";
9
+ import { Mention } from "./mention.js";
9
10
  const indentContinuation = (h) => {
10
11
  let first = true;
11
12
  return {
@@ -60,7 +61,9 @@ function createSchemaExtensions(options = {}) {
60
61
  TaskList,
61
62
  UixTaskItem.configure({ nested: true }),
62
63
  TableKit.configure({ table: { resizable: false } }),
63
- options.image ?? Image.configure({ inline: true, allowBase64: false })
64
+ options.image ?? Image.configure({ inline: true, allowBase64: false }),
65
+ // HAR-1599: `@[Name](mention:<id>)` is one token in every editor and headless pipeline.
66
+ Mention
64
67
  ];
65
68
  }
66
69
  const ENTITY_AFTER_AMP = /^(?:#\d+;|#x[\da-f]+;|[a-z][a-z\d]*;)/i;