Refactor routing in App component to enhance navigation and improve error handling by integrating dynamic routes and updating the NotFound route.

This commit is contained in:
becarta
2025-05-23 12:43:00 +02:00
parent f40db0f5c9
commit a544759a3b
11127 changed files with 1647032 additions and 0 deletions

View File

@@ -0,0 +1,55 @@
import type {Align} from './lib/infer.js'
export {gfmTableHtml} from './lib/html.js'
export {gfmTable} from './lib/syntax.js'
/**
* Augment types.
*/
declare module 'micromark-util-types' {
/**
* State tracked to compile events as HTML,
* extended by `micromark-extension-gfm-table`.
*/
interface CompileData {
/**
* Alignment of current table.
*/
tableAlign?: Array<Align> | undefined
/**
* Current table column.
*/
tableColumn?: number | undefined
}
/**
* Augment token;
* `align` is patched on `table` tokens by
* `micromark-extension-gfm-table`.
*/
interface Token {
/**
* Alignment of current table.
*/
_align?: Array<Align> | undefined
}
/**
* Map of allowed token types,
* extended by `micromark-extension-gfm-table`.
*/
interface TokenTypeMap {
tableBody: 'tableBody'
tableCellDivider: 'tableCellDivider'
tableContent: 'tableContent'
tableData: 'tableData'
tableDelimiterFiller: 'tableDelimiterFiller'
tableDelimiterMarker: 'tableDelimiterMarker'
tableDelimiterRow: 'tableDelimiterRow'
tableDelimiter: 'tableDelimiter'
tableHeader: 'tableHeader'
tableHead: 'tableHead'
tableRow: 'tableRow'
table: 'table'
}
}

View File

@@ -0,0 +1,2 @@
export {gfmTableHtml} from './lib/html.js'
export {gfmTable} from './lib/syntax.js'

View File

@@ -0,0 +1,38 @@
/**
* @import {Event} from 'micromark-util-types'
*/
/**
* @typedef {[number, number, Array<Event>]} Change
* @typedef {[number, number, number]} Jump
*/
/**
* Tracks a bunch of edits.
*/
export class EditMap {
/**
* Record of changes.
*
* @type {Array<Change>}
*/
map: Array<Change>;
/**
* Create an edit: a remove and/or add at a certain place.
*
* @param {number} index
* @param {number} remove
* @param {Array<Event>} add
* @returns {undefined}
*/
add(index: number, remove: number, add: Array<Event>): undefined;
/**
* Done, change the events.
*
* @param {Array<Event>} events
* @returns {undefined}
*/
consume(events: Array<Event>): undefined;
}
export type Change = [number, number, Array<Event>];
export type Jump = [number, number, number];
import type { Event } from 'micromark-util-types';
//# sourceMappingURL=edit-map.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"edit-map.d.ts","sourceRoot":"","sources":["edit-map.js"],"names":[],"mappings":"AAAA;;GAEG;AAeH;;;GAGG;AAEH;;GAEG;AACH;IAKI;;;;OAIG;IACH,KAFU,KAAK,CAAC,MAAM,CAAC,CAEV;IAGf;;;;;;;OAOG;IACH,WALW,MAAM,UACN,MAAM,OACN,KAAK,CAAC,KAAK,CAAC,GACV,SAAS,CAIrB;IAeD;;;;;OAKG;IACH,gBAHW,KAAK,CAAC,KAAK,CAAC,GACV,SAAS,CA2DrB;CACF;qBA7GY,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;mBAC9B,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC;2BAlBb,sBAAsB"}

View File

@@ -0,0 +1,212 @@
/**
* @import {Event} from 'micromark-util-types'
*/
// Port of `edit_map.rs` from `markdown-rs`.
// This should move to `markdown-js` later.
// Deal with several changes in events, batching them together.
//
// Preferably, changes should be kept to a minimum.
// Sometimes, its needed to change the list of events, because parsing can be
// messy, and it helps to expose a cleaner interface of events to the compiler
// and other users.
// It can also help to merge many adjacent similar events.
// And, in other cases, its needed to parse subcontent: pass some events
// through another tokenizer and inject the result.
/**
* @typedef {[number, number, Array<Event>]} Change
* @typedef {[number, number, number]} Jump
*/
/**
* Tracks a bunch of edits.
*/
export class EditMap {
/**
* Create a new edit map.
*/
constructor() {
/**
* Record of changes.
*
* @type {Array<Change>}
*/
this.map = []
}
/**
* Create an edit: a remove and/or add at a certain place.
*
* @param {number} index
* @param {number} remove
* @param {Array<Event>} add
* @returns {undefined}
*/
add(index, remove, add) {
addImplementation(this, index, remove, add)
}
// To do: add this when moving to `micromark`.
// /**
// * Create an edit: but insert `add` before existing additions.
// *
// * @param {number} index
// * @param {number} remove
// * @param {Array<Event>} add
// * @returns {undefined}
// */
// addBefore(index, remove, add) {
// addImplementation(this, index, remove, add, true)
// }
/**
* Done, change the events.
*
* @param {Array<Event>} events
* @returns {undefined}
*/
consume(events) {
this.map.sort(function (a, b) {
return a[0] - b[0]
})
/* c8 ignore next 3 -- `resolve` is never called without tables, so without edits. */
if (this.map.length === 0) {
return
}
// To do: if links are added in events, like they are in `markdown-rs`,
// this is needed.
// // Calculate jumps: where items in the current list move to.
// /** @type {Array<Jump>} */
// const jumps = []
// let index = 0
// let addAcc = 0
// let removeAcc = 0
// while (index < this.map.length) {
// const [at, remove, add] = this.map[index]
// removeAcc += remove
// addAcc += add.length
// jumps.push([at, removeAcc, addAcc])
// index += 1
// }
//
// . shiftLinks(events, jumps)
let index = this.map.length
/** @type {Array<Array<Event>>} */
const vecs = []
while (index > 0) {
index -= 1
vecs.push(
events.slice(this.map[index][0] + this.map[index][1]),
this.map[index][2]
)
// Truncate rest.
events.length = this.map[index][0]
}
vecs.push(events.slice())
events.length = 0
let slice = vecs.pop()
while (slice) {
for (const element of slice) {
events.push(element)
}
slice = vecs.pop()
}
// Truncate everything.
this.map.length = 0
}
}
/**
* Create an edit.
*
* @param {EditMap} editMap
* @param {number} at
* @param {number} remove
* @param {Array<Event>} add
* @returns {undefined}
*/
function addImplementation(editMap, at, remove, add) {
let index = 0
/* c8 ignore next 3 -- `resolve` is never called without tables, so without edits. */
if (remove === 0 && add.length === 0) {
return
}
while (index < editMap.map.length) {
if (editMap.map[index][0] === at) {
editMap.map[index][1] += remove
// To do: before not used by tables, use when moving to micromark.
// if (before) {
// add.push(...editMap.map[index][2])
// editMap.map[index][2] = add
// } else {
editMap.map[index][2].push(...add)
// }
return
}
index += 1
}
editMap.map.push([at, remove, add])
}
// /**
// * Shift `previous` and `next` links according to `jumps`.
// *
// * This fixes links in case there are events removed or added between them.
// *
// * @param {Array<Event>} events
// * @param {Array<Jump>} jumps
// */
// function shiftLinks(events, jumps) {
// let jumpIndex = 0
// let index = 0
// let add = 0
// let rm = 0
// while (index < events.length) {
// const rmCurr = rm
// while (jumpIndex < jumps.length && jumps[jumpIndex][0] <= index) {
// add = jumps[jumpIndex][2]
// rm = jumps[jumpIndex][1]
// jumpIndex += 1
// }
// // Ignore items that will be removed.
// if (rm > rmCurr) {
// index += rm - rmCurr
// } else {
// // ?
// // if let Some(link) = &events[index].link {
// // if let Some(next) = link.next {
// // events[next].link.as_mut().unwrap().previous = Some(index + add - rm);
// // while jumpIndex < jumps.len() && jumps[jumpIndex].0 <= next {
// // add = jumps[jumpIndex].2;
// // rm = jumps[jumpIndex].1;
// // jumpIndex += 1;
// // }
// // events[index].link.as_mut().unwrap().next = Some(next + add - rm);
// // index = next;
// // continue;
// // }
// // }
// index += 1
// }
// }
// }

View File

@@ -0,0 +1,11 @@
/**
* Create an HTML extension for `micromark` to support GitHub tables when
* serializing to HTML.
*
* @returns {HtmlExtension}
* Extension for `micromark` that can be passed in `htmlExtensions` to
* support GitHub tables when serializing to HTML.
*/
export function gfmTableHtml(): HtmlExtension;
import type { HtmlExtension } from 'micromark-util-types';
//# sourceMappingURL=html.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"html.d.ts","sourceRoot":"","sources":["html.js"],"names":[],"mappings":"AAeA;;;;;;;GAOG;AACH,gCAJa,aAAa,CAsHzB;mCAxI+B,sBAAsB"}

View File

@@ -0,0 +1,148 @@
/**
* @import {HtmlExtension} from 'micromark-util-types'
*/
import {ok as assert} from 'devlop'
const alignment = {
none: '',
left: ' align="left"',
right: ' align="right"',
center: ' align="center"'
}
// To do: micromark@5: use `infer` here, when all events are exposed.
/**
* Create an HTML extension for `micromark` to support GitHub tables when
* serializing to HTML.
*
* @returns {HtmlExtension}
* Extension for `micromark` that can be passed in `htmlExtensions` to
* support GitHub tables when serializing to HTML.
*/
export function gfmTableHtml() {
return {
enter: {
table(token) {
const tableAlign = token._align
assert(tableAlign, 'expected `_align`')
this.lineEndingIfNeeded()
this.tag('<table>')
this.setData('tableAlign', tableAlign)
},
tableBody() {
this.tag('<tbody>')
},
tableData() {
const tableAlign = this.getData('tableAlign')
const tableColumn = this.getData('tableColumn')
assert(tableAlign, 'expected `tableAlign`')
assert(typeof tableColumn === 'number', 'expected `tableColumn`')
const align = alignment[tableAlign[tableColumn]]
if (align === undefined) {
// Capture results to ignore them.
this.buffer()
} else {
this.lineEndingIfNeeded()
this.tag('<td' + align + '>')
}
},
tableHead() {
this.lineEndingIfNeeded()
this.tag('<thead>')
},
tableHeader() {
const tableAlign = this.getData('tableAlign')
const tableColumn = this.getData('tableColumn')
assert(tableAlign, 'expected `tableAlign`')
assert(typeof tableColumn === 'number', 'expected `tableColumn`')
const align = alignment[tableAlign[tableColumn]]
this.lineEndingIfNeeded()
this.tag('<th' + align + '>')
},
tableRow() {
this.setData('tableColumn', 0)
this.lineEndingIfNeeded()
this.tag('<tr>')
}
},
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')
// Note: we dont set `slurpAllLineEndings` anymore, in delimiter rows,
// but we do need to reset it to match a funky newline GH generates for
// list items combined with tables.
this.setData('slurpAllLineEndings')
this.lineEndingIfNeeded()
this.tag('</table>')
},
tableBody() {
this.lineEndingIfNeeded()
this.tag('</tbody>')
},
tableData() {
const tableAlign = this.getData('tableAlign')
const tableColumn = this.getData('tableColumn')
assert(tableAlign, 'expected `tableAlign`')
assert(typeof tableColumn === 'number', 'expected `tableColumn`')
if (tableColumn in tableAlign) {
this.tag('</td>')
this.setData('tableColumn', tableColumn + 1)
} else {
// Stop capturing.
this.resume()
}
},
tableHead() {
this.lineEndingIfNeeded()
this.tag('</thead>')
},
tableHeader() {
const tableColumn = this.getData('tableColumn')
assert(typeof tableColumn === 'number', 'expected `tableColumn`')
this.tag('</th>')
this.setData('tableColumn', tableColumn + 1)
},
tableRow() {
const tableAlign = this.getData('tableAlign')
let tableColumn = this.getData('tableColumn')
assert(tableAlign, 'expected `tableAlign`')
assert(typeof tableColumn === 'number', 'expected `tableColumn`')
while (tableColumn < tableAlign.length) {
this.lineEndingIfNeeded()
this.tag('<td' + alignment[tableAlign[tableColumn]] + '></td>')
tableColumn++
}
this.setData('tableColumn', tableColumn)
this.lineEndingIfNeeded()
this.tag('</tr>')
}
}
}
}
/**
* @param {string} $0
* @param {string} $1
* @returns {string}
*/
function replace($0, $1) {
// Pipes work, backslashes dont (but cant escape pipes).
return $1 === '|' ? $1 : $0
}

