mdast-util-to-hast
Advanced tools
Comparing version 11.3.0 to 12.0.0
/** | ||
* @param {H} h | ||
*/ | ||
export function footer(h: H): import('hast').Element | null | ||
export function footer(h: H): { | ||
type: string | ||
tagName: string | ||
properties: { | ||
dataFootnotes: boolean | ||
className: string[] | ||
} | ||
children: ( | ||
| { | ||
type: string | ||
tagName: string | ||
properties: { | ||
id: string | ||
className: string[] | ||
} | ||
children: { | ||
type: 'text' | ||
value: string | ||
}[] | ||
value?: undefined | ||
} | ||
| { | ||
type: string | ||
value: string | ||
tagName?: undefined | ||
properties?: undefined | ||
children?: undefined | ||
} | ||
| { | ||
type: string | ||
tagName: string | ||
properties: { | ||
id?: undefined | ||
className?: undefined | ||
} | ||
children: import('hast').ElementContent[] | ||
value?: undefined | ||
} | ||
)[] | ||
} | null | ||
export type BlockContent = import('mdast').BlockContent | ||
export type FootnoteDefinition = import('mdast').FootnoteDefinition | ||
export type Link = import('mdast').Link | ||
export type ListItem = import('mdast').ListItem | ||
export type Paragraph = import('mdast').Paragraph | ||
export type Element = import('hast').Element | ||
export type ElementContent = import('hast').ElementContent | ||
export type H = import('./index.js').H |
/** | ||
* @typedef {import('mdast').BlockContent} BlockContent | ||
* @typedef {import('mdast').FootnoteDefinition} FootnoteDefinition | ||
* @typedef {import('mdast').Link} Link | ||
* @typedef {import('mdast').ListItem} ListItem | ||
* @typedef {import('mdast').Paragraph} Paragraph | ||
* @typedef {import('hast').Element} Element | ||
* @typedef {import('hast').ElementContent} ElementContent | ||
* @typedef {import('./index.js').H} H | ||
*/ | ||
import {thematicBreak} from './handlers/thematic-break.js' | ||
import {list} from './handlers/list.js' | ||
import {sanitizeUri} from 'micromark-util-sanitize-uri' | ||
import {u} from 'unist-builder' | ||
import {all} from './traverse.js' | ||
import {wrap} from './wrap.js' | ||
@@ -18,10 +18,8 @@ | ||
export function footer(h) { | ||
const footnoteById = h.footnoteById | ||
const footnoteOrder = h.footnoteOrder | ||
let index = -1 | ||
/** @type {Array.<ListItem>} */ | ||
/** @type {ElementContent[]} */ | ||
const listItems = [] | ||
while (++index < footnoteOrder.length) { | ||
const def = footnoteById[footnoteOrder[index].toUpperCase()] | ||
while (++index < h.footnoteOrder.length) { | ||
const def = h.footnoteById[h.footnoteOrder[index].toUpperCase()] | ||
@@ -32,28 +30,71 @@ if (!def) { | ||
const marker = String(index + 1) | ||
const content = [...def.children] | ||
/** @type {Link} */ | ||
const backReference = { | ||
type: 'link', | ||
url: '#fnref' + marker, | ||
data: {hProperties: {className: ['footnote-back'], role: 'doc-backlink'}}, | ||
children: [{type: 'text', value: '↩'}] | ||
const content = all(h, def) | ||
const id = String(def.identifier) | ||
const safeId = sanitizeUri(id.toLowerCase()) | ||
let referenceIndex = 0 | ||
/** @type {ElementContent[]} */ | ||
const backReferences = [] | ||
while (++referenceIndex <= h.footnoteCounts[id]) { | ||
/** @type {Element} */ | ||
const backReference = { | ||
type: 'element', | ||
tagName: 'a', | ||
properties: { | ||
href: | ||
'#' + | ||
h.clobberPrefix + | ||
'fnref-' + | ||
safeId + | ||
(referenceIndex > 1 ? '-' + referenceIndex : ''), | ||
dataFootnoteBackref: true, | ||
className: ['data-footnote-backref'], | ||
ariaLabel: h.footnoteBackLabel | ||
}, | ||
children: [{type: 'text', value: '↩'}] | ||
} | ||
if (referenceIndex > 1) { | ||
backReference.children.push({ | ||
type: 'element', | ||
tagName: 'sup', | ||
children: [{type: 'text', value: String(referenceIndex)}] | ||
}) | ||
} | ||
if (backReferences.length > 0) { | ||
backReferences.push({type: 'text', value: ' '}) | ||
} | ||
backReferences.push(backReference) | ||
} | ||
const tail = content[content.length - 1] | ||
if (tail && tail.type === 'paragraph') { | ||
tail.children.push(backReference) | ||
if (tail && tail.type === 'element' && tail.tagName === 'p') { | ||
const tailTail = tail.children[tail.children.length - 1] | ||
if (tailTail && tailTail.type === 'text') { | ||
tailTail.value += ' ' | ||
} else { | ||
tail.children.push({type: 'text', value: ' '}) | ||
} | ||
tail.children.push(...backReferences) | ||
} else { | ||
// @ts-expect-error Indeed, link directly added in block content. | ||
// Which we do because that way at least the handlers will be called | ||
// for the other HTML we’re generating (as markdown). | ||
content.push(backReference) | ||
content.push(...backReferences) | ||
} | ||
listItems.push({ | ||
type: 'listItem', | ||
data: {hProperties: {id: 'fn' + marker, role: 'doc-endnote'}}, | ||
children: content, | ||
position: def.position | ||
}) | ||
/** @type {Element} */ | ||
const listItem = { | ||
type: 'element', | ||
tagName: 'li', | ||
properties: {id: h.clobberPrefix + 'fn-' + safeId}, | ||
children: wrap(content, true) | ||
} | ||
if (def.position) { | ||
listItem.position = def.position | ||
} | ||
listItems.push(listItem) | ||
} | ||
@@ -65,14 +106,23 @@ | ||
return h( | ||
null, | ||
'section', | ||
{className: ['footnotes'], role: 'doc-endnotes'}, | ||
wrap( | ||
[ | ||
thematicBreak(h), | ||
list(h, {type: 'list', ordered: true, children: listItems}) | ||
], | ||
true | ||
) | ||
) | ||
return { | ||
type: 'element', | ||
tagName: 'section', | ||
properties: {dataFootnotes: true, className: ['footnotes']}, | ||
children: [ | ||
{ | ||
type: 'element', | ||
tagName: 'h2', | ||
properties: {id: 'footnote-label', className: ['sr-only']}, | ||
children: [u('text', h.footnoteLabel)] | ||
}, | ||
{type: 'text', value: '\n'}, | ||
{ | ||
type: 'element', | ||
tagName: 'ol', | ||
properties: {}, | ||
children: wrap(listItems, true) | ||
}, | ||
{type: 'text', value: '\n'} | ||
] | ||
} | ||
} |
@@ -6,2 +6,3 @@ /** | ||
import {sanitizeUri} from 'micromark-util-sanitize-uri' | ||
import {u} from 'unist-builder' | ||
@@ -14,20 +15,36 @@ | ||
export function footnoteReference(h, node) { | ||
const footnoteOrder = h.footnoteOrder | ||
const identifier = String(node.identifier) | ||
const index = footnoteOrder.indexOf(identifier) | ||
const marker = String( | ||
index === -1 ? footnoteOrder.push(identifier) : index + 1 | ||
) | ||
const id = String(node.identifier) | ||
const safeId = sanitizeUri(id.toLowerCase()) | ||
const index = h.footnoteOrder.indexOf(id) | ||
/** @type {number} */ | ||
let counter | ||
return h( | ||
node, | ||
'a', | ||
{ | ||
href: '#fn' + marker, | ||
className: ['footnote-ref'], | ||
id: 'fnref' + marker, | ||
role: 'doc-noteref' | ||
}, | ||
[h(node.position, 'sup', [u('text', marker)])] | ||
) | ||
if (index === -1) { | ||
h.footnoteOrder.push(id) | ||
h.footnoteCounts[id] = 1 | ||
counter = h.footnoteOrder.length | ||
} else { | ||
h.footnoteCounts[id]++ | ||
counter = index + 1 | ||
} | ||
const reuseCounter = h.footnoteCounts[id] | ||
return h(node, 'sup', [ | ||
h( | ||
node.position, | ||
'a', | ||
{ | ||
href: '#' + h.clobberPrefix + 'fn-' + safeId, | ||
id: | ||
h.clobberPrefix + | ||
'fnref-' + | ||
safeId + | ||
(reuseCounter > 1 ? '-' + reuseCounter : ''), | ||
dataFootnoteRef: true, | ||
ariaDescribedBy: 'footnote-label' | ||
}, | ||
[u('text', String(counter))] | ||
) | ||
]) | ||
} |
/** | ||
* @typedef {import('mdast').Footnote} Footnote | ||
* @typedef {import('../index.js').Handler} Handler | ||
* | ||
* @todo | ||
* `footnote` (or “inline note”) are a pandoc footnotes feature (`^[a note]`) | ||
* that does not exist in GFM. | ||
* We still have support for it, so that things remain working with | ||
* `micromark-extension-footnote` and `mdast-util-footnote`, but in the future | ||
* we might be able to remove it? | ||
*/ | ||
@@ -14,3 +21,2 @@ | ||
const footnoteById = h.footnoteById | ||
const footnoteOrder = h.footnoteOrder | ||
let no = 1 | ||
@@ -22,6 +28,2 @@ | ||
// No need to check if `identifier` exists in `footnoteOrder`, it’s guaranteed | ||
// to not exist because we just generated it. | ||
footnoteOrder.push(identifier) | ||
footnoteById[identifier] = { | ||
@@ -28,0 +30,0 @@ type: 'footnoteDefinition', |
@@ -76,2 +76,14 @@ /** | ||
/** | ||
* Prefix to use to prevent DOM clobbering | ||
*/ | ||
clobberPrefix: string | ||
/** | ||
* Label to use to introduce the footnote section | ||
*/ | ||
footnoteLabel: string | ||
/** | ||
* Label to use to go back to a footnote call from the footnote section | ||
*/ | ||
footnoteBackLabel: string | ||
/** | ||
* Definition cache | ||
@@ -91,2 +103,6 @@ */ | ||
/** | ||
* Counts the same footnote was used | ||
*/ | ||
footnoteCounts: Record<string, number> | ||
/** | ||
* Applied handlers | ||
@@ -117,2 +133,28 @@ */ | ||
/** | ||
* Prefix to use before the `id` attribute to prevent it from *clobbering*. | ||
* attributes. | ||
* DOM clobbering is this: | ||
* | ||
* ```html | ||
* <p id=x></p> | ||
* <script>alert(x)</script> | ||
* ``` | ||
* | ||
* Elements by their ID are made available in browsers on the `window` object. | ||
* Using a prefix prevents this from being a problem. | ||
*/ | ||
clobberPrefix?: string | undefined | ||
/** | ||
* Label to use for the footnotes section. | ||
* Affects screen reader users. | ||
* Change it if you’re authoring in a different language. | ||
*/ | ||
footnoteLabel?: string | undefined | ||
/** | ||
* Label to use from backreferences back to their footnote call. | ||
* Affects screen reader users. | ||
* Change it if you’re authoring in a different language. | ||
*/ | ||
footnoteBackLabel?: string | undefined | ||
/** | ||
* Object mapping mdast nodes to functions handling them | ||
@@ -119,0 +161,0 @@ */ |
@@ -45,5 +45,9 @@ /** | ||
* @property {boolean} dangerous Whether HTML is allowed | ||
* @property {string} clobberPrefix Prefix to use to prevent DOM clobbering | ||
* @property {string} footnoteLabel Label to use to introduce the footnote section | ||
* @property {string} footnoteBackLabel Label to use to go back to a footnote call from the footnote section | ||
* @property {(identifier: string) => Definition|null} definition Definition cache | ||
* @property {Object.<string, FootnoteDefinition>} footnoteById Footnote cache | ||
* @property {Array.<string>} footnoteOrder Order in which footnotes occur | ||
* @property {Record.<string, number>} footnoteCounts Counts the same footnote was used | ||
* @property {Handlers} handlers Applied handlers | ||
@@ -55,9 +59,35 @@ * @property {Handler} unknownHandler Handler for any none not in `passThrough` or otherwise handled | ||
* @typedef Options | ||
* @property {boolean} [allowDangerousHtml=false] Whether to allow `html` nodes and inject them as `raw` HTML | ||
* @property {Handlers} [handlers] Object mapping mdast nodes to functions handling them | ||
* @property {Array.<string>} [passThrough] List of custom mdast node types to pass through (keep) in hast | ||
* @property {Handler} [unknownHandler] Handler for all unknown nodes. | ||
* @property {boolean} [allowDangerousHtml=false] | ||
* Whether to allow `html` nodes and inject them as `raw` HTML | ||
* @property {string} [clobberPrefix='user-content-'] | ||
* Prefix to use before the `id` attribute to prevent it from *clobbering*. | ||
* attributes. | ||
* DOM clobbering is this: | ||
* | ||
* @typedef {Record.<string, Handler>} Handlers Map of node types to handlers | ||
* @typedef {HFunctionProps & HFunctionNoProps & HFields} H Handle context | ||
* ```html | ||
* <p id=x></p> | ||
* <script>alert(x)</script> | ||
* ``` | ||
* | ||
* Elements by their ID are made available in browsers on the `window` object. | ||
* Using a prefix prevents this from being a problem. | ||
* @property {string} [footnoteLabel='Footnotes'] | ||
* Label to use for the footnotes section. | ||
* Affects screen reader users. | ||
* Change it if you’re authoring in a different language. | ||
* @property {string} [footnoteBackLabel='Back to content'] | ||
* Label to use from backreferences back to their footnote call. | ||
* Affects screen reader users. | ||
* Change it if you’re authoring in a different language. | ||
* @property {Handlers} [handlers] | ||
* Object mapping mdast nodes to functions handling them | ||
* @property {Array.<string>} [passThrough] | ||
* List of custom mdast node types to pass through (keep) in hast | ||
* @property {Handler} [unknownHandler] | ||
* Handler for all unknown nodes. | ||
* | ||
* @typedef {Record.<string, Handler>} Handlers | ||
* Map of node types to handlers | ||
* @typedef {HFunctionProps & HFunctionNoProps & HFields} H | ||
* Handle context | ||
*/ | ||
@@ -89,2 +119,8 @@ | ||
h.dangerous = dangerous | ||
h.clobberPrefix = | ||
settings.clobberPrefix === undefined || settings.clobberPrefix === null | ||
? 'user-content-' | ||
: settings.clobberPrefix | ||
h.footnoteLabel = settings.footnoteLabel || 'Footnotes' | ||
h.footnoteBackLabel = settings.footnoteBackLabel || 'Back to content' | ||
h.definition = definitions(tree) | ||
@@ -94,2 +130,4 @@ h.footnoteById = footnoteById | ||
h.footnoteOrder = [] | ||
/** @type {Record.<string, number>} */ | ||
h.footnoteCounts = {} | ||
h.augment = augment | ||
@@ -96,0 +134,0 @@ h.handlers = {...handlers, ...settings.handlers} |
{ | ||
"name": "mdast-util-to-hast", | ||
"version": "11.3.0", | ||
"version": "12.0.0", | ||
"description": "mdast utility to transform to hast", | ||
@@ -43,2 +43,3 @@ "license": "MIT", | ||
"mdurl": "^1.0.0", | ||
"micromark-util-sanitize-uri": "^1.0.0", | ||
"unist-builder": "^3.0.0", | ||
@@ -53,7 +54,5 @@ "unist-util-generated": "^2.0.0", | ||
"hast-util-to-html": "^8.0.0", | ||
"mdast-util-footnote": "^1.0.0", | ||
"mdast-util-from-markdown": "^1.0.0", | ||
"mdast-util-gfm": "^1.0.0", | ||
"micromark-extension-footnote": "^1.0.0", | ||
"micromark-extension-gfm": "^1.0.0", | ||
"mdast-util-gfm": "^2.0.0", | ||
"micromark-extension-gfm": "^2.0.0", | ||
"prettier": "^2.0.0", | ||
@@ -66,3 +65,3 @@ "remark-cli": "^10.0.0", | ||
"typescript": "^4.0.0", | ||
"xo": "^0.44.0" | ||
"xo": "^0.45.0" | ||
}, | ||
@@ -69,0 +68,0 @@ "scripts": { |
@@ -75,2 +75,29 @@ # mdast-util-to-hast | ||
###### `options.clobberPrefix` | ||
Prefix to use before the `id` attribute on footnotes to prevent it from | ||
*clobbering* (`string`, default: `'user-content-'`). | ||
DOM clobbering is this: | ||
```html | ||
<p id=x></p> | ||
<script>alert(x)</script> | ||
``` | ||
Elements by their ID are made available in browsers on the `window` object. | ||
Using a prefix this that from being a problem. | ||
###### `options.footnoteLabel` | ||
Label to use for the footnotes section (`string`, default: `'Footnotes'`). | ||
Affects screen reader users. | ||
Change it if you’re authoring in a different language. | ||
###### `options.footnoteBackLabel` | ||
Label to use from backreferences back to their footnote call (`string`, default: | ||
`'Back to content'`). | ||
Affects screen reader users. | ||
Change it if you’re authoring in a different language. | ||
###### `options.handlers` | ||
@@ -233,2 +260,38 @@ | ||
## Recommended CSS | ||
The following CSS is needed to make footnotes look a bit like GitHub. | ||
For the complete actual CSS that GitHub uses see | ||
[`sindresorhus/github-markdown-css`](https://github.com/sindresorhus/github-markdown-css). | ||
```css | ||
/* Style the footnotes section. */ | ||
.footnotes { | ||
font-size: smaller; | ||
color: #8b949e; | ||
border-top: 1px solid #30363d; | ||
} | ||
/* Hide the section label for visual users. */ | ||
.sr-only { | ||
position: absolute; | ||
width: 1px; | ||
height: 1px; | ||
padding: 0; | ||
overflow: hidden; | ||
clip: rect(0, 0, 0, 0); | ||
word-wrap: normal; | ||
border: 0; | ||
} | ||
/* Place `[` and `]` around footnote calls. */ | ||
[data-footnote-ref]::before { | ||
content: '['; | ||
} | ||
[data-footnote-ref]::after { | ||
content: ']'; | ||
} | ||
``` | ||
## Security | ||
@@ -235,0 +298,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
64248
14
1744
478
10
+ Addedmicromark-util-character@1.2.0(transitive)
+ Addedmicromark-util-encode@1.1.0(transitive)
+ Addedmicromark-util-sanitize-uri@1.2.0(transitive)
+ Addedmicromark-util-symbol@1.1.0(transitive)
+ Addedmicromark-util-types@1.1.0(transitive)