From b977715d42d54d1c31d7ef36da66fa39f9e5fb0b Mon Sep 17 00:00:00 2001 From: "qingwei.li" Date: Thu, 1 Dec 2016 10:18:00 +0800 Subject: [PATCH 1/4] Add Snipaste into showcase --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index fdc4adcf9..e235059a5 100644 --- a/README.md +++ b/README.md @@ -31,15 +31,16 @@ Create a `404.html` and `README.md` into `/docs`. ``` ## 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 From 864935bfc11cfa3206d86276ecb8b4231595bc55 Mon Sep 17 00:00:00 2001 From: "cinwell.li" Date: Thu, 8 Dec 2016 21:11:18 +0800 Subject: [PATCH 2/4] 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 --- 404.dev.html | 6 ++--- CHANGELOG.md | 7 +++++ README.md | 16 +++++++++++ app.js | 4 +-- docs/README.md | 8 +++--- docs/{404.html => index.html} | 3 ++- docs/zh-cn.md | 12 +++++++++ package.json | 2 +- src/event.js | 21 +++++++++------ src/index.js | 50 ++++++++++++++++++++++++----------- src/render.js | 41 +++++++++++++++++++++------- src/themes/basic/_layout.css | 25 +++++++++++++++--- src/themes/buble.css | 8 ++---- src/themes/vue.css | 8 ++---- src/util.js | 23 ++++++++++++++++ 15 files changed, 174 insertions(+), 60 deletions(-) rename docs/{404.html => index.html} (93%) diff --git a/404.dev.html b/404.dev.html index a2b9beef8..18257df03 100644 --- a/404.dev.html +++ b/404.dev.html @@ -3,10 +3,10 @@ - + - +
- + diff --git a/CHANGELOG.md b/CHANGELOG.md index 860aabe1b..38cd6586a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## 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. diff --git a/README.md b/README.md index e235059a5..12c85e5f7 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,22 @@ Create a `404.html` and `README.md` into `/docs`. ``` +Or Create a `index.html` and using `hash router`. + +index.html + +```html + + + + + + + + + +``` + ## Showcase These open-source projects are using docsify to generate their sites. Pull requests welcome : ) diff --git a/app.js b/app.js index c3b48bcf9..e960cf44d 100644 --- a/app.js +++ b/app.js @@ -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`) diff --git a/docs/README.md b/docs/README.md index 678b1c431..7aea75c19 100644 --- a/docs/README.md +++ b/docs/README.md @@ -213,8 +213,10 @@ 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`. -[issues/7](https://github.com/QingWei-Li/docsify/issues/7) +```html + +``` diff --git a/docs/404.html b/docs/index.html similarity index 93% rename from docs/404.html rename to docs/index.html index 485fcec29..fdc506fc6 100644 --- a/docs/404.html +++ b/docs/index.html @@ -18,5 +18,6 @@ src="//unpkg.com/docsify/lib/docsify.min.js" data-repo="qingwei-li/docsify" data-max-level="3" - data-sidebar-toggle> + data-sidebar-toggle + data-router> diff --git a/docs/zh-cn.md b/docs/zh-cn.md index 150194440..726588f17 100644 --- a/docs/zh-cn.md +++ b/docs/zh-cn.md @@ -97,6 +97,10 @@ docsify serve docs ``` +### CDN + +目前可用的 CDN 有 [UNPKG](unpkg.com/docsify),如果觉得访问较慢可以将文件放到 Pages 的目录下。 + ### 配置参数 #### repo @@ -210,6 +214,14 @@ Sidebar 开关按钮 ``` +### router + +开启 hash router 功能,此时可以创建 `index.html` 作为入口文件,同时多页面切换不会重新加载资源。资源路径会被替换成 `/#/` 的形式。 + +```html + +``` + ## FAQ ### 为什么是 `404.html` 而不用 `index.html` diff --git a/package.json b/package.json index b01705dd6..e65f6305e 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "generator" ], "author": "qingwei-li (https://github.com/QingWei-Li)", - "homepage": "https://QingWei-Li.github.io/docsify", + "homepage": "https://docsify.js.org", "license": "MIT", "devDependencies": { "cssnano": "^3.8.1", diff --git a/src/event.js b/src/event.js index 1b20b3602..b71e902ed 100644 --- a/src/event.js +++ b/src/event.js @@ -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') @@ -42,9 +43,9 @@ export function scrollActiveSidebar () { highlight() function scrollIntoView () { - const id = window.location.hash.slice(1) - if (!id) return - const section = document.querySelector('#' + id) + const id = window.location.hash.match(/#[^#\/]+$/g) + if (!id || !id.length) return + const section = document.querySelector(id[0]) if (section) section.scrollIntoView() } @@ -57,7 +58,7 @@ export function scrollActiveSidebar () { * 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 +68,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') } }) } diff --git a/src/index.js b/src/index.js index 0596f5aae..d0dfc742a 100644 --- a/src/index.js +++ b/src/index.js @@ -1,4 +1,4 @@ -import { load, camel2kebab, isNil } from './util' +import { load, camel2kebab, isNil, getRoute } from './util' import * as render from './render' const OPTIONS = { @@ -8,7 +8,8 @@ const OPTIONS = { sidebar: '', sidebarToggle: false, loadSidebar: null, - loadNavbar: null + loadNavbar: null, + router: false } const script = document.currentScript || [].slice.call(document.getElementsByTagName('script')).pop() @@ -23,31 +24,48 @@ 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 - // Render app - render.renderApp(dom, replace, OPTIONS) +const mainRender = function () { + 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 - load(`${loc}.md`) - .then(content => render.renderArticle(content, OPTIONS), - _ => render.renderArticle(null, OPTIONS)) + load((!route || /\/$/.test(route)) ? `${route}README.md` : `${route}.md`) + .then(render.renderArticle, _ => render.renderArticle(null)) // Render sidebar if (OPTIONS.loadSidebar) { - load(OPTIONS.loadSidebar) - .then(content => render.renderSidebar(content, OPTIONS)) + load(basePath + OPTIONS.loadSidebar).then(render.renderSidebar) } // 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 + + // Render app + render.renderApp(dom, replace) + mainRender() + if (OPTIONS.router) { + if (!/^#\//.test(window.location.hash)) window.location.hash = '#/' + window.addEventListener('hashchange', mainRender) } } diff --git a/src/render.js b/src/render.js index 0f94b677f..680be9d3f 100644 --- a/src/render.js +++ b/src/render.js @@ -2,7 +2,9 @@ import marked from 'marked' import Prism from 'prismjs' import * as tpl from './tpl' import { activeLink, scrollActiveSidebar, bindToggle } from './event' -import { genTree } from './util' +import { genTree, getRoute } from './util' + +let OPTIONS = {} const renderTo = function (dom, content) { dom = typeof dom === 'object' ? dom : document.querySelector(dom) @@ -10,7 +12,7 @@ const renderTo = function (dom, content) { return dom } -const toc = [] +let toc = [] const renderer = new marked.Renderer() /** @@ -18,11 +20,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 = '' + + if (OPTIONS.router) { + route = `#/${getRoute()}` + } - toc.push({ level, slug: '#' + slug, title: text }) + toc.push({ level, slug: `${route}#${slug}`, title: text }) - return `${text}` + return `${text}` } // highlight code renderer.code = function (code, lang = '') { @@ -30,15 +37,22 @@ renderer.code = function (code, lang = '') { return `
${hl}
` } +renderer.link = function (href, title, text) { + if (OPTIONS.router && !/^(?:\w+:)?\/\/([^\s\.]+\.\S{2}|localhost[\:?\d]*)\S*$/.test(href)) { + href = !/^\/#/.test(href) ? `#${href}` : href + } + + return `${text}` +} 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 +62,18 @@ 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 } /** * navbar */ -export function renderNavbar (content, OPTIONS = {}) { +export function renderNavbar (content) { renderNavbar.rendered = true if (content) renderTo('nav', marked(content)) @@ -67,7 +83,7 @@ export function renderNavbar (content, OPTIONS = {}) { /** * sidebar */ -export function renderSidebar (content, OPTIONS = {}) { +export function renderSidebar (content) { renderSidebar.rendered = true let isToc = false @@ -83,4 +99,9 @@ export function renderSidebar (content, OPTIONS = {}) { renderTo('aside.sidebar', content) isToc ? scrollActiveSidebar() : activeLink('aside.sidebar', true) + toc = [] +} + +export function config (options) { + OPTIONS = options } diff --git a/src/themes/basic/_layout.css b/src/themes/basic/_layout.css index 745fb21e4..314daa1f2 100644 --- a/src/themes/basic/_layout.css +++ b/src/themes/basic/_layout.css @@ -13,7 +13,6 @@ html, body { } 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 +22,12 @@ body { overflow-x: hidden; } +img { + max-width: 100%; + display: block; + margin: 0 auto; +} + /* navbar */ nav { position: absolute; @@ -113,6 +118,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 +146,6 @@ main { /* sidebar */ .sidebar { - background-color: #fff; border-right: 1px solid rgba(0, 0, 0, .07); overflow-y: auto; padding-top: 40px; @@ -225,6 +236,10 @@ body.close { } @media (max-width: 600px) { + nav, .github-corner, .sidebar-toggle, .sidebar { + position: fixed; + } + nav { margin-top: 16px; } @@ -242,6 +257,8 @@ body.close { left: 0; min-width: 100vw; transition: transform 250ms ease; + position: static; + overflow-y: auto; } nav, .github-corner { @@ -264,8 +281,8 @@ body.close { .github-corner { &:hover .octo-arm { - animation: none; - } + animation: none; + } .octo-arm { animation: octocat-wave 560ms ease-in-out; } diff --git a/src/themes/buble.css b/src/themes/buble.css index a37201c12..e0b1cf483 100644 --- a/src/themes/buble.css +++ b/src/themes/buble.css @@ -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; } diff --git a/src/themes/vue.css b/src/themes/vue.css index e9d6f8675..ccd59c9c7 100644 --- a/src/themes/vue.css +++ b/src/themes/vue.css @@ -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; } diff --git a/src/util.js b/src/util.js index b64a16ae5..26208e6fd 100644 --- a/src/util.js +++ b/src/util.js @@ -66,3 +66,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 +} From 41be8176a63d15c170d165c4669133bd42ae483a Mon Sep 17 00:00:00 2001 From: "qingwei.li" Date: Thu, 8 Dec 2016 21:12:33 +0800 Subject: [PATCH 3/4] bump 1.0.0 --- lib/docsify.js | 143 +++++++++++++++++++++++++++++++------------ lib/docsify.min.js | 3 +- lib/themes/buble.css | 2 +- lib/themes/pure.css | 2 +- lib/themes/vue.css | 2 +- 5 files changed, 108 insertions(+), 44 deletions(-) diff --git a/lib/docsify.js b/lib/docsify.js index 216bed947..33fd3a73e 100644 --- a/lib/docsify.js +++ b/lib/docsify.js @@ -76,6 +76,29 @@ 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 +} + var commonjsGlobal = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; @@ -2231,9 +2254,11 @@ function scrollActiveSidebar () { for (var i = 0, len = lis.length; i < len; i += 1) { var li = lis[i]; - var a = li.querySelector('a'); + var 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 () { @@ -2244,8 +2269,7 @@ function scrollActiveSidebar () { if (bcr.top < 10 && bcr.bottom > 10) { var 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'); @@ -2260,9 +2284,9 @@ function scrollActiveSidebar () { highlight(); function scrollIntoView () { - var id = window.location.hash.slice(1); - if (!id) { return } - var section = document.querySelector('#' + id); + var id = window.location.hash.match(/#[^#\/]+$/g); + if (!id || !id.length) { return } + var section = document.querySelector(id[0]); if (section) { section.scrollIntoView(); } } @@ -2275,7 +2299,7 @@ function scrollActiveSidebar () { * Acitve link */ function activeLink (dom, activeParent) { - var host = document.location.origin + document.location.pathname; + var host = window.location.href; dom = typeof dom === 'object' ? dom : document.querySelector(dom); if (!dom) { return @@ -2285,6 +2309,10 @@ function activeLink (dom, activeParent) { activeParent ? node.parentNode.setAttribute('class', 'active') : node.setAttribute('class', 'active'); + } else { + activeParent + ? node.parentNode.removeAttribute('class') + : node.removeAttribute('class'); } }); } @@ -2305,6 +2333,8 @@ function bindToggle (dom) { }); } +var OPTIONS$1 = {}; + var renderTo = function (dom, content) { dom = typeof dom === 'object' ? dom : document.querySelector(dom); dom.innerHTML = content; @@ -2319,11 +2349,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 ("" + text + "") + toc.push({ level: level, slug: (route + "#" + slug), title: text }); + + return ("" + text + "") }; // highlight code renderer.code = function (code, lang) { @@ -2333,15 +2368,22 @@ renderer.code = function (code, lang) { return ("
" + hl + "
") }; +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 ("" + text + "") +}; 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 +2393,18 @@ 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; } /** * navbar */ -function renderNavbar (content, OPTIONS) { - if ( OPTIONS === void 0 ) OPTIONS = {}; - +function renderNavbar (content) { renderNavbar.rendered = true; if (content) { renderTo('nav', marked(content)); } @@ -2372,24 +2414,27 @@ function renderNavbar (content, OPTIONS) { /** * sidebar */ -function renderSidebar (content, OPTIONS) { - if ( OPTIONS === void 0 ) OPTIONS = {}; - +function renderSidebar (content) { renderSidebar.rendered = true; var isToc = false; if (content) { content = marked(content); - } else if (OPTIONS.sidebar) { - content = tree(OPTIONS.sidebar, '