diff --git a/apps/browser/src/background/notification.background.ts b/apps/browser/src/background/notification.background.ts index 64b7ca8a7e..127fc5203d 100644 --- a/apps/browser/src/background/notification.background.ts +++ b/apps/browser/src/background/notification.background.ts @@ -7,6 +7,7 @@ import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.serv import { AuthenticationStatus } from "@bitwarden/common/enums/authenticationStatus"; import { CipherType } from "@bitwarden/common/enums/cipherType"; import { PolicyType } from "@bitwarden/common/enums/policyType"; +import { ThemeType } from "@bitwarden/common/enums/themeType"; import { Utils } from "@bitwarden/common/misc/utils"; import { CipherView } from "@bitwarden/common/models/view/cipher.view"; import { LoginUriView } from "@bitwarden/common/models/view/login-uri.view"; @@ -125,13 +126,13 @@ export default class NotificationBackground { } if (tab != null) { - this.doNotificationQueueCheck(tab); + await this.doNotificationQueueCheck(tab); return; } const currentTab = await BrowserApi.getTabFromCurrentWindow(); if (currentTab != null) { - this.doNotificationQueueCheck(currentTab); + await this.doNotificationQueueCheck(currentTab); } } @@ -144,7 +145,7 @@ export default class NotificationBackground { setTimeout(() => this.cleanupNotificationQueue(), 2 * 60 * 1000); // check every 2 minutes } - private doNotificationQueueCheck(tab: chrome.tabs.Tab): void { + private async doNotificationQueueCheck(tab: chrome.tabs.Tab): Promise { if (tab == null) { return; } @@ -167,6 +168,7 @@ export default class NotificationBackground { type: "add", typeData: { isVaultLocked: this.notificationQueue[i].wasVaultLocked, + theme: await this.getCurrentTheme(), }, }); } else if (this.notificationQueue[i].type === NotificationQueueMessageType.ChangePassword) { @@ -174,6 +176,7 @@ export default class NotificationBackground { type: "change", typeData: { isVaultLocked: this.notificationQueue[i].wasVaultLocked, + theme: await this.getCurrentTheme(), }, }); } @@ -181,6 +184,18 @@ export default class NotificationBackground { } } + private async getCurrentTheme() { + const theme = await this.stateService.getTheme(); + + if (theme !== ThemeType.System) { + return theme; + } + + return window.matchMedia("(prefers-color-scheme: dark)").matches + ? ThemeType.Dark + : ThemeType.Light; + } + private removeTabFromNotificationQueue(tab: chrome.tabs.Tab) { for (let i = this.notificationQueue.length - 1; i >= 0; i--) { if (this.notificationQueue[i].tabId === tab.id) { diff --git a/apps/browser/src/content/notificationBar.ts b/apps/browser/src/content/notificationBar.ts index 92730e1629..76a8a7c277 100644 --- a/apps/browser/src/content/notificationBar.ts +++ b/apps/browser/src/content/notificationBar.ts @@ -498,17 +498,13 @@ document.addEventListener("DOMContentLoaded", (event) => { } function closeExistingAndOpenBar(type: string, typeData: any) { - let barPage = "notification/bar.html"; - switch (type) { - case "add": - barPage = barPage + "?add=1&isVaultLocked=" + typeData.isVaultLocked; - break; - case "change": - barPage = barPage + "?change=1&isVaultLocked=" + typeData.isVaultLocked; - break; - default: - break; - } + const barQueryParams = { + type, + isVaultLocked: typeData.isVaultLocked, + theme: typeData.theme, + }; + const barQueryString = new URLSearchParams(barQueryParams).toString(); + const barPage = "notification/bar.html?" + barQueryString; const frame = document.getElementById("bit-notification-bar-iframe") as HTMLIFrameElement; if (frame != null && frame.src.indexOf(barPage) >= 0) { diff --git a/apps/browser/src/images/close.png b/apps/browser/src/images/close.png deleted file mode 100644 index ccbb3b1d5c..0000000000 Binary files a/apps/browser/src/images/close.png and /dev/null differ diff --git a/apps/browser/src/images/close.svg b/apps/browser/src/images/close.svg new file mode 100644 index 0000000000..62c7001526 --- /dev/null +++ b/apps/browser/src/images/close.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/browser/src/manifest.json b/apps/browser/src/manifest.json index a6c51ea749..3730ac6c56 100644 --- a/apps/browser/src/manifest.json +++ b/apps/browser/src/manifest.json @@ -100,8 +100,7 @@ "web_accessible_resources": [ "notification/bar.html", "images/icon38.png", - "images/icon38_locked.png", - "images/close.png" + "images/icon38_locked.png" ], "applications": { "gecko": { diff --git a/apps/browser/src/manifest.v3.json b/apps/browser/src/manifest.v3.json index 66b39877d9..bbba272c19 100644 --- a/apps/browser/src/manifest.v3.json +++ b/apps/browser/src/manifest.v3.json @@ -106,12 +106,7 @@ }, "web_accessible_resources": [ { - "resources": [ - "notification/bar.html", - "images/icon38.png", - "images/icon38_locked.png", - "images/close.png" - ], + "resources": ["notification/bar.html", "images/icon38.png", "images/icon38_locked.png"], "matches": [""] } ], diff --git a/apps/browser/src/notification/bar.html b/apps/browser/src/notification/bar.html index 101b555df0..6f5b111f04 100644 --- a/apps/browser/src/notification/bar.html +++ b/apps/browser/src/notification/bar.html @@ -15,7 +15,16 @@
diff --git a/apps/browser/src/notification/bar.js b/apps/browser/src/notification/bar.js index 1c1021ad5f..316b817c36 100644 --- a/apps/browser/src/notification/bar.js +++ b/apps/browser/src/notification/bar.js @@ -2,6 +2,9 @@ require("./bar.scss"); document.addEventListener("DOMContentLoaded", () => { + const theme = getQueryVariable("theme"); + document.documentElement.classList.add("theme_" + theme); + let i18n = {}; let lang = window.navigator.language; @@ -39,10 +42,6 @@ document.addEventListener("DOMContentLoaded", () => { var changeButton = document.querySelector("#template-change .change-save"); changeButton.textContent = i18n.notificationChangeSave; - var closeIcon = document.getElementById("close"); - closeIcon.src = chrome.runtime.getURL("images/close.png"); - closeIcon.alt = i18n.close; - var closeButton = document.getElementById("close-button"); closeButton.title = i18n.close; closeButton.setAttribute("aria-label", i18n.close); @@ -51,54 +50,10 @@ document.addEventListener("DOMContentLoaded", () => { document.querySelector("#template-change .change-text").textContent = i18n.notificationChangeDesc; - if (getQueryVariable("add")) { - setContent(document.getElementById("template-add")); - - var addButton = document.querySelector("#template-add-clone .add-save"), // eslint-disable-line - neverButton = document.querySelector("#template-add-clone .never-save"); // eslint-disable-line - - addButton.addEventListener("click", (e) => { - e.preventDefault(); - - const folderId = document.querySelector("#template-add-clone .select-folder").value; - - const bgAddSaveMessage = { - command: "bgAddSave", - folder: folderId, - }; - sendPlatformMessage(bgAddSaveMessage); - }); - - neverButton.addEventListener("click", (e) => { - e.preventDefault(); - sendPlatformMessage({ - command: "bgNeverSave", - }); - }); - - if (!isVaultLocked) { - const responseFoldersCommand = "notificationBarGetFoldersList"; - chrome.runtime.onMessage.addListener((msg) => { - if (msg.command === responseFoldersCommand && msg.data) { - fillSelectorWithFolders(msg.data.folders); - } - }); - sendPlatformMessage({ - command: "bgGetDataForTab", - responseCommand: responseFoldersCommand, - }); - } - } else if (getQueryVariable("change")) { - setContent(document.getElementById("template-change")); - var changeButton = document.querySelector("#template-change-clone .change-save"); // eslint-disable-line - changeButton.addEventListener("click", (e) => { - e.preventDefault(); - - const bgChangeSaveMessage = { - command: "bgChangeSave", - }; - sendPlatformMessage(bgChangeSaveMessage); - }); + if (getQueryVariable("type") === "add") { + handleTypeAdd(isVaultLocked); + } else if (getQueryVariable("type") === "change") { + handleTypeChange(); } closeButton.addEventListener("click", (e) => { @@ -126,6 +81,58 @@ document.addEventListener("DOMContentLoaded", () => { return null; } + function handleTypeAdd(isVaultLocked) { + setContent(document.getElementById("template-add")); + + var addButton = document.querySelector("#template-add-clone .add-save"), // eslint-disable-line + neverButton = document.querySelector("#template-add-clone .never-save"); // eslint-disable-line + + addButton.addEventListener("click", (e) => { + e.preventDefault(); + + const folderId = document.querySelector("#template-add-clone .select-folder").value; + + const bgAddSaveMessage = { + command: "bgAddSave", + folder: folderId, + }; + sendPlatformMessage(bgAddSaveMessage); + }); + + neverButton.addEventListener("click", (e) => { + e.preventDefault(); + sendPlatformMessage({ + command: "bgNeverSave", + }); + }); + + if (!isVaultLocked) { + const responseFoldersCommand = "notificationBarGetFoldersList"; + chrome.runtime.onMessage.addListener((msg) => { + if (msg.command === responseFoldersCommand && msg.data) { + fillSelectorWithFolders(msg.data.folders); + } + }); + sendPlatformMessage({ + command: "bgGetDataForTab", + responseCommand: responseFoldersCommand, + }); + } + } + + function handleTypeChange() { + setContent(document.getElementById("template-change")); + var changeButton = document.querySelector("#template-change-clone .change-save"); // eslint-disable-line + changeButton.addEventListener("click", (e) => { + e.preventDefault(); + + const bgChangeSaveMessage = { + command: "bgChangeSave", + }; + sendPlatformMessage(bgChangeSaveMessage); + }); + } + function setContent(element) { const content = document.getElementById("content"); while (content.firstChild) { diff --git a/apps/browser/src/notification/bar.scss b/apps/browser/src/notification/bar.scss index 415bfee3f7..581b34f376 100644 --- a/apps/browser/src/notification/bar.scss +++ b/apps/browser/src/notification/bar.scss @@ -1,22 +1,31 @@ -body { - background-color: #ffffff; +@import "variables.scss"; + +body { padding: 0; margin: 0; height: 100%; font-size: 14px; line-height: 16px; - color: #333333; - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-family: $font-family-sans-serif; + + @include themify($themes) { + color: themed("textColor"); + background-color: themed("backgroundColor"); + } } .outer-wrapper { padding: 0 10px; - border-bottom: 2px solid #175ddc; + border-bottom: 2px solid transparent; display: grid; grid-template-columns: 24px auto 55px; grid-column-gap: 10px; box-sizing: border-box; min-height: 42px; + + @include themify($themes) { + border-bottom-color: themed("primaryColor"); + } } .inner-wrapper { @@ -41,42 +50,86 @@ img { display: block; } -#close { - width: 15px; - height: 15px; - display: block; - padding: 5px 0; +#close-button { margin-right: 10px; } -button:not(.link), -button:not(.neutral) { - background-color: #175ddc; - padding: 5px 15px; - border-radius: 3px; - color: #ffffff; - border: 0; +#close { + display: block; + width: 17px; + height: 17px; + + > path { + @include themify($themes) { + fill: themed("textColor"); + } + } +} + +#close-button:hover { + @include themify($themes) { + border-color: rgba(themed("textColor"), 0.2); + background-color: rgba(themed("textColor"), 0.2); + } +} + +button { + padding: 0.35rem 15px; + border-radius: $border-radius; + border: 1px solid transparent; + cursor: pointer; +} + +button:not(.neutral):not(.link) { + @include themify($themes) { + background-color: themed("primaryColor"); + color: themed("textContrast"); + border-color: themed("primaryColor"); + } &:hover { - cursor: pointer; - background-color: #1751bd; + @include themify($themes) { + background-color: darken(themed("primaryColor"), 1.5%); + color: darken(themed("textContrast"), 6%); + } } } button.link, button.neutral { - background: none; - padding: 5px 15px; - color: #175ddc; - border: 0; + @include themify($themes) { + background-color: transparent; + color: themed("primaryColor"); + } &:hover { - cursor: pointer; - background: none; text-decoration: underline; + + @include themify($themes) { + background-color: transparent; + color: darken(themed("primaryColor"), 6%); + } } } +select { + padding: 0.35rem; + border: 1px solid #000000; + border-radius: $border-radius; + + @include themify($themes) { + color: themed("textColor"); + background-color: themed("inputBackgroundColor"); + border-color: themed("inputBorderColor"); + } +} + +select, +button { + font-size: $font-size-base; + font-family: $font-family-sans-serif; +} + .select-folder[isVaultLocked="true"] { display: none; } diff --git a/apps/browser/src/notification/variables.scss b/apps/browser/src/notification/variables.scss new file mode 100644 index 0000000000..ced1569199 --- /dev/null +++ b/apps/browser/src/notification/variables.scss @@ -0,0 +1,80 @@ +@import "~nord/src/sass/nord.scss"; + +$font-family-sans-serif: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; +$font-size-base: 14px; +$text-color: #333333; +$border-color-dark: #ddd; +$border-radius: 3px; + +$brand-primary: #175ddc; + +$background-color: #f0f0f0; + +$solarizedDarkBase03: #002b36; +$solarizedDarkBase02: #073642; +$solarizedDarkBase01: #586e75; +$solarizedDarkBase2: #eee8d5; +$solarizedDarkCyan: #2aa198; +$solarizedDarkGreen: #859900; + +$themes: ( + light: ( + textColor: $text-color, + backgroundColor: $background-color, + primaryColor: $brand-primary, + buttonPrimaryColor: $brand-primary, + textContrast: $background-color, + inputBorderColor: darken($border-color-dark, 7%), + inputBackgroundColor: #ffffff, + ), + dark: ( + textColor: #ffffff, + backgroundColor: #2f343d, + buttonPrimaryColor: #6f9df1, + primaryColor: #6f9df1, + textContrast: #2f343d, + inputBorderColor: #4c525f, + inputBackgroundColor: #2f343d, + ), + nord: ( + textColor: $nord5, + backgroundColor: $nord1, + buttonPrimaryColor: $nord8, + primaryColor: $nord9, + textContrast: $nord2, + inputBorderColor: $nord0, + inputBackgroundColor: $nord2, + ), + solarizedDark: ( + textColor: $solarizedDarkBase2, + backgroundColor: $solarizedDarkBase03, + buttonPrimaryColor: $solarizedDarkCyan, + primaryColor: $solarizedDarkGreen, + textContrast: $solarizedDarkBase02, + inputBorderColor: rgba($solarizedDarkBase2, 0.2), + inputBackgroundColor: $solarizedDarkBase01, + ), +); + +@mixin themify($themes: $themes) { + @each $theme, $map in $themes { + html.theme_#{$theme} & { + $theme-map: () !global; + @each $key, $submap in $map { + $value: map-get(map-get($themes, $theme), "#{$key}"); + $theme-map: map-merge( + $theme-map, + ( + $key: $value, + ) + ) !global; + } + @content; + $theme-map: null !global; + } + } +} + +@function themed($key) { + @return map-get($theme-map, $key); +}