From 7334362b77935aba8fa1329d947a9fd41564f5c4 Mon Sep 17 00:00:00 2001 From: Joffrey JAFFEUX Date: Wed, 24 Apr 2019 10:37:34 +0200 Subject: [PATCH] FEATURE: generic theme component bbcode wrapper (#7400) Usage: ``` [wrap=name foo=bar] hello world [/wrap] ``` --- app/assets/javascripts/markdown-it-bundle.js | 1 + .../engines/discourse-markdown/d-wrap.js.es6 | 66 ++++++++++++++ spec/components/pretty_text_spec.rb | 90 +++++++++++++++++++ 3 files changed, 157 insertions(+) create mode 100644 app/assets/javascripts/pretty-text/engines/discourse-markdown/d-wrap.js.es6 diff --git a/app/assets/javascripts/markdown-it-bundle.js b/app/assets/javascripts/markdown-it-bundle.js index eba760fd6b5..6f88c5abbe2 100644 --- a/app/assets/javascripts/markdown-it-bundle.js +++ b/app/assets/javascripts/markdown-it-bundle.js @@ -16,3 +16,4 @@ //= require ./pretty-text/engines/discourse-markdown/text-post-process //= require ./pretty-text/engines/discourse-markdown/image-protocol //= require ./pretty-text/engines/discourse-markdown/inject-line-number +//= require ./pretty-text/engines/discourse-markdown/d-wrap diff --git a/app/assets/javascripts/pretty-text/engines/discourse-markdown/d-wrap.js.es6 b/app/assets/javascripts/pretty-text/engines/discourse-markdown/d-wrap.js.es6 new file mode 100644 index 00000000000..4b4b4ce4d20 --- /dev/null +++ b/app/assets/javascripts/pretty-text/engines/discourse-markdown/d-wrap.js.es6 @@ -0,0 +1,66 @@ +import { parseBBCodeTag } from "pretty-text/engines/discourse-markdown/bbcode-block"; + +const WRAP_CLASS = "d-wrap"; + +function parseAttributes(tagInfo) { + const attributes = tagInfo.attrs._default || ""; + + return ( + parseBBCodeTag(`[wrap wrap=${attributes}]`, 0, attributes.length + 12) + .attrs || {} + ); +} + +function applyDataAttributes(token, state, attributes) { + Object.keys(attributes).forEach(tag => { + const value = state.md.utils.escapeHtml(attributes[tag]); + tag = state.md.utils.escapeHtml(tag.replace(/[^a-z0-9\-]/g, "")); + + if (value && tag && tag.length > 1) { + token.attrs.push([`data-${tag}`, value]); + } + }); +} + +const blockRule = { + tag: "wrap", + + before(state, tagInfo) { + let token = state.push("wrap_open", "div", 1); + token.attrs = [["class", WRAP_CLASS]]; + + applyDataAttributes(token, state, parseAttributes(tagInfo)); + }, + + after(state) { + state.push("wrap_close", "div", -1); + } +}; + +const inlineRule = { + tag: "wrap", + + replace(state, tagInfo, content) { + let token = state.push("wrap_open", "span", 1); + token.attrs = [["class", WRAP_CLASS]]; + + applyDataAttributes(token, state, parseAttributes(tagInfo)); + + if (content) { + token = state.push("text", "", 0); + token.content = content; + } + + token = state.push("wrap_close", "span", -1); + return true; + } +}; + +export function setup(helper) { + helper.registerPlugin(md => { + md.inline.bbcode.ruler.push("inline-wrap", inlineRule); + md.block.bbcode.ruler.push("block-wrap", blockRule); + }); + + helper.whiteList([`div.${WRAP_CLASS}`, `span.${WRAP_CLASS}`, "span[data-*]"]); +} diff --git a/spec/components/pretty_text_spec.rb b/spec/components/pretty_text_spec.rb index 902f8046526..b76a253b84b 100644 --- a/spec/components/pretty_text_spec.rb +++ b/spec/components/pretty_text_spec.rb @@ -1325,4 +1325,94 @@ HTML expect(cooked).to eq(html) end + describe "d-wrap" do + it "wraps the [wrap] tag inline" do + cooked = PrettyText.cook("[wrap=toc]taco[/wrap]") + + html = <<~HTML +
+

taco

+
+ HTML + + expect(cooked).to eq(html.strip) + + cooked = PrettyText.cook("Hello [wrap=toc id=1]taco[/wrap] world") + + html = <<~HTML +

Hello taco world

+ HTML + + expect(cooked).to eq(html.strip) + end + + it "wraps the [wrap] tag in block" do + md = <<~MD + [wrap=toc] + taco + [/wrap] + MD + + cooked = PrettyText.cook(md) + + html = <<~HTML +
+

taco

+
+ HTML + + expect(cooked).to eq(html.strip) + end + + it "wraps the [wrap] tag without content" do + md = <<~MD + [wrap=toc] + [/wrap] + MD + + cooked = PrettyText.cook(md) + + html = <<~HTML +
+ HTML + + expect(cooked).to eq(html.strip) + end + + it "adds attributes as data-attributes" do + cooked = PrettyText.cook("[wrap=toc name=\"pepper bell\" id=1]taco[/wrap]") + + html = <<~HTML +
+

taco

+
+ HTML + + expect(cooked).to eq(html.strip) + end + + it "prevents xss" do + cooked = PrettyText.cook('[wrap=toc foo=""]taco[/wrap]') + + html = <<~HTML +
+

taco

+
+ HTML + + expect(cooked).to eq(html.strip) + end + + it "allows a limited set of attributes chars" do + cooked = PrettyText.cook('[wrap=toc fo@"èk-"!io=bar]taco[/wrap]') + + html = <<~HTML +
+

taco

+
+ HTML + + expect(cooked).to eq(html.strip) + end + end end