├── .babelrc.js ├── .editorconfig ├── .gitattributes ├── .github └── FUNDING.yml ├── .gitignore ├── .prettierrc ├── LICENSE ├── README.md ├── build ├── generate-required-languages.js ├── poi.lib.config.js ├── poi.website.config.js └── vue-compile.config.js ├── circle.yml ├── now.json ├── package-lock.json ├── package.json ├── postcss.config.js ├── release.config.js ├── src ├── PluginAPI.js ├── components │ ├── Badge.vue │ ├── DocuteSelect.vue │ ├── EditLink.vue │ ├── Gist.vue │ ├── Header.vue │ ├── HeaderNav.vue │ ├── ImageZoom.vue │ ├── InjectedComponents.js │ ├── Loading.vue │ ├── Note.vue │ ├── PageToc.vue │ ├── PrevNextLinks.vue │ ├── Root.vue │ ├── Sidebar.vue │ ├── SidebarItem.vue │ ├── SidebarMask.vue │ ├── SidebarToggle.vue │ ├── UniLink.vue │ └── icons │ │ └── ExternalLinkIcon.vue ├── css │ ├── global.css │ ├── injected-components.css │ ├── main.css │ ├── page-content.css │ └── prism.css ├── event.js ├── hooks.js ├── index.js ├── plugins │ ├── banner-footer │ │ └── index.js │ ├── dark-theme-toggler │ │ ├── DarkThemeToggler.vue │ │ └── index.js │ ├── evaluateContent │ │ └── index.js │ ├── i18n │ │ ├── LanguageSelector.vue │ │ └── index.js │ ├── search │ │ ├── SearchBar.vue │ │ └── index.js │ └── versions │ │ ├── VersionsSelector.vue │ │ └── index.js ├── router.js ├── store.js ├── utils │ ├── __test__ │ │ └── index.test.js │ ├── alternativeComponents.js │ ├── constants.js │ ├── cssVariables.js │ ├── highlight.js │ ├── index.js │ ├── load.js │ ├── marked.js │ ├── markedRenderer.js │ ├── parseCodeOptions.js │ ├── prismLanguages.json │ └── removeMarkdownExtension.js └── views │ └── Home.vue └── website ├── components └── ColorBox.vue ├── docs ├── README.md ├── builtin-components.md ├── credits.md ├── guide │ ├── customization.md │ ├── deployment.md │ ├── internationalization.md │ ├── markdown-features.md │ ├── offline-support.md │ ├── plugin.md │ ├── use-vue-in-markdown.md │ └── use-with-bundlers.md ├── options.md ├── plugin-api.md ├── sw.js └── zh │ ├── README.md │ ├── builtin-components.md │ ├── credits.md │ ├── guide │ ├── customization.md │ ├── deployment.md │ ├── internationalization.md │ ├── markdown-features.md │ ├── offline-support.md │ ├── plugin.md │ ├── use-vue-in-markdown.md │ └── use-with-bundlers.md │ ├── options.md │ └── plugin-api.md └── index.js /.babelrc.js: -------------------------------------------------------------------------------- 1 | const IS_TEST = process.env.NODE_ENV === 'test' 2 | 3 | module.exports = { 4 | presets: [ 5 | ['minimal', { 6 | mode: 'loose' 7 | }], 8 | IS_TEST && 'power-assert' 9 | ].filter(Boolean) 10 | } 11 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: egoist # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | open_collective: # Replace with a single Open Collective username 5 | ko_fi: support_egoist 6 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 7 | custom: # Replace with a single custom sponsorship URL 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | /lib 4 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "useTabs": false 5 | } 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) egoist <0x142857@gmail.com> (https://github.com/egoist) 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## ATTENTION: this project is no longer actively maintained (I still push some code once in a while), if you want to see improvements on this project, please consider [sponsoring me](https://github.com/sponsors/egoist). 2 | 3 | 4 | # Docute 5 | 6 | [![npm version](https://badgen.net/npm/v/docute)](https://npm.im/docute) [![jsdelivr downloads](https://data.jsdelivr.com/v1/package/npm/docute/badge?style=rounded)](https://www.jsdelivr.com/package/npm/docute) [![circleci](https://badgen.net/circleci/github/egoist/docute/master)](https://circleci.com/gh/egoist/docute/tree/master) [![donate](https://badgen.net/badge/support%20me/donate/ff69b4)](https://patreon.com/egoist) 7 | 8 | Effortless documentation, done right. 9 | 10 | ## Features 11 | 12 | - No build process, website is generated on the fly. 13 | - A simple yet elegant UI that is dedicated to documentation. 14 | - Leveraging the power of Markdown and Vue. 15 | - Extensible plugin system, plenty of official and community plugins. 16 | 17 | ## Documentation 18 | 19 | - **v4 (latest) docs**: https://docute.egoist.dev 20 | - v3 (legacy) docs: https://docute3.egoist.dev 21 | 22 | ## Resources 23 | 24 | - [Official Plugins](https://github.com/egoist/docute-plugins) 25 | - [Awesome Docute](https://github.com/egoist/awesome-docute) 26 | 27 | ## Contributing 28 | 29 | 1. Fork it! 30 | 2. Create your feature branch: `git checkout -b my-new-feature` 31 | 3. Commit your changes: `git commit -am 'Add some feature'` 32 | 4. Push to the branch: `git push origin my-new-feature` 33 | 5. Submit a pull request :D 34 | 35 | ## Author 36 | 37 | **Docute** © [EGOIST](https://github.com/egoist), Released under the [MIT](./LICENSE) License.
38 | Authored and maintained by EGOIST with help from contributors ([list](https://github.com/egoist/docute/contributors)). 39 | 40 | > [Website](https://egoist.dev) · GitHub [@EGOIST](https://github.com/egoist) · Twitter [@localhost_5173](https://twitter.com/localhost_5173) 41 | -------------------------------------------------------------------------------- /build/generate-required-languages.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | 3 | let {languages} = require('prismjs/components') 4 | 5 | languages = Object.keys(languages).reduce((res, name) => { 6 | if (languages[name].require) { 7 | res[name] = languages[name].require 8 | } 9 | return res 10 | }, {}) 11 | languages.builtin = Object.keys(require('prismjs').languages).filter(lang => { 12 | return !['extend', 'insertBefore', 'DFS'].includes(lang) 13 | }) 14 | 15 | fs.writeFileSync( 16 | './src/utils/prismLanguages.json', 17 | JSON.stringify(languages, null, 2), 18 | 'utf8' 19 | ) 20 | -------------------------------------------------------------------------------- /build/poi.lib.config.js: -------------------------------------------------------------------------------- 1 | const pkg = require('../package') 2 | 3 | module.exports = { 4 | entry: 'src/index.js', 5 | output: { 6 | format: 'umd', 7 | moduleName: 'Docute', 8 | fileNames: { 9 | js: 'docute.js', 10 | css: 'docute.css' 11 | }, 12 | clean: false 13 | }, 14 | constants: { 15 | __DOCUTE_VERSION__: JSON.stringify(pkg.version), 16 | __PRISM_VERSION__: JSON.stringify(require('prismjs/package').version) 17 | }, 18 | chainWebpack(config) { 19 | config.resolve.alias.set('vue$', 'vue/dist/vue.esm.js') 20 | config.output.libraryExport('default') 21 | config.output.globalObject('this') 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /build/poi.website.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const pkg = require('../package') 3 | 4 | module.exports = { 5 | entry: 'website/index.js', 6 | output: { 7 | dir: 'website/dist', 8 | html: { 9 | title: 'Docute' 10 | } 11 | }, 12 | publicFolder: path.join(__dirname, '../website/docs'), 13 | chainWebpack(config) { 14 | config.resolve.alias.set('vue$', 'vue/dist/vue.esm.js') 15 | }, 16 | constants: { 17 | __DOCUTE_VERSION__: JSON.stringify(pkg.version), 18 | __PRISM_VERSION__: JSON.stringify(require('prismjs/package').version), 19 | __DEPS__: JSON.stringify(Object.keys(pkg.dependencies)) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /build/vue-compile.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | outDir: 'lib', 3 | include: '**', 4 | constants: { 5 | __DOCUTE_VERSION__: JSON.stringify(require('../package').version), 6 | __PRISM_VERSION__: JSON.stringify(require('prismjs/package').version) 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build: 4 | docker: 5 | - image: circleci/node:latest-browsers 6 | branches: 7 | ignore: 8 | - gh-pages # list of branches to ignore 9 | - /release\/.*/ # or ignore regexes 10 | steps: 11 | - checkout 12 | - restore_cache: 13 | key: dependency-cache-{{ checksum "package-lock.json" }} 14 | - run: 15 | name: install dependences 16 | command: npm ci 17 | - save_cache: 18 | key: dependency-cache-{{ checksum "package-lock.json" }} 19 | paths: 20 | - ./node_modules 21 | - run: 22 | name: test 23 | command: npm test 24 | - run: 25 | name: Release 26 | command: npx semantic-release 27 | -------------------------------------------------------------------------------- /now.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "builds": [ 4 | { 5 | "src": "package.json", 6 | "use": "@now/static-build", 7 | "config": { 8 | "distDir": "website/dist" 9 | } 10 | } 11 | ], 12 | "routes": [ 13 | { 14 | "src": "^/sw.js", 15 | "headers": { "cache-control": "s-maxage=0" }, 16 | "dest": "/sw.js" 17 | }, 18 | { 19 | "src": "^/(.+)\\.(.+)", 20 | "headers": { "cache-control": "s-maxage=0, public, max-age=0, must-revalidate" }, 21 | "dest": "/$1.$2" 22 | }, 23 | { 24 | "src": "^/(.*)", 25 | "dest": "/index.html" 26 | } 27 | ], 28 | "github": { 29 | "silent": true 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "docute", 3 | "version": "4.11.0", 4 | "scripts": { 5 | "build:umd": "poi --config build/poi.lib.config.js --prod", 6 | "build:es": "vue-compile src --config build/vue-compile.config.js", 7 | "build": "npm run build:umd && npm run build:es", 8 | "test": "npm run lint && npm run test:unit", 9 | "lint": "xo", 10 | "website": "poi --serve --config build/poi.website.config.js", 11 | "build:website": "poi --prod --config build/poi.website.config.js", 12 | "now-build": "npm run build:website", 13 | "prepublishOnly": "npm run build", 14 | "commit": "git-cz", 15 | "test:unit": "poi puppet src/**/*.test.js --test --plugin @poi/puppet --framework mocha" 16 | }, 17 | "dependencies": { 18 | "jump.js": "^1.0.2", 19 | "loadjs": "^3.5.4", 20 | "marked": "^0.7.0", 21 | "medium-zoom": "^1.0.2", 22 | "prismjs": "^1.15.0", 23 | "throttle-debounce": "^2.1.0", 24 | "vue": "^2.6.10", 25 | "vue-content-loader": "^0.2.1", 26 | "vue-router": "^3.0.1", 27 | "vue-router-prefetch": "^1.1.1", 28 | "vue-template-compiler": "^2.6.10", 29 | "vuex": "^3.0.1", 30 | "vuex-router-sync": "^5.0.0" 31 | }, 32 | "devDependencies": { 33 | "@babel/plugin-syntax-object-rest-spread": "^7.0.0", 34 | "@poi/plugin-puppet": "^0.1.4", 35 | "@semantic-release/git": "^7.0.5", 36 | "babel-preset-minimal": "^0.1.1", 37 | "babel-preset-power-assert": "^3.0.0", 38 | "bili": "^4.8.1", 39 | "commitizen": "^3.0.5", 40 | "cz-conventional-changelog": "^2.1.0", 41 | "docute-google-analytics": "^1.1.0", 42 | "eslint-config-rem": "^3.0.0", 43 | "eslint-plugin-vue": "^5.0.0-beta.3", 44 | "fast-async": "^6.3.8", 45 | "html-template-tag": "^2.0.0", 46 | "husky": "^1.0.1", 47 | "lint-staged": "^7.3.0", 48 | "poi": "^12.4.8", 49 | "postcss-import": "^12.0.0", 50 | "postcss-preset-env": "^6.0.3", 51 | "power-assert": "^1.6.1", 52 | "rollup-plugin-vue": "^4.3.2", 53 | "semantic-release": "^15.12.0", 54 | "vue-compile": "^0.3.1", 55 | "webpack-node-externals": "^1.7.2", 56 | "xo": "^0.23.0" 57 | }, 58 | "repository": { 59 | "url": "egoist/docute", 60 | "type": "git" 61 | }, 62 | "main": "dist/docute.js", 63 | "module": "lib/index.js", 64 | "files": [ 65 | "dist", 66 | "lib", 67 | "!**/__test__/**" 68 | ], 69 | "description": "Effortlessly documentation done right.", 70 | "author": "egoist <0x142857@gmail.com>", 71 | "license": "MIT", 72 | "xo": { 73 | "extends": [ 74 | "rem", 75 | "plugin:vue/essential" 76 | ], 77 | "prettier": true, 78 | "ignores": [ 79 | "**/website/**", 80 | "**/dist/**" 81 | ], 82 | "envs": [ 83 | "browser", 84 | "mocha" 85 | ], 86 | "globals": [ 87 | "__DOCUTE_VERSION__" 88 | ], 89 | "extensions": [ 90 | "vue" 91 | ], 92 | "rules": { 93 | "unicorn/filename-case": "off", 94 | "unicorn/no-abusive-eslint-disable": "off", 95 | "require-atomic-updates": "off" 96 | } 97 | }, 98 | "husky": { 99 | "hooks": { 100 | "pre-commit": "lint-staged" 101 | } 102 | }, 103 | "lint-staged": { 104 | "*.{js,css,md,vue}": [ 105 | "xo --fix", 106 | "git add" 107 | ] 108 | }, 109 | "config": { 110 | "commitizen": { 111 | "path": "./node_modules/cz-conventional-changelog" 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | module.exports = { 4 | plugins: [ 5 | require('postcss-import')({ 6 | path: [path.join(__dirname, 'src/css')] 7 | }), 8 | require('postcss-preset-env')({ 9 | stage: 0 10 | }) 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /release.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | branch: 'master', 3 | plugins: [ 4 | [ 5 | '@semantic-release/commit-analyzer', 6 | { 7 | preset: 'angular', 8 | releaseRules: [{type: 'feat', scope: 'ui', release: 'patch'}] 9 | } 10 | ], 11 | '@semantic-release/release-notes-generator', 12 | '@semantic-release/npm', 13 | '@semantic-release/github' 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /src/PluginAPI.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import InjectedComponents from './components/InjectedComponents' 3 | import hooks from './hooks' 4 | 5 | export default class PluginAPI { 6 | constructor({plugins, store, router}) { 7 | this.plugins = plugins 8 | this.store = store 9 | this.router = router 10 | this.components = {} 11 | this.hooks = hooks 12 | this.search = {} 13 | 14 | Vue.component(InjectedComponents.name, InjectedComponents) 15 | } 16 | 17 | hasPlugin(name) { 18 | return this.plugins.filter(plugin => plugin.name === name).length > 0 19 | } 20 | 21 | registerComponent(position, component, props) { 22 | this.components[position] = this.components[position] || [] 23 | this.components[position].push({component, props}) 24 | return this 25 | } 26 | 27 | getComponents(position) { 28 | return this.components[position] || [] 29 | } 30 | 31 | processMarkdown(fn) { 32 | this.hooks.add('processMarkdown', fn) 33 | return this 34 | } 35 | 36 | processHTML(fn) { 37 | this.hooks.add('processHTML', fn) 38 | return this 39 | } 40 | 41 | extendMarkedRenderer(fn) { 42 | this.hooks.add('extendMarkedRenderer', fn) 43 | return this 44 | } 45 | 46 | onContentUpdated(fn) { 47 | this.hooks.add('onContentUpdated', fn) 48 | return this 49 | } 50 | 51 | extendMarkdownComponent(fn) { 52 | this.hooks.add('extendMarkdownComponent', fn) 53 | return this 54 | } 55 | 56 | enableSearch(search = {}) { 57 | this.search = search 58 | this.search.enabled = true 59 | return this 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/components/Badge.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 23 | 24 | 54 | -------------------------------------------------------------------------------- /src/components/DocuteSelect.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 36 | 37 | 97 | -------------------------------------------------------------------------------- /src/components/EditLink.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 38 | 39 | 55 | -------------------------------------------------------------------------------- /src/components/Gist.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 37 | 38 | 43 | -------------------------------------------------------------------------------- /src/components/Header.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 73 | 74 | 144 | -------------------------------------------------------------------------------- /src/components/HeaderNav.vue: -------------------------------------------------------------------------------- 1 | 35 | 36 | 57 | 58 | 197 | -------------------------------------------------------------------------------- /src/components/ImageZoom.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 47 | 48 | 60 | -------------------------------------------------------------------------------- /src/components/InjectedComponents.js: -------------------------------------------------------------------------------- 1 | export default { 2 | name: 'InjectedComponents', 3 | 4 | functional: true, 5 | 6 | props: { 7 | position: { 8 | type: String, 9 | required: true 10 | } 11 | }, 12 | 13 | render(h, {props, parent}) { 14 | const components = parent.$pluginApi.getComponents(props.position) 15 | 16 | if (components.length === 0) return 17 | 18 | return h( 19 | 'div', 20 | { 21 | class: 'InjectedComponents', 22 | attrs: { 23 | 'data-position': props.position 24 | } 25 | }, 26 | components.map(({component, props}) => h(component, {props})) 27 | ) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/components/Loading.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 13 | 14 | 65 | -------------------------------------------------------------------------------- /src/components/Note.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 25 | 26 | 81 | -------------------------------------------------------------------------------- /src/components/PageToc.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 33 | 34 | 60 | -------------------------------------------------------------------------------- /src/components/PrevNextLinks.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 56 | 57 | 77 | -------------------------------------------------------------------------------- /src/components/Root.vue: -------------------------------------------------------------------------------- 1 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /src/components/Sidebar.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 84 | 85 | 120 | -------------------------------------------------------------------------------- /src/components/SidebarItem.vue: -------------------------------------------------------------------------------- 1 | 55 | 56 | 110 | 111 | 214 | -------------------------------------------------------------------------------- /src/components/SidebarMask.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 19 | -------------------------------------------------------------------------------- /src/components/SidebarToggle.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 31 | 32 | 53 | -------------------------------------------------------------------------------- /src/components/UniLink.vue: -------------------------------------------------------------------------------- 1 | 46 | 47 | 52 | -------------------------------------------------------------------------------- /src/components/icons/ExternalLinkIcon.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 28 | -------------------------------------------------------------------------------- /src/css/global.css: -------------------------------------------------------------------------------- 1 | @import "main.css"; 2 | @import "injected-components.css"; 3 | -------------------------------------------------------------------------------- /src/css/injected-components.css: -------------------------------------------------------------------------------- 1 | .InjectedComponents { 2 | &[data-position="sidebar:start"], 3 | &[data-position="mobile-sidebar:start"] { 4 | margin-bottom: 25px; 5 | 6 | &>*:first-child { 7 | margin-top: 0; 8 | } 9 | } 10 | 11 | &[data-position="sidebar:end"], 12 | &[data-position="sidebar:post-end"] { 13 | margin-top: 25px; 14 | 15 | &>*:first-child { 16 | margin-top: 0; 17 | } 18 | } 19 | 20 | &[data-position="content:start"] { 21 | margin-bottom: 35px; 22 | 23 | &>*:first-child { 24 | margin-top: 0; 25 | } 26 | 27 | &>*:last-child { 28 | margin-bottom: 0; 29 | } 30 | } 31 | 32 | &[data-position="content:end"] { 33 | margin-top: 30px; 34 | } 35 | 36 | &[data-position="header-right:start"] + .header-nav { 37 | margin-left: 30px; 38 | } 39 | } 40 | 41 | @media (min-width: 768px) { 42 | .InjectedComponents { 43 | &[data-position="mobile-sidebar:start"], 44 | &[data-position="mobile-sidebar:end"] { 45 | display: none; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/css/main.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | color: var(--text-color); 4 | background: var(--page-background); 5 | text-rendering: optimizeLegibility; 6 | -webkit-font-smoothing: antialiased; 7 | font: 16px/1.7 -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; 8 | } 9 | 10 | * { 11 | box-sizing: border-box; 12 | } 13 | 14 | a { 15 | color: var(--link-color); 16 | text-decoration: none; 17 | } 18 | 19 | .Content a { 20 | &:hover { 21 | text-decoration: underline; 22 | } 23 | } 24 | 25 | .external-link-icon { 26 | color: #aaa; 27 | display: inline-block; 28 | } 29 | 30 | .medium-zoom-overlay, .medium-zoom-image--opened { 31 | z-index: 99; 32 | } 33 | 34 | .Wrap { 35 | max-width: 1180px; 36 | } 37 | 38 | .layout-wide .Wrap { 39 | max-width: 100%; 40 | } 41 | 42 | .layout-narrow .Wrap { 43 | margin: 0 auto; 44 | } 45 | 46 | .docute-banner { 47 | margin-bottom: 10px; 48 | 49 | &>*:first-child { 50 | margin-top: 0; 51 | } 52 | 53 | &>*:last-child { 54 | margin-bottom: 0; 55 | } 56 | } 57 | 58 | .docute-footer { 59 | padding-top: 60px; 60 | } 61 | -------------------------------------------------------------------------------- /src/css/page-content.css: -------------------------------------------------------------------------------- 1 | .page-content { 2 | & > *:first-child { 3 | margin-top: 0; 4 | } 5 | 6 | &.has-page-title > h2:first-child { 7 | margin-top: 7rem; 8 | } 9 | 10 | & h1, 11 | & h2, 12 | & h3, 13 | & h4, 14 | & h5, 15 | & h6 { 16 | font-weight: 300; 17 | line-height: 1.2; 18 | } 19 | 20 | & h1 { 21 | font-size: 3rem; 22 | margin-bottom: 1.4rem; 23 | } 24 | 25 | & h2 { 26 | font-size: 2rem; 27 | border-bottom: 1px solid var(--border-color); 28 | margin-top: 7rem; 29 | padding-bottom: 5px; 30 | } 31 | 32 | & h3 { 33 | font-size: 1.7rem; 34 | margin: 40px 0 30px 0; 35 | } 36 | 37 | & h4 { 38 | font-size: 1.4rem; 39 | } 40 | 41 | & h5 { 42 | font-size: 1.1rem; 43 | } 44 | 45 | & p { 46 | margin: 15px 0; 47 | } 48 | 49 | & table { 50 | width: 100%; 51 | border-spacing: 0; 52 | border-collapse: separate; 53 | } 54 | 55 | & table th, 56 | & table td { 57 | padding: 12px 10px; 58 | border-bottom: 1px solid var(--border-color); 59 | text-align: left; 60 | } 61 | 62 | & thead th { 63 | color: var(--table-header-color); 64 | background: var(--table-header-background); 65 | border-bottom: 1px solid var(--border-color); 66 | border-top: 1px solid var(--border-color); 67 | font-weight: 400; 68 | font-size: 12px; 69 | padding: 10px; 70 | 71 | &:first-child { 72 | border-left: 1px solid var(--border-color); 73 | border-radius: 4px 0px 0px 4px; 74 | } 75 | 76 | &:last-child { 77 | border-right: 1px solid var(--border-color); 78 | border-radius: 0 4px 4px 0; 79 | } 80 | } 81 | 82 | & .pre-wrapper { 83 | margin: 2rem 0; 84 | position: relative; 85 | border-radius: 4px; 86 | background: var(--code-block-background); 87 | box-shadow: inset 0 0 0 var(--code-block-shadow-width) var(--code-block-shadow-color); 88 | 89 | &:before { 90 | content: attr(data-lang); 91 | position: absolute; 92 | top: 5px; 93 | right: 10px; 94 | font-size: 12px; 95 | color: #cacaca; 96 | } 97 | 98 | & code { 99 | color: var(--code-block-text-color); 100 | } 101 | } 102 | 103 | & pre, 104 | & .code-mask { 105 | overflow: auto; 106 | position: relative; 107 | margin: 0; 108 | z-index: 2; 109 | font-family: var(--code-font); 110 | white-space: pre; 111 | 112 | & code { 113 | box-shadow: none; 114 | margin: 0; 115 | padding: 0; 116 | border: none; 117 | font-size: 1em; 118 | background: transparent; 119 | } 120 | 121 | @media print { 122 | white-space: pre-wrap; 123 | word-break: break-word; 124 | } 125 | } 126 | 127 | & pre { 128 | padding: 20px; 129 | } 130 | 131 | & .code-mask { 132 | position: absolute; 133 | top: 0; 134 | left: 0; 135 | right: 0; 136 | z-index: 1; 137 | padding-top: 20px; 138 | border: none; 139 | color: transparent; 140 | } 141 | 142 | & .code-line { 143 | display: block; 144 | padding: 0 20px; 145 | &.highlighted { 146 | background: var(--highlighted-line-background); 147 | position: relative; 148 | &:before { 149 | content: ''; 150 | display: block; 151 | width: 3px; 152 | top: 0; 153 | left: 0; 154 | bottom: 0; 155 | background: var(--highlighted-line-border-color); 156 | position: absolute; 157 | } 158 | } 159 | } 160 | 161 | & code { 162 | font-family: var(--code-font); 163 | font-size: 90%; 164 | background: var(--inline-code-background); 165 | border-radius: 4px; 166 | padding: 3px 5px; 167 | color: var(--inline-code-color); 168 | } 169 | 170 | & > ul, 171 | & > ol { 172 | padding-left: 20px; 173 | margin: 1rem 0; 174 | } 175 | 176 | & .contains-task-list { 177 | list-style: none; 178 | padding-left: 0; 179 | } 180 | 181 | & img { 182 | max-width: 100%; 183 | } 184 | 185 | & blockquote { 186 | background: #f1f1f1; 187 | border-left: 8px solid #ccc; 188 | margin: 20px 0; 189 | padding: 14px 16px; 190 | color: #6a737d; 191 | 192 | & p { 193 | margin: 15px 0 0 0; 194 | } 195 | 196 | & > *:first-child { 197 | margin-top: 0; 198 | } 199 | } 200 | 201 | & hr { 202 | height: 1px; 203 | padding: 0; 204 | margin: 3rem 0; 205 | background-color: #e1e4e8; 206 | border: 0; 207 | } 208 | 209 | & .header-anchor { 210 | float: left; 211 | line-height: 1; 212 | margin-left: -20px; 213 | padding-right: 4px; 214 | opacity: 0; 215 | border-bottom: none; 216 | 217 | &:hover { 218 | opacity: 1; 219 | border-bottom: none; 220 | } 221 | 222 | & .anchor-icon { 223 | vertical-align: middle; 224 | fill: currentColor; 225 | } 226 | } 227 | 228 | & .markdown-header:focus, 229 | & .markdown-header:hover { 230 | outline: none; 231 | & .header-anchor { 232 | opacity: 1; 233 | } 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /src/css/prism.css: -------------------------------------------------------------------------------- 1 | 2 | /* https://github.com/SaraVieira/prism-theme-night-owl */ 3 | 4 | .token.comment, 5 | .token.prolog, 6 | .token.cdata { 7 | color: rgb(99, 119, 119); 8 | font-style: italic; 9 | } 10 | 11 | .token.punctuation { 12 | color: rgb(199, 146, 234); 13 | } 14 | 15 | .namespace { 16 | color: rgb(178, 204, 214); 17 | } 18 | 19 | .token.deleted { 20 | color: rgba(239, 83, 80, 0.56); 21 | font-style: italic; 22 | } 23 | 24 | .token.symbol, 25 | .token.property { 26 | color: rgb(128, 203, 196); 27 | } 28 | 29 | .token.tag, 30 | .token.operator, 31 | .token.keyword { 32 | color: rgb(127, 219, 202); 33 | } 34 | 35 | .token.boolean { 36 | color: rgb(255, 88, 116); 37 | } 38 | 39 | .token.number { 40 | color: rgb(247, 140, 108); 41 | } 42 | 43 | .token.constant, 44 | .token.function, 45 | .token.builtin, 46 | .token.char { 47 | color: rgb(130, 170, 255); 48 | } 49 | 50 | .token.selector, 51 | .token.doctype { 52 | color: rgb(199, 146, 234); 53 | font-style: italic; 54 | } 55 | 56 | .token.attr-name, 57 | .token.inserted { 58 | color: rgb(173, 219, 103); 59 | font-style: italic; 60 | } 61 | 62 | .token.string, 63 | .token.url, 64 | .token.entity, 65 | .language-css .token.string, 66 | .style .token.string { 67 | color: rgb(173, 219, 103); 68 | } 69 | 70 | .token.class-name, 71 | .token.atrule, 72 | .token.attr-value { 73 | color: rgb(255, 203, 139); 74 | } 75 | 76 | .token.regex, 77 | .token.important, 78 | .token.variable { 79 | color: rgb(214, 222, 235); 80 | } 81 | 82 | .token.important, 83 | .token.bold { 84 | font-weight: bold; 85 | } 86 | 87 | .token.italic { 88 | font-style: italic; 89 | } 90 | -------------------------------------------------------------------------------- /src/event.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | export default new Vue() 4 | -------------------------------------------------------------------------------- /src/hooks.js: -------------------------------------------------------------------------------- 1 | class Hooks { 2 | constructor() { 3 | this.hooks = {} 4 | } 5 | 6 | add(name, fn) { 7 | this.hooks[name] = this.hooks[name] || [] 8 | this.hooks[name].push(fn) 9 | return this 10 | } 11 | 12 | invoke(name, ...args) { 13 | const hooks = this.hooks[name] || [] 14 | for (const fn of hooks) { 15 | fn(...args) 16 | } 17 | return this 18 | } 19 | 20 | process(name, arg) { 21 | const hooks = this.hooks[name] || [] 22 | for (const fn of hooks) { 23 | arg = fn(arg) || arg 24 | } 25 | return arg 26 | } 27 | 28 | async processPromise(name, arg) { 29 | const hooks = this.hooks[name] || [] 30 | for (const fn of hooks) { 31 | // eslint-disable-next-line no-await-in-loop 32 | arg = (await fn(arg)) || arg 33 | } 34 | return arg 35 | } 36 | } 37 | 38 | export default new Hooks() 39 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import {sync} from 'vuex-router-sync' 3 | import PluginAPI from './PluginAPI' 4 | import Root from './components/Root.vue' 5 | import store from './store' 6 | import createRouter from './router' 7 | import {inBrowser} from './utils' 8 | import alternativeComponents from './utils/alternativeComponents' 9 | import ImageZoom from './components/ImageZoom.vue' 10 | import Badge from './components/Badge.vue' 11 | import DocuteSelect from './components/DocuteSelect.vue' 12 | import Note from './components/Note.vue' 13 | import Gist from './components/Gist.vue' 14 | import Loading from './components/Loading.vue' 15 | import ExternalLinkIcon from './components/icons/ExternalLinkIcon.vue' 16 | import {INITIAL_STATE_NAME} from './utils/constants' 17 | 18 | // Built-in plugins 19 | import i18nPlugin from './plugins/i18n' 20 | import evaluateContentPlugin from './plugins/evaluateContent' 21 | import versionsPlugin from './plugins/versions' 22 | import bannerFooter from './plugins/banner-footer' 23 | import darkThemeToggler from './plugins/dark-theme-toggler' 24 | import searchPlugin from './plugins/search' 25 | 26 | Vue.component(ImageZoom.name, ImageZoom) 27 | Vue.component(Badge.name, Badge) 28 | Vue.component(DocuteSelect.name, DocuteSelect) 29 | Vue.component(Note.name, Note) 30 | Vue.component(ExternalLinkIcon.name, ExternalLinkIcon) 31 | Vue.component(Gist.name, Gist) 32 | Vue.component(Loading.name, Loading) 33 | Vue.use(alternativeComponents) 34 | 35 | Vue.mixin({ 36 | created() { 37 | const pluginApi = this.$options.pluginApi || this.$root.$pluginApi 38 | if (pluginApi) { 39 | this.$pluginApi = pluginApi 40 | } 41 | } 42 | }) 43 | 44 | class Docute { 45 | constructor(config = {}) { 46 | const router = createRouter(config.router) 47 | sync(store, router) 48 | 49 | this.router = router 50 | this.store = store 51 | 52 | store.commit('SET_CONFIG', { 53 | title: inBrowser && document.title, 54 | ...config 55 | }) 56 | 57 | const plugins = [ 58 | i18nPlugin, 59 | evaluateContentPlugin, 60 | versionsPlugin, 61 | bannerFooter, 62 | darkThemeToggler, 63 | searchPlugin, 64 | ...(store.state.originalConfig.plugins || []) 65 | ] 66 | this.pluginApi = new PluginAPI({plugins, store, router}) 67 | this.applyPlugins() 68 | 69 | this.app = new Vue({ 70 | router, 71 | store, 72 | pluginApi: this.pluginApi, 73 | render: h => h(Root) 74 | }) 75 | 76 | if (config.mount !== false) { 77 | this.mount() 78 | } 79 | } 80 | 81 | mount() { 82 | const {target} = store.getters 83 | // Force hydration when there's initial state 84 | if (window[INITIAL_STATE_NAME]) { 85 | this.app.$mount(`#${target}`, true) 86 | } else { 87 | this.app.$mount(`#${target}`) 88 | } 89 | return this 90 | } 91 | 92 | /** 93 | * @private 94 | */ 95 | applyPlugins() { 96 | for (const plugin of this.pluginApi.plugins) { 97 | plugin.extend(this.pluginApi) 98 | } 99 | } 100 | } 101 | 102 | Docute.version = __DOCUTE_VERSION__ 103 | 104 | export default Docute 105 | 106 | if (typeof window !== 'undefined') { 107 | window.Vue = Vue 108 | // eslint-disable-next-line 109 | window['__DOCUTE_VERSION__'] = __DOCUTE_VERSION__ 110 | } 111 | -------------------------------------------------------------------------------- /src/plugins/banner-footer/index.js: -------------------------------------------------------------------------------- 1 | const getComponent = (str, className) => 2 | typeof str === 'string' 3 | ? {template: `
${str}
`} 4 | : str 5 | 6 | export default { 7 | name: 'banner-footer', 8 | extend(api) { 9 | const {banner, footer} = api.store.getters.config 10 | if (banner) { 11 | api.registerComponent( 12 | 'content:start', 13 | getComponent(banner, 'docute-banner') 14 | ) 15 | } 16 | if (footer) { 17 | api.registerComponent( 18 | 'content:end', 19 | getComponent(footer, 'docute-footer') 20 | ) 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/plugins/dark-theme-toggler/DarkThemeToggler.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 69 | 70 | 170 | -------------------------------------------------------------------------------- /src/plugins/dark-theme-toggler/index.js: -------------------------------------------------------------------------------- 1 | import DarkThemeToggler from './DarkThemeToggler.vue' 2 | 3 | export default { 4 | name: 'dark-theme-toggler', 5 | extend: api => { 6 | const position = api.store.getters.config.darkThemeToggler 7 | if (position === true) { 8 | api.registerComponent('sidebar:post-end', DarkThemeToggler) 9 | } else if (position === 'sidebar') { 10 | api.registerComponent('header-right:start', DarkThemeToggler) 11 | api.registerComponent('mobile-sidebar:start', DarkThemeToggler) 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/plugins/evaluateContent/index.js: -------------------------------------------------------------------------------- 1 | export default { 2 | name: 'hoistTags', 3 | extend(api) { 4 | api.extendMarkedRenderer(renderer => { 5 | const hoistedTagsRe = /^<(script|style)(?=(\s|>|$))/i 6 | renderer.html = html => { 7 | html = html.trim() 8 | if (hoistedTagsRe.test(html)) { 9 | return html 10 | .replace(/^<(script|style)/, '$/, '') 12 | } 13 | return html 14 | } 15 | }) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/plugins/i18n/LanguageSelector.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 43 | 44 | 50 | -------------------------------------------------------------------------------- /src/plugins/i18n/index.js: -------------------------------------------------------------------------------- 1 | import LanguageSelector from './LanguageSelector.vue' 2 | 3 | export default { 4 | name: 'i18n', 5 | extend: api => { 6 | if (api.store.getters.languageOverrides) { 7 | api.registerComponent('sidebar:start', LanguageSelector) 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/plugins/search/SearchBar.vue: -------------------------------------------------------------------------------- 1 | 40 | 41 | 93 | 94 | 208 | -------------------------------------------------------------------------------- /src/plugins/search/index.js: -------------------------------------------------------------------------------- 1 | import SearchBar from './SearchBar.vue' 2 | 3 | export default { 4 | name: 'search', 5 | extend(api) { 6 | api.registerComponent('header-right:start', SearchBar) 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/plugins/versions/VersionsSelector.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 49 | 50 | 56 | -------------------------------------------------------------------------------- /src/plugins/versions/index.js: -------------------------------------------------------------------------------- 1 | import VersionsSelector from './VersionsSelector.vue' 2 | 3 | export default { 4 | name: 'versions', 5 | extend(api) { 6 | if (api.store.getters.config.versions) { 7 | api.registerComponent('sidebar:start', VersionsSelector) 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/router.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | import RouterPrefetch from 'vue-router-prefetch' 4 | import Home from './views/Home.vue' 5 | 6 | Vue.use(Router) 7 | Vue.use(RouterPrefetch) 8 | 9 | export default routerConfig => 10 | new Router({ 11 | scrollBehavior(to, from, savedPosition) { 12 | if (savedPosition) { 13 | return savedPosition 14 | } 15 | return {x: 0, y: 0} 16 | }, 17 | ...routerConfig, 18 | routes: [ 19 | { 20 | path: '*', 21 | component: Home 22 | } 23 | ] 24 | }) 25 | -------------------------------------------------------------------------------- /src/store.js: -------------------------------------------------------------------------------- 1 | /* globals __PRISM_VERSION__ */ 2 | import Vue from 'vue' 3 | import Vuex from 'vuex' 4 | import marked from './utils/marked' 5 | import highlight from './utils/highlight' 6 | import {getFilenameByPath, getFileUrl, isExternalLink, inBrowser} from './utils' 7 | import markedRenderer from './utils/markedRenderer' 8 | import hooks from './hooks' 9 | import load from './utils/load' 10 | import prismLanguages from './utils/prismLanguages' 11 | import {defaultCssVariables, darkCssVariables} from './utils/cssVariables' 12 | import {INITIAL_STATE_NAME} from './utils/constants' 13 | 14 | Vue.use(Vuex) 15 | 16 | const initialState = inBrowser && window[INITIAL_STATE_NAME] 17 | 18 | const getDefaultTheme = (store, {theme, detectSystemDarkTheme}) => { 19 | if (!inBrowser || !detectSystemDarkTheme) { 20 | return theme || 'default' 21 | } 22 | 23 | const mq = window.matchMedia('(prefers-color-scheme: dark)') 24 | 25 | mq.addListener(() => { 26 | store.commit('SET_THEME', mq.matches ? 'dark' : 'default') 27 | }) 28 | 29 | return theme || (mq.matches ? 'dark' : 'default') 30 | } 31 | 32 | const store = new Vuex.Store({ 33 | state: { 34 | originalConfig: {}, 35 | page: { 36 | title: null, 37 | headings: null, 38 | html: '' 39 | }, 40 | env: {}, 41 | showSidebar: false, 42 | fetchingFile: true, 43 | ...initialState 44 | }, 45 | 46 | mutations: { 47 | SET_CONFIG(state, config = {}) { 48 | config.layout = config.layout || 'narrow' 49 | // TODO: remove `centerContent` in next major version 50 | if (config.centerContent) { 51 | config.layout = 'narrow' 52 | } 53 | config.theme = getDefaultTheme(store, config) 54 | state.originalConfig = config 55 | }, 56 | 57 | SET_PAGE(state, page) { 58 | state.page = page 59 | }, 60 | 61 | TOGGLE_SIDEBAR(state, show) { 62 | state.showSidebar = typeof show === 'boolean' ? show : !state.showSidebar 63 | }, 64 | 65 | SET_FETCHING(state, fetching) { 66 | state.fetchingFile = fetching 67 | }, 68 | 69 | SET_ENV(state, env) { 70 | state.env = env 71 | }, 72 | 73 | SET_THEME(state, theme) { 74 | state.originalConfig.theme = theme 75 | } 76 | }, 77 | 78 | actions: { 79 | async fetchFile({commit, getters, dispatch}, path) { 80 | commit('TOGGLE_SIDEBAR', false) 81 | commit('SET_FETCHING', true) 82 | 83 | let page = { 84 | markdown: true, 85 | ...(getters.config.routes && getters.config.routes[path]) 86 | } 87 | 88 | if (!page.content && !page.file) { 89 | const filename = getFilenameByPath(path) 90 | page.file = getFileUrl(getters.config.sourcePath, filename) 91 | page.editLink = 92 | getters.config.editLinkBase && 93 | getFileUrl(getters.config.editLinkBase, filename) 94 | } 95 | 96 | await Promise.all([ 97 | !page.content && 98 | fetch(page.file, getters.config.fetchOptions) 99 | .then(res => res.text()) 100 | .then(res => { 101 | page.content = res 102 | }), 103 | dispatch('fetchPrismLanguages') 104 | ]) 105 | 106 | page.content = await hooks.processPromise('processMarkdown', page.content) 107 | page = await hooks.processPromise('processPage', page) 108 | 109 | const env = { 110 | headings: [], 111 | mixins: [], 112 | config: getters.config 113 | } 114 | if (page.markdown) { 115 | page.content = marked(page.content, { 116 | renderer: markedRenderer(hooks), 117 | highlight, 118 | env 119 | }) 120 | } 121 | page.content = await hooks.processPromise('processHTML', page.content) 122 | page.headings = env.headings 123 | if (!page.title) { 124 | page.title = env.title 125 | } 126 | commit('SET_PAGE', page) 127 | commit('SET_ENV', env) 128 | commit('SET_FETCHING', false) 129 | }, 130 | 131 | fetchPrismLanguages({getters}) { 132 | const langs = getters.config.highlight 133 | 134 | if (!langs || langs.length === 0) { 135 | return Promise.resolve() 136 | } 137 | 138 | return load( 139 | langs 140 | .reduce((res, lang) => { 141 | if (prismLanguages[lang]) { 142 | res = res.concat(prismLanguages[lang]) 143 | } 144 | res.push(lang) 145 | return res 146 | }, []) 147 | .filter((lang, i, arr) => { 148 | // Dedupe 149 | return ( 150 | arr.indexOf(lang) === i && 151 | prismLanguages.builtin.indexOf(lang) === -1 152 | ) 153 | }) 154 | .map(lang => { 155 | return `https://unpkg.com/prismjs@${__PRISM_VERSION__}/components/prism-${lang}.js` 156 | }), 157 | 'prism-languages' 158 | ) 159 | } 160 | }, 161 | 162 | getters: { 163 | target({originalConfig: {target}}) { 164 | if (!target) return 'docute' 165 | if (target[0] === '#') return target.slice(1) 166 | return target 167 | }, 168 | 169 | languageOverrides({originalConfig}) { 170 | // `locales` is for legacy support 171 | const overrides = originalConfig.overrides || originalConfig.locales 172 | return ( 173 | overrides && 174 | Object.keys(overrides).reduce((res, path) => { 175 | if (overrides[path].language) { 176 | res[path] = overrides[path] 177 | } 178 | return res 179 | }, {}) 180 | ) 181 | }, 182 | 183 | currentLocalePath({route}, {languageOverrides}) { 184 | if (languageOverrides) { 185 | // Is it a locale? 186 | for (const localePath of Object.keys(languageOverrides)) { 187 | if (localePath !== '/') { 188 | const RE = new RegExp(`^${localePath}`) 189 | if (RE.test(route.path)) { 190 | return localePath 191 | } 192 | } 193 | } 194 | } 195 | 196 | return '/' 197 | }, 198 | 199 | config({originalConfig}, {currentLocalePath, languageOverrides}) { 200 | return languageOverrides 201 | ? { 202 | ...originalConfig, 203 | ...languageOverrides[currentLocalePath] 204 | } 205 | : originalConfig 206 | }, 207 | 208 | homePaths(_, {languageOverrides}) { 209 | const localePaths = languageOverrides 210 | ? Object.keys(languageOverrides) 211 | : [] 212 | return [...localePaths, '/'] 213 | }, 214 | 215 | sidebarLinks(_, {sidebar}) { 216 | return sidebar 217 | ? sidebar 218 | .reduce((res, next) => { 219 | // backward compabillity 220 | const children = next.children || next.links || [] 221 | return [...res, ...children] 222 | }, []) 223 | .filter(item => { 224 | return !isExternalLink(item.link) 225 | }) 226 | : [] 227 | }, 228 | 229 | sidebar(_, {config}) { 230 | const sidebar = config.sidebar || [] 231 | return typeof sidebar === 'function' ? sidebar(store) : sidebar 232 | }, 233 | 234 | cssVariables(_, {config}) { 235 | return { 236 | ...(config.theme === 'dark' ? darkCssVariables : defaultCssVariables), 237 | ...(typeof config.cssVariables === 'function' 238 | ? config.cssVariables(config.theme) 239 | : config.cssVariables) 240 | } 241 | } 242 | } 243 | }) 244 | 245 | if (process.env.NODE_ENV === 'development' && inBrowser) { 246 | window.store = store 247 | } 248 | 249 | export default store 250 | -------------------------------------------------------------------------------- /src/utils/__test__/index.test.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert' 2 | import {getFilenameByPath, getFileUrl} from '..' 3 | 4 | describe('utils/index', () => { 5 | it('getFilenameByPath', () => { 6 | assert(getFilenameByPath('/') === '/README.md') 7 | assert(getFilenameByPath('/foo') === '/foo.md') 8 | assert(getFilenameByPath('foo') === '/foo.md') 9 | assert(getFilenameByPath('/foo/bar/') === '/foo/bar/README.md') 10 | }) 11 | 12 | it('getFileUrl', () => { 13 | assert(getFileUrl(null, getFilenameByPath('/')) === 'README.md') 14 | assert(getFileUrl('/', getFilenameByPath('/')) === '/README.md') 15 | assert(getFileUrl('./', getFilenameByPath('/')) === 'README.md') 16 | assert(getFileUrl('./docs/', getFilenameByPath('/')) === 'docs/README.md') 17 | assert(getFileUrl('/docs/', getFilenameByPath('/')) === '/docs/README.md') 18 | assert(getFileUrl('/docs/', getFilenameByPath('/foo')) === '/docs/foo.md') 19 | assert( 20 | getFileUrl('/docs/', getFilenameByPath('/foo.md')) === '/docs/foo.md' 21 | ) 22 | assert( 23 | getFileUrl('/docs/', getFilenameByPath('/foo/')) === '/docs/foo/README.md' 24 | ) 25 | }) 26 | }) 27 | -------------------------------------------------------------------------------- /src/utils/alternativeComponents.js: -------------------------------------------------------------------------------- 1 | const getComponent = tag => { 2 | return { 3 | functional: true, 4 | render(h, ctx) { 5 | return h(tag, ctx.data, ctx.children) 6 | } 7 | } 8 | } 9 | 10 | export default Vue => { 11 | Vue.component('v-style', getComponent('style')) 12 | Vue.component('v-script', getComponent('script')) 13 | } 14 | -------------------------------------------------------------------------------- /src/utils/constants.js: -------------------------------------------------------------------------------- 1 | export const INITIAL_STATE_NAME = '__DOCUTE_INITIAL_STATE__' 2 | -------------------------------------------------------------------------------- /src/utils/cssVariables.js: -------------------------------------------------------------------------------- 1 | const defaultCssVariables = { 2 | accentColor: '#009688', 3 | pageBackground: '#fff', 4 | headerBackground: '#fff', 5 | headerTextColor: 'var(--text-color)', 6 | textColor: '#000', 7 | linkColor: 'var(--accent-color)', 8 | sidebarWidth: '280px', 9 | sidebarBackground: 'var(--page-background)', 10 | sidebarLinkColor: '#444', 11 | sidebarLinkActiveColor: '#000', 12 | sidebarLinkArrowColor: '#999', 13 | mainBackground: 'var(--page-background)', 14 | borderColor: '#eaeaea', 15 | headerHeight: '55px', 16 | codeFont: 'SFMono-Regular,Consolas,Liberation Mono,Menlo,Courier,monospace', 17 | tipColor: 'rgb(6, 125, 247)', 18 | successColor: '#42b983', 19 | warningColor: '#ff9800', 20 | dangerColor: 'rgb(255, 0, 31)', 21 | navLinkColor: '#2c3e50', 22 | navLinkBorderColor: 'var(--accent-color)', 23 | codeBlockBackground: '#011627', 24 | codeBlockTextColor: 'white', 25 | codeBlockShadowColor: '#333', 26 | codeBlockShadowWidth: '0px', 27 | highlightedLineBackground: '#022a4b', 28 | highlightedLineBorderColor: '#ffa7c4', 29 | inlineCodeColor: 'rgb(116, 66, 16)', 30 | inlineCodeBackground: 'rgb(254, 252, 191)', 31 | loaderPrimaryColor: '#f3f3f3', 32 | loaderSecondaryColor: '#ecebeb', 33 | tableHeaderBackground: '#fafafa', 34 | tableHeaderColor: '#666', 35 | docuteSelectHeight: '38px', 36 | searchIconColor: '#999', 37 | searchFocusBorderColor: '#ccc', 38 | searchFocusIconColor: '#333', 39 | searchResultHoverBackground: '#f9f9f9' 40 | } 41 | 42 | const darkCssVariables = { 43 | ...defaultCssVariables, 44 | headerBackground: 'var(--page-background)', 45 | sidebarLinkColor: 'var(--text-color)', 46 | sidebarLinkActiveColor: 'var(--text-color)', 47 | textColor: 'hsla(0,0%,100%,0.88)', 48 | pageBackground: '#2f3136', 49 | navLinkColor: 'var(--text-color)', 50 | borderColor: '#3e4147', 51 | highlightedLineBackground: '#022a4b', 52 | highlightedLineBorderColor: '#ffa7c4', 53 | inlineCodeColor: '#e6e6e6', 54 | inlineCodeBackground: '#373c49', 55 | loaderPrimaryColor: 'hsla(0, 0%, 100%, 0.08)', 56 | loaderSecondaryColor: 'hsla(0, 0%, 100%, 0.18)', 57 | contentLinkBorder: '2px solid hsla(0, 0%, 100%, 0.28)', 58 | contentLinkHoverBorderColor: 'currentColor', 59 | tableHeaderBackground: 'var(--border-color)', 60 | tableHeaderColor: '#868686', 61 | searchIconColor: '#999', 62 | searchFocusBorderColor: '#999', 63 | searchFocusIconColor: '#ccc', 64 | searchResultBackground: '#27292f', 65 | searchResultHoverBackground: '#1e2025' 66 | } 67 | 68 | export {defaultCssVariables, darkCssVariables} 69 | -------------------------------------------------------------------------------- /src/utils/highlight.js: -------------------------------------------------------------------------------- 1 | import Prism from 'prismjs' 2 | 3 | export default function highlight(str, lang) { 4 | if (!lang) return str 5 | 6 | let resolvedLang = lang && Prism.languages[lang] 7 | if (!resolvedLang) { 8 | lang = 'markup' 9 | resolvedLang = Prism.languages.markup 10 | } 11 | 12 | return Prism.highlight(str, resolvedLang, lang) 13 | } 14 | -------------------------------------------------------------------------------- /src/utils/index.js: -------------------------------------------------------------------------------- 1 | export const isExternalLink = link => { 2 | return /^https?:\/\//.test(link) 3 | } 4 | 5 | export const slugify = str => { 6 | const RE = /[\u2000-\u206F\u2E00-\u2E7F\\'!"#$%&()*+,./:;<=>?@[\]^`{|}~]/g 7 | const REPLACEMENT = '-' 8 | const WHITESPACE = /\s/g 9 | 10 | return str 11 | .trim() 12 | .replace(RE, '') 13 | .replace(WHITESPACE, REPLACEMENT) 14 | .toLowerCase() 15 | } 16 | 17 | export const getFileUrl = (sourcePath, path) => { 18 | sourcePath = sourcePath || '.' 19 | 20 | // Remove trailing slash in `sourcePath` 21 | // Since `path` always starts with slash 22 | sourcePath = sourcePath.replace(/\/$/, '') 23 | 24 | const result = sourcePath + path 25 | 26 | return result.replace(/^\.\//, '') 27 | } 28 | 29 | export const getFilenameByPath = path => { 30 | // Ensure path always starts with slash 31 | path = path.replace(/^\/?/, '/') 32 | 33 | // Add .md suffix 34 | if (!/\.md$/.test(path)) { 35 | path = /\/$/.test(path) ? `${path}README.md` : `${path}.md` 36 | } 37 | 38 | return path 39 | } 40 | 41 | export const inBrowser = typeof window !== 'undefined' 42 | -------------------------------------------------------------------------------- /src/utils/load.js: -------------------------------------------------------------------------------- 1 | import loadjs from 'loadjs' 2 | 3 | export default (deps, id) => 4 | new Promise(resolve => { 5 | if (loadjs.isDefined(id)) return resolve() 6 | loadjs(deps, id, { 7 | success: resolve, 8 | error(err) { 9 | console.error('Deps not found:', err) 10 | resolve() 11 | } 12 | }) 13 | }) 14 | -------------------------------------------------------------------------------- /src/utils/markedRenderer.js: -------------------------------------------------------------------------------- 1 | import marked from './marked' 2 | import {slugify} from '.' 3 | 4 | export default hooks => { 5 | const renderer = new marked.Renderer() 6 | 7 | const slugs = [] 8 | renderer.heading = function(text, level, raw) { 9 | const {env} = this.options 10 | 11 | let slug = slugify(raw) 12 | slugs.push(slug) 13 | const sameSlugCount = slugs.filter(v => v === slug).length 14 | if (sameSlugCount > 1) { 15 | slug += `-${sameSlugCount}` 16 | } 17 | 18 | if (level === 1) { 19 | env.title = text 20 | // Remove h1 header 21 | return '' 22 | } 23 | 24 | if (level === 2) { 25 | env.headings.push({ 26 | level, 27 | raw, 28 | // Remove trailing HTML 29 | text: raw.replace(/<.*>\s*$/g, ''), 30 | slug 31 | }) 32 | } 33 | 34 | const tag = `h${level}` 35 | return `<${tag} class="markdown-header" id="${slug}"> 36 | 37 | 38 | 39 | ${text}` 40 | } 41 | 42 | // Disable template interpolation in code 43 | renderer.codespan = text => `${text}` 44 | const origCode = renderer.code 45 | renderer.code = function(code, lang, escaped, opts) { 46 | opts = opts || {} 47 | const {env} = this.options 48 | 49 | if (opts.mixin) { 50 | env.mixins.push(code) 51 | return '' 52 | } 53 | 54 | let res = origCode.call(this, code, lang, escaped) 55 | 56 | if (!opts.interpolate) { 57 | res = res.replace(/^
/, '
')
58 |     }
59 | 
60 |     if (opts.highlight) {
61 |       const codeMask = code
62 |         .split('\n')
63 |         .map((v, i) => {
64 |           i += 1
65 |           const shouldHighlight = opts.highlight.some(number => {
66 |             if (typeof number === 'number') {
67 |               return number === i
68 |             }
69 |             if (typeof number === 'string') {
70 |               const [start, end] = number.split('-').map(Number)
71 |               return i >= start && (!end || i <= end)
72 |             }
73 |             return false
74 |           })
75 |           const escapedLine = v ? marked.escape(v) : '​'
76 |           return shouldHighlight
77 |             ? `${escapedLine}`
78 |             : `${escapedLine}`
79 |         })
80 |         .join('')
81 |       res += `${codeMask}`
84 |     }
85 | 
86 |     return `
${res}
` 87 | } 88 | 89 | return hooks.process('extendMarkedRenderer', renderer) 90 | } 91 | -------------------------------------------------------------------------------- /src/utils/parseCodeOptions.js: -------------------------------------------------------------------------------- 1 | export default opts => { 2 | if (opts) { 3 | try { 4 | // eslint-disable-next-line no-new-func 5 | const fn = new Function(`return ${opts}`) 6 | opts = fn() 7 | } catch (error) { 8 | console.error( 9 | `You're using invalid options for code fences, it must be JSON or JS object!\n${error.message}` 10 | ) 11 | } 12 | } 13 | return opts || {} 14 | } 15 | -------------------------------------------------------------------------------- /src/utils/prismLanguages.json: -------------------------------------------------------------------------------- 1 | { 2 | "javascript": "clike", 3 | "actionscript": "javascript", 4 | "arduino": "cpp", 5 | "aspnet": [ 6 | "markup", 7 | "csharp" 8 | ], 9 | "bison": "c", 10 | "c": "clike", 11 | "csharp": "clike", 12 | "cpp": "c", 13 | "coffeescript": "javascript", 14 | "crystal": "ruby", 15 | "css-extras": "css", 16 | "d": "clike", 17 | "dart": "clike", 18 | "django": "markup", 19 | "erb": [ 20 | "ruby", 21 | "markup-templating" 22 | ], 23 | "fsharp": "clike", 24 | "flow": "javascript", 25 | "glsl": "clike", 26 | "go": "clike", 27 | "groovy": "clike", 28 | "haml": "ruby", 29 | "handlebars": "markup-templating", 30 | "haxe": "clike", 31 | "java": "clike", 32 | "jolie": "clike", 33 | "kotlin": "clike", 34 | "less": "css", 35 | "markdown": "markup", 36 | "markup-templating": "markup", 37 | "n4js": "javascript", 38 | "nginx": "clike", 39 | "objectivec": "c", 40 | "opencl": "cpp", 41 | "parser": "markup", 42 | "php": [ 43 | "clike", 44 | "markup-templating" 45 | ], 46 | "php-extras": "php", 47 | "plsql": "sql", 48 | "processing": "clike", 49 | "protobuf": "clike", 50 | "pug": "javascript", 51 | "qore": "clike", 52 | "jsx": [ 53 | "markup", 54 | "javascript" 55 | ], 56 | "tsx": [ 57 | "jsx", 58 | "typescript" 59 | ], 60 | "reason": "clike", 61 | "ruby": "clike", 62 | "sass": "css", 63 | "scss": "css", 64 | "scala": "java", 65 | "smarty": "markup-templating", 66 | "soy": "markup-templating", 67 | "swift": "clike", 68 | "tap": "yaml", 69 | "textile": "markup", 70 | "tt2": [ 71 | "clike", 72 | "markup-templating" 73 | ], 74 | "twig": "markup", 75 | "typescript": "javascript", 76 | "vbnet": "basic", 77 | "velocity": "markup", 78 | "wiki": "markup", 79 | "xeora": "markup", 80 | "xquery": "markup", 81 | "builtin": [ 82 | "markup", 83 | "xml", 84 | "html", 85 | "mathml", 86 | "svg", 87 | "css", 88 | "clike", 89 | "javascript", 90 | "js" 91 | ] 92 | } -------------------------------------------------------------------------------- /src/utils/removeMarkdownExtension.js: -------------------------------------------------------------------------------- 1 | const RE = /\.md$/ 2 | 3 | export default input => { 4 | let [path, hash] = input.split('#') 5 | if (RE.test(path)) { 6 | path = path.replace(RE, '') 7 | } 8 | return `${path}${hash ? `#${hash}` : ''}` 9 | } 10 | -------------------------------------------------------------------------------- /src/views/Home.vue: -------------------------------------------------------------------------------- 1 | 35 | 36 | 180 | 181 | 182 | 183 | 184 | 232 | -------------------------------------------------------------------------------- /website/components/ColorBox.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 10 | 11 | 12 | 20 | -------------------------------------------------------------------------------- /website/docs/README.md: -------------------------------------------------------------------------------- 1 | # Introduction to Docute 2 | 3 | The fastest way to create a documentation site for your project. 4 | 5 | ## What is Docute 6 | 7 | Docute is basically a JavaScript file that fetches Markdown files and renders them as a single-page application. 8 | 9 | It's totally runtime-driven so there's no server-side components involved which also means there's no build process. You only need to create an HTML file and a bunch of Markdown documents and your website is almost ready! 10 | 11 | ## How does it work 12 | 13 | In short: URL is the API. 14 | 15 | We fetch and render corresponding markdown file for the URL you visit: 16 | 17 | ``` 18 | / => /README.md 19 | /foo => /foo.md 20 | /foo/ => /foo/README.md 21 | /foo/bar => /foo/bar.md 22 | ``` 23 | 24 | ## Quick Start 25 | 26 | Let's say you have following files in `./my-docs` folder: 27 | 28 | ```bash 29 | . 30 | ├── README.md 31 | └── index.html 32 | ``` 33 | 34 | The `index.html` looks like: 35 | 36 | ```html {highlight:[7,'10-16']} 37 | 38 | 39 | 40 | 41 | 42 | My Docs 43 | 44 | 45 | 46 |
47 | 48 | 53 | 54 | 55 | ``` 56 | 57 | Then you can serve this folder as a static website on your machine using: 58 | 59 | - Node.js: `npm i -g serve && serve .` 60 | - Python: `python -m SimpleHTTPServer` 61 | - Golang: `caddy` 62 | - ..or whatever static web server 63 | 64 | Next, you may want to use [sidebar](./options.md#sidebar), [nav](./options.md#nav) or other [options](./options.md) to customize the website. 65 | 66 | Here's a [REPL](https://repl.it/@egoist/docute-starter) where you can try Docute online or [download](https://repl.it/@egoist/docute-starter.zip) it to run locally. 67 | 68 | ## Comparisons 69 | 70 | ### VuePress / GitBook / Hexo 71 | 72 | They all generate static HTML at build time, which is good for SEO. 73 | 74 | If you care about SEO, you may like using [presite](https://github.com/egoist/presite) to prerender your website. 75 | 76 | ### Docsify 77 | 78 | [Docsify](https://docsify.js.org/#/) and Docute are pretty much the same, but with different UI and different usages. 79 | 80 | Docute (60kB) is 3 times bigger than Docisfy (20kB), because we use Vue, Vue Router and Vuex while Docsify uses vanilla JavaScript under the hood. 81 | 82 | ## Browser Compatibility 83 | 84 | Docute supports all ever-green browsers, i.e. No IE support! 85 | -------------------------------------------------------------------------------- /website/docs/builtin-components.md: -------------------------------------------------------------------------------- 1 | # Built-in Components 2 | 3 | Docute comes with a set of built-in Vue components. 4 | 5 | ## `` 6 | 7 | Use medium-style zoom effect to display certain image. 8 | 9 | | Prop | Type | Default | Description | 10 | | ------ | --------- | ------- | ------------------------ | 11 | | src | `string` | N/A | URL to image | 12 | | title | `string` | N/A | Image title | 13 | | alt | `string` | N/A | Placeholder text | 14 | | border | `boolean` | `false` | Show border around image | 15 | | width | `string` | N/A | Image width | 16 | 17 |
18 | 19 | Example: 20 | 21 | ```markdown 22 | 27 | ``` 28 | 29 | 30 | 31 | ## `` 32 | 33 | A small count and labeling component. 34 | 35 | | Prop | Type | Default | Description | 36 | | -------- | -------------------------------------------------------------------- | ------- | ----------------------- | 37 | | type | 'tip' | 'success' | 'warning' | 'danger' | N/A | Badge type | 38 | | color | `string` | N/A | Custom background color | 39 | | children | `string` | N/A | Badge text | 40 | 41 |
42 | 43 | Example: 44 | 45 | ```markdown 46 | - Feature 1 Badge 47 | - Feature 2 Tip 48 | - Feature 3 Success 49 | - Feature 4 Warning 50 | - Feature 5 Danger 51 | - Feature 6 Custom Color 52 | ``` 53 | 54 | - Feature 1 Badge 55 | - Feature 2 Tip 56 | - Feature 3 Success 57 | - Feature 4 Warning 58 | - Feature 5 Danger 59 | - Feature 6 Custom Color 60 | 61 | ## `` 62 | 63 | Colored note blocks, to emphasize part of your page. 64 | 65 | | Prop | Type | Default | Description | 66 | | -------- | -------------------------------------------------------------------- | ------------------- | ------------------------------------------------- | 67 | | type | 'tip' | 'warning' | 'danger' | 'success' | N/A | Note type | 68 | | label | `string` `boolean` | The value of `type` | Custom note label text, use `false` to hide label | 69 | | children | `string` | N/A | Note content | 70 | 71 |
72 | 73 | Examples: 74 | 75 | ```markdown 76 | 77 | 78 | This is a note that details something important.
79 | [A link to helpful information.](https://docute.org) 80 | 81 |
82 | 83 | 84 | 85 | 86 | This is a tip for something that is possible. 87 | 88 | 89 | 90 | 91 | 92 | 93 | This is a warning for something very important. 94 | 95 | 96 | 97 | 98 | 99 | 100 | This is a danger for something to take action for. 101 | 102 | 103 | ``` 104 | 105 | 106 | 107 | This is a note that details something important.
108 | [A link to helpful information.](https://docute.org) 109 | 110 |
111 | 112 | 113 | 114 | 115 | This is a tip for something that is possible. 116 | 117 | 118 | 119 | 120 | 121 | 122 | This is a warning for something very important. 123 | 124 | 125 | 126 | 127 | 128 | 129 | This is a danger for something to take action for. 130 | 131 | 132 | 133 | ## `` 134 | 135 | Embed [GitHub Gist](https://gist.github.com/) into your Markdown documents. 136 | 137 | | Prop | Type | Default | Description | 138 | | ---- | -------- | ------- | ----------- | 139 | | id | `string` | N/A | Gist ID | 140 | 141 | Example: 142 | 143 | ```markdown 144 | 145 | ``` 146 | 147 | 148 | 149 | ## `` 150 | 151 | A customized `` component: 151 | 152 | 153 | ````vue 154 | 155 | 156 | 157 | 158 | 159 | 160 | Your favorite fruit: {{ favoriteFruit }} 161 | 162 | ```js {mixin: true} 163 | module.exports = { 164 | data() { 165 | return { 166 | favoriteFruit: 'banana' 167 | } 168 | }, 169 | methods: { 170 | handleChange(value) { 171 | this.favoriteFruit = value 172 | } 173 | } 174 | } 175 | ``` 176 | ```` 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | Your favorite fruit: {{ favoriteFruit }} 185 | 186 | ```js {mixin: true} 187 | { 188 | data() { 189 | return { 190 | favoriteFruit: 'banana' 191 | } 192 | }, 193 | methods: { 194 | handleChange(value) { 195 | this.favoriteFruit = value 196 | } 197 | } 198 | } 199 | ``` 200 | 201 | ## `` `` 202 | 203 | 在 Vue template 中替代 ` 131 | ``` 132 | 133 | to toggle the custom fonts on this website. 134 | 135 | By default a fresh Docute website will use system default fonts. 136 | 137 | ## 自定义样式 138 | 139 | You can use [`cssVariables`](../options.md#cssvariables) option to customize site style: 140 | 141 | ```js 142 | new Docute({ 143 | cssVariables: { 144 | sidebarWidth: '300px' 145 | } 146 | }) 147 | 148 | // Or using a function to get current theme 149 | new Docute({ 150 | cssVariables(theme) { 151 | return theme === 'dark' ? {} : {} 152 | } 153 | }) 154 | ``` 155 | 156 | The `cssVariables` used by the the {{ $store.getters.config.theme }} theme: 157 | 158 |
    159 |
  • 160 | {{key}}: 161 | {{value}} 162 |
  • 163 |
164 | 165 | Note that these properties are defined in camelCase but you should reference them in CSS using kebab-case: 166 | 167 | ```css 168 | .Sidebar { 169 | width: var(--sidebar-width); 170 | } 171 | ``` 172 | -------------------------------------------------------------------------------- /website/docs/zh/guide/deployment.md: -------------------------------------------------------------------------------- 1 | # 部署 2 | 3 | 请记住,它只是一个在任何地方都可以提供的静态 HTML 文件。 4 | 5 | ## ZEIT Now Recommended 6 | 7 | [ZEIT Now](https://zeit.co/now) is a platform for Global Serverless Deployments, it's also perfectly suitable for deploying a static website with or without build process. 8 | 9 | Assuming you have your docs in `./docs` folder, to deploy it you can simply populate a `now.json` in your project: 10 | 11 | ```json 12 | { 13 | "version": 2, 14 | "builds": [ 15 | { 16 | "src": "docs/**", 17 | "use": "@now/static" 18 | } 19 | ] 20 | } 21 | ``` 22 | 23 | Then [install Now](https://zeit.co/docs/v2/getting-started/installation/) on your machine. 24 | 25 | After that, you can run the command `now` in your project and you're all set. 26 | 27 | Make sure to check out Now's [GitHub Integration](https://zeit.co/docs/v2/integrations/now-for-github/) if you want automatic deployments on every push and pull request. 28 | 29 | ## Netlify Recommended 30 | 31 | 1. 登录你的 [Netlify](https://www.netlify.com/) 账号。 32 | 2. 在 [dashboard](https://app.netlify.com/) 页,点击 __New site from Git__. 33 | 3. 选择一个存储文档的仓库,将 __Build Command__ 区域留空, blank,用 `index.html` 的目录填写 __Publish directory__ 区域,例如,`docs/index.html`,应填为 `docs`。 34 | 35 | ## GitHub Pages 36 | 37 | 使用 Github Pages 最简单的方式是在 master 分支上的 `./docs` 文件夹中加入所有文件,然后将此文件夹用于 Github Pages: 38 | 39 | 40 | 41 | 但是你仍然可以使用 `gh-pages` 分支或者 `master` 分支来为你的文档提供服务,这一切都取决于你的需求。 42 | -------------------------------------------------------------------------------- /website/docs/zh/guide/internationalization.md: -------------------------------------------------------------------------------- 1 | # 国际化 2 | 3 | 由于 Docute 使用基于 URL 的 API实现,因此添加多语言支持非常简单: 4 | 5 | ``` 6 | docs 7 | ├─ README.md 8 | ├─ foo.md 9 | ├─ nested 10 | │ └─ README.md 11 | └─ zh 12 | ├─ README.md 13 | ├─ foo.md 14 | └─ nested 15 | └─ README.md 16 | ``` 17 | 18 | 使用上述文件夹结构,用户可以通过 URL `/zh/` 访问文档的*中文*版本。 19 | 20 | 然后,可以使用 `overrides` 选项来本地化 UI 中使用的文本: 21 | 22 | ```js 23 | new Docute({ 24 | sidebar: [ 25 | { 26 | children: [ 27 | { title: 'Guide', link: '/guide' } 28 | ] 29 | } 30 | ], 31 | overrides: { 32 | '/': { 33 | language: 'English' // Used by the language dropdown menu in the sidebar 34 | }, 35 | '/zh/': { 36 | language: 'Chinese', 37 | // Override the default sidebar 38 | sidebar: [ 39 | { 40 | children: [ 41 | { title: '指南', link: '/zh/guide' } 42 | ] 43 | } 44 | ] 45 | } 46 | } 47 | }) 48 | ``` 49 | -------------------------------------------------------------------------------- /website/docs/zh/guide/markdown-features.md: -------------------------------------------------------------------------------- 1 | # 撰写 2 | 3 | 文档应易于阅读且易于撰写。 4 | 5 | ## 文档规范 6 | 7 | 文档应以 Markdown 格式展现: 8 | 9 | ```markdown 10 | # 标题 11 | 12 | 内容填在这里... 13 | ``` 14 | 15 | 如果你不知道它是什么,请查阅 [Markdown](https://daringfireball.net/projects/markdown/)。 16 | 17 | ## 链接 18 | 19 | ### 内部链接 20 | 21 | 内部链接会转换为 `` 进行 SPA 式导航。 22 | 23 | __输入__: 24 | 25 | ```markdown 26 | - [首页](/zh/) 27 | - [在 Markdown 中使用 Vue](/zh/guide/use-vue-in-markdown) 28 | - [查看 `title` 选项](../options.md#title) 29 | ``` 30 | 31 | __输出__: 32 | 33 | - [首页](/zh/) 34 | - [在 Markdown 中使用 Vue](/zh/guide/use-vue-in-markdown) 35 | - [查看 `title` 选项](../options.md#title) 36 | 37 | ### 外部链接 38 | 39 | 外部链接会自动添加 HTML 属性 `target="_blank" rel="noopener noreferrer"`,例如: 40 | 41 | __输入__: 42 | 43 | ```markdown 44 | - [Docute website](https://docute.org) 45 | - [Docute repo](https://github.com/egoist/docute) 46 | ``` 47 | 48 | __输出__: 49 | 50 | - [Docute website](https://docute.org) 51 | - [Docute repo](https://github.com/egoist/docute) 52 | 53 | ## 任务列表 54 | 55 | __输入__: 56 | 57 | ```markdown 58 | - [ ] Rule the web 59 | - [x] Conquer the world 60 | - [ ] Learn Docute 61 | ``` 62 | 63 | __输出__: 64 | 65 | - [ ] Rule the web 66 | - [x] Conquer the world 67 | - [ ] Learn Docute 68 | 69 | ## 代码高亮 Highlighting 70 | 71 | 代码框使用 [Prism.js](https://prismjs.com/) 高亮显示,示例代码: 72 | 73 | ```js 74 | // Returns a function, that, as long as it continues to be invoked, will not 75 | // be triggered. The function will be called after it stops being called for 76 | // N milliseconds. If `immediate` is passed, trigger the function on the 77 | // leading edge, instead of the trailing. 78 | function debounce(func, wait, immediate) { 79 | var timeout; 80 | return function() { 81 | var context = this, args = arguments; 82 | var later = function() { 83 | timeout = null; 84 | if (!immediate) func.apply(context, args); 85 | }; 86 | var callNow = immediate && !timeout; 87 | clearTimeout(timeout); 88 | timeout = setTimeout(later, wait); 89 | if (callNow) func.apply(context, args); 90 | }; 91 | }; 92 | ``` 93 | 94 | 默认支持的语言: 95 | 96 |
    97 |
  • 98 | {{ lang }} 99 |
  • 100 |
101 | 102 | 你可以查看[高亮](/zh/options#highlight)选项添加更多语言。 103 | 104 | ## Code Fence Options 105 | 106 | Next to the code fence language, you can use a JS object to specify options: 107 | 108 | ````markdown 109 | ```js {highlightLines: [2]} 110 | function foo() { 111 | console.log('foo') 112 | } 113 | ``` 114 | ```` 115 | 116 | Available options: 117 | 118 | - `highlightLines`: [Line Highlighting in Code Fences](#line-highlighting-in-code-fences) 119 | - `mixin`: [Adding Vue Mixin](#adding-vue-mixin) 120 | 121 | ## 代码框中某行高亮显示 122 | 123 | __输入:__ 124 | 125 | ````markdown 126 | ```js {highlight:[3,'5-7',12]} 127 | class SkinnedMesh extends THREE.Mesh { 128 | constructor(geometry, materials) { 129 | super(geometry, materials); 130 | 131 | this.idMatrix = SkinnedMesh.defaultMatrix(); 132 | this.bones = []; 133 | this.boneMatrices = []; 134 | //... 135 | } 136 | update(camera) { 137 | //... 138 | super.update(); 139 | } 140 | static defaultMatrix() { 141 | return new THREE.Matrix4(); 142 | } 143 | } 144 | ``` 145 | ```` 146 | 147 | __输出:__ 148 | 149 | ```js {highlight:[3,'5-7',12]} 150 | class SkinnedMesh extends THREE.Mesh { 151 | constructor(geometry, materials) { 152 | super(geometry, materials); 153 | 154 | this.idMatrix = SkinnedMesh.defaultMatrix(); 155 | this.bones = []; 156 | this.boneMatrices = []; 157 | //... 158 | } 159 | update(camera) { 160 | //... 161 | super.update(); 162 | } 163 | static defaultMatrix() { 164 | return new THREE.Matrix4(); 165 | } 166 | } 167 | ``` 168 | 169 | ## Adding Vue Mixin 170 | 171 | Adding a Vue mixin to the Markdown component: 172 | 173 | ````markdown 174 | people love Docute. 175 | 176 | ```js {mixin:true} 177 | { 178 | data() { 179 | return { 180 | count: 1000 181 | } 182 | } 183 | } 184 | ``` 185 | ```` 186 | 187 | people love Docute. 188 | 189 | ```js {mixin:true} 190 | { 191 | data() { 192 | return { 193 | count: 1000 194 | } 195 | } 196 | } 197 | ``` 198 | 199 | ## 使用 Mermaid 200 | 201 | [Mermaid](https://mermaidjs.github.io/) 是一种纯文本撰写图表的方法,你可以使用简单的 Docute 插件来添加对 Mermaid 的支持: 202 | 203 | ```html 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 219 | ``` 220 | 221 | ## HTML in Markdown 222 | 223 | You can write HTML in Markdown, for example: 224 | 225 | ```markdown 226 | __FAQ__: 227 | 228 |
what is the meaning of life? 229 | 230 | some say it is __42__, some say it is still unknown. 231 |
232 | ``` 233 | 234 | __FAQ__: 235 | 236 |
what is the meaning of life? 237 | 238 | some say it is __42__, some say it is still unknown. 239 |
240 | 241 |
242 | 243 | In fact you can even use Vue directives and Vue components in Markdown too, learn more about it [here](./use-vue-in-markdown.md). 244 | -------------------------------------------------------------------------------- /website/docs/zh/guide/offline-support.md: -------------------------------------------------------------------------------- 1 | # Offline Support 2 | 3 | Improve your website's performance by caching and serving your files, powered by a [service worker](https://developer.mozilla.org/docs/Web/API/Service_Worker_API/Using_Service_Workers). 4 | 5 | First create a `sw.js` in your docs root directory: 6 | 7 | ```js 8 | importScripts( 9 | 'https://storage.googleapis.com/workbox-cdn/releases/3.6.1/workbox-sw.js' 10 | ) 11 | 12 | const ALLOWED_HOSTS = [ 13 | // The domain to load markdown files 14 | location.host, 15 | // The domain to load docute 16 | 'unpkg.com' 17 | ] 18 | 19 | const matchCb = ({ url, event }) => { 20 | return event.request.method === 'GET' && ALLOWED_HOSTS.includes(url.host) 21 | } 22 | 23 | workbox.routing.registerRoute( 24 | matchCb, 25 | workbox.strategies.networkFirst() 26 | ) 27 | ``` 28 | 29 | _[Workbox](https://developers.google.com/web/tools/workbox/) is a library that bakes in a set of best practices and removes the boilerplate every developer writes when working with service workers._ 30 | 31 | Then register this service worker in `index.html`: 32 | 33 | ```html {highlight:['16-18']} 34 | 35 | 36 | 37 | 38 | 42 | My Docs 43 | 44 | 45 | 46 |
47 | 48 | 57 | 58 | 59 | ``` 60 | 61 | __🥳 Now your website will be offline-ready.__ 62 | 63 | If you somehow no longer need this service worker, replace the content of `sw.js` with following code to disable it: 64 | 65 | ```js 66 | self.addEventListener('install', e => { 67 | self.skipWaiting() 68 | }) 69 | 70 | self.addEventListener('activate', e => { 71 | self.registration 72 | .unregister() 73 | .then(() => { 74 | return self.clients.matchAll() 75 | }) 76 | .then(clients => { 77 | clients.forEach(client => client.navigate(client.url)) 78 | }) 79 | }) 80 | ``` 81 | -------------------------------------------------------------------------------- /website/docs/zh/guide/plugin.md: -------------------------------------------------------------------------------- 1 | # 插件 2 | 3 | 插件本质上是一个纯对象(pure object): 4 | 5 | ```js 6 | const showAuthor = { 7 | // 插件名称 8 | name: 'showAuthor', 9 | // 扩展核心功能 10 | extend(api) { 11 | api.processMarkdown(text => { 12 | return text.replace(/{author}/g, '> Written by EGOIST') 13 | }) 14 | } 15 | } 16 | 17 | new Docute({ 18 | // ... 19 | plugins: [ 20 | showAuthor 21 | ] 22 | }) 23 | ``` 24 | 25 | 示例: 26 | 27 | ```markdown 28 | # Page 标题 29 | 30 | {author} 31 | ``` 32 | 33 | 34 | 35 | --- 36 | 37 | 要接收插件中的选项,可以使用工厂函数: 38 | 39 | ```js 40 | const myPlugin = opts => { 41 | return { 42 | name: 'my-plugin', 43 | extend(api) { 44 | // 使用 `opts` 和 `api` 做点什么 45 | } 46 | } 47 | } 48 | 49 | new Docute({ 50 | plugins: [ 51 | myPlugin({ foo: true }) 52 | ] 53 | }) 54 | ``` 55 | 56 | --- 57 | 58 | 欲了解更多如何开发插件的信息,请查阅[插件 API](/zh/plugin-api)。 59 | -------------------------------------------------------------------------------- /website/docs/zh/guide/use-vue-in-markdown.md: -------------------------------------------------------------------------------- 1 | # 在 Markdown 中使用 Vue 2 | 3 | 在编写 Markdown 文档时可以充分利用 Vue 和 JavaScript 的强大功能! 4 | 5 | ## 插值(Interpolation) 6 | 7 | 每个 Markdown 文件首先会编译为 HTML,然后渲染为 Vue 组件这意味着你可以在文本中使用 Vue 式的插值: 8 | 9 | __输入__: 10 | 11 | ```markdown 12 | {{ 1 + 1 }} 13 | ``` 14 | 15 | __输出__: 16 | 17 | ``` 18 | 2 19 | ``` 20 | 21 | ## Escaping 22 | 23 | 如果要在文本中禁用 Vue 式插值,可以将其包装在代码块或内联代码中,如下所示: 24 | 25 | __输入__: 26 | 27 | ````markdown 28 | ```js 29 | const foo = `{{ 安全,这不会被插值!}}` 30 | ``` 31 | 32 | `{{ bar }}` 也是安全的! 33 | ```` 34 | 35 | __输出__: 36 | 37 | ```js 38 | const foo = `{{ 安全,这不会被插值!}}` 39 | ``` 40 | 41 | `{{ bar }}` 也是安全的! 42 | 43 | ## 使用组件 44 | 45 | Docute 在 `window` 对象上暴露了 `Vue` 的构造函数,因此你可以使用它来注册全局组件,以便于在 Markdown 文档中使用: 46 | 47 | ```js {highlight:['6-13']} 48 | Vue.component('ReverseText', { 49 | props: ['text'], 50 | template: ` 51 |
52 | {{ reversedText }} 53 | 54 | .reverse-text { 55 | border: 1px solid var(--border-color); 56 | padding: 20px; 57 | font-weight: bold; 58 | border-radius: 4px; 59 | } 60 | 61 |
62 | `, 63 | computed: { 64 | reversedText() { 65 | return this.text.split('').reverse().join('') 66 | } 67 | } 68 | }) 69 | ``` 70 | 71 | 你可能会注意到高亮的部分,由于你不能直接在 Vue template 里使用 `style` 元素,于是我们提供了 `v-style` 组件来作为替代。类似地,我们也提供了 `v-script` 组件。 72 | 73 | __输入__: 74 | 75 | ```markdown 76 | 77 | ``` 78 | 79 | __输出__: 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /website/docs/zh/guide/use-with-bundlers.md: -------------------------------------------------------------------------------- 1 | # Use With Bundlers 2 | 3 | You don't have to use a bundler in order to use Docute, however if you want, you can! 4 | 5 | First you need to install Docute as a dependency in your project: 6 | 7 | ```bash 8 | npm install docute 9 | ``` 10 | 11 | Then see below for the usage with your bundler of choice. 12 | 13 | ## Webpack 14 | 15 | In your entry file: 16 | 17 | ```js 18 | import Docute from 'docute' 19 | 20 | new Docute({ 21 | target: '#app', 22 | // Other options 23 | }) 24 | ``` 25 | 26 | You may need to use [html-webpack-plugin](https://github.com/jantimon/html-webpack-plugin) to generate an HTML file too. 27 | 28 | If you're using [Vue CLI](https://cli.vuejs.org) or [Poi](https://poi.js.org), congratulations, there's no more build configs needed. 29 | 30 | ## Parcel 31 | 32 | [TODO] [PR WELCOME] 33 | -------------------------------------------------------------------------------- /website/docs/zh/options.md: -------------------------------------------------------------------------------- 1 | # 配置项 2 | 3 | 用于 `Docute` 构造函数的配置项。 4 | 5 | ```js 6 | new Docute(options) 7 | ``` 8 | 9 | 10 | ## target 11 | 12 | - Type: `string` 13 | - Default: `docute` 14 | 15 | 目标元素的 ID,会被用于创建 app,比如 `app` 或者 `#app`。 16 | 17 | ## title 18 | 19 | - 类型:`string` 20 | - 默认值:`document.title` 21 | 22 | 网站标题。 23 | 24 | ## logo 25 | 26 | - Type: `string` `object` 27 | - Default: `{{ $store.getters.config.title }}` 28 | 29 | Customize the logo in header. 30 | 31 | - `string`: Used as Vue template. 32 | - `object`: Used as Vue component. 33 | 34 | ## nav 35 | 36 | - 类型: `Array` 37 | 38 | 在头部显示的导航栏。 39 | 40 | ```ts 41 | interface NavItem { 42 | title: string 43 | link?: string 44 | // Whether to open the link in a new tab 45 | // Only works for external links 46 | // Defaults to `true` 47 | openInNewTab?: boolean 48 | // Use `children` instead of `link` to display dropdown 49 | children?: Array 50 | } 51 | 52 | interface NavItemLink { 53 | title: string 54 | link: string 55 | openInNewTab?: boolean 56 | } 57 | ``` 58 | 59 | ## sidebar 60 | 61 | - 类型:`Array` `(store: Vuex.Store) => Array` 62 | 63 | 在侧边栏中显示的导航栏。 64 | 65 | ```ts 66 | type SidebarItem = SingleItem | MultiItem 67 | 68 | interface SingleItem { 69 | title: string 70 | link: string 71 | // Whether to open the link in a new tab 72 | // Only works for external links 73 | // Defaults to `true` 74 | openInNewTab?: boolean 75 | } 76 | 77 | interface MultiItem { 78 | title: string 79 | children: Array 80 | /** 81 | * Whether to show TOC 82 | * Default to `true` 83 | */ 84 | toc?: boolean 85 | /** 86 | * Whether to make children collapsable 87 | * Default to `true` 88 | */ 89 | collapsable?: boolean 90 | } 91 | ``` 92 | 93 | ## sourcePath 94 | 95 | - 类型:`string` 96 | - 默认值:`'./'` 97 | 98 | 从 source path 获取 markdown 文件,默认情况下,我们从 `index.html` 所在的目录加载它们。 99 | 100 | 它也可以是完整的 URL,例如: `https://some-website/path/to/markdown/files`,以便于你可以从其他域名加载文件。 101 | 102 | 103 | ## routes 104 | 105 | - Type: `Routes` 106 | 107 | Use this option to make Docute fetch specific file or use given content for a path. 108 | 109 | ```ts 110 | interface Routes { 111 | [path: string]: RouteData 112 | } 113 | 114 | interface RouteData { 115 | /* Default to the content h1 header */ 116 | title?: string 117 | /* One of `content` and `file` is required */ 118 | content?: string 119 | /* Response will be used as `content` */ 120 | file?: string 121 | /* Parse the content as markdown, true by default */ 122 | markdown?: boolean 123 | [k: string]?: any 124 | } 125 | ``` 126 | 127 | ## componentMixins 128 | 129 | - 类型: `Array` 130 | 131 | 一个包含 [Vue mixins](https://vuejs.org/v2/api/#mixins) 的数组,会被应用到所有的 Markdown 组件中。 132 | 133 | ## highlight 134 | 135 | - 类型:`Array` 136 | 137 | 需要语法高亮的语言名称数组。查阅 [Prism.js](https://unpkg.com/prismjs/components/) 获取所有支持的语言名称。(不需要携带 `prism-` 前缀)。 138 | 139 | 例如:`highlight: ['typescript', 'go', 'graphql']`。 140 | 141 | ## editLinkBase 142 | 143 | - 类型:`string` 144 | 145 | *编辑链接*的 URL 基础路径。 146 | 147 | 例如,如果将 markdown 文件存储在 Github 仓库的 master 分支的 `docs` 文件夹中,那么该路径应该为: 148 | 149 | ``` 150 | https://github.com/USER/REPO/blob/master/docs 151 | ``` 152 | 153 | ## editLinkText 154 | 155 | - 类型:`string` 156 | - 默认值:`'Edit this page'` 157 | 158 | *编辑链接*的文字内容。 159 | 160 | # theme 161 | 162 | - 类型: `string` 163 | - 默认值: `default` 164 | - 内置项: `default` `dark` 165 | 166 | 网站主题。 167 | 168 | ## detectSystemDarkTheme 169 | 170 | - Type: `boolean` 171 | - Default: `undefined` 172 | 173 | In recent versions of macOS (Mojave) and Windows 10, users have been able to enable a system level dark mode. Set this option to `true` so that Docute will use the dark theme by default if your system has it enabled. 174 | 175 | ## darkThemeToggler 176 | 177 | - Type: `boolean` 178 | - Default: `undefined` 179 | 180 | 181 | 显示一个按钮用于切换夜间主题。 182 | 183 | ## layout 184 | 185 | - 类型: `string` 186 | - 默认值: `wide` 187 | - 可选项: `wide` `narrow` `left` 188 | 189 | 网站布局。 190 | 191 | ## versions 192 | 193 | - 类型: `Versions` 194 | 195 | 设置此项之后, Docute 会在侧边栏显示一个版本选择器。 196 | 197 | ```ts 198 | interface Versions { 199 | // 版本号, 比如 `v1` 200 | [version: string]: { 201 | // 指向相关文档的链接 202 | link: string 203 | } 204 | } 205 | ``` 206 | 207 | ## cssVariables 208 | 209 | - Type: `object` `(theme: string) => object` 210 | 211 | Override CSS variables. 212 | 213 | ## overrides 214 | 215 | - 类型:`{[path: string]: LocaleOptions}` 216 | 217 | ```ts 218 | interface LocaleOptions extends Options { 219 | language: string 220 | } 221 | ``` 222 | 223 | ## router 224 | 225 | - Type: `ConstructionOptions` 226 | 227 | All vue-router's [Construction options](https://router.vuejs.org/api/#router-construction-options) except for `routes` are accepted here. 228 | 229 | For example, you can set `router: { mode: 'history' }` to [get rid of the hash](https://router.vuejs.org/guide/essentials/history-mode.html#example-server-configurations) in URLs. 230 | 231 | ## banner / footer 232 | 233 | - Type: `string` `VueComponent` 234 | 235 | Display banner and footer. A string will be wrapped inside `
` or `