View File

@@ -0,0 +1,14 @@
/**
* Figure out the alignment of a GFM table.
*
* @param {Readonly<Array<Event>>} events
* List of events.
* @param {number} index
* Table enter event.
* @returns {Array<Align>}
* List of aligns.
*/
export function gfmTableAlign(events: Readonly<Array<Event>>, index: number): Array<Align>;
export type Align = "center" | "left" | "none" | "right";
import type { Event } from 'micromark-util-types';
//# sourceMappingURL=infer.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"infer.d.ts","sourceRoot":"","sources":["infer.js"],"names":[],"mappings":"AAUA;;;;;;;;;GASG;AACH,sCAPW,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,SAEtB,MAAM,GAEJ,KAAK,CAAC,KAAK,CAAC,CA8CxB;oBA1DY,QAAQ,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO;2BAJzB,sBAAsB"}

View File

@@ -0,0 +1,64 @@
/**
* @import {Event} from 'micromark-util-types'
*/
/**
* @typedef {'center' | 'left' | 'none' | 'right'} Align
*/
import {ok as assert} from 'devlop'
/**
* Figure out the alignment of a GFM table.
*
* @param {Readonly<Array<Event>>} events
* List of events.
* @param {number} index
* Table enter event.
* @returns {Array<Align>}
* List of aligns.
*/
export function gfmTableAlign(events, index) {
assert(events[index][1].type === 'table', 'expected table')
let inDelimiterRow = false
/** @type {Array<Align>} */
const align = []
while (index < events.length) {
const event = events[index]
if (inDelimiterRow) {
if (event[0] === 'enter') {
// Start of alignment value: set a new column.
// To do: `markdown-rs` uses `tableDelimiterCellValue`.
if (event[1].type === 'tableContent') {
align.push(
events[index + 1][1].type === 'tableDelimiterMarker'
? 'left'
: 'none'
)
}
}
// Exits:
// End of alignment value: change the column.
// To do: `markdown-rs` uses `tableDelimiterCellValue`.
else if (event[1].type === 'tableContent') {
if (events[index - 1][1].type === 'tableDelimiterMarker') {
const alignIndex = align.length - 1
align[alignIndex] = align[alignIndex] === 'left' ? 'center' : 'right'
}
}
// Done!
else if (event[1].type === 'tableDelimiterRow') {
break
}
} else if (event[0] === 'enter' && event[1].type === 'tableDelimiterRow') {
inDelimiterRow = true
}
index += 1
}
return align
}

View File

@@ -0,0 +1,18 @@
/**
* Create an HTML extension for `micromark` to support GitHub tables syntax.
*
* @returns {Extension}
* Extension for `micromark` that can be passed in `extensions` to enable GFM
* table syntax.
*/
export function gfmTable(): Extension;
/**
* Cell info.
*/
export type Range = [number, number, number, number];
/**
* Where we are: `1` for head row, `2` for delimiter row, `3` for body row.
*/
export type RowKind = 0 | 1 | 2 | 3;
import type { Extension } from 'micromark-util-types';
//# sourceMappingURL=syntax.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"syntax.d.ts","sourceRoot":"","sources":["syntax.js"],"names":[],"mappings":"AAuBA;;;;;;GAMG;AACH,4BAJa,SAAS,CAUrB;;;;oBA/BY,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC;;;;sBAGhC,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC;+BAPoE,sBAAsB"}

View File

