diff --git a/.eslintrc b/.eslintrc
index 527ed9ba..86d102d9 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -2,8 +2,5 @@
"extends": ["vue"],
"env": {
"browser": true
- },
- "globals": {
- "$docsify": true
}
}
diff --git a/src/core/fetch/index.js b/src/core/fetch/index.js
index 2fb7aa01..3602baf4 100644
--- a/src/core/fetch/index.js
+++ b/src/core/fetch/index.js
@@ -1,14 +1,15 @@
import { get } from './ajax'
import { callHook } from '../init/lifecycle'
-import { getCurrentRoot } from '../route/util'
+import { getRoot } from '../route/util'
+import { noop } from '../util/core'
export function fetchMixin (Docsify) {
let last
- Docsify.prototype._fetch = function (cb) {
+ Docsify.prototype._fetch = function (cb = noop) {
const { path } = this.route
const { loadNavbar, loadSidebar } = this.config
- const currentRoot = getCurrentRoot(path)
+ const root = getRoot(path)
// Abort last request
last && last.abort && last.abort()
@@ -21,14 +22,14 @@ export function fetchMixin (Docsify) {
const fn = result => { this._renderSidebar(result); cb() }
// Load sidebar
- get(this.$getFile(currentRoot + loadSidebar))
+ get(this.$getFile(root + loadSidebar))
.then(fn, _ => get(loadSidebar).then(fn))
},
_ => this._renderMain(null))
// Load nav
loadNavbar &&
- get(this.$getFile(currentRoot + loadNavbar))
+ get(this.$getFile(root + loadNavbar))
.then(
this._renderNav,
_ => get(loadNavbar).then(this._renderNav)
diff --git a/src/core/index.js b/src/core/index.js
index e2c94a48..3ac8b56d 100644
--- a/src/core/index.js
+++ b/src/core/index.js
@@ -23,4 +23,4 @@ initGlobalAPI()
/**
* Run Docsify
*/
-setTimeout(() => new Docsify(), 0)
+new Docsify()
diff --git a/src/core/render/compiler.js b/src/core/render/compiler.js
index daed38c1..2d202407 100644
--- a/src/core/render/compiler.js
+++ b/src/core/render/compiler.js
@@ -1,26 +1,56 @@
import marked from 'marked'
import Prism from 'prismjs'
+import { helper as helperTpl } from './tpl'
+import { slugify, clearSlugCache } from './slugify'
+import { emojify } from './emojify'
+import { toURL } from '../route/hash'
+import { isFn, merge, cached } from '../util/core'
-export const renderer = new marked.Renderer()
-
-export function markdown () {
-
-}
+let markdownCompiler = marked
+let contentBase = ''
+let renderer = new marked.Renderer()
const toc = []
+/**
+ * Compile markdown content
+ */
+export const markdown = cached(text => {
+ let html = ''
+
+ if (!text) return text
+
+ html = markdownCompiler(text)
+ html = emojify(html)
+ clearSlugCache()
+
+ return html
+})
+
+markdown.renderer = renderer
+
+markdown.init = function (config = {}, context = window.location.pathname) {
+ contentBase = context
+
+ if (isFn(config)) {
+ markdownCompiler = config(marked, renderer)
+ } else {
+ renderer = merge(renderer, config.renderer)
+ marked.setOptions(merge(config, { renderer }))
+ }
+}
+
/**
* render anchor tag
* @link https://github.com/chjj/marked#overriding-renderer-methods
*/
renderer.heading = function (text, level) {
const slug = slugify(text)
- let route = ''
+ const url = toURL(contentBase, { id: slug })
- route = `#/${getRoute()}`
- toc.push({ level, slug: `${route}#${encodeURIComponent(slug)}`, title: text })
+ toc.push({ level, slug: url, title: text })
- return `${text}`
+ return `${text}`
}
// highlight code
renderer.code = function (code, lang = '') {
@@ -30,21 +60,31 @@ renderer.code = function (code, lang = '') {
}
renderer.link = function (href, title, text) {
if (!/:|(\/{2})/.test(href)) {
+ // TODO
href = `#/${href}`.replace(/\/+/g, '/')
}
return `${text}`
}
renderer.paragraph = function (text) {
if (/^!>/.test(text)) {
- return tpl.helper('tip', text)
+ return helperTpl('tip', text)
} else if (/^\?>/.test(text)) {
- return tpl.helper('warn', text)
+ return helperTpl('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}"` : ''
+ // TODO
+ // get base path
+ // const url = /:|(\/{2})/.test(href) ? href : ($docsify.basePath + href).replace(/\/+/g, '/')
+ // const titleHTML = title ? ` title="${title}"` : ''
+
+ // return `
`
+}
+
+/**
+ * Compile sidebar
+ */
+export function sidebar (text) {
- return `
`
}
diff --git a/src/core/render/emojify.js b/src/core/render/emojify.js
new file mode 100644
index 00000000..bc414a3b
--- /dev/null
+++ b/src/core/render/emojify.js
@@ -0,0 +1,6 @@
+export function emojify (text) {
+ return text
+ .replace(/<(pre|template)[^>]*?>([\s\S]+)<\/(pre|template)>/g, m => m.replace(/:/g, '__colon__'))
+ .replace(/:(\w+?):/ig, '
')
+ .replace(/__colon__/g, ':')
+}
diff --git a/src/core/render/gen-tree.js b/src/core/render/gen-tree.js
new file mode 100644
index 00000000..84ff05e7
--- /dev/null
+++ b/src/core/render/gen-tree.js
@@ -0,0 +1,27 @@
+/**
+ * gen toc tree
+ * @link https://github.com/killercup/grock/blob/5280ae63e16c5739e9233d9009bc235ed7d79a50/styles/solarized/assets/js/behavior.coffee#L54-L81
+ * @param {Array} toc
+ * @param {Number} maxLevel
+ * @return {Array}
+ */
+export function genTree (toc, maxLevel) {
+ const headlines = []
+ const last = {}
+
+ toc.forEach(headline => {
+ const level = headline.level || 1
+ const len = level - 1
+
+ if (level > maxLevel) return
+ if (last[len]) {
+ last[len].children = last[len].children || []
+ last[len].children.push(headline)
+ } else {
+ headlines.push(headline)
+ }
+ last[level] = headline
+ })
+
+ return headlines
+}
diff --git a/src/core/render/index.js b/src/core/render/index.js
index 2f9610f2..ff25518e 100644
--- a/src/core/render/index.js
+++ b/src/core/render/index.js
@@ -1,30 +1,47 @@
import * as dom from '../util/dom'
import cssVars from '../util/polyfill/css-vars'
import * as tpl from './tpl'
+import { markdown, sidebar } from './compiler'
+import { callHook } from '../init/lifecycle'
-function renderMain () {
-
-}
-
-function renderNav () {
-}
-
-function renderSidebar () {
+function renderMain (html) {
+ if (!html) {
+ // TODO: Custom 404 page
+ }
+ this._renderTo('.markdown-section', html)
}
export function renderMixin (Docsify) {
- Docsify.prototype._renderTo = function (el, content, replace) {
+ const proto = Docsify.prototype
+
+ proto._renderTo = function (el, content, replace) {
const node = dom.getNode(el)
if (node) node[replace ? 'outerHTML' : 'innerHTML'] = content
}
- Docsify.prototype._renderSidebar = renderSidebar
- Docsify.prototype._renderNav = renderNav
- Docsify.prototype._renderMain = renderMain
+ proto._renderSidebar = function (text) {
+ this._renderTo('.sidebar-nav', sidebar(text))
+ // bind event
+ }
+
+ proto._renderNav = function (text) {
+ this._renderTo('nav', markdown(text))
+ }
+
+ proto._renderMain = function (text) {
+ callHook(this, 'beforeEach', text, result => {
+ const html = markdown(result)
+ callHook(this, 'afterEach', html, text => renderMain.call(this, text))
+ })
+ }
}
export function initRender (vm) {
const config = vm.config
+
+ // Init markdown compiler
+ markdown.init(vm.config.markdown)
+
const id = config.el || '#app'
const navEl = dom.find('nav') || dom.create('nav')
diff --git a/src/core/render/slugify.js b/src/core/render/slugify.js
new file mode 100644
index 00000000..a6e9d39b
--- /dev/null
+++ b/src/core/render/slugify.js
@@ -0,0 +1,27 @@
+let cache = {}
+const re = /[\u2000-\u206F\u2E00-\u2E7F\\'!"#$%&()*+,.\/:;<=>?@\[\]^`{|}~]/g
+
+export function slugify (str) {
+ if (typeof str !== 'string') return ''
+
+ let slug = str.toLowerCase().trim()
+ .replace(/<[^>\d]+>/g, '')
+ .replace(re, '')
+ .replace(/\s/g, '-')
+ .replace(/-+/g, '-')
+ .replace(/^(\d)/, '_$1')
+ let count = cache[slug]
+
+ count = cache.hasOwnProperty(slug) ? (count + 1) : 0
+ cache[slug] = count
+
+ if (count) {
+ slug = slug + '-' + count
+ }
+
+ return slug
+}
+
+export function clearSlugCache () {
+ cache = {}
+}
diff --git a/src/core/route/hash.js b/src/core/route/hash.js
index fd0607e6..b114f3db 100644
--- a/src/core/route/hash.js
+++ b/src/core/route/hash.js
@@ -1,4 +1,5 @@
-import { parseQuery } from './util'
+import { merge } from '../util/core'
+import { parseQuery, stringifyQuery, cleanPath } from './util'
function replaceHash (path) {
const i = window.location.href.indexOf('#')
@@ -34,11 +35,11 @@ export function getHash () {
}
/**
- * Parse the current url
+ * Parse the url
+ * @param {string} [path=window.location.herf]
* @return {object} { path, query }
*/
-export function parse () {
- let path = window.location.href
+export function parse (path = window.location.href) {
let query = ''
const queryIndex = path.indexOf('?')
@@ -57,9 +58,14 @@ export function parse () {
/**
* to URL
- * @param {String} path
- * @param {String} qs query string
+ * @param {string} path
+ * @param {object} qs query params
*/
-export function toURL (path, qs) {
+export function toURL (path, params) {
+ const route = parse(path)
+ route.query = merge({}, route.query, params)
+ path = route.path + stringifyQuery(route.query)
+
+ return '#' + path
}
diff --git a/src/core/route/index.js b/src/core/route/index.js
index 0d4a3c61..3fa1020f 100644
--- a/src/core/route/index.js
+++ b/src/core/route/index.js
@@ -30,13 +30,19 @@ export function routeMixin (Docsify) {
}
}
+let lastRoute = {}
+
export function initRoute (vm) {
normalize()
- vm.route = parse()
+ lastRoute = vm.route = parse()
on('hashchange', _ => {
normalize()
- vm.route = parse()
+ lastRoute = vm.route = parse()
+ if (lastRoute.path === vm.route.path) {
+ // TODO: goto xxx
+ return
+ }
vm._fetch()
})
}
diff --git a/src/core/route/util.js b/src/core/route/util.js
index 300253a4..635273b3 100644
--- a/src/core/route/util.js
+++ b/src/core/route/util.js
@@ -1,6 +1,7 @@
import { cached } from '../util/core'
const decode = decodeURIComponent
+const encode = encodeURIComponent
export const parseQuery = cached(query => {
const res = {}
@@ -11,35 +12,35 @@ export const parseQuery = cached(query => {
return res
}
+ // Simple parse
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]
- }
+ res[parts[0]] = decode(parts[1])
})
-
return res
})
+export function stringifyQuery (obj) {
+ const qs = []
+
+ for (const key in obj) {
+ qs.push(`${encode(key)}=${encode(obj[key])}`)
+ }
+
+ return qs.length ? `?${qs.join('&')}` : ''
+}
+
+export const getBasePath = cached(base => {
+ return /^(\/|https?:)/g.test(base)
+ ? base
+ : cleanPath(window.location.pathname + '/' + base)
+})
+
+export const getRoot = cached(path => {
+ return /\/$/g.test(path) ? path : path.match(/(\S*\/)[^\/]+$/)[1]
+})
+
export function cleanPath (path) {
return path.replace(/\/+/g, '/')
}
-
-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/util.js b/src/util.js
deleted file mode 100644
index 8f182dff..00000000
--- a/src/util.js
+++ /dev/null
@@ -1,166 +0,0 @@
-/**
- * Simple ajax
- * @param {String} url
- * @param {String} [method=GET]
- * @param {Function} [loading] handle loading
- * @return {Promise}
- */
-export function load (url, method = 'GET', loading) {
- const xhr = new XMLHttpRequest()
-
- xhr.open(method, url)
- xhr.send()
-
- return {
- then: function (success, error = function () {}) {
- if (loading) {
- const id = setInterval(_ =>
- loading({ step: Math.floor(Math.random() * 5 + 1) }),
- 500)
- xhr.addEventListener('progress', loading)
- xhr.addEventListener('loadend', evt => {
- loading(evt)
- clearInterval(id)
- })
- }
- xhr.addEventListener('error', error)
- xhr.addEventListener('load', ({ target }) => {
- target.status >= 400 ? error(target) : success(target.response)
- })
- },
- abort: () => xhr.readyState !== 4 && xhr.abort()
- }
-}
-
-/**
- * gen toc tree
- * @link https://github.com/killercup/grock/blob/5280ae63e16c5739e9233d9009bc235ed7d79a50/styles/solarized/assets/js/behavior.coffee#L54-L81
- * @param {Array} toc
- * @param {Number} maxLevel
- * @return {Array}
- */
-export function genTree (toc, maxLevel) {
- const headlines = []
- const last = {}
-
- toc.forEach(headline => {
- const level = headline.level || 1
- const len = level - 1
-
- if (level > maxLevel) return
- if (last[len]) {
- last[len].children = last[len].children || []
- last[len].children.push(headline)
- } else {
- headlines.push(headline)
- }
- last[level] = headline
- })
-
- return headlines
-}
-
-/**
- * camel to kebab
- * @link https://github.com/bokuweb/kebab2camel/blob/master/index.js
- * @param {String} str
- * @return {String}
- */
-export function camel2kebab (str) {
- return str.replace(/([A-Z])/g, m => '-' + m.toLowerCase())
-}
-
-/**
- * is nil
- * @param {Object} object
- * @return {Boolean}
- */
-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.replace(/%23/g, '#').match(/^#\/([^#]+)/)
-
- if (route && route.length === 2) {
- route = route[1]
- } else {
- route = /^#\//.test(loc.hash) ? '' : loc.pathname
- }
- cacheRoute = route
- cacheHash = loc.hash
-
- return route
-}
-
-export function isMobile () {
- return document.body.clientWidth <= 600
-}
-
-export function slugify (string) {
- const re = /[\u2000-\u206F\u2E00-\u2E7F\\'!"#$%&()*+,.\/:;<=>?@\[\]^`{|}~]/g
- const maintainCase = false
- const replacement = '-'
-
- slugify.occurrences = slugify.occurrences || {}
-
- if (typeof string !== 'string') return ''
- if (!maintainCase) string = string.toLowerCase()
-
- let slug = string.trim()
- .replace(/<[^>\d]+>/g, '')
- .replace(re, '')
- .replace(/\s/g, replacement)
- .replace(/-+/g, replacement)
- .replace(/^(\d)/, '_$1')
- let occurrences = slugify.occurrences[slug]
-
- if (slugify.occurrences.hasOwnProperty(slug)) {
- occurrences++
- } else {
- occurrences = 0
- }
-
- slugify.occurrences[slug] = occurrences
-
- if (occurrences) {
- slug = slug + '-' + occurrences
- }
-
- return slug
-}
-
-slugify.clear = function () {
- slugify.occurrences = {}
-}
-
-const hasOwnProperty = Object.prototype.hasOwnProperty
-export const merge = Object.assign || function (to) {
- for (let i = 1; i < arguments.length; i++) {
- const from = Object(arguments[i])
-
- for (const key in from) {
- if (hasOwnProperty.call(from, key)) {
- to[key] = from[key]
- }
- }
- }
-
- return to
-}
-
-export function emojify (text) {
- return text
- .replace(/<(pre|template)[^>]*?>([\s\S]+)<\/(pre|template)>/g, match => match.replace(/:/g, '__colon__'))
- .replace(/:(\w+?):/ig, '
')
- .replace(/__colon__/g, ':')
-}