├── .husky ├── .gitignore ├── pre-push ├── pre-commit ├── commit-msg └── prepare-commit-msg ├── .npmrc ├── docs ├── README.md ├── zh │ ├── README.md │ └── example │ │ └── README.md ├── .vuepress │ ├── public │ │ └── favicon.ico │ └── config.js └── example │ └── README.md ├── test ├── .eslintrc.js ├── index.test.js ├── OnlineEdit.test.js ├── __snapshots__ │ ├── OnlineEdit.test.js.snap │ └── DemoAndCode.test.js.snap ├── DemoAndCode.test.js └── utils.test.js ├── .gitignore ├── src ├── client │ ├── enhanceAppFile.js │ ├── icons │ │ ├── CodeSandboxIcon.vue │ │ ├── JsfiddleIcon.vue │ │ └── CodepenIcon.vue │ ├── constants.js │ ├── highlight.js │ ├── utils.js │ ├── OnlineEdit.vue │ └── DemoAndCode.vue └── node │ ├── index.d.ts │ └── index.js ├── commitlint.config.js ├── .editorconfig ├── tsconfig.json ├── .github ├── ISSUE_TEMPLATE │ ├── feature_request_cn.md │ ├── bug_report_cn.md │ ├── feature_request.md │ └── bug_report.md └── workflows │ ├── docs.yml │ ├── release.yml │ └── ci.yml ├── babel.config.js ├── .eslintrc.js ├── jest.config.js ├── LICENSE ├── .all-contributorsrc ├── package.json ├── README-zh_CN.md ├── README.md └── CHANGELOG.md /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | shamefully-hoist=true 2 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | <[include](../README.md) 2 | -------------------------------------------------------------------------------- /docs/zh/README.md: -------------------------------------------------------------------------------- 1 | <[include](../README-zh_CN.md) 2 | -------------------------------------------------------------------------------- /test/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { jest: true }, 3 | } 4 | -------------------------------------------------------------------------------- /.husky/pre-push: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npm test 5 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx --no-install commitlint --edit 5 | -------------------------------------------------------------------------------- /.husky/prepare-commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | exec < /dev/tty && npx --no-install cz --hook || true 5 | -------------------------------------------------------------------------------- /docs/.vuepress/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BuptStEve/vuepress-plugin-demo-code/HEAD/docs/.vuepress/public/favicon.ico -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | *.log 3 | coverage 4 | .DS_Store 5 | node_modules 6 | docs/.vuepress/dist/ 7 | 8 | .cache 9 | .temp 10 | 11 | yarn.lock 12 | -------------------------------------------------------------------------------- /src/client/enhanceAppFile.js: -------------------------------------------------------------------------------- 1 | import DemoAndCode from './DemoAndCode.vue' 2 | 3 | export default ({ Vue }) => { 4 | Vue.component('DemoAndCode', DemoAndCode) 5 | } 6 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // https://www.npmjs.com/package/@commitlint/config-conventional 3 | extends: ['@commitlint/config-conventional'], 4 | rules: {}, 5 | } 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": true, 4 | "jsx": "preserve", 5 | "baseUrl": ".", 6 | "paths": { 7 | "@/*": ["./src/*"] 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /test/index.test.js: -------------------------------------------------------------------------------- 1 | import demoCode from '@/node/' 2 | 3 | describe('vuepress-plugin-demo-code', () => { 4 | it('works well', () => { 5 | const result = demoCode() 6 | 7 | expect(result.name).toBe('vuepress-plugin-demo-code') 8 | }) 9 | 10 | // helpers 11 | }) 12 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request_cn.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 添加 Feature 3 | about: 新特性支持 4 | title: 'feat: your feature...' 5 | labels: enhancement 6 | assignees: BuptStEve 7 | 8 | --- 9 | 10 | **你的功能请求是否与某些问题相关?请描述** 11 | 简洁清晰地描述一下当前有什么问题。如果方便的话,添加一些截图描述你的问题。 12 | 13 | **描述您想要的解决方案** 14 | 简洁清晰地描述一下你想要的特性是怎样的。 15 | 16 | **描述你考虑过的备选方案** 17 | 简洁清晰地描述一下你考虑过的其他备选方案,可能会有什么问题。 18 | 19 | **附加上下文** 20 | 添加一些问题的相关上下文。 21 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | test: { 4 | presets: [ 5 | [ 6 | '@babel/preset-env', 7 | { targets: { node: 'current' } }, 8 | ], 9 | ], 10 | }, 11 | production: { 12 | presets: [ 13 | [ 14 | '@babel/preset-env', 15 | { modules: false }, 16 | ], 17 | ], 18 | plugins: [], 19 | }, 20 | }, 21 | } 22 | -------------------------------------------------------------------------------- /test/OnlineEdit.test.js: -------------------------------------------------------------------------------- 1 | 2 | import { shallowMount } from '@vue/test-utils' 3 | 4 | import OnlineEdit from '@/client/OnlineEdit.vue' 5 | 6 | describe('OnlineEdit', () => { 7 | let wrapper 8 | 9 | beforeEach(() => { 10 | wrapper = shallowMount(OnlineEdit, { 11 | propsData: { 12 | platform: 'codepen', 13 | }, 14 | }) 15 | }) 16 | 17 | it('should be rendered', () => { 18 | expect(wrapper.element).toMatchSnapshot() 19 | }) 20 | }) 21 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parserOptions: { 4 | parser: '@babel/eslint-parser', 5 | sourceType: 'module', 6 | ecmaFeatures: { 7 | legacyDecorators: true, 8 | }, 9 | }, 10 | env: { 11 | es6: true, 12 | node: true, 13 | jest: true, 14 | browser: true, 15 | }, 16 | extends: [ 17 | 'standard', 18 | 'plugin:vue/essential', 19 | ], 20 | plugins: ['vue'], 21 | rules: { 22 | 'promise/param-names': 0, 23 | 'comma-dangle': [2, 'always-multiline'], 24 | }, 25 | } 26 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report_cn.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 报告 Bug 3 | about: 发现了一个 bug! 4 | title: 'bug: your bug...' 5 | labels: bug 6 | assignees: BuptStEve 7 | 8 | --- 9 | 10 | **版本** 11 | Version [e.g. 0.1.0] 12 | 13 | **描述一下 bug** 14 | 简洁清晰地描述一下 bug。如果方便的话,添加一些截图描述你的问题。 15 | 16 | **复现 bug** 17 | 复现的步骤: 18 | 19 | 1. 首先 '...' 20 | 2. 点击了 '...' 21 | 3. 滚动到了 '...' 22 | 4. 看到了错误 23 | 24 | 如果方便的话: 25 | 26 | * 复现 bug 的在线地址: 27 | * codepen: https://codepen.io/ 28 | * jsfiddle: https://jsfiddle.net/ 29 | * codesandbox: https://codesandbox.io/ 30 | * 复现 bug 的仓库地址:https://github.com/new 31 | 32 | **预期行为** 33 | 简洁清晰地描述一下预期行为。 34 | 35 | **附加上下文** 36 | 添加一些问题的相关上下文。 37 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | verbose: false, 3 | testURL: 'http://localhost/', 4 | testEnvironment: 'jsdom', 5 | collectCoverage: true, 6 | collectCoverageFrom: [ 7 | 'src/**', 8 | '!src/icons/**', 9 | '!src/highlight.js', 10 | '!src/enhanceAppFile.js', 11 | ], 12 | coveragePathIgnorePatterns: ['/__snapshots__/'], 13 | transform: { 14 | '^.+\\.vue$': 'vue-jest', 15 | '^.+\\.jsx?$': 'babel-jest', 16 | '.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$': 'jest-transform-stub', 17 | }, 18 | moduleNameMapper: { 19 | '@/(.*)$': '/src/$1', 20 | }, 21 | moduleFileExtensions: ['js', 'vue'], 22 | snapshotSerializers: [ 23 | 'jest-serializer-vue', 24 | ], 25 | } 26 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: 'feat: your feature...' 5 | labels: enhancement 6 | assignees: BuptStEve 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /src/client/icons/CodeSandboxIcon.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /test/__snapshots__/OnlineEdit.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`OnlineEdit should be rendered 1`] = ` 4 |
10 | \\\\n\\\\n\\\\n\\\\n\\",\\"layout\\":\\"left\\",\\"editors\\":\\"101\\",\\"js_external\\":\\"https://unpkg.com/vue@%5E2.6.14\\",\\"css_external\\":\\"\\",\\"js_pre_processor\\":\\"babel\\"}" 14 | /> 15 | 16 | 17 | 18 | 19 | 20 | 26 |
27 | `; 28 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | build-and-deploy: 10 | concurrency: ci-${{ github.ref }} # Recommended if you intend to make multiple deployments in quick succession. 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | 15 | - name: Install pnpm 16 | uses: pnpm/action-setup@v2.2.1 17 | 18 | - name: Set node 19 | uses: actions/setup-node@v2 20 | with: 21 | node-version: 16.x 22 | cache: pnpm 23 | 24 | - name: Setup 25 | run: | 26 | pnpm i -g @antfu/ni 27 | nci 28 | 29 | - name: build docs 30 | run: nr docs:build 31 | 32 | - name: Deploy 🚀 33 | uses: JamesIves/github-pages-deploy-action@v4.3.0 34 | with: 35 | branch: gh-pages 36 | folder: docs/.vuepress/dist 37 | -------------------------------------------------------------------------------- /test/DemoAndCode.test.js: -------------------------------------------------------------------------------- 1 | 2 | import { shallowMount } from '@vue/test-utils' 3 | 4 | import DemoAndCode from '@/client/DemoAndCode.vue' 5 | 6 | describe('DemoAndCode', () => { 7 | let wrapper 8 | 9 | beforeEach(() => { 10 | const div = document.createElement('div') 11 | div.id = 'root' 12 | document.body.appendChild(div) 13 | 14 | wrapper = shallowMount(DemoAndCode, { 15 | attachTo: '#root', 16 | }) 17 | }) 18 | 19 | it('should be rendered', () => { 20 | expect(wrapper.element).toMatchSnapshot() 21 | }) 22 | 23 | it('should validate minHeight', () => { 24 | const validateFn = wrapper.vm.$options.props.minHeight.validator 25 | 26 | expect(validateFn(-1)).toBeFalsy() 27 | 28 | expect(validateFn(0)).toBeTruthy() 29 | expect(validateFn(1)).toBeTruthy() 30 | expect(validateFn(9.8)).toBeTruthy() 31 | expect(validateFn(99.0)).toBeTruthy() 32 | expect(validateFn(100.0)).toBeTruthy() 33 | }) 34 | }) 35 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: 'bug: your bug...' 5 | labels: bug 6 | assignees: BuptStEve 7 | 8 | --- 9 | 10 | **Version** 11 | Version [e.g. 0.1.0] 12 | 13 | **Describe the bug** 14 | A clear and concise description of what the bug is. If applicable, add screenshots to help explain your problem. 15 | 16 | **To Reproduce** 17 | Steps to reproduce the behavior: 18 | 1. Go to '...' 19 | 2. Click on '....' 20 | 3. Scroll down to '....' 21 | 4. See error 22 | 23 | If it's convenient: 24 | 25 | * Add an online address to reproduce: 26 | * codepen: https://codepen.io/ 27 | * jsfiddle: https://jsfiddle.net/ 28 | * codesandbox: https://codesandbox.io/ 29 | * Add a repository to reproduce:https://github.com/new 30 | 31 | **Expected behavior** 32 | A clear and concise description of what you expected to happen. 33 | 34 | **Additional context** 35 | Add any other context about the problem here. 36 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | jobs: 9 | release: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | 14 | - name: Install pnpm 15 | uses: pnpm/action-setup@v2.2.1 16 | 17 | - name: Set node version to v16 18 | uses: actions/setup-node@v2 19 | with: 20 | node-version: 16.x 21 | registry-url: https://registry.npmjs.org/ 22 | cache: pnpm 23 | 24 | - name: Setup 25 | run: pnpm i -g conventional-github-releaser 26 | 27 | - run: conventional-github-releaser -p angular 28 | env: 29 | CONVENTIONAL_GITHUB_RELEASER_TOKEN: ${{secrets.GITHUB_TOKEN}} 30 | 31 | - name: Install Dependencies 32 | run: pnpm install 33 | 34 | - name: Publish to NPM 35 | run: pnpm publish --access public --no-git-checks 36 | env: 37 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} 38 | -------------------------------------------------------------------------------- /src/client/constants.js: -------------------------------------------------------------------------------- 1 | const JS_RE = / 21 | 22 | 27 | ::: 28 | ``` 29 | 30 | ### 实现效果 31 | 32 | ::: demo 33 | 36 | 37 | 44 | 45 | 50 | ::: 51 | 52 | ## 代码语言 53 | 你可能注意到了展示代码右上角显示了 `vue`(默认),这里的语言可配置。 54 | 55 | ### 使用示例 56 | 57 | ```md 58 | ::: demo html 59 |

