diff --git a/README.md b/README.md index fa2196a..dcc1778 100644 --- a/README.md +++ b/README.md @@ -42,8 +42,10 @@ const content = seeMarkReactParse(markdown); | ------------------------- | ------- | -------------- | ------------------------------------------------------------------ | | enableLatex | boolean | true | When false, LaTeX expressions are not parsed as math. | | enableAsciimath | boolean | true | When false, AsciiMath expressions are not parsed as math. | -| enableNemeth | boolean | true | When false, the Nemeth braille math extension is disabled and `@…@` syntax is not parsed. | +| enableNemeth | boolean | true | When false, the Nemeth braille math extension is disabled. | | latexDelimiter | string | 'bracket' | The delimiter for LaTeX expressions. Options: 'bracket', 'dollar'. | +| asciimathDelimiter | string | 'graveaccent' | The delimiter for AsciiMath expressions. Options: 'graveaccent', 'asciimath'. | +| nemethDelimiter | string | 'at' | The delimiter for Nemeth braille expressions. Options: 'at' (`@…@`), 'nemeth' (`\n…\n`). | | documentFormat | string | 'inline' | The format of the document. Options: 'inline', 'block'. | | imageFiles | object | { [ID]: File } | A map of image IDs to File objects for image rendering. | | shouldBuildImageObjectURL | boolean | false | should build image object URL. | @@ -132,8 +134,10 @@ const toc = createTableOfContents(markdown); | -------------- | ------- | ------------- | ------------------------------------------------------------------------------------------- | | enableLatex | boolean | true | When false, LaTeX expressions are not parsed as math. | | enableAsciimath | boolean | true | When false, AsciiMath expressions are not parsed as math. | -| enableNemeth | boolean | true | When false, the Nemeth braille math extension is disabled and `@…@` syntax is not parsed. | +| enableNemeth | boolean | true | When false, the Nemeth braille math extension is disabled. | | latexDelimiter | string | 'bracket' | The delimiter for LaTeX expressions. Options: 'bracket', 'dollar'. Must match the renderer. | +| asciimathDelimiter | string | 'graveaccent' | The delimiter for AsciiMath expressions. Options: 'graveaccent', 'asciimath'. Must match the renderer. | +| nemethDelimiter | string | 'at' | The delimiter for Nemeth braille expressions. Options: 'at' (`@…@`), 'nemeth' (`\n…\n`). Must match the renderer. | ### Return value diff --git a/package.json b/package.json index fde1c7c..4894324 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@coseeing/see-mark", - "version": "1.9.0", + "version": "1.10.0", "description": "A markdown parser for a11y", "main": "./lib/see-mark.cjs", "files": [ diff --git a/src/markdown-processor/__snapshots__/markdown-processor.test.js.snap b/src/markdown-processor/__snapshots__/markdown-processor.test.js.snap index 02241b2..58e43a9 100644 --- a/src/markdown-processor/__snapshots__/markdown-processor.test.js.snap +++ b/src/markdown-processor/__snapshots__/markdown-processor.test.js.snap @@ -9,7 +9,7 @@ exports[`markdownProcessor should handle list of math expressions, with list ite

\\n a\\n +\\n b\\n =\\n c\\n","svg":"","position":{"start":2,"end":11}}" + data-seemark-payload="{"math":"a+b=c","typed":"latex","mathMl":"\\n a\\n +\\n b\\n =\\n c\\n","svg":"","position":{"start":2,"end":11}}" />