@@ -0,0 +1,941 @@
/**
* @import {Event, Extension, Point, Resolver, State, Token, TokenizeContext, Tokenizer} from 'micromark-util-types'
*/
/**
* @typedef {[number, number, number, number]} Range
* Cell info.
*
* @typedef {0 | 1 | 2 | 3} RowKind
* Where we are: `1` for head row, `2` for delimiter row, `3` for body row.
*/
import {ok as assert} from 'devlop'
import {factorySpace} from 'micromark-factory-space'
import {
markdownLineEnding,
markdownLineEndingOrSpace,
markdownSpace
} from 'micromark-util-character'
import {codes, constants, types} from 'micromark-util-symbol'
import {EditMap} from './edit-map.js'
import {gfmTableAlign} from './infer.js'
/**
* Create an HTML extension for `micromark` to support GitHub tables syntax.
*
* @returns {Extension}
* Extension for `micromark` that can be passed in `extensions` to enable GFM
* table syntax.
*/
export function gfmTable() {
return {
flow: {
null: {name: 'table', tokenize: tokenizeTable, resolveAll: resolveTable}
}
}
}
/**
* @this {TokenizeContext}
* @type {Tokenizer}
*/
function tokenizeTable(effects, ok, nok) {
const self = this
let size = 0
let sizeB = 0
/** @type {boolean | undefined} */
let seen
return start
/**
* Start of a GFM table.
*
* If there is a valid table row or table head before, then we try to parse
* another row.
* Otherwise, we try to parse a head.
*
* ```markdown
* > | | a |
* ^
* | | - |
* > | | b |
* ^
* ```
* @type {State}
*/
function start(code) {
let index = self.events.length - 1
while (index > -1) {
const type = self.events[index][1].type
if (
type === types.lineEnding ||
// Note: markdown-rs uses `whitespace` instead of `linePrefix`
type === types.linePrefix
)
index--
else break
}
const tail = index > -1 ? self.events[index][1].type : null
const next =
tail === 'tableHead' || tail === 'tableRow' ? bodyRowStart : headRowBefore
// Dont allow lazy body rows.
if (next === bodyRowStart && self.parser.lazy[self.now().line]) {
return nok(code)
}
return next(code)
}
/**
* Before table head row.
*
* ```markdown
* > | | a |
* ^
* | | - |
* | | b |
* ```
*
* @type {State}
*/
function headRowBefore(code) {
effects.enter('tableHead')
effects.enter('tableRow')
return headRowStart(code)
}
/**
* Before table head row, after whitespace.
*
* ```markdown
* > | | a |
* ^
* | | - |
* | | b |
* ```
*
* @type {State}
*/
function headRowStart(code) {
if (code === codes.verticalBar) {
return headRowBreak(code)
}
// To do: micromark-js should let us parse our own whitespace in extensions,
// like `markdown-rs`:
//
// ```js
// // 4+ spaces.
// if (markdownSpace(code)) {
// return nok(code)
// }
// ```
seen = true
// Count the first character, that isnt a pipe, double.
sizeB += 1
return headRowBreak(code)
}
/**
* At break in table head row.
*
* ```markdown
* > | | a |
* ^
* ^
* ^
* | | - |
* | | b |
* ```
*
* @type {State}
*/
function headRowBreak(code) {
if (code === codes.eof) {
// Note: in `markdown-rs`, we need to reset, in `micromark-js` we dont.
return nok(code)
}
if (markdownLineEnding(code)) {
// If anything other than one pipe (ignoring whitespace) was used, its fine.
if (sizeB > 1) {
sizeB = 0
// To do: check if this works.
// Feel free to interrupt:
self.interrupt = true
effects.exit('tableRow')
effects.enter(types.lineEnding)
effects.consume(code)
effects.exit(types.lineEnding)
return headDelimiterStart
}
// Note: in `markdown-rs`, we need to reset, in `micromark-js` we dont.
return nok(code)
}
if (markdownSpace(code)) {
// To do: check if this is fine.
// effects.attempt(State::Next(StateName::GfmTableHeadRowBreak), State::Nok)
// State::Retry(space_or_tab(tokenizer))
return factorySpace(effects, headRowBreak, types.whitespace)(code)
}
sizeB += 1
if (seen) {
seen = false
// Header cell count.
size += 1
}
if (code === codes.verticalBar) {
effects.enter('tableCellDivider')
effects.consume(code)
effects.exit('tableCellDivider')
// Whether a delimiter was seen.
seen = true
return headRowBreak
}
// Anything else is cell data.
effects.enter(types.data)
return headRowData(code)
}
/**
* In table head row data.
*
* ```markdown
* > | | a |
* ^
* | | - |
* | | b |
* ```
*
* @type {State}
*/
function headRowData(code) {
if (
code === codes.eof ||
code === codes.verticalBar ||
markdownLineEndingOrSpace(code)
) {
effects.exit(types.data)
return headRowBreak(code)
}
effects.consume(code)
return code === codes.backslash ? headRowEscape : headRowData
}
/**
* In table head row escape.
*
* ```markdown
* > | | a\-b |
* ^
* | | ---- |
* | | c |
* ```
*
* @type {State}
*/
function headRowEscape(code) {
if (code === codes.backslash || code === codes.verticalBar) {
effects.consume(code)
return headRowData
}
return headRowData(code)
}
/**
* Before delimiter row.
*
* ```markdown
* | | a |
* > | | - |
* ^
* | | b |
* ```
*
* @type {State}
*/
function headDelimiterStart(code) {
// Reset `interrupt`.
self.interrupt = false
// Note: in `markdown-rs`, we need to handle piercing here too.
if (self.parser.lazy[self.now().line]) {
return nok(code)
}
effects.enter('tableDelimiterRow')
// Track if weve seen a `:` or `|`.
seen = false
if (markdownSpace(code)) {
assert(self.parser.constructs.disable.null, 'expected `disabled.null`')
return factorySpace(
effects,
headDelimiterBefore,
types.linePrefix,
self.parser.constructs.disable.null.includes('codeIndented')
? undefined
: constants.tabSize
)(code)
}
return headDelimiterBefore(code)
}
/**
* Before delimiter row, after optional whitespace.
*
* Reused when a `|` is found later, to parse another cell.
*
* ```markdown
* | | a |
* > | | - |
* ^
* | | b |
* ```
*
* @type {State}
*/
function headDelimiterBefore(code) {
if (code === codes.dash || code === codes.colon) {
return headDelimiterValueBefore(code)
}
if (code === codes.verticalBar) {
seen = true
// If we start with a pipe, we open a cell marker.
effects.enter('tableCellDivider')
effects.consume(code)
effects.exit('tableCellDivider')
return headDelimiterCellBefore
}
// More whitespace / empty row not allowed at start.
return headDelimiterNok(code)
}
/**
* After `|`, before delimiter cell.
*
* ```markdown
* | | a |
* > | | - |
* ^
* ```
*
* @type {State}
*/
function headDelimiterCellBefore(code) {
if (markdownSpace(code)) {
return factorySpace(
effects,
headDelimiterValueBefore,
types.whitespace
)(code)
}
return headDelimiterValueBefore(code)
}
/**
* Before delimiter cell value.
*
* ```markdown
* | | a |
* > | | - |
* ^
* ```
*
* @type {State}
*/
function headDelimiterValueBefore(code) {
// Align: left.
if (code === codes.colon) {
sizeB += 1
seen = true
effects.enter('tableDelimiterMarker')
effects.consume(code)
effects.exit('tableDelimiterMarker')
return headDelimiterLeftAlignmentAfter
}
// Align: none.
if (code === codes.dash) {
sizeB += 1
// To do: seems weird that this *isnt* left aligned, but that state is used?
return headDelimiterLeftAlignmentAfter(code)
}
if (code === codes.eof || markdownLineEnding(code)) {
return headDelimiterCellAfter(code)
}
return headDelimiterNok(code)
}
/**
* After delimiter cell left alignment marker.
*
* ```markdown
* | | a |
* > | | :- |
* ^
* ```
*
* @type {State}
*/
function headDelimiterLeftAlignmentAfter(code) {
if (code === codes.dash) {
effects.enter('tableDelimiterFiller')
return headDelimiterFiller(code)
}
// Anything else is not ok after the left-align colon.
return headDelimiterNok(code)
}
/**
* In delimiter cell filler.
*
* ```markdown
* | | a |
* > | | - |
* ^
* ```
*
* @type {State}
*/
function headDelimiterFiller(code) {
if (code === codes.dash) {
effects.consume(code)
return headDelimiterFiller
}
// Align is `center` if it was `left`, `right` otherwise.
if (code === codes.colon) {
seen = true
effects.exit('tableDelimiterFiller')
effects.enter('tableDelimiterMarker')
effects.consume(code)
effects.exit('tableDelimiterMarker')
return headDelimiterRightAlignmentAfter
}
effects.exit('tableDelimiterFiller')
return headDelimiterRightAlignmentAfter(code)
}
/**
* After delimiter cell right alignment marker.
*
* ```markdown
* | | a |
* > | | -: |
* ^
* ```
*
* @type {State}
*/
function headDelimiterRightAlignmentAfter(code) {
if (markdownSpace(code)) {
return factorySpace(
effects,
headDelimiterCellAfter,
types.whitespace
)(code)
}
return headDelimiterCellAfter(code)
}
/**
* After delimiter cell.
*
* ```markdown
* | | a |
* > | | -: |
* ^
* ```
*
* @type {State}
*/
function headDelimiterCellAfter(code) {
if (code === codes.verticalBar) {
return headDelimiterBefore(code)
}
if (code === codes.eof || markdownLineEnding(code)) {
// Exit when:
// * there was no `:` or `|` at all (its a thematic break or setext
// underline instead)
// * the header cell count is not the delimiter cell count
if (!seen || size !== sizeB) {
return headDelimiterNok(code)
}
// Note: in markdown-rs`, a reset is needed here.
effects.exit('tableDelimiterRow')
effects.exit('tableHead')
// To do: in `markdown-rs`, resolvers need to be registered manually.
// effects.register_resolver(ResolveName::GfmTable)
return ok(code)
}
return headDelimiterNok(code)
}
/**
* In delimiter row, at a disallowed byte.
*
* ```markdown
* | | a |
* > | | x |
* ^
* ```
*
* @type {State}
*/
function headDelimiterNok(code) {
// Note: in `markdown-rs`, we need to reset, in `micromark-js` we dont.
return nok(code)
}
/**
* Before table body row.
*
* ```markdown
* | | a |
* | | - |
* > | | b |
* ^
* ```
*
* @type {State}
*/
function bodyRowStart(code) {
// Note: in `markdown-rs` we need to manually take care of a prefix,
// but in `micromark-js` that is done for us, so if were here, were
// never at whitespace.
effects.enter('tableRow')
return bodyRowBreak(code)
}
/**
* At break in table body row.
*
* ```markdown
* | | a |
* | | - |
* > | | b |
* ^
* ^
* ^
* ```
*
* @type {State}
*/
function bodyRowBreak(code) {
if (code === codes.verticalBar) {
effects.enter('tableCellDivider')
effects.consume(code)
effects.exit('tableCellDivider')
return bodyRowBreak
}
if (code === codes.eof || markdownLineEnding(code)) {
effects.exit('tableRow')
return ok(code)
}
if (markdownSpace(code)) {
return factorySpace(effects, bodyRowBreak, types.whitespace)(code)
}
// Anything else is cell content.
effects.enter(types.data)
return bodyRowData(code)
}
/**
* In table body row data.
*
* ```markdown
* | | a |
* | | - |
* > | | b |
* ^
* ```
*
* @type {State}
*/
function bodyRowData(code) {
if (
code === codes.eof ||
code === codes.verticalBar ||
markdownLineEndingOrSpace(code)
) {
effects.exit(types.data)
return bodyRowBreak(code)
}
effects.consume(code)
return code === codes.backslash ? bodyRowEscape : bodyRowData
}
/**
* In table body row escape.
*
* ```markdown
* | | a |
* | | ---- |
* > | | b\-c |
* ^
* ```
*
* @type {State}
*/
function bodyRowEscape(code) {
if (code === codes.backslash || code === codes.verticalBar) {
effects.consume(code)
return bodyRowData
}
return bodyRowData(code)
}
}
/** @type {Resolver} */
function resolveTable(events, context) {
let index = -1
let inFirstCellAwaitingPipe = true
/** @type {RowKind} */
let rowKind = 0
/** @type {Range} */
let lastCell = [0, 0, 0, 0]
/** @type {Range} */
let cell = [0, 0, 0, 0]
let afterHeadAwaitingFirstBodyRow = false
let lastTableEnd = 0
/** @type {Token | undefined} */
let currentTable
/** @type {Token | undefined} */
let currentBody
/** @type {Token | undefined} */
let currentCell
const map = new EditMap()
while (++index < events.length) {
const event = events[index]
const token = event[1]
if (event[0] === 'enter') {
// Start of head.
if (token.type === 'tableHead') {
afterHeadAwaitingFirstBodyRow = false
// Inject previous (body end and) table end.
if (lastTableEnd !== 0) {
assert(currentTable, 'there should be a table opening')
flushTableEnd(map, context, lastTableEnd, currentTable, currentBody)
currentBody = undefined
lastTableEnd = 0
}
// Inject table start.
currentTable = {
type: 'table',
start: Object.assign({}, token.start),
// Note: correct end is set later.
end: Object.assign({}, token.end)
}
map.add(index, 0, [['enter', currentTable, context]])
} else if (
token.type === 'tableRow' ||
token.type === 'tableDelimiterRow'
) {
inFirstCellAwaitingPipe = true
currentCell = undefined
lastCell = [0, 0, 0, 0]
cell = [0, index + 1, 0, 0]
// Inject table body start.
if (afterHeadAwaitingFirstBodyRow) {
afterHeadAwaitingFirstBodyRow = false
currentBody = {
type: 'tableBody',
start: Object.assign({}, token.start),
// Note: correct end is set later.
end: Object.assign({}, token.end)
}
map.add(index, 0, [['enter', currentBody, context]])
}
rowKind = token.type === 'tableDelimiterRow' ? 2 : currentBody ? 3 : 1
}
// Cell data.
else if (
rowKind &&
(token.type === types.data ||
token.type === 'tableDelimiterMarker' ||
token.type === 'tableDelimiterFiller')
) {
inFirstCellAwaitingPipe = false
// First value in cell.
if (cell[2] === 0) {
if (lastCell[1] !== 0) {
cell[0] = cell[1]
currentCell = flushCell(
map,
context,
lastCell,
rowKind,
undefined,
currentCell
)
lastCell = [0, 0, 0, 0]
}
cell[2] = index
}
} else if (token.type === 'tableCellDivider') {
if (inFirstCellAwaitingPipe) {
inFirstCellAwaitingPipe = false
} else {
if (lastCell[1] !== 0) {
cell[0] = cell[1]
currentCell = flushCell(
map,
context,
lastCell,
rowKind,
undefined,
currentCell
)
}
lastCell = cell
cell = [lastCell[1], index, 0, 0]
}
}
}
// Exit events.
else if (token.type === 'tableHead') {
afterHeadAwaitingFirstBodyRow = true
lastTableEnd = index
} else if (
token.type === 'tableRow' ||
token.type === 'tableDelimiterRow'
) {
lastTableEnd = index
if (lastCell[1] !== 0) {
cell[0] = cell[1]
currentCell = flushCell(
map,
context,
lastCell,
rowKind,
index,
currentCell
)
} else if (cell[1] !== 0) {
currentCell = flushCell(map, context, cell, rowKind, index, currentCell)
}
rowKind = 0
} else if (
rowKind &&
(token.type === types.data ||
token.type === 'tableDelimiterMarker' ||
token.type === 'tableDelimiterFiller')
) {
cell[3] = index
}
}
if (lastTableEnd !== 0) {
assert(currentTable, 'expected table opening')
flushTableEnd(map, context, lastTableEnd, currentTable, currentBody)
}
map.consume(context.events)
// To do: move this into `html`, when events are exposed there.
// Thats what `markdown-rs` does.
// That needs updates to `mdast-util-gfm-table`.
index = -1
while (++index < context.events.length) {
const event = context.events[index]
if (event[0] === 'enter' && event[1].type === 'table') {
event[1]._align = gfmTableAlign(context.events, index)
}
}
return events
}
/**
* Generate a cell.
*
* @param {EditMap} map
* @param {Readonly<TokenizeContext>} context
* @param {Readonly<Range>} range
* @param {RowKind} rowKind
* @param {number | undefined} rowEnd
* @param {Token | undefined} previousCell
* @returns {Token | undefined}
*/
// eslint-disable-next-line max-params
function flushCell(map, context, range, rowKind, rowEnd, previousCell) {
// `markdown-rs` uses:
// rowKind === 2 ? 'tableDelimiterCell' : 'tableCell'
const groupName =
rowKind === 1
? 'tableHeader'
: rowKind === 2
? 'tableDelimiter'
: 'tableData'
// `markdown-rs` uses:
// rowKind === 2 ? 'tableDelimiterCellValue' : 'tableCellText'
const valueName = 'tableContent'
// Insert an exit for the previous cell, if there is one.
//
// ```markdown
// > | | aa | bb | cc |
// ^-- exit
// ^^^^-- this cell
// ```
if (range[0] !== 0) {
assert(previousCell, 'expected previous cell enter')
previousCell.end = Object.assign({}, getPoint(context.events, range[0]))
map.add(range[0], 0, [['exit', previousCell, context]])
}
// Insert enter of this cell.
//
// ```markdown
// > | | aa | bb | cc |
// ^-- enter
// ^^^^-- this cell
// ```
const now = getPoint(context.events, range[1])
previousCell = {
type: groupName,
start: Object.assign({}, now),
// Note: correct end is set later.
end: Object.assign({}, now)
}
map.add(range[1], 0, [['enter', previousCell, context]])
// Insert text start at first data start and end at last data end, and
// remove events between.
//
// ```markdown
// > | | aa | bb | cc |
// ^-- enter
// ^-- exit
// ^^^^-- this cell
// ```
if (range[2] !== 0) {
const relatedStart = getPoint(context.events, range[2])
const relatedEnd = getPoint(context.events, range[3])
/** @type {Token} */
const valueToken = {
type: valueName,
start: Object.assign({}, relatedStart),
end: Object.assign({}, relatedEnd)
}
map.add(range[2], 0, [['enter', valueToken, context]])
assert(range[3] !== 0)
if (rowKind !== 2) {
// Fix positional info on remaining events
const start = context.events[range[2]]
const end = context.events[range[3]]
start[1].end = Object.assign({}, end[1].end)
start[1].type = types.chunkText
start[1].contentType = constants.contentTypeText
// Remove if needed.
if (range[3] > range[2] + 1) {
const a = range[2] + 1
const b = range[3] - range[2] - 1
map.add(a, b, [])
}
}
map.add(range[3] + 1, 0, [['exit', valueToken, context]])
}
// Insert an exit for the last cell, if at the row end.
//
// ```markdown
// > | | aa | bb | cc |
// ^-- exit
// ^^^^^^-- this cell (the last one contains two “between” parts)
// ```
if (rowEnd !== undefined) {
previousCell.end = Object.assign({}, getPoint(context.events, rowEnd))
map.add(rowEnd, 0, [['exit', previousCell, context]])
previousCell = undefined
}
return previousCell
}
/**
* Generate table end (and table body end).
*
* @param {Readonly<EditMap>} map
* @param {Readonly<TokenizeContext>} context
* @param {number} index
* @param {Token} table
* @param {Token | undefined} tableBody
*/
// eslint-disable-next-line max-params
function flushTableEnd(map, context, index, table, tableBody) {
/** @type {Array<Event>} */
const exits = []
const related = getPoint(context.events, index)
if (tableBody) {
tableBody.end = Object.assign({}, related)
exits.push(['exit', tableBody, context])
}
table.end = Object.assign({}, related)
exits.push(['exit', table, context])
map.add(index + 1, 0, exits)
}
/**
* @param {Readonly<Array<Event>>} events
* @param {number} index
* @returns {Readonly<Point>}
*/
function getPoint(events, index) {
const event = events[index]
const side = event[0] === 'enter' ? 'start' : 'end'
return event[1][side]
}

