@nectary/components 5.9.1 → 5.10.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nectary/components",
3
- "version": "5.9.1",
3
+ "version": "5.10.1",
4
4
  "files": [
5
5
  "**/*/*.css",
6
6
  "**/*/*.json",
@@ -24,7 +24,7 @@
24
24
  },
25
25
  "dependencies": {
26
26
  "@babel/runtime": "^7.22.15",
27
- "@nectary/assets": "3.3.1"
27
+ "@nectary/assets": "3.3.2"
28
28
  },
29
29
  "devDependencies": {
30
30
  "@babel/cli": "^7.22.15",
@@ -40,6 +40,6 @@
40
40
  "vite": "^7.0.6"
41
41
  },
42
42
  "peerDependencies": {
43
- "@nectary/theme-base": "1.7.1"
43
+ "@nectary/theme-base": "1.8.0"
44
44
  }
45
45
  }
@@ -1,6 +1,7 @@
1
1
  import '../emoji';
2
2
  import '../code-tag';
3
3
  import '../link';
4
+ import '../tag';
4
5
  import { NectaryElement } from '../utils';
5
6
  import type { TSinchTextType } from '../text/types';
6
7
  export * from './types';
@@ -2,6 +2,7 @@ import { getEmojiBaseUrl } from "../emoji/utils.js";
2
2
  import "../emoji/index.js";
3
3
  import "../code-tag/index.js";
4
4
  import "../link/index.js";
5
+ import "../tag/index.js";
5
6
  import { getLiteralAttribute, updateLiteralAttribute, getAttribute, updateAttribute } from "../utils/dom.js";
6
7
  import { defineCustomElement, NectaryElement } from "../utils/element.js";
7
8
  import { getReactEventHandler } from "../utils/get-react-event-handler.js";
@@ -34,6 +34,11 @@ const createParseVisitor = (doc) => {
34
34
  $codeTag.text = text;
35
35
  $p.appendChild($codeTag);
36
36
  },
