/** * @import {Handle, Info, State} from 'mdast-util-to-markdown' * @import {PhrasingParents} from '../types.js' */ import {encodeCharacterReference} from './encode-character-reference.js' /** * Serialize the children of a parent that contains phrasing children. * * These children will be joined flush together. * * @param {PhrasingParents} parent * Parent of flow nodes. * @param {State} state * Info passed around about the current state. * @param {Info} info * Info on where we are in the document we are generating. * @returns {string} * Serialized children, joined together. */ export function containerPhrasing(parent, state, info) { const indexStack = state.indexStack const children = parent.children || [] /** @type {Array} */ const results = [] let index = -1 let before = info.before /** @type {string | undefined} */ let encodeAfter indexStack.push(-1) let tracker = state.createTracker(info) while (++index < children.length) { const child = children[index] /** @type {string} */ let after indexStack[indexStack.length - 1] = index if (index + 1 < children.length) { /** @type {Handle} */ // @ts-expect-error: hush, it’s actually a `zwitch`. let handle = state.handle.handlers[children[index + 1].type] /** @type {Handle} */ // @ts-expect-error: hush, it’s actually a `zwitch`. if (handle && handle.peek) handle = handle.peek after = handle ? handle(children[index + 1], parent, state, { before: '', after: '', ...tracker.current() }).charAt(0) : '' } else { after = info.after } // In some cases, html (text) can be found in phrasing right after an eol. // When we’d serialize that, in most cases that would be seen as html // (flow). // As we can’t escape or so to prevent it from happening, we take a somewhat // reasonable approach: replace that eol with a space. // See: if ( results.length > 0 && (before === '\r' || before === '\n') && child.type === 'html' ) { results[results.length - 1] = results[results.length - 1].replace( /(\r?\n|\r)$/, ' ' ) before = ' ' // To do: does this work to reset tracker? tracker = state.createTracker(info) tracker.move(results.join('')) } let value = state.handle(child, parent, state, { ...tracker.current(), after, before }) // If we had to encode the first character after the previous node and it’s // still the same character, // encode it. if (encodeAfter && encodeAfter === value.slice(0, 1)) { value = encodeCharacterReference(encodeAfter.charCodeAt(0)) + value.slice(1) } const encodingInfo = state.attentionEncodeSurroundingInfo state.attentionEncodeSurroundingInfo = undefined encodeAfter = undefined // If we have to encode the first character before the current node and // it’s still the same character, // encode it. if (encodingInfo) { if ( results.length > 0 && encodingInfo.before && before === results[results.length - 1].slice(-1) ) { results[results.length - 1] = results[results.length - 1].slice(0, -1) + encodeCharacterReference(before.charCodeAt(0)) } if (encodingInfo.after) encodeAfter = after } tracker.move(value) results.push(value) before = value.slice(-1) } indexStack.pop() return results.join('') }