Path: blob/master/src/packages/frontend/editors/slate/util.ts
1691 views
/*1* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.2* License: MS-RSL – see LICENSE.md for details3*/45import { capitalize, is_whitespace, replace_all } from "@cocalc/util/misc";67// Note: this markdown_escape is based on https://github.com/edwmurph/escape-markdown/blob/master/index.js89const MAP = {10"*": "\\*",11"+": "\\+",12"-": "\\-",13"#": "\\#",14"(": "\\(",15")": "\\)",16"[": "\\[",17"]": "\\]",18"|": "\\|",19_: "\\_",20"\\": "\\\\",21"`": "\\`",22"<": "<",23">": ">",24"&": "&",25"\xa0": " ", // we do this so that the markdown nbsp's are explicit26$: "\\$",27} as const;2829export function markdownEscape(30s: string,31isFirstChild: boolean = false32): string {33// The 1-character replacements we make in any text.34s = s.replace(/[\*\(\)\[\]\$\+\-\\_`#<>]/g, (m) => MAP[m]);35// Version of the above, but with some keys from the map purposely missing here,36// since overescaping makes the generated markdown ugly. However, sadly we HAVE37// to escape everything (as above), since otherwise collaborative editing gets38// broken, and tons of trouble with slate. E.g., User a types a single - at the39// beginning of the line, and user40// B types something somewhere else in the document. The dash then automatically41// turns into a list without user A doing anything. NOT good.42// Fortunately, caching makes this less painful.43// s = s.replace(/[\\_`<>$&\xa0|]/g, (m) => MAP[m]);4445// Links - we do this to avoid escaping [ and ] when not necessary.46s = s.replace(/\[([^\]]+)\]\(([^\)]+)\)/g, (link) =>47link.replace(/[\[\]]/g, (m) => MAP[m])48);4950if (isFirstChild) {51// Escape three dashes at start of line mod whitespace (which is hr).52s = s.replace(/^\s*---/, (m) => m.replace("---", "\\-\\-\\-"));5354// Escape # signs at start of line (headers).55s = s.replace(/^\s*#+/, (m) => replace_all(m, "#", "\\#"));56}5758return s;59}6061export function indent(s: string, n: number): string {62if (n == 0) {63return s;64}65let left = "";66for (let i = 0; i < n; i++) {67left += " ";68}6970// add space at beginning of all non-whitespace lines71const v = s.split("\n");72for (let i = 0; i < v.length; i++) {73if (!is_whitespace(v[i])) {74v[i] = left + v[i];75}76}77return v.join("\n");78}7980/*81li_indent -- indent all but the first line by amount spaces.8283NOTE: There are some cases where more than 2 spaces are needed.84For example, here we need 3:85861. one872. two88- foo89- bar90*/91export function li_indent(s: string, amount: number = 2): string {92const i = s.indexOf("\n");93if (i != -1 && i != s.length - 1) {94return s.slice(0, i + 1) + indent(s.slice(i + 1), amount);95} else {96return s;97}98}99100// Ensure that s ends in **exactly one** newline.101export function ensure_ends_in_exactly_one_newline(s: string): string {102if (s[s.length - 1] != "\n") {103return s + "\n";104}105while (s[s.length - 2] == "\n") {106s = s.slice(0, s.length - 1);107}108return s;109}110111export function ensure_ends_in_two_newline(s: string): string {112if (s[s.length - 1] !== "\n") {113return s + "\n\n";114} else if (s[s.length - 2] !== "\n") {115return s + "\n";116} else {117return s;118}119}120121export function mark_block(s: string, mark: string): string {122const v: string[] = [];123for (const line of s.trim().split("\n")) {124if (is_whitespace(line)) {125v.push(mark);126} else {127v.push(mark + " " + line);128}129}130return v.join("\n") + "\n\n";131}132133function indexOfNonWhitespace(s: string): number {134// regexp finds where the first non-whitespace starts135return /\S/.exec(s)?.index ?? -1;136}137138function lastIndexOfNonWhitespace(s: string): number {139// regexp finds where the whitespace starts at the end of the string.140return (/\s+$/.exec(s)?.index ?? s.length) - 1;141}142143export function stripWhitespace(s: string): {144before: string;145trimmed: string;146after: string;147} {148const i = indexOfNonWhitespace(s);149const j = lastIndexOfNonWhitespace(s);150return {151before: s.slice(0, i),152trimmed: s.slice(i, j + 1),153after: s.slice(j + 1),154};155}156157export function markInlineText(158text: string,159left: string,160right?: string // defaults to left if not given161): string {162// For non-HTML, we have to put the mark *inside* of any163// whitespace on the outside.164// See https://www.markdownguide.org/basic-syntax/#bold165// where it says "... without spaces ...".166// In particular, `** bold **` does NOT work.167// This is NOT true for html, of course.168if (left.indexOf("<") != -1) {169// html - always has right set.170return left + text + right;171}172const { before, trimmed, after } = stripWhitespace(text);173if (trimmed.length == 0) {174// all whitespace, so don't mark it.175return text;176}177return `${before}${left}${trimmed}${right ?? left}${after}`;178}179180export function padLeft(s: string, n: number): string {181while (s.length < n) {182s = " " + s;183}184return s;185}186187export function padRight(s: string, n: number): string {188while (s.length < n) {189s += " ";190}191return s;192}193194export function padCenter(s: string, n: number): string {195while (s.length < n) {196s = " " + s + " ";197}198return s.slice(0, n);199}200201export const FOCUSED_COLOR = "#7eb6e2";202export const SELECTED_COLOR = "#1990ff";203/* This focused color is "Jupyter notebook classic" focused cell green. */204export const CODE_FOCUSED_COLOR = "#66bb6a";205export const CODE_FOCUSED_BACKGROUND = "#cfe8fc";206export const DARK_GREY_BORDER = "#cfcfcf";207208export function string_to_style(style: string): any {209const obj: any = {};210for (const x of style.split(";")) {211const j = x.indexOf("=");212if (j == -1) continue;213let key = x.slice(0, j);214const i = key.indexOf("-");215if (i != -1) {216key = x.slice(0, i) + capitalize(x.slice(i + 1));217}218obj[key] = x.slice(j + 1);219}220return obj;221}222223export const DEFAULT_CHILDREN = [{ text: "" }];224225export function removeBlankLines(s: string): string {226return s.replace(/^\s*\n/gm, "");227}228229230