55
node_modules/micromark-extension-gfm-table/index.d.ts generated vendored Normal file
View File

@@ -0,0 +1,55 @@
import type {Align} from './lib/infer.js'
export {gfmTableHtml} from './lib/html.js'
export {gfmTable} from './lib/syntax.js'
/**
* Augment types.
*/
declare module 'micromark-util-types' {
/**
* State tracked to compile events as HTML,
* extended by `micromark-extension-gfm-table`.
*/
interface CompileData {
/**
* Alignment of current table.
*/
tableAlign?: Array<Align> | undefined
/**
* Current table column.
*/
tableColumn?: number | undefined
}
/**
* Augment token;
* `align` is patched on `table` tokens by
* `micromark-extension-gfm-table`.
*/
interface Token {
/**
* Alignment of current table.
*/
_align?: Array<Align> | undefined
}
/**
* Map of allowed token types,
* extended by `micromark-extension-gfm-table`.
*/
interface TokenTypeMap {
tableBody: 'tableBody'
tableCellDivider: 'tableCellDivider'
tableContent: 'tableContent'
tableData: 'tableData'
tableDelimiterFiller: 'tableDelimiterFiller'
tableDelimiterMarker: 'tableDelimiterMarker'
tableDelimiterRow: 'tableDelimiterRow'
tableDelimiter: 'tableDelimiter'
tableHeader: 'tableHeader'
tableHead: 'tableHead'
tableRow: 'tableRow'
table: 'table'
}
}

2
node_modules/micromark-extension-gfm-table/index.js generated vendored Normal file
View File

@@ -0,0 +1,2 @@
export { gfmTableHtml } from './lib/html.js';
export { gfmTable } from './lib/syntax.js';

View File

@@ -0,0 +1,38 @@
/**
* @import {Event} from 'micromark-util-types'
*/
/**
* @typedef {[number, number, Array<Event>]} Change
* @typedef {[number, number, number]} Jump
*/
/**
* Tracks a bunch of edits.
*/
export class EditMap {
/**
* Record of changes.
*
* @type {Array<Change>}
*/
map: Array<Change>;
/**
* Create an edit: a remove and/or add at a certain place.
*
* @param {number} index
* @param {number} remove
* @param {Array<Event>} add
* @returns {undefined}
*/
add(index: number, remove: number, add: Array<Event>): undefined;
/**
* Done, change the events.
*
* @param {Array<Event>} events
* @returns {undefined}
*/
consume(events: Array<Event>): undefined;
}
export type Change = [number, number, Array<Event>];
export type Jump = [number, number, number];
import type { Event } from 'micromark-util-types';
//# sourceMappingURL=edit-map.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"edit-map.d.ts","sourceRoot":"","sources":["edit-map.js"],"names":[],"mappings":"AAAA;;GAEG;AAeH;;;GAGG;AAEH;;GAEG;AACH;IAKI;;;;OAIG;IACH,KAFU,KAAK,CAAC,MAAM,CAAC,CAEV;IAGf;;;;;;;OAOG;IACH,WALW,MAAM,UACN,MAAM,OACN,KAAK,CAAC,KAAK,CAAC,GACV,SAAS,CAIrB;IAeD;;;;;OAKG;IACH,gBAHW,KAAK,CAAC,KAAK,CAAC,GACV,SAAS,CA2DrB;CACF;qBA7GY,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;mBAC9B,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC;2BAlBb,sBAAsB"}

View File

@@ -0,0 +1,202 @@
/**
* @import {Event} from 'micromark-util-types'
*/
// Port of `edit_map.rs` from `markdown-rs`.
// This should move to `markdown-js` later.
// Deal with several changes in events, batching them together.
//
// Preferably, changes should be kept to a minimum.
// Sometimes, its needed to change the list of events, because parsing can be
// messy, and it helps to expose a cleaner interface of events to the compiler
// and other users.
// It can also help to merge many adjacent similar events.
// And, in other cases, its needed to parse subcontent: pass some events
// through another tokenizer and inject the result.
/**
* @typedef {[number, number, Array<Event>]} Change
* @typedef {[number, number, number]} Jump
*/
/**
* Tracks a bunch of edits.
*/
export class EditMap {
/**
* Create a new edit map.
*/
constructor() {
/**
* Record of changes.
*
* @type {Array<Change>}
*/
this.map = [];
}
/**
* Create an edit: a remove and/or add at a certain place.
*
* @param {number} index
* @param {number} remove
* @param {Array<Event>} add
* @returns {undefined}
*/
add(index, remove, add) {
addImplementation(this, index, remove, add);
}
// To do: add this when moving to `micromark`.
// /**
// * Create an edit: but insert `add` before existing additions.
// *
// * @param {number} index
// * @param {number} remove
// * @param {Array<Event>} add
// * @returns {undefined}
// */
// addBefore(index, remove, add) {
// addImplementation(this, index, remove, add, true)
// }
/**
* Done, change the events.
*
* @param {Array<Event>} events
* @returns {undefined}
*/
consume(events) {
this.map.sort(function (a, b) {
return a[0] - b[0];
});
/* c8 ignore next 3 -- `resolve` is never called without tables, so without edits. */
if (this.map.length === 0) {
return;
}
// To do: if links are added in events, like they are in `markdown-rs`,
// this is needed.
// // Calculate jumps: where items in the current list move to.
// /** @type {Array<Jump>} */
// const jumps = []
// let index = 0
// let addAcc = 0
// let removeAcc = 0
// while (index < this.map.length) {
// const [at, remove, add] = this.map[index]
// removeAcc += remove
// addAcc += add.length
// jumps.push([at, removeAcc, addAcc])
// index += 1
// }
//
// . shiftLinks(events, jumps)
let index = this.map.length;
/** @type {Array<Array<Event>>} */
const vecs = [];
while (index > 0) {
index -= 1;
vecs.push(events.slice(this.map[index][0] + this.map[index][1]), this.map[index][2]);
// Truncate rest.
events.length = this.map[index][0];
}
vecs.push(events.slice());
events.length = 0;
let slice = vecs.pop();
while (slice) {
for (const element of slice) {
events.push(element);
}
slice = vecs.pop();
}
// Truncate everything.
this.map.length = 0;
}
}
/**
* Create an edit.
*
* @param {EditMap} editMap
* @param {number} at
* @param {number} remove
* @param {Array<Event>} add
* @returns {undefined}
*/
function addImplementation(editMap, at, remove, add) {
let index = 0;
/* c8 ignore next 3 -- `resolve` is never called without tables, so without edits. */
if (remove === 0 && add.length === 0) {
return;
}
while (index < editMap.map.length) {
if (editMap.map[index][0] === at) {
editMap.map[index][1] += remove;
// To do: before not used by tables, use when moving to micromark.
// if (before) {
// add.push(...editMap.map[index][2])
// editMap.map[index][2] = add
// } else {
editMap.map[index][2].push(...add);
// }
return;
}
index += 1;
}
editMap.map.push([at, remove, add]);
}
// /**
// * Shift `previous` and `next` links according to `jumps`.
// *
// * This fixes links in case there are events removed or added between them.
// *
// * @param {Array<Event>} events
// * @param {Array<Jump>} jumps
// */
// function shiftLinks(events, jumps) {
// let jumpIndex = 0
// let index = 0
// let add = 0
// let rm = 0
// while (index < events.length) {
// const rmCurr = rm
// while (jumpIndex < jumps.length && jumps[jumpIndex][0] <= index) {
// add = jumps[jumpIndex][2]
// rm = jumps[jumpIndex][1]
// jumpIndex += 1
// }
// // Ignore items that will be removed.
// if (rm > rmCurr) {
// index += rm - rmCurr
// } else {
// // ?
// // if let Some(link) = &events[index].link {
// // if let Some(next) = link.next {
// // events[next].link.as_mut().unwrap().previous = Some(index + add - rm);
// // while jumpIndex < jumps.len() && jumps[jumpIndex].0 <= next {
// // add = jumps[jumpIndex].2;
// // rm = jumps[jumpIndex].1;
// // jumpIndex += 1;
// // }
// // events[index].link.as_mut().unwrap().next = Some(next + add - rm);
// // index = next;
// // continue;
// // }
// // }
// index += 1
// }
// }
// }