60 | this is common html 61 |

62 | 63 | 68 | ::: 69 | ``` 70 | 71 | ### 实现效果 72 | 73 | ::: demo html 74 |

75 | this is common html 76 |

77 | 78 | 83 | ::: 84 | -------------------------------------------------------------------------------- /src/client/highlight.js: -------------------------------------------------------------------------------- 1 | if (typeof window !== 'undefined') { 2 | // prevent highlighting automatically 3 | window.Prism = { manual: true } 4 | } 5 | 6 | const prism = require('prismjs') 7 | const escapeHtml = require('escape-html') 8 | const loadLanguages = require('prismjs/components/index') 9 | 10 | function wrap (code, lang) { 11 | if (lang === 'text') { 12 | code = escapeHtml(code) 13 | } 14 | return `
${code}
` 15 | } 16 | 17 | function getLangCodeFromExtension (extension) { 18 | const extensionMap = { 19 | vue: 'markup', 20 | html: 'markup', 21 | md: 'markdown', 22 | rb: 'ruby', 23 | ts: 'typescript', 24 | py: 'python', 25 | sh: 'bash', 26 | yml: 'yaml', 27 | styl: 'stylus', 28 | kt: 'kotlin', 29 | rs: 'rust', 30 | } 31 | 32 | return extensionMap[extension] || extension 33 | } 34 | 35 | module.exports = (str, lang) => { 36 | if (!lang) { 37 | return wrap(str, 'text') 38 | } 39 | lang = lang.toLowerCase() 40 | const rawLang = lang 41 | 42 | lang = getLangCodeFromExtension(lang) 43 | 44 | if (!prism.languages[lang]) { 45 | try { 46 | loadLanguages([lang]) 47 | } catch (e) { 48 | console.warn(`[vuepress] Syntax highlight for language "${lang}" is not supported.`) 49 | } 50 | } 51 | if (prism.languages[lang]) { 52 | const code = prism.highlight(str, prism.languages[lang], lang) 53 | return wrap(code, rawLang) 54 | } 55 | return wrap(str, 'text') 56 | } 57 | -------------------------------------------------------------------------------- /docs/example/README.md: -------------------------------------------------------------------------------- 1 | # Example 2 | 3 | ## Common Usage 4 | 5 | Just wrapper your demo code between `::: demo` and `:::`. Then you will get runnable demo and code. 6 | 7 | ### Usage Example 8 | 9 | ```md 10 | ::: demo 11 | 14 | 15 | 22 | 23 | 28 | ::: 29 | ``` 30 | 31 | ### Result 32 | 33 | ::: demo 34 | 37 | 38 | 45 | 46 | 51 | ::: 52 | 53 | ## Code Language 54 | You may notice that the top right corner of the display code shows `vue`(default), it's configurable. 55 | 56 | ### Usage Example 57 | 58 | ```md 59 | ::: demo html 60 |

