├── .babelrc ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .idea └── vcs.xml ├── .storybook └── config.js ├── LICENSE ├── README.md ├── index.js ├── jest-transformer.js ├── jest.config.js ├── lib ├── constans.js ├── func.js ├── index.js ├── react_block.js └── react_inline.js ├── package.json ├── rollup.config.js ├── stories ├── editor.js ├── github-markdown.css ├── highlight.js ├── index.css ├── index.js ├── markdown │ ├── antd.md │ ├── base.md │ ├── editor.md │ ├── simple.md │ └── xss.md └── md.js └── tests ├── __snapshots__ └── broswer.test.js.snap ├── broswer.test.js ├── index.test.js ├── spec ├── code.md ├── component.md ├── error_code.md ├── error_react_inline_code.md ├── md_refs.md ├── react_block_code.md ├── react_block_code_multi.md ├── react_inline_break_line_code.md ├── react_inline_code.md ├── xss.md ├── xss_constructor.md ├── xss_constructor_str.md ├── xss_dangerouslySetInnerHTML.md └── xss_this.md └── util.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/env" 4 | ], 5 | "plugins": [ 6 | [ 7 | "@babel/plugin-transform-runtime", 8 | { 9 | "corejs": false, 10 | "helpers": false, 11 | "regenerator": true, 12 | "useESModules": true 13 | } 14 | ], 15 | "@babel/plugin-syntax-dynamic-import", 16 | "@babel/plugin-proposal-object-rest-spread", 17 | "@babel/plugin-transform-parameters" 18 | ] 19 | } -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | output 3 | tests/components/__snapshots__ 4 | src/server/apiTest/dist/*h 5 | test-report 6 | api-report 7 | !src/.wg -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'env': { 3 | 'browser': true, 4 | 'commonjs': true, 5 | 'es6': true, 6 | 'node': true, 7 | 'jest': true 8 | }, 9 | 'extends': ['eslint:recommended', 'plugin:react/recommended'], 10 | 'parserOptions': { 11 | 'ecmaFeatures': { 12 | 'experimentalObjectRestSpread': true, 13 | 'jsx': true 14 | }, 15 | 'sourceType': 'module' 16 | }, 17 | 'plugins': [ 18 | 'react', 19 | 'jest' 20 | ], 21 | 'rules': { 22 | 'indent': [ 23 | 'error', 24 | 2 25 | ], 26 | 'linebreak-style': [ 27 | 'error', 28 | 'unix' 29 | ], 30 | 'quotes': [ 31 | 'error', 32 | 'single' 33 | ], 34 | 'semi': [ 35 | 'error', 36 | 'always' 37 | ], 38 | 'no-console': 1, 39 | 'block-spacing': [ 40 | 'error', 41 | 'always' 42 | ], 43 | 'no-trailing-spaces': [ 44 | 'error' 45 | ], 46 | 'object-curly-spacing': ['error', 'always'], 47 | 'semi-spacing': 'error', 48 | 'space-infix-ops': 'error', 49 | 'keyword-spacing': ['error', { 'before': true }], 50 | 'key-spacing': ['error', { 'beforeColon': false, 'afterColon': true }], 51 | 'react/prop-types': [1, { ignore: ['children'] }], 52 | "comma-dangle": ["error", "never"] 53 | }, 54 | 'parser': 'babel-eslint', 55 | }; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | 63 | .DS_Store 64 | 65 | dist/ 66 | package-lock.json 67 | yarn.lock -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.storybook/config.js: -------------------------------------------------------------------------------- 1 | import { configure } from '@storybook/react'; 2 | 3 | function loadStories() { 4 | require('../stories/index.js'); 5 | // You can require as many stories as you need. 6 | } 7 | 8 | configure(loadStories, module); -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 林风 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 | # markdown-it-react-component 2 | Plugin to support react component for markdown-it markdown parser 3 | 4 | With this plugin you can support react component like: 5 | 6 | ``` 7 | ``` rc 8 | function Hello(props){ 9 | return ( 10 |
11 | Hello: 12 |

{props.text}

