feat: admin rendering (wip)

pull/6775/head
Nicolas Giard 2 years ago
parent 8c71470d3a
commit 9b2e0f79d5
No known key found for this signature in database
GPG Key ID: 85061B8F9D55B7C8

@ -1,8 +0,0 @@
key: markdownAbbr
title: Abbreviations
description: Parse abbreviations into abbr tags
author: requarks.io
icon: mdi-contain-start
enabledDefault: true
dependsOn: markdown-core
props: {}

@ -1,11 +0,0 @@
const mdAbbr = require('markdown-it-abbr')
// ------------------------------------
// Markdown - Abbreviations
// ------------------------------------
module.exports = {
init (md, conf) {
md.use(mdAbbr)
}
}

@ -1,63 +0,0 @@
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

@ -1,53 +0,0 @@
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)
}
}

@ -1,12 +0,0 @@
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
}

@ -1,8 +0,0 @@
key: markdownEmoji
title: Emoji
description: Convert tags to emojis
author: requarks.io
icon: mdi-sticker-emoji
enabledDefault: true
dependsOn: markdown-core
props: {}

@ -1,20 +0,0 @@
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`
}
})
}
}
}

@ -1,13 +0,0 @@
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

@ -1,14 +0,0 @@
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)
})
}
}

@ -1,8 +0,0 @@
key: markdownFootnotes
title: Footnotes
description: Parse footnotes references
author: requarks.io
icon: mdi-page-layout-footer
enabledDefault: true
dependsOn: markdown-core
props: {}

@ -1,11 +0,0 @@
const mdFootnote = require('markdown-it-footnote')
// ------------------------------------
// Markdown - Footnotes
// ------------------------------------
module.exports = {
init (md, conf) {
md.use(mdFootnote)
}
}

@ -1,8 +0,0 @@
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: {}

@ -1,11 +0,0 @@
const mdImsize = require('markdown-it-imsize')
// ------------------------------------
// Markdown - Image Size
// ------------------------------------
module.exports = {
init (md, conf) {
md.use(mdImsize)
}
}

@ -1,20 +0,0 @@
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

File diff suppressed because it is too large Load Diff

@ -1,193 +0,0 @@
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
}

@ -1,29 +0,0 @@
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

@ -1,143 +0,0 @@
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
})
}
}

@ -1,20 +0,0 @@
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

@ -1,205 +0,0 @@
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
}

@ -1,23 +0,0 @@
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

@ -1,11 +0,0 @@
const multiTable = require('markdown-it-multimd-table')
module.exports = {
init (md, conf) {
md.use(multiTable, {
multiline: conf.multilineEnabled,
rowspan: conf.rowspanEnabled,
headerless: conf.headerlessEnabled
})
}
}

@ -1,41 +0,0 @@
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

@ -1,190 +0,0 @@
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 '?'
}

@ -1,18 +0,0 @@
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

@ -1,17 +0,0 @@
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)
}
}
}

@ -1,8 +0,0 @@
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: {}

@ -1,11 +0,0 @@
const mdTaskLists = require('markdown-it-task-lists')
// ------------------------------------
// Markdown - Task Lists
// ------------------------------------
module.exports = {
init (md, conf) {
md.use(mdTaskLists, { label: false, labelAfter: false })
}
}

@ -1,8 +0,0 @@
key: openapiCore
title: Core
description: Basic OpenAPI Parser
author: requarks.io
input: openapi
output: html
icon: mdi-api
props: {}

@ -1,14 +0,0 @@
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
}
}

@ -0,0 +1 @@
<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>

After

Width:  |  Height:  |  Size: 1.9 KiB

@ -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') }}

@ -11,7 +11,7 @@ q-page.admin-mail
icon='las la-question-circle'
flat
color='grey'
href='https://docs.js.wiki/admin/rendering'
:href='siteStore.docsBase + `/system/rendering`'
target='_blank'
type='a'
)
@ -19,7 +19,7 @@ q-page.admin-mail
icon='las la-redo-alt'
flat
color='secondary'
:loading='loading > 0'
:loading='state.loading > 0'
@click='load'
)
q-btn(
@ -28,284 +28,88 @@ q-page.admin-mail
:label='$t(`common.actions.apply`)'
color='secondary'
@click='save'
:disabled='loading > 0'
:disabled='state.loading > 0'
)
q-separator(inset)
//- v-container(fluid, grid-list-lg)
//- v-layout(row, wrap)
//- v-flex(xs12)
//- .admin-header
//- img.animated.fadeInUp(src='/_assets/svg/icon-process.svg', alt='Rendering', style='width: 80px;')
//- .admin-header-title
//- .headline.primary--text.animated.fadeInLeft {{ $t('admin.rendering.title') }}
//- .subtitle-1.grey--text.animated.fadeInLeft.wait-p4s {{ $t('admin.rendering.subtitle') }}
//- v-spacer
//- v-btn.animated.fadeInDown.wait-p3s(icon, outlined, color='grey', href='https://docs.requarks.io/rendering', target='_blank')
//- v-icon mdi-help-circle
//- v-btn.mx-3.animated.fadeInDown.wait-p2s(icon, outlined, color='grey', @click='refresh')
//- v-icon mdi-refresh
//- v-btn.animated.fadeInDown(color='success', @click='save', depressed, large)
//- v-icon(left) mdi-check
//- span {{$t('common.actions.apply')}}
//- v-flex.animated.fadeInUp(lg3, xs12)
//- v-toolbar(
//- color='blue darken-2'
//- dense
//- flat
//- dark
//- )
//- .subtitle-1 Pipeline
//- v-expansion-panels.adm-rendering-pipeline(
//- v-model='selectedCore'
//- accordion
//- mandatory
//- )
//- v-expansion-panel(
//- v-for='core in renderers'
//- :key='core.key'
//- )
//- v-expansion-panel-header(
//- hide-actions
//- ripple
//- )
//- v-toolbar(
//- color='blue'
//- dense
//- dark
//- flat
//- )
//- v-spacer
//- .body-2 {{core.input}}
//- v-icon.mx-2 mdi-arrow-right-circle
//- .caption {{core.output}}
//- v-spacer
//- v-expansion-panel-content
//- v-list.py-0(two-line, dense)
//- template(v-for='(rdr, n) in core.children')
//- v-list-item(
//- :key='rdr.key'
//- @click='selectRenderer(rdr.key)'
//- :class='currentRenderer.key === rdr.key ? ($vuetify.theme.dark ? `grey darken-4-l4` : `blue lighten-5`) : ``'
//- )
//- v-list-item-avatar(size='24', tile)
//- v-icon(:color='currentRenderer.key === rdr.key ? "primary" : "grey"') {{rdr.icon}}
//- v-list-item-content
//- v-list-item-title {{rdr.title}}
//- v-list-item-subtitle: .caption {{rdr.description}}
//- v-list-item-avatar(size='24')
//- status-indicator(v-if='rdr.isEnabled', positive, pulse)
//- status-indicator(v-else, negative, pulse)
//- v-divider.my-0(v-if='n < core.children.length - 1')
.row.q-pa-md.q-col-gutter-md
.col-auto
q-card.rounded-borders.bg-dark
q-list(
style='min-width: 300px;'
padding
dark
)
q-item(
v-for='rdr of state.renderers'
:key='rdr.key'
active-class='bg-primary text-white'
:active='state.selectedRenderer === rdr.id'
@click='state.selectedRenderer = rdr.id'
clickable
)
q-item-section(side)
q-icon(:name='`img:` + rdr.icon')
q-item-section
q-item-label {{rdr.title}}
q-item-label(caption) {{rdr.description}}
q-item-section(side)
status-light(:color='rdr.isEnabled ? `positive` : `negative`', :pulse='rdr.isEnabled')
.col
.row.q-col-gutter-md
.col-12.col-lg
//- v-flex(lg9, xs12)
//- v-card.wiki-form.animated.fadeInUp
//- v-toolbar(
//- color='indigo'
//- dark
//- flat
//- dense
//- )
//- v-icon.mr-2 {{currentRenderer.icon}}
//- .subtitle-1 {{currentRenderer.title}}
//- v-spacer
//- v-switch(
//- dark
//- color='white'
//- label='Enabled'
//- v-model='currentRenderer.isEnabled'
//- hide-details
//- inset
//- )
//- v-card-info(color='blue')
//- div
//- div {{currentRenderer.description}}
//- span.caption: a(href='https://docs.requarks.io/en/rendering', target='_blank') Documentation
//- v-card-text.pb-4.pl-4
//- .overline.mb-5 Rendering Module Configuration
//- .body-2.ml-3(v-if='!currentRenderer.config || currentRenderer.config.length < 1'): em This rendering module has no configuration options you can modify.
//- template(v-else, v-for='(cfg, idx) in currentRenderer.config')
//- v-select(
//- v-if='cfg.value.type === "string" && cfg.value.enum'
//- outlined
//- :items='cfg.value.enum'
//- :key='cfg.key'
//- :label='cfg.value.title'
//- v-model='cfg.value.value'
//- :hint='cfg.value.hint ? cfg.value.hint : ""'
//- persistent-hint
//- :class='cfg.value.hint ? "mb-2" : ""'
//- color='indigo'
//- )
//- v-switch(
//- v-else-if='cfg.value.type === "boolean"'
//- :key='cfg.key'
//- :label='cfg.value.title'
//- v-model='cfg.value.value'
//- color='indigo'
//- :hint='cfg.value.hint ? cfg.value.hint : ""'
//- persistent-hint
//- inset
//- )
//- v-text-field(
//- v-else
//- outlined
//- :key='cfg.key'
//- :label='cfg.value.title'
//- v-model='cfg.value.value'
//- :hint='cfg.value.hint ? cfg.value.hint : ""'
//- persistent-hint
//- :class='cfg.value.hint ? "mb-2" : ""'
//- color='indigo'
//- )
//- v-divider.my-5(v-if='idx < currentRenderer.config.length - 1')
//- v-card-chin
//- v-spacer
//- .caption.pr-3.grey--text Module: {{ currentRenderer.key }}
</template>
<script>
<script setup>
import { cloneDeep, concat, filter, find, findIndex, reduce, reverse, sortBy } from 'lodash-es'
import { DepGraph } from 'dependency-graph'
import gql from 'graphql-tag'
export default {
data () {
return {
selectedCore: -1,
renderers: [],
currentRenderer: {}
}
},
watch: {
renderers (newValue, oldValue) {
setTimeout(() => {
this.selectedCore = findIndex(newValue, ['key', 'markdownCore'])
this.selectRenderer('markdownCore')
}, 500)
}
},
methods: {
async load () {
this.loading++
try {
const resp = await this.$apollo.query({
query: gql`
query getRenderingConfig {
mailConfig {
senderName
senderEmail
host
port
secure
verifySSL
user
pass
useDKIM
dkimDomainName
dkimKeySelector
dkimPrivateKey
}
}
`,
fetchPolicy: 'no-cache'
})
if (!resp?.data?.mailConfig) {
throw new Error('Failed to fetch mail config.')
}
const renderers = cloneDeep(resp.data.rendering.renderers).map(str => ({
...str,
config: sortBy(str.config.map(cfg => ({
...cfg,
value: JSON.parse(cfg.value)
})), [t => t.value.order])
}))
// Build tree
const graph = new DepGraph({ circular: true })
const rawCores = filter(renderers, ['dependsOn', null]).map(core => {
core.children = concat([cloneDeep(core)], filter(renderers, ['dependsOn', core.key]))
return core
})
// Build dependency graph
rawCores.forEach(core => { graph.addNode(core.key) })
rawCores.forEach(core => {
rawCores.forEach(coreTarget => {
if (core.key !== coreTarget.key) {
if (core.output === coreTarget.input) {
graph.addDependency(core.key, coreTarget.key)
}
}
})
})
// Reorder cores in reverse dependency order
const orderedCores = []
reverse(graph.overallOrder()).forEach(coreKey => {
orderedCores.push(find(rawCores, ['key', coreKey]))
})
this.renderers = orderedCores
} catch (err) {
this.$q.notify({
type: 'negative',
message: 'Failed to fetch mail config',
caption: err.message
})
}
this.loading--
},
selectRenderer (key) {
this.renderers.forEach(rdr => {
if (rdr.children.some(c => c.key === key)) {
this.currentRenderer = find(rdr.children, ['key', key])
}
})
},
async refresh () {
await this.$apollo.queries.renderers.refetch()
this.$store.commit('showNotification', {
message: 'Rendering active configuration has been reloaded.',
style: 'success',
icon: 'cached'
})
},
async save () {
this.$store.commit('loadingStart', 'admin-rendering-saverenderers')
await this.$apollo.mutate({
mutation: null,
variables: {
renderers: reduce(this.renderers, (result, core) => {
result.push(...core.children.map(rd => ({
key: rd.key,
isEnabled: rd.isEnabled,
config: rd.config.map(cfg => ({ key: cfg.key, value: JSON.stringify({ v: cfg.value.value }) }))
})))
return result
}, [])
}
})
this.$store.commit('showNotification', {
message: 'Rendering configuration saved successfully.',
style: 'success',
icon: 'check'
})
this.$store.commit('loadingStop', 'admin-rendering-saverenderers')
}
}
}
</script>
import { useI18n } from 'vue-i18n'
import { useMeta, useQuasar } from 'quasar'
import { computed, onMounted, reactive, watch } from 'vue'
import { useAdminStore } from 'src/stores/admin'
import { useSiteStore } from 'src/stores/site'
// QUASAR
const $q = useQuasar()
// STORES
const adminStore = useAdminStore()
const siteStore = useSiteStore()
// I18N
const { t } = useI18n()
<style lang='scss'>
.adm-rendering-pipeline {
.v-expansion-panel--active .v-expansion-panel-header {
min-height: 0;
}
// META
.v-expansion-panel-header {
padding: 0;
margin-top: 1px;
}
useMeta({
title: t('admin.rendering.title')
})
// DATA
const state = reactive({
renderers: [
{ id: '123', title: 'Core', description: 'Base HTML Transformer', isEnabled: true, icon: '/_assets/icons/ultraviolet-brick.svg' }
],
selectedRenderer: '',
loading: 0
})
// METHODS
async function load () {
.v-expansion-panel-content__wrap {
padding: 0;
}
}
</style>
async function save () {
}
</script>

@ -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') },

Loading…
Cancel
Save