From 0097dfc870382a5aaa818ffa57c57684df74fcad Mon Sep 17 00:00:00 2001 From: abose Date: Sun, 28 Dec 2025 22:26:52 +0530 Subject: [PATCH 1/4] chore: update strings --- src/nls/root/strings.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index 7b0be0991..7b6c06499 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -1650,7 +1650,6 @@ define({ "CONTACT_SUPPORT": "Contact Support", "SIGN_OUT": "Sign Out", "SIGN_IN": "Sign In", - "SIGN_IN_WITH_PRO": "Sign in with Pro", "ACCOUNT_DETAILS": "Account Details", "LOGIN_REFRESH": "Check Login Status", "SIGN_IN_WAITING_TITLE": "Waiting for Sign In", From a97d879fe0b9d10c9c36d262e85aa3cf347f900a Mon Sep 17 00:00:00 2001 From: abose Date: Mon, 29 Dec 2025 11:39:32 +0530 Subject: [PATCH 2/4] refactor: simplify inapp banner code --- .../InAppNotifications/banner.js | 43 +++++++++++------ .../InAppNotifications/utils.js | 47 ------------------- src/utils/Metrics.js | 2 +- .../Extn-InAppNotifications-integ-test.js | 28 +---------- 4 files changed, 30 insertions(+), 90 deletions(-) delete mode 100644 src/extensionsIntegrated/InAppNotifications/utils.js diff --git a/src/extensionsIntegrated/InAppNotifications/banner.js b/src/extensionsIntegrated/InAppNotifications/banner.js index 8d228037a..d6c01f669 100644 --- a/src/extensionsIntegrated/InAppNotifications/banner.js +++ b/src/extensionsIntegrated/InAppNotifications/banner.js @@ -30,7 +30,7 @@ define(function (require, exports, module) { PreferencesManager = require("preferences/PreferencesManager"), ExtensionUtils = require("utils/ExtensionUtils"), Metrics = require("utils/Metrics"), - utils = require("./utils"), + semver = require("thirdparty/semver.browser"), NotificationBarHtml = require("text!./htmlContent/notificationContainer.html"); ExtensionUtils.loadStyleSheet(module, "styles/styles.css"); @@ -44,6 +44,26 @@ define(function (require, exports, module) { PreferencesManager.stateManager.definePreference(IN_APP_NOTIFICATIONS_BANNER_SHOWN_STATE, "object", {}); + function _isValidForThisVersion(versionFilter) { + return semver.satisfies(brackets.metadata.apiVersion, versionFilter); + } + + // platformFilter is a string subset of + // "mac,win,linux,allDesktop,firefox,chrome,safari,allBrowser,all" + function _isValidForThisPlatform(platformFilter) { + platformFilter = platformFilter.split(","); + if(platformFilter.includes("all") + || (platformFilter.includes(brackets.platform) && Phoenix.isNativeApp) // win linux and mac is only for tauri and not for browser in platform + || (platformFilter.includes("allDesktop") && Phoenix.isNativeApp) + || (platformFilter.includes("firefox") && Phoenix.browser.desktop.isFirefox && !Phoenix.isNativeApp) + || (platformFilter.includes("chrome") && Phoenix.browser.desktop.isChromeBased && !Phoenix.isNativeApp) + || (platformFilter.includes("safari") && Phoenix.browser.desktop.isSafari && !Phoenix.isNativeApp) + || (platformFilter.includes("allBrowser") && !Phoenix.isNativeApp)){ + return true; + } + return false; + } + /** * If there are multiple notifications, thew will be shown one after the other and not all at once. * A sample notifications is as follows: @@ -86,21 +106,18 @@ define(function (require, exports, module) { for(const notificationID of Object.keys(notifications)){ if(!_InAppBannerShownAndDone[notificationID]) { const notification = notifications[notificationID]; - if(!utils.isValidForThisVersion(notification.FOR_VERSIONS)){ + if(!_isValidForThisVersion(notification.FOR_VERSIONS)){ continue; } - if(!utils.isValidForThisPlatform(notification.PLATFORM)){ + if(!_isValidForThisPlatform(notification.PLATFORM)){ continue; } - if(!notification.HTML_CONTENT.includes(NOTIFICATION_ACK_CLASS) - && !notification.DANGER_SHOW_ON_EVERY_BOOT){ + if(!notification.DANGER_SHOW_ON_EVERY_BOOT){ // One time notification. mark as shown and never show again + // all notifications are one time, we track metrics for each notification separately _markAsShownAndDone(notificationID); } await showBannerAndWaitForDismiss(notification.HTML_CONTENT, notificationID); - if(!notification.DANGER_SHOW_ON_EVERY_BOOT){ - _markAsShownAndDone(notificationID); - } } } } @@ -185,25 +202,21 @@ define(function (require, exports, module) { $closeIcon = $notificationBar.find('.close-icon'); $notificationContent.append($htmlContent); - Metrics.countEvent(Metrics.EVENT_TYPE.NOTIFICATIONS, "banner-"+notificationID, - "shown"); + Metrics.countEvent(Metrics.EVENT_TYPE.NOTIFICATIONS, "banner-shown", notificationID); // Click handlers on actionable elements if ($closeIcon.length > 0) { $closeIcon.click(function () { cleanNotificationBanner(); - Metrics.countEvent(Metrics.EVENT_TYPE.NOTIFICATIONS, "banner-"+notificationID, - "closeClick"); + Metrics.countEvent(Metrics.EVENT_TYPE.NOTIFICATIONS, "banner-close", notificationID); !resolved && resolve($htmlContent); resolved = true; }); } $notificationBar.find(`.${NOTIFICATION_ACK_CLASS}`).click(function() { - // Your click event handler logic here cleanNotificationBanner(); - Metrics.countEvent(Metrics.EVENT_TYPE.NOTIFICATIONS, "banner-"+notificationID, - "ackClick"); + Metrics.countEvent(Metrics.EVENT_TYPE.NOTIFICATIONS, "banner-ack", notificationID); !resolved && resolve($htmlContent); resolved = true; }); diff --git a/src/extensionsIntegrated/InAppNotifications/utils.js b/src/extensionsIntegrated/InAppNotifications/utils.js deleted file mode 100644 index d9c64cf08..000000000 --- a/src/extensionsIntegrated/InAppNotifications/utils.js +++ /dev/null @@ -1,47 +0,0 @@ -/* - * GNU AGPL-3.0 License - * - * Copyright (c) 2021 - present core.ai . All rights reserved. - * Original work Copyright (c) 2018 - 2021 Adobe Systems Incorporated. All rights reserved. - * - * This program is free software: you can redistribute it and/or modify it - * under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License - * for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see https://opensource.org/licenses/AGPL-3.0. - * - */ - -define(function (require, exports, module) { - const semver = require("thirdparty/semver.browser"); - function isValidForThisVersion(versionFilter) { - return semver.satisfies(brackets.metadata.apiVersion, versionFilter); - } - - // platformFilter is a string subset of - // "mac,win,linux,allDesktop,firefox,chrome,safari,allBrowser,all" - function isValidForThisPlatform(platformFilter) { - platformFilter = platformFilter.split(","); - if(platformFilter.includes("all") - || (platformFilter.includes(brackets.platform) && Phoenix.isNativeApp) // win linux and mac is only for tauri and not for browser in platform - || (platformFilter.includes("allDesktop") && Phoenix.isNativeApp) - || (platformFilter.includes("firefox") && Phoenix.browser.desktop.isFirefox && !Phoenix.isNativeApp) - || (platformFilter.includes("chrome") && Phoenix.browser.desktop.isChromeBased && !Phoenix.isNativeApp) - || (platformFilter.includes("safari") && Phoenix.browser.desktop.isSafari && !Phoenix.isNativeApp) - || (platformFilter.includes("allBrowser") && !Phoenix.isNativeApp)){ - return true; - } - return false; - } - - // api - exports.isValidForThisVersion = isValidForThisVersion; - exports.isValidForThisPlatform = isValidForThisPlatform; -}); diff --git a/src/utils/Metrics.js b/src/utils/Metrics.js index da4876105..25d7c139c 100644 --- a/src/utils/Metrics.js +++ b/src/utils/Metrics.js @@ -104,7 +104,7 @@ define(function (require, exports, module) { PROJECT: "project", THEMES: "themes", EXTENSIONS: "extensions", - NOTIFICATIONS: "notifications", + NOTIFICATIONS: "notify", UI: "UI", UI_MENU: "UIMenu", UI_DIALOG: "ui-dialog", diff --git a/test/spec/Extn-InAppNotifications-integ-test.js b/test/spec/Extn-InAppNotifications-integ-test.js index d8be2e422..9f946a3dc 100644 --- a/test/spec/Extn-InAppNotifications-integ-test.js +++ b/test/spec/Extn-InAppNotifications-integ-test.js @@ -165,13 +165,9 @@ define(function (require, exports, module) { expect(testWindow.$(id).length).toEqual(1); }); - it("Should show notification if not acknowledged with close click", async function () { + it("Should show notification only once", async function () { banner.cleanNotificationBanner(); const {notification, id} = getRandomNotification("all", false, true); - banner._renderNotifications(notification); - - // clear notification without clicking close - banner.cleanNotificationBanner(); // show the same banner again banner._renderNotifications(notification); @@ -186,27 +182,5 @@ define(function (require, exports, module) { banner._renderNotifications(notification); expect(testWindow.$(id).length).toEqual(0); }); - - it("Should show notification if not acknowledged with click on item with notification ack class", async function () { - banner.cleanNotificationBanner(); - const {notification, id} = getRandomNotification("all", false, true); - banner._renderNotifications(notification); - - // clear notification without clicking close - banner.cleanNotificationBanner(); - - // show the same banner again - banner._renderNotifications(notification); - expect(testWindow.$(id).length).toEqual(1); - - // now close the notification by clicking the close icon - testWindow.$(".notification_ack").click(); - expect(testWindow.$(id).length).toEqual(0); - - await awaits(300); - // acknowledged banner should not show the same banner again - banner._renderNotifications(notification); - expect(testWindow.$(id).length).toEqual(0); - }); }); }); From 7f08f3b403e222cb4297f30e59af0a4574cea32a Mon Sep 17 00:00:00 2001 From: abose Date: Mon, 29 Dec 2025 12:10:48 +0530 Subject: [PATCH 3/4] chore: support for custom filter for in app notifications and tests --- src/assets/notifications/dev/root/toast.json | 2 - .../InAppNotifications/banner.js | 35 ++++++++ .../Extn-InAppNotifications-integ-test.js | 89 ++++++++++++++++++- 3 files changed, 123 insertions(+), 3 deletions(-) delete mode 100644 src/assets/notifications/dev/root/toast.json diff --git a/src/assets/notifications/dev/root/toast.json b/src/assets/notifications/dev/root/toast.json deleted file mode 100644 index 7a73a41bf..000000000 --- a/src/assets/notifications/dev/root/toast.json +++ /dev/null @@ -1,2 +0,0 @@ -{ -} \ No newline at end of file diff --git a/src/extensionsIntegrated/InAppNotifications/banner.js b/src/extensionsIntegrated/InAppNotifications/banner.js index d6c01f669..211969036 100644 --- a/src/extensionsIntegrated/InAppNotifications/banner.js +++ b/src/extensionsIntegrated/InAppNotifications/banner.js @@ -35,6 +35,9 @@ define(function (require, exports, module) { ExtensionUtils.loadStyleSheet(module, "styles/styles.css"); + let latestBannerJSON; + let customFilterCallback; + // duration of one day in milliseconds const ONE_DAY = 1000 * 60 * 60 * 24; const IN_APP_NOTIFICATIONS_BANNER_SHOWN_STATE = "InAppNotificationsBannerShown"; @@ -64,6 +67,14 @@ define(function (require, exports, module) { return false; } + /** + * Registers a custom filter callback function for notifications + * @param {Function} cfbn - async function that filters notifications + */ + function registerCustomFilter(cfbn) { + customFilterCallback = cfbn; + } + /** * If there are multiple notifications, thew will be shown one after the other and not all at once. * A sample notifications is as follows: @@ -112,6 +123,9 @@ define(function (require, exports, module) { if(!_isValidForThisPlatform(notification.PLATFORM)){ continue; } + if(customFilterCallback && !(await customFilterCallback(notification, notificationID))){ + continue; + } if(!notification.DANGER_SHOW_ON_EVERY_BOOT){ // One time notification. mark as shown and never show again // all notifications are one time, we track metrics for each notification separately @@ -136,6 +150,12 @@ define(function (require, exports, module) { return null; } return response.json(); + }) + .then(json => { + if (json !== null) { + latestBannerJSON = json; + } + return json; }); } @@ -167,6 +187,15 @@ define(function (require, exports, module) { }); } + /** + * Re-renders notifications using the latest cached banner JSON + */ + function reRenderNotifications() { + if(latestBannerJSON) { + _renderNotifications(latestBannerJSON); + } + } + /** * Removes and cleans up the notification bar from DOM @@ -232,8 +261,14 @@ define(function (require, exports, module) { setInterval(_fetchAndRenderNotifications, ONE_DAY); }); + exports.registerCustomFilter = registerCustomFilter; + exports.reRenderNotifications = reRenderNotifications; + if(Phoenix.isTestWindow){ exports.cleanNotificationBanner = cleanNotificationBanner; exports._renderNotifications = _renderNotifications; + exports._setBannerCache = function(notifications) { + latestBannerJSON = notifications; + }; } }); diff --git a/test/spec/Extn-InAppNotifications-integ-test.js b/test/spec/Extn-InAppNotifications-integ-test.js index 9f946a3dc..28cd5c68f 100644 --- a/test/spec/Extn-InAppNotifications-integ-test.js +++ b/test/spec/Extn-InAppNotifications-integ-test.js @@ -19,7 +19,7 @@ * */ -/*global describe, it, expect, beforeAll, afterAll, awaits, Phoenix */ +/*global describe, it, expect, beforeAll, afterAll, awaits, awaitsFor */ define(function (require, exports, module) { // Recommended to avoid reloading the integration test window Phoenix instance for each test. @@ -40,6 +40,12 @@ define(function (require, exports, module) { await SpecRunnerUtils.loadProjectInTestWindow(testPath); }, 30000); + async function _waitForBannerShown() { + await awaitsFor(function () { + return testWindow.$('#notification-bar').is(":visible"); + }, "banner to be shown"); + } + afterAll(async function () { testWindow = null; // comment out below line if you want to debug the test window post running tests @@ -182,5 +188,86 @@ define(function (require, exports, module) { banner._renderNotifications(notification); expect(testWindow.$(id).length).toEqual(0); }); + + it("Should apply custom filter to block notification", async function () { + banner.cleanNotificationBanner(); + banner.registerCustomFilter(async () => false); + + const {notification, id} = getRandomNotification("all", true); + banner._renderNotifications(notification); + await awaits(50); + + expect(testWindow.$('#notification-bar').is(":visible")).toBe(false); + expect(testWindow.$(id).length).toEqual(0); + + // Cleanup: remove custom filter + banner.registerCustomFilter(null); + }); + + it("Should apply custom filter to allow notification", async function () { + banner.cleanNotificationBanner(); + banner.registerCustomFilter(async () => true); + + const {notification, id} = getRandomNotification("all", true); + banner._renderNotifications(notification); + await _waitForBannerShown(); + + expect(testWindow.$(id).length).toEqual(1); + + // Cleanup + banner.registerCustomFilter(null); + banner.cleanNotificationBanner(); + }); + + it("Should pass correct parameters to custom filter", async function () { + banner.cleanNotificationBanner(); + let receivedNotification, receivedID; + + const {notification} = getRandomNotification("all", true); + const expectedID = Object.keys(notification)[0]; + + banner.registerCustomFilter(async (notif, notifID) => { + receivedNotification = notif; + receivedID = notifID; + return true; + }); + + banner._renderNotifications(notification); + await _waitForBannerShown(); + + expect(receivedID).toEqual(expectedID); + expect(receivedNotification).toEqual(notification[expectedID]); + + // Cleanup + banner.registerCustomFilter(null); + banner.cleanNotificationBanner(); + }); + + it("Should apply custom filter on reRenderNotifications", async function () { + banner.cleanNotificationBanner(); + + const {notification, id} = getRandomNotification("all", true); + + // Set cache and render + banner._setBannerCache(notification); + banner._renderNotifications(notification); + await _waitForBannerShown(); + expect(testWindow.$(id).length).toEqual(1); + + banner.cleanNotificationBanner(); + + // Set filter to block + banner.registerCustomFilter(async () => false); + + // Re-render should not show notification due to filter + banner.reRenderNotifications(); + await awaits(50); + expect(testWindow.$('#notification-bar').is(":visible")).toBe(false); + expect(testWindow.$(id).length).toEqual(0); + + // Cleanup + banner.registerCustomFilter(null); + banner.cleanNotificationBanner(); + }); }); }); From 4ace00550a28771c7ce088d2f0314c73e2f9a9d8 Mon Sep 17 00:00:00 2001 From: abose Date: Mon, 29 Dec 2025 12:13:25 +0530 Subject: [PATCH 4/4] chore: update deps --- tracking-repos.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tracking-repos.json b/tracking-repos.json index dcc6cd8ce..6b115d5c4 100644 --- a/tracking-repos.json +++ b/tracking-repos.json @@ -1,5 +1,5 @@ { "phoenixPro": { - "commitID": "ef3d7518e411d9ca6c9938af2bc0d5cd8d791e34" + "commitID": "427d40ce3b176fa707ac0ab04ab9b58a4d0b4706" } }