/** * @typedef {import('micromark-util-types').HtmlExtension} HtmlExtension * @typedef {import('./syntax.js').Align} Align */ const alignment = { none: '', left: ' align="left"', right: ' align="right"', center: ' align="center"' } /** * HTML extension for micromark (passed in `htmlExtensions`). * * @type {HtmlExtension} */ export const gfmTableHtml = { enter: { table(token) { /** @type {Array} */ // @ts-expect-error Custom. const tableAlign = token._align this.lineEndingIfNeeded() this.tag('') this.setData('tableAlign', tableAlign) }, tableBody() { // Clear slurping line ending from the delimiter row. this.setData('slurpOneLineEnding') this.tag('') }, tableData() { const tableAlign = /** @type {Array} */ ( this.getData('tableAlign') ) const tableColumn = /** @type {number} */ (this.getData('tableColumn')) const align = alignment[tableAlign[tableColumn]] if (align === undefined) { // Capture results to ignore them. this.buffer() } else { this.lineEndingIfNeeded() this.tag('') } }, tableHead() { this.lineEndingIfNeeded() this.tag('') }, tableHeader() { const tableAlign = /** @type {Array} */ ( this.getData('tableAlign') ) const tableColumn = /** @type {number} */ (this.getData('tableColumn')) const align = alignment[tableAlign[tableColumn]] this.lineEndingIfNeeded() this.tag('') }, tableRow() { this.setData('tableColumn', 0) this.lineEndingIfNeeded() this.tag('') } }, exit: { // Overwrite the default code text data handler to unescape escaped pipes when // they are in tables. codeTextData(token) { let value = this.sliceSerialize(token) if (this.getData('tableAlign')) { value = value.replace(/\\([\\|])/g, replace) } this.raw(this.encode(value)) }, table() { this.setData('tableAlign') // If there was no table body, make sure the slurping from the delimiter row // is cleared. this.setData('slurpAllLineEndings') this.lineEndingIfNeeded() this.tag('
') }, tableBody() { this.lineEndingIfNeeded() this.tag('') }, tableData() { const tableAlign = /** @type {Array} */ ( this.getData('tableAlign') ) const tableColumn = /** @type {number} */ (this.getData('tableColumn')) if (tableColumn in tableAlign) { this.tag('') this.setData('tableColumn', tableColumn + 1) } else { // Stop capturing. this.resume() } }, tableHead() { this.lineEndingIfNeeded() this.tag('') this.setData('slurpOneLineEnding', true) // Slurp the line ending from the delimiter row. }, tableHeader() { const tableColumn = /** @type {number} */ (this.getData('tableColumn')) this.tag('') this.setData('tableColumn', tableColumn + 1) }, tableRow() { const tableAlign = /** @type {Array} */ ( this.getData('tableAlign') ) let tableColumn = /** @type {number} */ (this.getData('tableColumn')) while (tableColumn < tableAlign.length) { this.lineEndingIfNeeded() this.tag('') tableColumn++ } this.setData('tableColumn', tableColumn) this.lineEndingIfNeeded() this.tag('') } } } /** * @param {string} $0 * @param {string} $1 * @returns {string} */ function replace($0, $1) { // Pipes work, backslashes don’t (but can’t escape pipes). return $1 === '|' ? $1 : $0 }