Contact
CoCalc Logo Icon
StoreFeaturesDocsShareSupport News AboutSign UpSign In
sagemathinc
GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/frontend/markdown/checkbox-plugin.ts
Views: 263
1
/*
2
* LICENSE: MIT (same as upstream)
3
*/
4
5
// This code is inspired by https://github.com/mcecot/markdown-it-checkbox
6
7
// However it is meant to behave much like Github, in terms of parsing.
8
9
function checkboxReplace(_md, _options) {
10
let index = 0;
11
const pattern = /\[(X|\s)\](.*)/i;
12
function createTokens(
13
checked: boolean,
14
before: string,
15
after: string,
16
Token
17
) {
18
// before <input type="checkbox" data-index="{n}" checked="true"> after
19
const checkbox_token = new Token("checkbox_input", "input", 0);
20
checkbox_token.attrs = [
21
[
22
"style",
23
"margin: 0 0.2em 0.2em 0.2em; transform: scale(1.5); vertical-align: middle;",
24
],
25
["type", "checkbox"],
26
["data-index", `${index}`],
27
["disabled", "true"] /* disabled: anything in cocalc that is just directly
28
rendering this doesn't know how to change it.*/,
29
];
30
if (checked) {
31
checkbox_token.attrs.push(["checked", "true"]);
32
checkbox_token.checked = checked;
33
}
34
35
const before_token = new Token("text", "", 0);
36
before_token.content = before;
37
38
const after_token = new Token("text", "", 0);
39
after_token.content = after;
40
index += 1;
41
return [before_token, checkbox_token, after_token];
42
}
43
44
function splitTextToken(original, Token) {
45
const markup = original.markup?.[0];
46
if (markup == "$" || markup == "`") {
47
// don't make checkboxes, e.g., inside of `code` like `R[x]` or math like $\QQ[x]$.
48
// NOTE: I did have this for *any* markup at all, but that breaks this:
49
// - foo \(stuff\)
50
// since \) is considered "markup" for some reason. So this fix may result in some
51
// subtle bug somewhere else, which will get fixed by adding another case to the if above.
52
// See https://github.com/sagemathinc/cocalc/issues/6464
53
return null;
54
}
55
const text = original.content;
56
const match = text.match(pattern);
57
if (match === null) {
58
return null;
59
}
60
const before = text.slice(0, match.index);
61
const value = match[1];
62
const checked = value === "X" || value === "x";
63
const after = match[2];
64
return createTokens(checked, before, after, Token);
65
}
66
67
return (state) => {
68
for (const token of state.tokens) {
69
if (token.type !== "inline") {
70
// fenced blocks, etc., should be ignored of course.
71
continue;
72
}
73
// Process all the children, setting has_checkboxes
74
// to true if any are found.
75
let has_checkboxes: boolean = false;
76
const v: any[] = [];
77
for (const child of token.children) {
78
const x = splitTextToken(child, state.Token);
79
if (x != null) {
80
has_checkboxes = true;
81
v.push(x);
82
} else {
83
v.push([child]);
84
}
85
}
86
87
if (has_checkboxes) {
88
// Found at least one checkbox, so replace children. See
89
// https://stackoverflow.com/questions/5080028/what-is-the-most-efficient-way-to-concatenate-n-arrays
90
// for why we concat arrays this way.
91
token.children = [].concat.apply([], v);
92
}
93
}
94
};
95
}
96
97
export function checkboxPlugin(md, options) {
98
md.core.ruler.push("checkbox", checkboxReplace(md, options));
99
}
100
101