From c051bfc2fc5fd154df83750141a2b1990b5e5338 Mon Sep 17 00:00:00 2001 From: Mark VanLandingham Date: Wed, 13 Dec 2023 15:15:42 -0600 Subject: [PATCH] DEV: Plugin-api methods for user-notifications route customizations (#24873) --- Gemfile.lock | 1 + .../app/controllers/user-notifications.gjs | 13 ++++++++ .../discourse/app/lib/plugin-api.js | 31 ++++++++++++++++++- .../app/routes/user-notifications.js | 8 +++++ .../templates/user/notifications-index.hbs | 4 +++ docs/CHANGELOG-JAVASCRIPT-PLUGIN-API.md | 8 +++++ spec/system/page_objects/pages/user.rb | 4 +++ .../page_objects/pages/user_notifications.rb | 4 +++ .../user_page/user_notifications_spec.rb | 25 ++++++++++++++- 9 files changed, 96 insertions(+), 2 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 90e5d4f3402..204d73d9e4c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -523,6 +523,7 @@ GEM PLATFORMS aarch64-linux arm64-darwin-20 + arm64-darwin-21 arm64-darwin-22 x86_64-darwin-18 x86_64-darwin-19 diff --git a/app/assets/javascripts/discourse/app/controllers/user-notifications.gjs b/app/assets/javascripts/discourse/app/controllers/user-notifications.gjs index aef0a45b3eb..31bb62218ac 100644 --- a/app/assets/javascripts/discourse/app/controllers/user-notifications.gjs +++ b/app/assets/javascripts/discourse/app/controllers/user-notifications.gjs @@ -11,6 +11,11 @@ import { iconHTML } from "discourse-common/lib/icon-library"; import discourseComputed from "discourse-common/utils/decorators"; import I18n from "discourse-i18n"; +const _beforeLoadMoreCallbacks = []; +export function addBeforeLoadMoreCallback(fn) { + _beforeLoadMoreCallbacks.push(fn); +} + export default class UserNotificationsController extends Controller { @service modal; @service appEvents; @@ -102,6 +107,14 @@ export default class UserNotificationsController extends Controller { @action loadMore() { + if ( + _beforeLoadMoreCallbacks.length && + !_beforeLoadMoreCallbacks.some((fn) => fn(this)) + ) { + // Return early if any callbacks return false, short-circuiting the default loading more logic + return; + } + this.model.loadMore(); } } diff --git a/app/assets/javascripts/discourse/app/lib/plugin-api.js b/app/assets/javascripts/discourse/app/lib/plugin-api.js index 41d6acc9e92..377254ebf07 100644 --- a/app/assets/javascripts/discourse/app/lib/plugin-api.js +++ b/app/assets/javascripts/discourse/app/lib/plugin-api.js @@ -31,6 +31,7 @@ import { addUserMenuProfileTabItem } from "discourse/components/user-menu/profil import { addDiscoveryQueryParam } from "discourse/controllers/discovery/list"; import { registerFullPageSearchType } from "discourse/controllers/full-page-search"; import { registerCustomPostMessageCallback as registerCustomPostMessageCallback1 } from "discourse/controllers/topic"; +import { addBeforeLoadMoreCallback as addBeforeLoadMoreNotificationsCallback } from "discourse/controllers/user-notifications"; import { registerCustomUserNavMessagesDropdownRow } from "discourse/controllers/user-private-messages"; import { addExtraIconRenderer, @@ -88,6 +89,7 @@ import { addSaveableUserOptionField, } from "discourse/models/user"; import { setNewCategoryDefaultColors } from "discourse/routes/new-category"; +import { setNotificationsLimit } from "discourse/routes/user-notifications"; import { addComposerSaveErrorCallback } from "discourse/services/composer"; import { addToHeaderIcons, @@ -141,7 +143,7 @@ import { modifySelectKit } from "select-kit/mixins/plugin-api"; // docs/CHANGELOG-JAVASCRIPT-PLUGIN-API.md whenever you change the version // using the format described at https://keepachangelog.com/en/1.0.0/. -export const PLUGIN_API_VERSION = "1.18.0"; +export const PLUGIN_API_VERSION = "1.19.0"; // This helper prevents us from applying the same `modifyClass` over and over in test mode. function canModify(klass, type, resolverName, changes) { @@ -819,6 +821,21 @@ class PluginApi { registerCustomPostMessageCallback1(type, callback); } + /** + * Registers a callback that will be evaluated when infinite scrolling would cause + * more notifications to be loaded. This can be used to prevent loading more unless + * a specific condition is met. + * + * Example: + * + * api.addBeforeLoadMoreNotificationsCallback((controller) => { + * return controller.allowLoadMore; + * }); + */ + addBeforeLoadMoreNotificationsCallback(fn) { + addBeforeLoadMoreNotificationsCallback(fn); + } + /** * Changes a setting associated with a widget. For example, if * you wanted small avatars in the post stream: @@ -1773,6 +1790,18 @@ class PluginApi { setNewCategoryDefaultColors(backgroundColor, textColor); } + /** + * Change the number of notifications that are loaded at /my/notifications + * + * ``` + * api.setNotificationsLimit(20) + * ``` + * + **/ + setNotificationsLimit(limit) { + setNotificationsLimit(limit); + } + /** * Add a callback to modify search results before displaying them. * diff --git a/app/assets/javascripts/discourse/app/routes/user-notifications.js b/app/assets/javascripts/discourse/app/routes/user-notifications.js index a1617b2a70a..7b0d4c32a6c 100644 --- a/app/assets/javascripts/discourse/app/routes/user-notifications.js +++ b/app/assets/javascripts/discourse/app/routes/user-notifications.js @@ -2,6 +2,13 @@ import ViewingActionType from "discourse/mixins/viewing-action-type"; import DiscourseRoute from "discourse/routes/discourse"; import I18n from "discourse-i18n"; +const DEFAULT_LIMIT = 60; +let limit = DEFAULT_LIMIT; + +export function setNotificationsLimit(newLimit) { + limit = newLimit; +} + export default DiscourseRoute.extend(ViewingActionType, { controllerName: "user-notifications", queryParams: { filter: { refreshModel: true } }, @@ -16,6 +23,7 @@ export default DiscourseRoute.extend(ViewingActionType, { return this.store.find("notification", { username, filter: params.filter, + limit, }); } }, diff --git a/app/assets/javascripts/discourse/app/templates/user/notifications-index.hbs b/app/assets/javascripts/discourse/app/templates/user/notifications-index.hbs index a70cd4372fa..b7f8537ee50 100644 --- a/app/assets/javascripts/discourse/app/templates/user/notifications-index.hbs +++ b/app/assets/javascripts/discourse/app/templates/user/notifications-index.hbs @@ -27,6 +27,10 @@ {{/each}} + {{/if}} {{/if}} \ No newline at end of file diff --git a/docs/CHANGELOG-JAVASCRIPT-PLUGIN-API.md b/docs/CHANGELOG-JAVASCRIPT-PLUGIN-API.md index 91470ac0c92..4bd3a041e25 100644 --- a/docs/CHANGELOG-JAVASCRIPT-PLUGIN-API.md +++ b/docs/CHANGELOG-JAVASCRIPT-PLUGIN-API.md @@ -7,6 +7,14 @@ 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.19.0] - 2023-12-13 + +### Added + +- Added `setNotificationsLimit` function, which sets a new limit for how many notifications are loaded for the user notifications route + +- Added `addBeforeLoadMoreNotificationsCallback` function, which takes a function as the argument. All added callbacks are evaluated before `loadMore` is triggered for user notifications. If any callback returns false, notifications will not be loaded. + ## [1.18.0] - 2023-12-1 ### Added diff --git a/spec/system/page_objects/pages/user.rb b/spec/system/page_objects/pages/user.rb index 5888015d0bc..f5021a238f0 100644 --- a/spec/system/page_objects/pages/user.rb +++ b/spec/system/page_objects/pages/user.rb @@ -52,6 +52,10 @@ module PageObjects self end + def click_primary_navigation_item(name) + page.find(primary_navigation_selector(name)).click + end + private def primary_navigation_selector(name) diff --git a/spec/system/page_objects/pages/user_notifications.rb b/spec/system/page_objects/pages/user_notifications.rb index 4500c05ff0f..574541432bc 100644 --- a/spec/system/page_objects/pages/user_notifications.rb +++ b/spec/system/page_objects/pages/user_notifications.rb @@ -27,6 +27,10 @@ module PageObjects def has_no_notification?(notification) page.has_no_css?(".notification a[href='#{notification.url}']") end + + def has_notification_count_of?(count) + page.has_css?(".notification", count: count) + end end end end diff --git a/spec/system/user_page/user_notifications_spec.rb b/spec/system/user_page/user_notifications_spec.rb index 4a2d5d59be0..3fff7dfaa1a 100644 --- a/spec/system/user_page/user_notifications_spec.rb +++ b/spec/system/user_page/user_notifications_spec.rb @@ -3,6 +3,7 @@ describe "User notifications", type: :system do fab!(:user) let(:user_notifications_page) { PageObjects::Pages::UserNotifications.new } + let(:user_page) { PageObjects::Pages::User.new } fab!(:read_notification) { Fabricate(:notification, user: user, read: true) } fab!(:unread_notification) { Fabricate(:notification, user: user, read: false) } @@ -10,7 +11,7 @@ describe "User notifications", type: :system do before { sign_in(user) } describe "filtering" do - it "saves custom picture and system assigned pictures" do + it "correctly filters all / read / unread notifications" do user_notifications_page.visit(user) user_notifications_page.filter_dropdown expect(user_notifications_page).to have_selected_filter_value("all") @@ -28,4 +29,26 @@ describe "User notifications", type: :system do expect(user_notifications_page).to have_notification(unread_notification) end end + + describe "setNotificationLimit & addBeforeLoadMoreNotificationsCallback plugin-api functions" do + it "Allows blocking loading via callback and limit" do + user_page.visit(user) + + page.execute_script <<~JS + require("discourse/lib/plugin-api").withPluginApi("1.19.0", (api) => { + api.setNotificationsLimit(1); + + api.addBeforeLoadMoreNotificationsCallback(() => { + return false; + }) + }) + JS + + user_page.click_primary_navigation_item("notifications") + + # It is 1 here because we blocked infinite scrolling. Even though the limit is 1, + # without the callback, we would have 2 items here as it immediately fires another request. + expect(user_notifications_page).to have_notification_count_of(1) + end + end end