import cheerio from 'cheerio' import crypto from 'crypto' import ejs from 'ejs' import scopeCss from '@patarapolw/scope-css' import showdown from 'showdown' export class MakeHtml { $ = cheerio.load('') mdConverter = new showdown.Converter({ noHeaderId: true, parseImgDimensions: true, simplifiedAutoLink: true, // literalMidWordUnderscores: true, strikethrough: true, tables: true, tasklists: true, // simpleLineBreaks: true, // openLinksInNewWindow: true, backslashEscapesHTMLTags: true, emoji: true, underline: true, disableForced4SpacesIndentedSublists: true, metadata: true }) constructor () { this.mdConverter.addExtension( { type: 'lang', regex: /\\\n/g, replace: '
' }, 'commonmark-simple-linebreak' ) /** * ! For some reasons default underscore italic fails to render... */ this.mdConverter.addExtension( { type: 'lang', regex: /([^\\])_{1}(.*?[^\\])_{1}/g, replace: '$1$2' }, 'underscore-italic' ) /** * ! This is the only way to preserve syntax highlighting, while output multiline strings correctly */ this.mdConverter.addExtension( { type: 'lang', regex: /(^|\n)```html parsed([^]+)\n```\n/g, replace: (_: string, p1: string, p2: string) => { return ( p1 + ejs.render(p2, { rmWhitespace: true, beautify: false }) ) } }, 'html-parsed' ) } async parse ( filename: string ): Promise<{ html: string text: string }> { const id = 'md-' + crypto .createHash('sha256') .update(filename) .digest() .toString('hex') .substr(0, 8) const $div = this.$(`
`) $div.html(await this.getHTML(filename)) $div.find('style').each((_, el) => { const $el = this.$(el) const html = $el.html() || '' if (html.trim()) { $el.html(scopeCss(html, `#${id}`)) } }) $div.find('a').each((_, el) => { const $el = this.$(el) const href = $el.attr('href') || '' if (/^https?:\/\//.test(href)) { $el.attr({ rel: 'noopener', target: '_blank' }) } }) return { html: this.$('
').append($div).html() || '', text: $div.text().trim() } } private async getHTML (filename: string) { return this.mdConverter.makeHtml( await ejs.renderFile(filename, { youtube: (id: string) => ejs.render( /* html */ `
`, { rmWhitespace: true, beautify: false } ), xCard: (opts: { href: string image?: string title: string description?: string }) => this.$('
') .append( this.$('') .attr(opts) .append( this.$('') .attr({ href: opts.href, rel: 'noopener', target: '_blank' }) .text(opts.title) ) ) .html() }) ) } }