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
This commit is contained in:
cinwell.li 2016-12-08 21:11:18 +08:00 • committed by GitHub
commit 864935bfc1
15 changed files with 174 additions and 60 deletions

View file

@ -3,10 +3,10 @@
<head> <head>
<meta charset="UTF-8"> <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"> <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> </head>
<body> <body class="">
<div id="app"></div> <div id="app"></div>
</body> </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-sidebar-toggle data-router data-load-sidebar></script>
</html> </html>

View file

@ -1,3 +1,10 @@
## 1.0.0
## Features
- Support hash router
### Bug fixes
- Improved scrolling on mobile
## 0.7.0 ## 0.7.0
### Breaking change ### Breaking change
- `themes/` was removed, only exists in the npm package. - `themes/` was removed, only exists in the npm package.

View file

@ -30,6 +30,22 @@ Create a `404.html` and `README.md` into `/docs`.
</html> </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 ## Showcase
These open-source projects are using docsify to generate their sites. Pull requests welcome : ) These open-source projects are using docsify to generate their sites. Pull requests welcome : )

4
app.js
View file

@ -7,6 +7,6 @@ http.createServer(function (req, res) {
res.writeHead(404, { 'Content-Type': 'text/html' }) res.writeHead(404, { 'Content-Type': 'text/html' })
res.end(fs.readFileSync('404.dev.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`)

View file

@ -213,8 +213,10 @@ If you write a sub level list, it will generate a dropdown list.
- [chinese](/zh-cn) - [chinese](/zh-cn)
``` ```
## FAQ ### router
### Why use `404.html` instead of `index.html` Hash router. You can replace `404.html` with `index.html`.
[issues/7](https://github.com/QingWei-Li/docsify/issues/7) ```html
<script src="/lib/docsify.js" data-router></script>
```

View file

@ -18,5 +18,6 @@
src="//unpkg.com/docsify/lib/docsify.min.js" src="//unpkg.com/docsify/lib/docsify.min.js"
data-repo="qingwei-li/docsify" data-repo="qingwei-li/docsify"
data-max-level="3" data-max-level="3"
data-sidebar-toggle></script> data-sidebar-toggle
data-router></script>
</html> </html>

View file

@ -97,6 +97,10 @@ docsify serve docs
</nav> </nav>
``` ```
### CDN
目前可用的 CDN 有 [UNPKG](unpkg.com/docsify),如果觉得访问较慢可以将文件放到 Pages 的目录下。
### 配置参数 ### 配置参数
#### repo #### repo
@ -210,6 +214,14 @@ Sidebar 开关按钮
``` ```
### router
开启 hash router 功能,此时可以创建 `index.html` 作为入口文件,同时多页面切换不会重新加载资源。资源路径会被替换成 `/#/` 的形式。
```html
<script src="/lib/docsify.js" data-router></script>
```
## FAQ ## FAQ
### 为什么是 `404.html` 而不用 `index.html` ### 为什么是 `404.html` 而不用 `index.html`

View file

@ -25,7 +25,7 @@
"generator" "generator"
], ],
"author": "qingwei-li <cinwell.li@gmail.com> (https://github.com/QingWei-Li)", "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", "license": "MIT",
"devDependencies": { "devDependencies": {
"cssnano": "^3.8.1", "cssnano": "^3.8.1",

View file

@ -13,9 +13,11 @@ export function scrollActiveSidebar () {
for (let i = 0, len = lis.length; i < len; i += 1) { for (let i = 0, len = lis.length; i < len; i += 1) {
const li = lis[i] 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 () { function highlight () {
@ -26,8 +28,7 @@ export function scrollActiveSidebar () {
if (bcr.top < 10 && bcr.bottom > 10) { if (bcr.top < 10 && bcr.bottom > 10) {
const li = nav[node.id] const li = nav[node.id]
if (!li) return if (!li || li === active) return
if (li === active) return
if (active) active.setAttribute('class', '') if (active) active.setAttribute('class', '')
li.setAttribute('class', 'active') li.setAttribute('class', 'active')
@ -42,9 +43,9 @@ export function scrollActiveSidebar () {
highlight() highlight()
function scrollIntoView () { function scrollIntoView () {
const id = window.location.hash.slice(1) const id = window.location.hash.match(/#[^#\/]+$/g)
if (!id) return if (!id || !id.length) return
const section = document.querySelector('#' + id) const section = document.querySelector(id[0])
if (section) section.scrollIntoView() if (section) section.scrollIntoView()
} }
@ -57,7 +58,7 @@ export function scrollActiveSidebar () {
* Acitve link * Acitve link
*/ */
export function activeLink (dom, activeParent) { 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) dom = typeof dom === 'object' ? dom : document.querySelector(dom)
if (!dom) return if (!dom) return
@ -67,6 +68,10 @@ export function activeLink (dom, activeParent) {
activeParent activeParent
? node.parentNode.setAttribute('class', 'active') ? node.parentNode.setAttribute('class', 'active')
: node.setAttribute('class', 'active') : node.setAttribute('class', 'active')
} else {
activeParent
? node.parentNode.removeAttribute('class')
: node.removeAttribute('class')
} }
}) })
} }

View file

@ -1,4 +1,4 @@
import { load, camel2kebab, isNil } from './util' import { load, camel2kebab, isNil, getRoute } from './util'
import * as render from './render' import * as render from './render'
const OPTIONS = { const OPTIONS = {
@ -8,7 +8,8 @@ const OPTIONS = {
sidebar: '', sidebar: '',
sidebarToggle: false, sidebarToggle: false,
loadSidebar: null, loadSidebar: null,
loadNavbar: null loadNavbar: null,
router: false
} }
const script = document.currentScript || [].slice.call(document.getElementsByTagName('script')).pop() const script = document.currentScript || [].slice.call(document.getElementsByTagName('script')).pop()
@ -23,31 +24,48 @@ if (script) {
if (OPTIONS.sidebar) OPTIONS.sidebar = window[OPTIONS.sidebar] if (OPTIONS.sidebar) OPTIONS.sidebar = window[OPTIONS.sidebar]
} }
const Docsify = function () { // load options
const dom = document.querySelector(OPTIONS.el) || document.body render.config(OPTIONS)
const replace = dom !== document.body
let loc = document.location.pathname
if (/\/$/.test(loc)) loc += 'README' let cacheRoute = null
// Render app const mainRender = function () {
render.renderApp(dom, replace, OPTIONS) const route = getRoute()
if (cacheRoute === route) return
let basePath = cacheRoute = route
if (!/\//.test(basePath)) {
basePath = ''
} else if (basePath && !/\/$/.test(basePath)) {
basePath = basePath.match(/(\S+\/)[^\/]+$/)[1]
}
// Render markdown file // Render markdown file
load(`${loc}.md`) load((!route || /\/$/.test(route)) ? `${route}README.md` : `${route}.md`)
.then(content => render.renderArticle(content, OPTIONS), .then(render.renderArticle, _ => render.renderArticle(null))
_ => render.renderArticle(null, OPTIONS))
// Render sidebar // Render sidebar
if (OPTIONS.loadSidebar) { if (OPTIONS.loadSidebar) {
load(OPTIONS.loadSidebar) load(basePath + OPTIONS.loadSidebar).then(render.renderSidebar)
.then(content => render.renderSidebar(content, OPTIONS))
} }
// Render navbar // Render navbar
if (OPTIONS.loadNavbar) { if (OPTIONS.loadNavbar) {
load(OPTIONS.loadNavbar) load(basePath + OPTIONS.loadNavbar).then(render.renderNavbar)
.then(content => render.renderNavbar(content, OPTIONS)) }
}
const Docsify = function () {
const dom = document.querySelector(OPTIONS.el) || document.body
const replace = dom !== document.body
// Render app
render.renderApp(dom, replace)
mainRender()
if (OPTIONS.router) {
if (!/^#\//.test(window.location.hash)) window.location.hash = '#/'
window.addEventListener('hashchange', mainRender)
} }
} }

View file

@ -2,7 +2,9 @@ import marked from 'marked'
import Prism from 'prismjs' import Prism from 'prismjs'
import * as tpl from './tpl' import * as tpl from './tpl'
import { activeLink, scrollActiveSidebar, bindToggle } from './event' import { activeLink, scrollActiveSidebar, bindToggle } from './event'
import { genTree } from './util' import { genTree, getRoute } from './util'
let OPTIONS = {}
const renderTo = function (dom, content) { const renderTo = function (dom, content) {
dom = typeof dom === 'object' ? dom : document.querySelector(dom) dom = typeof dom === 'object' ? dom : document.querySelector(dom)
@ -10,7 +12,7 @@ const renderTo = function (dom, content) {
return dom return dom
} }
const toc = [] let toc = []
const renderer = new marked.Renderer() const renderer = new marked.Renderer()
/** /**
@ -18,11 +20,16 @@ const renderer = new marked.Renderer()
* @link https://github.com/chjj/marked#overriding-renderer-methods * @link https://github.com/chjj/marked#overriding-renderer-methods
*/ */
renderer.heading = function (text, level) { 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 // highlight code
renderer.code = function (code, lang = '') { renderer.code = function (code, lang = '') {
@ -30,15 +37,22 @@ renderer.code = function (code, lang = '') {
return `<pre data-lang="${lang}"><code class="lang-${lang}">${hl}</code></pre>` 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 }) marked.setOptions({ renderer })
/** /**
* App * App
*/ */
export function renderApp (dom, replace, opts) { export function renderApp (dom, replace) {
const nav = document.querySelector('nav') || document.createElement('nav') 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]) document.body.insertBefore(nav, document.body.children[0])
// bind toggle // bind toggle
@ -48,16 +62,18 @@ export function renderApp (dom, replace, opts) {
/** /**
* article * article
*/ */
export function renderArticle (content, OPTIONS) { export function renderArticle (content) {
renderTo('article', content ? marked(content) : 'not found') renderTo('article', content ? marked(content) : 'not found')
if (!renderSidebar.rendered) renderSidebar(null, OPTIONS) if (!renderSidebar.rendered) renderSidebar(null, OPTIONS)
if (!renderNavbar.rendered) renderNavbar(null, OPTIONS) if (!renderNavbar.rendered) renderNavbar(null, OPTIONS)
renderSidebar.rendered = false
renderNavbar.rendered = false
} }
/** /**
* navbar * navbar
*/ */
export function renderNavbar (content, OPTIONS = {}) { export function renderNavbar (content) {
renderNavbar.rendered = true renderNavbar.rendered = true
if (content) renderTo('nav', marked(content)) if (content) renderTo('nav', marked(content))
@ -67,7 +83,7 @@ export function renderNavbar (content, OPTIONS = {}) {
/** /**
* sidebar * sidebar
*/ */
export function renderSidebar (content, OPTIONS = {}) { export function renderSidebar (content) {
renderSidebar.rendered = true renderSidebar.rendered = true
let isToc = false let isToc = false
@ -83,4 +99,9 @@ export function renderSidebar (content, OPTIONS = {}) {
renderTo('aside.sidebar', content) renderTo('aside.sidebar', content)
isToc ? scrollActiveSidebar() : activeLink('aside.sidebar', true) isToc ? scrollActiveSidebar() : activeLink('aside.sidebar', true)
toc = []
}
export function config (options) {
OPTIONS = options
} }

View file

@ -13,7 +13,6 @@ html, body {
} }
body { body {
background-color: #fff;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
font-family: 'Source Sans Pro', 'Helvetica Neue', Arial, sans-serif; font-family: 'Source Sans Pro', 'Helvetica Neue', Arial, sans-serif;
@ -23,6 +22,12 @@ body {
overflow-x: hidden; overflow-x: hidden;
} }
img {
max-width: 100%;
display: block;
margin: 0 auto;
}
/* navbar */ /* navbar */
nav { nav {
position: absolute; position: absolute;
@ -113,6 +118,13 @@ nav {
top: 0; top: 0;
right: 0; right: 0;
z-index: 1; z-index: 1;
text-decoration: none;
border-bottom: 0;
&:hover {
background-color: inherit;
color: inherit;
}
&:hover .octo-arm { &:hover .octo-arm {
animation:octocat-wave 560ms ease-in-out; animation:octocat-wave 560ms ease-in-out;
@ -134,7 +146,6 @@ main {
/* sidebar */ /* sidebar */
.sidebar { .sidebar {
background-color: #fff;
border-right: 1px solid rgba(0, 0, 0, .07); border-right: 1px solid rgba(0, 0, 0, .07);
overflow-y: auto; overflow-y: auto;
padding-top: 40px; padding-top: 40px;
@ -225,6 +236,10 @@ body.close {
} }
@media (max-width: 600px) { @media (max-width: 600px) {
nav, .github-corner, .sidebar-toggle, .sidebar {
position: fixed;
}
nav { nav {
margin-top: 16px; margin-top: 16px;
} }
@ -242,6 +257,8 @@ body.close {
left: 0; left: 0;
min-width: 100vw; min-width: 100vw;
transition: transform 250ms ease; transition: transform 250ms ease;
position: static;
overflow-y: auto;
} }
nav, .github-corner { nav, .github-corner {
@ -264,8 +281,8 @@ body.close {
.github-corner { .github-corner {
&:hover .octo-arm { &:hover .octo-arm {
animation: none; animation: none;
} }
.octo-arm { .octo-arm {
animation: octocat-wave 560ms ease-in-out; animation: octocat-wave 560ms ease-in-out;
} }

View file

@ -146,7 +146,7 @@ body {
padding: 8px; padding: 8px;
margin: 0 0 1em 0; margin: 0 0 1em 0;
font-family: Inconsolata; font-family: Inconsolata;
padding: 12px 10px 12px 12px; padding: 0 10px 12px 0;
font-size: 16px; font-size: 16px;
overflow: auto; overflow: auto;
word-wrap: normal; word-wrap: normal;
@ -228,7 +228,7 @@ body {
max-width: inherit; max-width: inherit;
position: relative; position: relative;
background-color: #f8f8f8; background-color: #f8f8f8;
padding: 0.8em 0.8em 0.4em; padding: 20px 0.8em 20px;
line-height: 1.1em; line-height: 1.1em;
border-radius: 2px; border-radius: 2px;
} }
@ -243,10 +243,6 @@ code .token {
-moz-osx-font-smoothing: initial; -moz-osx-font-smoothing: initial;
} }
.content img {
max-width: 100%;
}
.content span.light { .content span.light {
color: #7f8c8d; color: #7f8c8d;
} }

View file

@ -145,7 +145,7 @@ body {
font-family: 'Roboto Mono', Monaco, courier, monospace; font-family: 'Roboto Mono', Monaco, courier, monospace;
line-height: 1.5em; line-height: 1.5em;
margin: 1.2em 0; margin: 1.2em 0;
padding: 1.2em 1.4em; padding: 0 1.4em;
position: relative; position: relative;
overflow: auto; overflow: auto;
word-wrap: normal; word-wrap: normal;
@ -259,7 +259,7 @@ body {
line-height: inherit; line-height: inherit;
margin: 0 2px; margin: 0 2px;
overflow: inherit; overflow: inherit;
padding: 3px 5px; padding: 2.2em 5px;
white-space: inherit; white-space: inherit;
max-width: inherit; max-width: inherit;
} }
@ -288,10 +288,6 @@ pre::after {
top: 0; top: 0;
} }
.content img {
max-width: 100%;
}
.content span.light { .content span.light {
color: #7f8c8d; color: #7f8c8d;
} }

View file

@ -66,3 +66,26 @@ export function camel2kebab (str) {
export function isNil (o) { export function isNil (o) {
return o === null || o === undefined 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
}