diff --git a/packages/vfm/src/index.test.ts b/packages/vfm/src/index.test.ts
index 921e2af..d2b6ee4 100644
--- a/packages/vfm/src/index.test.ts
+++ b/packages/vfm/src/index.test.ts
@@ -21,6 +21,24 @@ it.skip('handle custom attributes', () => {
).toBe(`
Introduction
`);
});
+it('handle role', () => {
+ expect(
+ partial(`
+:::@tip
+# Tips
+:::
+`),
+ ).toBe(``);
+
+ expect(
+ partial(`
+:::@appendix
+# Appendix
+:::
+`),
+ ).toBe(``);
+});
+
it('reject incorrect fences', () => {
expect(
partial(`
diff --git a/packages/vfm/src/plugins/fencedBlock.ts b/packages/vfm/src/plugins/fencedBlock.ts
index 0ca3bd6..7a4a910 100644
--- a/packages/vfm/src/plugins/fencedBlock.ts
+++ b/packages/vfm/src/plugins/fencedBlock.ts
@@ -4,7 +4,11 @@ import u from 'unist-builder';
import {H, Handler} from 'mdast-util-to-hast';
import all from 'mdast-util-to-hast/lib/all';
+import {roleMappingTable} from '../utils/wai-aria';
+
const FENCE = ':';
+const ROLE_SYMBOL = '@';
+const FALLBACK_TAG = 'div';
let DEPTH = 0;
@@ -18,7 +22,7 @@ const tokenizer: Tokenizer = function (eat, value, silent) {
const fenceSymbol = FENCE.repeat(DEPTH + 3);
const match = new RegExp(
- `^${fenceSymbol}(.*?)\\n([\\w\\W]+?)\\n${fenceSymbol}$`,
+ `^${fenceSymbol}\s*(.*?)\s*\\n([\\w\\W]+?)\\n${fenceSymbol}$`,
'm',
).exec(value);
if (!match) return;
@@ -28,6 +32,11 @@ const tokenizer: Tokenizer = function (eat, value, silent) {
if (silent) return true;
+ const isRole = blockType.startsWith(ROLE_SYMBOL);
+ const role = isRole ? 'doc-' + blockType.substring(1) : undefined;
+ const type = (role && roleMappingTable[role]?.[0]) || FALLBACK_TAG;
+ const className = !isRole && blockType ? [blockType] : undefined;
+
const add = eat(eaten);
DEPTH += 1;
@@ -36,15 +45,14 @@ const tokenizer: Tokenizer = function (eat, value, silent) {
exit();
DEPTH -= 1;
- const type = 'div';
-
return add({
type,
children,
data: {
hName: type,
hProperties: {
- className: blockType ? [blockType] : undefined,
+ className,
+ role,
},
},
});
diff --git a/packages/vfm/src/utils/wai-aria.ts b/packages/vfm/src/utils/wai-aria.ts
new file mode 100644
index 0000000..8339f18
--- /dev/null
+++ b/packages/vfm/src/utils/wai-aria.ts
@@ -0,0 +1,46 @@
+// Digital Publishing WAI-ARIA Module 1.0
+// https://idpf.github.io/epub-guides/epub-aria-authoring/
+
+export type RoleMappingTable = Record;
+
+export const roleMappingTable: RoleMappingTable = {
+ 'doc-toc': ['nav', 'section'],
+ 'doc-tip': ['aside'],
+ 'doc-subtitle': ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'],
+ 'doc-backlink': ['a'],
+ 'doc-qna': ['section'],
+ 'doc-pullquote': ['aside', 'section'],
+ 'doc-prologue': ['section'],
+ 'doc-preface': ['section'],
+ 'doc-part': ['section'],
+ 'doc-pagebreak': ['hr'],
+ 'doc-pagelist': ['nav', 'section'],
+ 'doc-notice': ['section'],
+ 'doc-noteref': ['a'],
+ 'doc-example': ['aside', 'section'],
+ 'doc-introduction': ['section'],
+ 'doc-index': ['nav', 'section'],
+ 'doc-glossref': ['a'],
+ 'doc-glossary': ['section'],
+ 'doc-foreword': ['section'],
+ 'doc-footnote': ['aside', 'footer', 'header'],
+ 'doc-errata': ['section'],
+ 'doc-epilogue': ['section'],
+ 'doc-epigraph': ['div'],
+ 'doc-endnotes': ['section'],
+ 'doc-endnote': ['li'],
+ 'doc-dedication': ['section'],
+ 'doc-credits': ['section'],
+ 'doc-credit': ['section'],
+ 'doc-cover': ['img'],
+ 'doc-conclusion': ['section'],
+ 'doc-colophon': ['section'],
+ 'doc-chapter': ['section'],
+ 'doc-biblioref': ['a'],
+ 'doc-bibliography': ['section'],
+ 'doc-biblioentry': ['li'],
+ 'doc-appendix': ['section'],
+ 'doc-afterword': ['section'],
+ 'doc-acknowledgements': ['section'],
+ 'doc-abstract': ['section'],
+};