/**
* @import {HtmlOptions as Options} from 'micromark-extension-gfm-footnote'
* @import {HtmlExtension} from 'micromark-util-types'
*/
import { normalizeIdentifier } from 'micromark-util-normalize-identifier';
import { sanitizeUri } from 'micromark-util-sanitize-uri';
const own = {}.hasOwnProperty;
/** @type {Options} */
const emptyOptions = {};
/**
* Generate the default label that GitHub uses on backreferences.
*
* @param {number} referenceIndex
* Index of the definition in the order that they are first referenced,
* 0-indexed.
* @param {number} rereferenceIndex
* Index of calls to the same definition, 0-indexed.
* @returns {string}
* Default label.
*/
export function defaultBackLabel(referenceIndex, rereferenceIndex) {
return 'Back to reference ' + (referenceIndex + 1) + (rereferenceIndex > 1 ? '-' + rereferenceIndex : '');
}
/**
* Create an extension for `micromark` to support GFM footnotes when
* serializing to HTML.
*
* @param {Options | null | undefined} [options={}]
* Configuration (optional).
* @returns {HtmlExtension}
* Extension for `micromark` that can be passed in `htmlExtensions` to
* support GFM footnotes when serializing to HTML.
*/
export function gfmFootnoteHtml(options) {
const config = options || emptyOptions;
const label = config.label || 'Footnotes';
const labelTagName = config.labelTagName || 'h2';
const labelAttributes = config.labelAttributes === null || config.labelAttributes === undefined ? 'class="sr-only"' : config.labelAttributes;
const backLabel = config.backLabel || defaultBackLabel;
const clobberPrefix = config.clobberPrefix === null || config.clobberPrefix === undefined ? 'user-content-' : config.clobberPrefix;
return {
enter: {
gfmFootnoteDefinition() {
const stack = this.getData('tightStack');
stack.push(false);
},
gfmFootnoteDefinitionLabelString() {
this.buffer();
},
gfmFootnoteCallString() {
this.buffer();
}
},
exit: {
gfmFootnoteDefinition() {
let definitions = this.getData('gfmFootnoteDefinitions');
const footnoteStack = this.getData('gfmFootnoteDefinitionStack');
const tightStack = this.getData('tightStack');
const current = footnoteStack.pop();
const value = this.resume();
if (!definitions) {
this.setData('gfmFootnoteDefinitions', definitions = {});
}
if (!own.call(definitions, current)) definitions[current] = value;
tightStack.pop();
this.setData('slurpOneLineEnding', true);
// “Hack” to prevent a line ending from showing up if we’re in a definition in
// an empty list item.
this.setData('lastWasTag');
},
gfmFootnoteDefinitionLabelString(token) {
let footnoteStack = this.getData('gfmFootnoteDefinitionStack');
if (!footnoteStack) {
this.setData('gfmFootnoteDefinitionStack', footnoteStack = []);
}
footnoteStack.push(normalizeIdentifier(this.sliceSerialize(token)));
this.resume(); // Drop the label.
this.buffer(); // Get ready for a value.
},
gfmFootnoteCallString(token) {
let calls = this.getData('gfmFootnoteCallOrder');
let counts = this.getData('gfmFootnoteCallCounts');
const id = normalizeIdentifier(this.sliceSerialize(token));
/** @type {number} */
let counter;
this.resume();
if (!calls) this.setData('gfmFootnoteCallOrder', calls = []);
if (!counts) this.setData('gfmFootnoteCallCounts', counts = {});
const index = calls.indexOf(id);
const safeId = sanitizeUri(id.toLowerCase());
if (index === -1) {
calls.push(id);
counts[id] = 1;
counter = calls.length;
} else {
counts[id]++;
counter = index + 1;
}
const reuseCounter = counts[id];
this.tag('' + String(counter) + '');
},
null() {
const calls = this.getData('gfmFootnoteCallOrder') || [];
const counts = this.getData('gfmFootnoteCallCounts') || {};
const definitions = this.getData('gfmFootnoteDefinitions') || {};
let index = -1;
if (calls.length > 0) {
this.lineEndingIfNeeded();
this.tag('');
}
}
}
};
}