├── .babelrc ├── .editorconfig ├── .eslintrc.js ├── .gitignore ├── LICENSE ├── README.md ├── dist ├── vue-markdown.common.js └── vue-markdown.js ├── example ├── simple │ └── index.html └── webpack-simple │ ├── .babelrc │ ├── .gitignore │ ├── README.md │ ├── index.html │ ├── package.json │ ├── src │ ├── App.vue │ └── main.js │ └── webpack.config.js ├── index.html ├── package.json ├── src ├── VueMarkdown.js └── build.js ├── webpack.common.js └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015"], 3 | "plugins": ["transform-runtime"] 4 | } -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | // https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style 4 | extends: 'standard', 5 | // required to lint *.vue files 6 | env: { 7 | 'browser': true, 8 | }, 9 | plugins: [ 10 | 'html' 11 | ], 12 | // add your custom rules here 13 | 'rules': { 14 | // allow paren-less arrow functions 15 | 'arrow-parens': 0, 16 | // allow debugger during development 17 | 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0, 18 | 'comma-dangle': 0, 19 | 'no-unused-vars': 1, 20 | 'space-before-function-paren': 0, 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 18 | .grunt 19 | 20 | # node-waf configuration 21 | .lock-wscript 22 | 23 | # Compiled binary addons (http://nodejs.org/api/addons.html) 24 | build/Release 25 | 26 | # Dependency directory 27 | node_modules 28 | 29 | # Optional npm cache directory 30 | .npm 31 | 32 | # Optional REPL history 33 | .node_repl_history 34 | package-lock.json 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Chao Lee 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue-markdown 2 | 3 | [![npm](https://img.shields.io/npm/v/vue-markdown.svg?style=flat)](https://www.npmjs.com/package/vue-markdown) 4 | [![npm](https://img.shields.io/npm/l/vue-markdown.svg?style=flat)](https://www.npmjs.com/package/vue-markdown) 5 | [![npm](https://img.shields.io/npm/dt/vue-markdown.svg?style=flat)](https://www.npmjs.com/package/vue-markdown) 6 | 7 | > If you want vue-markdown for `vue1.X.X`, please checkout [vue-markdown1.X.X](https://github.com/miaolz123/vue-markdown/tree/v1). 8 | 9 | A Powerful and Highspeed Markdown Parser for Vue. 10 | 11 | Quick start: `i am a ~~tast~~ **test**.` 12 | 13 | Supported Markdown Syntax: 14 | 15 | * [x] automatic table of contents 16 | * [x] table & class customize 17 | * [x] *SyntaxHighlighter 18 | * [x] definition list 19 | * [x] strikethrough 20 | * [x] GFM task list 21 | * [x] abbreviation 22 | * [x] superscript 23 | * [x] subscript 24 | * [x] footnote 25 | * [x] insert 26 | * [x] *katex 27 | * [x] emoji 28 | * [x] mark 29 | 30 | `*SyntaxHighlighter` work with [Prism](https://prismjs.com) recommend 31 | 32 | `*katex` need add [katex css](https://unpkg.com/katex/dist/katex.min.css). 33 | 34 | # Example 35 | 36 | [simple](https://github.com/miaolz123/vue-markdown/blob/master/example/simple) 37 | 38 | [webpack-simple](https://github.com/miaolz123/vue-markdown/blob/master/example/webpack-simple) 39 | 40 | [Live Demo](https://miaolz123.github.io/vue-markdown/) 41 | 42 | # Installation 43 | 44 | ### Browser globals 45 | 46 | > The **dist** folder contains `vue-markdown.js` with the component exported in the `window.VueMarkdown` object. 47 | 48 | ```html 49 | 50 | i am a ~~tast~~ **test**. 51 | 52 | 53 | 54 | 60 | ``` 61 | 62 | ### NPM 63 | 64 | ```shell 65 | $ npm install --save vue-markdown 66 | ``` 67 | 68 | ### Yarn 69 | 70 | ```shell 71 | $ yarn add vue-markdown --save 72 | ``` 73 | 74 | ## CommonJS 75 | 76 | ```js 77 | var VueMarkdown = require('vue-markdown'); 78 | 79 | new Vue({ 80 | components: { 81 | 'vue-markdown': VueMarkdown 82 | } 83 | }) 84 | ``` 85 | 86 | ## ES6 (Vue-CLI users) 87 | 88 | After installing via Yarn or NPM, use the following snippet in the script portion of the Vue component which you wish to render the Markdown. 89 | 90 | ```js 91 | import VueMarkdown from 'vue-markdown' 92 | 93 | new Vue({ 94 | components: { 95 | VueMarkdown 96 | } 97 | }) 98 | ``` 99 | 100 | # Slots 101 | 102 | ```html 103 | this is the default slot 104 | ``` 105 | 106 | After setting up the middleware in your vue component above, using the embedded markdown is as easy as writing it between the `vue-markdown` tags. 107 | 108 | VueMarkdown has a default slot which is used to write the `markdown` source. 109 | 110 | TIP: The default slot only renders **once** at the beginning, and it will overwrite the prop of `source`! 111 | 112 | # Props 113 | 114 | | Prop | Type | Default | Describe | 115 | | ---- | ---- | ------- | ------- | 116 | | watches | Array | `["source", "show", "toc"]` | HTML refresh automatically when the prop in this array changed | 117 | | source | String | `null` | the markdown source code | 118 | | show | Boolean | `true` | enable render to the default slot automatically | 119 | | html | Boolean | `true` | enable HTML syntax in source | 120 | | xhtml-out | Boolean | `true` | `

