mirror of
https://github.com/docsifyjs/docsify.git
synced 2025-12-08 19:55:52 +00:00
refactor(core): and markdown compiler
This commit is contained in:
parent
30da0d5d46
commit
fe88c154b0
@ -2,8 +2,5 @@
|
||||
"extends": ["vue"],
|
||||
"env": {
|
||||
"browser": true
|
||||
},
|
||||
"globals": {
|
||||
"$docsify": true
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -23,4 +23,4 @@ initGlobalAPI()
|
||||
/**
|
||||
* Run Docsify
|
||||
*/
|
||||
setTimeout(() => new Docsify(), 0)
|
||||
new Docsify()
|
||||
|
||||
@ -1,26 +1,56 @@
|
||||
import marked from 'marked'
|
||||
import Prism from 'prismjs'
|
||||
import { helper as helperTpl } from './tpl'
|
||||
import { slugify, clearSlugCache } from './slugify'
|
||||
import { emojify } from './emojify'
|
||||
import { toURL } from '../route/hash'
|
||||
import { isFn, merge, cached } from '../util/core'
|
||||
|
||||
export const renderer = new marked.Renderer()
|
||||
|
||||
export function markdown () {
|
||||
|
||||
}
|
||||
let markdownCompiler = marked
|
||||
let contentBase = ''
|
||||
let renderer = new marked.Renderer()
|
||||
|
||||
const toc = []
|
||||
|
||||
/**
|
||||
* Compile markdown content
|
||||
*/
|
||||
export const markdown = cached(text => {
|
||||
let html = ''
|
||||
|
||||
if (!text) return text
|
||||
|
||||
html = markdownCompiler(text)
|
||||
html = emojify(html)
|
||||
clearSlugCache()
|
||||
|
||||
return html
|
||||
})
|
||||
|
||||
markdown.renderer = renderer
|
||||
|
||||
markdown.init = function (config = {}, context = window.location.pathname) {
|
||||
contentBase = context
|
||||
|
||||
if (isFn(config)) {
|
||||
markdownCompiler = config(marked, renderer)
|
||||
} else {
|
||||
renderer = merge(renderer, config.renderer)
|
||||
marked.setOptions(merge(config, { renderer }))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* render anchor tag
|
||||
* @link https://github.com/chjj/marked#overriding-renderer-methods
|
||||
*/
|
||||
renderer.heading = function (text, level) {
|
||||
const slug = slugify(text)
|
||||
let route = ''
|
||||
const url = toURL(contentBase, { id: slug })
|
||||
|
||||
route = `#/${getRoute()}`
|
||||
toc.push({ level, slug: `${route}#${encodeURIComponent(slug)}`, title: text })
|
||||
toc.push({ level, slug: url, title: text })
|
||||
|
||||
return `<h${level} id="${slug}"><a href="${route}#${slug}" data-id="${slug}" class="anchor"><span>${text}</span></a></h${level}>`
|
||||
return `<h${level} id="${slug}"><a href="${url}" data-id="${slug}" class="anchor"><span>${text}</span></a></h${level}>`
|
||||
}
|
||||
// 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 `<a href="${href}" title="${title || ''}">${text}</a>`
|
||||
}
|
||||
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 `<p>${text}</p>`
|
||||
}
|
||||
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 `<img src="${url}" alt="${text}"${titleHTML} />`
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile sidebar
|
||||
*/
|
||||
export function sidebar (text) {
|
||||
|
||||
return `<img src="${url}" alt="${text}"${titleHTML} />`
|
||||
}
|
||||
|
||||
6
src/core/render/emojify.js
Normal file
6
src/core/render/emojify.js
Normal file
@ -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, '<img class="emoji" src="https://assets-cdn.github.com/images/icons/emoji/$1.png" alt="$1" />')
|
||||
.replace(/__colon__/g, ':')
|
||||
}
|
||||
27
src/core/render/gen-tree.js
Normal file
27
src/core/render/gen-tree.js
Normal file
@ -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
|
||||
}
|
||||
@ -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')
|
||||
|
||||
|
||||
27
src/core/render/slugify.js
Normal file
27
src/core/render/slugify.js
Normal file
@ -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 = {}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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()
|
||||
})
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { cached } from '../util/core'
|
||||
|
||||
const decode = decodeURIComponent
|
||||
const encode = encodeURIComponent
|
||||
|
||||
export const parseQuery = cached(query => {
|
||||
const res = {}
|
||||
@ -11,35 +12,35 @@ export const parseQuery = cached(query => {
|
||||
return res
|
||||
}
|
||||
|
||||
// Simple parse
|
||||
query.split('&').forEach(function (param) {
|
||||
const parts = param.replace(/\+/g, ' ').split('=')
|
||||
const key = decode(parts.shift())
|
||||
const val = parts.length > 0
|
||||
? decode(parts.join('='))
|
||||
: null
|
||||
|
||||
if (res[key] === undefined) {
|
||||
res[key] = val
|
||||
} else if (Array.isArray(res[key])) {
|
||||
res[key].push(val)
|
||||
} else {
|
||||
res[key] = [res[key], val]
|
||||
}
|
||||
res[parts[0]] = decode(parts[1])
|
||||
})
|
||||
|
||||
return res
|
||||
})
|
||||
|
||||
export function stringifyQuery (obj) {
|
||||
const qs = []
|
||||
|
||||
for (const key in obj) {
|
||||
qs.push(`${encode(key)}=${encode(obj[key])}`)
|
||||
}
|
||||
|
||||
return qs.length ? `?${qs.join('&')}` : ''
|
||||
}
|
||||
|
||||
export const getBasePath = cached(base => {
|
||||
return /^(\/|https?:)/g.test(base)
|
||||
? base
|
||||
: cleanPath(window.location.pathname + '/' + base)
|
||||
})
|
||||
|
||||
export const getRoot = cached(path => {
|
||||
return /\/$/g.test(path) ? path : path.match(/(\S*\/)[^\/]+$/)[1]
|
||||
})
|
||||
|
||||
export function cleanPath (path) {
|
||||
return path.replace(/\/+/g, '/')
|
||||
}
|
||||
|
||||
export function getBasePath (base) {
|
||||
return /^(\/|https?:)/g.test(base)
|
||||
? base
|
||||
: cleanPath(window.location.pathname + '/' + base)
|
||||
}
|
||||
|
||||
export function getCurrentRoot (path) {
|
||||
return /\/$/g.test(path) ? path : path.match(/(\S*\/)[^\/]+$/)[1]
|
||||
}
|
||||
|
||||
166
src/util.js
166
src/util.js
@ -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, '<img class="emoji" src="https://assets-cdn.github.com/images/icons/emoji/$1.png" alt="$1" />')
|
||||
.replace(/__colon__/g, ':')
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user