@inclusive-design/eleventy-plugin-inclusive-footnotes 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.markdownlint-cli2.mjs +10 -0
- package/.nvmrc +1 -0
- package/.zed/settings.json +13 -0
- package/CHANGELOG.md +20 -0
- package/README.md +129 -0
- package/commitlint.config.js +5 -0
- package/index.js +73 -0
- package/localization/translations.json +12 -0
- package/package.json +59 -0
- package/tests/references.test.js +16 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import markdownlintConfig from '@inclusive-design/markdownlint-config';
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
config: Object.assign(markdownlintConfig.config, {
|
|
5
|
+
'no-hard-tabs': {
|
|
6
|
+
code_blocks: false,
|
|
7
|
+
},
|
|
8
|
+
}),
|
|
9
|
+
ignores: ['node_modules', 'CHANGELOG.md', 'fixtures/**/*.md'],
|
|
10
|
+
};
|
package/.nvmrc
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
24
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [1.0.0](https://github.com/inclusive-design/eleventy-plugin-inclusive-footnotes/compare/v0.1.0...v1.0.0) (2026-03-23)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* initial commit ([fe4498f](https://github.com/inclusive-design/eleventy-plugin-inclusive-footnotes/commit/fe4498f0889b5b00a3d4a6cb467bacb32eb2ab64))
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* rename workflow ([#9](https://github.com/inclusive-design/eleventy-plugin-inclusive-footnotes/issues/9)) ([1be9d7c](https://github.com/inclusive-design/eleventy-plugin-inclusive-footnotes/commit/1be9d7c77b55dd003ca1f3d800c309393e39b1e8))
|
|
14
|
+
* resolve linting issue ([#5](https://github.com/inclusive-design/eleventy-plugin-inclusive-footnotes/issues/5)) ([a4b8a28](https://github.com/inclusive-design/eleventy-plugin-inclusive-footnotes/commit/a4b8a28c34acddf07989c639beed586f4eaceb58))
|
|
15
|
+
* resolve linting issues, configure husky, add a test ([#3](https://github.com/inclusive-design/eleventy-plugin-inclusive-footnotes/issues/3)) ([b1dba7a](https://github.com/inclusive-design/eleventy-plugin-inclusive-footnotes/commit/b1dba7a71d4cb4a6eca9998ebbd46466d4cef9e6))
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### Miscellaneous Chores
|
|
19
|
+
|
|
20
|
+
* **release:** release 1.0.0 ([#13](https://github.com/inclusive-design/eleventy-plugin-inclusive-footnotes/issues/13)) ([ecfe1f8](https://github.com/inclusive-design/eleventy-plugin-inclusive-footnotes/commit/ecfe1f8a14c99332062b65e739cd4e83f1138be9))
|
package/README.md
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# Inclusive Footnotes for Eleventy
|
|
2
|
+
|
|
3
|
+
This plugin improves accessibility of HTML generated by the `markdown-it-footnotes` plugin while maintaining the ease of
|
|
4
|
+
authoring provided by Markdown footnotes. This plugin adds the following features:
|
|
5
|
+
|
|
6
|
+
- Footnote references have the `doc-noteref` role and a [localized](#localization) `aria-label`.
|
|
7
|
+
- The footnote container is a `<section>` with the `doc-endnotes` role.
|
|
8
|
+
- The footnote container has a [localized](#localization) heading which is semantically linked to the container with `aria-labelledby`.
|
|
9
|
+
- The footnote container heading's heading level can be [customized](#customization).
|
|
10
|
+
- Footnotes have the `doc-endnote` role.
|
|
11
|
+
- Backlinks to footnote references have the `doc-backlink` role and a [localized](#localization) `aria-label`.
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
Install the plugin in your project:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm i --save @inclusive-design/eleventy-plugin-inclusive-footnotes
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Add it to your Eleventy configuration file:
|
|
22
|
+
|
|
23
|
+
```js
|
|
24
|
+
import inclusiveFootnotesPlugin from "@inclusive-design/eleventy-plugin-inclusive-footnotes";
|
|
25
|
+
|
|
26
|
+
export default function (eleventyConfig) {
|
|
27
|
+
eleventyConfig.addPlugin(inclusiveFootnotesPlugin);
|
|
28
|
+
};
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Use footnotes in a Markdown file:
|
|
32
|
+
|
|
33
|
+
```markdown
|
|
34
|
+
Here is a footnote reference,[^1] and another.[^longnote]
|
|
35
|
+
|
|
36
|
+
Here is another reference[^1] to the first footnote.
|
|
37
|
+
|
|
38
|
+
[^1]: Here is the footnote.
|
|
39
|
+
|
|
40
|
+
[^longnote]: Here's one with multiple blocks.
|
|
41
|
+
|
|
42
|
+
Subsequent paragraphs are indented to show that they belong to the previous footnote.
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Here is the resulting HTML:
|
|
46
|
+
|
|
47
|
+
```html
|
|
48
|
+
<p>Here is a footnote reference,<sup class="footnote-ref"><a href="#fn1" role="doc-noteref" id="fnref1" aria-label="Footnote 1">1</a></sup> and another.<sup class="footnote-ref"><a href="#fn2" role="doc-noteref" id="fnref2" aria-label="Footnote 2">2</a></sup></p>
|
|
49
|
+
|
|
50
|
+
<p>Here is another reference<sup class="footnote-ref"><a href="#fn1" role="doc-noteref" id="fnref1:1" aria-label="Footnote 1">1</a></sup> to the first footnote.</p>
|
|
51
|
+
|
|
52
|
+
<section class="footnotes" role="doc-endnotes" aria-labelledby="footnotes">
|
|
53
|
+
<h2 id="footnotes">Footnotes</h2>
|
|
54
|
+
<ol class="footnotes-list">
|
|
55
|
+
<li id="fn1" role="doc-endnote" class="footnote-item">
|
|
56
|
+
<p>Here is the footnote.<a href="#fnref1" role="doc-backlink" class="footnote-backref" aria-label="Back to reference 1 for footnote 1">↩︎</a><a href="#fnref1:1" role="doc-backlink" class="footnote-backref" aria-label="Back to reference 2 for footnote 1">↩︎</a></p>
|
|
57
|
+
</li>
|
|
58
|
+
<li id="fn2" role="doc-endnote" class="footnote-item">
|
|
59
|
+
<p>Here's one with multiple blocks.</p>
|
|
60
|
+
<p>Subsequent paragraphs are indented to show that they belong to the previous footnote.<a href="#fnref2" role="doc-backlink" class="footnote-backref" aria-label="Back to reference 1 for footnote 2">↩︎</a></p>
|
|
61
|
+
</li>
|
|
62
|
+
</ol>
|
|
63
|
+
</section>
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Customization
|
|
67
|
+
|
|
68
|
+
There are two options when adding the plugin.
|
|
69
|
+
|
|
70
|
+
The `headingLevel` option can be used to change the footnotes
|
|
71
|
+
container's heading level from the default (2). For example, if the footnotes section should have an `<h3>`, add the
|
|
72
|
+
plugin with the following configuration:
|
|
73
|
+
|
|
74
|
+
```js
|
|
75
|
+
import inclusiveFootnotesPlugin from "@inclusive-design/eleventy-plugin-inclusive-footnotes";
|
|
76
|
+
|
|
77
|
+
export default function (eleventyConfig) {
|
|
78
|
+
eleventyConfig.addPlugin(inclusiveFootnotesPlugin, {
|
|
79
|
+
headingLevel: 3
|
|
80
|
+
});
|
|
81
|
+
};
|
|
82
|
+
````
|
|
83
|
+
|
|
84
|
+
The `translations` option allows the default localizations to be supplemented or overridden.
|
|
85
|
+
|
|
86
|
+
## Localization
|
|
87
|
+
|
|
88
|
+
The plugin's three strings are localized in French and English (the translations can be found in [translations.json](localization/translations.json)).
|
|
89
|
+
|
|
90
|
+
To supplement or override the default translations, pass your translations to the `translations` option:
|
|
91
|
+
|
|
92
|
+
```js
|
|
93
|
+
import inclusiveFootnotesPlugin from "@inclusive-design/eleventy-plugin-inclusive-footnotes";
|
|
94
|
+
|
|
95
|
+
export default function (eleventyConfig) {
|
|
96
|
+
eleventyConfig.addPlugin(inclusiveFootnotesPlugin, {
|
|
97
|
+
translations: {
|
|
98
|
+
en: {
|
|
99
|
+
footnote_ref: "Endnote %{id}",
|
|
100
|
+
footnotes: "Endnotes",
|
|
101
|
+
backlink: "Back to reference %{ref} for endnote %{footnote}"
|
|
102
|
+
},
|
|
103
|
+
de: {
|
|
104
|
+
footnote_ref: "Endnote %{id}",
|
|
105
|
+
footnotes: "Endnoten",
|
|
106
|
+
backlink: "Zurück zur Referenz %{ref} für Endnote %{footnote}"
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
};
|
|
111
|
+
````
|
|
112
|
+
|
|
113
|
+
This example changes the English strings to use "endnote" and "endnotes" instead of "footnote" and "footnotes" and adds
|
|
114
|
+
German localization. Because the French strings are not provided, the default French translations will be used.
|
|
115
|
+
|
|
116
|
+
## Credits
|
|
117
|
+
|
|
118
|
+
[Kitty Giraudel](https://kittygiraudel.com/blog/search/?q=footnotes)'s writing on accessible footnotes was
|
|
119
|
+
essential for the development of this plugin.
|
|
120
|
+
|
|
121
|
+
## Third Party Software in `@inclusive-design/eleventy-plugin-inclusive-footnotes`
|
|
122
|
+
|
|
123
|
+
`@inclusive-design/eleventy-plugin-inclusive-footnotes` is based on other publicly available software, categorized by license:
|
|
124
|
+
|
|
125
|
+
### MIT License
|
|
126
|
+
|
|
127
|
+
- [@11ty/eleventy-utils](https://github.com/11ty/eleventy-utils)
|
|
128
|
+
- [i18n-js](https://github.com/fnando/i18n)
|
|
129
|
+
- [markdown-it-footnote](https://github.com/markdown-it/markdown-it-footnote)
|
package/index.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import {Merge as eleventyMerge} from '@11ty/eleventy-utils';
|
|
2
|
+
import {I18n} from 'i18n-js';
|
|
3
|
+
import translations from './localization/translations.json' with {type: 'json'};
|
|
4
|
+
import markdownItFootnote from 'markdown-it-footnote';
|
|
5
|
+
|
|
6
|
+
const inclusiveFootnotesPlugin = {
|
|
7
|
+
configFunction(eleventyConfig, options = {}) {
|
|
8
|
+
options = eleventyMerge({
|
|
9
|
+
headingLevel: 2,
|
|
10
|
+
translations,
|
|
11
|
+
}, options);
|
|
12
|
+
|
|
13
|
+
const i18n = new I18n(options.translations);
|
|
14
|
+
|
|
15
|
+
eleventyConfig.addGlobalData('footnotesHeadingLevel', options.headingLevel);
|
|
16
|
+
|
|
17
|
+
eleventyConfig.amendLibrary('md', md => {
|
|
18
|
+
md.use(markdownItFootnote);
|
|
19
|
+
|
|
20
|
+
md.renderer.rules.footnote_caption = (tokens, index, _options, _environment, _slf) => (Number(tokens[index].meta.id + 1).toString());
|
|
21
|
+
|
|
22
|
+
md.renderer.rules.footnote_ref = (tokens, index, options, environment, slf) => {
|
|
23
|
+
i18n.locale = environment.lang ?? 'en';
|
|
24
|
+
|
|
25
|
+
const id = slf.rules.footnote_anchor_name(tokens, index, options, environment, slf);
|
|
26
|
+
const caption = slf.rules.footnote_caption(tokens, index, options, environment, slf);
|
|
27
|
+
let refid = id;
|
|
28
|
+
|
|
29
|
+
if (tokens[index].meta.subId > 0) {
|
|
30
|
+
refid += `:${tokens[index].meta.subId}`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return (`<sup class="footnote-ref"><a href="#fn${id}" role="doc-noteref" id="fnref${refid}" aria-label="${i18n.t('footnote_ref', {id})}">${caption}</a></sup>`);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
md.renderer.rules.footnote_block_open = (_tokens, _index, options, environment, _slf) => {
|
|
37
|
+
i18n.locale = environment.lang ?? 'en';
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
`<section class="footnotes" role="doc-endnotes" aria-labelledby="footnotes">
|
|
41
|
+
<h${environment.footnotesHeadingLevel} id="footnotes">${i18n.t('footnotes')}</h${environment.footnotesHeadingLevel}>
|
|
42
|
+
<ol class="footnotes-list">`
|
|
43
|
+
);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
md.renderer.rules.footnote_open = (tokens, index, options, environment, slf) => {
|
|
47
|
+
let id = slf.rules.footnote_anchor_name(tokens, index, options, environment, slf);
|
|
48
|
+
|
|
49
|
+
if (tokens[index].meta.subId > 0) {
|
|
50
|
+
id += `:${tokens[index].meta.subId}`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return (`<li id="fn${id}" role="doc-endnote" class="footnote-item">`);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
md.renderer.rules.footnote_anchor = (tokens, index, options, environment, slf) => {
|
|
57
|
+
i18n.locale = environment.lang ?? 'en';
|
|
58
|
+
|
|
59
|
+
let id = slf.rules.footnote_anchor_name(tokens, index, options, environment, slf);
|
|
60
|
+
const label = i18n.t('backlink', {ref: tokens[index].meta.subId + 1, footnote: id});
|
|
61
|
+
|
|
62
|
+
if (tokens[index].meta.subId > 0) {
|
|
63
|
+
id += `:${tokens[index].meta.subId}`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/* ↩ with escape code to prevent display as Apple Emoji on iOS */
|
|
67
|
+
return (`<a href="#fnref${id}" role="doc-backlink" class="footnote-backref" aria-label="${label}">\u21A9\uFE0E</a>`);
|
|
68
|
+
};
|
|
69
|
+
});
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export default inclusiveFootnotesPlugin;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"en": {
|
|
3
|
+
"footnote_ref": "Footnote %{id}",
|
|
4
|
+
"footnotes": "Footnotes",
|
|
5
|
+
"backlink": "Back to reference %{ref} for footnote %{footnote}"
|
|
6
|
+
},
|
|
7
|
+
"fr": {
|
|
8
|
+
"footnote_ref": "Note de bas de page %{id}",
|
|
9
|
+
"footnotes": "Notes de bas de page",
|
|
10
|
+
"backlink": "Retourner à la référence %{ref} pour la note de bas de page %{footnote}"
|
|
11
|
+
}
|
|
12
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@inclusive-design/eleventy-plugin-inclusive-footnotes",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "This plugin improves accessibility of HTML generated by the markdown-it-footnotes plugin for Eleventy.",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"lint": "run-s lint:*",
|
|
9
|
+
"lint:eslint": "eslint",
|
|
10
|
+
"lint:markdownlint": "markdownlint-cli2 \"**/*.md\"",
|
|
11
|
+
"start": "eleventy --serve",
|
|
12
|
+
"test": "node --test",
|
|
13
|
+
"prepare": "husky"
|
|
14
|
+
},
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+ssh://git@github.com/inclusive-design/eleventy-plugin-inclusive-footnotes.git"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"11ty",
|
|
21
|
+
"markdown-it",
|
|
22
|
+
"footnotes"
|
|
23
|
+
],
|
|
24
|
+
"author": "OCAD University <idrc@ocadu.ca>",
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"bugs": {
|
|
27
|
+
"url": "https://github.com/inclusive-design/eleventy-plugin-inclusive-footnotes/issues"
|
|
28
|
+
},
|
|
29
|
+
"homepage": "https://github.com/inclusive-design/eleventy-plugin-inclusive-footnotes#readme",
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@11ty/eleventy-utils": "^2.0.7",
|
|
32
|
+
"i18n-js": "^4.5.3",
|
|
33
|
+
"markdown-it-footnote": "^4.0.0"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@11ty/eleventy": "^3.1.2",
|
|
37
|
+
"@commitlint/cli": "^20.5.0",
|
|
38
|
+
"@commitlint/config-conventional": "^20.5.0",
|
|
39
|
+
"@inclusive-design/eslint-config": "^0.3.0",
|
|
40
|
+
"@inclusive-design/markdownlint-config": "^0.1.0",
|
|
41
|
+
"eslint": "^10.0.3",
|
|
42
|
+
"husky": "^9.1.7",
|
|
43
|
+
"lint-staged": "^16.4.0",
|
|
44
|
+
"markdownlint-cli2": "^0.22.0",
|
|
45
|
+
"npm-run-all2": "^8.0.4"
|
|
46
|
+
},
|
|
47
|
+
"engines": {
|
|
48
|
+
"node": ">=24"
|
|
49
|
+
},
|
|
50
|
+
"lint-staged": {
|
|
51
|
+
"*.{cjs,js,json,mjs}": [
|
|
52
|
+
"eslint --fix"
|
|
53
|
+
],
|
|
54
|
+
"*.md": [
|
|
55
|
+
"eslint --fix",
|
|
56
|
+
"markdownlint-cli2 --fix"
|
|
57
|
+
]
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import assert from 'node:assert';
|
|
2
|
+
import {beforeEach, test} from 'node:test';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import Eleventy from '@11ty/eleventy';
|
|
5
|
+
|
|
6
|
+
beforeEach(async () => {
|
|
7
|
+
const elev = new Eleventy('.', '_site', {
|
|
8
|
+
quietMode: true,
|
|
9
|
+
});
|
|
10
|
+
await elev.write();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
test('References have appropriate role', async () => {
|
|
14
|
+
const indexPage = fs.readFileSync('_site/index.html', 'utf8');
|
|
15
|
+
assert.ok(indexPage.includes('<a href="#fn1" role="doc-noteref"'));
|
|
16
|
+
});
|