` => `
` | 121 | | breaks | Boolean | `true` | `\n` => `
` | 122 | | linkify | Boolean | `true` | autoconvert URL-like text to link | 123 | | emoji | Boolean | `true` | `:)` => `😃` | 124 | | typographer | Boolean | `true` | enable some language-neutral replacement and quotes beautification | 125 | | lang-prefix | String | `language-` | CSS language prefix for fenced blocks | 126 | | quotes | String | `“”‘’` | use `“”‘’` for Chinese, `„“‚‘` for German, `«»„“` for Russian | 127 | | table-class | String | `table` | customize html class of the `` | 128 | | task-lists | Boolean | `true` | enable GFM task list | 129 | | toc | Boolean | `false` | enable automatic table of contents | 130 | | toc-id | String | `undefined` | the HTML id to render TOC | 131 | | toc-class | String | `table` | customize html class of the `
\n'; 282 | }; 283 | var defaultLinkRenderer = this.md.renderer.rules.link_open || function (tokens, idx, options, env, self) { 284 | return self.renderToken(tokens, idx, options); 285 | }; 286 | this.md.renderer.rules.link_open = function (tokens, idx, options, env, self) { 287 | (0, _keys2.default)(_this.anchorAttributes).map(function (attribute) { 288 | var aIndex = tokens[idx].attrIndex(attribute); 289 | var value = _this.anchorAttributes[attribute]; 290 | if (aIndex < 0) { 291 | tokens[idx].attrPush([attribute, value]); // add new attribute 292 | } else { 293 | tokens[idx].attrs[aIndex][1] = value; 294 | } 295 | }); 296 | return defaultLinkRenderer(tokens, idx, options, env, self); 297 | }; 298 | 299 | if (this.toc) { 300 | this.md.use(_markdownItTocAndAnchor2.default, { 301 | tocClassName: this.tocClass, 302 | tocFirstLevel: this.tocFirstLevel, 303 | tocLastLevel: this.tocLastLevelComputed, 304 | anchorLink: this.tocAnchorLink, 305 | anchorLinkSymbol: this.tocAnchorLinkSymbol, 306 | anchorLinkSpace: this.tocAnchorLinkSpace, 307 | anchorClassName: this.tocAnchorClass, 308 | anchorLinkSymbolClassName: this.tocAnchorLinkClass, 309 | tocCallback: function tocCallback(tocMarkdown, tocArray, tocHtml) { 310 | if (tocHtml) { 311 | if (_this.tocId && document.getElementById(_this.tocId)) { 312 | document.getElementById(_this.tocId).innerHTML = tocHtml; 313 | } 314 | 315 | _this.$emit('toc-rendered', tocHtml); 316 | } 317 | } 318 | }); 319 | } 320 | 321 | var outHtml = this.show ? this.md.render(this.prerender(this.sourceData)) : ''; 322 | outHtml = this.postrender(outHtml); 323 | 324 | this.$emit('rendered', outHtml); 325 | return createElement('div', { 326 | domProps: { 327 | innerHTML: outHtml 328 | } 329 | }); 330 | }, 331 | beforeMount: function beforeMount() { 332 | var _this2 = this; 333 | 334 | if (this.$slots.default) { 335 | this.sourceData = ''; 336 | var _iteratorNormalCompletion = true; 337 | var _didIteratorError = false; 338 | var _iteratorError = undefined; 339 | 340 | try { 341 | for (var _iterator = (0, _getIterator3.default)(this.$slots.default), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { 342 | var slot = _step.value; 343 | 344 | this.sourceData += slot.text; 345 | } 346 | } catch (err) { 347 | _didIteratorError = true; 348 | _iteratorError = err; 349 | } finally { 350 | try { 351 | if (!_iteratorNormalCompletion && _iterator.return) { 352 | _iterator.return(); 353 | } 354 | } finally { 355 | if (_didIteratorError) { 356 | throw _iteratorError; 357 | } 358 | } 359 | } 360 | } 361 | 362 | this.$watch('source', function () { 363 | _this2.sourceData = _this2.prerender(_this2.source); 364 | _this2.$forceUpdate(); 365 | }); 366 | 367 | this.watches.forEach(function (v) { 368 | _this2.$watch(v, function () { 369 | _this2.$forceUpdate(); 370 | }); 371 | }); 372 | } 373 | }; 374 | 375 | /***/ }), 376 | /* 1 */ 377 | /***/ (function(module, exports) { 378 | 379 | module.exports = __WEBPACK_EXTERNAL_MODULE_1__; 380 | 381 | /***/ }), 382 | /* 2 */ 383 | /***/ (function(module, exports) { 384 | 385 | module.exports = __WEBPACK_EXTERNAL_MODULE_2__; 386 | 387 | /***/ }), 388 | /* 3 */ 389 | /***/ (function(module, exports) { 390 | 391 | module.exports = __WEBPACK_EXTERNAL_MODULE_3__; 392 | 393 | /***/ }), 394 | /* 4 */ 395 | /***/ (function(module, exports) { 396 | 397 | module.exports = __WEBPACK_EXTERNAL_MODULE_4__; 398 | 399 | /***/ }), 400 | /* 5 */ 401 | /***/ (function(module, exports) { 402 | 403 | module.exports = __WEBPACK_EXTERNAL_MODULE_5__; 404 | 405 | /***/ }), 406 | /* 6 */ 407 | /***/ (function(module, exports) { 408 | 409 | module.exports = __WEBPACK_EXTERNAL_MODULE_6__; 410 | 411 | /***/ }), 412 | /* 7 */ 413 | /***/ (function(module, exports) { 414 | 415 | module.exports = __WEBPACK_EXTERNAL_MODULE_7__; 416 | 417 | /***/ }), 418 | /* 8 */ 419 | /***/ (function(module, exports) { 420 | 421 | module.exports = __WEBPACK_EXTERNAL_MODULE_8__; 422 | 423 | /***/ }), 424 | /* 9 */ 425 | /***/ (function(module, exports) { 426 | 427 | module.exports = __WEBPACK_EXTERNAL_MODULE_9__; 428 | 429 | /***/ }), 430 | /* 10 */ 431 | /***/ (function(module, exports) { 432 | 433 | module.exports = __WEBPACK_EXTERNAL_MODULE_10__; 434 | 435 | /***/ }), 436 | /* 11 */ 437 | /***/ (function(module, exports) { 438 | 439 | module.exports = __WEBPACK_EXTERNAL_MODULE_11__; 440 | 441 | /***/ }), 442 | /* 12 */ 443 | /***/ (function(module, exports) { 444 | 445 | module.exports = __WEBPACK_EXTERNAL_MODULE_12__; 446 | 447 | /***/ }), 448 | /* 13 */ 449 | /***/ (function(module, exports) { 450 | 451 | module.exports = __WEBPACK_EXTERNAL_MODULE_13__; 452 | 453 | /***/ }), 454 | /* 14 */ 455 | /***/ (function(module, exports) { 456 | 457 | module.exports = __WEBPACK_EXTERNAL_MODULE_14__; 458 | 459 | /***/ }) 460 | /******/ ]) 461 | }); 462 | ; -------------------------------------------------------------------------------- /example/simple/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | simple 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | The time of **NOW** is : 16 | 17 | [A link to a website](https://google.com) 18 | 19 | SyntaxHighlighter by highlight.js: 20 | 21 | ``` js 22 | function $initHighlight(block, cls) { 23 | try { 24 | if (cls.search(/\bno\-highlight\b/) != -1) 25 | return process(block, true, 0x0F) + 26 | ` class="${cls}"`; 27 | } catch (e) { 28 | /* handle exception */ 29 | } 30 | for (var i = 0 / 2; i < classes.length; i++) { 31 | if (checkCondition(classes[i]) === undefined) 32 | console.log('undefined'); 33 | } 34 | } 35 | ``` 36 | 37 | **Inline Math**: $\sqrt{3x-1}+(1+x)^2$ 38 | 39 | **Block Math** 40 | $$\begin{array}{c} 41 | 42 | \nabla \times \vec{\mathbf{B}} -\, \frac1c\, \frac{\partial\vec{\mathbf{E}}}{\partial t} & 43 | = \frac{4\pi}{c}\vec{\mathbf{j}} \nabla \cdot \vec{\mathbf{E}} & = 4 \pi \rho \\ 44 | 45 | \nabla \times \vec{\mathbf{E}}\, +\, \frac1c\, \frac{\partial\vec{\mathbf{B}}}{\partial t} & = \vec{\mathbf{0}} \\ 46 | 47 | \nabla \cdot \vec{\mathbf{B}} & = 0 48 | 49 | \end{array}$$ 50 | 51 | 52 | ### Solar System Exploration, 1950s – 1960s 53 | 54 | - [ ] Mercury 55 | - [x] Venus 56 | - [x] Earth (Orbit/Moon) 57 | - [x] Mars 58 | - [ ] Jupiter 59 | - [ ] Saturn 60 | - [ ] Uranus 61 | - [ ] Neptune 62 | - [ ] Comet Haley 63 | 64 | 65 |
66 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /example/webpack-simple/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["es2015", { "modules": false }] 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /example/webpack-simple/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | dist/ 4 | npm-debug.log 5 | -------------------------------------------------------------------------------- /example/webpack-simple/README.md: -------------------------------------------------------------------------------- 1 | # webpack-simple 2 | 3 | > A Vue.js project 4 | 5 | ## Build Setup 6 | 7 | ``` bash 8 | # install dependencies 9 | npm install 10 | 11 | # serve with hot reload at localhost:8080 12 | npm run dev 13 | 14 | # build for production with minification 15 | npm run build 16 | ``` 17 | 18 | For detailed explanation on how things work, consult the [docs for vue-loader](http://vuejs.github.io/vue-loader). 19 | -------------------------------------------------------------------------------- /example/webpack-simple/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | webpack-simple 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /example/webpack-simple/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webpack-simple", 3 | "description": "A Vue.js project", 4 | "author": "miaolz123 ", 5 | "private": true, 6 | "scripts": { 7 | "dev": "cross-env NODE_ENV=development webpack-dev-server --open --inline --hot", 8 | "build": "cross-env NODE_ENV=production webpack --progress --hide-modules" 9 | }, 10 | "dependencies": { 11 | "vue": "^2.0.1", 12 | "vue-markdown": "^2.2.4" 13 | }, 14 | "devDependencies": { 15 | "babel-core": "^6.0.0", 16 | "babel-loader": "^6.0.0", 17 | "babel-preset-es2015": "^6.0.0", 18 | "cross-env": "^3.0.0", 19 | "css-loader": "^0.25.0", 20 | "file-loader": "^0.9.0", 21 | "json-loader": "^0.5.4", 22 | "loglevel": "^1.4.1", 23 | "vue-loader": "^9.7.0", 24 | "webpack": "^2.1.0-beta.25", 25 | "webpack-dev-server": "^2.1.0-beta.0" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /example/webpack-simple/src/App.vue: -------------------------------------------------------------------------------- 1 | 54 | 55 | 80 | -------------------------------------------------------------------------------- /example/webpack-simple/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App.vue' 3 | 4 | new Vue({ 5 | el: '#app', 6 | render: h => h(App) 7 | }) 8 | -------------------------------------------------------------------------------- /example/webpack-simple/webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var webpack = require('webpack') 3 | 4 | module.exports = { 5 | entry: './src/main.js', 6 | output: { 7 | path: path.resolve(__dirname, './dist'), 8 | publicPath: '/dist/', 9 | filename: 'build.js' 10 | }, 11 | module: { 12 | rules: [ 13 | { 14 | test: /\.vue$/, 15 | loader: 'vue-loader', 16 | options: { 17 | // vue-loader options go here 18 | } 19 | }, 20 | { 21 | test: /\.json$/, 22 | loader: 'json-loader' 23 | }, 24 | { 25 | test: /\.js$/, 26 | loader: 'babel-loader', 27 | exclude: /node_modules/ 28 | }, 29 | { 30 | test: /\.(png|jpg|gif|svg)$/, 31 | loader: 'file-loader', 32 | options: { 33 | name: '[name].[ext]?[hash]' 34 | } 35 | } 36 | ] 37 | }, 38 | resolve: { 39 | alias: { 40 | 'vue$': 'vue/dist/vue' 41 | } 42 | }, 43 | devServer: { 44 | historyApiFallback: true, 45 | noInfo: true 46 | }, 47 | devtool: '#eval-source-map' 48 | } 49 | 50 | if (process.env.NODE_ENV === 'production') { 51 | module.exports.devtool = '#source-map' 52 | // http://vue-loader.vuejs.org/en/workflow/production.html 53 | module.exports.plugins = (module.exports.plugins || []).concat([ 54 | new webpack.DefinePlugin({ 55 | 'process.env': { 56 | NODE_ENV: '"production"' 57 | } 58 | }), 59 | new webpack.optimize.UglifyJsPlugin({ 60 | sourceMap: true, 61 | compress: { 62 | warnings: false 63 | } 64 | }), 65 | new webpack.LoaderOptionsPlugin({ 66 | minimize: true 67 | }) 68 | ]) 69 | } 70 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | vue-markdown 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |

