From b9224cea4f4581e59c44046453109af2be9a5edc Mon Sep 17 00:00:00 2001 From: "qingwei.li" Date: Thu, 16 Feb 2017 23:37:39 +0800 Subject: [PATCH 01/25] refactor(core): adjust directory structure: --- src/core/config.js | 38 +++++++++++++ src/core/event/index.js | 11 ++++ src/core/fetch/ajax.js | 37 ++++++++++++ src/core/fetch/index.js | 13 +++++ src/core/index.js | 30 ++++++++++ src/core/init/index.js | 27 +++++++++ src/core/init/lifecycle.js | 41 +++++++++++++ src/core/render/dom.js | 12 ++++ src/core/render/index.js | 9 +++ src/core/render/progressbar.js | 45 +++++++++++++++ src/{ => core/render}/tpl.js | 2 +- src/core/route/hash.js | 24 ++++++++ src/core/route/index.js | 17 ++++++ src/core/route/util.js | 11 ++++ src/core/util/core.js | 56 ++++++++++++++++++ src/core/util/env.js | 5 ++ src/core/util/index.js | 2 + .../util/polyfill/css-vars.js} | 4 +- src/hook.js | 57 ------------------- src/render.js | 33 ----------- 20 files changed, 381 insertions(+), 93 deletions(-) create mode 100644 src/core/config.js create mode 100644 src/core/event/index.js create mode 100644 src/core/fetch/ajax.js create mode 100644 src/core/fetch/index.js create mode 100644 src/core/index.js create mode 100644 src/core/init/index.js create mode 100644 src/core/init/lifecycle.js create mode 100644 src/core/render/dom.js create mode 100644 src/core/render/index.js create mode 100644 src/core/render/progressbar.js rename src/{ => core/render}/tpl.js (98%) create mode 100644 src/core/route/hash.js create mode 100644 src/core/route/index.js create mode 100644 src/core/route/util.js create mode 100644 src/core/util/core.js create mode 100644 src/core/util/env.js create mode 100644 src/core/util/index.js rename src/{polyfill.js => core/util/polyfill/css-vars.js} (91%) delete mode 100644 src/hook.js diff --git a/src/core/config.js b/src/core/config.js new file mode 100644 index 000000000..d344aae91 --- /dev/null +++ b/src/core/config.js @@ -0,0 +1,38 @@ +import { merge, camelize, isPrimitive } from './util/core' + +const config = merge({ + el: '#app', + repo: '', + maxLevel: 6, + subMaxLevel: 0, + loadSidebar: null, + loadNavbar: null, + homepage: 'README.md', + coverpage: '', + basePath: '', + auto2top: false, + name: '', + themeColor: '', + nameLink: window.location.pathname, + ga: '' +}, window.$docsify) + +const script = document.currentScript || [].slice.call(document.getElementsByTagName('script')).pop() + +if (script) { + for (const prop in config) { + const val = script.getAttribute('data-' + camelize(prop)) + + if (isPrimitive(val)) { + config[prop] = val === '' ? true : val + } + } + + if (config.loadSidebar === true) config.loadSidebar = '_sidebar.md' + if (config.loadNavbar === true) config.loadNavbar = '_navbar.md' + if (config.coverpage === true) config.coverpage = '_coverpage.md' + if (config.repo === true) config.repo = '' + if (config.name === true) config.name = '' +} + +export default config diff --git a/src/core/event/index.js b/src/core/event/index.js new file mode 100644 index 000000000..46d9d284a --- /dev/null +++ b/src/core/event/index.js @@ -0,0 +1,11 @@ +export function eventMixin (Docsify) { + Docsify.prototype.$bindEvents = function () { + } + + Docsify.prototype.$resetEvents = function () { + } +} + +export function initEvent (vm) { + vm.$bindEvents() +} diff --git a/src/core/fetch/ajax.js b/src/core/fetch/ajax.js new file mode 100644 index 000000000..ba79b766f --- /dev/null +++ b/src/core/fetch/ajax.js @@ -0,0 +1,37 @@ +import progressbar from '../render/progressbar' +import { noop } from '../util/core' + +/** + * Simple ajax get + * @param {String} url + * @param {Boolean} [loading=false] has loading bar + * @return { then(resolve, reject), abort } + */ +export function get (url, hasLoading = false) { + const xhr = new XMLHttpRequest() + + xhr.open('GET', url) + xhr.send() + + return { + then: function (success, error = noop) { + const on = xhr.addEventListener + + if (hasLoading) { + const id = setInterval(_ => progressbar({}), 500) + + on('progress', progressbar) + on('loadend', evt => { + progressbar(evt) + clearInterval(id) + }) + } + + on('error', error) + on('load', ({ target }) => { + target.status >= 400 ? error(target) : success(target.response) + }) + }, + abort: () => xhr.readyState !== 4 && xhr.abort() + } +} diff --git a/src/core/fetch/index.js b/src/core/fetch/index.js new file mode 100644 index 000000000..7771e898c --- /dev/null +++ b/src/core/fetch/index.js @@ -0,0 +1,13 @@ +import { callHook } from '../init/lifecycle' + +export function fetchMixin (Docsify) { + Docsify.prototype.$fetch = function () { + } +} + +export function initFetch (vm) { + vm.$fetch(result => { + vm.$resetEvents() + callHook(vm, 'doneEach') + }) +} diff --git a/src/core/index.js b/src/core/index.js new file mode 100644 index 000000000..96cab23a3 --- /dev/null +++ b/src/core/index.js @@ -0,0 +1,30 @@ +import { initMixin } from './init' +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' + +function Docsify () { + this._init() +} + +initMixin(Docsify) +routeMixin(Docsify) +renderMixin(Docsify) +fetchMixin(Docsify) +eventMixin(Docsify) + +/** + * Global API + */ +window.Docsify = { + util: util.merge({ load }, util, routeUtil) +} + +/** + * Run Docsify + */ +setTimeout(() => new Docsify(), 0) diff --git a/src/core/init/index.js b/src/core/init/index.js new file mode 100644 index 000000000..8a6c2a3e2 --- /dev/null +++ b/src/core/init/index.js @@ -0,0 +1,27 @@ +import config from '../config' +import { initLifecycle, callHook } from './lifecycle' +import { initRender } from '../render' +import { initRoute } from '../route' +import { initEvent } from '../event' +import { initFetch } from '../fetch' +import { isFn } from '../util/core' + +export function initMixin (Docsify) { + Docsify.prototype._init = function () { + const vm = this + vm._config = config || {} + + initLifecycle(vm) // Init hooks + initPlugin(vm) // Install plugins + callHook(vm, 'init') + initRender(vm) // Render base DOM + initEvent(vm) // Bind events + initRoute(vm) // Add hashchange eventListener + initFetch(vm) // Fetch data + callHook(vm, 'ready') + } +} + +function initPlugin (vm) { + [].concat(vm.config.plugins).forEach(fn => isFn(fn) && fn(vm.bindHook)) +} diff --git a/src/core/init/lifecycle.js b/src/core/init/lifecycle.js new file mode 100644 index 000000000..e4a48a84d --- /dev/null +++ b/src/core/init/lifecycle.js @@ -0,0 +1,41 @@ +import { noop } from '../util/core' + +export function initLifecycle (vm) { + const hooks = ['init', 'beforeEach', 'afterEach', 'doneEach', 'ready'] + + vm._hooks = {} + vm.bindHook = {} + hooks.forEach(hook => { + const arr = vm._hooks[hook] = [] + vm._bindHook[hook] = fn => arr.push(fn) + }) +} + +export function callHook (vm, hook, data, next = noop) { + let newData = data + const queue = vm._hooks[hook] + + const step = function (index) { + const hook = queue[index] + if (index >= queue.length) { + next(newData) + } else { + if (typeof hook === 'function') { + if (hook.length === 2) { + hook(data, result => { + newData = result + step(index + 1) + }) + } else { + const result = hook(data) + newData = result !== undefined ? result : newData + step(index + 1) + } + } else { + step(index + 1) + } + } + } + + step(0) +} diff --git a/src/core/render/dom.js b/src/core/render/dom.js new file mode 100644 index 000000000..8277095c2 --- /dev/null +++ b/src/core/render/dom.js @@ -0,0 +1,12 @@ +const cacheNode = {} + +export function getCacheNode (el) { + if (typeof el === 'string') { + const selector = el + + el = cacheNode[el] || document.querySelector(el) + if (!el) console.error('Cannot find element:', selector) + } + + return el +} diff --git a/src/core/render/index.js b/src/core/render/index.js new file mode 100644 index 000000000..03ef3f0c3 --- /dev/null +++ b/src/core/render/index.js @@ -0,0 +1,9 @@ +export function renderMixin (Docsify) { + Docsify.prototype._renderTo = function (dom, content) { + + } +} + +export function initRender (vm) { + // init +} diff --git a/src/core/render/progressbar.js b/src/core/render/progressbar.js new file mode 100644 index 000000000..1da5b2255 --- /dev/null +++ b/src/core/render/progressbar.js @@ -0,0 +1,45 @@ +import { isPrimitive } from '../util/core' + +let loadingEl +let timeId + +/** + * Init progress component + */ +function init () { + if (loadingEl) return + const div = document.createElement('div') + + div.classList.add('progress') + document.body.appendChild(div) + loadingEl = div +} +/** + * Render progress bar + */ +export default function ({ loaded, total, step }) { + let num + + loadingEl = init() + + if (!isPrimitive(step)) { + step = Math.floor(Math.random() * 5 + 1) + } + if (step) { + num = parseInt(loadingEl.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 + '%' + + if (num >= 95) { + clearTimeout(timeId) + timeId = setTimeout(_ => { + loadingEl.style.opacity = 0 + loadingEl.style.width = '0%' + }, 200) + } +} diff --git a/src/tpl.js b/src/core/render/tpl.js similarity index 98% rename from src/tpl.js rename to src/core/render/tpl.js index 66551296b..df5897910 100644 --- a/src/tpl.js +++ b/src/core/render/tpl.js @@ -1,4 +1,4 @@ -import { isMobile } from './util' +import { isMobile } from '../util/core' /** * Render github corner * @param {Object} data diff --git a/src/core/route/hash.js b/src/core/route/hash.js new file mode 100644 index 000000000..90d180c6d --- /dev/null +++ b/src/core/route/hash.js @@ -0,0 +1,24 @@ +import { cleanPath, getLocation } from './util' + +export function ensureSlash () { + const path = getHash() + if (path.charAt(0) === '/') return + replaceHash('/' + path) +} + +export function getHash () { + // We can't use window.location.hash here because it's not + // consistent across browsers - Firefox will pre-decode it! + const href = window.location.href + const index = href.indexOf('#') + 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 + ) +} + +// TODO 把第二个 hash 转成 ?id= diff --git a/src/core/route/index.js b/src/core/route/index.js new file mode 100644 index 000000000..011db7413 --- /dev/null +++ b/src/core/route/index.js @@ -0,0 +1,17 @@ +import { ensureSlash } from './hash' + +export function routeMixin (Docsify) { + Docsify.prototype.$route = { + query: location.query || {}, + path: location.path || '/', + base: '' + } +} + +export function initRoute (vm) { + ensureSlash() + window.addEventListener('hashchange', () => { + ensureSlash() + vm.$fetch() + }) +} diff --git a/src/core/route/util.js b/src/core/route/util.js new file mode 100644 index 000000000..4cbf898bc --- /dev/null +++ b/src/core/route/util.js @@ -0,0 +1,11 @@ +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 +} diff --git a/src/core/util/core.js b/src/core/util/core.js new file mode 100644 index 000000000..968898393 --- /dev/null +++ b/src/core/util/core.js @@ -0,0 +1,56 @@ +/** + * Create a cached version of a pure function. + */ +function cached (fn) { + const cache = Object.create(null) + return function cachedFn (str) { + const hit = cache[str] + return hit || (cache[str] = fn(str)) + } +} + +/** + * Camelize a hyphen-delimited string. + */ +const camelizeRE = /-(\w)/g +export const camelize = cached((str) => { + return str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : '') +}) + +/** + * Simple Object.assign polyfill + */ +export const merge = Object.assign || function (to) { + const hasOwn = Object.prototype.hasOwnProperty + + for (let i = 1; i < arguments.length; i++) { + const from = Object(arguments[i]) + + for (const key in from) { + if (hasOwn.call(from, key)) { + to[key] = from[key] + } + } + } + + return to +} + +/** + * Check if value is primitive + */ +export function isPrimitive (value) { + return typeof value === 'string' || typeof value === 'number' +} + +/** + * Perform no operation. + */ +export function noop () {} + +/** + * Check if value is function + */ +export function isFn (obj) { + return typeof obj === 'function' +} diff --git a/src/core/util/env.js b/src/core/util/env.js new file mode 100644 index 000000000..1e79cfe0d --- /dev/null +++ b/src/core/util/env.js @@ -0,0 +1,5 @@ +export const UA = window.navigator.userAgent.toLowerCase() + +export const isIE = UA && /msie|trident/.test(UA) + +export const isMobile = document.body.clientWidth <= 600 diff --git a/src/core/util/index.js b/src/core/util/index.js new file mode 100644 index 000000000..bfcc8b27d --- /dev/null +++ b/src/core/util/index.js @@ -0,0 +1,2 @@ +export * from './core' +export * from './env' diff --git a/src/polyfill.js b/src/core/util/polyfill/css-vars.js similarity index 91% rename from src/polyfill.js rename to src/core/util/polyfill/css-vars.js index f3a032b70..f0200e6ff 100644 --- a/src/polyfill.js +++ b/src/core/util/polyfill/css-vars.js @@ -1,4 +1,4 @@ -import { load } from './util' +import { get } from '../fetch/ajax' function replaceVar (block) { block.innerHTML = block.innerHTML.replace(/var\(\s*--theme-color.*?\)/g, $docsify.themeColor) @@ -18,7 +18,7 @@ export function cssVars () { if (!/\.css$/.test(href)) return - load(href).then(res => { + get(href).then(res => { const style = document.createElement('style') style.innerHTML = res diff --git a/src/hook.js b/src/hook.js deleted file mode 100644 index 401765aed..000000000 --- a/src/hook.js +++ /dev/null @@ -1,57 +0,0 @@ -export default class Hook { - constructor () { - this.beforeHooks = [] - this.afterHooks = [] - this.initHooks = [] - this.readyHooks = [] - this.doneEachHooks = [] - } - - beforeEach (fn) { - this.beforeHooks.push(fn) - } - - afterEach (fn) { - this.afterHooks.push(fn) - } - - doneEach (fn) { - this.doneEachHooks.push(fn) - } - - init (fn) { - this.initHooks.push(fn) - } - - ready (fn) { - this.readyHooks.push(fn) - } - - emit (name, data, next) { - let newData = data - const queue = this[name + 'Hooks'] - const step = function (index) { - const hook = queue[index] - if (index >= queue.length) { - next && next(newData) - } else { - if (typeof hook === 'function') { - if (hook.length === 2) { - hook(data, result => { - newData = result - step(index + 1) - }) - } else { - const result = hook(data) - newData = result !== undefined ? result : newData - step(index + 1) - } - } else { - step(index + 1) - } - } - } - - step(0) - } -} diff --git a/src/render.js b/src/render.js index 26a5d5384..412508875 100644 --- a/src/render.js +++ b/src/render.js @@ -235,36 +235,3 @@ export function renderCover (content) { event.sticky() } - -/** - * render loading bar - * @return {[type]} [description] - */ -export function renderLoading ({ loaded, total, step }) { - let num - - if (!CACHE.loading) { - const div = document.createElement('div') - - div.classList.add('progress') - document.body.appendChild(div) - CACHE.loading = div - } - if (step) { - num = parseInt(CACHE.loading.style.width, 10) + step - num = num > 80 ? 80 : num - } else { - num = Math.floor(loaded / total * 100) - } - - CACHE.loading.style.opacity = 1 - CACHE.loading.style.width = num >= 95 ? '100%' : num + '%' - - if (num >= 95) { - clearTimeout(renderLoading.cacheTimeout) - renderLoading.cacheTimeout = setTimeout(_ => { - CACHE.loading.style.opacity = 0 - CACHE.loading.style.width = '0%' - }, 200) - } -} From bdbedd2f98d15cfb60c83fbc67dc6d40fdccd3b5 Mon Sep 17 00:00:00 2001 From: "qingwei.li" Date: Fri, 17 Feb 2017 23:09:09 +0800 Subject: [PATCH 02/25] refactor(core): add render --- README.md | 2 +- build/build.js | 61 ++++++++++++++++------------- docs/README.md | 2 +- docs/zh-cn/README.md | 2 +- src/core/config.js | 4 +- src/core/event/index.js | 16 ++++++-- src/core/event/scroll.js | 10 +++++ src/core/event/sidebar.js | 28 +++++++++++++ src/core/fetch/index.js | 3 +- src/core/init/index.js | 4 +- src/core/init/lifecycle.js | 4 +- src/core/render/dom.js | 12 ------ src/core/render/index.js | 44 +++++++++++++++++++-- src/core/render/progressbar.js | 5 ++- src/core/render/tpl.js | 63 ++++++++++++++++-------------- src/core/route/hash.js | 3 +- src/core/util/dom.js | 41 +++++++++++++++++++ src/core/util/index.js | 1 + src/core/util/polyfill/css-vars.js | 27 +++++++------ src/themes/basic/_layout.css | 2 +- 20 files changed, 232 insertions(+), 102 deletions(-) create mode 100644 src/core/event/scroll.js create mode 100644 src/core/event/sidebar.js delete mode 100644 src/core/render/dom.js create mode 100644 src/core/util/dom.js diff --git a/README.md b/README.md index dd7de59b9..3e7554ccb 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ - Smart full-text search plugin - Multiple themes - Useful plugin API -- Compatible with IE9+ +- Compatible with IE10+ ## Quick start Create a `index.html`. diff --git a/build/build.js b/build/build.js index cbeefc929..96adf07f7 100644 --- a/build/build.js +++ b/build/build.js @@ -27,34 +27,39 @@ var build = function (opts) { } build({ - entry: 'index.js', + entry: 'core/index.js', output: 'docsify.js', plugins: [commonjs(), nodeResolve()] }) -isProd && build({ - entry: 'index.js', - output: 'docsify.min.js', - plugins: [commonjs(), nodeResolve(), uglify()] -}) -build({ - entry: 'plugins/search.js', - output: 'plugins/search.js', - moduleName: 'D.Search' -}) -isProd && build({ - entry: 'plugins/search.js', - output: 'plugins/search.min.js', - moduleName: 'D.Search', - plugins: [uglify()] -}) -build({ - entry: 'plugins/ga.js', - output: 'plugins/ga.js', - moduleName: 'D.GA' -}) -isProd && build({ - entry: 'plugins/ga.js', - output: 'plugins/ga.min.js', - moduleName: 'D.GA', - plugins: [uglify()] -}) + +// build({ +// entry: 'plugins/search.js', +// output: 'plugins/search.js', +// moduleName: 'D.Search' +// }) + +// build({ +// entry: 'plugins/ga.js', +// output: 'plugins/ga.js', +// moduleName: 'D.GA' +// }) + +if (isProd) { + build({ + entry: 'index.js', + output: 'docsify.min.js', + plugins: [commonjs(), nodeResolve(), uglify()] + }) + build({ + entry: 'plugins/search.js', + output: 'plugins/search.min.js', + moduleName: 'D.Search', + plugins: [uglify()] + }) + build({ + entry: 'plugins/ga.js', + output: 'plugins/ga.min.js', + moduleName: 'D.GA', + plugins: [uglify()] + }) +} diff --git a/docs/README.md b/docs/README.md index 529c54be3..51f250680 100644 --- a/docs/README.md +++ b/docs/README.md @@ -15,7 +15,7 @@ See the [Quick start](/quickstart) for more details. - Smart full-text search plugin - Multiple themes - Useful plugin API -- Compatible with IE9+ +- Compatible with IE10+ ## Examples diff --git a/docs/zh-cn/README.md b/docs/zh-cn/README.md index e5051b1c9..5f6e9fa94 100644 --- a/docs/zh-cn/README.md +++ b/docs/zh-cn/README.md @@ -16,7 +16,7 @@ docsify 是一个动态生成文档网站的工具。不同于 GitBook、Hexo - 智能的全文搜索 - 提供多套主题 - 丰富的 API -- 兼容 IE9+ +- 兼容 IE10+ ## 例子 diff --git a/src/core/config.js b/src/core/config.js index d344aae91..d11c3d1fb 100644 --- a/src/core/config.js +++ b/src/core/config.js @@ -17,7 +17,9 @@ const config = merge({ ga: '' }, window.$docsify) -const script = document.currentScript || [].slice.call(document.getElementsByTagName('script')).pop() +const script = document.currentScript || + [].slice.call(document.getElementsByTagName('script')) + .filter(n => /docsify\./.test(n.src))[0] if (script) { for (const prop in config) { diff --git a/src/core/event/index.js b/src/core/event/index.js index 46d9d284a..a31188b9f 100644 --- a/src/core/event/index.js +++ b/src/core/event/index.js @@ -1,11 +1,19 @@ -export function eventMixin (Docsify) { - Docsify.prototype.$bindEvents = function () { - } +import { isMobile } from '../util/env' +import { dom, on } from '../util/dom' +import * as sidebar from './sidebar' +export function eventMixin (Docsify) { Docsify.prototype.$resetEvents = function () { } } export function initEvent (vm) { - vm.$bindEvents() + // Bind toggle button + sidebar.btn('button.sidebar-toggle') + // Bind sticky effect + if (vm.config.coverpage) { + !isMobile && on('scroll', sidebar.sticky) + } else { + dom.body.classList.add('sticky') + } } diff --git a/src/core/event/scroll.js b/src/core/event/scroll.js new file mode 100644 index 000000000..b79589b82 --- /dev/null +++ b/src/core/event/scroll.js @@ -0,0 +1,10 @@ +export function activeSidebar () { + +} + +export function scrollIntoView () { + +} + +export function scroll2Top () { +} diff --git a/src/core/event/sidebar.js b/src/core/event/sidebar.js new file mode 100644 index 000000000..aeaeeedd6 --- /dev/null +++ b/src/core/event/sidebar.js @@ -0,0 +1,28 @@ +import { isMobile } from '../util/env' +import { getNode, on, dom } from '../util/dom' +/** + * Toggle button + */ +export function btn (el) { + const toggle = () => dom.body.classList.toggle('close') + + el = getNode(el) + on(el, 'click', toggle) + + if (isMobile) { + const sidebar = getNode('.sidebar') + + on(sidebar, 'click', () => { + toggle() + setTimeout(() => activeLink(sidebar, true), 0) + }) + } +} + +export function sticky () { + +} + +export function activeLink () { + +} diff --git a/src/core/fetch/index.js b/src/core/fetch/index.js index 7771e898c..61acc8144 100644 --- a/src/core/fetch/index.js +++ b/src/core/fetch/index.js @@ -1,7 +1,8 @@ import { callHook } from '../init/lifecycle' export function fetchMixin (Docsify) { - Docsify.prototype.$fetch = function () { + Docsify.prototype.$fetch = function (path) { + // 加载侧边栏、导航、内容 } } diff --git a/src/core/init/index.js b/src/core/init/index.js index 8a6c2a3e2..826e6efb3 100644 --- a/src/core/init/index.js +++ b/src/core/init/index.js @@ -9,7 +9,7 @@ import { isFn } from '../util/core' export function initMixin (Docsify) { Docsify.prototype._init = function () { const vm = this - vm._config = config || {} + vm.config = config || {} initLifecycle(vm) // Init hooks initPlugin(vm) // Install plugins @@ -23,5 +23,5 @@ export function initMixin (Docsify) { } function initPlugin (vm) { - [].concat(vm.config.plugins).forEach(fn => isFn(fn) && fn(vm.bindHook)) + [].concat(vm.config.plugins).forEach(fn => isFn(fn) && fn(vm._lifecycle, vm)) } diff --git a/src/core/init/lifecycle.js b/src/core/init/lifecycle.js index e4a48a84d..380bac1eb 100644 --- a/src/core/init/lifecycle.js +++ b/src/core/init/lifecycle.js @@ -4,10 +4,10 @@ export function initLifecycle (vm) { const hooks = ['init', 'beforeEach', 'afterEach', 'doneEach', 'ready'] vm._hooks = {} - vm.bindHook = {} + vm._lifecycle = {} hooks.forEach(hook => { const arr = vm._hooks[hook] = [] - vm._bindHook[hook] = fn => arr.push(fn) + vm._lifecycle[hook] = fn => arr.push(fn) }) } diff --git a/src/core/render/dom.js b/src/core/render/dom.js deleted file mode 100644 index 8277095c2..000000000 --- a/src/core/render/dom.js +++ /dev/null @@ -1,12 +0,0 @@ -const cacheNode = {} - -export function getCacheNode (el) { - if (typeof el === 'string') { - const selector = el - - el = cacheNode[el] || document.querySelector(el) - if (!el) console.error('Cannot find element:', selector) - } - - return el -} diff --git a/src/core/render/index.js b/src/core/render/index.js index 03ef3f0c3..12f50c540 100644 --- a/src/core/render/index.js +++ b/src/core/render/index.js @@ -1,9 +1,47 @@ -export function renderMixin (Docsify) { - Docsify.prototype._renderTo = function (dom, content) { +import { getNode, dom } from '../util/dom' +import cssVars from '../util/polyfill/css-vars' +import * as tpl from './tpl' +export function renderMixin (Docsify) { + Docsify.prototype._renderTo = function (el, content, replace) { + const node = getNode(el) + if (node) node[replace ? 'outerHTML' : 'innerHTML'] = content } } export function initRender (vm) { - // init + const config = vm.config + const id = config.el || '#app' + const navEl = dom.find('nav') || dom.create('nav') + + let el = dom.find(id) + let html = '' + + navEl.classList.add('app-nav') + + if (!config.repo) { + navEl.classList.add('no-badge') + } + if (!el) { + el = dom.create(id) + dom.appendTo(dom.body, el) + } + if (config.repo) { + html += tpl.corner(config.repo) + } + if (config.coverpage) { + html += tpl.cover() + } + + html += tpl.main(config) + // Render main app + vm._renderTo(el, html, true) + // Add nav + dom.body.insertBefore(navEl, dom.body.children[0]) + + if (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 1da5b2255..8eff66b4d 100644 --- a/src/core/render/progressbar.js +++ b/src/core/render/progressbar.js @@ -1,3 +1,4 @@ +import { dom } from '../util/dom' import { isPrimitive } from '../util/core' let loadingEl @@ -8,10 +9,10 @@ let timeId */ function init () { if (loadingEl) return - const div = document.createElement('div') + const div = dom.create('div') div.classList.add('progress') - document.body.appendChild(div) + dom.appendTo(div, dom.body) loadingEl = div } /** diff --git a/src/core/render/tpl.js b/src/core/render/tpl.js index df5897910..cdf870d65 100644 --- a/src/core/render/tpl.js +++ b/src/core/render/tpl.js @@ -1,4 +1,4 @@ -import { isMobile } from '../util/core' +import { isMobile } from '../util/env' /** * Render github corner * @param {Object} data @@ -9,27 +9,38 @@ export function corner (data) { if (!/\/\//.test(data)) data = 'https://github.com/' + data data = data.replace(/^git\+/, '') - return ` - - - ` + return ( + '' + + '' + + '`') } /** * Render main content */ -export function main () { - const aside = `${toggle()}` +export function main (config) { + const aside = ( + '' + + '') - return (isMobile() ? `${aside}
` : `
${aside}`) + - `
-
-
-
` + return (isMobile ? `${aside}
` : `
${aside}`) + + '
' + + '
' + + '
' + + '
' } /** @@ -37,20 +48,14 @@ export function main () { */ export function cover () { const SL = ', 100%, 85%' - const bgc = `linear-gradient(to left bottom, hsl(${Math.floor(Math.random() * 255) + SL}) 0%, hsl(${Math.floor(Math.random() * 255) + SL}) 100%)` + const bgc = 'linear-gradient(to left bottom, ' + + `hsl(${Math.floor(Math.random() * 255) + SL}) 0%,` + + `hsl(${Math.floor(Math.random() * 255) + SL}) 100%)` - return `
-
-
-
` -} - -export function toggle () { - return `` + return `
` + + '
' + + '
' + + '
' } /** diff --git a/src/core/route/hash.js b/src/core/route/hash.js index 90d180c6d..1f544d49d 100644 --- a/src/core/route/hash.js +++ b/src/core/route/hash.js @@ -1,5 +1,4 @@ -import { cleanPath, getLocation } from './util' - +// import { cleanPath, getLocation } from './util' export function ensureSlash () { const path = getHash() if (path.charAt(0) === '/') return diff --git a/src/core/util/dom.js b/src/core/util/dom.js new file mode 100644 index 000000000..22322199d --- /dev/null +++ b/src/core/util/dom.js @@ -0,0 +1,41 @@ +import { isFn } from '../util/core' + +const cacheNode = {} + +/** + * Get Node + * @param {String|Element} el + * @param {Boolean} noCache + * @return {Element} + */ +export function getNode (el, noCache = false) { + if (typeof el === 'string') { + el = noCache ? dom.find(el) : (cacheNode[el] || dom.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 function on (el, type, handler) { + isFn(type) + ? window.addEventListener(el, type) + : el.addEventListener(type, handler) +} + +export const off = function on (el, type, handler) { + isFn(type) + ? window.removeEventListener(el, type) + : el.removeEventListener(type, handler) +} diff --git a/src/core/util/index.js b/src/core/util/index.js index bfcc8b27d..38687ba59 100644 --- a/src/core/util/index.js +++ b/src/core/util/index.js @@ -1,2 +1,3 @@ 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 f0200e6ff..686ad7261 100644 --- a/src/core/util/polyfill/css-vars.js +++ b/src/core/util/polyfill/css-vars.js @@ -1,29 +1,32 @@ -import { get } from '../fetch/ajax' +import { dom } from '../dom' +import { get } from '../../fetch/ajax' -function replaceVar (block) { - block.innerHTML = block.innerHTML.replace(/var\(\s*--theme-color.*?\)/g, $docsify.themeColor) +function replaceVar (block, themeColor) { + block.innerHTML = block.innerHTML + .replace(/var\(\s*--theme-color.*?\)/g, themeColor) } -export function cssVars () { - // variable support - if (window.CSS && window.CSS.supports && window.CSS.supports('(--foo: red)')) return +export default function (themeColor) { + // Variable support + if (window.CSS + && window.CSS.supports + && window.CSS.supports('(--foo: red)')) return - const styleBlocks = document.querySelectorAll('style:not(.inserted),link') + const styleBlocks = dom.findAll('style:not(.inserted),link') ;[].forEach.call(styleBlocks, block => { if (block.nodeName === 'STYLE') { - replaceVar(block) + replaceVar(block, themeColor) } else if (block.nodeName === 'LINK') { const href = block.getAttribute('href') if (!/\.css$/.test(href)) return get(href).then(res => { - const style = document.createElement('style') + const style = dom.create('style', res) - style.innerHTML = res - document.head.appendChild(style) - replaceVar(style) + dom.head.appendChild(style) + replaceVar(style, themeColor) }) } }) diff --git a/src/themes/basic/_layout.css b/src/themes/basic/_layout.css index e194affd3..d413c10ac 100644 --- a/src/themes/basic/_layout.css +++ b/src/themes/basic/_layout.css @@ -64,7 +64,7 @@ kbd { } /* navbar */ -nav { +nav.app-nav { position: absolute; right: 0; left: 0; From b1c676c9120b9fca7fbef54bc9cc26c0742e99fc Mon Sep 17 00:00:00 2001 From: "qingwei.li" Date: Sat, 18 Feb 2017 11:41:33 +0800 Subject: [PATCH 03/25] refactor(core): add router --- src/core/config.js | 4 +- src/core/event/index.js | 4 +- src/core/event/sidebar.js | 4 +- src/core/fetch/ajax.js | 31 +++++++++++---- src/core/fetch/index.js | 36 +++++++++++++++-- src/core/global-api.js | 13 +++++++ src/core/index.js | 8 +--- src/core/render/compiler.js | 50 ++++++++++++++++++++++++ src/core/render/index.js | 20 ++++++++-- src/core/render/progressbar.js | 21 +++++----- src/core/route/hash.js | 62 +++++++++++++++++++++++++----- src/core/route/index.js | 43 ++++++++++++++++----- src/core/route/util.js | 46 +++++++++++++++++++--- src/core/util/core.js | 9 ++--- src/core/util/dom.js | 34 ++++++++++------ src/core/util/index.js | 1 - src/core/util/polyfill/css-vars.js | 18 ++++----- 17 files changed, 316 insertions(+), 88 deletions(-) create mode 100644 src/core/global-api.js create mode 100644 src/core/render/compiler.js diff --git a/src/core/config.js b/src/core/config.js index d11c3d1fb..a1bf9bb8b 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 a31188b9f..4c44100b9 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 aeaeeedd6..a02ef7be2 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 ba79b766f..09d43905a 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 61acc8144..2fb7aa018 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 000000000..83a328870 --- /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 96cab23a3..e2c94a488 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 000000000..daed38c1b --- /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 `${text}` +} diff --git a/src/core/render/index.js b/src/core/render/index.js index 12f50c540..2f9610f2b 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 8eff66b4d..c8347e621 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 1f544d49d..fd0607e6c 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 011db7413..0d4a3c616 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 4cbf898bc..300253a40 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 968898393..5a153b84c 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 22322199d..a2bd22cce 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 38687ba59..bfcc8b27d 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 686ad7261..bb8e49866 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) }) } }) From 8090ef9179174f9f6e0c86e04874640d86058a71 Mon Sep 17 00:00:00 2001 From: "qingwei.li" Date: Sat, 18 Feb 2017 14:09:17 +0800 Subject: [PATCH 04/25] refactor(core): and markdown compiler --- .eslintrc | 3 - src/core/fetch/index.js | 11 +-- src/core/index.js | 2 +- src/core/render/compiler.js | 66 +++++++++++--- src/core/render/emojify.js | 6 ++ src/core/render/gen-tree.js | 27 ++++++ src/core/render/index.js | 41 ++++++--- src/core/render/slugify.js | 27 ++++++ src/core/route/hash.js | 20 +++-- src/core/route/index.js | 10 ++- src/core/route/util.js | 37 ++++---- src/util.js | 166 ------------------------------------ 12 files changed, 189 insertions(+), 227 deletions(-) create mode 100644 src/core/render/emojify.js create mode 100644 src/core/render/gen-tree.js create mode 100644 src/core/render/slugify.js delete mode 100644 src/util.js diff --git a/.eslintrc b/.eslintrc index 527ed9baa..86d102d90 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 2fb7aa018..3602baf4b 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 e2c94a488..3ac8b56d9 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 daed38c1b..2d2024070 100644 --- a/src/core/render/compiler.js +++ b/src/core/render/compiler.js @@ -1,13 +1,44 @@ 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() +let markdownCompiler = marked +let contentBase = '' +let renderer = new marked.Renderer() -export function markdown () { +const toc = [] -} +/** + * Compile markdown content + */ +export const markdown = cached(text => { + let html = '' -const toc = [] + 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 @@ -15,12 +46,11 @@ const toc = [] */ 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 `${text}` +} + +/** + * Compile sidebar + */ +export function sidebar (text) { - return `${text}` } diff --git a/src/core/render/emojify.js b/src/core/render/emojify.js new file mode 100644 index 000000000..bc414a3b5 --- /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, '$1') + .replace(/__colon__/g, ':') +} diff --git a/src/core/render/gen-tree.js b/src/core/render/gen-tree.js new file mode 100644 index 000000000..84ff05e78 --- /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 2f9610f2b..ff25518e8 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 000000000..a6e9d39b9 --- /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 fd0607e6c..b114f3db2 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 0d4a3c616..3fa1020f7 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 300253a40..635273b32 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 cleanPath (path) { - return path.replace(/\/+/g, '/') +export function stringifyQuery (obj) { + const qs = [] + + for (const key in obj) { + qs.push(`${encode(key)}=${encode(obj[key])}`) + } + + return qs.length ? `?${qs.join('&')}` : '' } -export function getBasePath (base) { +export const getBasePath = cached(base => { return /^(\/|https?:)/g.test(base) ? base : cleanPath(window.location.pathname + '/' + base) -} +}) -export function getCurrentRoot (path) { +export const getRoot = cached(path => { return /\/$/g.test(path) ? path : path.match(/(\S*\/)[^\/]+$/)[1] +}) + +export function cleanPath (path) { + return path.replace(/\/+/g, '/') } diff --git a/src/util.js b/src/util.js deleted file mode 100644 index 8f182dff0..000000000 --- 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, '$1') - .replace(/__colon__/g, ':') -} From 1143de2944552c4e80596a4e3547cce733382658 Mon Sep 17 00:00:00 2001 From: "qingwei.li" Date: Sat, 18 Feb 2017 16:18:07 +0800 Subject: [PATCH 05/25] refactor(core): and sidebar event --- src/core/event/scroll.js | 2 +- src/core/event/sidebar.js | 28 +++++++++++++++++++++++--- src/core/render/compiler.js | 40 +++++++++++++++++++++++++++++++------ src/core/render/index.js | 13 ++++++++++-- src/core/route/index.js | 3 ++- src/core/util/dom.js | 31 ++++++++++++++++++++++++---- 6 files changed, 100 insertions(+), 17 deletions(-) diff --git a/src/core/event/scroll.js b/src/core/event/scroll.js index b79589b82..503c6b462 100644 --- a/src/core/event/scroll.js +++ b/src/core/event/scroll.js @@ -1,4 +1,4 @@ -export function activeSidebar () { +export function scrollActiveSidebar () { } diff --git a/src/core/event/sidebar.js b/src/core/event/sidebar.js index a02ef7be2..498354f5b 100644 --- a/src/core/event/sidebar.js +++ b/src/core/event/sidebar.js @@ -1,5 +1,6 @@ import { isMobile } from '../util/env' -import { getNode, on, body } from '../util/dom' +import { getNode, on, body, findAll, toggleClass } from '../util/dom' +import { getHash } from '../route/hash' /** * Toggle button */ @@ -14,7 +15,7 @@ export function btn (el) { on(sidebar, 'click', () => { toggle() - setTimeout(() => activeLink(sidebar, true), 0) + setTimeout(() => getAndActive(true), 0) }) } } @@ -23,6 +24,27 @@ export function sticky () { } -export function activeLink () { +export function getAndActive (el, isParent) { + const dom = getNode(el) + const links = findAll(dom, 'a') + const hash = '#' + getHash() + let target + + links + .sort((a, b) => b.href.length - a.href.length) + .forEach(a => { + const href = a.getAttribute('href') + const node = isParent ? a.parentNode : a + + if (hash.indexOf(href) === 0 && !target) { + target = a + toggleClass(node, 'add', 'active') + } else { + toggleClass(node, 'remove', 'active') + } + }) + + // TODO FIXED + return target } diff --git a/src/core/render/compiler.js b/src/core/render/compiler.js index 2d2024070..129be25bf 100644 --- a/src/core/render/compiler.js +++ b/src/core/render/compiler.js @@ -1,6 +1,7 @@ import marked from 'marked' import Prism from 'prismjs' -import { helper as helperTpl } from './tpl' +import { helper as helperTpl, tree as treeTpl } from './tpl' +import { genTree } from './gen-tree' import { slugify, clearSlugCache } from './slugify' import { emojify } from './emojify' import { toURL } from '../route/hash' @@ -9,8 +10,7 @@ import { isFn, merge, cached } from '../util/core' let markdownCompiler = marked let contentBase = '' let renderer = new marked.Renderer() - -const toc = [] +let toc = [] /** * Compile markdown content @@ -29,8 +29,8 @@ export const markdown = cached(text => { markdown.renderer = renderer -markdown.init = function (config = {}, context = window.location.pathname) { - contentBase = context +markdown.init = function (config = {}, base = window.location.pathname) { + contentBase = base if (isFn(config)) { markdownCompiler = config(marked, renderer) @@ -85,6 +85,34 @@ renderer.image = function (href, title, text) { /** * Compile sidebar */ -export function sidebar (text) { +export function sidebar (text, level) { + let html = '' + + if (text) { + html = markdown(text) + html = html.match(/]*>([\s\S]+)<\/ul>/g)[0] + } else { + const tree = genTree(toc, level) + html = treeTpl(tree, '
    ') + } + + return html +} +/** + * Compile sub sidebar + */ +export function subSidebar (el, level) { + if (el) { + toc[0] && toc[0].level === 1 && toc.shift() + const tree = genTree(toc, level) + el.parentNode.innerHTML += treeTpl(tree, '
      ') + } + toc = [] +} + +/** + * Compile cover page + */ +export function cover (text) { } diff --git a/src/core/render/index.js b/src/core/render/index.js index ff25518e8..2f4d1b886 100644 --- a/src/core/render/index.js +++ b/src/core/render/index.js @@ -1,14 +1,19 @@ import * as dom from '../util/dom' +import { getAndActive } from '../event/sidebar' +import { scrollActiveSidebar } from '../event/scroll' import cssVars from '../util/polyfill/css-vars' import * as tpl from './tpl' -import { markdown, sidebar } from './compiler' +import { markdown, sidebar, subSidebar } from './compiler' import { callHook } from '../init/lifecycle' function renderMain (html) { if (!html) { // TODO: Custom 404 page } + this._renderTo('.markdown-section', html) + // Render sidebar with the TOC + !this.config.loadSidebar && this._renderSidebar() } export function renderMixin (Docsify) { @@ -20,8 +25,12 @@ export function renderMixin (Docsify) { } proto._renderSidebar = function (text) { - this._renderTo('.sidebar-nav', sidebar(text)) + const { maxLevel, subMaxLevel } = this.config + + this._renderTo('.sidebar-nav', sidebar(text, maxLevel)) + subSidebar(getAndActive('.sidebar-nav', true), subMaxLevel) // bind event + scrollActiveSidebar() } proto._renderNav = function (text) { diff --git a/src/core/route/index.js b/src/core/route/index.js index 3fa1020f7..dc8eca2ae 100644 --- a/src/core/route/index.js +++ b/src/core/route/index.js @@ -38,11 +38,12 @@ export function initRoute (vm) { on('hashchange', _ => { normalize() - lastRoute = vm.route = parse() + vm.route = parse() if (lastRoute.path === vm.route.path) { // TODO: goto xxx return } vm._fetch() + lastRoute = vm.route }) } diff --git a/src/core/util/dom.js b/src/core/util/dom.js index a2bd22cce..06016a571 100644 --- a/src/core/util/dom.js +++ b/src/core/util/dom.js @@ -22,12 +22,24 @@ export const body = $.body export const head = $.head -export function find (node) { - return $.querySelector(node) +/** + * Find element + * @example + * find('nav') => document.querySelector('nav') + * find(nav, 'a') => nav.querySelector('a') + */ +export function find (el, node) { + return node ? el.querySelector(node) : $.querySelector(el) } -export function findAll (node) { - return [].clice.call($.querySelectorAll(node)) +/** + * Find all elements + * @example + * findAll('a') => [].slice.call(document.querySelectorAll('a')) + * findAll(nav, 'a') => [].slice.call(nav.querySelectorAll('a')) + */ +export function findAll (el, node) { + return [].slice.call(node ? el.querySelectorAll(node) : $.querySelectorAll(el)) } export function create (node, tpl) { @@ -51,3 +63,14 @@ export const off = function on (el, type, handler) { ? window.removeEventListener(el, type) : el.removeEventListener(type, handler) } + +/** + * Toggle class + * + * @example + * toggleClass(el, 'active') => el.classList.toggle('active') + * toggleClass(el, 'add', 'active') => el.classList.add('active') + */ +export function toggleClass (el, type, val) { + el.classList[val ? type : 'toggle'](val || type) +} From 1de67af1e0b1abb1f899a7e84f32902a37ee0156 Mon Sep 17 00:00:00 2001 From: "qingwei.li" Date: Sat, 18 Feb 2017 17:02:16 +0800 Subject: [PATCH 06/25] refactor(core): and cover --- src/core/event/sidebar.js | 12 ++++++++++-- src/core/fetch/index.js | 22 ++++++++++++++++++++-- src/core/render/compiler.js | 8 +++++++- src/core/render/index.js | 32 +++++++++++++++++++++++++++++--- src/core/route/hash.js | 2 +- src/core/route/index.js | 3 +++ 6 files changed, 70 insertions(+), 9 deletions(-) diff --git a/src/core/event/sidebar.js b/src/core/event/sidebar.js index 498354f5b..aa4475b4a 100644 --- a/src/core/event/sidebar.js +++ b/src/core/event/sidebar.js @@ -1,6 +1,7 @@ import { isMobile } from '../util/env' import { getNode, on, body, findAll, toggleClass } from '../util/dom' import { getHash } from '../route/hash' + /** * Toggle button */ @@ -21,7 +22,15 @@ export function btn (el) { } export function sticky () { - + const cover = getNode('section.cover') + if (!cover) return + const coverHeight = cover.getBoundingClientRect().height + + if (window.pageYOffset >= coverHeight || cover.classList.contains('hidden')) { + toggleClass(body, 'add', 'sticky') + } else { + toggleClass(body, 'remove', 'sticky') + } } export function getAndActive (el, isParent) { @@ -45,6 +54,5 @@ export function getAndActive (el, isParent) { } }) - // TODO FIXED return target } diff --git a/src/core/fetch/index.js b/src/core/fetch/index.js index 3602baf4b..d5316aeb5 100644 --- a/src/core/fetch/index.js +++ b/src/core/fetch/index.js @@ -15,6 +15,8 @@ export function fetchMixin (Docsify) { last && last.abort && last.abort() last = get(this.$getFile(path), true) + + // Load main content last.then(text => { this._renderMain(text) if (!loadSidebar) return cb() @@ -23,6 +25,7 @@ export function fetchMixin (Docsify) { // Load sidebar get(this.$getFile(root + loadSidebar)) + // fallback root navbar when fail .then(fn, _ => get(loadSidebar).then(fn)) }, _ => this._renderMain(null)) @@ -31,13 +34,28 @@ export function fetchMixin (Docsify) { loadNavbar && get(this.$getFile(root + loadNavbar)) .then( - this._renderNav, - _ => get(loadNavbar).then(this._renderNav) + text => this._renderNav(text), + // fallback root navbar when fail + _ => get(loadNavbar).then(text => this._renderNav(text)) ) } + + Docsify.prototype._fetchCover = function () { + const { coverpage } = this.config + const root = getRoot(this.route.path) + + if (this.route.path !== '/' || !coverpage) { + this._renderCover() + return + } + + get(this.$getFile(root + coverpage)) + .then(text => this._renderCover(text)) + } } export function initFetch (vm) { + vm._fetchCover(vm) vm._fetch(result => { vm.$resetEvents() callHook(vm, 'doneEach') diff --git a/src/core/render/compiler.js b/src/core/render/compiler.js index 129be25bf..06c046c49 100644 --- a/src/core/render/compiler.js +++ b/src/core/render/compiler.js @@ -5,7 +5,7 @@ import { genTree } from './gen-tree' import { slugify, clearSlugCache } from './slugify' import { emojify } from './emojify' import { toURL } from '../route/hash' -import { isFn, merge, cached } from '../util/core' +import { isFn, merge, cached, noop } from '../util/core' let markdownCompiler = marked let contentBase = '' @@ -115,4 +115,10 @@ export function subSidebar (el, level) { * Compile cover page */ export function cover (text) { + const cacheToc = toc.slice() + const html = markdown(text) + + toc = cacheToc.slice() + + return html } diff --git a/src/core/render/index.js b/src/core/render/index.js index 2f4d1b886..53cc62dad 100644 --- a/src/core/render/index.js +++ b/src/core/render/index.js @@ -1,9 +1,9 @@ import * as dom from '../util/dom' -import { getAndActive } from '../event/sidebar' +import { getAndActive, sticky } from '../event/sidebar' import { scrollActiveSidebar } from '../event/scroll' import cssVars from '../util/polyfill/css-vars' import * as tpl from './tpl' -import { markdown, sidebar, subSidebar } from './compiler' +import { markdown, sidebar, subSidebar, cover } from './compiler' import { callHook } from '../init/lifecycle' function renderMain (html) { @@ -34,7 +34,8 @@ export function renderMixin (Docsify) { } proto._renderNav = function (text) { - this._renderTo('nav', markdown(text)) + text && this._renderTo('nav', markdown(text)) + getAndActive('nav') } proto._renderMain = function (text) { @@ -43,6 +44,31 @@ export function renderMixin (Docsify) { callHook(this, 'afterEach', html, text => renderMain.call(this, text)) }) } + + proto._renderCover = function (text) { + const el = dom.getNode('.cover') + if (!text) { + dom.toggleClass(el, 'remove', 'show') + return + } + dom.toggleClass(el, 'add', 'show') + + let html = cover(text) + const m = html.trim().match('

      ([^<]*?)

      $') + + if (m) { + if (m[2] === 'color') { + el.style.background = m[1] + (m[3] || '') + } else { + dom.toggleClass(el, 'add', 'has-mask') + el.style.backgroundImage = `url(${m[1]})` + } + html = html.replace(m[0], '') + } + + this._renderTo('.cover-main', html) + sticky() + } } export function initRender (vm) { diff --git a/src/core/route/hash.js b/src/core/route/hash.js index b114f3db2..7f0129df3 100644 --- a/src/core/route/hash.js +++ b/src/core/route/hash.js @@ -1,5 +1,5 @@ import { merge } from '../util/core' -import { parseQuery, stringifyQuery, cleanPath } from './util' +import { parseQuery, stringifyQuery } from './util' function replaceHash (path) { const i = window.location.href.indexOf('#') diff --git a/src/core/route/index.js b/src/core/route/index.js index dc8eca2ae..ad29c5747 100644 --- a/src/core/route/index.js +++ b/src/core/route/index.js @@ -39,10 +39,13 @@ export function initRoute (vm) { on('hashchange', _ => { normalize() vm.route = parse() + if (lastRoute.path === vm.route.path) { // TODO: goto xxx return } + + vm._fetchCover() vm._fetch() lastRoute = vm.route }) From a999a0c87504487d37509a1bbb4d7a90a1256f1e Mon Sep 17 00:00:00 2001 From: "qingwei.li" Date: Sat, 18 Feb 2017 17:20:05 +0800 Subject: [PATCH 07/25] refactor(core): add scroll event --- src/core/event/index.js | 5 -- src/core/event/scroll.js | 85 ++++++++++++++++++++- src/core/fetch/index.js | 5 +- src/core/index.js | 2 - src/event.js | 159 --------------------------------------- 5 files changed, 86 insertions(+), 170 deletions(-) delete mode 100644 src/event.js diff --git a/src/core/event/index.js b/src/core/event/index.js index 4c44100b9..1181c9ba5 100644 --- a/src/core/event/index.js +++ b/src/core/event/index.js @@ -2,11 +2,6 @@ import { isMobile } from '../util/env' import { body, on } from '../util/dom' import * as sidebar from './sidebar' -export function eventMixin (Docsify) { - Docsify.prototype.$resetEvents = function () { - } -} - export function initEvent (vm) { // Bind toggle button sidebar.btn('button.sidebar-toggle') diff --git a/src/core/event/scroll.js b/src/core/event/scroll.js index 503c6b462..89b9b82c8 100644 --- a/src/core/event/scroll.js +++ b/src/core/event/scroll.js @@ -1,10 +1,89 @@ +import { isMobile } from '../util/env' +import * as dom from '../util/dom' + export function scrollActiveSidebar () { + if (isMobile) return -} + let hoverOver = false + const anchors = dom.findAll('.anchor') + const sidebar = dom.find('.sidebar') + const wrap = dom.find(sidebar, '.sidebar-nav') + const height = sidebar.clientHeight + + const nav = {} + const lis = dom.findAll(sidebar, 'li') + let active = dom.find(sidebar, 'li.active') + + for (let i = 0, len = lis.length; i < len; i += 1) { + const li = lis[i] + const a = li.querySelector('a') + if (!a) continue + let href = a.getAttribute('href') + + if (href !== '/') { + const match = href.match('#([^#]+)$') + if (match && match.length) href = match[0].slice(1) + } + + nav[decodeURIComponent(href)] = li + } + + function highlight () { + const top = dom.body.scrollTop + let last + + for (let i = 0, len = anchors.length; i < len; i += 1) { + const node = anchors[i] -export function scrollIntoView () { + if (node.offsetTop > top) { + if (!last) last = node + break + } else { + last = node + } + } + if (!last) return + const li = nav[last.getAttribute('data-id')] + if (!li || li === active) return + if (active) active.classList.remove('active') + + li.classList.add('active') + active = li + + // scroll into view + // https://github.com/vuejs/vuejs.org/blob/master/themes/vue/source/js/common.js#L282-L297 + if (!hoverOver && dom.body.classList.contains('sticky')) { + const curOffset = 0 + const cur = active.offsetTop + active.clientHeight + 40 + const isInView = ( + active.offsetTop >= wrap.scrollTop && + cur <= wrap.scrollTop + height + ) + const notThan = cur - curOffset < height + const top = isInView + ? wrap.scrollTop + : notThan + ? curOffset + : cur - height + + sidebar.scrollTop = top + } + } + + dom.off('scroll', highlight) + dom.on('scroll', highlight) + dom.on(sidebar, 'mouseover', () => { hoverOver = true }) + dom.on(sidebar, 'mouseleave', () => { hoverOver = false }) } -export function scroll2Top () { +export function scrollIntoView (id) { + const section = dom.find('#' + id) + section && setTimeout(() => section.scrollIntoView(), 0) +} + +const scrollEl = dom.$.scrollingElement || dom.$.documentElement + +export function scroll2Top (offset = 0) { + scrollEl.scrollTop = offset === true ? 0 : Number(offset) } diff --git a/src/core/fetch/index.js b/src/core/fetch/index.js index d5316aeb5..25b74387f 100644 --- a/src/core/fetch/index.js +++ b/src/core/fetch/index.js @@ -2,6 +2,8 @@ import { get } from './ajax' import { callHook } from '../init/lifecycle' import { getRoot } from '../route/util' import { noop } from '../util/core' +import { scrollIntoView } from '../event/scroll' +import { getAndActive } from '../event/sidebar' export function fetchMixin (Docsify) { let last @@ -57,7 +59,8 @@ export function fetchMixin (Docsify) { export function initFetch (vm) { vm._fetchCover(vm) vm._fetch(result => { - vm.$resetEvents() + scrollIntoView(vm.route.query.id) + getAndActive('nav') callHook(vm, 'doneEach') }) } diff --git a/src/core/index.js b/src/core/index.js index 3ac8b56d9..f5b8b4b69 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -2,7 +2,6 @@ import { initMixin } from './init' import { routeMixin } from './route' import { renderMixin } from './render' import { fetchMixin } from './fetch' -import { eventMixin } from './event' import initGlobalAPI from './global-api' function Docsify () { @@ -13,7 +12,6 @@ initMixin(Docsify) routeMixin(Docsify) renderMixin(Docsify) fetchMixin(Docsify) -eventMixin(Docsify) /** * Global API diff --git a/src/event.js b/src/event.js deleted file mode 100644 index 536899c05..000000000 --- a/src/event.js +++ /dev/null @@ -1,159 +0,0 @@ -import { isMobile } from './util' - -/** - * Active sidebar when scroll - * @link https://buble.surge.sh/ - */ -export function scrollActiveSidebar () { - if (isMobile()) return - - let hoveredOverSidebar = false - const anchors = document.querySelectorAll('.anchor') - const sidebar = document.querySelector('.sidebar') - const sidebarContainer = sidebar.querySelector('.sidebar-nav') - const sidebarHeight = sidebar.clientHeight - - const nav = {} - const lis = sidebar.querySelectorAll('li') - let active = sidebar.querySelector('li.active') - - for (let i = 0, len = lis.length; i < len; i += 1) { - const li = lis[i] - const a = li.querySelector('a') - if (!a) continue - let href = a.getAttribute('href') - - if (href !== '/') { - const match = href.match('#([^#]+)$') - if (match && match.length) href = match[0].slice(1) - } - - nav[decodeURIComponent(href)] = li - } - - function highlight () { - const top = document.body.scrollTop - let last - - for (let i = 0, len = anchors.length; i < len; i += 1) { - const node = anchors[i] - - if (node.offsetTop > top) { - if (!last) last = node - break - } else { - last = node - } - } - if (!last) return - const li = nav[last.getAttribute('data-id')] - - if (!li || li === active) return - if (active) active.classList.remove('active') - - li.classList.add('active') - active = li - - // scroll into view - // https://github.com/vuejs/vuejs.org/blob/master/themes/vue/source/js/common.js#L282-L297 - if (!hoveredOverSidebar && !sticky.noSticky) { - const currentPageOffset = 0 - const currentActiveOffset = active.offsetTop + active.clientHeight + 40 - const currentActiveIsInView = ( - active.offsetTop >= sidebarContainer.scrollTop && - currentActiveOffset <= sidebarContainer.scrollTop + sidebarHeight - ) - const linkNotFurtherThanSidebarHeight = currentActiveOffset - currentPageOffset < sidebarHeight - const newScrollTop = currentActiveIsInView - ? sidebarContainer.scrollTop - : linkNotFurtherThanSidebarHeight - ? currentPageOffset - : currentActiveOffset - sidebarHeight - - sidebar.scrollTop = newScrollTop - } - } - - window.removeEventListener('scroll', highlight) - window.addEventListener('scroll', highlight) - sidebar.addEventListener('mouseover', () => { hoveredOverSidebar = true }) - sidebar.addEventListener('mouseleave', () => { hoveredOverSidebar = false }) -} - -export function scrollIntoView () { - const id = window.location.hash.match(/#[^#\/]+$/g) - if (!id || !id.length) return - const section = document.querySelector(decodeURIComponent(id[0])) - - if (section) setTimeout(() => section.scrollIntoView(), 0) - - return section -} - -/** - * Acitve link - */ -export function activeLink (dom, activeParent) { - const host = window.location.href - - dom = typeof dom === 'object' ? dom : document.querySelector(dom) - if (!dom) return - let target - - ;[].slice.call(dom.querySelectorAll('a')) - .sort((a, b) => b.href.length - a.href.length) - .forEach(node => { - if (host.indexOf(node.href) === 0 && !target) { - activeParent - ? node.parentNode.classList.add('active') - : node.classList.add('active') - target = node - } else { - activeParent - ? node.parentNode.classList.remove('active') - : node.classList.remove('active') - } - }) - - return target -} - -/** - * sidebar toggle - */ -export function bindToggle (dom) { - dom = typeof dom === 'object' ? dom : document.querySelector(dom) - if (!dom) return - const body = document.body - - dom.addEventListener('click', () => body.classList.toggle('close')) - - if (isMobile()) { - const sidebar = document.querySelector('.sidebar') - sidebar.addEventListener('click', () => { - body.classList.toggle('close') - setTimeout(() => activeLink(sidebar, true), 0) - }) - } -} - -const scrollingElement = document.scrollingElement || document.documentElement - -export function scroll2Top (offset = 0) { - scrollingElement.scrollTop = offset === true ? 0 : Number(offset) -} - -export function sticky () { - sticky.dom = sticky.dom || document.querySelector('section.cover') - const coverHeight = sticky.dom.getBoundingClientRect().height - - return (function () { - if (window.pageYOffset >= coverHeight || sticky.dom.classList.contains('hidden')) { - document.body.classList.add('sticky') - sticky.noSticky = false - } else { - document.body.classList.remove('sticky') - sticky.noSticky = true - } - })() -} From e566d5a118b0cfbb7bbccea35fbd845debf3be4a Mon Sep 17 00:00:00 2001 From: "qingwei.li" Date: Sat, 18 Feb 2017 19:35:14 +0800 Subject: [PATCH 08/25] refactor(core): fix route path --- src/core/event/index.js | 8 ++ src/core/event/scroll.js | 4 +- src/core/fetch/ajax.js | 4 +- src/core/fetch/index.js | 24 ++-- src/core/index.js | 12 +- src/core/init/index.js | 4 +- src/core/render/compiler.js | 34 +++-- src/core/render/index.js | 39 +++++- src/core/render/progressbar.js | 6 +- src/core/render/tpl.js | 2 +- src/core/route/hash.js | 13 +- src/core/route/index.js | 16 +-- src/core/route/util.js | 12 +- src/index.js | 145 -------------------- src/render.js | 237 --------------------------------- src/themes/vue.css | 9 ++ 16 files changed, 125 insertions(+), 444 deletions(-) delete mode 100644 src/index.js delete mode 100644 src/render.js diff --git a/src/core/event/index.js b/src/core/event/index.js index 1181c9ba5..6c469a592 100644 --- a/src/core/event/index.js +++ b/src/core/event/index.js @@ -1,6 +1,14 @@ import { isMobile } from '../util/env' import { body, on } from '../util/dom' import * as sidebar from './sidebar' +import { scrollIntoView } from './scroll' + +export function eventMixin (proto) { + proto.$resetEvents = function () { + scrollIntoView(this.route.query.id) + sidebar.getAndActive('nav') + } +} export function initEvent (vm) { // Bind toggle button diff --git a/src/core/event/scroll.js b/src/core/event/scroll.js index 89b9b82c8..5cfdb1171 100644 --- a/src/core/event/scroll.js +++ b/src/core/event/scroll.js @@ -1,5 +1,6 @@ import { isMobile } from '../util/env' import * as dom from '../util/dom' +import { parse } from '../route/hash' export function scrollActiveSidebar () { if (isMobile) return @@ -21,8 +22,7 @@ export function scrollActiveSidebar () { let href = a.getAttribute('href') if (href !== '/') { - const match = href.match('#([^#]+)$') - if (match && match.length) href = match[0].slice(1) + href = parse(href).query.id } nav[decodeURIComponent(href)] = li diff --git a/src/core/fetch/ajax.js b/src/core/fetch/ajax.js index 09d43905a..503889742 100644 --- a/src/core/fetch/ajax.js +++ b/src/core/fetch/ajax.js @@ -28,7 +28,9 @@ export function get (url, hasBar = false) { return { then: function (success, error = noop) { if (hasBar) { - const id = setInterval(_ => progressbar({}), 500) + const id = setInterval(_ => progressbar({ + step: Math.floor(Math.random() * 5 + 1) + }), 500) on('progress', progressbar) on('loadend', evt => { diff --git a/src/core/fetch/index.js b/src/core/fetch/index.js index 25b74387f..e3f4c0d77 100644 --- a/src/core/fetch/index.js +++ b/src/core/fetch/index.js @@ -2,13 +2,10 @@ import { get } from './ajax' import { callHook } from '../init/lifecycle' import { getRoot } from '../route/util' import { noop } from '../util/core' -import { scrollIntoView } from '../event/scroll' -import { getAndActive } from '../event/sidebar' -export function fetchMixin (Docsify) { +export function fetchMixin (proto) { let last - - Docsify.prototype._fetch = function (cb = noop) { + proto._fetch = function (cb = noop) { const { path } = this.route const { loadNavbar, loadSidebar } = this.config const root = getRoot(path) @@ -42,7 +39,7 @@ export function fetchMixin (Docsify) { ) } - Docsify.prototype._fetchCover = function () { + proto._fetchCover = function () { const { coverpage } = this.config const root = getRoot(this.route.path) @@ -54,13 +51,16 @@ export function fetchMixin (Docsify) { get(this.$getFile(root + coverpage)) .then(text => this._renderCover(text)) } + + proto.$fetch = function () { + this._fetchCover() + this._fetch(result => { + this.$resetEvents() + callHook(this, 'doneEach') + }) + } } export function initFetch (vm) { - vm._fetchCover(vm) - vm._fetch(result => { - scrollIntoView(vm.route.query.id) - getAndActive('nav') - callHook(vm, 'doneEach') - }) + vm.$fetch() } diff --git a/src/core/index.js b/src/core/index.js index f5b8b4b69..3a8b6be99 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -2,16 +2,20 @@ import { initMixin } from './init' import { routeMixin } from './route' import { renderMixin } from './render' import { fetchMixin } from './fetch' +import { eventMixin } from './event' import initGlobalAPI from './global-api' function Docsify () { this._init() } -initMixin(Docsify) -routeMixin(Docsify) -renderMixin(Docsify) -fetchMixin(Docsify) +const proto = Docsify.prototype + +initMixin(proto) +routeMixin(proto) +renderMixin(proto) +fetchMixin(proto) +eventMixin(proto) /** * Global API diff --git a/src/core/init/index.js b/src/core/init/index.js index 826e6efb3..064704fa7 100644 --- a/src/core/init/index.js +++ b/src/core/init/index.js @@ -6,8 +6,8 @@ import { initEvent } from '../event' import { initFetch } from '../fetch' import { isFn } from '../util/core' -export function initMixin (Docsify) { - Docsify.prototype._init = function () { +export function initMixin (proto) { + proto._init = function () { const vm = this vm.config = config || {} diff --git a/src/core/render/compiler.js b/src/core/render/compiler.js index 06c046c49..c4abe6a12 100644 --- a/src/core/render/compiler.js +++ b/src/core/render/compiler.js @@ -4,12 +4,15 @@ import { helper as helperTpl, tree as treeTpl } from './tpl' import { genTree } from './gen-tree' import { slugify, clearSlugCache } from './slugify' import { emojify } from './emojify' -import { toURL } from '../route/hash' -import { isFn, merge, cached, noop } from '../util/core' +import { toURL, parse } from '../route/hash' +import { getBasePath, getPath } from '../route/util' +import { isFn, merge, cached } from '../util/core' let markdownCompiler = marked let contentBase = '' +let currentPath = '' let renderer = new marked.Renderer() +const TOC = {} let toc = [] /** @@ -30,7 +33,8 @@ export const markdown = cached(text => { markdown.renderer = renderer markdown.init = function (config = {}, base = window.location.pathname) { - contentBase = base + contentBase = getBasePath(base) + currentPath = parse().path if (isFn(config)) { markdownCompiler = config(marked, renderer) @@ -40,13 +44,17 @@ markdown.init = function (config = {}, base = window.location.pathname) { } } +markdown.update = function () { + currentPath = parse().path +} + /** * render anchor tag * @link https://github.com/chjj/marked#overriding-renderer-methods */ renderer.heading = function (text, level) { const slug = slugify(text) - const url = toURL(contentBase, { id: slug }) + const url = toURL(currentPath, { id: slug }) toc.push({ level, slug: url, title: text }) @@ -60,8 +68,7 @@ renderer.code = function (code, lang = '') { } renderer.link = function (href, title, text) { if (!/:|(\/{2})/.test(href)) { - // TODO - href = `#/${href}`.replace(/\/+/g, '/') + href = toURL(href) } return `${text}` } @@ -74,12 +81,10 @@ renderer.paragraph = function (text) { return `

      ${text}

      ` } renderer.image = function (href, title, text) { - // TODO - // get base path - // const url = /:|(\/{2})/.test(href) ? href : ($docsify.basePath + href).replace(/\/+/g, '/') - // const titleHTML = title ? ` title="${title}"` : '' + const url = getPath(contentBase, href) + const titleHTML = title ? ` title="${title}"` : '' - // return `${text}` + return `${text}` } /** @@ -105,8 +110,11 @@ export function sidebar (text, level) { export function subSidebar (el, level) { if (el) { toc[0] && toc[0].level === 1 && toc.shift() - const tree = genTree(toc, level) - el.parentNode.innerHTML += treeTpl(tree, '
        ') + const tree = genTree(TOC[currentPath] || toc, level) + el.parentNode.innerHTML += treeTpl(tree, '
          ') + } + if (toc.length) { + TOC[currentPath] = toc.slice() } toc = [] } diff --git a/src/core/render/index.js b/src/core/render/index.js index 53cc62dad..5fcaf1d4f 100644 --- a/src/core/render/index.js +++ b/src/core/render/index.js @@ -1,10 +1,21 @@ import * as dom from '../util/dom' import { getAndActive, sticky } from '../event/sidebar' -import { scrollActiveSidebar } from '../event/scroll' +import { scrollActiveSidebar, scroll2Top } from '../event/scroll' import cssVars from '../util/polyfill/css-vars' import * as tpl from './tpl' import { markdown, sidebar, subSidebar, cover } from './compiler' import { callHook } from '../init/lifecycle' +import { getBasePath, getPath } from '../route/util' + +function executeScript () { + const script = dom.findAll('.markdown-section>script') + .filter(s => !/template/.test(s.type))[0] + if (!script) return false + const code = script.innerText.trim() + if (!code) return false + + window.__EXECUTE_RESULT__ = new Function('return ' + code)() +} function renderMain (html) { if (!html) { @@ -14,11 +25,21 @@ function renderMain (html) { this._renderTo('.markdown-section', html) // Render sidebar with the TOC !this.config.loadSidebar && this._renderSidebar() -} + // execute script + this.config.executeScript && executeScript() + + if (!this.config.executeScript + && typeof window.Vue !== 'undefined' + && !executeScript()) { + window.__EXECUTE_RESULT__ = new window.Vue().$mount('#main') + } -export function renderMixin (Docsify) { - const proto = Docsify.prototype + if (this.config.auto2top) { + setTimeout(() => scroll2Top(this.config.auto2top), 0) + } +} +export function renderMixin (proto) { proto._renderTo = function (el, content, replace) { const node = dom.getNode(el) if (node) node[replace ? 'outerHTML' : 'innerHTML'] = content @@ -54,14 +75,14 @@ export function renderMixin (Docsify) { dom.toggleClass(el, 'add', 'show') let html = cover(text) - const m = html.trim().match('

          ([^<]*?)

          $') + const m = html.trim().match('

          ([^<]*?)

          $') if (m) { if (m[2] === 'color') { el.style.background = m[1] + (m[3] || '') } else { dom.toggleClass(el, 'add', 'has-mask') - el.style.backgroundImage = `url(${m[1]})` + el.style.backgroundImage = `url(${getPath(getBasePath(this.config.basePath), m[1])})` } html = html.replace(m[0], '') } @@ -69,13 +90,17 @@ export function renderMixin (Docsify) { this._renderTo('.cover-main', html) sticky() } + + proto._updateRender = function () { + markdown.update() + } } export function initRender (vm) { const config = vm.config // Init markdown compiler - markdown.init(vm.config.markdown) + markdown.init(config.markdown, config.basePath) const id = config.el || '#app' const navEl = dom.find('nav') || dom.create('nav') diff --git a/src/core/render/progressbar.js b/src/core/render/progressbar.js index c8347e621..420fd6d03 100644 --- a/src/core/render/progressbar.js +++ b/src/core/render/progressbar.js @@ -1,5 +1,4 @@ import * as dom from '../util/dom' -import { isPrimitive } from '../util/core' let barEl let timeId @@ -22,11 +21,8 @@ export default function ({ loaded, total, step }) { !barEl && init() - if (!isPrimitive(step)) { - step = Math.floor(Math.random() * 5 + 1) - } if (step) { - num = parseInt(barEl.style.width, 10) + step + num = parseInt(barEl.style.width || 0, 10) + step num = num > 80 ? 80 : num } else { num = Math.floor(loaded / total * 100) diff --git a/src/core/render/tpl.js b/src/core/render/tpl.js index cdf870d65..0bd5198c7 100644 --- a/src/core/render/tpl.js +++ b/src/core/render/tpl.js @@ -38,7 +38,7 @@ export function main (config) { return (isMobile ? `${aside}
          ` : `
          ${aside}`) + '
          ' + - '
          ' + + '
          ' + '
          ' + '
          ' } diff --git a/src/core/route/hash.js b/src/core/route/hash.js index 7f0129df3..0ca788009 100644 --- a/src/core/route/hash.js +++ b/src/core/route/hash.js @@ -1,4 +1,4 @@ -import { merge } from '../util/core' +import { merge, cached } from '../util/core' import { parseQuery, stringifyQuery } from './util' function replaceHash (path) { @@ -8,6 +8,11 @@ function replaceHash (path) { ) } +const replaceSlug = cached(path => { + return path + .replace('#', '?id=') + .replace(/\?(\w+)=/g, (_, slug) => slug === 'id' ? '?id=' : `&${slug}=`) +}) /** * Normalize the current url * @@ -18,9 +23,7 @@ function replaceHash (path) { export function normalize () { let path = getHash() - path = path - .replace('#', '?id=') - .replace(/\?(\w+)=/g, (_, slug) => slug === 'id' ? '?id=' : `&${slug}=`) + path = replaceSlug(path) if (path.charAt(0) === '/') return replaceHash(path) replaceHash('/' + path) @@ -62,7 +65,7 @@ export function parse (path = window.location.href) { * @param {object} qs query params */ export function toURL (path, params) { - const route = parse(path) + const route = parse(replaceSlug(path)) route.query = merge({}, route.query, params) path = route.path + stringifyQuery(route.query) diff --git a/src/core/route/index.js b/src/core/route/index.js index ad29c5747..7e6606d05 100644 --- a/src/core/route/index.js +++ b/src/core/route/index.js @@ -1,5 +1,5 @@ import { normalize, parse } from './hash' -import { getBasePath, cleanPath } from './util' +import { getBasePath, getPath } from './util' import { on } from '../util/dom' function getAlias (path, alias) { @@ -15,16 +15,16 @@ function getFileName (path) { : `${path}.md` } -export function routeMixin (Docsify) { - Docsify.prototype.route = {} - Docsify.prototype.$getFile = function (path) { +export function routeMixin (proto) { + proto.route = {} + proto.$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) + path = getPath(base, path) return path } @@ -39,14 +39,14 @@ export function initRoute (vm) { on('hashchange', _ => { normalize() vm.route = parse() + vm._updateRender() if (lastRoute.path === vm.route.path) { - // TODO: goto xxx + vm.$resetEvents() return } - vm._fetchCover() - vm._fetch() + vm.$fetch() lastRoute = vm.route }) } diff --git a/src/core/route/util.js b/src/core/route/util.js index 635273b32..f49afde47 100644 --- a/src/core/route/util.js +++ b/src/core/route/util.js @@ -3,7 +3,7 @@ import { cached } from '../util/core' const decode = decodeURIComponent const encode = encodeURIComponent -export const parseQuery = cached(query => { +export function parseQuery (query) { const res = {} query = query.trim().replace(/^(\?|#|&)/, '') @@ -19,7 +19,7 @@ export const parseQuery = cached(query => { res[parts[0]] = decode(parts[1]) }) return res -}) +} export function stringifyQuery (obj) { const qs = [] @@ -37,6 +37,14 @@ export const getBasePath = cached(base => { : cleanPath(window.location.pathname + '/' + base) }) +export function getPath (...args) { + const path = args.find(path => /:|(\/{2})/.test(path)) + + if (path) return path + + return cleanPath(args.join('/')) +} + export const getRoot = cached(path => { return /\/$/g.test(path) ? path : path.match(/(\S*\/)[^\/]+$/)[1] }) diff --git a/src/index.js b/src/index.js deleted file mode 100644 index 49dac575c..000000000 --- a/src/index.js +++ /dev/null @@ -1,145 +0,0 @@ -import * as utils from './util' -import { scrollIntoView, activeLink } from './event' -import * as render from './render' -import Hook from './hook' - -const OPTIONS = utils.merge({ - el: '#app', - repo: '', - maxLevel: 6, - subMaxLevel: 0, - loadSidebar: null, - loadNavbar: null, - homepage: 'README.md', - coverpage: '', - basePath: '', - auto2top: false, - name: '', - themeColor: '', - nameLink: window.location.pathname, - ga: '' -}, window.$docsify) -const script = document.currentScript || [].slice.call(document.getElementsByTagName('script')).pop() - -// load configuration for script attribute -if (script) { - for (const prop in OPTIONS) { - const val = script.getAttribute('data-' + utils.camel2kebab(prop)) - OPTIONS[prop] = utils.isNil(val) ? OPTIONS[prop] : (val || true) - } - if (OPTIONS.loadSidebar === true) OPTIONS.loadSidebar = '_sidebar.md' - if (OPTIONS.loadNavbar === true) OPTIONS.loadNavbar = '_navbar.md' - if (OPTIONS.coverpage === true) OPTIONS.coverpage = '_coverpage.md' - if (OPTIONS.repo === true) OPTIONS.repo = '' - if (OPTIONS.name === true) OPTIONS.name = '' -} - -const hook = new Hook() - -// utils -window.$docsify = OPTIONS -window.Docsify = { - installed: true, - utils: utils.merge({}, utils), - hook -} - -// load options -render.init() - -let cacheRoute = null -let cacheXhr = null - -const getAlias = function (route) { - if (OPTIONS.alias[route]) { - return getAlias(OPTIONS.alias[route]) - } else { - return route - } -} - -const mainRender = function (cb) { - let page - let route = utils.getRoute() - if (cacheRoute === route) return cb() - - let basePath = cacheRoute = OPTIONS.basePath + route - - if (!/\//.test(basePath)) { - basePath = '' - } else if (basePath && !/\/$/.test(basePath)) { - basePath = basePath.match(/(\S*\/)[^\/]+$/)[1] - } - - // replace route - if (OPTIONS.alias && OPTIONS.alias['/' + route]) { - route = getAlias('/' + route) - } else { - route = (OPTIONS.basePath + route).replace(/\/+/, '/') - } - - if (!route) { - page = OPTIONS.homepage || 'README.md' - } else if (/\.md$/.test(route)) { - page = route - } else if (/\/$/.test(route)) { - page = `${route}README.md` - } else { - page = `${route}.md` - } - - // Render Cover page - if (OPTIONS.coverpage && page === OPTIONS.homepage) { - utils.load(OPTIONS.coverpage).then(render.renderCover) - } - - cacheXhr && cacheXhr.abort && cacheXhr.abort() - // Render markdown file - cacheXhr = utils.load(page, 'GET', render.renderLoading) - cacheXhr.then(result => { - render.renderArticle(result) - // clear cover - if (OPTIONS.coverpage && page !== OPTIONS.homepage) render.renderCover() - // render sidebar - if (OPTIONS.loadSidebar) { - const renderSidebar = result => { render.renderSidebar(result); cb() } - - utils.load(basePath + OPTIONS.loadSidebar).then(renderSidebar, - _ => utils.load(OPTIONS.loadSidebar).then(renderSidebar)) - } else { - cb() - } - }, _ => render.renderArticle(null)) - - // Render navbar - if (OPTIONS.loadNavbar) { - utils.load(basePath + OPTIONS.loadNavbar).then(render.renderNavbar, - _ => utils.load(OPTIONS.loadNavbar).then(render.renderNavbar)) - } -} - -const Docsify = function () { - setTimeout(_ => { - ;[].concat(OPTIONS.plugins).forEach(fn => typeof fn === 'function' && fn(hook)) - window.Docsify.hook.emit('init') - - const dom = document.querySelector(OPTIONS.el) || document.body - const replace = dom !== document.body - const main = function () { - mainRender(_ => { - scrollIntoView() - activeLink('nav') - window.Docsify.hook.emit('doneEach') - }) - } - - // Render app - render.renderApp(dom, replace) - main() - if (!/^#\//.test(window.location.hash)) window.location.hash = '#/' - window.addEventListener('hashchange', main) - window.Docsify.hook.emit('ready') - }, 0) -} - -export default Docsify() diff --git a/src/render.js b/src/render.js deleted file mode 100644 index 412508875..000000000 --- a/src/render.js +++ /dev/null @@ -1,237 +0,0 @@ -import marked from 'marked' -import Prism from 'prismjs' -import * as tpl from './tpl' -import * as event from './event' -import * as polyfill from './polyfill' -import { genTree, getRoute, isMobile, slugify, merge, emojify } from './util' - -let markdown = marked -let toc = [] -const CACHE = {} -const originTitle = document.title - -const renderTo = function (dom, content) { - dom = typeof dom === 'object' ? dom : document.querySelector(dom) - dom.innerHTML = content - slugify.clear() - - return dom -} - -/** - * init render - */ -export function init () { - const renderer = new marked.Renderer() - /** - * 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 `${text}` - } - - if (typeof $docsify.markdown === 'function') { - markdown = $docsify.markdown.call(this, markdown, renderer) - } else { - if ($docsify.markdown && $docsify.markdown.renderer) { - $docsify.markdown.renderer = merge(renderer, $docsify.markdown.renderer) - } - markdown.setOptions(merge({ renderer }, $docsify.markdown)) - } - - const md = markdown - - markdown = text => emojify(md(text)) - - window.Docsify.utils.marked = text => { - const result = markdown(text) - toc = [] - return result - } -} - -/** - * App - */ -export function renderApp (dom, replace) { - const nav = document.querySelector('nav') || document.createElement('nav') - const body = document.body - const head = document.head - - if (!$docsify.repo) nav.classList.add('no-badge') - - dom[replace ? 'outerHTML' : 'innerHTML'] = tpl.corner($docsify.repo) + - ($docsify.coverpage ? tpl.cover() : '') + - tpl.main() - body.insertBefore(nav, body.children[0]) - - // theme color - if ($docsify.themeColor) { - head.innerHTML += tpl.theme($docsify.themeColor) - polyfill.cssVars() - } - - // render name - if ($docsify.name) { - const aside = document.querySelector('.sidebar') - aside.innerHTML = `

          ${$docsify.name}

          ` + aside.innerHTML - } - - // bind toggle - event.bindToggle('button.sidebar-toggle') - // bind sticky effect - if ($docsify.coverpage) { - !isMobile() && window.addEventListener('scroll', event.sticky) - } else { - body.classList.add('sticky') - } -} - -/** - * article - */ -export function renderArticle (content) { - const hook = window.Docsify.hook - const renderFn = function (data) { - renderTo('article', data) - if (!$docsify.loadSidebar) renderSidebar() - - if (data && typeof Vue !== 'undefined') { - CACHE.vm && CACHE.vm.$destroy() - - const script = [].slice.call( - document.body.querySelectorAll('article>script')) - .filter(script => !/template/.test(script.type) - )[0] - const code = script ? script.innerText.trim() : null - - script && script.remove() - CACHE.vm = code - ? new Function(`return ${code}`)() - : new Vue({ el: 'main' }) // eslint-disable-line - CACHE.vm && CACHE.vm.$nextTick(_ => event.scrollActiveSidebar()) - } - if ($docsify.auto2top) setTimeout(() => event.scroll2Top($docsify.auto2top), 0) - } - - hook.emit('before', content, result => { - const html = result ? markdown(result) : '' - - hook.emit('after', html, result => renderFn(result || 'not found')) - }) -} - -/** - * navbar - */ -export function renderNavbar (content) { - if (CACHE.navbar && CACHE.navbar === content) return - CACHE.navbar = content - - if (content) renderTo('nav', markdown(content)) - event.activeLink('nav') -} - -/** - * sidebar - */ -export function renderSidebar (content) { - let html - - if (content) { - html = markdown(content) - // find url tag - html = html.match(/]*>([\s\S]+)<\/ul>/g)[0] - } else { - html = tpl.tree(genTree(toc, $docsify.maxLevel), '
            ') - } - - renderTo('.sidebar-nav', html) - - if (toc[0] && toc[0].level === 1) { - document.title = `${toc[0].title}${originTitle ? ' - ' + originTitle : ''}` - } - - const target = event.activeLink('.sidebar-nav', true) - if (target) renderSubSidebar(target) - - toc = [] - event.scrollActiveSidebar() -} - -export function renderSubSidebar (target) { - if (!$docsify.subMaxLevel) return - toc[0] && toc[0].level === 1 && toc.shift() - target.parentNode.innerHTML += tpl.tree(genTree(toc, $docsify.subMaxLevel), '
              ') -} - -/** - * Cover Page - */ -export function renderCover (content) { - renderCover.dom = renderCover.dom || document.querySelector('section.cover') - if (!content) { - renderCover.dom.classList.remove('show') - return - } - renderCover.dom.classList.add('show') - if (renderCover.rendered) return event.sticky() - - // render cover - const cacheToc = toc.slice() - let html = markdown(content) - const match = html.trim().match('

              ([^<]*?)

              $') - - toc = cacheToc.slice() - - // render background - if (match) { - const coverEl = document.querySelector('section.cover') - - if (match[2] === 'color') { - coverEl.style.background = match[1] + (match[3] || '') - } else { - coverEl.classList.add('has-mask') - coverEl.style.backgroundImage = `url(${match[1]})` - } - html = html.replace(match[0], '') - } - - renderTo('.cover-main', html) - renderCover.rendered = true - - event.sticky() -} diff --git a/src/themes/vue.css b/src/themes/vue.css index 70ed86c3a..4c3d03f76 100644 --- a/src/themes/vue.css +++ b/src/themes/vue.css @@ -46,6 +46,15 @@ body { } } +.app-sub-sidebar { + .section-link { + &::before { + content: '-'; + padding-right: 4px; + } + } +} + /* markdown content found on pages */ .markdown-section h1, .markdown-section h2, From d3158249bda46403b1bcad2d0cc738716e3b0e8d Mon Sep 17 00:00:00 2001 From: "qingwei.li" Date: Sat, 18 Feb 2017 19:36:28 +0800 Subject: [PATCH 09/25] docs: update --- docs/_sidebar.md | 2 +- docs/cover.md | 2 +- docs/vue.md | 4 ++-- docs/zh-cn/_sidebar.md | 2 +- docs/zh-cn/vue.md | 4 ++-- src/core/render/index.js | 6 +++--- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/_sidebar.md b/docs/_sidebar.md index 76261f0de..37534268e 100644 --- a/docs/_sidebar.md +++ b/docs/_sidebar.md @@ -4,7 +4,7 @@ - [Custom navbar](/custom-navbar) - [Cover page](/cover) -- Configuration +- Customization - [Configuration](/configuration) - [Themes](/themes) - [Using plugins](/plugins) diff --git a/docs/cover.md b/docs/cover.md index 7054a06b2..2a538e75d 100644 --- a/docs/cover.md +++ b/docs/cover.md @@ -1,6 +1,6 @@ # Cover -Activate the cover feature by setting `coverpage`. The detail in [Configuration#coverpage](zh-cn/configuration#coverpage). +Activate the cover feature by setting `coverpage`. The detail in [Configuration#coverpage](configuration#coverpage). ## Basic usage diff --git a/docs/vue.md b/docs/vue.md index 40b98fd47..27c420412 100644 --- a/docs/vue.md +++ b/docs/vue.md @@ -13,7 +13,7 @@ Load the Vue in `index.html`. ``` Then you can immediately write Vue code at Markdown file. -`new Vue({ el: 'main' })` script is executed by default to create instance. +`new Vue({ el: '#main' })` script is executed by default to create instance. *README.md* @@ -44,7 +44,7 @@ You can manually initialize a Vue instance. diff --git a/docs/zh-cn/_sidebar.md b/docs/zh-cn/_sidebar.md index e911c388c..952a6fdc6 100644 --- a/docs/zh-cn/_sidebar.md +++ b/docs/zh-cn/_sidebar.md @@ -4,7 +4,7 @@ - [定制导航栏](zh-cn/custom-navbar) - [封面](zh-cn/cover) -- 配置 +- 定制化 - [配置项](zh-cn/configuration) - [主题](zh-cn/themes) - [使用插件](zh-cn/plugins) diff --git a/docs/zh-cn/vue.md b/docs/zh-cn/vue.md index 37dd8079b..3c811c427 100644 --- a/docs/zh-cn/vue.md +++ b/docs/zh-cn/vue.md @@ -12,7 +12,7 @@ ``` -接着就可以愉快地在 Markdown 里写 Vue 了。默认会执行 `new Vue({ el: 'main' })` 创建示例。 +接着就可以愉快地在 Markdown 里写 Vue 了。默认会执行 `new Vue({ el: '#main' })` 创建示例。 *README.md* @@ -43,7 +43,7 @@ diff --git a/src/core/render/index.js b/src/core/render/index.js index 5fcaf1d4f..4a744bd63 100644 --- a/src/core/render/index.js +++ b/src/core/render/index.js @@ -28,9 +28,9 @@ function renderMain (html) { // execute script this.config.executeScript && executeScript() - if (!this.config.executeScript - && typeof window.Vue !== 'undefined' - && !executeScript()) { + if (!this.config.executeScript && + typeof window.Vue !== 'undefined' && + !executeScript()) { window.__EXECUTE_RESULT__ = new window.Vue().$mount('#main') } From 4b1494d2f593ac880d74fbddec1b1a041294ca4c Mon Sep 17 00:00:00 2001 From: "qingwei.li" Date: Sat, 18 Feb 2017 21:59:19 +0800 Subject: [PATCH 10/25] feat(render): add auto header --- src/core/config.js | 1 + src/core/event/sidebar.js | 44 +++++++++++++++++++++++-------------- src/core/render/compiler.js | 5 +---- src/core/render/index.js | 16 +++++++++++--- src/core/route/hash.js | 4 ++-- src/core/util/dom.js | 6 ++++- 6 files changed, 50 insertions(+), 26 deletions(-) diff --git a/src/core/config.js b/src/core/config.js index a1bf9bb8b..230348a2b 100644 --- a/src/core/config.js +++ b/src/core/config.js @@ -14,6 +14,7 @@ const config = merge({ name: '', themeColor: '', nameLink: window.location.pathname, + autoHeader: false, ga: '' }, window.$docsify) diff --git a/src/core/event/sidebar.js b/src/core/event/sidebar.js index aa4475b4a..9bb1d07e5 100644 --- a/src/core/event/sidebar.js +++ b/src/core/event/sidebar.js @@ -1,43 +1,51 @@ import { isMobile } from '../util/env' -import { getNode, on, body, findAll, toggleClass } from '../util/dom' +import * as dom from '../util/dom' import { getHash } from '../route/hash' +const title = dom.$.title /** * Toggle button */ export function btn (el) { - const toggle = () => body.classList.toggle('close') + const toggle = () => dom.body.classList.toggle('close') - el = getNode(el) - on(el, 'click', toggle) + el = dom.getNode(el) + dom.on(el, 'click', toggle) if (isMobile) { - const sidebar = getNode('.sidebar') + const sidebar = dom.getNode('.sidebar') - on(sidebar, 'click', () => { + dom.on(sidebar, 'click', () => { toggle() - setTimeout(() => getAndActive(true), 0) + setTimeout(() => getAndActive(sidebar, true, true), 0) }) } } export function sticky () { - const cover = getNode('section.cover') + const cover = dom.getNode('section.cover') if (!cover) return const coverHeight = cover.getBoundingClientRect().height if (window.pageYOffset >= coverHeight || cover.classList.contains('hidden')) { - toggleClass(body, 'add', 'sticky') + dom.toggleClass(dom.body, 'add', 'sticky') } else { - toggleClass(body, 'remove', 'sticky') + dom.toggleClass(dom.body, 'remove', 'sticky') } } -export function getAndActive (el, isParent) { - const dom = getNode(el) - const links = findAll(dom, 'a') - const hash = '#' + getHash() +/** + * Get and active link + * @param {string|element} el + * @param {Boolean} isParent acitve parent + * @param {Boolean} autoTitle auto set title + * @return {element} + */ +export function getAndActive (el, isParent, autoTitle) { + el = dom.getNode(el) + const links = dom.findAll(el, 'a') + const hash = '#' + getHash() let target links @@ -48,11 +56,15 @@ export function getAndActive (el, isParent) { if (hash.indexOf(href) === 0 && !target) { target = a - toggleClass(node, 'add', 'active') + dom.toggleClass(node, 'add', 'active') } else { - toggleClass(node, 'remove', 'active') + dom.toggleClass(node, 'remove', 'active') } }) + if (autoTitle) { + dom.$.title = target ? `${target.innerText} - ${title}` : title + } + return target } diff --git a/src/core/render/compiler.js b/src/core/render/compiler.js index c4abe6a12..92d27ba07 100644 --- a/src/core/render/compiler.js +++ b/src/core/render/compiler.js @@ -67,10 +67,7 @@ renderer.code = function (code, lang = '') { return `
              ${hl}
              ` } renderer.link = function (href, title, text) { - if (!/:|(\/{2})/.test(href)) { - href = toURL(href) - } - return `${text}` + return `${text}` } renderer.paragraph = function (text) { if (/^!>/.test(text)) { diff --git a/src/core/render/index.js b/src/core/render/index.js index 4a744bd63..746fdf70d 100644 --- a/src/core/render/index.js +++ b/src/core/render/index.js @@ -46,12 +46,22 @@ export function renderMixin (proto) { } proto._renderSidebar = function (text) { - const { maxLevel, subMaxLevel } = this.config + const { maxLevel, subMaxLevel, autoHeader } = this.config this._renderTo('.sidebar-nav', sidebar(text, maxLevel)) - subSidebar(getAndActive('.sidebar-nav', true), subMaxLevel) + const active = getAndActive('.sidebar-nav', true, true) + subSidebar(active, subMaxLevel) // bind event scrollActiveSidebar() + + if (autoHeader && active) { + const main = dom.getNode('#main') + if (main.children[0].tagName !== 'H1') { + const h1 = dom.create('h1') + h1.innerText = active.innerText + dom.before(main, h1) + } + } } proto._renderNav = function (text) { @@ -128,7 +138,7 @@ export function initRender (vm) { // Render main app vm._renderTo(el, html, true) // Add nav - dom.body.insertBefore(navEl, dom.body.children[0]) + dom.before(dom.body, navEl) if (config.themeColor) { dom.$.head += tpl.theme(config.themeColor) diff --git a/src/core/route/hash.js b/src/core/route/hash.js index 0ca788009..60e0164f4 100644 --- a/src/core/route/hash.js +++ b/src/core/route/hash.js @@ -1,5 +1,5 @@ import { merge, cached } from '../util/core' -import { parseQuery, stringifyQuery } from './util' +import { parseQuery, stringifyQuery, cleanPath } from './util' function replaceHash (path) { const i = window.location.href.indexOf('#') @@ -70,5 +70,5 @@ export function toURL (path, params) { route.query = merge({}, route.query, params) path = route.path + stringifyQuery(route.query) - return '#' + path + return cleanPath('#/' + path) } diff --git a/src/core/util/dom.js b/src/core/util/dom.js index 06016a571..bbd3601ac 100644 --- a/src/core/util/dom.js +++ b/src/core/util/dom.js @@ -52,6 +52,10 @@ export function appendTo (target, el) { return target.appendChild(el) } +export function before (target, el) { + return target.insertBefore(el, target.children[0]) +} + export function on (el, type, handler) { isFn(type) ? window.addEventListener(el, type) @@ -72,5 +76,5 @@ export const off = function on (el, type, handler) { * toggleClass(el, 'add', 'active') => el.classList.add('active') */ export function toggleClass (el, type, val) { - el.classList[val ? type : 'toggle'](val || type) + el && el.classList[val ? type : 'toggle'](val || type) } From 6b80b2acdf81ec3c2f09dea3875efdc809fcb38b Mon Sep 17 00:00:00 2001 From: "qingwei.li" Date: Sat, 18 Feb 2017 22:12:17 +0800 Subject: [PATCH 11/25] docs: add autoHeader --- docs/configuration.md | 13 +++++++++++++ docs/zh-cn/configuration.md | 14 +++++++++++++- docs/zh-cn/quickstart.md | 1 - 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 5af251500..487df25e2 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -254,3 +254,16 @@ window.$docsify = { } } ``` + +## auto-header + +- type: `Boolean` + +If `loadSidebar` and `autoHeader` are both enabled, for each link in _sidebar.md, prepend a header to the page before converting it to html. [#78](https://github.com/QingWei-Li/docsify/issues/78) + +```js +window.$docsify = { + loadSidebar: true, + autoHeader: true +} +``` diff --git a/docs/zh-cn/configuration.md b/docs/zh-cn/configuration.md index 23c96d49c..8ab815735 100644 --- a/docs/zh-cn/configuration.md +++ b/docs/zh-cn/configuration.md @@ -229,7 +229,7 @@ window.$docsify = { - 类型:`String` -替换主题色。利用 [CSS3 支持变量]((https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_variables)的特性,对于老的浏览器有 polyfill 处理。 +替换主题色。利用 [CSS3 支持变量](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_variables)的特性,对于老的浏览器有 polyfill 处理。 ```js window.$docsify = { @@ -253,3 +253,15 @@ window.$docsify = { } ``` +## auto-header + +- 类型:`Boolean` + +同时设置 `loadSidebar` 和 `autoHeader` 后,可以根据 `_sidebar.md` 的内容自动为每个页面增加标题。[#78](https://github.com/QingWei-Li/docsify/issues/78) + +```js +window.$docsify = { + loadSidebar: true, + autoHeader: true +} +``` diff --git a/docs/zh-cn/quickstart.md b/docs/zh-cn/quickstart.md index c4ebc2821..8ae891f2f 100644 --- a/docs/zh-cn/quickstart.md +++ b/docs/zh-cn/quickstart.md @@ -1,4 +1,3 @@ -# 快速开始 推荐安装 `docsify-cli` 工具,可以方便创建及本地预览文档网站。 From 925b64f9d4e9d9243cfa3e4e8bfaa814783c0b8c Mon Sep 17 00:00:00 2001 From: "qingwei.li" Date: Sat, 18 Feb 2017 22:13:03 +0800 Subject: [PATCH 12/25] fix(compiler): link --- src/core/render/compiler.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/core/render/compiler.js b/src/core/render/compiler.js index 92d27ba07..70e899115 100644 --- a/src/core/render/compiler.js +++ b/src/core/render/compiler.js @@ -67,7 +67,13 @@ renderer.code = function (code, lang = '') { return `
              ${hl}
              ` } renderer.link = function (href, title, text) { - return `${text}` + let blank = '' + if (!/:|(\/{2})/.test(href)) { + href = toURL(href) + } else { + blank = ' target="_blank"' + } + return `${text}` } renderer.paragraph = function (text) { if (/^!>/.test(text)) { From 145d01264c59d2c06b65e8ebcbc926f179080006 Mon Sep 17 00:00:00 2001 From: "qingwei.li" Date: Sat, 18 Feb 2017 22:13:45 +0800 Subject: [PATCH 13/25] docs: move changelog to history --- CHANGELOG.md | 97 -------------------------------------------------- HISTORY.md | 99 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 99 insertions(+), 97 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 76f6013bf..e69de29bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,97 +0,0 @@ -### 2.4.3 - -> 2017-02-15 - -#### Bug fixes -* fix emoji replacing error (#76) - -### 2.4.2 - -> 2017-02-14 - -#### Bug fixes -- fix(index): load file path error - - -### 2.4.1 - -> 2017-02-13 - -#### Bug fixes -- fix(index): cover page - -### 2.4.0 - -> 2017-02-13 - -#### Features - -- feat(hook): add `doneEach` - - -### 2.3.0 - -> 2017-02-13 - -#### Features - -- feat(src): add alias feature -- docs: update all documents -- feat(src): dynamic title -- feat(hook): support custom plugin -- feat(themes): add dark theme - -#### Bug fixes -- fix(event): `auto2top` has no effect on a FF mobile browser, fixed #67 -- fix: sidebar style -- fix(render): fix render link - -### 2.2.1 - -> 2017-02-11 - -#### Bug fixes -- fix(search): crash when not content, fixed #68 -- fix(event): scroll active sidebar -- fix(search): not work in mobile - -### 2.2.0 - -#### Features -- Add `Google Analytics` plugin. -```html - - -``` - -### 2.1.0 -#### Features -- Add search plugin -```html - - -``` - -#### Bug fixes -- fix sidebar style - -### 2.0.3 -#### Bug fixes -- fix: rendering emojis -- fix: css var polyfill - -### 2.0.2 - -#### Bug fixes -- fix button style in cover page. - -### 2.0.1 -#### Bug fixes -- border style. - -### 2.0.0 -#### Features -- Customize the theme color - -#### Break change -- Remove `data-router`, `data-sidebar`, `data-sidebar-toggle` APIs diff --git a/HISTORY.md b/HISTORY.md index d3cc3e3ce..6c9358eee 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,3 +1,102 @@ +### 2.4.3 + +> 2017-02-15 + +#### Bug fixes +* fix emoji replacing error (#76) + +### 2.4.2 + +> 2017-02-14 + +#### Bug fixes +- fix(index): load file path error + + +### 2.4.1 + +> 2017-02-13 + +#### Bug fixes +- fix(index): cover page + +### 2.4.0 + +> 2017-02-13 + +#### Features + +- feat(hook): add `doneEach` + + +### 2.3.0 + +> 2017-02-13 + +#### Features + +- feat(src): add alias feature +- docs: update all documents +- feat(src): dynamic title +- feat(hook): support custom plugin +- feat(themes): add dark theme + +#### Bug fixes +- fix(event): `auto2top` has no effect on a FF mobile browser, fixed #67 +- fix: sidebar style +- fix(render): fix render link + +### 2.2.1 + +> 2017-02-11 + +#### Bug fixes +- fix(search): crash when not content, fixed #68 +- fix(event): scroll active sidebar +- fix(search): not work in mobile + +### 2.2.0 + +#### Features +- Add `Google Analytics` plugin. +```html + + +``` + +### 2.1.0 +#### Features +- Add search plugin +```html + + +``` + +#### Bug fixes +- fix sidebar style + +### 2.0.3 +#### Bug fixes +- fix: rendering emojis +- fix: css var polyfill + +### 2.0.2 + +#### Bug fixes +- fix button style in cover page. + +### 2.0.1 +#### Bug fixes +- border style. + +### 2.0.0 +#### Features +- Customize the theme color + +#### Break change +- Remove `data-router`, `data-sidebar`, `data-sidebar-toggle` APIs + + ### 1.10.5 #### Bug fixes - fix initialize the Vue instance From 35deb9c210fa1ceecba49a24c73686123491c6c8 Mon Sep 17 00:00:00 2001 From: "qingwei.li" Date: Sat, 18 Feb 2017 23:04:52 +0800 Subject: [PATCH 14/25] docs: add executeScript demo --- docs/configuration.md | 22 ++++++++++++++++++++ docs/themes.md | 34 +++++++++++++++++++++++++++++++ docs/zh-cn/configuration.md | 22 ++++++++++++++++++++ docs/zh-cn/themes.md | 40 ++++++++++++++++++++++++++++++++++++- src/core/config.js | 1 + src/core/render/index.js | 10 +++++++--- 6 files changed, 125 insertions(+), 4 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 487df25e2..4b05476e6 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -267,3 +267,25 @@ window.$docsify = { autoHeader: true } ``` + +## execute-script + +- type: `Boolean` + +Execute the script on the page. Only parse the first script tag([demo](themes)). If Vue is present, it is turned on by default. + +```js +window.$docsify = { + executeScript: true +} +``` + +```markdown +## This is test + + + +``` + diff --git a/docs/themes.md b/docs/themes.md index 67b9a5d99..ed5ae40a9 100644 --- a/docs/themes.md +++ b/docs/themes.md @@ -12,3 +12,37 @@ There are currently three themes available. Copy [Vue](//vuejs.org) and [buble]( !> This compressed files in `/lib/themes/`. If you have any ideas or would like to develop new theme, welcome submit [PR](https://github.com/QingWei-Li/docsify/pulls). + +#### Click to preview + + + + + + + + diff --git a/docs/zh-cn/configuration.md b/docs/zh-cn/configuration.md index 8ab815735..685de3c1c 100644 --- a/docs/zh-cn/configuration.md +++ b/docs/zh-cn/configuration.md @@ -265,3 +265,25 @@ window.$docsify = { autoHeader: true } ``` + +## execute-script + +- 类型:`Boolean` + +执行文档里的 script 标签里的脚本,只执行第一个 script ([demo](zh-cn/themes))。 如果 Vue 存在,则自动开启。 + +```js +window.$docsify = { + executeScript: true +} +``` + +```markdown +## This is test + + + +``` + diff --git a/docs/zh-cn/themes.md b/docs/zh-cn/themes.md index b728d7ab8..28596e2ad 100644 --- a/docs/zh-cn/themes.md +++ b/docs/zh-cn/themes.md @@ -8,6 +8,44 @@ ``` +vue.css +buble.css +dark.css + !> CSS 的压缩文件位于 `/lib/themes/` -如果你有其他想法或者想开发别的主题,欢迎提 [PR](https://github.com/QingWei-Li/docsify/pulls)。 \ No newline at end of file +如果你有其他想法或者想开发别的主题,欢迎提 [PR](https://github.com/QingWei-Li/docsify/pulls)。 + +#### 点击切换主题 + + + + + + + + diff --git a/src/core/config.js b/src/core/config.js index 230348a2b..f218e6dec 100644 --- a/src/core/config.js +++ b/src/core/config.js @@ -15,6 +15,7 @@ const config = merge({ themeColor: '', nameLink: window.location.pathname, autoHeader: false, + executeScript: false, ga: '' }, window.$docsify) diff --git a/src/core/render/index.js b/src/core/render/index.js index 746fdf70d..370a5701d 100644 --- a/src/core/render/index.js +++ b/src/core/render/index.js @@ -14,7 +14,9 @@ function executeScript () { const code = script.innerText.trim() if (!code) return false - window.__EXECUTE_RESULT__ = new Function('return ' + code)() + setTimeout(_ => { + window.__EXECUTE_RESULT__ = new Function(code)() + }, 0) } function renderMain (html) { @@ -31,11 +33,13 @@ function renderMain (html) { if (!this.config.executeScript && typeof window.Vue !== 'undefined' && !executeScript()) { - window.__EXECUTE_RESULT__ = new window.Vue().$mount('#main') + setTimeout(_ => { + window.__EXECUTE_RESULT__ = new window.Vue().$mount('#main') + }, 0) } if (this.config.auto2top) { - setTimeout(() => scroll2Top(this.config.auto2top), 0) + scroll2Top(this.config.auto2top) } } From ee77debf336a9b0f183e6c7d075489ee3dacb7db Mon Sep 17 00:00:00 2001 From: "qingwei.li" Date: Sun, 19 Feb 2017 00:10:38 +0800 Subject: [PATCH 15/25] fix(render): support html file --- src/core/event/scroll.js | 2 +- src/core/fetch/index.js | 7 ++++++- src/core/render/compiler.js | 8 ++++++-- src/core/render/index.js | 20 ++++++++++++++------ src/core/render/tpl.js | 2 +- src/core/route/index.js | 2 +- src/core/route/util.js | 12 ++++++------ 7 files changed, 35 insertions(+), 18 deletions(-) diff --git a/src/core/event/scroll.js b/src/core/event/scroll.js index 5cfdb1171..6b4eab7f4 100644 --- a/src/core/event/scroll.js +++ b/src/core/event/scroll.js @@ -9,7 +9,6 @@ export function scrollActiveSidebar () { const anchors = dom.findAll('.anchor') const sidebar = dom.find('.sidebar') const wrap = dom.find(sidebar, '.sidebar-nav') - const height = sidebar.clientHeight const nav = {} const lis = dom.findAll(sidebar, 'li') @@ -54,6 +53,7 @@ export function scrollActiveSidebar () { // scroll into view // https://github.com/vuejs/vuejs.org/blob/master/themes/vue/source/js/common.js#L282-L297 if (!hoverOver && dom.body.classList.contains('sticky')) { + const height = sidebar.clientHeight const curOffset = 0 const cur = active.offsetTop + active.clientHeight + 40 const isInView = ( diff --git a/src/core/fetch/index.js b/src/core/fetch/index.js index e3f4c0d77..84654490a 100644 --- a/src/core/fetch/index.js +++ b/src/core/fetch/index.js @@ -15,6 +15,9 @@ export function fetchMixin (proto) { last = get(this.$getFile(path), true) + // Current page is html + this.isHTML = /\.html$/g.test(path) + // Load main content last.then(text => { this._renderMain(text) @@ -42,13 +45,15 @@ export function fetchMixin (proto) { proto._fetchCover = function () { const { coverpage } = this.config const root = getRoot(this.route.path) + const path = this.$getFile(root + coverpage) if (this.route.path !== '/' || !coverpage) { this._renderCover() return } - get(this.$getFile(root + coverpage)) + this.coverIsHTML = /\.html$/g.test(path) + get(path) .then(text => this._renderCover(text)) } diff --git a/src/core/render/compiler.js b/src/core/render/compiler.js index 70e899115..d369c1d1e 100644 --- a/src/core/render/compiler.js +++ b/src/core/render/compiler.js @@ -5,7 +5,7 @@ import { genTree } from './gen-tree' import { slugify, clearSlugCache } from './slugify' import { emojify } from './emojify' import { toURL, parse } from '../route/hash' -import { getBasePath, getPath } from '../route/util' +import { getBasePath, isResolvePath, getPath } from '../route/util' import { isFn, merge, cached } from '../util/core' let markdownCompiler = marked @@ -84,9 +84,13 @@ renderer.paragraph = function (text) { return `

              ${text}

              ` } renderer.image = function (href, title, text) { - const url = getPath(contentBase, href) + let url = href const titleHTML = title ? ` title="${title}"` : '' + if (!isResolvePath(href)) { + url = getPath(contentBase, href) + } + return `${text}` } diff --git a/src/core/render/index.js b/src/core/render/index.js index 370a5701d..c8df33749 100644 --- a/src/core/render/index.js +++ b/src/core/render/index.js @@ -5,7 +5,7 @@ import cssVars from '../util/polyfill/css-vars' import * as tpl from './tpl' import { markdown, sidebar, subSidebar, cover } from './compiler' import { callHook } from '../init/lifecycle' -import { getBasePath, getPath } from '../route/util' +import { getBasePath, getPath, isResolvePath } from '../route/util' function executeScript () { const script = dom.findAll('.markdown-section>script') @@ -22,6 +22,7 @@ function executeScript () { function renderMain (html) { if (!html) { // TODO: Custom 404 page + html = 'not found' } this._renderTo('.markdown-section', html) @@ -56,11 +57,13 @@ export function renderMixin (proto) { const active = getAndActive('.sidebar-nav', true, true) subSidebar(active, subMaxLevel) // bind event + this.activeLink = active scrollActiveSidebar() if (autoHeader && active) { const main = dom.getNode('#main') - if (main.children[0].tagName !== 'H1') { + const firstNode = main.children[0] + if (firstNode && firstNode.tagName !== 'H1') { const h1 = dom.create('h1') h1.innerText = active.innerText dom.before(main, h1) @@ -75,7 +78,7 @@ export function renderMixin (proto) { proto._renderMain = function (text) { callHook(this, 'beforeEach', text, result => { - const html = markdown(result) + const html = this.isHTML ? result : markdown(result) callHook(this, 'afterEach', html, text => renderMain.call(this, text)) }) } @@ -88,15 +91,20 @@ export function renderMixin (proto) { } dom.toggleClass(el, 'add', 'show') - let html = cover(text) + let html = this.coverIsHTML ? text : cover(text) const m = html.trim().match('

              ([^<]*?)

              $') if (m) { if (m[2] === 'color') { el.style.background = m[1] + (m[3] || '') } else { + let path = m[1] + dom.toggleClass(el, 'add', 'has-mask') - el.style.backgroundImage = `url(${getPath(getBasePath(this.config.basePath), m[1])})` + if (isResolvePath(m[1])) { + path = getPath(getBasePath(this.config.basePath), m[1]) + } + el.style.backgroundImage = `url(${path})` } html = html.replace(m[0], '') } @@ -145,7 +153,7 @@ export function initRender (vm) { dom.before(dom.body, navEl) if (config.themeColor) { - dom.$.head += tpl.theme(config.themeColor) + dom.$.head.innerHTML += tpl.theme(config.themeColor) // Polyfll cssVars(config.themeColor) } diff --git a/src/core/render/tpl.js b/src/core/render/tpl.js index 0bd5198c7..ce9cabb44 100644 --- a/src/core/render/tpl.js +++ b/src/core/render/tpl.js @@ -10,7 +10,7 @@ export function corner (data) { data = data.replace(/^git\+/, '') return ( - '' + + `` + '
              Please wait...
              +``` + +You should set the `data-app` attribute if you changed `el`. + +*index.html* +```html +
              Please wait...
              + + +``` diff --git a/docs/zh-cn/plugins.md b/docs/zh-cn/plugins.md index 8145f016e..aeabf8d80 100644 --- a/docs/zh-cn/plugins.md +++ b/docs/zh-cn/plugins.md @@ -61,7 +61,7 @@ docsify 提供了一套插件机制,其中提供的钩子(hook)支持处 ```js window.$docsify = { plugins: [ - function (hook) { + function (hook, vm) { hook.init(function() { // 初始化时调用,只调用一次,没有参数。 }) diff --git a/docs/zh-cn/quickstart.md b/docs/zh-cn/quickstart.md index c4ebc2821..5bdaf2da8 100644 --- a/docs/zh-cn/quickstart.md +++ b/docs/zh-cn/quickstart.md @@ -59,3 +59,26 @@ docsify serve docs ```bash cd docs && python -m SimpleHTTPServer 3000 ``` + +## Loading 提示 + +初始化时会显示 `Loading...` 内容,你可以自定义提示信息。 + +*index.html* +```html +
              加载中
              +``` + +如果更改了 `el` 的配置,需要将该元素加上 `data-app` 属性。 + +*index.html* +```html +
              加载中
              + + +``` + diff --git a/src/themes/basic/_layout.css b/src/themes/basic/_layout.css index d413c10ac..6241ffdb8 100644 --- a/src/themes/basic/_layout.css +++ b/src/themes/basic/_layout.css @@ -7,6 +7,23 @@ -webkit-font-smoothing: antialiased; } +body:not(.ready) { + [data-cloak] { + display: none; + } +} + +div#app { + text-align: center; + font-size: 30px; + font-weight: lighter; + margin: 40vw auto; + + &:empty::before { + content: "Loading..."; + } +} + .emoji { height: 1.2em; vertical-align: middle; From 81cf3db4c94604c0dd558e67d927d0ad851514bb Mon Sep 17 00:00:00 2001 From: "qingwei.li" Date: Sun, 19 Feb 2017 10:47:47 +0800 Subject: [PATCH 18/25] refactor(plugins): update search plugin --- .eslintrc | 4 + build/build.js | 24 +-- docs/index.html | 2 +- docs/plugins.md | 2 +- docs/zh-cn/plugins.md | 2 +- package.json | 2 +- src/core/config.js | 2 + src/core/event/scroll.js | 4 +- src/core/fetch/index.js | 5 +- src/core/global-api.js | 2 +- src/core/index.js | 3 +- src/core/init/index.js | 1 - src/core/render/compiler.js | 4 +- src/core/render/index.js | 5 +- src/core/route/hash.js | 1 + src/core/route/index.js | 7 +- src/core/route/util.js | 6 +- src/plugins/ga.js | 13 +- src/plugins/search.js | 349 -------------------------------- src/plugins/search/component.js | 116 +++++++++++ src/plugins/search/index.js | 31 +++ src/plugins/search/search.js | 156 ++++++++++++++ 22 files changed, 348 insertions(+), 393 deletions(-) delete mode 100644 src/plugins/search.js create mode 100644 src/plugins/search/component.js create mode 100644 src/plugins/search/index.js create mode 100644 src/plugins/search/search.js diff --git a/.eslintrc b/.eslintrc index 86d102d90..d6a85f8e0 100644 --- a/.eslintrc +++ b/.eslintrc @@ -2,5 +2,9 @@ "extends": ["vue"], "env": { "browser": true + }, + "globals": { + "Docsify": true, + "$docsify": true } } diff --git a/build/build.js b/build/build.js index 96adf07f7..ceeb3c335 100644 --- a/build/build.js +++ b/build/build.js @@ -32,26 +32,26 @@ build({ plugins: [commonjs(), nodeResolve()] }) -// build({ -// entry: 'plugins/search.js', -// output: 'plugins/search.js', -// moduleName: 'D.Search' -// }) +build({ + entry: 'plugins/search/index.js', + output: 'plugins/search.js', + moduleName: 'D.Search' +}) -// build({ -// entry: 'plugins/ga.js', -// output: 'plugins/ga.js', -// moduleName: 'D.GA' -// }) +build({ + entry: 'plugins/ga.js', + output: 'plugins/ga.js', + moduleName: 'D.GA' +}) if (isProd) { build({ - entry: 'index.js', + entry: 'core/index.js', output: 'docsify.min.js', plugins: [commonjs(), nodeResolve(), uglify()] }) build({ - entry: 'plugins/search.js', + entry: 'plugins/search/index.js', output: 'plugins/search.min.js', moduleName: 'D.Search', plugins: [uglify()] diff --git a/docs/index.html b/docs/index.html index 7748c821b..2b2f9d340 100644 --- a/docs/index.html +++ b/docs/index.html @@ -10,7 +10,7 @@ -