Square CoCalc Logo
FeaturesSoftwarePricingInfoPoliciesShareSupport Try Sign InSign Up
(IconFonts not available)
sagemathinc
GitHub Repository: sagemathinc/cocalc
(IconFonts not available)
Path:
blob/master/src/packages/frontend/markdown/mentions-plugin.ts
Views: 179
1
/*
2
* LICENSE: MIT (same as upstream hashtags plugin)
3
*/
4
5
/*
6
7
What this plugin mainly does is take a stream of inline tokens like this:
8
9
...
10
{type: "text", ...}
11
{type: "html_inline", content: "<span class="user-mention" account-id=47d0393e-4814-4452-bb6c-35bac4cbd314 >", ...}
12
{type: "text", content: "@Bella Welski", ...}
13
{type: "html_inline", content: "</span>", ...}
14
{type: "text", ...}
15
...
16
17
and turn it into
18
19
...
20
{type: "text", ...}
21
{type: "mention", account_id: "47d0393e-4814-4452-bb6c-35bac4cbd314 >", name:"Bella Welski", ...}
22
{type: "text", ...}
23
...
24
25
26
It also defines a renderer.
27
28
The motivation is that in CoCalc we store our mentions in markdown as
29
30
<span class="user-mention" account-id=47d0393e-4814-4452-bb6c-35bac4cbd314 >@Bella Welski</span>
31
32
With an appropriate class user-mention, this does render fine with no
33
processing at all. However, by parsing this, we can also use mentions
34
in our Slate editor, and it's much easier to dynamically update the
35
user's name (with given account_id) if they change it. Morever, we could
36
easily use user colors, avatars, etc. to render mention users
37
with this parser, but it's much harder without.
38
39
*/
40
41
import { startswith } from "@cocalc/util/misc";
42
43
function renderMention(tokens, idx): string {
44
// TODO: we could dynamically update the username using the account-id
45
// in case it changed from what is stored in the doc.
46
// This user-mention is a CSS class we defined somewhere...
47
const token = tokens[idx];
48
return `<span class="user-mention">@${token.name}</span>`;
49
}
50
51
function isMentionOpen(str: string): boolean {
52
return startswith(str, '<span class="user-mention" ');
53
}
54
function isMentionClose(str: string): boolean {
55
return str == "</span>";
56
}
57
58
export function mentionPlugin(md): void {
59
function mention(state) {
60
const { Token, tokens: blockTokens } = state;
61
62
for (let j = 0; j < blockTokens.length; j++) {
63
if (blockTokens[j].type !== "inline") {
64
continue;
65
}
66
67
let tokens = blockTokens[j].children;
68
69
for (let i = tokens.length - 1; i >= 2; i--) {
70
if (
71
!(
72
isMentionClose(tokens[i].content) &&
73
isMentionOpen(tokens[i - 2].content)
74
)
75
) {
76
continue;
77
}
78
// tokens[i-2] like: <span class="user-mention" account-id=47d0393e-4814-4452-bb6c-35bac4cbd314 >
79
// tokens[i-1] like: @Bella Welski
80
// and tokens[i] like: </span>
81
const { level } = tokens[i];
82
const token = new Token("mention", "", 0);
83
token.level = level;
84
const i0 = tokens[i - 2].content.lastIndexOf("=");
85
token.account_id = tokens[i - 2].content.slice(i0 + 1, i0 + 37);
86
token.name = tokens[i - 1].content.slice(1).trim();
87
88
tokens = tokens
89
.slice(0, i - 2)
90
.concat([token])
91
.concat(tokens.slice(i + 1));
92
93
// replace current node
94
blockTokens[j].children = tokens;
95
96
i -= 2;
97
}
98
}
99
}
100
101
md.core.ruler.after("inline", "mention", mention);
102
md.renderer.rules.mention = renderMention;
103
}
104
105