mirror of
https://github.com/wahyd4/docsify.git
synced 2026-08-09 13:26:34 +10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
83d7862505 | ||
|
|
75686f1624 | ||
|
|
780c1e580e | ||
|
|
35dd2e16fa | ||
|
|
be6dcacde7 | ||
|
|
066fff4beb | ||
|
|
42fb331079 | ||
|
|
f15e45c693 | ||
|
|
6b08546654 | ||
|
|
46ff5e14bf | ||
|
|
b51d714d3c | ||
|
|
2c9fdbe7f9 | ||
|
|
bf593a77ad | ||
|
|
3b127a161c | ||
|
|
20a83808f3 | ||
|
|
4419b2119e | ||
|
|
cd99b522e7 | ||
|
|
787656a1ee | ||
|
|
f573caa3c6 | ||
|
|
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 | ||
|
|
8e203b07ab | ||
|
|
eff6d21539 | ||
|
|
dc536a3930 |
@@ -4,6 +4,7 @@
|
||||
"browser": true
|
||||
},
|
||||
"globals": {
|
||||
"Docsify": true,
|
||||
"$docsify": true
|
||||
}
|
||||
}
|
||||
|
||||
+32
-76
@@ -1,82 +1,38 @@
|
||||
### 2.4.1
|
||||
|
||||
> 2017-02-13
|
||||
3.0.4 / 2017-02-20
|
||||
==================
|
||||
|
||||
#### Bug fixes
|
||||
- fix(index): cover page
|
||||
* fix(render): execute script
|
||||
* fix(render): disable rendering sub list when loadSidebar is false
|
||||
|
||||
### 2.4.0
|
||||
3.0.3 / 2017-02-19
|
||||
==================
|
||||
|
||||
> 2017-02-13
|
||||
* fixed look of links in blockquote
|
||||
* fix(scroll) highlight bug
|
||||
|
||||
3.0.2 / 2017-02-19
|
||||
==================
|
||||
|
||||
* fix(search): add lazy input
|
||||
* fix(compiler): link
|
||||
|
||||
3.0.1 / 2017-02-19
|
||||
==================
|
||||
|
||||
* fix(route): empty alias
|
||||
|
||||
3.0.0 / 2017-02-19
|
||||
==================
|
||||
|
||||
#### Breaking change
|
||||
- Not support IE9
|
||||
- The route becomes: `/#/page?id=slug`. The previous route will be redirected
|
||||
|
||||
#### Features
|
||||
|
||||
- feat(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
|
||||
- Faster rendering
|
||||
- Clearer source code
|
||||
- More powerful API
|
||||
- Auto header for each page [#78](https://github.com/QingWei-Li/docsify/issues/78)
|
||||
- Localization for search plugin [#80](https://github.com/QingWei-Li/docsify/issues/80)
|
||||
- Execute the script in markdown (see `executeScript`)
|
||||
|
||||
+99
@@ -1,3 +1,102 @@
|
||||
### 2.4.3
|
||||
|
||||
> 2017-02-15
|
||||
|
||||
#### Bug fixes
|
||||
* fix emoji replacing error (#76)
|
||||
|
||||
### 2.4.2
|
||||
|
||||
> 2017-02-14
|
||||
|
||||
#### Bug fixes
|
||||
- fix(index): load file path error
|
||||
|
||||
|
||||
### 2.4.1
|
||||
|
||||
> 2017-02-13
|
||||
|
||||
#### Bug fixes
|
||||
- fix(index): cover page
|
||||
|
||||
### 2.4.0
|
||||
|
||||
> 2017-02-13
|
||||
|
||||
#### Features
|
||||
|
||||
- feat(hook): add `doneEach`
|
||||
|
||||
|
||||
### 2.3.0
|
||||
|
||||
> 2017-02-13
|
||||
|
||||
#### Features
|
||||
|
||||
- feat(src): add alias feature
|
||||
- docs: update all documents
|
||||
- feat(src): dynamic title
|
||||
- feat(hook): support custom plugin
|
||||
- feat(themes): add dark theme
|
||||
|
||||
#### Bug fixes
|
||||
- fix(event): `auto2top` has no effect on a FF mobile browser, fixed #67
|
||||
- fix: sidebar style
|
||||
- fix(render): fix render link
|
||||
|
||||
### 2.2.1
|
||||
|
||||
> 2017-02-11
|
||||
|
||||
#### Bug fixes
|
||||
- fix(search): crash when not content, fixed #68
|
||||
- fix(event): scroll active sidebar
|
||||
- fix(search): not work in mobile
|
||||
|
||||
### 2.2.0
|
||||
|
||||
#### Features
|
||||
- Add `Google Analytics` plugin.
|
||||
```html
|
||||
<script src="//unpkg.com/docsify" data-ga="UA-XXXXX-Y"></script>
|
||||
<script src="//unpkg.com/docsify/lib/plugins/ga.js"></script>
|
||||
```
|
||||
|
||||
### 2.1.0
|
||||
#### Features
|
||||
- Add search plugin
|
||||
```html
|
||||
<script src="//unpkg.com/docsify"></script>
|
||||
<script src="//unpkg.com/docsify/lib/plugins/search.js"></script>
|
||||
```
|
||||
|
||||
#### Bug fixes
|
||||
- fix sidebar style
|
||||
|
||||
### 2.0.3
|
||||
#### Bug fixes
|
||||
- fix: rendering emojis
|
||||
- fix: css var polyfill
|
||||
|
||||
### 2.0.2
|
||||
|
||||
#### Bug fixes
|
||||
- fix button style in cover page.
|
||||
|
||||
### 2.0.1
|
||||
#### Bug fixes
|
||||
- border style.
|
||||
|
||||
### 2.0.0
|
||||
#### Features
|
||||
- Customize the theme color
|
||||
|
||||
#### Break change
|
||||
- Remove `data-router`, `data-sidebar`, `data-sidebar-toggle` APIs
|
||||
|
||||
|
||||
### 1.10.5
|
||||
#### Bug fixes
|
||||
- fix initialize the Vue instance
|
||||
|
||||
@@ -20,11 +20,11 @@
|
||||
|
||||
## Features
|
||||
- Not build static html files
|
||||
- Simple and lightweight (~13kB gzipped)
|
||||
- Simple and lightweight (~14kB gzipped)
|
||||
- Smart full-text search plugin
|
||||
- Multiple themes
|
||||
- Useful plugin API
|
||||
- Compatible with IE9+
|
||||
- Compatible with IE10+
|
||||
|
||||
## Quick start
|
||||
Create a `index.html`.
|
||||
@@ -52,7 +52,7 @@ Modern browsers and Internet Explorer 9+.
|
||||
## Showcase
|
||||
These open-source projects are using docsify to generate their sites. Pull requests welcome : )
|
||||
|
||||
- [Snipaste](https://docs.snipaste.com/) - A new way to boost your productivity.
|
||||
- [Snipaste](https://docs.snipaste.com/) - Snip & Paste
|
||||
- [puck](https://puck.zz173.com/) - A small & magical php framework.
|
||||
- [Samaritan](http://samaritan.stockdb.org) - An Algorithmic Trading Framework for Digital Currency.
|
||||
- [Vudash](http://vudash.github.io/vudash/) - Powerful, Flexible, Open Source dashboards for anything
|
||||
|
||||
+32
-30
@@ -9,7 +9,7 @@ var build = function (opts) {
|
||||
rollup
|
||||
.rollup({
|
||||
entry: 'src/' + opts.entry,
|
||||
plugins: [buble()].concat(opts.plugins || [])
|
||||
plugins: [buble(), commonjs(), nodeResolve()].concat(opts.plugins || [])
|
||||
})
|
||||
.then(function (bundle) {
|
||||
var dest = 'lib/' + (opts.output || opts.entry)
|
||||
@@ -27,34 +27,36 @@ var build = function (opts) {
|
||||
}
|
||||
|
||||
build({
|
||||
entry: 'index.js',
|
||||
output: 'docsify.js',
|
||||
plugins: [commonjs(), nodeResolve()]
|
||||
entry: 'core/index.js',
|
||||
output: 'docsify.js'
|
||||
})
|
||||
isProd && build({
|
||||
entry: 'index.js',
|
||||
output: 'docsify.min.js',
|
||||
plugins: [commonjs(), nodeResolve(), uglify()]
|
||||
})
|
||||
build({
|
||||
entry: 'plugins/search.js',
|
||||
output: 'plugins/search.js',
|
||||
moduleName: 'D.Search'
|
||||
})
|
||||
isProd && build({
|
||||
entry: 'plugins/search.js',
|
||||
output: 'plugins/search.min.js',
|
||||
moduleName: 'D.Search',
|
||||
plugins: [uglify()]
|
||||
})
|
||||
build({
|
||||
entry: 'plugins/ga.js',
|
||||
output: 'plugins/ga.js',
|
||||
moduleName: 'D.GA'
|
||||
})
|
||||
isProd && build({
|
||||
entry: 'plugins/ga.js',
|
||||
output: 'plugins/ga.min.js',
|
||||
moduleName: 'D.GA',
|
||||
plugins: [uglify()]
|
||||
|
||||
var plugins = [
|
||||
{ name: 'search', entry: 'search/index.js', moduleName: 'Search' },
|
||||
{ name: 'ga', entry: 'ga.js', moduleName: 'GA' }
|
||||
// { name: 'front-matter', entry: 'front-matter/index.js', moduleName: 'FrontMatter' }
|
||||
]
|
||||
|
||||
plugins.forEach(item => {
|
||||
build({
|
||||
entry: 'plugins/' + item.entry,
|
||||
output: 'plugins/' + item.name + '.js',
|
||||
moduleName: 'D.' + item.moduleName
|
||||
})
|
||||
})
|
||||
|
||||
if (isProd) {
|
||||
build({
|
||||
entry: 'core/index.js',
|
||||
output: 'docsify.min.js',
|
||||
plugins: [uglify()]
|
||||
})
|
||||
plugins.forEach(item => {
|
||||
build({
|
||||
entry: 'plugins/' + item.entry,
|
||||
output: 'plugins/' + item.name + '.min.js',
|
||||
moduleName: 'D.' + item.moduleName,
|
||||
plugins: [uglify()]
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4,10 +4,12 @@
|
||||
<meta charset="UTF-8">
|
||||
<title>docsify</title>
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
||||
<link rel="stylesheet" href="/themes/vue.css">
|
||||
<link rel="stylesheet" href="/themes/vue.css" title="vue">
|
||||
<link rel="stylesheet" href="/themes/dark.css" title="dark" disabled>
|
||||
<link rel="stylesheet" href="/themes/buble.css" title="buble" disabled>
|
||||
</head>
|
||||
<body>
|
||||
<nav>
|
||||
<nav data-cloak>
|
||||
<a href="#/">EN</a>
|
||||
<a href="#/zh-cn/">中文</a>
|
||||
</nav>
|
||||
@@ -19,16 +21,14 @@
|
||||
'/zh-cn/changelog': '/changelog',
|
||||
'/changelog': 'https://raw.githubusercontent.com/QingWei-Li/docsify/master/CHANGELOG'
|
||||
},
|
||||
search: {
|
||||
maxAge: 0
|
||||
}
|
||||
executeScript: true
|
||||
}
|
||||
</script>
|
||||
<script
|
||||
src="/lib/docsify.js"
|
||||
data-name="docsify"
|
||||
data-base-path="/docs/"
|
||||
data-load-sidebar
|
||||
data-sub-max-level="2"
|
||||
data-base-path="docs/"
|
||||
data-auto2top></script>
|
||||
</html>
|
||||
|
||||
+2
-2
@@ -11,11 +11,11 @@ See the [Quick start](/quickstart) for more details.
|
||||
## Features
|
||||
|
||||
- Not build static html files
|
||||
- Simple and lightweight (~13kB gzipped)
|
||||
- Simple and lightweight (~14kB gzipped)
|
||||
- Smart full-text search plugin
|
||||
- Multiple themes
|
||||
- Useful plugin API
|
||||
- Compatible with IE9+
|
||||
- Compatible with IE10+
|
||||
|
||||
## Examples
|
||||
|
||||
|
||||
+3
-3
@@ -1,13 +1,13 @@
|
||||

|
||||
|
||||
# docsify <small>2.4.1</small>
|
||||
# docsify <small>3.0</small>
|
||||
|
||||
> A magical documentation site generator.
|
||||
|
||||
- Simple and lightweight (~13kB gzipped)
|
||||
- Simple and lightweight (~14kB gzipped)
|
||||
- Not build static html files
|
||||
- Multiple themes
|
||||
|
||||
|
||||
[GitHub](https://github.com/QingWei-Li/docsify/)
|
||||
[Get Started](#quick-start)
|
||||
[Get Started](#docsify)
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
- [Custom navbar](/custom-navbar)
|
||||
- [Cover page](/cover)
|
||||
|
||||
- Configuration
|
||||
- Customization
|
||||
- [Configuration](/configuration)
|
||||
- [Themes](/themes)
|
||||
- [Using plugins](/plugins)
|
||||
|
||||
+47
-1
@@ -111,7 +111,7 @@ Add TOC in custom sidebar.
|
||||
|
||||
```js
|
||||
window.$docsify = {
|
||||
subMaxLevel: 3
|
||||
subMaxLevel: 2
|
||||
}
|
||||
```
|
||||
|
||||
@@ -219,6 +219,17 @@ See [Markdown configuration](/markdown).
|
||||
|
||||
```js
|
||||
window.$docsify = {
|
||||
// object
|
||||
markdown: {
|
||||
smartypants: true,
|
||||
renderer: {
|
||||
link: function() {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// function
|
||||
markdown: function (marked, renderer) {
|
||||
// ...
|
||||
return marked
|
||||
@@ -254,3 +265,38 @@ window.$docsify = {
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## auto-header
|
||||
|
||||
- type: `Boolean`
|
||||
|
||||
If `loadSidebar` and `autoHeader` are both enabled, for each link in _sidebar.md, prepend a header to the page before converting it to html. [#78](https://github.com/QingWei-Li/docsify/issues/78)
|
||||
|
||||
```js
|
||||
window.$docsify = {
|
||||
loadSidebar: true,
|
||||
autoHeader: true
|
||||
}
|
||||
```
|
||||
|
||||
## execute-script
|
||||
|
||||
- type: `Boolean`
|
||||
|
||||
Execute the script on the page. Only parse the first script tag([demo](themes)). If Vue is present, it is turned on by default.
|
||||
|
||||
```js
|
||||
window.$docsify = {
|
||||
executeScript: true
|
||||
}
|
||||
```
|
||||
|
||||
```markdown
|
||||
## This is test
|
||||
|
||||
<script>
|
||||
console.log(2333)
|
||||
</script>
|
||||
|
||||
```
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
# Cover
|
||||
|
||||
Activate the cover feature by setting `coverpage`. The detail in [Configuration#coverpage](zh-cn/configuration#coverpage).
|
||||
Activate the cover feature by setting `coverpage`. The detail in [Configuration#coverpage](configuration#coverpage).
|
||||
|
||||
## Basic usage
|
||||
|
||||
|
||||
+5
-2
@@ -7,10 +7,12 @@
|
||||
<meta name="keywords" content="doc,docs,documentation,gitbook,creator,generator,github,jekyll,github-pages">
|
||||
<meta name="description" content="A magical documentation generator.">
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
||||
<link rel="stylesheet" href="//unpkg.com/docsify/lib/themes/vue.css">
|
||||
<link rel="stylesheet" href="//unpkg.com/docsify/lib/themes/vue.css" title="vue">
|
||||
<link rel="stylesheet" href="//unpkg.com/docsify/lib/themes/dark.css" title="dark" disabled>
|
||||
<link rel="stylesheet" href="//unpkg.com/docsify/lib/themes/buble.css" title="buble" disabled>
|
||||
</head>
|
||||
<body>
|
||||
<nav>
|
||||
<nav data-cloak>
|
||||
<a href="#/">En</a>
|
||||
<a href="#/zh-cn/">中文</a>
|
||||
</nav>
|
||||
@@ -24,6 +26,7 @@
|
||||
auto2top: true,
|
||||
coverpage: true,
|
||||
loadSidebar: true,
|
||||
executeScript: true,
|
||||
alias: {
|
||||
'/zh-cn/changelog': '/changelog',
|
||||
'/changelog': 'https://raw.githubusercontent.com/QingWei-Li/docsify/master/CHANGELOG'
|
||||
|
||||
+7
-3
@@ -1,12 +1,16 @@
|
||||
# Markdown configuration
|
||||
|
||||
The Markdown parser is [marked](https://github.com/chjj/marked). You can customize how docsify renders your Markdown content to HTML.
|
||||
The Markdown parser is [marked](https://github.com/chjj/marked). You can customize how docsify renders your Markdown content to HTML. Support customize `renderer`.
|
||||
|
||||
```js
|
||||
window.$docsify = {
|
||||
markdown: {
|
||||
smartypants: true
|
||||
// ...
|
||||
smartypants: true,
|
||||
renderer: {
|
||||
link: function() {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
+14
-4
@@ -23,7 +23,13 @@ By default, the hyperlink on the current page is recognized and the content is s
|
||||
search: {
|
||||
maxAge: 86400000, // Expiration time, the default one day
|
||||
paths: [], // or 'auto'
|
||||
placeholder: 'Type to search'
|
||||
placeholder: 'Type to search',
|
||||
|
||||
// Localization
|
||||
placeholder: {
|
||||
'/zh-cn/': '搜索',
|
||||
'/': 'Type to search'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -65,7 +71,7 @@ The hook supports handling asynchronous tasks.
|
||||
```js
|
||||
window.$docsify = {
|
||||
plugins: [
|
||||
function (hook) {
|
||||
function (hook, vm) {
|
||||
hook.init(function() {
|
||||
// Called when the script starts running, only trigger once, no arguments,
|
||||
})
|
||||
@@ -89,15 +95,19 @@ window.$docsify = {
|
||||
// ...
|
||||
})
|
||||
|
||||
hook.mounted(function() {
|
||||
// Called after initial completion. Only trigger once, no arguments.
|
||||
})
|
||||
|
||||
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
|
||||
|
||||
|
||||
@@ -61,3 +61,25 @@ If your system has Python, you can easily to run a static server to preview your
|
||||
```bash
|
||||
cd docs && python -m SimpleHTTPServer 3000
|
||||
```
|
||||
|
||||
## Loading tip
|
||||
|
||||
Show `Loading...` Before starting rendering. You can customize the content.
|
||||
|
||||
*index.html*
|
||||
```html
|
||||
<div id="app">Please wait...</div>
|
||||
```
|
||||
|
||||
You should set the `data-app` attribute if you changed `el`.
|
||||
|
||||
*index.html*
|
||||
```html
|
||||
<div data-app id="main">Please wait...</div>
|
||||
|
||||
<script>
|
||||
window.$docsify = {
|
||||
el: '#main'
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
@@ -12,3 +12,37 @@ There are currently three themes available. Copy [Vue](//vuejs.org) and [buble](
|
||||
!> This compressed files in `/lib/themes/`.
|
||||
|
||||
If you have any ideas or would like to develop new theme, welcome submit [PR](https://github.com/QingWei-Li/docsify/pulls).
|
||||
|
||||
#### Click to preview
|
||||
|
||||
|
||||
<div class="demo-theme-preview">
|
||||
<a data-theme="vue">vue.css</a>
|
||||
<a data-theme="buble">buble.css</a>
|
||||
<a data-theme="dark">dark.css</a>
|
||||
</div>
|
||||
|
||||
|
||||
<style>
|
||||
.demo-theme-preview a {
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.demo-theme-preview a:hover {
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
var preview = Docsify.dom.find('.demo-theme-preview');
|
||||
var themes = Docsify.dom.findAll('[rel="stylesheet"]');
|
||||
|
||||
preview.onclick = function (e) {
|
||||
var title = e.target.getAttribute('data-theme')
|
||||
|
||||
themes.forEach(function (theme) {
|
||||
theme.disabled = theme.title !== title
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
+2
-2
@@ -13,7 +13,7 @@ Load the Vue in `index.html`.
|
||||
```
|
||||
|
||||
Then you can immediately write Vue code at Markdown file.
|
||||
`new Vue({ el: 'main' })` script is executed by default to create instance.
|
||||
`new Vue({ el: '#main' })` script is executed by default to create instance.
|
||||
|
||||
*README.md*
|
||||
|
||||
@@ -44,7 +44,7 @@ You can manually initialize a Vue instance.
|
||||
|
||||
<script>
|
||||
new Vue({
|
||||
el: 'main',
|
||||
el: '#main',
|
||||
data: { msg: 'Vue' }
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -12,11 +12,11 @@ docsify 是一个动态生成文档网站的工具。不同于 GitBook、Hexo
|
||||
|
||||
## 特性
|
||||
- 无需构建,写完文档直接发布
|
||||
- 容易使用并且轻量 (~13Kb gzipped)
|
||||
- 智能的全文检索
|
||||
- 容易使用并且轻量 (~14kB gzipped)
|
||||
- 智能的全文搜索
|
||||
- 提供多套主题
|
||||
- 丰富的 API
|
||||
- 兼容 IE9+
|
||||
- 兼容 IE10+
|
||||
|
||||
## 例子
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
- [定制导航栏](zh-cn/custom-navbar)
|
||||
- [封面](zh-cn/cover)
|
||||
|
||||
- 配置
|
||||
- 定制化
|
||||
- [配置项](zh-cn/configuration)
|
||||
- [主题](zh-cn/themes)
|
||||
- [使用插件](zh-cn/plugins)
|
||||
|
||||
@@ -108,12 +108,12 @@ window.$docsify = {
|
||||
- 类型:`Number`
|
||||
- 默认值: `0`
|
||||
|
||||
自定义侧边栏后默认不会再生成目录,你也可以通过设置生成目录的最大层级开启这个功能,
|
||||
自定义侧边栏后默认不会再生成目录,你也可以通过设置生成目录的最大层级开启这个功能。
|
||||
|
||||
|
||||
```js
|
||||
window.$docsify = {
|
||||
subMaxLevel: 3
|
||||
subMaxLevel: 2
|
||||
}
|
||||
```
|
||||
|
||||
@@ -212,12 +212,23 @@ window.$docsify = {
|
||||
|
||||
## markdown
|
||||
|
||||
- 类型: `Function`
|
||||
- 类型: `Object|Function`
|
||||
|
||||
参考 [Markdown 配置](/zh-cn/markdown)。
|
||||
|
||||
```js
|
||||
window.$docsify = {
|
||||
// object
|
||||
markdown: {
|
||||
smartypants: true,
|
||||
renderer: {
|
||||
link: function() {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// function
|
||||
markdown: function (marked, renderer) {
|
||||
// ...
|
||||
return marked
|
||||
@@ -229,7 +240,7 @@ window.$docsify = {
|
||||
|
||||
- 类型:`String`
|
||||
|
||||
替换主题色。利用 [CSS3 支持变量]((https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_variables)的特性,对于老的浏览器有 polyfill 处理。
|
||||
替换主题色。利用 [CSS3 支持变量](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_variables)的特性,对于老的浏览器有 polyfill 处理。
|
||||
|
||||
```js
|
||||
window.$docsify = {
|
||||
@@ -253,3 +264,37 @@ window.$docsify = {
|
||||
}
|
||||
```
|
||||
|
||||
## auto-header
|
||||
|
||||
- 类型:`Boolean`
|
||||
|
||||
同时设置 `loadSidebar` 和 `autoHeader` 后,可以根据 `_sidebar.md` 的内容自动为每个页面增加标题。[#78](https://github.com/QingWei-Li/docsify/issues/78)
|
||||
|
||||
```js
|
||||
window.$docsify = {
|
||||
loadSidebar: true,
|
||||
autoHeader: true
|
||||
}
|
||||
```
|
||||
|
||||
## execute-script
|
||||
|
||||
- 类型:`Boolean`
|
||||
|
||||
执行文档里的 script 标签里的脚本,只执行第一个 script ([demo](zh-cn/themes))。 如果 Vue 存在,则自动开启。
|
||||
|
||||
```js
|
||||
window.$docsify = {
|
||||
executeScript: true
|
||||
}
|
||||
```
|
||||
|
||||
```markdown
|
||||
## This is test
|
||||
|
||||
<script>
|
||||
console.log(2333)
|
||||
</script>
|
||||
|
||||
```
|
||||
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
# Markdown 配置
|
||||
|
||||
内置的 Markdown 解析器是 [marked](https://github.com/chjj/marked),可以修改它的配置。
|
||||
内置的 Markdown 解析器是 [marked](https://github.com/chjj/marked),可以修改它的配置。同时可以直接配置 `renderer`。
|
||||
|
||||
```js
|
||||
window.$docsify = {
|
||||
markdown: {
|
||||
smartypants: true
|
||||
// ...
|
||||
smartypants: true,
|
||||
renderer: {
|
||||
link: function() {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
+16
-6
@@ -2,9 +2,9 @@
|
||||
|
||||
## 内置插件
|
||||
|
||||
### 全文检索 - Search
|
||||
### 全文搜索 - Search
|
||||
|
||||
全文检索插件会根据当前页面上的超链接获取文档内容,在 `localStorage` 内建立文档索引。默认过期时间为一天,当然我们可以自己指定需要缓存的文件列表或者配置过期时间。
|
||||
全文搜索插件会根据当前页面上的超链接获取文档内容,在 `localStorage` 内建立文档索引。默认过期时间为一天,当然我们可以自己指定需要缓存的文件列表或者配置过期时间。
|
||||
|
||||
|
||||
```html
|
||||
@@ -23,7 +23,13 @@
|
||||
search: {
|
||||
maxAge: 86400000, // 过期时间,单位毫秒,默认一天
|
||||
paths: [], // or 'auto'
|
||||
placeholder: 'Type to search'
|
||||
placeholder: 'Type to search',
|
||||
|
||||
// 支持本地化
|
||||
placeholder: {
|
||||
'/zh-cn/': '搜索',
|
||||
'/': 'Type to search'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -61,7 +67,7 @@ docsify 提供了一套插件机制,其中提供的钩子(hook)支持处
|
||||
```js
|
||||
window.$docsify = {
|
||||
plugins: [
|
||||
function (hook) {
|
||||
function (hook, vm) {
|
||||
hook.init(function() {
|
||||
// 初始化时调用,只调用一次,没有参数。
|
||||
})
|
||||
@@ -84,15 +90,19 @@ window.$docsify = {
|
||||
// ...
|
||||
})
|
||||
|
||||
hook.mounted(function() {
|
||||
// 初始化完成后调用 ,只调用一次,没有参数。
|
||||
})
|
||||
|
||||
hook.ready(function() {
|
||||
// 初始化完成后调用,只调用一次,没有参数。
|
||||
// 初始化并第一次加完成数据后调用,没有参数。
|
||||
})
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
!> 如果需要用 docsify 的内部方法,可以通过 `window.Docsify.utils` 获取。
|
||||
!> 如果需要用 docsify 的内部方法,可以通过 `window.Docsify` 获取,通过 `vm` 获取当前实例。
|
||||
|
||||
#### 例子
|
||||
|
||||
|
||||
@@ -59,3 +59,26 @@ docsify serve docs
|
||||
```bash
|
||||
cd docs && python -m SimpleHTTPServer 3000
|
||||
```
|
||||
|
||||
## Loading 提示
|
||||
|
||||
初始化时会显示 `Loading...` 内容,你可以自定义提示信息。
|
||||
|
||||
*index.html*
|
||||
```html
|
||||
<div id="app">加载中</div>
|
||||
```
|
||||
|
||||
如果更改了 `el` 的配置,需要将该元素加上 `data-app` 属性。
|
||||
|
||||
*index.html*
|
||||
```html
|
||||
<div data-app id="main">加载中</div>
|
||||
|
||||
<script>
|
||||
window.$docsify = {
|
||||
el: '#main'
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
|
||||
+35
-1
@@ -10,4 +10,38 @@
|
||||
|
||||
!> CSS 的压缩文件位于 `/lib/themes/`
|
||||
|
||||
如果你有其他想法或者想开发别的主题,欢迎提 [PR](https://github.com/QingWei-Li/docsify/pulls)。
|
||||
如果你有其他想法或者想开发别的主题,欢迎提 [PR](https://github.com/QingWei-Li/docsify/pulls)。
|
||||
|
||||
#### 点击切换主题
|
||||
|
||||
|
||||
<div class="demo-theme-preview">
|
||||
<a data-theme="vue">vue.css</a>
|
||||
<a data-theme="buble">buble.css</a>
|
||||
<a data-theme="dark">dark.css</a>
|
||||
</div>
|
||||
|
||||
|
||||
<style>
|
||||
.demo-theme-preview a {
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.demo-theme-preview a:hover {
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
var preview = Docsify.dom.find('.demo-theme-preview');
|
||||
var themes = Docsify.dom.findAll('[rel="stylesheet"]');
|
||||
|
||||
preview.onclick = function (e) {
|
||||
var title = e.target.getAttribute('data-theme')
|
||||
|
||||
themes.forEach(function (theme) {
|
||||
theme.disabled = theme.title !== title
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
+2
-2
@@ -12,7 +12,7 @@
|
||||
<script src="//unpkg.com/docsify"></script>
|
||||
```
|
||||
|
||||
接着就可以愉快地在 Markdown 里写 Vue 了。默认会执行 `new Vue({ el: 'main' })` 创建示例。
|
||||
接着就可以愉快地在 Markdown 里写 Vue 了。默认会执行 `new Vue({ el: '#main' })` 创建示例。
|
||||
|
||||
*README.md*
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
|
||||
<script>
|
||||
new Vue({
|
||||
el: 'main',
|
||||
el: '#main',
|
||||
data: { msg: 'Vue' }
|
||||
})
|
||||
</script>
|
||||
|
||||
+1222
-779
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.GA = (function () {
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
// From https://github.com/egoist/vue-ga/blob/master/src/index.js
|
||||
|
||||
function appendScript () {
|
||||
var script = document.createElement('script');
|
||||
script.async = true;
|
||||
@@ -28,23 +27,15 @@ function collect () {
|
||||
window.ga('send', 'pageview');
|
||||
}
|
||||
|
||||
var install = function () {
|
||||
if (install.installed) { return }
|
||||
install.installed = true;
|
||||
|
||||
var install = function (hook) {
|
||||
if (!window.$docsify.ga) {
|
||||
console.error('[Docsify] ga is required.');
|
||||
return
|
||||
}
|
||||
|
||||
window.$docsify.plugins = [].concat(function (hook) {
|
||||
hook.init(collect);
|
||||
hook.beforeEach(collect);
|
||||
}, window.$docsify.plugins);
|
||||
hook.beforeEach(collect);
|
||||
};
|
||||
|
||||
var ga = install();
|
||||
|
||||
return ga;
|
||||
window.$docsify.plugins = [].concat(install, window.$docsify.plugins);
|
||||
|
||||
}());
|
||||
|
||||
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)}();
|
||||
|
||||
+164
-189
@@ -1,166 +1,82 @@
|
||||
this.D = this.D || {};
|
||||
this.D.Search = (function () {
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
var INDEXS = {};
|
||||
var CONFIG = {
|
||||
placeholder: 'Type to search',
|
||||
paths: 'auto',
|
||||
maxAge: 86400000 // 1 day
|
||||
};
|
||||
var helper;
|
||||
|
||||
var isObj = function (obj) {
|
||||
return Object.prototype.toString.call(obj) === '[object Object]'
|
||||
};
|
||||
|
||||
var escapeHtml = function (string) {
|
||||
function escapeHtml (string) {
|
||||
var entityMap = {
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
"'": ''',
|
||||
'\'': ''',
|
||||
'/': '/'
|
||||
};
|
||||
|
||||
return String(string).replace(/[&<>"'\/]/g, function (s) { return entityMap[s]; })
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* find all filepath from A tag
|
||||
*/
|
||||
var getAllPaths = function () {
|
||||
var paths = [];[].slice.call(document.querySelectorAll('a'))
|
||||
function getAllPaths () {
|
||||
var paths = [];
|
||||
|
||||
helper.dom.findAll('a')
|
||||
.map(function (node) {
|
||||
var href = node.href;
|
||||
if (/#\/[^#]*?$/.test(href)) {
|
||||
var path = href.replace(/^[^#]+#/, '');
|
||||
var originHref = node.getAttribute('href');
|
||||
var path = helper.route.parse(href).path;
|
||||
|
||||
if (paths.indexOf(path) <= 0) { paths.push(path); }
|
||||
if (path &&
|
||||
paths.indexOf(path) === -1 &&
|
||||
!helper.route.isAbsolutePath(originHref)) {
|
||||
paths.push(path);
|
||||
}
|
||||
});
|
||||
|
||||
return paths
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* return file path
|
||||
*/
|
||||
var genFilePath = function (path, basePath) {
|
||||
if ( basePath === void 0 ) basePath = window.$docsify.basePath;
|
||||
function saveData (maxAge) {
|
||||
localStorage.setItem('docsify.search.expires', Date.now() + maxAge);
|
||||
localStorage.setItem('docsify.search.index', JSON.stringify(INDEXS));
|
||||
}
|
||||
|
||||
var filePath = /\/$/.test(path) ? (path + "README.md") : (path + ".md");
|
||||
|
||||
filePath = basePath + filePath;
|
||||
|
||||
return filePath.replace(/\/+/g, '/')
|
||||
};
|
||||
|
||||
/**
|
||||
* generate index
|
||||
*/
|
||||
var genIndex = function (path, content) {
|
||||
function genIndex (path, content) {
|
||||
if ( content === void 0 ) content = '';
|
||||
|
||||
INDEXS[path] = { slug: '', title: '', body: '' };
|
||||
var tokens = window.marked.lexer(content);
|
||||
var toURL = Docsify.route.toURL;
|
||||
var index = {};
|
||||
var slug;
|
||||
|
||||
content
|
||||
// remove PRE and TEMPLATE tag
|
||||
.replace(/<template[^>]*?>[\s\S]+?<\/template>/g, '')
|
||||
// find all html tag
|
||||
.replace(/<(\w+)([^>]*?)>([\s\S]+?)<\//g, function (match, tag, attr, html) {
|
||||
// remove all html tag
|
||||
var text = html.replace(/<[^>]+>/g, '');
|
||||
|
||||
// tag is headline
|
||||
if (/^h\d$/.test(tag)) {
|
||||
// <h1 id="xxx"></h1>
|
||||
var id = attr.match(/id="(\S+)"/)[1];
|
||||
|
||||
slug = ("#/" + path + "#" + id).replace(/\/+/, '/');
|
||||
INDEXS[slug] = { slug: slug, title: text, body: '' };
|
||||
} else {
|
||||
if (!slug) { return }
|
||||
// other html tag
|
||||
if (!INDEXS[slug]) {
|
||||
INDEXS[slug] = {};
|
||||
} else {
|
||||
if (INDEXS[slug].body && INDEXS[slug].body.length) {
|
||||
INDEXS[slug].body += '\n' + text;
|
||||
} else {
|
||||
INDEXS[slug].body = text;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* component
|
||||
*/
|
||||
var SearchComponent = function SearchComponent () {
|
||||
if (this.rendered) { return }
|
||||
|
||||
this.style();
|
||||
|
||||
var el = document.createElement('div');
|
||||
var aside = document.querySelector('aside');
|
||||
|
||||
el.classList.add('search');
|
||||
aside.insertBefore(el, aside.children[0]);
|
||||
this.render(el);
|
||||
this.rendered = true;
|
||||
this.bindEvent();
|
||||
};
|
||||
|
||||
SearchComponent.prototype.style = function style () {
|
||||
var code = "\n .sidebar {\n padding-top: 0;\n }\n\n .search {\n margin-bottom: 20px;\n padding: 6px;\n border-bottom: 1px solid #eee;\n }\n\n .search .results-panel {\n display: none;\n }\n\n .search .results-panel.show {\n display: block;\n }\n\n .search input {\n outline: none;\n border: none;\n width: 100%;\n padding: 7px;\n line-height: 22px;\n font-size: 14px;\n }\n\n .search h2 {\n font-size: 17px;\n margin: 10px 0;\n }\n\n .search a {\n text-decoration: none;\n color: inherit;\n }\n\n .search .matching-post {\n border-bottom: 1px solid #eee;\n }\n\n .search .matching-post:last-child {\n border-bottom: 0;\n }\n\n .search p {\n font-size: 14px;\n overflow: hidden;\n text-overflow: ellipsis;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n }\n\n .search p.empty {\n text-align: center;\n }\n ";
|
||||
var style = document.createElement('style');
|
||||
|
||||
style.innerHTML = code;
|
||||
document.head.appendChild(style);
|
||||
};
|
||||
|
||||
SearchComponent.prototype.render = function render (dom) {
|
||||
dom.innerHTML = "<input type=\"search\" placeholder=\"" + (CONFIG.placeholder) + "\" /><div class=\"results-panel\"></div>";
|
||||
};
|
||||
|
||||
SearchComponent.prototype.bindEvent = function bindEvent () {
|
||||
var this$1 = this;
|
||||
|
||||
var search = document.querySelector('.search');
|
||||
var input = search.querySelector('.search input');
|
||||
var panel = search.querySelector('.results-panel');
|
||||
|
||||
search.addEventListener('click', function (e) { return e.target.tagName !== 'A' && e.stopPropagation(); });
|
||||
input.addEventListener('input', function (e) {
|
||||
var target = e.target;
|
||||
|
||||
if (target.value.trim() !== '') {
|
||||
var matchingPosts = this$1.search(target.value);
|
||||
var html = '';
|
||||
|
||||
matchingPosts.forEach(function (post, index) {
|
||||
html += "\n <div class=\"matching-post\">\n <h2><a href=\"" + (post.url) + "\">" + (post.title) + "</a></h2>\n <p>" + (post.content) + "</p>\n </div>\n ";
|
||||
});
|
||||
if (panel.classList.contains('results-panel')) {
|
||||
panel.classList.add('show');
|
||||
panel.innerHTML = html || '<p class="empty">No Results!</p>';
|
||||
}
|
||||
tokens.forEach(function (token) {
|
||||
if (token.type === 'heading' && token.depth <= 2) {
|
||||
slug = toURL(path, { id: token.text });
|
||||
index[slug] = { slug: slug, title: token.text, body: '' };
|
||||
} else {
|
||||
if (panel.classList.contains('results-panel')) {
|
||||
panel.classList.remove('show');
|
||||
panel.innerHTML = '';
|
||||
if (!slug) { return }
|
||||
if (!index[slug]) {
|
||||
index[slug] = { slug: slug, title: '', body: '' };
|
||||
} else {
|
||||
if (index[slug].body) {
|
||||
index[slug].body += '\n' + (token.text || '');
|
||||
} else {
|
||||
index[slug].body = token.text;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// From [weex website] https://weex-project.io/js/common.js
|
||||
SearchComponent.prototype.search = function search (keywords) {
|
||||
return index
|
||||
}
|
||||
|
||||
function search (keywords) {
|
||||
var matchingResults = [];
|
||||
var data = Object.keys(INDEXS).map(function (key) { return INDEXS[key]; });
|
||||
var data = [];
|
||||
Object.keys(INDEXS).forEach(function (key) {
|
||||
data = data.concat(Object.keys(INDEXS[key]).map(function (page) { return INDEXS[key][page]; }));
|
||||
});
|
||||
|
||||
keywords = keywords.trim().split(/[\s\-\,\\/]+/);
|
||||
|
||||
@@ -172,14 +88,14 @@ SearchComponent.prototype.search = function search (keywords) {
|
||||
var postContent = post.body && post.body.trim();
|
||||
var postUrl = post.slug || '';
|
||||
|
||||
if (postTitle !== '' && postContent !== '') {
|
||||
if (postTitle && postContent) {
|
||||
keywords.forEach(function (keyword, i) {
|
||||
var regEx = new RegExp(keyword, 'gi');
|
||||
var indexTitle = -1;
|
||||
var indexContent = -1;
|
||||
|
||||
indexTitle = postTitle.search(regEx);
|
||||
indexContent = postContent.search(regEx);
|
||||
indexTitle = postTitle && postTitle.search(regEx);
|
||||
indexContent = postContent && postContent.search(regEx);
|
||||
|
||||
if (indexTitle < 0 && indexContent < 0) {
|
||||
isMatch = false;
|
||||
@@ -196,7 +112,7 @@ SearchComponent.prototype.search = function search (keywords) {
|
||||
if (end > postContent.length) { end = postContent.length; }
|
||||
|
||||
var matchContent = '...' +
|
||||
postContent
|
||||
escapeHtml(postContent)
|
||||
.substring(start, end)
|
||||
.replace(regEx, ("<em class=\"search-keyword\">" + keyword + "</em>")) +
|
||||
'...';
|
||||
@@ -217,13 +133,15 @@ SearchComponent.prototype.search = function search (keywords) {
|
||||
}
|
||||
};
|
||||
|
||||
for (var i = 0; i < data.length; i++) loop( i );
|
||||
for (var i = 0; i < data.length; i++) loop( i );
|
||||
|
||||
return matchingResults
|
||||
};
|
||||
}
|
||||
|
||||
var searchPlugin = function () {
|
||||
var isAuto = CONFIG.paths === 'auto';
|
||||
function init$1 (config, vm) {
|
||||
helper = Docsify;
|
||||
|
||||
var isAuto = config.paths === 'auto';
|
||||
var isExpired = localStorage.getItem('docsify.search.expires') < Date.now();
|
||||
|
||||
INDEXS = JSON.parse(localStorage.getItem('docsify.search.index'));
|
||||
@@ -234,68 +152,125 @@ var searchPlugin = function () {
|
||||
return
|
||||
}
|
||||
|
||||
var count = 0;
|
||||
var paths = isAuto ? getAllPaths() : CONFIG.paths;
|
||||
var paths = isAuto ? getAllPaths() : config.paths;
|
||||
var len = paths.length;
|
||||
var ref = window.Docsify.utils;
|
||||
var load = ref.load;
|
||||
var marked = ref.marked;
|
||||
var slugify = ref.slugify;
|
||||
var alias = window.$docsify.alias;
|
||||
var done = function () {
|
||||
localStorage.setItem('docsify.search.expires', Date.now() + CONFIG.maxAge);
|
||||
localStorage.setItem('docsify.search.index', JSON.stringify(INDEXS));
|
||||
};
|
||||
var count = 0;
|
||||
|
||||
paths.forEach(function (path) {
|
||||
if (INDEXS[path]) { return count++ }
|
||||
var route;
|
||||
|
||||
// replace route
|
||||
if (alias && alias[path]) {
|
||||
route = genFilePath(alias[path] || path, '');
|
||||
} else {
|
||||
route = genFilePath(path);
|
||||
helper
|
||||
.get(vm.$getFile(path))
|
||||
.then(function (result) {
|
||||
INDEXS[path] = genIndex(path, result);
|
||||
len === ++count && saveData(config.maxAge);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
var dom;
|
||||
|
||||
function style () {
|
||||
var code = "\n.sidebar {\n padding-top: 0;\n}\n\n.search {\n margin-bottom: 20px;\n padding: 6px;\n border-bottom: 1px solid #eee;\n}\n\n.search .results-panel {\n display: none;\n}\n\n.search .results-panel.show {\n display: block;\n}\n\n.search input {\n outline: none;\n border: none;\n width: 100%;\n padding: 7px;\n line-height: 22px;\n font-size: 14px;\n}\n\n.search h2 {\n font-size: 17px;\n margin: 10px 0;\n}\n\n.search a {\n text-decoration: none;\n color: inherit;\n}\n\n.search .matching-post {\n border-bottom: 1px solid #eee;\n}\n\n.search .matching-post:last-child {\n border-bottom: 0;\n}\n\n.search p {\n font-size: 14px;\n overflow: hidden;\n text-overflow: ellipsis;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n}\n\n.search p.empty {\n text-align: center;\n}";
|
||||
var style = dom.create('style', code);
|
||||
dom.appendTo(dom.head, style);
|
||||
}
|
||||
|
||||
function tpl (opts) {
|
||||
var html =
|
||||
"<input type=\"search\" />" +
|
||||
'<div class="results-panel"></div>' +
|
||||
'</div>';
|
||||
var el = dom.create('div', html);
|
||||
var aside = dom.find('aside');
|
||||
|
||||
dom.toggleClass(el, 'search');
|
||||
dom.before(aside, el);
|
||||
}
|
||||
|
||||
function bindEvents () {
|
||||
var $search = dom.find('div.search');
|
||||
var $input = dom.find($search, 'input');
|
||||
var $panel = dom.find($search, '.results-panel');
|
||||
var doSearch = function (value) {
|
||||
if (!value) {
|
||||
$panel.classList.remove('show');
|
||||
$panel.innerHTML = '';
|
||||
return
|
||||
}
|
||||
var matchs = search(value);
|
||||
|
||||
load(route).then(function (content) {
|
||||
genIndex(path, marked(content));
|
||||
slugify.clear();
|
||||
count++;
|
||||
|
||||
if (len === count) { done(); }
|
||||
var html = '';
|
||||
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>';
|
||||
};
|
||||
|
||||
var timeId;
|
||||
// Prevent to Fold sidebar
|
||||
dom.on($search, 'click',
|
||||
function (e) { return e.target.tagName !== 'A' && e.stopPropagation(); });
|
||||
dom.on($input, 'input', function (e) {
|
||||
clearTimeout(timeId);
|
||||
timeId = setTimeout(function (_) { return doSearch(e.target.value.trim()); }, 200);
|
||||
});
|
||||
}
|
||||
|
||||
function updatePlaceholder (text, path) {
|
||||
var $input = dom.getNode('.search input[type="search"]');
|
||||
|
||||
if (typeof text === 'string') {
|
||||
$input.placeholder = text;
|
||||
} else {
|
||||
var match = Object.keys(text).find(function (key) { return path.indexOf(key) > -1; });
|
||||
$input.placeholder = text[match];
|
||||
}
|
||||
}
|
||||
|
||||
function init$$1 (opts) {
|
||||
dom = Docsify.dom;
|
||||
style();
|
||||
tpl(opts);
|
||||
bindEvents();
|
||||
}
|
||||
|
||||
function update (opts, vm) {
|
||||
updatePlaceholder(opts.placeholder, vm.route.path);
|
||||
}
|
||||
|
||||
var CONFIG = {
|
||||
placeholder: 'Type to search',
|
||||
paths: 'auto',
|
||||
maxAge: 86400000 // 1 day
|
||||
};
|
||||
|
||||
var install = function (hook, vm) {
|
||||
var util = Docsify.util;
|
||||
var opts = vm.config.search || CONFIG;
|
||||
|
||||
if (Array.isArray(opts)) {
|
||||
CONFIG.paths = opts;
|
||||
} else if (typeof opts === 'object') {
|
||||
CONFIG.paths = Array.isArray(opts.paths) ? opts.paths : 'auto';
|
||||
CONFIG.maxAge = util.isPrimitive(opts.maxAge) ? opts.maxAge : CONFIG.maxAge;
|
||||
CONFIG.placeholder = opts.placeholder || CONFIG.placeholder;
|
||||
}
|
||||
|
||||
var isAuto = CONFIG.paths === 'auto';
|
||||
|
||||
hook.mounted(function (_) {
|
||||
init$$1(CONFIG);
|
||||
!isAuto && init$1(CONFIG, vm);
|
||||
});
|
||||
hook.doneEach(function (_) {
|
||||
update(CONFIG, vm);
|
||||
isAuto && init$1(CONFIG, vm);
|
||||
});
|
||||
};
|
||||
|
||||
var install = function () {
|
||||
if (install.installed) { return }
|
||||
install.installed = true;
|
||||
|
||||
var userConfig = window.$docsify.search;
|
||||
var isNil = window.Docsify.utils.isNil;
|
||||
|
||||
if (Array.isArray(userConfig)) {
|
||||
CONFIG.paths = userConfig;
|
||||
} else if (isObj(userConfig)) {
|
||||
CONFIG.paths = Array.isArray(userConfig.paths) ? userConfig.paths : 'auto';
|
||||
CONFIG.maxAge = isNil(userConfig.maxAge) ? CONFIG.maxAge : userConfig.maxAge;
|
||||
CONFIG.placeholder = userConfig.placeholder || CONFIG.placeholder;
|
||||
}
|
||||
|
||||
window.$docsify.plugins = [].concat(function (hook) {
|
||||
var isAuto = CONFIG.paths === 'auto';
|
||||
|
||||
hook.ready(function () {
|
||||
new SearchComponent();
|
||||
!isAuto && searchPlugin();
|
||||
});
|
||||
isAuto && hook.doneEach(searchPlugin);
|
||||
}, window.$docsify.plugins);
|
||||
};
|
||||
|
||||
var search = install();
|
||||
|
||||
return search;
|
||||
window.$docsify.plugins = [].concat(install, window.$docsify.plugins);
|
||||
|
||||
}());
|
||||
|
||||
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",
|
||||
"version": "2.4.1",
|
||||
"version": "3.0.4",
|
||||
"description": "A magical documentation generator.",
|
||||
"main": "lib/docsify.js",
|
||||
"files": [
|
||||
@@ -12,7 +12,7 @@
|
||||
"build": "rm -rf lib themes && node build/build.js && mkdir lib/themes && mkdir themes && node build/build-css.js",
|
||||
"dev:build": "rm -rf lib themes && mkdir themes && node build/build.js --dev && node build/build-css.js --dev",
|
||||
"dev": "node app.js & nodemon -w src -e js,css --exec 'npm run dev:build'",
|
||||
"test": "eslint src test"
|
||||
"test": "eslint src"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -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: null,
|
||||
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,90 @@
|
||||
import { isMobile } from '../util/env'
|
||||
import * as dom from '../util/dom'
|
||||
import { parse } from '../route/hash'
|
||||
|
||||
const nav = {}
|
||||
let hoverOver = false
|
||||
|
||||
function highlight () {
|
||||
const sidebar = dom.getNode('.sidebar')
|
||||
const anchors = dom.findAll('.anchor')
|
||||
const wrap = dom.find(sidebar, '.sidebar-nav')
|
||||
let active = dom.find(sidebar, 'li.active')
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
export function scrollActiveSidebar () {
|
||||
if (isMobile) return
|
||||
|
||||
const sidebar = dom.getNode('.sidebar')
|
||||
const lis = dom.findAll(sidebar, 'li')
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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,28 @@
|
||||
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,142 @@
|
||||
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"'
|
||||
}
|
||||
if (title) {
|
||||
title = ` title="${title}"`
|
||||
}
|
||||
return `<a href="${href}"${title}${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,164 @@
|
||||
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
|
||||
if (this.config.executeScript !== false &&
|
||||
typeof window.Vue !== 'undefined' &&
|
||||
!executeScript()) {
|
||||
setTimeout(_ => {
|
||||
const vueVM = window.__EXECUTE_RESULT__
|
||||
vueVM && vueVM.$destroy && vueVM.$destroy()
|
||||
window.__EXECUTE_RESULT__ = new window.Vue().$mount('#main')
|
||||
}, 0)
|
||||
} else {
|
||||
this.config.executeScript && executeScript()
|
||||
}
|
||||
|
||||
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, loadSidebar } = this.config
|
||||
|
||||
this._renderTo('.sidebar-nav', sidebar(text, maxLevel))
|
||||
const active = getAndActive('.sidebar-nav', true, true)
|
||||
loadSidebar && 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 = config.alias ? getAlias(path, config.alias) : path
|
||||
path = getFileName(path)
|
||||
path = path === '/README.md' ? (config.homepage || path) : path
|
||||
path = isAbsolutePath(path) ? path : getPath(base, path)
|
||||
|
||||
return path
|
||||
}
|
||||
}
|
||||
|
||||
let lastRoute = {}
|
||||
|
||||
export function initRoute (vm) {
|
||||
normalize()
|
||||
lastRoute = vm.route = parse()
|
||||
|
||||
on('hashchange', _ => {
|
||||
normalize()
|
||||
vm.route = parse()
|
||||
vm._updateRender()
|
||||
|
||||
if (lastRoute.path === vm.route.path) {
|
||||
vm.$resetEvents()
|
||||
return
|
||||
}
|
||||
|
||||
vm.$fetch()
|
||||
lastRoute = vm.route
|
||||
})
|
||||
}
|
||||
@@ -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 function off (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)
|
||||
}
|
||||
}
|
||||
-146
@@ -1,146 +0,0 @@
|
||||
import * as utils from './util'
|
||||
import { scrollIntoView, activeLink } from './event'
|
||||
import * as render from './render'
|
||||
import Hook from './hook'
|
||||
|
||||
const OPTIONS = utils.merge({
|
||||
el: '#app',
|
||||
repo: '',
|
||||
maxLevel: 6,
|
||||
subMaxLevel: 0,
|
||||
loadSidebar: null,
|
||||
loadNavbar: null,
|
||||
homepage: 'README.md',
|
||||
coverpage: '',
|
||||
basePath: '',
|
||||
auto2top: false,
|
||||
name: '',
|
||||
themeColor: '',
|
||||
nameLink: window.location.pathname,
|
||||
ga: ''
|
||||
}, window.$docsify)
|
||||
const script = document.currentScript || [].slice.call(document.getElementsByTagName('script')).pop()
|
||||
|
||||
// load configuration for script attribute
|
||||
if (script) {
|
||||
for (const prop in OPTIONS) {
|
||||
const val = script.getAttribute('data-' + utils.camel2kebab(prop))
|
||||
OPTIONS[prop] = utils.isNil(val) ? OPTIONS[prop] : (val || true)
|
||||
}
|
||||
if (OPTIONS.loadSidebar === true) OPTIONS.loadSidebar = '_sidebar.md'
|
||||
if (OPTIONS.loadNavbar === true) OPTIONS.loadNavbar = '_navbar.md'
|
||||
if (OPTIONS.coverpage === true) OPTIONS.coverpage = '_coverpage.md'
|
||||
if (OPTIONS.repo === true) OPTIONS.repo = ''
|
||||
if (OPTIONS.name === true) OPTIONS.name = ''
|
||||
}
|
||||
|
||||
const hook = new Hook()
|
||||
|
||||
// utils
|
||||
window.$docsify = OPTIONS
|
||||
window.Docsify = {
|
||||
installed: true,
|
||||
utils: utils.merge({}, utils),
|
||||
hook
|
||||
}
|
||||
|
||||
// load options
|
||||
render.init()
|
||||
|
||||
let cacheRoute = null
|
||||
let cacheXhr = null
|
||||
|
||||
const getAlias = function (route) {
|
||||
if (OPTIONS.alias[route]) {
|
||||
return getAlias(OPTIONS.alias[route])
|
||||
} else {
|
||||
return route
|
||||
}
|
||||
}
|
||||
|
||||
const mainRender = function (cb) {
|
||||
let page
|
||||
let route = utils.getRoute()
|
||||
if (cacheRoute === route) return cb()
|
||||
|
||||
let basePath = cacheRoute = OPTIONS.basePath + route
|
||||
|
||||
if (!/\//.test(basePath)) {
|
||||
basePath = ''
|
||||
} else if (basePath && !/\/$/.test(basePath)) {
|
||||
basePath = basePath.match(/(\S*\/)[^\/]+$/)[1]
|
||||
}
|
||||
|
||||
// replace route
|
||||
route = '/' + route
|
||||
if (OPTIONS.alias && OPTIONS.alias[route]) {
|
||||
route = getAlias(route)
|
||||
} else {
|
||||
route = (OPTIONS.basePath + route).replace(/\/+/, '/')
|
||||
}
|
||||
|
||||
if (!route) {
|
||||
page = OPTIONS.homepage || 'README.md'
|
||||
} else if (/\.md$/.test(route)) {
|
||||
page = route
|
||||
} else if (/\/$/.test(route)) {
|
||||
page = `${route}README.md`
|
||||
} else {
|
||||
page = `${route}.md`
|
||||
}
|
||||
|
||||
// Render Cover page
|
||||
if (OPTIONS.coverpage && page === ('/' + OPTIONS.homepage).replace(/\/+/, '/')) {
|
||||
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
|
||||
|
||||
function appendScript () {
|
||||
const script = document.createElement('script')
|
||||
script.async = true
|
||||
@@ -24,19 +23,13 @@ function collect () {
|
||||
window.ga('send', 'pageview')
|
||||
}
|
||||
|
||||
const install = function () {
|
||||
if (install.installed) return
|
||||
install.installed = true
|
||||
|
||||
const install = function (hook) {
|
||||
if (!window.$docsify.ga) {
|
||||
console.error('[Docsify] ga is required.')
|
||||
return
|
||||
}
|
||||
|
||||
window.$docsify.plugins = [].concat(function (hook) {
|
||||
hook.init(collect)
|
||||
hook.beforeEach(collect)
|
||||
}, window.$docsify.plugins)
|
||||
hook.beforeEach(collect)
|
||||
}
|
||||
|
||||
export default install()
|
||||
window.$docsify.plugins = [].concat(install, window.$docsify.plugins)
|
||||
|
||||
@@ -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,135 @@
|
||||
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')
|
||||
const doSearch = function (value) {
|
||||
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>'
|
||||
}
|
||||
|
||||
let timeId
|
||||
// Prevent to Fold sidebar
|
||||
dom.on($search, 'click',
|
||||
e => e.target.tagName !== 'A' && e.stopPropagation())
|
||||
dom.on($input, 'input', e => {
|
||||
clearTimeout(timeId)
|
||||
timeId = setTimeout(_ => doSearch(e.target.value.trim()), 200)
|
||||
})
|
||||
}
|
||||
|
||||
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 && postTitle.search(regEx)
|
||||
indexContent = postContent && 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)
|
||||
}
|
||||
}
|
||||
@@ -70,7 +70,7 @@ section.cover {
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
.cover-main p:last-child a {
|
||||
.cover-main > p:last-child a {
|
||||
border-radius: 2em;
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
@@ -100,4 +100,13 @@ section.cover {
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
blockquote > p > a {
|
||||
border-bottom: 2px solid var(--theme-color, $color-primary);
|
||||
transition: color .3s;
|
||||
|
||||
&:hover {
|
||||
color: var(--theme-color, $color-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,23 @@
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
body:not(.ready) {
|
||||
[data-cloak] {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
div#app {
|
||||
text-align: center;
|
||||
font-size: 30px;
|
||||
font-weight: lighter;
|
||||
margin: 40vh auto;
|
||||
|
||||
&:empty::before {
|
||||
content: "Loading...";
|
||||
}
|
||||
}
|
||||
|
||||
.emoji {
|
||||
height: 1.2em;
|
||||
vertical-align: middle;
|
||||
@@ -64,7 +81,7 @@ kbd {
|
||||
}
|
||||
|
||||
/* navbar */
|
||||
nav {
|
||||
nav.app-nav {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
left: 0;
|
||||
|
||||
@@ -46,6 +46,15 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
.app-sub-sidebar {
|
||||
.section-link {
|
||||
&::before {
|
||||
content: '-';
|
||||
padding-right: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* markdown content found on pages */
|
||||
.markdown-section h1,
|
||||
.markdown-section h2,
|
||||
|
||||
-81
@@ -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