diff --git a/src/assets/notifications/dev/root/banner.json b/src/assets/notifications/dev/root/banner.json index 45f2b5f0c4..dec52ecbdf 100644 --- a/src/assets/notifications/dev/root/banner.json +++ b/src/assets/notifications/dev/root/banner.json @@ -5,4 +5,4 @@ "FOR_VERSIONS": ">=3.0.0", "PLATFORM" : "all" } -} \ No newline at end of file +} diff --git a/src/assets/notifications/dev/root/toast.json b/src/assets/notifications/dev/root/toast.json deleted file mode 100644 index 7a73a41bfd..0000000000 --- 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 deleted file mode 100644 index 8d228037a7..0000000000 --- a/src/extensionsIntegrated/InAppNotifications/banner.js +++ /dev/null @@ -1,226 +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. - * - */ - -/*global Phoenix*/ -/** - * module for displaying in-app banner notifications - * - */ -define(function (require, exports, module) { - - const AppInit = require("utils/AppInit"), - PreferencesManager = require("preferences/PreferencesManager"), - ExtensionUtils = require("utils/ExtensionUtils"), - Metrics = require("utils/Metrics"), - utils = require("./utils"), - NotificationBarHtml = require("text!./htmlContent/notificationContainer.html"); - - ExtensionUtils.loadStyleSheet(module, "styles/styles.css"); - - // duration of one day in milliseconds - const ONE_DAY = 1000 * 60 * 60 * 24; - const IN_APP_NOTIFICATIONS_BANNER_SHOWN_STATE = "InAppNotificationsBannerShown"; - const NOTIFICATION_ACK_CLASS = "notification_ack"; - - // Init default last notification number - PreferencesManager.stateManager.definePreference(IN_APP_NOTIFICATIONS_BANNER_SHOWN_STATE, - "object", {}); - - /** - * If there are multiple notifications, thew will be shown one after the other and not all at once. - * A sample notifications is as follows: - * { - * "SAMPLE_NOTIFICATION_NAME": { - * "DANGER_SHOW_ON_EVERY_BOOT" : false, - * "HTML_CONTENT": "
hello world Click to acknowledge.
", - * "FOR_VERSIONS": "1.x || >=2.5.0 || 5.0.0 - 7.2.3", - * "PLATFORM" : "allDesktop" - * }, - * "ANOTHER_SAMPLE_NOTIFICATION_NAME": {etc} - * } - * By default, a notification is shown only once except if `DANGER_SHOW_ON_EVERY_BOOT` is set - * or there is an html element with class `notification_ack`. - * - * 1. `SAMPLE_NOTIFICATION_NAME` : This is a unique ID. It is used to check if the notification was shown to user. - * 2. `DANGER_SHOW_ON_EVERY_BOOT` : (Default false) Setting this to true will cause the - * notification to be shown on every boot. This is bad ux and only be used if there is a critical security issue - * that we want the version not to be used. - * 3. `HTML_CONTENT`: The actual html content to show to the user. It can have an optional `notification_ack` class. - * Setting this class will cause the notification to be shown once a day until the user explicitly clicks - * on any html element with class `notification_ack` or explicitly click the close button. - * If such a class is not present, then the notification is shown only once ever. - * 4. `FOR_VERSIONS` : [Semver compatible version filter](https://www.npmjs.com/package/semver). - * The notification will be shown to all versions satisfying this. - * 5. `PLATFORM`: A comma seperated list of all platforms in which the message will be shown. - * allowed values are: `mac,win,linux,allDesktop,firefox,chrome,safari,allBrowser,all` - * @param notifications - * @returns {false|*} - * @private - */ - async function _renderNotifications(notifications) { - if(!notifications) { - return; // nothing to show here - } - - const _InAppBannerShownAndDone = PreferencesManager.getViewState( - IN_APP_NOTIFICATIONS_BANNER_SHOWN_STATE); - - for(const notificationID of Object.keys(notifications)){ - if(!_InAppBannerShownAndDone[notificationID]) { - const notification = notifications[notificationID]; - if(!utils.isValidForThisVersion(notification.FOR_VERSIONS)){ - continue; - } - if(!utils.isValidForThisPlatform(notification.PLATFORM)){ - continue; - } - if(!notification.HTML_CONTENT.includes(NOTIFICATION_ACK_CLASS) - && !notification.DANGER_SHOW_ON_EVERY_BOOT){ - // One time notification. mark as shown and never show again - _markAsShownAndDone(notificationID); - } - await showBannerAndWaitForDismiss(notification.HTML_CONTENT, notificationID); - if(!notification.DANGER_SHOW_ON_EVERY_BOOT){ - _markAsShownAndDone(notificationID); - } - } - } - } - - function _markAsShownAndDone(notificationID) { - const _InAppBannersShownAndDone = PreferencesManager.getViewState(IN_APP_NOTIFICATIONS_BANNER_SHOWN_STATE); - _InAppBannersShownAndDone[notificationID] = true; - PreferencesManager.setViewState(IN_APP_NOTIFICATIONS_BANNER_SHOWN_STATE, - _InAppBannersShownAndDone); - } - - function fetchJSON(url) { - return fetch(url) - .then(response => { - if (!response.ok) { - return null; - } - return response.json(); - }); - } - - let inProgress = false; - function _fetchAndRenderNotifications() { - if(inProgress){ - return; - } - inProgress = true; - const locale = brackets.getLocale(); // en-US default - const fetchURL = `${brackets.config.app_notification_url}${locale}/banner.json`; - const defaultFetchURL = `${brackets.config.app_notification_url}root/banner.json`; - // Fetch data from fetchURL first - fetchJSON(fetchURL) - .then(fetchedJSON => { - // Check if fetchedJSON is empty or undefined - if (fetchedJSON === null) { - // Fetch data from defaultFetchURL if fetchURL didn't provide data - return fetchJSON(defaultFetchURL); - } - return fetchedJSON; - }) - .then(_renderNotifications) // Call the render function with the fetched JSON data - .catch(error => { - console.error(`Error fetching and rendering banner.json`, error); - }) - .finally(()=>{ - inProgress = false; - }); - } - - - /** - * Removes and cleans up the notification bar from DOM - */ - function cleanNotificationBanner() { - const $notificationBar = $('#notification-bar'); - if ($notificationBar.length > 0) { - $notificationBar.remove(); - } - } - - /** - * Displays the Notification Bar UI - * - */ - async function showBannerAndWaitForDismiss(htmlStr, notificationID) { - let resolved = false; - return new Promise((resolve)=>{ - const $htmlContent = $(``), - $notificationBarElement = $(NotificationBarHtml); - - // Remove any SCRIPT tag to avoid secuirity issues - $htmlContent.find('script').remove(); - - // Remove any STYLE tag to avoid styling impact on Brackets DOM - $htmlContent.find('style').remove(); - - cleanNotificationBanner(); //Remove an already existing notification bar, if any - $notificationBarElement.prependTo(".content"); - - var $notificationBar = $('#notification-bar'), - $notificationContent = $notificationBar.find('.content-container'), - $closeIcon = $notificationBar.find('.close-icon'); - - $notificationContent.append($htmlContent); - Metrics.countEvent(Metrics.EVENT_TYPE.NOTIFICATIONS, "banner-"+notificationID, - "shown"); - - // Click handlers on actionable elements - if ($closeIcon.length > 0) { - $closeIcon.click(function () { - cleanNotificationBanner(); - Metrics.countEvent(Metrics.EVENT_TYPE.NOTIFICATIONS, "banner-"+notificationID, - "closeClick"); - !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"); - !resolved && resolve($htmlContent); - resolved = true; - }); - }); - } - - - AppInit.appReady(function () { - if(Phoenix.isTestWindow) { - return; - } - _fetchAndRenderNotifications(); - setInterval(_fetchAndRenderNotifications, ONE_DAY); - }); - - if(Phoenix.isTestWindow){ - exports.cleanNotificationBanner = cleanNotificationBanner; - exports._renderNotifications = _renderNotifications; - } -}); diff --git a/src/extensionsIntegrated/InAppNotifications/htmlContent/notificationContainer.html b/src/extensionsIntegrated/InAppNotifications/htmlContent/notificationContainer.html deleted file mode 100644 index b420dee0e7..0000000000 --- a/src/extensionsIntegrated/InAppNotifications/htmlContent/notificationContainer.html +++ /dev/null @@ -1,7 +0,0 @@ -
-
-
-
- -
-
diff --git a/src/extensionsIntegrated/InAppNotifications/main.js b/src/extensionsIntegrated/InAppNotifications/main.js deleted file mode 100644 index d9bc23ef06..0000000000 --- a/src/extensionsIntegrated/InAppNotifications/main.js +++ /dev/null @@ -1,28 +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. - * - */ - -/** - * module for displaying in-app notifications - * - */ -define(function (require, exports, module) { - require("./banner"); -}); diff --git a/src/extensionsIntegrated/InAppNotifications/styles/styles.css b/src/extensionsIntegrated/InAppNotifications/styles/styles.css deleted file mode 100644 index b34d42e1c5..0000000000 --- a/src/extensionsIntegrated/InAppNotifications/styles/styles.css +++ /dev/null @@ -1,55 +0,0 @@ -#notification-bar { - display: block; - box-shadow: 0px 3px 6px rgba(0, 0, 0, 0.53); - padding: 5px 0px; - width: 100%; - min-height: 39px; - position: absolute; - z-index: 16; - left: 0px; - bottom: 25px; - outline: none; - overflow: hidden; - color: rgb(51, 51, 51); - background-color: rgb(223, 226, 226); -} - -.dark #notification-bar { - color: #ccc; - background: #2c2c2c; -} - -#notification-bar .content-container { - padding: 5px 10px; - float: left; - width: 100%; -} - -#notification-bar .close-icon-container { - height: auto; - position: absolute; - float: right; - text-align: center; - width: auto; - min-width: 66px; - right: 20px; - top: 10px; -} - -#notification-bar .close-icon-container .close-icon { - display: block; - font-size: 18px; - line-height: 18px; - text-decoration: none; - width: 18px; - height: 18px; - background-color: transparent; - border: none; - padding: 0px; /*This is needed to center the icon*/ - float: right; -} - -.dark #notification-bar .close-icon-container .close-icon { - color: #ccc; -} - diff --git a/src/extensionsIntegrated/InAppNotifications/utils.js b/src/extensionsIntegrated/InAppNotifications/utils.js deleted file mode 100644 index d9c64cf085..0000000000 --- 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/extensionsIntegrated/loader.js b/src/extensionsIntegrated/loader.js index a93c82ef1d..c98a862426 100644 --- a/src/extensionsIntegrated/loader.js +++ b/src/extensionsIntegrated/loader.js @@ -33,7 +33,6 @@ define(function (require, exports, module) { require("./RemoteFileAdapter/main"); require("./QuickOpen/main"); require("./Phoenix/main"); - require("./InAppNotifications/main"); require("./NoDistractions/main"); require("./Phoenix-live-preview/main"); require("./NavigationAndHistory/main"); diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index 7b0be0991a..7b6c064992 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", diff --git a/src/utils/Metrics.js b/src/utils/Metrics.js index da4876105e..25d7c139c3 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/UnitTestSuite.js b/test/UnitTestSuite.js index 396cbede28..e3f9a3e524 100644 --- a/test/UnitTestSuite.js +++ b/test/UnitTestSuite.js @@ -122,7 +122,6 @@ define(function (require, exports, module) { require("spec/LocalizationUtils-test"); require("spec/ScrollTrackHandler-integ-test"); // Integrated extension tests - require("spec/Extn-InAppNotifications-integ-test"); require("spec/Extn-RemoteFileAdapter-integ-test"); require("spec/Extn-NavigationAndHistory-integ-test"); require("spec/Extn-RecentProjects-integ-test"); diff --git a/test/spec/Extn-InAppNotifications-integ-test.js b/test/spec/Extn-InAppNotifications-integ-test.js deleted file mode 100644 index d8be2e4220..0000000000 --- a/test/spec/Extn-InAppNotifications-integ-test.js +++ /dev/null @@ -1,212 +0,0 @@ -/* - * GNU AGPL-3.0 License - * - * Copyright (c) 2021 - present core.ai . All rights reserved. - * Original work Copyright (c) 2012 - 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. - * - */ - -/*global describe, it, expect, beforeAll, afterAll, awaits, Phoenix */ - -define(function (require, exports, module) { - // Recommended to avoid reloading the integration test window Phoenix instance for each test. - - const SpecRunnerUtils = require("spec/SpecRunnerUtils"); - - const testPath = SpecRunnerUtils.getTestPath("/spec/JSUtils-test-files"); - - let testWindow, banner; - - - describe("integration:In App notification banner integration tests", function () { - - beforeAll(async function () { - testWindow = await SpecRunnerUtils.createTestWindowAndRun(); - banner = testWindow.require("extensionsIntegrated/InAppNotifications/banner"); - - await SpecRunnerUtils.loadProjectInTestWindow(testPath); - }, 30000); - - afterAll(async function () { - testWindow = null; - // comment out below line if you want to debug the test window post running tests - await SpecRunnerUtils.closeTestWindow(); - }, 30000); - - function getRandomNotification(platform, showOnEveryBoot=false, ack = false) { - const notification = {}; - const id = crypto.randomUUID(); - const ackClass = ack? "notification_ack" : ''; - notification[id] = { - "DANGER_SHOW_ON_EVERY_BOOT": showOnEveryBoot, - "HTML_CONTENT": `
random notification ${platform} with id ${id}, DANGER_SHOW_ON_EVERY_BOOT: ${showOnEveryBoot}, ack:${ack}
`, - "FOR_VERSIONS": ">=3.0.0", - "PLATFORM": platform || "all" - }; - return {notification, id: `#${id}`}; - } - - it("Should show notification only once", async function () { - const {notification, id} = getRandomNotification(); - banner._renderNotifications(notification); - expect(testWindow.$(id).length).toEqual(1); - - banner.cleanNotificationBanner(); - banner._renderNotifications(notification); - expect(testWindow.$(id).length).toEqual(0); - }); - - function verifyPlatform(platform) { - banner.cleanNotificationBanner(); - const {notification, id} = getRandomNotification(platform); - banner._renderNotifications(notification); - - const isCurrentPlatform = (Phoenix.platform === platform && Phoenix.isNativeApp); - expect(testWindow.$(id).length).toEqual(isCurrentPlatform ? 1 : 0); - } - - it("Should show notification only in windows tauri", async function () { - verifyPlatform("win"); - }); - - it("Should show notification only in linux tauri", async function () { - verifyPlatform("linux"); - }); - - it("Should show notification only in mac tauri", async function () { - verifyPlatform("mac"); - }); - - it("Should show notification only in any desktop tauri", async function () { - banner.cleanNotificationBanner(); - const {notification, id} = getRandomNotification("allDesktop"); - banner._renderNotifications(notification); - - expect(testWindow.$(id).length).toEqual(Phoenix.isNativeApp ? 1 : 0); - }); - - it("Should show notification only in any win,linux,mac tauri", async function () { - banner.cleanNotificationBanner(); - const {notification, id} = getRandomNotification("win,linux,mac"); - banner._renderNotifications(notification); - - expect(testWindow.$(id).length).toEqual(Phoenix.isNativeApp ? 1 : 0); - }); - - //firefox,chrome,safari,allBrowser, all - function verifyBrowser(platform) { - banner.cleanNotificationBanner(); - const {notification, id} = getRandomNotification(platform); - banner._renderNotifications(notification); - - let currentPlatform = "chrome"; - if(Phoenix.browser.desktop.isFirefox){ - currentPlatform = "firefox"; - } else if(Phoenix.browser.desktop.isSafari){ - currentPlatform = "safari"; - } - const isCurrentPlatform = (currentPlatform === platform && !Phoenix.isNativeApp); - expect(testWindow.$(id).length).toEqual(isCurrentPlatform ? 1 : 0); - } - - it("Should show notification only in firefox non tauri", async function () { - verifyBrowser("firefox"); - }); - - it("Should show notification only in chrome non tauri", async function () { - verifyBrowser("chrome"); - }); - - it("Should show notification only in safari non tauri", async function () { - verifyBrowser("safari"); - }); - - it("Should show notification only in any browser non tauri", async function () { - banner.cleanNotificationBanner(); - const {notification, id} = getRandomNotification("allBrowser"); - banner._renderNotifications(notification); - - expect(testWindow.$(id).length).toEqual(!Phoenix.isNativeApp ? 1 : 0); - }); - - it("Should show notification only in any firefox,chrome,safari tauri", async function () { - banner.cleanNotificationBanner(); - const {notification, id} = getRandomNotification("firefox,chrome,safari"); - banner._renderNotifications(notification); - - expect(testWindow.$(id).length).toEqual(!Phoenix.isNativeApp ? 1 : 0); - }); - - it("Should show notification on every boot", async function () { - banner.cleanNotificationBanner(); - const {notification, id} = getRandomNotification("all", true); - banner._renderNotifications(notification); - - // now close the notification by clicking the close icon - testWindow.$(".close-icon").click(); - expect(testWindow.$(id).length).toEqual(0); - - await awaits(300); - // show the same banner again - banner._renderNotifications(notification); - expect(testWindow.$(id).length).toEqual(1); - }); - - it("Should show notification if not acknowledged with close click", 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.$(".close-icon").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); - }); - - 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); - }); - }); -}); diff --git a/tracking-repos.json b/tracking-repos.json index dcc6cd8ceb..6b115d5c4f 100644 --- a/tracking-repos.json +++ b/tracking-repos.json @@ -1,5 +1,5 @@ { "phoenixPro": { - "commitID": "ef3d7518e411d9ca6c9938af2bc0d5cd8d791e34" + "commitID": "427d40ce3b176fa707ac0ab04ab9b58a4d0b4706" } }