refactor(embed): async fetch embed files, fixed #387
This commit is contained in:
parent
471e407c73
commit
62ce447fc3
5 changed files with 177 additions and 85 deletions
|
|
@ -47,12 +47,10 @@ export function fetchMixin (proto) {
|
||||||
// Load main content
|
// Load main content
|
||||||
last.then(
|
last.then(
|
||||||
(text, opt) => {
|
(text, opt) => {
|
||||||
this._renderMain(text, opt)
|
this._renderMain(text, opt, loadSideAndNav)
|
||||||
loadSideAndNav()
|
|
||||||
},
|
},
|
||||||
_ => {
|
_ => {
|
||||||
this._renderMain(null)
|
this._renderMain(null, {}, loadSideAndNav)
|
||||||
loadSideAndNav()
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,12 @@ import { genTree } from './gen-tree'
|
||||||
import { slugify } from './slugify'
|
import { slugify } from './slugify'
|
||||||
import { emojify } from './emojify'
|
import { emojify } from './emojify'
|
||||||
import { isAbsolutePath, getPath } from '../router/util'
|
import { isAbsolutePath, getPath } from '../router/util'
|
||||||
import { isFn, merge, cached } from '../util/core'
|
import { isFn, merge, cached, isPrimitive } from '../util/core'
|
||||||
import { get } from '../fetch/ajax'
|
import { get } from '../fetch/ajax'
|
||||||
|
|
||||||
const cachedLinks = {}
|
const cachedLinks = {}
|
||||||
let uid = 0
|
|
||||||
|
|
||||||
function getAndRemoveConfig (str = '') {
|
export function getAndRemoveConfig (str = '') {
|
||||||
const config = {}
|
const config = {}
|
||||||
|
|
||||||
if (str) {
|
if (str) {
|
||||||
|
|
@ -25,62 +24,37 @@ function getAndRemoveConfig (str = '') {
|
||||||
|
|
||||||
return { str, config }
|
return { str, config }
|
||||||
}
|
}
|
||||||
|
|
||||||
const compileMedia = {
|
const compileMedia = {
|
||||||
markdown (url) {
|
markdown (url) {
|
||||||
const id = `docsify-get-${uid++}`
|
return {
|
||||||
|
url
|
||||||
if (!process.env.SSR) {
|
|
||||||
get(url, false).then(text => {
|
|
||||||
document.getElementById(id).innerHTML = this.compile(text)
|
|
||||||
})
|
|
||||||
|
|
||||||
return `<div data-origin="${url}" id=${id}></div>`
|
|
||||||
} else {
|
|
||||||
return `<div data-origin="${url}" id=${uid}></div>
|
|
||||||
<script>
|
|
||||||
var compile = window.__current_docsify_compiler__
|
|
||||||
Docsify.get('${url}', false).then(function(text) {
|
|
||||||
document.getElementById('${uid}').innerHTML = compile(text)
|
|
||||||
})
|
|
||||||
</script>`
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
iframe (url, title) {
|
iframe (url, title) {
|
||||||
return `<iframe src="${url}" ${title || 'width=100% height=400'}></iframe>`
|
return {
|
||||||
|
code: `<iframe src="${url}" ${title || 'width=100% height=400'}></iframe>`
|
||||||
|
}
|
||||||
},
|
},
|
||||||
video (url, title) {
|
video (url, title) {
|
||||||
return `<video src="${url}" ${title || 'controls'}>Not Support</video>`
|
return {
|
||||||
|
code: `<video src="${url}" ${title || 'controls'}>Not Support</video>`
|
||||||
|
}
|
||||||
},
|
},
|
||||||
audio (url, title) {
|
audio (url, title) {
|
||||||
return `<audio src="${url}" ${title || 'controls'}>Not Support</audio>`
|
return {
|
||||||
|
code: `<audio src="${url}" ${title || 'controls'}>Not Support</audio>`
|
||||||
|
}
|
||||||
},
|
},
|
||||||
code (url, title) {
|
code (url, title) {
|
||||||
const id = `docsify-get-${uid++}`
|
let lang = url.match(/\.(\w+)$/)
|
||||||
let ext = url.match(/\.(\w+)$/)
|
|
||||||
|
|
||||||
ext = title || (ext && ext[1])
|
lang = title || (lang && lang[1])
|
||||||
if (ext === 'md') ext = 'markdown'
|
if (lang === 'md') lang = 'markdown'
|
||||||
|
|
||||||
if (!process.env.SSR) {
|
return {
|
||||||
get(url, false).then(text => {
|
url,
|
||||||
document.getElementById(id).innerHTML = this.compile(
|
lang
|
||||||
'```' + ext + '\n' + text.replace(/`/g, '@qm@') + '\n```\n'
|
|
||||||
).replace(/@qm@/g, '`')
|
|
||||||
})
|
|
||||||
|
|
||||||
return `<div data-origin="${url}" id=${id}></div>`
|
|
||||||
} else {
|
|
||||||
return `<div data-origin="${url}" id=${id}></div>
|
|
||||||
<script>
|
|
||||||
setTimeout(() => {
|
|
||||||
var compiler = window.__current_docsify_compiler__
|
|
||||||
Docsify.get('${url}', false).then(function(text) {
|
|
||||||
document.getElementById('${id}').innerHTML = compiler
|
|
||||||
.compile('\`\`\`${ext}\\n' + text.replace(/\`/g, '@qm@') + '\\n\`\`\`\\n')
|
|
||||||
.replace(/@qm@/g, '\`')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
</script>`
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -109,12 +83,18 @@ export class Compiler {
|
||||||
compile = marked
|
compile = marked
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this._marked = compile
|
||||||
this.compile = cached(text => {
|
this.compile = cached(text => {
|
||||||
let html = ''
|
let html = ''
|
||||||
|
|
||||||
if (!text) return text
|
if (!text) return text
|
||||||
|
|
||||||
html = compile(text)
|
if (isPrimitive(text)) {
|
||||||
|
html = compile(text)
|
||||||
|
} else {
|
||||||
|
html = compile.parser(text)
|
||||||
|
}
|
||||||
|
|
||||||
html = config.noEmoji ? html : emojify(html)
|
html = config.noEmoji ? html : emojify(html)
|
||||||
slugify.clear()
|
slugify.clear()
|
||||||
|
|
||||||
|
|
@ -122,7 +102,40 @@ export class Compiler {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
matchNotCompileLink (link) {
|
compileEmbed (href, title) {
|
||||||
|
const { str, config } = getAndRemoveConfig(title)
|
||||||
|
let embed
|
||||||
|
title = str
|
||||||
|
|
||||||
|
if (config.include) {
|
||||||
|
if (!isAbsolutePath(href)) {
|
||||||
|
href = getPath(this.contentBase, href)
|
||||||
|
}
|
||||||
|
|
||||||
|
let media
|
||||||
|
if (config.type && (media = compileMedia[config.type])) {
|
||||||
|
embed = media.call(this, href, title)
|
||||||
|
embed.type = config.type
|
||||||
|
} else {
|
||||||
|
let type = 'code'
|
||||||
|
if (/\.(md|markdown)/.test(href)) {
|
||||||
|
type = 'markdown'
|
||||||
|
} else if (/\.html?/.test(href)) {
|
||||||
|
type = 'iframe'
|
||||||
|
} else if (/\.(mp4|ogg)/.test(href)) {
|
||||||
|
type = 'video'
|
||||||
|
} else if (/\.mp3/.test(href)) {
|
||||||
|
type = 'audio'
|
||||||
|
}
|
||||||
|
embed = compileMedia[type].call(this, href, title)
|
||||||
|
embed.type = type
|
||||||
|
}
|
||||||
|
|
||||||
|
return embed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_matchNotCompileLink (link) {
|
||||||
const links = this.config.noCompileLinks || []
|
const links = this.config.noCompileLinks || []
|
||||||
|
|
||||||
for (var i = 0; i < links.length; i++) {
|
for (var i = 0; i < links.length; i++) {
|
||||||
|
|
@ -182,33 +195,9 @@ export class Compiler {
|
||||||
const { str, config } = getAndRemoveConfig(title)
|
const { str, config } = getAndRemoveConfig(title)
|
||||||
title = str
|
title = str
|
||||||
|
|
||||||
if (config.include) {
|
|
||||||
if (!isAbsolutePath(href)) {
|
|
||||||
href = getPath(contentBase, href)
|
|
||||||
}
|
|
||||||
|
|
||||||
let media
|
|
||||||
if (config.type && (media = compileMedia[config.type])) {
|
|
||||||
return media.call(_self, href, title)
|
|
||||||
}
|
|
||||||
|
|
||||||
let type = 'code'
|
|
||||||
if (/\.(md|markdown)/.test(href)) {
|
|
||||||
type = 'markdown'
|
|
||||||
} else if (/\.html?/.test(href)) {
|
|
||||||
type = 'iframe'
|
|
||||||
} else if (/\.(mp4|ogg)/.test(href)) {
|
|
||||||
type = 'video'
|
|
||||||
} else if (/\.mp3/.test(href)) {
|
|
||||||
type = 'audio'
|
|
||||||
}
|
|
||||||
|
|
||||||
return compileMedia[type].call(_self, href, title)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!/:|(\/{2})/.test(href) &&
|
!/:|(\/{2})/.test(href) &&
|
||||||
!_self.matchNotCompileLink(href) &&
|
!_self._matchNotCompileLink(href) &&
|
||||||
!config.ignore
|
!config.ignore
|
||||||
) {
|
) {
|
||||||
if (href === _self.config.homepage) href = 'README'
|
if (href === _self.config.homepage) href = 'README'
|
||||||
|
|
|
||||||
84
src/core/render/embed.js
Normal file
84
src/core/render/embed.js
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
import { get } from '../fetch/ajax'
|
||||||
|
import { merge } from '../util/core'
|
||||||
|
|
||||||
|
const cached = {}
|
||||||
|
|
||||||
|
function walkFetchEmbed ({ step = 0, embedTokens, compile }, cb) {
|
||||||
|
const token = embedTokens[step]
|
||||||
|
|
||||||
|
if (!token) {
|
||||||
|
return cb({})
|
||||||
|
}
|
||||||
|
|
||||||
|
get(token.embed.url).then(text => {
|
||||||
|
let embedToken
|
||||||
|
|
||||||
|
if (token.embed.type === 'markdown') {
|
||||||
|
embedToken = compile.lexer(text)
|
||||||
|
} else if (token.embed.type === 'code') {
|
||||||
|
embedToken = compile.lexer(
|
||||||
|
'```' +
|
||||||
|
token.embed.lang +
|
||||||
|
'\n' +
|
||||||
|
text.replace(/`/g, '@DOCSIFY_QM@') +
|
||||||
|
'\n```\n'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
cb({ token, embedToken })
|
||||||
|
walkFetchEmbed({ step: ++step, compile, embedTokens }, cb)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function prerenderEmbed ({ compiler, raw }, done) {
|
||||||
|
let hit
|
||||||
|
if ((hit = cached[raw])) {
|
||||||
|
return done(hit)
|
||||||
|
}
|
||||||
|
|
||||||
|
const compile = compiler._marked
|
||||||
|
let tokens = compile.lexer(raw)
|
||||||
|
const embedTokens = []
|
||||||
|
const linkRE = compile.InlineLexer.rules.link
|
||||||
|
const links = tokens.links
|
||||||
|
|
||||||
|
tokens.forEach((token, index) => {
|
||||||
|
if (token.type === 'paragraph') {
|
||||||
|
token.text = token.text.replace(
|
||||||
|
new RegExp(linkRE, 'g'),
|
||||||
|
(src, filename, href, title) => {
|
||||||
|
const embed = compiler.compileEmbed(href, title)
|
||||||
|
|
||||||
|
if (embed) {
|
||||||
|
if (embed.type === 'markdown' || embed.type === 'code') {
|
||||||
|
embedTokens.push({
|
||||||
|
index,
|
||||||
|
embed
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return embed.code
|
||||||
|
}
|
||||||
|
|
||||||
|
return src
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
let moveIndex = 0
|
||||||
|
walkFetchEmbed({ compile, embedTokens }, ({ embedToken, token }) => {
|
||||||
|
if (token) {
|
||||||
|
const index = token.index + moveIndex
|
||||||
|
|
||||||
|
merge(links, embedToken.links)
|
||||||
|
|
||||||
|
tokens = tokens
|
||||||
|
.slice(0, index)
|
||||||
|
.concat(embedToken, tokens.slice(index + 1))
|
||||||
|
moveIndex += embedToken.length - 1
|
||||||
|
} else {
|
||||||
|
cached[raw] = tokens.concat()
|
||||||
|
tokens.links = cached[raw].links = links
|
||||||
|
done(tokens)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -9,6 +9,7 @@ import { getPath, isAbsolutePath } from '../router/util'
|
||||||
import { isMobile, inBrowser } from '../util/env'
|
import { isMobile, inBrowser } from '../util/env'
|
||||||
import { isPrimitive } from '../util/core'
|
import { isPrimitive } from '../util/core'
|
||||||
import { scrollActiveSidebar, scroll2Top } from '../event/scroll'
|
import { scrollActiveSidebar, scroll2Top } from '../event/scroll'
|
||||||
|
import { prerenderEmbed } from './embed'
|
||||||
|
|
||||||
function executeScript () {
|
function executeScript () {
|
||||||
const script = dom
|
const script = dom
|
||||||
|
|
@ -119,18 +120,37 @@ export function renderMixin (proto) {
|
||||||
getAndActive(this.router, 'nav')
|
getAndActive(this.router, 'nav')
|
||||||
}
|
}
|
||||||
|
|
||||||
proto._renderMain = function (text, opt = {}) {
|
proto._renderMain = function (text, opt = {}, next) {
|
||||||
if (!text) {
|
if (!text) {
|
||||||
return renderMain.call(this, text)
|
return renderMain.call(this, text)
|
||||||
}
|
}
|
||||||
|
|
||||||
callHook(this, 'beforeEach', text, result => {
|
callHook(this, 'beforeEach', text, result => {
|
||||||
let html = this.isHTML ? result : this.compiler.compile(result)
|
let html
|
||||||
if (opt.updatedAt) {
|
const callback = () => {
|
||||||
html = formatUpdated(html, opt.updatedAt, this.config.formatUpdated)
|
if (opt.updatedAt) {
|
||||||
}
|
html = formatUpdated(html, opt.updatedAt, this.config.formatUpdated)
|
||||||
|
}
|
||||||
|
|
||||||
callHook(this, 'afterEach', html, text => renderMain.call(this, text))
|
callHook(this, 'afterEach', html, text => renderMain.call(this, text))
|
||||||
|
}
|
||||||
|
if (this.isHTML) {
|
||||||
|
html = this.result
|
||||||
|
callback()
|
||||||
|
next()
|
||||||
|
} else {
|
||||||
|
prerenderEmbed(
|
||||||
|
{
|
||||||
|
compiler: this.compiler,
|
||||||
|
raw: text
|
||||||
|
},
|
||||||
|
tokens => {
|
||||||
|
html = this.compiler.compile(tokens)
|
||||||
|
callback()
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,9 @@
|
||||||
export function cached (fn) {
|
export function cached (fn) {
|
||||||
const cache = Object.create(null)
|
const cache = Object.create(null)
|
||||||
return function cachedFn (str) {
|
return function cachedFn (str) {
|
||||||
const hit = cache[str]
|
const key = isPrimitive(str) ? str : JSON.stringify(str)
|
||||||
return hit || (cache[str] = fn(str))
|
const hit = cache[key]
|
||||||
|
return hit || (cache[key] = fn(str))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue