├── .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(`\/(${p1})?>\\s*$`);
12 | }]
13 | // [ /^/, true ],
14 | // [ new RegExp('^?(' + block_names.join('|') + ')(?=(\\s|/?>|$))', '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 |
122 | );
123 | ```
124 | ````
125 |
126 | ### Output
127 |
128 | ``` rc
129 | const Progress = Antd.Progress
130 | return (
131 |
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 | cccnote
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 | cccnote
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 |
461 | );
462 | ```
463 | ````
464 |
465 | ### Outpit
466 | ```rc
467 | const { Divider } = Antd
468 | return (
469 |
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 | 3note
72 | 4
73 |
74 |
75 | ```
76 |
77 | 1
78 | 2
79 | 3note
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 = '') => `\n`;
5 | export const getSimpleInput = (filename) => fs.readFileSync(path.resolve(__dirname,`./spec/${filename}.md`),'utf-8');
6 | export const getSimpleResult = (html = '') => `${html}`;
--------------------------------------------------------------------------------