diff --git a/src/core/config.js b/src/core/config.js
index d11c3d1..a1bf9bb 100644
--- a/src/core/config.js
+++ b/src/core/config.js
@@ -1,4 +1,4 @@
-import { merge, camelize, isPrimitive } from './util/core'
+import { merge, hyphenate, isPrimitive } from './util/core'
const config = merge({
el: '#app',
@@ -23,7 +23,7 @@ const script = document.currentScript ||
if (script) {
for (const prop in config) {
- const val = script.getAttribute('data-' + camelize(prop))
+ const val = script.getAttribute('data-' + hyphenate(prop))
if (isPrimitive(val)) {
config[prop] = val === '' ? true : val
diff --git a/src/core/event/index.js b/src/core/event/index.js
index a31188b..4c44100 100644
--- a/src/core/event/index.js
+++ b/src/core/event/index.js
@@ -1,5 +1,5 @@
import { isMobile } from '../util/env'
-import { dom, on } from '../util/dom'
+import { body, on } from '../util/dom'
import * as sidebar from './sidebar'
export function eventMixin (Docsify) {
@@ -14,6 +14,6 @@ export function initEvent (vm) {
if (vm.config.coverpage) {
!isMobile && on('scroll', sidebar.sticky)
} else {
- dom.body.classList.add('sticky')
+ body.classList.add('sticky')
}
}
diff --git a/src/core/event/sidebar.js b/src/core/event/sidebar.js
index aeaeeed..a02ef7b 100644
--- a/src/core/event/sidebar.js
+++ b/src/core/event/sidebar.js
@@ -1,10 +1,10 @@
import { isMobile } from '../util/env'
-import { getNode, on, dom } from '../util/dom'
+import { getNode, on, body } from '../util/dom'
/**
* Toggle button
*/
export function btn (el) {
- const toggle = () => dom.body.classList.toggle('close')
+ const toggle = () => body.classList.toggle('close')
el = getNode(el)
on(el, 'click', toggle)
diff --git a/src/core/fetch/ajax.js b/src/core/fetch/ajax.js
index ba79b76..09d4390 100644
--- a/src/core/fetch/ajax.js
+++ b/src/core/fetch/ajax.js
@@ -1,23 +1,33 @@
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} [loading=false] has loading bar
+ * @param {string} url
+ * @param {boolean} [hasBar=false] has progress bar
* @return { then(resolve, reject), abort }
*/
-export function get (url, hasLoading = false) {
+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) {
- const on = xhr.addEventListener
-
- if (hasLoading) {
+ if (hasBar) {
const id = setInterval(_ => progressbar({}), 500)
on('progress', progressbar)
@@ -29,9 +39,14 @@ export function get (url, hasLoading = false) {
on('error', error)
on('load', ({ target }) => {
- target.status >= 400 ? error(target) : success(target.response)
+ if (target.status >= 400) {
+ error(target)
+ } else {
+ cache[url] = target.response
+ success(target.response)
+ }
})
},
- abort: () => xhr.readyState !== 4 && xhr.abort()
+ abort: _ => xhr.readyState !== 4 && xhr.abort()
}
}
diff --git a/src/core/fetch/index.js b/src/core/fetch/index.js
index 61acc81..2fb7aa0 100644
--- a/src/core/fetch/index.js
+++ b/src/core/fetch/index.js
@@ -1,13 +1,43 @@
+import { get } from './ajax'
import { callHook } from '../init/lifecycle'
+import { getCurrentRoot } from '../route/util'
export function fetchMixin (Docsify) {
- Docsify.prototype.$fetch = function (path) {
- // 加载侧边栏、导航、内容
+ let last
+
+ Docsify.prototype._fetch = function (cb) {
+ const { path } = this.route
+ const { loadNavbar, loadSidebar } = this.config
+ const currentRoot = getCurrentRoot(path)
+
+ // Abort last request
+ last && last.abort && last.abort()
+
+ last = get(this.$getFile(path), true)
+ last.then(text => {
+ this._renderMain(text)
+ if (!loadSidebar) return cb()
+
+ const fn = result => { this._renderSidebar(result); cb() }
+
+ // Load sidebar
+ get(this.$getFile(currentRoot + loadSidebar))
+ .then(fn, _ => get(loadSidebar).then(fn))
+ },
+ _ => this._renderMain(null))
+
+ // Load nav
+ loadNavbar &&
+ get(this.$getFile(currentRoot + loadNavbar))
+ .then(
+ this._renderNav,
+ _ => get(loadNavbar).then(this._renderNav)
+ )
}
}
export function initFetch (vm) {
- vm.$fetch(result => {
+ vm._fetch(result => {
vm.$resetEvents()
callHook(vm, 'doneEach')
})
diff --git a/src/core/global-api.js b/src/core/global-api.js
new file mode 100644
index 0000000..83a3288
--- /dev/null
+++ b/src/core/global-api.js
@@ -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/util'
+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
+}
diff --git a/src/core/index.js b/src/core/index.js
index 96cab23..e2c94a4 100644
--- a/src/core/index.js
+++ b/src/core/index.js
@@ -3,9 +3,7 @@ import { routeMixin } from './route'
import { renderMixin } from './render'
import { fetchMixin } from './fetch'
import { eventMixin } from './event'
-import * as util from './util'
-import { get as load } from './fetch/ajax'
-import * as routeUtil from './route/util'
+import initGlobalAPI from './global-api'
function Docsify () {
this._init()
@@ -20,9 +18,7 @@ eventMixin(Docsify)
/**
* Global API
*/
-window.Docsify = {
- util: util.merge({ load }, util, routeUtil)
-}
+initGlobalAPI()
/**
* Run Docsify
diff --git a/src/core/render/compiler.js b/src/core/render/compiler.js
new file mode 100644
index 0000000..daed38c
--- /dev/null
+++ b/src/core/render/compiler.js
@@ -0,0 +1,50 @@
+import marked from 'marked'
+import Prism from 'prismjs'
+
+export const renderer = new marked.Renderer()
+
+export function markdown () {
+
+}
+
+const toc = []
+
+/**
+ * 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 `${text}`
+}
+// highlight code
+renderer.code = function (code, lang = '') {
+ const hl = Prism.highlight(code, Prism.languages[lang] || Prism.languages.markup)
+
+ return `
${hl}
`
+}
+renderer.link = function (href, title, text) {
+ if (!/:|(\/{2})/.test(href)) {
+ href = `#/${href}`.replace(/\/+/g, '/')
+ }
+ return `${text}`
+}
+renderer.paragraph = function (text) {
+ if (/^!>/.test(text)) {
+ return tpl.helper('tip', text)
+ } else if (/^\?>/.test(text)) {
+ return tpl.helper('warn', text)
+ }
+ return `${text}
`
+}
+renderer.image = function (href, title, text) {
+ const url = /:|(\/{2})/.test(href) ? href : ($docsify.basePath + href).replace(/\/+/g, '/')
+ const titleHTML = title ? ` title="${title}"` : ''
+
+ return `
`
+}
diff --git a/src/core/render/index.js b/src/core/render/index.js
index 12f50c5..2f9610f 100644
--- a/src/core/render/index.js
+++ b/src/core/render/index.js
@@ -1,12 +1,26 @@
-import { getNode, dom } from '../util/dom'
+import * as dom from '../util/dom'
import cssVars from '../util/polyfill/css-vars'
import * as tpl from './tpl'
+function renderMain () {
+
+}
+
+function renderNav () {
+}
+
+function renderSidebar () {
+}
+
export function renderMixin (Docsify) {
Docsify.prototype._renderTo = function (el, content, replace) {
- const node = getNode(el)
+ const node = dom.getNode(el)
if (node) node[replace ? 'outerHTML' : 'innerHTML'] = content
}
+
+ Docsify.prototype._renderSidebar = renderSidebar
+ Docsify.prototype._renderNav = renderNav
+ Docsify.prototype._renderMain = renderMain
}
export function initRender (vm) {
@@ -40,7 +54,7 @@ export function initRender (vm) {
dom.body.insertBefore(navEl, dom.body.children[0])
if (config.themeColor) {
- dom.head += tpl.theme(config.themeColor)
+ dom.$.head += tpl.theme(config.themeColor)
// Polyfll
cssVars(config.themeColor)
}
diff --git a/src/core/render/progressbar.js b/src/core/render/progressbar.js
index 8eff66b..c8347e6 100644
--- a/src/core/render/progressbar.js
+++ b/src/core/render/progressbar.js
@@ -1,19 +1,18 @@
-import { dom } from '../util/dom'
+import * as dom from '../util/dom'
import { isPrimitive } from '../util/core'
-let loadingEl
+let barEl
let timeId
/**
* Init progress component
*/
function init () {
- if (loadingEl) return
const div = dom.create('div')
div.classList.add('progress')
- dom.appendTo(div, dom.body)
- loadingEl = div
+ dom.appendTo(dom.body, div)
+ barEl = div
}
/**
* Render progress bar
@@ -21,26 +20,26 @@ function init () {
export default function ({ loaded, total, step }) {
let num
- loadingEl = init()
+ !barEl && init()
if (!isPrimitive(step)) {
step = Math.floor(Math.random() * 5 + 1)
}
if (step) {
- num = parseInt(loadingEl.style.width, 10) + step
+ num = parseInt(barEl.style.width, 10) + step
num = num > 80 ? 80 : num
} else {
num = Math.floor(loaded / total * 100)
}
- loadingEl.style.opacity = 1
- loadingEl.style.width = num >= 95 ? '100%' : num + '%'
+ barEl.style.opacity = 1
+ barEl.style.width = num >= 95 ? '100%' : num + '%'
if (num >= 95) {
clearTimeout(timeId)
timeId = setTimeout(_ => {
- loadingEl.style.opacity = 0
- loadingEl.style.width = '0%'
+ barEl.style.opacity = 0
+ barEl.style.width = '0%'
}, 200)
}
}
diff --git a/src/core/route/hash.js b/src/core/route/hash.js
index 1f544d4..fd0607e 100644
--- a/src/core/route/hash.js
+++ b/src/core/route/hash.js
@@ -1,7 +1,27 @@
-// import { cleanPath, getLocation } from './util'
-export function ensureSlash () {
- const path = getHash()
- if (path.charAt(0) === '/') return
+import { parseQuery } from './util'
+
+function replaceHash (path) {
+ const i = window.location.href.indexOf('#')
+ window.location.replace(
+ window.location.href.slice(0, i >= 0 ? i : 0) + '#' + path
+ )
+}
+
+/**
+ * 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 = path
+ .replace('#', '?id=')
+ .replace(/\?(\w+)=/g, (_, slug) => slug === 'id' ? '?id=' : `&${slug}=`)
+
+ if (path.charAt(0) === '/') return replaceHash(path)
replaceHash('/' + path)
}
@@ -13,11 +33,33 @@ export function getHash () {
return index === -1 ? '' : href.slice(index + 1)
}
-function replaceHash (path) {
- const i = window.location.href.indexOf('#')
- window.location.replace(
- window.location.href.slice(0, i >= 0 ? i : 0) + '#' + path
- )
+/**
+ * Parse the current url
+ * @return {object} { path, query }
+ */
+export function parse () {
+ let 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) }
}
-// TODO 把第二个 hash 转成 ?id=
+/**
+ * to URL
+ * @param {String} path
+ * @param {String} qs query string
+ */
+export function toURL (path, qs) {
+
+}
diff --git a/src/core/route/index.js b/src/core/route/index.js
index 011db74..0d4a3c6 100644
--- a/src/core/route/index.js
+++ b/src/core/route/index.js
@@ -1,17 +1,42 @@
-import { ensureSlash } from './hash'
+import { normalize, parse } from './hash'
+import { getBasePath, cleanPath } from './util'
+import { on } from '../util/dom'
+
+function getAlias (path, alias) {
+ if (alias[path]) return getAlias(alias[path], alias)
+ return path
+}
+
+function getFileName (path) {
+ return /\.(md|html)$/g.test(path)
+ ? path
+ : /\/$/g.test(path)
+ ? `${path}README.md`
+ : `${path}.md`
+}
export function routeMixin (Docsify) {
- Docsify.prototype.$route = {
- query: location.query || {},
- path: location.path || '/',
- base: ''
+ Docsify.prototype.route = {}
+ Docsify.prototype.$getFile = function (path) {
+ const { config } = this
+ const base = getBasePath(config.basePath)
+
+ path = getAlias(path, config.alias)
+ path = getFileName(path)
+ path = path === '/README.md' ? ('/' + config.homepage || path) : path
+ path = cleanPath(base + path)
+
+ return path
}
}
export function initRoute (vm) {
- ensureSlash()
- window.addEventListener('hashchange', () => {
- ensureSlash()
- vm.$fetch()
+ normalize()
+ vm.route = parse()
+
+ on('hashchange', _ => {
+ normalize()
+ vm.route = parse()
+ vm._fetch()
})
}
diff --git a/src/core/route/util.js b/src/core/route/util.js
index 4cbf898..300253a 100644
--- a/src/core/route/util.js
+++ b/src/core/route/util.js
@@ -1,11 +1,45 @@
+import { cached } from '../util/core'
+
+const decode = decodeURIComponent
+
+export const parseQuery = cached(query => {
+ const res = {}
+
+ query = query.trim().replace(/^(\?|#|&)/, '')
+
+ if (!query) {
+ return res
+ }
+
+ query.split('&').forEach(function (param) {
+ const parts = param.replace(/\+/g, ' ').split('=')
+ const key = decode(parts.shift())
+ const val = parts.length > 0
+ ? decode(parts.join('='))
+ : null
+
+ if (res[key] === undefined) {
+ res[key] = val
+ } else if (Array.isArray(res[key])) {
+ res[key].push(val)
+ } else {
+ res[key] = [res[key], val]
+ }
+ })
+
+ return res
+})
+
export function cleanPath (path) {
return path.replace(/\/+/g, '/')
}
-export function getLocation (base) {
- let path = window.location.pathname
- if (base && path.indexOf(base) === 0) {
- path = path.slice(base.length)
- }
- return (path || '/') + window.location.search + window.location.hash
+export function getBasePath (base) {
+ return /^(\/|https?:)/g.test(base)
+ ? base
+ : cleanPath(window.location.pathname + '/' + base)
+}
+
+export function getCurrentRoot (path) {
+ return /\/$/g.test(path) ? path : path.match(/(\S*\/)[^\/]+$/)[1]
}
diff --git a/src/core/util/core.js b/src/core/util/core.js
index 9688983..5a153b8 100644
--- a/src/core/util/core.js
+++ b/src/core/util/core.js
@@ -1,7 +1,7 @@
/**
* Create a cached version of a pure function.
*/
-function cached (fn) {
+export function cached (fn) {
const cache = Object.create(null)
return function cachedFn (str) {
const hit = cache[str]
@@ -10,11 +10,10 @@ function cached (fn) {
}
/**
- * Camelize a hyphen-delimited string.
+ * Hyphenate a camelCase string.
*/
-const camelizeRE = /-(\w)/g
-export const camelize = cached((str) => {
- return str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : '')
+export const hyphenate = cached(str => {
+ return str.replace(/([A-Z])/g, m => '-' + m.toLowerCase())
})
/**
diff --git a/src/core/util/dom.js b/src/core/util/dom.js
index 2232219..a2bd22c 100644
--- a/src/core/util/dom.js
+++ b/src/core/util/dom.js
@@ -10,22 +10,34 @@ const cacheNode = {}
*/
export function getNode (el, noCache = false) {
if (typeof el === 'string') {
- el = noCache ? dom.find(el) : (cacheNode[el] || dom.find(el))
+ el = noCache ? find(el) : (cacheNode[el] || find(el))
}
return el
}
-export const dom = {
- body: document.body,
- head: document.head,
- find: node => document.querySelector(node),
- findAll: node => document.querySelectorAll(node),
- create: (node, tpl) => {
- node = document.createElement(node)
- if (tpl) node.innerHTML = tpl
- },
- appendTo: (target, el) => target.appendChild(el)
+export const $ = document
+
+export const body = $.body
+
+export const head = $.head
+
+export function find (node) {
+ return $.querySelector(node)
+}
+
+export function findAll (node) {
+ return [].clice.call($.querySelectorAll(node))
+}
+
+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 on (el, type, handler) {
diff --git a/src/core/util/index.js b/src/core/util/index.js
index 38687ba..bfcc8b2 100644
--- a/src/core/util/index.js
+++ b/src/core/util/index.js
@@ -1,3 +1,2 @@
export * from './core'
export * from './env'
-export * from './dom'
diff --git a/src/core/util/polyfill/css-vars.js b/src/core/util/polyfill/css-vars.js
index 686ad72..bb8e498 100644
--- a/src/core/util/polyfill/css-vars.js
+++ b/src/core/util/polyfill/css-vars.js
@@ -1,22 +1,22 @@
-import { dom } from '../dom'
+import * as dom from '../dom'
import { get } from '../../fetch/ajax'
-function replaceVar (block, themeColor) {
+function replaceVar (block, color) {
block.innerHTML = block.innerHTML
- .replace(/var\(\s*--theme-color.*?\)/g, themeColor)
+ .replace(/var\(\s*--theme-color.*?\)/g, color)
}
-export default function (themeColor) {
+export default function (color) {
// Variable support
- if (window.CSS
- && window.CSS.supports
- && window.CSS.supports('(--foo: red)')) return
+ 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, themeColor)
+ replaceVar(block, color)
} else if (block.nodeName === 'LINK') {
const href = block.getAttribute('href')
@@ -26,7 +26,7 @@ export default function (themeColor) {
const style = dom.create('style', res)
dom.head.appendChild(style)
- replaceVar(style, themeColor)
+ replaceVar(style, color)
})
}
})