View File

@@ -0,0 +1,11 @@
/**
* Create an HTML extension for `micromark` to support GitHub tables when
* serializing to HTML.
*
* @returns {HtmlExtension}
* Extension for `micromark` that can be passed in `htmlExtensions` to
* support GitHub tables when serializing to HTML.
*/
export function gfmTableHtml(): HtmlExtension;
import type { HtmlExtension } from 'micromark-util-types';
//# sourceMappingURL=html.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"html.d.ts","sourceRoot":"","sources":["html.js"],"names":[],"mappings":"AAeA;;;;;;;GAOG;AACH,gCAJa,aAAa,CAsHzB;mCAxI+B,sBAAsB"}

130
node_modules/micromark-extension-gfm-table/lib/html.js generated vendored Normal file
View File

@@ -0,0 +1,130 @@
/**
* @import {HtmlExtension} from 'micromark-util-types'
*/
const alignment = {
none: '',
left: ' align="left"',
right: ' align="right"',
center: ' align="center"'
};
// To do: micromark@5: use `infer` here, when all events are exposed.
/**
* Create an HTML extension for `micromark` to support GitHub tables when
* serializing to HTML.
*
* @returns {HtmlExtension}
* Extension for `micromark` that can be passed in `htmlExtensions` to
* support GitHub tables when serializing to HTML.
*/
export function gfmTableHtml() {
return {
enter: {
table(token) {
const tableAlign = token._align;
this.lineEndingIfNeeded();
this.tag('<table>');
this.setData('tableAlign', tableAlign);
},
tableBody() {
this.tag('<tbody>');
},
tableData() {
const tableAlign = this.getData('tableAlign');
const tableColumn = this.getData('tableColumn');
const align = alignment[tableAlign[tableColumn]];
if (align === undefined) {
// Capture results to ignore them.
this.buffer();
} else {
this.lineEndingIfNeeded();
this.tag('<td' + align + '>');
}
},
tableHead() {
this.lineEndingIfNeeded();
this.tag('<thead>');
},
tableHeader() {
const tableAlign = this.getData('tableAlign');
const tableColumn = this.getData('tableColumn');
const align = alignment[tableAlign[tableColumn]];
this.lineEndingIfNeeded();
this.tag('<th' + align + '>');
},
tableRow() {
this.setData('tableColumn', 0);
this.lineEndingIfNeeded();
this.tag('<tr>');
}
},
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');
// Note: we dont set `slurpAllLineEndings` anymore, in delimiter rows,
// but we do need to reset it to match a funky newline GH generates for
// list items combined with tables.
this.setData('slurpAllLineEndings');
this.lineEndingIfNeeded();
this.tag('</table>');
},
tableBody() {
this.lineEndingIfNeeded();
this.tag('</tbody>');
},
tableData() {
const tableAlign = this.getData('tableAlign');
const tableColumn = this.getData('tableColumn');
if (tableColumn in tableAlign) {
this.tag('</td>');
this.setData('tableColumn', tableColumn + 1);
} else {
// Stop capturing.
this.resume();
}
},
tableHead() {
this.lineEndingIfNeeded();
this.tag('</thead>');
},
tableHeader() {
const tableColumn = this.getData('tableColumn');
this.tag('</th>');
this.setData('tableColumn', tableColumn + 1);
},
tableRow() {
const tableAlign = this.getData('tableAlign');
let tableColumn = this.getData('tableColumn');
while (tableColumn < tableAlign.length) {
this.lineEndingIfNeeded();
this.tag('<td' + alignment[tableAlign[tableColumn]] + '></td>');
tableColumn++;
}
this.setData('tableColumn', tableColumn);
this.lineEndingIfNeeded();
this.tag('</tr>');
}
}
};
}
/**
* @param {string} $0
* @param {string} $1
* @returns {string}
*/
function replace($0, $1) {
// Pipes work, backslashes dont (but cant escape pipes).
return $1 === '|' ? $1 : $0;
}

View File

@@ -0,0 +1,14 @@
/**
* Figure out the alignment of a GFM table.
*
* @param {Readonly<Array<Event>>} events
* List of events.
* @param {number} index
* Table enter event.
* @returns {Array<Align>}
* List of aligns.
*/
export function gfmTableAlign(events: Readonly<Array<Event>>, index: number): Array<Align>;
export type Align = "center" | "left" | "none" | "right";
import type { Event } from 'micromark-util-types';
//# sourceMappingURL=infer.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"infer.d.ts","sourceRoot":"","sources":["infer.js"],"names":[],"mappings":"AAUA;;;;;;;;;GASG;AACH,sCAPW,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,SAEtB,MAAM,GAEJ,KAAK,CAAC,KAAK,CAAC,CA8CxB;oBA1DY,QAAQ,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO;2BAJzB,sBAAsB"}

View File

@@ -0,0 +1,52 @@
/**
* @import {Event} from 'micromark-util-types'
*/
/**
* @typedef {'center' | 'left' | 'none' | 'right'} Align
*/
/**
* Figure out the alignment of a GFM table.
*
* @param {Readonly<Array<Event>>} events
* List of events.
* @param {number} index
* Table enter event.
* @returns {Array<Align>}
* List of aligns.
*/
export function gfmTableAlign(events, index) {
let inDelimiterRow = false;
/** @type {Array<Align>} */
const align = [];
while (index < events.length) {
const event = events[index];
if (inDelimiterRow) {
if (event[0] === 'enter') {
// Start of alignment value: set a new column.
// To do: `markdown-rs` uses `tableDelimiterCellValue`.
if (event[1].type === 'tableContent') {
align.push(events[index + 1][1].type === 'tableDelimiterMarker' ? 'left' : 'none');
}
}
// Exits:
// End of alignment value: change the column.
// To do: `markdown-rs` uses `tableDelimiterCellValue`.
else if (event[1].type === 'tableContent') {
if (events[index - 1][1].type === 'tableDelimiterMarker') {
const alignIndex = align.length - 1;
align[alignIndex] = align[alignIndex] === 'left' ? 'center' : 'right';
}
}
// Done!
else if (event[1].type === 'tableDelimiterRow') {
break;
}
} else if (event[0] === 'enter' && event[1].type === 'tableDelimiterRow') {
inDelimiterRow = true;
}
index += 1;
}
return align;
}

View File

@@ -0,0 +1,18 @@
/**
* Create an HTML extension for `micromark` to support GitHub tables syntax.
*
* @returns {Extension}
* Extension for `micromark` that can be passed in `extensions` to enable GFM
* table syntax.
*/
export function gfmTable(): Extension;
/**
* Cell info.
*/
export type Range = [number, number, number, number];
/**
* Where we are: `1` for head row, `2` for delimiter row, `3` for body row.
*/
export type RowKind = 0 | 1 | 2 | 3;
import type { Extension } from 'micromark-util-types';
//# sourceMappingURL=syntax.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"syntax.d.ts","sourceRoot":"","sources":["syntax.js"],"names":[],"mappings":"AAuBA;;;;;;GAMG;AACH,4BAJa,SAAS,CAUrB;;;;oBA/BY,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC;;;;sBAGhC,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC;+BAPoE,sBAAsB"}

View File

