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 = $("
").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
|
\nAll 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