diff --git a/app/assets/javascripts/discourse/app/lib/plugin-api.js b/app/assets/javascripts/discourse/app/lib/plugin-api.js
index 6c4c308bde4..bb2dd1457bc 100644
--- a/app/assets/javascripts/discourse/app/lib/plugin-api.js
+++ b/app/assets/javascripts/discourse/app/lib/plugin-api.js
@@ -100,7 +100,7 @@ import { addSectionLink } from "discourse/lib/sidebar/custom-topics-section-link
// based on Semantic Versioning 2.0.0. Please update the changelog at
// docs/CHANGELOG-JAVASCRIPT-PLUGIN-API.md whenever you change the version
// using the format described at https://keepachangelog.com/en/1.0.0/.
-const PLUGIN_API_VERSION = "1.2.0";
+const PLUGIN_API_VERSION = "1.3.0";
// This helper prevents us from applying the same `modifyClass` over and over in test mode.
function canModify(klass, type, resolverName, changes) {
diff --git a/app/assets/javascripts/discourse/app/lib/to-markdown.js b/app/assets/javascripts/discourse/app/lib/to-markdown.js
index df260ab55e2..95ead9c4ae6 100644
--- a/app/assets/javascripts/discourse/app/lib/to-markdown.js
+++ b/app/assets/javascripts/discourse/app/lib/to-markdown.js
@@ -11,6 +11,50 @@ const hasChild = (e, n) => {
return (e.children || []).some((c) => c.name === n);
};
+let tagDecorateCallbacks = [];
+let blockDecorateCallbacks = [];
+
+/**
+ * Allows to add support for custom inline markdown/bbcode
+ *
+ * ```
+ * addTagDecorateCallback(function (text) {
+ * if (this.element.attributes.class === "loud") {
+ * this.prefix = "^^";
+ * this.suffix = "^^";
+ * return text.toLowerCase();
+ * }
+ * });
+ * ```
+ */
+export function addTagDecorateCallback(callback) {
+ tagDecorateCallbacks.push(callback);
+}
+
+export function clearTagDecorateCallbacks() {
+ tagDecorateCallbacks = [];
+}
+
+/**
+ * Allows to add support for custom block markdown/bbcode
+ *
+ * ```
+ * addBlockDecorateCallback(function (text) {
+ * if (this.element.attributes.class === "spoiled") {
+ * this.prefix = "[spoiler]";
+ * this.suffix = "[/spoiler]";
+ * }
+ * });
+ * ```
+ */
+export function addBlockDecorateCallback(callback) {
+ blockDecorateCallbacks.push(callback);
+}
+
+export function clearBlockDecorateCallbacks() {
+ blockDecorateCallbacks = [];
+}
+
export class Tag {
constructor(name, prefix = "", suffix = "", inline = false) {
this.name = name;
@@ -20,6 +64,14 @@ export class Tag {
}
decorate(text) {
+ for (const callback of tagDecorateCallbacks) {
+ const result = callback.call(this, text);
+
+ if (typeof result !== "undefined") {
+ text = result;
+ }
+ }
+
if (this.prefix || this.suffix) {
text = [this.prefix, text, this.suffix].join("");
}
@@ -137,6 +189,14 @@ export class Tag {
decorate(text) {
const parent = this.element.parent;
+ for (const callback of blockDecorateCallbacks) {
+ const result = callback.call(this, text);
+
+ if (typeof result !== "undefined") {
+ text = result;
+ }
+ }
+
if (this.name === "p" && parent && parent.name === "li") {
// fix for google docs
this.gap = "";
diff --git a/app/assets/javascripts/discourse/tests/helpers/qunit-helpers.js b/app/assets/javascripts/discourse/tests/helpers/qunit-helpers.js
index daa06030ec3..91077116d71 100644
--- a/app/assets/javascripts/discourse/tests/helpers/qunit-helpers.js
+++ b/app/assets/javascripts/discourse/tests/helpers/qunit-helpers.js
@@ -64,6 +64,10 @@ import {
} from "discourse/lib/user-presence";
import PreloadStore from "discourse/lib/preload-store";
import { resetDefaultSectionLinks as resetTopicsSectionLinks } from "discourse/lib/sidebar/custom-topics-section-links";
+import {
+ clearBlockDecorateCallbacks,
+ clearTagDecorateCallbacks,
+} from "discourse/lib/to-markdown";
const LEGACY_ENV = !setupApplicationTest;
@@ -188,6 +192,8 @@ function testCleanup(container, app) {
}
restoreBaseUri();
resetTopicsSectionLinks();
+ clearTagDecorateCallbacks();
+ clearBlockDecorateCallbacks();
}
export function discourseModule(name, options) {
diff --git a/app/assets/javascripts/discourse/tests/unit/lib/to-markdown-test.js b/app/assets/javascripts/discourse/tests/unit/lib/to-markdown-test.js
index c5e7b68b43f..b873104ad11 100644
--- a/app/assets/javascripts/discourse/tests/unit/lib/to-markdown-test.js
+++ b/app/assets/javascripts/discourse/tests/unit/lib/to-markdown-test.js
@@ -1,5 +1,8 @@
import { module, test } from "qunit";
-import toMarkdown from "discourse/lib/to-markdown";
+import toMarkdown, {
+ addBlockDecorateCallback,
+ addTagDecorateCallback,
+} from "discourse/lib/to-markdown";
module("Unit | Utility | to-markdown", function () {
test("converts styles between normal words", function (assert) {
@@ -458,4 +461,31 @@ test2
'';
assert.strictEqual(toMarkdown(html), "[image]");
});
+
+ test("addTagDecorateCallback", function (assert) {
+ const html = `HELLO THERE`;
+
+ addTagDecorateCallback(function (text) {
+ if (this.element.attributes.class === "loud") {
+ this.prefix = "^^";
+ this.suffix = "^^";
+ return text.toLowerCase();
+ }
+ });
+
+ assert.strictEqual(toMarkdown(html), "^^hello there^^");
+ });
+
+ test("addBlockDecorateCallback", function (assert) {
+ const html = `