@@ -0,0 +1,819 @@
/**
* @import {Event, Extension, Point, Resolver, State, Token, TokenizeContext, Tokenizer} from 'micromark-util-types'
*/
/**
* @typedef {[number, number, number, number]} Range
* Cell info.
*
* @typedef {0 | 1 | 2 | 3} RowKind
* Where we are: `1` for head row, `2` for delimiter row, `3` for body row.
*/
import { factorySpace } from 'micromark-factory-space';
import { markdownLineEnding, markdownLineEndingOrSpace, markdownSpace } from 'micromark-util-character';
import { EditMap } from './edit-map.js';
import { gfmTableAlign } from './infer.js';
/**
* Create an HTML extension for `micromark` to support GitHub tables syntax.
*
* @returns {Extension}
* Extension for `micromark` that can be passed in `extensions` to enable GFM
* table syntax.
*/
export function gfmTable() {
return {
flow: {
null: {
name: 'table',
tokenize: tokenizeTable,
resolveAll: resolveTable
}
}
};
}
/**
* @this {TokenizeContext}
* @type {Tokenizer}
*/
function tokenizeTable(effects, ok, nok) {
const self = this;
let size = 0;
let sizeB = 0;
/** @type {boolean | undefined} */
let seen;
return start;
/**
* Start of a GFM table.
*
* If there is a valid table row or table head before, then we try to parse
* another row.
* Otherwise, we try to parse a head.
*
* ```markdown
* > | | a |
* ^
* | | - |
* > | | b |
* ^
* ```
* @type {State}
*/
function start(code) {
let index = self.events.length - 1;
while (index > -1) {
const type = self.events[index][1].type;
if (type === "lineEnding" ||
// Note: markdown-rs uses `whitespace` instead of `linePrefix`
type === "linePrefix") index--;else break;
}
const tail = index > -1 ? self.events[index][1].type : null;
const next = tail === 'tableHead' || tail === 'tableRow' ? bodyRowStart : headRowBefore;
// Dont allow lazy body rows.
if (next === bodyRowStart && self.parser.lazy[self.now().line]) {
return nok(code);
}
return next(code);
}
/**
* Before table head row.
*
* ```markdown
* > | | a |
* ^
* | | - |
* | | b |
* ```
*
* @type {State}
*/
function headRowBefore(code) {
effects.enter('tableHead');
effects.enter('tableRow');
return headRowStart(code);
}
/**
* Before table head row, after whitespace.
*
* ```markdown
* > | | a |
* ^
* | | - |
* | | b |
* ```
*
* @type {State}
*/
function headRowStart(code) {
if (code === 124) {
return headRowBreak(code);
}
// To do: micromark-js should let us parse our own whitespace in extensions,
// like `markdown-rs`:
//
// ```js
// // 4+ spaces.
// if (markdownSpace(code)) {
// return nok(code)
// }
// ```
seen = true;
// Count the first character, that isnt a pipe, double.
sizeB += 1;
return headRowBreak(code);
}
/**
* At break in table head row.
*
* ```markdown
* > | | a |
* ^
* ^
* ^
* | | - |
* | | b |
* ```
*
* @type {State}
*/
function headRowBreak(code) {
if (code === null) {
// Note: in `markdown-rs`, we need to reset, in `micromark-js` we dont.
return nok(code);
}
if (markdownLineEnding(code)) {
// If anything other than one pipe (ignoring whitespace) was used, its fine.
if (sizeB > 1) {
sizeB = 0;
// To do: check if this works.
// Feel free to interrupt:
self.interrupt = true;
effects.exit('tableRow');
effects.enter("lineEnding");
effects.consume(code);
effects.exit("lineEnding");
return headDelimiterStart;
}
// Note: in `markdown-rs`, we need to reset, in `micromark-js` we dont.
return nok(code);
}
if (markdownSpace(code)) {
// To do: check if this is fine.
// effects.attempt(State::Next(StateName::GfmTableHeadRowBreak), State::Nok)
// State::Retry(space_or_tab(tokenizer))
return factorySpace(effects, headRowBreak, "whitespace")(code);
}
sizeB += 1;
if (seen) {
seen = false;
// Header cell count.
size += 1;
}
if (code === 124) {
effects.enter('tableCellDivider');
effects.consume(code);
effects.exit('tableCellDivider');
// Whether a delimiter was seen.
seen = true;
return headRowBreak;
}
// Anything else is cell data.
effects.enter("data");
return headRowData(code);
}
/**
* In table head row data.
*
* ```markdown
* > | | a |
* ^
* | | - |
* | | b |
* ```
*
* @type {State}
*/
function headRowData(code) {
if (code === null || code === 124 || markdownLineEndingOrSpace(code)) {
effects.exit("data");
return headRowBreak(code);
}
effects.consume(code);
return code === 92 ? headRowEscape : headRowData;
}
/**
* In table head row escape.
*
* ```markdown
* > | | a\-b |
* ^
* | | ---- |
* | | c |
* ```
*
* @type {State}
*/
function headRowEscape(code) {
if (code === 92 || code === 124) {
effects.consume(code);
return headRowData;
}
return headRowData(code);
}
/**
* Before delimiter row.
*
* ```markdown
* | | a |
* > | | - |
* ^
* | | b |
* ```
*
* @type {State}
*/
function headDelimiterStart(code) {
// Reset `interrupt`.
self.interrupt = false;
// Note: in `markdown-rs`, we need to handle piercing here too.
if (self.parser.lazy[self.now().line]) {
return nok(code);
}
effects.enter('tableDelimiterRow');
// Track if weve seen a `:` or `|`.
seen = false;
if (markdownSpace(code)) {
return factorySpace(effects, headDelimiterBefore, "linePrefix", self.parser.constructs.disable.null.includes('codeIndented') ? undefined : 4)(code);
}
return headDelimiterBefore(code);
}
/**
* Before delimiter row, after optional whitespace.
*
* Reused when a `|` is found later, to parse another cell.
*
* ```markdown
* | | a |
* > | | - |
* ^
* | | b |
* ```
*
* @type {State}
*/
function headDelimiterBefore(code) {
if (code === 45 || code === 58) {
return headDelimiterValueBefore(code);
}
if (code === 124) {
seen = true;
// If we start with a pipe, we open a cell marker.
effects.enter('tableCellDivider');
effects.consume(code);
effects.exit('tableCellDivider');
return headDelimiterCellBefore;
}
// More whitespace / empty row not allowed at start.
return headDelimiterNok(code);
}
/**
* After `|`, before delimiter cell.
*
* ```markdown
* | | a |
* > | | - |
* ^
* ```
*
* @type {State}
*/
function headDelimiterCellBefore(code) {
if (markdownSpace(code)) {
return factorySpace(effects, headDelimiterValueBefore, "whitespace")(code);
}
return headDelimiterValueBefore(code);
}
/**
* Before delimiter cell value.
*
* ```markdown
* | | a |
* > | | - |
* ^
* ```
*
* @type {State}
*/
function headDelimiterValueBefore(code) {
// Align: left.
if (code === 58) {
sizeB += 1;
seen = true;
effects.enter('tableDelimiterMarker');
effects.consume(code);
effects.exit('tableDelimiterMarker');
return headDelimiterLeftAlignmentAfter;
}
// Align: none.
if (code === 45) {
sizeB += 1;
// To do: seems weird that this *isnt* left aligned, but that state is used?
return headDelimiterLeftAlignmentAfter(code);
}
if (code === null || markdownLineEnding(code)) {
return headDelimiterCellAfter(code);
}
return headDelimiterNok(code);
}
/**
* After delimiter cell left alignment marker.
*
* ```markdown
* | | a |
* > | | :- |
* ^
* ```
*
* @type {State}
*/
function headDelimiterLeftAlignmentAfter(code) {
if (code === 45) {
effects.enter('tableDelimiterFiller');
return headDelimiterFiller(code);
}
// Anything else is not ok after the left-align colon.
return headDelimiterNok(code);
}
/**
* In delimiter cell filler.
*
* ```markdown
* | | a |
* > | | - |
* ^
* ```
*
* @type {State}
*/
function headDelimiterFiller(code) {
if (code === 45) {
effects.consume(code);
return headDelimiterFiller;
}
// Align is `center` if it was `left`, `right` otherwise.
if (code === 58) {
seen = true;
effects.exit('tableDelimiterFiller');
effects.enter('tableDelimiterMarker');
effects.consume(code);
effects.exit('tableDelimiterMarker');
return headDelimiterRightAlignmentAfter;
}
effects.exit('tableDelimiterFiller');
return headDelimiterRightAlignmentAfter(code);
}
/**
* After delimiter cell right alignment marker.
*
* ```markdown
* | | a |
* > | | -: |
* ^
* ```
*
* @type {State}
*/
function headDelimiterRightAlignmentAfter(code) {
if (markdownSpace(code)) {
return factorySpace(effects, headDelimiterCellAfter, "whitespace")(code);
}
return headDelimiterCellAfter(code);
}
/**
* After delimiter cell.
*
* ```markdown
* | | a |
* > | | -: |
* ^
* ```
*
* @type {State}
*/
function headDelimiterCellAfter(code) {
if (code === 124) {
return headDelimiterBefore(code);
}
if (code === null || markdownLineEnding(code)) {
// Exit when:
// * there was no `:` or `|` at all (its a thematic break or setext
// underline instead)
// * the header cell count is not the delimiter cell count
if (!seen || size !== sizeB) {
return headDelimiterNok(code);
}
// Note: in markdown-rs`, a reset is needed here.
effects.exit('tableDelimiterRow');
effects.exit('tableHead');
// To do: in `markdown-rs`, resolvers need to be registered manually.
// effects.register_resolver(ResolveName::GfmTable)
return ok(code);
}
return headDelimiterNok(code);
}
/**
* In delimiter row, at a disallowed byte.
*
* ```markdown
* | | a |
* > | | x |
* ^
* ```
*
* @type {State}
*/
function headDelimiterNok(code) {
// Note: in `markdown-rs`, we need to reset, in `micromark-js` we dont.
return nok(code);
}
/**
* Before table body row.
*
* ```markdown
* | | a |
* | | - |
* > | | b |
* ^
* ```
*
* @type {State}
*/
function bodyRowStart(code) {
// Note: in `markdown-rs` we need to manually take care of a prefix,
// but in `micromark-js` that is done for us, so if were here, were
// never at whitespace.
effects.enter('tableRow');
return bodyRowBreak(code);
}
/**
* At break in table body row.
*
* ```markdown
* | | a |
* | | - |
* > | | b |
* ^
* ^
* ^
* ```
*
* @type {State}
*/
function bodyRowBreak(code) {
if (code === 124) {
effects.enter('tableCellDivider');
effects.consume(code);
effects.exit('tableCellDivider');
return bodyRowBreak;
}
if (code === null || markdownLineEnding(code)) {
effects.exit('tableRow');
return ok(code);
}
if (markdownSpace(code)) {
return factorySpace(effects, bodyRowBreak, "whitespace")(code);
}
// Anything else is cell content.
effects.enter("data");
return bodyRowData(code);
}
/**
* In table body row data.
*
* ```markdown
* | | a |
* | | - |
* > | | b |
* ^
* ```
*
* @type {State}
*/
function bodyRowData(code) {
if (code === null || code === 124 || markdownLineEndingOrSpace(code)) {
effects.exit("data");
return bodyRowBreak(code);
}
effects.consume(code);
return code === 92 ? bodyRowEscape : bodyRowData;
}
/**
* In table body row escape.
*
* ```markdown
* | | a |
* | | ---- |
* > | | b\-c |
* ^
* ```
*
* @type {State}
*/
function bodyRowEscape(code) {
if (code === 92 || code === 124) {
effects.consume(code);
return bodyRowData;
}
return bodyRowData(code);
}
}
/** @type {Resolver} */
function resolveTable(events, context) {
let index = -1;
let inFirstCellAwaitingPipe = true;
/** @type {RowKind} */
let rowKind = 0;
/** @type {Range} */
let lastCell = [0, 0, 0, 0];
/** @type {Range} */
let cell = [0, 0, 0, 0];
let afterHeadAwaitingFirstBodyRow = false;
let lastTableEnd = 0;
/** @type {Token | undefined} */
let currentTable;
/** @type {Token | undefined} */
let currentBody;
/** @type {Token | undefined} */
let currentCell;
const map = new EditMap();
while (++index < events.length) {
const event = events[index];
const token = event[1];
if (event[0] === 'enter') {
// Start of head.
if (token.type === 'tableHead') {
afterHeadAwaitingFirstBodyRow = false;
// Inject previous (body end and) table end.
if (lastTableEnd !== 0) {
flushTableEnd(map, context, lastTableEnd, currentTable, currentBody);
currentBody = undefined;
lastTableEnd = 0;
}
// Inject table start.
currentTable = {
type: 'table',
start: Object.assign({}, token.start),
// Note: correct end is set later.
end: Object.assign({}, token.end)
};
map.add(index, 0, [['enter', currentTable, context]]);
} else if (token.type === 'tableRow' || token.type === 'tableDelimiterRow') {
inFirstCellAwaitingPipe = true;
currentCell = undefined;
lastCell = [0, 0, 0, 0];
cell = [0, index + 1, 0, 0];
// Inject table body start.
if (afterHeadAwaitingFirstBodyRow) {
afterHeadAwaitingFirstBodyRow = false;
currentBody = {
type: 'tableBody',
start: Object.assign({}, token.start),
// Note: correct end is set later.
end: Object.assign({}, token.end)
};
map.add(index, 0, [['enter', currentBody, context]]);
}
rowKind = token.type === 'tableDelimiterRow' ? 2 : currentBody ? 3 : 1;
}
// Cell data.
else if (rowKind && (token.type === "data" || token.type === 'tableDelimiterMarker' || token.type === 'tableDelimiterFiller')) {
inFirstCellAwaitingPipe = false;
// First value in cell.
if (cell[2] === 0) {
if (lastCell[1] !== 0) {
cell[0] = cell[1];
currentCell = flushCell(map, context, lastCell, rowKind, undefined, currentCell);
lastCell = [0, 0, 0, 0];
}
cell[2] = index;
}
} else if (token.type === 'tableCellDivider') {
if (inFirstCellAwaitingPipe) {
inFirstCellAwaitingPipe = false;
} else {
if (lastCell[1] !== 0) {
cell[0] = cell[1];
currentCell = flushCell(map, context, lastCell, rowKind, undefined, currentCell);
}
lastCell = cell;
cell = [lastCell[1], index, 0, 0];
}
}
}
// Exit events.
else if (token.type === 'tableHead') {
afterHeadAwaitingFirstBodyRow = true;
lastTableEnd = index;
} else if (token.type === 'tableRow' || token.type === 'tableDelimiterRow') {
lastTableEnd = index;
if (lastCell[1] !== 0) {
cell[0] = cell[1];
currentCell = flushCell(map, context, lastCell, rowKind, index, currentCell);
} else if (cell[1] !== 0) {
currentCell = flushCell(map, context, cell, rowKind, index, currentCell);
}
rowKind = 0;
} else if (rowKind && (token.type === "data" || token.type === 'tableDelimiterMarker' || token.type === 'tableDelimiterFiller')) {
cell[3] = index;
}
}
if (lastTableEnd !== 0) {
flushTableEnd(map, context, lastTableEnd, currentTable, currentBody);
}
map.consume(context.events);
// To do: move this into `html`, when events are exposed there.
// Thats what `markdown-rs` does.
// That needs updates to `mdast-util-gfm-table`.
index = -1;
while (++index < context.events.length) {
const event = context.events[index];
if (event[0] === 'enter' && event[1].type === 'table') {
event[1]._align = gfmTableAlign(context.events, index);
}
}
return events;
}
/**
* Generate a cell.
*
* @param {EditMap} map
* @param {Readonly<TokenizeContext>} context
* @param {Readonly<Range>} range
* @param {RowKind} rowKind
* @param {number | undefined} rowEnd
* @param {Token | undefined} previousCell
* @returns {Token | undefined}
*/
// eslint-disable-next-line max-params
function flushCell(map, context, range, rowKind, rowEnd, previousCell) {
// `markdown-rs` uses:
// rowKind === 2 ? 'tableDelimiterCell' : 'tableCell'
const groupName = rowKind === 1 ? 'tableHeader' : rowKind === 2 ? 'tableDelimiter' : 'tableData';
// `markdown-rs` uses:
// rowKind === 2 ? 'tableDelimiterCellValue' : 'tableCellText'
const valueName = 'tableContent';
// Insert an exit for the previous cell, if there is one.
//
// ```markdown
// > | | aa | bb | cc |
// ^-- exit
// ^^^^-- this cell
// ```
if (range[0] !== 0) {
previousCell.end = Object.assign({}, getPoint(context.events, range[0]));
map.add(range[0], 0, [['exit', previousCell, context]]);
}
// Insert enter of this cell.
//
// ```markdown
// > | | aa | bb | cc |
// ^-- enter
// ^^^^-- this cell
// ```
const now = getPoint(context.events, range[1]);
previousCell = {
type: groupName,
start: Object.assign({}, now),
// Note: correct end is set later.
end: Object.assign({}, now)
};
map.add(range[1], 0, [['enter', previousCell, context]]);
// Insert text start at first data start and end at last data end, and
// remove events between.
//
// ```markdown
// > | | aa | bb | cc |
// ^-- enter
// ^-- exit
// ^^^^-- this cell
// ```
if (range[2] !== 0) {
const relatedStart = getPoint(context.events, range[2]);
const relatedEnd = getPoint(context.events, range[3]);
/** @type {Token} */
const valueToken = {
type: valueName,
start: Object.assign({}, relatedStart),
end: Object.assign({}, relatedEnd)
};
map.add(range[2], 0, [['enter', valueToken, context]]);
if (rowKind !== 2) {
// Fix positional info on remaining events
const start = context.events[range[2]];
const end = context.events[range[3]];
start[1].end = Object.assign({}, end[1].end);
start[1].type = "chunkText";
start[1].contentType = "text";
// Remove if needed.
if (range[3] > range[2] + 1) {
const a = range[2] + 1;
const b = range[3] - range[2] - 1;
map.add(a, b, []);
}
}
map.add(range[3] + 1, 0, [['exit', valueToken, context]]);
}
// Insert an exit for the last cell, if at the row end.
//
// ```markdown
// > | | aa | bb | cc |
// ^-- exit
// ^^^^^^-- this cell (the last one contains two “between” parts)
// ```
if (rowEnd !== undefined) {
previousCell.end = Object.assign({}, getPoint(context.events, rowEnd));
map.add(rowEnd, 0, [['exit', previousCell, context]]);
previousCell = undefined;
}
return previousCell;
}
/**
* Generate table end (and table body end).
*
* @param {Readonly<EditMap>} map
* @param {Readonly<TokenizeContext>} context
* @param {number} index
* @param {Token} table
* @param {Token | undefined} tableBody
*/
// eslint-disable-next-line max-params
function flushTableEnd(map, context, index, table, tableBody) {
/** @type {Array<Event>} */
const exits = [];
const related = getPoint(context.events, index);
if (tableBody) {
tableBody.end = Object.assign({}, related);
exits.push(['exit', tableBody, context]);
}
table.end = Object.assign({}, related);
exits.push(['exit', table, context]);
map.add(index + 1, 0, exits);
}
/**
* @param {Readonly<Array<Event>>} events
* @param {number} index
* @returns {Readonly<Point>}
*/
function getPoint(events, index) {
const event = events[index];
const side = event[0] === 'enter' ? 'start' : 'end';
return event[1][side];
}

