diff --git a/app/assets/javascripts/discourse/app/lib/plugin-api.js b/app/assets/javascripts/discourse/app/lib/plugin-api.js index 8375b4f337e..74aff2737d3 100644 --- a/app/assets/javascripts/discourse/app/lib/plugin-api.js +++ b/app/assets/javascripts/discourse/app/lib/plugin-api.js @@ -84,6 +84,7 @@ import { registerTopicFooterDropdown } from "discourse/lib/register-topic-footer import { registerDesktopNotificationHandler } from "discourse/lib/desktop-notifications"; import { replaceFormatter } from "discourse/lib/utilities"; import { replaceTagRenderer } from "discourse/lib/render-tag"; +import { registerCustomLastUnreadUrlCallback } from "discourse/models/topic"; import { setNewCategoryDefaultColors } from "discourse/routes/new-category"; import { addSearchResultsCallback } from "discourse/lib/search"; import { @@ -98,7 +99,7 @@ import { consolePrefix } from "discourse/lib/source-identifier"; // 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.1.0"; +const PLUGIN_API_VERSION = "1.2.0"; // This helper prevents us from applying the same `modifyClass` over and over in test mode. function canModify(klass, type, resolverName, changes) { @@ -1290,6 +1291,21 @@ class PluginApi { replaceTagRenderer(fn); } + /** + * Register a custom last unread url for a topic list item. + * If a non-null value is returned, it will be used right away. + * + * Example: + * + * function testLastUnreadUrl(context) { + * return context.urlForPostNumber(1); + * } + * api.registerCustomLastUnreadUrlCallback(testLastUnreadUrl); + **/ + registerCustomLastUnreadUrlCallback(fn) { + registerCustomLastUnreadUrlCallback(fn); + } + /** * Registers custom languages for use with HighlightJS. * diff --git a/app/assets/javascripts/discourse/app/models/topic.js b/app/assets/javascripts/discourse/app/models/topic.js index ae74d2ea637..6cf798087e2 100644 --- a/app/assets/javascripts/discourse/app/models/topic.js +++ b/app/assets/javascripts/discourse/app/models/topic.js @@ -41,6 +41,7 @@ export function loadTopicView(topic, args) { } export const ID_CONSTRAINT = /^\d+$/; +let _customLastUnreadUrlCallbacks = []; const Topic = RestModel.extend({ message: null, @@ -256,6 +257,19 @@ const Topic = RestModel.extend({ @discourseComputed("last_read_post_number", "highest_post_number", "url") lastUnreadUrl(lastReadPostNumber, highestPostNumber) { + let customUrl = null; + _customLastUnreadUrlCallbacks.some((cb) => { + const result = cb(this); + if (result) { + customUrl = result; + return true; + } + }); + + if (customUrl) { + return customUrl; + } + if (highestPostNumber <= lastReadPostNumber) { if (this.get("category.navigate_to_first_post_after_read")) { return this.urlForPostNumber(1); @@ -876,4 +890,13 @@ export function mergeTopic(topicId, data) { ); } +export function registerCustomLastUnreadUrlCallback(fn) { + _customLastUnreadUrlCallbacks.push(fn); +} + +// Should only be used in tests +export function clearCustomLastUnreadUrlCallbacks() { + _customLastUnreadUrlCallbacks.clear(); +} + export default Topic; diff --git a/app/assets/javascripts/discourse/tests/acceptance/topic-list-plugin-api-test.js b/app/assets/javascripts/discourse/tests/acceptance/topic-list-plugin-api-test.js new file mode 100644 index 00000000000..77f92ba29bb --- /dev/null +++ b/app/assets/javascripts/discourse/tests/acceptance/topic-list-plugin-api-test.js @@ -0,0 +1,29 @@ +import { acceptance, query } from "discourse/tests/helpers/qunit-helpers"; +import { clearCustomLastUnreadUrlCallbacks } from "discourse/models/topic"; +import { test } from "qunit"; +import { visit } from "@ember/test-helpers"; +import { withPluginApi } from "discourse/lib/plugin-api"; + +acceptance("Topic list plugin API", function () { + function customLastUnreadUrl(context) { + return `${context.urlForPostNumber(1)}?overriden`; + } + + test("Overrides lastUnreadUrl", async function (assert) { + try { + withPluginApi("1.2.0", (api) => { + api.registerCustomLastUnreadUrlCallback(customLastUnreadUrl); + }); + + await visit("/"); + assert.strictEqual( + query( + ".topic-list .topic-list-item:first-child a.raw-topic-link" + ).getAttribute("href"), + "/t/error-after-upgrade-to-0-9-7-9/11557/1?overriden" + ); + } finally { + clearCustomLastUnreadUrlCallbacks(); + } + }); +}); diff --git a/docs/CHANGELOG-JAVASCRIPT-PLUGIN-API.md b/docs/CHANGELOG-JAVASCRIPT-PLUGIN-API.md index 8f0e699a4f3..0a380fbe826 100644 --- a/docs/CHANGELOG-JAVASCRIPT-PLUGIN-API.md +++ b/docs/CHANGELOG-JAVASCRIPT-PLUGIN-API.md @@ -2,36 +2,49 @@ All notable changes to the Discourse JavaScript plugin API located at app/assets/javascripts/discourse/app/lib/plugin-api.js will be described -in this file.. +in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [1.1.0] - 2021-12-15 +## [1.2.0] - 2022-03-18 + ### Added + +- Adds `registerCustomLastUnreadUrlCallback`, which allows users to register a custom + function that returns a last unread url for a topic list item. When multiple callbacks + are registered, the first non-null value that is returned will be used. + +## [1.1.0] - 2021-12-15 + +### Added + - Adds `addPosterIcons`, which allows users to add multiple icons to a poster. The -addition of this function also makes the existing `addPosterIcon` now an alias to this -function. Users may now just use `addPosterIcons` for both one or many icons. This -function allows users to now return many icons depending on an `attrs`. + addition of this function also makes the existing `addPosterIcon` now an alias to this + function. Users may now just use `addPosterIcons` for both one or many icons. This + function allows users to now return many icons depending on an `attrs`. ## [1.0.0] - 2021-11-25 + ### Removed + - Removes the `addComposerUploadProcessor` function, which is no longer used in -favour of `addComposerUploadPreProcessor`. The former was used to add preprocessors -for client side uploads via jQuery file uploader (described at -https://github.com/blueimp/jQuery-File-Upload/wiki/Options#file-processing-options). -The new `addComposerUploadPreProcessor` adds preprocessors for client side -uploads in the form of an Uppy plugin. See https://uppy.io/docs/writing-plugins/ -for the Uppy documentation, but other examples of preprocessors in core can be found -in the UppyMediaOptimization and UppyChecksum classes. This has been done because -of the overarching move towards Uppy in the Discourse codebase rather than -jQuery fileupload, which will eventually be removed altogether as a broader effort -to remove jQuery from the codebase. + favour of `addComposerUploadPreProcessor`. The former was used to add preprocessors + for client side uploads via jQuery file uploader (described at + https://github.com/blueimp/jQuery-File-Upload/wiki/Options#file-processing-options). + The new `addComposerUploadPreProcessor` adds preprocessors for client side + uploads in the form of an Uppy plugin. See https://uppy.io/docs/writing-plugins/ + for the Uppy documentation, but other examples of preprocessors in core can be found + in the UppyMediaOptimization and UppyChecksum classes. This has been done because + of the overarching move towards Uppy in the Discourse codebase rather than + jQuery fileupload, which will eventually be removed altogether as a broader effort + to remove jQuery from the codebase. ### Changed + - Changes `addComposerUploadHandler`'s behaviour. Instead of being only usable -for single files at a time, now multiple files are sent to the upload handler -at once. These multiple files are sent based on the groups in which they are -added (e.g. multiple files selected from the system upload dialog, or multiple -files dropped in to the composer). Files will be sent in buckets to the handlers -they match. + for single files at a time, now multiple files are sent to the upload handler + at once. These multiple files are sent based on the groups in which they are + added (e.g. multiple files selected from the system upload dialog, or multiple + files dropped in to the composer). Files will be sent in buckets to the handlers + they match.