refactor: Pre-render TeX + MathML server-side to SVG
This commit is contained in:
parent
13d355bd1c
commit
3d9aa18c05
@ -1,5 +0,0 @@
|
||||
window.MathJax = {
|
||||
root: '/js/mathjax',
|
||||
delayStartupUntil: 'configured'
|
||||
}
|
||||
;
|
@ -65,56 +65,6 @@ module.exports = Promise.mapSeries([
|
||||
}
|
||||
})
|
||||
},
|
||||
/**
|
||||
* MathJax
|
||||
*/
|
||||
() => {
|
||||
return fs.accessAsync('./assets/js/mathjax').then(() => {
|
||||
console.info(colors.white(' └── ') + colors.magenta('MathJax directory already exists. Task aborted.'))
|
||||
return true
|
||||
}).catch(err => {
|
||||
if (err.code === 'ENOENT') {
|
||||
console.info(colors.white(' └── ') + colors.green('Copy MathJax dependencies to assets...'))
|
||||
return fs.ensureDirAsync('./assets/js/mathjax').then(() => {
|
||||
return fs.copyAsync('./node_modules/mathjax', './assets/js/mathjax', {
|
||||
filter: (src, dest) => {
|
||||
let srcNormalized = src.replace(/\\/g, '/')
|
||||
let shouldCopy = false
|
||||
console.info(colors.white(' ' + srcNormalized))
|
||||
_.forEach([
|
||||
'/node_modules/mathjax',
|
||||
'/node_modules/mathjax/jax',
|
||||
'/node_modules/mathjax/jax/input',
|
||||
'/node_modules/mathjax/jax/output'
|
||||
], chk => {
|
||||
if (srcNormalized.endsWith(chk)) {
|
||||
shouldCopy = true
|
||||
}
|
||||
})
|
||||
_.forEach([
|
||||
'/node_modules/mathjax/extensions',
|
||||
'/node_modules/mathjax/MathJax.js',
|
||||
'/node_modules/mathjax/jax/element',
|
||||
'/node_modules/mathjax/jax/input/MathML',
|
||||
'/node_modules/mathjax/jax/input/TeX',
|
||||
'/node_modules/mathjax/jax/output/SVG'
|
||||
], chk => {
|
||||
if (srcNormalized.indexOf(chk) > 0) {
|
||||
shouldCopy = true
|
||||
}
|
||||
})
|
||||
if (shouldCopy && srcNormalized.indexOf('/fonts/') > 0 && srcNormalized.indexOf('/STIX-Web') <= 1) {
|
||||
shouldCopy = false
|
||||
}
|
||||
return shouldCopy
|
||||
}
|
||||
})
|
||||
})
|
||||
} else {
|
||||
throw err
|
||||
}
|
||||
})
|
||||
},
|
||||
/**
|
||||
* i18n
|
||||
*/
|
||||
@ -136,21 +86,6 @@ module.exports = Promise.mapSeries([
|
||||
})
|
||||
})
|
||||
},
|
||||
/**
|
||||
* Bundle pre-init scripts
|
||||
*/
|
||||
() => {
|
||||
console.info(colors.white(' └── ') + colors.green('Bundling pre-init scripts...'))
|
||||
let preInitContent = ''
|
||||
return fs.readdirAsync('./client/js/pre-init').map(f => {
|
||||
let fPath = path.join('./client/js/pre-init/', f)
|
||||
return fs.readFileAsync(fPath, 'utf8').then(fContent => {
|
||||
preInitContent += fContent + ';\n'
|
||||
})
|
||||
}).then(() => {
|
||||
return fs.outputFileAsync('./.build/_preinit.js', preInitContent, 'utf8')
|
||||
})
|
||||
},
|
||||
/**
|
||||
* Delete Fusebox cache
|
||||
*/
|
||||
|
@ -1,6 +1,6 @@
|
||||
'use strict'
|
||||
|
||||
/* global $ */
|
||||
/* global $, siteRoot */
|
||||
/* eslint-disable no-new */
|
||||
|
||||
import Vue from 'vue'
|
||||
@ -64,6 +64,7 @@ import colorPickerComponent from './components/color-picker.vue'
|
||||
import editorCodeblockComponent from './components/editor-codeblock.vue'
|
||||
import editorFileComponent from './components/editor-file.vue'
|
||||
import editorVideoComponent from './components/editor-video.vue'
|
||||
import historyComponent from './components/history.vue'
|
||||
import loadingSpinnerComponent from './components/loading-spinner.vue'
|
||||
import modalCreatePageComponent from './components/modal-create-page.vue'
|
||||
import modalCreateUserComponent from './components/modal-create-user.vue'
|
||||
@ -130,7 +131,7 @@ i18next
|
||||
.use(i18nextXHR)
|
||||
.init({
|
||||
backend: {
|
||||
loadPath: '/js/i18n/{{lng}}.json'
|
||||
loadPath: siteRoot + '/js/i18n/{{lng}}.json'
|
||||
},
|
||||
lng: siteLang,
|
||||
fallbackLng: siteLang
|
||||
@ -176,6 +177,7 @@ $(() => {
|
||||
editorCodeblock: editorCodeblockComponent,
|
||||
editorFile: editorFileComponent,
|
||||
editorVideo: editorVideoComponent,
|
||||
history: historyComponent,
|
||||
loadingSpinner: loadingSpinnerComponent,
|
||||
modalCreatePage: modalCreatePageComponent,
|
||||
modalCreateUser: modalCreateUserComponent,
|
||||
|
@ -1,6 +1,6 @@
|
||||
'use strict'
|
||||
|
||||
/* global $ */
|
||||
/* global $, siteRoot */
|
||||
|
||||
let mde
|
||||
|
||||
@ -30,7 +30,7 @@ export default {
|
||||
return resp.json()
|
||||
}).then(resp => {
|
||||
if (resp.ok) {
|
||||
window.location.assign('/' + self.currentPath)
|
||||
window.location.assign(siteRoot + '/' + self.currentPath)
|
||||
} else {
|
||||
self.$store.dispatch('alert', {
|
||||
style: 'red',
|
||||
|
41
client/js/components/history.vue
Normal file
41
client/js/components/history.vue
Normal file
@ -0,0 +1,41 @@
|
||||
<template lang="pug">
|
||||
div {{ currentPath }}
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'history',
|
||||
props: ['currentPath'],
|
||||
data() {
|
||||
return {
|
||||
tree: []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
fetch(basePath) {
|
||||
let self = this
|
||||
self.$store.dispatch('startLoading')
|
||||
self.$nextTick(() => {
|
||||
socket.emit('treeFetch', { basePath }, (data) => {
|
||||
if (self.tree.length > 0) {
|
||||
let branch = self._.last(self.tree)
|
||||
branch.hasChildren = true
|
||||
self._.find(branch.pages, { _id: basePath }).isActive = true
|
||||
}
|
||||
self.tree.push({
|
||||
hasChildren: false,
|
||||
pages: data
|
||||
})
|
||||
self.$store.dispatch('stopLoading')
|
||||
})
|
||||
})
|
||||
},
|
||||
goto(entryPath) {
|
||||
window.location.assign(siteRoot + '/' + entryPath)
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
@ -10,7 +10,7 @@
|
||||
li(v-if='searchres.length === 0')
|
||||
a: em {{ $t('search.nomatch') }}
|
||||
li(v-for='sres in searchres', v-bind:class='{ "is-active": searchmovekey === "res." + sres.entryPath }')
|
||||
a(v-bind:href='"/" + sres.entryPath') {{ sres.title }}
|
||||
a(v-bind:href='siteRoot + "/" + sres.entryPath') {{ sres.title }}
|
||||
p.searchresults-label(v-if='searchsuggest.length > 0') {{ $t('search.didyoumean') }}
|
||||
ul.searchresults-list(v-if='searchsuggest.length > 0')
|
||||
li(v-for='sug in searchsuggest', v-bind:class='{ "is-active": searchmovekey === "sug." + sug }')
|
||||
@ -18,81 +18,81 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
searchq: '',
|
||||
searchres: [],
|
||||
searchsuggest: [],
|
||||
searchload: 0,
|
||||
searchactive: false,
|
||||
searchmoveidx: 0,
|
||||
searchmovekey: '',
|
||||
searchmovearr: []
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
searchq: function (val, oldVal) {
|
||||
let self = this
|
||||
self.searchmoveidx = 0
|
||||
if (val.length >= 3) {
|
||||
self.searchactive = true
|
||||
self.searchload++
|
||||
socket.emit('search', { terms: val }, (data) => {
|
||||
self.searchres = data.match
|
||||
self.searchsuggest = data.suggest
|
||||
self.searchmovearr = self._.concat([], self.searchres, self.searchsuggest)
|
||||
if (self.searchload > 0) { self.searchload-- }
|
||||
})
|
||||
} else {
|
||||
self.searchactive = false
|
||||
self.searchres = []
|
||||
self.searchsuggest = []
|
||||
self.searchmovearr = []
|
||||
self.searchload = 0
|
||||
}
|
||||
},
|
||||
searchmoveidx: function (val, oldVal) {
|
||||
if (val > 0) {
|
||||
this.searchmovekey = (this.searchmovearr[val - 1])
|
||||
? 'res.' + this.searchmovearr[val - 1].entryPath
|
||||
: 'sug.' + this.searchmovearr[val - 1]
|
||||
} else {
|
||||
this.searchmovekey = ''
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
useSuggestion: function (sug) {
|
||||
this.searchq = sug
|
||||
},
|
||||
closeSearch: function() {
|
||||
this.searchq = ''
|
||||
},
|
||||
moveSelectSearch: function () {
|
||||
if (this.searchmoveidx < 1) { return }
|
||||
let i = this.searchmoveidx - 1
|
||||
|
||||
if (this.searchmovearr[i]) {
|
||||
window.location.assign('/' + this.searchmovearr[i].entryPath)
|
||||
} else {
|
||||
this.searchq = this.searchmovearr[i]
|
||||
}
|
||||
},
|
||||
moveDownSearch: function () {
|
||||
if (this.searchmoveidx < this.searchmovearr.length) {
|
||||
this.searchmoveidx++
|
||||
}
|
||||
},
|
||||
moveUpSearch: function () {
|
||||
if (this.searchmoveidx > 0) {
|
||||
this.searchmoveidx--
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted: function () {
|
||||
let self = this
|
||||
$('main').on('click', self.closeSearch)
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
searchq: '',
|
||||
searchres: [],
|
||||
searchsuggest: [],
|
||||
searchload: 0,
|
||||
searchactive: false,
|
||||
searchmoveidx: 0,
|
||||
searchmovekey: '',
|
||||
searchmovearr: []
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
searchq: function (val, oldVal) {
|
||||
let self = this
|
||||
self.searchmoveidx = 0
|
||||
if (val.length >= 3) {
|
||||
self.searchactive = true
|
||||
self.searchload++
|
||||
socket.emit('search', { terms: val }, (data) => {
|
||||
self.searchres = data.match
|
||||
self.searchsuggest = data.suggest
|
||||
self.searchmovearr = self._.concat([], self.searchres, self.searchsuggest)
|
||||
if (self.searchload > 0) { self.searchload-- }
|
||||
})
|
||||
} else {
|
||||
self.searchactive = false
|
||||
self.searchres = []
|
||||
self.searchsuggest = []
|
||||
self.searchmovearr = []
|
||||
self.searchload = 0
|
||||
}
|
||||
},
|
||||
searchmoveidx: function (val, oldVal) {
|
||||
if (val > 0) {
|
||||
this.searchmovekey = (this.searchmovearr[val - 1])
|
||||
? 'res.' + this.searchmovearr[val - 1].entryPath
|
||||
: 'sug.' + this.searchmovearr[val - 1]
|
||||
} else {
|
||||
this.searchmovekey = ''
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
useSuggestion: function (sug) {
|
||||
this.searchq = sug
|
||||
},
|
||||
closeSearch: function () {
|
||||
this.searchq = ''
|
||||
},
|
||||
moveSelectSearch: function () {
|
||||
if (this.searchmoveidx < 1) { return }
|
||||
let i = this.searchmoveidx - 1
|
||||
|
||||
if (this.searchmovearr[i]) {
|
||||
window.location.assign(siteRoot + '/' + this.searchmovearr[i].entryPath)
|
||||
} else {
|
||||
this.searchq = this.searchmovearr[i]
|
||||
}
|
||||
},
|
||||
moveDownSearch: function () {
|
||||
if (this.searchmoveidx < this.searchmovearr.length) {
|
||||
this.searchmoveidx++
|
||||
}
|
||||
},
|
||||
moveUpSearch: function () {
|
||||
if (this.searchmoveidx > 0) {
|
||||
this.searchmoveidx--
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted: function () {
|
||||
let self = this
|
||||
$('main').on('click', self.closeSearch)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -16,7 +16,7 @@
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: '',
|
||||
name: 'tree',
|
||||
data () {
|
||||
return {
|
||||
tree: []
|
||||
|
@ -2,8 +2,6 @@
|
||||
|
||||
/* global $ */
|
||||
|
||||
import MathJax from 'mathjax'
|
||||
|
||||
export default {
|
||||
name: 'content-view',
|
||||
data() {
|
||||
@ -19,23 +17,5 @@ export default {
|
||||
return false
|
||||
})
|
||||
})
|
||||
MathJax.Hub.Config({
|
||||
jax: ['input/TeX', 'input/MathML', 'output/SVG'],
|
||||
extensions: ['tex2jax.js', 'mml2jax.js'],
|
||||
TeX: {
|
||||
extensions: ['AMSmath.js', 'AMSsymbols.js', 'noErrors.js', 'noUndefined.js']
|
||||
},
|
||||
SVG: {
|
||||
scale: 120,
|
||||
font: 'STIX-Web'
|
||||
},
|
||||
tex2jax: {
|
||||
preview: 'none'
|
||||
},
|
||||
showMathMenu: false,
|
||||
showProcessingMessages: false,
|
||||
messageStyle: 'none'
|
||||
})
|
||||
MathJax.Hub.Configured()
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
'use strict'
|
||||
|
||||
/* global siteRoot */
|
||||
|
||||
export default {
|
||||
name: 'source-view',
|
||||
data() {
|
||||
@ -7,7 +9,7 @@ export default {
|
||||
},
|
||||
mounted() {
|
||||
let self = this
|
||||
FuseBox.import('/js/ace/ace.js', (ace) => {
|
||||
FuseBox.import(siteRoot + '/js/ace/ace.js', (ace) => {
|
||||
let scEditor = ace.edit('source-display')
|
||||
scEditor.setTheme('ace/theme/dawn')
|
||||
scEditor.getSession().setMode('ace/mode/markdown')
|
||||
|
@ -1,4 +0,0 @@
|
||||
window.MathJax = {
|
||||
root: '/js/mathjax',
|
||||
delayStartupUntil: 'configured'
|
||||
}
|
@ -134,14 +134,6 @@ git:
|
||||
# Whether to use user email as author in commits
|
||||
showUserEmail: true
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# Features
|
||||
# ---------------------------------------------------------------------
|
||||
# You can enable / disable specific features below
|
||||
|
||||
features:
|
||||
mathjax: true
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# External Logging
|
||||
# ---------------------------------------------------------------------
|
||||
|
8
fuse.js
8
fuse.js
@ -53,17 +53,9 @@ const ALIASES = {
|
||||
'vue-lodash': 'vue-lodash/dist/vue-lodash.min.js'
|
||||
}
|
||||
const SHIMS = {
|
||||
_preinit: {
|
||||
source: '.build/_preinit.js',
|
||||
exports: '_preinit'
|
||||
},
|
||||
jquery: {
|
||||
source: 'node_modules/jquery/dist/jquery.js',
|
||||
exports: '$'
|
||||
},
|
||||
mathjax: {
|
||||
source: 'node_modules/mathjax/MathJax.js',
|
||||
exports: 'MathJax'
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -132,14 +132,6 @@ git:
|
||||
# Whether to use user email as author in commits
|
||||
showUserEmail: true
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# Features
|
||||
# ---------------------------------------------------------------------
|
||||
# You can enable / disable specific features below
|
||||
|
||||
features:
|
||||
mathjax: true
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# External Logging
|
||||
# ---------------------------------------------------------------------
|
||||
|
@ -132,14 +132,6 @@ git:
|
||||
# Whether to use user email as author in commits
|
||||
showUserEmail: $(WIKI_SHOW_USER_EMAIL)
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# Features
|
||||
# ---------------------------------------------------------------------
|
||||
# You can enable / disable specific features below
|
||||
|
||||
features:
|
||||
mathjax: true
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# External Logging
|
||||
# ---------------------------------------------------------------------
|
||||
|
15
package.json
15
package.json
@ -85,6 +85,7 @@
|
||||
"markdown-it-footnote": "^3.0.1",
|
||||
"markdown-it-mathjax": "^2.0.0",
|
||||
"markdown-it-task-lists": "^2.0.1",
|
||||
"mathjax-node": "^1.1.0",
|
||||
"memdown": "^1.2.4",
|
||||
"mime-types": "^2.1.15",
|
||||
"moment": "^2.18.1",
|
||||
@ -100,7 +101,7 @@
|
||||
"passport-facebook": "^2.1.1",
|
||||
"passport-github2": "^0.1.10",
|
||||
"passport-google-oauth20": "^1.0.0",
|
||||
"passport-ldapauth": "^1.0.0",
|
||||
"passport-ldapauth": "^2.0.0",
|
||||
"passport-local": "^1.0.0",
|
||||
"passport-slack": "0.0.7",
|
||||
"passport-windowslive": "^1.0.2",
|
||||
@ -134,9 +135,9 @@
|
||||
"brace": "^0.10.0",
|
||||
"colors": "^1.1.2",
|
||||
"consolidate": "^0.14.5",
|
||||
"eslint": "^3.19.0",
|
||||
"eslint": "^4.0.0",
|
||||
"eslint-config-standard": "^10.2.1",
|
||||
"eslint-plugin-import": "^2.3.0",
|
||||
"eslint-plugin-import": "^2.6.0",
|
||||
"eslint-plugin-node": "^5.0.0",
|
||||
"eslint-plugin-promise": "^3.5.0",
|
||||
"eslint-plugin-standard": "^3.0.1",
|
||||
@ -154,12 +155,12 @@
|
||||
"node-sass": "^4.5.3",
|
||||
"nodemon": "^1.11.0",
|
||||
"pug-lint": "^2.4.0",
|
||||
"snyk": "^1.34.3",
|
||||
"snyk": "^1.36.0",
|
||||
"twemoji-awesome": "^1.0.6",
|
||||
"typescript": "^2.3.4",
|
||||
"uglify-es": "^3.0.15",
|
||||
"uglify-js": "^3.0.15",
|
||||
"vee-validate": "^2.0.0-rc.5",
|
||||
"uglify-es": "^3.0.19",
|
||||
"uglify-js": "^3.0.19",
|
||||
"vee-validate": "^2.0.0-rc.6",
|
||||
"vue": "^2.3.4",
|
||||
"vue-clipboards": "^1.0.2",
|
||||
"vue-lodash": "^1.0.3",
|
||||
|
@ -22,7 +22,7 @@ module.exports = {
|
||||
*
|
||||
* @return {Object} Entries model instance
|
||||
*/
|
||||
init () {
|
||||
init() {
|
||||
let self = this
|
||||
|
||||
self._repoPath = path.resolve(ROOTPATH, appconfig.paths.repo)
|
||||
@ -39,7 +39,7 @@ module.exports = {
|
||||
* @param {String} entryPath The entry path
|
||||
* @return {Promise<Boolean>} True if exists, false otherwise
|
||||
*/
|
||||
exists (entryPath) {
|
||||
exists(entryPath) {
|
||||
let self = this
|
||||
|
||||
return self.fetchOriginal(entryPath, {
|
||||
@ -62,7 +62,7 @@ module.exports = {
|
||||
* @param {String} entryPath The entry path
|
||||
* @return {Promise<Object>} Page Data
|
||||
*/
|
||||
fetch (entryPath) {
|
||||
fetch(entryPath) {
|
||||
let self = this
|
||||
|
||||
let cpath = entryHelper.getCachePath(entryPath)
|
||||
@ -97,7 +97,7 @@ module.exports = {
|
||||
* @param {Object} options The options
|
||||
* @return {Promise<Object>} Page data
|
||||
*/
|
||||
fetchOriginal (entryPath, options) {
|
||||
fetchOriginal(entryPath, options) {
|
||||
let self = this
|
||||
|
||||
let fpath = entryHelper.getFullPath(entryPath)
|
||||
@ -115,43 +115,47 @@ module.exports = {
|
||||
return fs.statAsync(fpath).then((st) => {
|
||||
if (st.isFile()) {
|
||||
return fs.readFileAsync(fpath, 'utf8').then((contents) => {
|
||||
let htmlProcessor = (options.parseMarkdown) ? mark.parseContent(contents) : Promise.resolve('')
|
||||
|
||||
// Parse contents
|
||||
|
||||
let pageData = {
|
||||
markdown: (options.includeMarkdown) ? contents : '',
|
||||
html: (options.parseMarkdown) ? mark.parseContent(contents) : '',
|
||||
meta: (options.parseMeta) ? mark.parseMeta(contents) : {},
|
||||
tree: (options.parseTree) ? mark.parseTree(contents) : []
|
||||
}
|
||||
|
||||
if (!pageData.meta.title) {
|
||||
pageData.meta.title = _.startCase(entryPath)
|
||||
}
|
||||
|
||||
pageData.meta.path = entryPath
|
||||
|
||||
// Get parent
|
||||
|
||||
let parentPromise = (options.includeParentInfo) ? self.getParentInfo(entryPath).then((parentData) => {
|
||||
return (pageData.parent = parentData)
|
||||
}).catch((err) => { // eslint-disable-line handle-callback-err
|
||||
return (pageData.parent = false)
|
||||
}) : Promise.resolve(true)
|
||||
|
||||
return parentPromise.then(() => {
|
||||
// Cache to disk
|
||||
|
||||
if (options.cache) {
|
||||
let cacheData = JSON.stringify(_.pick(pageData, ['html', 'meta', 'tree', 'parent']), false, false, false)
|
||||
return fs.writeFileAsync(cpath, cacheData).catch((err) => {
|
||||
winston.error('Unable to write to cache! Performance may be affected.')
|
||||
winston.error(err)
|
||||
return true
|
||||
})
|
||||
} else {
|
||||
return true
|
||||
return htmlProcessor.then(html => {
|
||||
let pageData = {
|
||||
markdown: (options.includeMarkdown) ? contents : '',
|
||||
html,
|
||||
meta: (options.parseMeta) ? mark.parseMeta(contents) : {},
|
||||
tree: (options.parseTree) ? mark.parseTree(contents) : []
|
||||
}
|
||||
}).return(pageData)
|
||||
|
||||
if (!pageData.meta.title) {
|
||||
pageData.meta.title = _.startCase(entryPath)
|
||||
}
|
||||
|
||||
pageData.meta.path = entryPath
|
||||
|
||||
// Get parent
|
||||
|
||||
let parentPromise = (options.includeParentInfo) ? self.getParentInfo(entryPath).then((parentData) => {
|
||||
return (pageData.parent = parentData)
|
||||
}).catch((err) => { // eslint-disable-line handle-callback-err
|
||||
return (pageData.parent = false)
|
||||
}) : Promise.resolve(true)
|
||||
|
||||
return parentPromise.then(() => {
|
||||
// Cache to disk
|
||||
|
||||
if (options.cache) {
|
||||
let cacheData = JSON.stringify(_.pick(pageData, ['html', 'meta', 'tree', 'parent']), false, false, false)
|
||||
return fs.writeFileAsync(cpath, cacheData).catch((err) => {
|
||||
winston.error('Unable to write to cache! Performance may be affected.')
|
||||
winston.error(err)
|
||||
return true
|
||||
})
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}).return(pageData)
|
||||
})
|
||||
})
|
||||
} else {
|
||||
return false
|
||||
@ -167,7 +171,7 @@ module.exports = {
|
||||
* @param {String} entryPath The entry path
|
||||
* @return {Promise<Object|False>} The parent information.
|
||||
*/
|
||||
getParentInfo (entryPath) {
|
||||
getParentInfo(entryPath) {
|
||||
if (_.includes(entryPath, '/')) {
|
||||
let parentParts = _.initial(_.split(entryPath, '/'))
|
||||
let parentPath = _.join(parentParts, '/')
|
||||
@ -202,7 +206,7 @@ module.exports = {
|
||||
* @param {Object} author The author user object
|
||||
* @return {Promise<Boolean>} True on success, false on failure
|
||||
*/
|
||||
update (entryPath, contents, author) {
|
||||
update(entryPath, contents, author) {
|
||||
let self = this
|
||||
let fpath = entryHelper.getFullPath(entryPath)
|
||||
|
||||
@ -228,7 +232,7 @@ module.exports = {
|
||||
* @param {String} entryPath The entry path
|
||||
* @return {Promise} Promise of the operation
|
||||
*/
|
||||
updateCache (entryPath) {
|
||||
updateCache(entryPath) {
|
||||
let self = this
|
||||
|
||||
return self.fetchOriginal(entryPath, {
|
||||
@ -256,21 +260,21 @@ module.exports = {
|
||||
return db.Entry.findOneAndUpdate({
|
||||
_id: content.entryPath
|
||||
}, {
|
||||
_id: content.entryPath,
|
||||
title: content.meta.title || content.entryPath,
|
||||
subtitle: content.meta.subtitle || '',
|
||||
parentTitle: content.parent.title || '',
|
||||
parentPath: parentPath,
|
||||
isDirectory: false,
|
||||
isEntry: true
|
||||
}, {
|
||||
new: true,
|
||||
upsert: true
|
||||
}).then(result => {
|
||||
let plainResult = result.toObject()
|
||||
plainResult.text = content.text
|
||||
return plainResult
|
||||
})
|
||||
_id: content.entryPath,
|
||||
title: content.meta.title || content.entryPath,
|
||||
subtitle: content.meta.subtitle || '',
|
||||
parentTitle: content.parent.title || '',
|
||||
parentPath: parentPath,
|
||||
isDirectory: false,
|
||||
isEntry: true
|
||||
}, {
|
||||
new: true,
|
||||
upsert: true
|
||||
}).then(result => {
|
||||
let plainResult = result.toObject()
|
||||
plainResult.text = content.text
|
||||
return plainResult
|
||||
})
|
||||
}).then(result => {
|
||||
return self.updateTreeInfo().then(() => {
|
||||
return result
|
||||
@ -286,7 +290,7 @@ module.exports = {
|
||||
*
|
||||
* @returns {Promise<Boolean>} Promise of the operation
|
||||
*/
|
||||
updateTreeInfo () {
|
||||
updateTreeInfo() {
|
||||
return db.Entry.distinct('parentPath', { parentPath: { $ne: '' } }).then(allPaths => {
|
||||
if (allPaths.length > 0) {
|
||||
return Promise.map(allPaths, pathItem => {
|
||||
@ -311,7 +315,7 @@ module.exports = {
|
||||
* @param {Object} author The author user object
|
||||
* @return {Promise<Boolean>} True on success, false on failure
|
||||
*/
|
||||
create (entryPath, contents, author) {
|
||||
create(entryPath, contents, author) {
|
||||
let self = this
|
||||
|
||||
return self.exists(entryPath).then((docExists) => {
|
||||
@ -338,7 +342,7 @@ module.exports = {
|
||||
* @param {Object} author The author user object
|
||||
* @return {Promise<Boolean>} True on success, false on failure
|
||||
*/
|
||||
makePersistent (entryPath, contents, author) {
|
||||
makePersistent(entryPath, contents, author) {
|
||||
let fpath = entryHelper.getFullPath(entryPath)
|
||||
|
||||
return fs.outputFileAsync(fpath, contents).then(() => {
|
||||
@ -354,7 +358,7 @@ module.exports = {
|
||||
* @param {Object} author The author user object
|
||||
* @return {Promise} Promise of the operation
|
||||
*/
|
||||
move (entryPath, newEntryPath, author) {
|
||||
move(entryPath, newEntryPath, author) {
|
||||
let self = this
|
||||
|
||||
if (_.isEmpty(entryPath) || entryPath === 'home') {
|
||||
@ -387,7 +391,7 @@ module.exports = {
|
||||
* @param {String} entryPath The entry path
|
||||
* @return {Promise<String>} Starter content
|
||||
*/
|
||||
getStarter (entryPath) {
|
||||
getStarter(entryPath) {
|
||||
let formattedTitle = _.startCase(_.last(_.split(entryPath, '/')))
|
||||
|
||||
return fs.readFileAsync(path.join(SERVERPATH, 'app/content/create.md'), 'utf8').then((contents) => {
|
||||
@ -402,7 +406,7 @@ module.exports = {
|
||||
* @param {Object} usr Current user
|
||||
* @return {Promise<Array>} List of entries
|
||||
*/
|
||||
getFromTree (basePath, usr) {
|
||||
getFromTree(basePath, usr) {
|
||||
return db.Entry.find({ parentPath: basePath }, 'title parentPath isDirectory isEntry').sort({ title: 'asc' }).then(results => {
|
||||
return _.filter(results, r => {
|
||||
return rights.checkRole('/' + r._id, usr.rights, 'read')
|
||||
@ -410,7 +414,7 @@ module.exports = {
|
||||
})
|
||||
},
|
||||
|
||||
getHistory (entryPath) {
|
||||
getHistory(entryPath) {
|
||||
return db.Entry.findOne({ _id: entryPath, isEntry: true }).then(entry => {
|
||||
if (!entry) { return false }
|
||||
return git.getHistory(entryPath).then(history => {
|
||||
|
@ -1,5 +1,6 @@
|
||||
'use strict'
|
||||
|
||||
const Promise = require('bluebird')
|
||||
const md = require('markdown-it')
|
||||
const mdEmoji = require('markdown-it-emoji')
|
||||
const mdTaskLists = require('markdown-it-task-lists')
|
||||
@ -9,6 +10,8 @@ const mdFootnote = require('markdown-it-footnote')
|
||||
const mdExternalLinks = require('markdown-it-external-links')
|
||||
const mdExpandTabs = require('markdown-it-expand-tabs')
|
||||
const mdAttrs = require('markdown-it-attrs')
|
||||
const mdMathjax = require('markdown-it-mathjax')()
|
||||
const mathjax = require('mathjax-node')
|
||||
const hljs = require('highlight.js')
|
||||
const cheerio = require('cheerio')
|
||||
const _ = require('lodash')
|
||||
@ -50,11 +53,7 @@ var mkdown = md({
|
||||
tabWidth: 4
|
||||
})
|
||||
.use(mdAttrs)
|
||||
|
||||
if (appconfig) {
|
||||
const mdMathjax = require('markdown-it-mathjax')
|
||||
mkdown.use(mdMathjax())
|
||||
}
|
||||
.use(mdMathjax)
|
||||
|
||||
// Rendering rules
|
||||
|
||||
@ -87,9 +86,40 @@ const videoRules = [
|
||||
}
|
||||
]
|
||||
|
||||
// Non-markdown filter
|
||||
// Regex
|
||||
|
||||
const textRegex = new RegExp('\\b[a-z0-9-.,' + appdata.regex.cjk + appdata.regex.arabic + ']+\\b', 'g')
|
||||
const mathRegex = [
|
||||
{
|
||||
format: 'TeX',
|
||||
regex: /\\\[([\s\S]*?)\\\]/g
|
||||
},
|
||||
{
|
||||
format: 'inline-TeX',
|
||||
regex: /\\\((.*?)\\\)/g
|
||||
},
|
||||
{
|
||||
format: 'MathML',
|
||||
regex: /<math([\s\S]*?)<\/math>/g
|
||||
}
|
||||
]
|
||||
|
||||
// MathJax
|
||||
|
||||
mathjax.config({
|
||||
MathJax: {
|
||||
jax: ['input/TeX', 'input/MathML', 'output/SVG'],
|
||||
extensions: ['tex2jax.js', 'mml2jax.js'],
|
||||
TeX: {
|
||||
extensions: ['AMSmath.js', 'AMSsymbols.js', 'noErrors.js', 'noUndefined.js']
|
||||
},
|
||||
SVG: {
|
||||
scale: 120,
|
||||
font: 'STIX-Web'
|
||||
}
|
||||
}
|
||||
})
|
||||
mathjax.start()
|
||||
|
||||
/**
|
||||
* Parse markdown content and build TOC tree
|
||||
@ -177,11 +207,10 @@ const parseTree = (content) => {
|
||||
* Parse markdown content to HTML
|
||||
*
|
||||
* @param {String} content Markdown content
|
||||
* @return {String} HTML formatted content
|
||||
* @return {Promise<String>} Promise
|
||||
*/
|
||||
const parseContent = (content) => {
|
||||
let output = mkdown.render(content)
|
||||
let cr = cheerio.load(output)
|
||||
let cr = cheerio.load(mkdown.render(content))
|
||||
|
||||
if (cr.root().children().length < 1) {
|
||||
return ''
|
||||
@ -265,9 +294,55 @@ const parseContent = (content) => {
|
||||
cr(elm).removeClass('align-center')
|
||||
})
|
||||
|
||||
output = cr.html()
|
||||
// Mathjax Post-processor
|
||||
|
||||
return output
|
||||
return processMathjax(cr.html())
|
||||
}
|
||||
|
||||
/**
|
||||
* Process MathJax expressions
|
||||
*
|
||||
* @param {String} content HTML content
|
||||
* @returns {Promise<String>} Promise
|
||||
*/
|
||||
const processMathjax = (content) => {
|
||||
let matchStack = []
|
||||
let replaceStack = []
|
||||
let currentMatch
|
||||
let mathjaxState = {}
|
||||
|
||||
_.forEach(mathRegex, mode => {
|
||||
do {
|
||||
currentMatch = mode.regex.exec(content)
|
||||
if (currentMatch) {
|
||||
matchStack.push(currentMatch[0])
|
||||
replaceStack.push(
|
||||
new Promise((resolve, reject) => {
|
||||
mathjax.typeset({
|
||||
math: (mode.format === 'MathML') ? currentMatch[0] : currentMatch[1],
|
||||
format: mode.format,
|
||||
speakText: false,
|
||||
svg: true,
|
||||
state: mathjaxState
|
||||
}, result => {
|
||||
if (!result.errors) {
|
||||
resolve(result.svg)
|
||||
} else {
|
||||
reject(new Error(result.errors.join(', ')))
|
||||
}
|
||||
})
|
||||
})
|
||||
)
|
||||
}
|
||||
} while (currentMatch)
|
||||
})
|
||||
|
||||
return (matchStack.length > 0) ? Promise.all(replaceStack).then(results => {
|
||||
_.forEach(matchStack, (repMatch, idx) => {
|
||||
content = content.replace(repMatch, results[idx])
|
||||
})
|
||||
return content
|
||||
}) : Promise.resolve(content)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -314,11 +389,13 @@ module.exports = {
|
||||
* @return {Object} Object containing meta, html and tree data
|
||||
*/
|
||||
parse(content) {
|
||||
return {
|
||||
meta: parseMeta(content),
|
||||
html: parseContent(content),
|
||||
tree: parseTree(content)
|
||||
}
|
||||
return parseContent(content).then(html => {
|
||||
return {
|
||||
meta: parseMeta(content),
|
||||
html,
|
||||
tree: parseTree(content)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
parseContent,
|
||||
|
@ -4,32 +4,24 @@ block rootNavRight
|
||||
i.nav-item#notifload
|
||||
.nav-item
|
||||
a.button(href='/' + pageData.meta._id)
|
||||
i.icon-circle-check
|
||||
i.nc-icon-outline.ui-3_select
|
||||
span= t('nav.viewlatest')
|
||||
|
||||
block content
|
||||
.container.is-fluid
|
||||
.columns.is-gapless
|
||||
|
||||
#page-type-history.page-type-container(data-entrypath=pageData.meta._id)
|
||||
.container.is-fluid.has-mkcontent
|
||||
.columns.is-gapless
|
||||
.column.is-narrow.is-hidden-touch.sidebar
|
||||
|
||||
.column.is-narrow.is-hidden-touch.sidebar
|
||||
aside.stickyscroll
|
||||
.sidebar-label
|
||||
span= t('sidebar.pastversions')
|
||||
ul.sidebar-menu
|
||||
each item, index in pageData.history
|
||||
- var itemDate = moment(item.date)
|
||||
li: a.is-multiline(class={ 'is-active': index < 1 }, href='', title=itemDate.format('LLLL'))
|
||||
span= itemDate.calendar(null, { sameElse: 'llll'})
|
||||
span.is-small= item.commitAbbr
|
||||
|
||||
aside.stickyscroll
|
||||
.sidebar-label
|
||||
span= t('sidebar.pastversions')
|
||||
ul.sidebar-menu
|
||||
each item, index in pageData.history
|
||||
- var itemDate = moment(item.date)
|
||||
li: a.is-multiline(class={ 'is-active': index < 1 }, href='', title=itemDate.format('LLLL'))
|
||||
span= itemDate.calendar(null, { sameElse: 'llll'})
|
||||
span.is-small= item.commitAbbr
|
||||
|
||||
.column
|
||||
|
||||
.hero
|
||||
h1.title#title= pageData.meta.title
|
||||
if pageData.meta.subtitle
|
||||
h2.subtitle= pageData.meta.subtitle
|
||||
.content.mkcontent
|
||||
!= pageData.html
|
||||
.column
|
||||
history(current-path=pageData.meta._id)
|
||||
|
@ -18,8 +18,8 @@ block rootNavRight
|
||||
a.button.is-outlined(href='/source/' + pageData.meta.path)
|
||||
i.nc-icon-outline.education_paper
|
||||
span= t('nav.source')
|
||||
//- a.button.is-outlined(href='/hist/' + pageData.meta.path)
|
||||
i.icon-clock
|
||||
a.button.is-outlined(href='/hist/' + pageData.meta.path)
|
||||
i.nc-icon-outline.ui-2_time
|
||||
span= t('nav.history')
|
||||
if rights.write
|
||||
a.button(href='/edit/' + pageData.meta.path)
|
||||
|
Loading…
Reference in New Issue
Block a user