13 |
14 | ) 15 | } 16 | return ( 17 |
18 | 19 |
20 | ); 21 | ``` 22 | ``` 23 | 24 | ## Installation 25 | 26 | Only browser: 27 | 28 | ```bash 29 | $ npm install markdown-it-react-component --save 30 | ``` 31 | 32 | ## API 33 | 34 | ```js 35 | import MarkdownIt from "markdown-it"; 36 | import { SupportReactComponent } from "markdown-it-react-component"; 37 | 38 | const md = new MarkdownIt().use(md => SupportReactComponent(md,[, options])) 39 | ``` 40 | 41 | ### Params 42 | * options: 43 | - sandbox - optional,sandbox to provide plugin,such as React Component. 44 | - babelOptions - optional,Babel Options,defalut: 45 | ``` 46 | { 47 | presets: ['stage-3', 'react', 'es2015'], 48 | plugins: ['filterXSS'] 49 | } 50 | ``` 51 | - babelInit - optional,Babel initialization callback. 52 | - enableSSR - optional,Whether to enable SSR rendering.defalut:`true` 53 | - allowErrorLog - optional,Whether log the info of render react code error.defalut:`false` 54 | 55 | ## Example 56 | run `npm run storybook` 57 | 58 | ## Test 59 | run `npm run test` 60 | 61 | ## License 62 | 63 | [MIT](https://github.com/LinFeng1997/markdown-it-react-component/blob/master/LICENSE) -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import container from 'markdown-it-container/dist/markdown-it-container.min'; 2 | import Replacer from './lib/index'; 3 | import reactBlockRule from './lib/react_block'; 4 | import { react_inline } from './lib/react_inline'; 5 | import { getWrapperId } from './lib/func'; 6 | 7 | export function renderSimpleComponent(replacer,content,env) { 8 | let wrapperId = getWrapperId(); 9 | 10 | if (env && typeof env === 'object') { 11 | replacer.sandbox = { 12 | ...replacer.sandbox, 13 | ...env 14 | }; 15 | } 16 | 17 | const code = `return (${content})`; 18 | try { 19 | return `${replacer.getHtml(wrapperId, code,env)}`; 20 | } catch (e) { 21 | console.error(e); 22 | return ``; 23 | } 24 | } 25 | export const SupportReactComponent = (md, options) => { 26 | md.use(...createContainer('rc', options, '`')); 27 | md.use(...createContainer('mixin-react', options, '`')); 28 | 29 | const replacer = new Replacer(options); 30 | md['md-it-rc-replacer'] = replacer; 31 | 32 | // // react_block 33 | md.block.ruler.before('html_block','react_block',reactBlockRule,[ 'paragraph', 'reference', 'blockquote' ]); 34 | md.renderer.rules.react_block = function (tokens, idx,options, env) { 35 | return renderSimpleComponent(replacer,tokens[idx].content,env); 36 | }; 37 | 38 | // // react_inline 39 | md.inline.ruler.before('html_inline','react_inline',react_inline); 40 | md.renderer.rules.react_inline = function (tokens, idx,options, env) { 41 | return renderSimpleComponent(replacer,tokens[idx].content,env); 42 | }; 43 | 44 | function createContainer (klass, options, marker = ':') { 45 | return [container, klass, { 46 | render (tokens, idx,_options,env) { 47 | const token = tokens[idx]; 48 | // const info = token.info.trim().slice(klass.length).trim(); 49 | if (token.nesting === 1) { 50 | if (env && typeof env === 'object') { 51 | replacer.sandbox = { 52 | ...replacer.sandbox, 53 | ...env 54 | }; 55 | } 56 | 57 | let wrapperId = getWrapperId(); 58 | 59 | try { 60 | const offset = tokens.slice(idx).findIndex(token => token.type === `container_${klass}_close`); 61 | let str = tokens.slice(idx,idx + offset).reduce((pre,cur)=>{ 62 | const str = pre + '\n' + cur.content; 63 | // reset token 64 | cur.content = ''; 65 | cur.children = []; 66 | return str; 67 | },''); 68 | const html = replacer.getHtml(wrapperId, str,env); 69 | return `
${html}`; 70 | } catch (e) { 71 | if (options && options.allowErrorLog) console.log(e); 72 | return `
`; 73 | } 74 | } else { 75 | return '
\n'; 76 | } 77 | }, 78 | marker 79 | }]; 80 | } 81 | }; -------------------------------------------------------------------------------- /jest-transformer.js: -------------------------------------------------------------------------------- 1 | const config = { 2 | babelrc: false, 3 | presets: [ 4 | [ 5 | "@babel/env", 6 | { 7 | modules: false 8 | } 9 | ], 10 | "@babel/react" 11 | ], 12 | plugins: [ 13 | ["@babel/plugin-proposal-decorators", { legacy: true }], 14 | ["@babel/plugin-proposal-class-properties", { loose: true }], 15 | "transform-es2015-modules-commonjs" 16 | ] 17 | }; 18 | module.exports = require("babel-jest").createTransformer(config); -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | // For a detailed explanation regarding each configuration property, visit: 2 | // https://jestjs.io/docs/en/configuration.html 3 | 4 | module.exports = { 5 | // All imported modules in your tests should be mocked automatically 6 | // automock: false, 7 | 8 | // Stop running tests after the first failure 9 | // bail: false, 10 | 11 | // Respect "browser" field in package.json when resolving modules 12 | // browser: false, 13 | 14 | // The directory where Jest should store its cached dependency information 15 | // cacheDirectory: "/tmp/jest_rs", 16 | 17 | // Automatically clear mock calls and instances between every test 18 | // clearMocks: false, 19 | 20 | // Indicates whether the coverage information should be collected while executing the test 21 | // collectCoverage: false, 22 | 23 | // An array of glob patterns indicating a set of files for which coverage information should be collected 24 | // collectCoverageFrom: null, 25 | 26 | // The directory where Jest should output its coverage files 27 | // coverageDirectory: null, 28 | 29 | // An array of regexp pattern strings used to skip coverage collection 30 | // coveragePathIgnorePatterns: [ 31 | // "/node_modules/" 32 | // ], 33 | 34 | // A list of reporter names that Jest uses when writing coverage reports 35 | // coverageReporters: [ 36 | // "json", 37 | // "text", 38 | // "lcov", 39 | // "clover" 40 | // ], 41 | 42 | // An object that configures minimum threshold enforcement for coverage results 43 | // coverageThreshold: null, 44 | 45 | // Make calling deprecated APIs throw helpful error messages 46 | // errorOnDeprecated: false, 47 | 48 | // Force coverage collection from ignored files usin a array of glob patterns 49 | // forceCoverageMatch: [], 50 | 51 | // A path to a module which exports an async function that is triggered once before all test suites 52 | // globalSetup: null, 53 | 54 | // A path to a module which exports an async function that is triggered once after all test suites 55 | // globalTeardown: null, 56 | 57 | // A set of global variables that need to be available in all test environments 58 | // globals: { 59 | // 'ts-jest': { 60 | // tsConfig: 'tsconfig.jest.json', 61 | // babelConfig: true 62 | // } 63 | // }, 64 | 65 | // An array of directory names to be searched recursively up from the requiring module's location 66 | // moduleDirectories: [ 67 | // "node_modules" 68 | // ], 69 | 70 | // An array of file extensions your modules use 71 | moduleFileExtensions: ['ts', 'tsx', 'js','md'], 72 | 73 | // A map from regular expressions to module names that allow to stub out resources with a single module 74 | moduleNameMapper: { 75 | '@lib/(.*)': '/lib/$1', 76 | '@tests/(.*)': '/tests/$1' 77 | }, 78 | 79 | // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader 80 | modulePathIgnorePatterns: ['/es'], 81 | 82 | // Activates notifications for test results 83 | // notify: false, 84 | 85 | // An enum that specifies notification mode. Requires { notify: true } 86 | // notifyMode: "always", 87 | 88 | // A preset that is used as a base for Jest's configuration 89 | // preset: null, 90 | 91 | // Run tests from one or more projects 92 | // projects: null, 93 | 94 | // Use this configuration option to add custom reporters to Jest 95 | // reporters: undefined, 96 | 97 | // Automatically reset mock state between every test 98 | // resetMocks: false, 99 | 100 | // Reset the module registry before running each individual test 101 | // resetModules: false, 102 | 103 | // A path to a custom resolver 104 | // Automatically restore mocxk state between every test 105 | // restoreMocks: false, 106 | 107 | // The root directory that Jest should scan for tests and modules within 108 | // rootDir: null, 109 | 110 | // A list of paths to directories that Jest should use to search for files in 111 | // roots: [ 112 | // "" 113 | // ], 114 | 115 | // Allows you to use a custom runner instead of Jest's default test runner 116 | // runner: "jest-runner", 117 | 118 | // The paths to modules that run some code to configure or set up the testing environment before each test 119 | // setupFiles: ['./tests/setupJest.js'], 120 | 121 | // The path to a module that runs some code to configure or set up the testing framework before each test 122 | // setupFilesAfterEnv: [ 123 | // "/test/helpers/index.ts", 124 | // "/node_modules/jest-enzyme/lib/index.js" 125 | // ], 126 | 127 | // A list of paths to snapshot serializer modules Jest should use for snapshot testing 128 | // snapshotSerializers: [], 129 | 130 | // The test environment that will be used for testing 131 | // testEnvironment: "jest-environment-jsdom", 132 | 133 | // Options that will be passed to the testEnvironment 134 | // testEnvironmentOptions: {}, 135 | 136 | // Adds a location field to test results 137 | // testLocationInResults: false, 138 | 139 | // The glob patterns Jest uses to detect test files 140 | testMatch: ['**/*.test.(ts|tsx|js)'], 141 | 142 | // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped 143 | testPathIgnorePatterns: [ 144 | '/node_modules/', 145 | '/dist', 146 | '/es', 147 | '/(build|dist|node_modules)/' 148 | ], 149 | // The regexp pattern Jest uses to detect test files 150 | // testRegex: "", 151 | 152 | // This option allows the use of a custom results processor 153 | // testResultsProcessor: null, 154 | 155 | // This option allows use of a custom test runner 156 | // testRunner: "jasmine2", 157 | 158 | // This option sets the URL for the jsdom environment. It is reflected in properties such as location.href 159 | // testURL: "http://localhost", 160 | 161 | // Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout" 162 | // timers: "real", 163 | 164 | // A map from regular expressions to paths to transformers 165 | transform: { 166 | // '^.+\\.(ts|tsx)$': 'ts-jest', 167 | '^.+\\.jsx?$': '/jest-transformer.js' 168 | }, 169 | 170 | // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation 171 | transformIgnorePatterns: ["/node_modules/"] 172 | 173 | // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them 174 | // unmockedModulePathPatterns: undefined, 175 | 176 | // Indicates whether each individual test should be reported during the run 177 | // verbose: null, 178 | 179 | // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode 180 | // watchPathIgnorePatterns: [], 181 | 182 | // Whether to use watchman for file crawling 183 | // watchman: true, 184 | // testEnvironment: "enzyme", 185 | // testEnvironmentOptions: { 186 | // enzymeAdapter: "react16" 187 | // } 188 | }; 189 | -------------------------------------------------------------------------------- /lib/constans.js: -------------------------------------------------------------------------------- 1 | export const babelTransformOptions = { 2 | presets: ['stage-3', 'react', 'es2015'], 3 | plugins: ['filterXSS'] 4 | }; 5 | 6 | var attr_name = '[a-zA-Z_:][a-zA-Z0-9:._-]*'; 7 | var unquoted = '[^"\'=<>`\\x00-\\x20]+'; 8 | var single_quoted = '\'[^\']*\''; 9 | var double_quoted = '"[^"]*"'; 10 | 11 | var attr_value = '(?:' + unquoted + '|' + single_quoted + '|' + double_quoted + ')'; 12 | var attribute = '(?:\\s+' + attr_name + '(?:\\s*=\\s*' + attr_value + ')?)'; 13 | var open_tag = '<[A-Z][A-Za-z0-9\\-]*' + attribute + '*\\s*\\/>'; 14 | 15 | // var close_tag = '<\\/[A-Z][A-Za-z0-9\\-]*\\s*>'; 16 | 17 | export const REACT_TAG_RE = new RegExp('^(?:' + open_tag + ')'); 18 | export const REACT_OPEN_CLOSE_TAG_RE = /^<([A-Z][A-Za-z0-9]*)(?:.|[\n\f\r\t\v])*?<\/\1>/; -------------------------------------------------------------------------------- /lib/func.js: -------------------------------------------------------------------------------- 1 | export function filterXSS() { 2 | return { 3 | visitor: { 4 | Identifier(path) { 5 | if (path.isIdentifier({ name: 'constructor' })) { 6 | console.warn('constructor xss'); 7 | // path.node.name = 'x'; 8 | } 9 | // else if (typeof window !== 'undefined' && window[path.node.name]) { 10 | // path.node.name = 'x'; 11 | // } 12 | }, 13 | // Function traverse 14 | FunctionExpression(path) { 15 | path.traverse({ 16 | ThisExpression() { 17 | console.warn('this escape xss attack'); 18 | // path.remove(); 19 | } 20 | }); 21 | }, 22 | FunctionDeclaration(path) { 23 | const funcName = path.node.id.name; 24 | 25 | if (funcName !== 'YMGGKLK') { 26 | path.traverse({ 27 | ThisExpression() { 28 | console.warn('this escape xss attack'); 29 | // path.remove(); 30 | } 31 | }); 32 | } 33 | }, 34 | // ThisExpression() { 35 | // console.log('this'); 36 | // }, 37 | JSXIdentifier(path) { 38 | if (path.node.name === 'dangerouslySetInnerHTML'){ 39 | console.warn('dangerouslySetInnerHTML xss'); 40 | // path.node.name = 'sanitizition'; 41 | } 42 | }, 43 | MemberExpression(path){ 44 | if (path.node.computed) { 45 | let string = ' '; 46 | path.traverse({ 47 | StringLiteral(path) { 48 | string += path.node.value; 49 | } 50 | }); 51 | if (string.includes('constructor')) { 52 | console.warn('constructor str xss'); 53 | path.remove(); 54 | } 55 | } 56 | } 57 | } 58 | }; 59 | } 60 | 61 | // compile javascript code with sandbox 62 | export function compileCode(code) { 63 | code = 'with (sandbox) {' + code + '}'; 64 | const fn = new Function('sandbox', code); 65 | return (sandbox) => { 66 | const proxy = new Proxy(sandbox, { 67 | has() { 68 | return true; // cheet prop 69 | }, 70 | get(target, key, receiver) { 71 | // protect escape 72 | if (key === Symbol.unscopables) { 73 | return undefined; 74 | } 75 | return Reflect.get(target, key, receiver); 76 | } 77 | }); 78 | return fn(proxy); 79 | }; 80 | } 81 | 82 | // render react code to dom 83 | export function renderReact(wrapperId, reactCode, sandbox) { 84 | if (typeof window === 'undefined' || !document) { 85 | return; 86 | } 87 | let __document = document; 88 | if (this && this.document){ 89 | __document = this.document; 90 | } 91 | const __container = __document.getElementById(wrapperId); 92 | if (!__container) { 93 | return; 94 | } 95 | 96 | const newSandbox = { 97 | ...sandbox, 98 | __container 99 | }; 100 | const func = compileCode(reactCode); 101 | func(newSandbox); 102 | // style resume 103 | __container.style.opacity = 1; 104 | } 105 | 106 | export function getWrapperId() { 107 | return 'rc' + Math.random().toString(36).substr(2, 10); 108 | } -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import ReactDOMServer from 'react-dom/server'; 4 | import * as babel from 'babel-standalone/babel.min'; 5 | import { babelTransformOptions } from './constans'; 6 | import { compileCode, filterXSS,renderReact } from './func'; 7 | 8 | const NOOP = () => { 9 | }; 10 | export default class Replacer { 11 | constructor(options = {}) { 12 | if (options.sandbox && typeof options.sandbox !== 'object') { 13 | throw Error('sandbox must be object!'); 14 | } 15 | if (options.babelInit && typeof options.babelInit !== 'function') { 16 | throw Error('babelInit must be function!'); 17 | } 18 | this._init(options); 19 | } 20 | 21 | _init({ 22 | babelInit = NOOP, 23 | sandbox = {}, 24 | babelOptions, 25 | enableSSR = true 26 | }) { 27 | if (!babel.availablePlugins.filterXSS) { 28 | babel.registerPlugin('filterXSS', filterXSS); 29 | } 30 | babelInit(babel); 31 | this.babel = babel; 32 | this.babelOptions = { 33 | ...babelTransformOptions, 34 | ...babelOptions 35 | }; 36 | this.sandbox = { 37 | React, 38 | ReactDOM, 39 | NULL: Object.create(null), 40 | ReactDOMServer, 41 | ...sandbox 42 | }; 43 | this.enableSSR = enableSSR; 44 | this.env = {}; 45 | } 46 | 47 | _replace({ 48 | str, 49 | wrapperId, 50 | lang 51 | }) { 52 | if (lang === 'mixin-react') { 53 | return this._replacerReact(str, wrapperId); 54 | } 55 | return str; 56 | } 57 | 58 | // get html of react code 59 | _replacerReact(code, wrapperId) { 60 | const reactCode = `function YMGGKLK(props) { ${code} } 61 | ReactDOM.hydrate(YMGGKLK.call(NULL),__container) 62 | `; 63 | 64 | const finalCode = this._transReactCode(reactCode); 65 | const html = this.enableSSR ? this._codeToHtml(wrapperId, code) : '
'; 66 | 67 | setTimeout(renderReact.bind(this.env, wrapperId, finalCode, this.sandbox), 0); 68 | 69 | return html; 70 | } 71 | 72 | // pre-render HTML 73 | _codeToHtml(wrapperId, code) { 74 | const reactCode = `function YMGGKLK(props) { ${code} } 75 | Html["${wrapperId}"] = ReactDOMServer.renderToStaticMarkup(YMGGKLK.call(NULL)) 76 | `; 77 | const Html = {}; 78 | const newSandbox = { 79 | ...this.sandbox, 80 | Html 81 | }; 82 | const func = compileCode(this._transReactCode(reactCode)); 83 | func(newSandbox); 84 | return Html[wrapperId]; 85 | } 86 | 87 | // use babel translation react code 88 | _transReactCode(code) { 89 | const rst = this.babel.transform(code, this.babelOptions); 90 | 91 | return rst.code; 92 | } 93 | 94 | getHtml(wrapperId,str,env = {},lang = 'mixin-react') { 95 | this.env = env; 96 | const html = this._replace({ 97 | str, 98 | wrapperId, 99 | lang 100 | }); 101 | return html; 102 | } 103 | } -------------------------------------------------------------------------------- /lib/react_block.js: -------------------------------------------------------------------------------- 1 | // React block 2 | // An array of opening and corresponding closing sequences for react jsx tags, 3 | // last argument defines whether it can terminate a paragraph or not 4 | // 5 | import {REACT_TAG_RE} from './constans'; 6 | 7 | var REACT_SEQUENCES = [ 8 | // PascalCase Components 9 | [REACT_TAG_RE, /^$/, true], 10 | [/^<([A-Z][A-Za-z0-9]*)/, /^$/, true, (p1) => { 11 | return new RegExp(`\\s*$`); 12 | }] 13 | // [ /^/, true ], 14 | // [ new RegExp('^|$))', 'i'), /^$/, true ], 15 | // [ new RegExp(HTML_OPEN_CLOSE_TAG_RE.source + '\\s*$'), /^$/, false ] 16 | ]; 17 | var htmlReg = /<[a-z]+.*>/; 18 | 19 | export default function react_block(state, startLine, endLine, silent) { 20 | var i, nextLine, token, lineText, 21 | pos = state.bMarks[startLine] + state.tShift[startLine], 22 | max = state.eMarks[startLine], 23 | match = ''; 24 | 25 | // if it's indented more than 3 spaces, it should be a code block 26 | if (state.sCount[startLine] - state.blkIndent >= 4) { return false; } 27 | 28 | if (!state.md.options.html) { return false; } 29 | 30 | if (state.src.charCodeAt(pos) !== 0x3C/* < */) { return false; } 31 | 32 | lineText = state.src.slice(pos, max); 33 | 34 | for (i = 0; i < REACT_SEQUENCES.length; i++) { 35 | if (REACT_SEQUENCES[i][0].test(lineText)) { 36 | match = lineText.match(REACT_SEQUENCES[i][0])[1]; 37 | break; 38 | } 39 | } 40 | 41 | if (i === REACT_SEQUENCES.length) { return false; } 42 | 43 | if (silent) { 44 | // true if this sequence can be a terminator, false otherwise 45 | return REACT_SEQUENCES[i][2]; 46 | } 47 | 48 | nextLine = startLine + 1; 49 | 50 | if (typeof REACT_SEQUENCES[i][3] === 'function'){ 51 | REACT_SEQUENCES[i][1] = REACT_SEQUENCES[i][3](match); 52 | } 53 | // If we are here - we detected HTML block. 54 | // Let's roll down till block end. 55 | if (!REACT_SEQUENCES[i][1].test(lineText)) { 56 | for (; nextLine < endLine; nextLine++) { 57 | if (state.sCount[nextLine] < state.blkIndent) { break; } 58 | 59 | pos = state.bMarks[nextLine] + state.tShift[nextLine]; 60 | max = state.eMarks[nextLine]; 61 | lineText = state.src.slice(pos, max); 62 | 63 | if ((REACT_SEQUENCES[i][1].test(lineText)) && !htmlReg.test(lineText)) { 64 | if (lineText.length !== 0) { nextLine++; } 65 | break; 66 | } 67 | } 68 | } 69 | 70 | state.line = nextLine; 71 | 72 | token = state.push('react_block', '', 0); 73 | token.map = [ startLine, nextLine ]; 74 | token.content = state.getLines(startLine, nextLine, state.blkIndent, true); 75 | 76 | return true; 77 | } 78 | -------------------------------------------------------------------------------- /lib/react_inline.js: -------------------------------------------------------------------------------- 1 | import { REACT_TAG_RE,REACT_OPEN_CLOSE_TAG_RE } from './constans'; 2 | 3 | function isLetter(ch) { 4 | /*eslint no-bitwise:0*/ 5 | var lc = ch | 0x20; // to lower case 6 | return (lc >= 0x61/* a */) && (lc <= 0x7a/* z */); 7 | } 8 | 9 | function isFailedSecondCh(ch) { 10 | return ch !== 0x21/* ! */ && 11 | ch !== 0x3F/* ? */ && 12 | ch !== 0x2F/* / */ && 13 | !isLetter(ch); 14 | } 15 | 16 | export function react_inline(state, silent) { 17 | let pos = state.pos; 18 | 19 | if (!state.md.options.html) { return false; } 20 | 21 | function isStart(){ 22 | return state.src.charCodeAt(pos) === 0x3C/* < */ && 23 | pos + 2 < state.posMax; 24 | } 25 | 26 | // Check start 27 | if (!isStart()) { 28 | return false; 29 | } 30 | 31 | // Quick fail on second char 32 | if (isFailedSecondCh(state.src.charCodeAt(pos + 1))) { 33 | return false; 34 | } 35 | 36 | let match = state.src.slice(pos).match(REACT_OPEN_CLOSE_TAG_RE) || state.src.slice(pos).match(REACT_TAG_RE); 37 | if (!match) { return false; } 38 | 39 | if (!silent) { 40 | let token = state.push('react_inline', '', 0); 41 | token.content = state.src.slice(pos, pos + match[0].length); 42 | } 43 | state.pos += match[0].length; 44 | return true; 45 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "markdown-it-react-component", 3 | "version": "0.5.8", 4 | "description": "Plugin to support react component for markdown-it markdown parser", 5 | "main": "dist/index.js", 6 | "scripts": { 7 | "storybook": "start-storybook -p 9001 -c .storybook", 8 | "build": "rollup -c", 9 | "lint": "eslint lib --fix", 10 | "pub": "npm run test && npm run build && npm version patch && npm publish", 11 | "test": "./node_modules/.bin/jest", 12 | "test:debug": "node --inspect node_modules/.bin/jest --runInBand", 13 | "test:coverage": "jest -u --coverage" 14 | }, 15 | "author": "", 16 | "license": "MIT", 17 | "keywords": [ 18 | "markdown-it-plugin", 19 | "markdown-it", 20 | "markdown", 21 | "react" 22 | ], 23 | "dependencies": { 24 | "babel-standalone": "^6.26.0", 25 | "dompurify": "^1.0.10", 26 | "markdown-it-container": "^2.0.0", 27 | "react": "^16.9.0", 28 | "react-dom": "^16.9.0" 29 | }, 30 | "repository": { 31 | "type": "git", 32 | "url": "git+https://github.com/LinFeng1997/markdown-it-react-component.git" 33 | }, 34 | "devDependencies": { 35 | "@babel/core": "^7.2.2", 36 | "@babel/plugin-proposal-decorators": "^7.6.0", 37 | "@babel/plugin-proposal-object-rest-spread": "^7.4.0", 38 | "@babel/plugin-syntax-dynamic-import": "^7.2.0", 39 | "@babel/plugin-transform-parameters": "^7.4.0", 40 | "@babel/plugin-transform-runtime": "^7.2.0", 41 | "@babel/preset-env": "^7.3.1", 42 | "@babel/runtime": "^7.3.1", 43 | "@storybook/react": "^4.1.11", 44 | "antd": "^3.13.2", 45 | "babel-eslint": "^10.0.3", 46 | "babel-loader": "^8.0.6", 47 | "babel-plugin-transform-es2015-modules-commonjs": "^6.26.2", 48 | "enzyme": "^3.10.0", 49 | "enzyme-adapter-react-16": "^1.4.1", 50 | "eslint": "^6.8.0", 51 | "eslint-plugin-jest": "^23.7.0", 52 | "eslint-plugin-react": "^7.18.3", 53 | "highlight.js": "^9.14.2", 54 | "jest": "^24.9.0", 55 | "markdown-it": "^8.4.2", 56 | "react": "^16.9.0", 57 | "react-dom": "^16.9.0", 58 | "rollup": "^1.2.1", 59 | "rollup-plugin-babel": "^4.3.2", 60 | "tui-editor": "^1.3.0" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | // rollup.config.js 2 | import babel from 'rollup-plugin-babel'; 3 | export default { 4 | input: 'index.js', 5 | output: [{ 6 | file: 'dist/index.js', 7 | name: 'replacer', 8 | format: 'umd' 9 | }, { 10 | file: 'dist/es/index.js', 11 | format: 'esm' 12 | }], 13 | plugins: [ 14 | babel({ 15 | exclude: 'node_modules/**' // 仅仅转译我们的源码 16 | }) 17 | ], 18 | }; -------------------------------------------------------------------------------- /stories/editor.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | import TuiEditor from 'tui-editor/dist/tui-editor-Editor.min'; 3 | import 'tui-editor/dist/tui-editor.min.css'; 4 | import 'tui-editor/dist/tui-editor-contents.min.css'; 5 | import 'codemirror/lib/codemirror.css'; 6 | import md from './md'; 7 | 8 | TuiEditor.markdownitHighlight.use(old => { 9 | for (let key in old){ 10 | old[key] = md[key]; 11 | } 12 | return md; 13 | }); 14 | export default class MarkdownEditor extends PureComponent { 15 | componentDidMount(){ 16 | const editor = new TuiEditor({ 17 | el: document.getElementById('editor'), 18 | initialEditType: 'markdown', 19 | previewStyle: 'vertical', 20 | height: '500px' 21 | }); 22 | editor.setValue(this.props.markdown); 23 | } 24 | render(){ 25 | return
; 26 | } 27 | } -------------------------------------------------------------------------------- /stories/github-markdown.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: octicons-link; 3 | src: url(data:font/woff;charset=utf-8;base64,d09GRgABAAAAAAZwABAAAAAACFQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABEU0lHAAAGaAAAAAgAAAAIAAAAAUdTVUIAAAZcAAAACgAAAAoAAQAAT1MvMgAAAyQAAABJAAAAYFYEU3RjbWFwAAADcAAAAEUAAACAAJThvmN2dCAAAATkAAAABAAAAAQAAAAAZnBnbQAAA7gAAACyAAABCUM+8IhnYXNwAAAGTAAAABAAAAAQABoAI2dseWYAAAFsAAABPAAAAZwcEq9taGVhZAAAAsgAAAA0AAAANgh4a91oaGVhAAADCAAAABoAAAAkCA8DRGhtdHgAAAL8AAAADAAAAAwGAACfbG9jYQAAAsAAAAAIAAAACABiATBtYXhwAAACqAAAABgAAAAgAA8ASm5hbWUAAAToAAABQgAAAlXu73sOcG9zdAAABiwAAAAeAAAAME3QpOBwcmVwAAAEbAAAAHYAAAB/aFGpk3jaTY6xa8JAGMW/O62BDi0tJLYQincXEypYIiGJjSgHniQ6umTsUEyLm5BV6NDBP8Tpts6F0v+k/0an2i+itHDw3v2+9+DBKTzsJNnWJNTgHEy4BgG3EMI9DCEDOGEXzDADU5hBKMIgNPZqoD3SilVaXZCER3/I7AtxEJLtzzuZfI+VVkprxTlXShWKb3TBecG11rwoNlmmn1P2WYcJczl32etSpKnziC7lQyWe1smVPy/Lt7Kc+0vWY/gAgIIEqAN9we0pwKXreiMasxvabDQMM4riO+qxM2ogwDGOZTXxwxDiycQIcoYFBLj5K3EIaSctAq2kTYiw+ymhce7vwM9jSqO8JyVd5RH9gyTt2+J/yUmYlIR0s04n6+7Vm1ozezUeLEaUjhaDSuXHwVRgvLJn1tQ7xiuVv/ocTRF42mNgZGBgYGbwZOBiAAFGJBIMAAizAFoAAABiAGIAznjaY2BkYGAA4in8zwXi+W2+MjCzMIDApSwvXzC97Z4Ig8N/BxYGZgcgl52BCSQKAA3jCV8CAABfAAAAAAQAAEB42mNgZGBg4f3vACQZQABIMjKgAmYAKEgBXgAAeNpjYGY6wTiBgZWBg2kmUxoDA4MPhGZMYzBi1AHygVLYQUCaawqDA4PChxhmh/8ODDEsvAwHgMKMIDnGL0x7gJQCAwMAJd4MFwAAAHjaY2BgYGaA4DAGRgYQkAHyGMF8NgYrIM3JIAGVYYDT+AEjAwuDFpBmA9KMDEwMCh9i/v8H8sH0/4dQc1iAmAkALaUKLgAAAHjaTY9LDsIgEIbtgqHUPpDi3gPoBVyRTmTddOmqTXThEXqrob2gQ1FjwpDvfwCBdmdXC5AVKFu3e5MfNFJ29KTQT48Ob9/lqYwOGZxeUelN2U2R6+cArgtCJpauW7UQBqnFkUsjAY/kOU1cP+DAgvxwn1chZDwUbd6CFimGXwzwF6tPbFIcjEl+vvmM/byA48e6tWrKArm4ZJlCbdsrxksL1AwWn/yBSJKpYbq8AXaaTb8AAHja28jAwOC00ZrBeQNDQOWO//sdBBgYGRiYWYAEELEwMTE4uzo5Zzo5b2BxdnFOcALxNjA6b2ByTswC8jYwg0VlNuoCTWAMqNzMzsoK1rEhNqByEyerg5PMJlYuVueETKcd/89uBpnpvIEVomeHLoMsAAe1Id4AAAAAAAB42oWQT07CQBTGv0JBhagk7HQzKxca2sJCE1hDt4QF+9JOS0nbaaYDCQfwCJ7Au3AHj+LO13FMmm6cl7785vven0kBjHCBhfpYuNa5Ph1c0e2Xu3jEvWG7UdPDLZ4N92nOm+EBXuAbHmIMSRMs+4aUEd4Nd3CHD8NdvOLTsA2GL8M9PODbcL+hD7C1xoaHeLJSEao0FEW14ckxC+TU8TxvsY6X0eLPmRhry2WVioLpkrbp84LLQPGI7c6sOiUzpWIWS5GzlSgUzzLBSikOPFTOXqly7rqx0Z1Q5BAIoZBSFihQYQOOBEdkCOgXTOHA07HAGjGWiIjaPZNW13/+lm6S9FT7rLHFJ6fQbkATOG1j2OFMucKJJsxIVfQORl+9Jyda6Sl1dUYhSCm1dyClfoeDve4qMYdLEbfqHf3O/AdDumsjAAB42mNgYoAAZQYjBmyAGYQZmdhL8zLdDEydARfoAqIAAAABAAMABwAKABMAB///AA8AAQAAAAAAAAAAAAAAAAABAAAAAA==) format('woff'); 4 | } 5 | 6 | .markdown-body .octicon { 7 | display: inline-block; 8 | fill: currentColor; 9 | vertical-align: text-bottom; 10 | } 11 | 12 | .markdown-body .anchor { 13 | float: left; 14 | line-height: 1; 15 | margin-left: -20px; 16 | padding-right: 4px; 17 | } 18 | 19 | .markdown-body .anchor:focus { 20 | outline: none; 21 | } 22 | 23 | .markdown-body h1 .octicon-link, 24 | .markdown-body h2 .octicon-link, 25 | .markdown-body h3 .octicon-link, 26 | .markdown-body h4 .octicon-link, 27 | .markdown-body h5 .octicon-link, 28 | .markdown-body h6 .octicon-link { 29 | color: #1b1f23; 30 | vertical-align: middle; 31 | visibility: hidden; 32 | } 33 | 34 | .markdown-body h1:hover .anchor, 35 | .markdown-body h2:hover .anchor, 36 | .markdown-body h3:hover .anchor, 37 | .markdown-body h4:hover .anchor, 38 | .markdown-body h5:hover .anchor, 39 | .markdown-body h6:hover .anchor { 40 | text-decoration: none; 41 | } 42 | 43 | .markdown-body h1:hover .anchor .octicon-link, 44 | .markdown-body h2:hover .anchor .octicon-link, 45 | .markdown-body h3:hover .anchor .octicon-link, 46 | .markdown-body h4:hover .anchor .octicon-link, 47 | .markdown-body h5:hover .anchor .octicon-link, 48 | .markdown-body h6:hover .anchor .octicon-link { 49 | visibility: visible; 50 | } 51 | 52 | .markdown-body { 53 | -ms-text-size-adjust: 100%; 54 | -webkit-text-size-adjust: 100%; 55 | color: #24292e; 56 | line-height: 1.5; 57 | font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol; 58 | font-size: 16px; 59 | line-height: 1.5; 60 | word-wrap: break-word; 61 | } 62 | 63 | .markdown-body .pl-c { 64 | color: #6a737d; 65 | } 66 | 67 | .markdown-body .pl-c1, 68 | .markdown-body .pl-s .pl-v { 69 | color: #005cc5; 70 | } 71 | 72 | .markdown-body .pl-e, 73 | .markdown-body .pl-en { 74 | color: #6f42c1; 75 | } 76 | 77 | .markdown-body .pl-s .pl-s1, 78 | .markdown-body .pl-smi { 79 | color: #24292e; 80 | } 81 | 82 | .markdown-body .pl-ent { 83 | color: #22863a; 84 | } 85 | 86 | .markdown-body .pl-k { 87 | color: #d73a49; 88 | } 89 | 90 | .markdown-body .pl-pds, 91 | .markdown-body .pl-s, 92 | .markdown-body .pl-s .pl-pse .pl-s1, 93 | .markdown-body .pl-sr, 94 | .markdown-body .pl-sr .pl-cce, 95 | .markdown-body .pl-sr .pl-sra, 96 | .markdown-body .pl-sr .pl-sre { 97 | color: #032f62; 98 | } 99 | 100 | .markdown-body .pl-smw, 101 | .markdown-body .pl-v { 102 | color: #e36209; 103 | } 104 | 105 | .markdown-body .pl-bu { 106 | color: #b31d28; 107 | } 108 | 109 | .markdown-body .pl-ii { 110 | background-color: #b31d28; 111 | color: #fafbfc; 112 | } 113 | 114 | .markdown-body .pl-c2 { 115 | background-color: #d73a49; 116 | color: #fafbfc; 117 | } 118 | 119 | .markdown-body .pl-c2:before { 120 | content: "^M"; 121 | } 122 | 123 | .markdown-body .pl-sr .pl-cce { 124 | color: #22863a; 125 | font-weight: 700; 126 | } 127 | 128 | .markdown-body .pl-ml { 129 | color: #735c0f; 130 | } 131 | 132 | .markdown-body .pl-mh, 133 | .markdown-body .pl-mh .pl-en, 134 | .markdown-body .pl-ms { 135 | color: #005cc5; 136 | font-weight: 700; 137 | } 138 | 139 | .markdown-body .pl-mi { 140 | color: #24292e; 141 | font-style: italic; 142 | } 143 | 144 | .markdown-body .pl-mb { 145 | color: #24292e; 146 | font-weight: 700; 147 | } 148 | 149 | .markdown-body .pl-md { 150 | background-color: #ffeef0; 151 | color: #b31d28; 152 | } 153 | 154 | .markdown-body .pl-mi1 { 155 | background-color: #f0fff4; 156 | color: #22863a; 157 | } 158 | 159 | .markdown-body .pl-mc { 160 | background-color: #ffebda; 161 | color: #e36209; 162 | } 163 | 164 | .markdown-body .pl-mi2 { 165 | background-color: #005cc5; 166 | color: #f6f8fa; 167 | } 168 | 169 | .markdown-body .pl-mdr { 170 | color: #6f42c1; 171 | font-weight: 700; 172 | } 173 | 174 | .markdown-body .pl-ba { 175 | color: #586069; 176 | } 177 | 178 | .markdown-body .pl-sg { 179 | color: #959da5; 180 | } 181 | 182 | .markdown-body .pl-corl { 183 | color: #032f62; 184 | text-decoration: underline; 185 | } 186 | 187 | .markdown-body details { 188 | display: block; 189 | } 190 | 191 | .markdown-body summary { 192 | display: list-item; 193 | } 194 | 195 | .markdown-body a { 196 | background-color: transparent; 197 | } 198 | 199 | .markdown-body a:active, 200 | .markdown-body a:hover { 201 | outline-width: 0; 202 | } 203 | 204 | .markdown-body strong { 205 | font-weight: inherit; 206 | font-weight: bolder; 207 | } 208 | 209 | .markdown-body h1 { 210 | font-size: 2em; 211 | margin: .67em 0; 212 | } 213 | 214 | .markdown-body img { 215 | border-style: none; 216 | } 217 | 218 | .markdown-body code, 219 | .markdown-body kbd, 220 | .markdown-body pre { 221 | font-family: monospace,monospace; 222 | font-size: 1em; 223 | } 224 | 225 | .markdown-body hr { 226 | box-sizing: content-box; 227 | height: 0; 228 | overflow: visible; 229 | } 230 | 231 | .markdown-body input { 232 | font: inherit; 233 | margin: 0; 234 | } 235 | 236 | .markdown-body input { 237 | overflow: visible; 238 | } 239 | 240 | .markdown-body [type=checkbox] { 241 | box-sizing: border-box; 242 | padding: 0; 243 | } 244 | 245 | .markdown-body * { 246 | box-sizing: border-box; 247 | } 248 | 249 | .markdown-body input { 250 | font-family: inherit; 251 | font-size: inherit; 252 | line-height: inherit; 253 | } 254 | 255 | .markdown-body a { 256 | color: #0366d6; 257 | text-decoration: none; 258 | } 259 | 260 | .markdown-body a:hover { 261 | text-decoration: underline; 262 | } 263 | 264 | .markdown-body strong { 265 | font-weight: 600; 266 | } 267 | 268 | .markdown-body hr { 269 | background: transparent; 270 | border: 0; 271 | border-bottom: 1px solid #dfe2e5; 272 | height: 0; 273 | margin: 15px 0; 274 | overflow: hidden; 275 | } 276 | 277 | .markdown-body hr:before { 278 | content: ""; 279 | display: table; 280 | } 281 | 282 | .markdown-body hr:after { 283 | clear: both; 284 | content: ""; 285 | display: table; 286 | } 287 | 288 | .markdown-body table { 289 | border-collapse: collapse; 290 | border-spacing: 0; 291 | } 292 | 293 | .markdown-body td, 294 | .markdown-body th { 295 | padding: 0; 296 | } 297 | 298 | .markdown-body details summary { 299 | cursor: pointer; 300 | } 301 | 302 | .markdown-body h1, 303 | .markdown-body h2, 304 | .markdown-body h3, 305 | .markdown-body h4, 306 | .markdown-body h5, 307 | .markdown-body h6 { 308 | margin-bottom: 0; 309 | margin-top: 0; 310 | } 311 | 312 | .markdown-body h1 { 313 | font-size: 32px; 314 | } 315 | 316 | .markdown-body h1, 317 | .markdown-body h2 { 318 | font-weight: 600; 319 | } 320 | 321 | .markdown-body h2 { 322 | font-size: 24px; 323 | } 324 | 325 | .markdown-body h3 { 326 | font-size: 20px; 327 | } 328 | 329 | .markdown-body h3, 330 | .markdown-body h4 { 331 | font-weight: 600; 332 | } 333 | 334 | .markdown-body h4 { 335 | font-size: 16px; 336 | } 337 | 338 | .markdown-body h5 { 339 | font-size: 14px; 340 | } 341 | 342 | .markdown-body h5, 343 | .markdown-body h6 { 344 | font-weight: 600; 345 | } 346 | 347 | .markdown-body h6 { 348 | font-size: 12px; 349 | } 350 | 351 | .markdown-body p { 352 | margin-bottom: 10px; 353 | margin-top: 0; 354 | } 355 | 356 | .markdown-body blockquote { 357 | margin: 0; 358 | } 359 | 360 | .markdown-body ol, 361 | .markdown-body ul { 362 | margin-bottom: 0; 363 | margin-top: 0; 364 | padding-left: 0; 365 | } 366 | 367 | .markdown-body ol ol, 368 | .markdown-body ul ol { 369 | list-style-type: lower-roman; 370 | } 371 | 372 | .markdown-body ol ol ol, 373 | .markdown-body ol ul ol, 374 | .markdown-body ul ol ol, 375 | .markdown-body ul ul ol { 376 | list-style-type: lower-alpha; 377 | } 378 | 379 | .markdown-body dd { 380 | margin-left: 0; 381 | } 382 | 383 | .markdown-body code, 384 | .markdown-body pre { 385 | font-family: SFMono-Regular,Consolas,Liberation Mono,Menlo,Courier,monospace; 386 | font-size: 12px; 387 | } 388 | 389 | .markdown-body pre { 390 | margin-bottom: 0; 391 | margin-top: 0; 392 | } 393 | 394 | .markdown-body input::-webkit-inner-spin-button, 395 | .markdown-body input::-webkit-outer-spin-button { 396 | -webkit-appearance: none; 397 | appearance: none; 398 | margin: 0; 399 | } 400 | 401 | .markdown-body .border { 402 | border: 1px solid #e1e4e8!important; 403 | } 404 | 405 | .markdown-body .border-0 { 406 | border: 0!important; 407 | } 408 | 409 | .markdown-body .border-bottom { 410 | border-bottom: 1px solid #e1e4e8!important; 411 | } 412 | 413 | .markdown-body .rounded-1 { 414 | border-radius: 3px!important; 415 | } 416 | 417 | .markdown-body .bg-white { 418 | background-color: #fff!important; 419 | } 420 | 421 | .markdown-body .bg-gray-light { 422 | background-color: #fafbfc!important; 423 | } 424 | 425 | .markdown-body .text-gray-light { 426 | color: #6a737d!important; 427 | } 428 | 429 | .markdown-body .mb-0 { 430 | margin-bottom: 0!important; 431 | } 432 | 433 | .markdown-body .my-2 { 434 | margin-bottom: 8px!important; 435 | margin-top: 8px!important; 436 | } 437 | 438 | .markdown-body .pl-0 { 439 | padding-left: 0!important; 440 | } 441 | 442 | .markdown-body .py-0 { 443 | padding-bottom: 0!important; 444 | padding-top: 0!important; 445 | } 446 | 447 | .markdown-body .pl-1 { 448 | padding-left: 4px!important; 449 | } 450 | 451 | .markdown-body .pl-2 { 452 | padding-left: 8px!important; 453 | } 454 | 455 | .markdown-body .py-2 { 456 | padding-bottom: 8px!important; 457 | padding-top: 8px!important; 458 | } 459 | 460 | .markdown-body .pl-3, 461 | .markdown-body .px-3 { 462 | padding-left: 16px!important; 463 | } 464 | 465 | .markdown-body .px-3 { 466 | padding-right: 16px!important; 467 | } 468 | 469 | .markdown-body .pl-4 { 470 | padding-left: 24px!important; 471 | } 472 | 473 | .markdown-body .pl-5 { 474 | padding-left: 32px!important; 475 | } 476 | 477 | .markdown-body .pl-6 { 478 | padding-left: 40px!important; 479 | } 480 | 481 | .markdown-body .f6 { 482 | font-size: 12px!important; 483 | } 484 | 485 | .markdown-body .lh-condensed { 486 | line-height: 1.25!important; 487 | } 488 | 489 | .markdown-body .text-bold { 490 | font-weight: 600!important; 491 | } 492 | 493 | .markdown-body:before { 494 | content: ""; 495 | display: table; 496 | } 497 | 498 | .markdown-body:after { 499 | clear: both; 500 | content: ""; 501 | display: table; 502 | } 503 | 504 | .markdown-body>:first-child { 505 | margin-top: 0!important; 506 | } 507 | 508 | .markdown-body>:last-child { 509 | margin-bottom: 0!important; 510 | } 511 | 512 | .markdown-body a:not([href]) { 513 | color: inherit; 514 | text-decoration: none; 515 | } 516 | 517 | .markdown-body blockquote, 518 | .markdown-body dl, 519 | .markdown-body ol, 520 | .markdown-body p, 521 | .markdown-body pre, 522 | .markdown-body table, 523 | .markdown-body ul { 524 | margin-bottom: 16px; 525 | margin-top: 0; 526 | } 527 | 528 | .markdown-body hr { 529 | background-color: #e1e4e8; 530 | border: 0; 531 | height: .25em; 532 | margin: 24px 0; 533 | padding: 0; 534 | } 535 | 536 | .markdown-body blockquote { 537 | border-left: .25em solid #dfe2e5; 538 | color: #6a737d; 539 | padding: 0 1em; 540 | } 541 | 542 | .markdown-body blockquote>:first-child { 543 | margin-top: 0; 544 | } 545 | 546 | .markdown-body blockquote>:last-child { 547 | margin-bottom: 0; 548 | } 549 | 550 | .markdown-body kbd { 551 | background-color: #fafbfc; 552 | border: 1px solid #c6cbd1; 553 | border-bottom-color: #959da5; 554 | border-radius: 3px; 555 | box-shadow: inset 0 -1px 0 #959da5; 556 | color: #444d56; 557 | display: inline-block; 558 | font-size: 11px; 559 | line-height: 10px; 560 | padding: 3px 5px; 561 | vertical-align: middle; 562 | } 563 | 564 | .markdown-body h1, 565 | .markdown-body h2, 566 | .markdown-body h3, 567 | .markdown-body h4, 568 | .markdown-body h5, 569 | .markdown-body h6 { 570 | font-weight: 600; 571 | line-height: 1.25; 572 | margin-bottom: 16px; 573 | margin-top: 24px; 574 | } 575 | 576 | .markdown-body h1 { 577 | font-size: 2em; 578 | } 579 | 580 | .markdown-body h1, 581 | .markdown-body h2 { 582 | border-bottom: 1px solid #eaecef; 583 | padding-bottom: .3em; 584 | } 585 | 586 | .markdown-body h2 { 587 | font-size: 1.5em; 588 | } 589 | 590 | .markdown-body h3 { 591 | font-size: 1.25em; 592 | } 593 | 594 | .markdown-body h4 { 595 | font-size: 1em; 596 | } 597 | 598 | .markdown-body h5 { 599 | font-size: .875em; 600 | } 601 | 602 | .markdown-body h6 { 603 | color: #6a737d; 604 | font-size: .85em; 605 | } 606 | 607 | .markdown-body ol, 608 | .markdown-body ul { 609 | padding-left: 2em; 610 | } 611 | 612 | .markdown-body ol ol, 613 | .markdown-body ol ul, 614 | .markdown-body ul ol, 615 | .markdown-body ul ul { 616 | margin-bottom: 0; 617 | margin-top: 0; 618 | } 619 | 620 | .markdown-body li { 621 | word-wrap: break-all; 622 | } 623 | 624 | .markdown-body li>p { 625 | margin-top: 16px; 626 | } 627 | 628 | .markdown-body li+li { 629 | margin-top: .25em; 630 | } 631 | 632 | .markdown-body dl { 633 | padding: 0; 634 | } 635 | 636 | .markdown-body dl dt { 637 | font-size: 1em; 638 | font-style: italic; 639 | font-weight: 600; 640 | margin-top: 16px; 641 | padding: 0; 642 | } 643 | 644 | .markdown-body dl dd { 645 | margin-bottom: 16px; 646 | padding: 0 16px; 647 | } 648 | 649 | .markdown-body table { 650 | display: block; 651 | overflow: auto; 652 | width: 100%; 653 | } 654 | 655 | .markdown-body table th { 656 | font-weight: 600; 657 | } 658 | 659 | .markdown-body table td, 660 | .markdown-body table th { 661 | border: 1px solid #dfe2e5; 662 | padding: 6px 13px; 663 | } 664 | 665 | .markdown-body table tr { 666 | background-color: #fff; 667 | border-top: 1px solid #c6cbd1; 668 | } 669 | 670 | .markdown-body table tr:nth-child(2n) { 671 | background-color: #f6f8fa; 672 | } 673 | 674 | .markdown-body img { 675 | background-color: #fff; 676 | box-sizing: content-box; 677 | max-width: 100%; 678 | } 679 | 680 | .markdown-body img[align=right] { 681 | padding-left: 20px; 682 | } 683 | 684 | .markdown-body img[align=left] { 685 | padding-right: 20px; 686 | } 687 | 688 | .markdown-body code { 689 | background-color: rgba(27,31,35,.05); 690 | border-radius: 3px; 691 | font-size: 85%; 692 | margin: 0; 693 | padding: .2em .4em; 694 | } 695 | 696 | .markdown-body pre { 697 | word-wrap: normal; 698 | } 699 | 700 | .markdown-body pre>code { 701 | background: transparent; 702 | border: 0; 703 | font-size: 100%; 704 | margin: 0; 705 | padding: 0; 706 | white-space: pre; 707 | word-break: normal; 708 | } 709 | 710 | .markdown-body .highlight { 711 | margin-bottom: 16px; 712 | } 713 | 714 | .markdown-body .highlight pre { 715 | margin-bottom: 0; 716 | word-break: normal; 717 | } 718 | 719 | .markdown-body .highlight pre, 720 | .markdown-body pre { 721 | background-color: #f6f8fa; 722 | border-radius: 3px; 723 | font-size: 85%; 724 | line-height: 1.45; 725 | overflow: auto; 726 | padding: 16px; 727 | } 728 | 729 | .markdown-body pre code { 730 | background-color: transparent; 731 | border: 0; 732 | display: inline; 733 | line-height: inherit; 734 | margin: 0; 735 | max-width: auto; 736 | overflow: visible; 737 | padding: 0; 738 | word-wrap: normal; 739 | } 740 | 741 | .markdown-body .commit-tease-sha { 742 | color: #444d56; 743 | display: inline-block; 744 | font-family: SFMono-Regular,Consolas,Liberation Mono,Menlo,Courier,monospace; 745 | font-size: 90%; 746 | } 747 | 748 | .markdown-body .blob-wrapper { 749 | border-bottom-left-radius: 3px; 750 | border-bottom-right-radius: 3px; 751 | overflow-x: auto; 752 | overflow-y: hidden; 753 | } 754 | 755 | .markdown-body .blob-wrapper-embedded { 756 | max-height: 240px; 757 | overflow-y: auto; 758 | } 759 | 760 | .markdown-body .blob-num { 761 | -moz-user-select: none; 762 | -ms-user-select: none; 763 | -webkit-user-select: none; 764 | color: rgba(27,31,35,.3); 765 | cursor: pointer; 766 | font-family: SFMono-Regular,Consolas,Liberation Mono,Menlo,Courier,monospace; 767 | font-size: 12px; 768 | line-height: 20px; 769 | min-width: 50px; 770 | padding-left: 10px; 771 | padding-right: 10px; 772 | text-align: right; 773 | user-select: none; 774 | vertical-align: top; 775 | white-space: nowrap; 776 | width: 1%; 777 | } 778 | 779 | .markdown-body .blob-num:hover { 780 | color: rgba(27,31,35,.6); 781 | } 782 | 783 | .markdown-body .blob-num:before { 784 | content: attr(data-line-number); 785 | } 786 | 787 | .markdown-body .blob-code { 788 | line-height: 20px; 789 | padding-left: 10px; 790 | padding-right: 10px; 791 | position: relative; 792 | vertical-align: top; 793 | } 794 | 795 | .markdown-body .blob-code-inner { 796 | color: #24292e; 797 | font-family: SFMono-Regular,Consolas,Liberation Mono,Menlo,Courier,monospace; 798 | font-size: 12px; 799 | overflow: visible; 800 | white-space: pre; 801 | word-wrap: normal; 802 | } 803 | 804 | .markdown-body .pl-token.active, 805 | .markdown-body .pl-token:hover { 806 | background: #ffea7f; 807 | cursor: pointer; 808 | } 809 | 810 | .markdown-body kbd { 811 | background-color: #fafbfc; 812 | border: 1px solid #d1d5da; 813 | border-bottom-color: #c6cbd1; 814 | border-radius: 3px; 815 | box-shadow: inset 0 -1px 0 #c6cbd1; 816 | color: #444d56; 817 | display: inline-block; 818 | font: 11px SFMono-Regular,Consolas,Liberation Mono,Menlo,Courier,monospace; 819 | line-height: 10px; 820 | padding: 3px 5px; 821 | vertical-align: middle; 822 | } 823 | 824 | .markdown-body :checked+.radio-label { 825 | border-color: #0366d6; 826 | position: relative; 827 | z-index: 1; 828 | } 829 | 830 | .markdown-body .tab-size[data-tab-size="1"] { 831 | -moz-tab-size: 1; 832 | tab-size: 1; 833 | } 834 | 835 | .markdown-body .tab-size[data-tab-size="2"] { 836 | -moz-tab-size: 2; 837 | tab-size: 2; 838 | } 839 | 840 | .markdown-body .tab-size[data-tab-size="3"] { 841 | -moz-tab-size: 3; 842 | tab-size: 3; 843 | } 844 | 845 | .markdown-body .tab-size[data-tab-size="4"] { 846 | -moz-tab-size: 4; 847 | tab-size: 4; 848 | } 849 | 850 | .markdown-body .tab-size[data-tab-size="5"] { 851 | -moz-tab-size: 5; 852 | tab-size: 5; 853 | } 854 | 855 | .markdown-body .tab-size[data-tab-size="6"] { 856 | -moz-tab-size: 6; 857 | tab-size: 6; 858 | } 859 | 860 | .markdown-body .tab-size[data-tab-size="7"] { 861 | -moz-tab-size: 7; 862 | tab-size: 7; 863 | } 864 | 865 | .markdown-body .tab-size[data-tab-size="8"] { 866 | -moz-tab-size: 8; 867 | tab-size: 8; 868 | } 869 | 870 | .markdown-body .tab-size[data-tab-size="9"] { 871 | -moz-tab-size: 9; 872 | tab-size: 9; 873 | } 874 | 875 | .markdown-body .tab-size[data-tab-size="10"] { 876 | -moz-tab-size: 10; 877 | tab-size: 10; 878 | } 879 | 880 | .markdown-body .tab-size[data-tab-size="11"] { 881 | -moz-tab-size: 11; 882 | tab-size: 11; 883 | } 884 | 885 | .markdown-body .tab-size[data-tab-size="12"] { 886 | -moz-tab-size: 12; 887 | tab-size: 12; 888 | } 889 | 890 | .markdown-body .task-list-item { 891 | list-style-type: none; 892 | } 893 | 894 | .markdown-body .task-list-item+.task-list-item { 895 | margin-top: 3px; 896 | } 897 | 898 | .markdown-body .task-list-item input { 899 | margin: 0 .2em .25em -1.6em; 900 | vertical-align: middle; 901 | } 902 | 903 | .markdown-body hr { 904 | border-bottom-color: #eee; 905 | } 906 | 907 | .markdown-body .pl-0 { 908 | padding-left: 0!important; 909 | } 910 | 911 | .markdown-body .pl-1 { 912 | padding-left: 4px!important; 913 | } 914 | 915 | .markdown-body .pl-2 { 916 | padding-left: 8px!important; 917 | } 918 | 919 | .markdown-body .pl-3 { 920 | padding-left: 16px!important; 921 | } 922 | 923 | .markdown-body .pl-4 { 924 | padding-left: 24px!important; 925 | } 926 | 927 | .markdown-body .pl-5 { 928 | padding-left: 32px!important; 929 | } 930 | 931 | .markdown-body .pl-6 { 932 | padding-left: 40px!important; 933 | } 934 | 935 | .markdown-body .pl-7 { 936 | padding-left: 48px!important; 937 | } 938 | 939 | .markdown-body .pl-8 { 940 | padding-left: 64px!important; 941 | } 942 | 943 | .markdown-body .pl-9 { 944 | padding-left: 80px!important; 945 | } 946 | 947 | .markdown-body .pl-10 { 948 | padding-left: 96px!important; 949 | } 950 | 951 | .markdown-body .pl-11 { 952 | padding-left: 112px!important; 953 | } 954 | 955 | .markdown-body .pl-12 { 956 | padding-left: 128px!important; 957 | } -------------------------------------------------------------------------------- /stories/highlight.js: -------------------------------------------------------------------------------- 1 | import hljs from 'highlight.js'; 2 | 3 | export default function highlight (str, lang) { 4 | let html = hljs.getLanguage(lang) ? hljs.highlight(lang, str).value : escape(str, false); 5 | // return html; 6 | return `
${html}
`; 7 | } 8 | 9 | /** 10 | * escape code from markdown-it 11 | * @param {string} html HTML string 12 | * @param {string} encode Boolean value of whether encode or not 13 | * @returns {string} 14 | * @ignore 15 | */ 16 | function escape(html, encode) { 17 | return html.replace(!encode ? /&(?!#?\w+;)/g : /&/g, '&') 18 | .replace(//g, '>') 20 | .replace(/"/g, '"') 21 | .replace(/'/g, '''); 22 | } -------------------------------------------------------------------------------- /stories/index.css: -------------------------------------------------------------------------------- 1 | .ant-carousel .slick-slide { 2 | text-align: center; 3 | height: 160px; 4 | line-height: 160px; 5 | background: #364d79; 6 | overflow: hidden; 7 | } -------------------------------------------------------------------------------- /stories/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { storiesOf } from '@storybook/react'; 3 | import md from './md'; 4 | 5 | import str from './markdown/base.md'; 6 | import antdStr from './markdown/antd.md'; 7 | import simpleStr from './markdown/simple.md'; 8 | import 'antd/dist/antd.min.css'; 9 | import './github-markdown.css'; 10 | import 'highlight.js/styles/github.css'; 11 | import 'antd/es/button/style/index.css'; 12 | import './index.css'; 13 | 14 | import MarkdownEditor from './editor'; 15 | 16 | // console.log('parse',md.parse(simpleStr)) 17 | storiesOf('mixin-react', module) 18 | .add('base', () => ( 19 |
20 | )) 21 | .add('antd', () => ( 22 |
23 | )) 24 | .add('simple', () => ( 25 |
26 | )) 27 | .add('editor',() => ( 28 | 29 | )); -------------------------------------------------------------------------------- /stories/markdown/antd.md: -------------------------------------------------------------------------------- 1 | # Antd Example 2 | usage: 3 | ```javascript 4 | import MarkdownIt from 'markdown-it'; 5 | import { SupportReactComponent } from '../index'; 6 | import * as Antd from 'antd'; 7 | 8 | const md = new MarkdownIt({ highlight }).use(SupportReactComponent,{ 9 | sandbox: { 10 | Antd 11 | }, 12 | }); 13 | ``` 14 | 15 | ## Button 16 | 17 | ### Input 18 | 19 | ```` js 20 | ``` rc 21 | const { Button } = Antd; 22 | const xss = () => { 23 | console.log('global methods is forbidden'); 24 | alert('xss attack'); 25 | }; 26 | return ( 27 |
28 | 31 |
32 | ); 33 | ``` 34 | ```` 35 | 36 | ### Output 37 | ``` rc 38 | const { Button } = Antd; 39 | const xss = () => { 40 | console.log('global methods is forbidden'); 41 | alert('xss attack'); 42 | }; 43 | return ( 44 |
45 | 48 |
49 | ); 50 | ``` 51 | 52 | ## Icon 53 | 54 | ### Input 55 | ````js 56 | ``` rc 57 | const Icon = Antd.Icon 58 | return ( 59 |
60 | 61 | 62 | 63 |
64 | ); 65 | ``` 66 | ```` 67 | 68 | ### OutPut 69 | ``` rc 70 | const Icon = Antd.Icon 71 | return ( 72 |
73 | 74 | 75 | 76 |
77 | ); 78 | ``` 79 | 80 | 81 | ## Carousel 82 | 83 | ### Input 84 | ````js 85 | ```rc 86 | const { Carousel } = Antd 87 | return ( 88 | 89 |

1

90 |

2

91 |

3

92 |

4

93 |
94 | ) 95 | ``` 96 | ```` 97 | 98 | ### Output 99 | ``` rc 100 | const { Carousel } = Antd 101 | return ( 102 | 103 |

1

104 |

2

105 |

3

106 |

4

107 |
108 | ) 109 | ``` 110 | 111 | ## Progress 112 | 113 | ### Input 114 | 115 | ````js 116 | ```rc 117 | const Progress = Antd.Progress 118 | return ( 119 |
120 | 121 |
122 | ); 123 | ``` 124 | ```` 125 | 126 | ### Output 127 | 128 | ``` rc 129 | const Progress = Antd.Progress 130 | return ( 131 |
132 | 133 |
134 | ); 135 | ``` 136 | 137 | 138 | ## Timeline 139 | 140 | ### Input 141 | ````js 142 | ```rc 143 | const Timeline = Antd.Timeline 144 | const noteStyle = {color:'grey',margin:0} 145 | return ( 146 |
147 | 148 | aaa 149 | bbb 150 | ccc

note

151 | ddd 152 |
153 |
154 | ); 155 | ``` 156 | ```` 157 | 158 | ### Output 159 | ```rc 160 | const Timeline = Antd.Timeline 161 | const noteStyle = {color:'grey',margin:0} 162 | return ( 163 |
164 | 165 | aaa 166 | bbb 167 | ccc

note

168 | ddd 169 |
170 |
171 | ); 172 | ``` 173 | 174 | ## Steps 175 | 176 | ### Input 177 | ````js 178 | ```rc 179 | const { Steps, Popover } = Antd; 180 | 181 | const Step = Steps.Step; 182 | const contents = ['1号内容','2号内容','3号内容','4号内容'] 183 | 184 | const customDot = (dot, { index }) => ( 185 | 186 | {dot} 187 | 188 | ); 189 | return ( 190 |
191 | 192 | 193 | 194 | 195 | 196 | 197 |
198 | ); 199 | ``` 200 | ```` 201 | 202 | ### Output 203 | 204 | ```rc 205 | const { Steps, Popover } = Antd; 206 | 207 | const Step = Steps.Step; 208 | const contents = ['1号内容','2号内容','3号内容','4号内容'] 209 | 210 | const customDot = (dot, { index }) => ( 211 | 212 | {dot} 213 | 214 | ); 215 | return ( 216 |
217 | 218 | 219 | 220 | 221 | 222 | 223 |
224 | ); 225 | ``` 226 | 227 | ## Rate 228 | 229 | ### Input 230 | ````js 231 | ```rc 232 | const { Rate } = Antd 233 | return ( 234 |
235 | 236 |
237 | ); 238 | ``` 239 | ```` 240 | 241 | ### Output 242 | ```rc 243 | const { Rate } = Antd 244 | return ( 245 |
246 | 247 |
248 | ); 249 | ``` 250 | 251 | ## Card 252 | 253 | ### Input 254 | ````javascript 255 | ```rc 256 | const { Card } = Antd 257 | const { Meta } = Card 258 | return ( 259 |
260 | } 264 | > 265 | 269 | 270 |
271 | ); 272 | ``` 273 | ```` 274 | 275 | ### Output 276 | ```rc 277 | const { Card } = Antd 278 | const { Meta } = Card 279 | return ( 280 |
281 | } 285 | > 286 | 290 | 291 |
292 | ); 293 | ``` 294 | 295 | ## Collapse 296 | 297 | ### Input 298 | ````js 299 | ```rc 300 | const { Collapse } = Antd; 301 | 302 | const Panel = Collapse.Panel; 303 | 304 | return ( 305 |
306 | 307 | 308 | 嘿嘿嘿 309 | 310 | 311 | 嘻嘻嘻 312 | 313 | 314 | 哈哈哈 315 | 316 | 317 |
318 | ); 319 | ``` 320 | ```` 321 | 322 | ### Output 323 | ```rc 324 | const { Collapse } = Antd; 325 | 326 | const Panel = Collapse.Panel; 327 | 328 | return ( 329 |
330 | 331 | 332 | 嘿嘿嘿 333 | 334 | 335 | 嘻嘻嘻 336 | 337 | 338 | 哈哈哈 339 | 340 | 341 |
342 | ); 343 | ``` 344 | 345 | ## Tabs 346 | 347 | ### Input 348 | ````js 349 | ```rc 350 | const { Tabs } = Antd; 351 | 352 | const TabPane = Tabs.TabPane; 353 | return ( 354 |
355 | 356 | 1号内容 357 | 2号内容 358 | 3号内容 359 | 360 |
361 | ); 362 | ``` 363 | ```` 364 | 365 | ### Output 366 | ```rc 367 | const { Tabs } = Antd; 368 | 369 | const TabPane = Tabs.TabPane; 370 | return ( 371 |
372 | 373 | 1号内容 374 | 2号内容 375 | 3号内容 376 | 377 |
378 | ); 379 | ``` 380 | 381 | ## Alert 382 | 383 | ### Input 384 | ````js 385 | ```rc 386 | const { Alert } = Antd 387 | return ( 388 |
389 | 395 | 401 | 407 | 413 |
414 | ); 415 | ``` 416 | ```` 417 | 418 | ### Output 419 | ```rc 420 | const { Alert } = Antd 421 | return ( 422 |
423 | 429 | 435 | 441 | 447 |
448 | ); 449 | ``` 450 | 451 | ## Divider 452 | 453 | ### Input 454 | ````js 455 | ```rc 456 | const { Divider } = Antd 457 | return ( 458 |
459 | 华丽的分割线 460 |
461 | ); 462 | ``` 463 | ```` 464 | 465 | ### Outpit 466 | ```rc 467 | const { Divider } = Antd 468 | return ( 469 |
470 | 华丽的分割线 471 |
472 | ); 473 | ``` 474 | 475 | ## Row&Col 476 | 477 | ### Input 478 | ````js 479 | ```rc 480 | const { Row,Col } = Antd 481 | return ( 482 |
483 | 484 | 左边12列 485 | 右边12列~~~~ 486 | 487 |
488 | ); 489 | ``` 490 | ```` 491 | 492 | ### Output 493 | ```rc 494 | const { Row,Col } = Antd 495 | return ( 496 |
497 | 498 | 左边12列 499 | 右边12列~~~~ 500 | 501 |
502 | ); 503 | ``` -------------------------------------------------------------------------------- /stories/markdown/base.md: -------------------------------------------------------------------------------- 1 | # Example 2 | 3 | usage: 4 | ```javascript 5 | import MarkdownIt from 'markdown-it'; 6 | import { SupportReactComponent } from '../index'; 7 | const md = new MarkdownIt().use(SupportReactComponent); 8 | ``` 9 | 10 | ### Input 11 | ```` js 12 | ```rc 13 | function Hello(props){ 14 | return ( 15 |
16 | Hello: 17 |

{props.text}

18 |
19 | ) 20 | } 21 | return ( 22 |
23 | 24 |
25 | ); 26 | ``` 27 | ```` 28 | 29 | ### Output 30 | ``` rc 31 | const { useState } = React; 32 | function Hello(props){ 33 | const [count,setCount] = useState(0); 34 | return ( 35 |
36 | Hello: 37 |

setCount(count + 1)} style={{color:'red'}}>{props.text}{count}

38 |
39 | ) 40 | } 41 | return ( 42 |
43 | 44 |
45 | ); 46 | ``` -------------------------------------------------------------------------------- /stories/markdown/editor.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LinFeng1997/markdown-it-react-component/0ce72548c0324480efdb52a047c9e5fceaddf04d/stories/markdown/editor.md -------------------------------------------------------------------------------- /stories/markdown/simple.md: -------------------------------------------------------------------------------- 1 | 13 | 14 | ``` 15 | 27 | ``` 28 | 29 | ## 图标 30 | 31 | 32 | 34 | 35 | ``` 36 | 37 | 39 | ``` 40 | 41 | ## 轮播图 42 | 43 | 44 |

1

45 |

2

46 |

3

47 |

4

48 |
49 | ``` 50 | 51 |

