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(`

Appendix

`); +}); + 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'], +};