22
node_modules/micromark-extension-gfm-table/license generated vendored Normal file
View File

@@ -0,0 +1,22 @@
(The MIT License)
Copyright (c) Titus Wormer <tituswormer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

128
node_modules/micromark-extension-gfm-table/package.json generated vendored Normal file
View File

@@ -0,0 +1,128 @@
{
"author": "Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)",
"bugs": "https://github.com/micromark/micromark-extension-gfm-table/issues",
"contributors": [
"Titus Wormer <tituswormer@gmail.com> (https://wooorm.com)"
],
"dependencies": {
"devlop": "^1.0.0",
"micromark-factory-space": "^2.0.0",
"micromark-util-character": "^2.0.0",
"micromark-util-symbol": "^2.0.0",
"micromark-util-types": "^2.0.0"
},
"description": "micromark extension to support GFM tables",
"devDependencies": {
"@types/node": "^22.0.0",
"c8": "^10.0.0",
"create-gfm-fixtures": "^2.0.0",
"micromark": "^4.0.0",
"micromark-build": "^2.0.0",
"prettier": "^3.0.0",
"remark-cli": "^12.0.0",
"remark-preset-wooorm": "^10.0.0",
"type-coverage": "^2.0.0",
"typescript": "^5.0.0",
"xo": "^0.60.0"
},
"exports": {
"development": "./dev/index.js",
"default": "./index.js"
},
"files": [
"dev/",
"index.d.ts",
"index.js",
"lib/"
],
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
},
"keywords": [
"cell",
"column",
"gfm",
"markdown",
"micromark-extension",
"micromark",
"row",
"table",
"tabular",
"unified"
],
"license": "MIT",
"name": "micromark-extension-gfm-table",
"prettier": {
"bracketSpacing": false,
"semi": false,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "none",
"useTabs": false
},
"remarkConfig": {
"plugins": [
"remark-preset-wooorm"
]
},
"repository": "micromark/micromark-extension-gfm-table",
"scripts": {
"build": "tsc --build --clean && tsc --build && type-coverage && micromark-build",
"format": "remark --frail --quiet --output -- . && prettier --log-level warn --write -- . && xo --fix",
"test-api-dev": "node --conditions development test/index.js",
"test-api-prod": "node --conditions production test/index.js",
"test-api": "npm run test-api-dev && npm run test-api-prod",
"test-coverage": "c8 --100 --reporter lcov -- npm run test-api",
"test": "npm run build && npm run format && npm run test-coverage"
},
"sideEffects": false,
"typeCoverage": {
"atLeast": 100,
"strict": true
},
"type": "module",
"version": "2.1.1",
"xo": {
"overrides": [
{
"files": [
"**/*.d.ts"
],
"rules": {
"@typescript-eslint/array-type": [
"error",
{
"default": "generic"
}
],
"@typescript-eslint/ban-types": [
"error",
{
"extendDefaults": true
}
],
"@typescript-eslint/consistent-type-definitions": [
"error",
"interface"
]
}
},
{
"files": [
"test/**/*.js"
],
"rules": {
"no-await-in-loop": "off"
}
}
],
"prettier": true,
"rules": {
"complexity": "off",
"max-depth": "off",
"unicorn/no-this-assignment": "off",
"unicorn/prefer-string-replace-all": "off"
}
}
}

515
node_modules/micromark-extension-gfm-table/readme.md generated vendored Normal file
View File