1

52 |

2

53 |

3

54 |

4

55 |
56 | ``` 57 | 58 | ## 进度 59 | 60 | 61 | 62 | ``` 63 | 64 | ``` 65 | 66 | ## 时间轴 67 | 68 | 69 | 1 70 | 2 71 | 3

note

72 | 4 73 |
74 | 75 | ``` 76 | 77 | 1 78 | 2 79 | 3

note

80 | 4 81 |
82 | ``` 83 | 84 | ## 横向轴 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | ``` 94 | 95 | 96 | 97 | 98 | 99 | 100 | ``` 101 | 102 | ## 星级评分 103 | 104 | 105 | 106 | ``` 107 | 108 | ``` 109 | 110 | ## 卡片 111 | 112 | } 116 | > 117 | 121 | 122 | 123 | ``` 124 | } 128 | > 129 | 133 | 134 | ``` 135 | 136 | ## 折叠面板 137 | 138 | ### 简写版 139 | 140 | 141 | 嘿嘿嘿 142 | 143 | 144 | 嘻嘻嘻 145 | 146 | 147 | 哈哈哈 148 | 149 | 150 | ``` 151 | 152 | 153 | 嘿嘿嘿 154 | 155 | 156 | 嘻嘻嘻 157 | 158 | 159 | 哈哈哈 160 | 161 | 162 | ``` 163 | 164 | ## 标签页 165 | 166 | 167 | 1号内容 168 | 2号内容 169 | 3号内容 170 | 171 | 172 | ``` 173 | 174 | 1号内容 175 | 2号内容 176 | 3号内容 177 | 178 | ``` 179 | 180 | ## 提示 181 | 182 | 187 | 188 | 189 | 190 | ## 分割线 191 | 192 | 华丽的分割线 193 | ``` 194 | 华丽的分割线 195 | ``` 196 | 197 | ## 自定义布局 198 | 199 | 200 | 左边12列 201 | 右边12列~~~~ 202 | 203 | ``` 204 | 205 | 左边12列 206 | 右边12列~~~~ 207 | 208 | ``` -------------------------------------------------------------------------------- /stories/markdown/xss.md: -------------------------------------------------------------------------------- 1 | "'> 2 | 3 | 4 | -------------------------------------------------------------------------------- /stories/md.js: -------------------------------------------------------------------------------- 1 | import highlight from './highlight'; 2 | import MarkdownIt from 'markdown-it'; 3 | import * as Antd from 'antd/lib/index'; 4 | import { SupportReactComponent } from '../index'; 5 | import React from 'react'; 6 | 7 | export default new MarkdownIt({ 8 | highlight, 9 | breaks: true, 10 | html: true, 11 | quotes: '“”‘’', 12 | langPrefix: 'lang-' 13 | }).use(SupportReactComponent, { 14 | sandbox: { 15 | Antd, 16 | ...Antd, 17 | React, 18 | Array: { 19 | isArray: Array.isArray 20 | } 21 | }, 22 | allowErrorLog: true 23 | }); -------------------------------------------------------------------------------- /tests/__snapshots__/broswer.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`render should support render 1`] = ` 4 | initialize { 5 | "0": Object { 6 | "attribs": Object {}, 7 | "children": Array [ 8 | Object { 9 | "attribs": Object { 10 | "class": "rc", 11 | "id": "wrapperId", 12 | "style": "opacity: 0", 13 | }, 14 | "children": Array [ 15 | Object { 16 | "attribs": Object {}, 17 | "children": Array [ 18 | Object { 19 | "data": "hello world!", 20 | "next": null, 21 | "parent": [Circular], 22 | "prev": null, 23 | "type": "text", 24 | }, 25 | ], 26 | "name": "div", 27 | "namespace": "http://www.w3.org/1999/xhtml", 28 | "next": Object { 29 | "attribs": Object {}, 30 | "children": Array [], 31 | "name": "p", 32 | "namespace": "http://www.w3.org/1999/xhtml", 33 | "next": Object { 34 | "data": " 35 | ", 36 | "next": null, 37 | "parent": [Circular], 38 | "prev": [Circular], 39 | "type": "text", 40 | }, 41 | "parent": [Circular], 42 | "prev": [Circular], 43 | "type": "tag", 44 | "x-attribsNamespace": Object {}, 45 | "x-attribsPrefix": Object {}, 46 | }, 47 | "parent": [Circular], 48 | "prev": null, 49 | "type": "tag", 50 | "x-attribsNamespace": Object {}, 51 | "x-attribsPrefix": Object {}, 52 | }, 53 | Object { 54 | "attribs": Object {}, 55 | "children": Array [], 56 | "name": "p", 57 | "namespace": "http://www.w3.org/1999/xhtml", 58 | "next": Object { 59 | "data": " 60 | ", 61 | "next": null, 62 | "parent": [Circular], 63 | "prev": [Circular], 64 | "type": "text", 65 | }, 66 | "parent": [Circular], 67 | "prev": Object { 68 | "attribs": Object {}, 69 | "children": Array [ 70 | Object { 71 | "data": "hello world!", 72 | "next": null, 73 | "parent": [Circular], 74 | "prev": null, 75 | "type": "text", 76 | }, 77 | ], 78 | "name": "div", 79 | "namespace": "http://www.w3.org/1999/xhtml", 80 | "next": [Circular], 81 | "parent": [Circular], 82 | "prev": null, 83 | "type": "tag", 84 | "x-attribsNamespace": Object {}, 85 | "x-attribsPrefix": Object {}, 86 | }, 87 | "type": "tag", 88 | "x-attribsNamespace": Object {}, 89 | "x-attribsPrefix": Object {}, 90 | }, 91 | Object { 92 | "data": " 93 | ", 94 | "next": null, 95 | "parent": [Circular], 96 | "prev": Object { 97 | "attribs": Object {}, 98 | "children": Array [], 99 | "name": "p", 100 | "namespace": "http://www.w3.org/1999/xhtml", 101 | "next": [Circular], 102 | "parent": [Circular], 103 | "prev": Object { 104 | "attribs": Object {}, 105 | "children": Array [ 106 | Object { 107 | "data": "hello world!", 108 | "next": null, 109 | "parent": [Circular], 110 | "prev": null, 111 | "type": "text", 112 | }, 113 | ], 114 | "name": "div", 115 | "namespace": "http://www.w3.org/1999/xhtml", 116 | "next": [Circular], 117 | "parent": [Circular], 118 | "prev": null, 119 | "type": "tag", 120 | "x-attribsNamespace": Object {}, 121 | "x-attribsPrefix": Object {}, 122 | }, 123 | "type": "tag", 124 | "x-attribsNamespace": Object {}, 125 | "x-attribsPrefix": Object {}, 126 | }, 127 | "type": "text", 128 | }, 129 | ], 130 | "name": "div", 131 | "namespace": "http://www.w3.org/1999/xhtml", 132 | "next": Object { 133 | "data": " 134 | ", 135 | "next": null, 136 | "parent": [Circular], 137 | "prev": [Circular], 138 | "type": "text", 139 | }, 140 | "parent": [Circular], 141 | "prev": null, 142 | "type": "tag", 143 | "x-attribsNamespace": Object { 144 | "class": undefined, 145 | "id": undefined, 146 | "style": undefined, 147 | }, 148 | "x-attribsPrefix": Object { 149 | "class": undefined, 150 | "id": undefined, 151 | "style": undefined, 152 | }, 153 | }, 154 | Object { 155 | "data": " 156 | ", 157 | "next": null, 158 | "parent": [Circular], 159 | "prev": Object { 160 | "attribs": Object { 161 | "class": "rc", 162 | "id": "wrapperId", 163 | "style": "opacity: 0", 164 | }, 165 | "children": Array [ 166 | Object { 167 | "attribs": Object {}, 168 | "children": Array [ 169 | Object { 170 | "data": "hello world!", 171 | "next": null, 172 | "parent": [Circular], 173 | "prev": null, 174 | "type": "text", 175 | }, 176 | ], 177 | "name": "div", 178 | "namespace": "http://www.w3.org/1999/xhtml", 179 | "next": Object { 180 | "attribs": Object {}, 181 | "children": Array [], 182 | "name": "p", 183 | "namespace": "http://www.w3.org/1999/xhtml", 184 | "next": Object { 185 | "data": " 186 | ", 187 | "next": null, 188 | "parent": [Circular], 189 | "prev": [Circular], 190 | "type": "text", 191 | }, 192 | "parent": [Circular], 193 | "prev": [Circular], 194 | "type": "tag", 195 | "x-attribsNamespace": Object {}, 196 | "x-attribsPrefix": Object {}, 197 | }, 198 | "parent": [Circular], 199 | "prev": null, 200 | "type": "tag", 201 | "x-attribsNamespace": Object {}, 202 | "x-attribsPrefix": Object {}, 203 | }, 204 | Object { 205 | "attribs": Object {}, 206 | "children": Array [], 207 | "name": "p", 208 | "namespace": "http://www.w3.org/1999/xhtml", 209 | "next": Object { 210 | "data": " 211 | ", 212 | "next": null, 213 | "parent": [Circular], 214 | "prev": [Circular], 215 | "type": "text", 216 | }, 217 | "parent": [Circular], 218 | "prev": Object { 219 | "attribs": Object {}, 220 | "children": Array [ 221 | Object { 222 | "data": "hello world!", 223 | "next": null, 224 | "parent": [Circular], 225 | "prev": null, 226 | "type": "text", 227 | }, 228 | ], 229 | "name": "div", 230 | "namespace": "http://www.w3.org/1999/xhtml", 231 | "next": [Circular], 232 | "parent": [Circular], 233 | "prev": null, 234 | "type": "tag", 235 | "x-attribsNamespace": Object {}, 236 | "x-attribsPrefix": Object {}, 237 | }, 238 | "type": "tag", 239 | "x-attribsNamespace": Object {}, 240 | "x-attribsPrefix": Object {}, 241 | }, 242 | Object { 243 | "data": " 244 | ", 245 | "next": null, 246 | "parent": [Circular], 247 | "prev": Object { 248 | "attribs": Object {}, 249 | "children": Array [], 250 | "name": "p", 251 | "namespace": "http://www.w3.org/1999/xhtml", 252 | "next": [Circular], 253 | "parent": [Circular], 254 | "prev": Object { 255 | "attribs": Object {}, 256 | "children": Array [ 257 | Object { 258 | "data": "hello world!", 259 | "next": null, 260 | "parent": [Circular], 261 | "prev": null, 262 | "type": "text", 263 | }, 264 | ], 265 | "name": "div", 266 | "namespace": "http://www.w3.org/1999/xhtml", 267 | "next": [Circular], 268 | "parent": [Circular], 269 | "prev": null, 270 | "type": "tag", 271 | "x-attribsNamespace": Object {}, 272 | "x-attribsPrefix": Object {}, 273 | }, 274 | "type": "tag", 275 | "x-attribsNamespace": Object {}, 276 | "x-attribsPrefix": Object {}, 277 | }, 278 | "type": "text", 279 | }, 280 | ], 281 | "name": "div", 282 | "namespace": "http://www.w3.org/1999/xhtml", 283 | "next": [Circular], 284 | "parent": [Circular], 285 | "prev": null, 286 | "type": "tag", 287 | "x-attribsNamespace": Object { 288 | "class": undefined, 289 | "id": undefined, 290 | "style": undefined, 291 | }, 292 | "x-attribsPrefix": Object { 293 | "class": undefined, 294 | "id": undefined, 295 | "style": undefined, 296 | }, 297 | }, 298 | "type": "text", 299 | }, 300 | ], 301 | "name": "div", 302 | "namespace": "http://www.w3.org/1999/xhtml", 303 | "next": null, 304 | "parent": null, 305 | "prev": null, 306 | "root": Object { 307 | "attribs": Object {}, 308 | "children": Array [ 309 | [Circular], 310 | ], 311 | "name": "root", 312 | "namespace": "http://www.w3.org/1999/xhtml", 313 | "next": null, 314 | "parent": null, 315 | "prev": null, 316 | "type": "root", 317 | "x-attribsNamespace": Object {}, 318 | "x-attribsPrefix": Object {}, 319 | }, 320 | "type": "tag", 321 | "x-attribsNamespace": Object {}, 322 | "x-attribsPrefix": Object {}, 323 | }, 324 | "_root": [Circular], 325 | "length": 1, 326 | "options": Object { 327 | "decodeEntities": true, 328 | "normalizeWhitespace": false, 329 | "withDomLvl1": true, 330 | "xml": false, 331 | }, 332 | } 333 | `; 334 | -------------------------------------------------------------------------------- /tests/broswer.test.js: -------------------------------------------------------------------------------- 1 | // 前端 2 | // dom 3 | import MarkdownIt from 'markdown-it'; 4 | import Enzyme,{ render } from 'enzyme'; 5 | import Adapter from 'enzyme-adapter-react-16'; 6 | import { SupportReactComponent } from '../index'; 7 | import React from 'react'; 8 | import { getSimpleInput,wrapperId } from './util'; 9 | import * as funcs from '../lib/func'; 10 | 11 | funcs.getWrapperId = () => wrapperId; 12 | 13 | Enzyme.configure({ adapter: new Adapter() }) 14 | const Hello = (props) =>
{props.text} world!
; 15 | const md = new MarkdownIt({ 16 | breaks: true, 17 | html: true 18 | }).use(SupportReactComponent, { 19 | sandbox: { 20 | React, 21 | Array: { 22 | isArray: Array.isArray 23 | }, 24 | Hello 25 | } 26 | }); 27 | describe('render', () => { 28 | it('should support render', (done) => { 29 | document.body.innerHTML = `
` 30 | const html = md.render(getSimpleInput('component')); 31 | const wrapper = render( 32 |
, 33 | ); 34 | expect(wrapper).toMatchSnapshot(); 35 | setTimeout(()=>{ 36 | done(); 37 | },100) 38 | }); 39 | }); -------------------------------------------------------------------------------- /tests/index.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import MarkdownIt from 'markdown-it'; 3 | import {SupportReactComponent} from '../index'; 4 | import {getResult, wrapperId, getSimpleInput, getSimpleResult} from './util'; 5 | import * as funcs from '../lib/func'; 6 | 7 | funcs.getWrapperId = () => wrapperId; 8 | const Hello = (props) =>
{props.text} world!
; 9 | const md = new MarkdownIt({ 10 | breaks: true, 11 | html: true 12 | }).use(SupportReactComponent, { 13 | sandbox: { 14 | React, 15 | Array: { 16 | isArray: Array.isArray 17 | }, 18 | Hello 19 | }, 20 | allowErrorLog: true 21 | }); 22 | const logSpy = jest.spyOn(console, 'log').mockImplementation(() => {}); 23 | const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {}); 24 | const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); 25 | 26 | /** 27 | * only require other specs here 28 | */ 29 | describe('markdown-it-react-component', () => { 30 | 31 | beforeAll(() => { 32 | window = { 33 | alert: jest.fn() 34 | }; 35 | jest.useFakeTimers(); 36 | }); 37 | 38 | afterAll(() => { 39 | jest.useRealTimers(); 40 | }); 41 | 42 | it('react code should be render', () => { 43 | const html = md.render(getSimpleInput('code')); 44 | expect(html).toBe(getResult('rc', '
hello world!
')); 45 | }); 46 | 47 | it('react code include component which in sandbox should be render', () => { 48 | const html = md.render(getSimpleInput('component')); 49 | expect(html).toBe(getResult('rc', '
hello world!
')); 50 | }); 51 | 52 | describe('react_block', () => { 53 | it('react_block code should be render', () => { 54 | const html = md.render(getSimpleInput('react_block_code')); 55 | expect(html).toBe(getSimpleResult('
hello world!
')); 56 | }); 57 | }); 58 | 59 | describe('react_inline', () => { 60 | it('react_inline code should be render', () => { 61 | const html = md.render(getSimpleInput('react_inline_code')); 62 | expect(html).toBe('

a
\n' + getSimpleResult('

hello world!
') + '

\n'); 63 | }); 64 | 65 | it('break line code', () => { 66 | const html = md.render(getSimpleInput('react_inline_break_line_code')); 67 | expect(html).toBe('

a
\n' + getSimpleResult('

hello world!
') + '

\n'); 68 | }); 69 | 70 | it('error code', () => { 71 | const html = md.render(getSimpleInput('error_react_inline_code')); 72 | expect(errorSpy).toHaveBeenCalled(); 73 | errorSpy.mockRestore(); 74 | expect(html).toBe('

a
\n' + getSimpleResult('') + '

\n'); 75 | }); 76 | }); 77 | 78 | describe('option', () => { 79 | it('allowErrorLog option should work', () => { 80 | md.render(getSimpleInput('error_code')); 81 | expect(logSpy).toHaveBeenCalled(); 82 | logSpy.mockRestore(); 83 | 84 | const md2 = new MarkdownIt({ 85 | breaks: true, 86 | html: true 87 | }).use(SupportReactComponent, { 88 | allowErrorLog: false 89 | }); 90 | md2.render(getSimpleInput('error_code')); 91 | expect(logSpy).not.toHaveBeenCalled(); 92 | logSpy.mockRestore(); 93 | }); 94 | 95 | it('sandbox option should work', () => { 96 | const md = new MarkdownIt().use(SupportReactComponent, { 97 | sandbox: { 98 | Abc: () =>
Abc
, 99 | } 100 | }); 101 | 102 | const html = md.render(getSimpleInput('error_code')); 103 | expect(html).toBe(getResult('rc', '
Abc
')); 104 | }); 105 | }); 106 | 107 | describe('env', () => { 108 | it('mdRefs', () => { 109 | const html = md.render(getSimpleInput('md_refs'), { 110 | mdRefs: { 111 | str1: 'hello', 112 | str2: 'world' 113 | } 114 | }); 115 | expect(html).toBe(getResult('rc', '
hello world!
')); 116 | }); 117 | 118 | // it('document', () => { 119 | // 120 | // }); 121 | }); 122 | 123 | describe('xss', () => { 124 | // it('global object', () => { 125 | // const alert = jest.fn(); 126 | // window.alert = alert; 127 | // const md = new MarkdownIt().use(SupportReactComponent, { 128 | // sandbox: { 129 | // alert 130 | // } 131 | // }); 132 | // md.render(getSimpleInput('xss')); 133 | // expect(alert).toHaveBeenCalled(); 134 | // }); 135 | it('constructor', () => { 136 | md.render(getSimpleInput('xss_constructor')); 137 | expect(warnSpy).toHaveBeenCalledWith('constructor xss'); 138 | expect(warnSpy).toHaveBeenCalledWith('constructor str xss'); 139 | expect(warnSpy).toHaveBeenCalledTimes(5); 140 | }); 141 | 142 | it('str constructor', () => { 143 | md.render(getSimpleInput('xss_constructor_str')); 144 | expect(warnSpy).toHaveBeenCalledWith('constructor str xss'); 145 | }); 146 | 147 | it('this', () => { 148 | md.render(getSimpleInput('xss_this')); 149 | expect(warnSpy).toHaveBeenCalledWith('this escape xss attack'); 150 | }); 151 | 152 | it('dangerouslySetInnerHTML', () => { 153 | md.render(getSimpleInput('xss_dangerouslySetInnerHTML')); 154 | expect(warnSpy).toHaveBeenCalledWith('dangerouslySetInnerHTML xss'); 155 | }); 156 | }); 157 | }); 158 | // getWrapperId 159 | 160 | // 前端 161 | // snapshot 162 | 163 | // dom -------------------------------------------------------------------------------- /tests/spec/code.md: -------------------------------------------------------------------------------- 1 | ```rc 2 | return
hello world!
3 | ``` -------------------------------------------------------------------------------- /tests/spec/component.md: -------------------------------------------------------------------------------- 1 | ```rc 2 | return 3 | ``` -------------------------------------------------------------------------------- /tests/spec/error_code.md: -------------------------------------------------------------------------------- 1 | ```rc 2 | return 3 | ``` -------------------------------------------------------------------------------- /tests/spec/error_react_inline_code.md: -------------------------------------------------------------------------------- 1 | a 2 | -------------------------------------------------------------------------------- /tests/spec/md_refs.md: -------------------------------------------------------------------------------- 1 | ```rc 2 | return
{mdRefs.str1} {mdRefs.str2}!
3 | ``` -------------------------------------------------------------------------------- /tests/spec/react_block_code.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/spec/react_block_code_multi.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/spec/react_inline_break_line_code.md: -------------------------------------------------------------------------------- 1 | a 2 | -------------------------------------------------------------------------------- /tests/spec/react_inline_code.md: -------------------------------------------------------------------------------- 1 | a 2 | -------------------------------------------------------------------------------- /tests/spec/xss.md: -------------------------------------------------------------------------------- 1 | ```rc 2 | alert('xss') 3 | return
hello world!
4 | ``` -------------------------------------------------------------------------------- /tests/spec/xss_constructor.md: -------------------------------------------------------------------------------- 1 | ```rc 2 | const x = [].constructor.constructor('alert(2)')(); 3 | return
hello world!
; 4 | ``` 5 | 6 | ```rc 7 | const x = []['constructor']['constructor']('alert(2)')(); 8 | return
hello world!
; 9 | ``` -------------------------------------------------------------------------------- /tests/spec/xss_constructor_str.md: -------------------------------------------------------------------------------- 1 | ```rc 2 | const x = []['constr'+'uctor']['constr'+'uctor']('alert(2)')() 3 | return
hello world!
; 4 | ``` -------------------------------------------------------------------------------- /tests/spec/xss_dangerouslySetInnerHTML.md: -------------------------------------------------------------------------------- 1 | ```rc 2 | return
alert(2)', 4 | }}>hello world!
; 5 | ``` -------------------------------------------------------------------------------- /tests/spec/xss_this.md: -------------------------------------------------------------------------------- 1 | ```rc 2 | (function(){this['alert']('hi')})() 3 | return
hello world!
; 4 | ``` 5 | 6 | 7 | ```rc 8 | function a(){this['alert']('hi')} 9 | a() 10 | return
hello world!
; 11 | ``` -------------------------------------------------------------------------------- /tests/util.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | export const wrapperId = 'wrapperId'; 4 | export const getResult = (klass = 'rc', html = '') => `
${html}

\n
\n`; 5 | export const getSimpleInput = (filename) => fs.readFileSync(path.resolve(__dirname,`./spec/${filename}.md`),'utf-8'); 6 | export const getSimpleResult = (html = '') => `${html}`; --------------------------------------------------------------------------------