diff --git a/app/assets/javascripts/discourse/components/topic-list-item.js b/app/assets/javascripts/discourse/components/topic-list-item.js index 7bc2d64603c..0746ed95f69 100644 --- a/app/assets/javascripts/discourse/components/topic-list-item.js +++ b/app/assets/javascripts/discourse/components/topic-list-item.js @@ -7,6 +7,8 @@ import { findRawTemplate } from "discourse/lib/raw-templates"; import { wantsNewWindow } from "discourse/lib/intercept-click"; import { on } from "@ember/object/evented"; +import { topicTitleDecorators } from "discourse/components/topic-title"; + export function showEntrance(e) { let target = $(e.target); @@ -67,6 +69,18 @@ export default Component.extend({ } }); } + + schedule("afterRender", () => { + if (this.element && !this.isDestroying && !this.isDestroyed) { + const rawTopicLink = this.element.querySelector(".raw-topic-link"); + + rawTopicLink && + topicTitleDecorators && + topicTitleDecorators.forEach(cb => + cb(this.topic, rawTopicLink, "topic-list-item-title") + ); + } + }); }, willDestroyElement() { diff --git a/app/assets/javascripts/discourse/components/topic-title.js b/app/assets/javascripts/discourse/components/topic-title.js index 7bbc478cb33..82d41f4ad09 100644 --- a/app/assets/javascripts/discourse/components/topic-title.js +++ b/app/assets/javascripts/discourse/components/topic-title.js @@ -1,6 +1,33 @@ import Component from "@ember/component"; import KeyEnterEscape from "discourse/mixins/key-enter-escape"; +import { schedule } from "@ember/runloop"; + +export let topicTitleDecorators = []; + +export function addTopicTitleDecorator(decorator) { + topicTitleDecorators.push(decorator); +} + +export function resetTopicTitleDecorators() { + topicTitleDecorators = []; +} export default Component.extend(KeyEnterEscape, { - elementId: "topic-title" + elementId: "topic-title", + + didInsertElement() { + this._super(...arguments); + + schedule("afterRender", () => { + if (this.element && !this.isDestroying && !this.isDestroyed) { + const fancyTitle = this.element.querySelector(".fancy-title"); + + fancyTitle && + topicTitleDecorators && + topicTitleDecorators.forEach(cb => + cb(this.model, fancyTitle, "topic-title") + ); + } + }); + } }); diff --git a/app/assets/javascripts/discourse/lib/plugin-api.js b/app/assets/javascripts/discourse/lib/plugin-api.js index 9439b16319e..aaafba5b4ce 100644 --- a/app/assets/javascripts/discourse/lib/plugin-api.js +++ b/app/assets/javascripts/discourse/lib/plugin-api.js @@ -3,6 +3,7 @@ import deprecated from "discourse-common/lib/deprecated"; import { iconNode } from "discourse-common/lib/icon-library"; import { addDecorator } from "discourse/widgets/post-cooked"; import { addPluginOutletDecorator } from "discourse/components/plugin-connector"; +import { addTopicTitleDecorator } from "discourse/components/topic-title"; import ComposerEditor from "discourse/components/composer-editor"; import DiscourseBanner from "discourse/components/discourse-banner"; import { addButton } from "discourse/widgets/post-menu"; @@ -54,7 +55,7 @@ import { on } from "@ember/object/evented"; import KeyboardShortcuts, { bindings } from "discourse/lib/keyboard-shortcuts"; // If you add any methods to the API ensure you bump up this number -const PLUGIN_API_VERSION = "0.8.39"; +const PLUGIN_API_VERSION = "0.8.40"; class PluginApi { constructor(version, container) { @@ -1017,6 +1018,26 @@ class PluginApi { decoratePluginOutlet(outletName, callback, opts) { addPluginOutletDecorator(outletName, callback, opts || {}); } + + /** + * Allows altering the topic title in the topic list, and in the topic view + * + * topicTitleType can be `topic-title` or `topic-list-item-title` + * + * For example, to replace the topic title: + * + * ``` + * api.decorateTopicTitle( + * (topicModel, node, topicTitleType) => { + * node.innerText("my new topic title"); + * } + * ); + * ``` + * + **/ + decorateTopicTitle(callback) { + addTopicTitleDecorator(callback); + } } let _pluginv01; diff --git a/test/javascripts/acceptance/topic-test.js b/test/javascripts/acceptance/topic-test.js index af05e5fc9e7..68e6ab2c8aa 100644 --- a/test/javascripts/acceptance/topic-test.js +++ b/test/javascripts/acceptance/topic-test.js @@ -1,3 +1,4 @@ +import { withPluginApi } from "discourse/lib/plugin-api"; import selectKit from "helpers/select-kit-helper"; import { acceptance } from "helpers/qunit-helpers"; import { IMAGE_VERSION as v } from "pretty-text/emoji/version"; @@ -358,3 +359,30 @@ QUnit.test("Bookmarks Modal", async assert => { await click(".topic-post:first-child button.bookmark"); assert.ok(exists("#bookmark-reminder-modal"), "it shows the bookmark modal"); }); + +acceptance("Topic with title decorated", { + loggedIn: true, + beforeEach() { + withPluginApi("0.8.40", api => { + api.decorateTopicTitle((topic, node, topicTitleType) => { + node.innerText = `${node.innerText}-${topic.id}-${topicTitleType}`; + }); + }); + } +}); + +QUnit.test("Decorate topic title", async assert => { + await visit("/t/internationalization-localization/280"); + + assert.ok( + find(".fancy-title")[0].innerText.endsWith("-280-topic-title"), + "it decorates topic title" + ); + + assert.ok( + find(".raw-topic-link:nth-child(1)")[0].innerText.endsWith( + "-27331-topic-list-item-title" + ), + "it decorates topic list item title" + ); +}); diff --git a/test/javascripts/helpers/qunit-helpers.js b/test/javascripts/helpers/qunit-helpers.js index 4b6ed9330f3..e0621c294dd 100644 --- a/test/javascripts/helpers/qunit-helpers.js +++ b/test/javascripts/helpers/qunit-helpers.js @@ -16,6 +16,7 @@ import { clearRewrites } from "discourse/lib/url"; import { initSearchData } from "discourse/widgets/search-menu"; import { resetDecorators } from "discourse/widgets/widget"; import { resetWidgetCleanCallbacks } from "discourse/components/mount-widget"; +import { resetTopicTitleDecorators } from "discourse/components/topic-title"; import { resetDecorators as resetPostCookedDecorators } from "discourse/widgets/post-cooked"; import { resetDecorators as resetPluginOutletDecorators } from "discourse/components/plugin-connector"; import { resetCache as resetOneboxCache } from "pretty-text/oneboxer"; @@ -129,6 +130,7 @@ export function acceptance(name, options) { resetDecorators(); resetPostCookedDecorators(); resetPluginOutletDecorators(); + resetTopicTitleDecorators(); resetOneboxCache(); resetCustomPostMessageCallbacks(); Discourse._runInitializer("instanceInitializers", function(