Compare commits

..
47 Commits
Author SHA1 Message Date
qingwei.li 20a83808f3 -> v3.0.1 2017-02-19 14:55:28 +08:00
qingwei.li 4419b2119e bump: 3.0.1 2017-02-19 14:55:20 +08:00
qingwei.li cd99b522e7 fix(route): empty alias 2017-02-19 14:54:22 +08:00
qingwei.li 787656a1ee docs(index): enable execute script 2017-02-19 14:44:37 +08:00
qingwei.li f573caa3c6 docs(cover): update version number 2017-02-19 14:41:27 +08:00
qingwei.li 0455106a62 -> v3.0.0 2017-02-19 14:40:30 +08:00
qingwei.li 74ce0bf588 bump: 3.0.0 close #78 2017-02-19 14:40:16 +08:00
qingwei.li d355bb4ebe docs: add changeog 2017-02-19 14:38:38 +08:00
qingwei.li dbb92782cf feat(front-matter): add front matter[WIP] 2017-02-19 14:38:38 +08:00
qingwei.li 754f92ce8e fix(search) ignore empty link 2017-02-19 14:38:38 +08:00
qingwei.li fcb66e8bc7 fix(search): escape html 2017-02-19 14:38:38 +08:00
qingwei.li 2efd859f71 fix(search): fix default config 2017-02-19 14:38:38 +08:00
qingwei.li 515331763d docs(plugin): add search doc 2017-02-19 14:38:38 +08:00
qingwei.li 2351c3e68c feat(search): Localization for search placeholder, close #80 2017-02-19 14:38:38 +08:00
qingwei.li 079bd00395 refactor(plugins): update search plugin 2017-02-19 14:38:38 +08:00
qingwei.li 86594a3118 feat(themes): add loading info 2017-02-19 14:38:38 +08:00
qingwei.li a6d823b826 docs(markdown): update markdown config 2017-02-19 14:38:38 +08:00
qingwei.li 7b6a2ac404 fix(render): support html file 2017-02-19 14:38:38 +08:00
qingwei.li 3e7d6ab26c docs: add executeScript demo 2017-02-19 14:38:38 +08:00
qingwei.li 4b64386465 docs: move changelog to history 2017-02-19 14:38:38 +08:00
qingwei.li c7e09c34a7 fix(compiler): link 2017-02-19 14:38:38 +08:00
qingwei.li c108645e34 docs: add autoHeader 2017-02-19 14:38:38 +08:00
qingwei.li b7768b1b3c feat(render): add auto header 2017-02-19 14:38:38 +08:00
qingwei.li fd9c3bd19d docs: update 2017-02-19 14:38:38 +08:00
qingwei.li e2b7b976cf refactor(core): fix route path 2017-02-19 14:38:38 +08:00
qingwei.li 8cae16539d refactor(core): add scroll event 2017-02-19 14:38:38 +08:00
qingwei.li 27e7e749af refactor(core): and cover 2017-02-19 14:38:38 +08:00
qingwei.li fbd51afdcb refactor(core): and sidebar event 2017-02-19 14:38:38 +08:00
qingwei.li fe88c154b0 refactor(core): and markdown compiler 2017-02-19 14:38:38 +08:00
qingwei.li 30da0d5d46 refactor(core): add router 2017-02-19 14:38:38 +08:00
qingwei.li 8bee8f0213 refactor(core): add render 2017-02-19 14:38:38 +08:00
qingwei.li aad62b65f5 refactor(core): adjust directory structure: 2017-02-19 14:38:38 +08:00
qingwei.li e07d36f44e Merge branch 'master' of github.com:QingWei-Li/docsify 2017-02-15 20:02:02 +08:00
qingwei.li 5dcd6d43ea -> v2.4.3 2017-02-15 20:01:11 +08:00
qingwei.li 00e3609b25 bump: 2.4.3 2017-02-15 20:01:07 +08:00
qingwei.li a6d9669bfa docs: update package size 2017-02-15 20:00:56 +08:00
Jintzoandcinwell.li 94255e22b1 Correctly link Get Started (#77)
As `## docsify` is the new title of the `README.md`, *Get Started* should link to that.
2017-02-14 17:11:29 +08:00
Songandcinwell.li 00983ab796 fix emoji replacing error (#76) 2017-02-14 15:47:04 +08:00
qingwei.li 8e203b07ab -> v2.4.2 2017-02-14 10:23:14 +08:00
qingwei.li eff6d21539 bump: 2.4.2 2017-02-14 10:23:08 +08:00
qingwei.li dc536a3930 fix(index): load file path error 2017-02-14 10:22:44 +08:00
qingwei.li cc4ffb4ab7 -> v2.4.1 2017-02-13 23:23:25 +08:00
qingwei.li d62da2034f bump: 2.4.1 2017-02-13 23:23:13 +08:00
qingwei.li dd0c84bb40 fix(index): cover page 2017-02-13 23:21:27 +08:00
qingwei.li 02022af53c -> v2.4.0 2017-02-13 23:08:02 +08:00
qingwei.li 44d7257691 bump: 2.4.0 2017-02-13 23:07:54 +08:00
qingwei.li c6f760275c feat(hook): add doneEach 2017-02-13 23:06:30 +08:00
78 changed files with 4010 additions and 2360 deletions
+1
View File
@@ -4,6 +4,7 @@
"browser": true
},
"globals": {
"Docsify": true,
"$docsify": true
}
}
+17 -63
View File
@@ -1,66 +1,20 @@
### 2.3.0
> 2017-02-13
3.0.1 / 2017-02-19
==================
* fix(route): empty alias
3.0.0 / 2017-02-19
==================
#### Breaking change
- Not support IE9
- The route becomes: `/#/page?id=slug`. The previous route will be redirected
#### Features
- feat(src): add alias feature
- docs: update all documents
- feat(src): dynamic title
- feat(hook): support custom plugin
- feat(themes): add dark theme
#### Bug fixes
- fix(event): `auto2top` has no effect on a FF mobile browser, fixed #67
- fix: sidebar style
- fix(render): fix render link
### 2.2.1
> 2017-02-11
#### Bug fixes
- fix(search): crash when not content, fixed #68
- fix(event): scroll active sidebar
- fix(search): not work in mobile
### 2.2.0
#### Features
- Add `Google Analytics` plugin.
```html
<script src="//unpkg.com/docsify" data-ga="UA-XXXXX-Y"></script>
<script src="//unpkg.com/docsify/lib/plugins/ga.js"></script>
```
### 2.1.0
#### Features
- Add search plugin
```html
<script src="//unpkg.com/docsify"></script>
<script src="//unpkg.com/docsify/lib/plugins/search.js"></script>
```
#### Bug fixes
- fix sidebar style
### 2.0.3
#### Bug fixes
- fix: rendering emojis
- fix: css var polyfill
### 2.0.2
#### Bug fixes
- fix button style in cover page.
### 2.0.1
#### Bug fixes
- border style.
### 2.0.0
#### Features
- Customize the theme color
#### Break change
- Remove `data-router`, `data-sidebar`, `data-sidebar-toggle` APIs
- Faster rendering
- Clearer source code
- More powerful API
- Auto header for each page [#78](https://github.com/QingWei-Li/docsify/issues/78)
- Localization for search plugin [#80](https://github.com/QingWei-Li/docsify/issues/80)
- Execute the script in markdown (see `executeScript`)
+99
View File
@@ -1,3 +1,102 @@
### 2.4.3
> 2017-02-15
#### Bug fixes
* fix emoji replacing error (#76)
### 2.4.2
> 2017-02-14
#### Bug fixes
- fix(index): load file path error
### 2.4.1
> 2017-02-13
#### Bug fixes
- fix(index): cover page
### 2.4.0
> 2017-02-13
#### Features
- feat(hook): add `doneEach`
### 2.3.0
> 2017-02-13
#### Features
- feat(src): add alias feature
- docs: update all documents
- feat(src): dynamic title
- feat(hook): support custom plugin
- feat(themes): add dark theme
#### Bug fixes
- fix(event): `auto2top` has no effect on a FF mobile browser, fixed #67
- fix: sidebar style
- fix(render): fix render link
### 2.2.1
> 2017-02-11
#### Bug fixes
- fix(search): crash when not content, fixed #68
- fix(event): scroll active sidebar
- fix(search): not work in mobile
### 2.2.0
#### Features
- Add `Google Analytics` plugin.
```html
<script src="//unpkg.com/docsify" data-ga="UA-XXXXX-Y"></script>
<script src="//unpkg.com/docsify/lib/plugins/ga.js"></script>
```
### 2.1.0
#### Features
- Add search plugin
```html
<script src="//unpkg.com/docsify"></script>
<script src="//unpkg.com/docsify/lib/plugins/search.js"></script>
```
#### Bug fixes
- fix sidebar style
### 2.0.3
#### Bug fixes
- fix: rendering emojis
- fix: css var polyfill
### 2.0.2
#### Bug fixes
- fix button style in cover page.
### 2.0.1
#### Bug fixes
- border style.
### 2.0.0
#### Features
- Customize the theme color
#### Break change
- Remove `data-router`, `data-sidebar`, `data-sidebar-toggle` APIs
### 1.10.5
#### Bug fixes
- fix initialize the Vue instance
+2 -2
View File
@@ -20,11 +20,11 @@
## Features
- Not build static html files
- Simple and lightweight (~13kB gzipped)
- Simple and lightweight (~14kB gzipped)
- Smart full-text search plugin
- Multiple themes
- Useful plugin API
- Compatible with IE9+
- Compatible with IE10+
## Quick start
Create a `index.html`.
+32 -30
View File
@@ -9,7 +9,7 @@ var build = function (opts) {
rollup
.rollup({
entry: 'src/' + opts.entry,
plugins: [buble()].concat(opts.plugins || [])
plugins: [buble(), commonjs(), nodeResolve()].concat(opts.plugins || [])
})
.then(function (bundle) {
var dest = 'lib/' + (opts.output || opts.entry)
@@ -27,34 +27,36 @@ var build = function (opts) {
}
build({
entry: 'index.js',
output: 'docsify.js',
plugins: [commonjs(), nodeResolve()]
entry: 'core/index.js',
output: 'docsify.js'
})
isProd && build({
entry: 'index.js',
output: 'docsify.min.js',
plugins: [commonjs(), nodeResolve(), uglify()]
})
build({
entry: 'plugins/search.js',
output: 'plugins/search.js',
moduleName: 'D.Search'
})
isProd && build({
entry: 'plugins/search.js',
output: 'plugins/search.min.js',
moduleName: 'D.Search',
plugins: [uglify()]
})
build({
entry: 'plugins/ga.js',
output: 'plugins/ga.js',
moduleName: 'D.GA'
})
isProd && build({
entry: 'plugins/ga.js',
output: 'plugins/ga.min.js',
moduleName: 'D.GA',
plugins: [uglify()]
var plugins = [
{ name: 'search', entry: 'search/index.js', moduleName: 'Search' },
{ name: 'ga', entry: 'ga.js', moduleName: 'GA' }
// { name: 'front-matter', entry: 'front-matter/index.js', moduleName: 'FrontMatter' }
]
plugins.forEach(item => {
build({
entry: 'plugins/' + item.entry,
output: 'plugins/' + item.name + '.js',
moduleName: 'D.' + item.moduleName
})
})
if (isProd) {
build({
entry: 'core/index.js',
output: 'docsify.min.js',
plugins: [uglify()]
})
plugins.forEach(item => {
build({
entry: 'plugins/' + item.entry,
output: 'plugins/' + item.name + '.min.js',
moduleName: 'D.' + item.moduleName,
plugins: [uglify()]
})
})
}
+8 -7
View File
@@ -4,10 +4,12 @@
<meta charset="UTF-8">
<title>docsify</title>
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<link rel="stylesheet" href="/themes/vue.css">
<link rel="stylesheet" href="/themes/vue.css" title="vue">
<link rel="stylesheet" href="/themes/dark.css" title="dark" disabled>
<link rel="stylesheet" href="/themes/buble.css" title="buble" disabled>
</head>
<body>
<nav>
<nav data-cloak>
<a href="#/">EN</a>
<a href="#/zh-cn/">中文</a>
</nav>
@@ -19,16 +21,15 @@
'/zh-cn/changelog': '/changelog',
'/changelog': 'https://raw.githubusercontent.com/QingWei-Li/docsify/master/CHANGELOG'
},
search: {
maxAge: 0
}
executeScript: true
}
</script>
<script
src="/lib/docsify.js"
data-name="docsify"
data-name="sdfsdf"
data-name-link="//www.baidu.com"
data-base-path="/docs/"
data-load-sidebar
data-sub-max-level="2"
data-base-path="docs/"
data-auto2top></script>
</html>
+2 -2
View File
@@ -11,11 +11,11 @@ See the [Quick start](/quickstart) for more details.
## Features
- Not build static html files
- Simple and lightweight (~13kB gzipped)
- Simple and lightweight (~14kB gzipped)
- Smart full-text search plugin
- Multiple themes
- Useful plugin API
- Compatible with IE9+
- Compatible with IE10+
## Examples
+3 -3
View File
@@ -1,13 +1,13 @@
![logo](_media/icon.svg)
# docsify <small>2.3.0</small>
# docsify <small>3.0</small>
> A magical documentation site generator.
- Simple and lightweight (~13kB gzipped)
- Simple and lightweight (~14kB gzipped)
- Not build static html files
- Multiple themes
[GitHub](https://github.com/QingWei-Li/docsify/)
[Get Started](#quick-start)
[Get Started](#docsify)
+1 -1
View File
@@ -4,7 +4,7 @@
- [Custom navbar](/custom-navbar)
- [Cover page](/cover)
- Configuration
- Customization
- [Configuration](/configuration)
- [Themes](/themes)
- [Using plugins](/plugins)
+47 -1
View File
@@ -111,7 +111,7 @@ Add TOC in custom sidebar.
```js
window.$docsify = {
subMaxLevel: 3
subMaxLevel: 2
}
```
@@ -219,6 +219,17 @@ See [Markdown configuration](/markdown).
```js
window.$docsify = {
// object
markdown: {
smartypants: true,
renderer: {
link: function() {
// ...
}
}
},
// function
markdown: function (marked, renderer) {
// ...
return marked
@@ -254,3 +265,38 @@ window.$docsify = {
}
}
```
## auto-header
- type: `Boolean`
If `loadSidebar` and `autoHeader` are both enabled, for each link in _sidebar.md, prepend a header to the page before converting it to html. [#78](https://github.com/QingWei-Li/docsify/issues/78)
```js
window.$docsify = {
loadSidebar: true,
autoHeader: true
}
```
## execute-script
- type: `Boolean`
Execute the script on the page. Only parse the first script tag([demo](themes)). If Vue is present, it is turned on by default.
```js
window.$docsify = {
executeScript: true
}
```
```markdown
## This is test
<script>
console.log(2333)
</script>
```
+1 -1
View File
@@ -1,6 +1,6 @@
# Cover
Activate the cover feature by setting `coverpage`. The detail in [Configuration#coverpage](zh-cn/configuration#coverpage).
Activate the cover feature by setting `coverpage`. The detail in [Configuration#coverpage](configuration#coverpage).
## Basic usage
+5 -2
View File
@@ -7,10 +7,12 @@
<meta name="keywords" content="doc,docs,documentation,gitbook,creator,generator,github,jekyll,github-pages">
<meta name="description" content="A magical documentation generator.">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<link rel="stylesheet" href="//unpkg.com/docsify/lib/themes/vue.css">
<link rel="stylesheet" href="//unpkg.com/docsify/lib/themes/vue.css" title="vue">
<link rel="stylesheet" href="//unpkg.com/docsify/lib/themes/dark.css" title="dark" disabled>
<link rel="stylesheet" href="//unpkg.com/docsify/lib/themes/buble.css" title="buble" disabled>
</head>
<body>
<nav>
<nav data-cloak>
<a href="#/">En</a>
<a href="#/zh-cn/">中文</a>
</nav>
@@ -24,6 +26,7 @@
auto2top: true,
coverpage: true,
loadSidebar: true,
executeScript: true,
alias: {
'/zh-cn/changelog': '/changelog',
'/changelog': 'https://raw.githubusercontent.com/QingWei-Li/docsify/master/CHANGELOG'
+7 -3
View File
@@ -1,12 +1,16 @@
# Markdown configuration
The Markdown parser is [marked](https://github.com/chjj/marked). You can customize how docsify renders your Markdown content to HTML.
The Markdown parser is [marked](https://github.com/chjj/marked). You can customize how docsify renders your Markdown content to HTML. Support customize `renderer`.
```js
window.$docsify = {
markdown: {
smartypants: true
// ...
smartypants: true,
renderer: {
link: function() {
// ...
}
}
}
}
```
+20 -5
View File
@@ -23,7 +23,13 @@ By default, the hyperlink on the current page is recognized and the content is s
search: {
maxAge: 86400000, // Expiration time, the default one day
paths: [], // or 'auto'
placeholder: 'Type to search'
placeholder: 'Type to search',
// Localization
placeholder: {
'/zh-cn/': '搜索',
'/': 'Type to search'
}
}
}
</script>
@@ -65,9 +71,9 @@ The hook supports handling asynchronous tasks.
```js
window.$docsify = {
plugins: [
function (hook) {
function (hook, vm) {
hook.init(function() {
// Called when the script starts running, only trigger once.
// Called when the script starts running, only trigger once, no arguments,
})
hook.beforeEach(function(content) {
@@ -84,15 +90,24 @@ window.$docsify = {
next(html)
})
hook.doneEach(function() {
// Invoked each time after the data is fully loaded, no arguments,
// ...
})
hook.mounted(function() {
// Called after initial completion. Only trigger once, no arguments.
})
hook.ready(function() {
// Called after initialization is complete. Only trigger once.
// Called after initial completion, no arguments.
})
}
]
}
```
!> You can get internal methods through `window.Docsify.utils`.
!> You can get internal methods through `window.Docsify`. Get the current instance through the second argument.
#### Example
+22
View File
@@ -61,3 +61,25 @@ If your system has Python, you can easily to run a static server to preview your
```bash
cd docs && python -m SimpleHTTPServer 3000
```
## Loading tip
Show `Loading...` Before starting rendering. You can customize the content.
*index.html*
```html
<div id="app">Please wait...</div>
```
You should set the `data-app` attribute if you changed `el`.
*index.html*
```html
<div data-app id="main">Please wait...</div>
<script>
window.$docsify = {
el: '#main'
}
</script>
```
+34
View File
@@ -12,3 +12,37 @@ There are currently three themes available. Copy [Vue](//vuejs.org) and [buble](
!> This compressed files in `/lib/themes/`.
If you have any ideas or would like to develop new theme, welcome submit [PR](https://github.com/QingWei-Li/docsify/pulls).
#### Click to preview
<div class="demo-theme-preview">
<a data-theme="vue">vue.css</a>
<a data-theme="buble">buble.css</a>
<a data-theme="dark">dark.css</a>
</div>
<style>
.demo-theme-preview a {
padding-right: 10px;
}
.demo-theme-preview a:hover {
text-decoration: underline;
cursor: pointer;
}
</style>
<script>
var preview = Docsify.dom.find('.demo-theme-preview');
var themes = Docsify.dom.findAll('[rel="stylesheet"]');
preview.onclick = function (e) {
var title = e.target.getAttribute('data-theme')
themes.forEach(function (theme) {
theme.disabled = theme.title !== title
});
};
</script>
+2 -2
View File
@@ -13,7 +13,7 @@ Load the Vue in `index.html`.
```
Then you can immediately write Vue code at Markdown file.
`new Vue({ el: 'main' })` script is executed by default to create instance.
`new Vue({ el: '#main' })` script is executed by default to create instance.
*README.md*
@@ -44,7 +44,7 @@ You can manually initialize a Vue instance.
<script>
new Vue({
el: 'main',
el: '#main',
data: { msg: 'Vue' }
})
</script>
+3 -3
View File
@@ -12,11 +12,11 @@ docsify 是一个动态生成文档网站的工具。不同于 GitBook、Hexo
## 特性
- 无需构建,写完文档直接发布
- 容易使用并且轻量 (~13Kb gzipped)
- 智能的全文
- 容易使用并且轻量 (~14kB gzipped)
- 智能的全文
- 提供多套主题
- 丰富的 API
- 兼容 IE9+
- 兼容 IE10+
## 例子
+1 -1
View File
@@ -4,7 +4,7 @@
- [定制导航栏](zh-cn/custom-navbar)
- [封面](zh-cn/cover)
- 配置
- 定制化
- [配置项](zh-cn/configuration)
- [主题](zh-cn/themes)
- [使用插件](zh-cn/plugins)
+49 -4
View File
@@ -108,12 +108,12 @@ window.$docsify = {
- 类型:`Number`
- 默认值: `0`
自定义侧边栏后默认不会再生成目录,你也可以通过设置生成目录的最大层级开启这个功能
自定义侧边栏后默认不会再生成目录,你也可以通过设置生成目录的最大层级开启这个功能
```js
window.$docsify = {
subMaxLevel: 3
subMaxLevel: 2
}
```
@@ -212,12 +212,23 @@ window.$docsify = {
## markdown
- 类型: `Function`
- 类型: `Object|Function`
参考 [Markdown 配置](/zh-cn/markdown)。
```js
window.$docsify = {
// object
markdown: {
smartypants: true,
renderer: {
link: function() {
// ...
}
}
},
// function
markdown: function (marked, renderer) {
// ...
return marked
@@ -229,7 +240,7 @@ window.$docsify = {
- 类型:`String`
替换主题色。利用 [CSS3 支持变量]((https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_variables)的特性,对于老的浏览器有 polyfill 处理。
替换主题色。利用 [CSS3 支持变量](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_variables)的特性,对于老的浏览器有 polyfill 处理。
```js
window.$docsify = {
@@ -253,3 +264,37 @@ window.$docsify = {
}
```
## auto-header
- 类型:`Boolean`
同时设置 `loadSidebar``autoHeader` 后,可以根据 `_sidebar.md` 的内容自动为每个页面增加标题。[#78](https://github.com/QingWei-Li/docsify/issues/78)
```js
window.$docsify = {
loadSidebar: true,
autoHeader: true
}
```
## execute-script
- 类型:`Boolean`
执行文档里的 script 标签里的脚本,只执行第一个 script ([demo](zh-cn/themes))。 如果 Vue 存在,则自动开启。
```js
window.$docsify = {
executeScript: true
}
```
```markdown
## This is test
<script>
console.log(2333)
</script>
```
+7 -3
View File
@@ -1,12 +1,16 @@
# Markdown 配置
内置的 Markdown 解析器是 [marked](https://github.com/chjj/marked),可以修改它的配置。
内置的 Markdown 解析器是 [marked](https://github.com/chjj/marked),可以修改它的配置。同时可以直接配置 `renderer`
```js
window.$docsify = {
markdown: {
smartypants: true
// ...
smartypants: true,
renderer: {
link: function() {
// ...
}
}
}
}
```
+22 -7
View File
@@ -2,9 +2,9 @@
## 内置插件
### 全文索 - Search
### 全文索 - Search
全文索插件会根据当前页面上的超链接获取文档内容,在 `localStorage` 内建立文档索引。默认过期时间为一天,当然我们可以自己指定需要缓存的文件列表或者配置过期时间。
全文索插件会根据当前页面上的超链接获取文档内容,在 `localStorage` 内建立文档索引。默认过期时间为一天,当然我们可以自己指定需要缓存的文件列表或者配置过期时间。
```html
@@ -23,7 +23,13 @@
search: {
maxAge: 86400000, // 过期时间,单位毫秒,默认一天
paths: [], // or 'auto'
placeholder: 'Type to search'
placeholder: 'Type to search',
// 支持本地化
placeholder: {
'/zh-cn/': '搜索',
'/': 'Type to search'
}
}
}
</script>
@@ -61,9 +67,9 @@ docsify 提供了一套插件机制,其中提供的钩子(hook)支持处
```js
window.$docsify = {
plugins: [
function (hook) {
function (hook, vm) {
hook.init(function() {
// 初始化时调用,只调用一次
// 初始化时调用,只调用一次,没有参数。
})
hook.beforeEach(function(content) {
@@ -79,15 +85,24 @@ window.$docsify = {
next(html)
})
hook.doneEach(function() {
// 每次路由切换时数据全部加载完成后调用,没有参数。
// ...
})
hook.mounted(function() {
// 初始化完成后调用 ,只调用一次,没有参数。
})
hook.ready(function() {
// docsify 初始化完成后调用,只调用一次
// 初始化并第一次加完成数据后调用,没有参数。
})
}
]
}
```
!> 如果需要用 docsify 的内部方法,可以通过 `window.Docsify.utils` 获取。
!> 如果需要用 docsify 的内部方法,可以通过 `window.Docsify` 获取,通过 `vm` 获取当前实例
#### 例子
+23
View File
@@ -59,3 +59,26 @@ docsify serve docs
```bash
cd docs && python -m SimpleHTTPServer 3000
```
## Loading 提示
初始化时会显示 `Loading...` 内容,你可以自定义提示信息。
*index.html*
```html
<div id="app">加载中</div>
```
如果更改了 `el` 的配置,需要将该元素加上 `data-app` 属性。
*index.html*
```html
<div data-app id="main">加载中</div>
<script>
window.$docsify = {
el: '#main'
}
</script>
```
+35 -1
View File
@@ -10,4 +10,38 @@
!> CSS 的压缩文件位于 `/lib/themes/`
如果你有其他想法或者想开发别的主题,欢迎提 [PR](https://github.com/QingWei-Li/docsify/pulls)。
如果你有其他想法或者想开发别的主题,欢迎提 [PR](https://github.com/QingWei-Li/docsify/pulls)。
#### 点击切换主题
<div class="demo-theme-preview">
<a data-theme="vue">vue.css</a>
<a data-theme="buble">buble.css</a>
<a data-theme="dark">dark.css</a>
</div>
<style>
.demo-theme-preview a {
padding-right: 10px;
}
.demo-theme-preview a:hover {
text-decoration: underline;
cursor: pointer;
}
</style>
<script>
var preview = Docsify.dom.find('.demo-theme-preview');
var themes = Docsify.dom.findAll('[rel="stylesheet"]');
preview.onclick = function (e) {
var title = e.target.getAttribute('data-theme')
themes.forEach(function (theme) {
theme.disabled = theme.title !== title
});
};
</script>
+2 -2
View File
@@ -12,7 +12,7 @@
<script src="//unpkg.com/docsify"></script>
```
接着就可以愉快地在 Markdown 里写 Vue 了。默认会执行 `new Vue({ el: 'main' })` 创建示例。
接着就可以愉快地在 Markdown 里写 Vue 了。默认会执行 `new Vue({ el: '#main' })` 创建示例。
*README.md*
@@ -43,7 +43,7 @@
<script>
new Vue({
el: 'main',
el: '#main',
data: { msg: 'Vue' }
})
</script>
+1188 -746
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
File diff suppressed because one or more lines are too long
+4 -13
View File
@@ -1,9 +1,8 @@
this.D = this.D || {};
this.D.GA = (function () {
(function () {
'use strict';
// From https://github.com/egoist/vue-ga/blob/master/src/index.js
function appendScript () {
var script = document.createElement('script');
script.async = true;
@@ -28,23 +27,15 @@ function collect () {
window.ga('send', 'pageview');
}
var install = function () {
if (install.installed) { return }
install.installed = true;
var install = function (hook) {
if (!window.$docsify.ga) {
console.error('[Docsify] ga is required.');
return
}
window.$docsify.plugins = [].concat(function (hook) {
hook.init(collect);
hook.beforeEach(collect);
}, window.$docsify.plugins);
hook.beforeEach(collect);
};
var ga = install();
return ga;
window.$docsify.plugins = [].concat(install, window.$docsify.plugins);
}());
+1 -1
View File
@@ -1 +1 @@
this.D=this.D||{},this.D.GA=function(){"use strict";function i(){var i=document.createElement("script");i.async=!0,i.src="https://www.google-analytics.com/analytics.js",document.body.appendChild(i)}function n(n){window.ga||(i(),window.ga=window.ga||function(){(window.ga.q=window.ga.q||[]).push(arguments)},window.ga.l=Number(new Date),window.ga("create",n,"auto"))}function o(){n(window.$docsify.ga),window.ga("set","page",location.href),window.ga("send","pageview")}var t=function(){if(!t.installed)return t.installed=!0,window.$docsify.ga?void(window.$docsify.plugins=[].concat(function(i){i.init(o),i.beforeEach(o)},window.$docsify.plugins)):void console.error("[Docsify] ga is required.")},a=t();return a}();
this.D=this.D||{},function(){"use strict";function n(){var n=document.createElement("script");n.async=!0,n.src="https://www.google-analytics.com/analytics.js",document.body.appendChild(n)}function o(o){window.ga||(n(),window.ga=window.ga||function(){(window.ga.q=window.ga.q||[]).push(arguments)},window.ga.l=Number(new Date),window.ga("create",o,"auto"))}function i(){o(window.$docsify.ga),window.ga("set","page",location.href),window.ga("send","pageview")}var w=function(n){return window.$docsify.ga?void n.beforeEach(i):void console.error("[Docsify] ga is required.")};window.$docsify.plugins=[].concat(w,window.$docsify.plugins)}();
+158 -185
View File
@@ -1,166 +1,82 @@
this.D = this.D || {};
this.D.Search = (function () {
(function () {
'use strict';
var INDEXS = {};
var CONFIG = {
placeholder: 'Type to search',
paths: 'auto',
maxAge: 86400000 // 1 day
};
var helper;
var isObj = function (obj) {
return Object.prototype.toString.call(obj) === '[object Object]'
};
var escapeHtml = function (string) {
function escapeHtml (string) {
var entityMap = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#39;',
'\'': '&#39;',
'/': '&#x2F;'
};
return String(string).replace(/[&<>"'\/]/g, function (s) { return entityMap[s]; })
};
}
/**
* find all filepath from A tag
*/
var getAllPaths = function () {
var paths = [];[].slice.call(document.querySelectorAll('a'))
function getAllPaths () {
var paths = [];
helper.dom.findAll('a')
.map(function (node) {
var href = node.href;
if (/#\/[^#]*?$/.test(href)) {
var path = href.replace(/^[^#]+#/, '');
var originHref = node.getAttribute('href');
var path = helper.route.parse(href).path;
if (paths.indexOf(path) <= 0) { paths.push(path); }
if (path &&
paths.indexOf(path) === -1 &&
!helper.route.isAbsolutePath(originHref)) {
paths.push(path);
}
});
return paths
};
}
/**
* return file path
*/
var genFilePath = function (path, basePath) {
if ( basePath === void 0 ) basePath = window.$docsify.basePath;
function saveData (maxAge) {
localStorage.setItem('docsify.search.expires', Date.now() + maxAge);
localStorage.setItem('docsify.search.index', JSON.stringify(INDEXS));
}
var filePath = /\/$/.test(path) ? (path + "README.md") : (path + ".md");
filePath = basePath + filePath;
return filePath.replace(/\/+/g, '/')
};
/**
* generate index
*/
var genIndex = function (path, content) {
function genIndex (path, content) {
if ( content === void 0 ) content = '';
INDEXS[path] = { slug: '', title: '', body: '' };
var tokens = window.marked.lexer(content);
var toURL = Docsify.route.toURL;
var index = {};
var slug;
content
// remove PRE and TEMPLATE tag
.replace(/<template[^>]*?>[\s\S]+?<\/template>/g, '')
// find all html tag
.replace(/<(\w+)([^>]*?)>([\s\S]+?)<\//g, function (match, tag, attr, html) {
// remove all html tag
var text = html.replace(/<[^>]+>/g, '');
// tag is headline
if (/^h\d$/.test(tag)) {
// <h1 id="xxx"></h1>
var id = attr.match(/id="(\S+)"/)[1];
slug = ("#/" + path + "#" + id).replace(/\/+/, '/');
INDEXS[slug] = { slug: slug, title: text, body: '' };
} else {
if (!slug) { return }
// other html tag
if (!INDEXS[slug]) {
INDEXS[slug] = {};
} else {
if (INDEXS[slug].body && INDEXS[slug].body.length) {
INDEXS[slug].body += '\n' + text;
} else {
INDEXS[slug].body = text;
}
}
}
});
};
/**
* component
*/
var SearchComponent = function SearchComponent () {
if (this.rendered) { return }
this.style();
var el = document.createElement('div');
var aside = document.querySelector('aside');
el.classList.add('search');
aside.insertBefore(el, aside.children[0]);
this.render(el);
this.rendered = true;
this.bindEvent();
};
SearchComponent.prototype.style = function style () {
var code = "\n .sidebar {\n padding-top: 0;\n }\n\n .search {\n margin-bottom: 20px;\n padding: 6px;\n border-bottom: 1px solid #eee;\n }\n\n .search .results-panel {\n display: none;\n }\n\n .search .results-panel.show {\n display: block;\n }\n\n .search input {\n outline: none;\n border: none;\n width: 100%;\n padding: 7px;\n line-height: 22px;\n font-size: 14px;\n }\n\n .search h2 {\n font-size: 17px;\n margin: 10px 0;\n }\n\n .search a {\n text-decoration: none;\n color: inherit;\n }\n\n .search .matching-post {\n border-bottom: 1px solid #eee;\n }\n\n .search .matching-post:last-child {\n border-bottom: 0;\n }\n\n .search p {\n font-size: 14px;\n overflow: hidden;\n text-overflow: ellipsis;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n }\n\n .search p.empty {\n text-align: center;\n }\n ";
var style = document.createElement('style');
style.innerHTML = code;
document.head.appendChild(style);
};
SearchComponent.prototype.render = function render (dom) {
dom.innerHTML = "<input type=\"search\" placeholder=\"" + (CONFIG.placeholder) + "\" /><div class=\"results-panel\"></div>";
};
SearchComponent.prototype.bindEvent = function bindEvent () {
var this$1 = this;
var search = document.querySelector('.search');
var input = search.querySelector('.search input');
var panel = search.querySelector('.results-panel');
search.addEventListener('click', function (e) { return e.target.tagName !== 'A' && e.stopPropagation(); });
input.addEventListener('input', function (e) {
var target = e.target;
if (target.value.trim() !== '') {
var matchingPosts = this$1.search(target.value);
var html = '';
matchingPosts.forEach(function (post, index) {
html += "\n <div class=\"matching-post\">\n <h2><a href=\"" + (post.url) + "\">" + (post.title) + "</a></h2>\n <p>" + (post.content) + "</p>\n </div>\n ";
});
if (panel.classList.contains('results-panel')) {
panel.classList.add('show');
panel.innerHTML = html || '<p class="empty">No Results!</p>';
}
tokens.forEach(function (token) {
if (token.type === 'heading' && token.depth <= 2) {
slug = toURL(path, { id: token.text });
index[slug] = { slug: slug, title: token.text, body: '' };
} else {
if (panel.classList.contains('results-panel')) {
panel.classList.remove('show');
panel.innerHTML = '';
if (!slug) { return }
if (!index[slug]) {
index[slug] = { slug: slug, title: '', body: '' };
} else {
if (index[slug].body) {
index[slug].body += '\n' + (token.text || '');
} else {
index[slug].body = token.text;
}
}
}
});
};
// From [weex website] https://weex-project.io/js/common.js
SearchComponent.prototype.search = function search (keywords) {
return index
}
function search (keywords) {
var matchingResults = [];
var data = Object.keys(INDEXS).map(function (key) { return INDEXS[key]; });
var data = [];
Object.keys(INDEXS).forEach(function (key) {
data = data.concat(Object.keys(INDEXS[key]).map(function (page) { return INDEXS[key][page]; }));
});
keywords = keywords.trim().split(/[\s\-\\\/]+/);
@@ -196,7 +112,7 @@ SearchComponent.prototype.search = function search (keywords) {
if (end > postContent.length) { end = postContent.length; }
var matchContent = '...' +
postContent
escapeHtml(postContent)
.substring(start, end)
.replace(regEx, ("<em class=\"search-keyword\">" + keyword + "</em>")) +
'...';
@@ -217,13 +133,15 @@ SearchComponent.prototype.search = function search (keywords) {
}
};
for (var i = 0; i < data.length; i++) loop( i );
for (var i = 0; i < data.length; i++) loop( i );
return matchingResults
};
}
var searchPlugin = function () {
var isAuto = CONFIG.paths === 'auto';
function init$1 (config, vm) {
helper = Docsify;
var isAuto = config.paths === 'auto';
var isExpired = localStorage.getItem('docsify.search.expires') < Date.now();
INDEXS = JSON.parse(localStorage.getItem('docsify.search.index'));
@@ -234,68 +152,123 @@ var searchPlugin = function () {
return
}
var count = 0;
var paths = isAuto ? getAllPaths() : CONFIG.paths;
var paths = isAuto ? getAllPaths() : config.paths;
var len = paths.length;
var ref = window.Docsify.utils;
var load = ref.load;
var marked = ref.marked;
var slugify = ref.slugify;
var alias = window.$docsify.alias;
var done = function () {
localStorage.setItem('docsify.search.expires', Date.now() + CONFIG.maxAge);
localStorage.setItem('docsify.search.index', JSON.stringify(INDEXS));
};
var count = 0;
paths.forEach(function (path) {
if (INDEXS[path]) { return count++ }
var route;
// replace route
if (alias && alias[path]) {
route = genFilePath(alias[path] || path, '');
} else {
route = genFilePath(path);
helper
.get(vm.$getFile(path))
.then(function (result) {
INDEXS[path] = genIndex(path, result);
len === ++count && saveData(config.maxAge);
});
});
}
var dom;
function style () {
var code = "\n.sidebar {\n padding-top: 0;\n}\n\n.search {\n margin-bottom: 20px;\n padding: 6px;\n border-bottom: 1px solid #eee;\n}\n\n.search .results-panel {\n display: none;\n}\n\n.search .results-panel.show {\n display: block;\n}\n\n.search input {\n outline: none;\n border: none;\n width: 100%;\n padding: 7px;\n line-height: 22px;\n font-size: 14px;\n}\n\n.search h2 {\n font-size: 17px;\n margin: 10px 0;\n}\n\n.search a {\n text-decoration: none;\n color: inherit;\n}\n\n.search .matching-post {\n border-bottom: 1px solid #eee;\n}\n\n.search .matching-post:last-child {\n border-bottom: 0;\n}\n\n.search p {\n font-size: 14px;\n overflow: hidden;\n text-overflow: ellipsis;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n}\n\n.search p.empty {\n text-align: center;\n}";
var style = dom.create('style', code);
dom.appendTo(dom.head, style);
}
function tpl (opts) {
var html =
"<input type=\"search\" />" +
'<div class="results-panel"></div>' +
'</div>';
var el = dom.create('div', html);
var aside = dom.find('aside');
dom.toggleClass(el, 'search');
dom.before(aside, el);
}
function bindEvents () {
var $search = dom.find('div.search');
var $input = dom.find($search, 'input');
var $panel = dom.find($search, '.results-panel');
// Prevent to Fold sidebar
dom.on($search, 'click',
function (e) { return e.target.tagName !== 'A' && e.stopPropagation(); });
dom.on($input, 'input', function (e) {
var value = e.target.value.trim();
if (!value) {
$panel.classList.remove('show');
$panel.innerHTML = '';
return
}
var matchs = search(value);
load(route).then(function (content) {
genIndex(path, marked(content));
slugify.clear();
count++;
var html = '';
if (len === count) { done(); }
matchs.forEach(function (post) {
html += "<div class=\"matching-post\">\n <h2><a href=\"" + (post.url) + "\">" + (post.title) + "</a></h2>\n <p>" + (post.content) + "</p>\n</div>";
});
$panel.classList.add('show');
$panel.innerHTML = html || '<p class="empty">No Results!</p>';
});
}
function updatePlaceholder (text, path) {
var $input = dom.getNode('.search input[type="search"]');
if (typeof text === 'string') {
$input.placeholder = text;
} else {
var match = Object.keys(text).find(function (key) { return path.indexOf(key) > -1; });
$input.placeholder = text[match];
}
}
function init$$1 (opts) {
dom = Docsify.dom;
style();
tpl(opts);
bindEvents();
}
function update (opts, vm) {
updatePlaceholder(opts.placeholder, vm.route.path);
}
var CONFIG = {
placeholder: 'Type to search',
paths: 'auto',
maxAge: 86400000 // 1 day
};
var install = function (hook, vm) {
var util = Docsify.util;
var opts = vm.config.search || CONFIG;
if (Array.isArray(opts)) {
CONFIG.paths = opts;
} else if (typeof opts === 'object') {
CONFIG.paths = Array.isArray(opts.paths) ? opts.paths : 'auto';
CONFIG.maxAge = util.isPrimitive(opts.maxAge) ? opts.maxAge : CONFIG.maxAge;
CONFIG.placeholder = opts.placeholder || CONFIG.placeholder;
}
var isAuto = CONFIG.paths === 'auto';
hook.mounted(function (_) {
init$$1(CONFIG);
!isAuto && init$1(CONFIG, vm);
});
hook.doneEach(function (_) {
update(CONFIG, vm);
isAuto && init$1(CONFIG, vm);
});
};
var install = function () {
if (install.installed) { return }
install.installed = true;
var userConfig = window.$docsify.search;
var isNil = window.Docsify.utils.isNil;
if (Array.isArray(userConfig)) {
CONFIG.paths = userConfig;
} else if (isObj(userConfig)) {
CONFIG.paths = Array.isArray(userConfig.paths) ? userConfig.paths : 'auto';
CONFIG.maxAge = isNil(userConfig.maxAge) ? CONFIG.maxAge : userConfig.maxAge;
CONFIG.placeholder = userConfig.placeholder || CONFIG.placeholder;
}
window.$docsify.plugins = [].concat(function (hook) {
var isAuto = CONFIG.paths === 'auto';
hook.ready(function () {
new SearchComponent();
!isAuto && searchPlugin();
});
isAuto && hook.beforeEach(searchPlugin);
}, window.$docsify.plugins);
};
var search = install();
return search;
window.$docsify.plugins = [].concat(install, window.$docsify.plugins);
}());
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "docsify",
"version": "2.3.0",
"version": "3.0.1",
"description": "A magical documentation generator.",
"main": "lib/docsify.js",
"files": [
@@ -12,7 +12,7 @@
"build": "rm -rf lib themes && node build/build.js && mkdir lib/themes && mkdir themes && node build/build-css.js",
"dev:build": "rm -rf lib themes && mkdir themes && node build/build.js --dev && node build/build-css.js --dev",
"dev": "node app.js & nodemon -w src -e js,css --exec 'npm run dev:build'",
"test": "eslint src test"
"test": "eslint src"
},
"repository": {
"type": "git",
+44
View File
@@ -0,0 +1,44 @@
import { merge, hyphenate, isPrimitive } from './util/core'
const config = merge({
el: '#app',
repo: '',
maxLevel: 6,
subMaxLevel: 0,
loadSidebar: null,
loadNavbar: null,
homepage: 'README.md',
coverpage: '',
basePath: '',
auto2top: false,
name: '',
themeColor: '',
nameLink: window.location.pathname,
autoHeader: false,
executeScript: false,
ga: ''
}, window.$docsify)
const script = document.currentScript ||
[].slice.call(document.getElementsByTagName('script'))
.filter(n => /docsify\./.test(n.src))[0]
if (script) {
for (const prop in config) {
const val = script.getAttribute('data-' + hyphenate(prop))
if (isPrimitive(val)) {
config[prop] = val === '' ? true : val
}
}
if (config.loadSidebar === true) config.loadSidebar = '_sidebar.md'
if (config.loadNavbar === true) config.loadNavbar = '_navbar.md'
if (config.coverpage === true) config.coverpage = '_coverpage.md'
if (config.repo === true) config.repo = ''
if (config.name === true) config.name = ''
}
window.$docsify = config
export default config
+22
View File
@@ -0,0 +1,22 @@
import { isMobile } from '../util/env'
import { body, on } from '../util/dom'
import * as sidebar from './sidebar'
import { scrollIntoView } from './scroll'
export function eventMixin (proto) {
proto.$resetEvents = function () {
scrollIntoView(this.route.query.id)
sidebar.getAndActive('nav')
}
}
export function initEvent (vm) {
// Bind toggle button
sidebar.btn('button.sidebar-toggle')
// Bind sticky effect
if (vm.config.coverpage) {
!isMobile && on('scroll', sidebar.sticky)
} else {
body.classList.add('sticky')
}
}
+89
View File
@@ -0,0 +1,89 @@
import { isMobile } from '../util/env'
import * as dom from '../util/dom'
import { parse } from '../route/hash'
export function scrollActiveSidebar () {
if (isMobile) return
let hoverOver = false
const anchors = dom.findAll('.anchor')
const sidebar = dom.find('.sidebar')
const wrap = dom.find(sidebar, '.sidebar-nav')
const nav = {}
const lis = dom.findAll(sidebar, 'li')
let active = dom.find(sidebar, 'li.active')
for (let i = 0, len = lis.length; i < len; i += 1) {
const li = lis[i]
const a = li.querySelector('a')
if (!a) continue
let href = a.getAttribute('href')
if (href !== '/') {
href = parse(href).query.id
}
nav[decodeURIComponent(href)] = li
}
function highlight () {
const top = dom.body.scrollTop
let last
for (let i = 0, len = anchors.length; i < len; i += 1) {
const node = anchors[i]
if (node.offsetTop > top) {
if (!last) last = node
break
} else {
last = node
}
}
if (!last) return
const li = nav[last.getAttribute('data-id')]
if (!li || li === active) return
active && active.classList.remove('active')
li.classList.add('active')
active = li
// scroll into view
// https://github.com/vuejs/vuejs.org/blob/master/themes/vue/source/js/common.js#L282-L297
if (!hoverOver && dom.body.classList.contains('sticky')) {
const height = sidebar.clientHeight
const curOffset = 0
const cur = active.offsetTop + active.clientHeight + 40
const isInView = (
active.offsetTop >= wrap.scrollTop &&
cur <= wrap.scrollTop + height
)
const notThan = cur - curOffset < height
const top = isInView
? wrap.scrollTop
: notThan
? curOffset
: cur - height
sidebar.scrollTop = top
}
}
dom.off('scroll', highlight)
dom.on('scroll', highlight)
dom.on(sidebar, 'mouseover', () => { hoverOver = true })
dom.on(sidebar, 'mouseleave', () => { hoverOver = false })
}
export function scrollIntoView (id) {
const section = dom.find('#' + id)
section && section.scrollIntoView()
}
const scrollEl = dom.$.scrollingElement || dom.$.documentElement
export function scroll2Top (offset = 0) {
scrollEl.scrollTop = offset === true ? 0 : Number(offset)
}
+70
View File
@@ -0,0 +1,70 @@
import { isMobile } from '../util/env'
import * as dom from '../util/dom'
import { getHash } from '../route/hash'
const title = dom.$.title
/**
* Toggle button
*/
export function btn (el) {
const toggle = () => dom.body.classList.toggle('close')
el = dom.getNode(el)
dom.on(el, 'click', toggle)
if (isMobile) {
const sidebar = dom.getNode('.sidebar')
dom.on(sidebar, 'click', () => {
toggle()
setTimeout(() => getAndActive(sidebar, true, true), 0)
})
}
}
export function sticky () {
const cover = dom.getNode('section.cover')
if (!cover) return
const coverHeight = cover.getBoundingClientRect().height
if (window.pageYOffset >= coverHeight || cover.classList.contains('hidden')) {
dom.toggleClass(dom.body, 'add', 'sticky')
} else {
dom.toggleClass(dom.body, 'remove', 'sticky')
}
}
/**
* Get and active link
* @param {string|element} el
* @param {Boolean} isParent acitve parent
* @param {Boolean} autoTitle auto set title
* @return {element}
*/
export function getAndActive (el, isParent, autoTitle) {
el = dom.getNode(el)
const links = dom.findAll(el, 'a')
const hash = '#' + getHash()
let target
links
.sort((a, b) => b.href.length - a.href.length)
.forEach(a => {
const href = a.getAttribute('href')
const node = isParent ? a.parentNode : a
if (hash.indexOf(href) === 0 && !target) {
target = a
dom.toggleClass(node, 'add', 'active')
} else {
dom.toggleClass(node, 'remove', 'active')
}
})
if (autoTitle) {
dom.$.title = target ? `${target.innerText} - ${title}` : title
}
return target
}
+54
View File
@@ -0,0 +1,54 @@
import progressbar from '../render/progressbar'
import { noop } from '../util/core'
const cache = {}
const RUN_VERSION = Date.now()
/**
* Simple ajax get
* @param {string} url
* @param {boolean} [hasBar=false] has progress bar
* @return { then(resolve, reject), abort }
*/
export function get (url, hasBar = false) {
const xhr = new XMLHttpRequest()
const on = function () {
xhr.addEventListener.apply(xhr, arguments)
}
url += (/\?(\w+)=/g.test(url) ? '&' : '?') + `v=${RUN_VERSION}`
if (cache[url]) {
return { then: cb => cb(cache[url]), abort: noop }
}
xhr.open('GET', url)
xhr.send()
return {
then: function (success, error = noop) {
if (hasBar) {
const id = setInterval(_ => progressbar({
step: Math.floor(Math.random() * 5 + 1)
}), 500)
on('progress', progressbar)
on('loadend', evt => {
progressbar(evt)
clearInterval(id)
})
}
on('error', error)
on('load', ({ target }) => {
if (target.status >= 400) {
error(target)
} else {
cache[url] = target.response
success(target.response)
}
})
},
abort: _ => xhr.readyState !== 4 && xhr.abort()
}
}
+72
View File
@@ -0,0 +1,72 @@
import { get } from './ajax'
import { callHook } from '../init/lifecycle'
import { getRoot } from '../route/util'
import { noop } from '../util/core'
export function fetchMixin (proto) {
let last
proto._fetch = function (cb = noop) {
const { path } = this.route
const { loadNavbar, loadSidebar } = this.config
const root = getRoot(path)
// Abort last request
last && last.abort && last.abort()
last = get(this.$getFile(path), true)
// Current page is html
this.isHTML = /\.html$/g.test(path)
// Load main content
last.then(text => {
this._renderMain(text)
if (!loadSidebar) return cb()
const fn = result => { this._renderSidebar(result); cb() }
// Load sidebar
get(this.$getFile(root + loadSidebar))
// fallback root navbar when fail
.then(fn, _ => get(loadSidebar).then(fn))
},
_ => this._renderMain(null))
// Load nav
loadNavbar &&
get(this.$getFile(root + loadNavbar))
.then(
text => this._renderNav(text),
// fallback root navbar when fail
_ => get(loadNavbar).then(text => this._renderNav(text))
)
}
proto._fetchCover = function () {
const { coverpage } = this.config
const root = getRoot(this.route.path)
const path = this.$getFile(root + coverpage)
if (this.route.path !== '/' || !coverpage) {
this._renderCover()
return
}
this.coverIsHTML = /\.html$/g.test(path)
get(path)
.then(text => this._renderCover(text))
}
proto.$fetch = function (cb = noop) {
this._fetchCover()
this._fetch(result => {
this.$resetEvents()
callHook(this, 'doneEach')
cb()
})
}
}
export function initFetch (vm) {
vm.$fetch(_ => callHook(vm, 'ready'))
}
+13
View File
@@ -0,0 +1,13 @@
import * as util from './util'
import * as dom from './util/dom'
import * as render from './render/compiler'
import * as route from './route/hash'
import { get } from './fetch/ajax'
import marked from 'marked'
import prism from 'prismjs'
export default function () {
window.Docsify = { util, dom, render, route, get }
window.marked = marked
window.Prism = prism
}
+29
View File
@@ -0,0 +1,29 @@
import { initMixin } from './init'
import { routeMixin } from './route'
import { renderMixin } from './render'
import { fetchMixin } from './fetch'
import { eventMixin } from './event'
import initGlobalAPI from './global-api'
function Docsify () {
this._init()
}
const proto = Docsify.prototype
initMixin(proto)
routeMixin(proto)
renderMixin(proto)
fetchMixin(proto)
eventMixin(proto)
/**
* Global API
*/
initGlobalAPI()
/**
* Run Docsify
*/
setTimeout(_ => new Docsify(), 0)
+27
View File
@@ -0,0 +1,27 @@
import config from '../config'
import { initLifecycle, callHook } from './lifecycle'
import { initRender } from '../render'
import { initRoute } from '../route'
import { initEvent } from '../event'
import { initFetch } from '../fetch'
import { isFn } from '../util/core'
export function initMixin (proto) {
proto._init = function () {
const vm = this
vm.config = config || {}
initLifecycle(vm) // Init hooks
initPlugin(vm) // Install plugins
callHook(vm, 'init')
initRender(vm) // Render base DOM
initEvent(vm) // Bind events
initRoute(vm) // Add hashchange eventListener
initFetch(vm) // Fetch data
callHook(vm, 'mounted')
}
}
function initPlugin (vm) {
[].concat(vm.config.plugins).forEach(fn => isFn(fn) && fn(vm._lifecycle, vm))
}
+48
View File
@@ -0,0 +1,48 @@
import { noop } from '../util/core'
export function initLifecycle (vm) {
const hooks = [
'init',
'mounted',
'beforeEach',
'afterEach',
'doneEach',
'ready'
]
vm._hooks = {}
vm._lifecycle = {}
hooks.forEach(hook => {
const arr = vm._hooks[hook] = []
vm._lifecycle[hook] = fn => arr.push(fn)
})
}
export function callHook (vm, hook, data, next = noop) {
let newData = data
const queue = vm._hooks[hook]
const step = function (index) {
const hook = queue[index]
if (index >= queue.length) {
next(newData)
} else {
if (typeof hook === 'function') {
if (hook.length === 2) {
hook(data, result => {
newData = result
step(index + 1)
})
} else {
const result = hook(data)
newData = result !== undefined ? result : newData
step(index + 1)
}
} else {
step(index + 1)
}
}
}
step(0)
}
+139
View File
@@ -0,0 +1,139 @@
import marked from 'marked'
import Prism from 'prismjs'
import { helper as helperTpl, tree as treeTpl } from './tpl'
import { genTree } from './gen-tree'
import { slugify, clearSlugCache } from './slugify'
import { emojify } from './emojify'
import { toURL, parse } from '../route/hash'
import { getBasePath, isAbsolutePath, getPath } from '../route/util'
import { isFn, merge, cached } from '../util/core'
let markdownCompiler = marked
let contentBase = ''
let currentPath = ''
let renderer = new marked.Renderer()
const TOC = {}
let toc = []
/**
* Compile markdown content
*/
export const markdown = cached(text => {
let html = ''
if (!text) return text
html = markdownCompiler(text)
html = emojify(html)
clearSlugCache()
return html
})
markdown.renderer = renderer
markdown.init = function (config = {}, base = window.location.pathname) {
contentBase = getBasePath(base)
currentPath = parse().path
if (isFn(config)) {
markdownCompiler = config(marked, renderer)
} else {
renderer = merge(renderer, config.renderer)
marked.setOptions(merge(config, { renderer }))
}
}
markdown.update = function () {
currentPath = parse().path
}
/**
* render anchor tag
* @link https://github.com/chjj/marked#overriding-renderer-methods
*/
renderer.heading = function (text, level) {
const slug = slugify(text)
const url = toURL(currentPath, { id: slug })
toc.push({ level, slug: url, title: text })
return `<h${level} id="${slug}"><a href="${url}" data-id="${slug}" class="anchor"><span>${text}</span></a></h${level}>`
}
// highlight code
renderer.code = function (code, lang = '') {
const hl = Prism.highlight(code, Prism.languages[lang] || Prism.languages.markup)
return `<pre v-pre data-lang="${lang}"><code class="lang-${lang}">${hl}</code></pre>`
}
renderer.link = function (href, title, text) {
let blank = ''
if (!/:|(\/{2})/.test(href)) {
href = toURL(href)
} else {
blank = ' target="_blank"'
}
return `<a href="${href}" title="${title || text}"${blank}>${text}</a>`
}
renderer.paragraph = function (text) {
if (/^!&gt;/.test(text)) {
return helperTpl('tip', text)
} else if (/^\?&gt;/.test(text)) {
return helperTpl('warn', text)
}
return `<p>${text}</p>`
}
renderer.image = function (href, title, text) {
let url = href
const titleHTML = title ? ` title="${title}"` : ''
if (!isAbsolutePath(href)) {
url = getPath(contentBase, href)
}
return `<img src="${url}" data-origin="${href}" alt="${text}"${titleHTML}>`
}
/**
* Compile sidebar
*/
export function sidebar (text, level) {
let html = ''
if (text) {
html = markdown(text)
html = html.match(/<ul[^>]*>([\s\S]+)<\/ul>/g)[0]
} else {
const tree = genTree(toc, level)
html = treeTpl(tree, '<ul>')
}
return html
}
/**
* Compile sub sidebar
*/
export function subSidebar (el, level) {
if (el) {
toc[0] && toc[0].level === 1 && toc.shift()
const tree = genTree(TOC[currentPath] || toc, level)
el.parentNode.innerHTML += treeTpl(tree, '<ul class="app-sub-sidebar">')
}
if (toc.length) {
TOC[currentPath] = toc.slice()
}
toc = []
}
/**
* Compile cover page
*/
export function cover (text) {
const cacheToc = toc.slice()
const html = markdown(text)
toc = cacheToc.slice()
return html
}
+6
View File
@@ -0,0 +1,6 @@
export function emojify (text) {
return text
.replace(/<(pre|template)[^>]*?>([\s\S]+)<\/(pre|template)>/g, m => m.replace(/:/g, '__colon__'))
.replace(/:(\w+?):/ig, '<img class="emoji" src="https://assets-cdn.github.com/images/icons/emoji/$1.png" alt="$1" />')
.replace(/__colon__/g, ':')
}
+27
View File
@@ -0,0 +1,27 @@
/**
* gen toc tree
* @link https://github.com/killercup/grock/blob/5280ae63e16c5739e9233d9009bc235ed7d79a50/styles/solarized/assets/js/behavior.coffee#L54-L81
* @param {Array} toc
* @param {Number} maxLevel
* @return {Array}
*/
export function genTree (toc, maxLevel) {
const headlines = []
const last = {}
toc.forEach(headline => {
const level = headline.level || 1
const len = level - 1
if (level > maxLevel) return
if (last[len]) {
last[len].children = last[len].children || []
last[len].children.push(headline)
} else {
headlines.push(headline)
}
last[level] = headline
})
return headlines
}
+161
View File
@@ -0,0 +1,161 @@
import * as dom from '../util/dom'
import { getAndActive, sticky } from '../event/sidebar'
import { scrollActiveSidebar, scroll2Top } from '../event/scroll'
import cssVars from '../util/polyfill/css-vars'
import * as tpl from './tpl'
import { markdown, sidebar, subSidebar, cover } from './compiler'
import { callHook } from '../init/lifecycle'
import { getBasePath, getPath, isAbsolutePath } from '../route/util'
function executeScript () {
const script = dom.findAll('.markdown-section>script')
.filter(s => !/template/.test(s.type))[0]
if (!script) return false
const code = script.innerText.trim()
if (!code) return false
setTimeout(_ => {
window.__EXECUTE_RESULT__ = new Function(code)()
}, 0)
}
function renderMain (html) {
if (!html) {
// TODO: Custom 404 page
html = 'not found'
}
this._renderTo('.markdown-section', html)
// Render sidebar with the TOC
!this.config.loadSidebar && this._renderSidebar()
// execute script
this.config.executeScript && executeScript()
if (!this.config.executeScript &&
typeof window.Vue !== 'undefined' &&
!executeScript()) {
setTimeout(_ => {
window.__EXECUTE_RESULT__ = new window.Vue().$mount('#main')
}, 0)
}
if (this.config.auto2top) {
scroll2Top(this.config.auto2top)
}
}
export function renderMixin (proto) {
proto._renderTo = function (el, content, replace) {
const node = dom.getNode(el)
if (node) node[replace ? 'outerHTML' : 'innerHTML'] = content
}
proto._renderSidebar = function (text) {
const { maxLevel, subMaxLevel, autoHeader } = this.config
this._renderTo('.sidebar-nav', sidebar(text, maxLevel))
const active = getAndActive('.sidebar-nav', true, true)
subSidebar(active, subMaxLevel)
// bind event
this.activeLink = active
scrollActiveSidebar()
if (autoHeader && active) {
const main = dom.getNode('#main')
const firstNode = main.children[0]
if (firstNode && firstNode.tagName !== 'H1') {
const h1 = dom.create('h1')
h1.innerText = active.innerText
dom.before(main, h1)
}
}
}
proto._renderNav = function (text) {
text && this._renderTo('nav', markdown(text))
getAndActive('nav')
}
proto._renderMain = function (text) {
callHook(this, 'beforeEach', text, result => {
const html = this.isHTML ? result : markdown(result)
callHook(this, 'afterEach', html, text => renderMain.call(this, text))
})
}
proto._renderCover = function (text) {
const el = dom.getNode('.cover')
if (!text) {
dom.toggleClass(el, 'remove', 'show')
return
}
dom.toggleClass(el, 'add', 'show')
let html = this.coverIsHTML ? text : cover(text)
const m = html.trim().match('<p><img.*?data-origin="(.*?)"[^a]+alt="(.*?)">([^<]*?)</p>$')
if (m) {
if (m[2] === 'color') {
el.style.background = m[1] + (m[3] || '')
} else {
let path = m[1]
dom.toggleClass(el, 'add', 'has-mask')
if (isAbsolutePath(m[1])) {
path = getPath(getBasePath(this.config.basePath), m[1])
}
el.style.backgroundImage = `url(${path})`
}
html = html.replace(m[0], '')
}
this._renderTo('.cover-main', html)
sticky()
}
proto._updateRender = function () {
markdown.update()
}
}
export function initRender (vm) {
const config = vm.config
// Init markdown compiler
markdown.init(config.markdown, config.basePath)
const id = config.el || '#app'
const navEl = dom.find('nav') || dom.create('nav')
let el = dom.find(id)
let html = ''
navEl.classList.add('app-nav')
if (!config.repo) {
navEl.classList.add('no-badge')
}
if (!el) {
el = dom.create(id)
dom.appendTo(dom.body, el)
}
if (config.repo) {
html += tpl.corner(config.repo)
}
if (config.coverpage) {
html += tpl.cover()
}
html += tpl.main(config)
// Render main app
vm._renderTo(el, html, true)
// Add nav
dom.before(dom.body, navEl)
if (config.themeColor) {
dom.$.head.innerHTML += tpl.theme(config.themeColor)
// Polyfll
cssVars(config.themeColor)
}
dom.toggleClass(dom.body, 'ready')
}
+41
View File
@@ -0,0 +1,41 @@
import * as dom from '../util/dom'
let barEl
let timeId
/**
* Init progress component
*/
function init () {
const div = dom.create('div')
div.classList.add('progress')
dom.appendTo(dom.body, div)
barEl = div
}
/**
* Render progress bar
*/
export default function ({ loaded, total, step }) {
let num
!barEl && init()
if (step) {
num = parseInt(barEl.style.width || 0, 10) + step
num = num > 80 ? 80 : num
} else {
num = Math.floor(loaded / total * 100)
}
barEl.style.opacity = 1
barEl.style.width = num >= 95 ? '100%' : num + '%'
if (num >= 95) {
clearTimeout(timeId)
timeId = setTimeout(_ => {
barEl.style.opacity = 0
barEl.style.width = '0%'
}, 200)
}
}
+27
View File
@@ -0,0 +1,27 @@
let cache = {}
const re = /[\u2000-\u206F\u2E00-\u2E7F\\'!"#$%&()*+,.\/:;<=>?@\[\]^`{|}~]/g
export function slugify (str) {
if (typeof str !== 'string') return ''
let slug = str.toLowerCase().trim()
.replace(/<[^>\d]+>/g, '')
.replace(re, '')
.replace(/\s/g, '-')
.replace(/-+/g, '-')
.replace(/^(\d)/, '_$1')
let count = cache[slug]
count = cache.hasOwnProperty(slug) ? (count + 1) : 0
cache[slug] = count
if (count) {
slug = slug + '-' + count
}
return slug
}
export function clearSlugCache () {
cache = {}
}
+86
View File
@@ -0,0 +1,86 @@
import { isMobile } from '../util/env'
/**
* Render github corner
* @param {Object} data
* @return {String}
*/
export function corner (data) {
if (!data) return ''
if (!/\/\//.test(data)) data = 'https://github.com/' + data
data = data.replace(/^git\+/, '')
return (
`<a href="${data}" class="github-corner" aria-label="View source on Github">` +
'<svg viewBox="0 0 250 250" aria-hidden="true">' +
'<path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path>' +
'<path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path>' +
'<path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path>' +
'</svg>' +
'</a>`')
}
/**
* Render main content
*/
export function main (config) {
const aside = (
'<button class="sidebar-toggle">' +
'<div class="sidebar-toggle-button">' +
'<span></span><span></span><span></span>' +
'</div>' +
'</button>' +
'<aside class="sidebar">' +
(config.name
? `<h1><a href="${config.nameLink}">${config.name}</a></h1>`
: '') +
'<div class="sidebar-nav"></div>' +
'</aside>')
return (isMobile ? `${aside}<main>` : `<main>${aside}`) +
'<section class="content">' +
'<article class="markdown-section" id="main"></article>' +
'</section>' +
'</main>'
}
/**
* Cover Page
*/
export function cover () {
const SL = ', 100%, 85%'
const bgc = 'linear-gradient(to left bottom, ' +
`hsl(${Math.floor(Math.random() * 255) + SL}) 0%,` +
`hsl(${Math.floor(Math.random() * 255) + SL}) 100%)`
return `<section class="cover" style="background: ${bgc}">` +
'<div class="cover-main"></div>' +
'<div class="mask"></div>' +
'</section>'
}
/**
* Render tree
* @param {Array} tree
* @param {String} tpl
* @return {String}
*/
export function tree (toc, tpl = '') {
if (!toc || !toc.length) return ''
toc.forEach(node => {
tpl += `<li><a class="section-link" href="${node.slug}">${node.title}</a></li>`
if (node.children) {
tpl += `<li><ul class="children">${tree(node.children)}</li></ul>`
}
})
return tpl
}
export function helper (className, content) {
return `<p class="${className}">${content.slice(5).trim()}</p>`
}
export function theme (color) {
return `<style>:root{--theme-color: ${color};}</style>`
}
+75
View File
@@ -0,0 +1,75 @@
import { merge, cached } from '../util/core'
import { parseQuery, stringifyQuery, cleanPath } from './util'
export * from './util'
function replaceHash (path) {
const i = window.location.href.indexOf('#')
window.location.replace(
window.location.href.slice(0, i >= 0 ? i : 0) + '#' + path
)
}
const replaceSlug = cached(path => {
return path
.replace('#', '?id=')
.replace(/\?(\w+)=/g, (_, slug) => slug === 'id' ? '?id=' : `&${slug}=`)
})
/**
* Normalize the current url
*
* @example
* domain.com/docs/ => domain.com/docs/#/
* domain.com/docs/#/#slug => domain.com/docs/#/?id=slug
*/
export function normalize () {
let path = getHash()
path = replaceSlug(path)
if (path.charAt(0) === '/') return replaceHash(path)
replaceHash('/' + path)
}
export function getHash () {
// We can't use window.location.hash here because it's not
// consistent across browsers - Firefox will pre-decode it!
const href = window.location.href
const index = href.indexOf('#')
return index === -1 ? '' : href.slice(index + 1)
}
/**
* Parse the url
* @param {string} [path=window.location.herf]
* @return {object} { path, query }
*/
export function parse (path = window.location.href) {
let query = ''
const queryIndex = path.indexOf('?')
if (queryIndex >= 0) {
query = path.slice(queryIndex + 1)
path = path.slice(0, queryIndex)
}
const hashIndex = path.indexOf('#')
if (hashIndex) {
path = path.slice(hashIndex + 1)
}
return { path, query: parseQuery(query) }
}
/**
* to URL
* @param {string} path
* @param {object} qs query params
*/
export function toURL (path, params) {
const route = parse(replaceSlug(path))
route.query = merge({}, route.query, params)
path = route.path + stringifyQuery(route.query)
return cleanPath('#/' + path)
}
+51
View File
@@ -0,0 +1,51 @@
import { normalize, parse } from './hash'
import { getBasePath, getPath, isAbsolutePath } from './util'
import { on } from '../util/dom'
function getAlias (path, alias) {
return alias[path] ? getAlias(alias[path], alias) : path
}
function getFileName (path) {
return /\.(md|html)$/g.test(path)
? path
: /\/$/g.test(path)
? `${path}README.md`
: `${path}.md`
}
export function routeMixin (proto) {
proto.route = {}
proto.$getFile = function (path) {
const { config } = this
const base = getBasePath(config.basePath)
path = config.alias ? getAlias(path, config.alias) : path
path = getFileName(path)
path = path === '/README.md' ? (config.homepage || path) : path
path = isAbsolutePath(path) ? path : getPath(base, path)
return path
}
}
let lastRoute = {}
export function initRoute (vm) {
normalize()
lastRoute = vm.route = parse()
on('hashchange', _ => {
normalize()
vm.route = parse()
vm._updateRender()
if (lastRoute.path === vm.route.path) {
vm.$resetEvents()
return
}
vm.$fetch()
lastRoute = vm.route
})
}
+54
View File
@@ -0,0 +1,54 @@
import { cached } from '../util/core'
const decode = decodeURIComponent
const encode = encodeURIComponent
export function parseQuery (query) {
const res = {}
query = query.trim().replace(/^(\?|#|&)/, '')
if (!query) {
return res
}
// Simple parse
query.split('&').forEach(function (param) {
const parts = param.replace(/\+/g, ' ').split('=')
res[parts[0]] = decode(parts[1])
})
return res
}
export function stringifyQuery (obj) {
const qs = []
for (const key in obj) {
qs.push(`${encode(key)}=${encode(obj[key])}`.toLowerCase())
}
return qs.length ? `?${qs.join('&')}` : ''
}
export const getBasePath = cached(base => {
return /^(\/|https?:)/g.test(base)
? base
: cleanPath(window.location.pathname + '/' + base)
})
export function getPath (...args) {
return cleanPath(args.join('/'))
}
export const isAbsolutePath = cached(path => {
return /(:|(\/{2}))/.test(path)
})
export const getRoot = cached(path => {
return /\/$/g.test(path) ? path : path.match(/(\S*\/)[^\/]+$/)[1]
})
export const cleanPath = cached(path => {
return path.replace(/\/+/g, '/')
})
+55
View File
@@ -0,0 +1,55 @@
/**
* Create a cached version of a pure function.
*/
export function cached (fn) {
const cache = Object.create(null)
return function cachedFn (str) {
const hit = cache[str]
return hit || (cache[str] = fn(str))
}
}
/**
* Hyphenate a camelCase string.
*/
export const hyphenate = cached(str => {
return str.replace(/([A-Z])/g, m => '-' + m.toLowerCase())
})
/**
* Simple Object.assign polyfill
*/
export const merge = Object.assign || function (to) {
const hasOwn = Object.prototype.hasOwnProperty
for (let i = 1; i < arguments.length; i++) {
const from = Object(arguments[i])
for (const key in from) {
if (hasOwn.call(from, key)) {
to[key] = from[key]
}
}
}
return to
}
/**
* Check if value is primitive
*/
export function isPrimitive (value) {
return typeof value === 'string' || typeof value === 'number'
}
/**
* Perform no operation.
*/
export function noop () {}
/**
* Check if value is function
*/
export function isFn (obj) {
return typeof obj === 'function'
}
+80
View File
@@ -0,0 +1,80 @@
import { isFn } from '../util/core'
const cacheNode = {}
/**
* Get Node
* @param {String|Element} el
* @param {Boolean} noCache
* @return {Element}
*/
export function getNode (el, noCache = false) {
if (typeof el === 'string') {
el = noCache ? find(el) : (cacheNode[el] || find(el))
}
return el
}
export const $ = document
export const body = $.body
export const head = $.head
/**
* Find element
* @example
* find('nav') => document.querySelector('nav')
* find(nav, 'a') => nav.querySelector('a')
*/
export function find (el, node) {
return node ? el.querySelector(node) : $.querySelector(el)
}
/**
* Find all elements
* @example
* findAll('a') => [].slice.call(document.querySelectorAll('a'))
* findAll(nav, 'a') => [].slice.call(nav.querySelectorAll('a'))
*/
export function findAll (el, node) {
return [].slice.call(node ? el.querySelectorAll(node) : $.querySelectorAll(el))
}
export function create (node, tpl) {
node = $.createElement(node)
if (tpl) node.innerHTML = tpl
return node
}
export function appendTo (target, el) {
return target.appendChild(el)
}
export function before (target, el) {
return target.insertBefore(el, target.children[0])
}
export function on (el, type, handler) {
isFn(type)
? window.addEventListener(el, type)
: el.addEventListener(type, handler)
}
export const off = function on (el, type, handler) {
isFn(type)
? window.removeEventListener(el, type)
: el.removeEventListener(type, handler)
}
/**
* Toggle class
*
* @example
* toggleClass(el, 'active') => el.classList.toggle('active')
* toggleClass(el, 'add', 'active') => el.classList.add('active')
*/
export function toggleClass (el, type, val) {
el && el.classList[val ? type : 'toggle'](val || type)
}
+5
View File
@@ -0,0 +1,5 @@
export const UA = window.navigator.userAgent.toLowerCase()
export const isIE = UA && /msie|trident/.test(UA)
export const isMobile = document.body.clientWidth <= 600
+2
View File
@@ -0,0 +1,2 @@
export * from './core'
export * from './env'
+33
View File
@@ -0,0 +1,33 @@
import * as dom from '../dom'
import { get } from '../../fetch/ajax'
function replaceVar (block, color) {
block.innerHTML = block.innerHTML
.replace(/var\(\s*--theme-color.*?\)/g, color)
}
export default function (color) {
// Variable support
if (window.CSS &&
window.CSS.supports &&
window.CSS.supports('(--v:red)')) return
const styleBlocks = dom.findAll('style:not(.inserted),link')
;[].forEach.call(styleBlocks, block => {
if (block.nodeName === 'STYLE') {
replaceVar(block, color)
} else if (block.nodeName === 'LINK') {
const href = block.getAttribute('href')
if (!/\.css$/.test(href)) return
get(href).then(res => {
const style = dom.create('style', res)
dom.head.appendChild(style)
replaceVar(style, color)
})
}
})
}
-159
View File
@@ -1,159 +0,0 @@
import { isMobile } from './util'
/**
* Active sidebar when scroll
* @link https://buble.surge.sh/
*/
export function scrollActiveSidebar () {
if (isMobile()) return
let hoveredOverSidebar = false
const anchors = document.querySelectorAll('.anchor')
const sidebar = document.querySelector('.sidebar')
const sidebarContainer = sidebar.querySelector('.sidebar-nav')
const sidebarHeight = sidebar.clientHeight
const nav = {}
const lis = sidebar.querySelectorAll('li')
let active = sidebar.querySelector('li.active')
for (let i = 0, len = lis.length; i < len; i += 1) {
const li = lis[i]
const a = li.querySelector('a')
if (!a) continue
let href = a.getAttribute('href')
if (href !== '/') {
const match = href.match('#([^#]+)$')
if (match && match.length) href = match[0].slice(1)
}
nav[decodeURIComponent(href)] = li
}
function highlight () {
const top = document.body.scrollTop
let last
for (let i = 0, len = anchors.length; i < len; i += 1) {
const node = anchors[i]
if (node.offsetTop > top) {
if (!last) last = node
break
} else {
last = node
}
}
if (!last) return
const li = nav[last.getAttribute('data-id')]
if (!li || li === active) return
if (active) active.classList.remove('active')
li.classList.add('active')
active = li
// scroll into view
// https://github.com/vuejs/vuejs.org/blob/master/themes/vue/source/js/common.js#L282-L297
if (!hoveredOverSidebar && !sticky.noSticky) {
const currentPageOffset = 0
const currentActiveOffset = active.offsetTop + active.clientHeight + 40
const currentActiveIsInView = (
active.offsetTop >= sidebarContainer.scrollTop &&
currentActiveOffset <= sidebarContainer.scrollTop + sidebarHeight
)
const linkNotFurtherThanSidebarHeight = currentActiveOffset - currentPageOffset < sidebarHeight
const newScrollTop = currentActiveIsInView
? sidebarContainer.scrollTop
: linkNotFurtherThanSidebarHeight
? currentPageOffset
: currentActiveOffset - sidebarHeight
sidebar.scrollTop = newScrollTop
}
}
window.removeEventListener('scroll', highlight)
window.addEventListener('scroll', highlight)
sidebar.addEventListener('mouseover', () => { hoveredOverSidebar = true })
sidebar.addEventListener('mouseleave', () => { hoveredOverSidebar = false })
}
export function scrollIntoView () {
const id = window.location.hash.match(/#[^#\/]+$/g)
if (!id || !id.length) return
const section = document.querySelector(decodeURIComponent(id[0]))
if (section) setTimeout(() => section.scrollIntoView(), 0)
return section
}
/**
* Acitve link
*/
export function activeLink (dom, activeParent) {
const host = window.location.href
dom = typeof dom === 'object' ? dom : document.querySelector(dom)
if (!dom) return
let target
;[].slice.call(dom.querySelectorAll('a'))
.sort((a, b) => b.href.length - a.href.length)
.forEach(node => {
if (host.indexOf(node.href) === 0 && !target) {
activeParent
? node.parentNode.classList.add('active')
: node.classList.add('active')
target = node
} else {
activeParent
? node.parentNode.classList.remove('active')
: node.classList.remove('active')
}
})
return target
}
/**
* sidebar toggle
*/
export function bindToggle (dom) {
dom = typeof dom === 'object' ? dom : document.querySelector(dom)
if (!dom) return
const body = document.body
dom.addEventListener('click', () => body.classList.toggle('close'))
if (isMobile()) {
const sidebar = document.querySelector('.sidebar')
sidebar.addEventListener('click', () => {
body.classList.toggle('close')
setTimeout(() => activeLink(sidebar, true), 0)
})
}
}
const scrollingElement = document.scrollingElement || document.documentElement
export function scroll2Top (offset = 0) {
scrollingElement.scrollTop = offset === true ? 0 : Number(offset)
}
export function sticky () {
sticky.dom = sticky.dom || document.querySelector('section.cover')
const coverHeight = sticky.dom.getBoundingClientRect().height
return (function () {
if (window.pageYOffset >= coverHeight || sticky.dom.classList.contains('hidden')) {
document.body.classList.add('sticky')
sticky.noSticky = false
} else {
document.body.classList.remove('sticky')
sticky.noSticky = true
}
})()
}
-52
View File
@@ -1,52 +0,0 @@
export default class Hook {
constructor () {
this.beforeHooks = []
this.afterHooks = []
this.initHooks = []
this.readyHooks = []
}
beforeEach (fn) {
this.beforeHooks.push(fn)
}
afterEach (fn) {
this.afterHooks.push(fn)
}
init (fn) {
this.initHooks.push(fn)
}
ready (fn) {
this.readyHooks.push(fn)
}
emit (name, data, next) {
let newData = data
const queue = this[name + 'Hooks']
const step = function (index) {
const hook = queue[index]
if (index >= queue.length) {
next && next(newData)
} else {
if (typeof hook === 'function') {
if (hook.length === 2) {
hook(data, result => {
newData = result
step(index + 1)
})
} else {
const result = hook(data)
newData = result !== undefined ? result : newData
step(index + 1)
}
} else {
step(index + 1)
}
}
}
step(0)
}
}
-145
View File
@@ -1,145 +0,0 @@
import * as utils from './util'
import { scrollIntoView, activeLink } from './event'
import * as render from './render'
import Hook from './hook'
const OPTIONS = utils.merge({
el: '#app',
repo: '',
maxLevel: 6,
subMaxLevel: 0,
loadSidebar: null,
loadNavbar: null,
homepage: 'README.md',
coverpage: '',
basePath: '',
auto2top: false,
name: '',
themeColor: '',
nameLink: window.location.pathname,
ga: ''
}, window.$docsify)
const script = document.currentScript || [].slice.call(document.getElementsByTagName('script')).pop()
// load configuration for script attribute
if (script) {
for (const prop in OPTIONS) {
const val = script.getAttribute('data-' + utils.camel2kebab(prop))
OPTIONS[prop] = utils.isNil(val) ? OPTIONS[prop] : (val || true)
}
if (OPTIONS.loadSidebar === true) OPTIONS.loadSidebar = '_sidebar.md'
if (OPTIONS.loadNavbar === true) OPTIONS.loadNavbar = '_navbar.md'
if (OPTIONS.coverpage === true) OPTIONS.coverpage = '_coverpage.md'
if (OPTIONS.repo === true) OPTIONS.repo = ''
if (OPTIONS.name === true) OPTIONS.name = ''
}
const hook = new Hook()
// utils
window.$docsify = OPTIONS
window.Docsify = {
installed: true,
utils: utils.merge({}, utils),
hook
}
// load options
render.init()
let cacheRoute = null
let cacheXhr = null
const getAlias = function (route) {
if (OPTIONS.alias[route]) {
return getAlias(OPTIONS.alias[route])
} else {
return route
}
}
const mainRender = function (cb) {
let page
let route = utils.getRoute()
if (cacheRoute === route) return cb()
let basePath = cacheRoute = OPTIONS.basePath + route
if (!/\//.test(basePath)) {
basePath = ''
} else if (basePath && !/\/$/.test(basePath)) {
basePath = basePath.match(/(\S*\/)[^\/]+$/)[1]
}
// replace route
route = '/' + route
if (OPTIONS.alias && OPTIONS.alias[route]) {
route = getAlias(route)
} else {
route = (OPTIONS.basePath + route).replace(/\/+/, '/')
}
if (!route) {
page = OPTIONS.homepage || 'README.md'
} else if (/\.md$/.test(route)) {
page = route
} else if (/\/$/.test(route)) {
page = `${route}README.md`
} else {
page = `${route}.md`
}
// Render Cover page
if (OPTIONS.coverpage && page === OPTIONS.homepage) {
utils.load(OPTIONS.coverpage).then(render.renderCover)
}
cacheXhr && cacheXhr.abort && cacheXhr.abort()
// Render markdown file
cacheXhr = utils.load(page, 'GET', render.renderLoading)
cacheXhr.then(result => {
render.renderArticle(result)
// clear cover
if (OPTIONS.coverpage && page !== OPTIONS.homepage) render.renderCover()
// render sidebar
if (OPTIONS.loadSidebar) {
const renderSidebar = result => { render.renderSidebar(result); cb() }
utils.load(basePath + OPTIONS.loadSidebar).then(renderSidebar,
_ => utils.load(OPTIONS.loadSidebar).then(renderSidebar))
} else {
cb()
}
}, _ => render.renderArticle(null))
// Render navbar
if (OPTIONS.loadNavbar) {
utils.load(basePath + OPTIONS.loadNavbar).then(render.renderNavbar,
_ => utils.load(OPTIONS.loadNavbar).then(render.renderNavbar))
}
}
const Docsify = function () {
setTimeout(_ => {
;[].concat(OPTIONS.plugins).forEach(fn => typeof fn === 'function' && fn(hook))
window.Docsify.hook.emit('init')
const dom = document.querySelector(OPTIONS.el) || document.body
const replace = dom !== document.body
const main = function () {
mainRender(_ => {
scrollIntoView()
activeLink('nav')
})
}
// Render app
render.renderApp(dom, replace)
main()
if (!/^#\//.test(window.location.hash)) window.location.hash = '#/'
window.addEventListener('hashchange', main)
window.Docsify.hook.emit('ready')
}, 0)
}
export default Docsify()
+13
View File
@@ -0,0 +1,13 @@
import parser from './parser'
const install = function (hook, vm) {
hook.beforeEach(content => {
const { attributes, body } = parser(content)
Docsify.util.merge(vm.config, attributes.config)
return body
})
}
window.$docsify.plugins = [].concat(install, window.$docsify.plugins)
+54
View File
@@ -0,0 +1,54 @@
/**
* Fork https://github.com/egoist/docute/blob/master/src/utils/front-matter.js
*/
/* eslint-disable */
import parser from './yaml'
var optionalByteOrderMark = '\\ufeff?'
var pattern = '^(' +
optionalByteOrderMark +
'(= yaml =|---)' +
'$([\\s\\S]*?)' +
'(?:\\2|\\.\\.\\.)' +
'$' +
'' +
'(?:\\n)?)'
// NOTE: If this pattern uses the 'g' flag the `regex` variable definition will
// need to be moved down into the functions that use it.
var regex = new RegExp(pattern, 'm')
function extractor (string) {
string = string || ''
var lines = string.split(/(\r?\n)/)
if (lines[0] && /= yaml =|---/.test(lines[0])) {
return parse(string)
} else {
return { attributes: {}, body: string }
}
}
function parse (string) {
var match = regex.exec(string)
if (!match) {
return {
attributes: {},
body: string
}
}
var yaml = match[match.length - 1].replace(/^\s+|\s+$/g, '')
var attributes = parser(yaml) || {}
var body = string.replace(match[0], '')
return { attributes: attributes, body: body, frontmatter: yaml }
}
function test (string) {
string = string || ''
return regex.test(string)
}
export default extractor
+469
View File
@@ -0,0 +1,469 @@
/**
* Fork https://github.com/egoist/docute/blob/master/src/utils/yaml.js
*/
/* eslint-disable */
/*
YAML parser for Javascript
Author: Diogo Costa
This program is released under the MIT License as follows:
Copyright (c) 2011 Diogo Costa (costa.h4evr@gmail.com)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
/**
* @name YAML
* @namespace
*/
var errors = [],
reference_blocks = [],
processing_time = 0,
regex =
{
"regLevel" : new RegExp("^([\\s\\-]+)"),
"invalidLine" : new RegExp("^\\-\\-\\-|^\\.\\.\\.|^\\s*#.*|^\\s*$"),
"dashesString" : new RegExp("^\\s*\\\"([^\\\"]*)\\\"\\s*$"),
"quotesString" : new RegExp("^\\s*\\\'([^\\\']*)\\\'\\s*$"),
"float" : new RegExp("^[+-]?[0-9]+\\.[0-9]+(e[+-]?[0-9]+(\\.[0-9]+)?)?$"),
"integer" : new RegExp("^[+-]?[0-9]+$"),
"array" : new RegExp("\\[\\s*(.*)\\s*\\]"),
"map" : new RegExp("\\{\\s*(.*)\\s*\\}"),
"key_value" : new RegExp("([a-z0-9_-][ a-z0-9_-]*):( .+)", "i"),
"single_key_value" : new RegExp("^([a-z0-9_-][ a-z0-9_-]*):( .+?)$", "i"),
"key" : new RegExp("([a-z0-9_-][ a-z0-9_-]+):( .+)?", "i"),
"item" : new RegExp("^-\\s+"),
"trim" : new RegExp("^\\s+|\\s+$"),
"comment" : new RegExp("([^\\\'\\\"#]+([\\\'\\\"][^\\\'\\\"]*[\\\'\\\"])*)*(#.*)?")
};
/**
* @class A block of lines of a given level.
* @param {int} lvl The block's level.
* @private
*/
function Block(lvl) {
return {
/* The block's parent */
parent: null,
/* Number of children */
length: 0,
/* Block's level */
level: lvl,
/* Lines of code to process */
lines: [],
/* Blocks with greater level */
children : [],
/* Add a block to the children collection */
addChild : function(obj) {
this.children.push(obj);
obj.parent = this;
++this.length;
}
};
}
// function to create an XMLHttpClient in a cross-browser manner
function createXMLHTTPRequest() {
var xmlhttp;
try {
// Mozilla / Safari / IE7
xmlhttp = new XMLHttpRequest();
} catch (e) {
// IE
var XMLHTTP_IDS = new Array('MSXML2.XMLHTTP.5.0',
'MSXML2.XMLHTTP.4.0',
'MSXML2.XMLHTTP.3.0',
'MSXML2.XMLHTTP',
'Microsoft.XMLHTTP' );
var success = false;
for (var i=0;i < XMLHTTP_IDS.length && !success; i++) {
try {
xmlhttp = new ActiveXObject(XMLHTTP_IDS[i]);
success = true;
} catch (e) {}
}
if (!success) {
throw new Error('Unable to create XMLHttpRequest.');
}
}
return xmlhttp;
}
function parser(str) {
var regLevel = regex["regLevel"];
var invalidLine = regex["invalidLine"];
var lines = str.split("\n");
var m;
var level = 0, curLevel = 0;
var blocks = [];
var result = new Block(-1);
var currentBlock = new Block(0);
result.addChild(currentBlock);
var levels = [];
var line = "";
blocks.push(currentBlock);
levels.push(level);
for(var i = 0, len = lines.length; i < len; ++i) {
line = lines[i];
if(line.match(invalidLine)) {
continue;
}
if(m = regLevel.exec(line)) {
level = m[1].length;
} else
level = 0;
if(level > curLevel) {
var oldBlock = currentBlock;
currentBlock = new Block(level);
oldBlock.addChild(currentBlock);
blocks.push(currentBlock);
levels.push(level);
} else if(level < curLevel) {
var added = false;
var k = levels.length - 1;
for(; k >= 0; --k) {
if(levels[k] == level) {
currentBlock = new Block(level);
blocks.push(currentBlock);
levels.push(level);
if(blocks[k].parent!= null)
blocks[k].parent.addChild(currentBlock);
added = true;
break;
}
}
if(!added) {
errors.push("Error: Invalid indentation at line " + i + ": " + line);
return;
}
}
currentBlock.lines.push(line.replace(regex["trim"], ""));
curLevel = level;
}
return result;
}
function processValue(val) {
val = val.replace(regex["trim"], "");
var m = null;
if(val == 'true') {
return true;
} else if(val == 'false') {
return false;
} else if(val == '.NaN') {
return Number.NaN;
} else if(val == 'null') {
return null;
} else if(val == '.inf') {
return Number.POSITIVE_INFINITY;
} else if(val == '-.inf') {
return Number.NEGATIVE_INFINITY;
} else if(m = val.match(regex["dashesString"])) {
return m[1];
} else if(m = val.match(regex["quotesString"])) {
return m[1];
} else if(m = val.match(regex["float"])) {
return parseFloat(m[0]);
} else if(m = val.match(regex["integer"])) {
return parseInt(m[0]);
} else if( !isNaN(m = Date.parse(val))) {
return new Date(m);
} else if(m = val.match(regex["single_key_value"])) {
var res = {};
res[m[1]] = processValue(m[2]);
return res;
} else if(m = val.match(regex["array"])){
var count = 0, c = ' ';
var res = [];
var content = "";
var str = false;
for(var j = 0, lenJ = m[1].length; j < lenJ; ++j) {
c = m[1][j];
if(c == '\'' || c == '"') {
if(str === false) {
str = c;
content += c;
continue;
} else if((c == '\'' && str == '\'') || (c == '"' && str == '"')) {
str = false;
content += c;
continue;
}
} else if(str === false && (c == '[' || c == '{')) {
++count;
} else if(str === false && (c == ']' || c == '}')) {
--count;
} else if(str === false && count == 0 && c == ',') {
res.push(processValue(content));
content = "";
continue;
}
content += c;
}
if(content.length > 0)
res.push(processValue(content));
return res;
} else if(m = val.match(regex["map"])){
var count = 0, c = ' ';
var res = [];
var content = "";
var str = false;
for(var j = 0, lenJ = m[1].length; j < lenJ; ++j) {
c = m[1][j];
if(c == '\'' || c == '"') {
if(str === false) {
str = c;
content += c;
continue;
} else if((c == '\'' && str == '\'') || (c == '"' && str == '"')) {
str = false;
content += c;
continue;
}
} else if(str === false && (c == '[' || c == '{')) {
++count;
} else if(str === false && (c == ']' || c == '}')) {
--count;
} else if(str === false && count == 0 && c == ',') {
res.push(content);
content = "";
continue;
}
content += c;
}
if(content.length > 0)
res.push(content);
var newRes = {};
for(var j = 0, lenJ = res.length; j < lenJ; ++j) {
if(m = res[j].match(regex["key_value"])) {
newRes[m[1]] = processValue(m[2]);
}
}
return newRes;
} else
return val;
}
function processFoldedBlock(block) {
var lines = block.lines;
var children = block.children;
var str = lines.join(" ");
var chunks = [str];
for(var i = 0, len = children.length; i < len; ++i) {
chunks.push(processFoldedBlock(children[i]));
}
return chunks.join("\n");
}
function processLiteralBlock(block) {
var lines = block.lines;
var children = block.children;
var str = lines.join("\n");
for(var i = 0, len = children.length; i < len; ++i) {
str += processLiteralBlock(children[i]);
}
return str;
}
function processBlock(blocks) {
var m = null;
var res = {};
var lines = null;
var children = null;
var currentObj = null;
var level = -1;
var processedBlocks = [];
var isMap = true;
for(var j = 0, lenJ = blocks.length; j < lenJ; ++j) {
if(level != -1 && level != blocks[j].level)
continue;
processedBlocks.push(j);
level = blocks[j].level;
lines = blocks[j].lines;
children = blocks[j].children;
currentObj = null;
for(var i = 0, len = lines.length; i < len; ++i) {
var line = lines[i];
if(m = line.match(regex["key"])) {
var key = m[1];
if(key[0] == '-') {
key = key.replace(regex["item"], "");
if (isMap) {
isMap = false;
if (typeof(res.length) === "undefined") {
res = [];
}
}
if(currentObj != null) res.push(currentObj);
currentObj = {};
isMap = true;
}
if(typeof m[2] != "undefined") {
var value = m[2].replace(regex["trim"], "");
if(value[0] == '&') {
var nb = processBlock(children);
if(currentObj != null) currentObj[key] = nb;
else res[key] = nb;
reference_blocks[value.substr(1)] = nb;
} else if(value[0] == '|') {
if(currentObj != null) currentObj[key] = processLiteralBlock(children.shift());
else res[key] = processLiteralBlock(children.shift());
} else if(value[0] == '*') {
var v = value.substr(1);
var no = {};
if(typeof reference_blocks[v] == "undefined") {
errors.push("Reference '" + v + "' not found!");
} else {
for(var k in reference_blocks[v]) {
no[k] = reference_blocks[v][k];
}
if(currentObj != null) currentObj[key] = no;
else res[key] = no;
}
} else if(value[0] == '>') {
if(currentObj != null) currentObj[key] = processFoldedBlock(children.shift());
else res[key] = processFoldedBlock(children.shift());
} else {
if(currentObj != null) currentObj[key] = processValue(value);
else res[key] = processValue(value);
}
} else {
if(currentObj != null) currentObj[key] = processBlock(children);
else res[key] = processBlock(children);
}
} else if(line.match(/^-\s*$/)) {
if (isMap) {
isMap = false;
if (typeof(res.length) === "undefined") {
res = [];
}
}
if(currentObj != null) res.push(currentObj);
currentObj = {};
isMap = true;
continue;
} else if(m = line.match(/^-\s*(.*)/)) {
if(currentObj != null)
currentObj.push(processValue(m[1]));
else {
if (isMap) {
isMap = false;
if (typeof(res.length) === "undefined") {
res = [];
}
}
res.push(processValue(m[1]));
}
continue;
}
}
if(currentObj != null) {
if (isMap) {
isMap = false;
if (typeof(res.length) === "undefined") {
res = [];
}
}
res.push(currentObj);
}
}
for(var j = processedBlocks.length - 1; j >= 0; --j) {
blocks.splice.call(blocks, processedBlocks[j], 1);
}
return res;
}
function semanticAnalysis(blocks) {
var res = processBlock(blocks.children);
return res;
}
function preProcess(src) {
var m;
var lines = src.split("\n");
var r = regex["comment"];
for(var i in lines) {
if(m = lines[i].match(r)) {
/* var cmt = "";
if(typeof m[3] != "undefined")
lines[i] = m[1];
else if(typeof m[3] != "undefined")
lines[i] = m[3];
else
lines[i] = "";
*/
if(typeof m[3] !== "undefined") {
lines[i] = m[0].substr(0, m[0].length - m[3].length);
}
}
}
return lines.join("\n");
}
function load(str) {
errors = [];
reference_blocks = [];
processing_time = (new Date()).getTime();
var pre = preProcess(str)
var doc = parser(pre);
var res = semanticAnalysis(doc);
processing_time = (new Date()).getTime() - processing_time;
return res;
}
export default load
+3 -10
View File
@@ -1,5 +1,4 @@
// From https://github.com/egoist/vue-ga/blob/master/src/index.js
function appendScript () {
const script = document.createElement('script')
script.async = true
@@ -24,19 +23,13 @@ function collect () {
window.ga('send', 'pageview')
}
const install = function () {
if (install.installed) return
install.installed = true
const install = function (hook) {
if (!window.$docsify.ga) {
console.error('[Docsify] ga is required.')
return
}
window.$docsify.plugins = [].concat(function (hook) {
hook.init(collect)
hook.beforeEach(collect)
}, window.$docsify.plugins)
hook.beforeEach(collect)
}
export default install()
window.$docsify.plugins = [].concat(install, window.$docsify.plugins)
-349
View File
@@ -1,349 +0,0 @@
let INDEXS = {}
const CONFIG = {
placeholder: 'Type to search',
paths: 'auto',
maxAge: 86400000 // 1 day
}
const isObj = function (obj) {
return Object.prototype.toString.call(obj) === '[object Object]'
}
const escapeHtml = function (string) {
const entityMap = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#39;',
'/': '&#x2F;'
}
return String(string).replace(/[&<>"'\/]/g, s => entityMap[s])
}
/**
* find all filepath from A tag
*/
const getAllPaths = function () {
const paths = []
;[].slice.call(document.querySelectorAll('a'))
.map(node => {
const href = node.href
if (/#\/[^#]*?$/.test(href)) {
const path = href.replace(/^[^#]+#/, '')
if (paths.indexOf(path) <= 0) paths.push(path)
}
})
return paths
}
/**
* return file path
*/
const genFilePath = function (path, basePath = window.$docsify.basePath) {
let filePath = /\/$/.test(path) ? `${path}README.md` : `${path}.md`
filePath = basePath + filePath
return filePath.replace(/\/+/g, '/')
}
/**
* generate index
*/
const genIndex = function (path, content = '') {
INDEXS[path] = { slug: '', title: '', body: '' }
let slug
content
// remove PRE and TEMPLATE tag
.replace(/<template[^>]*?>[\s\S]+?<\/template>/g, '')
// find all html tag
.replace(/<(\w+)([^>]*?)>([\s\S]+?)<\//g, (match, tag, attr, html) => {
// remove all html tag
const text = html.replace(/<[^>]+>/g, '')
// tag is headline
if (/^h\d$/.test(tag)) {
// <h1 id="xxx"></h1>
const id = attr.match(/id="(\S+)"/)[1]
slug = `#/${path}#${id}`.replace(/\/+/, '/')
INDEXS[slug] = { slug, title: text, body: '' }
} else {
if (!slug) return
// other html tag
if (!INDEXS[slug]) {
INDEXS[slug] = {}
} else {
if (INDEXS[slug].body && INDEXS[slug].body.length) {
INDEXS[slug].body += '\n' + text
} else {
INDEXS[slug].body = text
}
}
}
})
}
/**
* component
*/
class SearchComponent {
constructor () {
if (this.rendered) return
this.style()
const el = document.createElement('div')
const aside = document.querySelector('aside')
el.classList.add('search')
aside.insertBefore(el, aside.children[0])
this.render(el)
this.rendered = true
this.bindEvent()
}
style () {
const code = `
.sidebar {
padding-top: 0;
}
.search {
margin-bottom: 20px;
padding: 6px;
border-bottom: 1px solid #eee;
}
.search .results-panel {
display: none;
}
.search .results-panel.show {
display: block;
}
.search input {
outline: none;
border: none;
width: 100%;
padding: 7px;
line-height: 22px;
font-size: 14px;
}
.search h2 {
font-size: 17px;
margin: 10px 0;
}
.search a {
text-decoration: none;
color: inherit;
}
.search .matching-post {
border-bottom: 1px solid #eee;
}
.search .matching-post:last-child {
border-bottom: 0;
}
.search p {
font-size: 14px;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.search p.empty {
text-align: center;
}
`
const style = document.createElement('style')
style.innerHTML = code
document.head.appendChild(style)
}
render (dom) {
dom.innerHTML = `<input type="search" placeholder="${CONFIG.placeholder}" /><div class="results-panel"></div>`
}
bindEvent () {
const search = document.querySelector('.search')
const input = search.querySelector('.search input')
const panel = search.querySelector('.results-panel')
search.addEventListener('click', e => e.target.tagName !== 'A' && e.stopPropagation())
input.addEventListener('input', e => {
const target = e.target
if (target.value.trim() !== '') {
const matchingPosts = this.search(target.value)
let html = ''
matchingPosts.forEach(function (post, index) {
html += `
<div class="matching-post">
<h2><a href="${post.url}">${post.title}</a></h2>
<p>${post.content}</p>
</div>
`
})
if (panel.classList.contains('results-panel')) {
panel.classList.add('show')
panel.innerHTML = html || '<p class="empty">No Results!</p>'
}
} else {
if (panel.classList.contains('results-panel')) {
panel.classList.remove('show')
panel.innerHTML = ''
}
}
})
}
// From [weex website] https://weex-project.io/js/common.js
search (keywords) {
const matchingResults = []
const data = Object.keys(INDEXS).map(key => INDEXS[key])
keywords = keywords.trim().split(/[\s\-\\\/]+/)
for (let i = 0; i < data.length; i++) {
const post = data[i]
let isMatch = false
let resultStr = ''
const postTitle = post.title && post.title.trim()
const postContent = post.body && post.body.trim()
const postUrl = post.slug || ''
if (postTitle !== '' && postContent !== '') {
keywords.forEach((keyword, i) => {
const regEx = new RegExp(keyword, 'gi')
let indexTitle = -1
let indexContent = -1
indexTitle = postTitle.search(regEx)
indexContent = postContent.search(regEx)
if (indexTitle < 0 && indexContent < 0) {
isMatch = false
} else {
isMatch = true
if (indexContent < 0) indexContent = 0
let start = 0
let end = 0
start = indexContent < 11 ? 0 : indexContent - 10
end = start === 0 ? 70 : indexContent + keyword.length + 60
if (end > postContent.length) end = postContent.length
const matchContent = '...' +
postContent
.substring(start, end)
.replace(regEx, `<em class="search-keyword">${keyword}</em>`) +
'...'
resultStr += matchContent
}
})
if (isMatch) {
const matchingPost = {
title: escapeHtml(postTitle),
content: resultStr,
url: postUrl
}
matchingResults.push(matchingPost)
}
}
}
return matchingResults
}
}
const searchPlugin = function () {
const isAuto = CONFIG.paths === 'auto'
const isExpired = localStorage.getItem('docsify.search.expires') < Date.now()
INDEXS = JSON.parse(localStorage.getItem('docsify.search.index'))
if (isExpired) {
INDEXS = {}
} else if (!isAuto) {
return
}
let count = 0
const paths = isAuto ? getAllPaths() : CONFIG.paths
const len = paths.length
const { load, marked, slugify } = window.Docsify.utils
const alias = window.$docsify.alias
const done = () => {
localStorage.setItem('docsify.search.expires', Date.now() + CONFIG.maxAge)
localStorage.setItem('docsify.search.index', JSON.stringify(INDEXS))
}
paths.forEach(path => {
if (INDEXS[path]) return count++
let route
// replace route
if (alias && alias[path]) {
route = genFilePath(alias[path] || path, '')
} else {
route = genFilePath(path)
}
load(route).then(content => {
genIndex(path, marked(content))
slugify.clear()
count++
if (len === count) done()
})
})
}
const install = function () {
if (install.installed) return
install.installed = true
const userConfig = window.$docsify.search
const isNil = window.Docsify.utils.isNil
if (Array.isArray(userConfig)) {
CONFIG.paths = userConfig
} else if (isObj(userConfig)) {
CONFIG.paths = Array.isArray(userConfig.paths) ? userConfig.paths : 'auto'
CONFIG.maxAge = isNil(userConfig.maxAge) ? CONFIG.maxAge : userConfig.maxAge
CONFIG.placeholder = userConfig.placeholder || CONFIG.placeholder
}
window.$docsify.plugins = [].concat(hook => {
const isAuto = CONFIG.paths === 'auto'
hook.ready(() => {
new SearchComponent()
!isAuto && searchPlugin()
})
isAuto && hook.beforeEach(searchPlugin)
}, window.$docsify.plugins)
}
export default install()
+133
View File
@@ -0,0 +1,133 @@
import { search } from './search'
let dom
function style () {
const code = `
.sidebar {
padding-top: 0;
}
.search {
margin-bottom: 20px;
padding: 6px;
border-bottom: 1px solid #eee;
}
.search .results-panel {
display: none;
}
.search .results-panel.show {
display: block;
}
.search input {
outline: none;
border: none;
width: 100%;
padding: 7px;
line-height: 22px;
font-size: 14px;
}
.search h2 {
font-size: 17px;
margin: 10px 0;
}
.search a {
text-decoration: none;
color: inherit;
}
.search .matching-post {
border-bottom: 1px solid #eee;
}
.search .matching-post:last-child {
border-bottom: 0;
}
.search p {
font-size: 14px;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.search p.empty {
text-align: center;
}`
const style = dom.create('style', code)
dom.appendTo(dom.head, style)
}
function tpl (opts) {
const html =
`<input type="search" />` +
'<div class="results-panel"></div>' +
'</div>'
const el = dom.create('div', html)
const aside = dom.find('aside')
dom.toggleClass(el, 'search')
dom.before(aside, el)
}
function bindEvents () {
const $search = dom.find('div.search')
const $input = dom.find($search, 'input')
const $panel = dom.find($search, '.results-panel')
// Prevent to Fold sidebar
dom.on($search, 'click',
e => e.target.tagName !== 'A' && e.stopPropagation())
dom.on($input, 'input', e => {
const value = e.target.value.trim()
if (!value) {
$panel.classList.remove('show')
$panel.innerHTML = ''
return
}
const matchs = search(value)
let html = ''
matchs.forEach(post => {
html += `<div class="matching-post">
<h2><a href="${post.url}">${post.title}</a></h2>
<p>${post.content}</p>
</div>`
})
$panel.classList.add('show')
$panel.innerHTML = html || '<p class="empty">No Results!</p>'
})
}
function updatePlaceholder (text, path) {
const $input = dom.getNode('.search input[type="search"]')
if (typeof text === 'string') {
$input.placeholder = text
} else {
const match = Object.keys(text).find(key => path.indexOf(key) > -1)
$input.placeholder = text[match]
}
}
export function init (opts) {
dom = Docsify.dom
style()
tpl(opts)
bindEvents()
}
export function update (opts, vm) {
updatePlaceholder(opts.placeholder, vm.route.path)
}
+34
View File
@@ -0,0 +1,34 @@
import { init as initComponet, update as updateComponent } from './component'
import { init as initSearch } from './search'
const CONFIG = {
placeholder: 'Type to search',
paths: 'auto',
maxAge: 86400000 // 1 day
}
const install = function (hook, vm) {
const util = Docsify.util
const opts = vm.config.search || CONFIG
if (Array.isArray(opts)) {
CONFIG.paths = opts
} else if (typeof opts === 'object') {
CONFIG.paths = Array.isArray(opts.paths) ? opts.paths : 'auto'
CONFIG.maxAge = util.isPrimitive(opts.maxAge) ? opts.maxAge : CONFIG.maxAge
CONFIG.placeholder = opts.placeholder || CONFIG.placeholder
}
const isAuto = CONFIG.paths === 'auto'
hook.mounted(_ => {
initComponet(CONFIG)
!isAuto && initSearch(CONFIG, vm)
})
hook.doneEach(_ => {
updateComponent(CONFIG, vm)
isAuto && initSearch(CONFIG, vm)
})
}
window.$docsify.plugins = [].concat(install, window.$docsify.plugins)
+162
View File
@@ -0,0 +1,162 @@
let INDEXS = {}
let helper
function escapeHtml (string) {
const entityMap = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
'\'': '&#39;',
'/': '&#x2F;'
}
return String(string).replace(/[&<>"'\/]/g, s => entityMap[s])
}
function getAllPaths () {
const paths = []
helper.dom.findAll('a')
.map(node => {
const href = node.href
const originHref = node.getAttribute('href')
const path = helper.route.parse(href).path
if (path &&
paths.indexOf(path) === -1 &&
!helper.route.isAbsolutePath(originHref)) {
paths.push(path)
}
})
return paths
}
function saveData (maxAge) {
localStorage.setItem('docsify.search.expires', Date.now() + maxAge)
localStorage.setItem('docsify.search.index', JSON.stringify(INDEXS))
}
export function genIndex (path, content = '') {
const tokens = window.marked.lexer(content)
const toURL = Docsify.route.toURL
const index = {}
let slug
tokens.forEach(token => {
if (token.type === 'heading' && token.depth <= 2) {
slug = toURL(path, { id: token.text })
index[slug] = { slug, title: token.text, body: '' }
} else {
if (!slug) return
if (!index[slug]) {
index[slug] = { slug, title: '', body: '' }
} else {
if (index[slug].body) {
index[slug].body += '\n' + (token.text || '')
} else {
index[slug].body = token.text
}
}
}
})
return index
}
export function search (keywords) {
const matchingResults = []
let data = []
Object.keys(INDEXS).forEach(key => {
data = data.concat(Object.keys(INDEXS[key]).map(page => INDEXS[key][page]))
})
keywords = keywords.trim().split(/[\s\-\\\/]+/)
for (let i = 0; i < data.length; i++) {
const post = data[i]
let isMatch = false
let resultStr = ''
const postTitle = post.title && post.title.trim()
const postContent = post.body && post.body.trim()
const postUrl = post.slug || ''
if (postTitle !== '' && postContent !== '') {
keywords.forEach((keyword, i) => {
const regEx = new RegExp(keyword, 'gi')
let indexTitle = -1
let indexContent = -1
indexTitle = postTitle.search(regEx)
indexContent = postContent.search(regEx)
if (indexTitle < 0 && indexContent < 0) {
isMatch = false
} else {
isMatch = true
if (indexContent < 0) indexContent = 0
let start = 0
let end = 0
start = indexContent < 11 ? 0 : indexContent - 10
end = start === 0 ? 70 : indexContent + keyword.length + 60
if (end > postContent.length) end = postContent.length
const matchContent = '...' +
escapeHtml(postContent)
.substring(start, end)
.replace(regEx, `<em class="search-keyword">${keyword}</em>`) +
'...'
resultStr += matchContent
}
})
if (isMatch) {
const matchingPost = {
title: escapeHtml(postTitle),
content: resultStr,
url: postUrl
}
matchingResults.push(matchingPost)
}
}
}
return matchingResults
}
export function init (config, vm) {
helper = Docsify
const isAuto = config.paths === 'auto'
const isExpired = localStorage.getItem('docsify.search.expires') < Date.now()
INDEXS = JSON.parse(localStorage.getItem('docsify.search.index'))
if (isExpired) {
INDEXS = {}
} else if (!isAuto) {
return
}
const paths = isAuto ? getAllPaths() : config.paths
const len = paths.length
let count = 0
paths.forEach(path => {
if (INDEXS[path]) return count++
helper
.get(vm.$getFile(path))
.then(result => {
INDEXS[path] = genIndex(path, result)
len === ++count && saveData(config.maxAge)
})
})
}
-30
View File
@@ -1,30 +0,0 @@
import { load } from './util'
function replaceVar (block) {
block.innerHTML = block.innerHTML.replace(/var\(\s*--theme-color.*?\)/g, $docsify.themeColor)
}
export function cssVars () {
// variable support
if (window.CSS && window.CSS.supports && window.CSS.supports('(--foo: red)')) return
const styleBlocks = document.querySelectorAll('style:not(.inserted),link')
;[].forEach.call(styleBlocks, block => {
if (block.nodeName === 'STYLE') {
replaceVar(block)
} else if (block.nodeName === 'LINK') {
const href = block.getAttribute('href')
if (!/\.css$/.test(href)) return
load(href).then(res => {
const style = document.createElement('style')
style.innerHTML = res
document.head.appendChild(style)
replaceVar(style)
})
}
})
}
-270
View File
@@ -1,270 +0,0 @@
import marked from 'marked'
import Prism from 'prismjs'
import * as tpl from './tpl'
import * as event from './event'
import * as polyfill from './polyfill'
import { genTree, getRoute, isMobile, slugify, merge, emojify } from './util'
let markdown = marked
let toc = []
const CACHE = {}
const originTitle = document.title
const renderTo = function (dom, content) {
dom = typeof dom === 'object' ? dom : document.querySelector(dom)
dom.innerHTML = content
slugify.clear()
return dom
}
/**
* init render
*/
export function init () {
const renderer = new marked.Renderer()
/**
* render anchor tag
* @link https://github.com/chjj/marked#overriding-renderer-methods
*/
renderer.heading = function (text, level) {
const slug = slugify(text)
let route = ''
route = `#/${getRoute()}`
toc.push({ level, slug: `${route}#${encodeURIComponent(slug)}`, title: text })
return `<h${level} id="${slug}"><a href="${route}#${slug}" data-id="${slug}" class="anchor"><span>${text}</span></a></h${level}>`
}
// highlight code
renderer.code = function (code, lang = '') {
const hl = Prism.highlight(code, Prism.languages[lang] || Prism.languages.markup)
return `<pre v-pre data-lang="${lang}"><code class="lang-${lang}">${hl}</code></pre>`
}
renderer.link = function (href, title, text) {
if (!/:|(\/{2})/.test(href)) {
href = `#/${href}`.replace(/\/+/g, '/')
}
return `<a href="${href}" title="${title || ''}">${text}</a>`
}
renderer.paragraph = function (text) {
if (/^!&gt;/.test(text)) {
return tpl.helper('tip', text)
} else if (/^\?&gt;/.test(text)) {
return tpl.helper('warn', text)
}
return `<p>${text}</p>`
}
renderer.image = function (href, title, text) {
const url = /:|(\/{2})/.test(href) ? href : ($docsify.basePath + href).replace(/\/+/g, '/')
const titleHTML = title ? ` title="${title}"` : ''
return `<img src="${url}" alt="${text}"${titleHTML} />`
}
if (typeof $docsify.markdown === 'function') {
markdown = $docsify.markdown.call(this, markdown, renderer)
} else {
if ($docsify.markdown && $docsify.markdown.renderer) {
$docsify.markdown.renderer = merge(renderer, $docsify.markdown.renderer)
}
markdown.setOptions(merge({ renderer }, $docsify.markdown))
}
const md = markdown
markdown = text => emojify(md(text))
window.Docsify.utils.marked = text => {
const result = markdown(text)
toc = []
return result
}
}
/**
* App
*/
export function renderApp (dom, replace) {
const nav = document.querySelector('nav') || document.createElement('nav')
const body = document.body
const head = document.head
if (!$docsify.repo) nav.classList.add('no-badge')
dom[replace ? 'outerHTML' : 'innerHTML'] = tpl.corner($docsify.repo) +
($docsify.coverpage ? tpl.cover() : '') +
tpl.main()
body.insertBefore(nav, body.children[0])
// theme color
if ($docsify.themeColor) {
head.innerHTML += tpl.theme($docsify.themeColor)
polyfill.cssVars()
}
// render name
if ($docsify.name) {
const aside = document.querySelector('.sidebar')
aside.innerHTML = `<h1><a href="${$docsify.nameLink}">${$docsify.name}</a></h1>` + aside.innerHTML
}
// bind toggle
event.bindToggle('button.sidebar-toggle')
// bind sticky effect
if ($docsify.coverpage) {
!isMobile() && window.addEventListener('scroll', event.sticky)
} else {
body.classList.add('sticky')
}
}
/**
* article
*/
export function renderArticle (content) {
const hook = window.Docsify.hook
const renderFn = function (data) {
renderTo('article', data)
if (!$docsify.loadSidebar) renderSidebar()
if (data && typeof Vue !== 'undefined') {
CACHE.vm && CACHE.vm.$destroy()
const script = [].slice.call(
document.body.querySelectorAll('article>script'))
.filter(script => !/template/.test(script.type)
)[0]
const code = script ? script.innerText.trim() : null
script && script.remove()
CACHE.vm = code
? new Function(`return ${code}`)()
: new Vue({ el: 'main' }) // eslint-disable-line
CACHE.vm && CACHE.vm.$nextTick(_ => event.scrollActiveSidebar())
}
if ($docsify.auto2top) setTimeout(() => event.scroll2Top($docsify.auto2top), 0)
}
hook.emit('before', content, result => {
const html = result ? markdown(result) : ''
hook.emit('after', html, result => renderFn(result || 'not found'))
})
}
/**
* navbar
*/
export function renderNavbar (content) {
if (CACHE.navbar && CACHE.navbar === content) return
CACHE.navbar = content
if (content) renderTo('nav', markdown(content))
event.activeLink('nav')
}
/**
* sidebar
*/
export function renderSidebar (content) {
let html
if (content) {
html = markdown(content)
// find url tag
html = html.match(/<ul[^>]*>([\s\S]+)<\/ul>/g)[0]
} else {
html = tpl.tree(genTree(toc, $docsify.maxLevel), '<ul>')
}
renderTo('.sidebar-nav', html)
if (toc[0] && toc[0].level === 1) {
document.title = `${toc[0].title}${originTitle ? ' - ' + originTitle : ''}`
}
const target = event.activeLink('.sidebar-nav', true)
if (target) renderSubSidebar(target)
toc = []
event.scrollActiveSidebar()
}
export function renderSubSidebar (target) {
if (!$docsify.subMaxLevel) return
toc[0] && toc[0].level === 1 && toc.shift()
target.parentNode.innerHTML += tpl.tree(genTree(toc, $docsify.subMaxLevel), '<ul>')
}
/**
* Cover Page
*/
export function renderCover (content) {
renderCover.dom = renderCover.dom || document.querySelector('section.cover')
if (!content) {
renderCover.dom.classList.remove('show')
return
}
renderCover.dom.classList.add('show')
if (renderCover.rendered) return event.sticky()
// render cover
const cacheToc = toc.slice()
let html = markdown(content)
const match = html.trim().match('<p><img[^s]+src="(.*?)"[^a]+alt="(.*?)">([^<]*?)</p>$')
toc = cacheToc.slice()
// render background
if (match) {
const coverEl = document.querySelector('section.cover')
if (match[2] === 'color') {
coverEl.style.background = match[1] + (match[3] || '')
} else {
coverEl.classList.add('has-mask')
coverEl.style.backgroundImage = `url(${match[1]})`
}
html = html.replace(match[0], '')
}
renderTo('.cover-main', html)
renderCover.rendered = true
event.sticky()
}
/**
* render loading bar
* @return {[type]} [description]
*/
export function renderLoading ({ loaded, total, step }) {
let num
if (!CACHE.loading) {
const div = document.createElement('div')
div.classList.add('progress')
document.body.appendChild(div)
CACHE.loading = div
}
if (step) {
num = parseInt(CACHE.loading.style.width, 10) + step
num = num > 80 ? 80 : num
} else {
num = Math.floor(loaded / total * 100)
}
CACHE.loading.style.opacity = 1
CACHE.loading.style.width = num >= 95 ? '100%' : num + '%'
if (num >= 95) {
clearTimeout(renderLoading.cacheTimeout)
renderLoading.cacheTimeout = setTimeout(_ => {
CACHE.loading.style.opacity = 0
CACHE.loading.style.width = '0%'
}, 200)
}
}
+18 -1
View File
@@ -7,6 +7,23 @@
-webkit-font-smoothing: antialiased;
}
body:not(.ready) {
[data-cloak] {
display: none;
}
}
div#app {
text-align: center;
font-size: 30px;
font-weight: lighter;
margin: 40vw auto;
&:empty::before {
content: "Loading...";
}
}
.emoji {
height: 1.2em;
vertical-align: middle;
@@ -64,7 +81,7 @@ kbd {
}
/* navbar */
nav {
nav.app-nav {
position: absolute;
right: 0;
left: 0;
+9
View File
@@ -46,6 +46,15 @@ body {
}
}
.app-sub-sidebar {
.section-link {
&::before {
content: '-';
padding-right: 4px;
}
}
}
/* markdown content found on pages */
.markdown-section h1,
.markdown-section h2,
-81
View File
@@ -1,81 +0,0 @@
import { isMobile } from './util'
/**
* Render github corner
* @param {Object} data
* @return {String}
*/
export function corner (data) {
if (!data) return ''
if (!/\/\//.test(data)) data = 'https://github.com/' + data
data = data.replace(/^git\+/, '')
return `
<a href="${data}" class="github-corner" aria-label="View source on Github">
<svg viewBox="0 0 250 250" aria-hidden="true">
<path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path>
<path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path>
<path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path>
</svg>
</a>`
}
/**
* Render main content
*/
export function main () {
const aside = `${toggle()}<aside class="sidebar"><div class="sidebar-nav"></div></aside>`
return (isMobile() ? `${aside}<main>` : `<main>${aside}`) +
`<section class="content">
<article class="markdown-section"></article>
</section>
</main>`
}
/**
* Cover Page
*/
export function cover () {
const SL = ', 100%, 85%'
const bgc = `linear-gradient(to left bottom, hsl(${Math.floor(Math.random() * 255) + SL}) 0%, hsl(${Math.floor(Math.random() * 255) + SL}) 100%)`
return `<section class="cover" style="background: ${bgc}">
<div class="cover-main"></div>
<div class="mask"></div>
</section>`
}
export function toggle () {
return `<button class="sidebar-toggle">
<div class="sidebar-toggle-button">
<span></span><span></span><span></span>
</div>
</button>`
}
/**
* Render tree
* @param {Array} tree
* @param {String} tpl
* @return {String}
*/
export function tree (toc, tpl = '') {
if (!toc || !toc.length) return ''
toc.forEach(node => {
tpl += `<li><a class="section-link" href="${node.slug}">${node.title}</a></li>`
if (node.children) {
tpl += `<li><ul class="children">${tree(node.children)}</li></ul>`
}
})
return tpl
}
export function helper (className, content) {
return `<p class="${className}">${content.slice(5).trim()}</p>`
}
export function theme (color) {
return `<style>:root{--theme-color: ${color};}</style>`
}
-166
View File
@@ -1,166 +0,0 @@
/**
* Simple ajax
* @param {String} url
* @param {String} [method=GET]
* @param {Function} [loading] handle loading
* @return {Promise}
*/
export function load (url, method = 'GET', loading) {
const xhr = new XMLHttpRequest()
xhr.open(method, url)
xhr.send()
return {
then: function (success, error = function () {}) {
if (loading) {
const id = setInterval(_ =>
loading({ step: Math.floor(Math.random() * 5 + 1) }),
500)
xhr.addEventListener('progress', loading)
xhr.addEventListener('loadend', evt => {
loading(evt)
clearInterval(id)
})
}
xhr.addEventListener('error', error)
xhr.addEventListener('load', ({ target }) => {
target.status >= 400 ? error(target) : success(target.response)
})
},
abort: () => xhr.readyState !== 4 && xhr.abort()
}
}
/**
* gen toc tree
* @link https://github.com/killercup/grock/blob/5280ae63e16c5739e9233d9009bc235ed7d79a50/styles/solarized/assets/js/behavior.coffee#L54-L81
* @param {Array} toc
* @param {Number} maxLevel
* @return {Array}
*/
export function genTree (toc, maxLevel) {
const headlines = []
const last = {}
toc.forEach(headline => {
const level = headline.level || 1
const len = level - 1
if (level > maxLevel) return
if (last[len]) {
last[len].children = last[len].children || []
last[len].children.push(headline)
} else {
headlines.push(headline)
}
last[level] = headline
})
return headlines
}
/**
* camel to kebab
* @link https://github.com/bokuweb/kebab2camel/blob/master/index.js
* @param {String} str
* @return {String}
*/
export function camel2kebab (str) {
return str.replace(/([A-Z])/g, m => '-' + m.toLowerCase())
}
/**
* is nil
* @param {Object} object
* @return {Boolean}
*/
export function isNil (o) {
return o === null || o === undefined
}
let cacheRoute = null
let cacheHash = null
/**
* hash route
*/
export function getRoute () {
const loc = window.location
if (cacheHash === loc.hash && !isNil(cacheRoute)) return cacheRoute
let route = loc.hash.replace(/%23/g, '#').match(/^#\/([^#]+)/)
if (route && route.length === 2) {
route = route[1]
} else {
route = /^#\//.test(loc.hash) ? '' : loc.pathname
}
cacheRoute = route
cacheHash = loc.hash
return route
}
export function isMobile () {
return document.body.clientWidth <= 600
}
export function slugify (string) {
const re = /[\u2000-\u206F\u2E00-\u2E7F\\'!"#$%&()*+,.\/:;<=>?@\[\]^`{|}~]/g
const maintainCase = false
const replacement = '-'
slugify.occurrences = slugify.occurrences || {}
if (typeof string !== 'string') return ''
if (!maintainCase) string = string.toLowerCase()
let slug = string.trim()
.replace(/<[^>\d]+>/g, '')
.replace(re, '')
.replace(/\s/g, replacement)
.replace(/-+/g, replacement)
.replace(/^(\d)/, '_$1')
let occurrences = slugify.occurrences[slug]
if (slugify.occurrences.hasOwnProperty(slug)) {
occurrences++
} else {
occurrences = 0
}
slugify.occurrences[slug] = occurrences
if (occurrences) {
slug = slug + '-' + occurrences
}
return slug
}
slugify.clear = function () {
slugify.occurrences = {}
}
const hasOwnProperty = Object.prototype.hasOwnProperty
export const merge = Object.assign || function (to) {
for (let i = 1; i < arguments.length; i++) {
const from = Object(arguments[i])
for (const key in from) {
if (hasOwnProperty.call(from, key)) {
to[key] = from[key]
}
}
}
return to
}
export function emojify (text) {
return text
.replace(/<(pre|template)[^>]*?>([\s\S]+)<\/(pre|template)>/g, match => match.replace(/:/g, '__colon__'))
.replace(/:(\w*?):/ig, '<img class="emoji" src="https://assets-cdn.github.com/images/icons/emoji/$1.png" alt="$1" />')
.replace(/__colon__/g, ':')
}