├── .travis.yml ├── .idea ├── encodings.xml ├── misc.xml ├── vcs.xml ├── modules.xml └── markdown-it-katex.iml ├── .github └── workflows │ └── npm.yml ├── test ├── all.js └── fixtures │ └── default.txt ├── package.json ├── index.html ├── LICENSE ├── browser.js ├── README.md ├── .gitignore └── index.js /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "9" 4 | - "8" -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/markdown-it-katex.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.github/workflows/npm.yml: -------------------------------------------------------------------------------- 1 | name: Node.js CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | 9 | strategy: 10 | matrix: 11 | node-version: [10, 12, 14] 12 | 13 | steps: 14 | - uses: actions/checkout@v2 15 | - name: Use Node.js ${{ matrix.node-version }} 16 | uses: actions/setup-node@v1 17 | with: 18 | node-version: ${{ matrix.node-version }} 19 | - run: npm install 20 | - run: npm run build --if-present 21 | - run: npm test 22 | env: 23 | CI: true 24 | -------------------------------------------------------------------------------- /test/all.js: -------------------------------------------------------------------------------- 1 | var path = require('path'), 2 | tape = require('tape'), 3 | testLoad = require('markdown-it-testgen').load, 4 | mdk = require('../index'); 5 | 6 | var md = require('markdown-it')() 7 | .use(mdk); 8 | 9 | /* this uses the markdown-it-testgen module to automatically generate tests 10 | based on an easy to read text file 11 | */ 12 | testLoad(path.join(__dirname, 'fixtures/default.txt'), function(data){ 13 | data.fixtures.forEach(function (fixture){ 14 | 15 | /* generic test definition code using tape */ 16 | tape(fixture.header, function(t){ 17 | t.plan(1); 18 | 19 | var expected = fixture.second.text, 20 | actual = md.render(fixture.first.text); 21 | 22 | t.equals(actual, expected); 23 | 24 | }); 25 | 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@iktakahiro/markdown-it-katex", 3 | "version": "4.0.1", 4 | "description": "Fast math support for markdown-it with KaTeX", 5 | "main": "index.js", 6 | "scripts": { 7 | "watch": "watchify browser.js -o bundle.js -v", 8 | "test": "node test/all.js" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+ssh://git@github.com:iktakahiro/markdown-it-katex.git" 13 | }, 14 | "keywords": [ 15 | "markdown", 16 | "KaTeX", 17 | "math", 18 | "LaTeX", 19 | "markdown-it-plugin", 20 | "markdown-it" 21 | ], 22 | "author": "Takahiro Ethan Ikeuchi @iktakahiro", 23 | "license": "MIT", 24 | "dependencies": { 25 | "katex": "^0.12.0" 26 | }, 27 | "devDependencies": { 28 | "markdown-it": "11.0.0", 29 | "markdown-it-testgen": "0.1.6", 30 | "tape": "5.0.1" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 23 | 24 | 25 | 36 | 37 |
38 | 39 | 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Waylon Flinn 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | --- 24 | 25 | The MIT License (MIT) 26 | 27 | Copyright (c) 2018 Takahiro Ethan Ikeuchi @iktakahiro 28 | 29 | Permission is hereby granted, free of charge, to any person obtaining a copy 30 | of this software and associated documentation files (the "Software"), to deal 31 | in the Software without restriction, including without limitation the rights 32 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 33 | copies of the Software, and to permit persons to whom the Software is 34 | furnished to do so, subject to the following conditions: 35 | 36 | The above copyright notice and this permission notice shall be included in all 37 | copies or substantial portions of the Software. 38 | 39 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 40 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 41 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 42 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 43 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 44 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 45 | SOFTWARE. 46 | -------------------------------------------------------------------------------- /browser.js: -------------------------------------------------------------------------------- 1 | var md = require('markdown-it')(), 2 | mk = require('./index'); 3 | 4 | md.use(mk); 5 | 6 | var input = document.getElementById('input'), 7 | output = document.getElementById('output'), 8 | button = document.getElementById('button'); 9 | 10 | button.addEventListener('click', function(ev){ 11 | 12 | var result = md.render(input.value); 13 | 14 | output.innerHTML = result; 15 | 16 | }); 17 | 18 | /* 19 | 20 | # Some Math 21 | 22 | $\sqrt{3x-1}+(1+x)^2$ 23 | 24 | # Maxwells Equations 25 | 26 | $\nabla \times \vec{\mathbf{B}} -\, \frac1c\, \frac{\partial\vec{\mathbf{E}}}{\partial t} 27 | = \frac{4\pi}{c}\vec{\mathbf{j}} \nabla \cdot \vec{\mathbf{E}} = 4 \pi \rho$ 28 | 29 | $\nabla \times \vec{\mathbf{E}}\, +\, \frac1c\, \frac{\partial\vec{\mathbf{B}}}{\partial t} = \vec{\mathbf{0}}$ (curl of $\vec{\mathbf{E}}$ is proportional to the time derivative of $\vec{\mathbf{B}}$) 30 | 31 | $\nabla \cdot \vec{\mathbf{B}} = 0$ 32 | 33 | 34 | 35 | \sqrt{3x-1}+(1+x)^2 36 | 37 | c = \pm\sqrt{a^2 + b^2} 38 | 39 | Maxwell's Equations 40 | 41 | \nabla \times \vec{\mathbf{B}} -\, \frac1c\, \frac{\partial\vec{\mathbf{E}}}{\partial t} 42 | = \frac{4\pi}{c}\vec{\mathbf{j}} \nabla \cdot \vec{\mathbf{E}} = 4 \pi \rho 43 | 44 | \nabla \times \vec{\mathbf{E}}\, +\, \frac1c\, \frac{\partial\vec{\mathbf{B}}}{\partial t} = \vec{\mathbf{0}} 45 | 46 | \nabla \cdot \vec{\mathbf{B}} = 0 47 | 48 | Same thing in a LaTeX array 49 | \begin{array}{c} 50 | 51 | \nabla \times \vec{\mathbf{B}} -\, \frac1c\, \frac{\partial\vec{\mathbf{E}}}{\partial t} & 52 | = \frac{4\pi}{c}\vec{\mathbf{j}} \nabla \cdot \vec{\mathbf{E}} & = 4 \pi \rho \\ 53 | 54 | \nabla \times \vec{\mathbf{E}}\, +\, \frac1c\, \frac{\partial\vec{\mathbf{B}}}{\partial t} & = \vec{\mathbf{0}} \\ 55 | 56 | \nabla \cdot \vec{\mathbf{B}} & = 0 57 | 58 | \end{array} 59 | 60 | 61 | \begin{array}{c} 62 | y_1 \\ 63 | y_2 \mathtt{t}_i \\ 64 | z_{3,4} 65 | \end{array} 66 | 67 | \begin{array}{c} 68 | x' &=& &x \sin\phi &+& z \cos\phi \\ 69 | z' &=& - &x \cos\phi &+& z \sin\phi \\ 70 | \end{array} 71 | 72 | 73 | 74 | # Maxwell's Equations 75 | 76 | 77 | equation | description 78 | ----------|------------ 79 | $\nabla \cdot \vec{\mathbf{B}} = 0$ | divergence of $\vec{\mathbf{B}}$ is zero 80 | $\nabla \times \vec{\mathbf{E}}\, +\, \frac1c\, \frac{\partial\vec{\mathbf{B}}}{\partial t} = \vec{\mathbf{0}}$ | curl of $\vec{\mathbf{E}}$ is proportional to the rate of change of $\vec{\mathbf{B}}$ 81 | $\nabla \times \vec{\mathbf{B}} -\, \frac1c\, \frac{\partial\vec{\mathbf{E}}}{\partial t} = \frac{4\pi}{c}\vec{\mathbf{j}} \nabla \cdot \vec{\mathbf{E}} = 4 \pi \rho$ | wha? 82 | 83 | ![electricity](http://i.giphy.com/Gty2oDYQ1fih2.gif) 84 | */ 85 | -------------------------------------------------------------------------------- /test/fixtures/default.txt: -------------------------------------------------------------------------------- 1 | 2 | Simple inline math 3 | . 4 | $1+1 = 2$ 5 | . 6 |

1+1=21+1 = 2

7 | . 8 | 9 | Simple block math 10 | . 11 | $$1+1 = 2$$ 12 | . 13 |

1+1=21+1 = 2 14 |

15 | . 16 | 17 | Ignore single dollar sign 18 | . 19 | It costs $5. 20 | . 21 |

It costs $5.

22 | . 23 | 24 | Ignore multiple dollar signs in separate paragraphs 25 | . 26 | This costs $5. 27 | 28 | That costs $10. 29 | . 30 |

This costs $5.

31 |

That costs $10.

32 | . 33 | 34 | Inline math inside heading 35 | . 36 | # The $N$-eigenvalue problem and two applications 37 | . 38 |

The NN-eigenvalue problem and two applications

39 | . 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # markdown-it-katex 2 | 3 | Add Math to your Markdown 4 | 5 | [![Build Status](https://travis-ci.org/iktakahiro/markdown-it-katex.svg?branch=master)](https://travis-ci.org/iktakahiro/markdown-it-katex) 6 | 7 | [KaTeX](https://github.com/Khan/KaTeX) is a faster alternative to MathJax. This plugin makes it easy to support in your markdown. 8 | 9 | Need convincing? 10 | 11 | * Check out the comparative benchmark: [KaTeX vs MathJax](https://jsperf.com/katex-vs-mathjax/42) 12 | 13 | ## Usage 14 | 15 | Install markdown-it 16 | 17 | ```bash 18 | npm install markdown-it 19 | ``` 20 | 21 | Install the plugin 22 | 23 | ```bash 24 | npm install @iktakahiro/markdown-it-katex 25 | ``` 26 | 27 | Use it in your javascript 28 | 29 | ```javascript 30 | var md = require('markdown-it')(), 31 | mk = require('@iktakahiro/markdown-it-katex'); 32 | 33 | md.use(mk); 34 | 35 | // double backslash is required for javascript strings, but not html input 36 | var result = md.render('# Math Rulez! \n $\\sqrt{3x-1}+(1+x)^2$'); 37 | ``` 38 | 39 | Include the KaTeX stylesheet in your html: 40 | 41 | ```html 42 | 43 | ``` 44 | 45 | If you're using the default markdown-it parser, I also recommend the [github stylesheet](https://github.com/sindresorhus/github-markdown-css): 46 | 47 | ```html 48 | 49 | ``` 50 | 51 | `KaTeX` options can be supplied with the second argument to use. 52 | 53 | ```javascript 54 | md.use(mk, {"throwOnError" : false, "errorColor" : " #cc0000"}); 55 | ``` 56 | 57 | ## Examples 58 | 59 | ### Inline 60 | 61 | Surround your LaTeX with a single `$` on each side for inline rendering. 62 | 63 | ```latex 64 | $\sqrt{3x-1}+(1+x)^2$ 65 | ``` 66 | 67 | ### Block 68 | 69 | Use two (`$$`) for block rendering. This mode uses bigger symbols and centers 70 | the result. 71 | 72 | ```latex 73 | $$\begin{array}{c} 74 | 75 | \nabla \times \vec{\mathbf{B}} -\, \frac1c\, \frac{\partial\vec{\mathbf{E}}}{\partial t} & 76 | = \frac{4\pi}{c}\vec{\mathbf{j}} \nabla \cdot \vec{\mathbf{E}} & = 4 \pi \rho \\ 77 | 78 | \nabla \times \vec{\mathbf{E}}\, +\, \frac1c\, \frac{\partial\vec{\mathbf{B}}}{\partial t} & = \vec{\mathbf{0}} \\ 79 | 80 | \nabla \cdot \vec{\mathbf{B}} & = 0 81 | 82 | \end{array}$$ 83 | ``` 84 | 85 | ## Syntax 86 | 87 | Math parsing in markdown is designed to agree with the conventions set by pandoc: 88 | 89 | Anything between two $ characters will be treated as TeX math. The opening $ must 90 | have a non-space character immediately to its right, while the closing $ must 91 | have a non-space character immediately to its left, and must not be followed 92 | immediately by a digit. Thus, $20,000 and $30,000 won’t parse as math. If for some 93 | reason you need to enclose text in literal $ characters, backslash-escape them and 94 | they won’t be treated as math delimiters. 95 | 96 | ## Math Syntax Support 97 | 98 | KaTeX is based on TeX and LaTeX. Support for both is growing. Here's a list of 99 | currently supported functions: 100 | 101 | [Things that KaTeX does not (yet) support](https://github.com/KaTeX/KaTeX/wiki/Things-that-KaTeX-does-not-%28yet%29-support) 102 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### macOS template 3 | # General 4 | .DS_Store 5 | .AppleDouble 6 | .LSOverride 7 | 8 | # Icon must end with two \r 9 | Icon 10 | 11 | # Thumbnails 12 | ._* 13 | 14 | # Files that might appear in the root of a volume 15 | .DocumentRevisions-V100 16 | .fseventsd 17 | .Spotlight-V100 18 | .TemporaryItems 19 | .Trashes 20 | .VolumeIcon.icns 21 | .com.apple.timemachine.donotpresent 22 | 23 | # Directories potentially created on remote AFP share 24 | .AppleDB 25 | .AppleDesktop 26 | Network Trash Folder 27 | Temporary Items 28 | .apdisk 29 | ### JetBrains template 30 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 31 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 32 | 33 | # User-specific stuff 34 | .idea/**/workspace.xml 35 | .idea/**/tasks.xml 36 | .idea/**/dictionaries 37 | .idea/**/shelf 38 | 39 | # Sensitive or high-churn files 40 | .idea/**/dataSources/ 41 | .idea/**/dataSources.ids 42 | .idea/**/dataSources.local.xml 43 | .idea/**/sqlDataSources.xml 44 | .idea/**/dynamic.xml 45 | .idea/**/uiDesigner.xml 46 | .idea/**/dbnavigator.xml 47 | 48 | # Gradle 49 | .idea/**/gradle.xml 50 | .idea/**/libraries 51 | 52 | # CMake 53 | cmake-build-debug/ 54 | cmake-build-release/ 55 | 56 | # Mongo Explorer plugin 57 | .idea/**/mongoSettings.xml 58 | 59 | # File-based project format 60 | *.iws 61 | 62 | # IntelliJ 63 | out/ 64 | 65 | # mpeltonen/sbt-idea plugin 66 | .idea_modules/ 67 | 68 | # JIRA plugin 69 | atlassian-ide-plugin.xml 70 | 71 | # Cursive Clojure plugin 72 | .idea/replstate.xml 73 | 74 | # Crashlytics plugin (for Android Studio and IntelliJ) 75 | com_crashlytics_export_strings.xml 76 | crashlytics.properties 77 | crashlytics-build.properties 78 | fabric.properties 79 | 80 | # Editor-based Rest Client 81 | .idea/httpRequests 82 | ### Windows template 83 | # Windows thumbnail cache files 84 | Thumbs.db 85 | ehthumbs.db 86 | ehthumbs_vista.db 87 | 88 | # Dump file 89 | *.stackdump 90 | 91 | # Folder config file 92 | [Dd]esktop.ini 93 | 94 | # Recycle Bin used on file shares 95 | $RECYCLE.BIN/ 96 | 97 | # Windows Installer files 98 | *.cab 99 | *.msi 100 | *.msix 101 | *.msm 102 | *.msp 103 | 104 | # Windows shortcuts 105 | *.lnk 106 | ### Node template 107 | # Logs 108 | logs 109 | *.log 110 | npm-debug.log* 111 | yarn-debug.log* 112 | yarn-error.log* 113 | 114 | # Runtime data 115 | pids 116 | *.pid 117 | *.seed 118 | *.pid.lock 119 | 120 | # Directory for instrumented libs generated by jscoverage/JSCover 121 | lib-cov 122 | 123 | # Coverage directory used by tools like istanbul 124 | coverage 125 | 126 | # nyc test coverage 127 | .nyc_output 128 | 129 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 130 | .grunt 131 | 132 | # Bower dependency directory (https://bower.io/) 133 | bower_components 134 | 135 | # node-waf configuration 136 | .lock-wscript 137 | 138 | # Compiled binary addons (https://nodejs.org/api/addons.html) 139 | build/Release 140 | 141 | # Dependency directories 142 | node_modules/ 143 | jspm_packages/ 144 | 145 | # TypeScript v1 declaration files 146 | typings/ 147 | 148 | # Optional npm cache directory 149 | .npm 150 | 151 | # Optional eslint cache 152 | .eslintcache 153 | 154 | # Optional REPL history 155 | .node_repl_history 156 | 157 | # Output of 'npm pack' 158 | *.tgz 159 | 160 | # Yarn Integrity file 161 | .yarn-integrity 162 | 163 | # dotenv environment variables file 164 | .env 165 | 166 | # next.js build output 167 | .next 168 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /* Process inline math */ 2 | /* 3 | Like markdown-it-simplemath, this is a stripped down, simplified version of: 4 | https://github.com/runarberg/markdown-it-math 5 | 6 | It differs in that it takes (a subset of) LaTeX as input and relies on KaTeX 7 | for rendering output. 8 | */ 9 | 10 | /*jslint node: true */ 11 | 'use strict'; 12 | 13 | var katex = require('katex'); 14 | 15 | // Test if potential opening or closing delimieter 16 | // Assumes that there is a "$" at state.src[pos] 17 | function isValidDelim(state, pos) { 18 | var prevChar, nextChar, 19 | max = state.posMax, 20 | can_open = true, 21 | can_close = true; 22 | 23 | prevChar = pos > 0 ? state.src.charCodeAt(pos - 1) : -1; 24 | nextChar = pos + 1 <= max ? state.src.charCodeAt(pos + 1) : -1; 25 | 26 | // Check non-whitespace conditions for opening and closing, and 27 | // check that closing delimeter isn't followed by a number 28 | if (prevChar === 0x20/* " " */ || prevChar === 0x09/* \t */ || 29 | (nextChar >= 0x30/* "0" */ && nextChar <= 0x39/* "9" */)) { 30 | can_close = false; 31 | } 32 | if (nextChar === 0x20/* " " */ || nextChar === 0x09/* \t */) { 33 | can_open = false; 34 | } 35 | 36 | return { 37 | can_open: can_open, 38 | can_close: can_close 39 | }; 40 | } 41 | 42 | function math_inline(state, silent) { 43 | var start, match, token, res, pos, esc_count; 44 | 45 | if (state.src[state.pos] !== "$") { return false; } 46 | 47 | res = isValidDelim(state, state.pos); 48 | if (!res.can_open) { 49 | if (!silent) { state.pending += "$"; } 50 | state.pos += 1; 51 | return true; 52 | } 53 | 54 | // First check for and bypass all properly escaped delimieters 55 | // This loop will assume that the first leading backtick can not 56 | // be the first character in state.src, which is known since 57 | // we have found an opening delimieter already. 58 | start = state.pos + 1; 59 | match = start; 60 | while ( (match = state.src.indexOf("$", match)) !== -1) { 61 | // Found potential $, look for escapes, pos will point to 62 | // first non escape when complete 63 | pos = match - 1; 64 | while (state.src[pos] === "\\") { pos -= 1; } 65 | 66 | // Even number of escapes, potential closing delimiter found 67 | if ( ((match - pos) % 2) == 1 ) { break; } 68 | match += 1; 69 | } 70 | 71 | // No closing delimter found. Consume $ and continue. 72 | if (match === -1) { 73 | if (!silent) { state.pending += "$"; } 74 | state.pos = start; 75 | return true; 76 | } 77 | 78 | // Check if we have empty content, ie: $$. Do not parse. 79 | if (match - start === 0) { 80 | if (!silent) { state.pending += "$$"; } 81 | state.pos = start + 1; 82 | return true; 83 | } 84 | 85 | // Check for valid closing delimiter 86 | res = isValidDelim(state, match); 87 | if (!res.can_close) { 88 | if (!silent) { state.pending += "$"; } 89 | state.pos = start; 90 | return true; 91 | } 92 | 93 | if (!silent) { 94 | token = state.push('math_inline', 'math', 0); 95 | token.markup = "$"; 96 | token.content = state.src.slice(start, match); 97 | } 98 | 99 | state.pos = match + 1; 100 | return true; 101 | } 102 | 103 | function math_block(state, start, end, silent){ 104 | var firstLine, lastLine, next, lastPos, found = false, token, 105 | pos = state.bMarks[start] + state.tShift[start], 106 | max = state.eMarks[start] 107 | 108 | if(pos + 2 > max){ return false; } 109 | if(state.src.slice(pos,pos+2)!=='$$'){ return false; } 110 | 111 | pos += 2; 112 | firstLine = state.src.slice(pos,max); 113 | 114 | if(silent){ return true; } 115 | if(firstLine.trim().slice(-2)==='$$'){ 116 | // Single line expression 117 | firstLine = firstLine.trim().slice(0, -2); 118 | found = true; 119 | } 120 | 121 | for(next = start; !found; ){ 122 | 123 | next++; 124 | 125 | if(next >= end){ break; } 126 | 127 | pos = state.bMarks[next]+state.tShift[next]; 128 | max = state.eMarks[next]; 129 | 130 | if(pos < max && state.tShift[next] < state.blkIndent){ 131 | // non-empty line with negative indent should stop the list: 132 | break; 133 | } 134 | 135 | if(state.src.slice(pos,max).trim().slice(-2)==='$$'){ 136 | lastPos = state.src.slice(0,max).lastIndexOf('$$'); 137 | lastLine = state.src.slice(pos,lastPos); 138 | found = true; 139 | } 140 | 141 | } 142 | 143 | state.line = next + 1; 144 | 145 | token = state.push('math_block', 'math', 0); 146 | token.block = true; 147 | token.content = (firstLine && firstLine.trim() ? firstLine + '\n' : '') 148 | + state.getLines(start + 1, next, state.tShift[start], true) 149 | + (lastLine && lastLine.trim() ? lastLine : ''); 150 | token.map = [ start, state.line ]; 151 | token.markup = '$$'; 152 | return true; 153 | } 154 | 155 | function escapeHtml(unsafe) { 156 | return unsafe 157 | .replace(/&/g, "&") 158 | .replace(//g, ">") 160 | .replace(/"/g, """) 161 | .replace(/'/g, "'"); 162 | } 163 | 164 | module.exports = function math_plugin(md, options) { 165 | // Default options 166 | 167 | options = options || {}; 168 | 169 | // set KaTeX as the renderer for markdown-it-simplemath 170 | var katexInline = function(latex){ 171 | options.displayMode = false; 172 | try{ 173 | return katex.renderToString(latex, options); 174 | } 175 | catch(error){ 176 | if(options.throwOnError){ console.log(error); } 177 | return `${escapeHtml(latex)}`; 178 | } 179 | }; 180 | 181 | var inlineRenderer = function(tokens, idx){ 182 | return katexInline(tokens[idx].content); 183 | }; 184 | 185 | var katexBlock = function(latex){ 186 | options.displayMode = true; 187 | try{ 188 | return "

" + katex.renderToString(latex, options) + "

"; 189 | } 190 | catch(error){ 191 | if(options.throwOnError){ console.log(error); } 192 | return `

${escapeHtml(latex)}

`; 193 | } 194 | } 195 | 196 | var blockRenderer = function(tokens, idx){ 197 | return katexBlock(tokens[idx].content) + '\n'; 198 | } 199 | 200 | md.inline.ruler.after('escape', 'math_inline', math_inline); 201 | md.block.ruler.after('blockquote', 'math_block', math_block, { 202 | alt: [ 'paragraph', 'reference', 'blockquote', 'list' ] 203 | }); 204 | md.renderer.rules.math_inline = inlineRenderer; 205 | md.renderer.rules.math_block = blockRenderer; 206 | }; 207 | --------------------------------------------------------------------------------