export function renderMarkdown()

in pxtlib/docsrender.ts [465:714]


    export function renderMarkdown(opts: RenderOptions): string {
        let hasPubInfo = true

        if (!opts.pubinfo) {
            hasPubInfo = false
            opts.pubinfo = {}
        }

        let pubinfo = opts.pubinfo

        if (!opts.theme) opts.theme = {}

        delete opts.pubinfo["private"] // just in case

        if (pubinfo["time"]) {
            let tm = parseInt(pubinfo["time"])
            if (!pubinfo["timems"])
                pubinfo["timems"] = 1000 * tm + ""
            if (!pubinfo["humantime"])
                pubinfo["humantime"] = U.isoTime(tm)
        }
        if (pubinfo["name"]) {
            pubinfo["dirname"] = pubinfo["name"].replace(/[^A-Za-z0-9_]/g, "-")
            pubinfo["title"] = pubinfo["name"]
        }

        if (hasPubInfo) {
            pubinfo["JSON"] = JSON.stringify(pubinfo, null, 4).replace(/</g, "\\u003c")
        }

        let template = opts.template
        template = template
            .replace(/<!--\s*@include\s+(\S+)\s*-->/g,
                (full, fn) => {
                    let cont = (opts.theme.htmlDocIncludes || {})[fn] || ""
                    return "<!-- include " + fn + " -->\n" + cont + "\n<!-- end include -->\n"
                })


        template = renderConditionalMacros(template, pubinfo);

        if (opts.locale)
            template = translate(template, opts.locale).text

        let d: RenderData = {
            html: template,
            theme: opts.theme,
            filepath: opts.filepath,
            versionPath: opts.versionPath,
            ghEditURLs: opts.ghEditURLs,
            params: pubinfo,
            TOC: opts.TOC
        }
        prepTemplate(d)

        if (!markedInstance) {
            markedInstance = requireMarked();
        }

        // We have to re-create the renderer every time to avoid the link() function's closure capturing the opts
        let renderer = new markedInstance.Renderer()
        setupRenderer(renderer);
        const linkRenderer = renderer.link;
        renderer.link = function (href: string, title: string, text: string) {
            const relative = new RegExp('^[/#]').test(href);
            const target = !relative ? '_blank' : '';
            if (relative && d.versionPath) href = `/${d.versionPath}${href}`;
            const html = linkRenderer.call(renderer, href, title, text);
            return html.replace(/^<a /, `<a ${target ? `target="${target}"` : ''} rel="nofollow noopener" `);
        };

        let sanitizer = requireDOMSanitizer();
        markedInstance.setOptions({
            renderer: renderer,
            gfm: true,
            tables: true,
            breaks: false,
            pedantic: false,
            sanitize: true,
            sanitizer: sanitizer,
            smartLists: true,
            smartypants: true
        });

        let markdown = opts.markdown

        // append repo info if any
        if (opts.repo)
            markdown += `
\`\`\`package
${opts.repo.name.replace(/^pxt-/, '')}=github:${opts.repo.fullName}#${opts.repo.tag || "master"}
\`\`\`
`;

        //Uses the CmdLink definitions to replace links to YouTube and Vimeo (limited at the moment)
        markdown = markdown.replace(/^\s*https?:\/\/(\S+)\s*$/mg, (f, lnk) => {
            for (let ent of links) {
                let m = ent.rx.exec(lnk)
                if (m) {
                    return ent.cmd.replace(/\$(\d+)/g, (f, k) => {
                        return m[parseInt(k)] || ""
                    }) + "\n"
                }
            }
            return f
        })

        // replace pre-template in markdown
        markdown = markdown.replace(/@([a-z]+)@/ig, (m, param) => {
            let macro = pubinfo[param];
            if (!macro && opts.throwOnError)
                U.userError(`unknown macro ${param}`);
            return macro || 'unknown macro'
        });

        let html = markedInstance(markdown)

        // support for breaks which somehow don't work out of the box
        html = html.replace(/&lt;br\s*\/&gt;/ig, "<br/>");

        // github will render images if referenced as ![](/docs/static/foo.png)
        // we require /static/foo.png
        html = html.replace(/(<img [^>]* src=")\/docs\/static\/([^">]+)"/g,
            (f, pref, addr) => pref + '/static/' + addr + '"')

        let endBox = ""
        let boxSize = 0;
        function appendEndBox(size: number, box: string, html: string): string {
            let r = html;
            if (size <= boxSize) {
                r = endBox + r;
                endBox = "";
                boxSize = 0;
            }
            return r;
        }

        html = html.replace(/<h(\d)[^>]+>\s*([~@])?\s*(.*?)<\/h\d>/g, (f, lvl, tp, body) => {
            let m = /^(\w+)\s+(.*)/.exec(body)
            let cmd = m ? m[1] : body
            let args = m ? m[2] : ""
            let rawArgs = args
            args = html2Quote(args)
            cmd = html2Quote(cmd)
            lvl = parseInt(lvl);

            if (!tp) {
                return appendEndBox(lvl, endBox, f);
            } else if (tp == "@") {
                let expansion = U.lookup(d.settings, cmd)
                if (expansion != null) {
                    pubinfo[cmd] = args
                } else {
                    expansion = U.lookup(d.macros, cmd)
                    if (expansion == null) {
                        if (opts.throwOnError)
                            U.userError(`Unknown command: @${cmd}`);
                        return error(`Unknown command: @${cmd}`)
                    }
                }

                let ivars: Map<string> = {
                    ARGS: args,
                    CMD: cmd
                }

                return appendEndBox(lvl, endBox, injectHtml(expansion, ivars, ["ARGS", "CMD"]))
            } else {
                if (!cmd) {
                    let r = endBox
                    endBox = ""
                    return r
                }

                let box = U.lookup(d.boxes, cmd)
                if (box) {
                    let parts = box.split("@BODY@")
                    let r = appendEndBox(lvl, endBox, parts[0].replace("@ARGS@", args));
                    endBox = parts[1];

                    let attrs = box.match(/data-[^>\s]+/ig);
                    if (attrs && attrs.indexOf('data-inferred') >= 0) {
                        boxSize = lvl;
                    }
                    return r;
                } else {
                    if (opts.throwOnError)
                        U.userError(`Unknown box: ~ ${cmd}`);
                    return error(`Unknown box: ~ ${cmd}`)
                }
            }
        })

        if (endBox) html = html + endBox;

        if (!pubinfo["title"]) {
            let titleM = /<h1[^<>]*>([^<>]+)<\/h1>/.exec(html)
            if (titleM)
                pubinfo["title"] = html2Quote(titleM[1])
        }

        if (!pubinfo["description"]) {
            let descM = /<p>([^]+?)<\/p>/.exec(html)
            if (descM)
                pubinfo["description"] = html2Quote(descM[1])
        }

        // try getting a better custom image for twitter
        const imgM = /<div class="ui embed mdvid"[^<>]+?data-placeholder="([^"]+)"[^>]*\/?>/i.exec(html)
            || /<img class="ui [^"]*image" src="([^"]+)"[^>]*\/?>/i.exec(html);
        if (imgM)
            pubinfo["cardLogo"] = html2Quote(imgM[1]);

        pubinfo["twitter"] = html2Quote(opts.theme.twitter || "@msmakecode");

        let registers: Map<string> = {}
        registers["main"] = "" // first

        html = html.replace(/<!-- BEGIN-ASIDE (\S+) -->([^]*?)<!-- END-ASIDE -->/g, (f, nam, cont) => {
            let s = U.lookup(registers, nam)
            registers[nam] = (s || "") + cont
            return "<!-- aside -->"
        })

        // fix up spourious newlines at the end of code blocks
        html = html.replace(/\n<\/code>/g, "</code>")

        registers["main"] = html

        let injectBody = (tmpl: string, body: string) =>
            injectHtml(d.boxes[tmpl] || "@BODY@", { BODY: body }, ["BODY"])

        html = ""

        for (let k of Object.keys(registers)) {
            html += injectBody(k + "-container", registers[k])
        }

        pubinfo["body"] = html
        // don't mangle target name in title, it is already in the sitename
        pubinfo["name"] = pubinfo["title"] || ""

        for (let k of Object.keys(opts.theme)) {
            let v = (opts.theme as any)[k]
            if (typeof v == "string")
                pubinfo["theme_" + k] = v
        }

        return d.finish()
    }