diff --git a/package.json b/package.json index 9f61b5b2..f8dc092a 100644 --- a/package.json +++ b/package.json @@ -113,6 +113,7 @@ "highlight.js": "^9.18.1", "katex": "^0.11.1", "markdown-it-emoji": "^1.4.0", + "mathjax-full": "^3.0.5", "postcss": "^7.0.27", "postcss-minify-params": "^4.0.2", "postcss-minify-selectors": "^4.0.2", diff --git a/src/fitting/fitting.scss b/src/fitting/fitting.scss index d78abf11..c15e897a 100644 --- a/src/fitting/fitting.scss +++ b/src/fitting/fitting.scss @@ -19,6 +19,7 @@ svg[data-marp-fitting='svg'].__reflow__ { } [data-marp-fitting-svg-content] { + width: 100%; display: table; white-space: nowrap; } diff --git a/src/marp.ts b/src/marp.ts index 1477110d..2c364000 100644 --- a/src/marp.ts +++ b/src/marp.ts @@ -9,6 +9,7 @@ import * as emojiPlugin from './emoji/emoji' import * as fittingPlugin from './fitting/fitting' import * as htmlPlugin from './html/html' import * as mathPlugin from './math/math' +import * as mathjaxPlugin from './mathjax/math' import * as scriptPlugin from './script/script' import * as sizePlugin from './size/size' import defaultTheme from '../themes/default.scss' @@ -25,6 +26,7 @@ export interface MarpOptions extends Options { | { [attr: string]: boolean | ((value: string) => string) } } markdown?: object + mathjax?: boolean math?: mathPlugin.MathOptions minifyCSS?: boolean script?: boolean | scriptPlugin.ScriptOptions @@ -47,6 +49,7 @@ export class Marp extends Marpit { super({ inlineSVG: true, looseYAML: true, + mathjax: false, math: true, minifyCSS: true, script: true, @@ -86,7 +89,10 @@ export class Marp extends Marpit { md.use(htmlPlugin.markdown) .use(emojiPlugin.markdown) - .use(mathPlugin.markdown, (flag) => (this.renderedMath = flag)) + .use( + this.options.mathjax ? mathjaxPlugin.markdown : mathPlugin.markdown, + (flag) => (this.renderedMath = flag) + ) .use(fittingPlugin.markdown) .use(sizePlugin.markdown) .use(scriptPlugin.markdown) @@ -126,7 +132,7 @@ export class Marp extends Marpit { if (typeof math === 'object') path = math.katexFontPath || undefined // Add KaTeX css - prepend(mathPlugin.css(path)) + prepend(this.options.mathjax ? mathjaxPlugin.css() : mathPlugin.css(path)) } return base diff --git a/src/mathjax/math.ts b/src/mathjax/math.ts new file mode 100644 index 00000000..2984336e --- /dev/null +++ b/src/mathjax/math.ts @@ -0,0 +1,206 @@ +import marpitPlugin from '@marp-team/marpit/plugin' + +import { mathjax } from 'mathjax-full/js/mathjax' +import { TeX } from 'mathjax-full/js/input/tex' +import { SVG } from 'mathjax-full/js/output/svg' +import { liteAdaptor } from 'mathjax-full/js/adaptors/liteAdaptor' +import { RegisterHTMLHandler } from 'mathjax-full/js/handlers/html' +import { AllPackages } from 'mathjax-full/js/input/tex/AllPackages' + +const adaptor = liteAdaptor() +RegisterHTMLHandler(adaptor) +const tex = new TeX({ packages: AllPackages }) +const svg = new SVG({ fontCache: 'none' }) +const mathDocument = mathjax.document('', { InputJax: tex, OutputJax: svg }) + +interface MathOptionsInterface { + katexOption?: object +} + +export type MathOptions = boolean | MathOptionsInterface + +/** + * marp-core math plugin + * + * It is implemented based on markdown-it-katex plugin. However, that is no + * longer maintained by author. So we have ported math typesetting parser. + * + * @see https://github.com/waylonflinn/markdown-it-katex + */ +export const markdown = marpitPlugin( + (md, updateState: (rendered: boolean) => void = () => {}) => { + const genOpts = (display: boolean) => { + const math: MathOptions = md.marpit.options.math + + return { + ...(typeof math === 'object' && typeof math.katexOption === 'object' + ? math.katexOption + : {}), + display, + } + } + + md.core.ruler.before('block', 'marp_math_initialize', (state) => { + if (state.inlineMode) return + + updateState(false) + + if (md.marpit.options.math) { + md.block.ruler.enable('marp_math_block') + md.inline.ruler.enable('marp_math_inline') + } else { + md.block.ruler.disable('marp_math_block') + md.inline.ruler.disable('marp_math_inline') + } + }) + + // Inline + md.inline.ruler.after('escape', 'marp_math_inline', (state, silent) => { + if (parseInlineMath(state, silent)) { + updateState(true) + return true + } + return false + }) + + md.renderer.rules.marp_math_inline = (tokens, idx) => { + const { content } = tokens[idx] + + try { + return adaptor.outerHTML(mathDocument.convert(content, genOpts(false))) + } catch (e) { + console.warn(e) + return content + } + } + + // Block + md.block.ruler.after( + 'blockquote', + 'marp_math_block', + (state, start, end, silent) => { + if (parseMathBlock(state, start, end, silent)) { + updateState(true) + return true + } + return false + }, + { alt: ['paragraph', 'reference', 'blockquote', 'list'] } + ) + + md.renderer.rules.marp_math_block = (tokens, idx) => { + const { content } = tokens[idx] + + try { + return `

${adaptor.outerHTML( + mathDocument.convert(content, genOpts(true)) + )}

` + } catch (e) { + console.warn(e) + return `

${content}

` + } + } + } +) + +export function css(): string { + return adaptor.textContent(svg.styleSheet(mathDocument) as any) +} + +function isValidDelim(state, pos = state.pos) { + const ret = { openable: true, closable: true } + const { posMax, src } = state + const prev = pos > 0 ? src.charCodeAt(pos - 1) : -1 + const next = pos + 1 <= posMax ? src.charCodeAt(pos + 1) : -1 + + if (next === 0x20 || next === 0x09) ret.openable = false + if (prev === 0x20 || prev === 0x09 || (next >= 0x30 && next <= 0x39)) { + ret.closable = false + } + + return ret +} + +function parseInlineMath(state, silent) { + const { src, pos } = state + if (src[pos] !== '$') return false + + const addPending = (stt: string) => (state.pending += stt) + const found = (manipulation: () => void, newPos: number) => { + if (!silent) manipulation() + state.pos = newPos + return true + } + + const start = pos + 1 + if (!isValidDelim(state).openable) return found(() => addPending('$'), start) + + let match = start + while ((match = src.indexOf('$', match)) !== -1) { + let dollarPos = match - 1 + while (src[dollarPos] === '\\') dollarPos -= 1 + + if ((match - dollarPos) % 2 === 1) break + match += 1 + } + + if (match === -1) return found(() => addPending('$'), start) + if (match - start === 0) return found(() => addPending('$$'), start + 1) + if (!isValidDelim(state, match).closable) { + return found(() => addPending('$'), start) + } + + return found(() => { + const token = state.push('marp_math_inline', 'math', 0) + token.markup = '$' + token.content = src.slice(start, match) + }, match + 1) +} + +function parseMathBlock(state, start, end, silent) { + const { blkIndent, bMarks, eMarks, src, tShift } = state + let pos = bMarks[start] + tShift[start] + let max = eMarks[start] + + if (pos + 2 > max || src.slice(pos, pos + 2) !== '$$') return false + if (silent) return true + + pos += 2 + + let firstLine = src.slice(pos, max) + let lastLine + let found = firstLine.trim().slice(-2) === '$$' + + if (found) firstLine = firstLine.trim().slice(0, -2) + + let next = start + for (; !found; ) { + next += 1 + if (next >= end) break + + pos = bMarks[next] + tShift[next] + max = eMarks[next] + if (pos < max && tShift[next] < blkIndent) break + + const target = src.slice(pos, max).trim() + + if (target.slice(-2) === '$$') { + found = true + lastLine = src.slice(pos, src.slice(0, max).lastIndexOf('$$')) + } + } + + state.line = next + 1 + + const token = state.push('marp_math_block', 'math', 0) + token.block = true + token.content = '' + token.map = [start, state.line] + token.markup = '$$' + + if (firstLine?.trim()) token.content += `${firstLine}\n` + token.content += state.getLines(start + 1, next, tShift[start], true) + if (lastLine?.trim()) token.content += lastLine + + return true +} diff --git a/test/fitting/observer.ts b/test/fitting/observer.ts index 06f362ef..24cb8e49 100644 --- a/test/fitting/observer.ts +++ b/test/fitting/observer.ts @@ -155,4 +155,78 @@ describe('Fitting observer', () => { expect(mathSvg.getAttribute('viewBox')).toBe('0 0 60 100') }) }) + + context('when the auto-scalable elements is rendered with MathJax', () => { + let codeSvg: SVGSVGElement + let codePre: HTMLPreElement + let codeContent: HTMLSpanElement + let mathSvg: SVGSVGElement + let mathP: HTMLParagraphElement + let mathContent: HTMLSpanElement + + beforeEach(() => { + document.body.innerHTML = new Marp({ mathjax: true }).render( + '```\nauto-scalble\n```\n\n$$ auto-scalable $$' + ).html + + codeSvg = document.querySelector( + 'svg[data-marp-fitting-code]' + )! + codePre = document.querySelector('section pre')! + codeContent = codeSvg.querySelector( + 'span[data-marp-fitting-svg-content]' + )! + mathSvg = document.querySelector( + 'svg[data-marp-fitting-math]' + )! + mathP = mathSvg.parentElement + mathContent = ( + mathSvg.querySelector('span[data-marp-fitting-svg-content]')! + ) + + setContentSize(codeContent, 200, 100) + setContentSize(mathContent, 50, 100) + }) + + const setClientWidth = (target, clientWidth) => + Object.defineProperty(target, 'clientWidth', { + configurable: true, + get: () => clientWidth, + }) + + it("restricts min width to
 element's width without padding", () => {
+      const computed = jest.spyOn(window, 'getComputedStyle')
+
+      computed.mockImplementation((): any => ({
+        paddingLeft: 0,
+        paddingRight: 0,
+        getPropertyValue: () => undefined,
+      }))
+
+      setClientWidth(codePre, 300) // pre width > code svg width
+      setClientWidth(mathP, 400) // p width > math svg width
+      fittingObserver()
+      expect(codeSvg.getAttribute('viewBox')).toBe('0 0 300 100')
+      expect(mathSvg.getAttribute('viewBox')).toBe('0 0 400 100')
+
+      setClientWidth(codePre, 100) // pre width < code svg width
+      setClientWidth(mathP, 25) // o width < math svg width
+      fittingObserver()
+      expect(codeSvg.getAttribute('viewBox')).toBe('0 0 200 100')
+      expect(mathSvg.getAttribute('viewBox')).toBe('0 0 50 100')
+
+      // Consider padding
+      computed.mockImplementation((): any => ({
+        paddingLeft: '50px',
+        paddingRight: '70px',
+        getPropertyValue: () => undefined,
+      }))
+
+      setClientWidth(codePre, 300) // 300 - 50 - 70 = 180px
+      setClientWidth(mathP, 180) // 180 - 50 - 70 = 60px
+      fittingObserver()
+      expect(codeSvg.getAttribute('viewBox')).toBe('0 0 200 100')
+      expect(mathSvg.getAttribute('viewBox')).toBe('0 0 60 100')
+    })
+  })
 })
diff --git a/test/marp.ts b/test/marp.ts
index 639a60c1..533aed09 100644
--- a/test/marp.ts
+++ b/test/marp.ts
@@ -319,6 +319,13 @@ describe('Marp', () => {
       expect($('.katex')).toHaveLength(2)
     })
 
+    it('renders math typesetting by MathJax', () => {
+      const { html } = marp({ mathjax: true }).render(`${inline}\n\n${block}`)
+      const $ = cheerio.load(html)
+
+      expect($('.MathJax')).toHaveLength(2)
+    })
+
     it('injects KaTeX css with replacing web font URL to CDN', () => {
       const { css } = marp().render(block)
       expect(css).toContain('.katex')
diff --git a/test/mathjax/__snapshots__/math.ts.snap b/test/mathjax/__snapshots__/math.ts.snap
new file mode 100644
index 00000000..a75c3d38
--- /dev/null
+++ b/test/mathjax/__snapshots__/math.ts.snap
@@ -0,0 +1,113 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`markdown-it math plugin allows to place text immediately after inline math 1`] = `
+"

-th order

+" +`; + +exports[`markdown-it math plugin can appear both maths in lists 1`] = ` +"
    +
  • +
  • +

  • +
+" +`; + +exports[`markdown-it math plugin does not allow paragraph break in inline math 1`] = ` +"

foo $1+1

+

= 2$ bar

+" +`; + +exports[`markdown-it math plugin does not process apparent markup in inline math 1`] = ` +"

foo bar

+" +`; + +exports[`markdown-it math plugin does not recognize inline block math 1`] = ` +"

It's well know that $$1 + 1 = 3$$ for sufficiently large 1.

+" +`; + +exports[`markdown-it math plugin does not render block math with indented up to 4 spaces (code block) 1`] = ` +"
$$
+1+1 = 2
+$$
+" +`; + +exports[`markdown-it math plugin does not render math when delimiters are escaped 1`] = ` +"

Foo $1$ bar +$$ +1 +$$

+" +`; + +exports[`markdown-it math plugin does not render math when numbers are followed closing inline math 1`] = ` +"

Thus, $20,000 and USD$30,000 won't parse as math.

+" +`; + +exports[`markdown-it math plugin does not render with empty content 1`] = ` +"

aaa $$ bbb

+" +`; + +exports[`markdown-it math plugin recognizes escaped delimiters in math mode 1`] = ` +"

Money adds: .

+" +`; + +exports[`markdown-it math plugin recognizes multiline escaped delimiters in math module 1`] = ` +"

Weird-o: .

+" +`; + +exports[`markdown-it math plugin renders block math composed multiple lines with starting/ending expression on delimited lines 1`] = `"

"`; + +exports[`markdown-it math plugin renders block math with indented up to 3 spaces 1`] = `"

"`; + +exports[`markdown-it math plugin renders block math with self-closing at the end of document 1`] = `"

"`; + +exports[`markdown-it math plugin renders block math written in one line 1`] = `"

"`; + +exports[`markdown-it math plugin renders math even when it starts with a negative sign 1`] = ` +"

foobar

+" +`; + +exports[`markdown-it math plugin renders math without whitespace before and after delimiter 1`] = ` +"

foobar

+" +`; + +exports[`markdown-it math plugin renders multiline display math 1`] = `"

"`; + +exports[`markdown-it math plugin renders multiline inline math 1`] = ` +"

foo bar

+" +`; + +exports[`markdown-it math plugin renders simple block math 1`] = `"

"`; + +exports[`markdown-it math plugin renders simple inline math 1`] = ` +"

+" +`; + +exports[`markdown-it math plugin requires a closing delimiter to render math 1`] = ` +"

aaa $5.99 bbb

+" +`; + +exports[`markdown-it math plugin requires non whitespace to left of closing inline math 1`] = ` +"

I will give you $20 today, if you give me more $ tomorrow.

+" +`; + +exports[`markdown-it math plugin requires non whitespace to right of opening inline math 1`] = ` +"

For some Europeans, it is 2$ for a can of soda, not 1$.

+" +`; diff --git a/test/mathjax/math.ts b/test/mathjax/math.ts new file mode 100644 index 00000000..f7aa4416 --- /dev/null +++ b/test/mathjax/math.ts @@ -0,0 +1,175 @@ +/** + * All test cases are ported from markdown-it-katex. + * + * @see https://github.com/waylonflinn/markdown-it-katex/blob/master/test/fixtures/default.txt + */ + +import MarkdownIt from 'markdown-it' +import { markdown as mathjaxPlugin } from '../../src/mathjax/math' + +const countMath = (stt) => stt.split('class="MathJax"').length - 1 +const countBlockMath = (stt) => stt.split('display="true"').length - 1 + +describe('markdown-it math plugin', () => { + const md = new MarkdownIt() + + md.marpit = { options: { mathjax: true, math: true } } + md.use(mathjaxPlugin) + + it('renders simple inline math', () => { + const rendered = md.render('$1+1 = 2$') + expect(countMath(rendered)).toBe(1) + expect(rendered).toMatchSnapshot() + }) + + it('renders simple block math', () => { + const rendered = md.render('$$1+1 = 2$$') + expect(countBlockMath(rendered)).toBe(1) + expect(rendered).toMatchSnapshot() + }) + + it('renders math without whitespace before and after delimiter', () => { + const rendered = md.render('foo$1+1 = 2$bar') + expect(countMath(rendered)).toBe(1) + expect(rendered).toMatchSnapshot() + }) + + it('renders math even when it starts with a negative sign', () => { + const rendered = md.render('foo$-1+1 = 0$bar') + expect(countMath(rendered)).toBe(1) + expect(rendered).toMatchSnapshot() + }) + + it('does not render with empty content', () => { + const rendered = md.render('aaa $$ bbb') + expect(countMath(rendered)).toBe(0) + expect(rendered).toMatchSnapshot() + }) + + it('requires a closing delimiter to render math', () => { + const rendered = md.render('aaa $5.99 bbb') + expect(countMath(rendered)).toBe(0) + expect(rendered).toMatchSnapshot() + }) + + it('does not allow paragraph break in inline math', () => { + const rendered = md.render('foo $1+1\n\n= 2$ bar') + expect(countMath(rendered)).toBe(0) + expect(rendered).toMatchSnapshot() + }) + + it('does not process apparent markup in inline math', () => { + const rendered = md.render('foo $1 *i* 1$ bar') + expect(countMath(rendered)).toBe(1) + expect(rendered).not.toContain('') + expect(rendered).toMatchSnapshot() + }) + + it('renders block math with indented up to 3 spaces', () => { + const rendered = md.render(' $$\n 1+1 = 2\n $$') + expect(countBlockMath(rendered)).toBe(1) + expect(rendered).toMatchSnapshot() + }) + + it('does not render block math with indented up to 4 spaces (code block)', () => { + const rendered = md.render(' $$\n 1+1 = 2\n $$') + expect(countBlockMath(rendered)).toBe(0) + expect(rendered).toContain('') + expect(rendered).toMatchSnapshot() + }) + + it('renders multiline inline math', () => { + const rendered = md.render('foo $1 + 1\n= 2$ bar') + expect(countMath(rendered)).toBe(1) + expect(rendered).toMatchSnapshot() + }) + + it('renders multiline display math', () => { + const rendered = md.render('$$\n\n 1\n+ 1\n\n= 2\n\n$$') + expect(countBlockMath(rendered)).toBe(1) + expect(rendered).toMatchSnapshot() + }) + + it('allows to place text immediately after inline math', () => { + const rendered = md.render('$n$-th order') + expect(countMath(rendered)).toBe(1) + expect(rendered).toMatchSnapshot() + }) + + it('renders block math with self-closing at the end of document', () => { + const rendered = md.render('$$\n1+1 = 2') + expect(countBlockMath(rendered)).toBe(1) + expect(rendered).toMatchSnapshot() + }) + + it('can appear both maths in lists', () => { + const rendered = md.render('* $1+1 = 2$\n* $$\n 1+1 = 2\n $$') + expect(countMath(rendered)).toBe(2) + expect(countBlockMath(rendered)).toBe(1) + expect(rendered).toMatchSnapshot() + }) + + it('renders block math written in one line', () => { + const rendered = md.render('$$1+1 = 2$$') + expect(countBlockMath(rendered)).toBe(1) + expect(rendered).toMatchSnapshot() + }) + + it('renders block math composed multiple lines with starting/ending expression on delimited lines', () => { + const rendered = md.render('$$[\n[1, 2]\n[3, 4]\n]$$') + expect(countBlockMath(rendered)).toBe(1) + expect(rendered).toMatchSnapshot() + }) + + it('does not render math when delimiters are escaped', () => { + const rendered = md.render('Foo \\$1$ bar\n\\$\\$\n1\n\\$\\$') + expect(countMath(rendered)).toBe(0) + expect(rendered).toMatchSnapshot() + }) + + it('does not render math when numbers are followed closing inline math', () => { + const rendered = md.render( + "Thus, $20,000 and USD$30,000 won't parse as math." + ) + expect(countMath(rendered)).toBe(0) + expect(rendered).toMatchSnapshot() + }) + + it('requires non whitespace to right of opening inline math', () => { + const rendered = md.render( + 'For some Europeans, it is 2$ for a can of soda, not 1$.' + ) + expect(countMath(rendered)).toBe(0) + expect(rendered).toMatchSnapshot() + }) + + it('requires non whitespace to left of closing inline math', () => { + const rendered = md.render( + 'I will give you $20 today, if you give me more $ tomorrow.' + ) + expect(countMath(rendered)).toBe(0) + expect(rendered).toMatchSnapshot() + }) + + it('does not recognize inline block math', () => { + const rendered = md.render( + "It's well know that $$1 + 1 = 3$$ for sufficiently large 1." + ) + expect(countMath(rendered)).toBe(0) + expect(rendered).toMatchSnapshot() + }) + + it('recognizes escaped delimiters in math mode', () => { + const rendered = md.render('Money adds: $\\$X + \\$Y = \\$Z$.') + expect(countMath(rendered)).toBe(1) + expect(rendered).toMatchSnapshot() + }) + + it('recognizes multiline escaped delimiters in math module', () => { + const rendered = md.render( + 'Weird-o: $\\displaystyle{\\begin{pmatrix} \\$ & 1\\\\\\$ \\end{pmatrix}}$.' + ) + expect(countMath(rendered)).toBe(1) + expect(rendered).toMatchSnapshot() + }) +}) diff --git a/tsconfig.json b/tsconfig.json index e8430cf5..e645814d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,7 +9,8 @@ "rootDir": ".", "sourceMap": true, "strict": true, - "target": "es2015" + "target": "es2015", + "skipLibCheck": true }, "include": ["src"] } diff --git a/yarn.lock b/yarn.lock index 8405f6b5..f1ec3255 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1694,6 +1694,11 @@ combined-stream@^1.0.6, combined-stream@~1.0.6: dependencies: delayed-stream "~1.0.0" +commander@*: + version "5.1.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae" + integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg== + commander@^2.12.1, commander@^2.19.0, commander@^2.20.0, commander@^2.9.0: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" @@ -2406,6 +2411,11 @@ escodegen@^1.11.1: optionalDependencies: source-map "~0.6.1" +esm@^3.2.25: + version "3.2.25" + resolved "https://registry.yarnpkg.com/esm/-/esm-3.2.25.tgz#342c18c29d56157688ba5ce31f8431fbb795cc10" + integrity sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA== + esprima@^4.0.0, esprima@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" @@ -4673,6 +4683,15 @@ marky@^1.2.0: resolved "https://registry.yarnpkg.com/marky/-/marky-1.2.1.tgz#a3fcf82ffd357756b8b8affec9fdbf3a30dc1b02" integrity sha512-md9k+Gxa3qLH6sUKpeC2CNkJK/Ld+bEz5X96nYwloqphQE0CKCVEKco/6jxEZixinqNdz5RFi/KaCyfbMDMAXQ== +mathjax-full@^3.0.5: + version "3.0.5" + resolved "https://registry.yarnpkg.com/mathjax-full/-/mathjax-full-3.0.5.tgz#f8d08965b318b9e1f830fde6ae7caa731797b393" + integrity sha512-RzZ03DDMOgqeHPl3dkqDVS3bmXU+lfZZimj7+k3VA+s93LbZcOAKa2hlILF3rgBXgzGLR3YOR8Z8R/B3uAeAeA== + dependencies: + esm "^3.2.25" + mj-context-menu "^0.2.2" + speech-rule-engine "^3.0.0-beta.10" + mathml-tag-names@^2.1.3: version "2.1.3" resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz#4ddadd67308e780cf16a47685878ee27b736a0a3" @@ -4848,6 +4867,11 @@ mixin-deep@^1.2.0: for-in "^1.0.2" is-extendable "^1.0.1" +mj-context-menu@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/mj-context-menu/-/mj-context-menu-0.2.2.tgz#446647475e22c6d83f7c6ff4afa37e66fd280784" + integrity sha512-OHlnKQqfFPEYZGdz2JWL0obrr82vVilha0WCUZslYfN+v+oz4VpmERnoHdTUWvOUVHNYjFkpOYnLEeHnt1BdsQ== + mkdirp-classic@^0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.2.tgz#54c441ce4c96cd7790e10b41a87aa51068ecab2b" @@ -6895,6 +6919,15 @@ specificity@^0.4.1: resolved "https://registry.yarnpkg.com/specificity/-/specificity-0.4.1.tgz#aab5e645012db08ba182e151165738d00887b019" integrity sha512-1klA3Gi5PD1Wv9Q0wUoOQN1IWAuPu0D1U03ThXTr0cJ20+/iq2tHSDnK7Kk/0LXJ1ztUB2/1Os0wKmfyNgUQfg== +speech-rule-engine@^3.0.0-beta.10: + version "3.0.1" + resolved "https://registry.yarnpkg.com/speech-rule-engine/-/speech-rule-engine-3.0.1.tgz#84d3a8e936488329b1f3485289efc90430ba054c" + integrity sha512-07B/V6eKZQfoKHdw8QaRu3ENKwbE8XcgCFNJAGeKnz751TixF6xUmxqLnsAN4zIj0qUCUKxCy4/LHybBMBmNfQ== + dependencies: + commander "*" + wicked-good-xpath "*" + xmldom-sre "^0.1.31" + split-string@^3.0.1, split-string@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" @@ -8069,6 +8102,11 @@ which@^2.0.1, which@^2.0.2: dependencies: isexe "^2.0.0" +wicked-good-xpath@*: + version "1.3.0" + resolved "https://registry.yarnpkg.com/wicked-good-xpath/-/wicked-good-xpath-1.3.0.tgz#81b0e95e8650e49c94b22298fff8686b5553cf6c" + integrity sha1-gbDpXoZQ5JyUsiKY//hoa1VTz2w= + widest-line@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-3.1.0.tgz#8292333bbf66cb45ff0de1603b136b7ae1496eca" @@ -8144,6 +8182,11 @@ xmlchars@^2.1.1: resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== +xmldom-sre@^0.1.31: + version "0.1.31" + resolved "https://registry.yarnpkg.com/xmldom-sre/-/xmldom-sre-0.1.31.tgz#10860d5bab2c603144597d04bf2c4980e98067f4" + integrity sha512-f9s+fUkX04BxQf+7mMWAp5zk61pciie+fFLC9hX9UVvCeJQfNHRHXpeo5MPcR0EUf57PYLdt+ZO4f3Ipk2oZUw== + xss@^1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/xss/-/xss-1.0.6.tgz#eaf11e9fc476e3ae289944a1009efddd8a124b51"