Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/src/packages/frontend/markdown/index.ts
Views: 686
/*1* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.2* License: MS-RSL – see LICENSE.md for details3*/45/*6Conversion from Markdown *to* HTML, trying not to horribly mangle math.78We also define and configure our Markdown parsers below, which are used9in other code directly, e.g, in supporting use of the slate editor.10```11*/1213export * from "./types";14export * from "./table-of-contents";1516import * as cheerio from "cheerio";17import MarkdownIt from "markdown-it";18import emojiPlugin from "markdown-it-emoji";19import { checkboxPlugin } from "./checkbox-plugin";20import { hashtagPlugin } from "./hashtag-plugin";21import { mentionPlugin } from "./mentions-plugin";22import mathPlugin from "./math-plugin";23export { parseHeader } from "./header";24import Markdown from "./component";25export { Markdown };2627const MarkdownItFrontMatter = require("markdown-it-front-matter");2829export const OPTIONS: MarkdownIt.Options = {30html: true,31typographer: false,32linkify: true,33breaks: false, // breaks=true is NOT liked by many devs.34};3536const PLUGINS = [37[38mathPlugin,39{40delimiters: "cocalc",41engine: {42renderToString: (tex, options) => {43// We **used to** need to continue to support rendering to MathJax as an option,44// but texmath only supports katex. Thus we output by default to45// html using script tags, which are then parsed later using our46// katex/mathjax plugin.47// We no longer support MathJax, so maybe this can be simplified?48return `<script type="math/tex${49options.displayMode ? "; mode=display" : ""50}">${tex}</script>`;51},52},53},54],55[emojiPlugin],56[checkboxPlugin],57[hashtagPlugin],58[mentionPlugin],59];6061function usePlugins(m, plugins) {62for (const [plugin, options] of plugins) {63m.use(plugin, options);64}65}6667export const markdown_it = new MarkdownIt(OPTIONS);68usePlugins(markdown_it, PLUGINS);6970/*71export function markdownParser() {72const m = new MarkdownIt(OPTIONS);73usePlugins(m, PLUGINS);74return m;75}*/7677/*78Inject line numbers for sync.79- We track only headings and paragraphs, at any level.80- TODO Footnotes content causes jumps. Level limit filters it automatically.8182See https://github.com/digitalmoksha/markdown-it-inject-linenumbers/blob/master/index.js83*/84function inject_linenumbers_plugin(md) {85function injectLineNumbers(tokens, idx, options, env, slf) {86if (tokens[idx].map) {87const line = tokens[idx].map[0];88tokens[idx].attrJoin("class", "source-line");89tokens[idx].attrSet("data-source-line", String(line));90}91return slf.renderToken(tokens, idx, options, env, slf);92}9394md.renderer.rules.paragraph_open = injectLineNumbers;95md.renderer.rules.heading_open = injectLineNumbers;96md.renderer.rules.list_item_open = injectLineNumbers;97md.renderer.rules.table_open = injectLineNumbers;98}99const markdown_it_line_numbers = new MarkdownIt(OPTIONS);100markdown_it_line_numbers.use(inject_linenumbers_plugin);101usePlugins(markdown_it_line_numbers, PLUGINS);102103/*104Turn the given markdown *string* into an HTML *string*.105We heuristically try to remove and put back the math via106remove_math, so that markdown itself doesn't107mangle it too much before Mathjax/Katex finally see it.108Note that remove_math is NOT perfect, e.g., it messes up109110<a href="http://abc" class="foo-$">test $</a>111112However, at least it is based on code in Jupyter classical,113so agrees with them, so people are used it it as a "standard".114115See https://github.com/sagemathinc/cocalc/issues/2863116for another example where remove_math is annoying.117*/118119export interface MD2html {120html: string;121frontmatter: string;122}123124interface Options {125line_numbers?: boolean; // if given, embed extra line number info useful for inverse/forward search.126processMath?: (string) => string; // if given, apply this function to all the math127}128129function process(130markdown_string: string,131mode: "default" | "frontmatter",132options?: Options,133): MD2html {134let text = markdown_string;135if (typeof text != "string") {136console.warn(137"WARNING: called markdown process with non-string input",138text,139);140// this function can get used for rendering markdown errors, and it's better141// to show something then blow up in our face.142text = JSON.stringify(text);143}144145let html: string;146let frontmatter = "";147148// avoid instantiating a new markdown object for normal md processing149if (mode == "frontmatter") {150const md_frontmatter = new MarkdownIt(OPTIONS).use(151MarkdownItFrontMatter,152(fm) => {153frontmatter = fm;154},155);156html = md_frontmatter.render(text);157} else {158if (options?.line_numbers) {159html = markdown_it_line_numbers.render(text);160} else {161html = markdown_it.render(text);162}163}164return { html, frontmatter };165}166167export function markdown_to_html_frontmatter(s: string): MD2html {168return process(s, "frontmatter");169}170171export function markdown_to_html(s: string, options?: Options): string {172return process(s, "default", options).html;173}174175export function markdown_to_cheerio(s: string, options?: Options) {176return cheerio.load(`<div>${markdown_to_html(s, options)}</div>`);177}178179180