61 | this is common html 62 |

63 | 64 | 69 | ::: 70 | ``` 71 | 72 | ### Result 73 | 74 | ::: demo html 75 |

76 | this is common html 77 |

78 | 79 | 84 | ::: 85 | -------------------------------------------------------------------------------- /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "projectName": "vuepress-plugin-demo-code", 3 | "projectOwner": "BuptStEve", 4 | "repoType": "github", 5 | "repoHost": "https://github.com", 6 | "files": [ 7 | "README.md", 8 | "README-zh_CN.md" 9 | ], 10 | "imageSize": 100, 11 | "commit": true, 12 | "commitConvention": "angular", 13 | "contributors": [ 14 | { 15 | "login": "BuptStEve", 16 | "name": "StEve Young", 17 | "avatar_url": "https://avatars2.githubusercontent.com/u/11501493?v=4", 18 | "profile": "https://buptsteve.github.io", 19 | "contributions": [ 20 | "code", 21 | "doc", 22 | "infra", 23 | "translation" 24 | ] 25 | }, 26 | { 27 | "login": "leandrofngl", 28 | "name": "leandrofngl", 29 | "avatar_url": "https://avatars1.githubusercontent.com/u/11283636?v=4", 30 | "profile": "https://github.com/leandrofngl", 31 | "contributions": [ 32 | "bug" 33 | ] 34 | }, 35 | { 36 | "login": "tianjianchn", 37 | "name": "Tian Jian", 38 | "avatar_url": "https://avatars0.githubusercontent.com/u/7612199?v=4", 39 | "profile": "https://github.com/tianjianchn", 40 | "contributions": [ 41 | "bug" 42 | ] 43 | }, 44 | { 45 | "login": "mrspence", 46 | "name": "Spence", 47 | "avatar_url": "https://avatars.githubusercontent.com/u/30320791?v=4", 48 | "profile": "https://barr.media", 49 | "contributions": [ 50 | "code" 51 | ] 52 | } 53 | ], 54 | "contributorsPerLine": 7, 55 | "skipCi": true 56 | } 57 | -------------------------------------------------------------------------------- /src/node/index.d.ts: -------------------------------------------------------------------------------- 1 | export interface DemoCodePluginOptions { 2 | /** Js libraries for the demo. */ 3 | jsLibs?: string[], 4 | /** Css libraries for the demo. */ 5 | cssLibs?: string[], 6 | /** 7 | * The semantic version string of vue. For more information on semantic versioning syntax, see the [npm semver calculator](https://semver.npmjs.com/). 8 | */ 9 | vueVersion?: string, 10 | /** The display text of unfold code button. */ 11 | showText?: string, 12 | /** The display text of fold code button. */ 13 | hideText?: string, 14 | /** The height of the code when it is folded. */ 15 | minHeight?: number, 16 | /** Display online edit buttons. */ 17 | onlineBtns?: { 18 | codepen?: boolean, 19 | jsfiddle?: boolean, 20 | codesandbox?: boolean, 21 | }, 22 | /** 23 | * It passes [jsfiddle options](https://docs.jsfiddle.net/api/display-a-fiddle-from-post). 24 | */ 25 | jsfiddle?: Record, 26 | /** 27 | * It passes [CodeSandbox options](https://codesandbox.io/docs/importing#define-api). 28 | * > `deps` is dependencies 29 | */ 30 | codesandbox?: { 31 | deps?: Record 32 | json?: string, 33 | query?: string, 34 | embed?: string, 35 | }, 36 | /** The mark of the plugin, follows the tag after `:::`. */ 37 | demoCodeMark?: string, 38 | /** 39 | * It passes [vuepress-plugin-code-copy](https://github.com/znicholasbrown/vuepress-plugin-code-copy#options)'s options, or `false` to disable it. 40 | */ 41 | copyOptions?: false | { 42 | align?: string, 43 | selector?: string, 44 | }, 45 | } 46 | -------------------------------------------------------------------------------- /src/client/icons/JsfiddleIcon.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /src/client/utils.js: -------------------------------------------------------------------------------- 1 | const { getParameters } = require('codesandbox-import-utils/lib/api/define') 2 | const { 3 | CODE_SANDBOX_JS, 4 | CODE_SANDBOX_HTML, 5 | } = require('./constants') 6 | 7 | const getJsTmpl = (js) => { 8 | const vueAttrs = js 9 | .replace(/export\s+default\s*?\{\n*/, '') 10 | .replace(/\n*\}\s*$/, '') 11 | .trim() 12 | 13 | return `new Vue({\n\tel: '#app', \n\t${vueAttrs}\n})` 14 | } 15 | 16 | /* istanbul ignore next */ 17 | const getHtmlTmpl = html => `
\n\n${html}\n\n
` 18 | 19 | const getMatchedResult = (re) => (str) => { 20 | const matched = str.match(re) 21 | 22 | return matched && matched[1].trim() 23 | } 24 | 25 | /* istanbul ignore next */ 26 | const urlToHtmlTag = type => url => type === 'js' 27 | ? `\n` 28 | : type === 'css' 29 | ? `\n` 30 | : 'Error type: js | css' 31 | 32 | /* istanbul ignore next */ 33 | const getCodeSandboxTmpl = ({ 34 | js, 35 | css, 36 | html, 37 | deps, 38 | jsLibs, 39 | cssLibs, 40 | vueVersion, 41 | }) => getParameters({ 42 | files: { 43 | 'index.js': { isBinary: false, content: CODE_SANDBOX_JS }, 44 | 'App.vue': { 45 | isBinary: false, 46 | content: 47 | `\n\n` + 48 | `\n\n` + 49 | `\n`, 50 | }, 51 | 'index.html': { 52 | isBinary: false, 53 | content: 54 | cssLibs.map(urlToHtmlTag('css')) + 55 | jsLibs.map(urlToHtmlTag('js')) + 56 | CODE_SANDBOX_HTML, 57 | }, 58 | 'package.json': { 59 | isBinary: false, 60 | content: JSON.stringify({ 61 | dependencies: Object.assign({ vue: vueVersion }, deps), 62 | devDependencies: { 63 | '@vue/cli-service': '^4.1.1', 64 | }, 65 | }), 66 | }, 67 | }, 68 | }) 69 | 70 | const parseAndDecode = str => JSON.parse(decodeURIComponent(str)) 71 | const encodeAndStringify = obj => encodeURIComponent(JSON.stringify(obj)) 72 | 73 | module.exports = { 74 | getJsTmpl, 75 | getHtmlTmpl, 76 | parseAndDecode, 77 | getMatchedResult, 78 | encodeAndStringify, 79 | getCodeSandboxTmpl, 80 | } 81 | -------------------------------------------------------------------------------- /test/__snapshots__/DemoAndCode.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`DemoAndCode should be rendered 1`] = ` 4 |
7 | 8 |
12 | 24 | 25 |
28 | 43 | 58 | 73 |
74 |
75 | 76 |
80 |
83 |
87 |         
88 |       
89 |
90 |
91 |
92 | `; 93 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vuepress-plugin-demo-code", 3 | "version": "1.0.0", 4 | "description": "📝 Demo and code plugin for vuepress", 5 | "main": "src/node/index.js", 6 | "types": "src/node/index.d.ts", 7 | "files": [ 8 | "src" 9 | ], 10 | "scripts": { 11 | "prepare": "husky install", 12 | "cov": "open coverage/lcov-report/index.html", 13 | "docs": "vuepress dev docs", 14 | "docs:build": "vuepress build docs", 15 | "lint": "eslint --fix . docs/.vuepress/ --ext .js,.vue --ignore-path .gitignore", 16 | "test": "cross-env NODE_ENV=test jest", 17 | "test:tdd": "cross-env NODE_ENV=test jest --watch", 18 | "release": "HUSKY=0 standard-version", 19 | "pub:n": "HUSKY=0 bumpp --commit 'chore: v%s' --push && npm publish --tag next" 20 | }, 21 | "lint-staged": { 22 | "{src,test}/**/*.{js,vue}": [ 23 | "eslint --fix" 24 | ] 25 | }, 26 | "config": { 27 | "commitizen": { 28 | "path": "./node_modules/cz-conventional-changelog" 29 | } 30 | }, 31 | "dependencies": { 32 | "codesandbox-import-utils": "^2.2.3", 33 | "markdown-it-container": "^3.0.0", 34 | "prismjs": "^1.28.0", 35 | "vuepress-plugin-code-copy": "^1.0.6" 36 | }, 37 | "devDependencies": { 38 | "@babel/core": "^7.17.9", 39 | "@babel/eslint-parser": "^7.17.0", 40 | "@babel/preset-env": "^7.16.11", 41 | "@commitlint/cli": "^12.1.4", 42 | "@commitlint/config-conventional": "^12.1.4", 43 | "@vue/test-utils": "^1.3.0", 44 | "all-contributors-cli": "^6.20.0", 45 | "babel-core": "^7.0.0-bridge.0", 46 | "bumpp": "^7.1.1", 47 | "codecov": "^3.8.3", 48 | "cross-env": "^7.0.3", 49 | "cz-conventional-changelog": "^3.3.0", 50 | "eslint": "^7.32.0", 51 | "eslint-config-standard": "^16.0.3", 52 | "eslint-config-vue": "^2.0.2", 53 | "eslint-plugin-import": "^2.26.0", 54 | "eslint-plugin-node": "^11.1.0", 55 | "eslint-plugin-promise": "^5.2.0", 56 | "eslint-plugin-standard": "^5.0.0", 57 | "eslint-plugin-vue": "^7.20.0", 58 | "gh-pages": "^3.2.3", 59 | "husky": "^6.0.0", 60 | "jest": "^27.5.1", 61 | "jest-serializer-vue": "^2.0.2", 62 | "jest-transform-stub": "^2.0.0", 63 | "lint-staged": "^11.2.6", 64 | "markdown-it-include": "^2.0.0", 65 | "rimraf": "^3.0.2", 66 | "standard-version": "^9.3.2", 67 | "vue-jest": "^3.0.7", 68 | "vuepress": "^1.9.7", 69 | "vuepress-plugin-smooth-scroll": "^0.0.10" 70 | }, 71 | "keywords": [ 72 | "vue", 73 | "code", 74 | "demo", 75 | "vuepress", 76 | "demo-code", 77 | "documentation" 78 | ], 79 | "repository": { 80 | "type": "git", 81 | "url": "git+https://github.com/BuptStEve/vuepress-plugin-demo-code.git" 82 | }, 83 | "homepage": "https://buptsteve.github.io/vuepress-plugin-demo-code/", 84 | "author": "StEve YoUng", 85 | "license": "MIT", 86 | "packageManager": "pnpm@^6" 87 | } 88 | -------------------------------------------------------------------------------- /docs/.vuepress/config.js: -------------------------------------------------------------------------------- 1 | const { defineConfig } = require('vuepress/config') 2 | 3 | const demoCode = require('../../src/node') 4 | const { name, description } = require('../../package.json') 5 | 6 | /** 7 | * @type {import('../../src/node').DemoCodePluginOptions} 8 | */ 9 | const demoCodeOptions = { 10 | cssLibs: [ 11 | 'https://unpkg.com/animate.css@3.7.0/animate.min.css', 12 | ], 13 | showText: 'show more', 14 | hideText: 'hide', 15 | } 16 | 17 | module.exports = defineConfig({ 18 | base: `/${name}/`, 19 | locales: { 20 | '/': { lang: 'en-US', title: 'demo-code', description }, 21 | '/zh/': { 22 | lang: 'zh-CN', 23 | title: 'demo-code', 24 | description: '📝 同时展示 demo 和 code 的 vuepress 插件', 25 | }, 26 | }, 27 | head: [ 28 | ['link', { rel: 'icon', href: '/favicon.ico' }], 29 | ['link', { rel: 'stylesheet', href: 'https://unpkg.com/animate.css@3.7.0/animate.min.css' }], 30 | ], 31 | plugins: [ 32 | ['smooth-scroll'], 33 | // @ts-ignore 34 | [demoCode, demoCodeOptions], 35 | ], 36 | markdown: { 37 | extendMarkdown: (md) => { 38 | md.use(require('markdown-it-include'), { 39 | root: './docs/', 40 | includeRe: /<\[include\](.+)/i, 41 | bracesAreOptional: true, 42 | }) 43 | }, 44 | }, 45 | evergreen: true, 46 | serviceWorker: true, 47 | themeConfig: { 48 | repo: 'BuptStEve/' + name, 49 | docsDir: 'docs', 50 | sidebarDepth: 2, 51 | editLinks: true, 52 | locales: { 53 | '/': { 54 | selectText: '🌍Languages', 55 | label: 'English', 56 | editLinkText: 'Edit this page on GitHub', 57 | nav: [ 58 | { text: '🌱Guide', link: '/' }, 59 | { text: '😎Example', link: '/example/' }, 60 | { 61 | text: '🔥Ecosystem', 62 | items: [ 63 | { text: '📖markdown-it-vuese', link: 'https://buptsteve.github.io/markdown-it-vuese/' }, 64 | ], 65 | }, 66 | ], 67 | sidebar: { 68 | '/example/': [{ 69 | title: '😎Example', 70 | collapsable: false, 71 | children: [''], 72 | }], 73 | '/': [['', '🌱Guide']], 74 | }, 75 | }, 76 | '/zh/': { 77 | selectText: '🌍选择语言', 78 | label: '简体中文', 79 | editLinkText: '在 GitHub 上编辑此页', 80 | nav: [ 81 | { text: '🌱指南', link: '/zh/' }, 82 | { text: '😎示例', link: '/zh/example/' }, 83 | { 84 | text: '🔥生态系统', 85 | items: [ 86 | { text: '📖markdown-it-vuese', link: 'https://buptsteve.github.io/markdown-it-vuese/' }, 87 | ], 88 | }, 89 | ], 90 | sidebar: { 91 | '/zh/example/': [{ 92 | title: '😎示例', 93 | collapsable: false, 94 | children: [''], 95 | }], 96 | '/zh/': [['', '🌱指南']], 97 | }, 98 | }, 99 | }, 100 | }, 101 | }) 102 | -------------------------------------------------------------------------------- /src/node/index.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const markdownItContainer = require('markdown-it-container') 3 | 4 | const encodeAndStringify = obj => encodeURIComponent(JSON.stringify(obj)) 5 | 6 | const defaults = { 7 | onlineBtns: { 8 | codepen: true, 9 | jsfiddle: true, 10 | codesandbox: true, 11 | }, 12 | 13 | // https://docs.jsfiddle.net/api/display-a-fiddle-from-post 14 | jsfiddle: { 15 | framework: 'library/pure', 16 | }, 17 | 18 | // https://codesandbox.io/docs/importing#define-api 19 | codesandbox: { 20 | deps: {}, // dependencies 21 | json: '', 22 | query: 'module=App.vue', 23 | embed: '', 24 | }, 25 | } 26 | 27 | module.exports = (options = {}) => { 28 | const { 29 | demoCodeMark = 'demo', 30 | copyOptions = { 31 | align: 'top', 32 | selector: '.demo-and-code-wrapper div[class*="language-"] pre', 33 | }, 34 | } = options 35 | const END_TYPE = `container_${demoCodeMark}_close` 36 | 37 | return { 38 | name: 'vuepress-plugin-demo-code', 39 | plugins: [ 40 | ['code-copy', copyOptions], 41 | ], 42 | enhanceAppFiles: [ 43 | path.resolve(__dirname, '../client/enhanceAppFile.js'), 44 | ], 45 | extendMarkdown: (md) => { 46 | md.use(markdownItContainer, demoCodeMark, { render }) 47 | }, 48 | } 49 | 50 | function render (tokens, idx) { 51 | const { nesting, info } = tokens[idx] 52 | 53 | if (nesting === -1) { 54 | return '\n' 55 | } 56 | 57 | let rawHtmlStr = '' 58 | let lastLine = 0 59 | const language = (info.split(demoCodeMark)[1] || 'vue').trim() 60 | 61 | for (let index = idx; index < tokens.length; index++) { 62 | const { map, type, content } = tokens[index] 63 | 64 | if (type === END_TYPE) break 65 | 66 | // add empty lines 67 | if (map) { 68 | const delta = map[0] - (lastLine || map[1]) 69 | 70 | if (delta > 0) { 71 | rawHtmlStr += '\n'.repeat(delta) 72 | } 73 | 74 | lastLine = map[1] 75 | } 76 | 77 | rawHtmlStr += content 78 | } 79 | 80 | const { 81 | jsLibs = [], 82 | cssLibs = [], 83 | showText = 'show code', 84 | hideText = 'hide code', 85 | minHeight, 86 | vueVersion = '^2.6.14', 87 | } = options 88 | 89 | const jsfiddle = Object.assign({}, defaults.jsfiddle, options.jsfiddle) 90 | const onlineBtns = Object.assign({}, defaults.onlineBtns, options.onlineBtns) 91 | const codesandbox = Object.assign({}, defaults.codesandbox, options.codesandbox) 92 | 93 | const htmlStr = encodeURIComponent(rawHtmlStr) 94 | const jsLibsStr = encodeAndStringify(jsLibs) 95 | const cssLibsStr = encodeAndStringify(cssLibs) 96 | const jsfiddleStr = encodeAndStringify(jsfiddle) 97 | const onlineBtnsStr = encodeAndStringify(onlineBtns) 98 | const codesandboxStr = encodeAndStringify(codesandbox) 99 | 100 | return ` 101 | 114 |