Compare commits

...
19 Commits
Author SHA1 Message Date
qingwei.li 8b719238e6 -> v1.1.0 2016-12-16 22:38:53 +08:00
qingwei.li 7f0b1089c0 bump 1.1.0 2016-12-16 22:38:44 +08:00
cinwell.liandGitHub 2610e25040 Add progress bar and auto2top (#27)
* Add progress bar

* Add abort ajax

* Remove console

* Add cache folder

* Add auto2top
2016-12-16 22:37:21 +08:00
cinwell.li ec53f1e307 Add Beerpay's badge 2016-12-15 19:11:15 +08:00
qingwei.li 670cc68a28 -> v1.0.3 2016-12-13 22:27:36 +08:00
qingwei.li 844ba3e022 bump 1.0.3 2016-12-13 22:27:26 +08:00
qingwei.li 9092b22246 Fix cache sidebar content 2016-12-13 22:26:56 +08:00
qingwei.li d175ba6913 -> v1.0.2 2016-12-13 22:12:18 +08:00
qingwei.li 2574778605 bump 1.0.2 2016-12-13 22:11:50 +08:00
cinwell.liandGitHub 44a255103c Fix/1.0.2 (#25)
* Fix regular expression, fixed #23

* Fix repeat binding events

* Add content cache

* Add changelog
2016-12-13 22:10:58 +08:00
qingwei.li 440635aeeb -> v1.0.1 2016-12-08 21:24:05 +08:00
qingwei.li a8cc8e46cd bump 1.0.1 2016-12-08 21:23:38 +08:00
qingwei.li 626227be4d Update changelog 2016-12-08 21:22:33 +08:00
qingwei.li c4f02027a8 Fix img style 2016-12-08 21:20:37 +08:00
qingwei.li 8f3eb17dbf Update doc 2016-12-08 21:18:01 +08:00
qingwei.li 61d2474202 -> v1.0.0 2016-12-08 21:13:04 +08:00
qingwei.li 41be8176a6 bump 1.0.0 2016-12-08 21:12:33 +08:00
cinwell.liandGitHub 864935bfc1 1.0 features (#21)
* feat: hash routing, close #2

* Fix router bug

* Remove console

* Add hash router docs

* Improved scrolling on mobile

* Add change log

* Use hash router
2016-12-08 21:11:18 +08:00
qingwei.li b977715d42 Add Snipaste into showcase 2016-12-01 10:18:00 +08:00
22 changed files with 573 additions and 216 deletions
+3
View File
@@ -1,3 +1,6 @@
sudo: false
cache:
directories:
- $HOME/.yarn-cache
language: node_js
node_js: stable
+3 -3
View File
@@ -3,10 +3,10 @@
<head>
<meta charset="UTF-8">
<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/buble.css">
<link rel="stylesheet" href="/themes/vue.css">
</head>
<body>
<body class="">
<div id="app"></div>
</body>
<script src="/lib/docsify.js" data-repo="qingwei-li/docsify" data-sidebar-toggle data-load-navbar></script>
<script src="/lib/docsify.js" data-repo="qingwei-li/docsify" data-auto2top data-sidebar-toggle data-router></script>
</html>
+25
View File
@@ -1,3 +1,28 @@
## 1.1.0
## Features
- Add progress bar
- Add `auto2top` option for hash router
## 1.0.3
### Bug fixes
- Fix cache
## 1.0.2
### Bug fixes
- Fix binding events bug, fixed #24
- Fix regular expression, fixed #23
## 1.0.1
### Bug fixes
- `img` style
## 1.0.0
## Features
- Support hash router
### Bug fixes
- Improved scrolling on mobile
## 0.7.0
### Breaking change
- `themes/` was removed, only exists in the npm package.
+25 -3
View File
@@ -30,16 +30,38 @@ Create a `404.html` and `README.md` into `/docs`.
</html>
```
Or Create a `index.html` and using `hash router`.
index.html
```html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="//unpkg.com/docsify/themes/vue.css">
</head>
<body></body>
<script src="//unpkg.com/docsify" data-router></script>
</html>
```
## Showcase
These open-source projects are using docsify to generate their sites.
These open-source projects are using docsify to generate their sites. Pull requests welcome : )
- [docsify](https://docsify.js.org) - A magical documentation site generator.
- [Snipaste](https://docs.snipaste.com/) - A new way to boost your productivity.
## How to contribute
- Fork it and clone!
- Fork it!
- Run `npm i && npm run dev`
- open `localhost:3000`
## License
MIT
## Support on Beerpay
Hey dude! Help me out for a couple of :beers:!
[![Beerpay](https://beerpay.io/QingWei-Li/docsify/badge.svg?style=beer-square)](https://beerpay.io/QingWei-Li/docsify) [![Beerpay](https://beerpay.io/QingWei-Li/docsify/make-wish.svg?style=flat-square)](https://beerpay.io/QingWei-Li/docsify?focus=wish)
+2 -2
View File
@@ -7,6 +7,6 @@ http.createServer(function (req, res) {
res.writeHead(404, { 'Content-Type': 'text/html' })
res.end(fs.readFileSync('404.dev.html'))
})
}).listen(3000)
}).listen(3000, '0.0.0.0')
console.log(`\nListening at http://localhost:3000\n`)
console.log(`\nListening at http://0.0.0.0:3000\n`)
+15 -3
View File
@@ -213,8 +213,20 @@ If you write a sub level list, it will generate a dropdown list.
- [chinese](/zh-cn)
```
## FAQ
### router
### Why use `404.html` instead of `index.html`
Hash router. You can replace `404.html` with `index.html`.
```html
<script src="/lib/docsify.js" data-router></script>
```
### auto2top
Scroll to the top on changing hash.
```html
<script src="/lib/docsify.js" data-auto2top></script>
```
[issues/7](https://github.com/QingWei-Li/docsify/issues/7)
+4 -3
View File
@@ -9,8 +9,8 @@
</head>
<body>
<nav>
<a href="/">En</a>
<a href="/zh-cn">中文</a>
<a href="#/">En</a>
<a href="#/zh-cn">中文</a>
</nav>
<div id="app"></div>
</body>
@@ -18,5 +18,6 @@
src="//unpkg.com/docsify/lib/docsify.min.js"
data-repo="qingwei-li/docsify"
data-max-level="3"
data-sidebar-toggle></script>
data-sidebar-toggle
data-router></script>
</html>
+17 -5
View File
@@ -97,6 +97,10 @@ docsify serve docs
</nav>
```
### CDN
目前可用的 CDN 有 [UNPKG](unpkg.com/docsify),如果觉得访问较慢可以将文件放到 Pages 的目录下。
### 配置参数
#### repo
@@ -210,12 +214,20 @@ Sidebar 开关按钮
```
## FAQ
### router
### 为什么是 `404.html` 而不用 `index.html`
开启 hash router 功能,此时可以创建 `index.html` 作为入口文件,同时多页面切换不会重新加载资源。资源路径会被替换成 `/#/` 的形式。
docsify 想要实现的是用最简单的方式 **动态渲染内容**
```html
<script src="/lib/docsify.js" data-router></script>
```
例如我有两个文档分别为 `README.md``guide.md`,如果我用 `index.html` 作为文件名,`README.md` 可以被正确的渲染因为我们已经规定它为首页文件,但是如果我们访问 `my-domain.com/guide` 想要得到的结果是 `guide.md` 的内容,它将无法工作,因为目录下并不存在一个 `guide.html` 的文件。
### auto2top
切换路由时自动跳转到页面顶部
```html
<script src="/lib/docsify.js" data-auto2top></script>
```
但是 GitHub Pages 服务器找不到资源, 就会回退并渲染 `404.html` 文件。😄
+255 -126
View File
@@ -4,11 +4,12 @@ var Docsify = (function () {
/**
* Simple ajax
* @param {String} url
* @param {String} [method=get]
* @param {String} [method=GET]
* @param {Function} [loading] handle loading
* @return {Promise}
*/
function load (url, method) {
if ( method === void 0 ) method = 'get';
function load (url, method, loading) {
if ( method === void 0 ) method = 'GET';
var xhr = new XMLHttpRequest();
@@ -19,13 +20,18 @@ function load (url, method) {
then: function (success, error) {
if ( error === void 0 ) error = function () {};
if (loading) {
xhr.addEventListener('progress', loading);
xhr.addEventListener('loaded', loading);
}
xhr.addEventListener('error', error);
xhr.addEventListener('load', function (ref) {
var target = ref.target;
target.status >= 400 ? error(target) : success(target.response);
});
}
},
abort: function () { return xhr.readyState !== 4 && xhr.abort(); }
}
}
@@ -76,6 +82,127 @@ function isNil (o) {
return o === null || o === undefined
}
var cacheRoute$1 = null;
var cacheHash = null;
/**
* hash route
*/
function getRoute () {
var loc = window.location;
if (cacheHash === loc.hash && !isNil(cacheRoute$1)) { return cacheRoute$1 }
var route = loc.hash.match(/^#\/([^#]+)/);
if (route && route.length === 2) {
route = route[1];
} else {
route = /^#\//.test(loc.hash) ? '' : loc.pathname;
}
cacheRoute$1 = route;
cacheHash = loc.hash;
return route
}
/**
* Active sidebar when scroll
* @link https://buble.surge.sh/
*/
function scrollActiveSidebar () {
if (/mobile/i.test(navigator.userAgent)) { return }
var anchors = document.querySelectorAll('.anchor');
var nav = {};
var lis = document.querySelectorAll('.sidebar li');
var active = null;
for (var i = 0, len = lis.length; i < len; i += 1) {
var li = lis[i];
var href = li.querySelector('a').getAttribute('href');
if (href !== '/') { href = href.match(/#([^#]+)$/g)[0].slice(1); }
nav[href] = li;
}
function highlight () {
for (var i = 0, len = anchors.length; i < len; i += 1) {
var node = anchors[i].parentNode;
var bcr = node.getBoundingClientRect();
if (bcr.top < 10 && bcr.bottom > 10) {
var li = nav[node.id];
if (!li || li === active) { return }
if (active) { active.setAttribute('class', ''); }
li.setAttribute('class', 'active');
active = li;
return
}
}
}
var dom = document.querySelector('main .content');
dom.removeEventListener('scroll', highlight);
dom.addEventListener('scroll', highlight);
highlight();
}
function scrollIntoView () {
var id = window.location.hash.match(/#[^#\/]+$/g);
if (!id || !id.length) { return }
var section = document.querySelector(id[0]);
if (section) { section.scrollIntoView(); }
}
/**
* Acitve link
*/
function activeLink (dom, activeParent) {
var host = window.location.href;
dom = typeof dom === 'object' ? dom : document.querySelector(dom);
if (!dom) { return
; }[].slice.call(dom.querySelectorAll('a')).forEach(function (node) {
if (node.href === host) {
activeParent
? node.parentNode.setAttribute('class', 'active')
: node.setAttribute('class', 'active');
} else {
activeParent
? node.parentNode.removeAttribute('class')
: node.removeAttribute('class');
}
});
}
/**
* sidebar toggle
*/
function bindToggle (dom) {
dom = typeof dom === 'object' ? dom : document.querySelector(dom);
if (!dom) { return }
var body = document.body;
dom.addEventListener('click', function () { return body.classList.toggle('close'); });
if (!/mobile/i.test(navigator.userAgent)) { return }
document.querySelector('aside').addEventListener('click', function (event) {
body.classList.toggle('close');
});
}
var cacheContentDOM;
function scroll2Top (dom) {
cacheContentDOM = cacheContentDOM || document.querySelector(dom);
cacheContentDOM.scrollTop = 0;
}
var commonjsGlobal = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
@@ -2217,93 +2344,8 @@ function tree (toc, tpl) {
return tpl
}
/**
* Active sidebar when scroll
* @link https://buble.surge.sh/
*/
function scrollActiveSidebar () {
if (/mobile/i.test(navigator.userAgent)) { return }
var anchors = document.querySelectorAll('.anchor');
var nav = {};
var lis = document.querySelectorAll('.sidebar li');
var active = null;
for (var i = 0, len = lis.length; i < len; i += 1) {
var li = lis[i];
var a = li.querySelector('a');
nav[a.getAttribute('href').slice(1)] = li;
}
function highlight () {
for (var i = 0, len = anchors.length; i < len; i += 1) {
var node = anchors[i].parentNode;
var bcr = node.getBoundingClientRect();
if (bcr.top < 10 && bcr.bottom > 10) {
var li = nav[node.id];
if (!li) { return }
if (li === active) { return }
if (active) { active.setAttribute('class', ''); }
li.setAttribute('class', 'active');
active = li;
return
}
}
}
document.querySelector('main .content').addEventListener('scroll', highlight);
highlight();
function scrollIntoView () {
var id = window.location.hash.slice(1);
if (!id) { return }
var section = document.querySelector('#' + id);
if (section) { section.scrollIntoView(); }
}
window.addEventListener('hashchange', scrollIntoView);
scrollIntoView();
}
/**
* Acitve link
*/
function activeLink (dom, activeParent) {
var host = document.location.origin + document.location.pathname;
dom = typeof dom === 'object' ? dom : document.querySelector(dom);
if (!dom) { return
; }[].slice.call(dom.querySelectorAll('a')).forEach(function (node) {
if (node.href === host) {
activeParent
? node.parentNode.setAttribute('class', 'active')
: node.setAttribute('class', 'active');
}
});
}
/**
* sidebar toggle
*/
function bindToggle (dom) {
dom = typeof dom === 'object' ? dom : document.querySelector(dom);
if (!dom) { return }
var body = document.body;
dom.addEventListener('click', function () { return body.classList.toggle('close'); });
if (!/mobile/i.test(navigator.userAgent)) { return }
document.querySelector('aside').addEventListener('click', function (event) {
body.classList.toggle('close');
});
}
var OPTIONS$1 = {};
var CACHE = {};
var renderTo = function (dom, content) {
dom = typeof dom === 'object' ? dom : document.querySelector(dom);
@@ -2319,11 +2361,16 @@ var renderer = new marked.Renderer();
* @link https://github.com/chjj/marked#overriding-renderer-methods
*/
renderer.heading = function (text, level) {
var slug = text.toLowerCase().replace(/<(?:.|\n)*?>/gm, '').replace(/[\s\n\t]+/g, '-');
var slug = text.toLowerCase().replace(/<(?:.|\n)*?>/gm, '').replace(/[^\w|\u4e00-\u9fa5]+/g, '-');
var route = '';
toc.push({ level: level, slug: '#' + slug, title: text });
if (OPTIONS$1.router) {
route = "#/" + (getRoute());
}
return ("<h" + level + " id=\"" + slug + "\"><a href=\"#" + slug + "\" class=\"anchor\"></a>" + text + "</h" + level + ">")
toc.push({ level: level, slug: (route + "#" + slug), title: text });
return ("<h" + level + " id=\"" + slug + "\"><a href=\"" + route + "#" + slug + "\" class=\"anchor\"></a>" + text + "</h" + level + ">")
};
// highlight code
renderer.code = function (code, lang) {
@@ -2333,15 +2380,22 @@ renderer.code = function (code, lang) {
return ("<pre data-lang=\"" + lang + "\"><code class=\"lang-" + lang + "\">" + hl + "</code></pre>")
};
renderer.link = function (href, title, text) {
if (OPTIONS$1.router && !/^(?:\w+:)?\/\/([^\s\.]+\.\S{2}|localhost[\:?\d]*)\S*$/.test(href)) {
href = !/^\/#/.test(href) ? ("#" + href) : href;
}
return ("<a href=\"" + href + "\" title=\"" + (title || '') + "\">" + text + "</a>")
};
marked.setOptions({ renderer: renderer });
/**
* App
*/
function renderApp (dom, replace, opts) {
function renderApp (dom, replace) {
var nav = document.querySelector('nav') || document.createElement('nav');
dom[replace ? 'outerHTML' : 'innerHTML'] = toggle(opts.sidebarToggle) + corner(opts.repo) + main();
dom[replace ? 'outerHTML' : 'innerHTML'] = toggle(OPTIONS$1.sidebarToggle) + corner(OPTIONS$1.repo) + main();
document.body.insertBefore(nav, document.body.children[0]);
// bind toggle
@@ -2351,18 +2405,22 @@ function renderApp (dom, replace, opts) {
/**
* article
*/
function renderArticle (content, OPTIONS) {
function renderArticle (content) {
renderTo('article', content ? marked(content) : 'not found');
if (!renderSidebar.rendered) { renderSidebar(null, OPTIONS); }
if (!renderNavbar.rendered) { renderNavbar(null, OPTIONS); }
if (!renderSidebar.rendered) { renderSidebar(null, OPTIONS$1); }
if (!renderNavbar.rendered) { renderNavbar(null, OPTIONS$1); }
renderSidebar.rendered = false;
renderNavbar.rendered = false;
if (OPTIONS$1.auto2top) { scroll2Top('section.content'); }
}
/**
* navbar
*/
function renderNavbar (content, OPTIONS) {
if ( OPTIONS === void 0 ) OPTIONS = {};
function renderNavbar (content) {
if (CACHE['navbar'] && CACHE['navbar'] === content) { return }
CACHE['navbar'] = content;
renderNavbar.rendered = true;
if (content) { renderTo('nav', marked(content)); }
@@ -2372,24 +2430,52 @@ function renderNavbar (content, OPTIONS) {
/**
* sidebar
*/
function renderSidebar (content, OPTIONS) {
if ( OPTIONS === void 0 ) OPTIONS = {};
renderSidebar.rendered = true;
function renderSidebar (content) {
var isToc = false;
if (content) {
content = marked(content);
} else if (OPTIONS.sidebar) {
content = tree(OPTIONS.sidebar, '<ul>');
} else if (OPTIONS$1.sidebar) {
content = tree(OPTIONS$1.sidebar, '<ul>');
} else {
content = tree(genTree(toc, OPTIONS.maxLevel), '<ul>');
content = tree(genTree(toc, OPTIONS$1.maxLevel), '<ul>');
isToc = true;
}
renderSidebar.rendered = true;
if (CACHE['sidebar'] && CACHE['sidebar'] === content) { return }
CACHE['sidebar'] = content;
renderTo('aside.sidebar', content);
isToc ? scrollActiveSidebar() : activeLink('aside.sidebar', true);
if (isToc) { scrollActiveSidebar(); }
toc = [];
}
/**
* render loading bar
* @return {[type]} [description]
*/
function renderLoading (ref) {
var loaded = ref.loaded;
var total = ref.total;
var num = Math.floor(loaded / total * 100);
if (!CACHE['loading']) {
var div = document.createElement('div');
div.classList.add('progress');
document.body.appendChild(div);
CACHE['loading'] = div;
}
CACHE['loading'].style.width = num >= 95 ? '0%' : num + '%';
}
/**
* Load Config
* @param {Object} options
*/
function config (options) {
OPTIONS$1 = options;
}
var OPTIONS = {
@@ -2399,7 +2485,9 @@ var OPTIONS = {
sidebar: '',
sidebarToggle: false,
loadSidebar: null,
loadNavbar: null
loadNavbar: null,
router: false,
auto2top: false
};
var script = document.currentScript || [].slice.call(document.getElementsByTagName('script')).pop();
@@ -2414,31 +2502,72 @@ if (script) {
if (OPTIONS.sidebar) { OPTIONS.sidebar = window[OPTIONS.sidebar]; }
}
var Docsify = function () {
var dom = document.querySelector(OPTIONS.el) || document.body;
var replace = dom !== document.body;
var loc = document.location.pathname;
// load options
config(OPTIONS);
if (/\/$/.test(loc)) { loc += 'README'; }
var cacheRoute = null;
var cacheXhr = null;
// Render app
renderApp(dom, replace, OPTIONS);
var mainRender = function (cb) {
var route = getRoute();
if (cacheRoute === route) { return cb() }
var wait;
var basePath = cacheRoute = route;
if (!/\//.test(basePath)) {
basePath = '';
} else if (basePath && !/\/$/.test(basePath)) {
basePath = basePath.match(/(\S*\/)[^\/]+$/)[1];
}
cacheXhr && cacheXhr.abort && cacheXhr.abort();
// Render markdown file
load((loc + ".md"))
.then(function (content) { return renderArticle(content, OPTIONS); },
function (_) { return renderArticle(null, OPTIONS); });
cacheXhr = load(
(!route || /\/$/.test(route)) ? (route + "README.md") : (route + ".md"),
'GET',
renderLoading);
cacheXhr.then(function (result) {
renderArticle(result);
if (OPTIONS.loadSidebar) {
if (wait === false) { cb(); }
wait = false;
} else {
cb();
}
}, function (_) { return renderArticle(null); });
// Render sidebar
if (OPTIONS.loadSidebar) {
load(OPTIONS.loadSidebar)
.then(function (content) { return renderSidebar(content, OPTIONS); });
load(basePath + OPTIONS.loadSidebar).then(function (result) {
renderSidebar(result);
if (wait === false) { cb(); }
wait = false;
});
}
// Render navbar
if (OPTIONS.loadNavbar) {
load(OPTIONS.loadNavbar)
.then(function (content) { return renderNavbar(content, OPTIONS); });
load(basePath + OPTIONS.loadNavbar).then(renderNavbar);
}
};
var Docsify = function () {
var dom = document.querySelector(OPTIONS.el) || document.body;
var replace = dom !== document.body;
var main = function () {
mainRender(function (_) {
activeLink('aside.sidebar', true);
scrollIntoView();
});
};
// Render app
renderApp(dom, replace);
main();
if (OPTIONS.router) {
if (!/^#\//.test(window.location.hash)) { window.location.hash = '#/'; }
window.addEventListener('hashchange', main);
}
};
+2 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "docsify",
"version": "0.7.0",
"version": "1.1.0",
"description": "A magical documentation generator.",
"main": "lib/docsify.js",
"files": [
@@ -25,7 +25,7 @@
"generator"
],
"author": "qingwei-li <cinwell.li@gmail.com> (https://github.com/QingWei-Li)",
"homepage": "https://QingWei-Li.github.io/docsify",
"homepage": "https://docsify.js.org",
"license": "MIT",
"devDependencies": {
"cssnano": "^3.8.1",
+25 -15
View File
@@ -13,9 +13,11 @@ export function scrollActiveSidebar () {
for (let i = 0, len = lis.length; i < len; i += 1) {
const li = lis[i]
const a = li.querySelector('a')
let href = li.querySelector('a').getAttribute('href')
nav[a.getAttribute('href').slice(1)] = li
if (href !== '/') href = href.match(/#([^#]+)$/g)[0].slice(1)
nav[href] = li
}
function highlight () {
@@ -26,8 +28,7 @@ export function scrollActiveSidebar () {
if (bcr.top < 10 && bcr.bottom > 10) {
const li = nav[node.id]
if (!li) return
if (li === active) return
if (!li || li === active) return
if (active) active.setAttribute('class', '')
li.setAttribute('class', 'active')
@@ -38,26 +39,25 @@ export function scrollActiveSidebar () {
}
}
document.querySelector('main .content').addEventListener('scroll', highlight)
const dom = document.querySelector('main .content')
dom.removeEventListener('scroll', highlight)
dom.addEventListener('scroll', highlight)
highlight()
}
function scrollIntoView () {
const id = window.location.hash.slice(1)
if (!id) return
const section = document.querySelector('#' + id)
export function scrollIntoView () {
const id = window.location.hash.match(/#[^#\/]+$/g)
if (!id || !id.length) return
const section = document.querySelector(id[0])
if (section) section.scrollIntoView()
}
window.addEventListener('hashchange', scrollIntoView)
scrollIntoView()
if (section) section.scrollIntoView()
}
/**
* Acitve link
*/
export function activeLink (dom, activeParent) {
const host = document.location.origin + document.location.pathname
const host = window.location.href
dom = typeof dom === 'object' ? dom : document.querySelector(dom)
if (!dom) return
@@ -67,6 +67,10 @@ export function activeLink (dom, activeParent) {
activeParent
? node.parentNode.setAttribute('class', 'active')
: node.setAttribute('class', 'active')
} else {
activeParent
? node.parentNode.removeAttribute('class')
: node.removeAttribute('class')
}
})
}
@@ -86,3 +90,9 @@ export function bindToggle (dom) {
body.classList.toggle('close')
})
}
let cacheContentDOM
export function scroll2Top (dom) {
cacheContentDOM = cacheContentDOM || document.querySelector(dom)
cacheContentDOM.scrollTop = 0
}
+60 -16
View File
@@ -1,4 +1,5 @@
import { load, camel2kebab, isNil } from './util'
import { load, camel2kebab, isNil, getRoute } from './util'
import { activeLink, scrollIntoView } from './event'
import * as render from './render'
const OPTIONS = {
@@ -8,7 +9,9 @@ const OPTIONS = {
sidebar: '',
sidebarToggle: false,
loadSidebar: null,
loadNavbar: null
loadNavbar: null,
router: false,
auto2top: false
}
const script = document.currentScript || [].slice.call(document.getElementsByTagName('script')).pop()
@@ -23,31 +26,72 @@ if (script) {
if (OPTIONS.sidebar) OPTIONS.sidebar = window[OPTIONS.sidebar]
}
const Docsify = function () {
const dom = document.querySelector(OPTIONS.el) || document.body
const replace = dom !== document.body
let loc = document.location.pathname
// load options
render.config(OPTIONS)
if (/\/$/.test(loc)) loc += 'README'
let cacheRoute = null
let cacheXhr = null
// Render app
render.renderApp(dom, replace, OPTIONS)
const mainRender = function (cb) {
const route = getRoute()
if (cacheRoute === route) return cb()
let wait
let basePath = cacheRoute = route
if (!/\//.test(basePath)) {
basePath = ''
} else if (basePath && !/\/$/.test(basePath)) {
basePath = basePath.match(/(\S*\/)[^\/]+$/)[1]
}
cacheXhr && cacheXhr.abort && cacheXhr.abort()
// Render markdown file
load(`${loc}.md`)
.then(content => render.renderArticle(content, OPTIONS),
_ => render.renderArticle(null, OPTIONS))
cacheXhr = load(
(!route || /\/$/.test(route)) ? `${route}README.md` : `${route}.md`,
'GET',
render.renderLoading)
cacheXhr.then(result => {
render.renderArticle(result)
if (OPTIONS.loadSidebar) {
if (wait === false) cb()
wait = false
} else {
cb()
}
}, _ => render.renderArticle(null))
// Render sidebar
if (OPTIONS.loadSidebar) {
load(OPTIONS.loadSidebar)
.then(content => render.renderSidebar(content, OPTIONS))
load(basePath + OPTIONS.loadSidebar).then(result => {
render.renderSidebar(result)
if (wait === false) cb()
wait = false
})
}
// Render navbar
if (OPTIONS.loadNavbar) {
load(OPTIONS.loadNavbar)
.then(content => render.renderNavbar(content, OPTIONS))
load(basePath + OPTIONS.loadNavbar).then(render.renderNavbar)
}
}
const Docsify = function () {
const dom = document.querySelector(OPTIONS.el) || document.body
const replace = dom !== document.body
const main = function () {
mainRender(_ => {
activeLink('aside.sidebar', true)
scrollIntoView()
})
}
// Render app
render.renderApp(dom, replace)
main()
if (OPTIONS.router) {
if (!/^#\//.test(window.location.hash)) window.location.hash = '#/'
window.addEventListener('hashchange', main)
}
}
+63 -14
View File
@@ -1,8 +1,11 @@
import marked from 'marked'
import Prism from 'prismjs'
import * as tpl from './tpl'
import { activeLink, scrollActiveSidebar, bindToggle } from './event'
import { genTree } from './util'
import { activeLink, scrollActiveSidebar, bindToggle, scroll2Top } from './event'
import { genTree, getRoute } from './util'
let OPTIONS = {}
const CACHE = {}
const renderTo = function (dom, content) {
dom = typeof dom === 'object' ? dom : document.querySelector(dom)
@@ -10,7 +13,7 @@ const renderTo = function (dom, content) {
return dom
}
const toc = []
let toc = []
const renderer = new marked.Renderer()
/**
@@ -18,11 +21,16 @@ const renderer = new marked.Renderer()
* @link https://github.com/chjj/marked#overriding-renderer-methods
*/
renderer.heading = function (text, level) {
const slug = text.toLowerCase().replace(/<(?:.|\n)*?>/gm, '').replace(/[\s\n\t]+/g, '-')
const slug = text.toLowerCase().replace(/<(?:.|\n)*?>/gm, '').replace(/[^\w|\u4e00-\u9fa5]+/g, '-')
let route = ''
toc.push({ level, slug: '#' + slug, title: text })
if (OPTIONS.router) {
route = `#/${getRoute()}`
}
return `<h${level} id="${slug}"><a href="#${slug}" class="anchor"></a>${text}</h${level}>`
toc.push({ level, slug: `${route}#${slug}`, title: text })
return `<h${level} id="${slug}"><a href="${route}#${slug}" class="anchor"></a>${text}</h${level}>`
}
// highlight code
renderer.code = function (code, lang = '') {
@@ -30,15 +38,22 @@ renderer.code = function (code, lang = '') {
return `<pre data-lang="${lang}"><code class="lang-${lang}">${hl}</code></pre>`
}
renderer.link = function (href, title, text) {
if (OPTIONS.router && !/^(?:\w+:)?\/\/([^\s\.]+\.\S{2}|localhost[\:?\d]*)\S*$/.test(href)) {
href = !/^\/#/.test(href) ? `#${href}` : href
}
return `<a href="${href}" title="${title || ''}">${text}</a>`
}
marked.setOptions({ renderer })
/**
* App
*/
export function renderApp (dom, replace, opts) {
export function renderApp (dom, replace) {
const nav = document.querySelector('nav') || document.createElement('nav')
dom[replace ? 'outerHTML' : 'innerHTML'] = tpl.toggle(opts.sidebarToggle) + tpl.corner(opts.repo) + tpl.main()
dom[replace ? 'outerHTML' : 'innerHTML'] = tpl.toggle(OPTIONS.sidebarToggle) + tpl.corner(OPTIONS.repo) + tpl.main()
document.body.insertBefore(nav, document.body.children[0])
// bind toggle
@@ -48,16 +63,22 @@ export function renderApp (dom, replace, opts) {
/**
* article
*/
export function renderArticle (content, OPTIONS) {
export function renderArticle (content) {
renderTo('article', content ? marked(content) : 'not found')
if (!renderSidebar.rendered) renderSidebar(null, OPTIONS)
if (!renderNavbar.rendered) renderNavbar(null, OPTIONS)
renderSidebar.rendered = false
renderNavbar.rendered = false
if (OPTIONS.auto2top) scroll2Top('section.content')
}
/**
* navbar
*/
export function renderNavbar (content, OPTIONS = {}) {
export function renderNavbar (content) {
if (CACHE['navbar'] && CACHE['navbar'] === content) return
CACHE['navbar'] = content
renderNavbar.rendered = true
if (content) renderTo('nav', marked(content))
@@ -67,9 +88,7 @@ export function renderNavbar (content, OPTIONS = {}) {
/**
* sidebar
*/
export function renderSidebar (content, OPTIONS = {}) {
renderSidebar.rendered = true
export function renderSidebar (content) {
let isToc = false
if (content) {
@@ -81,6 +100,36 @@ export function renderSidebar (content, OPTIONS = {}) {
isToc = true
}
renderSidebar.rendered = true
if (CACHE['sidebar'] && CACHE['sidebar'] === content) return
CACHE['sidebar'] = content
renderTo('aside.sidebar', content)
isToc ? scrollActiveSidebar() : activeLink('aside.sidebar', true)
if (isToc) scrollActiveSidebar()
toc = []
}
/**
* render loading bar
* @return {[type]} [description]
*/
export function renderLoading ({ loaded, total }) {
const num = Math.floor(loaded / total * 100)
if (!CACHE['loading']) {
const div = document.createElement('div')
div.classList.add('progress')
document.body.appendChild(div)
CACHE['loading'] = div
}
CACHE['loading'].style.width = num >= 95 ? '0%' : num + '%'
}
/**
* Load Config
* @param {Object} options
*/
export function config (options) {
OPTIONS = options
}
+31 -4
View File
@@ -8,12 +8,23 @@
-webkit-font-smoothing: antialiased;
}
.progress {
background-color: $color-primary;
height: 2px;
left: 0px;
position: fixed;
right: 0px;
top: 0px;
transition: width 0.2s, opacity 0.4s;
width: 0%;
z-index: 999999;
}
html, body {
height: 100%;
}
body {
background-color: #fff;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
font-family: 'Source Sans Pro', 'Helvetica Neue', Arial, sans-serif;
@@ -23,6 +34,10 @@ body {
overflow-x: hidden;
}
img {
max-width: 100%;
}
/* navbar */
nav {
position: absolute;
@@ -113,6 +128,13 @@ nav {
top: 0;
right: 0;
z-index: 1;
text-decoration: none;
border-bottom: 0;
&:hover {
background-color: inherit;
color: inherit;
}
&:hover .octo-arm {
animation:octocat-wave 560ms ease-in-out;
@@ -134,7 +156,6 @@ main {
/* sidebar */
.sidebar {
background-color: #fff;
border-right: 1px solid rgba(0, 0, 0, .07);
overflow-y: auto;
padding-top: 40px;
@@ -225,6 +246,10 @@ body.close {
}
@media (max-width: 600px) {
nav, .github-corner, .sidebar-toggle, .sidebar {
position: fixed;
}
nav {
margin-top: 16px;
}
@@ -242,6 +267,8 @@ body.close {
left: 0;
min-width: 100vw;
transition: transform 250ms ease;
position: static;
overflow-y: auto;
}
nav, .github-corner {
@@ -264,8 +291,8 @@ body.close {
.github-corner {
&:hover .octo-arm {
animation: none;
}
animation: none;
}
.octo-arm {
animation: octocat-wave 560ms ease-in-out;
}
+2 -6
View File
@@ -146,7 +146,7 @@ body {
padding: 8px;
margin: 0 0 1em 0;
font-family: Inconsolata;
padding: 12px 10px 12px 12px;
padding: 0 10px 12px 0;
font-size: 16px;
overflow: auto;
word-wrap: normal;
@@ -228,7 +228,7 @@ body {
max-width: inherit;
position: relative;
background-color: #f8f8f8;
padding: 0.8em 0.8em 0.4em;
padding: 20px 0.8em 20px;
line-height: 1.1em;
border-radius: 2px;
}
@@ -243,10 +243,6 @@ code .token {
-moz-osx-font-smoothing: initial;
}
.content img {
max-width: 100%;
}
.content span.light {
color: #7f8c8d;
}
+3 -7
View File
@@ -36,7 +36,7 @@ body {
ul li.active>a {
color: $color-primary;
font-weight: 500;
font-weight: 600;
}
}
@@ -145,7 +145,7 @@ body {
font-family: 'Roboto Mono', Monaco, courier, monospace;
line-height: 1.5em;
margin: 1.2em 0;
padding: 1.2em 1.4em;
padding: 0 1.4em;
position: relative;
overflow: auto;
word-wrap: normal;
@@ -259,7 +259,7 @@ body {
line-height: inherit;
margin: 0 2px;
overflow: inherit;
padding: 3px 5px;
padding: 2.2em 5px;
white-space: inherit;
max-width: inherit;
}
@@ -288,10 +288,6 @@ pre::after {
top: 0;
}
.content img {
max-width: 100%;
}
.content span.light {
color: #7f8c8d;
}
+1
View File
@@ -57,3 +57,4 @@ export function tree (toc, tpl = '') {
return tpl
}
+32 -3
View File
@@ -1,10 +1,11 @@
/**
* Simple ajax
* @param {String} url
* @param {String} [method=get]
* @param {String} [method=GET]
* @param {Function} [loading] handle loading
* @return {Promise}
*/
export function load (url, method = 'get') {
export function load (url, method = 'GET', loading) {
const xhr = new XMLHttpRequest()
xhr.open(method, url)
@@ -12,11 +13,16 @@ export function load (url, method = 'get') {
return {
then: function (success, error = function () {}) {
if (loading) {
xhr.addEventListener('progress', loading)
xhr.addEventListener('loaded', loading)
}
xhr.addEventListener('error', error)
xhr.addEventListener('load', ({ target }) => {
target.status >= 400 ? error(target) : success(target.response)
})
}
},
abort: () => xhr.readyState !== 4 && xhr.abort()
}
}
@@ -66,3 +72,26 @@ export function camel2kebab (str) {
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.match(/^#\/([^#]+)/)
if (route && route.length === 2) {
route = route[1]
} else {
route = /^#\//.test(loc.hash) ? '' : loc.pathname
}
cacheRoute = route
cacheHash = loc.hash
return route
}