mirror of
https://github.com/wahyd4/docsify.git
synced 2026-08-25 05:06:07 +10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0455106a62 | ||
|
|
74ce0bf588 | ||
|
|
d355bb4ebe | ||
|
|
dbb92782cf | ||
|
|
754f92ce8e | ||
|
|
fcb66e8bc7 | ||
|
|
2efd859f71 | ||
|
|
515331763d | ||
|
|
2351c3e68c | ||
|
|
079bd00395 | ||
|
|
86594a3118 | ||
|
|
a6d823b826 | ||
|
|
7b6a2ac404 | ||
|
|
3e7d6ab26c | ||
|
|
4b64386465 | ||
|
|
c7e09c34a7 | ||
|
|
c108645e34 | ||
|
|
b7768b1b3c | ||
|
|
fd9c3bd19d | ||
|
|
e2b7b976cf | ||
|
|
8cae16539d | ||
|
|
27e7e749af | ||
|
|
fbd51afdcb | ||
|
|
fe88c154b0 | ||
|
|
30da0d5d46 | ||
|
|
8bee8f0213 | ||
|
|
aad62b65f5 | ||
|
|
e07d36f44e | ||
|
|
5dcd6d43ea | ||
|
|
00e3609b25 | ||
|
|
a6d9669bfa | ||
|
|
94255e22b1 | ||
|
|
00983ab796 |
@@ -4,6 +4,7 @@
|
|||||||
"browser": true
|
"browser": true
|
||||||
},
|
},
|
||||||
"globals": {
|
"globals": {
|
||||||
|
"Docsify": true,
|
||||||
"$docsify": true
|
"$docsify": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-86
@@ -1,90 +1,15 @@
|
|||||||
### 2.4.2
|
|
||||||
|
|
||||||
> 2017-02-14
|
3.0.0 / 2017-02-19
|
||||||
|
==================
|
||||||
|
|
||||||
#### Bug fixes
|
#### Breaking change
|
||||||
- fix(index): load file path error
|
- Not support IE9
|
||||||
|
- The route becomes: `/#/page?id=slug`. The previous route will be redirected
|
||||||
|
|
||||||
### 2.4.1
|
|
||||||
|
|
||||||
> 2017-02-13
|
|
||||||
|
|
||||||
#### Bug fixes
|
|
||||||
- fix(index): cover page
|
|
||||||
|
|
||||||
### 2.4.0
|
|
||||||
|
|
||||||
> 2017-02-13
|
|
||||||
|
|
||||||
#### Features
|
#### Features
|
||||||
|
- Faster rendering
|
||||||
- feat(hook): add `doneEach`
|
- Clearer source code
|
||||||
|
- More powerful API
|
||||||
|
- Auto header for each page [#78](https://github.com/QingWei-Li/docsify/issues/78)
|
||||||
### 2.3.0
|
- Localization for search plugin [#80](https://github.com/QingWei-Li/docsify/issues/80)
|
||||||
|
- Execute the script in markdown (see `executeScript`)
|
||||||
> 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
|
|
||||||
|
|||||||
+99
@@ -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
|
### 1.10.5
|
||||||
#### Bug fixes
|
#### Bug fixes
|
||||||
- fix initialize the Vue instance
|
- fix initialize the Vue instance
|
||||||
|
|||||||
@@ -20,11 +20,11 @@
|
|||||||
|
|
||||||
## Features
|
## Features
|
||||||
- Not build static html files
|
- Not build static html files
|
||||||
- Simple and lightweight (~13kB gzipped)
|
- Simple and lightweight (~14kB gzipped)
|
||||||
- Smart full-text search plugin
|
- Smart full-text search plugin
|
||||||
- Multiple themes
|
- Multiple themes
|
||||||
- Useful plugin API
|
- Useful plugin API
|
||||||
- Compatible with IE9+
|
- Compatible with IE10+
|
||||||
|
|
||||||
## Quick start
|
## Quick start
|
||||||
Create a `index.html`.
|
Create a `index.html`.
|
||||||
|
|||||||
+32
-30
@@ -9,7 +9,7 @@ var build = function (opts) {
|
|||||||
rollup
|
rollup
|
||||||
.rollup({
|
.rollup({
|
||||||
entry: 'src/' + opts.entry,
|
entry: 'src/' + opts.entry,
|
||||||
plugins: [buble()].concat(opts.plugins || [])
|
plugins: [buble(), commonjs(), nodeResolve()].concat(opts.plugins || [])
|
||||||
})
|
})
|
||||||
.then(function (bundle) {
|
.then(function (bundle) {
|
||||||
var dest = 'lib/' + (opts.output || opts.entry)
|
var dest = 'lib/' + (opts.output || opts.entry)
|
||||||
@@ -27,34 +27,36 @@ var build = function (opts) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
build({
|
build({
|
||||||
entry: 'index.js',
|
entry: 'core/index.js',
|
||||||
output: 'docsify.js',
|
output: 'docsify.js'
|
||||||
plugins: [commonjs(), nodeResolve()]
|
|
||||||
})
|
})
|
||||||
isProd && build({
|
|
||||||
entry: 'index.js',
|
var plugins = [
|
||||||
output: 'docsify.min.js',
|
{ name: 'search', entry: 'search/index.js', moduleName: 'Search' },
|
||||||
plugins: [commonjs(), nodeResolve(), uglify()]
|
{ name: 'ga', entry: 'ga.js', moduleName: 'GA' }
|
||||||
})
|
// { name: 'front-matter', entry: 'front-matter/index.js', moduleName: 'FrontMatter' }
|
||||||
build({
|
]
|
||||||
entry: 'plugins/search.js',
|
|
||||||
output: 'plugins/search.js',
|
plugins.forEach(item => {
|
||||||
moduleName: 'D.Search'
|
build({
|
||||||
})
|
entry: 'plugins/' + item.entry,
|
||||||
isProd && build({
|
output: 'plugins/' + item.name + '.js',
|
||||||
entry: 'plugins/search.js',
|
moduleName: 'D.' + item.moduleName
|
||||||
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()]
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
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()]
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,10 +4,12 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<title>docsify</title>
|
<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">
|
<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>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<nav>
|
<nav data-cloak>
|
||||||
<a href="#/">EN</a>
|
<a href="#/">EN</a>
|
||||||
<a href="#/zh-cn/">中文</a>
|
<a href="#/zh-cn/">中文</a>
|
||||||
</nav>
|
</nav>
|
||||||
@@ -19,16 +21,15 @@
|
|||||||
'/zh-cn/changelog': '/changelog',
|
'/zh-cn/changelog': '/changelog',
|
||||||
'/changelog': 'https://raw.githubusercontent.com/QingWei-Li/docsify/master/CHANGELOG'
|
'/changelog': 'https://raw.githubusercontent.com/QingWei-Li/docsify/master/CHANGELOG'
|
||||||
},
|
},
|
||||||
search: {
|
executeScript: true
|
||||||
maxAge: 0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<script
|
<script
|
||||||
src="/lib/docsify.js"
|
src="/lib/docsify.js"
|
||||||
data-name="docsify"
|
data-name="sdfsdf"
|
||||||
|
data-name-link="//www.baidu.com"
|
||||||
|
data-base-path="/docs/"
|
||||||
data-load-sidebar
|
data-load-sidebar
|
||||||
data-sub-max-level="2"
|
data-sub-max-level="2"
|
||||||
data-base-path="docs/"
|
|
||||||
data-auto2top></script>
|
data-auto2top></script>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
+2
-2
@@ -11,11 +11,11 @@ See the [Quick start](/quickstart) for more details.
|
|||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Not build static html files
|
- Not build static html files
|
||||||
- Simple and lightweight (~13kB gzipped)
|
- Simple and lightweight (~14kB gzipped)
|
||||||
- Smart full-text search plugin
|
- Smart full-text search plugin
|
||||||
- Multiple themes
|
- Multiple themes
|
||||||
- Useful plugin API
|
- Useful plugin API
|
||||||
- Compatible with IE9+
|
- Compatible with IE10+
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -1,13 +1,13 @@
|
|||||||

|

|
||||||
|
|
||||||
# docsify <small>2.4.2</small>
|
# docsify <small>2.4.3</small>
|
||||||
|
|
||||||
> A magical documentation site generator.
|
> A magical documentation site generator.
|
||||||
|
|
||||||
- Simple and lightweight (~13kB gzipped)
|
- Simple and lightweight (~14kB gzipped)
|
||||||
- Not build static html files
|
- Not build static html files
|
||||||
- Multiple themes
|
- Multiple themes
|
||||||
|
|
||||||
|
|
||||||
[GitHub](https://github.com/QingWei-Li/docsify/)
|
[GitHub](https://github.com/QingWei-Li/docsify/)
|
||||||
[Get Started](#quick-start)
|
[Get Started](#docsify)
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@
|
|||||||
- [Custom navbar](/custom-navbar)
|
- [Custom navbar](/custom-navbar)
|
||||||
- [Cover page](/cover)
|
- [Cover page](/cover)
|
||||||
|
|
||||||
- Configuration
|
- Customization
|
||||||
- [Configuration](/configuration)
|
- [Configuration](/configuration)
|
||||||
- [Themes](/themes)
|
- [Themes](/themes)
|
||||||
- [Using plugins](/plugins)
|
- [Using plugins](/plugins)
|
||||||
|
|||||||
+47
-1
@@ -111,7 +111,7 @@ Add TOC in custom sidebar.
|
|||||||
|
|
||||||
```js
|
```js
|
||||||
window.$docsify = {
|
window.$docsify = {
|
||||||
subMaxLevel: 3
|
subMaxLevel: 2
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -219,6 +219,17 @@ See [Markdown configuration](/markdown).
|
|||||||
|
|
||||||
```js
|
```js
|
||||||
window.$docsify = {
|
window.$docsify = {
|
||||||
|
// object
|
||||||
|
markdown: {
|
||||||
|
smartypants: true,
|
||||||
|
renderer: {
|
||||||
|
link: function() {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// function
|
||||||
markdown: function (marked, renderer) {
|
markdown: function (marked, renderer) {
|
||||||
// ...
|
// ...
|
||||||
return marked
|
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
@@ -1,6 +1,6 @@
|
|||||||
# Cover
|
# 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
|
## Basic usage
|
||||||
|
|
||||||
|
|||||||
+4
-2
@@ -7,10 +7,12 @@
|
|||||||
<meta name="keywords" content="doc,docs,documentation,gitbook,creator,generator,github,jekyll,github-pages">
|
<meta name="keywords" content="doc,docs,documentation,gitbook,creator,generator,github,jekyll,github-pages">
|
||||||
<meta name="description" content="A magical documentation generator.">
|
<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">
|
<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>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<nav>
|
<nav data-cloak>
|
||||||
<a href="#/">En</a>
|
<a href="#/">En</a>
|
||||||
<a href="#/zh-cn/">中文</a>
|
<a href="#/zh-cn/">中文</a>
|
||||||
</nav>
|
</nav>
|
||||||
|
|||||||
+7
-3
@@ -1,12 +1,16 @@
|
|||||||
# Markdown configuration
|
# 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
|
```js
|
||||||
window.$docsify = {
|
window.$docsify = {
|
||||||
markdown: {
|
markdown: {
|
||||||
smartypants: true
|
smartypants: true,
|
||||||
// ...
|
renderer: {
|
||||||
|
link: function() {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
+14
-4
@@ -23,7 +23,13 @@ By default, the hyperlink on the current page is recognized and the content is s
|
|||||||
search: {
|
search: {
|
||||||
maxAge: 86400000, // Expiration time, the default one day
|
maxAge: 86400000, // Expiration time, the default one day
|
||||||
paths: [], // or 'auto'
|
paths: [], // or 'auto'
|
||||||
placeholder: 'Type to search'
|
placeholder: 'Type to search',
|
||||||
|
|
||||||
|
// Localization
|
||||||
|
placeholder: {
|
||||||
|
'/zh-cn/': '搜索',
|
||||||
|
'/': 'Type to search'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -65,7 +71,7 @@ The hook supports handling asynchronous tasks.
|
|||||||
```js
|
```js
|
||||||
window.$docsify = {
|
window.$docsify = {
|
||||||
plugins: [
|
plugins: [
|
||||||
function (hook) {
|
function (hook, vm) {
|
||||||
hook.init(function() {
|
hook.init(function() {
|
||||||
// Called when the script starts running, only trigger once, no arguments,
|
// Called when the script starts running, only trigger once, no arguments,
|
||||||
})
|
})
|
||||||
@@ -89,15 +95,19 @@ window.$docsify = {
|
|||||||
// ...
|
// ...
|
||||||
})
|
})
|
||||||
|
|
||||||
|
hook.mounted(function() {
|
||||||
|
// Called after initial completion. Only trigger once, no arguments.
|
||||||
|
})
|
||||||
|
|
||||||
hook.ready(function() {
|
hook.ready(function() {
|
||||||
// Called after initialization is complete. Only trigger once, no arguments.
|
// 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
|
#### Example
|
||||||
|
|
||||||
|
|||||||
@@ -61,3 +61,25 @@ If your system has Python, you can easily to run a static server to preview your
|
|||||||
```bash
|
```bash
|
||||||
cd docs && python -m SimpleHTTPServer 3000
|
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>
|
||||||
|
```
|
||||||
|
|||||||
@@ -12,3 +12,37 @@ There are currently three themes available. Copy [Vue](//vuejs.org) and [buble](
|
|||||||
!> This compressed files in `/lib/themes/`.
|
!> 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).
|
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
@@ -13,7 +13,7 @@ Load the Vue in `index.html`.
|
|||||||
```
|
```
|
||||||
|
|
||||||
Then you can immediately write Vue code at Markdown file.
|
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*
|
*README.md*
|
||||||
|
|
||||||
@@ -44,7 +44,7 @@ You can manually initialize a Vue instance.
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
new Vue({
|
new Vue({
|
||||||
el: 'main',
|
el: '#main',
|
||||||
data: { msg: 'Vue' }
|
data: { msg: 'Vue' }
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -12,11 +12,11 @@ docsify 是一个动态生成文档网站的工具。不同于 GitBook、Hexo
|
|||||||
|
|
||||||
## 特性
|
## 特性
|
||||||
- 无需构建,写完文档直接发布
|
- 无需构建,写完文档直接发布
|
||||||
- 容易使用并且轻量 (~13Kb gzipped)
|
- 容易使用并且轻量 (~14kB gzipped)
|
||||||
- 智能的全文检索
|
- 智能的全文搜索
|
||||||
- 提供多套主题
|
- 提供多套主题
|
||||||
- 丰富的 API
|
- 丰富的 API
|
||||||
- 兼容 IE9+
|
- 兼容 IE10+
|
||||||
|
|
||||||
## 例子
|
## 例子
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
- [定制导航栏](zh-cn/custom-navbar)
|
- [定制导航栏](zh-cn/custom-navbar)
|
||||||
- [封面](zh-cn/cover)
|
- [封面](zh-cn/cover)
|
||||||
|
|
||||||
- 配置
|
- 定制化
|
||||||
- [配置项](zh-cn/configuration)
|
- [配置项](zh-cn/configuration)
|
||||||
- [主题](zh-cn/themes)
|
- [主题](zh-cn/themes)
|
||||||
- [使用插件](zh-cn/plugins)
|
- [使用插件](zh-cn/plugins)
|
||||||
|
|||||||
@@ -108,12 +108,12 @@ window.$docsify = {
|
|||||||
- 类型:`Number`
|
- 类型:`Number`
|
||||||
- 默认值: `0`
|
- 默认值: `0`
|
||||||
|
|
||||||
自定义侧边栏后默认不会再生成目录,你也可以通过设置生成目录的最大层级开启这个功能,
|
自定义侧边栏后默认不会再生成目录,你也可以通过设置生成目录的最大层级开启这个功能。
|
||||||
|
|
||||||
|
|
||||||
```js
|
```js
|
||||||
window.$docsify = {
|
window.$docsify = {
|
||||||
subMaxLevel: 3
|
subMaxLevel: 2
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -212,12 +212,23 @@ window.$docsify = {
|
|||||||
|
|
||||||
## markdown
|
## markdown
|
||||||
|
|
||||||
- 类型: `Function`
|
- 类型: `Object|Function`
|
||||||
|
|
||||||
参考 [Markdown 配置](/zh-cn/markdown)。
|
参考 [Markdown 配置](/zh-cn/markdown)。
|
||||||
|
|
||||||
```js
|
```js
|
||||||
window.$docsify = {
|
window.$docsify = {
|
||||||
|
// object
|
||||||
|
markdown: {
|
||||||
|
smartypants: true,
|
||||||
|
renderer: {
|
||||||
|
link: function() {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// function
|
||||||
markdown: function (marked, renderer) {
|
markdown: function (marked, renderer) {
|
||||||
// ...
|
// ...
|
||||||
return marked
|
return marked
|
||||||
@@ -229,7 +240,7 @@ window.$docsify = {
|
|||||||
|
|
||||||
- 类型:`String`
|
- 类型:`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
|
```js
|
||||||
window.$docsify = {
|
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>
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
# Markdown 配置
|
# Markdown 配置
|
||||||
|
|
||||||
内置的 Markdown 解析器是 [marked](https://github.com/chjj/marked),可以修改它的配置。
|
内置的 Markdown 解析器是 [marked](https://github.com/chjj/marked),可以修改它的配置。同时可以直接配置 `renderer`。
|
||||||
|
|
||||||
```js
|
```js
|
||||||
window.$docsify = {
|
window.$docsify = {
|
||||||
markdown: {
|
markdown: {
|
||||||
smartypants: true
|
smartypants: true,
|
||||||
// ...
|
renderer: {
|
||||||
|
link: function() {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
+16
-6
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
## 内置插件
|
## 内置插件
|
||||||
|
|
||||||
### 全文检索 - Search
|
### 全文搜索 - Search
|
||||||
|
|
||||||
全文检索插件会根据当前页面上的超链接获取文档内容,在 `localStorage` 内建立文档索引。默认过期时间为一天,当然我们可以自己指定需要缓存的文件列表或者配置过期时间。
|
全文搜索插件会根据当前页面上的超链接获取文档内容,在 `localStorage` 内建立文档索引。默认过期时间为一天,当然我们可以自己指定需要缓存的文件列表或者配置过期时间。
|
||||||
|
|
||||||
|
|
||||||
```html
|
```html
|
||||||
@@ -23,7 +23,13 @@
|
|||||||
search: {
|
search: {
|
||||||
maxAge: 86400000, // 过期时间,单位毫秒,默认一天
|
maxAge: 86400000, // 过期时间,单位毫秒,默认一天
|
||||||
paths: [], // or 'auto'
|
paths: [], // or 'auto'
|
||||||
placeholder: 'Type to search'
|
placeholder: 'Type to search',
|
||||||
|
|
||||||
|
// 支持本地化
|
||||||
|
placeholder: {
|
||||||
|
'/zh-cn/': '搜索',
|
||||||
|
'/': 'Type to search'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -61,7 +67,7 @@ docsify 提供了一套插件机制,其中提供的钩子(hook)支持处
|
|||||||
```js
|
```js
|
||||||
window.$docsify = {
|
window.$docsify = {
|
||||||
plugins: [
|
plugins: [
|
||||||
function (hook) {
|
function (hook, vm) {
|
||||||
hook.init(function() {
|
hook.init(function() {
|
||||||
// 初始化时调用,只调用一次,没有参数。
|
// 初始化时调用,只调用一次,没有参数。
|
||||||
})
|
})
|
||||||
@@ -84,15 +90,19 @@ window.$docsify = {
|
|||||||
// ...
|
// ...
|
||||||
})
|
})
|
||||||
|
|
||||||
|
hook.mounted(function() {
|
||||||
|
// 初始化完成后调用 ,只调用一次,没有参数。
|
||||||
|
})
|
||||||
|
|
||||||
hook.ready(function() {
|
hook.ready(function() {
|
||||||
// 初始化完成后调用,只调用一次,没有参数。
|
// 初始化并第一次加完成数据后调用,没有参数。
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
!> 如果需要用 docsify 的内部方法,可以通过 `window.Docsify.utils` 获取。
|
!> 如果需要用 docsify 的内部方法,可以通过 `window.Docsify` 获取,通过 `vm` 获取当前实例。
|
||||||
|
|
||||||
#### 例子
|
#### 例子
|
||||||
|
|
||||||
|
|||||||
@@ -59,3 +59,26 @@ docsify serve docs
|
|||||||
```bash
|
```bash
|
||||||
cd docs && python -m SimpleHTTPServer 3000
|
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
@@ -10,4 +10,38 @@
|
|||||||
|
|
||||||
!> CSS 的压缩文件位于 `/lib/themes/`
|
!> 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
@@ -12,7 +12,7 @@
|
|||||||
<script src="//unpkg.com/docsify"></script>
|
<script src="//unpkg.com/docsify"></script>
|
||||||
```
|
```
|
||||||
|
|
||||||
接着就可以愉快地在 Markdown 里写 Vue 了。默认会执行 `new Vue({ el: 'main' })` 创建示例。
|
接着就可以愉快地在 Markdown 里写 Vue 了。默认会执行 `new Vue({ el: '#main' })` 创建示例。
|
||||||
|
|
||||||
*README.md*
|
*README.md*
|
||||||
|
|
||||||
@@ -43,7 +43,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
new Vue({
|
new Vue({
|
||||||
el: 'main',
|
el: '#main',
|
||||||
data: { msg: 'Vue' }
|
data: { msg: 'Vue' }
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
+1188
-751
File diff suppressed because it is too large
Load Diff
Vendored
+2
-2
File diff suppressed because one or more lines are too long
+4
-13
@@ -1,9 +1,8 @@
|
|||||||
this.D = this.D || {};
|
this.D = this.D || {};
|
||||||
this.D.GA = (function () {
|
(function () {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
// From https://github.com/egoist/vue-ga/blob/master/src/index.js
|
// From https://github.com/egoist/vue-ga/blob/master/src/index.js
|
||||||
|
|
||||||
function appendScript () {
|
function appendScript () {
|
||||||
var script = document.createElement('script');
|
var script = document.createElement('script');
|
||||||
script.async = true;
|
script.async = true;
|
||||||
@@ -28,23 +27,15 @@ function collect () {
|
|||||||
window.ga('send', 'pageview');
|
window.ga('send', 'pageview');
|
||||||
}
|
}
|
||||||
|
|
||||||
var install = function () {
|
var install = function (hook) {
|
||||||
if (install.installed) { return }
|
|
||||||
install.installed = true;
|
|
||||||
|
|
||||||
if (!window.$docsify.ga) {
|
if (!window.$docsify.ga) {
|
||||||
console.error('[Docsify] ga is required.');
|
console.error('[Docsify] ga is required.');
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
window.$docsify.plugins = [].concat(function (hook) {
|
hook.beforeEach(collect);
|
||||||
hook.init(collect);
|
|
||||||
hook.beforeEach(collect);
|
|
||||||
}, window.$docsify.plugins);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var ga = install();
|
window.$docsify.plugins = [].concat(install, window.$docsify.plugins);
|
||||||
|
|
||||||
return ga;
|
|
||||||
|
|
||||||
}());
|
}());
|
||||||
|
|||||||
Vendored
+1
-1
@@ -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
@@ -1,166 +1,82 @@
|
|||||||
this.D = this.D || {};
|
this.D = this.D || {};
|
||||||
this.D.Search = (function () {
|
(function () {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var INDEXS = {};
|
var INDEXS = {};
|
||||||
var CONFIG = {
|
var helper;
|
||||||
placeholder: 'Type to search',
|
|
||||||
paths: 'auto',
|
|
||||||
maxAge: 86400000 // 1 day
|
|
||||||
};
|
|
||||||
|
|
||||||
var isObj = function (obj) {
|
function escapeHtml (string) {
|
||||||
return Object.prototype.toString.call(obj) === '[object Object]'
|
|
||||||
};
|
|
||||||
|
|
||||||
var escapeHtml = function (string) {
|
|
||||||
var entityMap = {
|
var entityMap = {
|
||||||
'&': '&',
|
'&': '&',
|
||||||
'<': '<',
|
'<': '<',
|
||||||
'>': '>',
|
'>': '>',
|
||||||
'"': '"',
|
'"': '"',
|
||||||
"'": ''',
|
'\'': ''',
|
||||||
'/': '/'
|
'/': '/'
|
||||||
};
|
};
|
||||||
|
|
||||||
return String(string).replace(/[&<>"'\/]/g, function (s) { return entityMap[s]; })
|
return String(string).replace(/[&<>"'\/]/g, function (s) { return entityMap[s]; })
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
function getAllPaths () {
|
||||||
* find all filepath from A tag
|
var paths = [];
|
||||||
*/
|
|
||||||
var getAllPaths = function () {
|
helper.dom.findAll('a')
|
||||||
var paths = [];[].slice.call(document.querySelectorAll('a'))
|
|
||||||
.map(function (node) {
|
.map(function (node) {
|
||||||
var href = node.href;
|
var href = node.href;
|
||||||
if (/#\/[^#]*?$/.test(href)) {
|
var originHref = node.getAttribute('href');
|
||||||
var path = href.replace(/^[^#]+#/, '');
|
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 paths
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
function saveData (maxAge) {
|
||||||
* return file path
|
localStorage.setItem('docsify.search.expires', Date.now() + maxAge);
|
||||||
*/
|
localStorage.setItem('docsify.search.index', JSON.stringify(INDEXS));
|
||||||
var genFilePath = function (path, basePath) {
|
}
|
||||||
if ( basePath === void 0 ) basePath = window.$docsify.basePath;
|
|
||||||
|
|
||||||
var filePath = /\/$/.test(path) ? (path + "README.md") : (path + ".md");
|
function genIndex (path, content) {
|
||||||
|
|
||||||
filePath = basePath + filePath;
|
|
||||||
|
|
||||||
return filePath.replace(/\/+/g, '/')
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* generate index
|
|
||||||
*/
|
|
||||||
var genIndex = function (path, content) {
|
|
||||||
if ( content === void 0 ) 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;
|
var slug;
|
||||||
|
|
||||||
content
|
tokens.forEach(function (token) {
|
||||||
// remove PRE and TEMPLATE tag
|
if (token.type === 'heading' && token.depth <= 2) {
|
||||||
.replace(/<template[^>]*?>[\s\S]+?<\/template>/g, '')
|
slug = toURL(path, { id: token.text });
|
||||||
// find all html tag
|
index[slug] = { slug: slug, title: token.text, body: '' };
|
||||||
.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>';
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if (panel.classList.contains('results-panel')) {
|
if (!slug) { return }
|
||||||
panel.classList.remove('show');
|
if (!index[slug]) {
|
||||||
panel.innerHTML = '';
|
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
|
return index
|
||||||
SearchComponent.prototype.search = function search (keywords) {
|
}
|
||||||
|
|
||||||
|
function search (keywords) {
|
||||||
var matchingResults = [];
|
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\-\,\\/]+/);
|
keywords = keywords.trim().split(/[\s\-\,\\/]+/);
|
||||||
|
|
||||||
@@ -196,7 +112,7 @@ SearchComponent.prototype.search = function search (keywords) {
|
|||||||
if (end > postContent.length) { end = postContent.length; }
|
if (end > postContent.length) { end = postContent.length; }
|
||||||
|
|
||||||
var matchContent = '...' +
|
var matchContent = '...' +
|
||||||
postContent
|
escapeHtml(postContent)
|
||||||
.substring(start, end)
|
.substring(start, end)
|
||||||
.replace(regEx, ("<em class=\"search-keyword\">" + keyword + "</em>")) +
|
.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
|
return matchingResults
|
||||||
};
|
}
|
||||||
|
|
||||||
var searchPlugin = function () {
|
function init$1 (config, vm) {
|
||||||
var isAuto = CONFIG.paths === 'auto';
|
helper = Docsify;
|
||||||
|
|
||||||
|
var isAuto = config.paths === 'auto';
|
||||||
var isExpired = localStorage.getItem('docsify.search.expires') < Date.now();
|
var isExpired = localStorage.getItem('docsify.search.expires') < Date.now();
|
||||||
|
|
||||||
INDEXS = JSON.parse(localStorage.getItem('docsify.search.index'));
|
INDEXS = JSON.parse(localStorage.getItem('docsify.search.index'));
|
||||||
@@ -234,68 +152,123 @@ var searchPlugin = function () {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var count = 0;
|
var paths = isAuto ? getAllPaths() : config.paths;
|
||||||
var paths = isAuto ? getAllPaths() : CONFIG.paths;
|
|
||||||
var len = paths.length;
|
var len = paths.length;
|
||||||
var ref = window.Docsify.utils;
|
var count = 0;
|
||||||
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));
|
|
||||||
};
|
|
||||||
|
|
||||||
paths.forEach(function (path) {
|
paths.forEach(function (path) {
|
||||||
if (INDEXS[path]) { return count++ }
|
if (INDEXS[path]) { return count++ }
|
||||||
var route;
|
|
||||||
|
|
||||||
// replace route
|
helper
|
||||||
if (alias && alias[path]) {
|
.get(vm.$getFile(path))
|
||||||
route = genFilePath(alias[path] || path, '');
|
.then(function (result) {
|
||||||
} else {
|
INDEXS[path] = genIndex(path, result);
|
||||||
route = genFilePath(path);
|
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) {
|
var html = '';
|
||||||
genIndex(path, marked(content));
|
|
||||||
slugify.clear();
|
|
||||||
count++;
|
|
||||||
|
|
||||||
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 () {
|
window.$docsify.plugins = [].concat(install, window.$docsify.plugins);
|
||||||
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.doneEach(searchPlugin);
|
|
||||||
}, window.$docsify.plugins);
|
|
||||||
};
|
|
||||||
|
|
||||||
var search = install();
|
|
||||||
|
|
||||||
return search;
|
|
||||||
|
|
||||||
}());
|
}());
|
||||||
|
|||||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "docsify",
|
"name": "docsify",
|
||||||
"version": "2.4.2",
|
"version": "3.0.0",
|
||||||
"description": "A magical documentation generator.",
|
"description": "A magical documentation generator.",
|
||||||
"main": "lib/docsify.js",
|
"main": "lib/docsify.js",
|
||||||
"files": [
|
"files": [
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
"build": "rm -rf lib themes && node build/build.js && mkdir lib/themes && mkdir themes && node build/build-css.js",
|
"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: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'",
|
"dev": "node app.js & nodemon -w src -e js,css --exec 'npm run dev:build'",
|
||||||
"test": "eslint src test"
|
"test": "eslint src"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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')
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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'))
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
@@ -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))
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
@@ -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 (/^!>/.test(text)) {
|
||||||
|
return helperTpl('tip', text)
|
||||||
|
} else if (/^\?>/.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
|
||||||
|
}
|
||||||
@@ -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, ':')
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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')
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 = {}
|
||||||
|
}
|
||||||
@@ -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>`
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
@@ -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 = getAlias(path, config.alias)
|
||||||
|
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
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -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, '/')
|
||||||
|
})
|
||||||
@@ -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'
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export * from './core'
|
||||||
|
export * from './env'
|
||||||
@@ -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
@@ -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
|
|
||||||
}
|
|
||||||
})()
|
|
||||||
}
|
|
||||||
-57
@@ -1,57 +0,0 @@
|
|||||||
export default class Hook {
|
|
||||||
constructor () {
|
|
||||||
this.beforeHooks = []
|
|
||||||
this.afterHooks = []
|
|
||||||
this.initHooks = []
|
|
||||||
this.readyHooks = []
|
|
||||||
this.doneEachHooks = []
|
|
||||||
}
|
|
||||||
|
|
||||||
beforeEach (fn) {
|
|
||||||
this.beforeHooks.push(fn)
|
|
||||||
}
|
|
||||||
|
|
||||||
afterEach (fn) {
|
|
||||||
this.afterHooks.push(fn)
|
|
||||||
}
|
|
||||||
|
|
||||||
doneEach (fn) {
|
|
||||||
this.doneEachHooks.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
@@ -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
|
|
||||||
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')
|
|
||||||
window.Docsify.hook.emit('doneEach')
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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()
|
|
||||||
@@ -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)
|
||||||
@@ -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
|
||||||
@@ -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
@@ -1,5 +1,4 @@
|
|||||||
// From https://github.com/egoist/vue-ga/blob/master/src/index.js
|
// From https://github.com/egoist/vue-ga/blob/master/src/index.js
|
||||||
|
|
||||||
function appendScript () {
|
function appendScript () {
|
||||||
const script = document.createElement('script')
|
const script = document.createElement('script')
|
||||||
script.async = true
|
script.async = true
|
||||||
@@ -24,19 +23,13 @@ function collect () {
|
|||||||
window.ga('send', 'pageview')
|
window.ga('send', 'pageview')
|
||||||
}
|
}
|
||||||
|
|
||||||
const install = function () {
|
const install = function (hook) {
|
||||||
if (install.installed) return
|
|
||||||
install.installed = true
|
|
||||||
|
|
||||||
if (!window.$docsify.ga) {
|
if (!window.$docsify.ga) {
|
||||||
console.error('[Docsify] ga is required.')
|
console.error('[Docsify] ga is required.')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
window.$docsify.plugins = [].concat(function (hook) {
|
hook.beforeEach(collect)
|
||||||
hook.init(collect)
|
|
||||||
hook.beforeEach(collect)
|
|
||||||
}, window.$docsify.plugins)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default install()
|
window.$docsify.plugins = [].concat(install, window.$docsify.plugins)
|
||||||
|
|||||||
@@ -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 = {
|
|
||||||
'&': '&',
|
|
||||||
'<': '<',
|
|
||||||
'>': '>',
|
|
||||||
'"': '"',
|
|
||||||
"'": ''',
|
|
||||||
'/': '/'
|
|
||||||
}
|
|
||||||
|
|
||||||
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.doneEach(searchPlugin)
|
|
||||||
}, window.$docsify.plugins)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default install()
|
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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)
|
||||||
@@ -0,0 +1,162 @@
|
|||||||
|
let INDEXS = {}
|
||||||
|
let helper
|
||||||
|
|
||||||
|
function escapeHtml (string) {
|
||||||
|
const entityMap = {
|
||||||
|
'&': '&',
|
||||||
|
'<': '<',
|
||||||
|
'>': '>',
|
||||||
|
'"': '"',
|
||||||
|
'\'': ''',
|
||||||
|
'/': '/'
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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
@@ -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 (/^!>/.test(text)) {
|
|
||||||
return tpl.helper('tip', text)
|
|
||||||
} else if (/^\?>/.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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -7,6 +7,23 @@
|
|||||||
-webkit-font-smoothing: antialiased;
|
-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 {
|
.emoji {
|
||||||
height: 1.2em;
|
height: 1.2em;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
@@ -64,7 +81,7 @@ kbd {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* navbar */
|
/* navbar */
|
||||||
nav {
|
nav.app-nav {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0;
|
right: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
|||||||
@@ -46,6 +46,15 @@ body {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.app-sub-sidebar {
|
||||||
|
.section-link {
|
||||||
|
&::before {
|
||||||
|
content: '-';
|
||||||
|
padding-right: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* markdown content found on pages */
|
/* markdown content found on pages */
|
||||||
.markdown-section h1,
|
.markdown-section h1,
|
||||||
.markdown-section h2,
|
.markdown-section h2,
|
||||||
|
|||||||
-81
@@ -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
@@ -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, ':')
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user