@@ -0,0 +1,515 @@
# micromark-extension-gfm-table
[![Build][build-badge]][build]
[![Coverage][coverage-badge]][coverage]
[![Downloads][downloads-badge]][downloads]
[![Size][size-badge]][size]
[![Sponsors][sponsors-badge]][collective]
[![Backers][backers-badge]][collective]
[![Chat][chat-badge]][chat]
[micromark][] extensions to support GFM [tables][].
## Contents
* [What is this?](#what-is-this)
* [When to use this](#when-to-use-this)
* [Install](#install)
* [Use](#use)
* [API](#api)
* [`gfmTable()`](#gfmtable)
* [`gfmTableHtml()`](#gfmtablehtml)
* [Bugs](#bugs)
* [Authoring](#authoring)
* [HTML](#html)
* [CSS](#css)
* [Syntax](#syntax)
* [Types](#types)
* [Compatibility](#compatibility)
* [Security](#security)
* [Related](#related)
* [Contribute](#contribute)
* [License](#license)
## What is this?
This package contains extensions that add support for the table syntax enabled
by GFM to [`micromark`][micromark].
These extensions match github.com.
## When to use this
This project is useful when you want to support tables in markdown.
You can use these extensions when you are working with [`micromark`][micromark].
To support all GFM features, use
[`micromark-extension-gfm`][micromark-extension-gfm] instead.
When you need a syntax tree, combine this package with
[`mdast-util-gfm-table`][mdast-util-gfm-table].
All these packages are used in [`remark-gfm`][remark-gfm], which focusses on
making it easier to transform content by abstracting these internals away.
## Install
This package is [ESM only][esm].
In Node.js (version 16+), install with [npm][]:
```sh
npm install micromark-extension-gfm-table
```
In Deno with [`esm.sh`][esmsh]:
```js
import {gfmTable, gfmTableHtml} from 'https://esm.sh/micromark-extension-gfm-table@2'
```
In browsers with [`esm.sh`][esmsh]:
```html
<script type="module">
import {gfmTable, gfmTableHtml} from 'https://esm.sh/micromark-extension-gfm-table@2?bundle'
</script>
```
## Use
```js
import {micromark} from 'micromark'
import {gfmTable, gfmTableHtml} from 'micromark-extension-gfm-table'
const output = micromark('| a |\n| - |', {
extensions: [gfmTable()],
htmlExtensions: [gfmTableHtml()]
})
console.log(output)
```
Yields:
```html
<table>
<thead>
<tr>
<th>a</th>
</tr>
</thead>
</table>
```
## API
This package exports the identifiers [`gfmTable`][api-gfm-table] and
[`gfmTableHtml`][api-gfm-table-html].
There is no default export.
The export map supports the [`development` condition][development].
Run `node --conditions development module.js` to get instrumented dev code.
Without this condition, production code is loaded.
### `gfmTable()`
Create an HTML extension for `micromark` to support GitHub tables syntax.
###### Returns
Extension for `micromark` that can be passed in `extensions` to enable GFM
table syntax ([`Extension`][micromark-extension]).
### `gfmTableHtml()`
Create an HTML extension for `micromark` to support GitHub tables when
serializing to HTML.
###### Returns
Extension for `micromark` that can be passed in `htmlExtensions` to support
GFM tables when serializing to HTML
([`HtmlExtension`][micromark-html-extension]).
## Bugs
GitHubs own algorithm to parse tables contains a bug.
This bug is not present in this project.
The issue relating to tables is:
* [GFM tables: escaped escapes are incorrectly treated as
escapes](https://github.com/github/cmark-gfm/issues/277)
## Authoring
When authoring markdown with GFM tables, its recommended to *always* put
pipes around cells.
Without them, it can be hard to infer whether the table will work, how many
columns there are, and which column you are currently editing.
It is recommended to not use many columns, as it results in very long lines,
making it hard to infer which column you are currently editing.
For larger tables, particularly when cells vary in size, it is recommended
*not* to manually “pad” cell text.
While it can look better, it results in a lot of time spent realigning
everything when a new, longer cell is added or the longest cell removed, as
every row then must be changed.
Other than costing time, it also causes large diffs in Git.
To illustrate, when authoring large tables, it is discouraged to pad cells
like this:
```markdown
| Alpha bravo charlie | delta |
| ------------------- | -----------------: |
| Echo | Foxtrot golf hotel |
```
Instead, use single spaces (and single filler dashes):
```markdown
| Alpha bravo charlie | delta |
| - | -: |
| Echo | Foxtrot golf hotel |
```
## HTML
GFM tables relate to several HTML elements: `<table>`, `<tbody>`, `<td>`,
`<th>`, `<thead>`, and `<tr>`.
See
[*§ 4.9.1 The `table` element*][html-table],
[*§ 4.9.5 The `tbody` element*][html-tbody],
[*§ 4.9.9 The `td` element*][html-td],
[*§ 4.9.10 The `th` element*][html-th],
[*§ 4.9.6 The `thead` element*][html-thead], and
[*§ 4.9.8 The `tr` element*][html-tr]
in the HTML spec for more info.
If the alignment of a column is left, right, or center, a deprecated
`align` attribute is added to each `<th>` and `<td>` element belonging to
that column.
That attribute is interpreted by browsers as if a CSS `text-align` property
was included, with its value set to that same keyword.
## CSS
The following CSS is needed to make tables look a bit like GitHub.
For the complete actual CSS see
[`sindresorhus/github-markdown-css`][github-markdown-css]
```css
/* Light theme. */
:root {
--color-canvas-default: #ffffff;
--color-canvas-subtle: #f6f8fa;
--color-border-default: #d0d7de;
--color-border-muted: hsla(210, 18%, 87%, 1);
}
/* Dark theme. */
@media (prefers-color-scheme: dark) {
:root {
--color-canvas-default: #0d1117;
--color-canvas-subtle: #161b22;
--color-border-default: #30363d;
--color-border-muted: #21262d;
}
}
table {
border-spacing: 0;
border-collapse: collapse;
display: block;
margin-top: 0;
margin-bottom: 16px;
width: max-content;
max-width: 100%;
overflow: auto;
}
tr {
background-color: var(--color-canvas-default);
border-top: 1px solid var(--color-border-muted);
}
tr:nth-child(2n) {
background-color: var(--color-canvas-subtle);
}
td,
th {
padding: 6px 13px;
border: 1px solid var(--color-border-default);
}
th {
font-weight: 600;
}
table img {
background-color: transparent;
}
```
## Syntax
Tables form with the following BNF:
```abnf
gfmTable ::= gfmTableHead 0*(eol gfmTableBodyRow)
; Restriction: both rows must have the same number of cells.
gfmTableHead ::= gfmTableRow eol gfmTableDelimiterRow
gfmTableRow ::= ["|"] gfmTableCell 0*("|" gfmTableCell) ["|"] *spaceOrTab
gfmTableCell ::= *spaceOrTab gfmTableText *spaceOrTab
gfmTableText ::= 0*(line - "\\" - "|" / "\\" ["\\" / "|"])
gfmTableDelimiterRow ::= ["|"] gfmTableDelimiterCell 0*("|" gfmTableDelimiterCell) ["|"] *spaceOrTab
gfmTableDelimiterCell ::= *spaceOrTab gfmTableDelimiterValue *spaceOrTab
gfmTableDelimiterValue ::= [":"] 1*"-" [":"]
```
As this construct occurs in flow, like all flow constructs, it must be
followed by an eol (line ending) or eof (end of file).
The above grammar shows that basically anything can be a cell or a row.
The main thing that makes something a row, is that it occurs directly before
or after a delimiter row, or after another row.
It is not required for a table to have a body: it can end right after the
delimiter row.
Each column can be marked with an alignment.
The alignment marker is a colon (`:`) used before and/or after delimiter row
filler.
To illustrate:
```markdown
| none | left | right | center |
| ---- | :--- | ----: | :----: |
```
The number of cells in the delimiter row, is the number of columns of the
table.
Only the head row is required to have the same number of cells.
Body rows are not required to have a certain number of cells.
For body rows that have less cells than the number of columns of the table,
empty cells are injected.
When a row has more cells than the number of columns of the table, the
superfluous cells are dropped.
To illustrate:
```markdown
| a | b |
| - | - |
| c |
| d | e | f |
```
Yields:
```html
<table>
<thead>
<tr>
<th>a</th>
<th>b</th>
</tr>
</thead>
<tbody>
<tr>
<td>c</td>
<td></td>
</tr>
<tr>
<td>d</td>
<td>e</td>
</tr>
</tbody>
</table>
```
Each cells text is interpreted as the [text][micromark-content-type] content
type.
That means that it can include constructs such as attention (emphasis, strong).
The grammar for cells prohibits the use of `|` in them.
To use pipes in cells, encode them as a character reference or character
escape: `&vert;` (or `&VerticalLine;`, `&verbar;`, `&#124;`, `&#x7c;`) or
`\|`.
Escapes will typically work, but they are not supported in
code (text) (and the math (text) extension).
To work around this, GitHub came up with a rather weird “trick”.
When inside a table cell *and* inside code, escaped pipes *are* decoded.
To illustrate:
```markdown
| Name | Character |
| - | - |
| Left curly brace | `{` |
| Pipe | `\|` |
| Right curly brace | `}` |
```
Yields:
```html
<table>
<thead>
<tr>
<th>Name</th>
<th>Character</th>
</tr>
</thead>
<tbody>
<tr>
<td>Left curly brace</td>
<td><code>{</code></td>
</tr>
<tr>
<td>Pipe</td>
<td><code>|</code></td>
</tr>
<tr>
<td>Right curly brace</td>
<td><code>}</code></td>
</tr>
</tbody>
</table>
```
> 👉 **Note**: no other character can be escaped like this.
> Escaping pipes in code does not work when not inside a table, either.
## Types
This package is fully typed with [TypeScript][].
It exports no additional types.
## Compatibility
Projects maintained by the unified collective are compatible with maintained
versions of Node.js.
When we cut a new major release, we drop support for unmaintained versions of
Node.
This means we try to keep the current release line,
`micromark-extension-gfm-table@^2`, compatible with Node.js 16.
This package works with `micromark` version `3` and later.
## Security
This package is safe.
## Related
* [`micromark-extension-gfm`][micromark-extension-gfm]
— support all of GFM
* [`mdast-util-gfm-table`][mdast-util-gfm-table]
— support all of GFM in mdast
* [`mdast-util-gfm`][mdast-util-gfm]
— support all of GFM in mdast
* [`remark-gfm`][remark-gfm]
— support all of GFM in remark
## Contribute
See [`contributing.md` in `micromark/.github`][contributing] for ways to get
started.
See [`support.md`][support] for ways to get help.
This project has a [code of conduct][coc].
By interacting with this repository, organization, or community you agree to
abide by its terms.
## License
[MIT][license] © [Titus Wormer][author]
<!-- Definitions -->
[build-badge]: https://github.com/micromark/micromark-extension-gfm-table/workflows/main/badge.svg
[build]: https://github.com/micromark/micromark-extension-gfm-table/actions
[coverage-badge]: https://img.shields.io/codecov/c/github/micromark/micromark-extension-gfm-table.svg
[coverage]: https://codecov.io/github/micromark/micromark-extension-gfm-table
[downloads-badge]: https://img.shields.io/npm/dm/micromark-extension-gfm-table.svg
[downloads]: https://www.npmjs.com/package/micromark-extension-gfm-table
[size-badge]: https://img.shields.io/badge/dynamic/json?label=minzipped%20size&query=$.size.compressedSize&url=https://deno.bundlejs.com/?q=micromark-extension-gfm-table
[size]: https://bundlejs.com/?q=micromark-extension-gfm-table
[sponsors-badge]: https://opencollective.com/unified/sponsors/badge.svg
[backers-badge]: https://opencollective.com/unified/backers/badge.svg
[collective]: https://opencollective.com/unified
[chat-badge]: https://img.shields.io/badge/chat-discussions-success.svg
[chat]: https://github.com/micromark/micromark/discussions
[npm]: https://docs.npmjs.com/cli/install
[esmsh]: https://esm.sh
[license]: license
[author]: https://wooorm.com
[contributing]: https://github.com/micromark/.github/blob/main/contributing.md
[support]: https://github.com/micromark/.github/blob/main/support.md
[coc]: https://github.com/micromark/.github/blob/main/code-of-conduct.md
[esm]: https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c
[typescript]: https://www.typescriptlang.org
[development]: https://nodejs.org/api/packages.html#packages_resolving_user_conditions
[micromark]: https://github.com/micromark/micromark
[micromark-extension]: https://github.com/micromark/micromark#syntaxextension
[micromark-html-extension]: https://github.com/micromark/micromark#htmlextension
[micromark-content-type]: https://github.com/micromark/micromark#content-types
[micromark-extension-gfm]: https://github.com/micromark/micromark-extension-gfm
[mdast-util-gfm]: https://github.com/syntax-tree/mdast-util-gfm
[mdast-util-gfm-table]: https://github.com/syntax-tree/mdast-util-gfm-table
[remark-gfm]: https://github.com/remarkjs/remark-gfm
[tables]: https://github.github.com/gfm/#tables-extension-
[html-table]: https://html.spec.whatwg.org/multipage/tables.html#the-table-element
[html-tbody]: https://html.spec.whatwg.org/multipage/tables.html#the-tbody-element
[html-thead]: https://html.spec.whatwg.org/multipage/tables.html#the-thead-element
[html-tr]: https://html.spec.whatwg.org/multipage/tables.html#the-tr-element
[html-td]: https://html.spec.whatwg.org/multipage/tables.html#the-td-element
[html-th]: https://html.spec.whatwg.org/multipage/tables.html#the-th-element
[github-markdown-css]: https://github.com/sindresorhus/github-markdown-css
[api-gfm-table]: #gfmtable
[api-gfm-table-html]: #gfmtablehtml