Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemathinc
GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/frontend/editors/slate/markdown-to-slate/handle-marks.ts
1697 views
1
/*
2
* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.
3
* License: MS-RSL – see LICENSE.md for details
4
*/
5
6
import { startswith } from "@cocalc/util/misc";
7
import { register } from "./register";
8
9
export interface Marks {
10
text: string;
11
italic?: boolean;
12
bold?: boolean;
13
strikethrough?: boolean;
14
underline?: boolean;
15
sup?: boolean;
16
sub?: boolean;
17
tt?: boolean;
18
code?: boolean;
19
small?: boolean;
20
search?: boolean; // search highlighting...
21
}
22
23
// Map from prefix of markdown token types to Slate marks.
24
// This shouldn't need to change ever, since markdown is done,
25
// though maybe a markdown-it plugin could add to this.
26
const TYPES = {
27
em: "italic",
28
strong: "bold",
29
s: "strikethrough",
30
};
31
32
// Map from inline HTML tags to Slate marks.
33
// The "cool" thing is that if you have some bits of html that
34
// use these tags, then they will get transformed into proper
35
// markdown (when possible) when you edit the slate file.
36
// Obviously, if the user had nested inline html,
37
// it may break, unless we can handle everything. Of course,
38
// html is defined and we might actually handle most everything.
39
const TAGS = {
40
u: "underline",
41
sup: "sup",
42
sub: "sub",
43
tt: "tt",
44
code: "code",
45
i: "italic",
46
em: "italic",
47
strong: "bold",
48
b: "bold",
49
small: "small",
50
};
51
52
// Expand the above info into some useful tables to
53
// make processing faster. Better to do these for
54
// loops once and for all, rather than on *every token*.
55
56
const HOOKS = [];
57
for (const type in TYPES) {
58
HOOKS[type + "_open"] = { [TYPES[type]]: true };
59
HOOKS[type + "_close"] = { [TYPES[type]]: false };
60
}
61
62
for (const tag in TAGS) {
63
HOOKS["<" + tag + ">"] = { [TAGS[tag]]: true };
64
HOOKS["</" + tag + ">"] = { [TAGS[tag]]: false };
65
}
66
67
/*
68
updateMarkState updates the state of text marks if this token
69
just changing marking state. If there is a change, return true
70
to stop further processing.
71
*/
72
function handleMarks({ token, state }) {
73
const t = HOOKS[token.type];
74
if (t != null) {
75
for (const mark in t) {
76
state.marks[mark] = t[mark];
77
return [];
78
}
79
}
80
81
if (token.type == "html_inline") {
82
// special cases for underlining, sup, sub, which markdown doesn't have.
83
const x = token.content;
84
const t = HOOKS[x];
85
if (t != null) {
86
for (const mark in t) {
87
state.marks[mark] = t[mark];
88
return [];
89
}
90
}
91
92
// The following are trickier, since they involve
93
// parameters...
94
95
if (x == "</span>") {
96
for (const mark in state.marks) {
97
if (startswith(mark, "color:")) {
98
delete state.marks[mark];
99
return [];
100
}
101
for (const c of ["family", "size"]) {
102
if (startswith(mark, `font-${c}:`)) {
103
delete state.marks[mark];
104
return [];
105
}
106
}
107
}
108
}
109
110
if (!startswith(x, "<span style=")) {
111
// don't waste time parsing further.
112
return;
113
}
114
115
// Colors look like <span style='color:#ff7f50'>:
116
const singleColor = startswith(x, "<span style='color:");
117
const doubleColor = !singleColor && startswith(x, `<span style="color:`);
118
if (singleColor || doubleColor) {
119
// delete any other colors -- only one at a time
120
for (const mark in state.marks) {
121
if (startswith(mark, "color:")) {
122
delete state.marks[mark];
123
}
124
}
125
// now set our color
126
const c = x.split(":")[1]?.split(singleColor ? "'" : `"`)[0];
127
if (c) {
128
state.marks["color:" + c] = true;
129
}
130
return [];
131
}
132
133
for (const c of ["family", "size"]) {
134
const single = startswith(x, `<span style='font-${c}:`);
135
const double = !single && startswith(x, `<span style="font-${c}:`);
136
if (single || double) {
137
const n = `<span style='font-${c}:`.length;
138
// delete any other fonts -- only one at a time
139
for (const mark in state.marks) {
140
if (startswith(mark, `font-${c}:`)) {
141
delete state.marks[mark];
142
}
143
}
144
// now set our font family or size
145
state.marks[`font-${c}:${x.slice(n, x.length - 2)}`] = true;
146
return [];
147
}
148
}
149
}
150
}
151
152
register(handleMarks);
153
154