diff --git a/src/css/popup-new.css b/src/css/popup-new.css deleted file mode 100644 index bffdbd825..000000000 --- a/src/css/popup-new.css +++ /dev/null @@ -1,1061 +0,0 @@ -/* Global Panel Styles */ - -.fx-relay-panel-wrapper { - --panelWidth: 360px; - /* Any changes to --panelHeight should be reflected in the screen media query */ - --panelHeight: 500px; - background-color: var(--colorGrey05); - min-width: var(--panelWidth); - min-height: var(--panelHeight); - max-width: var(--panelWidth); - color: var(--relayInk70); - overflow: hidden; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - box-sizing: border-box; - font-family: var(--fontStackBase); - font-size: var(--fontSizeBodyMd); - position: relative; - padding: 0; - margin: 0; -} - -.fx-relay-panel-wrapper *, .fx-relay-panel-wrapper *:before, .fx-relay-panel-wrapper *:after { - box-sizing: inherit; -} - -/* Utilities */ -.fx-relay-panel-wrapper .is-hidden { - display: none; -} - -/* Main Content */ - -.fx-relay-panel-content { - padding: var(--spacingMd); -} - -/* FIXME: Refactor to account for height dynamically */ -/* Magic Number: Custom max-height for masks panel */ -#masks-panel[data-account-level="free"] .fx-relay-panel-content { - max-height: 384px; - overflow: auto; -} - -#masks-panel[data-account-level="premium"] .fx-relay-panel-content { - max-height: 336px; - overflow: auto; -} - -/* Header */ - -.fx-relay-menu-header { - border: 0; - box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.12); - /* Bottom corners only */ - border-radius: 0 0 var(--borderRadiusSm) var(--borderRadiusSm); - background-color: var(--colorWhite); - position: sticky; - top: 0; -} - -.fx-relay-menu-header-logo-bar { - display: flex; - justify-content: space-between; - width: 100%; - padding: var(--spacingSm); -} - -.fx-relay-menu-logo { - margin: 0; - padding: 0; - display: flex; - flex-direction: row; - justify-content: center; - gap: var(--spacingSm); -} - -.fx-relay-menu-logo-image-fx-relay { - width: 26px; -} - -.fx-relay-menu-logo-text { - width: 104px; - /* Optical offset to center "Firefox Relay" text in logo */ - margin-top: 2px; -} - -.fx-relay-menu-header-navigation { - display: flex; - gap: var(--spacingXs); -} - -.fx-relay-menu-dashboard-link { - display: flex; - align-items: center; - justify-content: center; - text-align: center; - font-size: var(--fontSizeBodySm); - position: relative; - width: 32px; - height: 32px; -} - -.fx-relay-menu-dashboard-link .news-count { - position: absolute; - right: calc(var(--spacingXs) * -1); - top: calc(var(--spacingXs) * -1); - border-radius: 100%; - width: 16px; - height: 16px; - font-size: var(--fontSizeBodyXs); - display: flex; - justify-content: center; - align-items: center; - background-color: var(--colorError); - color: var(--colorWhite); -} - -.news-count.is-hidden { - display: none; -} - -/* Default icon width */ -.fx-relay-menu-dashboard-link img { - pointer-events: none; - width: 14px; - filter: grayscale(1); -} - -.fx-relay-menu-dashboard-link.is-active img, -.fx-relay-menu-dashboard-link:hover img { - filter: grayscale(0); -} - -.fx-relay-menu-dashboard-link[data-panel-id="settings"] img { - width: 16px; -} - -.fx-relay-menu-dashboard-link.is-active, -.fx-relay-menu-dashboard-link:hover, -.fx-relay-menu-dashboard-link:focus { - background-color: var(--colorGrey10); - border-radius: 100%; -} - -.fx-relay-menu-dashboard-link.is-active .fx-relay-menu-dashboard-link-tooltip, -.fx-relay-menu-dashboard-link .fx-relay-menu-dashboard-link-tooltip { - display: none; - color: var(--colorWhite); - background-color: var(--colorGrey40); - border-radius: var(--borderRadiusSm); - padding: var(--spacingXs) var(--spacingSm); - position: absolute; - top: calc(100% + var(--spacingXs)); - right: 0; - white-space: nowrap; -} - -.fx-relay-menu-dashboard-link:hover .fx-relay-menu-dashboard-link-tooltip, -.fx-relay-menu-dashboard-link:focus .fx-relay-menu-dashboard-link-tooltip, -.fx-relay-menu-dashboard-link:focus-visible .fx-relay-menu-dashboard-link-tooltip { - display: block; - margin: 0 var(--spacingXs); -} - -/* Panel Header */ - -.fx-relay-panel-header { - display: flex; - justify-content: center; - align-items: center; - width: 100%; -} - -.fx-relay-panel-header-btn-back { - position: absolute; - left: var(--spacingSm); - appearance: none; - background-color: transparent; - border: 0; - margin: 0; - padding: var(--spacingXs); - cursor: pointer; -} - -.fx-relay-panel-header-btn-back:focus, -.fx-relay-panel-header-btn-back:hover { - background-color: var(--colorGrey10); - border-radius: 100%; -} - -.fx-relay-panel-header-btn-back img { - display: block; - width: 100%; - width: 24px; - height: 24px; - pointer-events: none; -} - -.fx-relay-panel-header-title { - font-size: var(--fontSizeBodyMd); - font-family: var(--relayMetropolis); - font-weight: 500; -} - -/* Sign Up/In Panel */ -sign-up-panel { - height: 100%; - display: flex; - flex-direction: column; - font-size: var(--fontSizeBodySm); - position: relative; -} - -sign-up-panel::after { - height: 3px; - background: var(--relayFxGradient); - content: ""; - position: absolute; - lefT: 0; - right: 0; - top: -1px; - background-size: 120%; -} - -.fx-relay-sign-in-copy { - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - text-align: center; - padding-top: var(--layoutSm); -} - -.fx-relay-sign-in-copy img { - margin-bottom: var(--spacingMd); -} - -.fx-relay-sign-in-copy h4 { - font-size: var(--fontSizeTitleXs); - font-weight: 700; - color: var(--colorGrey50); - margin: 0 0 var(--spacingMd); - padding: 0; -} - -.fx-relay-sign-in-copy p { - color: var(--colorGrey50); - max-width: var(--contentXs); - margin: 0 auto; -} - -.fx-relay-sign-in-button { - position: fixed; - bottom: 0; - width: 100%; - left: 0; - padding: var(--spacingMd); - background-color: var(--colorGrey05); -} - -/* Settings */ -#settings-panel { - font-size: var(--fontSizeBodyXs); -} - -.fx-relay-settings-toggles { - padding: 0 var(--spacingMd); - background-color: var(--colorWhite); - border-radius: var(--borderRadiusSm); - display: flex; - flex-direction: column; -} - -.fx-relay-settings-toggle-wrapper { - display: flex; - align-items: center; - justify-content: space-between; - width: 100%; - padding: var(--spacingMd) 0; -} - -.fx-relay-settings-toggle-wrapper + -.fx-relay-settings-toggle-wrapper { - border-top: 1px solid var(--colorGrey10); -} - -.fx-relay-settings-toggle { - display: block; - height: 16px; - width: 28px; - min-width: 28px; - position: relative; - overflow: hidden; - border: none; - border-radius: 1.5em; - outline: none; - background-color: var(--colorGreen50); - background-size: 20px; - margin: 0; - cursor: pointer; -} - -.fx-relay-settings-toggle:checked:hover { - background-color: var(--colorGreen60); -} - -.fx-relay-settings-toggle:checked:focus { - box-shadow: var(--colorGreen20); -} - -.fx-relay-settings-toggle:checked:active { - background-color: var(--colorGreen70); -} - -.fx-relay-settings-toggle::after { - content: ""; - height: 10px; - width: 10px; - border-radius: 50%; - background-color: rgba(255, 255, 255, 1); - position: absolute; - top: 0px; - bottom: 0; - margin: auto; - right: 4px; - transition: all 0.2s ease; -} - -.data-disabled::after, -.input-icons-disabled::after { - left: 4px; - right: 18px; - transition: all 0.2s ease; -} - -.data-disabled:hover, -.data-disabled:focus, -.input-icons-disabled:hover, -.input-icons-disabled:focus { - background-color: var(--colorGrey40); -} - -.data-disabled:active, -.input-icons-disabled:active { - background-color: var(--colorGrey50); -} - -.data-disabled, -.input-icons-disabled { - background-color: var(--colorGrey30); -} - -.fx-relay-settings-links { - display: flex; - flex-direction: column; - gap: var(--spacingSm); - padding: var(--spacingMd) 0; -} - -.fx-relay-settings-link { - background-color: transparent; - padding: var(--spacingSm) var(--spacingMd); - text-decoration: none; - color: var(--colorBlack); - cursor: pointer; -} - -.fx-relay-settings-link:hover, -.fx-relay-settings-link:focus { - background-color: var(--colorViolet05); - border-radius: var(--borderRadiusSm); -} - -/* Report Panel */ - -.report-issue-content { - display: flex; - flex-direction: column; - gap: var(--spacingMd); - padding: var(--spacingMd); - border: 0 none; - font-size: var(--fontSizeBodySm); -} - -.report-section { - margin-bottom: var(--spacingMd); - display: flex; - gap: var(--spacingMd); - flex-direction: column; -} - -.report-label::after { - content: ":"; -} - -.report-section ul { - list-style-type: none; - padding: 0; - margin: 0; -} - -.report-section li { - display: flex; - align-items: flex-start; - margin-bottom: var(--spacingMd); -} - -.report-section input[type=checkbox]{ - margin: 2px var(--spacingSm) 0 0; /* Custom Alignment Issue with Checkbox and Label */ - color: var(--colorGrey50); -} - -.report-section input[type=checkbox]:checked + label { - color: var(--colorInformational); -} - -.report-section input[type=text]{ - width: 100%; - padding: var(--spacingSm) var(--spacing2xl) var(--spacingSm) var(--spacingMd); - border-radius: var(--borderRadiusSm); - background-color: var(--colorWhite); - border: 2px solid transparent; - outline: 1px solid var(--colorGrey30); -} - -.report-section input[type=text]:focus{ - outline: 4px solid var(--colorInformationalFocus); - border: 2px solid var(--colorInformational); -} - -.report-issue-content input[type=submit]:disabled{ - background: var(--colorInformationalDisabled); -} - -.report-success { - padding: var(--spacingMd); - padding-top: 0; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - text-align: center; - font-size: var(--fontSizeBodyMd); -} - -.report-image-success { - max-width: 200px; -} - -.report-success h1 { - font-weight: 600; - font-size: var(--fontSizeBodyMd); -} - -.report-continue { - margin-top: var(--spacingLg); - cursor: pointer; - color: var(--colorInformational); -} - -/* Stats */ -.dashboard-stats-list { - width: 100%; - border-radius: var(--borderRadiusSm); - padding: 0 var(--spacingLg); - font-size: var(--fontSizeBodySm); - background: var(--colorWhite); - margin-bottom: var(--spacingMd); -} - -.dashboard-stats-list ul { - list-style-type: none; - padding: 0; - display: flex; - flex-direction: column; -} - -.dashboard-stats-list li:first-child { - font-weight: 600; - color: var(--colorBlack) -} - -.dashboard-stats-list li:not(:first-child){ - padding-left: var(--spacingLg); - color: var(--colorGrey50) -} - -.dashboard-info { - display: flex; - flex-direction: row; - justify-content: space-between; - padding: var(--spacingMd) 0; -} - -.dashboard-info + .dashboard-info { - border-top: 1px solid var(--colorGrey10); -} - -.dashboard-info::before { - content: ""; - /* Custom size/positioning for icon on the stat row */ - width: 20px; - height: 20px; - margin-left: -25px; - position: absolute; - background-repeat: no-repeat; - background-color: rgba(0, 0, 0, 0); - transition: opacity 0.2s ease; -} - -.dashboard-info-emails-blocked::before { - background-image: url("/icons/blocked-icon.svg"); -} - -.dashboard-info-emails-forwarded::before { - background-image: url("/icons/forward-icon.svg"); -} - -.dashboard-info-trackers-removed::before { - background-image: url("/icons/email-trackers-icon.svg"); -} - -.dashboard-stats { - right: 0; - display: flex; - align-items: right; -} - -/* News */ - -.fx-relay-news { - display: flex; - flex-direction: column; - gap: var(--spacingXs); - list-style-type: none; - margin: 0; - padding: 0; - text-align: left; - align-items: flex-start; - justify-content: left; -} - -.fx-relay-news-item button { - appearance: none; - outline: none; - border: none; - background-color: transparent; - background-color: var(--colorWhite); - border-radius: var(--borderRadiusSm); - padding: var(--spacingMd); - cursor: pointer; - display: flex; - gap: var(--spacingMd); -} - -.fx-relay-news-item button:focus, -.fx-relay-news-item button:hover { - background-color: var(--colorPurple05) -} - -.fx-relay-news-item-image { - pointer-events: none; - max-width: var(--layoutMd); - flex-shrink: 0; - flex-grow: 0; -} - -.fx-relay-news-item-image img { - display: block; - width: 100%; -} - -.fx-relay-news-item-content { - text-align: left; - pointer-events: none; - width: 100%; -} - -.fx-relay-news-item-hero { - font-weight: 600; - font-size: var(--fontSizeBodyMd); - margin: 0 0 var(--spacingXs); - line-height: 20px; -} - -.fx-relay-news-item-body { - font-size: var(--fontSizeBodySm); - line-height: 20px; - display: -webkit-box; - -webkit-line-clamp: 2; - -webkit-box-orient: vertical; - overflow: hidden; -} - -.fx-relay-news-story-link { - margin-top: var(--spacingMd); - color: var(--colorInformational); - display: block; - text-decoration: none; -} - -.fx-relay-news-story-link:focus { - color: var(--colorInformationalFocus); -} - -.fx-relay-news-story-link:hover { - color: var(--colorInformationalActive); -} - -.fx-relay-news-story img { - display: block; - width: 100%; -} - -/* Masks Panel */ - -.fx-relay-masks-available-count { - color: var(--colorGrey40); - font-size: var(--fontSizeBodySm); - display: inline-block; - margin-bottom: var(--spacingSm); -} - -.fx-relay-masks-limit-upgrade { - background-color: var(--colorErrorHover); - border-radius: var(--borderRadiusSm); - padding: var(--spacingSm); - color: var(--colorWhite); - font-size: var(--fontSizeBodyXs); - display: flex; - justify-content: center; - gap: var(--spacingSm); - margin-bottom: var(--spacingSm); -} - -.fx-relay-masks-error-message { - background-color: var(--colorError); - border-radius: var(--borderRadiusSm); - color: var(--colorWhite); - font-size: var(--fontSizeBodyXs); - display: none; - margin-bottom: var(--spacingSm); - align-items: stretch; - padding: 0; - cursor: pointer; - overflow: hidden; -} - -.fx-relay-masks-error-message:hover { - background-color: var(--colorErrorHover); -} - -.fx-relay-masks-error-message:active { - background-color: var(--colorErrorActive); -} - -.fx-relay-masks-error-message:focus .fx-relay-masks-error-message-icon { - background-color: var(--colorErrorHover); -} - -.fx-relay-masks-error-message-string { - padding: var(--spacingSm) var(--spacingMd); -} - -.fx-relay-masks-error-message-icon { - width: 40px; - display: flex; - justify-content: center; - align-items: center; - background-color: var(--colorError); -} - -.fx-relay-masks-error-message.is-shown { - display: flex; -} - -.fx-relay-masks-error-message > * { - pointer-events: none; -} - -.fx-relay-no-masks-created { - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - text-align: center; - padding-top: var(--layoutLg); -} - -.fx-relay-no-masks-created img { - max-width: var(--contentXs); - margin-bottom: var(--spacingMd); -} - -.fx-relay-no-masks-created h2 { - font-size: var(--fontSizeTitleXs); - font-weight: 700; - color: var(--colorGrey50); - margin: 0 auto var(--spacingMd); - max-width: var(--content2xs); -} - -.fx-relay-no-masks-created p { - font-size: var(--fontSizeBodySm); - max-width: var(--contentXs); - padding: 0; - margin: 0 0 var(--spacingLg); -} - -.fx-relay-generate-mask-buttons { - border-top: 1px solid var(--colorGrey10); - position: fixed; - bottom: 0; - width: 100%; - left: 0; - padding: var(--spacingMd); - background-color: var(--colorGrey05); - display: flex; - gap: var(--spacingMd); - flex-direction: column; -} - -/* Mask List/Item */ - -.fx-relay-mask-list { - list-style: none; - margin: 0; - padding: 0; -} - -.fx-relay-mask-item { - display: flex; - flex-direction: column; - border-radius: var(--borderRadiusSm); - padding: var(--spacingSm); - position: relative; -} - -.fx-relay-mask-item-new-mask-created { - /* opacity: 1; */ - opacity: 0; - pointer-events: none; - transition: opacity 2s; - background-color: var(--colorGreen50); - border-radius: var(--borderRadiusSm); - padding: var(--spacingXs) var(--spacingSm); - font-size: var(--fontSizeBodyXs); - font-weight: 500; - /* - By allowing this to overlap other elements, - we don't need to reserve empty space for it. - Otherwise, this empty space would push the - .expand-toggle out of the card on small screens: - */ - position: absolute; - left: var(--spacingSm); - top: 100%; - transform: translateY(-50%); -} - -.fx-relay-mask-item.is-new-mask .fx-relay-mask-item-new-mask-created { - pointer-events: auto; - opacity: 1; - /* Don't fade in when appearing: */ - transition: opacity 0s; -} - -.fx-relay-mask-item:focus-within, -.fx-relay-mask-item:focus, -.fx-relay-mask-item:hover { - background-color: var(--colorPurple05); -} - -.fx-relay-mask-item-label { - font-size: var(--fontSizeBodyXs); - color: var(--colorGrey40); - overflow: hidden; - display: block; - line-height: 1; -} - -.fx-relay-mask-item-address-bar { - display: flex; - align-items: center; - gap: var(--spacingMd); -} - -.fx-relay-mask-item-address-wrapper { - display: flex; - gap: var(--spacingXs); - flex-direction: column; -} - -.fx-relay-mask-item-address { - user-select: all; - font-size: var(--fontSizeBodyMd); - font-weight: 600; - color: var(--colorGrey50); - cursor: pointer; - overflow: hidden; - text-overflow: ellipsis; -} - -.fx-relay-mask-item-address::selection { - background: var(--colorPurple10); -} - -.fx-relay-mask-item-address-actions { - margin-left: auto; - display: flex; - gap: var(--spacingSm); - position: relative; -} - -.fx-relay-mask-item-address-actions > button { - appearance: none; - border: 0; - border-radius: 100%; - width: 32px; - height: 32px; - background-repeat: no-repeat; - background-position: center; - filter: grayscale(1); - cursor: pointer; -} - -.fx-relay-mask-item-address-actions > button:focus, -.fx-relay-mask-item-address-actions > button:hover { - background-color: white; - filter: grayscale(0); -} - -.fx-relay-mask-item-address-copy { - background-image: url(/icons/nebula-copy-text.svg); -} - -.fx-relay-mask-item-address-copy-success { - opacity: 0; - pointer-events: none; - transition: opacity 2s; - background-color: var(--colorGreen50); - border-radius: var(--borderRadiusSm); - padding: var(--spacingXs) var(--spacingSm); - font-size: var(--fontSizeBodyXs); - font-weight: 500; - /* - By allowing this to overlap other elements, - we don't need to reserve empty space for it. - Otherwise, this empty space would push the - .expand-toggle out of the card on small screens: - */ - position: absolute; - right: calc(100% + var(--spacingSm)); - top: 50%; - transform: translateY(-50%); -} - -.fx-relay-mask-item-address-copy-success.is-shown { - pointer-events: auto; - opacity: 1; - /* Don't fade in when appearing: */ - transition: opacity 0s; -} - -.fx-relay-mask-item-address-toggle { - background-image: url(/icons/nebula-arrow-up-down.svg); -} - -/* Loading Bar */ - -/* Page Loader */ -.fx-relay-menu-loading-bar { - width: 100%; - height: 5px; - position: relative; - overflow: hidden; - display: none; -} - -.fx-relay-menu-loading-bar-wrapper { - width: 100%; - height: 2px; - position: absolute; - left: 50%; - top: 1px; -} - -.fx-relay-menu-loading-bar-border { - height: 100%; - width: 100%; - position: relative; - left: -50%; - top: -50%; - padding: 0; - background-color: var(--colorViolet20); - padding: 0; -} - -.fx-relay-menu-loading-bar-whitespace { - overflow: hidden; - height: 100%; - width: 100%; - margin: 0 auto; - overflow: hidden; - position: relative; -} - -.fx-relay-menu-loading-bar-line { - position: absolute; - height: 100%; - width: 60%; - background-color: var(--colorViolet70); - animation: cssload-slide 1s ease-in-out infinite; -} - -@keyframes cssload-slide { - 0% { - left: -100%; - } - - 100% { - left: 100%; - } -} - -.is-loading .fx-relay-menu-loading-bar { - display: block; -} - -.is-loading .fx-relay-menu { - min-height: 200px; -} - -.is-loading .fx-relay-menu-content { - visibility: hidden; -} - -/* Mask - Generate Custom Panel */ - - -.fx-relay-panel-custom-mask-form { - display: flex; - flex-direction: column; - gap: var(--spacingMd); - padding: var(--spacingMd); - border: 0 none; - font-size: var(--fontSizeBodySm); -} - -.fx-relay-panel-custom-mask-input { - margin-bottom: var(--spacingMd); - display: flex; - gap: var(--spacingMd); - flex-direction: column; -} - -.fx-relay-panel-custom-mask-checkbox { - display: flex; - align-items: flex-start; - margin-bottom: var(--spacingMd); - position: relative; -} - -.fx-relay-panel-custom-mask-checkbox input[type=checkbox]{ - margin: 2px var(--spacingSm) 0 0; /* Custom Alignment Issue with Checkbox and Label */ - color: var(--colorGrey50); -} - -.fx-relay-panel-custom-mask-checkbox input[type=checkbox]:checked + label { - color: var(--colorInformational); -} - -.fx-relay-panel-custom-mask-input input[type=text]{ - width: 100%; - padding: var(--spacingSm) var(--spacing2xl) var(--spacingSm) var(--spacingMd); - border-radius: var(--borderRadiusSm); - background-color: var(--colorWhite); - border: 2px solid transparent; - outline: 1px solid var(--colorGrey30); -} - -.fx-relay-panel-custom-mask-input-domain { - text-align: center; -} - -.fx-relay-panel-custom-mask-input input[type=text]:focus{ - outline: 4px solid var(--colorInformationalFocus); - border: 2px solid var(--colorInformational); -} - -.fx-relay-panel-custom-mask-submit input[type=submit]:disabled{ - background: var(--colorInformationalDisabled); -} - -.fx-relay-panel-custom-mask-copy { - font-size: var(--fontSizeBodySm); - padding: 0 var(--spacingMd) var(--spacingMd); - margin: 0; - border-bottom: 1px solid var(--colorGrey10); - overflow-wrap: break-word; - -} - -.fx-relay-panel-custom-mask-promo-blocking-icon { - width: 16px; - height: 16px; - margin-left: var(--spacingSm); -} - -.fx-relay-panel-custom-mask-promo-blocking-tooltip-wrapper:focus .fx-relay-panel-custom-mask-promo-blocking-tooltip, -.fx-relay-panel-custom-mask-promo-blocking-tooltip-wrapper:hover .fx-relay-panel-custom-mask-promo-blocking-tooltip { - display: flex; -} - -.fx-relay-panel-custom-mask-promo-blocking-tooltip { - display: none; - position: absolute; - left: 0; - bottom: calc(100%); - padding: var(--spacingMd); - box-shadow: 0px 0px 4px var(--colorGrey20); - z-index: 1; - background-color: var(--colorWhite); - border-radius: var(--borderRadiusSm); - flex-direction: column; - gap: var(--spacingSm); - /* This negative margin positions the promo blocking checkbox ABOVE the item */ - margin-top: calc(var(--spacingMd) * -1); -} - -.fx-relay-panel-custom-mask-promo-blocking-tooltip h3 { - font-size: var(--fontSizeBodyMd); - margin: 0; - padding: 0; -} - -.fx-relay-panel-custom-mask-promo-blocking-tooltip p { - font-size: var(--fontSizeBodySm); - margin: 0; - padding: 0; -} - -.fx-relay-panel-custom-mask-promo-blocking-tooltip a { - font-size: var(--fontSizeBodySm); - margin: 0; - padding: 0; -} - -/* Search Bar */ - -.fx-relay-masks-search-form { - padding: var(--spacingXs) var(--spacingSm); - margin-bottom: var(--spacingSm); - position: relative; - display: none; -} - -.fx-relay-masks-search-form.is-visible { - display: block; -} diff --git a/src/css/popup.css b/src/css/popup.css index 994c0b571..bffdbd825 100644 --- a/src/css/popup.css +++ b/src/css/popup.css @@ -1,545 +1,523 @@ +/* Global Panel Styles */ -.fx-relay-panel { +.fx-relay-panel-wrapper { --panelWidth: 360px; /* Any changes to --panelHeight should be reflected in the screen media query */ --panelHeight: 500px; - background-color: var(--colorWhite); + background-color: var(--colorGrey05); min-width: var(--panelWidth); min-height: var(--panelHeight); + max-width: var(--panelWidth); color: var(--relayInk70); overflow: hidden; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; box-sizing: border-box; font-family: var(--fontStackBase); + font-size: var(--fontSizeBodyMd); + position: relative; + padding: 0; + margin: 0; } -.fx-relay-panel *, .fx-relay-panel *:before, .fx-relay-panel *:after { +.fx-relay-panel-wrapper *, .fx-relay-panel-wrapper *:before, .fx-relay-panel-wrapper *:after { box-sizing: inherit; } -sign-up-panel { - height: 100%; - display: flex; - flex-direction: column; - font-size: 14px; - position: relative; +/* Utilities */ +.fx-relay-panel-wrapper .is-hidden { + display: none; } -sign-up-panel::after { - height: 3px; - background: var(--relayFxGradient); - content: ""; - position: absolute; - lefT: 0; - right: 0; - top: -1px; - background-size: 120%; +/* Main Content */ + +.fx-relay-panel-content { + padding: var(--spacingMd); } -::-moz-focus-inner { - border: 0; +/* FIXME: Refactor to account for height dynamically */ +/* Magic Number: Custom max-height for masks panel */ +#masks-panel[data-account-level="free"] .fx-relay-panel-content { + max-height: 384px; + overflow: auto; } -p { - margin-top: 0; - line-height: 1.5; - text-align: center; +#masks-panel[data-account-level="premium"] .fx-relay-panel-content { + max-height: 336px; + overflow: auto; } -settings, report-issue, report-issue-success { - background-color: rgba(255, 255, 255, 1); - position: absolute; +/* Header */ + +.fx-relay-menu-header { + border: 0; + box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.12); + /* Bottom corners only */ + border-radius: 0 0 var(--borderRadiusSm) var(--borderRadiusSm); + background-color: var(--colorWhite); + position: sticky; top: 0; - bottom: 0; - left: 0; - right: 0; - visibility: hidden; - z-index: 2; - transform: translateX(360px); - transition: all 0.5s ease; - font-size: 14px; - line-height: 1.4; - padding: 0 24px; } -.report-issue-content { +.fx-relay-menu-header-logo-bar { display: flex; - flex-direction: column; - gap: 16px; - padding: 0; - border: 0 none; + justify-content: space-between; + width: 100%; + padding: var(--spacingSm); } -.report-section { - margin-bottom: 8px; +.fx-relay-menu-logo { + margin: 0; + padding: 0; display: flex; - gap: 8px; - flex-direction: column; + flex-direction: row; + justify-content: center; + gap: var(--spacingSm); } -.report-label::after { - content: ":"; +.fx-relay-menu-logo-image-fx-relay { + width: 26px; } -.report-section ul { - list-style-type: none; - padding: 0; - margin: 0; +.fx-relay-menu-logo-text { + width: 104px; + /* Optical offset to center "Firefox Relay" text in logo */ + margin-top: 2px; } -.report-section li { +.fx-relay-menu-header-navigation { display: flex; - align-items:flex-start; - margin-bottom: 8px; -} - -.report-section input[type=checkbox]{ - margin-right: 8px; -} - -.report-section input[type=text]{ - width: 100%; - padding: 4px; + gap: var(--spacingXs); } -.report-section input[type=submit]:disabled{ - background: var(--colorInformationalDisabled); +.fx-relay-menu-dashboard-link { + display: flex; + align-items: center; + justify-content: center; + text-align: center; + font-size: var(--fontSizeBodySm); + position: relative; + width: 32px; + height: 32px; } -.report-success { - padding-top: 24px; +.fx-relay-menu-dashboard-link .news-count { + position: absolute; + right: calc(var(--spacingXs) * -1); + top: calc(var(--spacingXs) * -1); + border-radius: 100%; + width: 16px; + height: 16px; + font-size: var(--fontSizeBodyXs); display: flex; - flex-direction: column; justify-content: center; align-items: center; + background-color: var(--colorError); + color: var(--colorWhite); } -.report-success h1 { - font-weight: 700; - font-size: 16px; +.news-count.is-hidden { + display: none; } -.report-continue { - cursor: pointer; - color: var(--relayBlue3); +/* Default icon width */ +.fx-relay-menu-dashboard-link img { + pointer-events: none; + width: 14px; + filter: grayscale(1); } -.setting { - padding: 12px 0; - border-bottom: 1px solid var(--relayGrey20); - display: flex; - justify-content: space-between; +.fx-relay-menu-dashboard-link.is-active img, +.fx-relay-menu-dashboard-link:hover img { + filter: grayscale(0); } -.flex-col { - flex-direction: column; +.fx-relay-menu-dashboard-link[data-panel-id="settings"] img { + width: 16px; } -.setting-label { - display: inline-block; - color: var(--relayInk70); +.fx-relay-menu-dashboard-link.is-active, +.fx-relay-menu-dashboard-link:hover, +.fx-relay-menu-dashboard-link:focus { + background-color: var(--colorGrey10); + border-radius: 100%; } -a.setting-label { - text-decoration: none; +.fx-relay-menu-dashboard-link.is-active .fx-relay-menu-dashboard-link-tooltip, +.fx-relay-menu-dashboard-link .fx-relay-menu-dashboard-link-tooltip { + display: none; + color: var(--colorWhite); + background-color: var(--colorGrey40); + border-radius: var(--borderRadiusSm); + padding: var(--spacingXs) var(--spacingSm); + position: absolute; + top: calc(100% + var(--spacingXs)); + right: 0; + white-space: nowrap; } -a.setting-label:hover { - background-color: var(--relayGrey20); +.fx-relay-menu-dashboard-link:hover .fx-relay-menu-dashboard-link-tooltip, +.fx-relay-menu-dashboard-link:focus .fx-relay-menu-dashboard-link-tooltip, +.fx-relay-menu-dashboard-link:focus-visible .fx-relay-menu-dashboard-link-tooltip { + display: block; + margin: 0 var(--spacingXs); } -a.setting-label:focus { - box-shadow: var(--relayButtonFocus); -} +/* Panel Header */ -a.setting-label:active { - background-color: var(--relayGrey30); +.fx-relay-panel-header { + display: flex; + justify-content: center; + align-items: center; + width: 100%; } -.settings-list { - padding: 0; - margin: 0; - list-style-type: none; +.fx-relay-panel-header-btn-back { + position: absolute; + left: var(--spacingSm); + appearance: none; + background-color: transparent; + border: 0; margin: 0; + padding: var(--spacingXs); + cursor: pointer; } -.setting-link { - margin-bottom: 12px; +.fx-relay-panel-header-btn-back:focus, +.fx-relay-panel-header-btn-back:hover { + background-color: var(--colorGrey10); + border-radius: 100%; } -.setting-link:last-of-type { - margin-bottom: 0; +.fx-relay-panel-header-btn-back img { + display: block; + width: 100%; + width: 24px; + height: 24px; + pointer-events: none; } -fx-relay-logo-wrapper { - background-color: white; +.fx-relay-panel-header-title { + font-size: var(--fontSizeBodyMd); + font-family: var(--relayMetropolis); + font-weight: 500; } -.settings-header, -fx-relay-logo-wrapper { +/* Sign Up/In Panel */ +sign-up-panel { + height: 100%; display: flex; - flex-direction: row; - justify-content: flex-start; + flex-direction: column; + font-size: var(--fontSizeBodySm); + position: relative; +} + +sign-up-panel::after { + height: 3px; + background: var(--relayFxGradient); + content: ""; + position: absolute; + lefT: 0; + right: 0; + top: -1px; + background-size: 120%; +} + +.fx-relay-sign-in-copy { + display: flex; + flex-direction: column; + justify-content: center; align-items: center; - padding: 0 24px 0 24px; - width: 100%; - min-height: 64px; - height: 64px; - max-height: 64px; + text-align: center; + padding-top: var(--layoutSm); } -.show-settings settings { - visibility: visible; - transform: translateX(0); - transition: all 0.3s ease; +.fx-relay-sign-in-copy img { + margin-bottom: var(--spacingMd); } -.show-settings fx-relay-logo-wrapper, -.show-settings .signed-in-panel { - opacity: 0; - visibility: hidden; - transition: all 0.3s ease; +.fx-relay-sign-in-copy h4 { + font-size: var(--fontSizeTitleXs); + font-weight: 700; + color: var(--colorGrey50); + margin: 0 0 var(--spacingMd); + padding: 0; } -.show-report-issue report-issue { - visibility: visible; - transform: translateX(0); - transition: all 0.3s ease; +.fx-relay-sign-in-copy p { + color: var(--colorGrey50); + max-width: var(--contentXs); + margin: 0 auto; } -.show-report-issue fx-relay-logo-wrapper, -.show-report-issue settings { - opacity: 0; - visibility: hidden; - transition: all 0.3s ease; +.fx-relay-sign-in-button { + position: fixed; + bottom: 0; + width: 100%; + left: 0; + padding: var(--spacingMd); + background-color: var(--colorGrey05); } -fx-relay-logo-wrapper, -.signed-in-panel { - opacity: 1; - transition: all 0.5s ease; +/* Settings */ +#settings-panel { + font-size: var(--fontSizeBodyXs); } -.settings-hl { - margin: auto; - justify-content: center; - text-align: center; - font-size: 18px; - font-family: var(--relayMetropolis); - font-weight: 600; +.fx-relay-settings-toggles { + padding: 0 var(--spacingMd); + background-color: var(--colorWhite); + border-radius: var(--borderRadiusSm); + display: flex; + flex-direction: column; } -.close-settings { - background-image: url("/images/arrowhead-left.svg"); - left: 24px; +.fx-relay-settings-toggle-wrapper { + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + padding: var(--spacingMd) 0; } -.open-settings { - background-image: url("/images/preferences.svg"); - right: 24px; +.fx-relay-settings-toggle-wrapper + +.fx-relay-settings-toggle-wrapper { + border-top: 1px solid var(--colorGrey10); } -.settings-report-issue { +.fx-relay-settings-toggle { + display: block; + height: 16px; + width: 28px; + min-width: 28px; + position: relative; + overflow: hidden; + border: none; + border-radius: 1.5em; + outline: none; + background-color: var(--colorGreen50); + background-size: 20px; + margin: 0; cursor: pointer; } -.settings-toggle:hover, .settings-report-issue-return:hover { - opacity: .8; +.fx-relay-settings-toggle:checked:hover { + background-color: var(--colorGreen60); } -.settings-toggle:focus, .settings-report-issue-return:focus { - box-shadow: var(--relayButtonFocus); +.fx-relay-settings-toggle:checked:focus { + box-shadow: var(--colorGreen20); } -.settings-toggle:active, .settings-report-issue-return:active { - opacity: 1; +.fx-relay-settings-toggle:checked:active { + background-color: var(--colorGreen70); } -.settings-toggle, .settings-report-issue-return { +.fx-relay-settings-toggle::after { + content: ""; + height: 10px; + width: 10px; border-radius: 50%; - background-repeat: no-repeat; - background-size: 18px; - background-position: center center; - height: 26px; - width: 26px; + background-color: rgba(255, 255, 255, 1); position: absolute; - background-color: rgba(255, 255, 255, 0); - border: 1px solid rgba(255, 255, 255, 0); -} - -.intro { - margin: 0 auto 16px auto; - max-width: 260px; - color: var(--relayInkLight); - line-height: 1.3; - font-size: 15px; + top: 0px; + bottom: 0; + margin: auto; + right: 4px; + transition: all 0.2s ease; } -.hidden, -panel-content { +.data-disabled::after, +.input-icons-disabled::after { + left: 4px; + right: 18px; transition: all 0.2s ease; } -panel-content { - display: flex; - flex-direction: column; - position: absolute; - top: 0; - right: 0; - left: 0; - bottom: 0; - overflow: hidden; +.data-disabled:hover, +.data-disabled:focus, +.input-icons-disabled:hover, +.input-icons-disabled:focus { + background-color: var(--colorGrey40); } -.button { - text-decoration: none; +.data-disabled:active, +.input-icons-disabled:active { + background-color: var(--colorGrey50); } -.welcome { - font-weight: 800; - font-size: 28px; - line-height: 1.2; - max-width: 260px; - text-align: center; - color: var(--relayInk); - font-family: var(--relayMetropolisBold); - margin: auto auto 8px auto; +.data-disabled, +.input-icons-disabled { + background-color: var(--colorGrey30); } -.blue-primary-btn { +.fx-relay-settings-links { display: flex; - align-items: center; - justify-content: center; - background: var(--colorInformational); - color: var(--relayInk); - font-family: var(--relayMetropolis); - font-weight: 800; - font-size: 15px; - border: 0 none; - text-decoration: none; - color: rgba(255, 255, 255, 1); - min-width: 200px; - min-height: 48px; - margin: 0 auto auto auto; - padding: 12px 16px; - border-radius: 4px; -} - -.blue-primary-btn:hover { - background: var(--relayBlueHover); + flex-direction: column; + gap: var(--spacingSm); + padding: var(--spacingMd) 0; } -.blue-primary-btn:focus { - box-shadow: var(--relayButtonFocus); +.fx-relay-settings-link { + background-color: transparent; + padding: var(--spacingSm) var(--spacingMd); + text-decoration: none; + color: var(--colorBlack); + cursor: pointer; } -.blue-primary-btn:active { - background: var(--relayBlueActive); +.fx-relay-settings-link:hover, +.fx-relay-settings-link:focus { + background-color: var(--colorViolet05); + border-radius: var(--borderRadiusSm); } -.survey-link:hover { - color: var(--relayBlueHover); -} +/* Report Panel */ -main-panel { +.report-issue-content { display: flex; flex-direction: column; - height: 100%; -} - -.text-link { - font-weight: 700; + gap: var(--spacingMd); + padding: var(--spacingMd); + border: 0 none; + font-size: var(--fontSizeBodySm); } -.panel-status { +.report-section { + margin-bottom: var(--spacingMd); display: flex; - justify-content: space-between; - padding: 8px 16px; - line-height: 150%; - font-size: 1rem; - border-bottom: 1px solid var(--relayGrey20); - border-top: 1px solid var(--relayGrey20); - width: 100%; - font-size: 13px; - font-family: var(--relayInterUiRegular); - margin-bottom: var(--spacingLg); + gap: var(--spacingMd); + flex-direction: column; } -.aliases-remaining { - margin: 0; - font-family: var(--relayInterUiRegular); - text-align: start; +.report-label::after { + content: ":"; } -.premium-cta { +.report-section ul { + list-style-type: none; + padding: 0; margin: 0; - font-family: var(--relayInterUiRegular); - color: var(--colorInformational); - font-weight: 700; - text-decoration: none; } -.premium-cta a { - margin: 0; +.report-section li { + display: flex; + align-items: flex-start; + margin-bottom: var(--spacingMd); } -.num-aliases-remaining, -.max-num-aliases { - font-weight: 700; +.report-section input[type=checkbox]{ + margin: 2px var(--spacingSm) 0 0; /* Custom Alignment Issue with Checkbox and Label */ + color: var(--colorGrey50); } -/*upgrade banner*/ +.report-section input[type=checkbox]:checked + label { + color: var(--colorInformational); +} -.upgrade-banner-wrapper { - background: var(--relayGrey20); - border-radius: 8px; - padding: 10px; - margin-top: 8px; - display: flex; - text-decoration: none; +.report-section input[type=text]{ + width: 100%; + padding: var(--spacingSm) var(--spacing2xl) var(--spacingSm) var(--spacingMd); + border-radius: var(--borderRadiusSm); + background-color: var(--colorWhite); + border: 2px solid transparent; + outline: 1px solid var(--colorGrey30); } -.upgrade-banner-wrapper img { - width: 30px; - height: 30px; - margin: auto; +.report-section input[type=text]:focus{ + outline: 4px solid var(--colorInformationalFocus); + border: 2px solid var(--colorInformational); } -.upgrade-banner { - font-family: var(--relayInterUiRegular); - padding-left: 10px; - color: #5953F3; - margin: auto; - font-weight: 700; +.report-issue-content input[type=submit]:disabled{ + background: var(--colorInformationalDisabled); } -footer { +.report-success { + padding: var(--spacingMd); + padding-top: 0; display: flex; flex-direction: column; + justify-content: center; align-items: center; - margin: auto 0 0 0; - padding-top: var(--spacingMd); - border-top: 0.5px solid var(--colorGrey10); -} - -.footer-button { - width: 100%; - font-size: 13px; - padding-bottom: var(--spacingMd); - color: var(--colorInformational); - font-weight: 600; - text-decoration: none; - font-family: var(--relayInterUiRegular); text-align: center; + font-size: var(--fontSizeBodyMd); } -.footer-button:hover { - color: var(--colorInformationalHover) +.report-image-success { + max-width: 200px; } -.footer-button:active { - background-color: var(--relayGrey50); +.report-success h1 { + font-weight: 600; + font-size: var(--fontSizeBodyMd); } -.footer-button:focus { - color: var(--colorInformationalFocus) +.report-continue { + margin-top: var(--spacingLg); + cursor: pointer; + color: var(--colorInformational); } -/* onboarding */ +/* Stats */ +.dashboard-stats-list { + width: 100%; + border-radius: var(--borderRadiusSm); + padding: 0 var(--spacingLg); + font-size: var(--fontSizeBodySm); + background: var(--colorWhite); + margin-bottom: var(--spacingMd); +} -onboarding-panel { - font-size: 14px !important; - padding: 8px 30px 30px 30px; +.dashboard-stats-list ul { + list-style-type: none; + padding: 0; display: flex; - align-items: flex-start; - height: 100%; flex-direction: column; - position: relative; - transition: all 0.2s ease; - background: white; } -/* Premium Panel */ +.dashboard-stats-list li:first-child { + font-weight: 600; + color: var(--colorBlack) +} -.premiumPanel { - padding: 8px 16px 0px 16px; - font-family: var(--relayInterUiRegular); +.dashboard-stats-list li:not(:first-child){ + padding-left: var(--spacingLg); + color: var(--colorGrey50) } -.content-wrapper, .premium-wrapper { - display: flex; - width: 100%; - flex-direction: column; - gap: var(--spacingSm); -} - -.end-of-intro-pricing-wrapper { - display: flex; - height: 360px; - width: 100%; - flex-direction: column; - justify-content: space-around; -} - -.dashboard-stats-list { - width: 100%; - border-radius: 8px; - padding: 0 15px; - font-size: 13px; - background: #F7F7F7; -} - -.dashboard-stats-list ul { - list-style-type: none; - padding: 0; -} - -.dashboard-stats-list li:first-child { - font-weight: 600; - padding-bottom: 12px; -} - -.dashboard-stats-list li:nth-child(2) { - padding: 12px 0; -} - -.dashboard-stats-list li:nth-child(3) { - padding: 12px 0; -} - -.dashboard-stats-list li:not(:first-child){ - border-top: 1px solid var(--relayGrey20); - padding-top: 12px; - padding-left: 30px; -} - -.dashboard-info { +.dashboard-info { display: flex; flex-direction: row; justify-content: space-between; + padding: var(--spacingMd) 0; +} + +.dashboard-info + .dashboard-info { + border-top: 1px solid var(--colorGrey10); } .dashboard-info::before { content: ""; + /* Custom size/positioning for icon on the stat row */ width: 20px; height: 20px; - margin-left: -25px; + margin-left: -25px; position: absolute; background-repeat: no-repeat; background-color: rgba(0, 0, 0, 0); transition: opacity 0.2s ease; } -.dashboard-info:nth-child(2)::before { +.dashboard-info-emails-blocked::before { background-image: url("/icons/blocked-icon.svg"); } -.dashboard-info:nth-child(3)::before { +.dashboard-info-emails-forwarded::before { background-image: url("/icons/forward-icon.svg"); } -.dashboard-info:nth-child(4)::before { +.dashboard-info-trackers-removed::before { background-image: url("/icons/email-trackers-icon.svg"); } @@ -549,452 +527,535 @@ onboarding-panel { align-items: right; } -.email-domain-illustration { - margin: 0 auto; - display: block; - height: 80px; -} +/* News */ -.register-domain-component { - margin: 0 auto; - padding-top: var(--spacingMd); +.fx-relay-news { display: flex; flex-direction: column; - height: auto; - gap: var(--spacingSm); + gap: var(--spacingXs); + list-style-type: none; + margin: 0; + padding: 0; + text-align: left; + align-items: flex-start; + justify-content: left; } -.register-domain-cta { - max-width: 200px; - font-size: 1rem; - padding: var(--spacingXs) var(--spacingSm); - text-align: center; +.fx-relay-news-item button { + appearance: none; + outline: none; + border: none; + background-color: transparent; + background-color: var(--colorWhite); + border-radius: var(--borderRadiusSm); + padding: var(--spacingMd); + cursor: pointer; + display: flex; + gap: var(--spacingMd); } -.register-domain-headline { - font-weight: 700; - font-size: 14px; - text-align: center; +.fx-relay-news-item button:focus, +.fx-relay-news-item button:hover { + background-color: var(--colorPurple05) } -.educational-component { - display: flex; - flex-direction: row; - padding: var(--spacingSm) 0; - justify-content: space-between; - height: auto; - gap: var(--spacingMd); - align-items: start; +.fx-relay-news-item-image { + pointer-events: none; + max-width: var(--layoutMd); + flex-shrink: 0; + flex-grow: 0; } -.education-description { - display: flex; - flex-direction: column; - flex: 2; +.fx-relay-news-item-image img { + display: block; width: 100%; } -.education-img-wrapper { - display: flex; - justify-content: center; - align-items: center; - flex: 1.5; - height: auto; - max-width: 120px; +.fx-relay-news-item-content { + text-align: left; + pointer-events: none; + width: 100%; } -#bundle-phones-promo #panel1 .education-img-wrapper, -#bundle-phones-promo #panel2 .education-img-wrapper - { - box-shadow: var(--boxShadowSm); - border-radius: var(--borderRadiusMd); - display: flex; - justify-content: center; - align-items: center; +.fx-relay-news-item-hero { + font-weight: 600; + font-size: var(--fontSizeBodyMd); + margin: 0 0 var(--spacingXs); + line-height: 20px; +} + +.fx-relay-news-item-body { + font-size: var(--fontSizeBodySm); + line-height: 20px; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; overflow: hidden; - object-fit: cover; - height: auto; } -.education-img { - width: 100%; - height: auto; - justify-content: center; - align-items: center; - display: flex; +.fx-relay-news-story-link { + margin-top: var(--spacingMd); + color: var(--colorInformational); + display: block; + text-decoration: none; } -.education-headline { - font-family: var(--relayInterUiRegular); - font-weight: 700; - font-size: 15px; +.fx-relay-news-story-link:focus { + color: var(--colorInformationalFocus); } -.onboarding-h1, .educational-headline { - font-size: 15px; - font-family: var(--relayMetropolis); - margin: 0; +.fx-relay-news-story-link:hover { + color: var(--colorInformationalActive); } -onboarding-panel p, .education-body { - text-align: start; - margin: 0; - font-size: 12.5px; - line-height: 130%; +.fx-relay-news-story img { + display: block; + width: 100%; } -/* Add this class if description is too long */ -.small-font-size { - font-size: 11px; +/* Masks Panel */ + +.fx-relay-masks-available-count { + color: var(--colorGrey40); + font-size: var(--fontSizeBodySm); + display: inline-block; + margin-bottom: var(--spacingSm); } -.onboarding-cta { - cursor: pointer; - font-weight: 600; - font-size: 12.5px; - line-height: 170%; - text-decoration: underline; - color: var(--colorInformational); - align-self: flex-start; +.fx-relay-masks-limit-upgrade { + background-color: var(--colorErrorHover); + border-radius: var(--borderRadiusSm); + padding: var(--spacingSm); + color: var(--colorWhite); + font-size: var(--fontSizeBodyXs); + display: flex; + justify-content: center; + gap: var(--spacingSm); + margin-bottom: var(--spacingSm); } -.js-new-label { +.fx-relay-masks-error-message { + background-color: var(--colorError); + border-radius: var(--borderRadiusSm); + color: var(--colorWhite); + font-size: var(--fontSizeBodyXs); display: none; + margin-bottom: var(--spacingSm); + align-items: stretch; + padding: 0; + cursor: pointer; + overflow: hidden; } -.onboarding-cta:hover { - color: var(--colorInformationalHover); +.fx-relay-masks-error-message:hover { + background-color: var(--colorErrorHover); } -.onboarding-cta:focus { - color: var(--colorInformationalFocus); +.fx-relay-masks-error-message:active { + background-color: var(--colorErrorActive); } -.onboarding-img { - width: 100%; +.fx-relay-masks-error-message:focus .fx-relay-masks-error-message-icon { + background-color: var(--colorErrorHover); } -.img-wrapper { - max-height: 100%; - overflow: hidden; - text-align: center; - margin: 0; +.fx-relay-masks-error-message-string { + padding: var(--spacingSm) var(--spacingMd); } -.maxAliasesPanel .onboarding-pagination { - display: none !important; +.fx-relay-masks-error-message-icon { + width: 40px; + display: flex; + justify-content: center; + align-items: center; + background-color: var(--colorError); } -.maxAliasesPanel .onboarding-img { - width: 200px; +.fx-relay-masks-error-message.is-shown { + display: flex; } -.survey-link { - color: var(--colorInformational); - margin: 12px auto 0 0; - border-radius: 2px; +.fx-relay-masks-error-message > * { + pointer-events: none; } -.onboarding-pagination { - position: absolute; +.fx-relay-no-masks-created { display: flex; - bottom: var(--spacingSm); + flex-direction: column; justify-content: center; - flex-direction: row; - left: 0; - right: 0; - margin: auto; + align-items: center; + text-align: center; + padding-top: var(--layoutLg); } -.panel-nav, .premium-panel-nav { - border: none; - outline: none; - border-radius: 50%; - width: 15px; - padding: 4px; - position: relative; - background-color: rgba(0, 0, 0, 0); +.fx-relay-no-masks-created img { + max-width: var(--contentXs); + margin-bottom: var(--spacingMd); } -.panel-nav::after, .premium-panel-nav::after { - content: ""; - display: inline-block; - position: absolute; - top: 0; - right: 0; - left: 0; - bottom: 0; - background-position: center center; - background-size: 90%; - background-repeat: no-repeat; - background-image: url("/images/arrowhead-left.svg"); - opacity: .4; - transition: opacity 0.2s ease; +.fx-relay-no-masks-created h2 { + font-size: var(--fontSizeTitleXs); + font-weight: 700; + color: var(--colorGrey50); + margin: 0 auto var(--spacingMd); + max-width: var(--content2xs); } -.settings-toggle:hover::after, .settings-report-issue-return:hover::after, -.panel-nav:hover::after, .premium-panel-nav:hover::after { - opacity: .8; - transition: opacity 0.2s ease; +.fx-relay-no-masks-created p { + font-size: var(--fontSizeBodySm); + max-width: var(--contentXs); + padding: 0; + margin: 0 0 var(--spacingLg); } -.settings-toggle:focus, .settings-report-issue-return:focus, -.panel-nav:focus, .js-panel-nav:focus { - box-shadow: var(--relayButtonFocus); +.fx-relay-generate-mask-buttons { + border-top: 1px solid var(--colorGrey10); + position: fixed; + bottom: 0; + width: 100%; + left: 0; + padding: var(--spacingMd); + background-color: var(--colorGrey05); + display: flex; + gap: var(--spacingMd); + flex-direction: column; } -.settings-toggle:active::after, .settings-report-issue-return:active::after, -.panel-nav:active::after, .premium-panel-nav:active::after { - opacity: 1; - transition: opacity 0.2s ease; +/* Mask List/Item */ + +.fx-relay-mask-list { + list-style: none; + margin: 0; + padding: 0; } -.next-panel::after { - transform: rotate(180deg); +.fx-relay-mask-item { + display: flex; + flex-direction: column; + border-radius: var(--borderRadiusSm); + padding: var(--spacingSm); + position: relative; } -.panel-num, .js-panel-num { - width: auto; - margin: 0; - text-align: center; - justify-self: center; +.fx-relay-mask-item-new-mask-created { + /* opacity: 1; */ + opacity: 0; + pointer-events: none; + transition: opacity 2s; + background-color: var(--colorGreen50); + border-radius: var(--borderRadiusSm); + padding: var(--spacingXs) var(--spacingSm); + font-size: var(--fontSizeBodyXs); + font-weight: 500; + /* + By allowing this to overlap other elements, + we don't need to reserve empty space for it. + Otherwise, this empty space would push the + .expand-toggle out of the card on small screens: + */ + position: absolute; + left: var(--spacingSm); + top: 100%; + transform: translateY(-50%); } -.current-panel, -.panel-num, .js-panel-num { - font-weight: 600; - font-size: 14px; - padding: 0 var(--spacingXs); - text-align: center; - display: inline; +.fx-relay-mask-item.is-new-mask .fx-relay-mask-item-new-mask-created { + pointer-events: auto; + opacity: 1; + /* Don't fade in when appearing: */ + transition: opacity 0s; } -.current-panel, .total-panels { - margin: 0 var(--spacingXs); - padding-right: var(--spacingXs); +.fx-relay-mask-item:focus-within, +.fx-relay-mask-item:focus, +.fx-relay-mask-item:hover { + background-color: var(--colorPurple05); } +.fx-relay-mask-item-label { + font-size: var(--fontSizeBodyXs); + color: var(--colorGrey40); + overflow: hidden; + display: block; + line-height: 1; +} +.fx-relay-mask-item-address-bar { + display: flex; + align-items: center; + gap: var(--spacingMd); +} -/* For mobile screens with a bigger height viewport than the browser popup. - This takes the --panelHeight + 1px as its min-height. */ +.fx-relay-mask-item-address-wrapper { + display: flex; + gap: var(--spacingXs); + flex-direction: column; +} -/* TODO: Understand why this exists before removing */ -/* @media screen and (min-height: 501px) { - .fx-relay-panel { - height: 100vh; - } +.fx-relay-mask-item-address { + user-select: all; + font-size: var(--fontSizeBodyMd); + font-weight: 600; + color: var(--colorGrey50); + cursor: pointer; + overflow: hidden; + text-overflow: ellipsis; +} - fx-relay-logo-wrapper { - margin-top: 2rem; - } +.fx-relay-mask-item-address::selection { + background: var(--colorPurple10); +} - fx-relay-logomark { - width: 48px; - height: 50px; - } +.fx-relay-mask-item-address-actions { + margin-left: auto; + display: flex; + gap: var(--spacingSm); + position: relative; +} - fx-relay-logotype { - width: 70%; - height: 50px; - } +.fx-relay-mask-item-address-actions > button { + appearance: none; + border: 0; + border-radius: 100%; + width: 32px; + height: 32px; + background-repeat: no-repeat; + background-position: center; + filter: grayscale(1); + cursor: pointer; +} - .intro { - margin-top: 5rem; - margin-bottom: 2rem; - } +.fx-relay-mask-item-address-actions > button:focus, +.fx-relay-mask-item-address-actions > button:hover { + background-color: white; + filter: grayscale(0); +} - .intro, - .alias, - .error-message { - font-size: 1rem; - } +.fx-relay-mask-item-address-copy { + background-image: url(/icons/nebula-copy-text.svg); +} - .text-link { - border-bottom: 1px solid var(--relayInk); - } +.fx-relay-mask-item-address-copy-success { + opacity: 0; + pointer-events: none; + transition: opacity 2s; + background-color: var(--colorGreen50); + border-radius: var(--borderRadiusSm); + padding: var(--spacingXs) var(--spacingSm); + font-size: var(--fontSizeBodyXs); + font-weight: 500; + /* + By allowing this to overlap other elements, + we don't need to reserve empty space for it. + Otherwise, this empty space would push the + .expand-toggle out of the card on small screens: + */ + position: absolute; + right: calc(100% + var(--spacingSm)); + top: 50%; + transform: translateY(-50%); +} - .create-account { - font-size: 1.1rem; - padding: 1rem 3rem; - } +.fx-relay-mask-item-address-copy-success.is-shown { + pointer-events: auto; + opacity: 1; + /* Don't fade in when appearing: */ + transition: opacity 0s; +} - alias-creation { - margin-top: 12rem; - } +.fx-relay-mask-item-address-toggle { + background-image: url(/icons/nebula-arrow-up-down.svg); +} - .view-dashboard { - margin: auto auto 0 auto; - } -} */ +/* Loading Bar */ -.newsflash-wrapper.is-hidden, .newsflash-wrapper .is-hidden { +/* Page Loader */ +.fx-relay-menu-loading-bar { + width: 100%; + height: 5px; + position: relative; + overflow: hidden; display: none; } -.newsflash-wrapper { - background-color: var(--relayGrey10); - padding: 8px; - margin-top: 16px; - height: 100%; +.fx-relay-menu-loading-bar-wrapper { + width: 100%; + height: 2px; + position: absolute; + left: 50%; + top: 1px; } -.newsflash-description { - padding: 0 8px; +.fx-relay-menu-loading-bar-border { + height: 100%; + width: 100%; + position: relative; + left: -50%; + top: -50%; + padding: 0; + background-color: var(--colorViolet20); + padding: 0; } -.newsflash-headline { - background: white; - font-size: 15px; - font-weight: bold; - padding: 12px 12px 12px 48px; - border-radius: 8px; - box-shadow: 0 5px 10px -3px rgba(29, 17, 51, .12); - font-family: var(--relayMetropolis); +.fx-relay-menu-loading-bar-whitespace { + overflow: hidden; + height: 100%; + width: 100%; + margin: 0 auto; + overflow: hidden; + position: relative; } -.newsflash-headline:before { - content: ""; - display: inline-block; - height: 20px; - width: 20px; - margin-left: -32px; - margin-top: -3px; - background-image: url("/images/icon-orange-info.svg"); - background-size: 20px; - background-repeat: no-repeat; - background-position: center center; +.fx-relay-menu-loading-bar-line { position: absolute; + height: 100%; + width: 60%; + background-color: var(--colorViolet70); + animation: cssload-slide 1s ease-in-out infinite; } -.newsflash-description a { - color: var(--colorInformational); - font-weight: bold; - padding-top: 8px; - text-decoration: none; +@keyframes cssload-slide { + 0% { + left: -100%; + } + + 100% { + left: 100%; + } } -.newsflash-wrapper:not(.has-approval-button) .newsflash-description a { +.is-loading .fx-relay-menu-loading-bar { display: block; - text-align: center; } -.newsflash-wrapper:not(.has-approval-button) .newsflash-description a:hover { - text-decoration: underline; + +.is-loading .fx-relay-menu { + min-height: 200px; } -.newsflash-body { - padding-top: 8px; +.is-loading .fx-relay-menu-content { + visibility: hidden; } -.newsflash-buttons { +/* Mask - Generate Custom Panel */ + + +.fx-relay-panel-custom-mask-form { display: flex; - margin-top: 48px; - justify-content: space-evenly; + flex-direction: column; + gap: var(--spacingMd); + padding: var(--spacingMd); + border: 0 none; + font-size: var(--fontSizeBodySm); } -.newsflash-buttons > button { - width: 120px; - padding: 8px 0; - font-size: 15px; - border-radius: 4px; - font-weight: bold; - text-align: center; - text-decoration: none; - border: 2px solid var(--colorInformational); - cursor: pointer; +.fx-relay-panel-custom-mask-input { + margin-bottom: var(--spacingMd); + display: flex; + gap: var(--spacingMd); + flex-direction: column; } -.newsflash-button-dismiss { - color: var(--colorInformational); - background: none; +.fx-relay-panel-custom-mask-checkbox { + display: flex; + align-items: flex-start; + margin-bottom: var(--spacingMd); + position: relative; } -.newsflash-button-allow { - background: var(--colorInformational); - color: white; +.fx-relay-panel-custom-mask-checkbox input[type=checkbox]{ + margin: 2px var(--spacingSm) 0 0; /* Custom Alignment Issue with Checkbox and Label */ + color: var(--colorGrey50); } -.newsflash-button-allow:hover, -.newsflash-button-dismiss:hover { - background: var(--relayBlueHover); - color: white; +.fx-relay-panel-custom-mask-checkbox input[type=checkbox]:checked + label { + color: var(--colorInformational); } -.newsflash-button-allow:focus, -.newsflash-button-dismiss:focus { - box-shadow: var(--relayButtonFocus); +.fx-relay-panel-custom-mask-input input[type=text]{ + width: 100%; + padding: var(--spacingSm) var(--spacing2xl) var(--spacingSm) var(--spacingMd); + border-radius: var(--borderRadiusSm); + background-color: var(--colorWhite); + border: 2px solid transparent; + outline: 1px solid var(--colorGrey30); } -.end-of-intro-pricing h1 { - font-size: 1.3em; +.fx-relay-panel-custom-mask-input-domain { + text-align: center; +} + +.fx-relay-panel-custom-mask-input input[type=text]:focus{ + outline: 4px solid var(--colorInformationalFocus); + border: 2px solid var(--colorInformational); } -.end-of-intro-pricing p { - line-height: 120%; +.fx-relay-panel-custom-mask-submit input[type=submit]:disabled{ + background: var(--colorInformationalDisabled); } -.countdown-timer { - align-self: center; +.fx-relay-panel-custom-mask-copy { + font-size: var(--fontSizeBodySm); + padding: 0 var(--spacingMd) var(--spacingMd); margin: 0; - font-family: var(--relayMetropolisBold); + border-bottom: 1px solid var(--colorGrey10); + overflow-wrap: break-word; + } -.countdown-timer dl { - display: flex; - gap: 8px; +.fx-relay-panel-custom-mask-promo-blocking-icon { + width: 16px; + height: 16px; + margin-left: var(--spacingSm); } -.countdown-timer dl > div { +.fx-relay-panel-custom-mask-promo-blocking-tooltip-wrapper:focus .fx-relay-panel-custom-mask-promo-blocking-tooltip, +.fx-relay-panel-custom-mask-promo-blocking-tooltip-wrapper:hover .fx-relay-panel-custom-mask-promo-blocking-tooltip { display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - gap: var(--spacingXs); - width: var(--layoutLg); - background-color: var(--colorViolet80); - border-radius: 8px; - color: white; - border: 2px solid var(--colorWhite); - box-shadow: 0px 5px 8px var(--colorGrey20); - padding: 8px; } -.countdown-timer dt { - font-weight: 600; +.fx-relay-panel-custom-mask-promo-blocking-tooltip { + display: none; + position: absolute; + left: 0; + bottom: calc(100%); + padding: var(--spacingMd); + box-shadow: 0px 0px 4px var(--colorGrey20); + z-index: 1; + background-color: var(--colorWhite); + border-radius: var(--borderRadiusSm); + flex-direction: column; + gap: var(--spacingSm); + /* This negative margin positions the promo blocking checkbox ABOVE the item */ + margin-top: calc(var(--spacingMd) * -1); } -.countdown-timer dd { - font-weight: 700; - font-size: 2.3em; +.fx-relay-panel-custom-mask-promo-blocking-tooltip h3 { + font-size: var(--fontSizeBodyMd); margin: 0; - font-family: var(--fontStackFirefoxBold); + padding: 0; } -.new-label { - background: var(--colorViolet70); - color: white; - font-size: 0.75em; - font-weight: 700; - align-self: flex-start; - text-transform: uppercase; - border-radius: var(--spacingXs); - padding: var(--spacingXs) var(--spacingSm); +.fx-relay-panel-custom-mask-promo-blocking-tooltip p { + font-size: var(--fontSizeBodySm); + margin: 0; + padding: 0; } -.hidden { - position: absolute; - opacity: 0; - visibility: hidden; - pointer-events: none; +.fx-relay-panel-custom-mask-promo-blocking-tooltip a { + font-size: var(--fontSizeBodySm); + margin: 0; + padding: 0; } -.is-hidden, .hidden { - position: absolute; - opacity: 0; - visibility: hidden; - pointer-events: none; +/* Search Bar */ + +.fx-relay-masks-search-form { + padding: var(--spacingXs) var(--spacingSm); + margin-bottom: var(--spacingSm); + position: relative; + display: none; } -.is-invisible { - opacity: 0; -} \ No newline at end of file +.fx-relay-masks-search-form.is-visible { + display: block; +} diff --git a/src/js/popup/popup-new.js b/src/js/popup/popup-new.js deleted file mode 100644 index bb7cf774a..000000000 --- a/src/js/popup/popup-new.js +++ /dev/null @@ -1,1299 +0,0 @@ -/* global getBrowser checkWaffleFlag psl */ - -(async () => { - // Global Data - const { relaySiteOrigin } = await browser.storage.local.get( - "relaySiteOrigin" - ); - - const state = { - currentPanel: null, - newsItemsCount: 0 - }; - - // audience can be premium, free, phones, all - // Optional data: waffle, fullCta* - const savings = "40%"; // For "Save 40%!" in the Bundle promo body - const getBundlePlans = (await browser.storage.local.get("bundlePlans")).bundlePlans.BUNDLE_PLANS; - const getBundlePrice = getBundlePlans.plan_country_lang_mapping[getBundlePlans.country_code].en.yearly.price; - const getBundleCurrency = getBundlePlans.plan_country_lang_mapping[getBundlePlans.country_code].en.yearly.currency - const userLocale = navigator.language; - const formattedBundlePrice = new Intl.NumberFormat(userLocale, { - style: "currency", - currency: getBundleCurrency, - }).format(getBundlePrice); - - const isBundleAvailableInCountry = ( - await browser.storage.local.get("bundlePlans") - ).bundlePlans.BUNDLE_PLANS.available_in_country; - const isPhoneAvailableInCountry = ( - await browser.storage.local.get("phonePlans") - ).phonePlans.PHONE_PLANS.available_in_country; - - const hasPhone = (await browser.storage.local.get("has_phone")).has_phone; - const hasVpn = (await browser.storage.local.get("has_vpn")).has_vpn; - - // Conditions for phone masking announcement to be shown: if the user is in US/CAN, phone flag is on, and user has not purchased phone plan yet - const isPhoneMaskingAvailable = isPhoneAvailableInCountry && !hasPhone; - - // Conditions for bundle announcement to be shown: if the user is in US/CAN, bundle flag is on, and user has not purchased bundle plan yet - const isBundleAvailable = isBundleAvailableInCountry && !hasVpn; - - // FIXME: The order is not being set correctly - const newsContent = [ - { - id: "phones", - logicCheck: isPhoneMaskingAvailable, - headlineString: "popupPhoneMaskingPromoHeadline", - bodyString: "popupPhoneMaskingPromoBody", - teaserImg: - "/images/panel-images/announcements/premium-announcement-phone-masking.svg", - fullImg: - "/images/panel-images/announcements/premium-announcement-phone-masking-hero.svg", - fullCta: "popupPhoneMaskingPromoCTA", - fullCtaRelayURL: true, - fullCtaHref: - "premium/#pricing?utm_source=fx-relay-addon&utm_medium=popup&utm_content=panel-news-phone-masking-cta", - fullCtaEventLabel: "panel-news-phone-masking-cta", - fullCtaEventAction: "click", - }, - - { - id: "mozilla-vpn-bundle", - logicCheck: isBundleAvailable, - headlineString: "popupBundlePromoHeadline_2", - headlineStringArgs: savings, - bodyString: "popupBundlePromoBody_3", - bodyStringArgs: formattedBundlePrice, - teaserImg: - "/images/panel-images/announcements/panel-bundle-announcement-square.svg", - fullImg: - "/images/panel-images/announcements/panel-bundle-announcement.svg", - fullCta: "popupPhoneMaskingPromoCTA", - fullCtaRelayURL: true, - fullCtaHref: - "/premium/#pricing?utm_source=fx-relay-addon&utm_medium=popup&utm_content=panel-news-bundle-cta", - fullCtaEventLabel: "panel-news-bundle-cta", - fullCtaEventAction: "click", - }, - { - id: "firefox-integration", - waffle: "firefox_integration", - locale: "us", - audience: "premium", - headlineString: "popupPasswordManagerRelayHeadline", - bodyString: "popupPasswordManagerRelayBody", - teaserImg: - "/images/panel-images/announcements/panel-announcement-password-manager-relay-square-illustration.svg", - fullImg: - "/images/panel-images/announcements/panel-announcement-password-manager-relay-illustration.svg", - }, - ]; - - // Update news item count - state.newsItemsCount = newsContent.length; - - const popup = { - events: { - backClick: (e) => { - e.preventDefault(); - const backTarget = e.target.dataset.backTarget; - const backNavLevel = e.target.dataset.navLevel; - - if (backNavLevel === "root") { - document - .querySelector(".js-internal-link.is-active") - ?.classList.remove("is-active"); - } - - // Custom rule to send "Closed Report Issue" event - if (e.target.dataset.navId && e.target.dataset.navId === "webcompat") { - sendRelayEvent("Panel", "click", "closed-report-issue"); - } - - popup.panel.update(backTarget); - }, - dismissErrorClick: async (e) => { - e.preventDefault(); - e.target.classList.remove("is-shown"); - }, - externalClick: async (e) => { - e.preventDefault(); - if (e.target.dataset.eventLabel && e.target.dataset.eventAction) { - sendRelayEvent( - "Panel", - e.target.dataset.eventAction, - e.target.dataset.eventLabel - ); - } - await browser.tabs.create({ url: e.target.href }); - window.close(); - }, - navigationClick: (e) => { - e.preventDefault(); - document - .querySelector(".js-internal-link.is-active") - ?.classList.remove("is-active"); - e.target.classList.add("is-active"); - const panelId = e.target.dataset.panelId; - popup.panel.update(panelId); - }, - generateMask: async (event, type = "random", data = null) => { - - // Types: "random", "custom" - sendRelayEvent("Panel", "click", `popup-generate-${type}-mask`); - preventDefaultBehavior(event); - - const isRandomMask = (type == "random"); - const isCustomMask = (type == "custom"); - const { premium } = await browser.storage.local.get("premium"); - - event.target.classList.add("is-loading"); - - const newRelayAddressResponseArgs = isCustomMask ? { method: "makeDomainAddress" } : { method: "makeRelayAddress" } - - if (isRandomMask) { - // When rebuilding panel, scroll to the top of it - const panel = document.querySelector(".fx-relay-mask-list"); - panel.scrollIntoView(true); - } - - // Request the active tab from the background script and parse the `document.location.hostname` - const currentPageHostName = await browser.runtime.sendMessage({ - method: "getCurrentPageHostname", - }); - - // If active tab is a non-internal browser page, add a label to the creation request - if (currentPageHostName !== null) { - newRelayAddressResponseArgs.description = currentPageHostName; - } - - if (isCustomMask && data) { - newRelayAddressResponseArgs.address = data.address - newRelayAddressResponseArgs.block_list_emails = data.block_list_emails - } - - // Attempt to create a new alias - const newRelayAddressResponse = await browser.runtime.sendMessage(newRelayAddressResponseArgs); - - // Catch edge cases where the "Generate New Alias" button is still enabled, - // but the user has already reached the max number of aliases. - if (newRelayAddressResponse.status === 402) { - event.target.classList.remove("is-loading"); - throw new Error( - browser.i18n.getMessage("pageInputIconMaxAliasesError_mask") - ); - } - - // Reset previous form - if (premium && isCustomMask) { - const customMaskDomainInput = document.getElementById("customMaskName"); - customMaskDomainInput.value = ""; - const customMaskBlockPromosCheckbox = document.getElementById("customMaskBlockPromos"); - customMaskBlockPromosCheckbox.checked = false; - } - - // Catch edge cases where the "Generate New Alias" button is still enabled, - // but the user has already reached the max number of aliases. - if (newRelayAddressResponse.status === 409 || newRelayAddressResponse.status === 400) { - event.target.classList.remove("is-loading"); - - const errorMessage = document.querySelector(".fx-relay-masks-error-message"); - errorMessage.classList.add("is-shown"); - - errorMessage.addEventListener("click",popup.events.dismissErrorClick, false); - - await popup.panel.masks.utilities.buildMasksList({newMaskCreated: false}); - - return; - } - - event.target.classList.remove("is-loading"); - - // Hide onboarding panel - const noMasksCreatedPanel = document.querySelector(".fx-relay-no-masks-created"); - noMasksCreatedPanel.classList.add("is-hidden"); - - await popup.panel.masks.utilities.buildMasksList({newMaskCreated: true}); - - - if (!premium) { - await popup.panel.masks.utilities.setRemainingMaskCount(); - } - - } - }, - init: async () => { - // Set Navigation Listeners - const navigationButtons = document.querySelectorAll(".js-internal-link"); - navigationButtons.forEach((button) => { - button.addEventListener("click", popup.events.navigationClick, false); - }); - - // Set Back Button Listeners - const backButtons = document.querySelectorAll( - ".fx-relay-panel-header-btn-back" - ); - backButtons.forEach((button) => { - button.addEventListener("click", popup.events.backClick, false); - }); - - // Check if user is signed in to show default/sign-in panel - if (await popup.utilities.isUserSignedIn()) { - popup.panel.update("masks"); - popup.utilities.unhideNavigationItemsOnceLoggedIn(); - } else { - popup.panel.update("sign-up"); - document.body.classList.remove("is-loading"); - } - - // Set External Event Listerners - await popup.utilities.setExternalLinkEventListeners(); - - // Set Notification Bug for Unread News Items - popup.panel.news.utilities.initNewsItemCountNotification(); - - // TODO: Focus On Generate Button for Free / Search Filter for Premiums - - }, - panel: { - update: (panelId, data) => { - const panels = document.querySelectorAll(".fx-relay-panel"); - panels.forEach((panel) => { - panel.classList.add("is-hidden"); - - if (panel.dataset.panelId === panelId) { - panel.classList.remove("is-hidden"); - popup.panel.init(panelId, data); - } - }); - - state.currentPanel = panelId; - }, - init: (panelId, data) => { - switch (panelId) { - case "custom": - popup.panel.masks.custom.init(); - break; - - case "masks": - popup.panel.masks.init(); - break; - - case "news": - sendRelayEvent("Panel", "click", "opened-news"); - popup.panel.news.init(); - - popup.panel.news.utilities.updateNewsItemCountNotification(true); - - break; - case "newsStory": - sendRelayEvent("Panel", "click", "opened-news-item"); - popup.panel.news.storyPanel.update(data.newsItemId); - break; - case "settings": - popup.utilities.enableInputIconDisabling(); - // Function is imported from data-opt-out-toggle.js - enableDataOptOut(); - - document - .getElementById("popupSettingsReportIssue") - .addEventListener( - "click", - (e) => { - e.preventDefault(); - popup.panel.update("webcompat"); - }, - false - ); - - break; - case "stats": - sendRelayEvent("Panel", "click", "opened-stats"); - popup.panel.stats.init(); - break; - - case "webcompat": - sendRelayEvent("Panel", "click", "opened-report-issue"); - popup.panel.webcompat.init(); - break; - - default: - break; - } - }, - masks: { - custom: { - init: async () => { - const customMaskForm = document.querySelector(".fx-relay-panel-custom-mask-form"); - const customMaskDomainInput = customMaskForm.querySelector(".fx-relay-panel-custom-mask-input-name"); - const customMaskDomainLabel = customMaskForm.querySelector(".fx-relay-panel-custom-mask-input-domain"); - const customMaskDomainSubmitButton = customMaskForm.querySelector(".fx-relay-panel-custom-mask-submit button"); - const { premiumSubdomainSet } = await browser.storage.local.get("premiumSubdomainSet"); - customMaskDomainInput.placeholder = browser.i18n.getMessage("popupCreateCustomFormMaskInputPlaceholder"); - customMaskDomainLabel.textContent = browser.i18n.getMessage("popupCreateCustomFormMaskInputDescription", premiumSubdomainSet); - - customMaskDomainInput.addEventListener("input", popup.panel.masks.custom.validateForm); - customMaskForm.addEventListener("submit", popup.panel.masks.custom.submit); - - const currentPageHostName = await browser.runtime.sendMessage({ - method: "getCurrentPageHostname", - }); - - if (currentPageHostName) { - const parsedDomain = psl.parse(currentPageHostName) - customMaskDomainInput.value = parsedDomain.sld; - customMaskDomainSubmitButton.disabled = false - } - - customMaskDomainInput.focus(); - - }, - submit: async (event) => { - event.preventDefault(); - // const customMaskForm = document.querySelector(".fx-relay-panel-custom-mask-form"); - const customMaskDomainInput = document.getElementById("customMaskName"); - const customMaskBlockPromosCheckbox = document.getElementById("customMaskBlockPromos"); - - if (!customMaskDomainInput.value) { - throw new Error(`No address name set`) - } - - popup.events.generateMask(event, "custom", { - address: customMaskDomainInput.value, - block_list_emails: customMaskBlockPromosCheckbox.checked, - }); - - popup.panel.update("masks"); - - }, - validateForm: async () => { - const customMaskForm = document.querySelector(".fx-relay-panel-custom-mask-form"); - const customMaskDomainInput = customMaskForm.querySelector(".fx-relay-panel-custom-mask-input-name"); - const customMaskDomainSubmitButton = customMaskForm.querySelector(".fx-relay-panel-custom-mask-submit button"); - - // If there's input, make the form submission possible - customMaskDomainSubmitButton.disabled = !(customMaskDomainInput.value) - } - }, - init: async () => { - - const masks = await popup.utilities.getMasks(); - - // If no masks are created, - if (masks.length === 0) { - const noMasksCreatedPanel = document.querySelector(".fx-relay-no-masks-created"); - noMasksCreatedPanel.classList.remove("is-hidden"); - } - - const { premium } = await browser.storage.local.get("premium"); - const maskPanel = document.getElementById("masks-panel"); - const generateRandomMask = document.querySelector(".js-generate-random-mask"); - - if (!premium) { - await popup.panel.masks.utilities.setRemainingMaskCount(); - maskPanel.setAttribute("data-account-level", "free"); - } else { - maskPanel.setAttribute("data-account-level", "premium"); - - // Update language of Generate Random Mask to "Generate random mask" - generateRandomMask.textContent = browser.i18n.getMessage("pageInputIconGenerateRandomMask"); - - // Prompt user to register subdomain - const { premiumSubdomainSet } = await browser.storage.local.get("premiumSubdomainSet"); - const isPremiumSubdomainSet = premiumSubdomainSet !== "None"; - - if (!isPremiumSubdomainSet) { - const registerSubdomainButton = document.querySelector(".fx-relay-regsiter-subdomain-button"); - registerSubdomainButton.classList.remove("is-hidden"); - } else { - - const generateCustomMask = document.querySelector(".js-generate-custom-mask"); - - // Show "Generate custom mask" button - generateCustomMask.classList.remove("is-hidden"); - - generateCustomMask.addEventListener("click", (e) => { - e.preventDefault(); - popup.panel.update("custom"); - }, false); - - // Restyle Random Mask button to secondary - generateRandomMask.classList.remove("t-primary"); - generateRandomMask.classList.add("t-secondary"); - } - } - - generateRandomMask.addEventListener("click", (e) => { - popup.events.generateMask(e, "random"); - }, false); - - // Build initial list - // Note: If premium, buildMasksList runs `popup.panel.masks.search.init()` after completing - popup.panel.masks.utilities.buildMasksList(); - - // Remove loading state - document.body.classList.remove("is-loading"); - - }, - search: { - filter: (query)=> { - - const searchInput = document.querySelector(".fx-relay-masks-search-input"); - searchInput.classList.add("is-active"); - - const maskSearchResults = Array.from(document.querySelectorAll(".fx-relay-mask-list li")); - - maskSearchResults.forEach((maskResult) => { - const emailAddress = maskResult.dataset.maskAddress; - const label = maskResult.dataset.maskDescription; - const usedOn = maskResult.dataset.maskUsedOn; - const generated = maskResult.dataset.maskGenerated; - - // Check search input against any mask name, label or used-on/generated for web details - const matchesSearchFilter = - emailAddress.toLowerCase().includes(query.toLowerCase()) || - label.toLowerCase().includes(query.toLowerCase()) || - usedOn.toLowerCase().includes(query.toLowerCase()) || - generated.toLowerCase().includes(query.toLowerCase()); - - if (matchesSearchFilter) { - maskResult.classList.remove("is-hidden"); - } else { - maskResult.classList.add("is-hidden"); - } - - // Set #/# labels inside search bar to show results count - const searchFilterTotal = document.querySelector(".js-filter-masks-total"); - const searchFilterVisible = document.querySelector(".js-filter-masks-visible"); - - searchFilterVisible.textContent = maskSearchResults.filter((maskResult) => !maskResult.classList.contains("is-hidden")).length; - searchFilterTotal.textContent = maskSearchResults.length; - }); - - }, - init: () => { - const searchForm = document.querySelector(".fx-relay-masks-search-form"); - - const searchInput = document.querySelector(".fx-relay-masks-search-input"); - searchInput.placeholder = browser.i18n.getMessage("labelSearch"); - - searchForm.addEventListener("submit", (event) => { - event.preventDefault(); - searchInput.blur(); - }); - - searchInput.addEventListener("input", (event) => { - if (event.target.value.length) { - popup.panel.masks.search.filter(event.target.value); - return; - } - - popup.panel.masks.search.reset() - }); - - searchInput.addEventListener("reset", popup.panel.masks.search.reset); - - const maskSearchResults = Array.from(document.querySelectorAll(".fx-relay-mask-list li")); - const searchFilterTotal = document.querySelector(".js-filter-masks-total"); - const searchFilterVisible = document.querySelector(".js-filter-masks-visible"); - searchFilterVisible.textContent = maskSearchResults.length; - searchFilterTotal.textContent = maskSearchResults.length; - - // Show bar if there's at least one mask created - if (maskSearchResults.length) { - searchForm.classList.add("is-visible"); - searchInput.focus(); - } - - - }, - reset: () => { - const searchInput = document.querySelector(".fx-relay-masks-search-input"); - searchInput.classList.remove("is-active"); - - const maskSearchResults = Array.from(document.querySelectorAll(".fx-relay-mask-list li")); - const searchFilterTotal = document.querySelector(".js-filter-masks-total"); - const searchFilterVisible = document.querySelector(".js-filter-masks-visible"); - searchFilterVisible.textContent = maskSearchResults.length; - searchFilterTotal.textContent = maskSearchResults.length; - - maskSearchResults.forEach((maskResult) => { - maskResult.classList.remove("is-hidden"); - }); - - } - }, - utilities: { - buildMasksList: async (opts = null) => { - let getMasksOptions = { fetchCustomMasks: false }; - const { premium } = await browser.storage.local.get("premium"); - - if (premium) { - // Check if user may have custom domain masks - const { premiumSubdomainSet } = await browser.storage.local.get( - "premiumSubdomainSet" - ); - - // API Note: If a user has not registered a subdomain yet, its default stored/queried value is "None"; - const isPremiumSubdomainSet = premiumSubdomainSet !== "None"; - getMasksOptions.fetchCustomMasks = isPremiumSubdomainSet; - - // If not set, prompt user to register domain - if (!isPremiumSubdomainSet) { - const registerSubdomainButton = document.querySelector(".fx-relay-regsiter-subdomain-button"); - registerSubdomainButton.classList.remove("is-hidden"); - } - - // Show Generate Button - const generateRandomMask = document.querySelector(".js-generate-random-mask"); - generateRandomMask.classList.remove("is-hidden"); - } - - const masks = await popup.utilities.getMasks(getMasksOptions); - - const maskList = document.querySelector(".fx-relay-mask-list"); - // Reset mask list - maskList.textContent = ""; - - masks.forEach(mask => { - const maskListItem = document.createElement("li"); - - // Attributes used to power search filtering - maskListItem.setAttribute("data-mask-address", mask.full_address); - maskListItem.setAttribute("data-mask-description", mask.description ?? ""); - maskListItem.setAttribute("data-mask-used-on", mask.used_on ?? ""); - maskListItem.setAttribute("data-mask-generated", mask.generated_for ?? ""); - - maskListItem.classList.add("fx-relay-mask-item"); - - const maskListItemNewMaskCreatedLabel = document.createElement("span"); - maskListItemNewMaskCreatedLabel.textContent = browser.i18n.getMessage("labelMaskCreated"); - maskListItemNewMaskCreatedLabel.classList.add("fx-relay-mask-item-new-mask-created"); - maskListItem.appendChild(maskListItemNewMaskCreatedLabel); - - const maskListItemAddressBar = document.createElement("div"); - maskListItemAddressBar.classList.add("fx-relay-mask-item-address-bar"); - - const maskListItemAddressWrapper = document.createElement("div"); - maskListItemAddressWrapper.classList.add("fx-relay-mask-item-address-wrapper"); - - const maskListItemLabel = document.createElement("span"); - maskListItemLabel.classList.add("fx-relay-mask-item-label"); - maskListItemLabel.textContent = mask.description; - - // Append Label if it exists - if (mask.description !== "") { - maskListItemAddressWrapper.appendChild(maskListItemLabel); - } - - const maskListItemAddress = document.createElement("div"); - maskListItemAddress.classList.add("fx-relay-mask-item-address"); - maskListItemAddress.textContent = mask.full_address; - maskListItemAddressWrapper.appendChild(maskListItemAddress); - - // Add Mask Address Bar Contents - maskListItemAddressBar.appendChild(maskListItemAddressWrapper); - - const maskListItemAddressActions = document.createElement("div"); - maskListItemAddressActions.classList.add("fx-relay-mask-item-address-actions"); - - const maskListItemCopyButton = document.createElement("button"); - maskListItemCopyButton.classList.add("fx-relay-mask-item-address-copy"); - maskListItemCopyButton.setAttribute("data-mask-address", mask.full_address); - - const maskListItemCopyButtonSuccessMessage = document.createElement("span"); - maskListItemCopyButtonSuccessMessage.textContent = browser.i18n.getMessage("popupCopyMaskButtonCopied"); - maskListItemCopyButtonSuccessMessage.classList.add("fx-relay-mask-item-address-copy-success"); - maskListItemAddressActions.appendChild(maskListItemCopyButtonSuccessMessage); - - maskListItemCopyButton.addEventListener("click", (e)=> { - e.preventDefault(); - navigator.clipboard.writeText(e.target.dataset.maskAddress); - maskListItemCopyButtonSuccessMessage.classList.add("is-shown"); - setTimeout(() => { - maskListItemCopyButtonSuccessMessage.classList.remove("is-shown") - }, 1000); - }, false); - maskListItemAddressActions.appendChild(maskListItemCopyButton); - - const maskListItemToggleButton = document.createElement("button"); - maskListItemToggleButton.classList.add("fx-relay-mask-item-address-toggle"); - maskListItemToggleButton.addEventListener("click", ()=> { - // TODO: Add Toggle Function - }, false); - maskListItemToggleButton.setAttribute("data-mask-id", mask.id); - maskListItemToggleButton.setAttribute("data-mask-type", mask.mask_type); - maskListItemToggleButton.setAttribute("data-mask-address", mask.full_address); - - // TODO: Add toggle button back - // maskListItemAddressActions.appendChild(maskListItemToggleButton); - - maskListItemAddressBar.appendChild(maskListItemAddressActions); - maskListItem.appendChild(maskListItemAddressBar); - maskList.appendChild(maskListItem); - }); - - // Display "Mask created" temporary label when a new mask is created in the panel - if (opts && opts.newMaskCreated && maskList.firstElementChild) { - maskList.firstElementChild.classList.add("is-new-mask"); - - setTimeout(() => { - maskList.firstElementChild.classList.remove("is-new-mask"); - }, 1000); - } - - if (premium) { - popup.panel.masks.search.init(); - } - - }, - getRemainingAliases: async () => { - const masks = await popup.utilities.getMasks(); - const { maxNumAliases } = await browser.storage.local.get("maxNumAliases"); - return { masks, maxNumAliases }; - }, - getRemainingMaskCount: async () => { - const { masks, maxNumAliases } = await popup.panel.masks.utilities.getRemainingAliases(); - const numRemaining = maxNumAliases - masks.length; - return numRemaining; - }, - setRemainingMaskCount: async () => { - const { masks, maxNumAliases } = await popup.panel.masks.utilities.getRemainingAliases(); - const numRemaining = maxNumAliases - masks.length; - const masksAvailable = document.querySelector(".fx-relay-masks-available-count"); - const masksLimitReached = document.querySelector(".fx-relay-masks-limit-upgrade-string"); - const limitReachedToast = document.querySelector(".fx-relay-masks-limit-upgrade"); - - masksAvailable.textContent = browser.i18n.getMessage("popupFreeMasksAvailable", [numRemaining, maxNumAliases]); - masksLimitReached.textContent = browser.i18n.getMessage("popupFreeMasksLimitReached", [maxNumAliases]); - - const generateRandomMask = document.querySelector(".js-generate-random-mask"); - - if (masks.length === 0) { - generateRandomMask.classList.remove("is-hidden"); - return; - } - - if (numRemaining === 0) { - // No masks remaining - limitReachedToast.classList.remove("is-hidden"); - masksAvailable.classList.add("is-hidden"); - - // Hide Generate Button - generateRandomMask.classList.add("is-hidden"); - - // Show Upgrade Button - const getUnlimitedMasksBtn = document.querySelector(".fx-relay-mask-upgrade-button"); - getUnlimitedMasksBtn.classList.remove("is-hidden"); - } else { - - // Show Masks Count/Generate Button - masksAvailable.classList.remove("is-hidden"); - generateRandomMask.classList.remove("is-hidden"); - } - - - - } - }, - }, - news: { - init: async () => { - - const newsList = document.querySelector(".fx-relay-news"); - - // If there's no news items, go build them - if ( !newsList.hasChildNodes() ) { - newsContent.forEach(async (newsItem) => { - // Check for any catches to not display the item - const hasLogicCheck = Object.prototype.hasOwnProperty.call(newsItem, "logicCheck"); - - if ( - // Check for waffle (Waffle must return false to catch) - ( - newsItem.waffle && - !(await checkWaffleFlag(newsItem.waffle))) - || - // logicCheck Function (Must return false to catch) - (hasLogicCheck && !newsItem.logicCheck) - ) { - return; - } - - // Build and attach news item - const liFxRelayNewsItem = document.createElement("li"); - liFxRelayNewsItem.classList.add("fx-relay-news-item"); - - const button = document.createElement("button"); - button.classList.add("fx-relay-news-item-button"); - button.setAttribute("data-news-item-id", newsItem.id); - liFxRelayNewsItem.appendChild(button); - - const divTeaserImage = document.createElement("div"); - divTeaserImage.classList.add("fx-relay-news-item-image"); - - const imgTeaserImage = document.createElement("img"); - imgTeaserImage.src = newsItem.teaserImg; - divTeaserImage.appendChild(imgTeaserImage); - button.appendChild(divTeaserImage); - - const divTeaserCopy = document.createElement("div"); - divTeaserCopy.classList.add("fx-relay-news-item-content"); - - const h3TeaserTitle = document.createElement("h3"); - h3TeaserTitle.classList.add("fx-relay-news-item-hero"); - // Pass i18n Args if applicable - const h3TeaserTitleTextContent = newsItem.headlineStringArgs - ? browser.i18n.getMessage( - newsItem.headlineString, - newsItem.headlineStringArgs - ) - : browser.i18n.getMessage(newsItem.headlineString); - h3TeaserTitle.textContent = h3TeaserTitleTextContent; - - const divTeaserBody = document.createElement("div"); - divTeaserBody.classList.add("fx-relay-news-item-body"); - // Pass i18n Args if applicable - const divTeaserBodyTextContent = newsItem.bodyStringArgs - ? browser.i18n.getMessage( - newsItem.bodyString, - newsItem.bodyStringArgs - ) - : browser.i18n.getMessage(newsItem.bodyString); - divTeaserBody.textContent = divTeaserBodyTextContent; - - divTeaserCopy.appendChild(h3TeaserTitle); - divTeaserCopy.appendChild(divTeaserBody); - button.appendChild(divTeaserCopy); - - newsList.appendChild(liFxRelayNewsItem); - - button.addEventListener( - "click", - popup.panel.news.storyPanel.show, - false - ); - }); - } - }, - storyPanel: { - show: (event) => { - popup.panel.update("newsStory", { - newsItemId: event.target.dataset.newsItemId, - }); - }, - update: (newsItemId) => { - // Get content for news detail view - const storyData = newsContent.filter((story) => { return story.id == newsItemId }); - const newsItemContent = storyData[0]; - - const newsStoryDetail = document.querySelector(".fx-relay-news-story"); - - // Reset news detail item - newsStoryDetail.textContent = ""; - - // Populate HTML - const newsStoryHeroImage = document.createElement("img"); - newsStoryHeroImage.src = newsItemContent.fullImg; - newsStoryDetail.appendChild(newsStoryHeroImage); - - const newsStoryHeroTitle = document.createElement("h3"); - const newsStoryHeroTitleTextContent = newsItemContent.headlineStringArgs - ? browser.i18n.getMessage( - newsItemContent.headlineString, - newsItemContent.headlineStringArgs - ) - : browser.i18n.getMessage(newsItemContent.headlineString); - newsStoryHeroTitle.textContent = newsStoryHeroTitleTextContent; - newsStoryDetail.appendChild(newsStoryHeroTitle); - - const newsStoryHeroBody = document.createElement("div"); - // Pass i18n Args if applicable - const newsStoryHeroBodyTextContent = newsItemContent.bodyStringArgs - ? browser.i18n.getMessage( - newsItemContent.bodyString, - newsItemContent.bodyStringArgs - ) - : browser.i18n.getMessage(newsItemContent.bodyString); - newsStoryHeroBody.textContent = newsStoryHeroBodyTextContent; - newsStoryDetail.appendChild(newsStoryHeroBody); - - // If the section has a CTA, add it. - if (newsItemContent.fullCta) { - const newsStoryHeroCTA = document.createElement("a"); - newsStoryHeroCTA.classList.add("fx-relay-news-story-link"); - - // If the URL points towards Relay, choose the correct server - if (newsItemContent.fullCtaRelayURL) { - newsStoryHeroCTA.href = `${relaySiteOrigin}${newsItemContent.fullCtaHref}`; - } else { - newsStoryHeroCTA.href = `${newsItemContent.fullCtaHref}`; - } - - // Set GA data if applicable - if (newsItemContent.fullCtaEventLabel && newsItemContent.fullCtaEventAction) { - newsStoryHeroCTA.setAttribute("data-event-action", newsItemContent.fullCtaEventAction); - newsStoryHeroCTA.setAttribute("data-event-label", newsItemContent.fullCtaEventLabel); - } - - newsStoryHeroCTA.textContent = browser.i18n.getMessage(newsItemContent.fullCta); - newsStoryHeroCTA.addEventListener("click", popup.events.externalClick, false); - newsStoryDetail.appendChild(newsStoryHeroCTA); - } - }, - }, - utilities: { - initNewsItemCountNotification: async () => { - - const localStorage = await browser.storage.local.get(); - - const unreadNewsItemsCountExists = - Object.prototype.hasOwnProperty.call( - localStorage, - "unreadNewsItemsCount" - ); - - const readNewsItemsCountExists = - Object.prototype.hasOwnProperty.call( - localStorage, - "readNewsItemCount" - ); - - // First-run user: No unread data present - if (!unreadNewsItemsCountExists && !readNewsItemsCountExists) { - await browser.storage.local.set({ - unreadNewsItemsCount: state.newsItemsCount, - readNewsItemCount: 0, - }); - } - - // FIXME: The total news item count may differ than what is displayed to the user - // Example: Three items total but user doesn't have waffle for one news item. - // Regardless - update the unreadNews count to match whatever is in state - await browser.storage.local.set({ - unreadNewsItemsCount: state.newsItemsCount, - }); - - const { readNewsItemCount } = await browser.storage.local.get( - "readNewsItemCount" - ); - - const { unreadNewsItemsCount } = await browser.storage.local.get( - "unreadNewsItemsCount" - ); - - // Set unread count - const newsItemCountNotification = document.querySelector( - ".fx-relay-menu-dashboard-link[data-panel-id='news'] .news-count" - ); - - const unreadCount = unreadNewsItemsCount - readNewsItemCount; - - // Show count is it exists - if (unreadCount > 0) { - newsItemCountNotification.textContent = unreadCount.toString(); - newsItemCountNotification.classList.remove("is-hidden"); - } - - }, - updateNewsItemCountNotification: async (markAllUnread = false) => { - if (markAllUnread) { - await browser.storage.local.set({ - readNewsItemCount: state.newsItemsCount, - }); - - const newsItemCountNotification = document.querySelector( - ".fx-relay-menu-dashboard-link[data-panel-id='news'] .news-count" - ); - - newsItemCountNotification.classList.add("is-hidden"); - - } - } - }, - }, - stats: { - init: async () => { - // Get Global Mask Stats data - const { aliasesUsedVal } = await browser.storage.local.get( - "aliasesUsedVal" - ); - const { emailsForwardedVal } = await browser.storage.local.get( - "emailsForwardedVal" - ); - const { emailsBlockedVal } = await browser.storage.local.get( - "emailsBlockedVal" - ); - - const globalStatSet = document.querySelector( - ".dashboard-stats-list.global-stats" - ); - - const globalAliasesUsedValEl = - globalStatSet.querySelector(".aliases-used"); - const globalEmailsBlockedValEl = - globalStatSet.querySelector(".emails-blocked"); - const globalEmailsForwardedValEl = - globalStatSet.querySelector(".emails-forwarded"); - - globalAliasesUsedValEl.textContent = aliasesUsedVal; - globalEmailsBlockedValEl.textContent = emailsBlockedVal; - globalEmailsForwardedValEl.textContent = emailsForwardedVal; - - // Check if any data applies to the current site - const currentPageHostName = await browser.runtime.sendMessage({ - method: "getCurrentPageHostname", - }); - - // Check if user is premium (and then check if they have a domain set) - // This is needed in order to query both random and custom masks - const { premium } = await browser.storage.local.get("premium"); - let getMasksOptions = { fetchCustomMasks: false }; - - if (premium) { - // Check if user may have custom domain masks - const { premiumSubdomainSet } = await browser.storage.local.get( - "premiumSubdomainSet" - ); - - // API Note: If a user has not registered a subdomain yet, its default stored/queried value is "None"; - const isPremiumSubdomainSet = premiumSubdomainSet !== "None"; - getMasksOptions.fetchCustomMasks = isPremiumSubdomainSet; - } - - const masks = await popup.utilities.getMasks(getMasksOptions); - - const currentWebsiteStateSet = document.querySelector( - ".dashboard-stats-list.current-website-stats" - ); - - if ( - popup.utilities.checkIfAnyMasksWereGeneratedOnCurrentWebsite( - masks, - currentPageHostName - ) - ) { - // Some masks are used on the current site. Time to calculate! - const filteredMasks = masks.filter( - (mask) => - mask.generated_for === currentPageHostName || - popup.utilities.hasMaskBeenUsedOnCurrentSite( - mask, - currentPageHostName - ) - ); - - let currentWebsiteForwardedVal = 0; - let currentWebsiteBlockedVal = 0; - - filteredMasks.forEach((mask) => { - currentWebsiteForwardedVal += mask.num_forwarded; - currentWebsiteBlockedVal += mask.num_blocked; - }); - - const currentWebsiteAliasesUsedValEl = - currentWebsiteStateSet.querySelector(".aliases-used"); - currentWebsiteAliasesUsedValEl.textContent = filteredMasks.length; - - const currentWebsiteEmailsForwardedValEl = - currentWebsiteStateSet.querySelector(".emails-forwarded"); - currentWebsiteEmailsForwardedValEl.textContent = - currentWebsiteForwardedVal; - - const currentWebsiteEmailsBlockedValEl = - currentWebsiteStateSet.querySelector(".emails-blocked"); - currentWebsiteEmailsBlockedValEl.textContent = - currentWebsiteBlockedVal; - - const currentWebsiteEmailsBlocked = - currentWebsiteStateSet.querySelector( - ".dashboard-info-emails-blocked" - ); - const currentWebsiteEmailsForwarded = - currentWebsiteStateSet.querySelector( - ".dashboard-info-emails-forwarded" - ); - currentWebsiteEmailsBlocked.classList.remove("is-hidden"); - currentWebsiteEmailsForwarded.classList.remove("is-hidden"); - } - }, - }, - webcompat: { - init: () => { - popup.panel.webcompat.setURLwithIssue(); - popup.panel.webcompat.showReportInputOtherTextField(); - popup.panel.webcompat.showSuccessReportSubmission(); - - const reportForm = document.querySelector(".report-issue-content"); - reportForm.addEventListener("submit", async (event) => { - await popup.panel.webcompat.handleReportIssueFormSubmission(event); - }); - - const reportContinueButton = - document.querySelector(".report-continue"); - reportContinueButton.addEventListener( - "click", - popup.events.backClick, - false - ); - }, - setURLwithIssue: async () => { - // Add Site URL placeholder - const currentPage = (await popup.utilities.getCurrentPage()).url; - const reportIssueSubmitBtn = document.querySelector( - ".report-issue-submit-btn" - ); - const inputFieldUrl = document.querySelector( - 'input[name="issue_on_domain"]' - ); - reportIssueSubmitBtn.disabled = true; - - // Allow for custom URL inputs - inputFieldUrl.addEventListener("input", () => { - reportIssueSubmitBtn.disabled = true; - // Ensure that the custom input looks like a URL without https:// or http:// (e.g. test.com, www.test.com) - if (popup.utilities.isSortaAURL(inputFieldUrl.value)) { - reportIssueSubmitBtn.disabled = false; - } - }); - - // Check that the host site has a valid URL - if (currentPage) { - const url = new URL(currentPage); - // returns a http:// or https:// value - inputFieldUrl.value = url.origin; - reportIssueSubmitBtn.disabled = false; - } - }, - showReportInputOtherTextField: () => { - const otherCheckbox = document.querySelector( - 'input[name="issue-case-other"]' - ); - const otherTextField = document.querySelector( - 'input[name="other_issue"]' - ); - otherCheckbox.addEventListener("click", () => { - otherTextField.classList.toggle("is-hidden"); - }); - - // Add placeholder to report input on 'Other' selection - const inputFieldOtherDetails = document.querySelector( - 'input[name="other_issue"]' - ); - inputFieldOtherDetails.placeholder = browser.i18n.getMessage( - "popupReportIssueCaseOtherDetails" - ); - }, - showSuccessReportSubmission: () => { - const reportIssueSubmitBtn = document.querySelector( - ".report-issue-submit-btn" - ); - const reportSuccess = document.querySelector(".report-success"); - const reportContent = document.querySelector(".report-issue-content"); - reportIssueSubmitBtn.addEventListener("click", () => { - reportSuccess.classList.remove("is-hidden"); - reportContent.classList.add("is-hidden"); - }); - }, - handleReportIssueFormSubmission: async (event) => { - event.preventDefault(); - const data = new FormData(event.target); - const reportData = Object.fromEntries(data.entries()); - reportData.user_agent = await getBrowser(); - - Object.keys(reportData).forEach(function (value) { - // Switch "on" to true - if (reportData[value] === "on") { - reportData[value] = true; - } - // Remove from report if empty string - if (reportData[value] === "") { - delete reportData[value]; - } - }); - - // Clean URL data to add "http://" before it if the custom input doesn't contain a HTTP protocol - if ( - !( - reportData.issue_on_domain.startsWith("http://") || - reportData.issue_on_domain.startsWith("https://") - ) - ) { - reportData.issue_on_domain = "http://" + reportData.issue_on_domain; - } - - await browser.runtime.sendMessage({ - method: "postReportWebcompatIssue", - description: reportData, - }); - }, - }, - }, - utilities: { - checkIfAnyMasksWereGeneratedOnCurrentWebsite: (masks, domain) => { - return masks.some((mask) => { - return domain === mask.generated_for; - }); - }, - clearBrowserActionBadge: async () => { - const { browserActionBadgesClicked } = await browser.storage.local.get( - "browserActionBadgesClicked" - ); - - // Dismiss the browserActionBadge only when it exists - if (browserActionBadgesClicked === false) { - browser.storage.local.set({ browserActionBadgesClicked: true }); - browser.browserAction.setBadgeBackgroundColor({ color: null }); - browser.browserAction.setBadgeText({ text: "" }); - } - }, - enableInputIconDisabling: async () => { - const inputIconVisibilityToggle = document.querySelector( - ".toggle-icon-in-page-visibility" - ); - - const stylePrefToggle = (inputsEnabled) => { - if (inputsEnabled === "show-input-icons") { - inputIconVisibilityToggle.dataset.iconVisibilityOption = - "disable-input-icon"; - inputIconVisibilityToggle.classList.remove("input-icons-disabled"); - return; - } - inputIconVisibilityToggle.dataset.iconVisibilityOption = - "enable-input-icon"; - inputIconVisibilityToggle.classList.add("input-icons-disabled"); - }; - - const iconsAreEnabled = await areInputIconsEnabled(); - const userIconChoice = iconsAreEnabled - ? "show-input-icons" - : "hide-input-icons"; - stylePrefToggle(userIconChoice); - - inputIconVisibilityToggle.addEventListener("click", async () => { - const userIconPreference = - inputIconVisibilityToggle.dataset.iconVisibilityOption === - "disable-input-icon" - ? "hide-input-icons" - : "show-input-icons"; - await browser.runtime.sendMessage({ - method: "updateInputIconPref", - iconPref: userIconPreference, - }); - sendRelayEvent("Panel", "click", userIconPreference); - return stylePrefToggle(userIconPreference); - }); - }, - hasMaskBeenUsedOnCurrentSite: (mask, domain) => { - const domainList = mask.used_on; - - // Short circuit out if there's no used_on entry - if ( - domainList === null || - domainList === "" || - domainList === undefined - ) { - return false; - } - - // Domain already exists in used_on field. Just return the list! - if (domainList.split(",").includes(domain)) { - return true; - } - - // No match found! - return false; - }, - isSortaAURL: (str) => { - return str.includes(".") && !str.endsWith(".") && !str.startsWith("."); - }, - isUserSignedIn: async () => { - const userApiToken = await browser.storage.local.get("apiToken"); - const signedInUser = Object.prototype.hasOwnProperty.call( - userApiToken, - "apiToken" - ); - return signedInUser; - }, - getCachedServerStoragePref: async () => { - const serverStoragePref = await browser.storage.local.get( - "server_storage" - ); - const serverStoragePrefInLocalStorage = - Object.prototype.hasOwnProperty.call( - serverStoragePref, - "server_storage" - ); - - if (!serverStoragePrefInLocalStorage) { - // There is no reference to the users storage preference saved. Fetch it from the server. - return await browser.runtime.sendMessage({ - method: "getServerStoragePref", - }); - } else { - // If the stored pref exists, return value - return serverStoragePref.server_storage; - } - }, - getCurrentPage: async () => { - const [currentTab] = await browser.tabs.query({ - active: true, - currentWindow: true, - }); - return currentTab; - }, - getMasks: async (options = { fetchCustomMasks: false }) => { - const serverStoragePref = - await popup.utilities.getCachedServerStoragePref(); - - if (serverStoragePref) { - try { - return await browser.runtime.sendMessage({ - method: "getAliasesFromServer", - options, - }); - } catch (error) { - console.warn(`getAliasesFromServer Error: ${error}`); - - // API Error — Fallback to local storage - const { relayAddresses } = await browser.storage.local.get( - "relayAddresses" - ); - - return relayAddresses; - } - } - - // User is not syncing with the server. Use local storage. - const { relayAddresses } = await browser.storage.local.get("relayAddresses"); - return relayAddresses; - }, - setExternalLinkEventListeners: async () => { - const externalLinks = document.querySelectorAll(".js-external-link"); - - externalLinks.forEach((link) => { - // Because we dynamically set the Relay origin URL (local/dev/stage/prod), - // we have to catch Relay-specific links and prepend the correct Relay website URL - if (link.dataset.relayInternal === "true") { - // TODO: Remove "/" from here. It'll be error prone - link.href = `${relaySiteOrigin}/${link.dataset.href}`; - } else { - link.href = `${link.dataset.href}`; - } - - link.addEventListener("click", popup.events.externalClick, false); - }); - }, - unhideNavigationItemsOnceLoggedIn: () => { - document - .querySelectorAll(".fx-relay-menu-dashboard-link.is-hidden") - .forEach((link) => { - link.classList.remove("is-hidden"); - }); - }, - }, - }; - - popup.init(); -})(); diff --git a/src/js/popup/popup.js b/src/js/popup/popup.js index c6448fe50..bb7cf774a 100644 --- a/src/js/popup/popup.js +++ b/src/js/popup/popup.js @@ -1,17 +1,18 @@ +/* global getBrowser checkWaffleFlag psl */ -// eslint-disable-next-line no-unused-vars -async function checkWaffleFlag(flag) { - const waffleFlagArray = (await browser.storage.local.get("waffleFlags")).waffleFlags.WAFFLE_FLAGS; - for (let i of waffleFlagArray) { - if (i[0] === flag && i[1] === true) { - return true; - } - } - return false; -} - -// Announcements -async function getPromoPanels() { +(async () => { + // Global Data + const { relaySiteOrigin } = await browser.storage.local.get( + "relaySiteOrigin" + ); + + const state = { + currentPanel: null, + newsItemsCount: 0 + }; + + // audience can be premium, free, phones, all + // Optional data: waffle, fullCta* const savings = "40%"; // For "Save 40%!" in the Bundle promo body const getBundlePlans = (await browser.storage.local.get("bundlePlans")).bundlePlans.BUNDLE_PLANS; const getBundlePrice = getBundlePlans.plan_country_lang_mapping[getBundlePlans.country_code].en.yearly.price; @@ -21,782 +22,1278 @@ async function getPromoPanels() { style: "currency", currency: getBundleCurrency, }).format(getBundlePrice); - // Conditions for showing the Firefox password manager announcement - const isFirefoxIntegration = await checkWaffleFlag("firefox_integration"); - - const panels = { - "announcements": { - // Phone Masking Announcement - "panel1": { - "imgSrc": "announcements/panel-phone-masking-announcement.svg", - "imgSrcPremium": "announcements/premium-announcement-phone-masking.svg", - "tipHeadline": browser.i18n.getMessage("popupPhoneMaskingPromoHeadline"), - "longText": true, - "tipBody": browser.i18n.getMessage("popupPhoneMaskingPromoBody"), - "tipCta": browser.i18n.getMessage("popupPhoneMaskingPromoCTA"), - }, - "panel2": { - "imgSrc": "announcements/panel-bundle-announcement.svg", - "imgSrcPremium": "announcements/premium-announcement-bundle.svg", - "tipHeadline": browser.i18n.getMessage("popupBundlePromoHeadline_2", savings), - "tipBody": browser.i18n.getMessage("popupBundlePromoBody_3", formattedBundlePrice), - "tipCta": browser.i18n.getMessage("popupBundlePromoCTA"), - }, + + const isBundleAvailableInCountry = ( + await browser.storage.local.get("bundlePlans") + ).bundlePlans.BUNDLE_PLANS.available_in_country; + const isPhoneAvailableInCountry = ( + await browser.storage.local.get("phonePlans") + ).phonePlans.PHONE_PLANS.available_in_country; + + const hasPhone = (await browser.storage.local.get("has_phone")).has_phone; + const hasVpn = (await browser.storage.local.get("has_vpn")).has_vpn; + + // Conditions for phone masking announcement to be shown: if the user is in US/CAN, phone flag is on, and user has not purchased phone plan yet + const isPhoneMaskingAvailable = isPhoneAvailableInCountry && !hasPhone; + + // Conditions for bundle announcement to be shown: if the user is in US/CAN, bundle flag is on, and user has not purchased bundle plan yet + const isBundleAvailable = isBundleAvailableInCountry && !hasVpn; + + // FIXME: The order is not being set correctly + const newsContent = [ + { + id: "phones", + logicCheck: isPhoneMaskingAvailable, + headlineString: "popupPhoneMaskingPromoHeadline", + bodyString: "popupPhoneMaskingPromoBody", + teaserImg: + "/images/panel-images/announcements/premium-announcement-phone-masking.svg", + fullImg: + "/images/panel-images/announcements/premium-announcement-phone-masking-hero.svg", + fullCta: "popupPhoneMaskingPromoCTA", + fullCtaRelayURL: true, + fullCtaHref: + "premium/#pricing?utm_source=fx-relay-addon&utm_medium=popup&utm_content=panel-news-phone-masking-cta", + fullCtaEventLabel: "panel-news-phone-masking-cta", + fullCtaEventAction: "click", }, - "premiumPanel": { - "aliasesUsedText": browser.i18n.getMessage("popupAliasesUsed_mask"), - "emailsBlockedText": browser.i18n.getMessage("popupEmailsBlocked"), - "emailsForwardedText": browser.i18n.getMessage("popupEmailsForwarded"), - } - } - - if (isFirefoxIntegration) { - panels.announcements["panel3"] = panels.announcements["panel1"] - panels.announcements["panel1"] = { - "imgSrc": "announcements/panel-announcement-password-manager-relay-illustration.svg", - "imgSrcPremium": "announcements/panel-announcement-password-manager-relay-square-illustration.svg", - "tipHeadline": browser.i18n.getMessage("popupPasswordManagerRelayHeadline"), - "tipBody": browser.i18n.getMessage("popupPasswordManagerRelayBody") - } - } - - return panels; -} - -async function getOnboardingPanels() { - // Conditions for showing the Firefox password manager announcement - const isFirefoxIntegration = await checkWaffleFlag("firefox_integration"); - - const panels = { - "announcements": { - "panel1": { - "imgSrc": "announcements/panel-announcement-attachment-limit.svg", - "tipHeadline": browser.i18n.getMessage("popupAttachmentSizeIncreaseHeadline"), - "tipBody": browser.i18n.getMessage("popupAttachmentSizeIncreaseBody"), - }, - "panel2": { - "imgSrc": "announcements/panel-announcement-critical-emails.svg", - "tipHeadline": browser.i18n.getMessage("popupBlockPromotionalEmailsHeadline_2"), - "tipBody": browser.i18n.getMessage("popupBlockPromotionalEmailsBodyNonPremium"), - }, - "panel3": { - "imgSrc": "announcements/panel-announcement-sign-back-in.svg", - "tipHeadline": browser.i18n.getMessage("popupSignBackInHeadline_mask"), - "tipBody": browser.i18n.getMessage("popupSignBackInBody_mask_v2"), - }, + + { + id: "mozilla-vpn-bundle", + logicCheck: isBundleAvailable, + headlineString: "popupBundlePromoHeadline_2", + headlineStringArgs: savings, + bodyString: "popupBundlePromoBody_3", + bodyStringArgs: formattedBundlePrice, + teaserImg: + "/images/panel-images/announcements/panel-bundle-announcement-square.svg", + fullImg: + "/images/panel-images/announcements/panel-bundle-announcement.svg", + fullCta: "popupPhoneMaskingPromoCTA", + fullCtaRelayURL: true, + fullCtaHref: + "/premium/#pricing?utm_source=fx-relay-addon&utm_medium=popup&utm_content=panel-news-bundle-cta", + fullCtaEventLabel: "panel-news-bundle-cta", + fullCtaEventAction: "click", }, - "maxAliasesPanel": { - "imgSrc": "high-five.svg", - "tipHeadline": browser.i18n.getMessage("popupOnboardingMaxAliasesPanelHeadline"), - "tipBody": browser.i18n.getMessage("popupOnboardingMaxAliasesPanelBody"), - "upgradeButton": browser.i18n.getMessage("popupUpgradeToPremiumBanner"), - "upgradeButtonIcon": "/icons/icon.svg", + { + id: "firefox-integration", + waffle: "firefox_integration", + locale: "us", + audience: "premium", + headlineString: "popupPasswordManagerRelayHeadline", + bodyString: "popupPasswordManagerRelayBody", + teaserImg: + "/images/panel-images/announcements/panel-announcement-password-manager-relay-square-illustration.svg", + fullImg: + "/images/panel-images/announcements/panel-announcement-password-manager-relay-illustration.svg", }, - "premiumPanel": { - "aliasesUsedText": browser.i18n.getMessage("popupAliasesUsed_mask"), - "emailsBlockedText": browser.i18n.getMessage("popupEmailsBlocked"), - "emailsForwardedText": browser.i18n.getMessage("popupEmailsForwarded"), - } - }; + ]; - if (isFirefoxIntegration) { - panels.announcements["panel4"] = panels.announcements["panel1"] - panels.announcements["panel1"] = { - "imgSrc": "announcements/panel-announcement-password-manager-relay-illustration.svg", - "tipHeadline": browser.i18n.getMessage("popupPasswordManagerRelayHeadline"), - "tipBody": browser.i18n.getMessage("popupPasswordManagerRelayBody"), - } - } - - return panels -} - -function getEducationalStrings() { - return { - "announcements": { - "panel1": { - "imgSrcPremium": "educational-matrix/educationalImg1.png", - "tipHeadline": browser.i18n.getMessage("popupEducationalComponent1Headline"), - "tipBody": browser.i18n.getMessage("popupEducationalComponent1Body"), + // Update news item count + state.newsItemsCount = newsContent.length; + + const popup = { + events: { + backClick: (e) => { + e.preventDefault(); + const backTarget = e.target.dataset.backTarget; + const backNavLevel = e.target.dataset.navLevel; + + if (backNavLevel === "root") { + document + .querySelector(".js-internal-link.is-active") + ?.classList.remove("is-active"); + } + + // Custom rule to send "Closed Report Issue" event + if (e.target.dataset.navId && e.target.dataset.navId === "webcompat") { + sendRelayEvent("Panel", "click", "closed-report-issue"); + } + + popup.panel.update(backTarget); + }, + dismissErrorClick: async (e) => { + e.preventDefault(); + e.target.classList.remove("is-shown"); }, - "panel2": { - "imgSrcPremium": "educational-matrix/educationalImg-attachment-limit.svg", - "tipHeadline": browser.i18n.getMessage("popupAttachmentSizeIncreaseHeadline"), - "tipBody": browser.i18n.getMessage("popupAttachmentSizeIncreaseBody"), + externalClick: async (e) => { + e.preventDefault(); + if (e.target.dataset.eventLabel && e.target.dataset.eventAction) { + sendRelayEvent( + "Panel", + e.target.dataset.eventAction, + e.target.dataset.eventLabel + ); + } + await browser.tabs.create({ url: e.target.href }); + window.close(); }, - "panel3": { - "imgSrcPremium": "educational-matrix/educationalImg-block-emails.svg", - "tipHeadline": browser.i18n.getMessage("popupBlockPromotionalEmailsHeadline_2"), - "tipBody": browser.i18n.getMessage("popupBlockPromotionalEmailsBody_mask"), + navigationClick: (e) => { + e.preventDefault(); + document + .querySelector(".js-internal-link.is-active") + ?.classList.remove("is-active"); + e.target.classList.add("is-active"); + const panelId = e.target.dataset.panelId; + popup.panel.update(panelId); }, - "panel4": { - "imgSrcPremium": "educational-matrix/educationalImg-sign-back-in.svg", - "tipHeadline": browser.i18n.getMessage("popupSignBackInHeadline_mask"), - "tipBody": browser.i18n.getMessage("popupSignBackInBody_mask_v2"), - "longText": true, + generateMask: async (event, type = "random", data = null) => { + + // Types: "random", "custom" + sendRelayEvent("Panel", "click", `popup-generate-${type}-mask`); + preventDefaultBehavior(event); + + const isRandomMask = (type == "random"); + const isCustomMask = (type == "custom"); + const { premium } = await browser.storage.local.get("premium"); + + event.target.classList.add("is-loading"); + + const newRelayAddressResponseArgs = isCustomMask ? { method: "makeDomainAddress" } : { method: "makeRelayAddress" } + + if (isRandomMask) { + // When rebuilding panel, scroll to the top of it + const panel = document.querySelector(".fx-relay-mask-list"); + panel.scrollIntoView(true); + } + + // Request the active tab from the background script and parse the `document.location.hostname` + const currentPageHostName = await browser.runtime.sendMessage({ + method: "getCurrentPageHostname", + }); + + // If active tab is a non-internal browser page, add a label to the creation request + if (currentPageHostName !== null) { + newRelayAddressResponseArgs.description = currentPageHostName; + } + + if (isCustomMask && data) { + newRelayAddressResponseArgs.address = data.address + newRelayAddressResponseArgs.block_list_emails = data.block_list_emails + } + + // Attempt to create a new alias + const newRelayAddressResponse = await browser.runtime.sendMessage(newRelayAddressResponseArgs); + + // Catch edge cases where the "Generate New Alias" button is still enabled, + // but the user has already reached the max number of aliases. + if (newRelayAddressResponse.status === 402) { + event.target.classList.remove("is-loading"); + throw new Error( + browser.i18n.getMessage("pageInputIconMaxAliasesError_mask") + ); + } + + // Reset previous form + if (premium && isCustomMask) { + const customMaskDomainInput = document.getElementById("customMaskName"); + customMaskDomainInput.value = ""; + const customMaskBlockPromosCheckbox = document.getElementById("customMaskBlockPromos"); + customMaskBlockPromosCheckbox.checked = false; + } + + // Catch edge cases where the "Generate New Alias" button is still enabled, + // but the user has already reached the max number of aliases. + if (newRelayAddressResponse.status === 409 || newRelayAddressResponse.status === 400) { + event.target.classList.remove("is-loading"); + + const errorMessage = document.querySelector(".fx-relay-masks-error-message"); + errorMessage.classList.add("is-shown"); + + errorMessage.addEventListener("click",popup.events.dismissErrorClick, false); + + await popup.panel.masks.utilities.buildMasksList({newMaskCreated: false}); + + return; + } + + event.target.classList.remove("is-loading"); + + // Hide onboarding panel + const noMasksCreatedPanel = document.querySelector(".fx-relay-no-masks-created"); + noMasksCreatedPanel.classList.add("is-hidden"); + + await popup.panel.masks.utilities.buildMasksList({newMaskCreated: true}); + + + if (!premium) { + await popup.panel.masks.utilities.setRemainingMaskCount(); + } + } - } - }; -} - -function showSignUpPanel() { - const signUpOrInPanel = document.querySelector(".sign-up-panel"); - document.body.classList.add("sign-up"); - return signUpOrInPanel.classList.remove("hidden"); -} - -const serverStoragePanel = { - isRelevant: async () => { - const { serverStoragePrompt } = await browser.storage.local.get( - "serverStoragePrompt" - ); - - const serverStoragePref = await browser.runtime.sendMessage({ - method: "getServerStoragePref" - }); - - // TODO: Check when user was created - - // Only show the server prompt panel the user has not already opt'd in, - // or if they have not interacted with the panel before. - if (!serverStoragePref && !serverStoragePrompt) { - return true; - } - - return false; - }, - hide: () => { - const serverStoragePanelWrapper = document.querySelector( - ".js-server-storage-wrapper" - ); - - document.querySelectorAll(".content-wrapper").forEach((div) => { - div.classList.remove("is-hidden"); - }); - - serverStoragePanelWrapper.classList.add("is-hidden"); - serverStoragePanelWrapper - .querySelectorAll(".is-hidden") - .forEach((childDiv) => childDiv.classList.add("is-hidden")); - }, - init: (premium) => { - // Server Storage Prompt Panel - const serverStoragePanelWrapper = document.querySelector( - ".js-server-storage-wrapper" - ); - - if (premium) { - const panelStatus = document.querySelector(".panel-status"); - panelStatus.classList.add("is-hidden"); - } - - document.querySelectorAll(".content-wrapper").forEach((div) => { - div.classList.add("is-hidden"); - }); - - serverStoragePanelWrapper.classList.remove("is-hidden"); - - serverStoragePanelWrapper - .querySelectorAll(".is-hidden") - .forEach((childDiv) => childDiv.classList.remove("is-hidden")); - - const serverStoragePanelButtonDismiss = - serverStoragePanelWrapper.querySelector(".js-button-dismiss"); - - const serverStoragePanelButtonAllow = - serverStoragePanelWrapper.querySelector(".js-button-allow"); - - serverStoragePanelButtonDismiss.addEventListener( - "click", - serverStoragePanel.event.dismiss, - false - ); - - serverStoragePanelButtonAllow.addEventListener( - "click", - serverStoragePanel.event.allow, - false - ); - }, - event: { - dismiss: async (e) => { - e.preventDefault(); - serverStoragePanel.event.dontShowPanelAgain(); - serverStoragePanel.hide(); - showRelayPanel(1); }, + init: async () => { + // Set Navigation Listeners + const navigationButtons = document.querySelectorAll(".js-internal-link"); + navigationButtons.forEach((button) => { + button.addEventListener("click", popup.events.navigationClick, false); + }); - allow: async (e) => { - e.preventDefault(); - - const { relaySiteOrigin } = await browser.storage.local.get( - "relaySiteOrigin" + // Set Back Button Listeners + const backButtons = document.querySelectorAll( + ".fx-relay-panel-header-btn-back" ); - - serverStoragePanel.event.dontShowPanelAgain(); - - browser.tabs.create({ - url: `${relaySiteOrigin}/accounts/profile/?utm_source=fx-relay-addon&utm_medium=popup&utm_content=allow-labels-sync#sync-labels`, - active: true, + backButtons.forEach((button) => { + button.addEventListener("click", popup.events.backClick, false); }); - window.close(); - }, - - dontShowPanelAgain: () => { - browser.storage.local.set({ serverStoragePrompt: true }); - } - }, -}; - -async function choosePanel(panelId, premium, premiumSubdomainSet) { - const premiumPanelWrapper = document.querySelector(".premium-wrapper"); - - if (premium) { - document.getElementsByClassName("content-wrapper")[0].remove(); - premiumPanelWrapper.classList.remove("is-hidden"); - //Toggle register domain or education module - checkUserSubdomain(premiumSubdomainSet); - return "premiumPanel"; - } else { - const premiumWrapper = document.getElementsByClassName("premium-wrapper"); - if (premiumWrapper.length) { - premiumWrapper[0].remove(); - } - - return `panel${panelId}`; - } -} - -function checkUserSubdomain(premiumSubdomainSet) { - const educationalComponent = document.querySelector(".educational-component"); - const registerDomainComponent = document.querySelector(".register-domain-component"); - - if (premiumSubdomainSet !== "None") { - registerDomainComponent.classList.add("is-hidden"); - } - - else { - educationalComponent.classList.add("is-hidden"); - } -} - - -async function showRelayPanel(tipPanelToShow) { - const onboardingPanelWrapper = document.querySelector("onboarding-panel"); - const tipImageEl = onboardingPanelWrapper.querySelector("img"); - const tipHeadlineEl = onboardingPanelWrapper.querySelector(".onboarding-h1"); - const tipBodyEl = onboardingPanelWrapper.querySelector(".onboarding-p"); - const currentPanel = onboardingPanelWrapper.querySelector(".current-panel"); - const upgradeButtonEl = onboardingPanelWrapper.querySelector(".upgrade-banner"); - const upgradeButtonIconEl = onboardingPanelWrapper.querySelector(".upgrade-banner-icon"); - const promoElements = onboardingPanelWrapper.querySelectorAll(".js-promo-item"); - const tipCtaEl = onboardingPanelWrapper.querySelector(".onboarding-cta"); - let premiumPanelStrings = getEducationalStrings(); - let onboardingPanelStrings = await getOnboardingPanels(); - - const isBundleAvailableInCountry = (await browser.storage.local.get("bundlePlans")).bundlePlans.BUNDLE_PLANS.available_in_country; - const isPhoneAvailableInCountry = (await browser.storage.local.get("phonePlans")).phonePlans.PHONE_PLANS.available_in_country; - - // Conditions for phone masking announcement to be shown: if the user is in US/CAN, phone flag is on, and user has not purchased phone plan yet - const isPhoneMaskingAvailable = await checkWaffleFlag("phones") && isPhoneAvailableInCountry; - // Conditions for bundle announcement to be shown: if the user is in US/CAN, bundle flag is on, and user has not purchased bundle plan yet - const isBundleAvailable = await checkWaffleFlag("bundle") && isBundleAvailableInCountry; - - if ( - isPhoneMaskingAvailable || isBundleAvailable - ) { - promoElements.forEach(i => { - i.classList.remove("is-hidden"); - }); - onboardingPanelWrapper.setAttribute("id", "bundle-phones-promo"); - - // If phone masking / bundle is available, switch panels to promo panel set to advertise phone masking/bundle plans - onboardingPanelStrings = await getPromoPanels(); - premiumPanelStrings = await getPromoPanels(); - } - - if (!browser.menus) { - // Remove sign back in for browsers that don't support menus API (Chrome) - delete onboardingPanelStrings.announcements.panel3; - delete premiumPanelStrings.announcements.panel4; - } - - //Premium Panel - const premiumPanelWrapper = document.querySelector(".premium-wrapper"); - const registerDomainImgEl = premiumPanelWrapper.querySelector(".email-domain-illustration"); - - //Dashboard Statistics - const dashboardStatistics = document.querySelectorAll(".dashboard-stats-list"); - - //Get profile data from site - const { aliasesUsedVal } = await browser.storage.local.get("aliasesUsedVal"); - const { emailsForwardedVal } = await browser.storage.local.get("emailsForwardedVal"); - const { emailsBlockedVal } = await browser.storage.local.get("emailsBlockedVal"); - const { emailTrackersRemovedVal } = await browser.storage.local.get("emailTrackersRemovedVal"); - - dashboardStatistics.forEach((statSet) => { - const aliasesUsedValEl = statSet.querySelector(".aliases-used"); - const emailsBlockedValEl = statSet.querySelector(".emails-blocked"); - const emailsForwardedValEl = statSet.querySelector(".emails-forwarded"); - const emailTrackersRemovedValEl = statSet.querySelector(".email-trackers-removed"); - - aliasesUsedValEl.textContent = aliasesUsedVal; - emailsBlockedValEl.textContent = emailsBlockedVal; - emailsForwardedValEl.textContent = emailsForwardedVal; - emailTrackersRemovedValEl.textContent = emailTrackersRemovedVal; - }); - - //Check if premium features are available - const premiumCountryAvailability = (await browser.storage.local.get("periodicalPremiumPlans")).periodicalPremiumPlans?.PERIODICAL_PREMIUM_PLANS - - //Check if user is premium - const { premium } = await browser.storage.local.get("premium"); - - //Check if user has a subdomain set - const { premiumSubdomainSet } = await browser.storage.local.get("premiumSubdomainSet"); - - //Educational Panel - const educationalImgEl = premiumPanelWrapper.querySelector(".education-img"); - const educationHeadlineEl = premiumPanelWrapper.querySelector(".education-headline"); - const educationBodyEl = premiumPanelWrapper.querySelector(".education-body"); - const currentEducationalPanel = premiumPanelWrapper.querySelector(".current-panel"); - const educationalCtaEl = premiumPanelWrapper.querySelector(".onboarding-cta"); - - const updatePremiumPanel = async (panelId) => { - const panelToShow = `panel${panelId}`; - premiumPanelWrapper.setAttribute("id", panelToShow); - let panelStrings = premiumPanelStrings.announcements[`${panelToShow}`]; - - if (!panelStrings) { - // Exit early if on a non-onboarding - return; - } - educationBodyEl.classList.remove("small-font-size"); - if (panelStrings.longText) { - educationBodyEl.classList.add("small-font-size"); - } - - // If bundle is unavailable but phone masking is, only show the phone masking promo - if (!isBundleAvailable && isPhoneMaskingAvailable) { - delete premiumPanelStrings.announcements.panel2; - } - - // If phone masking is unavailable but bundle is, only show bundle promo - if (!isPhoneMaskingAvailable && isBundleAvailable) { - // Force panel to start at panel2, which is the bundle promo - panelStrings = premiumPanelStrings.announcements.panel2; - delete premiumPanelStrings.announcements.panel1; - } - - setPagination(panelId); - - educationHeadlineEl.textContent = panelStrings.tipHeadline; - educationBodyEl.textContent = panelStrings.tipBody; - educationalImgEl.src = `/images/panel-images/${panelStrings.imgSrcPremium}`; - educationalCtaEl.textContent = panelStrings.tipCta; - currentEducationalPanel.textContent = `${tipPanelToShow}`; - - registerDomainImgEl.src = `/images/panel-images/email-domain-illustration.svg`; - - // Remove panel status if user has unlimited aliases, so no negative alias left count - if (premium) { - const panelStatus = document.querySelector(".panel-status"); - panelStatus.classList.add("is-hidden"); - } - - return; - }; - - const updatePanel = async (numRemaining, panelId) => { - const panelToShow = await choosePanel(panelId, premium, premiumSubdomainSet); - onboardingPanelWrapper.classList = [panelToShow]; - - let panelStrings = onboardingPanelStrings.announcements[`${panelToShow}`]; - - // If bundle is unavailable but phone masking is, only show the phone masking promo - if (!isBundleAvailable && isPhoneMaskingAvailable) { - delete onboardingPanelStrings.announcements.panel2; - } - - // If phone masking is unavailable but bundle is, only show bundle promo - if (!isPhoneMaskingAvailable && isBundleAvailable) { - // Force panel to start at panel2, which is the bundle promo - panelStrings = onboardingPanelStrings.announcements.panel2; - delete onboardingPanelStrings.announcements.panel1; - } - - setPagination(panelId); - - // Only show maxAliasesPanel to users where bundle / phone masking is unavailable - // Otherwise, show Phone masking and Bundle promo - if (!premium && numRemaining === 0 && !(isPhoneMaskingAvailable || isBundleAvailable)) { - panelStrings = onboardingPanelStrings["maxAliasesPanel"]; - onboardingPanelWrapper.classList = "maxAliasesPanel"; - - if (premiumCountryAvailability?.available_in_country === true) { - const upgradeButton = document.querySelector(".upgrade-banner-wrapper"); - upgradeButton.classList.remove("is-hidden"); + // Check if user is signed in to show default/sign-in panel + if (await popup.utilities.isUserSignedIn()) { + popup.panel.update("masks"); + popup.utilities.unhideNavigationItemsOnceLoggedIn(); + } else { + popup.panel.update("sign-up"); + document.body.classList.remove("is-loading"); } - } - if (!panelStrings) { - // Exit early if on a non-onboarding - return; - } - - tipImageEl.src = `/images/panel-images/${panelStrings.imgSrc}`; - tipHeadlineEl.textContent = panelStrings.tipHeadline; - tipBodyEl.textContent = panelStrings.tipBody; - tipCtaEl.textContent = panelStrings.tipCta; - currentPanel.textContent = `${panelId}`; - upgradeButtonEl.textContent = panelStrings.upgradeButton; - upgradeButtonIconEl.src = panelStrings.upgradeButtonIcon; - - //If Premium features are not available, do not show upgrade CTA on the panel - if (premiumCountryAvailability?.available_in_country === true) { - const premiumCTA = document.querySelector(".premium-cta"); - premiumCTA.classList.remove("is-hidden"); - } - - return; - }; - const setPagination = (activePanel) => { - const pagination = onboardingPanelWrapper.querySelector(".onboarding-pagination"); - const prevButton = onboardingPanelWrapper.querySelector(".previous-panel"); - const nextButton = onboardingPanelWrapper.querySelector(".next-panel"); - const totalPanelsEl = document.querySelector(".total-panels"); - // Number of panels available for free users - let totalPanels = Object.keys(onboardingPanelStrings.announcements).length; - if (premium) { - totalPanels = Object.keys(premiumPanelStrings.announcements).length; - } - totalPanelsEl.textContent = totalPanels; - prevButton.classList.remove("is-invisible"); - nextButton.classList.remove("is-invisible"); - // If user is at the start of the carousel, hide next button - if (activePanel === 1) { - prevButton.classList.add("is-invisible"); - } - // If user is at the end of the carousel, hide next button - if (activePanel === totalPanels) { - nextButton.classList.add("is-invisible"); - } - if (totalPanels === 1) { - pagination.classList.add("is-hidden"); - } - } - - //Nonpremium panel status - const { relayAddresses, maxNumAliases } = await getRemainingAliases(); - const numRemaining = maxNumAliases - relayAddresses.length; - const remainingAliasMessage = document.querySelector(".aliases-remaining"); - const getUnlimitedAliases = document.querySelector(".premium-cta"); - getUnlimitedAliases.textContent = browser.i18n.getMessage("popupGetUnlimitedAliases_mask"); - document.body.classList.add("relay-panel"); - - // Prevent negative masks from showing, default to 0 if all free masks have been used up - // TODO: Create re-usable data fetching and caching method for data syncing - let numRemainingNonNegative = numRemaining; - if (numRemaining <= 0) { - numRemainingNonNegative = 0; - } - remainingAliasMessage.textContent = browser.i18n.getMessage("popupRemainingAliases_2_mask", [numRemainingNonNegative, maxNumAliases]); - - updatePremiumPanel(tipPanelToShow); - updatePanel(numRemaining, tipPanelToShow); - - document.querySelectorAll(".panel-nav").forEach(navBtn => { - navBtn.addEventListener("click", () => { - sendRelayEvent("Panel", "click", "panel-navigation-arrow"); - // pointer events are disabled in popup CSS for the "previous" button on panel 1 - // and the "next" button on panel 3 - const nextPanel = (navBtn.dataset.direction === "-1") ? -1 : 1; - return updatePanel(numRemaining, tipPanelToShow += nextPanel); - }); - }); - - document.querySelectorAll(".premium-panel-nav").forEach(navBtn => { - navBtn.addEventListener("click", () => { - sendRelayEvent("Panel", "click", "panel-navigation-arrow"); - // pointer events are disabled in popup CSS for the "previous" button on panel 1 - // and the "next" button on panel 3 - const nextPanel = (navBtn.dataset.direction === "-1") ? -1 : 1; - return updatePremiumPanel(tipPanelToShow += nextPanel); - }); - }); - - if (premium) { - remainingAliasMessage.classList.add("is-hidden"); - } - - if (premiumCountryAvailability?.available_in_country === true) { - getUnlimitedAliases.classList.remove("is-hidden"); - } - - const relayPanel = document.querySelector(".signed-in-panel"); - relayPanel.classList.remove("hidden"); - - if (numRemaining === 0) { - return sendRelayEvent("Panel", "viewed-panel", "panel-max-aliases"); - } - return sendRelayEvent("Panel", "viewed-panel", "authenticated-user-panel"); -} - - -async function getAllAliases() { - return await browser.storage.local.get("relayAddresses"); -} - - -async function getRemainingAliases() { - const { relayAddresses } = await getAllAliases(); - const { maxNumAliases } = await browser.storage.local.get("maxNumAliases"); - return { relayAddresses, maxNumAliases }; -} - -async function getBrowser() { - if (typeof browser.runtime.getBrowserInfo === "function") { - /** @type {{ name: string, vendor: string, version: string, buildID: string }} */ - const browserInfo = await browser.runtime.getBrowserInfo(); - return browserInfo.name; - } - if (navigator.userAgent.toLowerCase().indexOf("firefox") !== -1) { - return "Firefox"; - } - return "Chrome"; -} - -async function enableSettingsPanel() { - const settingsToggles = document.querySelectorAll(".settings-toggle"); - settingsToggles.forEach(toggle => { - toggle.addEventListener("click", () => { - document.body.classList.toggle("show-settings"); - const eventLabel = document.body.classList.contains("show-settings") ? "opened-settings" : "closed-settings"; - if (document.body.classList.contains("show-settings")) { - sendRelayEvent("Panel", "click", eventLabel); - } - }); - }); - - const currentBrowser = await getBrowser(); - - if (currentBrowser === "Chrome") { - const supportLink = document.getElementById("popupSettingsLeaveFeedbackLink"); - const chromeSupportLink = "https://chrome.google.com/webstore/detail/firefox-relay/lknpoadjjkjcmjhbjpcljdednccbldeb/?utm_source=fx-relay-addon&utm_medium=popup" - supportLink.href = chromeSupportLink; - } -} - -function enableReportIssuePanel() { - const reportIssueToggle = document.querySelector(".settings-report-issue"); - const reportIssueSettingsReturn = document.querySelector(".settings-report-issue-return"); - const submissionSuccessContinue = document.querySelector(".report-continue"); - [reportIssueToggle, reportIssueSettingsReturn, submissionSuccessContinue].forEach(e => { - e.addEventListener("click", () => { - document.body.classList.toggle("show-report-issue"); - const eventLabel = document.body.classList.contains("show-report-issue") ? "opened-report-issue" : "closed-report-issue"; - if (document.body.classList.contains("show-report-issue")) { - sendRelayEvent("Panel", "click", eventLabel); - } - }); - }); - const reportForm = document.querySelector(".report-issue-content"); - setURLwithIssue(); - showReportInputOtherTextField(); - showSuccessReportSubmission(); - reportForm.addEventListener('submit', handleReportIssueFormSubmission); -} - -async function handleReportIssueFormSubmission(event) { - event.preventDefault(); - const data = new FormData(event.target); - const reportData = Object.fromEntries(data.entries()); - reportData.user_agent = await getBrowser(); - - Object.keys(reportData).forEach(function(value) { - // Switch "on" to true - if (reportData[value] === "on") { - reportData[value] = true; - } - // Remove from report if empty string - if (reportData[value] === "") { - delete reportData[value]; - } - }); - // Clean URL data to add "http://" before it if the custom input doesn't contain a HTTP protocol - if (!(reportData.issue_on_domain.startsWith("http://") || reportData.issue_on_domain.startsWith("https://"))) { - reportData.issue_on_domain = "http://" + reportData.issue_on_domain; - } - await browser.runtime.sendMessage({ - method: "postReportWebcompatIssue", - description: reportData - }); -} - -function showSuccessReportSubmission() { - const reportIssueSubmitBtn = document.querySelector(".report-issue-submit-btn"); - const reportSuccess = document.querySelector(".report-success"); - const reportContent = document.querySelector(".report-issue-content"); - reportIssueSubmitBtn.addEventListener("click", () => { - reportSuccess.classList.remove("is-hidden"); - reportContent.classList.add("is-hidden"); - }); -} - -function isSortaAURL(str) { - return str.includes(".") && !str.endsWith(".") && !str.startsWith("."); -} - -async function setURLwithIssue() { - // Add Site URL placeholder - const currentPage = (await getCurrentPage()).url; - const reportIssueSubmitBtn = document.querySelector(".report-issue-submit-btn"); - const inputFieldUrl = document.querySelector('input[name="issue_on_domain"]'); - reportIssueSubmitBtn.disabled = true; - - // Allow for custom URL inputs - inputFieldUrl.addEventListener('input', () => { - reportIssueSubmitBtn.disabled = true; - // Ensure that the custom input looks like a URL without https:// or http:// (e.g. test.com, www.test.com) - if (isSortaAURL(inputFieldUrl.value)) { - reportIssueSubmitBtn.disabled = false; - } - }); - - // Check that the host site has a valid URL - if (currentPage) { - const url = new URL(currentPage); - // returns a http:// or https:// value - inputFieldUrl.value = url.origin; - reportIssueSubmitBtn.disabled = false; - } -} - - function showReportInputOtherTextField() { - const otherCheckbox = document.querySelector('input[name="issue-case-other"]'); - const otherTextField = document.querySelector('input[name="other_issue"]'); - otherCheckbox.addEventListener("click", () => { - otherTextField.classList.toggle("is-hidden"); - }) - - // Add placeholder to report input on 'Other' selection - const inputFieldOtherDetails = document.querySelector('input[name="other_issue"]'); - inputFieldOtherDetails.placeholder = browser.i18n.getMessage("popupReportIssueCaseOtherDetails"); -} - -async function getCurrentPage() { - const [currentTab] = await browser.tabs.query({ - active: true, - currentWindow: true, - }); - return currentTab; -} - -async function enableInputIconDisabling() { - const inputIconVisibilityToggle = document.querySelector(".toggle-icon-in-page-visibility"); - - const stylePrefToggle = (inputsEnabled) => { - if (inputsEnabled === "show-input-icons") { - inputIconVisibilityToggle.dataset.iconVisibilityOption = "disable-input-icon"; - inputIconVisibilityToggle.classList.remove("input-icons-disabled"); - return; - } - inputIconVisibilityToggle.dataset.iconVisibilityOption = "enable-input-icon"; - inputIconVisibilityToggle.classList.add("input-icons-disabled"); - }; - - - const iconsAreEnabled = await areInputIconsEnabled(); - const userIconChoice = iconsAreEnabled ? "show-input-icons" : "hide-input-icons"; - stylePrefToggle(userIconChoice); - - inputIconVisibilityToggle.addEventListener("click", async () => { - const userIconPreference = (inputIconVisibilityToggle.dataset.iconVisibilityOption === "disable-input-icon") ? "hide-input-icons" : "show-input-icons"; - await browser.runtime.sendMessage({ - method: "updateInputIconPref", - iconPref: userIconPreference, - }); - sendRelayEvent("Panel", "click", userIconPreference); - return stylePrefToggle(userIconPreference); - }); - -} - -async function clearBrowserActionBadge() { - const { browserActionBadgesClicked } = await browser.storage.local.get( - "browserActionBadgesClicked" - ); - - // Dismiss the browserActionBadge only when it exists - if (browserActionBadgesClicked === false) { - browser.storage.local.set({ browserActionBadgesClicked: true }); - browser.browserAction.setBadgeBackgroundColor({ color: null }); - browser.browserAction.setBadgeText({ text: "" }); - } -} - -async function popup() { - sendRelayEvent("Panel", "opened-panel", "any-panel"); - clearBrowserActionBadge(); - const userApiToken = await browser.storage.local.get("apiToken"); - const signedInUser = (Object.prototype.hasOwnProperty.call(userApiToken, "apiToken")); - - // Set custom fonts from the add-on - await setCustomFonts(); - - if (!signedInUser) { - sendRelayEvent("Panel", "viewed-panel", "unauthenticated-user-panel"); - showSignUpPanel(); - } - - if (signedInUser) { - showRelayPanel(1); - } - - - await enableSettingsPanel(); - await enableReportIssuePanel(); - - enableDataOptOut(); - enableInputIconDisabling(); - - document.querySelectorAll(".close-popup-after-click").forEach(el => { - el.addEventListener("click", async (e) => { - e.preventDefault(); - if (e.target.dataset.eventLabel && e.target.dataset.eventAction) { - sendRelayEvent("Panel", e.target.dataset.eventAction, e.target.dataset.eventLabel); - } - await browser.tabs.create({ url: el.href }); - window.close(); - }); - }); - - const { relaySiteOrigin } = await browser.storage.local.get("relaySiteOrigin"); - - - document.querySelectorAll(".login-link").forEach(loginLink => { - loginLink.href = `${relaySiteOrigin}/accounts/profile?utm_source=fx-relay-addon&utm_medium=popup&utm_content=popup-continue-btn`; - }); - - document.querySelectorAll(".dashboard-link").forEach(dashboardLink => { - dashboardLink.href = `${relaySiteOrigin}/accounts/profile?utm_source=fx-relay-addon&utm_medium=popup&utm_content=manage-relay-addresses`; - }); - - document.querySelectorAll(".get-premium-link").forEach(premiumLink => { - premiumLink.href = `${relaySiteOrigin}/premium?utm_source=fx-relay-addon&utm_medium=popup&utm_content=get-premium-link`; - }); + // Set External Event Listerners + await popup.utilities.setExternalLinkEventListeners(); - document.querySelectorAll(".register-domain-cta").forEach(registerDomainLink => { - registerDomainLink.href = `${relaySiteOrigin}/accounts/profile?utm_source=fx-relay-addon&utm_medium=popup&utm_content=register-email-domain#mpp-choose-subdomain`; - }); + // Set Notification Bug for Unread News Items + popup.panel.news.utilities.initNewsItemCountNotification(); - // Add backlink to pricing section from promo CTAs - const promoCTAEl = document.querySelectorAll(".js-promo-link"); - promoCTAEl.forEach(i => { - i.href = `${relaySiteOrigin}/premium#pricing`; - }) + // TODO: Focus On Generate Button for Free / Search Filter for Premiums -} + }, + panel: { + update: (panelId, data) => { + const panels = document.querySelectorAll(".fx-relay-panel"); + panels.forEach((panel) => { + panel.classList.add("is-hidden"); + + if (panel.dataset.panelId === panelId) { + panel.classList.remove("is-hidden"); + popup.panel.init(panelId, data); + } + }); + + state.currentPanel = panelId; + }, + init: (panelId, data) => { + switch (panelId) { + case "custom": + popup.panel.masks.custom.init(); + break; + + case "masks": + popup.panel.masks.init(); + break; + + case "news": + sendRelayEvent("Panel", "click", "opened-news"); + popup.panel.news.init(); + + popup.panel.news.utilities.updateNewsItemCountNotification(true); + + break; + case "newsStory": + sendRelayEvent("Panel", "click", "opened-news-item"); + popup.panel.news.storyPanel.update(data.newsItemId); + break; + case "settings": + popup.utilities.enableInputIconDisabling(); + // Function is imported from data-opt-out-toggle.js + enableDataOptOut(); + + document + .getElementById("popupSettingsReportIssue") + .addEventListener( + "click", + (e) => { + e.preventDefault(); + popup.panel.update("webcompat"); + }, + false + ); + + break; + case "stats": + sendRelayEvent("Panel", "click", "opened-stats"); + popup.panel.stats.init(); + break; + + case "webcompat": + sendRelayEvent("Panel", "click", "opened-report-issue"); + popup.panel.webcompat.init(); + break; + + default: + break; + } + }, + masks: { + custom: { + init: async () => { + const customMaskForm = document.querySelector(".fx-relay-panel-custom-mask-form"); + const customMaskDomainInput = customMaskForm.querySelector(".fx-relay-panel-custom-mask-input-name"); + const customMaskDomainLabel = customMaskForm.querySelector(".fx-relay-panel-custom-mask-input-domain"); + const customMaskDomainSubmitButton = customMaskForm.querySelector(".fx-relay-panel-custom-mask-submit button"); + const { premiumSubdomainSet } = await browser.storage.local.get("premiumSubdomainSet"); + customMaskDomainInput.placeholder = browser.i18n.getMessage("popupCreateCustomFormMaskInputPlaceholder"); + customMaskDomainLabel.textContent = browser.i18n.getMessage("popupCreateCustomFormMaskInputDescription", premiumSubdomainSet); + + customMaskDomainInput.addEventListener("input", popup.panel.masks.custom.validateForm); + customMaskForm.addEventListener("submit", popup.panel.masks.custom.submit); + + const currentPageHostName = await browser.runtime.sendMessage({ + method: "getCurrentPageHostname", + }); + + if (currentPageHostName) { + const parsedDomain = psl.parse(currentPageHostName) + customMaskDomainInput.value = parsedDomain.sld; + customMaskDomainSubmitButton.disabled = false + } + + customMaskDomainInput.focus(); + + }, + submit: async (event) => { + event.preventDefault(); + // const customMaskForm = document.querySelector(".fx-relay-panel-custom-mask-form"); + const customMaskDomainInput = document.getElementById("customMaskName"); + const customMaskBlockPromosCheckbox = document.getElementById("customMaskBlockPromos"); + + if (!customMaskDomainInput.value) { + throw new Error(`No address name set`) + } + + popup.events.generateMask(event, "custom", { + address: customMaskDomainInput.value, + block_list_emails: customMaskBlockPromosCheckbox.checked, + }); + + popup.panel.update("masks"); + + }, + validateForm: async () => { + const customMaskForm = document.querySelector(".fx-relay-panel-custom-mask-form"); + const customMaskDomainInput = customMaskForm.querySelector(".fx-relay-panel-custom-mask-input-name"); + const customMaskDomainSubmitButton = customMaskForm.querySelector(".fx-relay-panel-custom-mask-submit button"); + + // If there's input, make the form submission possible + customMaskDomainSubmitButton.disabled = !(customMaskDomainInput.value) + } + }, + init: async () => { + + const masks = await popup.utilities.getMasks(); + + // If no masks are created, + if (masks.length === 0) { + const noMasksCreatedPanel = document.querySelector(".fx-relay-no-masks-created"); + noMasksCreatedPanel.classList.remove("is-hidden"); + } + + const { premium } = await browser.storage.local.get("premium"); + const maskPanel = document.getElementById("masks-panel"); + const generateRandomMask = document.querySelector(".js-generate-random-mask"); + + if (!premium) { + await popup.panel.masks.utilities.setRemainingMaskCount(); + maskPanel.setAttribute("data-account-level", "free"); + } else { + maskPanel.setAttribute("data-account-level", "premium"); + + // Update language of Generate Random Mask to "Generate random mask" + generateRandomMask.textContent = browser.i18n.getMessage("pageInputIconGenerateRandomMask"); + + // Prompt user to register subdomain + const { premiumSubdomainSet } = await browser.storage.local.get("premiumSubdomainSet"); + const isPremiumSubdomainSet = premiumSubdomainSet !== "None"; + + if (!isPremiumSubdomainSet) { + const registerSubdomainButton = document.querySelector(".fx-relay-regsiter-subdomain-button"); + registerSubdomainButton.classList.remove("is-hidden"); + } else { + + const generateCustomMask = document.querySelector(".js-generate-custom-mask"); + + // Show "Generate custom mask" button + generateCustomMask.classList.remove("is-hidden"); + + generateCustomMask.addEventListener("click", (e) => { + e.preventDefault(); + popup.panel.update("custom"); + }, false); + + // Restyle Random Mask button to secondary + generateRandomMask.classList.remove("t-primary"); + generateRandomMask.classList.add("t-secondary"); + } + } + + generateRandomMask.addEventListener("click", (e) => { + popup.events.generateMask(e, "random"); + }, false); + + // Build initial list + // Note: If premium, buildMasksList runs `popup.panel.masks.search.init()` after completing + popup.panel.masks.utilities.buildMasksList(); + + // Remove loading state + document.body.classList.remove("is-loading"); + + }, + search: { + filter: (query)=> { + + const searchInput = document.querySelector(".fx-relay-masks-search-input"); + searchInput.classList.add("is-active"); + + const maskSearchResults = Array.from(document.querySelectorAll(".fx-relay-mask-list li")); + + maskSearchResults.forEach((maskResult) => { + const emailAddress = maskResult.dataset.maskAddress; + const label = maskResult.dataset.maskDescription; + const usedOn = maskResult.dataset.maskUsedOn; + const generated = maskResult.dataset.maskGenerated; + + // Check search input against any mask name, label or used-on/generated for web details + const matchesSearchFilter = + emailAddress.toLowerCase().includes(query.toLowerCase()) || + label.toLowerCase().includes(query.toLowerCase()) || + usedOn.toLowerCase().includes(query.toLowerCase()) || + generated.toLowerCase().includes(query.toLowerCase()); + + if (matchesSearchFilter) { + maskResult.classList.remove("is-hidden"); + } else { + maskResult.classList.add("is-hidden"); + } + + // Set #/# labels inside search bar to show results count + const searchFilterTotal = document.querySelector(".js-filter-masks-total"); + const searchFilterVisible = document.querySelector(".js-filter-masks-visible"); + + searchFilterVisible.textContent = maskSearchResults.filter((maskResult) => !maskResult.classList.contains("is-hidden")).length; + searchFilterTotal.textContent = maskSearchResults.length; + }); + + }, + init: () => { + const searchForm = document.querySelector(".fx-relay-masks-search-form"); + + const searchInput = document.querySelector(".fx-relay-masks-search-input"); + searchInput.placeholder = browser.i18n.getMessage("labelSearch"); + + searchForm.addEventListener("submit", (event) => { + event.preventDefault(); + searchInput.blur(); + }); + + searchInput.addEventListener("input", (event) => { + if (event.target.value.length) { + popup.panel.masks.search.filter(event.target.value); + return; + } + + popup.panel.masks.search.reset() + }); + + searchInput.addEventListener("reset", popup.panel.masks.search.reset); + + const maskSearchResults = Array.from(document.querySelectorAll(".fx-relay-mask-list li")); + const searchFilterTotal = document.querySelector(".js-filter-masks-total"); + const searchFilterVisible = document.querySelector(".js-filter-masks-visible"); + searchFilterVisible.textContent = maskSearchResults.length; + searchFilterTotal.textContent = maskSearchResults.length; + + // Show bar if there's at least one mask created + if (maskSearchResults.length) { + searchForm.classList.add("is-visible"); + searchInput.focus(); + } + + + }, + reset: () => { + const searchInput = document.querySelector(".fx-relay-masks-search-input"); + searchInput.classList.remove("is-active"); + + const maskSearchResults = Array.from(document.querySelectorAll(".fx-relay-mask-list li")); + const searchFilterTotal = document.querySelector(".js-filter-masks-total"); + const searchFilterVisible = document.querySelector(".js-filter-masks-visible"); + searchFilterVisible.textContent = maskSearchResults.length; + searchFilterTotal.textContent = maskSearchResults.length; + + maskSearchResults.forEach((maskResult) => { + maskResult.classList.remove("is-hidden"); + }); + + } + }, + utilities: { + buildMasksList: async (opts = null) => { + let getMasksOptions = { fetchCustomMasks: false }; + const { premium } = await browser.storage.local.get("premium"); + + if (premium) { + // Check if user may have custom domain masks + const { premiumSubdomainSet } = await browser.storage.local.get( + "premiumSubdomainSet" + ); + + // API Note: If a user has not registered a subdomain yet, its default stored/queried value is "None"; + const isPremiumSubdomainSet = premiumSubdomainSet !== "None"; + getMasksOptions.fetchCustomMasks = isPremiumSubdomainSet; + + // If not set, prompt user to register domain + if (!isPremiumSubdomainSet) { + const registerSubdomainButton = document.querySelector(".fx-relay-regsiter-subdomain-button"); + registerSubdomainButton.classList.remove("is-hidden"); + } + + // Show Generate Button + const generateRandomMask = document.querySelector(".js-generate-random-mask"); + generateRandomMask.classList.remove("is-hidden"); + } + + const masks = await popup.utilities.getMasks(getMasksOptions); + + const maskList = document.querySelector(".fx-relay-mask-list"); + // Reset mask list + maskList.textContent = ""; + + masks.forEach(mask => { + const maskListItem = document.createElement("li"); + + // Attributes used to power search filtering + maskListItem.setAttribute("data-mask-address", mask.full_address); + maskListItem.setAttribute("data-mask-description", mask.description ?? ""); + maskListItem.setAttribute("data-mask-used-on", mask.used_on ?? ""); + maskListItem.setAttribute("data-mask-generated", mask.generated_for ?? ""); + + maskListItem.classList.add("fx-relay-mask-item"); + + const maskListItemNewMaskCreatedLabel = document.createElement("span"); + maskListItemNewMaskCreatedLabel.textContent = browser.i18n.getMessage("labelMaskCreated"); + maskListItemNewMaskCreatedLabel.classList.add("fx-relay-mask-item-new-mask-created"); + maskListItem.appendChild(maskListItemNewMaskCreatedLabel); + + const maskListItemAddressBar = document.createElement("div"); + maskListItemAddressBar.classList.add("fx-relay-mask-item-address-bar"); + + const maskListItemAddressWrapper = document.createElement("div"); + maskListItemAddressWrapper.classList.add("fx-relay-mask-item-address-wrapper"); + + const maskListItemLabel = document.createElement("span"); + maskListItemLabel.classList.add("fx-relay-mask-item-label"); + maskListItemLabel.textContent = mask.description; + + // Append Label if it exists + if (mask.description !== "") { + maskListItemAddressWrapper.appendChild(maskListItemLabel); + } + + const maskListItemAddress = document.createElement("div"); + maskListItemAddress.classList.add("fx-relay-mask-item-address"); + maskListItemAddress.textContent = mask.full_address; + maskListItemAddressWrapper.appendChild(maskListItemAddress); + + // Add Mask Address Bar Contents + maskListItemAddressBar.appendChild(maskListItemAddressWrapper); + + const maskListItemAddressActions = document.createElement("div"); + maskListItemAddressActions.classList.add("fx-relay-mask-item-address-actions"); + + const maskListItemCopyButton = document.createElement("button"); + maskListItemCopyButton.classList.add("fx-relay-mask-item-address-copy"); + maskListItemCopyButton.setAttribute("data-mask-address", mask.full_address); + + const maskListItemCopyButtonSuccessMessage = document.createElement("span"); + maskListItemCopyButtonSuccessMessage.textContent = browser.i18n.getMessage("popupCopyMaskButtonCopied"); + maskListItemCopyButtonSuccessMessage.classList.add("fx-relay-mask-item-address-copy-success"); + maskListItemAddressActions.appendChild(maskListItemCopyButtonSuccessMessage); + + maskListItemCopyButton.addEventListener("click", (e)=> { + e.preventDefault(); + navigator.clipboard.writeText(e.target.dataset.maskAddress); + maskListItemCopyButtonSuccessMessage.classList.add("is-shown"); + setTimeout(() => { + maskListItemCopyButtonSuccessMessage.classList.remove("is-shown") + }, 1000); + }, false); + maskListItemAddressActions.appendChild(maskListItemCopyButton); + + const maskListItemToggleButton = document.createElement("button"); + maskListItemToggleButton.classList.add("fx-relay-mask-item-address-toggle"); + maskListItemToggleButton.addEventListener("click", ()=> { + // TODO: Add Toggle Function + }, false); + maskListItemToggleButton.setAttribute("data-mask-id", mask.id); + maskListItemToggleButton.setAttribute("data-mask-type", mask.mask_type); + maskListItemToggleButton.setAttribute("data-mask-address", mask.full_address); + + // TODO: Add toggle button back + // maskListItemAddressActions.appendChild(maskListItemToggleButton); + + maskListItemAddressBar.appendChild(maskListItemAddressActions); + maskListItem.appendChild(maskListItemAddressBar); + maskList.appendChild(maskListItem); + }); + + // Display "Mask created" temporary label when a new mask is created in the panel + if (opts && opts.newMaskCreated && maskList.firstElementChild) { + maskList.firstElementChild.classList.add("is-new-mask"); + + setTimeout(() => { + maskList.firstElementChild.classList.remove("is-new-mask"); + }, 1000); + } + + if (premium) { + popup.panel.masks.search.init(); + } + + }, + getRemainingAliases: async () => { + const masks = await popup.utilities.getMasks(); + const { maxNumAliases } = await browser.storage.local.get("maxNumAliases"); + return { masks, maxNumAliases }; + }, + getRemainingMaskCount: async () => { + const { masks, maxNumAliases } = await popup.panel.masks.utilities.getRemainingAliases(); + const numRemaining = maxNumAliases - masks.length; + return numRemaining; + }, + setRemainingMaskCount: async () => { + const { masks, maxNumAliases } = await popup.panel.masks.utilities.getRemainingAliases(); + const numRemaining = maxNumAliases - masks.length; + const masksAvailable = document.querySelector(".fx-relay-masks-available-count"); + const masksLimitReached = document.querySelector(".fx-relay-masks-limit-upgrade-string"); + const limitReachedToast = document.querySelector(".fx-relay-masks-limit-upgrade"); + + masksAvailable.textContent = browser.i18n.getMessage("popupFreeMasksAvailable", [numRemaining, maxNumAliases]); + masksLimitReached.textContent = browser.i18n.getMessage("popupFreeMasksLimitReached", [maxNumAliases]); + + const generateRandomMask = document.querySelector(".js-generate-random-mask"); + + if (masks.length === 0) { + generateRandomMask.classList.remove("is-hidden"); + return; + } + + if (numRemaining === 0) { + // No masks remaining + limitReachedToast.classList.remove("is-hidden"); + masksAvailable.classList.add("is-hidden"); + + // Hide Generate Button + generateRandomMask.classList.add("is-hidden"); + + // Show Upgrade Button + const getUnlimitedMasksBtn = document.querySelector(".fx-relay-mask-upgrade-button"); + getUnlimitedMasksBtn.classList.remove("is-hidden"); + } else { + + // Show Masks Count/Generate Button + masksAvailable.classList.remove("is-hidden"); + generateRandomMask.classList.remove("is-hidden"); + } + + + + } + }, + }, + news: { + init: async () => { + + const newsList = document.querySelector(".fx-relay-news"); + + // If there's no news items, go build them + if ( !newsList.hasChildNodes() ) { + newsContent.forEach(async (newsItem) => { + // Check for any catches to not display the item + const hasLogicCheck = Object.prototype.hasOwnProperty.call(newsItem, "logicCheck"); + + if ( + // Check for waffle (Waffle must return false to catch) + ( + newsItem.waffle && + !(await checkWaffleFlag(newsItem.waffle))) + || + // logicCheck Function (Must return false to catch) + (hasLogicCheck && !newsItem.logicCheck) + ) { + return; + } + + // Build and attach news item + const liFxRelayNewsItem = document.createElement("li"); + liFxRelayNewsItem.classList.add("fx-relay-news-item"); + + const button = document.createElement("button"); + button.classList.add("fx-relay-news-item-button"); + button.setAttribute("data-news-item-id", newsItem.id); + liFxRelayNewsItem.appendChild(button); + + const divTeaserImage = document.createElement("div"); + divTeaserImage.classList.add("fx-relay-news-item-image"); + + const imgTeaserImage = document.createElement("img"); + imgTeaserImage.src = newsItem.teaserImg; + divTeaserImage.appendChild(imgTeaserImage); + button.appendChild(divTeaserImage); + + const divTeaserCopy = document.createElement("div"); + divTeaserCopy.classList.add("fx-relay-news-item-content"); + + const h3TeaserTitle = document.createElement("h3"); + h3TeaserTitle.classList.add("fx-relay-news-item-hero"); + // Pass i18n Args if applicable + const h3TeaserTitleTextContent = newsItem.headlineStringArgs + ? browser.i18n.getMessage( + newsItem.headlineString, + newsItem.headlineStringArgs + ) + : browser.i18n.getMessage(newsItem.headlineString); + h3TeaserTitle.textContent = h3TeaserTitleTextContent; + + const divTeaserBody = document.createElement("div"); + divTeaserBody.classList.add("fx-relay-news-item-body"); + // Pass i18n Args if applicable + const divTeaserBodyTextContent = newsItem.bodyStringArgs + ? browser.i18n.getMessage( + newsItem.bodyString, + newsItem.bodyStringArgs + ) + : browser.i18n.getMessage(newsItem.bodyString); + divTeaserBody.textContent = divTeaserBodyTextContent; + + divTeaserCopy.appendChild(h3TeaserTitle); + divTeaserCopy.appendChild(divTeaserBody); + button.appendChild(divTeaserCopy); + + newsList.appendChild(liFxRelayNewsItem); + + button.addEventListener( + "click", + popup.panel.news.storyPanel.show, + false + ); + }); + } + }, + storyPanel: { + show: (event) => { + popup.panel.update("newsStory", { + newsItemId: event.target.dataset.newsItemId, + }); + }, + update: (newsItemId) => { + // Get content for news detail view + const storyData = newsContent.filter((story) => { return story.id == newsItemId }); + const newsItemContent = storyData[0]; + + const newsStoryDetail = document.querySelector(".fx-relay-news-story"); + + // Reset news detail item + newsStoryDetail.textContent = ""; + + // Populate HTML + const newsStoryHeroImage = document.createElement("img"); + newsStoryHeroImage.src = newsItemContent.fullImg; + newsStoryDetail.appendChild(newsStoryHeroImage); + + const newsStoryHeroTitle = document.createElement("h3"); + const newsStoryHeroTitleTextContent = newsItemContent.headlineStringArgs + ? browser.i18n.getMessage( + newsItemContent.headlineString, + newsItemContent.headlineStringArgs + ) + : browser.i18n.getMessage(newsItemContent.headlineString); + newsStoryHeroTitle.textContent = newsStoryHeroTitleTextContent; + newsStoryDetail.appendChild(newsStoryHeroTitle); + + const newsStoryHeroBody = document.createElement("div"); + // Pass i18n Args if applicable + const newsStoryHeroBodyTextContent = newsItemContent.bodyStringArgs + ? browser.i18n.getMessage( + newsItemContent.bodyString, + newsItemContent.bodyStringArgs + ) + : browser.i18n.getMessage(newsItemContent.bodyString); + newsStoryHeroBody.textContent = newsStoryHeroBodyTextContent; + newsStoryDetail.appendChild(newsStoryHeroBody); + + // If the section has a CTA, add it. + if (newsItemContent.fullCta) { + const newsStoryHeroCTA = document.createElement("a"); + newsStoryHeroCTA.classList.add("fx-relay-news-story-link"); + + // If the URL points towards Relay, choose the correct server + if (newsItemContent.fullCtaRelayURL) { + newsStoryHeroCTA.href = `${relaySiteOrigin}${newsItemContent.fullCtaHref}`; + } else { + newsStoryHeroCTA.href = `${newsItemContent.fullCtaHref}`; + } + + // Set GA data if applicable + if (newsItemContent.fullCtaEventLabel && newsItemContent.fullCtaEventAction) { + newsStoryHeroCTA.setAttribute("data-event-action", newsItemContent.fullCtaEventAction); + newsStoryHeroCTA.setAttribute("data-event-label", newsItemContent.fullCtaEventLabel); + } + + newsStoryHeroCTA.textContent = browser.i18n.getMessage(newsItemContent.fullCta); + newsStoryHeroCTA.addEventListener("click", popup.events.externalClick, false); + newsStoryDetail.appendChild(newsStoryHeroCTA); + } + }, + }, + utilities: { + initNewsItemCountNotification: async () => { + + const localStorage = await browser.storage.local.get(); + + const unreadNewsItemsCountExists = + Object.prototype.hasOwnProperty.call( + localStorage, + "unreadNewsItemsCount" + ); + + const readNewsItemsCountExists = + Object.prototype.hasOwnProperty.call( + localStorage, + "readNewsItemCount" + ); + + // First-run user: No unread data present + if (!unreadNewsItemsCountExists && !readNewsItemsCountExists) { + await browser.storage.local.set({ + unreadNewsItemsCount: state.newsItemsCount, + readNewsItemCount: 0, + }); + } + + // FIXME: The total news item count may differ than what is displayed to the user + // Example: Three items total but user doesn't have waffle for one news item. + // Regardless - update the unreadNews count to match whatever is in state + await browser.storage.local.set({ + unreadNewsItemsCount: state.newsItemsCount, + }); + + const { readNewsItemCount } = await browser.storage.local.get( + "readNewsItemCount" + ); + + const { unreadNewsItemsCount } = await browser.storage.local.get( + "unreadNewsItemsCount" + ); + + // Set unread count + const newsItemCountNotification = document.querySelector( + ".fx-relay-menu-dashboard-link[data-panel-id='news'] .news-count" + ); + + const unreadCount = unreadNewsItemsCount - readNewsItemCount; + + // Show count is it exists + if (unreadCount > 0) { + newsItemCountNotification.textContent = unreadCount.toString(); + newsItemCountNotification.classList.remove("is-hidden"); + } + + }, + updateNewsItemCountNotification: async (markAllUnread = false) => { + if (markAllUnread) { + await browser.storage.local.set({ + readNewsItemCount: state.newsItemsCount, + }); + + const newsItemCountNotification = document.querySelector( + ".fx-relay-menu-dashboard-link[data-panel-id='news'] .news-count" + ); + + newsItemCountNotification.classList.add("is-hidden"); + + } + } + }, + }, + stats: { + init: async () => { + // Get Global Mask Stats data + const { aliasesUsedVal } = await browser.storage.local.get( + "aliasesUsedVal" + ); + const { emailsForwardedVal } = await browser.storage.local.get( + "emailsForwardedVal" + ); + const { emailsBlockedVal } = await browser.storage.local.get( + "emailsBlockedVal" + ); + + const globalStatSet = document.querySelector( + ".dashboard-stats-list.global-stats" + ); + + const globalAliasesUsedValEl = + globalStatSet.querySelector(".aliases-used"); + const globalEmailsBlockedValEl = + globalStatSet.querySelector(".emails-blocked"); + const globalEmailsForwardedValEl = + globalStatSet.querySelector(".emails-forwarded"); + + globalAliasesUsedValEl.textContent = aliasesUsedVal; + globalEmailsBlockedValEl.textContent = emailsBlockedVal; + globalEmailsForwardedValEl.textContent = emailsForwardedVal; + + // Check if any data applies to the current site + const currentPageHostName = await browser.runtime.sendMessage({ + method: "getCurrentPageHostname", + }); + + // Check if user is premium (and then check if they have a domain set) + // This is needed in order to query both random and custom masks + const { premium } = await browser.storage.local.get("premium"); + let getMasksOptions = { fetchCustomMasks: false }; + + if (premium) { + // Check if user may have custom domain masks + const { premiumSubdomainSet } = await browser.storage.local.get( + "premiumSubdomainSet" + ); + + // API Note: If a user has not registered a subdomain yet, its default stored/queried value is "None"; + const isPremiumSubdomainSet = premiumSubdomainSet !== "None"; + getMasksOptions.fetchCustomMasks = isPremiumSubdomainSet; + } + + const masks = await popup.utilities.getMasks(getMasksOptions); + + const currentWebsiteStateSet = document.querySelector( + ".dashboard-stats-list.current-website-stats" + ); + + if ( + popup.utilities.checkIfAnyMasksWereGeneratedOnCurrentWebsite( + masks, + currentPageHostName + ) + ) { + // Some masks are used on the current site. Time to calculate! + const filteredMasks = masks.filter( + (mask) => + mask.generated_for === currentPageHostName || + popup.utilities.hasMaskBeenUsedOnCurrentSite( + mask, + currentPageHostName + ) + ); + + let currentWebsiteForwardedVal = 0; + let currentWebsiteBlockedVal = 0; + + filteredMasks.forEach((mask) => { + currentWebsiteForwardedVal += mask.num_forwarded; + currentWebsiteBlockedVal += mask.num_blocked; + }); + + const currentWebsiteAliasesUsedValEl = + currentWebsiteStateSet.querySelector(".aliases-used"); + currentWebsiteAliasesUsedValEl.textContent = filteredMasks.length; + + const currentWebsiteEmailsForwardedValEl = + currentWebsiteStateSet.querySelector(".emails-forwarded"); + currentWebsiteEmailsForwardedValEl.textContent = + currentWebsiteForwardedVal; + + const currentWebsiteEmailsBlockedValEl = + currentWebsiteStateSet.querySelector(".emails-blocked"); + currentWebsiteEmailsBlockedValEl.textContent = + currentWebsiteBlockedVal; + + const currentWebsiteEmailsBlocked = + currentWebsiteStateSet.querySelector( + ".dashboard-info-emails-blocked" + ); + const currentWebsiteEmailsForwarded = + currentWebsiteStateSet.querySelector( + ".dashboard-info-emails-forwarded" + ); + currentWebsiteEmailsBlocked.classList.remove("is-hidden"); + currentWebsiteEmailsForwarded.classList.remove("is-hidden"); + } + }, + }, + webcompat: { + init: () => { + popup.panel.webcompat.setURLwithIssue(); + popup.panel.webcompat.showReportInputOtherTextField(); + popup.panel.webcompat.showSuccessReportSubmission(); + + const reportForm = document.querySelector(".report-issue-content"); + reportForm.addEventListener("submit", async (event) => { + await popup.panel.webcompat.handleReportIssueFormSubmission(event); + }); + + const reportContinueButton = + document.querySelector(".report-continue"); + reportContinueButton.addEventListener( + "click", + popup.events.backClick, + false + ); + }, + setURLwithIssue: async () => { + // Add Site URL placeholder + const currentPage = (await popup.utilities.getCurrentPage()).url; + const reportIssueSubmitBtn = document.querySelector( + ".report-issue-submit-btn" + ); + const inputFieldUrl = document.querySelector( + 'input[name="issue_on_domain"]' + ); + reportIssueSubmitBtn.disabled = true; + + // Allow for custom URL inputs + inputFieldUrl.addEventListener("input", () => { + reportIssueSubmitBtn.disabled = true; + // Ensure that the custom input looks like a URL without https:// or http:// (e.g. test.com, www.test.com) + if (popup.utilities.isSortaAURL(inputFieldUrl.value)) { + reportIssueSubmitBtn.disabled = false; + } + }); + + // Check that the host site has a valid URL + if (currentPage) { + const url = new URL(currentPage); + // returns a http:// or https:// value + inputFieldUrl.value = url.origin; + reportIssueSubmitBtn.disabled = false; + } + }, + showReportInputOtherTextField: () => { + const otherCheckbox = document.querySelector( + 'input[name="issue-case-other"]' + ); + const otherTextField = document.querySelector( + 'input[name="other_issue"]' + ); + otherCheckbox.addEventListener("click", () => { + otherTextField.classList.toggle("is-hidden"); + }); + + // Add placeholder to report input on 'Other' selection + const inputFieldOtherDetails = document.querySelector( + 'input[name="other_issue"]' + ); + inputFieldOtherDetails.placeholder = browser.i18n.getMessage( + "popupReportIssueCaseOtherDetails" + ); + }, + showSuccessReportSubmission: () => { + const reportIssueSubmitBtn = document.querySelector( + ".report-issue-submit-btn" + ); + const reportSuccess = document.querySelector(".report-success"); + const reportContent = document.querySelector(".report-issue-content"); + reportIssueSubmitBtn.addEventListener("click", () => { + reportSuccess.classList.remove("is-hidden"); + reportContent.classList.add("is-hidden"); + }); + }, + handleReportIssueFormSubmission: async (event) => { + event.preventDefault(); + const data = new FormData(event.target); + const reportData = Object.fromEntries(data.entries()); + reportData.user_agent = await getBrowser(); + + Object.keys(reportData).forEach(function (value) { + // Switch "on" to true + if (reportData[value] === "on") { + reportData[value] = true; + } + // Remove from report if empty string + if (reportData[value] === "") { + delete reportData[value]; + } + }); + + // Clean URL data to add "http://" before it if the custom input doesn't contain a HTTP protocol + if ( + !( + reportData.issue_on_domain.startsWith("http://") || + reportData.issue_on_domain.startsWith("https://") + ) + ) { + reportData.issue_on_domain = "http://" + reportData.issue_on_domain; + } + + await browser.runtime.sendMessage({ + method: "postReportWebcompatIssue", + description: reportData, + }); + }, + }, + }, + utilities: { + checkIfAnyMasksWereGeneratedOnCurrentWebsite: (masks, domain) => { + return masks.some((mask) => { + return domain === mask.generated_for; + }); + }, + clearBrowserActionBadge: async () => { + const { browserActionBadgesClicked } = await browser.storage.local.get( + "browserActionBadgesClicked" + ); + + // Dismiss the browserActionBadge only when it exists + if (browserActionBadgesClicked === false) { + browser.storage.local.set({ browserActionBadgesClicked: true }); + browser.browserAction.setBadgeBackgroundColor({ color: null }); + browser.browserAction.setBadgeText({ text: "" }); + } + }, + enableInputIconDisabling: async () => { + const inputIconVisibilityToggle = document.querySelector( + ".toggle-icon-in-page-visibility" + ); + + const stylePrefToggle = (inputsEnabled) => { + if (inputsEnabled === "show-input-icons") { + inputIconVisibilityToggle.dataset.iconVisibilityOption = + "disable-input-icon"; + inputIconVisibilityToggle.classList.remove("input-icons-disabled"); + return; + } + inputIconVisibilityToggle.dataset.iconVisibilityOption = + "enable-input-icon"; + inputIconVisibilityToggle.classList.add("input-icons-disabled"); + }; + + const iconsAreEnabled = await areInputIconsEnabled(); + const userIconChoice = iconsAreEnabled + ? "show-input-icons" + : "hide-input-icons"; + stylePrefToggle(userIconChoice); + + inputIconVisibilityToggle.addEventListener("click", async () => { + const userIconPreference = + inputIconVisibilityToggle.dataset.iconVisibilityOption === + "disable-input-icon" + ? "hide-input-icons" + : "show-input-icons"; + await browser.runtime.sendMessage({ + method: "updateInputIconPref", + iconPref: userIconPreference, + }); + sendRelayEvent("Panel", "click", userIconPreference); + return stylePrefToggle(userIconPreference); + }); + }, + hasMaskBeenUsedOnCurrentSite: (mask, domain) => { + const domainList = mask.used_on; + + // Short circuit out if there's no used_on entry + if ( + domainList === null || + domainList === "" || + domainList === undefined + ) { + return false; + } + + // Domain already exists in used_on field. Just return the list! + if (domainList.split(",").includes(domain)) { + return true; + } + + // No match found! + return false; + }, + isSortaAURL: (str) => { + return str.includes(".") && !str.endsWith(".") && !str.startsWith("."); + }, + isUserSignedIn: async () => { + const userApiToken = await browser.storage.local.get("apiToken"); + const signedInUser = Object.prototype.hasOwnProperty.call( + userApiToken, + "apiToken" + ); + return signedInUser; + }, + getCachedServerStoragePref: async () => { + const serverStoragePref = await browser.storage.local.get( + "server_storage" + ); + const serverStoragePrefInLocalStorage = + Object.prototype.hasOwnProperty.call( + serverStoragePref, + "server_storage" + ); + + if (!serverStoragePrefInLocalStorage) { + // There is no reference to the users storage preference saved. Fetch it from the server. + return await browser.runtime.sendMessage({ + method: "getServerStoragePref", + }); + } else { + // If the stored pref exists, return value + return serverStoragePref.server_storage; + } + }, + getCurrentPage: async () => { + const [currentTab] = await browser.tabs.query({ + active: true, + currentWindow: true, + }); + return currentTab; + }, + getMasks: async (options = { fetchCustomMasks: false }) => { + const serverStoragePref = + await popup.utilities.getCachedServerStoragePref(); + + if (serverStoragePref) { + try { + return await browser.runtime.sendMessage({ + method: "getAliasesFromServer", + options, + }); + } catch (error) { + console.warn(`getAliasesFromServer Error: ${error}`); + + // API Error — Fallback to local storage + const { relayAddresses } = await browser.storage.local.get( + "relayAddresses" + ); + + return relayAddresses; + } + } + + // User is not syncing with the server. Use local storage. + const { relayAddresses } = await browser.storage.local.get("relayAddresses"); + return relayAddresses; + }, + setExternalLinkEventListeners: async () => { + const externalLinks = document.querySelectorAll(".js-external-link"); + + externalLinks.forEach((link) => { + // Because we dynamically set the Relay origin URL (local/dev/stage/prod), + // we have to catch Relay-specific links and prepend the correct Relay website URL + if (link.dataset.relayInternal === "true") { + // TODO: Remove "/" from here. It'll be error prone + link.href = `${relaySiteOrigin}/${link.dataset.href}`; + } else { + link.href = `${link.dataset.href}`; + } + + link.addEventListener("click", popup.events.externalClick, false); + }); + }, + unhideNavigationItemsOnceLoggedIn: () => { + document + .querySelectorAll(".fx-relay-menu-dashboard-link.is-hidden") + .forEach((link) => { + link.classList.remove("is-hidden"); + }); + }, + }, + }; -document.addEventListener("DOMContentLoaded", popup); + popup.init(); +})(); diff --git a/src/manifest.json b/src/manifest.json index d1c7313d7..7f49f8539 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -29,7 +29,7 @@ "default_icon": { "32": "icons/icon_32.png" }, - "default_popup": "popup-new.html" + "default_popup": "popup.html" }, "content_scripts": [ diff --git a/src/popup-new.html b/src/popup-new.html deleted file mode 100644 index ae4db7a93..000000000 --- a/src/popup-new.html +++ /dev/null @@ -1,388 +0,0 @@ - - - - - Firefox Relay - - - - - - - - - - - - -
-
-

- - Firefox Relay -

- -
-
- - -
-
-
-
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/popup.html b/src/popup.html index 8913a85dc..0703dc7a5 100644 --- a/src/popup.html +++ b/src/popup.html @@ -3,10 +3,10 @@ Firefox Relay - - + + @@ -14,225 +14,375 @@ - - - - - - - - - -
- -

-
- -
- - - -
- -

+ +
+ -
- -
- - -
-
- - -
    -
  • - - - -
  • -
  • - - - -
  • -
  • - - - -
  • -
  • - - - -
  • -
  • - - -
  • -
+
+ + +
+
+
+
+
- - - - - - - - - -