@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/bundle.js +2500 -2407
- package/package.json +3 -3
- package/rich-text/index.d.ts +1 -0
- package/rich-text/index.js +1 -0
- package/rich-text/utils.js +5 -0
- package/rich-textarea/index.d.ts +1 -0
- package/rich-textarea/index.js +9 -2
- package/rich-textarea/types.d.ts +2 -0
- package/rich-textarea/utils.d.ts +1 -0
- package/rich-textarea/utils.js +80 -3
- package/utils/markdown.d.ts +1 -0
- package/utils/markdown.js +6 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nectary/components",
|
|
3
|
-
"version": "5.
|
|
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.
|
|
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.
|
|
43
|
+
"@nectary/theme-base": "1.8.0"
|
|
44
44
|
}
|
|
45
45
|
}
|
package/rich-text/index.d.ts
CHANGED
package/rich-text/index.js
CHANGED
|
@@ -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";
|
package/rich-text/utils.js
CHANGED
|
@@ -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));
|
package/rich-textarea/index.d.ts
CHANGED
|
@@ -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;
|
package/rich-textarea/index.js
CHANGED
|
@@ -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":
|
package/rich-textarea/types.d.ts
CHANGED
|
@@ -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;
|
package/rich-textarea/utils.d.ts
CHANGED
|
@@ -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;
|
package/rich-textarea/utils.js
CHANGED
|
@@ -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,
|
package/utils/markdown.d.ts
CHANGED
|
@@ -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
|
}
|