feat: admin rendering (wip)

parent 8c71470d
key: markdownAbbr
title: Abbreviations
description: Parse abbreviations into abbr tags
author: requarks.io
icon: mdi-contain-start
enabledDefault: true
dependsOn: markdown-core
props: {}
const mdAbbr = require('markdown-it-abbr')
// ------------------------------------
// Markdown - Abbreviations
// ------------------------------------
module.exports = {
init (md, conf) {
md.use(mdAbbr)
}
}
key: markdown-core
title: Core
description: Basic Markdown Parser
author: requarks.io
input: markdown
output: html
icon: mdi-language-markdown
props:
allowHTML:
type: Boolean
default: true
title: Allow HTML
hint: Enable HTML tags in content.
order: 1
public: true
linkify:
type: Boolean
default: true
title: Automatically convert links
hint: Links will automatically be converted to clickable links.
order: 2
public: true
linebreaks:
type: Boolean
default: true
title: Automatically convert line breaks
hint: Add linebreaks within paragraphs.
order: 3
public: true
underline:
type: Boolean
default: false
title: Underline Emphasis
hint: Enable text underlining by using _underline_ syntax.
order: 4
public: true
typographer:
type: Boolean
default: false
title: Typographer
hint: Enable some language-neutral replacement + quotes beautification.
order: 5
public: true
quotes:
type: String
default: English
title: Quotes style
hint: When typographer is enabled. Double + single quotes replacement pairs. e.g. «»„“ for Russian, „“‚‘ for German, etc.
order: 6
enum:
- Chinese
- English
- French
- German
- Greek
- Japanese
- Hungarian
- Polish
- Portuguese
- Russian
- Spanish
- Swedish
public: true
const md = require('markdown-it')
const mdAttrs = require('markdown-it-attrs')
const _ = require('lodash')
const underline = require('./underline')
const quoteStyles = {
Chinese: '””‘’',
English: '“”‘’',
French: [\xA0', '\xA0»', '‹\xA0', '\xA0›'],
German: '„“‚‘',
Greek: '«»‘’',
Japanese: '「」「」',
Hungarian: '„”’’',
Polish: '„”‚‘',
Portuguese: '«»‘’',
Russian: '«»„“',
Spanish: '«»‘’',
Swedish: '””’’'
}
module.exports = {
async render() {
const mkdown = md({
html: this.config.allowHTML,
breaks: this.config.linebreaks,
linkify: this.config.linkify,
typographer: this.config.typographer,
quotes: _.get(quoteStyles, this.config.quotes, quoteStyles.English),
highlight(str, lang) {
if (lang === 'diagram') {
return `<pre class="diagram">` + Buffer.from(str, 'base64').toString() + `</pre>`
} else {
return `<pre><code class="language-${lang}">${_.escape(str)}</code></pre>`
}
}
})
if (this.config.underline) {
mkdown.use(underline)
}
mkdown.use(mdAttrs, {
allowedAttributes: ['id', 'class', 'target']
})
for (let child of this.children) {
const renderer = require(`../${child.key}/renderer.js`)
await renderer.init(mkdown, child.config)
}
return mkdown.render(this.input)
}
}
const renderEm = (tokens, idx, opts, env, slf) => {
const token = tokens[idx]
if (token.markup === '_') {
token.tag = 'u'
}
return slf.renderToken(tokens, idx, opts)
}
module.exports = (md) => {
md.renderer.rules.em_open = renderEm
md.renderer.rules.em_close = renderEm
}
key: markdownEmoji
title: Emoji
description: Convert tags to emojis
author: requarks.io
icon: mdi-sticker-emoji
enabledDefault: true
dependsOn: markdown-core
props: {}
const mdEmoji = require('markdown-it-emoji')
const twemoji = require('twemoji')
// ------------------------------------
// Markdown - Emoji
// ------------------------------------
module.exports = {
init (md, conf) {
md.use(mdEmoji)
md.renderer.rules.emoji = (token, idx) => {
return twemoji.parse(token[idx].content, {
callback (icon, opts) {
return `/_assets/svg/twemoji/${icon}.svg`
}
})
}
}
}
key: markdownExpandtabs
title: Expand Tabs
description: Replace tabs with spaces in code blocks
author: requarks.io
icon: mdi-arrow-expand-horizontal
enabledDefault: true
dependsOn: markdown-core
props:
tabWidth:
type: Number
title: Tab Width
hint: Amount of spaces for each tab
default: 4
const mdExpandTabs = require('markdown-it-expand-tabs')
const _ = require('lodash')
// ------------------------------------
// Markdown - Expand Tabs
// ------------------------------------
module.exports = {
init (md, conf) {
md.use(mdExpandTabs, {
tabWidth: _.toInteger(conf.tabWidth || 4)
})
}
}
key: markdownFootnotes
title: Footnotes
description: Parse footnotes references
author: requarks.io
icon: mdi-page-layout-footer
enabledDefault: true
dependsOn: markdown-core
props: {}
const mdFootnote = require('markdown-it-footnote')
// ------------------------------------
// Markdown - Footnotes
// ------------------------------------
module.exports = {
init (md, conf) {
md.use(mdFootnote)
}
}
key: markdownImsize
title: Image Size
description: Adds dimensions attributes to images
author: requarks.io
icon: mdi-image-size-select-large
enabledDefault: true
dependsOn: markdown-core
props: {}
const mdImsize = require('markdown-it-imsize')
// ------------------------------------
// Markdown - Image Size
// ------------------------------------
module.exports = {
init (md, conf) {
md.use(mdImsize)
}
}
key: markdownKatex
title: Katex
description: LaTeX Math + Chemical Expression Typesetting Renderer
author: requarks.io
icon: mdi-math-integral
enabledDefault: true
dependsOn: markdown-core
props:
useInline:
type: Boolean
default: true
title: Inline TeX
hint: Process inline TeX expressions surrounded by $ symbols.
order: 1
useBlocks:
type: Boolean
default: true
title: TeX Blocks
hint: Process TeX blocks enclosed by $$ symbols.
order: 2
const katex = require('katex')
const chemParse = require('./mhchem')
// ------------------------------------
// Markdown - KaTeX Renderer
// ------------------------------------
//
// Includes code from https://github.com/liradb2000/markdown-it-katex
// Add \ce, \pu, and \tripledash to the KaTeX macros.
katex.__defineMacro('\\ce', function(context) {
return chemParse(context.consumeArgs(1)[0], 'ce')
})
katex.__defineMacro('\\pu', function(context) {
return chemParse(context.consumeArgs(1)[0], 'pu')
})
// Needed for \bond for the ~ forms
// Raise by 2.56mu, not 2mu. We're raising a hyphen-minus, U+002D, not
// a mathematical minus, U+2212. So we need that extra 0.56.
katex.__defineMacro('\\tripledash', '{\\vphantom{-}\\raisebox{2.56mu}{$\\mkern2mu' + '\\tiny\\text{-}\\mkern1mu\\text{-}\\mkern1mu\\text{-}\\mkern2mu$}}')
module.exports = {
init (mdinst, conf) {
if (conf.useInline) {
mdinst.inline.ruler.after('escape', 'katex_inline', katexInline)
mdinst.renderer.rules.katex_inline = (tokens, idx) => {
try {
return katex.renderToString(tokens[idx].content, {
displayMode: false
})
} catch (err) {
WIKI.logger.warn(err)
return tokens[idx].content
}
}
}
if (conf.useBlocks) {
mdinst.block.ruler.after('blockquote', 'katex_block', katexBlock, {
alt: [ 'paragraph', 'reference', 'blockquote', 'list' ]
})
mdinst.renderer.rules.katex_block = (tokens, idx) => {
try {
return `<p>` + katex.renderToString(tokens[idx].content, {
displayMode: true
}) + `</p>`
} catch (err) {
WIKI.logger.warn(err)
return tokens[idx].content
}
}
}
}
}
// Test if potential opening or closing delimieter
// Assumes that there is a "$" at state.src[pos]
function isValidDelim (state, pos) {
let prevChar
let nextChar
let max = state.posMax
let canOpen = true
let canClose = true
prevChar = pos > 0 ? state.src.charCodeAt(pos - 1) : -1
nextChar = pos + 1 <= max ? state.src.charCodeAt(pos + 1) : -1
// Check non-whitespace conditions for opening and closing, and
// check that closing delimeter isn't followed by a number
if (prevChar === 0x20/* " " */ || prevChar === 0x09/* \t */ ||
(nextChar >= 0x30/* "0" */ && nextChar <= 0x39/* "9" */)) {
canClose = false
}
if (nextChar === 0x20/* " " */ || nextChar === 0x09/* \t */) {
canOpen = false
}
return {
canOpen: canOpen,
canClose: canClose
}
}
function katexInline (state, silent) {
let start, match, token, res, pos
if (state.src[state.pos] !== '$') { return false }
res = isValidDelim(state, state.pos)
if (!res.canOpen) {
if (!silent) { state.pending += '$' }
state.pos += 1
return true
}
// First check for and bypass all properly escaped delimieters
// This loop will assume that the first leading backtick can not
// be the first character in state.src, which is known since
// we have found an opening delimieter already.
start = state.pos + 1
match = start
while ((match = state.src.indexOf('$', match)) !== -1) {
// Found potential $, look for escapes, pos will point to
// first non escape when complete
pos = match - 1
while (state.src[pos] === '\\') { pos -= 1 }
// Even number of escapes, potential closing delimiter found
if (((match - pos) % 2) === 1) { break }
match += 1
}
// No closing delimter found. Consume $ and continue.
if (match === -1) {
if (!silent) { state.pending += '$' }
state.pos = start
return true
}
// Check if we have empty content, ie: $$. Do not parse.
if (match - start === 0) {
if (!silent) { state.pending += '$$' }
state.pos = start + 1
return true
}
// Check for valid closing delimiter
res = isValidDelim(state, match)
if (!res.canClose) {
if (!silent) { state.pending += '$' }
state.pos = start
return true
}
if (!silent) {
token = state.push('katex_inline', 'math', 0)
token.markup = '$'
token.content = state.src.slice(start, match)
}
state.pos = match + 1
return true
}
function katexBlock (state, start, end, silent) {
let firstLine; let lastLine; let next; let lastPos; let found = false; let token
let pos = state.bMarks[start] + state.tShift[start]
let max = state.eMarks[start]
if (pos + 2 > max) { return false }
if (state.src.slice(pos, pos + 2) !== '$$') { return false }
pos += 2
firstLine = state.src.slice(pos, max)
if (silent) { return true }
if (firstLine.trim().slice(-2) === '$$') {
// Single line expression
firstLine = firstLine.trim().slice(0, -2)
found = true
}
for (next = start; !found;) {
next++
if (next >= end) { break }
pos = state.bMarks[next] + state.tShift[next]
max = state.eMarks[next]
if (pos < max && state.tShift[next] < state.blkIndent) {
// non-empty line with negative indent should stop the list:
break
}
if (state.src.slice(pos, max).trim().slice(-2) === '$$') {
lastPos = state.src.slice(0, max).lastIndexOf('$$')
lastLine = state.src.slice(pos, lastPos)
found = true
}
}
state.line = next + 1
token = state.push('katex_block', 'math', 0)
token.block = true
token.content = (firstLine && firstLine.trim() ? firstLine + '\n' : '') +
state.getLines(start + 1, next, state.tShift[start], true) +
(lastLine && lastLine.trim() ? lastLine : '')
token.map = [ start, state.line ]
token.markup = '$$'
return true
}
key: markdownKroki
title: Kroki
description: Kroki Diagrams Parser
author: rlanyi (based on PlantUML renderer)
icon: mdi-sitemap
enabledDefault: false
dependsOn: markdown-core
props:
server:
type: String
default: https://kroki.io
title: Kroki Server
hint: Kroki server used for image generation
order: 1
public: true
openMarker:
type: String
default: "```kroki"
title: Open Marker
hint: String to use as opening delimiter. Diagram type must be put in the next line in lowercase.
order: 2
public: true
closeMarker:
type: String
default: "```"
title: Close Marker
hint: String to use as closing delimiter
order: 3
public: true
const zlib = require('zlib')
// ------------------------------------
// Markdown - Kroki Preprocessor
// ------------------------------------
module.exports = {
init (mdinst, conf) {
mdinst.use((md, opts) => {
const openMarker = opts.openMarker || '```kroki'
const openChar = openMarker.charCodeAt(0)
const closeMarker = opts.closeMarker || '```'
const closeChar = closeMarker.charCodeAt(0)
const server = opts.server || 'https://kroki.io'
md.block.ruler.before('fence', 'kroki', (state, startLine, endLine, silent) => {
let nextLine
let markup
let params
let token
let i
let autoClosed = false
let start = state.bMarks[startLine] + state.tShift[startLine]
let max = state.eMarks[startLine]
// Check out the first character quickly,
// this should filter out most of non-uml blocks
//
if (openChar !== state.src.charCodeAt(start)) { return false }
// Check out the rest of the marker string
//
for (i = 0; i < openMarker.length; ++i) {
if (openMarker[i] !== state.src[start + i]) { return false }
}
markup = state.src.slice(start, start + i)
params = state.src.slice(start + i, max)
// Since start is found, we can report success here in validation mode
//
if (silent) { return true }
// Search for the end of the block
//
nextLine = startLine
for (;;) {
nextLine++
if (nextLine >= endLine) {
// unclosed block should be autoclosed by end of document.
// also block seems to be autoclosed by end of parent
break
}
start = state.bMarks[nextLine] + state.tShift[nextLine]
max = state.eMarks[nextLine]
if (start < max && state.sCount[nextLine] < state.blkIndent) {
// non-empty line with negative indent should stop the list:
// - ```
// test
break
}
if (closeChar !== state.src.charCodeAt(start)) {
// didn't find the closing fence
continue
}
if (state.sCount[nextLine] > state.sCount[startLine]) {
// closing fence should not be indented with respect of opening fence
continue
}
let closeMarkerMatched = true
for (i = 0; i < closeMarker.length; ++i) {
if (closeMarker[i] !== state.src[start + i]) {
closeMarkerMatched = false
break
}
}
if (!closeMarkerMatched) {
continue
}
// make sure tail has spaces only
if (state.skipSpaces(start + i) < max) {
continue
}
// found!
autoClosed = true
break
}
let contents = state.src
.split('\n')
.slice(startLine + 1, nextLine)
.join('\n')
// We generate a token list for the alt property, to mimic what the image parser does.
let altToken = []
// Remove leading space if any.
let alt = params ? params.slice(1) : 'uml diagram'
state.md.inline.parse(
alt,
state.md,
state.env,
altToken
)
let firstlf = contents.indexOf('\n')
if (firstlf === -1) firstlf = undefined
let diagramType = contents.substring(0, firstlf)
contents = contents.substring(firstlf + 1)
let result = zlib.deflateSync(contents).toString('base64').replace(/\+/g, '-').replace(/\//g, '_')
token = state.push('kroki', 'img', 0)
// alt is constructed from children. No point in populating it here.
token.attrs = [ [ 'src', `${server}/${diagramType}/svg/${result}` ], [ 'alt', '' ], ['class', 'uml-diagram prefetch-candidate'] ]
token.block = true
token.children = altToken
token.info = params
token.map = [ startLine, nextLine ]
token.markup = markup
state.line = nextLine + (autoClosed ? 1 : 0)
return true
}, {
alt: [ 'paragraph', 'reference', 'blockquote', 'list' ]
})
md.renderer.rules.kroki = md.renderer.rules.image
}, {
openMarker: conf.openMarker,
closeMarker: conf.closeMarker,
server: conf.server
})
}
}
key: markdownMathjax
title: Mathjax
description: LaTeX Math + Chemical Expression Typesetting Renderer
author: requarks.io
icon: mdi-math-integral
enabledDefault: false
dependsOn: markdown-core
props:
useInline:
type: Boolean
default: true
title: Inline TeX
hint: Process inline TeX expressions surrounded by $ symbols.
order: 1
useBlocks:
type: Boolean
default: true
title: TeX Blocks
hint: Process TeX blocks enclosed by $$ symbols.
order: 2
const mjax = require('mathjax')
// ------------------------------------
// Markdown - MathJax Renderer
// ------------------------------------
const extensions = [
'bbox',
'boldsymbol',
'braket',
'color',
'extpfeil',
'mhchem',
'newcommand',
'unicode',
'verb'
]
module.exports = {
async init (mdinst, conf) {
const MathJax = await mjax.init({
loader: {
require: require,
paths: { mathjax: 'mathjax/es5' },
load: [
'input/tex',
'output/svg',
...extensions.map(e => `[tex]/${e}`)
]
},
tex: {
packages: {'[+]': extensions}
}
})
if (conf.useInline) {
mdinst.inline.ruler.after('escape', 'mathjax_inline', mathjaxInline)
mdinst.renderer.rules.mathjax_inline = (tokens, idx) => {
try {
const result = MathJax.tex2svg(tokens[idx].content, {
display: false
})
return MathJax.startup.adaptor.innerHTML(result)
} catch (err) {
WIKI.logger.warn(err)
return tokens[idx].content
}
}
}
if (conf.useBlocks) {
mdinst.block.ruler.after('blockquote', 'mathjax_block', mathjaxBlock, {
alt: [ 'paragraph', 'reference', 'blockquote', 'list' ]
})
mdinst.renderer.rules.mathjax_block = (tokens, idx) => {
try {
const result = MathJax.tex2svg(tokens[idx].content, {
display: true
})
return `<p>` + MathJax.startup.adaptor.innerHTML(result) + `</p>`
} catch (err) {
WIKI.logger.warn(err)
return tokens[idx].content
}
}
}
}
}
// Test if potential opening or closing delimieter
// Assumes that there is a "$" at state.src[pos]
function isValidDelim (state, pos) {
let prevChar
let nextChar
let max = state.posMax
let canOpen = true
let canClose = true
prevChar = pos > 0 ? state.src.charCodeAt(pos - 1) : -1
nextChar = pos + 1 <= max ? state.src.charCodeAt(pos + 1) : -1
// Check non-whitespace conditions for opening and closing, and
// check that closing delimeter isn't followed by a number
if (prevChar === 0x20/* " " */ || prevChar === 0x09/* \t */ ||
(nextChar >= 0x30/* "0" */ && nextChar <= 0x39/* "9" */)) {
canClose = false
}
if (nextChar === 0x20/* " " */ || nextChar === 0x09/* \t */) {
canOpen = false
}
return {
canOpen: canOpen,
canClose: canClose
}
}
function mathjaxInline (state, silent) {
let start, match, token, res, pos
if (state.src[state.pos] !== '$') { return false }
res = isValidDelim(state, state.pos)
if (!res.canOpen) {
if (!silent) { state.pending += '$' }
state.pos += 1
return true
}
// First check for and bypass all properly escaped delimieters
// This loop will assume that the first leading backtick can not
// be the first character in state.src, which is known since
// we have found an opening delimieter already.
start = state.pos + 1
match = start
while ((match = state.src.indexOf('$', match)) !== -1) {
// Found potential $, look for escapes, pos will point to
// first non escape when complete
pos = match - 1
while (state.src[pos] === '\\') { pos -= 1 }
// Even number of escapes, potential closing delimiter found
if (((match - pos) % 2) === 1) { break }
match += 1
}
// No closing delimter found. Consume $ and continue.
if (match === -1) {
if (!silent) { state.pending += '$' }
state.pos = start
return true
}
// Check if we have empty content, ie: $$. Do not parse.
if (match - start === 0) {
if (!silent) { state.pending += '$$' }
state.pos = start + 1
return true
}
// Check for valid closing delimiter
res = isValidDelim(state, match)
if (!res.canClose) {
if (!silent) { state.pending += '$' }
state.pos = start
return true
}
if (!silent) {
token = state.push('mathjax_inline', 'math', 0)
token.markup = '$'
token.content = state.src.slice(start, match)
}
state.pos = match + 1
return true
}
function mathjaxBlock (state, start, end, silent) {
let firstLine; let lastLine; let next; let lastPos; let found = false; let token
let pos = state.bMarks[start] + state.tShift[start]
let max = state.eMarks[start]
if (pos + 2 > max) { return false }
if (state.src.slice(pos, pos + 2) !== '$$') { return false }
pos += 2
firstLine = state.src.slice(pos, max)
if (silent) { return true }
if (firstLine.trim().slice(-2) === '$$') {
// Single line expression
firstLine = firstLine.trim().slice(0, -2)
found = true
}
for (next = start; !found;) {
next++
if (next >= end) { break }
pos = state.bMarks[next] + state.tShift[next]
max = state.eMarks[next]
if (pos < max && state.tShift[next] < state.blkIndent) {
// non-empty line with negative indent should stop the list:
break
}
if (state.src.slice(pos, max).trim().slice(-2) === '$$') {
lastPos = state.src.slice(0, max).lastIndexOf('$$')
lastLine = state.src.slice(pos, lastPos)
found = true
}
}
state.line = next + 1
token = state.push('mathjax_block', 'math', 0)
token.block = true
token.content = (firstLine && firstLine.trim() ? firstLine + '\n' : '') +
state.getLines(start + 1, next, state.tShift[start], true) +
(lastLine && lastLine.trim() ? lastLine : '')
token.map = [ start, state.line ]
token.markup = '$$'
return true
}
key: markdownMultiTable
title: MultiMarkdown Table
description: Add MultiMarkdown table support
author: requarks.io
icon: mdi-table
enabledDefault: false
dependsOn: markdown-core
props:
multilineEnabled:
type: Boolean
title: Multiline
hint: Enable multiple lines rows
default: true
headerlessEnabled:
type: Boolean
title: Headerless
hint: Enable ommited table headers
default: true
rowspanEnabled:
type: Boolean
title: Rowspan
hint: Enable table row spans
default: true
const multiTable = require('markdown-it-multimd-table')
module.exports = {
init (md, conf) {
md.use(multiTable, {
multiline: conf.multilineEnabled,
rowspan: conf.rowspanEnabled,
headerless: conf.headerlessEnabled
})
}
}
key: markdownPlantuml
title: PlantUML
description: PlantUML Markdown Parser
author: ethanmdavidson
icon: mdi-sitemap
enabledDefault: true
dependsOn: markdown-core
props:
server:
type: String
default: https://plantuml.requarks.io
title: PlantUML Server
hint: PlantUML server used for image generation
order: 1
public: true
openMarker:
type: String
default: "```plantuml"
title: Open Marker
hint: String to use as opening delimiter
order: 2
public: true
closeMarker:
type: String
default: "```"
title: Close Marker
hint: String to use as closing delimiter
order: 3
public: true
imageFormat:
type: String
default: svg
title: Image Format
hint: Format to use for rendered PlantUML images
enum:
- svg
- png
- latex
- ascii
order: 4
public: true
const zlib = require('zlib')
// ------------------------------------
// Markdown - PlantUML Preprocessor
// ------------------------------------
module.exports = {
init (mdinst, conf) {
mdinst.use((md, opts) => {
const openMarker = opts.openMarker || '```plantuml'
const openChar = openMarker.charCodeAt(0)
const closeMarker = opts.closeMarker || '```'
const closeChar = closeMarker.charCodeAt(0)
const imageFormat = opts.imageFormat || 'svg'
const server = opts.server || 'https://plantuml.requarks.io'
md.block.ruler.before('fence', 'uml_diagram', (state, startLine, endLine, silent) => {
let nextLine
let markup
let params
let token
let i
let autoClosed = false
let start = state.bMarks[startLine] + state.tShift[startLine]
let max = state.eMarks[startLine]
// Check out the first character quickly,
// this should filter out most of non-uml blocks
//
if (openChar !== state.src.charCodeAt(start)) { return false }
// Check out the rest of the marker string
//
for (i = 0; i < openMarker.length; ++i) {
if (openMarker[i] !== state.src[start + i]) { return false }
}
markup = state.src.slice(start, start + i)
params = state.src.slice(start + i, max)
// Since start is found, we can report success here in validation mode
//
if (silent) { return true }
// Search for the end of the block
//
nextLine = startLine
for (;;) {
nextLine++
if (nextLine >= endLine) {
// unclosed block should be autoclosed by end of document.
// also block seems to be autoclosed by end of parent
break
}
start = state.bMarks[nextLine] + state.tShift[nextLine]
max = state.eMarks[nextLine]
if (start < max && state.sCount[nextLine] < state.blkIndent) {
// non-empty line with negative indent should stop the list:
// - ```
// test
break
}
if (closeChar !== state.src.charCodeAt(start)) {
// didn't find the closing fence
continue
}
if (state.sCount[nextLine] > state.sCount[startLine]) {
// closing fence should not be indented with respect of opening fence
continue
}
let closeMarkerMatched = true
for (i = 0; i < closeMarker.length; ++i) {
if (closeMarker[i] !== state.src[start + i]) {
closeMarkerMatched = false
break
}
}
if (!closeMarkerMatched) {
continue
}
// make sure tail has spaces only
if (state.skipSpaces(start + i) < max) {
continue
}
// found!
autoClosed = true
break
}
const contents = state.src
.split('\n')
.slice(startLine + 1, nextLine)
.join('\n')
// We generate a token list for the alt property, to mimic what the image parser does.
let altToken = []
// Remove leading space if any.
let alt = params ? params.slice(1) : 'uml diagram'
state.md.inline.parse(
alt,
state.md,
state.env,
altToken
)
const zippedCode = encode64(zlib.deflateRawSync('@startuml\n' + contents + '\n@enduml').toString('binary'))
token = state.push('uml_diagram', 'img', 0)
// alt is constructed from children. No point in populating it here.
token.attrs = [ [ 'src', `${server}/${imageFormat}/${zippedCode}` ], [ 'alt', '' ], ['class', 'uml-diagram prefetch-candidate'] ]
token.block = true
token.children = altToken
token.info = params
token.map = [ startLine, nextLine ]
token.markup = markup
state.line = nextLine + (autoClosed ? 1 : 0)
return true
}, {
alt: [ 'paragraph', 'reference', 'blockquote', 'list' ]
})
md.renderer.rules.uml_diagram = md.renderer.rules.image
}, {
openMarker: conf.openMarker,
closeMarker: conf.closeMarker,
imageFormat: conf.imageFormat,
server: conf.server
})
}
}
function encode64 (data) {
let r = ''
for (let i = 0; i < data.length; i += 3) {
if (i + 2 === data.length) {
r += append3bytes(data.charCodeAt(i), data.charCodeAt(i + 1), 0)
} else if (i + 1 === data.length) {
r += append3bytes(data.charCodeAt(i), 0, 0)
} else {
r += append3bytes(data.charCodeAt(i), data.charCodeAt(i + 1), data.charCodeAt(i + 2))
}
}
return r
}
function append3bytes (b1, b2, b3) {
let c1 = b1 >> 2
let c2 = ((b1 & 0x3) << 4) | (b2 >> 4)
let c3 = ((b2 & 0xF) << 2) | (b3 >> 6)
let c4 = b3 & 0x3F
let r = ''
r += encode6bit(c1 & 0x3F)
r += encode6bit(c2 & 0x3F)
r += encode6bit(c3 & 0x3F)
r += encode6bit(c4 & 0x3F)
return r
}
function encode6bit(raw) {
let b = raw
if (b < 10) {
return String.fromCharCode(48 + b)
}
b -= 10
if (b < 26) {
return String.fromCharCode(65 + b)
}
b -= 26
if (b < 26) {
return String.fromCharCode(97 + b)
}
b -= 26
if (b === 0) {
return '-'
}
if (b === 1) {
return '_'
}
return '?'
}
key: markdownSupsub
title: Subscript/Superscript
description: Parse subscript and superscript tags
author: requarks.io
icon: mdi-format-superscript
enabledDefault: true
dependsOn: markdown-core
props:
subEnabled:
type: Boolean
title: Subscript
hint: Enable subscript tags
default: true
supEnabled:
type: Boolean
title: Superscript
hint: Enable superscript tags
default: true
const mdSub = require('markdown-it-sub')
const mdSup = require('markdown-it-sup')
// ------------------------------------
// Markdown - Subscript / Superscript
// ------------------------------------
module.exports = {
init (md, conf) {
if (conf.subEnabled) {
md.use(mdSub)
}
if (conf.supEnabled) {
md.use(mdSup)
}
}
}
key: markdownTasklists
title: Task Lists
description: Parse task lists to checkboxes
author: requarks.io
icon: mdi-format-list-checks
enabledDefault: true
dependsOn: markdown-core
props: {}
const mdTaskLists = require('markdown-it-task-lists')
// ------------------------------------
// Markdown - Task Lists
// ------------------------------------
module.exports = {
init (md, conf) {
md.use(mdTaskLists, { label: false, labelAfter: false })
}
}
key: openapiCore
title: Core
description: Basic OpenAPI Parser
author: requarks.io
input: openapi
output: html
icon: mdi-api
props: {}
const _ = require('lodash')
module.exports = {
async render() {
let output = this.input
for (let child of this.children) {
const renderer = require(`../${_.kebabCase(child.key)}/renderer.js`)
output = await renderer.init(output, child.config)
}
return output
}
}
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40" width="80px" height="80px"><path fill="#98ccfd" d="M2.5 30.658L2.5 7.38 20 2.519 37.5 7.38 37.5 30.658 20 37.463z"/><path fill="#4788c7" d="M20,3.038L37,7.76v22.556l-17,6.611L3,30.316V7.76L20,3.038 M20,2L2,7v24l18,7l18-7V7L20,2L20,2z"/><path fill="#b6dcfe" d="M2.5 7.62L2.5 7.38 20 2.519 37.5 7.38 37.5 7.62 20 12.481z"/><path fill="#4788c7" d="M20,3.038L36.064,7.5L20,11.962L3.936,7.5L20,3.038 M20,2L2,7v1l18,5l18-5V7L20,2L20,2z"/><path fill="#98ccfd" d="M20.5 12.38L37.5 7.658 37.5 30.658 20.5 37.269z"/><path fill="#4788c7" d="M37,8.316v22l-16,6.222V12.76L37,8.316 M38,7l-18,5v26l18-7V7L38,7z"/><path fill="#fff" d="M16.408 31.227l-2.739-.923-1.008-3.471L9.03 25.646 8.25 28.466l-2.413-.803 3.715-12.818 2.957.781L16.408 31.227zM12.036 24.225l-1.176-5.15c-.087-.386-.149-.836-.185-1.351l-.061-.017c-.025.416-.088.818-.189 1.204l-1.161 4.457L12.036 24.225zM25.41 31.018V15.831l4.166-1.298c1.238-.386 2.178-.379 2.83.014.646.39.968 1.123.968 2.203 0 .782-.195 1.534-.588 2.256-.394.726-.9 1.313-1.519 1.761v.041c.776-.143 1.391.023 1.849.496.455.471.682 1.159.682 2.067 0 1.326-.35 2.512-1.053 3.564-.709 1.062-1.687 1.842-2.943 2.34L25.41 31.018zM28.011 17.505v3.548l1.12-.387c.521-.18.929-.488 1.225-.923.296-.434.443-.941.443-1.523 0-1.082-.607-1.425-1.833-1.026L28.011 17.505zM28.011 23.558v3.945l1.378-.527c.581-.222 1.034-.574 1.36-1.055.325-.479.487-1.025.487-1.64 0-.587-.159-.99-.479-1.209-.321-.22-.771-.226-1.352-.016L28.011 23.558zM19.52 10.459c-1.103-.149-2.278-.44-3.528-.872-1.629-.563-2.493-1.157-2.59-1.782s.55-1.189 1.942-1.692c1.482-.536 3.209-.789 5.18-.76 1.971.029 3.794.333 5.467.911 1.037.358 1.797.702 2.279 1.03l-2.01.727c-.34-.38-.965-.727-1.873-1.04-.997-.345-2.076-.524-3.235-.538s-2.207.149-3.141.487c-.896.324-1.321.682-1.276 1.074s.557.757 1.535 1.095c.933.322 1.985.546 3.157.67L19.52 10.459z"/></svg>
\ No newline at end of file
......@@ -449,7 +449,7 @@
"admin.navigation.visibilityMode.all": "Visible to everyone",
"admin.navigation.visibilityMode.restricted": "Visible to select groups...",
"admin.pages.title": "Pages",
"admin.rendering.subtitle": "Configure the page rendering pipeline",
"admin.rendering.subtitle": "Configure the content rendering pipeline",
"admin.rendering.title": "Rendering",
"admin.scheduler.active": "Active",
"admin.scheduler.activeNone": "There are no active jobs at the moment.",
......
......@@ -157,7 +157,7 @@ q-layout.admin(view='hHh Lpr lff')
q-item-section {{ t('admin.mail.title') }}
q-item-section(side)
status-light(:color='adminStore.info.isMailConfigured ? `positive` : `warning`')
q-item(to='/_admin/rendering', v-ripple, active-class='bg-primary text-white', disabled, v-if='flagsStore.experimental')
q-item(to='/_admin/rendering', v-ripple, active-class='bg-primary text-white')
q-item-section(avatar)
q-icon(name='img:/_assets/icons/fluent-rich-text-converter.svg')
q-item-section {{ t('admin.rendering.title') }}
......
......@@ -51,7 +51,7 @@ const routes = [
{ path: 'icons', component: () => import('pages/AdminIcons.vue') },
{ path: 'instances', component: () => import('pages/AdminInstances.vue') },
{ path: 'mail', component: () => import('pages/AdminMail.vue') },
// { path: 'rendering', component: () => import('pages/AdminRendering.vue') },
{ path: 'rendering', component: () => import('pages/AdminRendering.vue') },
{ path: 'scheduler', component: () => import('pages/AdminScheduler.vue') },
{ path: 'security', component: () => import('pages/AdminSecurity.vue') },
{ path: 'system', component: () => import('pages/AdminSystem.vue') },
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment