Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemathinc
GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/frontend/editors/slate/util.ts
1691 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 { capitalize, is_whitespace, replace_all } from "@cocalc/util/misc";
7
8
// Note: this markdown_escape is based on https://github.com/edwmurph/escape-markdown/blob/master/index.js
9
10
const MAP = {
11
"*": "\\*",
12
"+": "\\+",
13
"-": "\\-",
14
"#": "\\#",
15
"(": "\\(",
16
")": "\\)",
17
"[": "\\[",
18
"]": "\\]",
19
"|": "\\|",
20
_: "\\_",
21
"\\": "\\\\",
22
"`": "\\`",
23
"<": "&lt;",
24
">": "&gt;",
25
"&": "&amp;",
26
"\xa0": "&nbsp;", // we do this so that the markdown nbsp's are explicit
27
$: "\\$",
28
} as const;
29
30
export function markdownEscape(
31
s: string,
32
isFirstChild: boolean = false
33
): string {
34
// The 1-character replacements we make in any text.
35
s = s.replace(/[\*\(\)\[\]\$\+\-\\_`#<>]/g, (m) => MAP[m]);
36
// Version of the above, but with some keys from the map purposely missing here,
37
// since overescaping makes the generated markdown ugly. However, sadly we HAVE
38
// to escape everything (as above), since otherwise collaborative editing gets
39
// broken, and tons of trouble with slate. E.g., User a types a single - at the
40
// beginning of the line, and user
41
// B types something somewhere else in the document. The dash then automatically
42
// turns into a list without user A doing anything. NOT good.
43
// Fortunately, caching makes this less painful.
44
// s = s.replace(/[\\_`<>$&\xa0|]/g, (m) => MAP[m]);
45
46
// Links - we do this to avoid escaping [ and ] when not necessary.
47
s = s.replace(/\[([^\]]+)\]\(([^\)]+)\)/g, (link) =>
48
link.replace(/[\[\]]/g, (m) => MAP[m])
49
);
50
51
if (isFirstChild) {
52
// Escape three dashes at start of line mod whitespace (which is hr).
53
s = s.replace(/^\s*---/, (m) => m.replace("---", "\\-\\-\\-"));
54
55
// Escape # signs at start of line (headers).
56
s = s.replace(/^\s*#+/, (m) => replace_all(m, "#", "\\#"));
57
}
58
59
return s;
60
}
61
62
export function indent(s: string, n: number): string {
63
if (n == 0) {
64
return s;
65
}
66
let left = "";
67
for (let i = 0; i < n; i++) {
68
left += " ";
69
}
70
71
// add space at beginning of all non-whitespace lines
72
const v = s.split("\n");
73
for (let i = 0; i < v.length; i++) {
74
if (!is_whitespace(v[i])) {
75
v[i] = left + v[i];
76
}
77
}
78
return v.join("\n");
79
}
80
81
/*
82
li_indent -- indent all but the first line by amount spaces.
83
84
NOTE: There are some cases where more than 2 spaces are needed.
85
For example, here we need 3:
86
87
1. one
88
2. two
89
- foo
90
- bar
91
*/
92
export function li_indent(s: string, amount: number = 2): string {
93
const i = s.indexOf("\n");
94
if (i != -1 && i != s.length - 1) {
95
return s.slice(0, i + 1) + indent(s.slice(i + 1), amount);
96
} else {
97
return s;
98
}
99
}
100
101
// Ensure that s ends in **exactly one** newline.
102
export function ensure_ends_in_exactly_one_newline(s: string): string {
103
if (s[s.length - 1] != "\n") {
104
return s + "\n";
105
}
106
while (s[s.length - 2] == "\n") {
107
s = s.slice(0, s.length - 1);
108
}
109
return s;
110
}
111
112
export function ensure_ends_in_two_newline(s: string): string {
113
if (s[s.length - 1] !== "\n") {
114
return s + "\n\n";
115
} else if (s[s.length - 2] !== "\n") {
116
return s + "\n";
117
} else {
118
return s;
119
}
120
}
121
122
export function mark_block(s: string, mark: string): string {
123
const v: string[] = [];
124
for (const line of s.trim().split("\n")) {
125
if (is_whitespace(line)) {
126
v.push(mark);
127
} else {
128
v.push(mark + " " + line);
129
}
130
}
131
return v.join("\n") + "\n\n";
132
}
133
134
function indexOfNonWhitespace(s: string): number {
135
// regexp finds where the first non-whitespace starts
136
return /\S/.exec(s)?.index ?? -1;
137
}
138
139
function lastIndexOfNonWhitespace(s: string): number {
140
// regexp finds where the whitespace starts at the end of the string.
141
return (/\s+$/.exec(s)?.index ?? s.length) - 1;
142
}
143
144
export function stripWhitespace(s: string): {
145
before: string;
146
trimmed: string;
147
after: string;
148
} {
149
const i = indexOfNonWhitespace(s);
150
const j = lastIndexOfNonWhitespace(s);
151
return {
152
before: s.slice(0, i),
153
trimmed: s.slice(i, j + 1),
154
after: s.slice(j + 1),
155
};
156
}
157
158
export function markInlineText(
159
text: string,
160
left: string,
161
right?: string // defaults to left if not given
162
): string {
163
// For non-HTML, we have to put the mark *inside* of any
164
// whitespace on the outside.
165
// See https://www.markdownguide.org/basic-syntax/#bold
166
// where it says "... without spaces ...".
167
// In particular, `** bold **` does NOT work.
168
// This is NOT true for html, of course.
169
if (left.indexOf("<") != -1) {
170
// html - always has right set.
171
return left + text + right;
172
}
173
const { before, trimmed, after } = stripWhitespace(text);
174
if (trimmed.length == 0) {
175
// all whitespace, so don't mark it.
176
return text;
177
}
178
return `${before}${left}${trimmed}${right ?? left}${after}`;
179
}
180
181
export function padLeft(s: string, n: number): string {
182
while (s.length < n) {
183
s = " " + s;
184
}
185
return s;
186
}
187
188
export function padRight(s: string, n: number): string {
189
while (s.length < n) {
190
s += " ";
191
}
192
return s;
193
}
194
195
export function padCenter(s: string, n: number): string {
196
while (s.length < n) {
197
s = " " + s + " ";
198
}
199
return s.slice(0, n);
200
}
201
202
export const FOCUSED_COLOR = "#7eb6e2";
203
export const SELECTED_COLOR = "#1990ff";
204
/* This focused color is "Jupyter notebook classic" focused cell green. */
205
export const CODE_FOCUSED_COLOR = "#66bb6a";
206
export const CODE_FOCUSED_BACKGROUND = "#cfe8fc";
207
export const DARK_GREY_BORDER = "#cfcfcf";
208
209
export function string_to_style(style: string): any {
210
const obj: any = {};
211
for (const x of style.split(";")) {
212
const j = x.indexOf("=");
213
if (j == -1) continue;
214
let key = x.slice(0, j);
215
const i = key.indexOf("-");
216
if (i != -1) {
217
key = x.slice(0, i) + capitalize(x.slice(i + 1));
218
}
219
obj[key] = x.slice(j + 1);
220
}
221
return obj;
222
}
223
224
export const DEFAULT_CHILDREN = [{ text: "" }];
225
226
export function removeBlankLines(s: string): string {
227
return s.replace(/^\s*\n/gm, "");
228
}
229
230