37
+ tag(text) {
38
+ const $tag = doc.createElement("sinch-tag");
39
+ $tag.text = text;
40
+ $p.appendChild($tag);
41
+ },
37
42
  inline(text, { isBold, isItalic, isStrikethrough }) {
38
43
  const $inline = doc.createElement("SPAN");
39
44
  $inline.append(doc.createTextNode(text));
@@ -20,6 +20,7 @@ export declare class RichTextarea extends NectaryElement {
20
20
  blur(): void;
21
21
  insertText(value: string): void;
22
22
  insertLink(text: string, href: string): void;
23
+ insertMention(username: string): void;
23
24
  formatItalic(): void;
24
25
  formatBold(): void;
25
26
  formatStrikethrough(): void;
@@ -4,8 +4,8 @@ import { defineCustomElement, NectaryElement } from "../utils/element.js";
4
4
  import { isElementFocused } from "../utils/slot.js";
5
5
  import { getReactEventHandler } from "../utils/get-react-event-handler.js";
6
6
  import { parseMarkdown } from "../utils/markdown.js";
7
- import { createParseVisitor, getEndRange, formatList, handleEmojiMousedown, formatOutdent, formatIndent, formatInline, insertLink, insertText, insertLineBreak, deleteContentBackward, setBrowserCaret, serializeMarkdown, getSelectionInfo, isSelectionEqual, insertFromPaste, isEditorEmpty } from "./utils.js";
8
- const templateHTML = '<style>:host{display:block}#wrapper{display:flex;flex-direction:column;position:relative;width:100%;box-sizing:border-box;background-color:var(--sinch-comp-textarea-color-default-background-initial);border-radius:var(--sinch-local-shape-radius);overflow:hidden;--sinch-local-shape-radius:var(--sinch-comp-textarea-shape-radius)}#input-wrapper{position:relative;padding:8px 10px 8px 12px;box-sizing:border-box}#input{font:var(--sinch-comp-textarea-font-input);color:var(--sinch-comp-textarea-color-default-text-initial);white-space:pre-wrap;overflow-wrap:break-word;border:none;outline:0}#placeholder{display:none;position:absolute;left:0;top:0;font:var(--sinch-comp-textarea-font-input);color:var(--sinch-comp-textarea-color-default-text-placeholder);padding:8px 10px 8px 12px;pointer-events:none;user-select:none}#input.empty+#placeholder{display:block}#border{position:absolute;border:1px solid var(--sinch-comp-textarea-color-default-border-initial);border-radius:var(--sinch-local-shape-radius);inset:0;pointer-events:none}:host([invalid]) #border{border-color:var(--sinch-comp-textarea-color-invalid-border-initial)}:host([disabled]){color:var(--sinch-comp-textarea-color-disabled-text-initial);-webkit-text-fill-color:var(--sinch-comp-textarea-color-disabled-text-initial)}:host([disabled]) #border{border-color:var(--sinch-comp-textarea-color-disabled-border-initial)}:host(:not([disabled])) #input-wrapper:focus-within~#border{border-color:var(--sinch-comp-textarea-color-default-border-focus);border-width:2px}.oli,.p,.uli{margin:0}.oli.l0,.uli.l0{margin-left:6px}.oli.l1,.uli.l1{margin-left:36px}.oli.l2,.uli.l2{margin-left:64px}.oli.l3,.uli.l3{margin-left:92px}.oli.l4,.uli.l4{margin-left:120px}.uli.l0{counter-reset:list-0 list-1 list-2 list-3 list-4}.uli.l1{counter-reset:list-1 list-2 list-3 list-4}.uli.l2{counter-reset:list-2 list-3 list-4}.uli.l3{counter-reset:list-3 list-4}.uli.l4{counter-reset:list-4}.oli.l0{counter-reset:list-1 list-2 list-3 list-4}.oli.l1{counter-reset:list-2 list-3 list-4}.oli.l2{counter-reset:list-3 list-4}.oli.l3{counter-reset:list-4}.oli.l0::before{counter-increment:list-0;content:counter(list-0,decimal) ". "}.oli.l1::before{counter-increment:list-1;content:counter(list-1,lower-alpha) ". "}.oli.l2::before{counter-increment:list-2;content:counter(list-2,lower-roman) ". "}.oli.l3::before{counter-increment:list-3;content:counter(list-3,decimal) ". "}.oli.l4::before{counter-increment:list-4;content:counter(list-4,lower-alpha) ". "}.oli.block,.oli:first-of-type,.p+.oli{counter-reset:list-0 list-1 list-2 list-3 list-4}.uli::before{content:"\\25CF";display:inline-block;width:16px}.oli+.p,.oli.block,.p+.oli,.p+.uli,.uli+.p,.uli.block{margin-top:.5em}.c{font:var(--sinch-comp-code-tag-font-text);font-size:inherit;line-height:inherit;color:var(--sinch-comp-code-tag-color-default-text-initial);border:1px solid var(--sinch-comp-code-tag-color-default-border-initial);background-color:var(--sinch-comp-code-tag-color-default-background-initial);padding:0 .25em;border-radius:var(--sinch-comp-code-tag-shape-radius)}.l{font:var(--sinch-comp-link-default-font-initial);color:var(--sinch-comp-link-color-default-text-initial);text-decoration:underline}.i{font-style:italic}.b{font-weight:700}.s{text-decoration:line-through}.e{background-repeat:no-repeat;background-position:50% 50%;background-size:contain;width:1em;height:1em;vertical-align:-.2em}#top-wrapper{display:flex;flex-direction:row;align-items:center;gap:8px;padding:4px 4px 0}#top-wrapper.empty{display:none}#bottom-wrapper{display:flex;flex-direction:row;align-items:center;gap:8px;padding:0 4px 4px}#bottom-wrapper.empty{display:none}</style><div id="wrapper"><div id="top-wrapper"><slot id="top" name="top"></slot></div><div id="input-wrapper"><div id="input" contenteditable suppresscontenteditablewarning autocapitalize="false" autocorrect="false" autosave="false" spellcheck="false"></div><div id="placeholder"></div></div><div id="border"></div><div id="bottom-wrapper"><slot id="bottom" name="bottom"></slot></div></div>';
7
+ import { createParseVisitor, getEndRange, formatList, handleEmojiMousedown, formatOutdent, formatIndent, formatInline, insertTag, insertLink, insertText, insertLineBreak, deleteContentBackward, setBrowserCaret, serializeMarkdown, getSelectionInfo, isSelectionEqual, insertFromPaste, isEditorEmpty } from "./utils.js";
8
+ const templateHTML = '<style>:host{display:block}#wrapper{display:flex;flex-direction:column;position:relative;width:100%;box-sizing:border-box;background-color:var(--sinch-comp-textarea-color-default-background-initial);border-radius:var(--sinch-local-shape-radius);overflow:hidden;--sinch-local-shape-radius:var(--sinch-comp-textarea-shape-radius)}#input-wrapper{position:relative;padding:8px 10px 8px 12px;box-sizing:border-box}#input{font:var(--sinch-comp-textarea-font-input);color:var(--sinch-comp-textarea-color-default-text-initial);white-space:pre-wrap;overflow-wrap:break-word;border:none;outline:0}#placeholder{display:none;position:absolute;left:0;top:0;font:var(--sinch-comp-textarea-font-input);color:var(--sinch-comp-textarea-color-default-text-placeholder);padding:8px 10px 8px 12px;pointer-events:none;user-select:none}#input.empty+#placeholder{display:block}#border{position:absolute;border:1px solid var(--sinch-comp-textarea-color-default-border-initial);border-radius:var(--sinch-local-shape-radius);inset:0;pointer-events:none}:host([invalid]) #border{border-color:var(--sinch-comp-textarea-color-invalid-border-initial)}:host([disabled]){color:var(--sinch-comp-textarea-color-disabled-text-initial);-webkit-text-fill-color:var(--sinch-comp-textarea-color-disabled-text-initial)}:host([disabled]) #border{border-color:var(--sinch-comp-textarea-color-disabled-border-initial)}:host(:not([disabled])) #input-wrapper:focus-within~#border{border-color:var(--sinch-comp-textarea-color-default-border-focus);border-width:2px}.oli,.p,.uli{margin:0}.oli.l0,.uli.l0{margin-left:6px}.oli.l1,.uli.l1{margin-left:36px}.oli.l2,.uli.l2{margin-left:64px}.oli.l3,.uli.l3{margin-left:92px}.oli.l4,.uli.l4{margin-left:120px}.uli.l0{counter-reset:list-0 list-1 list-2 list-3 list-4}.uli.l1{counter-reset:list-1 list-2 list-3 list-4}.uli.l2{counter-reset:list-2 list-3 list-4}.uli.l3{counter-reset:list-3 list-4}.uli.l4{counter-reset:list-4}.oli.l0{counter-reset:list-1 list-2 list-3 list-4}.oli.l1{counter-reset:list-2 list-3 list-4}.oli.l2{counter-reset:list-3 list-4}.oli.l3{counter-reset:list-4}.oli.l0::before{counter-increment:list-0;content:counter(list-0,decimal) ". "}.oli.l1::before{counter-increment:list-1;content:counter(list-1,lower-alpha) ". "}.oli.l2::before{counter-increment:list-2;content:counter(list-2,lower-roman) ". "}.oli.l3::before{counter-increment:list-3;content:counter(list-3,decimal) ". "}.oli.l4::before{counter-increment:list-4;content:counter(list-4,lower-alpha) ". "}.oli.block,.oli:first-of-type,.p+.oli{counter-reset:list-0 list-1 list-2 list-3 list-4}.uli::before{content:"\\25CF";display:inline-block;width:16px}.oli+.p,.oli.block,.p+.oli,.p+.uli,.uli+.p,.uli.block{margin-top:.5em}.c{font:var(--sinch-comp-code-tag-font-text);font-size:inherit;line-height:inherit;color:var(--sinch-comp-code-tag-color-default-text-initial);border:1px solid var(--sinch-comp-code-tag-color-default-border-initial);background-color:var(--sinch-comp-code-tag-color-default-background-initial);padding:0 .25em;border-radius:var(--sinch-comp-code-tag-shape-radius)}.l{font:var(--sinch-comp-link-default-font-initial);color:var(--sinch-comp-link-color-default-text-initial);text-decoration:underline}.m{display:inline-flex;align-items:center;height:20px;padding:0 8px;border-radius:var(--sinch-comp-tag-shape-radius,4px);background-color:var(--sinch-comp-tag-color-default-background,#e3e8f0);color:var(--sinch-comp-tag-color-default-foreground,#1e2433);font-size:12px;font-weight:500;line-height:20px;white-space:nowrap;vertical-align:middle;user-select:none}.i{font-style:italic}.b{font-weight:700}.s{text-decoration:line-through}.e{background-repeat:no-repeat;background-position:50% 50%;background-size:contain;width:1em;height:1em;vertical-align:-.2em}#top-wrapper{display:flex;flex-direction:row;align-items:center;gap:8px;padding:4px 4px 0}#top-wrapper.empty{display:none}#bottom-wrapper{display:flex;flex-direction:row;align-items:center;gap:8px;padding:0 4px 4px}#bottom-wrapper.empty{display:none}</style><div id="wrapper"><div id="top-wrapper"><slot id="top" name="top"></slot></div><div id="input-wrapper"><div id="input" contenteditable suppresscontenteditablewarning autocapitalize="false" autocorrect="false" autosave="false" spellcheck="false"></div><div id="placeholder"></div></div><div id="border"></div><div id="bottom-wrapper"><slot id="bottom" name="bottom"></slot></div></div>';
9
9
  const template = document.createElement("template");
10
10
  template.innerHTML = templateHTML;
11
11
  const SUPPORTS_SHADOW_SELECTION = typeof window.ShadowRoot.prototype.getSelection === "function";
@@ -150,6 +150,10 @@ class RichTextarea extends NectaryElement {
150
150
  const res = this.#handleInput("insertLink", this.#getCurrentRange(), text, href);
151
151
  this.#handleActionResult(res);
152
152
  }
153
+ insertMention(username) {
154
+ const res = this.#handleInput("insertMention", this.#getCurrentRange(), username);
155
+ this.#handleActionResult(res);
156
+ }
153
157
  formatItalic() {
154
158
  const res = this.#handleInput("formatItalic", this.#getCurrentRange());
155
159
  this.#handleActionResult(res);
@@ -316,6 +320,9 @@ class RichTextarea extends NectaryElement {
316
320
  case "insertLink": {
317
321
  return insertLink(this.#$input, text, href, range);
318
322
  }
323
+ case "insertMention": {
324
+ return insertTag(this.#$input, text, range);
325
+ }
319
326
  // case 'formatUnderline':
320
327
  case "formatItalic":
321
328
  case "formatBold":
@@ -4,6 +4,7 @@ export type TRichTextareaSelection = {
4
4
  bold: boolean;
5
5
  strikethrough: boolean;
6
6
  codetag: boolean;
7
+ tag: boolean;
7
8
  link: boolean;
8
9
  ulist: boolean;
9
10
  olist: boolean;
@@ -18,6 +19,7 @@ export type TSinchRichTextareaProps = {
18
19
  export type TSinchRichTextareaMethods = {
19
20
  insertText(value: string): void;
20
21
  insertLink(text: string, href: string): void;
22
+ insertMention(username: string): void;
21
23
  formatItalic(): void;
22
24
  formatBold(): void;
23
25
  formatStrikethrough(): void;
@@ -22,6 +22,7 @@ export type TActionResult = {
22
22
  };
23
23
  export declare const deleteContentBackward: ($root: TRichTextareaRoot, range: TRange) => TActionResult;
24
24
  export declare const insertLink: ($root: TRichTextareaRoot, text: string, href: string, range: TRange) => TActionResult;
25
+ export declare const insertTag: ($root: TRichTextareaRoot, text: string, range: TRange) => TActionResult;
25
26
  export declare const insertLineBreak: (range: TRange) => TActionResult;
26
27
  export declare const insertText: ($root: TRichTextareaRoot, data: string | null, range: TRange) => TActionResult;
27
28
  export declare const insertFromPaste: (data: string, range: Readonly<TRange>, visitor: TMarkdownParseVisitor) => TActionResult;
@@ -138,6 +138,7 @@ const isFormatItalic = ($n) => isFormatName($n, "i");
138
138
  const isFormatStrikethrough = ($n) => isFormatName($n, "s");
139
139
  const isFormatCodetag = ($n) => isFormatName($n, "c");
140
140
  const isFormatLink = ($n) => isFormatName($n, "l");
141
+ const isFormatTag = ($n) => isFormatName($n, "m");
141
142
  const isAllInsideFormatName = ($a, $b, formatName) => {
142
143
  const aBlock = getParentTextBlock($a);
143
144
  const bBlock = getParentTextBlock($b);
@@ -184,6 +185,9 @@ const setInlineFormat = ($n, formatName, shouldEnable) => {
184
185
  $n.className = "";
185
186
  $n.removeAttribute(LINK_HREF_ATTR_NAME);
186
187
  }
188
+ if (formatName === "m" || isFormatName($n, "m")) {
189
+ $n.className = "";
190
+ }
187
191
  $n.classList.add(formatName);
188
192
  } else {
189
193
  if (formatName === "l") {
@@ -248,6 +252,11 @@ const createLink = (text, href, doc) => {
248
252
  $link.setAttribute(LINK_HREF_ATTR_NAME, href);
249
253
  return $link;
250
254
  };
255
+ const createTag = (text, doc) => {
256
+ const $tag = createInlineWithText(`${text}`, doc);
257
+ setInlineFormat($tag, "m", true);
258
+ return $tag;
259
+ };
251
260
  const EMOJI_CHAR_ATTR_NAME = "data-char";
252
261
  const createEmoji = (emojiChar, baseUrl, doc) => {
253
262
  const $emoji = doc.createElement("img");
@@ -1066,6 +1075,36 @@ const insertLink = ($root, text, href, range) => {
1066
1075
  )
1067
1076
  };
1068
1077
  };
1078
+ const insertTag = ($root, text, range) => {
1079
+ const cursor = removeContentInRange(range);
1080
+ const { $text, $inline, offset, isAfterInline: isAfterLastChild } = cursor;
1081
+ const $tag = createTag(text, $root.ownerDocument);
1082
+ if (isTextNode($text)) {
1083
+ if (isEmptyText($text.nodeValue)) {
1084
+ getParentTextBlock($inline).replaceChild($tag, $inline);
1085
+ } else {
1086
+ const [$before, $after] = splitTextNode($text, offset);
1087
+ if ($before === null) {
1088
+ assertNonNull($after);
1089
+ $after.before($tag);
1090
+ } else {
1091
+ $before.after($tag);
1092
+ }
1093
+ }
1094
+ } else if (isAfterLastChild === true) {
1095
+ $inline.after($tag);
1096
+ } else {
1097
+ $inline.before($tag);
1098
+ }
1099
+ const $trailingSpace = createInlineWithText(" ", $root.ownerDocument);
1100
+ $tag.after($trailingSpace);
1101
+ return {
1102
+ prevent: true,
1103
+ range: createCollapsedRange(
1104
+ createEndCursorFromTextContent($trailingSpace)
1105
+ )
1106
+ };
1107
+ };
1069
1108
  const insertLineBreak = (range) => {
1070
1109
  const cursor = removeContentInRange(range);
1071
1110
  const { $text, $inline, offset, isAfterInline: isAfterLastChild } = cursor;
@@ -1135,7 +1174,7 @@ const insertText = ($root, data, range) => {
1135
1174
  isTextNode(range.startContainer) && !isEmptyText(range.startContainer.nodeValue)
1136
1175
  ) {
1137
1176
  const $inline2 = getParentInline(range.startContainer);
1138
- const isHotTextWhitespace = range.startOffset === range.startContainer.length && data === TEXT_WHITESPACE && (isFormatCodetag($inline2) || isFormatLink($inline2));
1177
+ const isHotTextWhitespace = range.startOffset === range.startContainer.length && data === TEXT_WHITESPACE && (isFormatCodetag($inline2) || isFormatLink($inline2) || isFormatTag($inline2));
1139
1178
  if (!isHotTextWhitespace) {
1140
1179
  return DEFAULT_ACTION_RESULT;
1141
1180
  }
@@ -1174,7 +1213,7 @@ const insertText = ($root, data, range) => {
1174
1213
  };
1175
1214
  }
1176
1215
  if (offset === $text.length && data === TEXT_WHITESPACE) {
1177
- if (isFormatLink($inline) || isFormatCodetag($inline)) {
1216
+ if (isFormatLink($inline) || isFormatCodetag($inline) || isFormatTag($inline)) {
1178
1217
  const $newinline = createInlineWithText(data, $root.ownerDocument);
1179
1218
  $inline.after($newinline);
1180
1219
  return {
@@ -1184,6 +1223,27 @@ const insertText = ($root, data, range) => {
1184
1223
  )
1185
1224
  };
1186
1225
  }
1226
+ const content2 = $text.nodeValue;
1227
+ const tagMatch = content2.match(/\{\{([a-zA-Z0-9_-]+)\}\}$/);
1228
+ if (tagMatch !== null) {
1229
+ const text = tagMatch[1];
1230
+ const tagStartPos = content2.length - tagMatch[0].length;
1231
+ const $tag = createTag(text, $root.ownerDocument);
1232
+ const $space = createInlineWithText(data, $root.ownerDocument);
1233
+ if (tagStartPos === 0) {
1234
+ getParentTextBlock($inline).replaceChild($tag, $inline);
1235
+ } else {
1236
+ $text.nodeValue = content2.substring(0, tagStartPos);
1237
+ $inline.after($tag);
1238
+ }
1239
+ $tag.after($space);
1240
+ return {
1241
+ prevent: true,
1242
+ range: createCollapsedRange(
1243
+ createEndCursorFromTextContent($space)
1244
+ )
1245
+ };
1246
+ }
1187
1247
  }
1188
1248
  const content = $text.nodeValue;
1189
1249
  $text.nodeValue = content.substring(0, offset) + data + content.substring(offset);
@@ -1317,6 +1377,7 @@ const getSelectionInfo = (range) => {
1317
1377
  italic: isFormatItalic($inline),
1318
1378
  strikethrough: isFormatStrikethrough($inline),
1319
1379
  codetag: isFormatCodetag($inline),
1380
+ tag: isFormatTag($inline),
1320
1381
  link: isFormatLink($inline),
1321
1382
  olist: isInsideList(true, $inline),
1322
1383
  ulist: isInsideList(false, $inline)
@@ -1327,6 +1388,7 @@ const getSelectionInfo = (range) => {
1327
1388
  bold: false,
1328
1389
  strikethrough: false,
1329
1390
  codetag: false,
1391
+ tag: false,
1330
1392
  link: false,
1331
1393
  olist: false,
1332
1394
  ulist: false
@@ -1337,6 +1399,8 @@ const getSelectionInfo = (range) => {
1337
1399
  italic: isAllInsideItalic(aCursor.$inline, bCursor.$inline),
1338
1400
  strikethrough: isAllInsideStrikethrough(aCursor.$inline, bCursor.$inline),
1339
1401
  codetag: isAllInsideCodetag(aCursor.$inline, bCursor.$inline),
1402
+ tag: false,
1403
+ // Tags are atomic, so multi-selection doesn't apply
1340
1404
  link: isAllInsideLink(aCursor.$inline, bCursor.$inline),
1341
1405
  olist: isAllInsideList(true, aCursor.$inline, bCursor.$inline),
1342
1406
  ulist: isAllInsideList(false, aCursor.$inline, bCursor.$inline)
@@ -1351,7 +1415,7 @@ const isEmptyTextBlock = ($block) => {
1351
1415
  return false;
1352
1416
  }
1353
1417
  const $inline = blockChildren[0];
1354
- const isEmptyText2 = isInline($inline) && !isFormatCodetag($inline) && isEmptyTextNode(getChildText($inline));
1418
+ const isEmptyText2 = isInline($inline) && !isFormatCodetag($inline) && !isFormatTag($inline) && isEmptyTextNode(getChildText($inline));
1355
1419
  return isEmptyText2;
1356
1420
  };
1357
1421
  const isEditorEmpty = ($root) => {
@@ -1392,6 +1456,10 @@ const serializeDescriptorReducer = (range) => (state, $n) => {
1392
1456
  state.push({ isCodetag: true, text });
1393
1457
  return state;
1394
1458
  }
1459
+ if (isFormatTag($n)) {
1460
+ state.push({ isTag: true, text: `{{${text}}}` });
1461
+ return state;
1462
+ }
1395
1463
  if (isFormatLink($n)) {
1396
1464
  const href = $n.getAttribute(LINK_HREF_ATTR_NAME) ?? "#";
1397
1465
  state.push({ isLink: true, text, href });
@@ -1449,6 +1517,10 @@ const serializeTextReducer = (state, desc, i, descArray) => {
1449
1517
  chunks.push(`${MD_CODETAG_TOKEN}${desc.text}${MD_CODETAG_TOKEN}`);
1450
1518
  return state;
1451
1519
  }
1520
+ if (desc.isTag === true) {
1521
+ chunks.push(desc.text);
1522
+ return state;
1523
+ }
1452
1524
  if (desc.isWhitespace === true) {
1453
1525
  chunks.push(desc.text);
1454
1526
  return state;
@@ -1620,6 +1692,10 @@ const createParseVisitor = (doc) => {
1620
1692
  setInlineFormat($inline, "c", true);
1621
1693
  $currentBlock.appendChild($inline);
1622
1694
  },
1695
+ tag(text) {
1696
+ const $tag = createTag(text, doc);
1697
+ $currentBlock.appendChild($tag);
1698
+ },
1623
1699
  inline(text, { isBold, isItalic, isStrikethrough }) {
1624
1700
  const $inline = createInlineWithText(text, doc);
1625
1701
  setInlineFormat($inline, "b", isBold === true);
@@ -1704,6 +1780,7 @@ export {
1704
1780
  insertFromPaste,
1705
1781
  insertLineBreak,
1706
1782
  insertLink,
1783
+ insertTag,
1707
1784
  insertText,
1708
1785
  isEditorEmpty,
1709
1786
  isSelectionEqual,
@@ -8,6 +8,7 @@ export type TMarkdownParseVisitor = {
8
8
  link(text: string, href: string, attributes?: string[]): void;
9
9
  emoji(emojiChar: string): void;
10
10
  codetag(text: string): void;
11
+ tag(username: string): void;
11
12
  inline(text: string, params: TMarkdownInlineParams): void;
12
13
  linebreak(): void;
13
14
  paragraph(): void;
package/utils/markdown.js CHANGED
@@ -9,14 +9,16 @@ const regEm1Underscore = new RegExp("(?<!\\\\)_(?<em1>.+?)(?<!\\\\)_");
9
9
  const regCodeTag = new RegExp("(?<!\\\\)`(?<code>.+?)(?<!\\\\)`");
10
10
  const regStrikethrough = new RegExp("(?<!\\\\)~~(?<strike>.+?)(?<!\\\\)~~");
11
11
  const regLink = new RegExp("(?<!\\\\)!?\\[(?<linktext>[^\\]]*?)\\]\\((?<linkhref>[^)]+?)\\)(\\{(?<linkattrs>[^)]+?)\\})?");
12
+ const regMention = new RegExp("(?<!\\\\)\\{\\{(?<mention>[a-zA-Z0-9_-]+)\\}\\}");
12
13
  const regEmoji = new RegExp("(?<emoji>(?![0-9*#])\\p{Emoji})", "u");
13
14
  const regUList = /^(?<indent>[\t ]*?)[*+-][\t ]+(?<ultext>.*?)[\t ]*?$/;
14
15
  const regOList = /^(?<indent>[\t ]*?)\d+\.[\t ]+(?<oltext>.*?)[\t ]*?$/;
15
- const regEscapedChars = /\\(?<escaped>[\\\*_\[\]`~])/;
16
+ const regEscapedChars = /\\(?<escaped>[\\\*_\[\]`~\{\}])/;
16
17
  const allRegs = [
17
18
  regEscapedChars,
18
19
  regCodeTag,
19
20
  regLink,
21
+ regMention,
20
22
  regEm3Star,
21
23
  regEm2Star,
22
24
  regEm1Star,
@@ -64,6 +66,9 @@ const createLineParser = (visitor) => function parseLine(regs, md, context = INI
64
66
  if (groups?.code != null) {
65
67
  visitor.codetag(groups.code);
66
68
  }
69
+ if (groups?.mention != null) {
70
+ visitor.tag(groups.mention);
71
+ }
67
72
  if (groups?.emoji != null) {
68
73
  visitor.emoji(groups.emoji);
69
74
  }