@@ -21,7 +21,7 @@ exports[`markdownProcessor should handle list of math expressions, with list ite

\\n c\\n \\n b\\n =\\n a\\n","svg":"","position":{"start":15,"end":24}}" + data-seemark-payload="{"math":"c-b=a","typed":"latex","mathMl":"\\n c\\n \\n b\\n =\\n a\\n","svg":"","position":{"start":15,"end":24}}" />

@@ -66,7 +66,7 @@ exports[`markdownProcessor should handle nemeth braille math expressions 1`] = `

\\n \\n a\\n 2\\n \\n","svg":"","position":{"start":0,"end":5}}" + data-seemark-payload="{"math":"⠁⠘⠆","typed":"nemeth","latex":"a^2","mathMl":"\\n \\n a\\n 2\\n \\n","svg":"","position":{"start":0,"end":5}}" />

diff --git a/src/markdown-processor/markdown-processor.js b/src/markdown-processor/markdown-processor.js index 6a02285..37632cc 100644 --- a/src/markdown-processor/markdown-processor.js +++ b/src/markdown-processor/markdown-processor.js @@ -16,7 +16,8 @@ import imageDisplayLink from './marked-extentions/image-display-link'; import iframe from './marked-extentions/iframe'; export const createMarkdownProcessor = (options = {}) => { - const asciimathDelimiter = 'graveaccent'; + const asciimathDelimiter = options.asciimathDelimiter || 'graveaccent'; + const nemethDelimiter = options.nemethDelimiter || 'at'; const enableLatex = options.enableLatex !== false; const enableAsciimath = options.enableAsciimath !== false; @@ -25,6 +26,7 @@ export const createMarkdownProcessor = (options = {}) => { return markedProcessorFactory({ latexDelimiter: options.latexDelimiter, asciimathDelimiter, + nemethDelimiter, documentFormat: options.documentFormat, imageFiles: options.imageFiles, shouldBuildImageObjectURL: options.shouldBuildImageObjectURL, diff --git a/src/markdown-processor/markdown-processor.test.js b/src/markdown-processor/markdown-processor.test.js index 85d82a1..fdfaa64 100644 --- a/src/markdown-processor/markdown-processor.test.js +++ b/src/markdown-processor/markdown-processor.test.js @@ -61,6 +61,59 @@ describe('markdownProcessor', () => { expect(container).toMatchSnapshot(); }); + it('should handle asciimath expressions with graveaccent delimiter', () => { + const markdownContent = '`a+b=c`'; + const options = { + latexDelimiter: 'bracket', + documentFormat: 'inline', + imageFiles: {}, + }; + + const result = markdownProcessor(markdownContent, options); + + const container = createDOMFromHTML(result); + + const mathEl = getElementByType(container, SUPPORTED_COMPONENT_TYPES.MATH); + + expect(mathEl).toBeTruthy(); + + const payload = JSON.parse( + mathEl.getAttribute(SEE_MARK_PAYLOAD_DATA_ATTRIBUTES) + ); + + expect(payload.typed).toBe('asciimath'); + expect(payload.math).toBe('a+b=c'); + expect(payload.mathMl).toBeTruthy(); + expect(payload.svg).toBeTruthy(); + }); + + it('should handle asciimath expressions with asciimath delimiter', () => { + const markdownContent = '\\aa+b=c\\a'; + const options = { + asciimathDelimiter: 'asciimath', + latexDelimiter: 'bracket', + documentFormat: 'inline', + imageFiles: {}, + }; + + const result = markdownProcessor(markdownContent, options); + + const container = createDOMFromHTML(result); + + const mathEl = getElementByType(container, SUPPORTED_COMPONENT_TYPES.MATH); + + expect(mathEl).toBeTruthy(); + + const payload = JSON.parse( + mathEl.getAttribute(SEE_MARK_PAYLOAD_DATA_ATTRIBUTES) + ); + + expect(payload.typed).toBe('asciimath'); + expect(payload.math).toBe('a+b=c'); + expect(payload.mathMl).toBeTruthy(); + expect(payload.svg).toBeTruthy(); + }); + it('should handle nemeth braille math expressions', () => { const markdownContent = '@⠁⠘⠆@'; const options = { @@ -91,6 +144,34 @@ describe('markdownProcessor', () => { expect(container).toMatchSnapshot(); }); + it('should handle nemeth braille math expressions with backslash-n delimiter', () => { + const markdownContent = '\\n⠁⠘⠆\\n'; + const options = { + latexDelimiter: 'bracket', + nemethDelimiter: 'nemeth', + documentFormat: 'inline', + imageFiles: {}, + }; + + const result = markdownProcessor(markdownContent, options); + + const container = createDOMFromHTML(result); + + const mathEl = getElementByType(container, SUPPORTED_COMPONENT_TYPES.MATH); + + expect(mathEl).toBeTruthy(); + + const payload = JSON.parse( + mathEl.getAttribute(SEE_MARK_PAYLOAD_DATA_ATTRIBUTES) + ); + + expect(payload.typed).toBe('nemeth'); + expect(payload.latex).toBe('a^2'); + expect(payload.math).toBe('⠁⠘⠆'); + expect(payload.mathMl).toBeTruthy(); + expect(payload.svg).toBeTruthy(); + }); + it('should not parse nemeth braille when enableNemeth is false', () => { const markdownContent = '@⠁⠘⠆@'; const options = { diff --git a/src/markdown-processor/marked-extentions/nemeth.js b/src/markdown-processor/marked-extentions/nemeth.js index 0946513..bda535b 100644 --- a/src/markdown-processor/marked-extentions/nemeth.js +++ b/src/markdown-processor/marked-extentions/nemeth.js @@ -5,9 +5,12 @@ import { createRenderer } from './helpers'; import { SUPPORTED_COMPONENT_TYPES } from '../../shared/supported-components'; // ^@@ -const reNemeth = /^@([\u2800-\u28FF]+)@/; +const reNemethAt = /^@([\u2800-\u28FF]+)@/; +// ^\n\n (literal backslash+n) +const reNemethN = /^\\n([\u2800-\u28FF]+)\\n/; -const markedNemeth = ({ documentFormat }) => { +const markedNemeth = ({ documentFormat, nemethDelimiter = 'at' }) => { + const reNemeth = nemethDelimiter === 'nemeth' ? reNemethN : reNemethAt; const latex2mml = latex2mmlFactory({ htmlMathDisplay: documentFormat }); return { @@ -16,6 +19,11 @@ const markedNemeth = ({ documentFormat }) => { name: 'nemeth', level: 'inline', start(src) { + if (nemethDelimiter === 'nemeth') { + // literal \n followed by braille char + const result = src.match(/\\n(?=[\u2800-\u28FF])/); + return result?.index ?? Infinity; + } // @ followed by braille char (disambiguates from @[ and @!) const result = src.match(/@(?=[\u2800-\u28FF])/); return result?.index ?? Infinity; diff --git a/src/markdown-processor/marked-wrapper/marked-wrapper.js b/src/markdown-processor/marked-wrapper/marked-wrapper.js index 8d32f7f..72674d1 100644 --- a/src/markdown-processor/marked-wrapper/marked-wrapper.js +++ b/src/markdown-processor/marked-wrapper/marked-wrapper.js @@ -7,6 +7,7 @@ const markedProcessorFactory = ({ enableAsciimath = true, latexDelimiter, asciimathDelimiter, + nemethDelimiter, documentFormat, imageFiles = {}, shouldBuildImageObjectURL = false, @@ -31,6 +32,7 @@ const markedProcessorFactory = ({ enableAsciimath, latexDelimiter, asciimathDelimiter, + nemethDelimiter, documentFormat, imageFiles, shouldBuildImageObjectURL, diff --git a/src/table-of-contents/create-table-of-contents.js b/src/table-of-contents/create-table-of-contents.js index 3953966..2fe80cf 100644 --- a/src/table-of-contents/create-table-of-contents.js +++ b/src/table-of-contents/create-table-of-contents.js @@ -34,6 +34,10 @@ function extractPlainText(inlineTokens = []) { * Must match the value used by the markdown renderer so that math tokens * inside headings are recognised and their text is correctly extracted. * Defaults to 'bracket'. + * @param {string} [options.asciimathDelimiter] - AsciiMath delimiter style ('graveaccent', 'asciimath'). + * Must match the value used by the markdown renderer. Defaults to 'graveaccent'. + * @param {string} [options.nemethDelimiter] - Nemeth delimiter style ('at', 'nemeth'). + * Must match the value used by the markdown renderer. Defaults to 'at'. * @returns {{ level: number, id: string, text: string }[]} * * @example @@ -49,6 +53,8 @@ const createTableOfContents = (markdown, options = {}) => { enableAsciimath: options.enableAsciimath !== false, enableNemeth: options.enableNemeth !== false, latexDelimiter: options.latexDelimiter ?? 'bracket', + asciimathDelimiter: options.asciimathDelimiter ?? 'graveaccent', + nemethDelimiter: options.nemethDelimiter ?? 'at', }); const tokens = lexer(markdown); const usedIds = new Map(); diff --git a/src/table-of-contents/create-table-of-contents.test.js b/src/table-of-contents/create-table-of-contents.test.js index fb13b82..4dc2c7b 100644 --- a/src/table-of-contents/create-table-of-contents.test.js +++ b/src/table-of-contents/create-table-of-contents.test.js @@ -77,12 +77,10 @@ describe('createTableOfContents', () => { expect(result[0].id).toBe('hello-world'); }); - // FIXME: backtick code spans conflict with the graveaccent AsciiMath delimiter. - // With seemark's math extension registered, `npm install` is tokenised as a - // math token instead of a native code span, so text extraction drops the - // expression. - it.skip('should strip code span markdown from text field', () => { - const result = createTableOfContents('## Use `npm install`'); + it('should strip code span markdown from text field', () => { + const result = createTableOfContents('## Use `npm install`', { + asciimathDelimiter: 'asciimath', // backtick code spans conflict with the graveaccent AsciiMath delimiter + }); expect(result[0].text).toBe('Use npm install'); expect(result[0].id).toBe('use-npm-install');