12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109 |
- /**
- * While micromark is a lexer/tokenizer, the common case of going from markdown
- * to html is currently built in as this module, even though the parts can be
- * used separately to build ASTs, CSTs, or many other output formats.
- *
- * Having an HTML compiler built in is useful because it allows us to check for
- * compliancy to CommonMark, the de facto norm of markdown, specified in roughly
- * 600 input/output cases.
- *
- * This module has an interface that accepts lists of events instead of the
- * whole at once, however, because markdown can’t be truly streaming, we buffer
- * events before processing and outputting the final result.
- */
- /**
- * @typedef {import('micromark-util-types').Compile} Compile
- * @typedef {import('micromark-util-types').CompileContext} CompileContext
- * @typedef {import('micromark-util-types').CompileData} CompileData
- * @typedef {import('micromark-util-types').CompileOptions} CompileOptions
- * @typedef {import('micromark-util-types').Definition} Definition
- * @typedef {import('micromark-util-types').Event} Event
- * @typedef {import('micromark-util-types').Handle} Handle
- * @typedef {import('micromark-util-types').HtmlExtension} HtmlExtension
- * @typedef {import('micromark-util-types').NormalizedHtmlExtension} NormalizedHtmlExtension
- * @typedef {import('micromark-util-types').Token} Token
- */
- /**
- * @typedef Media
- * @property {boolean | undefined} [image]
- * @property {string | undefined} [labelId]
- * @property {string | undefined} [label]
- * @property {string | undefined} [referenceId]
- * @property {string | undefined} [destination]
- * @property {string | undefined} [title]
- */
- import {decodeNamedCharacterReference} from 'decode-named-character-reference'
- import {push} from 'micromark-util-chunked'
- import {combineHtmlExtensions} from 'micromark-util-combine-extensions'
- import {decodeNumericCharacterReference} from 'micromark-util-decode-numeric-character-reference'
- import {encode as _encode} from 'micromark-util-encode'
- import {normalizeIdentifier} from 'micromark-util-normalize-identifier'
- import {sanitizeUri} from 'micromark-util-sanitize-uri'
- const hasOwnProperty = {}.hasOwnProperty
- /**
- * These two are allowlists of safe protocols for full URLs in respectively the
- * `href` (on `<a>`) and `src` (on `<img>`) attributes.
- * They are based on what is allowed on GitHub,
- * <https://github.com/syntax-tree/hast-util-sanitize/blob/9275b21/lib/github.json#L31>
- */
- const protocolHref = /^(https?|ircs?|mailto|xmpp)$/i
- const protocolSrc = /^https?$/i
- /**
- * @param {CompileOptions | null | undefined} [options]
- * @returns {Compile}
- */
- export function compile(options) {
- const settings = options || {}
- /**
- * Tags is needed because according to markdown, links and emphasis and
- * whatnot can exist in images, however, as HTML doesn’t allow content in
- * images, the tags are ignored in the `alt` attribute, but the content
- * remains.
- *
- * @type {boolean | undefined}
- */
- let tags = true
- /**
- * An object to track identifiers to media (URLs and titles) defined with
- * definitions.
- *
- * @type {Record<string, Definition>}
- */
- const definitions = {}
- /**
- * A lot of the handlers need to capture some of the output data, modify it
- * somehow, and then deal with it.
- * We do that by tracking a stack of buffers, that can be opened (with
- * `buffer`) and closed (with `resume`) to access them.
- *
- * @type {Array<Array<string>>}
- */
- const buffers = [[]]
- /**
- * As we can have links in images and the other way around, where the deepest
- * ones are closed first, we need to track which one we’re in.
- *
- * @type {Array<Media>}
- */
- const mediaStack = []
- /**
- * Same as `mediaStack` for tightness, which is specific to lists.
- * We need to track if we’re currently in a tight or loose container.
- *
- * @type {Array<boolean>}
- */
- const tightStack = []
- /** @type {HtmlExtension} */
- const defaultHandlers = {
- enter: {
- blockQuote: onenterblockquote,
- codeFenced: onentercodefenced,
- codeFencedFenceInfo: buffer,
- codeFencedFenceMeta: buffer,
- codeIndented: onentercodeindented,
- codeText: onentercodetext,
- content: onentercontent,
- definition: onenterdefinition,
- definitionDestinationString: onenterdefinitiondestinationstring,
- definitionLabelString: buffer,
- definitionTitleString: buffer,
- emphasis: onenteremphasis,
- htmlFlow: onenterhtmlflow,
- htmlText: onenterhtml,
- image: onenterimage,
- label: buffer,
- link: onenterlink,
- listItemMarker: onenterlistitemmarker,
- listItemValue: onenterlistitemvalue,
- listOrdered: onenterlistordered,
- listUnordered: onenterlistunordered,
- paragraph: onenterparagraph,
- reference: buffer,
- resource: onenterresource,
- resourceDestinationString: onenterresourcedestinationstring,
- resourceTitleString: buffer,
- setextHeading: onentersetextheading,
- strong: onenterstrong
- },
- exit: {
- atxHeading: onexitatxheading,
- atxHeadingSequence: onexitatxheadingsequence,
- autolinkEmail: onexitautolinkemail,
- autolinkProtocol: onexitautolinkprotocol,
- blockQuote: onexitblockquote,
- characterEscapeValue: onexitdata,
- characterReferenceMarkerHexadecimal: onexitcharacterreferencemarker,
- characterReferenceMarkerNumeric: onexitcharacterreferencemarker,
- characterReferenceValue: onexitcharacterreferencevalue,
- codeFenced: onexitflowcode,
- codeFencedFence: onexitcodefencedfence,
- codeFencedFenceInfo: onexitcodefencedfenceinfo,
- codeFencedFenceMeta: onresumedrop,
- codeFlowValue: onexitcodeflowvalue,
- codeIndented: onexitflowcode,
- codeText: onexitcodetext,
- codeTextData: onexitdata,
- data: onexitdata,
- definition: onexitdefinition,
- definitionDestinationString: onexitdefinitiondestinationstring,
- definitionLabelString: onexitdefinitionlabelstring,
- definitionTitleString: onexitdefinitiontitlestring,
- emphasis: onexitemphasis,
- hardBreakEscape: onexithardbreak,
- hardBreakTrailing: onexithardbreak,
- htmlFlow: onexithtml,
- htmlFlowData: onexitdata,
- htmlText: onexithtml,
- htmlTextData: onexitdata,
- image: onexitmedia,
- label: onexitlabel,
- labelText: onexitlabeltext,
- lineEnding: onexitlineending,
- link: onexitmedia,
- listOrdered: onexitlistordered,
- listUnordered: onexitlistunordered,
- paragraph: onexitparagraph,
- reference: onresumedrop,
- referenceString: onexitreferencestring,
- resource: onresumedrop,
- resourceDestinationString: onexitresourcedestinationstring,
- resourceTitleString: onexitresourcetitlestring,
- setextHeading: onexitsetextheading,
- setextHeadingLineSequence: onexitsetextheadinglinesequence,
- setextHeadingText: onexitsetextheadingtext,
- strong: onexitstrong,
- thematicBreak: onexitthematicbreak
- }
- }
- /**
- * Combine the HTML extensions with the default handlers.
- * An HTML extension is an object whose fields are either `enter` or `exit`
- * (reflecting whether a token is entered or exited).
- * The values at such objects are names of tokens mapping to handlers.
- * Handlers are called, respectively when a token is opener or closed, with
- * that token, and a context as `this`.
- */
- const handlers =
- /** @type {NormalizedHtmlExtension} */
- combineHtmlExtensions(
- [defaultHandlers].concat(settings.htmlExtensions || [])
- )
- /**
- * Handlers do often need to keep track of some state.
- * That state is provided here as a key-value store (an object).
- *
- * @type {CompileData}
- */
- const data = {
- tightStack,
- definitions
- }
- /**
- * The context for handlers references a couple of useful functions.
- * In handlers from extensions, those can be accessed at `this`.
- * For the handlers here, they can be accessed directly.
- *
- * @type {Omit<CompileContext, 'sliceSerialize'>}
- */
- const context = {
- lineEndingIfNeeded,
- options: settings,
- encode,
- raw,
- tag,
- buffer,
- resume,
- setData,
- getData
- }
- /**
- * Generally, micromark copies line endings (`'\r'`, `'\n'`, `'\r\n'`) in the
- * markdown document over to the compiled HTML.
- * In some cases, such as `> a`, CommonMark requires that extra line endings
- * are added: `<blockquote>\n<p>a</p>\n</blockquote>`.
- * This variable hold the default line ending when given (or `undefined`),
- * and in the latter case will be updated to the first found line ending if
- * there is one.
- */
- let lineEndingStyle = settings.defaultLineEnding
- // Return the function that handles a slice of events.
- return compile
- /**
- * Deal w/ a slice of events.
- * Return either the empty string if there’s nothing of note to return, or the
- * result when done.
- *
- * @param {Array<Event>} events
- * @returns {string}
- */
- function compile(events) {
- let index = -1
- let start = 0
- /** @type {Array<number>} */
- const listStack = []
- // As definitions can come after references, we need to figure out the media
- // (urls and titles) defined by them before handling the references.
- // So, we do sort of what HTML does: put metadata at the start (in head), and
- // then put content after (`body`).
- /** @type {Array<Event>} */
- let head = []
- /** @type {Array<Event>} */
- let body = []
- while (++index < events.length) {
- // Figure out the line ending style used in the document.
- if (
- !lineEndingStyle &&
- (events[index][1].type === 'lineEnding' ||
- events[index][1].type === 'lineEndingBlank')
- ) {
- // @ts-expect-error Hush, it’s a line ending.
- lineEndingStyle = events[index][2].sliceSerialize(events[index][1])
- }
- // Preprocess lists to infer whether the list is loose or not.
- if (
- events[index][1].type === 'listOrdered' ||
- events[index][1].type === 'listUnordered'
- ) {
- if (events[index][0] === 'enter') {
- listStack.push(index)
- } else {
- prepareList(events.slice(listStack.pop(), index))
- }
- }
- // Move definitions to the front.
- if (events[index][1].type === 'definition') {
- if (events[index][0] === 'enter') {
- body = push(body, events.slice(start, index))
- start = index
- } else {
- head = push(head, events.slice(start, index + 1))
- start = index + 1
- }
- }
- }
- head = push(head, body)
- head = push(head, events.slice(start))
- index = -1
- const result = head
- // Handle the start of the document, if defined.
- if (handlers.enter.null) {
- handlers.enter.null.call(context)
- }
- // Handle all events.
- while (++index < events.length) {
- const handles = handlers[result[index][0]]
- const kind = result[index][1].type
- const handle = handles[kind]
- if (hasOwnProperty.call(handles, kind) && handle) {
- handle.call(
- Object.assign(
- {
- sliceSerialize: result[index][2].sliceSerialize
- },
- context
- ),
- result[index][1]
- )
- }
- }
- // Handle the end of the document, if defined.
- if (handlers.exit.null) {
- handlers.exit.null.call(context)
- }
- return buffers[0].join('')
- }
- /**
- * Figure out whether lists are loose or not.
- *
- * @param {Array<Event>} slice
- * @returns {undefined}
- */
- function prepareList(slice) {
- const length = slice.length
- let index = 0 // Skip open.
- let containerBalance = 0
- let loose = false
- /** @type {boolean | undefined} */
- let atMarker
- while (++index < length) {
- const event = slice[index]
- if (event[1]._container) {
- atMarker = undefined
- if (event[0] === 'enter') {
- containerBalance++
- } else {
- containerBalance--
- }
- } else
- switch (event[1].type) {
- case 'listItemPrefix': {
- if (event[0] === 'exit') {
- atMarker = true
- }
- break
- }
- case 'linePrefix': {
- // Ignore
- break
- }
- case 'lineEndingBlank': {
- if (event[0] === 'enter' && !containerBalance) {
- if (atMarker) {
- atMarker = undefined
- } else {
- loose = true
- }
- }
- break
- }
- default: {
- atMarker = undefined
- }
- }
- }
- slice[0][1]._loose = loose
- }
- /**
- * @type {CompileContext['setData']}
- */
- function setData(key, value) {
- // @ts-expect-error: assume `value` is omitted (`undefined` is passed) only
- // if allowed.
- data[key] = value
- }
- /**
- * @type {CompileContext['getData']}
- */
- function getData(key) {
- return data[key]
- }
- /** @type {CompileContext['buffer']} */
- function buffer() {
- buffers.push([])
- }
- /** @type {CompileContext['resume']} */
- function resume() {
- const buf = buffers.pop()
- return buf.join('')
- }
- /** @type {CompileContext['tag']} */
- function tag(value) {
- if (!tags) return
- setData('lastWasTag', true)
- buffers[buffers.length - 1].push(value)
- }
- /** @type {CompileContext['raw']} */
- function raw(value) {
- setData('lastWasTag')
- buffers[buffers.length - 1].push(value)
- }
- /**
- * Output an extra line ending.
- *
- * @returns {undefined}
- */
- function lineEnding() {
- raw(lineEndingStyle || '\n')
- }
- /** @type {CompileContext['lineEndingIfNeeded']} */
- function lineEndingIfNeeded() {
- const buffer = buffers[buffers.length - 1]
- const slice = buffer[buffer.length - 1]
- const previous = slice ? slice.charCodeAt(slice.length - 1) : null
- if (previous === 10 || previous === 13 || previous === null) {
- return
- }
- lineEnding()
- }
- /** @type {CompileContext['encode']} */
- function encode(value) {
- return getData('ignoreEncode') ? value : _encode(value)
- }
- //
- // Handlers.
- //
- /**
- * @returns {undefined}
- */
- function onresumedrop() {
- resume()
- }
- /**
- * @this {CompileContext}
- * @type {Handle}
- */
- function onenterlistordered(token) {
- tightStack.push(!token._loose)
- lineEndingIfNeeded()
- tag('<ol')
- setData('expectFirstItem', true)
- }
- /**
- * @this {CompileContext}
- * @type {Handle}
- */
- function onenterlistunordered(token) {
- tightStack.push(!token._loose)
- lineEndingIfNeeded()
- tag('<ul')
- setData('expectFirstItem', true)
- }
- /**
- * @this {CompileContext}
- * @type {Handle}
- */
- function onenterlistitemvalue(token) {
- if (getData('expectFirstItem')) {
- const value = Number.parseInt(this.sliceSerialize(token), 10)
- if (value !== 1) {
- tag(' start="' + encode(String(value)) + '"')
- }
- }
- }
- /**
- * @returns {undefined}
- */
- function onenterlistitemmarker() {
- if (getData('expectFirstItem')) {
- tag('>')
- } else {
- onexitlistitem()
- }
- lineEndingIfNeeded()
- tag('<li>')
- setData('expectFirstItem')
- // “Hack” to prevent a line ending from showing up if the item is empty.
- setData('lastWasTag')
- }
- /**
- * @returns {undefined}
- */
- function onexitlistordered() {
- onexitlistitem()
- tightStack.pop()
- lineEnding()
- tag('</ol>')
- }
- /**
- * @returns {undefined}
- */
- function onexitlistunordered() {
- onexitlistitem()
- tightStack.pop()
- lineEnding()
- tag('</ul>')
- }
- /**
- * @returns {undefined}
- */
- function onexitlistitem() {
- if (getData('lastWasTag') && !getData('slurpAllLineEndings')) {
- lineEndingIfNeeded()
- }
- tag('</li>')
- setData('slurpAllLineEndings')
- }
- /**
- * @this {CompileContext}
- * @type {Handle}
- */
- function onenterblockquote() {
- tightStack.push(false)
- lineEndingIfNeeded()
- tag('<blockquote>')
- }
- /**
- * @this {CompileContext}
- * @type {Handle}
- */
- function onexitblockquote() {
- tightStack.pop()
- lineEndingIfNeeded()
- tag('</blockquote>')
- setData('slurpAllLineEndings')
- }
- /**
- * @this {CompileContext}
- * @type {Handle}
- */
- function onenterparagraph() {
- if (!tightStack[tightStack.length - 1]) {
- lineEndingIfNeeded()
- tag('<p>')
- }
- setData('slurpAllLineEndings')
- }
- /**
- * @this {CompileContext}
- * @type {Handle}
- */
- function onexitparagraph() {
- if (tightStack[tightStack.length - 1]) {
- setData('slurpAllLineEndings', true)
- } else {
- tag('</p>')
- }
- }
- /**
- * @this {CompileContext}
- * @type {Handle}
- */
- function onentercodefenced() {
- lineEndingIfNeeded()
- tag('<pre><code')
- setData('fencesCount', 0)
- }
- /**
- * @this {CompileContext}
- * @type {Handle}
- */
- function onexitcodefencedfenceinfo() {
- const value = resume()
- tag(' class="language-' + value + '"')
- }
- /**
- * @this {CompileContext}
- * @type {Handle}
- */
- function onexitcodefencedfence() {
- const count = getData('fencesCount') || 0
- if (!count) {
- tag('>')
- setData('slurpOneLineEnding', true)
- }
- setData('fencesCount', count + 1)
- }
- /**
- * @this {CompileContext}
- * @type {Handle}
- */
- function onentercodeindented() {
- lineEndingIfNeeded()
- tag('<pre><code>')
- }
- /**
- * @this {CompileContext}
- * @type {Handle}
- */
- function onexitflowcode() {
- const count = getData('fencesCount')
- // One special case is if we are inside a container, and the fenced code was
- // not closed (meaning it runs to the end).
- // In that case, the following line ending, is considered *outside* the
- // fenced code and block quote by micromark, but CM wants to treat that
- // ending as part of the code.
- if (
- count !== undefined &&
- count < 2 &&
- data.tightStack.length > 0 &&
- !getData('lastWasTag')
- ) {
- lineEnding()
- }
- // But in most cases, it’s simpler: when we’ve seen some data, emit an extra
- // line ending when needed.
- if (getData('flowCodeSeenData')) {
- lineEndingIfNeeded()
- }
- tag('</code></pre>')
- if (count !== undefined && count < 2) lineEndingIfNeeded()
- setData('flowCodeSeenData')
- setData('fencesCount')
- setData('slurpOneLineEnding')
- }
- /**
- * @this {CompileContext}
- * @type {Handle}
- */
- function onenterimage() {
- mediaStack.push({
- image: true
- })
- tags = undefined // Disallow tags.
- }
- /**
- * @this {CompileContext}
- * @type {Handle}
- */
- function onenterlink() {
- mediaStack.push({})
- }
- /**
- * @this {CompileContext}
- * @type {Handle}
- */
- function onexitlabeltext(token) {
- mediaStack[mediaStack.length - 1].labelId = this.sliceSerialize(token)
- }
- /**
- * @this {CompileContext}
- * @type {Handle}
- */
- function onexitlabel() {
- mediaStack[mediaStack.length - 1].label = resume()
- }
- /**
- * @this {CompileContext}
- * @type {Handle}
- */
- function onexitreferencestring(token) {
- mediaStack[mediaStack.length - 1].referenceId = this.sliceSerialize(token)
- }
- /**
- * @this {CompileContext}
- * @type {Handle}
- */
- function onenterresource() {
- buffer() // We can have line endings in the resource, ignore them.
- mediaStack[mediaStack.length - 1].destination = ''
- }
- /**
- * @this {CompileContext}
- * @type {Handle}
- */
- function onenterresourcedestinationstring() {
- buffer()
- // Ignore encoding the result, as we’ll first percent encode the url and
- // encode manually after.
- setData('ignoreEncode', true)
- }
- /**
- * @this {CompileContext}
- * @type {Handle}
- */
- function onexitresourcedestinationstring() {
- mediaStack[mediaStack.length - 1].destination = resume()
- setData('ignoreEncode')
- }
- /**
- * @this {CompileContext}
- * @type {Handle}
- */
- function onexitresourcetitlestring() {
- mediaStack[mediaStack.length - 1].title = resume()
- }
- /**
- * @this {CompileContext}
- * @type {Handle}
- */
- function onexitmedia() {
- let index = mediaStack.length - 1 // Skip current.
- const media = mediaStack[index]
- const id = media.referenceId || media.labelId
- const context =
- media.destination === undefined
- ? definitions[normalizeIdentifier(id)]
- : media
- tags = true
- while (index--) {
- if (mediaStack[index].image) {
- tags = undefined
- break
- }
- }
- if (media.image) {
- tag(
- '<img src="' +
- sanitizeUri(
- context.destination,
- settings.allowDangerousProtocol ? undefined : protocolSrc
- ) +
- '" alt="'
- )
- raw(media.label)
- tag('"')
- } else {
- tag(
- '<a href="' +
- sanitizeUri(
- context.destination,
- settings.allowDangerousProtocol ? undefined : protocolHref
- ) +
- '"'
- )
- }
- tag(context.title ? ' title="' + context.title + '"' : '')
- if (media.image) {
- tag(' />')
- } else {
- tag('>')
- raw(media.label)
- tag('</a>')
- }
- mediaStack.pop()
- }
- /**
- * @this {CompileContext}
- * @type {Handle}
- */
- function onenterdefinition() {
- buffer()
- mediaStack.push({})
- }
- /**
- * @this {CompileContext}
- * @type {Handle}
- */
- function onexitdefinitionlabelstring(token) {
- // Discard label, use the source content instead.
- resume()
- mediaStack[mediaStack.length - 1].labelId = this.sliceSerialize(token)
- }
- /**
- * @this {CompileContext}
- * @type {Handle}
- */
- function onenterdefinitiondestinationstring() {
- buffer()
- setData('ignoreEncode', true)
- }
- /**
- * @this {CompileContext}
- * @type {Handle}
- */
- function onexitdefinitiondestinationstring() {
- mediaStack[mediaStack.length - 1].destination = resume()
- setData('ignoreEncode')
- }
- /**
- * @this {CompileContext}
- * @type {Handle}
- */
- function onexitdefinitiontitlestring() {
- mediaStack[mediaStack.length - 1].title = resume()
- }
- /**
- * @this {CompileContext}
- * @type {Handle}
- */
- function onexitdefinition() {
- const media = mediaStack[mediaStack.length - 1]
- const id = normalizeIdentifier(media.labelId)
- resume()
- if (!hasOwnProperty.call(definitions, id)) {
- definitions[id] = mediaStack[mediaStack.length - 1]
- }
- mediaStack.pop()
- }
- /**
- * @this {CompileContext}
- * @type {Handle}
- */
- function onentercontent() {
- setData('slurpAllLineEndings', true)
- }
- /**
- * @this {CompileContext}
- * @type {Handle}
- */
- function onexitatxheadingsequence(token) {
- // Exit for further sequences.
- if (getData('headingRank')) return
- setData('headingRank', this.sliceSerialize(token).length)
- lineEndingIfNeeded()
- tag('<h' + getData('headingRank') + '>')
- }
- /**
- * @this {CompileContext}
- * @type {Handle}
- */
- function onentersetextheading() {
- buffer()
- setData('slurpAllLineEndings')
- }
- /**
- * @this {CompileContext}
- * @type {Handle}
- */
- function onexitsetextheadingtext() {
- setData('slurpAllLineEndings', true)
- }
- /**
- * @this {CompileContext}
- * @type {Handle}
- */
- function onexitatxheading() {
- tag('</h' + getData('headingRank') + '>')
- setData('headingRank')
- }
- /**
- * @this {CompileContext}
- * @type {Handle}
- */
- function onexitsetextheadinglinesequence(token) {
- setData(
- 'headingRank',
- this.sliceSerialize(token).charCodeAt(0) === 61 ? 1 : 2
- )
- }
- /**
- * @this {CompileContext}
- * @type {Handle}
- */
- function onexitsetextheading() {
- const value = resume()
- lineEndingIfNeeded()
- tag('<h' + getData('headingRank') + '>')
- raw(value)
- tag('</h' + getData('headingRank') + '>')
- setData('slurpAllLineEndings')
- setData('headingRank')
- }
- /**
- * @this {CompileContext}
- * @type {Handle}
- */
- function onexitdata(token) {
- raw(encode(this.sliceSerialize(token)))
- }
- /**
- * @this {CompileContext}
- * @type {Handle}
- */
- function onexitlineending(token) {
- if (getData('slurpAllLineEndings')) {
- return
- }
- if (getData('slurpOneLineEnding')) {
- setData('slurpOneLineEnding')
- return
- }
- if (getData('inCodeText')) {
- raw(' ')
- return
- }
- raw(encode(this.sliceSerialize(token)))
- }
- /**
- * @this {CompileContext}
- * @type {Handle}
- */
- function onexitcodeflowvalue(token) {
- raw(encode(this.sliceSerialize(token)))
- setData('flowCodeSeenData', true)
- }
- /**
- * @this {CompileContext}
- * @type {Handle}
- */
- function onexithardbreak() {
- tag('<br />')
- }
- /**
- * @returns {undefined}
- */
- function onenterhtmlflow() {
- lineEndingIfNeeded()
- onenterhtml()
- }
- /**
- * @returns {undefined}
- */
- function onexithtml() {
- setData('ignoreEncode')
- }
- /**
- * @returns {undefined}
- */
- function onenterhtml() {
- if (settings.allowDangerousHtml) {
- setData('ignoreEncode', true)
- }
- }
- /**
- * @returns {undefined}
- */
- function onenteremphasis() {
- tag('<em>')
- }
- /**
- * @returns {undefined}
- */
- function onenterstrong() {
- tag('<strong>')
- }
- /**
- * @returns {undefined}
- */
- function onentercodetext() {
- setData('inCodeText', true)
- tag('<code>')
- }
- /**
- * @returns {undefined}
- */
- function onexitcodetext() {
- setData('inCodeText')
- tag('</code>')
- }
- /**
- * @returns {undefined}
- */
- function onexitemphasis() {
- tag('</em>')
- }
- /**
- * @returns {undefined}
- */
- function onexitstrong() {
- tag('</strong>')
- }
- /**
- * @returns {undefined}
- */
- function onexitthematicbreak() {
- lineEndingIfNeeded()
- tag('<hr />')
- }
- /**
- * @this {CompileContext}
- * @param {Token} token
- * @returns {undefined}
- */
- function onexitcharacterreferencemarker(token) {
- setData('characterReferenceType', token.type)
- }
- /**
- * @this {CompileContext}
- * @type {Handle}
- */
- function onexitcharacterreferencevalue(token) {
- let value = this.sliceSerialize(token)
- // @ts-expect-error `decodeNamedCharacterReference` can return false for
- // invalid named character references, but everything we’ve tokenized is
- // valid.
- value = getData('characterReferenceType')
- ? decodeNumericCharacterReference(
- value,
- getData('characterReferenceType') ===
- 'characterReferenceMarkerNumeric'
- ? 10
- : 16
- )
- : decodeNamedCharacterReference(value)
- raw(encode(value))
- setData('characterReferenceType')
- }
- /**
- * @this {CompileContext}
- * @type {Handle}
- */
- function onexitautolinkprotocol(token) {
- const uri = this.sliceSerialize(token)
- tag(
- '<a href="' +
- sanitizeUri(
- uri,
- settings.allowDangerousProtocol ? undefined : protocolHref
- ) +
- '">'
- )
- raw(encode(uri))
- tag('</a>')
- }
- /**
- * @this {CompileContext}
- * @type {Handle}
- */
- function onexitautolinkemail(token) {
- const uri = this.sliceSerialize(token)
- tag('<a href="' + sanitizeUri('mailto:' + uri) + '">')
- raw(encode(uri))
- tag('</a>')
- }
- }
|