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:
parent
b977715d42
commit
864935bfc1
15 changed files with 174 additions and 60 deletions
21
src/event.js
21
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')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
50
src/index.js
50
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 = ''
|
||||
|
||||
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 +37,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 +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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
23
src/util.js
23
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
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue