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