diff --git a/plugins/rss/bbcode.js b/plugins/rss/bbcode.js new file mode 100644 index 000000000..cfc74a37b --- /dev/null +++ b/plugins/rss/bbcode.js @@ -0,0 +1,328 @@ +const colorNamePattern = + /^(?:aliceblue|antiquewhite|aqua|aquamarine|azure|beige|bisque|black|blanchedalmond|blue|blueviolet|brown|burlywood|cadetblue|chartreuse|chocolate|coral|cornflowerblue|cornsilk|crimson|cyan|darkblue|darkcyan|darkgoldenrod|darkgray|darkgreen|darkkhaki|darkmagenta|darkolivegreen|darkorange|darkorchid|darkred|darksalmon|darkseagreen|darkslateblue|darkslategray|darkturquoise|darkviolet|deeppink|deepskyblue|dimgray|dodgerblue|firebrick|floralwhite|forestgreen|fuchsia|gainsboro|ghostwhite|gold|goldenrod|gray|green|greenyellow|honeydew|hotpink|indianred|indigo|ivory|khaki|lavender|lavenderblush|lawngreen|lemonchiffon|lightblue|lightcoral|lightcyan|lightgoldenrodyellow|lightgray|lightgreen|lightpink|lightsalmon|lightseagreen|lightskyblue|lightslategray|lightsteelblue|lightyellow|lime|limegreen|linen|magenta|maroon|mediumaquamarine|mediumblue|mediumorchid|mediumpurple|mediumseagreen|mediumslateblue|mediumspringgreen|mediumturquoise|mediumvioletred|midnightblue|mintcream|mistyrose|moccasin|navajowhite|navy|oldlace|olive|olivedrab|orange|orangered|orchid|palegoldenrod|palegreen|paleturquoise|palevioletred|papayawhip|peachpuff|peru|pink|plum|powderblue|purple|red|rosybrown|royalblue|saddlebrown|salmon|sandybrown|seagreen|seashell|sienna|silver|skyblue|slateblue|slategray|snow|springgreen|steelblue|tan|teal|thistle|tomato|turquoise|violet|wheat|white|whitesmoke|yellow|yellowgreen)$/; +const colorCodePattern = /^#?[a-f0-9]{6}$/i; +const restirctedFontSize = (value) => { + const size = Math.max(4, Math.min(40, Number.parseInt(value))); + return Number.isNaN(size) ? "normal" : `${size}px`; +}; +const bbNodeAttr = { + color: (value) => + colorNamePattern.test(value) + ? { color: value } + : colorCodePattern.test(value) + ? { color: value.startsWith("#") ? value : `#${value}` } + : null, + size: (value) => + ["small", "large", "normal"].includes(value) + ? "class" + : { "font-size": restirctedFontSize(value) }, + align: (value) => + ["left", "right", "center"].includes(value) ? "class" : null, + font: (value) => + [ + "times", + "courier", + "arial", + "serif", + "sans", + "fantasy", + "monospace", + "caps", + ].includes(value) + ? "class" + : null, +}; + +export function bbclassTransform(cfg) { + const node = cfg.node; + if (!["pre", "span"].includes(node.nodeName.toLowerCase())) { + return null; + } + let styles = {}; + let classes = []; + for (const bbClass of (node.attributes.class?.value || "").split(" ")) { + const [bbcode, key, value] = bbClass.split("-"); + if (bbcode === "bbcode") { + const style = key in bbNodeAttr ? bbNodeAttr[key](value || "") : null; + if (style !== null) { + if (style !== "class") { + styles = { ...styles, ...style }; + } + classes.push( + `${bbcode}-${key}` + (style === "class" ? `-${value}` : "") + ); + } + } + } + // replace existing attributes with style and class + [...node.attributes].forEach((attr) => node.removeAttribute(attr.name)); + for (const [name, value] of [ + [ + "style", + Object.entries(styles) + .map((e) => e.join(": ")) + .join("; "), + ], + ["class", classes.join(" ")], + ]) { + if (value) { + const attr = cfg.dom.createAttribute(name); + attr.value = value; + node.attributes[name] = attr; + } + } + return { + whitelist: Boolean(classes.length), + attr_whitelist: ["class", "style"], + node, + }; +} + +export function mapBBCodeToHTML(htmlText) { + const tags = { + ...Object.fromEntries( + [ + "b", + "i", + "sup", + "sub", + "table", + "thead", + "tbody", + "tfoot", + "tr", + "td", + "th", + "li", + ].map((t) => [t, () => [t]]) + ), + ...Object.fromEntries( + ["ul", "ol", "list"].map((name) => [ + name, + (_, content) => { + const htmlTag = name === "list" ? "ul" : name; + const ele = $(`<${htmlTag}>`).html(content); + const list = $(`<${htmlTag}>`); + let lastLiNode = $("
  • "); + for (const node of ele.contents()) { + if (node.nodeName.toLowerCase() === "li") { + // keep li nodes + lastLiNode = $(node); + } else { + if (node.nodeType === 3) { + // parse list items denoted by [*] and * + const items = String(node.nodeValue) + .replaceAll(/(^|[\s\]])\*\s/g, "[*]") + .split(/\[\*\]/g); + if (!list.children("li").length) { + // set text of empty list + list.text(items.shift()); + } + const firstItem = items.shift(); + if (firstItem) { + // add textnode to lastLiNode + lastLiNode.append(document.createTextNode(firstItem)); + } + for (const item of items) { + list.append(lastLiNode); + lastLiNode = $("
  • ").text(item); + } + } else { + // add some node to lastLiNode + lastLiNode.append($(node)); + } + } + // add lastLiNode to list (if not added already) + list.append(lastLiNode); + } + return [htmlTag, {}, list[0].innerHTML]; + }, + ]) + ), + u: () => ["ins"], + s: () => ["del"], + ...Object.fromEntries( + ["small", "normal", "large"].map((t) => [ + t, + () => ["span", { class: `bbcode-size-${t}` }], + ]) + ), + size: (arg) => ["span", { class: `bbcode-size-${arg}` }], + color: (arg) => ["span", { class: `bbcode-color-${arg}` }], + ...Object.fromEntries( + ["center", "left", "right"].map((t) => [ + t, + () => ["span", { class: `bbcode-align-${t}` }], + ]) + ), + ...Object.fromEntries( + ["font", "face"].map((t) => [ + t, + (arg) => ["span", { class: (arg || "").toLowerCase() }], + ]) + ), + style: (_, __, args) => [ + "span", + { + class: Object.entries(args) + .map(([k, v]) => `bbcode-${k}-${v}`) + .join(" "), + }, + ], + img: (arg, content, args) => [ + "img", + { + src: content, + ...Object.fromEntries( + (arg || "") + .split("x") + .map((v, k) => [["width", "height"][k], Number.parseInt(v)]) + .filter(([_, v]) => !Number.isNaN(v)) + ), + ...args, + }, + "", + ], + url: (arg, content) => ["a", { href: arg == null ? content : arg }], + email: (arg, content) => [ + "a", + { href: `mailto:${arg == null ? content : arg}` }, + ], + quote: (arg, content, args) => [ + "blockquote", + {}, + $("

    ").html(content)[0].outerHTML + + $("") + .addClass("bbcode-quote") + .text("-- ") + .append($("").text(arg || args["author"] || ""))[0].outerHTML, + ], + code: () => ["pre", { class: "bbcode-code" }], + spoiler: (arg, content) => [ + "details", + {}, + $("

    ").html(arg)[0].outerHTML + content, + ], + "bbcode-root": () => ["div"], + }; + + const trimArg = (arg) => + arg == null + ? null + : arg.startsWith('"') + ? arg.substring(1, arg.length - 1) + : arg.trim(); + const argsToDict = (args) => { + const dict = {}; + for (const match of args.matchAll( + /\s+?(?[a-z]+)=(?"(.*?)"|[^\s]*)/gi + )) { + const { name, arg } = match.groups; + if (name && arg) { + dict[name] = trimArg(arg); + } + } + return dict; + }; + + const nodeToElement = (node) => { + const htmlContent = node.children + .map((n) => (n.name ? nodeToElement(n).outerHTML : n)) + .join(""); + const arg = trimArg(node.arg); + const args = node.args ? argsToDict(node.args) : {}; + const [htmlTag, attribs, htmlContentProcessed] = tags[node.name]( + arg, + htmlContent, + args + ); + const ele = $(`<${htmlTag}>`) + .attr(attribs || {}) + .html(htmlContentProcessed || htmlContent)[0]; + return ele; + }; + + const simpleParamPattern = '\\s*?=\\s*?(?"(.*?)"|.*?)'; + const complexParamPattern = '(?(\\s+?[a-z]+=("(.*?)"|[^\\s]*?))+)'; + const tagPattern = new RegExp( + "\\[\\/?(?" + + Object.keys(tags).join("|") + + ")(" + + simpleParamPattern + + "|" + + complexParamPattern + + ")?\\s*?\\]", + "gsi" + ); + let nodeStack = [{ name: "bbcode-root", children: [] }]; + let offset = 0; + for (const match of htmlText.matchAll(tagPattern)) { + const parent = nodeStack[nodeStack.length - 1]; + const { name, arg, args } = match.groups; + const closing = match[0].startsWith("[/"); + // add textnode to parent + const textnode = match.input.substring(offset, match.index); + if (textnode) { + parent.children.push(textnode); + } + if (closing) { + if (parent.name === name && nodeStack.length > 1) { + nodeStack.pop(); + } else { + // encoutered unexpected close tag + nodeStack = [nodeStack[0]]; + } + } else { + const node = { name, arg, args, children: [] }; + parent.children.push(node); + // make curnode to parent node + nodeStack.push(node); + } + offset = match.index + match[0].length; + } + nodeStack[nodeStack.length - 1].children.push( + htmlText.substring(offset, htmlText.length) + ); + const htmlContent = nodeToElement(nodeStack[0]).innerHTML; + + // Support for some emoticons from WhatCD/Gazelle (https://github.com/WhatCD/Gazelle/tree/master/static/common/smileys) + // :code: => utf8 emoticon (https://utf8-icons.com/subset/emoticons) + const emoticons = { + smile: "🙂", + blank: "😐", + biggrin: "😁", + angry: "😡", + blush: "😊", + cool: "😎", + crying: "😢", + frown: "🙁", + unsure: "😕", + lol: "😄", + ninja: "🥷", + no: "🙅", + ohno: "😨", + ohnoes: "😨", + omg: "🙀", + shifty: "😒", + sick: "😷", + wink: "😉", + creepy: "😈", + tongue: "😜", + thumbsup: "👍", + "+1": "👍", + thumbsdown: "👎", + "-1": "👎", + }; + const emoticonRegExp = new RegExp( + ":(" + + Object.keys(emoticons) + .map((e) => e.replaceAll(/\+/g, "\\+")) + .join("|") + + "):", + "g" + ); + return htmlContent.replace( + emoticonRegExp, + (_, iconName) => emoticons[iconName] + ); +} diff --git a/plugins/rss/init.js b/plugins/rss/init.js index 946626393..1c8d5d107 100644 --- a/plugins/rss/init.js +++ b/plugins/rss/init.js @@ -3,6 +3,17 @@ if(browser.isIE && browser.versionMajor < 8) plugin.loadCSS("ie"); plugin.loadLang(); +// Dynamically import module for transforming bbcode to html +var bbcode = null; +import(`./${plugin.path}bbcode.js`) + .then(bbcodeModule => { + bbcode = bbcodeModule; + }) + .catch((err) => { + if (!bbcode) console.error(err); + else console.log('"bbcode" initialized without dynamic module import!'); + }); + theWebUI.rssListVisible = false; theWebUI.rssShowErrorsDelayed = true; theWebUI.delayedRSSErrors = {}; @@ -1085,310 +1096,15 @@ rTorrentStub.prototype.setrsssettings = function() this.rssCommon("mode=setsettings&interval="+this.ss[0]+"&delayerrui="+this.ss[1]); } -theWebUI.mapBBCodeToHTML = function (htmlText) { - const tags = { - ...Object.fromEntries( - [ "b", "i", "sup", "sub", "table", "thead", "tbody", "tfoot", "tr", "td", "th", "li" ].map((t) => [t, () => [t]]) - ), - ...Object.fromEntries( - ["ul", "ol", "list"].map((name) => [ - name, - (_, content) => { - const htmlTag = name === "list" ? "ul" : name; - const ele = $(`<${htmlTag}>`).html(content); - const list = $(`<${htmlTag}>`); - let lastLiNode = $("
  • "); - for (const node of ele.contents()) { - if (node.nodeName.toLowerCase() === "li") { - // keep li nodes - lastLiNode = $(node); - } else { - if (node.nodeType === 3) { - // parse list items denoted by [*] and * - const items = String(node.nodeValue) - .replaceAll(/(^|[\s\]])\*\s/g, "[*]") - .split(/\[\*\]/g); - if (!list.children("li").length) { - // set text of empty list - list.text(items.shift()); - } - const firstItem = items.shift(); - if (firstItem) { - // add textnode to lastLiNode - lastLiNode.append(document.createTextNode(firstItem)); - } - for (const item of items) { - list.append(lastLiNode); - lastLiNode = $("
  • ").text(item); - } - } else { - // add some node to lastLiNode - lastLiNode.append($(node)); - } - } - // add lastLiNode to list (if not added already) - list.append(lastLiNode); - } - return [htmlTag, {}, list[0].innerHTML]; - }, - ]) - ), - u: () => ["ins"], - s: () => ["del"], - ...Object.fromEntries( - ["small", "normal", "large"].map((t) => [ - t, - () => ["span", { class: `bbcode-size-${t}` }], - ]) - ), - size: (arg) => ["span", { class: `bbcode-size-${arg}` }], - color: (arg) => ["span", { class: `bbcode-color-${arg}` }], - ...Object.fromEntries( - ["center", "left", "right"].map((t) => [ - t, - () => ["span", { class: `bbcode-align-${t}` }], - ]) - ), - ...Object.fromEntries( - ["font", "face"].map((t) => [ - t, - (arg) => ["span", { class: (arg || "").toLowerCase() }], - ]) - ), - style: (_, __, args) => [ - "span", - { - class: Object.entries(args) - .map(([k, v]) => `bbcode-${k}-${v}`) - .join(" "), - }, - ], - img: (arg, content, args) => [ - "img", - { - src: content, - ...Object.fromEntries( - (arg || "") - .split("x") - .map((v, k) => [["width", "height"][k], Number.parseInt(v)]) - .filter(([_, v]) => !Number.isNaN(v)) - ), - ...args, - }, - "", - ], - url: (arg, content) => ["a", { href: arg == null ? content : arg }], - email: (arg, content) => [ - "a", - { href: `mailto:${arg == null ? content : arg}` }, - ], - quote: (arg, content, args) => [ - "blockquote", - {}, - $("

    ").html(content)[0].outerHTML + - $("") - .addClass("bbcode-quote") - .text("-- ") - .append($("").text(arg || args["author"] || ""))[0].outerHTML, - ], - code: () => ["pre", { class: "bbcode-code" }], - spoiler: (arg, content) => [ - "details", - {}, - $("

    ").html(arg)[0].outerHTML + content, - ], - "bbcode-root": () => ["div"], - }; - - const trimArg = (arg) => - arg == null - ? null - : arg.startsWith('"') - ? arg.substring(1, arg.length - 1) - : arg.trim(); - const argsToDict = (args) => { - const dict = {}; - for (const match of args.matchAll( - /\s+?(?[a-z]+)=(?"(.*?)"|[^\s]*)/gi - )) { - const { name, arg } = match.groups; - if (name && arg) { - dict[name] = trimArg(arg); - } - } - return dict; - }; - - const nodeToElement = (node) => { - const htmlContent = node.children - .map((n) => (n.name ? nodeToElement(n).outerHTML : n)) - .join(""); - const arg = trimArg(node.arg); - const args = node.args ? argsToDict(node.args) : {}; - const [htmlTag, attribs, htmlContentProcessed] = tags[node.name]( - arg, - htmlContent, - args - ); - const ele = $(`<${htmlTag}>`) - .attr(attribs || {}) - .html(htmlContentProcessed || htmlContent)[0]; - return ele; - }; - - const simpleParamPattern = '\\s*?=\\s*?(?"(.*?)"|.*?)'; - const complexParamPattern = '(?(\\s+?[a-z]+=("(.*?)"|[^\\s]*?))+)'; - const tagPattern = new RegExp( - "\\[\\/?(?" + - Object.keys(tags).join("|") + - ")(" + - simpleParamPattern + - "|" + - complexParamPattern + - ")?\\s*?\\]", - "gsi" - ); - let nodeStack = [{ name: "bbcode-root", children: [] }]; - let offset = 0; - for (const match of htmlText.matchAll(tagPattern)) { - const parent = nodeStack[nodeStack.length - 1]; - const { name, arg, args } = match.groups; - const closing = match[0].startsWith("[/"); - // add textnode to parent - const textnode = match.input.substring(offset, match.index); - if (textnode) { - parent.children.push(textnode); - } - if (closing) { - if (parent.name === name && nodeStack.length > 1) { - nodeStack.pop(); - } else { - // encoutered unexpected close tag - nodeStack = [nodeStack[0]]; - } - } else { - const node = { name, arg, args, children: [] }; - parent.children.push(node); - // make curnode to parent node - nodeStack.push(node); - } - offset = match.index + match[0].length; - } - nodeStack[nodeStack.length-1].children.push(htmlText.substring(offset, htmlText.length)); - const htmlContent = nodeToElement(nodeStack[0]).innerHTML; - - // Support for some emoticons from WhatCD/Gazelle (https://github.com/WhatCD/Gazelle/tree/master/static/common/smileys) - // :code: => utf8 emoticon (https://utf8-icons.com/subset/emoticons) - const emoticons = { - smile: "🙂", - blank: "😐", - biggrin: "😁", - angry: "😡", - blush: "😊", - cool: "😎", - crying: "😢", - frown: "🙁", - unsure: "😕", - lol: "😄", - ninja: "🥷", - no: "🙅", - ohno: "😨", - ohnoes: "😨", - omg: "🙀", - shifty: "😒", - sick: "😷", - wink: "😉", - creepy: "😈", - tongue: "😜", - thumbsup: "👍", - "+1": "👍", - thumbsdown: "👎", - "-1": "👎", - }; - const emoticonRegExp = new RegExp( - ":(" + - Object.keys(emoticons) - .map((e) => e.replaceAll(/\+/g, "\\+")) - .join("|") + - "):", - "g" - ); - return htmlContent.replace( - emoticonRegExp, - (_, iconName) => emoticons[iconName] - ); -}; - rTorrentStub.prototype.getrssdetailsResponse = function (data) { - const colorNamePattern = - /^(?:aliceblue|antiquewhite|aqua|aquamarine|azure|beige|bisque|black|blanchedalmond|blue|blueviolet|brown|burlywood|cadetblue|chartreuse|chocolate|coral|cornflowerblue|cornsilk|crimson|cyan|darkblue|darkcyan|darkgoldenrod|darkgray|darkgreen|darkkhaki|darkmagenta|darkolivegreen|darkorange|darkorchid|darkred|darksalmon|darkseagreen|darkslateblue|darkslategray|darkturquoise|darkviolet|deeppink|deepskyblue|dimgray|dodgerblue|firebrick|floralwhite|forestgreen|fuchsia|gainsboro|ghostwhite|gold|goldenrod|gray|green|greenyellow|honeydew|hotpink|indianred|indigo|ivory|khaki|lavender|lavenderblush|lawngreen|lemonchiffon|lightblue|lightcoral|lightcyan|lightgoldenrodyellow|lightgray|lightgreen|lightpink|lightsalmon|lightseagreen|lightskyblue|lightslategray|lightsteelblue|lightyellow|lime|limegreen|linen|magenta|maroon|mediumaquamarine|mediumblue|mediumorchid|mediumpurple|mediumseagreen|mediumslateblue|mediumspringgreen|mediumturquoise|mediumvioletred|midnightblue|mintcream|mistyrose|moccasin|navajowhite|navy|oldlace|olive|olivedrab|orange|orangered|orchid|palegoldenrod|palegreen|paleturquoise|palevioletred|papayawhip|peachpuff|peru|pink|plum|powderblue|purple|red|rosybrown|royalblue|saddlebrown|salmon|sandybrown|seagreen|seashell|sienna|silver|skyblue|slateblue|slategray|snow|springgreen|steelblue|tan|teal|thistle|tomato|turquoise|violet|wheat|white|whitesmoke|yellow|yellowgreen)$/; - const colorCodePattern = /^#?[a-f0-9]{6}$/i; - const restirctedFontSize = (value) => { - const size = Math.max(4, Math.min(40, Number.parseInt(value))); - return Number.isNaN(size) ? "normal" : `${size}px`; - }; - const bbNodeAttr = { - color: (value) => - colorNamePattern.test(value) - ? { color: value } - : colorCodePattern.test(value) - ? { color: value.startsWith("#") ? value : `#${value}` } - : null, - size: (value) => - ["small", "large", "normal"].includes(value) - ? "class" - : { "font-size": restirctedFontSize(value) }, - align: (value) => - ["left", "right", "center"].includes(value) ? "class" : null, - font: (value) => - [ "times", "courier", "arial", "serif", "sans", "fantasy", "monospace", "caps", ].includes(value) ? "class" : null, - }; - const bbclassTransform = (cfg) => { - const node = cfg.node; - if (!["pre", "span"].includes(node.nodeName.toLowerCase())) { - return null; - } - let styles = {}; - let classes = []; - for (const bbClass of (node.attributes.class?.value || "").split(" ")) { - const [bbcode, key, value] = bbClass.split("-"); - if (bbcode === "bbcode") { - const style = key in bbNodeAttr ? bbNodeAttr[key](value || "") : null; - if (style !== null) { - if (style !== "class") { - styles = { ...styles, ...style }; - } - classes.push( - `${bbcode}-${key}` + (style === "class" ? `-${value}` : "") - ); - } - } - } - // replace existing attributes with style and class - [...node.attributes].forEach((attr) => node.removeAttribute(attr.name)); - for (const [name, value] of [ - ["style", Object.entries(styles).map((e) => e.join(": ")).join("; ")], - ["class", classes.join(" ")], - ]) { - if (value) { - const attr = cfg.dom.createAttribute(name); - attr.value = value; - node.attributes[name] = attr; - } - } - return { - whitelist: Boolean(classes.length), - attr_whitelist: ["class", "style"], - node, - }; - }; + if (!bbcode) return false; const cfg = Sanitize.Config.RESTRICTED; const s = new Sanitize({ elements: [...cfg.elements, "ins", "details", "summary"], - transformers: [bbclassTransform], + transformers: [bbcode.bbclassTransform], }); const rawHTML = String(data); - const dirtyHTML = theWebUI.mapBBCodeToHTML(rawHTML); + const dirtyHTML = bbcode.mapBBCodeToHTML(rawHTML); const doc = new DOMParser().parseFromString(dirtyHTML, "text/html"); $("#rsslayout") .empty() diff --git a/tests/package.json b/tests/package.json index 4389e7c5e..1d032cfe4 100644 --- a/tests/package.json +++ b/tests/package.json @@ -39,7 +39,7 @@ "test", "rutorrent" ], - "target": "es6", + "target": "es2020", "module": "commonjs", "author": "TrimmingFool", "license": "GPL-3.0" diff --git a/tests/plugins/rss/bbcode.spec.js b/tests/plugins/rss/bbcode.spec.js new file mode 100644 index 000000000..fbf542b75 --- /dev/null +++ b/tests/plugins/rss/bbcode.spec.js @@ -0,0 +1,66 @@ +import { mapBBCodeToHTML } from "../../../plugins/rss/bbcode"; +window.$ = require("jquery"); + +describe("bbcode mapping", () => { + it("should handle incomplete or faulty bbcode", () => { + expect(mapBBCodeToHTML("[i]ABC[/i]abc[b]CDE")).toEqual( + "ABCabcCDE" + ); + expect(mapBBCodeToHTML("[i]ABC[/i]abc[b]CDE[/c]some")).toEqual( + "ABCabcCDE[/c]some" + ); + }); + + it("should map emoticons to html", () => { + const bbcode = + "[center ][color=red ]Important :smile::shifty:[/color][/center]"; + const htmlCode = mapBBCodeToHTML(bbcode); + expect(htmlCode).toEqual( + 'Important 🙂😒' + ); + }); + + it("should map styles to html", () => { + expect(mapBBCodeToHTML("[style font=caps ]Text[/style]")).toEqual( + 'Text' + ); + expect(mapBBCodeToHTML("[style color=?? font=caps]Text[/style]")).toEqual( + 'Text' + ); + }); + + it("should map bbcode tables to html", () => { + expect( + mapBBCodeToHTML( + '[table] [tr] [td][i]table[/i] [large][b]cell[/b][/large] 1[/td] [td][right]table[/right] cell 2[/td] [/tr] [tr] [td][url]https://google.com[/url]table cell 3[/td] [td][quote="The Devil"]Mmmmh, merely mortal mollusc mating maggots may molest my malevolent magnificence![/quote][/td] [/tr] [/table]\n[spoiler=All truth is...]true[/spoiler]' + ) + ).toEqual( + '
    table cell 1 table cell 2
    https://google.comtable cell 3

    Mmmmh, merely mortal mollusc mating maggots may molest my malevolent magnificence!

    -- The Devil
    \n
    All truth is...true
    ' + ); + }); + + it("should map bbcode lists to html code", () => { + expect( + mapBBCodeToHTML( + "[list]\n * One\n [*]Two \n listwith[i]more[/i] elements[li][*] blank \n * blank2[/li]* Three[/list]" + ) + ).toEqual( + "
      \n
    • One\n
    • Two \n listwithmore elements
    • [*] blank \n * blank2
    • Three
    " + ); + }); + + it("should map bbcode img to html code", () => { + expect( + mapBBCodeToHTML("[img width=150 height=160]https://linkto.img[/img]") + ).toEqual(''); + expect(mapBBCodeToHTML("[img=150x160]https://linkto.img[/img]")).toEqual( + '' + ); + expect(mapBBCodeToHTML("[img=150]https://linkto.img[/img]")).toEqual( + '' + ); + expect(mapBBCodeToHTML("[img]https://linkto.img[/img]")).toEqual( + '' + ); + }); +}); diff --git a/tests/plugins/rss/init.spec.js b/tests/plugins/rss/init.spec.js index 6fc28e237..daa422aac 100644 --- a/tests/plugins/rss/init.spec.js +++ b/tests/plugins/rss/init.spec.js @@ -1,5 +1,7 @@ import { readFileSync } from "fs"; import { CategoryList } from "../../../js/category-list"; +import * as bbcodeModule from "../../../plugins/rss/bbcode"; + window.$ = require("jquery"); window.theWebUI = { settings: { @@ -19,6 +21,8 @@ window.theWebUI = { window.GetActiveLanguage = function () { return "en"; }; +window.rsstestingbbcodeModule = bbcodeModule; + document.body.append( ...["category-list", "panel-label"].map((elementTag) => Object.assign(document.createElement("template"), { @@ -43,7 +47,14 @@ for (const src of [ const scriptEl = document.createElement("script"); let code = readFileSync(src, { encoding: "utf-8" }); if (src.endsWith("rss/init.js")) { - code = `(function () { var plugin = new rPlugin('rss', 4.0, 'a', 'b', 'c', 'd'); plugin.path="../plugins/rss/"; ${code}})();`; + // Dynamic imports and