diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index fe284ad..69924a4 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -17,5 +17,5 @@ jobs: strategy: matrix: node: - - lts/erbium + - lts/fermium - node diff --git a/package.json b/package.json index ce5b853..8355b47 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mdast-util-gfm-table", - "version": "1.0.4", + "version": "1.0.5", "description": "mdast extension to parse and serialize GFM tables", "license": "MIT", "keywords": [ @@ -48,7 +48,7 @@ "c8": "^7.0.0", "micromark-extension-gfm-table": "^1.0.0", "prettier": "^2.0.0", - "remark-cli": "^10.0.0", + "remark-cli": "^11.0.0", "remark-preset-wooorm": "^9.0.0", "rimraf": "^3.0.0", "string-width": "^5.0.0", @@ -56,7 +56,7 @@ "type-coverage": "^2.0.0", "typescript": "^4.0.0", "unist-util-remove-position": "^4.0.0", - "xo": "^0.48.0" + "xo": "^0.52.0" }, "scripts": { "build": "rimraf \"lib/**/*.d.ts\" \"*.d.ts\" && tsc && type-coverage", diff --git a/readme.md b/readme.md index f112262..073c2d3 100644 --- a/readme.md +++ b/readme.md @@ -8,31 +8,76 @@ [![Backers][backers-badge]][collective] [![Chat][chat-badge]][chat] -Extension for [`mdast-util-from-markdown`][from-markdown] and/or -[`mdast-util-to-markdown`][to-markdown] to support GitHub flavored markdown -tables in **[mdast][]**. -When parsing (`from-markdown`), must be combined with -[`micromark-extension-gfm-table`][extension]. +[mdast][] extensions to parse and serialize [GFM][] tables. + +## Contents + +* [What is this?](#what-is-this) +* [When to use this](#when-to-use-this) +* [Install](#install) +* [Use](#use) +* [API](#api) + * [`gfmTableFromMarkdown`](#gfmtablefrommarkdown) + * [`gfmTableToMarkdown(options?)`](#gfmtabletomarkdownoptions) +* [Examples](#examples) + * [Example: `stringLength`](#example-stringlength) +* [Syntax tree](#syntax-tree) + * [Nodes](#nodes) + * [Enumeration](#enumeration) + * [Content model](#content-model) +* [Types](#types) +* [Compatibility](#compatibility) +* [Related](#related) +* [Contribute](#contribute) +* [License](#license) + +## What is this? + +This package contains extensions that add support for the table syntax enabled +by GFM to [`mdast-util-from-markdown`][mdast-util-from-markdown] and +[`mdast-util-to-markdown`][mdast-util-to-markdown]. ## When to use this -Use [`mdast-util-gfm`][mdast-util-gfm] if you want all of GFM. -Use this otherwise. +These tools are all rather low-level. +In most cases, you’d want to use [`remark-gfm`][remark-gfm] with remark instead. -## Install +When you are working with syntax trees and want all of GFM, use +[`mdast-util-gfm`][mdast-util-gfm] instead. -This package is [ESM only](https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c): -Node 12+ is needed to use it and it must be `import`ed instead of `require`d. +When working with `mdast-util-from-markdown`, you must combine this package with +[`micromark-extension-gfm-table`][extension]. -[npm][]: +This utility does not handle how markdown is turned to HTML. +That’s done by [`mdast-util-to-hast`][mdast-util-to-hast]. +If your content is not in English, you should configure that utility. + +## Install + +This package is [ESM only][esm]. +In Node.js (version 12.20+, 14.14+, or 16.0+), install with [npm][]: ```sh npm install mdast-util-gfm-table ``` +In Deno with [`esm.sh`][esmsh]: + +```js +import {gfmTableFromMarkdown, gfmTableToMarkdown} from 'https://esm.sh/mdast-util-gfm-table@1' +``` + +In browsers with [`esm.sh`][esmsh]: + +```html + +``` + ## Use -Say we have the following file, `example.md`: +Say our document `example.md` contains: ```markdown | a | b | c | d | @@ -41,16 +86,16 @@ Say we have the following file, `example.md`: | g | h | i | j | k | ``` -And our module, `example.js`, looks as follows: +…and our module `example.js` looks as follows: ```js -import fs from 'node:fs' +import fs from 'node:fs/promises' import {fromMarkdown} from 'mdast-util-from-markdown' import {toMarkdown} from 'mdast-util-to-markdown' import {gfmTable} from 'micromark-extension-gfm-table' import {gfmTableFromMarkdown, gfmTableToMarkdown} from 'mdast-util-gfm-table' -const doc = fs.readFileSync('example.md') +const doc = await fs.readFile('example.md') const tree = fromMarkdown(doc, { extensions: [gfmTable], @@ -64,8 +109,7 @@ const out = toMarkdown(tree, {extensions: [gfmTableToMarkdown()]}) console.log(out) ``` -Now, running `node example` yields (positional info removed for the sake of -brevity): +…now running `node example.js` yields (positional info removed for brevity): ```js { @@ -116,57 +160,303 @@ brevity): ## API -This package exports the following identifier: `gfmTableFromMarkdown`, +This package exports the identifiers `gfmTableFromMarkdown` and `gfmTableToMarkdown`. There is no default export. ### `gfmTableFromMarkdown` +Extension for [`mdast-util-from-markdown`][mdast-util-from-markdown]. + ### `gfmTableToMarkdown(options?)` -Support GFM tables. -The exports of `fromMarkdown` is an extension for -[`mdast-util-from-markdown`][from-markdown]. -The export of `toMarkdown` is a function that can be called with options and -returns an extension for [`mdast-util-to-markdown`][to-markdown]. +Function that can be called to get an extension for +[`mdast-util-to-markdown`][mdast-util-to-markdown]. ##### `options` +Configuration (optional). + ###### `options.tableCellPadding` -Create tables with a space between cell delimiters (`|`) and content (`boolean`, -default: `true`). +Serialize tables with a space between delimiters (`|`) and cell content +(`boolean`, default: `true`). ###### `options.tablePipeAlign` -Align the delimiters (`|`) between table cells so that they all align nicely and -form a grid (`boolean`, default: `true`). +Serialize by aligning the delimiters (`|`) between table cells so that they all +align nicely and form a grid (`boolean`, default: `true`). ###### `options.stringLength` -Function passed to [`markdown-table`][markdown-table] to detect the length of a -table cell (`Function`, default: [`s => s.length`][string-length]). -Used to pad tables. +Function to detect the length of table cell content (`Function`, default: +`s => s.length`). +This is used when aligning the delimiters (`|`) between table cells. +Full-width characters and emoji mess up delimiter alignment when viewing the +markdown source. +To fix this, you can pass this function, which receives the cell content and +returns its “visible” size. +Note that what is and isn’t visible depends on where the text is displayed. + +## Examples + +### Example: `stringLength` + +It’s possible to align tables based on the visual width of cells. +First, let’s show the problem: + +```js +import {fromMarkdown} from 'mdast-util-from-markdown' +import {toMarkdown} from 'mdast-util-to-markdown' +import {gfmTable} from 'micromark-extension-gfm-table' +import {gfmTableFromMarkdown, gfmTableToMarkdown} from 'mdast-util-gfm-table' + +const doc = `| Alpha | Bravo | +| - | - | +| 中文 | Charlie | +| 👩‍❤️‍👩 | Delta |` + +const tree = fromMarkdown(doc, { + extensions: [gfmTable], + mdastExtensions: [gfmTableFromMarkdown] +}) + +console.log(toMarkdown(tree, {extensions: [gfmTableToMarkdown()]})) +``` + +The above code shows how these utilities can be used to format markdown. +The output is as follows: + +```markdown +| Alpha | Bravo | +| -------- | ------- | +| 中文 | Charlie | +| 👩‍❤️‍👩 | Delta | +``` + +To improve the alignment of these full-width characters and emoji, pass a +`stringLength` function that calculates the visual width of cells. +One such algorithm is [`string-width`][string-width]. +It can be used like so: + +```diff +@@ -2,6 +2,7 @@ import {fromMarkdown} from 'mdast-util-from-markdown' + import {toMarkdown} from 'mdast-util-to-markdown' + import {gfmTable} from 'micromark-extension-gfm-table' + import {gfmTableFromMarkdown, gfmTableToMarkdown} from 'mdast-util-gfm-table' ++import stringWidth from 'string-width' + + const doc = `| Alpha | Bravo | + | - | - | +@@ -13,4 +14,8 @@ const tree = fromMarkdown(doc, { + mdastExtensions: [gfmTableFromMarkdown] + }) + +-console.log(toMarkdown(tree, {extensions: [gfmTableToMarkdown()]})) ++console.log( ++ toMarkdown(tree, { ++ extensions: [gfmTableToMarkdown({stringLength: stringWidth})] ++ }) ++) +``` + +The output of our code with these changes is as follows: + +```markdown +| Alpha | Bravo | +| ----- | ------- | +| 中文 | Charlie | +| 👩‍❤️‍👩 | Delta | +``` + +## Syntax tree + +The following interfaces are added to **[mdast][]** by this utility. + +### Nodes + +#### `Table` + +```idl +interface Table <: Parent { + type: "table" + align: [alignType]? + children: [TableContent] +} +``` + +**Table** ([**Parent**][dfn-parent]) represents two-dimensional data. + +**Table** can be used where [**flow**][dfn-flow-content] content is expected. +Its content model is [**table**][dfn-table-content] content. + +The [*head*][term-head] of the node represents the labels of the columns. + +An `align` field can be present. +If present, it must be a list of [**alignType**s][dfn-enum-align-type]. +It represents how cells in columns are aligned. + +For example, the following markdown: + +```markdown +| foo | bar | +| :-- | :-: | +| baz | qux | +``` + +Yields: + +```js +{ + type: 'table', + align: ['left', 'center'], + children: [ + { + type: 'tableRow', + children: [ + { + type: 'tableCell', + children: [{type: 'text', value: 'foo'}] + }, + { + type: 'tableCell', + children: [{type: 'text', value: 'bar'}] + } + ] + }, + { + type: 'tableRow', + children: [ + { + type: 'tableCell', + children: [{type: 'text', value: 'baz'}] + }, + { + type: 'tableCell', + children: [{type: 'text', value: 'qux'}] + } + ] + } + ] +} +``` + +#### `TableRow` + +```idl +interface TableRow <: Parent { + type: "tableRow" + children: [RowContent] +} +``` + +**TableRow** ([**Parent**][dfn-parent]) represents a row of cells in a table. + +**TableRow** can be used where [**table**][dfn-table-content] content is +expected. +Its content model is [**row**][dfn-row-content] content. + +If the node is a [*head*][term-head], it represents the labels of the columns +for its parent [**Table**][dfn-table]. + +For an example, see [**Table**][dfn-table]. + +#### `TableCell` + +```idl +interface TableCell <: Parent { + type: "tableCell" + children: [PhrasingContent] +} +``` + +**TableCell** ([**Parent**][dfn-parent]) represents a header cell in a +[**Table**][dfn-table], if its parent is a [*head*][term-head], or a data +cell otherwise. + +**TableCell** can be used where [**row**][dfn-row-content] content is expected. +Its content model is [**phrasing**][dfn-phrasing-content] content excluding +[**Break**][dfn-break] nodes. + +For an example, see [**Table**][dfn-table]. + +### Enumeration + +#### `alignType` + +```idl +enum alignType { + "left" | "right" | "center" | null +} +``` + +**alignType** represents how phrasing content is aligned +([\[CSSTEXT\]][css-text]). + +* **`'left'`**: See the [`left`][css-left] value of the `text-align` CSS + property +* **`'right'`**: See the [`right`][css-right] value of the `text-align` + CSS property +* **`'center'`**: See the [`center`][css-center] value of the `text-align` + CSS property +* **`null`**: phrasing content is aligned as defined by the host environment + +### Content model + +#### `FlowContent` (GFM table) + +```idl +type FlowContentGfm = Table | FlowContent +``` + +#### `TableContent` + +```idl +type TableContent = TableRow +``` + +**Table** content represent the rows in a table. + +#### `RowContent` + +```idl +type RowContent = TableCell +``` + +**Row** content represent the cells in a row. + +## Types + +This package is fully typed with [TypeScript][]. +It exports the additional type `Options`. + +The `Table`, `TableRow`, and `TableCell` node types are supported in +`@types/mdast` by default. + +## Compatibility + +Projects maintained by the unified collective are compatible with all maintained +versions of Node.js. +As of now, that is Node.js 12.20+, 14.14+, and 16.0+. +Our projects sometimes work with older versions, but this is not guaranteed. + +This plugin works with `mdast-util-from-markdown` version 1+ and +`mdast-util-to-markdown` version 1+. ## Related -* [`remarkjs/remark`][remark] - — markdown processor powered by plugins * [`remarkjs/remark-gfm`][remark-gfm] — remark plugin to support GFM -* [`micromark/micromark`][micromark] - — the smallest commonmark-compliant markdown parser that exists +* [`syntax-tree/mdast-util-gfm`][mdast-util-gfm] + — same but all of GFM (autolink literals, footnotes, strikethrough, tables, + tasklists) * [`micromark/micromark-extension-gfm-table`][extension] — micromark extension to parse GFM tables -* [`syntax-tree/mdast-util-from-markdown`][from-markdown] - — mdast parser using `micromark` to create mdast from markdown -* [`syntax-tree/mdast-util-to-markdown`][to-markdown] - — mdast serializer to create markdown from mdast ## Contribute -See [`contributing.md` in `syntax-tree/.github`][contributing] for ways to get -started. +See [`contributing.md`][contributing] in [`syntax-tree/.github`][health] for +ways to get started. See [`support.md`][support] for ways to get help. This project has a [code of conduct][coc]. @@ -207,32 +497,64 @@ abide by its terms. [npm]: https://docs.npmjs.com/cli/install +[esm]: https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c + +[esmsh]: https://esm.sh + +[typescript]: https://www.typescriptlang.org + [license]: license [author]: https://wooorm.com -[contributing]: https://github.com/syntax-tree/.github/blob/HEAD/contributing.md +[health]: https://github.com/syntax-tree/.github -[support]: https://github.com/syntax-tree/.github/blob/HEAD/support.md +[contributing]: https://github.com/syntax-tree/.github/blob/main/contributing.md -[coc]: https://github.com/syntax-tree/.github/blob/HEAD/code-of-conduct.md - -[mdast]: https://github.com/syntax-tree/mdast +[support]: https://github.com/syntax-tree/.github/blob/main/support.md -[remark]: https://github.com/remarkjs/remark +[coc]: https://github.com/syntax-tree/.github/blob/main/code-of-conduct.md [remark-gfm]: https://github.com/remarkjs/remark-gfm -[from-markdown]: https://github.com/syntax-tree/mdast-util-from-markdown +[mdast]: https://github.com/syntax-tree/mdast + +[mdast-util-gfm]: https://github.com/syntax-tree/mdast-util-gfm -[to-markdown]: https://github.com/syntax-tree/mdast-util-to-markdown +[mdast-util-from-markdown]: https://github.com/syntax-tree/mdast-util-from-markdown -[micromark]: https://github.com/micromark/micromark +[mdast-util-to-markdown]: https://github.com/syntax-tree/mdast-util-to-markdown + +[mdast-util-to-hast]: https://github.com/syntax-tree/mdast-util-to-hast [extension]: https://github.com/micromark/micromark-extension-gfm-table -[markdown-table]: https://github.com/wooorm/markdown-table +[gfm]: https://github.github.com/gfm/ -[string-length]: https://github.com/wooorm/markdown-table#optionsstringlength +[string-width]: https://github.com/sindresorhus/string-width -[mdast-util-gfm]: https://github.com/syntax-tree/mdast-util-gfm +[css-text]: https://drafts.csswg.org/css-text/ + +[css-left]: https://drafts.csswg.org/css-text/#valdef-text-align-left + +[css-right]: https://drafts.csswg.org/css-text/#valdef-text-align-right + +[css-center]: https://drafts.csswg.org/css-text/#valdef-text-align-center + +[term-head]: https://github.com/syntax-tree/unist#head + +[dfn-parent]: https://github.com/syntax-tree/mdast#parent + +[dfn-phrasing-content]: https://github.com/syntax-tree/mdast#phrasingcontent + +[dfn-break]: https://github.com/syntax-tree/mdast#break + +[dfn-flow-content]: #flowcontent-gfm-table + +[dfn-table-content]: #tablecontent + +[dfn-enum-align-type]: #aligntype + +[dfn-row-content]: #rowcontent + +[dfn-table]: #table