diff --git a/client/components/editor/editor-markdown.vue b/client/components/editor/editor-markdown.vue
index baee118d0..606ba625f 100644
--- a/client/components/editor/editor-markdown.vue
+++ b/client/components/editor/editor-markdown.vue
@@ -224,6 +224,7 @@ import mermaid from 'mermaid'
// Helpers
import katexHelper from './common/katex'
+import spoiler from './markdown/spoiler'
import tabsetHelper from './markdown/tabset'
import cmFold from './common/cmFold'
@@ -264,6 +265,7 @@ const md = new MarkdownIt({
.use(mdAttrs, {
allowedAttributes: ['id', 'class', 'target']
})
+ .use(spoiler)
.use(mdDecorate)
.use(underline)
.use(mdEmoji)
diff --git a/client/components/editor/markdown/spoiler.js b/client/components/editor/markdown/spoiler.js
new file mode 100644
index 000000000..d742b1798
--- /dev/null
+++ b/client/components/editor/markdown/spoiler.js
@@ -0,0 +1,12 @@
+const container = require('markdown-it-container')
+
+module.exports = md => md.use(container, 'spoiler', {
+ validate: params => /^spoiler(?:\s+.*)?$/.test(params.trim()),
+ render(tokens, idx) {
+ if (tokens[idx].nesting === 1) {
+ const title = tokens[idx].info.trim().slice('spoiler'.length).trim() || 'Spoiler'
+ return `${md.utils.escapeHtml(title)}
\n`
+ }
+ return ' \n'
+ }
+})
diff --git a/server/modules/rendering/markdown-core/renderer.js b/server/modules/rendering/markdown-core/renderer.js
index 5723214e7..fc83c8823 100644
--- a/server/modules/rendering/markdown-core/renderer.js
+++ b/server/modules/rendering/markdown-core/renderer.js
@@ -2,6 +2,7 @@ const md = require('markdown-it')
const mdAttrs = require('markdown-it-attrs')
const mdDecorate = require('markdown-it-decorate')
const _ = require('lodash')
+const spoiler = require('./spoiler')
const underline = require('./underline')
const quoteStyles = {
@@ -43,6 +44,7 @@ module.exports = {
mkdown.use(mdAttrs, {
allowedAttributes: ['id', 'class', 'target']
})
+ mkdown.use(spoiler)
mkdown.use(mdDecorate)
for (let child of this.children) {
diff --git a/server/modules/rendering/markdown-core/spoiler.js b/server/modules/rendering/markdown-core/spoiler.js
new file mode 100644
index 000000000..d742b1798
--- /dev/null
+++ b/server/modules/rendering/markdown-core/spoiler.js
@@ -0,0 +1,12 @@
+const container = require('markdown-it-container')
+
+module.exports = md => md.use(container, 'spoiler', {
+ validate: params => /^spoiler(?:\s+.*)?$/.test(params.trim()),
+ render(tokens, idx) {
+ if (tokens[idx].nesting === 1) {
+ const title = tokens[idx].info.trim().slice('spoiler'.length).trim() || 'Spoiler'
+ return `${md.utils.escapeHtml(title)}
\n`
+ }
+ return ' \n'
+ }
+})
diff --git a/server/test/modules/rendering/markdown-core.test.js b/server/test/modules/rendering/markdown-core.test.js
new file mode 100644
index 000000000..4c0c02526
--- /dev/null
+++ b/server/test/modules/rendering/markdown-core.test.js
@@ -0,0 +1,38 @@
+const renderer = require('../../../modules/rendering/markdown-core/renderer')
+
+const render = input => renderer.render.call({
+ input,
+ children: [],
+ config: {
+ allowHTML: true,
+ linebreaks: true,
+ linkify: true,
+ typographer: false,
+ quotes: 'English',
+ underline: false
+ }
+})
+
+describe('markdown-core spoiler container', () => {
+ it('renders a closed details element with a default title', async () => {
+ const output = await render('::: spoiler\nHidden **content**.\n:::')
+
+ expect(output).toContain('Spoiler
')
+ expect(output).toContain('Hidden content.
')
+ expect(output).toContain(' ')
+ })
+
+ it('supports a custom escaped title', async () => {
+ const output = await render('::: spoiler Reveal \nSecret\n:::')
+
+ expect(output).toContain('Reveal <script>alert(1)</script>')
+ expect(output).not.toContain('Reveal