VueMarkdown Live Demo

15 |
16 |
17 | 18 |
19 |
20 | 21 |
22 |
23 | 24 |
25 |
26 | 27 |
28 |
29 | 30 |
31 |
32 | 33 |
34 |
35 | 36 |
37 |
38 |
39 |
40 |
41 | 263 |
264 |
265 | 269 |
270 |
271 |
272 |
Fork me on GitHub
273 | 274 | 275 | 276 | 277 | 301 | 302 | 303 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-markdown", 3 | "version": "2.2.4", 4 | "description": "A Powerful and Highspeed Markdown Parser for Vue", 5 | "main": "dist/vue-markdown.common.js", 6 | "files": [ 7 | "dist/vue-markdown.js", 8 | "dist/vue-markdown.common.js", 9 | "src" 10 | ], 11 | "scripts": { 12 | "start": "webpack --config webpack.config.js", 13 | "build": "webpack --config webpack.common.js", 14 | "test": "echo true" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/miaolz123/vue-markdown.git" 19 | }, 20 | "keywords": [ 21 | "vue", 22 | "markdown", 23 | "vue-markdown" 24 | ], 25 | "author": "miaolz123", 26 | "license": "MIT", 27 | "bugs": { 28 | "url": "https://github.com/miaolz123/vue-markdown/issues" 29 | }, 30 | "homepage": "https://github.com/miaolz123/vue-markdown#readme", 31 | "devDependencies": { 32 | "babel-core": "^6.8.0", 33 | "babel-loader": "^6.2.4", 34 | "babel-plugin-transform-es2015-parameters": "^6.8.0", 35 | "babel-plugin-transform-runtime": "^6.8.0", 36 | "babel-preset-es2015": "^6.6.0", 37 | "babel-runtime": "^6.6.1", 38 | "css-loader": "^0.23.1", 39 | "json-loader": "^0.5.7", 40 | "style-loader": "^0.13.1", 41 | "webpack": "^1.13.0" 42 | }, 43 | "dependencies": { 44 | "highlight.js": "^9.12.0", 45 | "markdown-it": "^6.0.1", 46 | "markdown-it-abbr": "^1.0.3", 47 | "markdown-it-deflist": "^2.0.1", 48 | "markdown-it-emoji": "^1.1.1", 49 | "markdown-it-footnote": "^2.0.0", 50 | "markdown-it-ins": "^2.0.0", 51 | "markdown-it-katex": "^2.0.3", 52 | "markdown-it-mark": "^2.0.0", 53 | "markdown-it-sub": "^1.0.0", 54 | "markdown-it-sup": "^1.0.0", 55 | "markdown-it-task-lists": "^2.0.1", 56 | "markdown-it-toc-and-anchor": "^4.1.2" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/VueMarkdown.js: -------------------------------------------------------------------------------- 1 | import markdownIt from 'markdown-it' 2 | import emoji from 'markdown-it-emoji' 3 | import subscript from 'markdown-it-sub' 4 | import superscript from 'markdown-it-sup' 5 | import footnote from 'markdown-it-footnote' 6 | import deflist from 'markdown-it-deflist' 7 | import abbreviation from 'markdown-it-abbr' 8 | import insert from 'markdown-it-ins' 9 | import mark from 'markdown-it-mark' 10 | import toc from 'markdown-it-toc-and-anchor' 11 | import katex from 'markdown-it-katex' 12 | import tasklists from 'markdown-it-task-lists' 13 | 14 | export default { 15 | md: new markdownIt(), 16 | 17 | template: '
', 18 | 19 | data() { 20 | return { 21 | sourceData: this.source, 22 | } 23 | }, 24 | 25 | props: { 26 | watches: { 27 | type: Array, 28 | default: () => ['source', 'show', 'toc'], 29 | }, 30 | source: { 31 | type: String, 32 | default: ``, 33 | }, 34 | show: { 35 | type: Boolean, 36 | default: true, 37 | }, 38 | highlight: { 39 | type: Boolean, 40 | default: true 41 | }, 42 | html: { 43 | type: Boolean, 44 | default: true, 45 | }, 46 | xhtmlOut: { 47 | type: Boolean, 48 | default: true, 49 | }, 50 | breaks: { 51 | type: Boolean, 52 | default: true, 53 | }, 54 | linkify: { 55 | type: Boolean, 56 | default: true, 57 | }, 58 | emoji: { 59 | type: Boolean, 60 | default: true, 61 | }, 62 | typographer: { 63 | type: Boolean, 64 | default: true, 65 | }, 66 | langPrefix: { 67 | type: String, 68 | default: 'language-', 69 | }, 70 | quotes: { 71 | type: String, 72 | default: '“”‘’', 73 | }, 74 | tableClass: { 75 | type: String, 76 | default: 'table', 77 | }, 78 | taskLists: { 79 | type: Boolean, 80 | default: true 81 | }, 82 | toc: { 83 | type: Boolean, 84 | default: false, 85 | }, 86 | tocId: { 87 | type: String, 88 | }, 89 | tocClass: { 90 | type: String, 91 | default: 'table-of-contents', 92 | }, 93 | tocFirstLevel: { 94 | type: Number, 95 | default: 2, 96 | }, 97 | tocLastLevel: { 98 | type: Number, 99 | }, 100 | tocAnchorLink: { 101 | type: Boolean, 102 | default: true, 103 | }, 104 | tocAnchorClass: { 105 | type: String, 106 | default: 'toc-anchor', 107 | }, 108 | tocAnchorLinkSymbol: { 109 | type: String, 110 | default: '#', 111 | }, 112 | tocAnchorLinkSpace: { 113 | type: Boolean, 114 | default: true, 115 | }, 116 | tocAnchorLinkClass: { 117 | type: String, 118 | default: 'toc-anchor-link', 119 | }, 120 | anchorAttributes: { 121 | type: Object, 122 | default: () => ({}) 123 | }, 124 | prerender: { 125 | type: Function, 126 | default: (sourceData) => { return sourceData } 127 | }, 128 | postrender: { 129 | type: Function, 130 | default: (htmlData) => { return htmlData } 131 | } 132 | }, 133 | 134 | computed: { 135 | tocLastLevelComputed() { 136 | return this.tocLastLevel > this.tocFirstLevel ? this.tocLastLevel : this.tocFirstLevel + 1 137 | } 138 | }, 139 | 140 | render(createElement) { 141 | this.md = new markdownIt() 142 | .use(subscript) 143 | .use(superscript) 144 | .use(footnote) 145 | .use(deflist) 146 | .use(abbreviation) 147 | .use(insert) 148 | .use(mark) 149 | .use(katex, { "throwOnError": false, "errorColor": " #cc0000" }) 150 | .use(tasklists, { enabled: this.taskLists }) 151 | 152 | if (this.emoji) { 153 | this.md.use(emoji) 154 | } 155 | 156 | this.md.set({ 157 | html: this.html, 158 | xhtmlOut: this.xhtmlOut, 159 | breaks: this.breaks, 160 | linkify: this.linkify, 161 | typographer: this.typographer, 162 | langPrefix: this.langPrefix, 163 | quotes: this.quotes, 164 | }) 165 | this.md.renderer.rules.table_open = () => `
\n` 166 | let defaultLinkRenderer = this.md.renderer.rules.link_open || 167 | function (tokens, idx, options, env, self) { 168 | return self.renderToken(tokens, idx, options) 169 | } 170 | this.md.renderer.rules.link_open = (tokens, idx, options, env, self) => { 171 | Object.keys(this.anchorAttributes).map((attribute) => { 172 | let aIndex = tokens[idx].attrIndex(attribute) 173 | let value = this.anchorAttributes[attribute] 174 | if (aIndex < 0) { 175 | tokens[idx].attrPush([attribute, value]) // add new attribute 176 | } else { 177 | tokens[idx].attrs[aIndex][1] = value 178 | } 179 | }) 180 | return defaultLinkRenderer(tokens, idx, options, env, self) 181 | } 182 | 183 | if (this.toc) { 184 | this.md.use(toc, { 185 | tocClassName: this.tocClass, 186 | tocFirstLevel: this.tocFirstLevel, 187 | tocLastLevel: this.tocLastLevelComputed, 188 | anchorLink: this.tocAnchorLink, 189 | anchorLinkSymbol: this.tocAnchorLinkSymbol, 190 | anchorLinkSpace: this.tocAnchorLinkSpace, 191 | anchorClassName: this.tocAnchorClass, 192 | anchorLinkSymbolClassName: this.tocAnchorLinkClass, 193 | tocCallback: (tocMarkdown, tocArray, tocHtml) => { 194 | if (tocHtml) { 195 | if (this.tocId && document.getElementById(this.tocId)) { 196 | document.getElementById(this.tocId).innerHTML = tocHtml 197 | } 198 | 199 | this.$emit('toc-rendered', tocHtml) 200 | } 201 | }, 202 | }) 203 | } 204 | 205 | let outHtml = this.show ? 206 | this.md.render( 207 | this.prerender(this.sourceData) 208 | ) : '' 209 | outHtml = this.postrender(outHtml); 210 | 211 | this.$emit('rendered', outHtml) 212 | return createElement( 213 | 'div', { 214 | domProps: { 215 | innerHTML: outHtml, 216 | }, 217 | }, 218 | ) 219 | }, 220 | 221 | beforeMount() { 222 | if (this.$slots.default) { 223 | this.sourceData = '' 224 | for (let slot of this.$slots.default) { 225 | this.sourceData += slot.text 226 | } 227 | } 228 | 229 | this.$watch('source', () => { 230 | this.sourceData = this.prerender(this.source) 231 | this.$forceUpdate() 232 | }) 233 | 234 | this.watches.forEach((v) => { 235 | this.$watch(v, () => { 236 | this.$forceUpdate() 237 | }) 238 | }) 239 | }, 240 | } 241 | -------------------------------------------------------------------------------- /src/build.js: -------------------------------------------------------------------------------- 1 | import VueMarkdownComponent from './VueMarkdown' 2 | 3 | export function install(Vue) { 4 | Vue.component('vue-markdown', VueMarkdownComponent) 5 | } 6 | -------------------------------------------------------------------------------- /webpack.common.js: -------------------------------------------------------------------------------- 1 | var webpack = require("webpack"); 2 | var version = require("./package.json").version; 3 | var banner = 4 | "/**\n" + 5 | " * vue-markdown v" + version + "\n" + 6 | " * https://github.com/miaolz123/vue-markdown\n" + 7 | " * MIT License\n" + 8 | " */\n"; 9 | 10 | module.exports = { 11 | entry: "./src/VueMarkdown.js", 12 | target: "node", 13 | output: { 14 | path: "./dist", 15 | filename: "vue-markdown.common.js", 16 | library: "VueMarkdown", 17 | libraryTarget: "umd" 18 | }, 19 | externals: /^[^.]/, 20 | plugins: [ 21 | new webpack.BannerPlugin(banner, { raw: true }) 22 | ], 23 | module: { 24 | loaders: [{ 25 | test: /\.vue$/, 26 | loader: "vue" 27 | }, { 28 | test: /\.js$/, 29 | loader: "babel", 30 | exclude: /node_modules/ 31 | }, { 32 | test: /\.css$/, 33 | loader: "style!css" 34 | }, { 35 | test: /\.json$/, 36 | loader: "json-loader" 37 | }] 38 | }, 39 | } 40 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var version = require("./package.json").version; 3 | var banner = 4 | "/**\n" + 5 | " * vue-markdown v" + version + "\n" + 6 | " * https://github.com/miaolz123/vue-markdown\n" + 7 | " * MIT License\n" + 8 | " */\n"; 9 | 10 | module.exports = { 11 | entry: './src/build.js', 12 | output: { 13 | path: './dist', 14 | filename: 'vue-markdown.js', 15 | library: 'VueMarkdown', 16 | libraryTarget: 'umd' 17 | }, 18 | plugins: [ 19 | new webpack.BannerPlugin(banner, { raw: true }) 20 | ], 21 | module: { 22 | loaders: [{ 23 | test: /\.vue$/, 24 | loader: 'vue' 25 | }, { 26 | test: /\.css$/, 27 | loader: "style!css" 28 | }, { 29 | test: /\.js$/, 30 | loader: 'babel', 31 | exclude: /node_modules/ 32 | }, { 33 | test: /\.json$/, 34 | loader: 'json-loader' 35 | }] 36 | }, 37 | } 38 | --------------------------------------------------------------------------------