From 0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 03:47:29 +0200 Subject: Adding upstream version 115.8.0esr. Signed-off-by: Daniel Baumann --- browser/components/pocket/content/Pocket.sys.mjs | 56 + .../components/pocket/content/SaveToPocket.sys.mjs | 245 ++ .../pocket/content/panels/css/global.scss | 67 + .../components/pocket/content/panels/css/home.scss | 115 + .../pocket/content/panels/css/main.compiled.css | 2418 ++++++++++++++++++++ .../components/pocket/content/panels/css/main.scss | 17 + .../pocket/content/panels/css/normalize.scss | 425 ++++ .../pocket/content/panels/css/panel.scss | 8 + .../pocket/content/panels/css/saved.scss | 939 ++++++++ .../pocket/content/panels/css/signup.scss | 360 +++ .../pocket/content/panels/css/styleguide.scss | 28 + .../content/panels/fonts/FiraSans-Regular.woff | Bin 0 -> 179188 bytes browser/components/pocket/content/panels/home.html | 20 + .../pocket/content/panels/img/chevron-right.svg | 3 + .../pocket/content/panels/img/list-view.svg | 8 + .../components/pocket/content/panels/img/open.svg | 3 + .../pocket/content/panels/img/pocketerror@1x.png | Bin 0 -> 923 bytes .../pocket/content/panels/img/pocketerror@2x.png | Bin 0 -> 1698 bytes .../pocket/content/panels/img/pocketlogo-dark.svg | 14 + .../pocket/content/panels/img/pocketlogo.svg | 16 + .../pocket/content/panels/img/pocketlogo@1x.png | Bin 0 -> 2120 bytes .../pocket/content/panels/img/pocketlogo@2x.png | Bin 0 -> 4060 bytes .../content/panels/img/pocketlogosolo@1x.png | Bin 0 -> 766 bytes .../content/panels/img/pocketlogosolo@2x.png | Bin 0 -> 1393 bytes .../content/panels/img/pocketsignup_button@1x.png | Bin 0 -> 2413 bytes .../content/panels/img/pocketsignup_button@2x.png | Bin 0 -> 4872 bytes .../content/panels/img/pocketsignup_devices@1x.png | Bin 0 -> 20258 bytes .../content/panels/img/pocketsignup_devices@2x.png | Bin 0 -> 68058 bytes .../content/panels/img/pocketsignup_hero@1x.png | Bin 0 -> 41497 bytes .../content/panels/img/pocketsignup_hero@2x.png | Bin 0 -> 137839 bytes .../pocket/content/panels/img/rainbow-reader.svg | 53 + .../content/panels/img/signup_firefoxlogo@1x.png | Bin 0 -> 412 bytes .../content/panels/img/signup_firefoxlogo@2x.png | Bin 0 -> 763 bytes .../pocket/content/panels/img/signup_help@1x.png | Bin 0 -> 420 bytes .../pocket/content/panels/img/signup_help@2x.png | Bin 0 -> 788 bytes .../pocket/content/panels/img/tag_close@1x.png | Bin 0 -> 176 bytes .../pocket/content/panels/img/tag_close@2x.png | Bin 0 -> 334 bytes .../content/panels/img/tag_closeactive@1x.png | Bin 0 -> 159 bytes .../content/panels/img/tag_closeactive@2x.png | Bin 0 -> 274 bytes .../js/components/ArticleList/ArticleList.jsx | 139 ++ .../js/components/ArticleList/ArticleList.scss | 65 + .../content/panels/js/components/Button/Button.jsx | 21 + .../panels/js/components/Button/Button.scss | 142 ++ .../content/panels/js/components/Header/Header.jsx | 16 + .../panels/js/components/Header/Header.scss | 22 + .../content/panels/js/components/Home/Home.jsx | 168 ++ .../js/components/PopularTopics/PopularTopics.jsx | 27 + .../js/components/PopularTopics/PopularTopics.scss | 54 + .../content/panels/js/components/Saved/Saved.jsx | 203 ++ .../content/panels/js/components/Saved/Saved.scss | 17 + .../content/panels/js/components/Signup/Signup.jsx | 79 + .../panels/js/components/Signup/Signup.scss | 19 + .../panels/js/components/TagPicker/TagPicker.jsx | 208 ++ .../panels/js/components/TagPicker/TagPicker.scss | 141 ++ .../js/components/TelemetryLink/TelemetryLink.jsx | 35 + .../pocket/content/panels/js/home/entry.js | 17 + .../pocket/content/panels/js/home/overlay.js | 62 + .../pocket/content/panels/js/main.bundle.js | 1355 +++++++++++ .../content/panels/js/main.bundle.js.LICENSE.txt | 23 + .../components/pocket/content/panels/js/main.js | 118 + .../pocket/content/panels/js/messages.js | 50 + .../pocket/content/panels/js/saved/entry.js | 17 + .../pocket/content/panels/js/saved/overlay.js | 46 + .../pocket/content/panels/js/signup/entry.js | 17 + .../pocket/content/panels/js/signup/overlay.js | 50 + .../pocket/content/panels/js/style-guide/entry.js | 45 + .../content/panels/js/style-guide/overlay.js | 106 + .../pocket/content/panels/js/vendor.bundle.js | 451 ++++ .../content/panels/js/vendor.bundle.js.LICENSE.txt | 32 + .../components/pocket/content/panels/license.txt | 35 + .../components/pocket/content/panels/saved.html | 20 + .../components/pocket/content/panels/signup.html | 21 + .../pocket/content/panels/style-guide.html | 34 + browser/components/pocket/content/pktApi.sys.mjs | 926 ++++++++ .../components/pocket/content/pktTelemetry.sys.mjs | 117 + browser/components/pocket/content/pktUI.js | 665 ++++++ 76 files changed, 10358 insertions(+) create mode 100644 browser/components/pocket/content/Pocket.sys.mjs create mode 100644 browser/components/pocket/content/SaveToPocket.sys.mjs create mode 100644 browser/components/pocket/content/panels/css/global.scss create mode 100644 browser/components/pocket/content/panels/css/home.scss create mode 100644 browser/components/pocket/content/panels/css/main.compiled.css create mode 100644 browser/components/pocket/content/panels/css/main.scss create mode 100644 browser/components/pocket/content/panels/css/normalize.scss create mode 100644 browser/components/pocket/content/panels/css/panel.scss create mode 100644 browser/components/pocket/content/panels/css/saved.scss create mode 100644 browser/components/pocket/content/panels/css/signup.scss create mode 100644 browser/components/pocket/content/panels/css/styleguide.scss create mode 100644 browser/components/pocket/content/panels/fonts/FiraSans-Regular.woff create mode 100644 browser/components/pocket/content/panels/home.html create mode 100644 browser/components/pocket/content/panels/img/chevron-right.svg create mode 100644 browser/components/pocket/content/panels/img/list-view.svg create mode 100644 browser/components/pocket/content/panels/img/open.svg create mode 100644 browser/components/pocket/content/panels/img/pocketerror@1x.png create mode 100644 browser/components/pocket/content/panels/img/pocketerror@2x.png create mode 100644 browser/components/pocket/content/panels/img/pocketlogo-dark.svg create mode 100644 browser/components/pocket/content/panels/img/pocketlogo.svg create mode 100644 browser/components/pocket/content/panels/img/pocketlogo@1x.png create mode 100644 browser/components/pocket/content/panels/img/pocketlogo@2x.png create mode 100644 browser/components/pocket/content/panels/img/pocketlogosolo@1x.png create mode 100644 browser/components/pocket/content/panels/img/pocketlogosolo@2x.png create mode 100644 browser/components/pocket/content/panels/img/pocketsignup_button@1x.png create mode 100644 browser/components/pocket/content/panels/img/pocketsignup_button@2x.png create mode 100644 browser/components/pocket/content/panels/img/pocketsignup_devices@1x.png create mode 100644 browser/components/pocket/content/panels/img/pocketsignup_devices@2x.png create mode 100644 browser/components/pocket/content/panels/img/pocketsignup_hero@1x.png create mode 100644 browser/components/pocket/content/panels/img/pocketsignup_hero@2x.png create mode 100644 browser/components/pocket/content/panels/img/rainbow-reader.svg create mode 100644 browser/components/pocket/content/panels/img/signup_firefoxlogo@1x.png create mode 100644 browser/components/pocket/content/panels/img/signup_firefoxlogo@2x.png create mode 100644 browser/components/pocket/content/panels/img/signup_help@1x.png create mode 100644 browser/components/pocket/content/panels/img/signup_help@2x.png create mode 100644 browser/components/pocket/content/panels/img/tag_close@1x.png create mode 100644 browser/components/pocket/content/panels/img/tag_close@2x.png create mode 100644 browser/components/pocket/content/panels/img/tag_closeactive@1x.png create mode 100644 browser/components/pocket/content/panels/img/tag_closeactive@2x.png create mode 100644 browser/components/pocket/content/panels/js/components/ArticleList/ArticleList.jsx create mode 100644 browser/components/pocket/content/panels/js/components/ArticleList/ArticleList.scss create mode 100644 browser/components/pocket/content/panels/js/components/Button/Button.jsx create mode 100644 browser/components/pocket/content/panels/js/components/Button/Button.scss create mode 100644 browser/components/pocket/content/panels/js/components/Header/Header.jsx create mode 100644 browser/components/pocket/content/panels/js/components/Header/Header.scss create mode 100644 browser/components/pocket/content/panels/js/components/Home/Home.jsx create mode 100644 browser/components/pocket/content/panels/js/components/PopularTopics/PopularTopics.jsx create mode 100644 browser/components/pocket/content/panels/js/components/PopularTopics/PopularTopics.scss create mode 100644 browser/components/pocket/content/panels/js/components/Saved/Saved.jsx create mode 100644 browser/components/pocket/content/panels/js/components/Saved/Saved.scss create mode 100644 browser/components/pocket/content/panels/js/components/Signup/Signup.jsx create mode 100644 browser/components/pocket/content/panels/js/components/Signup/Signup.scss create mode 100644 browser/components/pocket/content/panels/js/components/TagPicker/TagPicker.jsx create mode 100644 browser/components/pocket/content/panels/js/components/TagPicker/TagPicker.scss create mode 100644 browser/components/pocket/content/panels/js/components/TelemetryLink/TelemetryLink.jsx create mode 100644 browser/components/pocket/content/panels/js/home/entry.js create mode 100644 browser/components/pocket/content/panels/js/home/overlay.js create mode 100644 browser/components/pocket/content/panels/js/main.bundle.js create mode 100644 browser/components/pocket/content/panels/js/main.bundle.js.LICENSE.txt create mode 100644 browser/components/pocket/content/panels/js/main.js create mode 100644 browser/components/pocket/content/panels/js/messages.js create mode 100644 browser/components/pocket/content/panels/js/saved/entry.js create mode 100644 browser/components/pocket/content/panels/js/saved/overlay.js create mode 100644 browser/components/pocket/content/panels/js/signup/entry.js create mode 100644 browser/components/pocket/content/panels/js/signup/overlay.js create mode 100644 browser/components/pocket/content/panels/js/style-guide/entry.js create mode 100644 browser/components/pocket/content/panels/js/style-guide/overlay.js create mode 100644 browser/components/pocket/content/panels/js/vendor.bundle.js create mode 100644 browser/components/pocket/content/panels/js/vendor.bundle.js.LICENSE.txt create mode 100644 browser/components/pocket/content/panels/license.txt create mode 100644 browser/components/pocket/content/panels/saved.html create mode 100644 browser/components/pocket/content/panels/signup.html create mode 100644 browser/components/pocket/content/panels/style-guide.html create mode 100644 browser/components/pocket/content/pktApi.sys.mjs create mode 100644 browser/components/pocket/content/pktTelemetry.sys.mjs create mode 100644 browser/components/pocket/content/pktUI.js (limited to 'browser/components/pocket/content') diff --git a/browser/components/pocket/content/Pocket.sys.mjs b/browser/components/pocket/content/Pocket.sys.mjs new file mode 100644 index 0000000000..25e8fccdf8 --- /dev/null +++ b/browser/components/pocket/content/Pocket.sys.mjs @@ -0,0 +1,56 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + CustomizableUI: "resource:///modules/CustomizableUI.sys.mjs", +}); + +export var Pocket = { + get site() { + return Services.prefs.getCharPref("extensions.pocket.site"); + }, + get listURL() { + return "https://" + Pocket.site + "/firefox_learnmore?src=ff_library"; + }, + + _initPanelView(window) { + let urlToSave = Pocket._urlToSave; + let titleToSave = Pocket._titleToSave; + Pocket._urlToSave = null; + Pocket._titleToSave = null; + // ViewShowing fires immediately before it creates the contents, + // in lieu of an AfterViewShowing event, just spin the event loop. + window.setTimeout(function () { + if (urlToSave) { + window.pktUI.tryToSaveUrl(urlToSave, titleToSave); + } else { + window.pktUI.tryToSaveCurrentPage(); + } + }, 0); + }, + + _urlToSave: null, + _titleToSave: null, + savePage(browser, url, title) { + // We want to target the top browser which has the Pocket panel UI, + // which might not be the browser saving the article. + const ownerGlobal = browser?.ownerGlobal?.top; + const ownerDocument = ownerGlobal?.document; + + if (!ownerDocument || !ownerGlobal?.PanelUI) { + return; + } + + let widget = lazy.CustomizableUI.getWidget("save-to-pocket-button"); + let anchorNode = widget.areaType + ? widget.forWindow(ownerGlobal).anchor + : ownerDocument.getElementById("PanelUI-menu-button"); + + this._urlToSave = url; + this._titleToSave = title; + ownerGlobal.PanelUI.showSubView("PanelUI-savetopocket", anchorNode); + }, +}; diff --git a/browser/components/pocket/content/SaveToPocket.sys.mjs b/browser/components/pocket/content/SaveToPocket.sys.mjs new file mode 100644 index 0000000000..60674acc82 --- /dev/null +++ b/browser/components/pocket/content/SaveToPocket.sys.mjs @@ -0,0 +1,245 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; + +const lazy = {}; +ChromeUtils.defineESModuleGetters(lazy, { + AboutReaderParent: "resource:///actors/AboutReaderParent.sys.mjs", + CustomizableUI: "resource:///modules/CustomizableUI.sys.mjs", + Pocket: "chrome://pocket/content/Pocket.sys.mjs", +}); + +var PocketCustomizableWidget = { + init() { + lazy.CustomizableUI.createWidget({ + id: "save-to-pocket-button", + l10nId: "save-to-pocket-button", + type: "view", + viewId: "PanelUI-savetopocket", + // This closes any open Pocket panels if you change location. + locationSpecific: true, + onViewShowing(aEvent) { + let panelView = aEvent.target; + let panelNode = panelView.querySelector( + ".PanelUI-savetopocket-container" + ); + let doc = panelNode.ownerDocument; + let frame = doc.createXULElement("browser"); + + frame.setAttribute("type", "content"); + frame.setAttribute("remote", "true"); + frame.setAttribute("remoteType", "privilegedabout"); + frame.setAttribute("maychangeremoteness", "true"); + frame.setAttribute("autocompletepopup", "PopupAutoComplete"); + panelNode.appendChild(frame); + + SaveToPocket.onShownInToolbarPanel(panelNode, frame); + }, + onViewHiding(aEvent) { + let panelView = aEvent.target; + let panelNode = panelView.querySelector( + ".PanelUI-savetopocket-container" + ); + + panelNode.textContent = ""; + SaveToPocket.updateToolbarNodeState(panelNode.ownerGlobal); + }, + }); + }, + shutdown() { + lazy.CustomizableUI.destroyWidget("save-to-pocket-button"); + }, +}; + +var PocketOverlay = { + startup() { + PocketCustomizableWidget.init(); + }, + shutdown() { + PocketCustomizableWidget.shutdown(); + }, +}; + +function browserWindows() { + return Services.wm.getEnumerator("navigator:browser"); +} + +export var SaveToPocket = { + init() { + // migrate enabled pref + if (Services.prefs.prefHasUserValue("browser.pocket.enabled")) { + Services.prefs.setBoolPref( + "extensions.pocket.enabled", + Services.prefs.getBoolPref("browser.pocket.enabled") + ); + Services.prefs.clearUserPref("browser.pocket.enabled"); + } + // Only define the pref getter now, so we don't get notified for the + // migrated pref above. + XPCOMUtils.defineLazyPreferenceGetter( + this, + "prefEnabled", + "extensions.pocket.enabled", + true, + this.onPrefChange.bind(this) + ); + if (this.prefEnabled) { + PocketOverlay.startup(); + } else { + // We avoid calling onPrefChange or similar here, because we don't want to + // shut down things that haven't started up, or broadcast unnecessary messages. + this.updateElements(false); + Services.obs.addObserver(this, "browser-delayed-startup-finished"); + } + lazy.AboutReaderParent.addMessageListener("Reader:OnSetup", this); + lazy.AboutReaderParent.addMessageListener( + "Reader:Clicked-pocket-button", + this + ); + }, + + observe(subject, topic, data) { + if (topic == "browser-delayed-startup-finished") { + // We only get here if pocket is disabled; the observer is removed when + // we're enabled. + this.updateElementsInWindow(subject, false); + } + }, + + _readerButtonData: { + id: "pocket-button", + l10nId: "about-reader-toolbar-savetopocket", + telemetryId: "save-to-pocket", + image: "chrome://global/skin/icons/pocket.svg", + }, + + onPrefChange(pref, oldValue, newValue) { + if (!newValue) { + lazy.AboutReaderParent.broadcastAsyncMessage("Reader:RemoveButton", { + id: "pocket-button", + }); + PocketOverlay.shutdown(); + Services.obs.addObserver(this, "browser-delayed-startup-finished"); + } else { + lazy.AboutReaderParent.broadcastAsyncMessage( + "Reader:AddButton", + this._readerButtonData + ); + Services.obs.removeObserver(this, "browser-delayed-startup-finished"); + PocketOverlay.startup(); + } + this.updateElements(newValue); + }, + + // Sets or removes the "pocketed" attribute on the Pocket urlbar button as + // necessary. + updateToolbarNodeState(browserWindow) { + const toolbarNode = browserWindow.document.getElementById( + "save-to-pocket-button" + ); + if (!toolbarNode || toolbarNode.hidden) { + return; + } + + let browser = browserWindow.gBrowser.selectedBrowser; + + let pocketedInnerWindowID = this.innerWindowIDsByBrowser.get(browser); + if (pocketedInnerWindowID == browser.innerWindowID) { + // The current window in this browser is pocketed. + toolbarNode.setAttribute("pocketed", "true"); + } else { + // The window isn't pocketed. + toolbarNode.removeAttribute("pocketed"); + } + }, + + // For pocketed inner windows, this maps their s to those inner + // window IDs. If a browser's inner window changes, then the mapped ID will + // be out of date, meaning that the new inner window has not been pocketed. + // If a browser goes away, then it'll be gone from this map too since it's + // weak. To tell whether a window has been pocketed then, look up its browser + // in this map and compare the mapped inner window ID to the ID of the current + // inner window. + get innerWindowIDsByBrowser() { + delete this.innerWindowIDsByBrowser; + return (this.innerWindowIDsByBrowser = new WeakMap()); + }, + + onLocationChange(browserWindow) { + this.updateToolbarNodeState(browserWindow); + }, + + /** + * Functions related to the Pocket panel UI. + */ + onShownInToolbarPanel(panel, frame) { + let window = panel.ownerGlobal; + window.pktUI.setToolbarPanelFrame(frame); + lazy.Pocket._initPanelView(window); + }, + + // If an item is saved to Pocket, we cache the browser's inner window ID, + // so if you navigate to that tab again, we can check the ID + // and see if we need to update the toolbar icon. + itemSaved() { + const browserWindow = Services.wm.getMostRecentWindow("navigator:browser"); + const browser = browserWindow.gBrowser.selectedBrowser; + SaveToPocket.innerWindowIDsByBrowser.set(browser, browser.innerWindowID); + }, + + // If an item is removed from Pocket, we remove that browser's inner window ID, + // so if you navigate to that tab again, we can check the ID + // and see if we need to update the toolbar icon. + itemDeleted() { + const browserWindow = Services.wm.getMostRecentWindow("navigator:browser"); + const browser = browserWindow.gBrowser.selectedBrowser; + SaveToPocket.innerWindowIDsByBrowser.delete(browser); + }, + + updateElements(enabled) { + // loop through windows and show/hide all our elements. + for (let win of browserWindows()) { + this.updateElementsInWindow(win, enabled); + } + }, + + updateElementsInWindow(win, enabled) { + if (enabled) { + win.document.documentElement.removeAttribute("pocketdisabled"); + } else { + win.document.documentElement.setAttribute("pocketdisabled", "true"); + } + }, + + receiveMessage(message) { + if (!this.prefEnabled) { + return; + } + switch (message.name) { + case "Reader:OnSetup": { + // Tell the reader about our button. + message.target.sendMessageToActor( + "Reader:AddButton", + this._readerButtonData, + "AboutReader" + ); + break; + } + case "Reader:Clicked-pocket-button": { + let pocketPanel = message.target.ownerDocument.querySelector( + "#customizationui-widget-panel" + ); + if (pocketPanel?.getAttribute("panelopen")) { + pocketPanel.hidePopup(); + } else { + // Saves the currently viewed page. + lazy.Pocket.savePage(message.target); + } + break; + } + } + }, +}; diff --git a/browser/components/pocket/content/panels/css/global.scss b/browser/components/pocket/content/panels/css/global.scss new file mode 100644 index 0000000000..b1f2cb0776 --- /dev/null +++ b/browser/components/pocket/content/panels/css/global.scss @@ -0,0 +1,67 @@ +// Mixins + +@mixin theme_dark { + @at-root body.theme_dark & { + @content; + } +} + +html { + font: menu; +} + +body { + &.theme_dark { + background: #42414c; + color: #FBFBFE; + } +} + +hr { + margin: 12px -8px; + background-color: #F0F0F4; + height: 1px; + border: none; + + @include theme_dark { + background-color: #52525E; + } +} + +.header_large { + margin: 12px 0 8px; + font-size: 1.25rem; + line-height: 1.65rem; + + .stp_button { + margin: 0; + } +} + +.header_medium { + margin: 12px 0 8px; + font-size: 1.1rem; + line-height: 1.35rem; +} + +.header_small { + margin: 12px 0 8px; + font-size: 0.95rem; + line-height: 1.16rem; +} + +.header_flex { + display: flex; + align-items: center; + justify-content: space-between; +} + +.header_center { + text-align: center; +} + +p { + margin: 8px 0; + font-size: 0.95rem; + line-height: 1.16rem; +} diff --git a/browser/components/pocket/content/panels/css/home.scss b/browser/components/pocket/content/panels/css/home.scss new file mode 100644 index 0000000000..9780963fee --- /dev/null +++ b/browser/components/pocket/content/panels/css/home.scss @@ -0,0 +1,115 @@ +.pkt_ext_containerhome, +.pkt_ext_wrapperhome { + overflow: hidden; +} + +.pkt_ext_home { + line-height: 20px; + color: #363636; + + a { + color: #008078; + text-decoration: none; + } + + a, p { + font-size: 0.9em; + } + + .pkt_ext_hr { + height: 1px; + background: linear-gradient(90deg, #83EDB8 0%, #83EDB8 0%, #83EDB8 0.01%, #1CB0A8 33.15%, #EF4056 67.4%, #FCB643 100%); + } + + .pkt_ext_detail { + margin: 18px 20px; + } + + .pkt_ext_header { + display: flex; + justify-content: space-between; + align-items: center; + .pkt_ext_mylist_icon { + background: url(../img/list-view.svg) no-repeat; + background-size: contain; + height: 1.2em; + width: 1.2em; + margin-inline-end: 8px; + } + a { + height: 36px; + display: flex; + align-items: center; + box-sizing: border-box; + padding: 0 8px; + margin: 12px; + border-radius: 4px; + &:hover { + background: #F5F5F5; + } + } + .pkt_ext_logo { + background: url(../img/pocketlogo.svg) bottom right no-repeat; + background-size: contain; + height: 32px; + width: 123px; + margin: 0 20px; + } + } + .pkt_ext_detail { + a { + display: block; + } + h2 { + font-weight: 600; + font-size: 1em; + } + h2, + p { + margin: 8px 0; + } + h3 { + font-weight: 600; + font-size: 1em; + margin: 12px 0; + } + .pkt_ext_more { + margin: 19px 0; + .pkt_ext_chevron_right { + background: url(../img/chevron-right.svg) no-repeat; + background-size: contain; + height: 1.2em; + width: 1.2em; + } + ul { + list-style-type: none; + padding: 0; + line-height: 14px; + li { + a { + height: 44px; + width: 100%; + display: flex; + align-items: center; + justify-content: space-between; + box-sizing: border-box; + border-bottom: 1px solid #EAEAEA; + } + } + } + } + .pkt_ext_discover { + line-height: 12px; + margin: 20px 0; + height: 40px; + width: 100%; + display: flex; + justify-content: center; + align-items: center; + border-radius: 4px; + &:hover { + background: #F5F5F5; + } + } + } +} diff --git a/browser/components/pocket/content/panels/css/main.compiled.css b/browser/components/pocket/content/panels/css/main.compiled.css new file mode 100644 index 0000000000..7a6c89440b --- /dev/null +++ b/browser/components/pocket/content/panels/css/main.compiled.css @@ -0,0 +1,2418 @@ +/*! normalize.css v3.0.2 | MIT License | git.io/normalize */ +/** + * 1. Set default font family to sans-serif. + * 2. Prevent iOS text size adjust after orientation change, without disabling + * user zoom. + */ +html { + font-family: sans-serif; /* 1 */ +} + +/** + * Remove default margin. + */ +body { + margin: 0; +} + +/* HTML5 display definitions + ========================================================================== */ +/** + * Correct `block` display not defined for any HTML5 element in IE 8/9. + * Correct `block` display not defined for `details` or `summary` in IE 10/11 + * and Firefox. + * Correct `block` display not defined for `main` in IE 11. + */ +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +main, +menu, +nav, +section, +summary { + display: block; +} + +/** + * 1. Correct `inline-block` display not defined in IE 8/9. + * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera. + */ +audio, +canvas, +progress, +video { + display: inline-block; /* 1 */ + vertical-align: baseline; /* 2 */ +} + +/** + * Prevent modern browsers from displaying `audio` without controls. + * Remove excess height in iOS 5 devices. + */ +audio:not([controls]) { + display: none; + height: 0; +} + +/** + * Address `[hidden]` styling not present in IE 8/9/10. + * Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22. + */ +[hidden], +template { + display: none; +} + +/* Links + ========================================================================== */ +/** + * Remove the gray background color from active links in IE 10. + */ +a { + background-color: transparent; +} + +/** + * Improve readability when focused and also mouse hovered in all browsers. + */ +a:active, +a:hover { + outline: 0; +} + +/* Text-level semantics + ========================================================================== */ +/** + * Address styling not present in IE 8/9/10/11, Safari, and Chrome. + */ +abbr[title] { + border-bottom: 1px dotted; +} + +/** + * Address style set to `bolder` in Firefox 4+, Safari, and Chrome. + */ +b, +strong { + font-weight: bold; +} + +/** + * Address styling not present in Safari and Chrome. + */ +dfn { + font-style: italic; +} + +/** + * Address variable `h1` font-size and margin within `section` and `article` + * contexts in Firefox 4+, Safari, and Chrome. + */ +h1 { + font-size: 2em; + margin: 0.67em 0; +} + +/** + * Address styling not present in IE 8/9. + */ +mark { + background: #ff0; + color: #000; +} + +/** + * Address inconsistent and variable font size in all browsers. + */ +small { + font-size: 80%; +} + +/** + * Prevent `sub` and `sup` affecting `line-height` in all browsers. + */ +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sup { + top: -0.5em; +} + +sub { + bottom: -0.25em; +} + +/* Embedded content + ========================================================================== */ +/** + * Remove border when inside `a` element in IE 8/9/10. + */ +img { + border: 0; +} + +/** + * Correct overflow not hidden in IE 9/10/11. + */ +svg:not(:root) { + overflow: hidden; +} + +/* Grouping content + ========================================================================== */ +/** + * Address margin not present in IE 8/9 and Safari. + */ +figure { + margin: 1em 40px; +} + +/** + * Address differences between Firefox and other browsers. + */ +hr { + -moz-box-sizing: content-box; + box-sizing: content-box; + height: 0; +} + +/** + * Contain overflow in all browsers. + */ +pre { + overflow: auto; +} + +/** + * Address odd `em`-unit font size rendering in all browsers. + */ +code, +kbd, +pre, +samp { + font-family: monospace, monospace; + font-size: 1em; +} + +/* Forms + ========================================================================== */ +/** + * Known limitation: by default, Chrome and Safari on OS X allow very limited + * styling of `select`, unless a `border` property is set. + */ +/** + * 1. Correct color not being inherited. + * Known issue: affects color of disabled elements. + * 2. Correct font properties not being inherited. + * 3. Address margins set differently in Firefox 4+, Safari, and Chrome. + */ +button, +input, +optgroup, +select, +textarea { + color: inherit; /* 1 */ + font: inherit; /* 2 */ + margin: 0; /* 3 */ +} + +/** + * Address `overflow` set to `hidden` in IE 8/9/10/11. + */ +button { + overflow: visible; +} + +/** + * Address inconsistent `text-transform` inheritance for `button` and `select`. + * All other form control elements do not inherit `text-transform` values. + * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera. + * Correct `select` style inheritance in Firefox. + */ +button, +select { + text-transform: none; +} + +/** + * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` + * and `video` controls. + * 2. Correct inability to style clickable `input` types in iOS. + * 3. Improve usability and consistency of cursor style between image-type + * `input` and others. + */ +button, +html input[type=button], +input[type=reset], +input[type=submit] { + cursor: pointer; /* 3 */ +} + +/** + * Re-set default cursor for disabled elements. + */ +button[disabled], +html input[disabled] { + cursor: default; +} + +/** + * Remove inner padding and border in Firefox 4+. + */ +button::-moz-focus-inner, +input::-moz-focus-inner { + border: 0; + padding: 0; +} + +/** + * Address Firefox 4+ setting `line-height` on `input` using `!important` in + * the UA stylesheet. + */ +input { + line-height: normal; +} + +/** + * It's recommended that you don't attempt to style these elements. + * Firefox's implementation doesn't respect box-sizing, padding, or width. + * + * 1. Address box sizing set to `content-box` in IE 8/9/10. + * 2. Remove excess padding in IE 8/9/10. + */ +input[type=checkbox], +input[type=radio] { + box-sizing: border-box; /* 1 */ + padding: 0; /* 2 */ +} + +/** + * 1. Address `appearance` set to `searchfield` in Safari and Chrome. + * 2. Address `box-sizing` set to `border-box` in Safari and Chrome + * (include `-moz` to future-proof). + */ +input[type=search] { + box-sizing: content-box; +} + +/** + * Define consistent border, margin, and padding. + */ +fieldset { + border: 1px solid #c0c0c0; + margin: 0 2px; + padding: 0.35em 0.625em 0.75em; +} + +/** + * 1. Correct `color` not being inherited in IE 8/9/10/11. + * 2. Remove padding so people aren't caught out if they zero out fieldsets. + */ +legend { + border: 0; /* 1 */ + padding: 0; /* 2 */ +} + +/** + * Remove default vertical scrollbar in IE 8/9/10/11. + */ +textarea { + overflow: auto; +} + +/** + * Don't inherit the `font-weight` (applied by a rule above). + * NOTE: the default cannot safely be changed in Chrome and Safari on OS X. + */ +optgroup { + font-weight: bold; +} + +/* Tables + ========================================================================== */ +/** + * Remove most spacing between table cells. + */ +table { + border-collapse: collapse; + border-spacing: 0; +} + +td, +th { + padding: 0; +} + +/* Normalization for FF panel defauts + ========================================================================== */ +html { + outline: none; + padding: 0; +} + +a { + color: #0095dd; + margin: 0; + outline: none; + padding: 0; + text-decoration: none; +} + +a:hover, +a:active, +a:focus { + color: #008acb; + text-decoration: underline; +} + +a:active { + color: #006b9d; +} + +html { + font: menu; +} + +body.theme_dark { + background: #42414c; + color: #FBFBFE; +} + +hr { + margin: 12px -8px; + background-color: #F0F0F4; + height: 1px; + border: none; +} +body.theme_dark hr { + background-color: #52525E; +} + +.header_large { + margin: 12px 0 8px; + font-size: 1.25rem; + line-height: 1.65rem; +} +.header_large .stp_button { + margin: 0; +} + +.header_medium { + margin: 12px 0 8px; + font-size: 1.1rem; + line-height: 1.35rem; +} + +.header_small { + margin: 12px 0 8px; + font-size: 0.95rem; + line-height: 1.16rem; +} + +.header_flex { + display: flex; + align-items: center; + justify-content: space-between; +} + +.header_center { + text-align: center; +} + +p { + margin: 8px 0; + font-size: 0.95rem; + line-height: 1.16rem; +} + +.stp_panel_container { + overflow: hidden; +} + +.stp_panel { + padding: 0 16px; + margin: 16px 0 12px; +} + +/* saved.css + * + * Description: + * With base elements out of the way, this sets all custom styling for the page saved dialog. + * + * Contents: + * Global + * Loading spinner + * Core detail + * Tag entry + * Recent/suggested tags + * Premium upsell + * Token input/autocomplete + * Overflow mode + * Language overrides + */ +/*=Global +--------------------------------------------------------------------------------------- */ +.pkt_ext_containersaved { + background-color: #fbfbfb; + border-radius: 4px; + display: block; + padding: 0; + position: relative; + text-align: center; + overflow: hidden; +} + +.pkt_ext_cf:after { + content: " "; + display: table; + clear: both; +} + +.pkt_ext_containersaved .pkt_ext_tag_detail, +.pkt_ext_containersaved .pkt_ext_recenttag_detail, +.pkt_ext_containersaved .pkt_ext_suggestedtag_detail { + margin: 0 auto; + padding: 0.25em 1em; + position: relative; + width: auto; +} + +/*=Loading spinner +--------------------------------------------------------------------------------------- */ +@keyframes pkt_ext_spin { + to { + transform: rotate(1turn); + } +} +.pkt_ext_containersaved .pkt_ext_loadingspinner { + position: relative; + display: inline-block; + height: 2.5em; + inset-inline-start: 50%; + margin-block: 2em 0; + margin-inline: -1.25em 0; + font-size: 10px; + text-indent: 999em; + position: absolute; + top: 4em; + overflow: hidden; + width: 2.5em; + animation: pkt_ext_spin 0.7s infinite steps(8); +} + +.pkt_ext_containersaved .pkt_ext_loadingspinner:before, +.pkt_ext_containersaved .pkt_ext_loadingspinner:after, +.pkt_ext_containersaved .pkt_ext_loadingspinner > div:before, +.pkt_ext_containersaved .pkt_ext_loadingspinner > div:after { + content: ""; + position: absolute; + top: 0; + inset-inline-start: 1.125em; + width: 0.25em; + height: 0.75em; + border-radius: 0.2em; + background: #eee; + box-shadow: 0 1.75em #eee; + transform-origin: 50% 1.25em; +} + +.pkt_ext_containersaved .pkt_ext_loadingspinner:before { + background: #555; +} + +.pkt_ext_containersaved .pkt_ext_loadingspinner:after { + transform: rotate(-45deg); + background: #777; +} + +.pkt_ext_containersaved .pkt_ext_loadingspinner > div:before { + transform: rotate(-90deg); + background: #999; +} + +.pkt_ext_containersaved .pkt_ext_loadingspinner > div:after { + transform: rotate(-135deg); + background: #bbb; +} + +/*=Core detail +--------------------------------------------------------------------------------------- */ +.pkt_ext_containersaved .pkt_ext_initload { + inset-inline-start: 0; + position: absolute; + top: 0; + width: 100%; +} + +.pkt_ext_containersaved .pkt_ext_detail { + opacity: 0; + position: relative; + padding-bottom: 1.25em; +} + +.pkt_ext_container_detailactive .pkt_ext_initload { + opacity: 0; +} + +.pkt_ext_container_detailactive .pkt_ext_initload .pkt_ext_loadingspinner, +.pkt_ext_container_finalstate .pkt_ext_initload .pkt_ext_loadingspinner { + animation: none; +} + +.pkt_ext_container_detailactive .pkt_ext_detail { + max-height: 20em; + opacity: 1; +} + +.pkt_ext_container_finalstate .pkt_ext_edit_msg, +.pkt_ext_container_finalstate .pkt_ext_tag_detail, +.pkt_ext_container_finalstate .pkt_ext_suggestedtag_detail, +.pkt_ext_container_finalstate .pkt_ext_item_actions { + opacity: 0; + transition: opacity 0.2s ease-out; +} + +.pkt_ext_container_finalerrorstate .pkt_ext_edit_msg, +.pkt_ext_container_finalerrorstate .pkt_ext_tag_detail, +.pkt_ext_container_finalerrorstate .pkt_ext_suggestedtag_detail, +.pkt_ext_container_finalerrorstate .pkt_ext_item_actions { + display: none; + transition: none; +} + +.pkt_ext_containersaved h2 { + background: transparent; + border: none; + color: #333; + display: block; + float: none; + font-size: 1.2em; + font-weight: normal; + letter-spacing: normal; + line-height: 1; + margin: 19px 0 4px; + padding: 0; + position: relative; + text-align: start; + text-transform: none; +} + +@keyframes fade_in_out { + 0% { + opacity: 1; + } + 50% { + opacity: 0; + } + 100% { + opacity: 1; + } +} +.pkt_ext_container_finalstate h2 { + animation: fade_in_out 0.4s ease-out; +} + +.pkt_ext_container_finalerrorstate h2 { + animation: none; + color: #d74345; +} + +.pkt_ext_containersaved .pkt_ext_errordetail { + display: none; + font-size: 0.9em; + font-weight: normal; + inset-inline-start: 6.4em; + max-width: 21em; + opacity: 0; + position: absolute; + top: 2.7em; + text-align: start; + visibility: hidden; +} + +.pkt_ext_container_finalerrorstate { + max-height: 133px; +} + +.pkt_ext_container_finalerrorstate .pkt_ext_errordetail { + display: block; + opacity: 1; + visibility: visible; +} + +.pkt_ext_containersaved .pkt_ext_logo { + background: url(../img/pocketlogosolo@1x.png) center center no-repeat; + display: block; + float: inline-start; + height: 40px; + padding: 1.25em 1em; + position: relative; + width: 44px; +} + +@media (min-resolution: 1.1dppx) { + .pkt_ext_containersaved .pkt_ext_logo { + background-image: url(../img/pocketlogosolo@2x.png); + background-size: 44px 40px; + } +} +.pkt_ext_container_finalerrorstate .pkt_ext_logo { + background-image: url(../img/pocketerror@1x.png); + height: 44px; + width: 44px; +} + +@media (min-resolution: 1.1dppx) { + .pkt_ext_container_finalerrorstate .pkt_ext_logo { + background-image: url(../img/pocketerror@2x.png); + background-size: 44px 44px; + } +} +.pkt_ext_containersaved .pkt_ext_topdetail { + float: inline-start; +} + +.pkt_ext_containersaved .pkt_ext_edit_msg_container { + position: relative; +} +.pkt_ext_containersaved .pkt_ext_edit_msg_container .pkt_ext_edit_msg { + box-sizing: border-box; + display: none; + font-size: 0.75em; + inset-inline-start: auto; + padding: 0 1.4em; + position: absolute; + text-align: start; + top: 0; + width: 100%; + margin: 0; +} +.pkt_ext_containersaved .pkt_ext_edit_msg_container .pkt_ext_edit_msg.pkt_ext_edit_msg_error { + color: #d74345; +} +.pkt_ext_containersaved .pkt_ext_edit_msg_container .pkt_ext_edit_msg.pkt_ext_edit_msg_active { + display: block; +} + +.pkt_ext_containersaved .pkt_ext_item_actions { + background: transparent; + float: none; + height: auto; + margin-bottom: 1em; + margin-top: 0; + width: auto; +} + +.pkt_ext_containersaved .pkt_ext_item_actions_disabled { + opacity: 0.5; +} + +.pkt_ext_container_finalstate .pkt_ext_item_actions_disabled { + opacity: 0; +} + +.pkt_ext_containersaved .pkt_ext_item_actions ul { + background: none; + display: block; + float: none; + height: auto; + margin: 0; + padding: 0; + width: 100%; +} + +.pkt_ext_containersaved .pkt_ext_item_actions li { + box-sizing: border-box; + background: none; + border: 0; + float: inline-start; + list-style: none; + line-height: 0.8; + height: auto; + padding-inline-end: 0.4em; + width: auto; +} + +.pkt_ext_containersaved .pkt_ext_item_actions li:before { + content: none; +} + +.pkt_ext_containersaved .pkt_ext_item_actions .pkt_ext_actions_separator { + border-inline-start: 2px solid #777; + height: 1em; + margin-top: 0.3em; + padding: 0; + width: 10px; +} + +.pkt_ext_containersaved .pkt_ext_item_actions a { + background: transparent; + color: #0095dd; + display: block; + font-feature-settings: normal; + font-size: 0.9em; + font-weight: normal; + letter-spacing: normal; + line-height: inherit; + height: auto; + margin: 0; + padding: 0.5em; + float: inline-start; + text-align: start; + text-decoration: none; + text-transform: none; +} + +.pkt_ext_containersaved .pkt_ext_item_actions a:hover, +.pkt_ext_containersaved .pkt_ext_item_actions a:focus { + color: #008acb; + text-decoration: underline; +} + +.pkt_ext_containersaved .pkt_ext_item_actions a:before, +.pkt_ext_containersaved .pkt_ext_item_actions a:after { + background: transparent; + display: none; +} + +.pkt_ext_containersaved .pkt_ext_item_actions_disabled a { + cursor: default; +} + +.pkt_ext_containersaved .pkt_ext_item_actions .pkt_ext_openpocket { + float: inline-end; + padding-inline-end: 0.7em; + text-align: end; +} + +.pkt_ext_containersaved .pkt_ext_item_actions .pkt_ext_removeitem { + padding-inline-start: 0; +} + +.pkt_ext_containersaved .pkt_ext_close { + background: url(../img/tag_close@1x.png) center center no-repeat; + color: #333; + display: block; + font-size: 0.8em; + height: 10px; + inset-inline-end: 0.5em; + overflow: hidden; + position: absolute; + text-align: center; + text-indent: -9999px; + top: -1em; + width: 10px; +} + +@media (min-resolution: 1.1dppx) { + .pkt_ext_containersaved .pkt_ext_close { + background-image: url(../img/tag_close@2x.png); + background-size: 8px 8px; + } +} +.pkt_ext_containersaved .pkt_ext_close:hover { + color: #000; + text-decoration: none; +} + +/*=Tag entry +--------------------------------------------------------------------------------------- */ +.pkt_ext_containersaved .pkt_ext_tag_detail { + border: 1px solid #c1c1c1; + border-radius: 2px; + clear: both; + margin: 0 1em; + padding: 0; + display: flex; +} + +.pkt_ext_containersaved .pkt_ext_tag_error { + border: none; +} + +.pkt_ext_containersaved .pkt_ext_tag_input_wrapper { + box-sizing: border-box; + flex: 1; + background-color: #fff; + border-inline-end: 1px solid #c3c3c3; + color: #333; + display: block; + float: none; + font-size: 0.875em; + list-style: none; + margin: 0; + overflow: hidden; + padding: 0.25em 0.5em; + width: 14em; + padding-inline: 0.5em; +} + +.pkt_ext_containersaved .pkt_ext_tag_error .pkt_ext_tag_input_wrapper { + border: 1px solid #d74345; +} + +.pkt_ext_containersaved .pkt_ext_tag_input_wrapper .token-input-list { + display: block; + height: 1.7em; + overflow: hidden; + position: relative; + width: 60em; +} + +.pkt_ext_containersaved .pkt_ext_tag_input_wrapper .token-input-list, +.pkt_ext_containersaved .pkt_ext_tag_input_wrapper li { + font-size: 1em; +} + +.pkt_ext_containersaved .pkt_ext_tag_input_wrapper li { + height: auto; + width: auto; +} + +.pkt_ext_containersaved .pkt_ext_tag_input_wrapper li:before { + content: none; +} + +.pkt_ext_containersaved .pkt_ext_tag_input_wrapper input { + border: 0; + box-shadow: none; + background-color: #fff; + color: #333; + font-size: 1em; + float: inline-start; + line-height: normal; + height: auto; + min-height: 0; + min-width: 5em; + padding: 3px 2px 1px; + text-transform: none; +} + +.pkt_ext_containersaved .pkt_ext_tag_input_wrapper input::placeholder { + color: #a9a9a9; + letter-spacing: normal; + text-transform: none; +} + +.pkt_ext_containersaved .input_disabled { + cursor: default; + opacity: 0.5; +} + +.pkt_ext_containersaved .pkt_ext_btn { + box-sizing: border-box; + color: #333; + float: none; + font-size: 1em; + letter-spacing: normal; + height: 2.2em; + min-width: 4em; + padding: 0.5em 0; + text-decoration: none; + text-transform: none; + width: auto; +} + +.pkt_ext_containersaved .pkt_ext_btn:hover { + background-color: #ebebeb; +} + +.pkt_ext_containersaved .pkt_ext_btn:active { + background-color: #dadada; +} + +.pkt_ext_containersaved .pkt_ext_btn_disabled, +.pkt_ext_containersaved .pkt_ext_btn_disabled:hover, +.pkt_ext_containersaved .pkt_ext_btn_disabled:active { + background-color: transparent; + cursor: default; + opacity: 0.4; +} + +.pkt_ext_containersaved .pkt_ext_tag_error .pkt_ext_btn { + border: 1px solid #c3c3c3; + border-block-width: 1px; + border-inline-width: 0 1px; + height: 2.35em; +} + +.pkt_ext_containersaved .autocomplete-suggestions { + margin-top: 2.2em; +} + +/*=Recent/suggested tags +--------------------------------------------------------------------------------------- */ +.pkt_ext_containersaved .pkt_ext_suggestedtag_detail { + box-sizing: border-box; + clear: both; + inset-inline-start: 0; + opacity: 0; + min-height: 110px; + visibility: hidden; + width: 100%; +} + +.pkt_ext_container_detailactive .pkt_ext_suggestedtag_detail { + opacity: 1; + visibility: visible; +} + +.pkt_ext_container_finalstate .pkt_ext_suggestedtag_detail { + opacity: 0; + visibility: hidden; +} + +.pkt_ext_containersaved .pkt_ext_recenttag_detail h4, +.pkt_ext_containersaved .pkt_ext_suggestedtag_detail h4 { + color: #333; + font-size: 1em; + font-weight: normal; + font-style: normal; + letter-spacing: normal; + margin: 0.5em 0; + text-align: start; + text-transform: none; +} + +.pkt_ext_containersaved .pkt_ext_recenttag_detail .pkt_ext_loadingspinner, +.pkt_ext_containersaved .pkt_ext_suggestedtag_detail .pkt_ext_loadingspinner { + display: none; + position: absolute; +} + +.pkt_ext_containersaved .pkt_ext_recenttag_detail_loading .pkt_ext_loadingspinner, +.pkt_ext_containersaved .pkt_ext_suggestedtag_detail_loading .pkt_ext_loadingspinner { + display: block; + font-size: 6px; + inset-inline-start: 48%; +} + +.pkt_ext_containersaved .pkt_ext_recenttag_detail ul, +.pkt_ext_containersaved .pkt_ext_suggestedtag_detail ul { + display: block; + margin: 0; + height: 2em; + overflow: hidden; + padding: 2px 0 0; +} + +.pkt_ext_containersaved .pkt_ext_suggestedtag_detail ul { + height: auto; + margin: 0; + max-height: 4em; + padding-top: 6px; +} + +.pkt_ext_containersaved .pkt_ext_recenttag_detail li, +.pkt_ext_containersaved .pkt_ext_suggestedtag_detail li { + background: none; + float: inline-start; + height: inherit; + line-height: 1.5; + list-style: none; + margin-bottom: 0.5em; + width: inherit; +} + +.pkt_ext_containersaved .pkt_ext_recenttag_detail li:before, +.pkt_ext_containersaved .pkt_ext_suggestedtag_detail li:before { + content: none; +} + +.pkt_ext_containersaved .pkt_ext_recenttag_detail .recenttag_msg, +.pkt_ext_containersaved .pkt_ext_suggestedtag_detail .suggestedtag_msg { + color: #333; + font-size: 0.8125em; + line-height: 1.2; + inset-inline-start: auto; + position: absolute; + text-align: start; + top: 2em; +} + +.pkt_ext_containersaved .pkt_ext_suggestedtag_detail .suggestedtag_msg { + margin-inline-end: 1.3em; +} + +.pkt_ext_containersaved .token_tag { + border-radius: 4px; + background: #f7f7f7; + border: 1px solid #c3c3c3; + color: #333; + font-size: 1em; + font-weight: normal; + letter-spacing: normal; + margin-inline-end: 0.5em; + padding: 0.125em 0.625em; + text-decoration: none; + text-transform: none; +} + +.pkt_ext_containersaved .token_tag:hover { + background-color: #008acb; + border-color: #008acb; + color: #fff; + text-decoration: none; +} + +.pkt_ext_containersaved .token_tag:before, +.pkt_ext_containersaved .token_tag:after { + content: none; +} + +.pkt_ext_containersaved .token_tag:hover span { + background-image: url(../img/tag_closeactive@1x.png); +} + +@media (min-resolution: 1.1dppx) { + .pkt_ext_containersaved .token_tag:hover span { + background-image: url(../img/tag_closeactive@2x.png); + background-size: 8px 8px; + } +} +.pkt_ext_containersaved .pkt_ext_recenttag_detail_disabled .token_tag, +.pkt_ext_containersaved .pkt_ext_recenttag_detail_disabled .token_tag:hover, +.pkt_ext_containersaved .pkt_ext_suggestedtag_detail_disabled .token_tag, +.pkt_ext_containersaved .pkt_ext_suggestedtag_detail_disabled .token_tag:hover { + background-color: #f7f7f7; + cursor: default; + opacity: 0.5; +} + +.pkt_ext_containersaved .token_tag_inactive { + display: none; +} + +/*=Premium upsell +--------------------------------------------------------------------------------------- */ +.pkt_ext_detail .pkt_ext_premupsell { + background-color: #50bbb6; + display: block; + padding: 1.5em 0; + text-align: center; +} + +.pkt_ext_premupsell h4 { + color: #fff; + font-size: 1em; + margin-bottom: 1em; +} + +.pkt_ext_premupsell a { + color: #28605d; + border-bottom: 1px solid #47a7a3; + font-weight: normal; +} + +.pkt_ext_premupsell a:hover { + color: #14302f; +} + +/*=Token input/autocomplete +--------------------------------------------------------------------------------------- */ +.token-input-dropdown-tag { + border-radius: 4px; + box-sizing: border-box; + background: #fff; + border: 1px solid #cdcdcd; + margin-top: 0.5em; + inset-inline-start: 0 !important; + overflow-y: auto; + top: 1.9em !important; + z-index: 9000; +} + +.token-input-dropdown-tag ul { + height: inherit; + max-height: 115px; + margin: 0; + overflow: auto; + padding: 0.5em 0; +} + +.token-input-dropdown-tag ul li { + background: none; + color: #333; + font-weight: normal; + font-size: 1em; + float: none; + height: inherit; + letter-spacing: normal; + list-style: none; + padding: 0.75em; + text-align: start; + text-transform: none; + width: inherit; +} + +.token-input-dropdown-tag ul li:before { + content: none; +} + +.token-input-dropdown ul li.token-input-selected-dropdown-item { + background-color: #008acb; + color: #fff; +} + +.token-input-list { + list-style: none; + margin: 0; + padding: 0; +} + +.token-input-list li { + text-align: start; + list-style: none; +} + +.token-input-list li input { + border: 0; + background-color: white; +} + +.pkt_ext_containersaved .token-input-token { + background: none; + border-radius: 4px; + border: 1px solid #c3c3c3; + overflow: hidden; + margin: 0; + padding: 0 8px; + background-color: #f7f7f7; + color: #000; + font-weight: normal; + cursor: default; + line-height: 1.5; + display: block; + width: auto; + margin: 0 0.2em; + float: inline-start; +} + +.pkt_ext_containersaved .pkt_ext_tag_input_wrapper_disabled { + position: relative; +} + +.pkt_ext_containersaved .pkt_ext_tag_input_wrapper_disabled input { + opacity: 0.5; +} + +.pkt_ext_containersaved .pkt_ext_tag_input_wrapper_disabled .token-input-list { + opacity: 0.5; +} + +.pkt_ext_containersaved .pkt_ext_tag_input_wrapper_disabled .pkt_ext_tag_input_blocker { + height: 100%; + inset-inline-start: 0; + position: absolute; + top: 0; + width: 100%; + z-index: 5; +} + +.pkt_ext_containersaved .token-input-token p { + display: inline-block; + font-size: 1em; + font-weight: normal; + line-height: inherit; + letter-spacing: normal; + padding: 0; + margin: 0; + text-transform: none; + vertical-align: top; + width: auto; + unicode-bidi: plaintext; +} + +.pkt_ext_containersaved .token-input-token p:before { + content: none; + width: 0; +} + +.pkt_ext_containersaved .token-input-token span { + background: url(../img/tag_close@1x.png) center center no-repeat; + cursor: pointer; + display: inline-block; + height: 8px; + margin-block: 0; + margin-inline: 8px 0; + overflow: hidden; + width: 8px; + text-indent: -99px; +} + +@media (min-resolution: 1.1dppx) { + .pkt_ext_containersaved .token-input-token span { + background-image: url(../img/tag_close@2x.png); + background-size: 8px 8px; + } +} +.pkt_ext_containersaved .token-input-selected-token { + background-color: #008acb; + border-color: #008acb; + color: #fff; +} + +.pkt_ext_containersaved .token-input-selected-token span { + background-image: url(../img/tag_closeactive@1x.png); +} + +@media (min-resolution: 1.1dppx) { + .pkt_ext_containersaved .token-input-selected-token span { + background-image: url(../img/tag_closeactive@2x.png); + background-size: 8px 8px; + } +} +.pkt_ext_containersaved .pkt_ext_tag_input_wrapper_disabled .token-input-selected-token { + background-color: #f7f7f7; +} + +.pkt_ext_containersaved .pkt_ext_tag_input_wrapper_disabled .token-input-selected-token span { + color: #bbb; +} + +/*=Language overrides +--------------------------------------------------------------------------------------- */ +.pkt_ext_saved_es .pkt_ext_btn { + min-width: 5em; +} + +.pkt_ext_saved_de .pkt_ext_btn, +.pkt_ext_saved_ru .pkt_ext_btn { + min-width: 6em; +} + +/*=Coral Button +--------------------------------------------------------------------------------------- */ +button { + padding: 0; + margin: 0; + background: none; + border: 0; + outline: none; + color: inherit; + font: inherit; + overflow: visible; +} + +.pkt_ext_button { + padding: 3px; + background-color: #EF4056; + color: #FFF; + text-align: center; + cursor: pointer; + height: 32px; + box-sizing: border-box; + width: 320px; + margin: 0 auto; + border-radius: 2px; + font-size: 1em; +} + +.pkt_ext_button:hover, +.pkt_ext_button:active { + background-color: #d5374b; +} + +/* alt button */ +.pkt_ext_blue_button { + background-color: #0060df; + color: #FFF; +} + +.pkt_ext_blue_button:hover { + background-color: #003eaa; +} + +.pkt_ext_blue_button:active { + background-color: #002275; +} + +.pkt_ext_ffx_icon:after { + position: absolute; + height: 22px; + width: 22px; + top: -3px; + inset-inline-start: -28px; + content: ""; + background-image: url(../img/signup_firefoxlogo@2x.png); + background-size: 22px 22px; + background-repeat: no-repeat; +} + +.pkt_ext_subshell { + display: none; + border-top: 1px solid #c1c1c1; + background: #ebebeb; + width: 100%; +} + +.pkt_ext_subshell hr { + display: none; +} + +.recs_enabled .pkt_ext_subshell hr { + display: block; + border: 0; + border-top: 1px solid #D7D7DB; + margin: 0; +} + +.pkt_ext_item_recs { + text-align: start; + margin: 0 auto; + padding: 0.25em 1em; +} + +.pkt_ext_item_recs header { + display: flex; + justify-content: space-between; + align-items: center; + margin-top: 12px; +} + +.pkt_ext_item_recs header h4 { + color: #333; + font-size: 1em; + font-weight: normal; + font-style: normal; + letter-spacing: normal; + margin: 0.5em 0; + text-align: start; + text-transform: none; +} + +.pkt_ext_item_recs header a { + font-style: normal; + font-weight: 500; + font-size: 1em; + line-height: 20px; + color: #0095DD; +} + +.pkt_ext_containersaved .pkt_ext_item_recs ol { + padding: 0; + margin: 0 0 10px; + list-style: none; +} + +.pkt_ext_containersaved .pkt_ext_item_recs li { + float: none; + display: flex; + font-style: normal; + font-weight: normal; + line-height: 18px; + margin: 0 -1em; + min-height: 60px; +} + +.pkt_ext_containersaved .pkt_ext_item_recs li a { + padding-block: 8px; + padding-inline: 1em 40px; + background: url(../img/open.svg) top 8px right 14px no-repeat; + flex-grow: 1; +} + +.pkt_ext_containersaved .pkt_ext_item_recs li a:dir(rtl) { + background-position-x: left 14px; +} + +.pkt_ext_containersaved .pkt_ext_item_recs li:hover, +.pkt_ext_containersaved .pkt_ext_item_recs li a:focus { + background-color: rgba(12, 12, 13, 0.1); +} + +.pkt_ext_containersaved .pkt_ext_item_recs li:active { + background-color: rgba(12, 12, 13, 0.2); +} + +.pkt_ext_containersaved .pkt_ext_item_recs .pkt_ext_item_recs_link:hover { + text-decoration: none; +} + +.pkt_ext_containersaved .pkt_ext_item_recs .rec-thumb { + width: 40px; + height: 40px; + float: inline-start; + margin: 0; + margin-inline-end: 12px; + border-radius: 2px; +} + +.pkt_ext_containersaved .pkt_ext_item_recs .rec-thumb:-moz-broken { + display: none; +} + +.pkt_ext_containersaved .pkt_ext_item_recs p { + -webkit-box-orient: vertical; + display: -webkit-box; + overflow: hidden; + word-break: break-word; + font-style: normal; + font-weight: normal; + margin: 0; +} + +.pkt_ext_containersaved .pkt_ext_item_recs .rec-title { + -webkit-line-clamp: 2; + font-size: 1em; + line-height: 18px; + color: #0C0C0D; +} + +.pkt_ext_containersaved .pkt_ext_item_recs .rec-source { + -webkit-line-clamp: 1; + font-size: 0.9em; + line-height: 16px; + color: #737373; +} + +/* signup.css + * + * Description: + * With base elements out of the way, this sets all custom styling for the extension. + * + * Contents: + * Global + * Core detail + * Core detail - storyboard + * Buttons + * Overflow mode + * Language overrides + */ +/*=Global +--------------------------------------------------------------------------------------- */ +.pkt_ext_containersignup { + background-color: #ebebeb; + color: #333; + display: block; + margin: 0; + padding: 0; + position: relative; + text-align: center; + overflow: hidden; +} + +.pkt_ext_containersignup_inactive { + animation: pkt_ext_hide 0.3s ease-out; + opacity: 0; + visibility: hidden; +} + +.pkt_ext_cf:after { + content: " "; + display: table; + clear: both; +} + +@keyframes pkt_ext_hide { + 0% { + opacity: 1; + visibility: visible; + } + 99% { + opacity: 0; + visibility: visible; + } + 100% { + opacity: 0; + visibility: hidden; + } +} +/*=Core detail +--------------------------------------------------------------------------------------- */ +.pkt_ext_containersignup p { + font-size: 1em; + color: #333; + line-height: 1.3; + margin: 0 auto 1.5em; + max-width: 260px; +} + +.pkt_ext_containersignup a { + color: #4c8fd0; +} + +.pkt_ext_containersignup a:hover { + color: #3076b9; +} + +.pkt_ext_containersignup .pkt_ext_introdetail { + background-color: #fbfbfb; + border: 1px solid #c1c1c1; + border-width: 0 0 1px; +} + +.pkt_ext_containersignup .pkt_ext_logo { + background: url(../img/pocketlogo@1x.png) center bottom no-repeat; + display: block; + height: 32px; + margin: 0 auto 15px; + padding-top: 25px; + position: relative; + text-indent: -9999px; + width: 123px; +} + +@media (min-resolution: 1.1dppx) { + .pkt_ext_containersignup .pkt_ext_logo { + background-image: url(../img/pocketlogo@2x.png); + background-size: 123px 32px; + } +} +.pkt_ext_containersignup .pkt_ext_introimg { + background: url(../img/pocketsignup_hero@1x.png) center center no-repeat; + display: block; + height: 125px; + margin: 0 auto; + position: relative; + text-indent: -9999px; + width: 255px; +} + +@media (min-resolution: 1.1dppx) { + .pkt_ext_containersignup .pkt_ext_introimg { + background-image: url(../img/pocketsignup_hero@2x.png); + background-size: 255px 125px; + } +} +.pkt_ext_containersignup .pkt_ext_tagline { + margin-bottom: 0.5em; +} + +.pkt_ext_containersignup .pkt_ext_learnmore { + font-size: 0.9em; +} + +.pkt_ext_signupdetail { + overflow: hidden; +} + +.pkt_ext_signupdetail h4 { + font-size: 0.9em; + font-weight: normal; +} + +.pkt_ext_signupdetail .btn-container { + position: relative; + margin-bottom: 0.8em; +} + +.pkt_ext_containersignup .ff_signuphelp { + background: url(../img/signup_help@1x.png) center center no-repeat; + display: block; + height: 18px; + margin-top: -9px; + inset-inline-end: -15px; + position: absolute; + text-indent: -9999px; + width: 18px; + top: 50%; +} + +@media (min-resolution: 1.1dppx) { + .pkt_ext_containersignup .ff_signuphelp { + background-image: url(../img/signup_help@2x.png); + background-size: 18px 18px; + } +} +.pkt_ext_containersignup .alreadyhave { + font-size: 0.9em; + max-width: 320px; + margin-top: 15px; +} + +/*=Core detail - storyboard +--------------------------------------------------------------------------------------- */ +.pkt_ext_introstory { + align-items: center; + display: flex; + padding: 20px; +} + +.pkt_ext_introstory:after { + clear: both; + content: ""; + display: table; +} + +.pkt_ext_introstory p { + margin-bottom: 0; + text-align: start; +} + +.pkt_ext_introstoryone { + padding-block: 20px 15px; + padding-inline: 20px 18px; +} + +.pkt_ext_introstorytwo { + padding-block: 3px 0; + padding-inline: 20px 0; +} + +.pkt_ext_introstorytwo .pkt_ext_tagline { + margin-bottom: 1.5em; +} + +.pkt_ext_introstory_text { + flex: 1; +} + +.pkt_ext_introstoryone_img, +.pkt_ext_introstorytwo_img { + display: block; + overflow: hidden; + position: relative; + text-indent: -999px; +} + +.pkt_ext_introstoryone_img { + background: url(../img/pocketsignup_button@1x.png) center right no-repeat; + height: 82px; + padding-block: 0; + padding-inline: 0.7em 0; + width: 82px; +} + +.pkt_ext_introstoryone_img:dir(rtl) { + background-position-x: left; +} + +@media (min-resolution: 1.1dppx) { + .pkt_ext_introstoryone_img { + background-image: url(../img/pocketsignup_button@2x.png); + background-size: 82px 82px; + } +} +.pkt_ext_introstorytwo_img { + background: url(../img/pocketsignup_devices@1x.png) bottom right no-repeat; + height: 110px; + padding-block: 1em 0; + padding-inline: 0.7em 0; + width: 124px; +} + +.pkt_ext_introstorytwo_img:dir(rtl) { + background-position-x: left; +} + +@media (min-resolution: 1.1dppx) { + .pkt_ext_introstorytwo_img { + background-image: url(../img/pocketsignup_devices@2x.png); + background-size: 124px 110px; + } +} +.pkt_ext_introstorydivider { + border-top: 1px solid #c1c1c1; + height: 1px; + margin: 0 auto; + width: 125px; +} + +/*=Buttons +--------------------------------------------------------------------------------------- */ +.pkt_ext_containersignup .btn { + background-color: #0096dd; + border: 1px solid #0095dd; + border-radius: 2px; + color: #fff; + display: inline-block; + font-size: 1.1em; + font-weight: normal; + line-height: 1; + margin: 0; + padding: 11px 45px; + text-align: center; + text-decoration: none; + text-shadow: 0 -1px 0 rgba(142, 4, 17, 0.5); + transition: background-color 0.1s linear; + width: auto; +} + +.pkt_ext_containersignup .btn-secondary { + background-color: #fbfbfb; + border-color: #c1c1c1; + color: #444; + text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); +} + +.pkt_ext_containersignup .btn-small { + padding: 6px 20px; +} + +.pkt_ext_containersignup .btn:hover { + background-color: #008acb; + color: #fff; + text-decoration: none; +} + +.pkt_ext_containersignup .btn-secondary:hover, +.pkt_ext_containersignup .btn-important:hover { + background-color: #f6f6f6; + color: #222; +} + +.pkt_ext_containersignup .btn-disabled { + background-image: none; + color: #ccc; + color: rgba(255, 255, 255, 0.6); + cursor: default; + opacity: 0.9; +} + +.pkt_ext_containersignup .signup-btn-firefox, +.pkt_ext_containersignup .signup-btn-email, +.pkt_ext_containersignup .signupinterim-btn-login, +.pkt_ext_containersignup .signupinterim-btn-signup, +.pkt_ext_containersignup .forgot-btn-submit, +.pkt_ext_containersignup .forgotreset-btn-change { + min-width: 12.125em; + padding: 0.8em 1.1875em; + box-sizing: content-box; +} + +.pkt_ext_containersignup .signup-btn-email { + position: relative; + z-index: 10; +} + +.pkt_ext_containersignup .signup-btn-firefox { + min-width: 14.5em; + position: relative; + padding: 0; +} + +.pkt_ext_containersignup .signup-btn-firefox .logo { + background: url(../img/signup_firefoxlogo@1x.png) center center no-repeat; + height: 2.6em; + inset-inline-start: 10px; + margin: 0; + padding: 0; + width: 22px; + position: absolute; +} + +@media (min-resolution: 1.1dppx) { + .pkt_ext_containersignup .signup-btn-firefox .logo { + background-image: url(../img/signup_firefoxlogo@2x.png); + background-size: 22px 22px; + } +} +.pkt_ext_containersignup .forgotreset-btn-change { + margin-bottom: 2em; +} + +.pkt_ext_containersignup .signup-btn-firefox .text { + display: inline-block; + padding: 0.8em 1.625em; + position: relative; + text-shadow: none; + white-space: nowrap; +} + +.pkt_ext_containersignup .signup-btn-firefox .text { + color: #fff; +} + +.pkt_ext_containersignup .btn-disabled .text { + color: #ccc; + color: rgba(255, 255, 255, 0.6); +} + +/*=Language overrides +--------------------------------------------------------------------------------------- */ +.pkt_ext_signup_de .pkt_ext_introstoryone_img { + margin-inline-end: -5px; + padding-inline-start: 0; +} + +.pkt_ext_signup_de .pkt_ext_introstorytwo .pkt_ext_tagline, +.pkt_ext_signup_es .pkt_ext_introstorytwo .pkt_ext_tagline, +.pkt_ext_signup_ja .pkt_ext_introstorytwo .pkt_ext_tagline, +.pkt_ext_signup_ru .pkt_ext_introstorytwo .pkt_ext_tagline { + margin-bottom: 0.5em; +} + +.pkt_ext_signup_ja .signup-btn-firefox .text, +.pkt_ext_signup_ru .signup-btn-firefox .text { + inset-inline-start: 15px; +} + +.pkt_ext_signup_de .signup-btn-firefox .logo, +.pkt_ext_signup_ja .signup-btn-firefox .logo, +.pkt_ext_signup_ru .signup-btn-firefox .logo { + height: 2.4em; +} + +@media (min-resolution: 1.1dppx) { + .pkt_ext_signup_de .signup-btn-firefox .logo, +.pkt_ext_signup_ja .signup-btn-firefox .logo, +.pkt_ext_signup_ru .signup-btn-firefox .logo { + height: 2.5em; + } +} +.pkt_ext_signup_de .signup-btn-email, +.pkt_ext_signup_ja .signup-btn-email, +.pkt_ext_signup_ru .signup-btn-email { + min-width: 13em; + padding: 0.8533em 1.2667em; +} + +.pkt_ext_signup_de .pkt_ext_logo, +.pkt_ext_signup_es .pkt_ext_logo, +.pkt_ext_signup_ru .pkt_ext_logo { + padding-top: 15px; +} + +.pkt_ext_signup_overflow.pkt_ext_signup_de .signup-btn-firefox .logo, +.pkt_ext_signup_overflow.pkt_ext_signup_es .signup-btn-firefox .logo, +.pkt_ext_signup_overflow.pkt_ext_signup_ja .signup-btn-firefox .logo, +.pkt_ext_signup_overflow.pkt_ext_signup_ru .signup-btn-firefox .logo { + display: none; +} + +.pkt_ext_containerhome, +.pkt_ext_wrapperhome { + overflow: hidden; +} + +.pkt_ext_home { + line-height: 20px; + color: #363636; +} +.pkt_ext_home a { + color: #008078; + text-decoration: none; +} +.pkt_ext_home a, .pkt_ext_home p { + font-size: 0.9em; +} +.pkt_ext_home .pkt_ext_hr { + height: 1px; + background: linear-gradient(90deg, #83EDB8 0%, #83EDB8 0%, #83EDB8 0.01%, #1CB0A8 33.15%, #EF4056 67.4%, #FCB643 100%); +} +.pkt_ext_home .pkt_ext_detail { + margin: 18px 20px; +} +.pkt_ext_home .pkt_ext_header { + display: flex; + justify-content: space-between; + align-items: center; +} +.pkt_ext_home .pkt_ext_header .pkt_ext_mylist_icon { + background: url(../img/list-view.svg) no-repeat; + background-size: contain; + height: 1.2em; + width: 1.2em; + margin-inline-end: 8px; +} +.pkt_ext_home .pkt_ext_header a { + height: 36px; + display: flex; + align-items: center; + box-sizing: border-box; + padding: 0 8px; + margin: 12px; + border-radius: 4px; +} +.pkt_ext_home .pkt_ext_header a:hover { + background: #F5F5F5; +} +.pkt_ext_home .pkt_ext_header .pkt_ext_logo { + background: url(../img/pocketlogo.svg) bottom right no-repeat; + background-size: contain; + height: 32px; + width: 123px; + margin: 0 20px; +} +.pkt_ext_home .pkt_ext_detail a { + display: block; +} +.pkt_ext_home .pkt_ext_detail h2 { + font-weight: 600; + font-size: 1em; +} +.pkt_ext_home .pkt_ext_detail h2, +.pkt_ext_home .pkt_ext_detail p { + margin: 8px 0; +} +.pkt_ext_home .pkt_ext_detail h3 { + font-weight: 600; + font-size: 1em; + margin: 12px 0; +} +.pkt_ext_home .pkt_ext_detail .pkt_ext_more { + margin: 19px 0; +} +.pkt_ext_home .pkt_ext_detail .pkt_ext_more .pkt_ext_chevron_right { + background: url(../img/chevron-right.svg) no-repeat; + background-size: contain; + height: 1.2em; + width: 1.2em; +} +.pkt_ext_home .pkt_ext_detail .pkt_ext_more ul { + list-style-type: none; + padding: 0; + line-height: 14px; +} +.pkt_ext_home .pkt_ext_detail .pkt_ext_more ul li a { + height: 44px; + width: 100%; + display: flex; + align-items: center; + justify-content: space-between; + box-sizing: border-box; + border-bottom: 1px solid #EAEAEA; +} +.pkt_ext_home .pkt_ext_detail .pkt_ext_discover { + line-height: 12px; + margin: 20px 0; + height: 40px; + width: 100%; + display: flex; + justify-content: center; + align-items: center; + border-radius: 4px; +} +.pkt_ext_home .pkt_ext_detail .pkt_ext_discover:hover { + background: #F5F5F5; +} + +#stp_style_guide { + border: 1px solid #ddd; + margin: 20px auto; + padding: 20px; + width: 260px; +} +#stp_style_guide #dark_mode_toggle { + text-align: end; +} +body.theme_dark #stp_style_guide { + background: #42414c; +} + +#stp_style_guide .stp_superheader { + margin: 0; +} +#stp_style_guide .stp_styleguide_h4 { + border-bottom: 1px solid #ccc; + margin: 20px 0; +} +#stp_style_guide .stp_styleguide_h5 { + font-size: 10px; + margin: 10px 0; +} + +.stp_tag_picker .stp_tag_picker_tags { + display: flex; + align-items: center; + gap: 8px; + flex-wrap: wrap; + padding: 8px; + border: 1px solid #8F8F9D; + border-radius: 4px; + font-style: normal; + font-weight: normal; + font-size: 1rem; + line-height: 1.2rem; + color: #15141A; + margin-bottom: 10px; +} +.stp_tag_picker .stp_tag_picker_tag { + background: #F0F0F4; + border-radius: 4px; + color: #15141A; + display: inline-block; + font-size: 0.85rem; + line-height: 1rem; + font-style: normal; + font-weight: 600; + padding: 0 8px; + transition: background-color 200ms ease-in-out; +} +body.theme_dark .stp_tag_picker .stp_tag_picker_tag { + background: #2B2A33; + color: #FBFBFB; +} + +.stp_tag_picker .recent_tags .stp_tag_picker_tag { + margin-inline-end: 5px; +} +.stp_tag_picker .stp_tag_picker_tag_remove { + padding-top: 5px; + padding-bottom: 5px; + padding-inline-end: 5px; + color: #5B5B66; + font-weight: 400; +} +.stp_tag_picker .stp_tag_picker_tag_remove:hover { + color: #3E3E44; +} +.stp_tag_picker .stp_tag_picker_tag_remove:focus { + color: #3E3E44; + outline: 2px solid #0060df; + outline-offset: -4px; +} +body.theme_dark .stp_tag_picker .stp_tag_picker_tag_remove { + color: #8F8F9D; +} +body.theme_dark .stp_tag_picker .stp_tag_picker_tag_remove:hover { + color: #fff; +} +body.theme_dark .stp_tag_picker .stp_tag_picker_tag_remove:focus { + outline: 2px solid #00DDFF; +} + +.stp_tag_picker .stp_tag_picker_tag_duplicate { + background-color: #bbb; +} +body.theme_dark .stp_tag_picker .stp_tag_picker_tag_duplicate { + background-color: #666; +} + +.stp_tag_picker .stp_tag_picker_input_wrapper { + display: flex; + flex-grow: 1; +} +.stp_tag_picker .stp_tag_picker_input { + flex-grow: 1; + border: 1px solid #8F8F9D; + padding: 0 6px; + border-start-start-radius: 4px; + border-end-start-radius: 4px; +} +.stp_tag_picker .stp_tag_picker_input:focus { + border: 1px solid #0060DF; + outline: 1px solid #0060DF; +} +body.theme_dark .stp_tag_picker .stp_tag_picker_input { + background: none; + color: #FBFBFB; +} +body.theme_dark .stp_tag_picker .stp_tag_picker_input:focus { + border: 1px solid #00DDFF; + outline: 1px solid #00DDFF; +} + +.stp_tag_picker .stp_tag_picker_button { + font-size: 0.95rem; + line-height: 1.1rem; + padding: 4px 6px; + background-color: #F0F0F4; + border: 1px solid #8F8F9D; + border-inline-start: none; + border-start-end-radius: 4px; + border-end-end-radius: 4px; +} +.stp_tag_picker .stp_tag_picker_button:disabled { + color: #8F8F9D; +} +.stp_tag_picker .stp_tag_picker_button:hover:enabled { + background-color: #DADADF; +} +.stp_tag_picker .stp_tag_picker_button:focus:enabled { + border: 1px solid #0060DF; + outline: 1px solid #0060DF; +} +body.theme_dark .stp_tag_picker .stp_tag_picker_button { + background-color: #2B2A33; + color: #FBFBFB; +} +body.theme_dark .stp_tag_picker .stp_tag_picker_button:disabled { + color: #666; +} +body.theme_dark .stp_tag_picker .stp_tag_picker_button:hover:enabled { + background-color: #53535d; +} +body.theme_dark .stp_tag_picker .stp_tag_picker_button:focus:enabled { + border: 1px solid #00DDFF; + outline: 1px solid #00DDFF; +} + +.stp_popular_topics { + padding: 0; +} +.stp_popular_topics .stp_popular_topic { + display: inline-block; +} +.stp_popular_topics .stp_popular_topic .stp_popular_topic_link { + display: inline-block; + background: #F0F0F4; + border-radius: 4px; + font-size: 0.85rem; + line-height: 1rem; + font-style: normal; + font-weight: 600; + margin-inline-end: 8px; + margin-bottom: 8px; + padding: 4px 8px; + color: #000; +} +.stp_popular_topics .stp_popular_topic .stp_popular_topic_link:focus { + text-decoration: none; + background: #F0F0F4; + outline: 2px solid #0060df; + outline-offset: 2px; +} +.stp_popular_topics .stp_popular_topic .stp_popular_topic_link:hover { + background: #E0E0E6; + text-decoration: none; +} +.stp_popular_topics .stp_popular_topic .stp_popular_topic_link:active { + background: #CFCFD8; +} +.stp_popular_topics .stp_popular_topic .stp_popular_topic_link::after { + content: " >"; +} +body.theme_dark .stp_popular_topics .stp_popular_topic .stp_popular_topic_link { + background: #2B2A33; + color: #FBFBFE; +} +body.theme_dark .stp_popular_topics .stp_popular_topic .stp_popular_topic_link:focus { + outline: 2px solid #00DDFF; +} +body.theme_dark .stp_popular_topics .stp_popular_topic .stp_popular_topic_link:hover { + background: #53535d; +} + +.stp_article_list { + padding: 0; + list-style: none; +} +.stp_article_list .stp_article_list_saved_article, +.stp_article_list .stp_article_list_link { + display: flex; + border-radius: 4px; + padding: 8px; + margin: 0 -8px; +} +.stp_article_list .stp_article_list_link:hover, .stp_article_list .stp_article_list_link:focus { + text-decoration: none; + background-color: #ECECEE; +} +body.theme_dark .stp_article_list .stp_article_list_link:hover, body.theme_dark .stp_article_list .stp_article_list_link:focus { + background-color: #2B2A33; +} + +.stp_article_list .stp_article_list_thumb, +.stp_article_list .stp_article_list_thumb_placeholder { + width: 40px; + height: 40px; + border-radius: 4px; + margin-inline-end: 8px; + background-color: #ECECEE; + flex-shrink: 0; +} +.stp_article_list .stp_article_list_header { + font-style: normal; + font-weight: 600; + font-size: 0.95rem; + line-height: 1.18rem; + color: #15141A; + margin: 0 0 4px; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + display: -webkit-box; + overflow: hidden; + word-break: break-word; +} +body.theme_dark .stp_article_list .stp_article_list_header { + color: #FBFBFE; +} + +.stp_article_list .stp_article_list_publisher { + font-style: normal; + font-weight: normal; + font-size: 0.95rem; + line-height: 1.18rem; + color: #52525E; + margin: 4px 0 0; +} +body.theme_dark .stp_article_list .stp_article_list_publisher { + color: #CFCFD8; +} + +.stp_header { + display: flex; + justify-content: space-between; + align-items: center; + margin: 16px 0 12px; + font-weight: 600; +} +.stp_header .stp_header_logo { + background: url(../img/pocketlogo.svg) bottom center no-repeat; + background-size: contain; + height: 32px; + width: 121px; +} +body.theme_dark .stp_header .stp_header_logo { + background-image: url(../img/pocketlogo-dark.svg); +} + +.stp_header .stp_button { + margin: 0; +} + +.stp_button { + cursor: pointer; + display: inline-block; + margin: 12px 0; +} +.stp_button:hover { + text-decoration: none; +} +.stp_button.stp_button_text { + color: #0060DF; + font-size: 0.95rem; + line-height: 1.2rem; + font-style: normal; + font-weight: 600; +} +.stp_button.stp_button_text:focus { + text-decoration: underline; +} +.stp_button.stp_button_text:hover { + color: #0250BB; + text-decoration: none; +} +.stp_button.stp_button_text:active { + color: #054096; +} +body.theme_dark .stp_button.stp_button_text { + color: #00DDFF; +} + +.stp_button.stp_button_primary { + align-items: center; + background: #0060DF; + border-radius: 4px; + color: #FBFBFE; + font-size: 0.85rem; + line-height: 1rem; + font-style: normal; + font-weight: 600; + justify-content: center; + padding: 6px 12px; +} +.stp_button.stp_button_primary:focus { + text-decoration: none; + background: #0060DF; + outline: 2px solid #0060df; + outline-offset: 2px; +} +.stp_button.stp_button_primary:hover { + background: #0250BB; +} +.stp_button.stp_button_primary:active { + background: #054096; +} +body.theme_dark .stp_button.stp_button_primary { + background: #00DDFF; + color: #15141A; +} +body.theme_dark .stp_button.stp_button_primary:hover { + background: #80ebfe; +} +body.theme_dark .stp_button.stp_button_primary:focus { + outline: 2px solid #00DDFF; +} + +.stp_button.stp_button_secondary { + align-items: center; + background: #F0F0F4; + border-radius: 4px; + color: #15141A; + font-size: 0.85rem; + line-height: 1rem; + font-style: normal; + font-weight: 600; + padding: 6px 12px; +} +.stp_button.stp_button_secondary:focus { + text-decoration: none; + background: #F0F0F4; + outline: 2px solid #0060df; + outline-offset: 2px; +} +.stp_button.stp_button_secondary:hover { + background: #E0E0E6; +} +.stp_button.stp_button_secondary:active { + background: #CFCFD8; +} +body.theme_dark .stp_button.stp_button_secondary { + background: #2B2A33; + color: #FBFBFE; +} +body.theme_dark .stp_button.stp_button_secondary:focus { + outline: 2px solid #00DDFF; +} +body.theme_dark .stp_button.stp_button_secondary:hover { + background: #53535d; +} + +.stp_button_wide .stp_button { + display: block; + margin: 12px 0; + text-align: center; + padding: 8px 12px; +} +.stp_button_wide .stp_button.stp_button_primary { + font-size: 1.1rem; + line-height: 1.35rem; +} +.stp_button_wide .stp_button.stp_button_secondary { + font-size: 0.85rem; + line-height: 1rem; +} + +.stp_button_wide .stp_button { + display: block; + margin: 12px 0; + text-align: center; +} + +body.stp_signup_body { + overflow: hidden; +} + +.stp_panel_signup .stp_signup_content_wrapper { + margin: 12px 0 20px; +} +.stp_panel_signup .stp_signup_img_rainbow_reader { + background: url(../img/rainbow-reader.svg) bottom center no-repeat; + background-size: contain; + height: 72px; + width: 82px; + float: inline-end; + margin-inline-start: 16px; +} + +body.stp_saved_body { + overflow: hidden; +} + +.stp_panel_error { + margin: 23px 0 32px; +} +.stp_panel_error .stp_panel_error_icon { + float: inline-start; + margin-block: 6px 16px; + margin-inline: 7px 17px; + background-image: url(../img/pocketerror@1x.png); + height: 44px; + width: 44px; +} + +/*# sourceMappingURL=main.compiled.css.map */ diff --git a/browser/components/pocket/content/panels/css/main.scss b/browser/components/pocket/content/panels/css/main.scss new file mode 100644 index 0000000000..7a3111210a --- /dev/null +++ b/browser/components/pocket/content/panels/css/main.scss @@ -0,0 +1,17 @@ +@import "./normalize"; +@import "./global"; +@import "./panel"; +@import "./saved"; +@import "./signup"; +@import "./home"; +@import "./styleguide"; + +// Components + +@import "../js/components/TagPicker/TagPicker"; +@import "../js/components/PopularTopics/PopularTopics"; +@import "../js/components/ArticleList/ArticleList"; +@import "../js/components/Header/Header"; +@import "../js/components/Button/Button"; +@import "../js/components/Signup/Signup"; +@import "../js/components/Saved/Saved"; diff --git a/browser/components/pocket/content/panels/css/normalize.scss b/browser/components/pocket/content/panels/css/normalize.scss new file mode 100644 index 0000000000..af461f1e0b --- /dev/null +++ b/browser/components/pocket/content/panels/css/normalize.scss @@ -0,0 +1,425 @@ +/*! normalize.css v3.0.2 | MIT License | git.io/normalize */ + +/** + * 1. Set default font family to sans-serif. + * 2. Prevent iOS text size adjust after orientation change, without disabling + * user zoom. + */ + +html { + font-family: sans-serif; /* 1 */ +} + +/** + * Remove default margin. + */ + +body { + margin: 0; +} + +/* HTML5 display definitions + ========================================================================== */ + +/** + * Correct `block` display not defined for any HTML5 element in IE 8/9. + * Correct `block` display not defined for `details` or `summary` in IE 10/11 + * and Firefox. + * Correct `block` display not defined for `main` in IE 11. + */ + +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +main, +menu, +nav, +section, +summary { + display: block; +} + +/** + * 1. Correct `inline-block` display not defined in IE 8/9. + * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera. + */ + +audio, +canvas, +progress, +video { + display: inline-block; /* 1 */ + vertical-align: baseline; /* 2 */ +} + +/** + * Prevent modern browsers from displaying `audio` without controls. + * Remove excess height in iOS 5 devices. + */ + +audio:not([controls]) { + display: none; + height: 0; +} + +/** + * Address `[hidden]` styling not present in IE 8/9/10. + * Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22. + */ + +[hidden], +template { + display: none; +} + +/* Links + ========================================================================== */ + +/** + * Remove the gray background color from active links in IE 10. + */ + +a { + background-color: transparent; +} + +/** + * Improve readability when focused and also mouse hovered in all browsers. + */ + +a:active, +a:hover { + outline: 0; +} + +/* Text-level semantics + ========================================================================== */ + +/** + * Address styling not present in IE 8/9/10/11, Safari, and Chrome. + */ + +abbr[title] { + border-bottom: 1px dotted; +} + +/** + * Address style set to `bolder` in Firefox 4+, Safari, and Chrome. + */ + +b, +strong { + font-weight: bold; +} + +/** + * Address styling not present in Safari and Chrome. + */ + +dfn { + font-style: italic; +} + +/** + * Address variable `h1` font-size and margin within `section` and `article` + * contexts in Firefox 4+, Safari, and Chrome. + */ + +h1 { + font-size: 2em; + margin: 0.67em 0; +} + +/** + * Address styling not present in IE 8/9. + */ + +mark { + background: #ff0; + color: #000; +} + +/** + * Address inconsistent and variable font size in all browsers. + */ + +small { + font-size: 80%; +} + +/** + * Prevent `sub` and `sup` affecting `line-height` in all browsers. + */ + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sup { + top: -0.5em; +} + +sub { + bottom: -0.25em; +} + +/* Embedded content + ========================================================================== */ + +/** + * Remove border when inside `a` element in IE 8/9/10. + */ + +img { + border: 0; +} + +/** + * Correct overflow not hidden in IE 9/10/11. + */ + +svg:not(:root) { + overflow: hidden; +} + +/* Grouping content + ========================================================================== */ + +/** + * Address margin not present in IE 8/9 and Safari. + */ + +figure { + margin: 1em 40px; +} + +/** + * Address differences between Firefox and other browsers. + */ + +hr { + -moz-box-sizing: content-box; + box-sizing: content-box; + height: 0; +} + +/** + * Contain overflow in all browsers. + */ + +pre { + overflow: auto; +} + +/** + * Address odd `em`-unit font size rendering in all browsers. + */ + +code, +kbd, +pre, +samp { + font-family: monospace, monospace; + font-size: 1em; +} + +/* Forms + ========================================================================== */ + +/** + * Known limitation: by default, Chrome and Safari on OS X allow very limited + * styling of `select`, unless a `border` property is set. + */ + +/** + * 1. Correct color not being inherited. + * Known issue: affects color of disabled elements. + * 2. Correct font properties not being inherited. + * 3. Address margins set differently in Firefox 4+, Safari, and Chrome. + */ + +button, +input, +optgroup, +select, +textarea { + color: inherit; /* 1 */ + font: inherit; /* 2 */ + margin: 0; /* 3 */ +} + +/** + * Address `overflow` set to `hidden` in IE 8/9/10/11. + */ + +button { + overflow: visible; +} + +/** + * Address inconsistent `text-transform` inheritance for `button` and `select`. + * All other form control elements do not inherit `text-transform` values. + * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera. + * Correct `select` style inheritance in Firefox. + */ + +button, +select { + text-transform: none; +} + +/** + * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` + * and `video` controls. + * 2. Correct inability to style clickable `input` types in iOS. + * 3. Improve usability and consistency of cursor style between image-type + * `input` and others. + */ + +button, +html input[type="button"], /* 1 */ +input[type="reset"], +input[type="submit"] { + cursor: pointer; /* 3 */ +} + +/** + * Re-set default cursor for disabled elements. + */ + +button[disabled], +html input[disabled] { + cursor: default; +} + +/** + * Remove inner padding and border in Firefox 4+. + */ + +button::-moz-focus-inner, +input::-moz-focus-inner { + border: 0; + padding: 0; +} + +/** + * Address Firefox 4+ setting `line-height` on `input` using `!important` in + * the UA stylesheet. + */ + +input { + line-height: normal; +} + +/** + * It's recommended that you don't attempt to style these elements. + * Firefox's implementation doesn't respect box-sizing, padding, or width. + * + * 1. Address box sizing set to `content-box` in IE 8/9/10. + * 2. Remove excess padding in IE 8/9/10. + */ + +input[type="checkbox"], +input[type="radio"] { + box-sizing: border-box; /* 1 */ + padding: 0; /* 2 */ +} + +/** + * 1. Address `appearance` set to `searchfield` in Safari and Chrome. + * 2. Address `box-sizing` set to `border-box` in Safari and Chrome + * (include `-moz` to future-proof). + */ + +input[type="search"] { + box-sizing: content-box; +} + +/** + * Define consistent border, margin, and padding. + */ + +fieldset { + border: 1px solid #c0c0c0; + margin: 0 2px; + padding: 0.35em 0.625em 0.75em; +} + +/** + * 1. Correct `color` not being inherited in IE 8/9/10/11. + * 2. Remove padding so people aren't caught out if they zero out fieldsets. + */ + +legend { + border: 0; /* 1 */ + padding: 0; /* 2 */ +} + +/** + * Remove default vertical scrollbar in IE 8/9/10/11. + */ + +textarea { + overflow: auto; +} + +/** + * Don't inherit the `font-weight` (applied by a rule above). + * NOTE: the default cannot safely be changed in Chrome and Safari on OS X. + */ + +optgroup { + font-weight: bold; +} + +/* Tables + ========================================================================== */ + +/** + * Remove most spacing between table cells. + */ + +table { + border-collapse: collapse; + border-spacing: 0; +} + +td, +th { + padding: 0; +} + +/* Normalization for FF panel defauts + ========================================================================== */ +html { + outline: none; + padding: 0; +} + +a { + color: #0095dd; + margin: 0; + outline: none; + padding: 0; + text-decoration: none; +} + +a:hover, +a:active, +a:focus { + color: #008acb; + text-decoration: underline; +} + +a:active { + color: #006b9d; +} diff --git a/browser/components/pocket/content/panels/css/panel.scss b/browser/components/pocket/content/panels/css/panel.scss new file mode 100644 index 0000000000..e89ba4e0fe --- /dev/null +++ b/browser/components/pocket/content/panels/css/panel.scss @@ -0,0 +1,8 @@ +.stp_panel_container { + overflow: hidden; +} + +.stp_panel { + padding: 0 16px; + margin: 16px 0 12px; +} diff --git a/browser/components/pocket/content/panels/css/saved.scss b/browser/components/pocket/content/panels/css/saved.scss new file mode 100644 index 0000000000..7bede17cae --- /dev/null +++ b/browser/components/pocket/content/panels/css/saved.scss @@ -0,0 +1,939 @@ +/* saved.css + * + * Description: + * With base elements out of the way, this sets all custom styling for the page saved dialog. + * + * Contents: + * Global + * Loading spinner + * Core detail + * Tag entry + * Recent/suggested tags + * Premium upsell + * Token input/autocomplete + * Overflow mode + * Language overrides + */ + +/*=Global +--------------------------------------------------------------------------------------- */ +.pkt_ext_containersaved { + background-color: #fbfbfb; + border-radius: 4px; + display: block; + padding: 0; + position: relative; + text-align: center; + overflow: hidden; +} +.pkt_ext_cf:after { + content: " "; + display:table; + clear:both; +} +.pkt_ext_containersaved .pkt_ext_tag_detail, +.pkt_ext_containersaved .pkt_ext_recenttag_detail, +.pkt_ext_containersaved .pkt_ext_suggestedtag_detail { + margin: 0 auto; + padding: 0.25em 1em; + position: relative; + width: auto; +} + +/*=Loading spinner +--------------------------------------------------------------------------------------- */ +@keyframes pkt_ext_spin { + to { + transform: rotate(1turn); + } +} +.pkt_ext_containersaved .pkt_ext_loadingspinner { + display: inline-block; + height: 2.5em; + inset-inline-start: 50%; + margin-block: 2em 0; + margin-inline: -1.25em 0; + font-size: 10px; + text-indent: 999em; + position: absolute; + top: 4em; + overflow: hidden; + width: 2.5em; + animation: pkt_ext_spin 0.7s infinite steps(8); +} +.pkt_ext_containersaved .pkt_ext_loadingspinner:before, +.pkt_ext_containersaved .pkt_ext_loadingspinner:after, +.pkt_ext_containersaved .pkt_ext_loadingspinner > div:before, +.pkt_ext_containersaved .pkt_ext_loadingspinner > div:after { + content: ''; + position: absolute; + top: 0; + inset-inline-start: 1.125em; + width: 0.25em; + height: 0.75em; + border-radius: .2em; + background: #eee; + box-shadow: 0 1.75em #eee; + transform-origin: 50% 1.25em; +} +.pkt_ext_containersaved .pkt_ext_loadingspinner:before { + background: #555; +} +.pkt_ext_containersaved .pkt_ext_loadingspinner:after { + transform: rotate(-45deg); + background: #777; +} +.pkt_ext_containersaved .pkt_ext_loadingspinner > div:before { + transform: rotate(-90deg); + background: #999; +} +.pkt_ext_containersaved .pkt_ext_loadingspinner > div:after { + transform: rotate(-135deg); + background: #bbb; +} + +/*=Core detail +--------------------------------------------------------------------------------------- */ +.pkt_ext_containersaved .pkt_ext_initload { + inset-inline-start: 0; + position: absolute; + top: 0; + width: 100%; +} +.pkt_ext_containersaved .pkt_ext_detail { + opacity: 0; + position: relative; + padding-bottom: 1.25em; +} +.pkt_ext_container_detailactive .pkt_ext_initload { + opacity: 0; +} +.pkt_ext_container_detailactive .pkt_ext_initload .pkt_ext_loadingspinner, +.pkt_ext_container_finalstate .pkt_ext_initload .pkt_ext_loadingspinner { + animation: none; +} +.pkt_ext_container_detailactive .pkt_ext_detail { + max-height: 20em; + opacity: 1; +} +.pkt_ext_container_finalstate .pkt_ext_edit_msg, +.pkt_ext_container_finalstate .pkt_ext_tag_detail, +.pkt_ext_container_finalstate .pkt_ext_suggestedtag_detail, +.pkt_ext_container_finalstate .pkt_ext_item_actions { + opacity: 0; + transition: opacity 0.2s ease-out; +} +.pkt_ext_container_finalerrorstate .pkt_ext_edit_msg, +.pkt_ext_container_finalerrorstate .pkt_ext_tag_detail, +.pkt_ext_container_finalerrorstate .pkt_ext_suggestedtag_detail, +.pkt_ext_container_finalerrorstate .pkt_ext_item_actions { + display: none; + transition: none; +} +.pkt_ext_containersaved h2 { + background: transparent; + border: none; + color: #333; + display: block; + float: none; + font-size: 1.2em; + font-weight: normal; + letter-spacing: normal; + line-height: 1; + margin: 19px 0 4px; + padding: 0; + position: relative; + text-align: start; + text-transform: none; +} +@keyframes fade_in_out { + 0% { + opacity: 1; + } + 50% { + opacity: 0; + } + 100% { + opacity: 1; + } +} +.pkt_ext_container_finalstate h2 { + animation: fade_in_out 0.4s ease-out; +} +.pkt_ext_container_finalerrorstate h2 { + animation: none; + color: #d74345; +} +.pkt_ext_containersaved .pkt_ext_errordetail { + display: none; + font-size: 0.9em; + font-weight: normal; + inset-inline-start: 6.4em; + max-width: 21em; + opacity: 0; + position: absolute; + top: 2.7em; + text-align: start; + visibility: hidden; +} +.pkt_ext_container_finalerrorstate { + max-height: 133px; +} +.pkt_ext_container_finalerrorstate .pkt_ext_errordetail { + display: block; + opacity: 1; + visibility: visible; +} +.pkt_ext_containersaved .pkt_ext_logo { + background: url(../img/pocketlogosolo@1x.png) center center no-repeat; + display: block; + float: inline-start; + height: 40px; + padding: 1.25em 1em; + position: relative; + width: 44px; +} +@media (min-resolution: 1.1dppx) { + .pkt_ext_containersaved .pkt_ext_logo { + background-image: url(../img/pocketlogosolo@2x.png); + background-size: 44px 40px; + } +} +.pkt_ext_container_finalerrorstate .pkt_ext_logo { + background-image: url(../img/pocketerror@1x.png); + height: 44px; + width: 44px; +} +@media (min-resolution: 1.1dppx) { + .pkt_ext_container_finalerrorstate .pkt_ext_logo { + background-image: url(../img/pocketerror@2x.png); + background-size: 44px 44px; + } +} +.pkt_ext_containersaved .pkt_ext_topdetail { + float: inline-start; +} +.pkt_ext_containersaved { + .pkt_ext_edit_msg_container { + position: relative; + .pkt_ext_edit_msg { + box-sizing: border-box; + display: none; + font-size: 0.75em; + inset-inline-start: auto; + padding: 0 1.4em; + position: absolute; + text-align: start; + top: 0; + width: 100%; + margin: 0; + &.pkt_ext_edit_msg_error { + color: #d74345; + } + &.pkt_ext_edit_msg_active { + display: block; + } + } + } +} +.pkt_ext_containersaved .pkt_ext_item_actions { + background: transparent; + float: none; + height: auto; + margin-bottom: 1em; + margin-top: 0; + width: auto; +} +.pkt_ext_containersaved .pkt_ext_item_actions_disabled { + opacity: 0.5; +} +.pkt_ext_container_finalstate .pkt_ext_item_actions_disabled { + opacity: 0; +} +.pkt_ext_containersaved .pkt_ext_item_actions ul { + background: none; + display: block; + float: none; + height: auto; + margin: 0; + padding: 0; + width: 100%; +} +.pkt_ext_containersaved .pkt_ext_item_actions li { + box-sizing: border-box; + background: none; + border: 0; + float: inline-start; + list-style: none; + line-height: 0.8; + height: auto; + padding-inline-end: 0.4em; + width: auto; +} +.pkt_ext_containersaved .pkt_ext_item_actions li:before { + content: none; +} +.pkt_ext_containersaved .pkt_ext_item_actions .pkt_ext_actions_separator { + border-inline-start: 2px solid #777; + height: 1em; + margin-top: 0.3em; + padding: 0; + width: 10px; +} +.pkt_ext_containersaved .pkt_ext_item_actions a { + background: transparent; + color: #0095dd; + display: block; + font-feature-settings: normal; + font-size: 0.9em; + font-weight: normal; + letter-spacing: normal; + line-height: inherit; + height: auto; + margin: 0; + padding: 0.5em; + float: inline-start; + text-align: start; + text-decoration: none; + text-transform: none; +} +.pkt_ext_containersaved .pkt_ext_item_actions a:hover, +.pkt_ext_containersaved .pkt_ext_item_actions a:focus { + color: #008acb; + text-decoration: underline; +} +.pkt_ext_containersaved .pkt_ext_item_actions a:before, +.pkt_ext_containersaved .pkt_ext_item_actions a:after { + background: transparent; + display: none; +} +.pkt_ext_containersaved .pkt_ext_item_actions_disabled a { + cursor: default; +} +.pkt_ext_containersaved .pkt_ext_item_actions .pkt_ext_openpocket { + float: inline-end; + padding-inline-end: 0.7em; + text-align: end; +} +.pkt_ext_containersaved .pkt_ext_item_actions .pkt_ext_removeitem { + padding-inline-start: 0; +} +.pkt_ext_containersaved .pkt_ext_close { + background: url(../img/tag_close@1x.png) center center no-repeat; + color: #333; + display: block; + font-size: 0.8em; + height: 10px; + inset-inline-end: 0.5em; + overflow: hidden; + position: absolute; + text-align: center; + text-indent: -9999px; + top: -1em; + width: 10px; +} +@media (min-resolution: 1.1dppx) { + .pkt_ext_containersaved .pkt_ext_close { + background-image: url(../img/tag_close@2x.png); + background-size: 8px 8px; + } +} +.pkt_ext_containersaved .pkt_ext_close:hover { + color: #000; + text-decoration: none; +} + +/*=Tag entry +--------------------------------------------------------------------------------------- */ +.pkt_ext_containersaved .pkt_ext_tag_detail { + border: 1px solid #c1c1c1; + border-radius: 2px; + clear: both; + margin: 0 1em; + padding: 0; + display: flex; +} +.pkt_ext_containersaved .pkt_ext_tag_error { + border: none; +} +.pkt_ext_containersaved .pkt_ext_tag_input_wrapper { + box-sizing: border-box; + flex: 1; + background-color: #fff; + border-inline-end: 1px solid #c3c3c3; + color: #333; + display: block; + float: none; + font-size: 0.875em; + list-style: none; + margin: 0; + overflow: hidden; + padding: 0.25em 0.5em; + width: 14em; + padding-inline: 0.5em; +} +.pkt_ext_containersaved .pkt_ext_tag_error .pkt_ext_tag_input_wrapper { + border: 1px solid #d74345; +} +.pkt_ext_containersaved .pkt_ext_tag_input_wrapper .token-input-list { + display: block; + height: 1.7em; + overflow: hidden; + position: relative; + width: 60em; +} +.pkt_ext_containersaved .pkt_ext_tag_input_wrapper .token-input-list, +.pkt_ext_containersaved .pkt_ext_tag_input_wrapper li { + font-size: 1em; +} +.pkt_ext_containersaved .pkt_ext_tag_input_wrapper li { + height: auto; + width: auto; +} +.pkt_ext_containersaved .pkt_ext_tag_input_wrapper li:before { + content: none; +} +.pkt_ext_containersaved .pkt_ext_tag_input_wrapper input { + border: 0; + box-shadow: none; + background-color: #fff; + color: #333; + font-size: 1em; + float: inline-start; + line-height: normal; + height: auto; + min-height: 0; + min-width: 5em; + padding: 3px 2px 1px; + text-transform: none; +} +.pkt_ext_containersaved .pkt_ext_tag_input_wrapper input::placeholder { + color: #a9a9a9; + letter-spacing: normal; + text-transform: none; +} +.pkt_ext_containersaved .input_disabled { + cursor: default; + opacity: 0.5; +} +.pkt_ext_containersaved .pkt_ext_btn { + box-sizing: border-box; + color: #333; + float: none; + font-size: 1em; + letter-spacing: normal; + height: 2.2em; + min-width: 4em; + padding: 0.5em 0; + text-decoration: none; + text-transform: none; + width: auto; +} +.pkt_ext_containersaved .pkt_ext_btn:hover { + background-color: #ebebeb; +} +.pkt_ext_containersaved .pkt_ext_btn:active { + background-color: #dadada; +} +.pkt_ext_containersaved .pkt_ext_btn_disabled, +.pkt_ext_containersaved .pkt_ext_btn_disabled:hover, +.pkt_ext_containersaved .pkt_ext_btn_disabled:active { + background-color: transparent; + cursor: default; + opacity: 0.4; +} +.pkt_ext_containersaved .pkt_ext_tag_error .pkt_ext_btn { + border: 1px solid #c3c3c3; + border-block-width: 1px; + border-inline-width: 0 1px; + height: 2.35em; +} +.pkt_ext_containersaved .autocomplete-suggestions { + margin-top: 2.2em; +} + +/*=Recent/suggested tags +--------------------------------------------------------------------------------------- */ +.pkt_ext_containersaved .pkt_ext_suggestedtag_detail { + box-sizing: border-box; + clear: both; + inset-inline-start: 0; + opacity: 0; + min-height: 110px; + visibility: hidden; + width: 100%; +} +.pkt_ext_container_detailactive .pkt_ext_suggestedtag_detail { + opacity: 1; + visibility: visible; +} +.pkt_ext_container_finalstate .pkt_ext_suggestedtag_detail { + opacity: 0; + visibility: hidden; +} + +.pkt_ext_containersaved .pkt_ext_recenttag_detail h4, +.pkt_ext_containersaved .pkt_ext_suggestedtag_detail h4 { + color: #333; + font-size: 1em; + font-weight: normal; + font-style: normal; + letter-spacing: normal; + margin: 0.5em 0; + text-align: start; + text-transform: none; +} +.pkt_ext_containersaved .pkt_ext_recenttag_detail .pkt_ext_loadingspinner, +.pkt_ext_containersaved .pkt_ext_suggestedtag_detail .pkt_ext_loadingspinner { + display: none; + position: absolute; +} +.pkt_ext_containersaved .pkt_ext_recenttag_detail_loading .pkt_ext_loadingspinner, +.pkt_ext_containersaved .pkt_ext_suggestedtag_detail_loading .pkt_ext_loadingspinner { + display: block; + font-size: 6px; + inset-inline-start: 48%; +} +.pkt_ext_containersaved .pkt_ext_recenttag_detail ul, +.pkt_ext_containersaved .pkt_ext_suggestedtag_detail ul { + display: block; + margin: 0; + height: 2em; + overflow: hidden; + padding: 2px 0 0; +} +.pkt_ext_containersaved .pkt_ext_suggestedtag_detail ul { + height: auto; + margin: 0; + max-height: 4em; + padding-top: 6px; +} +.pkt_ext_containersaved .pkt_ext_recenttag_detail li, +.pkt_ext_containersaved .pkt_ext_suggestedtag_detail li { + background: none; + float: inline-start; + height: inherit; + line-height: 1.5; + list-style: none; + margin-bottom: 0.5em; + width: inherit; +} +.pkt_ext_containersaved .pkt_ext_recenttag_detail li:before, +.pkt_ext_containersaved .pkt_ext_suggestedtag_detail li:before { + content: none; +} +.pkt_ext_containersaved .pkt_ext_recenttag_detail .recenttag_msg, +.pkt_ext_containersaved .pkt_ext_suggestedtag_detail .suggestedtag_msg { + color: #333; + font-size: 0.8125em; + line-height: 1.2; + inset-inline-start: auto; + position: absolute; + text-align: start; + top: 2em; +} +.pkt_ext_containersaved .pkt_ext_suggestedtag_detail .suggestedtag_msg { + margin-inline-end: 1.3em; +} +.pkt_ext_containersaved .token_tag { + border-radius: 4px; + background: #f7f7f7; + border: 1px solid #c3c3c3; + color: #333; + font-size: 1em; + font-weight: normal; + letter-spacing: normal; + margin-inline-end: 0.5em; + padding: 0.125em 0.625em; + text-decoration: none; + text-transform: none; +} +.pkt_ext_containersaved .token_tag:hover { + background-color: #008acb; + border-color: #008acb; + color: #fff; + text-decoration: none; +} +.pkt_ext_containersaved .token_tag:before, +.pkt_ext_containersaved .token_tag:after { + content: none; +} +.pkt_ext_containersaved .token_tag:hover span { + background-image: url(../img/tag_closeactive@1x.png); +} +@media (min-resolution: 1.1dppx) { + .pkt_ext_containersaved .token_tag:hover span { + background-image: url(../img/tag_closeactive@2x.png); + background-size: 8px 8px; + } +} +.pkt_ext_containersaved .pkt_ext_recenttag_detail_disabled .token_tag, +.pkt_ext_containersaved .pkt_ext_recenttag_detail_disabled .token_tag:hover, +.pkt_ext_containersaved .pkt_ext_suggestedtag_detail_disabled .token_tag, +.pkt_ext_containersaved .pkt_ext_suggestedtag_detail_disabled .token_tag:hover { + background-color: #f7f7f7; + cursor: default; + opacity: 0.5; +} +.pkt_ext_containersaved .token_tag_inactive { + display: none; +} + +/*=Premium upsell +--------------------------------------------------------------------------------------- */ +.pkt_ext_detail .pkt_ext_premupsell { + background-color: #50bbb6; + display: block; + padding: 1.5em 0; + text-align: center; +} +.pkt_ext_premupsell h4 { + color: #fff; + font-size: 1em; + margin-bottom: 1em; +} +.pkt_ext_premupsell a { + color: #28605d; + border-bottom: 1px solid #47a7a3; + font-weight: normal; +} +.pkt_ext_premupsell a:hover { + color: #14302f; +} + +/*=Token input/autocomplete +--------------------------------------------------------------------------------------- */ +.token-input-dropdown-tag { + border-radius: 4px; + box-sizing: border-box; + background: #fff; + border: 1px solid #cdcdcd; + margin-top: 0.5em; + inset-inline-start: 0 !important; + overflow-y: auto; + top: 1.9em !important; + z-index: 9000; +} +.token-input-dropdown-tag ul { + height: inherit; + max-height: 115px; + margin: 0; + overflow: auto; + padding: 0.5em 0; +} +.token-input-dropdown-tag ul li { + background: none; + color: #333; + font-weight: normal; + font-size: 1em; + float: none; + height: inherit; + letter-spacing: normal; + list-style: none; + padding: 0.75em; + text-align: start; + text-transform: none; + width: inherit; +} +.token-input-dropdown-tag ul li:before { + content: none; +} +.token-input-dropdown ul li.token-input-selected-dropdown-item { + background-color: #008acb; + color: #fff; +} +.token-input-list { + list-style: none; + margin: 0; + padding: 0; +} +.token-input-list li { + text-align: start; + list-style: none; +} +.token-input-list li input { + border: 0; + background-color: white; +} +.pkt_ext_containersaved .token-input-token { + background: none; + border-radius: 4px; + border: 1px solid #c3c3c3; + overflow: hidden; + margin: 0 0.2em; + padding: 0 8px; + background-color: #f7f7f7; + color: #000; + font-weight: normal; + cursor: default; + line-height: 1.5; + display: block; + width: auto; + float: inline-start; +} +.pkt_ext_containersaved .pkt_ext_tag_input_wrapper_disabled { + position: relative; +} +.pkt_ext_containersaved .pkt_ext_tag_input_wrapper_disabled input { + opacity: 0.5; +} +.pkt_ext_containersaved .pkt_ext_tag_input_wrapper_disabled .token-input-list { + opacity: 0.5; +} +.pkt_ext_containersaved .pkt_ext_tag_input_wrapper_disabled .pkt_ext_tag_input_blocker { + height: 100%; + inset-inline-start: 0; + position: absolute; + top: 0; + width: 100%; + z-index: 5; +} +.pkt_ext_containersaved .token-input-token p { + display: inline-block; + font-size: 1em; + font-weight: normal; + line-height: inherit; + letter-spacing: normal; + padding: 0; + margin: 0; + text-transform: none; + vertical-align: top; + width: auto; + unicode-bidi: plaintext; +} +.pkt_ext_containersaved .token-input-token p:before { + content: none; + width: 0; +} +.pkt_ext_containersaved .token-input-token span { + background: url(../img/tag_close@1x.png) center center no-repeat; + cursor: pointer; + display: inline-block; + height: 8px; + margin-block: 0; + margin-inline: 8px 0; + overflow: hidden; + width: 8px; + text-indent: -99px; +} +@media (min-resolution: 1.1dppx) { + .pkt_ext_containersaved .token-input-token span { + background-image: url(../img/tag_close@2x.png); + background-size: 8px 8px; + } +} +.pkt_ext_containersaved .token-input-selected-token { + background-color: #008acb; + border-color: #008acb; + color: #fff; +} +.pkt_ext_containersaved .token-input-selected-token span { + background-image: url(../img/tag_closeactive@1x.png); +} +@media (min-resolution: 1.1dppx) { + .pkt_ext_containersaved .token-input-selected-token span { + background-image: url(../img/tag_closeactive@2x.png); + background-size: 8px 8px; + } +} +.pkt_ext_containersaved .pkt_ext_tag_input_wrapper_disabled .token-input-selected-token { + background-color: #f7f7f7; +} +.pkt_ext_containersaved .pkt_ext_tag_input_wrapper_disabled .token-input-selected-token span { + color: #bbb; +} + +/*=Language overrides +--------------------------------------------------------------------------------------- */ +.pkt_ext_saved_es .pkt_ext_btn { + min-width: 5em; +} +.pkt_ext_saved_de .pkt_ext_btn, +.pkt_ext_saved_ru .pkt_ext_btn { + min-width: 6em; +} + +/*=Coral Button +--------------------------------------------------------------------------------------- */ +button { + padding: 0; + margin: 0; + background: none; + border: 0; + outline: none; + color: inherit; + font: inherit; + overflow: visible; +} + +.pkt_ext_button { + padding: 3px; + background-color: #EF4056; + color: #FFF; + text-align: center; + cursor: pointer; + height: 32px; + box-sizing: border-box; + width: 320px; + margin: 0 auto; + border-radius: 2px; + font-size: 1em; +} + +.pkt_ext_button:hover, +.pkt_ext_button:active { + background-color: #d5374b; +} + +/* alt button */ +.pkt_ext_blue_button { + background-color: #0060df; + color: #FFF; +} + +.pkt_ext_blue_button:hover { + background-color: #003eaa; +} + +.pkt_ext_blue_button:active { + background-color: #002275; +} + +.pkt_ext_ffx_icon:after { + position: absolute; + height: 22px; + width: 22px; + top: -3px; + inset-inline-start: -28px; + content: ""; + background-image: url(../img/signup_firefoxlogo@2x.png); + background-size: 22px 22px; + background-repeat: no-repeat; +} + +.pkt_ext_subshell { + display: none; + border-top: 1px solid #c1c1c1; + background: #ebebeb; + width: 100%; +} + +.pkt_ext_subshell hr { + display: none; +} + +.recs_enabled .pkt_ext_subshell hr { + display: block; + border: 0; + border-top: 1px solid #D7D7DB; + margin: 0; +} + +.pkt_ext_item_recs { + text-align: start; + margin: 0 auto; + padding: 0.25em 1em; +} + +.pkt_ext_item_recs header { + display: flex; + justify-content: space-between; + align-items: center; + margin-top: 12px; +} + +.pkt_ext_item_recs header h4 { + color: #333; + font-size: 1em; + font-weight: normal; + font-style: normal; + letter-spacing: normal; + margin: 0.5em 0; + text-align: start; + text-transform: none; +} + +.pkt_ext_item_recs header a { + font-style: normal; + font-weight: 500; + font-size: 1em; + line-height: 20px; + color: #0095DD; +} + +.pkt_ext_containersaved .pkt_ext_item_recs ol { + padding: 0; + margin: 0 0 10px; + list-style: none; +} + +.pkt_ext_containersaved .pkt_ext_item_recs li { + float: none; + display: flex; + font-style: normal; + font-weight: normal; + line-height: 18px; + margin: 0 -1em; + min-height: 60px; +} + +.pkt_ext_containersaved .pkt_ext_item_recs li a { + padding-block: 8px; + padding-inline: 1em 40px; + background: url(../img/open.svg) top 8px right 14px no-repeat; + flex-grow: 1; +} + +.pkt_ext_containersaved .pkt_ext_item_recs li a:dir(rtl) { + background-position-x: left 14px; +} + +.pkt_ext_containersaved .pkt_ext_item_recs li:hover, +.pkt_ext_containersaved .pkt_ext_item_recs li a:focus { + background-color: rgba(12, 12, 13, 0.1); +} + +.pkt_ext_containersaved .pkt_ext_item_recs li:active { + background-color: rgba(12, 12, 13, 0.2); +} + +.pkt_ext_containersaved .pkt_ext_item_recs .pkt_ext_item_recs_link:hover { + text-decoration: none; +} + +.pkt_ext_containersaved .pkt_ext_item_recs .rec-thumb { + width: 40px; + height: 40px; + float: inline-start; + margin: 0; + margin-inline-end: 12px; + border-radius: 2px; +} + +.pkt_ext_containersaved .pkt_ext_item_recs .rec-thumb:-moz-broken { + display: none; +} + +.pkt_ext_containersaved .pkt_ext_item_recs p { + -webkit-box-orient: vertical; + display: -webkit-box; + overflow: hidden; + word-break: break-word; + font-style: normal; + font-weight: normal; + margin: 0; +} + +.pkt_ext_containersaved .pkt_ext_item_recs .rec-title { + -webkit-line-clamp: 2; + font-size: 1em; + line-height: 18px; + color: #0C0C0D; +} + +.pkt_ext_containersaved .pkt_ext_item_recs .rec-source { + -webkit-line-clamp: 1; + font-size: 0.9em; + line-height: 16px; + color: #737373; +} diff --git a/browser/components/pocket/content/panels/css/signup.scss b/browser/components/pocket/content/panels/css/signup.scss new file mode 100644 index 0000000000..da35a58eec --- /dev/null +++ b/browser/components/pocket/content/panels/css/signup.scss @@ -0,0 +1,360 @@ +/* signup.css + * + * Description: + * With base elements out of the way, this sets all custom styling for the extension. + * + * Contents: + * Global + * Core detail + * Core detail - storyboard + * Buttons + * Overflow mode + * Language overrides + */ + +/*=Global +--------------------------------------------------------------------------------------- */ +.pkt_ext_containersignup { + background-color: #ebebeb; + color: #333; + display: block; + margin: 0; + padding: 0; + position: relative; + text-align: center; + overflow: hidden; +} +.pkt_ext_containersignup_inactive { + animation: pkt_ext_hide 0.3s ease-out; + opacity: 0; + visibility: hidden; +} +.pkt_ext_cf:after { + content: " "; + display: table; + clear: both; +} +@keyframes pkt_ext_hide { + 0% { + opacity: 1; + visibility: visible; + } + 99% { + opacity: 0; + visibility: visible; + } + 100% { + opacity: 0; + visibility: hidden; + } +} + +/*=Core detail +--------------------------------------------------------------------------------------- */ +.pkt_ext_containersignup p { + font-size: 1em; + color: #333; + line-height: 1.3; + margin: 0 auto 1.5em; + max-width: 260px; +} +.pkt_ext_containersignup a { + color: #4c8fd0; +} +.pkt_ext_containersignup a:hover { + color: #3076b9; +} +.pkt_ext_containersignup .pkt_ext_introdetail { + background-color: #fbfbfb; + border: 1px solid #c1c1c1; + border-width: 0 0 1px; +} +.pkt_ext_containersignup .pkt_ext_logo { + background: url(../img/pocketlogo@1x.png) center bottom no-repeat; + display: block; + height: 32px; + margin: 0 auto 15px; + padding-top: 25px; + position: relative; + text-indent: -9999px; + width: 123px; +} +@media (min-resolution: 1.1dppx) { + .pkt_ext_containersignup .pkt_ext_logo { + background-image: url(../img/pocketlogo@2x.png); + background-size: 123px 32px; + } +} +.pkt_ext_containersignup .pkt_ext_introimg { + background: url(../img/pocketsignup_hero@1x.png) center center no-repeat; + display: block; + height: 125px; + margin: 0 auto; + position: relative; + text-indent: -9999px; + width: 255px; +} +@media (min-resolution: 1.1dppx) { + .pkt_ext_containersignup .pkt_ext_introimg { + background-image: url(../img/pocketsignup_hero@2x.png); + background-size: 255px 125px; + } +} +.pkt_ext_containersignup .pkt_ext_tagline { + margin-bottom: 0.5em; +} +.pkt_ext_containersignup .pkt_ext_learnmore { + font-size: 0.9em; +} +.pkt_ext_signupdetail { + overflow: hidden; +} +.pkt_ext_signupdetail h4 { + font-size: 0.9em; + font-weight: normal; +} +.pkt_ext_signupdetail .btn-container { + position: relative; + margin-bottom: 0.8em; +} +.pkt_ext_containersignup .ff_signuphelp { + background: url(../img/signup_help@1x.png) center center no-repeat; + display: block; + height: 18px; + margin-top: -9px; + inset-inline-end: -15px; + position: absolute; + text-indent: -9999px; + width: 18px; + top: 50%; +} +@media (min-resolution: 1.1dppx) { + .pkt_ext_containersignup .ff_signuphelp { + background-image: url(../img/signup_help@2x.png); + background-size: 18px 18px; + } +} +.pkt_ext_containersignup .alreadyhave { + font-size: 0.9em; + max-width: 320px; + margin-top: 15px; +} + +/*=Core detail - storyboard +--------------------------------------------------------------------------------------- */ +.pkt_ext_introstory { + align-items: center; + display: flex; + padding: 20px; +} +.pkt_ext_introstory:after { + clear: both; + content: ""; + display: table; +} +.pkt_ext_introstory p { + margin-bottom: 0; + text-align: start; +} +.pkt_ext_introstoryone { + padding-block: 20px 15px; + padding-inline: 20px 18px; +} +.pkt_ext_introstorytwo { + padding-block: 3px 0; + padding-inline: 20px 0; +} +.pkt_ext_introstorytwo .pkt_ext_tagline { + margin-bottom: 1.5em; +} +.pkt_ext_introstory_text { + flex: 1; +} +.pkt_ext_introstoryone_img, +.pkt_ext_introstorytwo_img { + display: block; + overflow: hidden; + position: relative; + text-indent: -999px; +} +.pkt_ext_introstoryone_img { + background: url(../img/pocketsignup_button@1x.png) center right no-repeat; + height: 82px; + padding-block: 0; + padding-inline: 0.7em 0; + width: 82px; +} +.pkt_ext_introstoryone_img:dir(rtl) { + background-position-x: left; +} +@media (min-resolution: 1.1dppx) { + .pkt_ext_introstoryone_img { + background-image: url(../img/pocketsignup_button@2x.png); + background-size: 82px 82px; + } +} +.pkt_ext_introstorytwo_img { + background: url(../img/pocketsignup_devices@1x.png) bottom right no-repeat; + height: 110px; + padding-block: 1em 0; + padding-inline: 0.7em 0; + width: 124px; +} +.pkt_ext_introstorytwo_img:dir(rtl) { + background-position-x: left; +} +@media (min-resolution: 1.1dppx) { + .pkt_ext_introstorytwo_img { + background-image: url(../img/pocketsignup_devices@2x.png); + background-size: 124px 110px; + } +} +.pkt_ext_introstorydivider { + border-top: 1px solid #c1c1c1; + height: 1px; + margin: 0 auto; + width: 125px; +} + +/*=Buttons +--------------------------------------------------------------------------------------- */ +.pkt_ext_containersignup .btn { + background-color: #0096dd; + border: 1px solid #0095dd; + border-radius: 2px; + color: #fff; + display: inline-block; + font-size: 1.1em; + font-weight: normal; + line-height: 1; + margin: 0; + padding: 11px 45px; + text-align: center; + text-decoration: none; + text-shadow: 0 -1px 0 rgba(142,4,17,0.5); + transition: background-color 0.1s linear; + width: auto; +} +.pkt_ext_containersignup .btn-secondary { + background-color: #fbfbfb; + border-color: #c1c1c1; + color: #444; + text-shadow: 0 1px 0 rgba(255,255,255,0.5); +} +.pkt_ext_containersignup .btn-small { + padding: 6px 20px; +} +.pkt_ext_containersignup .btn:hover { + background-color: #008acb; + color: #fff; + text-decoration: none; +} +.pkt_ext_containersignup .btn-secondary:hover, +.pkt_ext_containersignup .btn-important:hover { + background-color: #f6f6f6; + color: #222; +} +.pkt_ext_containersignup .btn-disabled { + background-image: none; + color: #ccc; + color: rgba(255,255,255,0.6); + cursor: default; + opacity: 0.9; +} +.pkt_ext_containersignup .signup-btn-firefox, +.pkt_ext_containersignup .signup-btn-email, +.pkt_ext_containersignup .signupinterim-btn-login, +.pkt_ext_containersignup .signupinterim-btn-signup, +.pkt_ext_containersignup .forgot-btn-submit, +.pkt_ext_containersignup .forgotreset-btn-change { + min-width: 12.125em; + padding: 0.8em 1.1875em; + box-sizing: content-box; +} +.pkt_ext_containersignup .signup-btn-email { + position: relative; + z-index: 10; +} +.pkt_ext_containersignup .signup-btn-firefox { + min-width: 14.5em; + position: relative; + padding: 0; +} +.pkt_ext_containersignup .signup-btn-firefox .logo { + background: url(../img/signup_firefoxlogo@1x.png) center center no-repeat; + height: 2.6em; + inset-inline-start: 10px; + margin: 0; + padding: 0; + width: 22px; + position: absolute; +} +@media (min-resolution: 1.1dppx) { + .pkt_ext_containersignup .signup-btn-firefox .logo { + background-image: url(../img/signup_firefoxlogo@2x.png); + background-size: 22px 22px; + } +} +.pkt_ext_containersignup .forgotreset-btn-change { + margin-bottom: 2em; +} +.pkt_ext_containersignup .signup-btn-firefox .text { + display: inline-block; + padding: 0.8em 1.625em; + position: relative; + text-shadow: none; + white-space: nowrap; +} +.pkt_ext_containersignup .signup-btn-firefox .text { + color: #fff; +} +.pkt_ext_containersignup .btn-disabled .text { + color: #ccc; + color: rgba(255,255,255,0.6); +} + +/*=Language overrides +--------------------------------------------------------------------------------------- */ +.pkt_ext_signup_de .pkt_ext_introstoryone_img { + margin-inline-end: -5px; + padding-inline-start: 0; +} +.pkt_ext_signup_de .pkt_ext_introstorytwo .pkt_ext_tagline, +.pkt_ext_signup_es .pkt_ext_introstorytwo .pkt_ext_tagline, +.pkt_ext_signup_ja .pkt_ext_introstorytwo .pkt_ext_tagline, +.pkt_ext_signup_ru .pkt_ext_introstorytwo .pkt_ext_tagline { + margin-bottom: 0.5em; +} +.pkt_ext_signup_ja .signup-btn-firefox .text, +.pkt_ext_signup_ru .signup-btn-firefox .text { + inset-inline-start: 15px; +} +.pkt_ext_signup_de .signup-btn-firefox .logo, +.pkt_ext_signup_ja .signup-btn-firefox .logo, +.pkt_ext_signup_ru .signup-btn-firefox .logo { + height: 2.4em; +} +@media (min-resolution: 1.1dppx) { + .pkt_ext_signup_de .signup-btn-firefox .logo, + .pkt_ext_signup_ja .signup-btn-firefox .logo, + .pkt_ext_signup_ru .signup-btn-firefox .logo { + height: 2.5em; + } +} +.pkt_ext_signup_de .signup-btn-email, +.pkt_ext_signup_ja .signup-btn-email, +.pkt_ext_signup_ru .signup-btn-email { + min-width: 13em; + padding: 0.8533em 1.2667em; +} +.pkt_ext_signup_de .pkt_ext_logo, +.pkt_ext_signup_es .pkt_ext_logo, +.pkt_ext_signup_ru .pkt_ext_logo { + padding-top: 15px; +} +.pkt_ext_signup_overflow.pkt_ext_signup_de .signup-btn-firefox .logo, +.pkt_ext_signup_overflow.pkt_ext_signup_es .signup-btn-firefox .logo, +.pkt_ext_signup_overflow.pkt_ext_signup_ja .signup-btn-firefox .logo, +.pkt_ext_signup_overflow.pkt_ext_signup_ru .signup-btn-firefox .logo { + display: none; +} diff --git a/browser/components/pocket/content/panels/css/styleguide.scss b/browser/components/pocket/content/panels/css/styleguide.scss new file mode 100644 index 0000000000..9310d6ba98 --- /dev/null +++ b/browser/components/pocket/content/panels/css/styleguide.scss @@ -0,0 +1,28 @@ +#stp_style_guide { + #dark_mode_toggle { + text-align: end; + } + + border: 1px solid #ddd; + margin: 20px auto; + padding: 20px; + width: 260px; + + @include theme_dark { + background: #42414c; + } + + .stp_superheader { + margin: 0; + } + + .stp_styleguide_h4 { + border-bottom: 1px solid #ccc; + margin: 20px 0; + } + + .stp_styleguide_h5 { + font-size: 10px; + margin: 10px 0; + } +} diff --git a/browser/components/pocket/content/panels/fonts/FiraSans-Regular.woff b/browser/components/pocket/content/panels/fonts/FiraSans-Regular.woff new file mode 100644 index 0000000000..f466cdda9b Binary files /dev/null and b/browser/components/pocket/content/panels/fonts/FiraSans-Regular.woff differ diff --git a/browser/components/pocket/content/panels/home.html b/browser/components/pocket/content/panels/home.html new file mode 100644 index 0000000000..44f087af90 --- /dev/null +++ b/browser/components/pocket/content/panels/home.html @@ -0,0 +1,20 @@ + + + + + + + + + Pocket: Home + + + + + + + + diff --git a/browser/components/pocket/content/panels/img/chevron-right.svg b/browser/components/pocket/content/panels/img/chevron-right.svg new file mode 100644 index 0000000000..ea4f72e649 --- /dev/null +++ b/browser/components/pocket/content/panels/img/chevron-right.svg @@ -0,0 +1,3 @@ + + + diff --git a/browser/components/pocket/content/panels/img/list-view.svg b/browser/components/pocket/content/panels/img/list-view.svg new file mode 100644 index 0000000000..234d1866ea --- /dev/null +++ b/browser/components/pocket/content/panels/img/list-view.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/browser/components/pocket/content/panels/img/open.svg b/browser/components/pocket/content/panels/img/open.svg new file mode 100644 index 0000000000..49406f04ba --- /dev/null +++ b/browser/components/pocket/content/panels/img/open.svg @@ -0,0 +1,3 @@ + + + diff --git a/browser/components/pocket/content/panels/img/pocketerror@1x.png b/browser/components/pocket/content/panels/img/pocketerror@1x.png new file mode 100644 index 0000000000..059812678a Binary files /dev/null and b/browser/components/pocket/content/panels/img/pocketerror@1x.png differ diff --git a/browser/components/pocket/content/panels/img/pocketerror@2x.png b/browser/components/pocket/content/panels/img/pocketerror@2x.png new file mode 100644 index 0000000000..f462f30a6c Binary files /dev/null and b/browser/components/pocket/content/panels/img/pocketerror@2x.png differ diff --git a/browser/components/pocket/content/panels/img/pocketlogo-dark.svg b/browser/components/pocket/content/panels/img/pocketlogo-dark.svg new file mode 100644 index 0000000000..7ec3f7dac0 --- /dev/null +++ b/browser/components/pocket/content/panels/img/pocketlogo-dark.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/browser/components/pocket/content/panels/img/pocketlogo.svg b/browser/components/pocket/content/panels/img/pocketlogo.svg new file mode 100644 index 0000000000..4b8dc67d6c --- /dev/null +++ b/browser/components/pocket/content/panels/img/pocketlogo.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/browser/components/pocket/content/panels/img/pocketlogo@1x.png b/browser/components/pocket/content/panels/img/pocketlogo@1x.png new file mode 100644 index 0000000000..4ae1a84dc9 Binary files /dev/null and b/browser/components/pocket/content/panels/img/pocketlogo@1x.png differ diff --git a/browser/components/pocket/content/panels/img/pocketlogo@2x.png b/browser/components/pocket/content/panels/img/pocketlogo@2x.png new file mode 100644 index 0000000000..86d4264d06 Binary files /dev/null and b/browser/components/pocket/content/panels/img/pocketlogo@2x.png differ diff --git a/browser/components/pocket/content/panels/img/pocketlogosolo@1x.png b/browser/components/pocket/content/panels/img/pocketlogosolo@1x.png new file mode 100644 index 0000000000..0af50f1f10 Binary files /dev/null and b/browser/components/pocket/content/panels/img/pocketlogosolo@1x.png differ diff --git a/browser/components/pocket/content/panels/img/pocketlogosolo@2x.png b/browser/components/pocket/content/panels/img/pocketlogosolo@2x.png new file mode 100644 index 0000000000..e3e203172f Binary files /dev/null and b/browser/components/pocket/content/panels/img/pocketlogosolo@2x.png differ diff --git a/browser/components/pocket/content/panels/img/pocketsignup_button@1x.png b/browser/components/pocket/content/panels/img/pocketsignup_button@1x.png new file mode 100644 index 0000000000..e0cb05a51a Binary files /dev/null and b/browser/components/pocket/content/panels/img/pocketsignup_button@1x.png differ diff --git a/browser/components/pocket/content/panels/img/pocketsignup_button@2x.png b/browser/components/pocket/content/panels/img/pocketsignup_button@2x.png new file mode 100644 index 0000000000..6f26cee95d Binary files /dev/null and b/browser/components/pocket/content/panels/img/pocketsignup_button@2x.png differ diff --git a/browser/components/pocket/content/panels/img/pocketsignup_devices@1x.png b/browser/components/pocket/content/panels/img/pocketsignup_devices@1x.png new file mode 100644 index 0000000000..effa073c60 Binary files /dev/null and b/browser/components/pocket/content/panels/img/pocketsignup_devices@1x.png differ diff --git a/browser/components/pocket/content/panels/img/pocketsignup_devices@2x.png b/browser/components/pocket/content/panels/img/pocketsignup_devices@2x.png new file mode 100644 index 0000000000..8a539070ad Binary files /dev/null and b/browser/components/pocket/content/panels/img/pocketsignup_devices@2x.png differ diff --git a/browser/components/pocket/content/panels/img/pocketsignup_hero@1x.png b/browser/components/pocket/content/panels/img/pocketsignup_hero@1x.png new file mode 100644 index 0000000000..6659c0843c Binary files /dev/null and b/browser/components/pocket/content/panels/img/pocketsignup_hero@1x.png differ diff --git a/browser/components/pocket/content/panels/img/pocketsignup_hero@2x.png b/browser/components/pocket/content/panels/img/pocketsignup_hero@2x.png new file mode 100644 index 0000000000..d31d610f54 Binary files /dev/null and b/browser/components/pocket/content/panels/img/pocketsignup_hero@2x.png differ diff --git a/browser/components/pocket/content/panels/img/rainbow-reader.svg b/browser/components/pocket/content/panels/img/rainbow-reader.svg new file mode 100644 index 0000000000..ce3cf54fb4 --- /dev/null +++ b/browser/components/pocket/content/panels/img/rainbow-reader.svg @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/browser/components/pocket/content/panels/img/signup_firefoxlogo@1x.png b/browser/components/pocket/content/panels/img/signup_firefoxlogo@1x.png new file mode 100644 index 0000000000..d8c9474477 Binary files /dev/null and b/browser/components/pocket/content/panels/img/signup_firefoxlogo@1x.png differ diff --git a/browser/components/pocket/content/panels/img/signup_firefoxlogo@2x.png b/browser/components/pocket/content/panels/img/signup_firefoxlogo@2x.png new file mode 100644 index 0000000000..c027d53d3f Binary files /dev/null and b/browser/components/pocket/content/panels/img/signup_firefoxlogo@2x.png differ diff --git a/browser/components/pocket/content/panels/img/signup_help@1x.png b/browser/components/pocket/content/panels/img/signup_help@1x.png new file mode 100644 index 0000000000..e11d56b6a8 Binary files /dev/null and b/browser/components/pocket/content/panels/img/signup_help@1x.png differ diff --git a/browser/components/pocket/content/panels/img/signup_help@2x.png b/browser/components/pocket/content/panels/img/signup_help@2x.png new file mode 100644 index 0000000000..2b37abf0e0 Binary files /dev/null and b/browser/components/pocket/content/panels/img/signup_help@2x.png differ diff --git a/browser/components/pocket/content/panels/img/tag_close@1x.png b/browser/components/pocket/content/panels/img/tag_close@1x.png new file mode 100644 index 0000000000..334ad03f70 Binary files /dev/null and b/browser/components/pocket/content/panels/img/tag_close@1x.png differ diff --git a/browser/components/pocket/content/panels/img/tag_close@2x.png b/browser/components/pocket/content/panels/img/tag_close@2x.png new file mode 100644 index 0000000000..c6834cc305 Binary files /dev/null and b/browser/components/pocket/content/panels/img/tag_close@2x.png differ diff --git a/browser/components/pocket/content/panels/img/tag_closeactive@1x.png b/browser/components/pocket/content/panels/img/tag_closeactive@1x.png new file mode 100644 index 0000000000..196004b2f8 Binary files /dev/null and b/browser/components/pocket/content/panels/img/tag_closeactive@1x.png differ diff --git a/browser/components/pocket/content/panels/img/tag_closeactive@2x.png b/browser/components/pocket/content/panels/img/tag_closeactive@2x.png new file mode 100644 index 0000000000..a1512f6ada Binary files /dev/null and b/browser/components/pocket/content/panels/img/tag_closeactive@2x.png differ diff --git a/browser/components/pocket/content/panels/js/components/ArticleList/ArticleList.jsx b/browser/components/pocket/content/panels/js/components/ArticleList/ArticleList.jsx new file mode 100644 index 0000000000..25679bc638 --- /dev/null +++ b/browser/components/pocket/content/panels/js/components/ArticleList/ArticleList.jsx @@ -0,0 +1,139 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import React, { useState } from "react"; +import TelemetryLink from "../TelemetryLink/TelemetryLink"; + +function ArticleUrl(props) { + // We turn off the link if we're either a saved article, or if the url doesn't exist. + if (props.savedArticle || !props.url) { + return ( +
{props.children}
+ ); + } + return ( + + {props.children} + + ); +} + +function Article(props) { + function encodeThumbnail(rawSource) { + return rawSource + ? `https://img-getpocket.cdn.mozilla.net/80x80/filters:format(jpeg):quality(60):no_upscale():strip_exif()/${encodeURIComponent( + rawSource + )}` + : null; + } + + const [thumbnailLoaded, setThumbnailLoaded] = useState(false); + const [thumbnailLoadFailed, setThumbnailLoadFailed] = useState(false); + + const { + article, + savedArticle, + position, + source, + model, + utmParams, + openInPocketReader, + } = props; + + if (!article.url && !article.resolved_url && !article.given_url) { + return null; + } + const url = new URL(article.url || article.resolved_url || article.given_url); + const urlSearchParams = new URLSearchParams(utmParams); + + if ( + openInPocketReader && + article.item_id && + !url.href.match(/getpocket\.com\/read/) + ) { + url.href = `https://getpocket.com/read/${article.item_id}`; + } + + for (let [key, val] of urlSearchParams.entries()) { + url.searchParams.set(key, val); + } + + // Using array notation because there is a key titled `1` (`images` is an object) + const thumbnail = + article.thumbnail || + encodeThumbnail(article?.top_image_url || article?.images?.["1"]?.src); + const alt = article.alt || "thumbnail image"; + const title = article.title || article.resolved_title || article.given_title; + // Sometimes domain_metadata is not there, depending on the source. + const publisher = + article.publisher || + article.domain_metadata?.name || + article.resolved_domain; + + return ( +
  • + + <> + {thumbnail && !thumbnailLoadFailed ? ( + {alt} { + setThumbnailLoaded(true); + }} + onError={() => { + setThumbnailLoadFailed(true); + }} + style={{ + visibility: thumbnailLoaded ? `visible` : `hidden`, + }} + /> + ) : ( +
    + )} +
    +
    {title}
    +

    {publisher}

    +
    + + +
  • + ); +} + +function ArticleList(props) { + return ( +
      + {props.articles?.map((article, position) => ( +
      + ))} +
    + ); +} + +export default ArticleList; diff --git a/browser/components/pocket/content/panels/js/components/ArticleList/ArticleList.scss b/browser/components/pocket/content/panels/js/components/ArticleList/ArticleList.scss new file mode 100644 index 0000000000..261367433d --- /dev/null +++ b/browser/components/pocket/content/panels/js/components/ArticleList/ArticleList.scss @@ -0,0 +1,65 @@ +.stp_article_list { + padding: 0; + list-style: none; + + .stp_article_list_saved_article, + .stp_article_list_link { + display: flex; + border-radius: 4px; + padding: 8px; + margin: 0 -8px; + } + + .stp_article_list_link { + &:hover, &:focus { + text-decoration: none; + background-color: #ECECEE; + + @include theme_dark { + background-color: #2B2A33; + } + } + } + + .stp_article_list_thumb, + .stp_article_list_thumb_placeholder { + width: 40px; + height: 40px; + border-radius: 4px; + margin-inline-end: 8px; + background-color: #ECECEE; + flex-shrink: 0; + } + + .stp_article_list_header { + font-style: normal; + font-weight: 600; + font-size: 0.95rem; + line-height: 1.18rem; + color: #15141A; + margin: 0 0 4px; + + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + display: -webkit-box; + overflow: hidden; + word-break: break-word; + + @include theme_dark { + color: #FBFBFE; + } + } + + .stp_article_list_publisher { + font-style: normal; + font-weight: normal; + font-size: 0.95rem; + line-height: 1.18rem; + color: #52525E; + margin: 4px 0 0; + + @include theme_dark { + color: #CFCFD8; + } + } +} diff --git a/browser/components/pocket/content/panels/js/components/Button/Button.jsx b/browser/components/pocket/content/panels/js/components/Button/Button.jsx new file mode 100644 index 0000000000..7f3d2ea7ce --- /dev/null +++ b/browser/components/pocket/content/panels/js/components/Button/Button.jsx @@ -0,0 +1,21 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import React from "react"; +import TelemetryLink from "../TelemetryLink/TelemetryLink"; + +function Button(props) { + return ( + + {props.children} + + ); +} + +export default Button; diff --git a/browser/components/pocket/content/panels/js/components/Button/Button.scss b/browser/components/pocket/content/panels/js/components/Button/Button.scss new file mode 100644 index 0000000000..c6001e19da --- /dev/null +++ b/browser/components/pocket/content/panels/js/components/Button/Button.scss @@ -0,0 +1,142 @@ +.stp_button { + cursor: pointer; + display: inline-block; + margin: 12px 0; + + &:hover { + text-decoration: none; + } + + &.stp_button_text { + color: #0060DF; + font-size: 0.95rem; + line-height: 1.2rem; + font-style: normal; + font-weight: 600; + + &:focus { + text-decoration: underline; + } + + &:hover { + color: #0250BB; + text-decoration: none; + } + + &:active { + color: #054096; + } + + @include theme_dark { + color: #00DDFF; + } + } + + &.stp_button_primary { + align-items: center; + background: #0060DF; + border-radius: 4px; + color: #FBFBFE; + font-size: 0.85rem; + line-height: 1rem; + font-style: normal; + font-weight: 600; + justify-content: center; + padding: 6px 12px; + + &:focus { + text-decoration: none; + background: #0060DF; + outline: 2px solid #0060df; + outline-offset: 2px; + } + + &:hover { + background: #0250BB; + } + + &:active { + background: #054096; + } + + @include theme_dark { + background: #00DDFF; + color: #15141A; + + &:hover { + background: #80ebfe; + } + + &:focus { + outline: 2px solid #00DDFF; + } + } + } + + &.stp_button_secondary { + align-items: center; + background: #F0F0F4; + border-radius: 4px; + color: #15141A; + font-size: 0.85rem; + line-height: 1rem; + font-style: normal; + font-weight: 600; + padding: 6px 12px; + + &:focus { + text-decoration: none; + background: #F0F0F4; + outline: 2px solid #0060df; + outline-offset: 2px; + } + + &:hover { + background: #E0E0E6; + } + + &:active { + background: #CFCFD8; + } + + @include theme_dark { + background: #2B2A33; + color: #FBFBFE; + + &:focus { + outline: 2px solid #00DDFF; + } + + &:hover { + background: #53535d; + } + } + } +} + +.stp_button_wide { + .stp_button { + display: block; + margin: 12px 0; + text-align: center; + padding: 8px 12px; + + &.stp_button_primary { + font-size: 1.1rem; + line-height: 1.35rem; + } + + &.stp_button_secondary { + font-size: 0.85rem; + line-height: 1rem; + } + } +} + +.stp_button_wide { + .stp_button { + display: block; + margin: 12px 0; + text-align: center; + } +} diff --git a/browser/components/pocket/content/panels/js/components/Header/Header.jsx b/browser/components/pocket/content/panels/js/components/Header/Header.jsx new file mode 100644 index 0000000000..be60fe764c --- /dev/null +++ b/browser/components/pocket/content/panels/js/components/Header/Header.jsx @@ -0,0 +1,16 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import React from "react"; + +function Header(props) { + return ( +

    +
    + {props.children} +

    + ); +} + +export default Header; diff --git a/browser/components/pocket/content/panels/js/components/Header/Header.scss b/browser/components/pocket/content/panels/js/components/Header/Header.scss new file mode 100644 index 0000000000..f6e4eca9d5 --- /dev/null +++ b/browser/components/pocket/content/panels/js/components/Header/Header.scss @@ -0,0 +1,22 @@ +.stp_header { + display: flex; + justify-content: space-between; + align-items: center; + margin: 16px 0 12px; + font-weight: 600; + + .stp_header_logo { + background: url(../img/pocketlogo.svg) bottom center no-repeat; + background-size: contain; + height: 32px; + width: 121px; + + @include theme_dark { + background-image: url(../img/pocketlogo-dark.svg); + } + } + + .stp_button { + margin: 0; + } +} diff --git a/browser/components/pocket/content/panels/js/components/Home/Home.jsx b/browser/components/pocket/content/panels/js/components/Home/Home.jsx new file mode 100644 index 0000000000..fc936bfa56 --- /dev/null +++ b/browser/components/pocket/content/panels/js/components/Home/Home.jsx @@ -0,0 +1,168 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import React, { useState, useEffect } from "react"; +import Header from "../Header/Header"; +import ArticleList from "../ArticleList/ArticleList"; +import PopularTopics from "../PopularTopics/PopularTopics"; +import Button from "../Button/Button"; +import panelMessaging from "../../messages"; + +function Home(props) { + const { + locale, + topics, + pockethost, + hideRecentSaves, + utmSource, + utmCampaign, + utmContent, + } = props; + const [{ articles, status }, setArticlesState] = useState({ + articles: [], + // Can be success, loading, or error. + status: "", + }); + + const utmParams = `utm_source=${utmSource}${ + utmCampaign && utmContent + ? `&utm_campaign=${utmCampaign}&utm_content=${utmContent}` + : `` + }`; + + useEffect(() => { + if (!hideRecentSaves) { + // We don't display the loading message until instructed. This is because cache + // loads should be fast, so using the loading message for cache just adds loading jank. + panelMessaging.addMessageListener( + "PKT_loadingRecentSaves", + function (resp) { + setArticlesState({ + articles, + status: "loading", + }); + } + ); + + panelMessaging.addMessageListener( + "PKT_renderRecentSaves", + function (resp) { + const { data } = resp; + + if (data.status === "error") { + setArticlesState({ + articles: [], + status: "error", + }); + return; + } + + setArticlesState({ + articles: data, + status: "success", + }); + } + ); + } + + // tell back end we're ready + panelMessaging.sendMessage("PKT_show_home"); + }, []); + + let recentSavesSection = null; + + if (status === "error" || hideRecentSaves) { + recentSavesSection = ( +

    + ); + } else if (status === "loading") { + recentSavesSection = ( + + ); + } else if (status === "success") { + if (articles?.length) { + recentSavesSection = ( + <> +

    + {articles.length > 3 ? ( + <> + + + + + + ) : ( + + )} + + ); + } else { + recentSavesSection = ( + <> +

    +

    + + ); + } + } + + return ( +
    +
    +
    + +
    +
    + {recentSavesSection} +
    + {pockethost && locale?.startsWith("en") && topics?.length && ( + <> +

    Explore popular topics:

    + + + )} +
    +
    + ); +} + +export default Home; diff --git a/browser/components/pocket/content/panels/js/components/PopularTopics/PopularTopics.jsx b/browser/components/pocket/content/panels/js/components/PopularTopics/PopularTopics.jsx new file mode 100644 index 0000000000..517bd6d53b --- /dev/null +++ b/browser/components/pocket/content/panels/js/components/PopularTopics/PopularTopics.jsx @@ -0,0 +1,27 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import React from "react"; +import TelemetryLink from "../TelemetryLink/TelemetryLink"; + +function PopularTopics(props) { + return ( +
      + {props.topics?.map((topic, position) => ( +
    • + + {topic.title} + +
    • + ))} +
    + ); +} + +export default PopularTopics; diff --git a/browser/components/pocket/content/panels/js/components/PopularTopics/PopularTopics.scss b/browser/components/pocket/content/panels/js/components/PopularTopics/PopularTopics.scss new file mode 100644 index 0000000000..bacd55ee42 --- /dev/null +++ b/browser/components/pocket/content/panels/js/components/PopularTopics/PopularTopics.scss @@ -0,0 +1,54 @@ +.stp_popular_topics { + padding: 0; + + .stp_popular_topic { + display: inline-block; + + .stp_popular_topic_link { + display: inline-block; + background: #F0F0F4; + border-radius: 4px; + font-size: 0.85rem; + line-height: 1rem; + font-style: normal; + font-weight: 600; + margin-inline-end: 8px; + margin-bottom: 8px; + padding: 4px 8px; + color: #000; + + &:focus { + text-decoration: none; + background: #F0F0F4; + outline: 2px solid #0060df; + outline-offset: 2px; + } + + &:hover { + background: #E0E0E6; + text-decoration: none; + } + + &:active { + background: #CFCFD8; + } + + &::after { + content: " >"; + } + + @include theme_dark { + background: #2B2A33; + color: #FBFBFE; + + &:focus { + outline: 2px solid #00DDFF; + } + + &:hover { + background: #53535d; + } + } + } + } +} diff --git a/browser/components/pocket/content/panels/js/components/Saved/Saved.jsx b/browser/components/pocket/content/panels/js/components/Saved/Saved.jsx new file mode 100644 index 0000000000..f81c6df198 --- /dev/null +++ b/browser/components/pocket/content/panels/js/components/Saved/Saved.jsx @@ -0,0 +1,203 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import React, { useState, useEffect } from "react"; +import Header from "../Header/Header"; +import Button from "../Button/Button"; +import ArticleList from "../ArticleList/ArticleList"; +import TagPicker from "../TagPicker/TagPicker"; +import panelMessaging from "../../messages"; + +function Saved(props) { + const { locale, pockethost, utmSource, utmCampaign, utmContent } = props; + // savedStatus can be success, loading, or error. + const [{ savedStatus, savedErrorId, itemId, itemUrl }, setSavedStatusState] = + useState({ savedStatus: "loading" }); + // removedStatus can be removed, removing, or error. + const [{ removedStatus, removedErrorMessage }, setRemovedStatusState] = + useState({}); + const [savedStory, setSavedStoryState] = useState(); + const [articleInfoAttempted, setArticleInfoAttempted] = useState(); + const [{ similarRecs, similarRecsModel }, setSimilarRecsState] = useState({}); + const utmParams = `utm_source=${utmSource}${ + utmCampaign && utmContent + ? `&utm_campaign=${utmCampaign}&utm_content=${utmContent}` + : `` + }`; + + function removeItem(event) { + event.preventDefault(); + setRemovedStatusState({ removedStatus: "removing" }); + panelMessaging.sendMessage( + "PKT_deleteItem", + { + itemId, + }, + function (resp) { + const { data } = resp; + if (data.status == "success") { + setRemovedStatusState({ removedStatus: "removed" }); + } else if (data.status == "error") { + let errorMessage = ""; + // The server returns English error messages, so in the case of + // non English, we do our best with a generic translated error. + if (data.error.message && locale?.startsWith("en")) { + errorMessage = data.error.message; + } + setRemovedStatusState({ + removedStatus: "error", + removedErrorMessage: errorMessage, + }); + } + } + ); + } + + useEffect(() => { + // Wait confirmation of save before flipping to final saved state + panelMessaging.addMessageListener("PKT_saveLink", function (resp) { + const { data } = resp; + if (data.status == "error") { + // Use localizedKey or fallback to a generic catch all error. + setSavedStatusState({ + savedStatus: "error", + savedErrorId: + data?.error?.localizedKey || "pocket-panel-saved-error-generic", + }); + return; + } + + // Success, so no localized error id needed. + setSavedStatusState({ + savedStatus: "success", + itemId: data.item?.item_id, + itemUrl: data.item?.given_url, + savedErrorId: "", + }); + }); + + panelMessaging.addMessageListener( + "PKT_articleInfoFetched", + function (resp) { + setSavedStoryState(resp?.data?.item_preview); + } + ); + + panelMessaging.addMessageListener( + "PKT_getArticleInfoAttempted", + function (resp) { + setArticleInfoAttempted(true); + } + ); + + panelMessaging.addMessageListener("PKT_renderItemRecs", function (resp) { + const { data } = resp; + + // This is the ML model used to recommend the story. + // Right now this value is the same for all three items returned together, + // so we can just use the first item's value for all. + const model = data?.recommendations?.[0]?.experiment || ""; + setSimilarRecsState({ + similarRecs: data?.recommendations?.map(rec => rec.item), + similarRecsModel: model, + }); + }); + + // tell back end we're ready + panelMessaging.sendMessage("PKT_show_saved"); + }, []); + + if (savedStatus === "error") { + return ( +
    +
    +
    +

    +

    +

    +
    + ); + } + + return ( +
    +
    +
    + +
    +
    + {!removedStatus && savedStatus === "success" && ( + <> +

    + + +

    + {savedStory && ( + + )} + {articleInfoAttempted && } + {articleInfoAttempted && + similarRecs?.length && + locale?.startsWith("en") && ( + <> +
    +

    Similar Stories

    + + + )} + + )} + {savedStatus === "loading" && ( +

    + )} + {removedStatus === "removing" && ( +

    + )} + {removedStatus === "removed" && ( +

    + )} + {removedStatus === "error" && ( + <> +

    + {removedErrorMessage &&

    {removedErrorMessage}

    } + + )} +

    +
    + ); +} + +export default Saved; diff --git a/browser/components/pocket/content/panels/js/components/Saved/Saved.scss b/browser/components/pocket/content/panels/js/components/Saved/Saved.scss new file mode 100644 index 0000000000..08003f14e1 --- /dev/null +++ b/browser/components/pocket/content/panels/js/components/Saved/Saved.scss @@ -0,0 +1,17 @@ +body { + &.stp_saved_body { + overflow: hidden; + } +} + +.stp_panel_error { + margin: 23px 0 32px; + .stp_panel_error_icon { + float: inline-start; + margin-block: 6px 16px; + margin-inline: 7px 17px; + background-image: url(../img/pocketerror@1x.png); + height: 44px; + width: 44px; + } +} diff --git a/browser/components/pocket/content/panels/js/components/Signup/Signup.jsx b/browser/components/pocket/content/panels/js/components/Signup/Signup.jsx new file mode 100644 index 0000000000..7e628c0ad5 --- /dev/null +++ b/browser/components/pocket/content/panels/js/components/Signup/Signup.jsx @@ -0,0 +1,79 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import React from "react"; +import Header from "../Header/Header"; +import Button from "../Button/Button"; + +function Signup(props) { + const { locale, pockethost, utmSource, utmCampaign, utmContent } = props; + const utmParams = `utm_source=${utmSource}${ + utmCampaign && utmContent + ? `&utm_campaign=${utmCampaign}&utm_content=${utmContent}` + : `` + }`; + return ( +
    +
    +
    + +
    +
    + {locale?.startsWith("en") ? ( + <> +
    +

    +

    +

    +
    +
    +
    +
    +
    +

    + Get thought-provoking article recommendations +

    +

    + Find stories that go deep into a subject or offer a new + perspective. +

    +
    + + ) : ( +
    +

    +

    + +

    + +

    + )} +
    + + + +
    +
    + ); +} + +export default Signup; diff --git a/browser/components/pocket/content/panels/js/components/Signup/Signup.scss b/browser/components/pocket/content/panels/js/components/Signup/Signup.scss new file mode 100644 index 0000000000..21b34ddcb6 --- /dev/null +++ b/browser/components/pocket/content/panels/js/components/Signup/Signup.scss @@ -0,0 +1,19 @@ +body { + &.stp_signup_body { + overflow: hidden; + } +} + +.stp_panel_signup { + .stp_signup_content_wrapper { + margin: 12px 0 20px; + } + .stp_signup_img_rainbow_reader { + background: url(../img/rainbow-reader.svg) bottom center no-repeat; + background-size: contain; + height: 72px; + width: 82px; + float: inline-end; + margin-inline-start: 16px; + } +} diff --git a/browser/components/pocket/content/panels/js/components/TagPicker/TagPicker.jsx b/browser/components/pocket/content/panels/js/components/TagPicker/TagPicker.jsx new file mode 100644 index 0000000000..9c1f658a80 --- /dev/null +++ b/browser/components/pocket/content/panels/js/components/TagPicker/TagPicker.jsx @@ -0,0 +1,208 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import React, { useState, useEffect } from "react"; +import panelMessaging from "../../messages"; + +function TagPicker(props) { + const [tags, setTags] = useState(props.tags); // New tag group to store + const [allTags, setAllTags] = useState([]); // All tags ever used (in no particular order) + const [recentTags, setRecentTags] = useState([]); // Most recently used tags + const [duplicateTag, setDuplicateTag] = useState(null); + const [inputValue, setInputValue] = useState(""); + + // Status can be success, waiting, or error. + const [{ tagInputStatus, tagInputErrorMessage }, setTagInputStatus] = + useState({ + tagInputStatus: "", + tagInputErrorMessage: "", + }); + + let handleKeyDown = e => { + const enterKey = e.keyCode === 13; + const commaKey = e.keyCode === 188; + const tabKey = inputValue && e.keyCode === 9; + + // Submit tags on enter with no input. + // Enter tag on comma, tab, or enter with input. + // Tab to next element with no input. + if (commaKey || enterKey || tabKey) { + e.preventDefault(); + if (inputValue) { + addTag(inputValue.trim()); + setInputValue(``); // Clear out input + } else if (enterKey) { + submitTags(); + } + } + }; + + let addTag = tagToAdd => { + if (!tagToAdd?.length) { + return; + } + + let newDuplicateTag = tags.find(item => item === tagToAdd); + + if (!newDuplicateTag) { + setTags([...tags, tagToAdd]); + } else { + setDuplicateTag(newDuplicateTag); + + setTimeout(() => { + setDuplicateTag(null); + }, 1000); + } + }; + + let removeTag = index => { + let updatedTags = tags.slice(0); // Shallow copied array + updatedTags.splice(index, 1); + setTags(updatedTags); + }; + + let submitTags = () => { + let tagsToSubmit = []; + + if (tags?.length) { + tagsToSubmit = tags; + } + + // Capture tags that have been typed in but not explicitly added to the tag collection + if (inputValue?.trim().length) { + tagsToSubmit.push(inputValue.trim()); + } + + if (!props.itemUrl || !tagsToSubmit?.length) { + return; + } + + setTagInputStatus({ + tagInputStatus: "waiting", + tagInputErrorMessage: "", + }); + panelMessaging.sendMessage( + "PKT_addTags", + { + url: props.itemUrl, + tags: tagsToSubmit, + }, + function (resp) { + const { data } = resp; + + if (data.status === "success") { + setTagInputStatus({ + tagInputStatus: "success", + tagInputErrorMessage: "", + }); + } else if (data.status === "error") { + setTagInputStatus({ + tagInputStatus: "error", + tagInputErrorMessage: data.error.message, + }); + } + } + ); + }; + + useEffect(() => { + panelMessaging.sendMessage("PKT_getTags", {}, resp => { + setAllTags(resp?.data?.tags); + }); + }, []); + + useEffect(() => { + panelMessaging.sendMessage("PKT_getRecentTags", {}, resp => { + setRecentTags(resp?.data?.recentTags); + }); + }, []); + + return ( +
    + {!tagInputStatus && ( + <> +

    +
    + {tags.map((tag, i) => ( +
    + + {tag} +
    + ))} +
    + setInputValue(e.target.value)} + onKeyDown={e => handleKeyDown(e)} + maxlength="25" + /> + + {allTags + .sort((a, b) => a.search(inputValue) - b.search(inputValue)) + .map(item => ( + +
    +
    +
    + {recentTags + .slice(0, 3) + .filter(recentTag => { + return !tags.find(item => item === recentTag); + }) + .map(tag => ( +
    + +
    + ))} +
    + + )} + {tagInputStatus === "waiting" && ( +

    + )} + {tagInputStatus === "success" && ( +

    + )} + {tagInputStatus === "error" && ( +

    {tagInputErrorMessage}

    + )} +
    + ); +} + +export default TagPicker; diff --git a/browser/components/pocket/content/panels/js/components/TagPicker/TagPicker.scss b/browser/components/pocket/content/panels/js/components/TagPicker/TagPicker.scss new file mode 100644 index 0000000000..215307d079 --- /dev/null +++ b/browser/components/pocket/content/panels/js/components/TagPicker/TagPicker.scss @@ -0,0 +1,141 @@ +.stp_tag_picker { + .stp_tag_picker_tags { + display: flex; + align-items: center; + gap: 8px; + flex-wrap: wrap; + padding: 8px; + border: 1px solid #8F8F9D; + border-radius: 4px; + font-style: normal; + font-weight: normal; + font-size: 1rem; + line-height: 1.2rem; + color: #15141A; + margin-bottom: 10px; + } + + .stp_tag_picker_tag { + background: #F0F0F4; + border-radius: 4px; + color: #15141A; + display: inline-block; + font-size: 0.85rem; + line-height: 1rem; + font-style: normal; + font-weight: 600; + padding: 0 8px; + transition: background-color 200ms ease-in-out; + + @include theme_dark { + background: #2B2A33; + color: #FBFBFB; + } + } + + .recent_tags .stp_tag_picker_tag { + margin-inline-end: 5px; + } + + .stp_tag_picker_tag_remove { + padding-top: 5px; + padding-bottom: 5px; + padding-inline-end: 5px; + color: #5B5B66; + font-weight: 400; + + &:hover { + color: #3E3E44; + } + + &:focus { + color: #3E3E44; + outline: 2px solid #0060df; + outline-offset: -4px; + } + + @include theme_dark { + color: #8F8F9D; + + &:hover { + color: #fff; + } + + &:focus { + outline: 2px solid #00DDFF; + } + } + } + + .stp_tag_picker_tag_duplicate { + background-color: #bbb; + + @include theme_dark { + background-color: #666; + } + } + + .stp_tag_picker_input_wrapper { + display: flex; + flex-grow: 1; + } + + .stp_tag_picker_input { + flex-grow: 1; + border: 1px solid #8F8F9D; + padding: 0 6px; + border-start-start-radius: 4px; + border-end-start-radius: 4px; + + &:focus { + border: 1px solid #0060DF; + outline: 1px solid #0060DF; + } + + @include theme_dark { + background: none; + color: #FBFBFB; + + &:focus { + border: 1px solid #00DDFF; + outline: 1px solid #00DDFF; + } + } + } + + .stp_tag_picker_button { + font-size: 0.95rem; + line-height: 1.1rem; + padding: 4px 6px; + background-color: #F0F0F4; + border: 1px solid #8F8F9D; + border-inline-start: none; + border-start-end-radius: 4px; + border-end-end-radius: 4px; + &:disabled { + color: #8F8F9D; + } + &:hover:enabled { + background-color: #DADADF; + } + &:focus:enabled { + border: 1px solid #0060DF; + outline: 1px solid #0060DF; + } + + @include theme_dark { + background-color: #2B2A33; + color: #FBFBFB; + &:disabled { + color: #666; + } + &:hover:enabled { + background-color: #53535d; + } + &:focus:enabled { + border: 1px solid #00DDFF; + outline: 1px solid #00DDFF; + } + } + } +} diff --git a/browser/components/pocket/content/panels/js/components/TelemetryLink/TelemetryLink.jsx b/browser/components/pocket/content/panels/js/components/TelemetryLink/TelemetryLink.jsx new file mode 100644 index 0000000000..c23a24897f --- /dev/null +++ b/browser/components/pocket/content/panels/js/components/TelemetryLink/TelemetryLink.jsx @@ -0,0 +1,35 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import React from "react"; +import panelMessaging from "../../messages"; + +function TelemetryLink(props) { + function onClick(event) { + if (props.onClick) { + props.onClick(event); + } else { + event.preventDefault(); + panelMessaging.sendMessage("PKT_openTabWithUrl", { + url: event.currentTarget.getAttribute(`href`), + source: props.source, + model: props.model, + position: props.position, + }); + } + } + + return ( + + {props.children} + + ); +} + +export default TelemetryLink; diff --git a/browser/components/pocket/content/panels/js/home/entry.js b/browser/components/pocket/content/panels/js/home/entry.js new file mode 100644 index 0000000000..56d8134577 --- /dev/null +++ b/browser/components/pocket/content/panels/js/home/entry.js @@ -0,0 +1,17 @@ +/* global PKT_PANEL:false */ + +function onDOMLoaded() { + if (!window.thePKT_PANEL) { + var thePKT_PANEL = new PKT_PANEL(); + /* global thePKT_PANEL */ + window.thePKT_PANEL = thePKT_PANEL; + thePKT_PANEL.initHome(); + } + window.thePKT_PANEL.create(); +} + +if (document.readyState != `loading`) { + onDOMLoaded(); +} else { + document.addEventListener(`DOMContentLoaded`, onDOMLoaded); +} diff --git a/browser/components/pocket/content/panels/js/home/overlay.js b/browser/components/pocket/content/panels/js/home/overlay.js new file mode 100644 index 0000000000..89c881ecee --- /dev/null +++ b/browser/components/pocket/content/panels/js/home/overlay.js @@ -0,0 +1,62 @@ +/* global Handlebars:false */ + +/* +HomeOverlay is the view itself and contains all of the methods to manipute the overlay and messaging. +It does not contain any logic for saving or communication with the extension or server. +*/ + +import React from "react"; +import ReactDOM from "react-dom"; +import Home from "../components/Home/Home"; + +var HomeOverlay = function (options) { + this.inited = false; + this.active = false; +}; + +HomeOverlay.prototype = { + create({ pockethost }) { + const { searchParams } = new URL(window.location.href); + const locale = searchParams.get(`locale`) || ``; + const hideRecentSaves = searchParams.get(`hiderecentsaves`) === `true`; + const utmSource = searchParams.get(`utmSource`); + const utmCampaign = searchParams.get(`utmCampaign`); + const utmContent = searchParams.get(`utmContent`); + + if (this.active) { + return; + } + + this.active = true; + + ReactDOM.render( + , + document.querySelector(`body`) + ); + + if (window?.matchMedia(`(prefers-color-scheme: dark)`).matches) { + document.querySelector(`body`).classList.add(`theme_dark`); + } + }, +}; + +export default HomeOverlay; diff --git a/browser/components/pocket/content/panels/js/main.bundle.js b/browser/components/pocket/content/panels/js/main.bundle.js new file mode 100644 index 0000000000..3bd91b9472 --- /dev/null +++ b/browser/components/pocket/content/panels/js/main.bundle.js @@ -0,0 +1,1355 @@ +/******/ (() => { // webpackBootstrap +/******/ "use strict"; +/******/ var __webpack_modules__ = ({ + +/***/ 299: +/***/ ((__unused_webpack_module, __unused_webpack___webpack_exports__, __webpack_require__) => { + + +// EXTERNAL MODULE: ./node_modules/react/index.js +var react = __webpack_require__(294); +// EXTERNAL MODULE: ./node_modules/react-dom/index.js +var react_dom = __webpack_require__(935); +;// CONCATENATED MODULE: ./content/panels/js/components/Header/Header.jsx +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + + +function Header(props) { + return /*#__PURE__*/react.createElement("h1", { + className: "stp_header" + }, /*#__PURE__*/react.createElement("div", { + className: "stp_header_logo" + }), props.children); +} + +/* harmony default export */ const Header_Header = (Header); +;// CONCATENATED MODULE: ./content/panels/js/messages.js +/* global RPMRemoveMessageListener:false, RPMAddMessageListener:false, RPMSendAsyncMessage:false */ +var pktPanelMessaging = { + removeMessageListener(messageId, callback) { + RPMRemoveMessageListener(messageId, callback); + }, + + addMessageListener(messageId, callback = () => {}) { + RPMAddMessageListener(messageId, callback); + }, + + sendMessage(messageId, payload = {}, callback) { + if (callback) { + // If we expect something back, we use RPMSendAsyncMessage and not RPMSendQuery. + // Even though RPMSendQuery returns something, our frame could be closed at any moment, + // and we don't want to close a RPMSendQuery promise loop unexpectedly. + // So instead we setup a response event. + const responseMessageId = `${messageId}_response`; + + var responseListener = responsePayload => { + callback(responsePayload); + this.removeMessageListener(responseMessageId, responseListener); + }; + + this.addMessageListener(responseMessageId, responseListener); + } // Send message + + + RPMSendAsyncMessage(messageId, payload); + }, + + // Click helper to reduce bugs caused by oversight + // from different implementations of similar code. + clickHelper(element, { + source = "", + position + }) { + element?.addEventListener(`click`, event => { + event.preventDefault(); + this.sendMessage("PKT_openTabWithUrl", { + url: event.currentTarget.getAttribute(`href`), + source, + position + }); + }); + }, + + log() { + RPMSendAsyncMessage("PKT_log", arguments); + } + +}; +/* harmony default export */ const messages = (pktPanelMessaging); +;// CONCATENATED MODULE: ./content/panels/js/components/TelemetryLink/TelemetryLink.jsx +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + + + +function TelemetryLink(props) { + function onClick(event) { + if (props.onClick) { + props.onClick(event); + } else { + event.preventDefault(); + messages.sendMessage("PKT_openTabWithUrl", { + url: event.currentTarget.getAttribute(`href`), + source: props.source, + model: props.model, + position: props.position + }); + } + } + + return /*#__PURE__*/react.createElement("a", { + href: props.href, + onClick: onClick, + target: "_blank", + className: props.className + }, props.children); +} + +/* harmony default export */ const TelemetryLink_TelemetryLink = (TelemetryLink); +;// CONCATENATED MODULE: ./content/panels/js/components/ArticleList/ArticleList.jsx +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + + + +function ArticleUrl(props) { + // We turn off the link if we're either a saved article, or if the url doesn't exist. + if (props.savedArticle || !props.url) { + return /*#__PURE__*/react.createElement("div", { + className: "stp_article_list_saved_article" + }, props.children); + } + + return /*#__PURE__*/react.createElement(TelemetryLink_TelemetryLink, { + className: "stp_article_list_link", + href: props.url, + source: props.source, + position: props.position, + model: props.model + }, props.children); +} + +function Article(props) { + function encodeThumbnail(rawSource) { + return rawSource ? `https://img-getpocket.cdn.mozilla.net/80x80/filters:format(jpeg):quality(60):no_upscale():strip_exif()/${encodeURIComponent(rawSource)}` : null; + } + + const [thumbnailLoaded, setThumbnailLoaded] = (0,react.useState)(false); + const [thumbnailLoadFailed, setThumbnailLoadFailed] = (0,react.useState)(false); + const { + article, + savedArticle, + position, + source, + model, + utmParams, + openInPocketReader + } = props; + + if (!article.url && !article.resolved_url && !article.given_url) { + return null; + } + + const url = new URL(article.url || article.resolved_url || article.given_url); + const urlSearchParams = new URLSearchParams(utmParams); + + if (openInPocketReader && article.item_id && !url.href.match(/getpocket\.com\/read/)) { + url.href = `https://getpocket.com/read/${article.item_id}`; + } + + for (let [key, val] of urlSearchParams.entries()) { + url.searchParams.set(key, val); + } // Using array notation because there is a key titled `1` (`images` is an object) + + + const thumbnail = article.thumbnail || encodeThumbnail(article?.top_image_url || article?.images?.["1"]?.src); + const alt = article.alt || "thumbnail image"; + const title = article.title || article.resolved_title || article.given_title; // Sometimes domain_metadata is not there, depending on the source. + + const publisher = article.publisher || article.domain_metadata?.name || article.resolved_domain; + return /*#__PURE__*/react.createElement("li", { + className: "stp_article_list_item" + }, /*#__PURE__*/react.createElement(ArticleUrl, { + url: url.href, + savedArticle: savedArticle, + position: position, + source: source, + model: model, + utmParams: utmParams + }, /*#__PURE__*/react.createElement(react.Fragment, null, thumbnail && !thumbnailLoadFailed ? /*#__PURE__*/react.createElement("img", { + className: "stp_article_list_thumb", + src: thumbnail, + alt: alt, + width: "40", + height: "40", + onLoad: () => { + setThumbnailLoaded(true); + }, + onError: () => { + setThumbnailLoadFailed(true); + }, + style: { + visibility: thumbnailLoaded ? `visible` : `hidden` + } + }) : /*#__PURE__*/react.createElement("div", { + className: "stp_article_list_thumb_placeholder" + }), /*#__PURE__*/react.createElement("div", { + className: "stp_article_list_meta" + }, /*#__PURE__*/react.createElement("header", { + className: "stp_article_list_header" + }, title), /*#__PURE__*/react.createElement("p", { + className: "stp_article_list_publisher" + }, publisher))))); +} + +function ArticleList(props) { + return /*#__PURE__*/react.createElement("ul", { + className: "stp_article_list" + }, props.articles?.map((article, position) => /*#__PURE__*/react.createElement(Article, { + article: article, + savedArticle: props.savedArticle, + position: position, + source: props.source, + model: props.model, + utmParams: props.utmParams, + openInPocketReader: props.openInPocketReader + }))); +} + +/* harmony default export */ const ArticleList_ArticleList = (ArticleList); +;// CONCATENATED MODULE: ./content/panels/js/components/PopularTopics/PopularTopics.jsx +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + + + +function PopularTopics(props) { + return /*#__PURE__*/react.createElement("ul", { + className: "stp_popular_topics" + }, props.topics?.map((topic, position) => /*#__PURE__*/react.createElement("li", { + key: `item-${topic.topic}`, + className: "stp_popular_topic" + }, /*#__PURE__*/react.createElement(TelemetryLink_TelemetryLink, { + className: "stp_popular_topic_link", + href: `https://${props.pockethost}/explore/${topic.topic}?${props.utmParams}`, + source: props.source, + position: position + }, topic.title)))); +} + +/* harmony default export */ const PopularTopics_PopularTopics = (PopularTopics); +;// CONCATENATED MODULE: ./content/panels/js/components/Button/Button.jsx +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + + + +function Button(props) { + return /*#__PURE__*/react.createElement(TelemetryLink_TelemetryLink, { + href: props.url, + onClick: props.onClick, + className: `stp_button${props?.style && ` stp_button_${props.style}`}`, + source: props.source + }, props.children); +} + +/* harmony default export */ const Button_Button = (Button); +;// CONCATENATED MODULE: ./content/panels/js/components/Home/Home.jsx +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + + + + + + + +function Home(props) { + const { + locale, + topics, + pockethost, + hideRecentSaves, + utmSource, + utmCampaign, + utmContent + } = props; + const [{ + articles, + status + }, setArticlesState] = (0,react.useState)({ + articles: [], + // Can be success, loading, or error. + status: "" + }); + const utmParams = `utm_source=${utmSource}${utmCampaign && utmContent ? `&utm_campaign=${utmCampaign}&utm_content=${utmContent}` : ``}`; + (0,react.useEffect)(() => { + if (!hideRecentSaves) { + // We don't display the loading message until instructed. This is because cache + // loads should be fast, so using the loading message for cache just adds loading jank. + messages.addMessageListener("PKT_loadingRecentSaves", function (resp) { + setArticlesState({ + articles, + status: "loading" + }); + }); + messages.addMessageListener("PKT_renderRecentSaves", function (resp) { + const { + data + } = resp; + + if (data.status === "error") { + setArticlesState({ + articles: [], + status: "error" + }); + return; + } + + setArticlesState({ + articles: data, + status: "success" + }); + }); + } // tell back end we're ready + + + messages.sendMessage("PKT_show_home"); + }, []); + let recentSavesSection = null; + + if (status === "error" || hideRecentSaves) { + recentSavesSection = /*#__PURE__*/react.createElement("h3", { + className: "header_medium", + "data-l10n-id": "pocket-panel-home-new-user-cta" + }); + } else if (status === "loading") { + recentSavesSection = /*#__PURE__*/react.createElement("span", { + "data-l10n-id": "pocket-panel-home-most-recent-saves-loading" + }); + } else if (status === "success") { + if (articles?.length) { + recentSavesSection = /*#__PURE__*/react.createElement(react.Fragment, null, /*#__PURE__*/react.createElement("h3", { + className: "header_medium", + "data-l10n-id": "pocket-panel-home-most-recent-saves" + }), articles.length > 3 ? /*#__PURE__*/react.createElement(react.Fragment, null, /*#__PURE__*/react.createElement(ArticleList_ArticleList, { + articles: articles.slice(0, 3), + source: "home_recent_save", + utmParams: utmParams, + openInPocketReader: true + }), /*#__PURE__*/react.createElement("span", { + className: "stp_button_wide" + }, /*#__PURE__*/react.createElement(Button_Button, { + style: "secondary", + url: `https://${pockethost}/a?${utmParams}`, + source: "home_view_list" + }, /*#__PURE__*/react.createElement("span", { + "data-l10n-id": "pocket-panel-button-show-all" + })))) : /*#__PURE__*/react.createElement(ArticleList_ArticleList, { + articles: articles, + source: "home_recent_save", + utmParams: utmParams + })); + } else { + recentSavesSection = /*#__PURE__*/react.createElement(react.Fragment, null, /*#__PURE__*/react.createElement("h3", { + className: "header_medium", + "data-l10n-id": "pocket-panel-home-new-user-cta" + }), /*#__PURE__*/react.createElement("h3", { + className: "header_medium", + "data-l10n-id": "pocket-panel-home-new-user-message" + })); + } + } + + return /*#__PURE__*/react.createElement("div", { + className: "stp_panel_container" + }, /*#__PURE__*/react.createElement("div", { + className: "stp_panel stp_panel_home" + }, /*#__PURE__*/react.createElement(Header_Header, null, /*#__PURE__*/react.createElement(Button_Button, { + style: "primary", + url: `https://${pockethost}/a?${utmParams}`, + source: "home_view_list" + }, /*#__PURE__*/react.createElement("span", { + "data-l10n-id": "pocket-panel-header-my-saves" + }))), /*#__PURE__*/react.createElement("hr", null), recentSavesSection, /*#__PURE__*/react.createElement("hr", null), pockethost && locale?.startsWith("en") && topics?.length && /*#__PURE__*/react.createElement(react.Fragment, null, /*#__PURE__*/react.createElement("h3", { + className: "header_medium" + }, "Explore popular topics:"), /*#__PURE__*/react.createElement(PopularTopics_PopularTopics, { + topics: topics, + pockethost: pockethost, + utmParams: utmParams, + source: "home_popular_topic" + })))); +} + +/* harmony default export */ const Home_Home = (Home); +;// CONCATENATED MODULE: ./content/panels/js/home/overlay.js +/* global Handlebars:false */ + +/* +HomeOverlay is the view itself and contains all of the methods to manipute the overlay and messaging. +It does not contain any logic for saving or communication with the extension or server. +*/ + + + + +var HomeOverlay = function (options) { + this.inited = false; + this.active = false; +}; + +HomeOverlay.prototype = { + create({ + pockethost + }) { + const { + searchParams + } = new URL(window.location.href); + const locale = searchParams.get(`locale`) || ``; + const hideRecentSaves = searchParams.get(`hiderecentsaves`) === `true`; + const utmSource = searchParams.get(`utmSource`); + const utmCampaign = searchParams.get(`utmCampaign`); + const utmContent = searchParams.get(`utmContent`); + + if (this.active) { + return; + } + + this.active = true; + react_dom.render( /*#__PURE__*/react.createElement(Home_Home, { + locale: locale, + hideRecentSaves: hideRecentSaves, + pockethost: pockethost, + utmSource: utmSource, + utmCampaign: utmCampaign, + utmContent: utmContent, + topics: [{ + title: "Technology", + topic: "technology" + }, { + title: "Self Improvement", + topic: "self-improvement" + }, { + title: "Food", + topic: "food" + }, { + title: "Parenting", + topic: "parenting" + }, { + title: "Science", + topic: "science" + }, { + title: "Entertainment", + topic: "entertainment" + }, { + title: "Career", + topic: "career" + }, { + title: "Health", + topic: "health" + }, { + title: "Travel", + topic: "travel" + }, { + title: "Must-Reads", + topic: "must-reads" + }] + }), document.querySelector(`body`)); + + if (window?.matchMedia(`(prefers-color-scheme: dark)`).matches) { + document.querySelector(`body`).classList.add(`theme_dark`); + } + } + +}; +/* harmony default export */ const overlay = (HomeOverlay); +;// CONCATENATED MODULE: ./content/panels/js/components/Signup/Signup.jsx +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + + + + +function Signup(props) { + const { + locale, + pockethost, + utmSource, + utmCampaign, + utmContent + } = props; + const utmParams = `utm_source=${utmSource}${utmCampaign && utmContent ? `&utm_campaign=${utmCampaign}&utm_content=${utmContent}` : ``}`; + return /*#__PURE__*/react.createElement("div", { + className: "stp_panel_container" + }, /*#__PURE__*/react.createElement("div", { + className: "stp_panel stp_panel_signup" + }, /*#__PURE__*/react.createElement(Header_Header, null, /*#__PURE__*/react.createElement(Button_Button, { + style: "secondary", + url: `https://${pockethost}/login?${utmParams}`, + source: "log_in" + }, /*#__PURE__*/react.createElement("span", { + "data-l10n-id": "pocket-panel-signup-login" + }))), /*#__PURE__*/react.createElement("hr", null), locale?.startsWith("en") ? /*#__PURE__*/react.createElement(react.Fragment, null, /*#__PURE__*/react.createElement("div", { + className: "stp_signup_content_wrapper" + }, /*#__PURE__*/react.createElement("h3", { + className: "header_medium", + "data-l10n-id": "pocket-panel-signup-cta-a-fix" + }), /*#__PURE__*/react.createElement("p", { + "data-l10n-id": "pocket-panel-signup-cta-b-updated" + })), /*#__PURE__*/react.createElement("div", { + className: "stp_signup_content_wrapper" + }, /*#__PURE__*/react.createElement("hr", null)), /*#__PURE__*/react.createElement("div", { + className: "stp_signup_content_wrapper" + }, /*#__PURE__*/react.createElement("div", { + className: "stp_signup_img_rainbow_reader" + }), /*#__PURE__*/react.createElement("h3", { + className: "header_medium" + }, "Get thought-provoking article recommendations"), /*#__PURE__*/react.createElement("p", null, "Find stories that go deep into a subject or offer a new perspective."))) : /*#__PURE__*/react.createElement("div", { + className: "stp_signup_content_wrapper" + }, /*#__PURE__*/react.createElement("h3", { + className: "header_large", + "data-l10n-id": "pocket-panel-signup-cta-a-fix" + }), /*#__PURE__*/react.createElement("p", { + "data-l10n-id": "pocket-panel-signup-cta-b-short" + }), /*#__PURE__*/react.createElement("strong", null, /*#__PURE__*/react.createElement("p", { + "data-l10n-id": "pocket-panel-signup-cta-c-updated" + }))), /*#__PURE__*/react.createElement("hr", null), /*#__PURE__*/react.createElement("span", { + className: "stp_button_wide" + }, /*#__PURE__*/react.createElement(Button_Button, { + style: "primary", + url: `https://${pockethost}/ff_signup?${utmParams}`, + source: "sign_up_1" + }, /*#__PURE__*/react.createElement("span", { + "data-l10n-id": "pocket-panel-button-activate" + }))))); +} + +/* harmony default export */ const Signup_Signup = (Signup); +;// CONCATENATED MODULE: ./content/panels/js/signup/overlay.js +/* +SignupOverlay is the view itself and contains all of the methods to manipute the overlay and messaging. +It does not contain any logic for saving or communication with the extension or server. +*/ + + + + + +var SignupOverlay = function (options) { + this.inited = false; + this.active = false; + + this.create = function ({ + pockethost + }) { + // Extract local variables passed into template via URL query params + const { + searchParams + } = new URL(window.location.href); + const locale = searchParams.get(`locale`) || ``; + const utmSource = searchParams.get(`utmSource`); + const utmCampaign = searchParams.get(`utmCampaign`); + const utmContent = searchParams.get(`utmContent`); + + if (this.active) { + return; + } + + this.active = true; // Create actual content + + react_dom.render( /*#__PURE__*/react.createElement(Signup_Signup, { + pockethost: pockethost, + utmSource: utmSource, + utmCampaign: utmCampaign, + utmContent: utmContent, + locale: locale + }), document.querySelector(`body`)); + + if (window?.matchMedia(`(prefers-color-scheme: dark)`).matches) { + document.querySelector(`body`).classList.add(`theme_dark`); + } // tell back end we're ready + + + messages.sendMessage("PKT_show_signup"); + }; +}; + +/* harmony default export */ const signup_overlay = (SignupOverlay); +;// CONCATENATED MODULE: ./content/panels/js/components/TagPicker/TagPicker.jsx +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + + + +function TagPicker(props) { + const [tags, setTags] = (0,react.useState)(props.tags); // New tag group to store + + const [allTags, setAllTags] = (0,react.useState)([]); // All tags ever used (in no particular order) + + const [recentTags, setRecentTags] = (0,react.useState)([]); // Most recently used tags + + const [duplicateTag, setDuplicateTag] = (0,react.useState)(null); + const [inputValue, setInputValue] = (0,react.useState)(""); // Status can be success, waiting, or error. + + const [{ + tagInputStatus, + tagInputErrorMessage + }, setTagInputStatus] = (0,react.useState)({ + tagInputStatus: "", + tagInputErrorMessage: "" + }); + + let handleKeyDown = e => { + const enterKey = e.keyCode === 13; + const commaKey = e.keyCode === 188; + const tabKey = inputValue && e.keyCode === 9; // Submit tags on enter with no input. + // Enter tag on comma, tab, or enter with input. + // Tab to next element with no input. + + if (commaKey || enterKey || tabKey) { + e.preventDefault(); + + if (inputValue) { + addTag(inputValue.trim()); + setInputValue(``); // Clear out input + } else if (enterKey) { + submitTags(); + } + } + }; + + let addTag = tagToAdd => { + if (!tagToAdd?.length) { + return; + } + + let newDuplicateTag = tags.find(item => item === tagToAdd); + + if (!newDuplicateTag) { + setTags([...tags, tagToAdd]); + } else { + setDuplicateTag(newDuplicateTag); + setTimeout(() => { + setDuplicateTag(null); + }, 1000); + } + }; + + let removeTag = index => { + let updatedTags = tags.slice(0); // Shallow copied array + + updatedTags.splice(index, 1); + setTags(updatedTags); + }; + + let submitTags = () => { + let tagsToSubmit = []; + + if (tags?.length) { + tagsToSubmit = tags; + } // Capture tags that have been typed in but not explicitly added to the tag collection + + + if (inputValue?.trim().length) { + tagsToSubmit.push(inputValue.trim()); + } + + if (!props.itemUrl || !tagsToSubmit?.length) { + return; + } + + setTagInputStatus({ + tagInputStatus: "waiting", + tagInputErrorMessage: "" + }); + messages.sendMessage("PKT_addTags", { + url: props.itemUrl, + tags: tagsToSubmit + }, function (resp) { + const { + data + } = resp; + + if (data.status === "success") { + setTagInputStatus({ + tagInputStatus: "success", + tagInputErrorMessage: "" + }); + } else if (data.status === "error") { + setTagInputStatus({ + tagInputStatus: "error", + tagInputErrorMessage: data.error.message + }); + } + }); + }; + + (0,react.useEffect)(() => { + messages.sendMessage("PKT_getTags", {}, resp => { + setAllTags(resp?.data?.tags); + }); + }, []); + (0,react.useEffect)(() => { + messages.sendMessage("PKT_getRecentTags", {}, resp => { + setRecentTags(resp?.data?.recentTags); + }); + }, []); + return /*#__PURE__*/react.createElement("div", { + className: "stp_tag_picker" + }, !tagInputStatus && /*#__PURE__*/react.createElement(react.Fragment, null, /*#__PURE__*/react.createElement("h3", { + className: "header_small", + "data-l10n-id": "pocket-panel-signup-add-tags" + }), /*#__PURE__*/react.createElement("div", { + className: "stp_tag_picker_tags" + }, tags.map((tag, i) => /*#__PURE__*/react.createElement("div", { + className: `stp_tag_picker_tag${duplicateTag === tag ? ` stp_tag_picker_tag_duplicate` : ``}` + }, /*#__PURE__*/react.createElement("button", { + onClick: () => removeTag(i), + className: `stp_tag_picker_tag_remove` + }, "X"), tag)), /*#__PURE__*/react.createElement("div", { + className: "stp_tag_picker_input_wrapper" + }, /*#__PURE__*/react.createElement("input", { + className: "stp_tag_picker_input", + type: "text", + list: "tag-list", + value: inputValue, + onChange: e => setInputValue(e.target.value), + onKeyDown: e => handleKeyDown(e), + maxlength: "25" + }), /*#__PURE__*/react.createElement("datalist", { + id: "tag-list" + }, allTags.sort((a, b) => a.search(inputValue) - b.search(inputValue)).map(item => /*#__PURE__*/react.createElement("option", { + key: item, + value: item + }))), /*#__PURE__*/react.createElement("button", { + className: "stp_tag_picker_button", + disabled: !inputValue?.length && !tags.length, + "data-l10n-id": "pocket-panel-saved-save-tags", + onClick: () => submitTags() + }))), /*#__PURE__*/react.createElement("div", { + className: "recent_tags" + }, recentTags.slice(0, 3).filter(recentTag => { + return !tags.find(item => item === recentTag); + }).map(tag => /*#__PURE__*/react.createElement("div", { + className: "stp_tag_picker_tag" + }, /*#__PURE__*/react.createElement("button", { + className: "stp_tag_picker_tag_remove", + onClick: () => addTag(tag) + }, "+ ", tag))))), tagInputStatus === "waiting" && /*#__PURE__*/react.createElement("h3", { + className: "header_large", + "data-l10n-id": "pocket-panel-saved-processing-tags" + }), tagInputStatus === "success" && /*#__PURE__*/react.createElement("h3", { + className: "header_large", + "data-l10n-id": "pocket-panel-saved-tags-saved" + }), tagInputStatus === "error" && /*#__PURE__*/react.createElement("h3", { + className: "header_small" + }, tagInputErrorMessage)); +} + +/* harmony default export */ const TagPicker_TagPicker = (TagPicker); +;// CONCATENATED MODULE: ./content/panels/js/components/Saved/Saved.jsx +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + + + + + + + +function Saved(props) { + const { + locale, + pockethost, + utmSource, + utmCampaign, + utmContent + } = props; // savedStatus can be success, loading, or error. + + const [{ + savedStatus, + savedErrorId, + itemId, + itemUrl + }, setSavedStatusState] = (0,react.useState)({ + savedStatus: "loading" + }); // removedStatus can be removed, removing, or error. + + const [{ + removedStatus, + removedErrorMessage + }, setRemovedStatusState] = (0,react.useState)({}); + const [savedStory, setSavedStoryState] = (0,react.useState)(); + const [articleInfoAttempted, setArticleInfoAttempted] = (0,react.useState)(); + const [{ + similarRecs, + similarRecsModel + }, setSimilarRecsState] = (0,react.useState)({}); + const utmParams = `utm_source=${utmSource}${utmCampaign && utmContent ? `&utm_campaign=${utmCampaign}&utm_content=${utmContent}` : ``}`; + + function removeItem(event) { + event.preventDefault(); + setRemovedStatusState({ + removedStatus: "removing" + }); + messages.sendMessage("PKT_deleteItem", { + itemId + }, function (resp) { + const { + data + } = resp; + + if (data.status == "success") { + setRemovedStatusState({ + removedStatus: "removed" + }); + } else if (data.status == "error") { + let errorMessage = ""; // The server returns English error messages, so in the case of + // non English, we do our best with a generic translated error. + + if (data.error.message && locale?.startsWith("en")) { + errorMessage = data.error.message; + } + + setRemovedStatusState({ + removedStatus: "error", + removedErrorMessage: errorMessage + }); + } + }); + } + + (0,react.useEffect)(() => { + // Wait confirmation of save before flipping to final saved state + messages.addMessageListener("PKT_saveLink", function (resp) { + const { + data + } = resp; + + if (data.status == "error") { + // Use localizedKey or fallback to a generic catch all error. + setSavedStatusState({ + savedStatus: "error", + savedErrorId: data?.error?.localizedKey || "pocket-panel-saved-error-generic" + }); + return; + } // Success, so no localized error id needed. + + + setSavedStatusState({ + savedStatus: "success", + itemId: data.item?.item_id, + itemUrl: data.item?.given_url, + savedErrorId: "" + }); + }); + messages.addMessageListener("PKT_articleInfoFetched", function (resp) { + setSavedStoryState(resp?.data?.item_preview); + }); + messages.addMessageListener("PKT_getArticleInfoAttempted", function (resp) { + setArticleInfoAttempted(true); + }); + messages.addMessageListener("PKT_renderItemRecs", function (resp) { + const { + data + } = resp; // This is the ML model used to recommend the story. + // Right now this value is the same for all three items returned together, + // so we can just use the first item's value for all. + + const model = data?.recommendations?.[0]?.experiment || ""; + setSimilarRecsState({ + similarRecs: data?.recommendations?.map(rec => rec.item), + similarRecsModel: model + }); + }); // tell back end we're ready + + messages.sendMessage("PKT_show_saved"); + }, []); + + if (savedStatus === "error") { + return /*#__PURE__*/react.createElement("div", { + className: "stp_panel_container" + }, /*#__PURE__*/react.createElement("div", { + className: "stp_panel stp_panel_error" + }, /*#__PURE__*/react.createElement("div", { + className: "stp_panel_error_icon" + }), /*#__PURE__*/react.createElement("h3", { + className: "header_large", + "data-l10n-id": "pocket-panel-saved-error-not-saved" + }), /*#__PURE__*/react.createElement("p", { + "data-l10n-id": savedErrorId + }))); + } + + return /*#__PURE__*/react.createElement("div", { + className: "stp_panel_container" + }, /*#__PURE__*/react.createElement("div", { + className: "stp_panel stp_panel_saved" + }, /*#__PURE__*/react.createElement(Header_Header, null, /*#__PURE__*/react.createElement(Button_Button, { + style: "primary", + url: `https://${pockethost}/a?${utmParams}`, + source: "view_list" + }, /*#__PURE__*/react.createElement("span", { + "data-l10n-id": "pocket-panel-header-my-saves" + }))), /*#__PURE__*/react.createElement("hr", null), !removedStatus && savedStatus === "success" && /*#__PURE__*/react.createElement(react.Fragment, null, /*#__PURE__*/react.createElement("h3", { + className: "header_large header_flex" + }, /*#__PURE__*/react.createElement("span", { + "data-l10n-id": "pocket-panel-saved-page-saved-b" + }), /*#__PURE__*/react.createElement(Button_Button, { + style: "text", + onClick: removeItem + }, /*#__PURE__*/react.createElement("span", { + "data-l10n-id": "pocket-panel-button-remove" + }))), savedStory && /*#__PURE__*/react.createElement(ArticleList_ArticleList, { + articles: [savedStory], + openInPocketReader: true, + utmParams: utmParams + }), articleInfoAttempted && /*#__PURE__*/react.createElement(TagPicker_TagPicker, { + tags: [], + itemUrl: itemUrl + }), articleInfoAttempted && similarRecs?.length && locale?.startsWith("en") && /*#__PURE__*/react.createElement(react.Fragment, null, /*#__PURE__*/react.createElement("hr", null), /*#__PURE__*/react.createElement("h3", { + className: "header_medium" + }, "Similar Stories"), /*#__PURE__*/react.createElement(ArticleList_ArticleList, { + articles: similarRecs, + source: "on_save_recs", + model: similarRecsModel, + utmParams: utmParams + }))), savedStatus === "loading" && /*#__PURE__*/react.createElement("h3", { + className: "header_large", + "data-l10n-id": "pocket-panel-saved-saving-tags" + }), removedStatus === "removing" && /*#__PURE__*/react.createElement("h3", { + className: "header_large header_center", + "data-l10n-id": "pocket-panel-saved-processing-remove" + }), removedStatus === "removed" && /*#__PURE__*/react.createElement("h3", { + className: "header_large header_center", + "data-l10n-id": "pocket-panel-saved-removed-updated" + }), removedStatus === "error" && /*#__PURE__*/react.createElement(react.Fragment, null, /*#__PURE__*/react.createElement("h3", { + className: "header_large", + "data-l10n-id": "pocket-panel-saved-error-remove" + }), removedErrorMessage && /*#__PURE__*/react.createElement("p", null, removedErrorMessage)))); +} + +/* harmony default export */ const Saved_Saved = (Saved); +;// CONCATENATED MODULE: ./content/panels/js/saved/overlay.js +/* +SavedOverlay is the view itself and contains all of the methods to manipute the overlay and messaging. +It does not contain any logic for saving or communication with the extension or server. +*/ + + + + +var SavedOverlay = function (options) { + this.inited = false; + this.active = false; +}; + +SavedOverlay.prototype = { + create({ + pockethost + }) { + if (this.active) { + return; + } + + this.active = true; + const { + searchParams + } = new URL(window.location.href); + const locale = searchParams.get(`locale`) || ``; + const utmSource = searchParams.get(`utmSource`); + const utmCampaign = searchParams.get(`utmCampaign`); + const utmContent = searchParams.get(`utmContent`); + react_dom.render( /*#__PURE__*/react.createElement(Saved_Saved, { + locale: locale, + pockethost: pockethost, + utmSource: utmSource, + utmCampaign: utmCampaign, + utmContent: utmContent + }), document.querySelector(`body`)); + + if (window?.matchMedia(`(prefers-color-scheme: dark)`).matches) { + document.querySelector(`body`).classList.add(`theme_dark`); + } + } + +}; +/* harmony default export */ const saved_overlay = (SavedOverlay); +;// CONCATENATED MODULE: ./content/panels/js/style-guide/overlay.js + + + + + + + + +var StyleGuideOverlay = function (options) {}; + +StyleGuideOverlay.prototype = { + create() { + // TODO: Wrap popular topics component in JSX to work without needing an explicit container hierarchy for styling + react_dom.render( /*#__PURE__*/react.createElement("div", null, /*#__PURE__*/react.createElement("h3", null, "JSX Components:"), /*#__PURE__*/react.createElement("h4", { + className: "stp_styleguide_h4" + }, "Buttons"), /*#__PURE__*/react.createElement("h5", { + className: "stp_styleguide_h5" + }, "text"), /*#__PURE__*/react.createElement(Button_Button, { + style: "text", + url: "https://example.org", + source: "styleguide" + }, "Text Button"), /*#__PURE__*/react.createElement("h5", { + className: "stp_styleguide_h5" + }, "primary"), /*#__PURE__*/react.createElement(Button_Button, { + style: "primary", + url: "https://example.org", + source: "styleguide" + }, "Primary Button"), /*#__PURE__*/react.createElement("h5", { + className: "stp_styleguide_h5" + }, "secondary"), /*#__PURE__*/react.createElement(Button_Button, { + style: "secondary", + url: "https://example.org", + source: "styleguide" + }, "Secondary Button"), /*#__PURE__*/react.createElement("h5", { + className: "stp_styleguide_h5" + }, "primary wide"), /*#__PURE__*/react.createElement("span", { + className: "stp_button_wide" + }, /*#__PURE__*/react.createElement(Button_Button, { + style: "primary", + url: "https://example.org", + source: "styleguide" + }, "Primary Wide Button")), /*#__PURE__*/react.createElement("h5", { + className: "stp_styleguide_h5" + }, "secondary wide"), /*#__PURE__*/react.createElement("span", { + className: "stp_button_wide" + }, /*#__PURE__*/react.createElement(Button_Button, { + style: "secondary", + url: "https://example.org", + source: "styleguide" + }, "Secondary Wide Button")), /*#__PURE__*/react.createElement("h4", { + className: "stp_styleguide_h4" + }, "Header"), /*#__PURE__*/react.createElement(Header_Header, null, /*#__PURE__*/react.createElement(Button_Button, { + style: "primary", + url: "https://example.org", + source: "styleguide" + }, "View My List")), /*#__PURE__*/react.createElement("h4", { + className: "stp_styleguide_h4" + }, "PopularTopics"), /*#__PURE__*/react.createElement(PopularTopics_PopularTopics, { + pockethost: `getpocket.com`, + source: `styleguide`, + utmParams: `utm_source=styleguide`, + topics: [{ + title: "Self Improvement", + topic: "self-improvement" + }, { + title: "Food", + topic: "food" + }, { + title: "Entertainment", + topic: "entertainment" + }, { + title: "Science", + topic: "science" + }] + }), /*#__PURE__*/react.createElement("h4", { + className: "stp_styleguide_h4" + }, "ArticleList"), /*#__PURE__*/react.createElement(ArticleList_ArticleList, { + source: `styleguide`, + articles: [{ + title: "Article Title", + publisher: "Publisher", + thumbnail: "https://img-getpocket.cdn.mozilla.net/80x80/https://www.raritanheadwaters.org/wp-content/uploads/2020/04/red-fox.jpg", + url: "https://example.org", + alt: "Alt Text" + }, { + title: "Article Title (No Publisher)", + thumbnail: "https://img-getpocket.cdn.mozilla.net/80x80/https://www.raritanheadwaters.org/wp-content/uploads/2020/04/red-fox.jpg", + url: "https://example.org", + alt: "Alt Text" + }, { + title: "Article Title (No Thumbnail)", + publisher: "Publisher", + url: "https://example.org", + alt: "Alt Text" + }] + }), /*#__PURE__*/react.createElement("h4", { + className: "stp_styleguide_h4" + }, "TagPicker"), /*#__PURE__*/react.createElement(TagPicker_TagPicker, { + tags: [`futurism`, `politics`, `mozilla`] + }), /*#__PURE__*/react.createElement("h3", null, "Typography:"), /*#__PURE__*/react.createElement("h2", { + className: "header_large" + }, ".header_large"), /*#__PURE__*/react.createElement("h3", { + className: "header_medium" + }, ".header_medium"), /*#__PURE__*/react.createElement("p", null, "paragraph"), /*#__PURE__*/react.createElement("h3", null, "Native Elements:"), /*#__PURE__*/react.createElement("h4", { + className: "stp_styleguide_h4" + }, "Horizontal Rule"), /*#__PURE__*/react.createElement("hr", null)), document.querySelector(`#stp_style_guide_components`)); + } + +}; +/* harmony default export */ const style_guide_overlay = (StyleGuideOverlay); +;// CONCATENATED MODULE: ./content/panels/js/main.js +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* global RPMGetStringPref:false */ + + + + + + +var PKT_PANEL = function () {}; + +PKT_PANEL.prototype = { + initHome() { + this.overlay = new overlay(); + this.init(); + }, + + initSignup() { + this.overlay = new signup_overlay(); + this.init(); + }, + + initSaved() { + this.overlay = new saved_overlay(); + this.init(); + }, + + initStyleGuide() { + this.overlay = new style_guide_overlay(); + this.init(); + }, + + setupObservers() { + this.setupMutationObserver(); // Mutation observer isn't always enough for fast loading, static pages. + // Sometimes the mutation observer fires before the page is totally visible. + // In this case, the resize tries to fire with 0 height, + // and because it's a static page, it only does one mutation. + // So in this case, we have a backup intersection observer that fires when + // the page is first visible, and thus, the page is going to guarantee a height. + + this.setupIntersectionObserver(); + }, + + init() { + if (this.inited) { + return; + } + + this.setupObservers(); + this.inited = true; + }, + + resizeParent() { + let clientHeight = document.body.clientHeight; + + if (this.overlay.tagsDropdownOpen) { + clientHeight = Math.max(clientHeight, 252); + } // We can ignore 0 height here. + // We rely on intersection observer to do the + // resize for 0 height loads. + + + if (clientHeight) { + messages.sendMessage("PKT_resizePanel", { + width: document.body.clientWidth, + height: clientHeight + }); + } + }, + + setupIntersectionObserver() { + const observer = new IntersectionObserver(entries => { + if (entries.find(e => e.isIntersecting)) { + this.resizeParent(); + observer.unobserve(document.body); + } + }); + observer.observe(document.body); + }, + + setupMutationObserver() { + // Select the node that will be observed for mutations + const targetNode = document.body; // Options for the observer (which mutations to observe) + + const config = { + attributes: false, + childList: true, + subtree: true + }; // Callback function to execute when mutations are observed + + const callback = (mutationList, observer) => { + mutationList.forEach(mutation => { + switch (mutation.type) { + case "childList": + { + /* One or more children have been added to and/or removed + from the tree. + (See mutation.addedNodes and mutation.removedNodes.) */ + this.resizeParent(); + break; + } + } + }); + }; // Create an observer instance linked to the callback function + + + const observer = new MutationObserver(callback); // Start observing the target node for configured mutations + + observer.observe(targetNode, config); + }, + + create() { + const pockethost = RPMGetStringPref("extensions.pocket.site") || "getpocket.com"; + this.overlay.create({ + pockethost + }); + } + +}; +window.PKT_PANEL = PKT_PANEL; +window.pktPanelMessaging = messages; + +/***/ }) + +/******/ }); +/************************************************************************/ +/******/ // The module cache +/******/ var __webpack_module_cache__ = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ // Check if module is in cache +/******/ var cachedModule = __webpack_module_cache__[moduleId]; +/******/ if (cachedModule !== undefined) { +/******/ return cachedModule.exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ var module = __webpack_module_cache__[moduleId] = { +/******/ // no module.id needed +/******/ // no module.loaded needed +/******/ exports: {} +/******/ }; +/******/ +/******/ // Execute the module function +/******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = __webpack_modules__; +/******/ +/************************************************************************/ +/******/ /* webpack/runtime/chunk loaded */ +/******/ (() => { +/******/ var deferred = []; +/******/ __webpack_require__.O = (result, chunkIds, fn, priority) => { +/******/ if(chunkIds) { +/******/ priority = priority || 0; +/******/ for(var i = deferred.length; i > 0 && deferred[i - 1][2] > priority; i--) deferred[i] = deferred[i - 1]; +/******/ deferred[i] = [chunkIds, fn, priority]; +/******/ return; +/******/ } +/******/ var notFulfilled = Infinity; +/******/ for (var i = 0; i < deferred.length; i++) { +/******/ var [chunkIds, fn, priority] = deferred[i]; +/******/ var fulfilled = true; +/******/ for (var j = 0; j < chunkIds.length; j++) { +/******/ if ((priority & 1 === 0 || notFulfilled >= priority) && Object.keys(__webpack_require__.O).every((key) => (__webpack_require__.O[key](chunkIds[j])))) { +/******/ chunkIds.splice(j--, 1); +/******/ } else { +/******/ fulfilled = false; +/******/ if(priority < notFulfilled) notFulfilled = priority; +/******/ } +/******/ } +/******/ if(fulfilled) { +/******/ deferred.splice(i--, 1) +/******/ var r = fn(); +/******/ if (r !== undefined) result = r; +/******/ } +/******/ } +/******/ return result; +/******/ }; +/******/ })(); +/******/ +/******/ /* webpack/runtime/hasOwnProperty shorthand */ +/******/ (() => { +/******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)) +/******/ })(); +/******/ +/******/ /* webpack/runtime/jsonp chunk loading */ +/******/ (() => { +/******/ // no baseURI +/******/ +/******/ // object to store loaded and loading chunks +/******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched +/******/ // [resolve, reject, Promise] = chunk loading, 0 = chunk loaded +/******/ var installedChunks = { +/******/ 179: 0 +/******/ }; +/******/ +/******/ // no chunk on demand loading +/******/ +/******/ // no prefetching +/******/ +/******/ // no preloaded +/******/ +/******/ // no HMR +/******/ +/******/ // no HMR manifest +/******/ +/******/ __webpack_require__.O.j = (chunkId) => (installedChunks[chunkId] === 0); +/******/ +/******/ // install a JSONP callback for chunk loading +/******/ var webpackJsonpCallback = (parentChunkLoadingFunction, data) => { +/******/ var [chunkIds, moreModules, runtime] = data; +/******/ // add "moreModules" to the modules object, +/******/ // then flag all "chunkIds" as loaded and fire callback +/******/ var moduleId, chunkId, i = 0; +/******/ if(chunkIds.some((id) => (installedChunks[id] !== 0))) { +/******/ for(moduleId in moreModules) { +/******/ if(__webpack_require__.o(moreModules, moduleId)) { +/******/ __webpack_require__.m[moduleId] = moreModules[moduleId]; +/******/ } +/******/ } +/******/ if(runtime) var result = runtime(__webpack_require__); +/******/ } +/******/ if(parentChunkLoadingFunction) parentChunkLoadingFunction(data); +/******/ for(;i < chunkIds.length; i++) { +/******/ chunkId = chunkIds[i]; +/******/ if(__webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId]) { +/******/ installedChunks[chunkId][0](); +/******/ } +/******/ installedChunks[chunkId] = 0; +/******/ } +/******/ return __webpack_require__.O(result); +/******/ } +/******/ +/******/ var chunkLoadingGlobal = self["webpackChunksave_to_pocket_ff"] = self["webpackChunksave_to_pocket_ff"] || []; +/******/ chunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0)); +/******/ chunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal)); +/******/ })(); +/******/ +/************************************************************************/ +/******/ +/******/ // startup +/******/ // Load entry module and return exports +/******/ // This entry module depends on other loaded chunks and execution need to be delayed +/******/ var __webpack_exports__ = __webpack_require__.O(undefined, [736], () => (__webpack_require__(299))) +/******/ __webpack_exports__ = __webpack_require__.O(__webpack_exports__); +/******/ +/******/ })() +; \ No newline at end of file diff --git a/browser/components/pocket/content/panels/js/main.bundle.js.LICENSE.txt b/browser/components/pocket/content/panels/js/main.bundle.js.LICENSE.txt new file mode 100644 index 0000000000..f42811bde3 --- /dev/null +++ b/browser/components/pocket/content/panels/js/main.bundle.js.LICENSE.txt @@ -0,0 +1,23 @@ +/*!***********************************!*\ + !*** ./content/panels/js/main.js ***! + \***********************************/ + +/*!***************************************!*\ + !*** ./content/panels/js/messages.js ***! + \***************************************/ + +/*!*******************************************!*\ + !*** ./content/panels/js/home/overlay.js ***! + \*******************************************/ + +/*!********************************************!*\ + !*** ./content/panels/js/saved/overlay.js ***! + \********************************************/ + +/*!*********************************************!*\ + !*** ./content/panels/js/signup/overlay.js ***! + \*********************************************/ + +/*!********************************************************!*\ + !*** ./content/panels/js/components/PopularTopics.jsx ***! + \********************************************************/ diff --git a/browser/components/pocket/content/panels/js/main.js b/browser/components/pocket/content/panels/js/main.js new file mode 100644 index 0000000000..46424d7919 --- /dev/null +++ b/browser/components/pocket/content/panels/js/main.js @@ -0,0 +1,118 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ +/* global RPMGetStringPref:false */ + +import HomeOverlay from "./home/overlay.js"; +import SignupOverlay from "./signup/overlay.js"; +import SavedOverlay from "./saved/overlay.js"; +import StyleGuideOverlay from "./style-guide/overlay.js"; +import pktPanelMessaging from "./messages.js"; + +var PKT_PANEL = function () {}; + +PKT_PANEL.prototype = { + initHome() { + this.overlay = new HomeOverlay(); + this.init(); + }, + + initSignup() { + this.overlay = new SignupOverlay(); + this.init(); + }, + + initSaved() { + this.overlay = new SavedOverlay(); + this.init(); + }, + + initStyleGuide() { + this.overlay = new StyleGuideOverlay(); + this.init(); + }, + + setupObservers() { + this.setupMutationObserver(); + // Mutation observer isn't always enough for fast loading, static pages. + // Sometimes the mutation observer fires before the page is totally visible. + // In this case, the resize tries to fire with 0 height, + // and because it's a static page, it only does one mutation. + // So in this case, we have a backup intersection observer that fires when + // the page is first visible, and thus, the page is going to guarantee a height. + this.setupIntersectionObserver(); + }, + + init() { + if (this.inited) { + return; + } + this.setupObservers(); + this.inited = true; + }, + + resizeParent() { + let clientHeight = document.body.clientHeight; + if (this.overlay.tagsDropdownOpen) { + clientHeight = Math.max(clientHeight, 252); + } + + // We can ignore 0 height here. + // We rely on intersection observer to do the + // resize for 0 height loads. + if (clientHeight) { + pktPanelMessaging.sendMessage("PKT_resizePanel", { + width: document.body.clientWidth, + height: clientHeight, + }); + } + }, + + setupIntersectionObserver() { + const observer = new IntersectionObserver(entries => { + if (entries.find(e => e.isIntersecting)) { + this.resizeParent(); + observer.unobserve(document.body); + } + }); + observer.observe(document.body); + }, + + setupMutationObserver() { + // Select the node that will be observed for mutations + const targetNode = document.body; + + // Options for the observer (which mutations to observe) + const config = { attributes: false, childList: true, subtree: true }; + + // Callback function to execute when mutations are observed + const callback = (mutationList, observer) => { + mutationList.forEach(mutation => { + switch (mutation.type) { + case "childList": { + /* One or more children have been added to and/or removed + from the tree. + (See mutation.addedNodes and mutation.removedNodes.) */ + this.resizeParent(); + break; + } + } + }); + }; + + // Create an observer instance linked to the callback function + const observer = new MutationObserver(callback); + + // Start observing the target node for configured mutations + observer.observe(targetNode, config); + }, + + create() { + const pockethost = + RPMGetStringPref("extensions.pocket.site") || "getpocket.com"; + this.overlay.create({ pockethost }); + }, +}; + +window.PKT_PANEL = PKT_PANEL; +window.pktPanelMessaging = pktPanelMessaging; diff --git a/browser/components/pocket/content/panels/js/messages.js b/browser/components/pocket/content/panels/js/messages.js new file mode 100644 index 0000000000..c91511be43 --- /dev/null +++ b/browser/components/pocket/content/panels/js/messages.js @@ -0,0 +1,50 @@ +/* global RPMRemoveMessageListener:false, RPMAddMessageListener:false, RPMSendAsyncMessage:false */ + +var pktPanelMessaging = { + removeMessageListener(messageId, callback) { + RPMRemoveMessageListener(messageId, callback); + }, + + addMessageListener(messageId, callback = () => {}) { + RPMAddMessageListener(messageId, callback); + }, + + sendMessage(messageId, payload = {}, callback) { + if (callback) { + // If we expect something back, we use RPMSendAsyncMessage and not RPMSendQuery. + // Even though RPMSendQuery returns something, our frame could be closed at any moment, + // and we don't want to close a RPMSendQuery promise loop unexpectedly. + // So instead we setup a response event. + const responseMessageId = `${messageId}_response`; + var responseListener = responsePayload => { + callback(responsePayload); + this.removeMessageListener(responseMessageId, responseListener); + }; + + this.addMessageListener(responseMessageId, responseListener); + } + + // Send message + RPMSendAsyncMessage(messageId, payload); + }, + + // Click helper to reduce bugs caused by oversight + // from different implementations of similar code. + clickHelper(element, { source = "", position }) { + element?.addEventListener(`click`, event => { + event.preventDefault(); + + this.sendMessage("PKT_openTabWithUrl", { + url: event.currentTarget.getAttribute(`href`), + source, + position, + }); + }); + }, + + log() { + RPMSendAsyncMessage("PKT_log", arguments); + }, +}; + +export default pktPanelMessaging; diff --git a/browser/components/pocket/content/panels/js/saved/entry.js b/browser/components/pocket/content/panels/js/saved/entry.js new file mode 100644 index 0000000000..b022c7a5e9 --- /dev/null +++ b/browser/components/pocket/content/panels/js/saved/entry.js @@ -0,0 +1,17 @@ +/* global PKT_PANEL:false */ + +function onDOMLoaded() { + if (!window.thePKT_PANEL) { + var thePKT_PANEL = new PKT_PANEL(); + /* global thePKT_PANEL */ + window.thePKT_PANEL = thePKT_PANEL; + thePKT_PANEL.initSaved(); + } + window.thePKT_PANEL.create(); +} + +if (document.readyState != `loading`) { + onDOMLoaded(); +} else { + document.addEventListener(`DOMContentLoaded`, onDOMLoaded); +} diff --git a/browser/components/pocket/content/panels/js/saved/overlay.js b/browser/components/pocket/content/panels/js/saved/overlay.js new file mode 100644 index 0000000000..aabc852e9b --- /dev/null +++ b/browser/components/pocket/content/panels/js/saved/overlay.js @@ -0,0 +1,46 @@ +/* +SavedOverlay is the view itself and contains all of the methods to manipute the overlay and messaging. +It does not contain any logic for saving or communication with the extension or server. +*/ + +import React from "react"; +import ReactDOM from "react-dom"; +import Saved from "../components/Saved/Saved"; + +var SavedOverlay = function (options) { + this.inited = false; + this.active = false; +}; + +SavedOverlay.prototype = { + create({ pockethost }) { + if (this.active) { + return; + } + + this.active = true; + + const { searchParams } = new URL(window.location.href); + const locale = searchParams.get(`locale`) || ``; + const utmSource = searchParams.get(`utmSource`); + const utmCampaign = searchParams.get(`utmCampaign`); + const utmContent = searchParams.get(`utmContent`); + + ReactDOM.render( + , + document.querySelector(`body`) + ); + + if (window?.matchMedia(`(prefers-color-scheme: dark)`).matches) { + document.querySelector(`body`).classList.add(`theme_dark`); + } + }, +}; + +export default SavedOverlay; diff --git a/browser/components/pocket/content/panels/js/signup/entry.js b/browser/components/pocket/content/panels/js/signup/entry.js new file mode 100644 index 0000000000..4beae984bb --- /dev/null +++ b/browser/components/pocket/content/panels/js/signup/entry.js @@ -0,0 +1,17 @@ +/* global PKT_PANEL:false */ + +function onDOMLoaded() { + if (!window.thePKT_PANEL) { + var thePKT_PANEL = new PKT_PANEL(); + /* global thePKT_PANEL */ + window.thePKT_PANEL = thePKT_PANEL; + thePKT_PANEL.initSignup(); + } + window.thePKT_PANEL.create(); +} + +if (document.readyState != `loading`) { + onDOMLoaded(); +} else { + document.addEventListener(`DOMContentLoaded`, onDOMLoaded); +} diff --git a/browser/components/pocket/content/panels/js/signup/overlay.js b/browser/components/pocket/content/panels/js/signup/overlay.js new file mode 100644 index 0000000000..7df5cd3744 --- /dev/null +++ b/browser/components/pocket/content/panels/js/signup/overlay.js @@ -0,0 +1,50 @@ +/* +SignupOverlay is the view itself and contains all of the methods to manipute the overlay and messaging. +It does not contain any logic for saving or communication with the extension or server. +*/ + +import React from "react"; +import ReactDOM from "react-dom"; +import pktPanelMessaging from "../messages.js"; +import Signup from "../components/Signup/Signup"; + +var SignupOverlay = function (options) { + this.inited = false; + this.active = false; + + this.create = function ({ pockethost }) { + // Extract local variables passed into template via URL query params + const { searchParams } = new URL(window.location.href); + const locale = searchParams.get(`locale`) || ``; + const utmSource = searchParams.get(`utmSource`); + const utmCampaign = searchParams.get(`utmCampaign`); + const utmContent = searchParams.get(`utmContent`); + + if (this.active) { + return; + } + + this.active = true; + + // Create actual content + ReactDOM.render( + , + document.querySelector(`body`) + ); + + if (window?.matchMedia(`(prefers-color-scheme: dark)`).matches) { + document.querySelector(`body`).classList.add(`theme_dark`); + } + + // tell back end we're ready + pktPanelMessaging.sendMessage("PKT_show_signup"); + }; +}; + +export default SignupOverlay; diff --git a/browser/components/pocket/content/panels/js/style-guide/entry.js b/browser/components/pocket/content/panels/js/style-guide/entry.js new file mode 100644 index 0000000000..56ecbc0aef --- /dev/null +++ b/browser/components/pocket/content/panels/js/style-guide/entry.js @@ -0,0 +1,45 @@ +/* global PKT_PANEL:false */ + +function onDOMLoaded() { + if (!window.thePKT_PANEL) { + var thePKT_PANEL = new PKT_PANEL(); + /* global thePKT_PANEL */ + window.thePKT_PANEL = thePKT_PANEL; + thePKT_PANEL.initStyleGuide(); + } + window.thePKT_PANEL.overlay.create(); + + setupDarkModeUI(); +} + +function setupDarkModeUI() { + let isDarkModeEnabled = window?.matchMedia( + `(prefers-color-scheme: dark)` + ).matches; + let elDarkModeToggle = document.querySelector(`#dark_mode_toggle input`); + let elBody = document.querySelector(`body`); + + function setTheme() { + if (isDarkModeEnabled) { + elBody.classList.add(`theme_dark`); + elDarkModeToggle.checked = true; + } else { + elBody.classList.remove(`theme_dark`); + elDarkModeToggle.checked = false; + } + } + + setTheme(); + + elDarkModeToggle.addEventListener(`click`, function (e) { + e.preventDefault; + isDarkModeEnabled = !isDarkModeEnabled; + setTheme(); + }); +} + +if (document.readyState != `loading`) { + onDOMLoaded(); +} else { + document.addEventListener(`DOMContentLoaded`, onDOMLoaded); +} diff --git a/browser/components/pocket/content/panels/js/style-guide/overlay.js b/browser/components/pocket/content/panels/js/style-guide/overlay.js new file mode 100644 index 0000000000..056711b870 --- /dev/null +++ b/browser/components/pocket/content/panels/js/style-guide/overlay.js @@ -0,0 +1,106 @@ +import React from "react"; +import ReactDOM from "react-dom"; +import Header from "../components/Header/Header"; +import ArticleList from "../components/ArticleList/ArticleList"; +import Button from "../components/Button/Button"; +import PopularTopics from "../components/PopularTopics/PopularTopics"; +import TagPicker from "../components/TagPicker/TagPicker"; + +var StyleGuideOverlay = function (options) {}; + +StyleGuideOverlay.prototype = { + create() { + // TODO: Wrap popular topics component in JSX to work without needing an explicit container hierarchy for styling + ReactDOM.render( +
    +

    JSX Components:

    +

    Buttons

    +
    text
    + +
    primary
    + +
    secondary
    + +
    primary wide
    + + + +
    secondary wide
    + + + +

    Header

    +
    + +
    +

    PopularTopics

    + +

    ArticleList

    + +

    TagPicker

    + +

    Typography:

    +

    .header_large

    +

    .header_medium

    +

    paragraph

    +

    Native Elements:

    +

    Horizontal Rule

    +
    +
    , + document.querySelector(`#stp_style_guide_components`) + ); + }, +}; + +export default StyleGuideOverlay; diff --git a/browser/components/pocket/content/panels/js/vendor.bundle.js b/browser/components/pocket/content/panels/js/vendor.bundle.js new file mode 100644 index 0000000000..5da4d17f6d --- /dev/null +++ b/browser/components/pocket/content/panels/js/vendor.bundle.js @@ -0,0 +1,451 @@ +"use strict"; +(self["webpackChunksave_to_pocket_ff"] = self["webpackChunksave_to_pocket_ff"] || []).push([[736],{ + +/***/ 448: +/***/ ((__unused_webpack_module, exports, __webpack_require__) => { + +/** + * @license React + * react-dom.production.min.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +/* + Modernizr 3.0.0pre (Custom Build) | MIT +*/ +var aa=__webpack_require__(294),ca=__webpack_require__(840);function p(a){for(var b="https://reactjs.org/docs/error-decoder.html?invariant="+a,c=1;cb}return!1}function v(a,b,c,d,e,f,g){this.acceptsBooleans=2===b||3===b||4===b;this.attributeName=d;this.attributeNamespace=e;this.mustUseProperty=c;this.propertyName=a;this.type=b;this.sanitizeURL=f;this.removeEmptyString=g}var z={}; +"children dangerouslySetInnerHTML defaultValue defaultChecked innerHTML suppressContentEditableWarning suppressHydrationWarning style".split(" ").forEach(function(a){z[a]=new v(a,0,!1,a,null,!1,!1)});[["acceptCharset","accept-charset"],["className","class"],["htmlFor","for"],["httpEquiv","http-equiv"]].forEach(function(a){var b=a[0];z[b]=new v(b,1,!1,a[1],null,!1,!1)});["contentEditable","draggable","spellCheck","value"].forEach(function(a){z[a]=new v(a,2,!1,a.toLowerCase(),null,!1,!1)}); +["autoReverse","externalResourcesRequired","focusable","preserveAlpha"].forEach(function(a){z[a]=new v(a,2,!1,a,null,!1,!1)});"allowFullScreen async autoFocus autoPlay controls default defer disabled disablePictureInPicture disableRemotePlayback formNoValidate hidden loop noModule noValidate open playsInline readOnly required reversed scoped seamless itemScope".split(" ").forEach(function(a){z[a]=new v(a,3,!1,a.toLowerCase(),null,!1,!1)}); +["checked","multiple","muted","selected"].forEach(function(a){z[a]=new v(a,3,!0,a,null,!1,!1)});["capture","download"].forEach(function(a){z[a]=new v(a,4,!1,a,null,!1,!1)});["cols","rows","size","span"].forEach(function(a){z[a]=new v(a,6,!1,a,null,!1,!1)});["rowSpan","start"].forEach(function(a){z[a]=new v(a,5,!1,a.toLowerCase(),null,!1,!1)});var ra=/[\-:]([a-z])/g;function sa(a){return a[1].toUpperCase()} +"accent-height alignment-baseline arabic-form baseline-shift cap-height clip-path clip-rule color-interpolation color-interpolation-filters color-profile color-rendering dominant-baseline enable-background fill-opacity fill-rule flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-name glyph-orientation-horizontal glyph-orientation-vertical horiz-adv-x horiz-origin-x image-rendering letter-spacing lighting-color marker-end marker-mid marker-start overline-position overline-thickness paint-order panose-1 pointer-events rendering-intent shape-rendering stop-color stop-opacity strikethrough-position strikethrough-thickness stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width text-anchor text-decoration text-rendering underline-position underline-thickness unicode-bidi unicode-range units-per-em v-alphabetic v-hanging v-ideographic v-mathematical vector-effect vert-adv-y vert-origin-x vert-origin-y word-spacing writing-mode xmlns:xlink x-height".split(" ").forEach(function(a){var b=a.replace(ra, +sa);z[b]=new v(b,1,!1,a,null,!1,!1)});"xlink:actuate xlink:arcrole xlink:role xlink:show xlink:title xlink:type".split(" ").forEach(function(a){var b=a.replace(ra,sa);z[b]=new v(b,1,!1,a,"http://www.w3.org/1999/xlink",!1,!1)});["xml:base","xml:lang","xml:space"].forEach(function(a){var b=a.replace(ra,sa);z[b]=new v(b,1,!1,a,"http://www.w3.org/XML/1998/namespace",!1,!1)});["tabIndex","crossOrigin"].forEach(function(a){z[a]=new v(a,1,!1,a.toLowerCase(),null,!1,!1)}); +z.xlinkHref=new v("xlinkHref",1,!1,"xlink:href","http://www.w3.org/1999/xlink",!0,!1);["src","href","action","formAction"].forEach(function(a){z[a]=new v(a,1,!1,a.toLowerCase(),null,!0,!0)}); +function ta(a,b,c,d){var e=z.hasOwnProperty(b)?z[b]:null;if(null!==e?0!==e.type:d||!(2h||e[g]!==f[h]){var k="\n"+e[g].replace(" at new "," at ");a.displayName&&k.includes("")&&(k=k.replace("",a.displayName));return k}while(1<=g&&0<=h)}break}}}finally{Na=!1,Error.prepareStackTrace=c}return(a=a?a.displayName||a.name:"")?Ma(a):""} +function Pa(a){switch(a.tag){case 5:return Ma(a.type);case 16:return Ma("Lazy");case 13:return Ma("Suspense");case 19:return Ma("SuspenseList");case 0:case 2:case 15:return a=Oa(a.type,!1),a;case 11:return a=Oa(a.type.render,!1),a;case 1:return a=Oa(a.type,!0),a;default:return""}} +function Qa(a){if(null==a)return null;if("function"===typeof a)return a.displayName||a.name||null;if("string"===typeof a)return a;switch(a){case ya:return"Fragment";case wa:return"Portal";case Aa:return"Profiler";case za:return"StrictMode";case Ea:return"Suspense";case Fa:return"SuspenseList"}if("object"===typeof a)switch(a.$$typeof){case Ca:return(a.displayName||"Context")+".Consumer";case Ba:return(a._context.displayName||"Context")+".Provider";case Da:var b=a.render;a=a.displayName;a||(a=b.displayName|| +b.name||"",a=""!==a?"ForwardRef("+a+")":"ForwardRef");return a;case Ga:return b=a.displayName||null,null!==b?b:Qa(a.type)||"Memo";case Ha:b=a._payload;a=a._init;try{return Qa(a(b))}catch(c){}}return null} +function Ra(a){var b=a.type;switch(a.tag){case 24:return"Cache";case 9:return(b.displayName||"Context")+".Consumer";case 10:return(b._context.displayName||"Context")+".Provider";case 18:return"DehydratedFragment";case 11:return a=b.render,a=a.displayName||a.name||"",b.displayName||(""!==a?"ForwardRef("+a+")":"ForwardRef");case 7:return"Fragment";case 5:return b;case 4:return"Portal";case 3:return"Root";case 6:return"Text";case 16:return Qa(b);case 8:return b===za?"StrictMode":"Mode";case 22:return"Offscreen"; +case 12:return"Profiler";case 21:return"Scope";case 13:return"Suspense";case 19:return"SuspenseList";case 25:return"TracingMarker";case 1:case 0:case 17:case 2:case 14:case 15:if("function"===typeof b)return b.displayName||b.name||null;if("string"===typeof b)return b}return null}function Sa(a){switch(typeof a){case "boolean":case "number":case "string":case "undefined":return a;case "object":return a;default:return""}} +function Ta(a){var b=a.type;return(a=a.nodeName)&&"input"===a.toLowerCase()&&("checkbox"===b||"radio"===b)} +function Ua(a){var b=Ta(a)?"checked":"value",c=Object.getOwnPropertyDescriptor(a.constructor.prototype,b),d=""+a[b];if(!a.hasOwnProperty(b)&&"undefined"!==typeof c&&"function"===typeof c.get&&"function"===typeof c.set){var e=c.get,f=c.set;Object.defineProperty(a,b,{configurable:!0,get:function(){return e.call(this)},set:function(a){d=""+a;f.call(this,a)}});Object.defineProperty(a,b,{enumerable:c.enumerable});return{getValue:function(){return d},setValue:function(a){d=""+a},stopTracking:function(){a._valueTracker= +null;delete a[b]}}}}function Va(a){a._valueTracker||(a._valueTracker=Ua(a))}function Wa(a){if(!a)return!1;var b=a._valueTracker;if(!b)return!0;var c=b.getValue();var d="";a&&(d=Ta(a)?a.checked?"true":"false":a.value);a=d;return a!==c?(b.setValue(a),!0):!1}function Xa(a){a=a||("undefined"!==typeof document?document:void 0);if("undefined"===typeof a)return null;try{return a.activeElement||a.body}catch(b){return a.body}} +function Ya(a,b){var c=b.checked;return A({},b,{defaultChecked:void 0,defaultValue:void 0,value:void 0,checked:null!=c?c:a._wrapperState.initialChecked})}function Za(a,b){var c=null==b.defaultValue?"":b.defaultValue,d=null!=b.checked?b.checked:b.defaultChecked;c=Sa(null!=b.value?b.value:c);a._wrapperState={initialChecked:d,initialValue:c,controlled:"checkbox"===b.type||"radio"===b.type?null!=b.checked:null!=b.value}}function ab(a,b){b=b.checked;null!=b&&ta(a,"checked",b,!1)} +function bb(a,b){ab(a,b);var c=Sa(b.value),d=b.type;if(null!=c)if("number"===d){if(0===c&&""===a.value||a.value!=c)a.value=""+c}else a.value!==""+c&&(a.value=""+c);else if("submit"===d||"reset"===d){a.removeAttribute("value");return}b.hasOwnProperty("value")?cb(a,b.type,c):b.hasOwnProperty("defaultValue")&&cb(a,b.type,Sa(b.defaultValue));null==b.checked&&null!=b.defaultChecked&&(a.defaultChecked=!!b.defaultChecked)} +function db(a,b,c){if(b.hasOwnProperty("value")||b.hasOwnProperty("defaultValue")){var d=b.type;if(!("submit"!==d&&"reset"!==d||void 0!==b.value&&null!==b.value))return;b=""+a._wrapperState.initialValue;c||b===a.value||(a.value=b);a.defaultValue=b}c=a.name;""!==c&&(a.name="");a.defaultChecked=!!a._wrapperState.initialChecked;""!==c&&(a.name=c)} +function cb(a,b,c){if("number"!==b||Xa(a.ownerDocument)!==a)null==c?a.defaultValue=""+a._wrapperState.initialValue:a.defaultValue!==""+c&&(a.defaultValue=""+c)}var eb=Array.isArray; +function fb(a,b,c,d){a=a.options;if(b){b={};for(var e=0;e"+b.valueOf().toString()+"";for(b=mb.firstChild;a.firstChild;)a.removeChild(a.firstChild);for(;b.firstChild;)a.appendChild(b.firstChild)}}); +function ob(a,b){if(b){var c=a.firstChild;if(c&&c===a.lastChild&&3===c.nodeType){c.nodeValue=b;return}}a.textContent=b} +var pb={animationIterationCount:!0,aspectRatio:!0,borderImageOutset:!0,borderImageSlice:!0,borderImageWidth:!0,boxFlex:!0,boxFlexGroup:!0,boxOrdinalGroup:!0,columnCount:!0,columns:!0,flex:!0,flexGrow:!0,flexPositive:!0,flexShrink:!0,flexNegative:!0,flexOrder:!0,gridArea:!0,gridRow:!0,gridRowEnd:!0,gridRowSpan:!0,gridRowStart:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnSpan:!0,gridColumnStart:!0,fontWeight:!0,lineClamp:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,tabSize:!0,widows:!0,zIndex:!0, +zoom:!0,fillOpacity:!0,floodOpacity:!0,stopOpacity:!0,strokeDasharray:!0,strokeDashoffset:!0,strokeMiterlimit:!0,strokeOpacity:!0,strokeWidth:!0},qb=["Webkit","ms","Moz","O"];Object.keys(pb).forEach(function(a){qb.forEach(function(b){b=b+a.charAt(0).toUpperCase()+a.substring(1);pb[b]=pb[a]})});function rb(a,b,c){return null==b||"boolean"===typeof b||""===b?"":c||"number"!==typeof b||0===b||pb.hasOwnProperty(a)&&pb[a]?(""+b).trim():b+"px"} +function sb(a,b){a=a.style;for(var c in b)if(b.hasOwnProperty(c)){var d=0===c.indexOf("--"),e=rb(c,b[c],d);"float"===c&&(c="cssFloat");d?a.setProperty(c,e):a[c]=e}}var tb=A({menuitem:!0},{area:!0,base:!0,br:!0,col:!0,embed:!0,hr:!0,img:!0,input:!0,keygen:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0}); +function ub(a,b){if(b){if(tb[a]&&(null!=b.children||null!=b.dangerouslySetInnerHTML))throw Error(p(137,a));if(null!=b.dangerouslySetInnerHTML){if(null!=b.children)throw Error(p(60));if("object"!==typeof b.dangerouslySetInnerHTML||!("__html"in b.dangerouslySetInnerHTML))throw Error(p(61));}if(null!=b.style&&"object"!==typeof b.style)throw Error(p(62));}} +function vb(a,b){if(-1===a.indexOf("-"))return"string"===typeof b.is;switch(a){case "annotation-xml":case "color-profile":case "font-face":case "font-face-src":case "font-face-uri":case "font-face-format":case "font-face-name":case "missing-glyph":return!1;default:return!0}}var wb=null;function xb(a){a=a.target||a.srcElement||window;a.correspondingUseElement&&(a=a.correspondingUseElement);return 3===a.nodeType?a.parentNode:a}var yb=null,zb=null,Ab=null; +function Bb(a){if(a=Cb(a)){if("function"!==typeof yb)throw Error(p(280));var b=a.stateNode;b&&(b=Db(b),yb(a.stateNode,a.type,b))}}function Eb(a){zb?Ab?Ab.push(a):Ab=[a]:zb=a}function Fb(){if(zb){var a=zb,b=Ab;Ab=zb=null;Bb(a);if(b)for(a=0;a>>=0;return 0===a?32:31-(pc(a)/qc|0)|0}var rc=64,sc=4194304; +function tc(a){switch(a&-a){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return a&4194240;case 4194304:case 8388608:case 16777216:case 33554432:case 67108864:return a&130023424;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 1073741824; +default:return a}}function uc(a,b){var c=a.pendingLanes;if(0===c)return 0;var d=0,e=a.suspendedLanes,f=a.pingedLanes,g=c&268435455;if(0!==g){var h=g&~e;0!==h?d=tc(h):(f&=g,0!==f&&(d=tc(f)))}else g=c&~e,0!==g?d=tc(g):0!==f&&(d=tc(f));if(0===d)return 0;if(0!==b&&b!==d&&0===(b&e)&&(e=d&-d,f=b&-b,e>=f||16===e&&0!==(f&4194240)))return b;0!==(d&4)&&(d|=c&16);b=a.entangledLanes;if(0!==b)for(a=a.entanglements,b&=d;0c;c++)b.push(a);return b} +function Ac(a,b,c){a.pendingLanes|=b;536870912!==b&&(a.suspendedLanes=0,a.pingedLanes=0);a=a.eventTimes;b=31-oc(b);a[b]=c}function Bc(a,b){var c=a.pendingLanes&~b;a.pendingLanes=b;a.suspendedLanes=0;a.pingedLanes=0;a.expiredLanes&=b;a.mutableReadLanes&=b;a.entangledLanes&=b;b=a.entanglements;var d=a.eventTimes;for(a=a.expirationTimes;0=be),ee=String.fromCharCode(32),fe=!1; +function ge(a,b){switch(a){case "keyup":return-1!==$d.indexOf(b.keyCode);case "keydown":return 229!==b.keyCode;case "keypress":case "mousedown":case "focusout":return!0;default:return!1}}function he(a){a=a.detail;return"object"===typeof a&&"data"in a?a.data:null}var ie=!1;function je(a,b){switch(a){case "compositionend":return he(b);case "keypress":if(32!==b.which)return null;fe=!0;return ee;case "textInput":return a=b.data,a===ee&&fe?null:a;default:return null}} +function ke(a,b){if(ie)return"compositionend"===a||!ae&&ge(a,b)?(a=nd(),md=ld=kd=null,ie=!1,a):null;switch(a){case "paste":return null;case "keypress":if(!(b.ctrlKey||b.altKey||b.metaKey)||b.ctrlKey&&b.altKey){if(b.char&&1=b)return{node:c,offset:b-a};a=d}a:{for(;c;){if(c.nextSibling){c=c.nextSibling;break a}c=c.parentNode}c=void 0}c=Je(c)}}function Le(a,b){return a&&b?a===b?!0:a&&3===a.nodeType?!1:b&&3===b.nodeType?Le(a,b.parentNode):"contains"in a?a.contains(b):a.compareDocumentPosition?!!(a.compareDocumentPosition(b)&16):!1:!1} +function Me(){for(var a=window,b=Xa();b instanceof a.HTMLIFrameElement;){try{var c="string"===typeof b.contentWindow.location.href}catch(d){c=!1}if(c)a=b.contentWindow;else break;b=Xa(a.document)}return b}function Ne(a){var b=a&&a.nodeName&&a.nodeName.toLowerCase();return b&&("input"===b&&("text"===a.type||"search"===a.type||"tel"===a.type||"url"===a.type||"password"===a.type)||"textarea"===b||"true"===a.contentEditable)} +function Oe(a){var b=Me(),c=a.focusedElem,d=a.selectionRange;if(b!==c&&c&&c.ownerDocument&&Le(c.ownerDocument.documentElement,c)){if(null!==d&&Ne(c))if(b=d.start,a=d.end,void 0===a&&(a=b),"selectionStart"in c)c.selectionStart=b,c.selectionEnd=Math.min(a,c.value.length);else if(a=(b=c.ownerDocument||document)&&b.defaultView||window,a.getSelection){a=a.getSelection();var e=c.textContent.length,f=Math.min(d.start,e);d=void 0===d.end?f:Math.min(d.end,e);!a.extend&&f>d&&(e=d,d=f,f=e);e=Ke(c,f);var g=Ke(c, +d);e&&g&&(1!==a.rangeCount||a.anchorNode!==e.node||a.anchorOffset!==e.offset||a.focusNode!==g.node||a.focusOffset!==g.offset)&&(b=b.createRange(),b.setStart(e.node,e.offset),a.removeAllRanges(),f>d?(a.addRange(b),a.extend(g.node,g.offset)):(b.setEnd(g.node,g.offset),a.addRange(b)))}b=[];for(a=c;a=a.parentNode;)1===a.nodeType&&b.push({element:a,left:a.scrollLeft,top:a.scrollTop});"function"===typeof c.focus&&c.focus();for(c=0;c=document.documentMode,Qe=null,Re=null,Se=null,Te=!1; +function Ue(a,b,c){var d=c.window===c?c.document:9===c.nodeType?c:c.ownerDocument;Te||null==Qe||Qe!==Xa(d)||(d=Qe,"selectionStart"in d&&Ne(d)?d={start:d.selectionStart,end:d.selectionEnd}:(d=(d.ownerDocument&&d.ownerDocument.defaultView||window).getSelection(),d={anchorNode:d.anchorNode,anchorOffset:d.anchorOffset,focusNode:d.focusNode,focusOffset:d.focusOffset}),Se&&Ie(Se,d)||(Se=d,d=oe(Re,"onSelect"),0Tf||(a.current=Sf[Tf],Sf[Tf]=null,Tf--)}function G(a,b){Tf++;Sf[Tf]=a.current;a.current=b}var Vf={},H=Uf(Vf),Wf=Uf(!1),Xf=Vf;function Yf(a,b){var c=a.type.contextTypes;if(!c)return Vf;var d=a.stateNode;if(d&&d.__reactInternalMemoizedUnmaskedChildContext===b)return d.__reactInternalMemoizedMaskedChildContext;var e={},f;for(f in c)e[f]=b[f];d&&(a=a.stateNode,a.__reactInternalMemoizedUnmaskedChildContext=b,a.__reactInternalMemoizedMaskedChildContext=e);return e} +function Zf(a){a=a.childContextTypes;return null!==a&&void 0!==a}function $f(){E(Wf);E(H)}function ag(a,b,c){if(H.current!==Vf)throw Error(p(168));G(H,b);G(Wf,c)}function bg(a,b,c){var d=a.stateNode;b=b.childContextTypes;if("function"!==typeof d.getChildContext)return c;d=d.getChildContext();for(var e in d)if(!(e in b))throw Error(p(108,Ra(a)||"Unknown",e));return A({},c,d)} +function cg(a){a=(a=a.stateNode)&&a.__reactInternalMemoizedMergedChildContext||Vf;Xf=H.current;G(H,a);G(Wf,Wf.current);return!0}function dg(a,b,c){var d=a.stateNode;if(!d)throw Error(p(169));c?(a=bg(a,b,Xf),d.__reactInternalMemoizedMergedChildContext=a,E(Wf),E(H),G(H,a)):E(Wf);G(Wf,c)}var eg=null,fg=!1,gg=!1;function hg(a){null===eg?eg=[a]:eg.push(a)}function ig(a){fg=!0;hg(a)} +function jg(){if(!gg&&null!==eg){gg=!0;var a=0,b=C;try{var c=eg;for(C=1;a>=g;e-=g;rg=1<<32-oc(b)+e|c<w?(x=u,u=null):x=u.sibling;var n=r(e,u,h[w],k);if(null===n){null===u&&(u=x);break}a&&u&&null===n.alternate&&b(e,u);g=f(n,g,w);null===m?l=n:m.sibling=n;m=n;u=x}if(w===h.length)return c(e,u),I&&tg(e,w),l;if(null===u){for(;ww?(x=m,m=null):x=m.sibling;var t=r(e,m,n.value,k);if(null===t){null===m&&(m=x);break}a&&m&&null===t.alternate&&b(e,m);g=f(t,g,w);null===u?l=t:u.sibling=t;u=t;m=x}if(n.done)return c(e, +m),I&&tg(e,w),l;if(null===m){for(;!n.done;w++,n=h.next())n=q(e,n.value,k),null!==n&&(g=f(n,g,w),null===u?l=n:u.sibling=n,u=n);I&&tg(e,w);return l}for(m=d(e,m);!n.done;w++,n=h.next())n=y(m,e,w,n.value,k),null!==n&&(a&&null!==n.alternate&&m.delete(null===n.key?w:n.key),g=f(n,g,w),null===u?l=n:u.sibling=n,u=n);a&&m.forEach(function(a){return b(e,a)});I&&tg(e,w);return l}function J(a,d,f,h){"object"===typeof f&&null!==f&&f.type===ya&&null===f.key&&(f=f.props.children);if("object"===typeof f&&null!==f){switch(f.$$typeof){case va:a:{for(var k= +f.key,l=d;null!==l;){if(l.key===k){k=f.type;if(k===ya){if(7===l.tag){c(a,l.sibling);d=e(l,f.props.children);d.return=a;a=d;break a}}else if(l.elementType===k||"object"===typeof k&&null!==k&&k.$$typeof===Ha&&uh(k)===l.type){c(a,l.sibling);d=e(l,f.props);d.ref=sh(a,l,f);d.return=a;a=d;break a}c(a,l);break}else b(a,l);l=l.sibling}f.type===ya?(d=Ah(f.props.children,a.mode,h,f.key),d.return=a,a=d):(h=yh(f.type,f.key,f.props,null,a.mode,h),h.ref=sh(a,d,f),h.return=a,a=h)}return g(a);case wa:a:{for(l=f.key;null!== +d;){if(d.key===l)if(4===d.tag&&d.stateNode.containerInfo===f.containerInfo&&d.stateNode.implementation===f.implementation){c(a,d.sibling);d=e(d,f.children||[]);d.return=a;a=d;break a}else{c(a,d);break}else b(a,d);d=d.sibling}d=zh(f,a.mode,h);d.return=a;a=d}return g(a);case Ha:return l=f._init,J(a,d,l(f._payload),h)}if(eb(f))return n(a,d,f,h);if(Ka(f))return t(a,d,f,h);th(a,f)}return"string"===typeof f&&""!==f||"number"===typeof f?(f=""+f,null!==d&&6===d.tag?(c(a,d.sibling),d=e(d,f),d.return=a,a=d): +(c(a,d),d=xh(f,a.mode,h),d.return=a,a=d),g(a)):c(a,d)}return J}var Bh=vh(!0),Ch=vh(!1),Dh={},Eh=Uf(Dh),Fh=Uf(Dh),Gh=Uf(Dh);function Hh(a){if(a===Dh)throw Error(p(174));return a}function Ih(a,b){G(Gh,b);G(Fh,a);G(Eh,Dh);a=b.nodeType;switch(a){case 9:case 11:b=(b=b.documentElement)?b.namespaceURI:lb(null,"");break;default:a=8===a?b.parentNode:b,b=a.namespaceURI||null,a=a.tagName,b=lb(b,a)}E(Eh);G(Eh,b)}function Jh(){E(Eh);E(Fh);E(Gh)} +function Kh(a){Hh(Gh.current);var b=Hh(Eh.current);var c=lb(b,a.type);b!==c&&(G(Fh,a),G(Eh,c))}function Lh(a){Fh.current===a&&(E(Eh),E(Fh))}var M=Uf(0); +function Mh(a){for(var b=a;null!==b;){if(13===b.tag){var c=b.memoizedState;if(null!==c&&(c=c.dehydrated,null===c||"$?"===c.data||"$!"===c.data))return b}else if(19===b.tag&&void 0!==b.memoizedProps.revealOrder){if(0!==(b.flags&128))return b}else if(null!==b.child){b.child.return=b;b=b.child;continue}if(b===a)break;for(;null===b.sibling;){if(null===b.return||b.return===a)return null;b=b.return}b.sibling.return=b.return;b=b.sibling}return null}var Nh=[]; +function Oh(){for(var a=0;ac?c:4;a(!0);var d=Qh.transition;Qh.transition={};try{a(!1),b()}finally{C=c,Qh.transition=d}}function Fi(){return di().memoizedState} +function Gi(a,b,c){var d=lh(a);c={lane:d,action:c,hasEagerState:!1,eagerState:null,next:null};if(Hi(a))Ii(b,c);else if(c=Yg(a,b,c,d),null!==c){var e=L();mh(c,a,d,e);Ji(c,b,d)}} +function ri(a,b,c){var d=lh(a),e={lane:d,action:c,hasEagerState:!1,eagerState:null,next:null};if(Hi(a))Ii(b,e);else{var f=a.alternate;if(0===a.lanes&&(null===f||0===f.lanes)&&(f=b.lastRenderedReducer,null!==f))try{var g=b.lastRenderedState,h=f(g,c);e.hasEagerState=!0;e.eagerState=h;if(He(h,g)){var k=b.interleaved;null===k?(e.next=e,Xg(b)):(e.next=k.next,k.next=e);b.interleaved=e;return}}catch(l){}finally{}c=Yg(a,b,e,d);null!==c&&(e=L(),mh(c,a,d,e),Ji(c,b,d))}} +function Hi(a){var b=a.alternate;return a===N||null!==b&&b===N}function Ii(a,b){Th=Sh=!0;var c=a.pending;null===c?b.next=b:(b.next=c.next,c.next=b);a.pending=b}function Ji(a,b,c){if(0!==(c&4194240)){var d=b.lanes;d&=a.pendingLanes;c|=d;b.lanes=c;Cc(a,c)}} +var ai={readContext:Vg,useCallback:Q,useContext:Q,useEffect:Q,useImperativeHandle:Q,useInsertionEffect:Q,useLayoutEffect:Q,useMemo:Q,useReducer:Q,useRef:Q,useState:Q,useDebugValue:Q,useDeferredValue:Q,useTransition:Q,useMutableSource:Q,useSyncExternalStore:Q,useId:Q,unstable_isNewReconciler:!1},Yh={readContext:Vg,useCallback:function(a,b){ci().memoizedState=[a,void 0===b?null:b];return a},useContext:Vg,useEffect:vi,useImperativeHandle:function(a,b,c){c=null!==c&&void 0!==c?c.concat([a]):null;return ti(4194308, +4,yi.bind(null,b,a),c)},useLayoutEffect:function(a,b){return ti(4194308,4,a,b)},useInsertionEffect:function(a,b){return ti(4,2,a,b)},useMemo:function(a,b){var c=ci();b=void 0===b?null:b;a=a();c.memoizedState=[a,b];return a},useReducer:function(a,b,c){var d=ci();b=void 0!==c?c(b):b;d.memoizedState=d.baseState=b;a={pending:null,interleaved:null,lanes:0,dispatch:null,lastRenderedReducer:a,lastRenderedState:b};d.queue=a;a=a.dispatch=Gi.bind(null,N,a);return[d.memoizedState,a]},useRef:function(a){var b= +ci();a={current:a};return b.memoizedState=a},useState:qi,useDebugValue:Ai,useDeferredValue:function(a){return ci().memoizedState=a},useTransition:function(){var a=qi(!1),b=a[0];a=Ei.bind(null,a[1]);ci().memoizedState=a;return[b,a]},useMutableSource:function(){},useSyncExternalStore:function(a,b,c){var d=N,e=ci();if(I){if(void 0===c)throw Error(p(407));c=c()}else{c=b();if(null===R)throw Error(p(349));0!==(Rh&30)||ni(d,b,c)}e.memoizedState=c;var f={value:c,getSnapshot:b};e.queue=f;vi(ki.bind(null,d, +f,a),[a]);d.flags|=2048;li(9,mi.bind(null,d,f,c,b),void 0,null);return c},useId:function(){var a=ci(),b=R.identifierPrefix;if(I){var c=sg;var d=rg;c=(d&~(1<<32-oc(d)-1)).toString(32)+c;b=":"+b+"R"+c;c=Uh++;0\x3c/script>",a=a.removeChild(a.firstChild)): +"string"===typeof d.is?a=g.createElement(c,{is:d.is}):(a=g.createElement(c),"select"===c&&(g=a,d.multiple?g.multiple=!0:d.size&&(g.size=d.size))):a=g.createElementNS(a,c);a[Of]=b;a[Pf]=d;Aj(a,b,!1,!1);b.stateNode=a;a:{g=vb(c,d);switch(c){case "dialog":D("cancel",a);D("close",a);e=d;break;case "iframe":case "object":case "embed":D("load",a);e=d;break;case "video":case "audio":for(e=0;eHj&&(b.flags|=128,d=!0,Ej(f,!1),b.lanes=4194304)}else{if(!d)if(a=Mh(g),null!==a){if(b.flags|=128,d=!0,c=a.updateQueue,null!==c&&(b.updateQueue=c,b.flags|=4),Ej(f,!0),null===f.tail&&"hidden"===f.tailMode&&!g.alternate&&!I)return S(b),null}else 2*B()-f.renderingStartTime>Hj&&1073741824!==c&&(b.flags|=128,d=!0,Ej(f,!1),b.lanes=4194304);f.isBackwards?(g.sibling=b.child,b.child=g):(c=f.last,null!==c?c.sibling=g:b.child=g,f.last=g)}if(null!==f.tail)return b=f.tail,f.rendering= +b,f.tail=b.sibling,f.renderingStartTime=B(),b.sibling=null,c=M.current,G(M,d?c&1|2:c&1),b;S(b);return null;case 22:case 23:return Ij(),d=null!==b.memoizedState,null!==a&&null!==a.memoizedState!==d&&(b.flags|=8192),d&&0!==(b.mode&1)?0!==(gj&1073741824)&&(S(b),b.subtreeFlags&6&&(b.flags|=8192)):S(b),null;case 24:return null;case 25:return null}throw Error(p(156,b.tag));} +function Jj(a,b){wg(b);switch(b.tag){case 1:return Zf(b.type)&&$f(),a=b.flags,a&65536?(b.flags=a&-65537|128,b):null;case 3:return Jh(),E(Wf),E(H),Oh(),a=b.flags,0!==(a&65536)&&0===(a&128)?(b.flags=a&-65537|128,b):null;case 5:return Lh(b),null;case 13:E(M);a=b.memoizedState;if(null!==a&&null!==a.dehydrated){if(null===b.alternate)throw Error(p(340));Ig()}a=b.flags;return a&65536?(b.flags=a&-65537|128,b):null;case 19:return E(M),null;case 4:return Jh(),null;case 10:return Rg(b.type._context),null;case 22:case 23:return Ij(), +null;case 24:return null;default:return null}}var Kj=!1,U=!1,Lj="function"===typeof WeakSet?WeakSet:Set,V=null;function Mj(a,b){var c=a.ref;if(null!==c)if("function"===typeof c)try{c(null)}catch(d){W(a,b,d)}else c.current=null}function Nj(a,b,c){try{c()}catch(d){W(a,b,d)}}var Oj=!1; +function Pj(a,b){Cf=dd;a=Me();if(Ne(a)){if("selectionStart"in a)var c={start:a.selectionStart,end:a.selectionEnd};else a:{c=(c=a.ownerDocument)&&c.defaultView||window;var d=c.getSelection&&c.getSelection();if(d&&0!==d.rangeCount){c=d.anchorNode;var e=d.anchorOffset,f=d.focusNode;d=d.focusOffset;try{c.nodeType,f.nodeType}catch(F){c=null;break a}var g=0,h=-1,k=-1,l=0,m=0,q=a,r=null;b:for(;;){for(var y;;){q!==c||0!==e&&3!==q.nodeType||(h=g+e);q!==f||0!==d&&3!==q.nodeType||(k=g+d);3===q.nodeType&&(g+= +q.nodeValue.length);if(null===(y=q.firstChild))break;r=q;q=y}for(;;){if(q===a)break b;r===c&&++l===e&&(h=g);r===f&&++m===d&&(k=g);if(null!==(y=q.nextSibling))break;q=r;r=q.parentNode}q=y}c=-1===h||-1===k?null:{start:h,end:k}}else c=null}c=c||{start:0,end:0}}else c=null;Df={focusedElem:a,selectionRange:c};dd=!1;for(V=b;null!==V;)if(b=V,a=b.child,0!==(b.subtreeFlags&1028)&&null!==a)a.return=b,V=a;else for(;null!==V;){b=V;try{var n=b.alternate;if(0!==(b.flags&1024))switch(b.tag){case 0:case 11:case 15:break; +case 1:if(null!==n){var t=n.memoizedProps,J=n.memoizedState,x=b.stateNode,w=x.getSnapshotBeforeUpdate(b.elementType===b.type?t:Lg(b.type,t),J);x.__reactInternalSnapshotBeforeUpdate=w}break;case 3:var u=b.stateNode.containerInfo;1===u.nodeType?u.textContent="":9===u.nodeType&&u.documentElement&&u.removeChild(u.documentElement);break;case 5:case 6:case 4:case 17:break;default:throw Error(p(163));}}catch(F){W(b,b.return,F)}a=b.sibling;if(null!==a){a.return=b.return;V=a;break}V=b.return}n=Oj;Oj=!1;return n} +function Qj(a,b,c){var d=b.updateQueue;d=null!==d?d.lastEffect:null;if(null!==d){var e=d=d.next;do{if((e.tag&a)===a){var f=e.destroy;e.destroy=void 0;void 0!==f&&Nj(b,c,f)}e=e.next}while(e!==d)}}function Rj(a,b){b=b.updateQueue;b=null!==b?b.lastEffect:null;if(null!==b){var c=b=b.next;do{if((c.tag&a)===a){var d=c.create;c.destroy=d()}c=c.next}while(c!==b)}}function Sj(a){var b=a.ref;if(null!==b){var c=a.stateNode;switch(a.tag){case 5:a=c;break;default:a=c}"function"===typeof b?b(a):b.current=a}} +function Tj(a){var b=a.alternate;null!==b&&(a.alternate=null,Tj(b));a.child=null;a.deletions=null;a.sibling=null;5===a.tag&&(b=a.stateNode,null!==b&&(delete b[Of],delete b[Pf],delete b[of],delete b[Qf],delete b[Rf]));a.stateNode=null;a.return=null;a.dependencies=null;a.memoizedProps=null;a.memoizedState=null;a.pendingProps=null;a.stateNode=null;a.updateQueue=null}function Uj(a){return 5===a.tag||3===a.tag||4===a.tag} +function Vj(a){a:for(;;){for(;null===a.sibling;){if(null===a.return||Uj(a.return))return null;a=a.return}a.sibling.return=a.return;for(a=a.sibling;5!==a.tag&&6!==a.tag&&18!==a.tag;){if(a.flags&2)continue a;if(null===a.child||4===a.tag)continue a;else a.child.return=a,a=a.child}if(!(a.flags&2))return a.stateNode}} +function Wj(a,b,c){var d=a.tag;if(5===d||6===d)a=a.stateNode,b?8===c.nodeType?c.parentNode.insertBefore(a,b):c.insertBefore(a,b):(8===c.nodeType?(b=c.parentNode,b.insertBefore(a,c)):(b=c,b.appendChild(a)),c=c._reactRootContainer,null!==c&&void 0!==c||null!==b.onclick||(b.onclick=Bf));else if(4!==d&&(a=a.child,null!==a))for(Wj(a,b,c),a=a.sibling;null!==a;)Wj(a,b,c),a=a.sibling} +function Xj(a,b,c){var d=a.tag;if(5===d||6===d)a=a.stateNode,b?c.insertBefore(a,b):c.appendChild(a);else if(4!==d&&(a=a.child,null!==a))for(Xj(a,b,c),a=a.sibling;null!==a;)Xj(a,b,c),a=a.sibling}var X=null,Yj=!1;function Zj(a,b,c){for(c=c.child;null!==c;)ak(a,b,c),c=c.sibling} +function ak(a,b,c){if(lc&&"function"===typeof lc.onCommitFiberUnmount)try{lc.onCommitFiberUnmount(kc,c)}catch(h){}switch(c.tag){case 5:U||Mj(c,b);case 6:var d=X,e=Yj;X=null;Zj(a,b,c);X=d;Yj=e;null!==X&&(Yj?(a=X,c=c.stateNode,8===a.nodeType?a.parentNode.removeChild(c):a.removeChild(c)):X.removeChild(c.stateNode));break;case 18:null!==X&&(Yj?(a=X,c=c.stateNode,8===a.nodeType?Kf(a.parentNode,c):1===a.nodeType&&Kf(a,c),bd(a)):Kf(X,c.stateNode));break;case 4:d=X;e=Yj;X=c.stateNode.containerInfo;Yj=!0; +Zj(a,b,c);X=d;Yj=e;break;case 0:case 11:case 14:case 15:if(!U&&(d=c.updateQueue,null!==d&&(d=d.lastEffect,null!==d))){e=d=d.next;do{var f=e,g=f.destroy;f=f.tag;void 0!==g&&(0!==(f&2)?Nj(c,b,g):0!==(f&4)&&Nj(c,b,g));e=e.next}while(e!==d)}Zj(a,b,c);break;case 1:if(!U&&(Mj(c,b),d=c.stateNode,"function"===typeof d.componentWillUnmount))try{d.props=c.memoizedProps,d.state=c.memoizedState,d.componentWillUnmount()}catch(h){W(c,b,h)}Zj(a,b,c);break;case 21:Zj(a,b,c);break;case 22:c.mode&1?(U=(d=U)||null!== +c.memoizedState,Zj(a,b,c),U=d):Zj(a,b,c);break;default:Zj(a,b,c)}}function bk(a){var b=a.updateQueue;if(null!==b){a.updateQueue=null;var c=a.stateNode;null===c&&(c=a.stateNode=new Lj);b.forEach(function(b){var d=ck.bind(null,a,b);c.has(b)||(c.add(b),b.then(d,d))})}} +function dk(a,b){var c=b.deletions;if(null!==c)for(var d=0;de&&(e=g);d&=~f}d=e;d=B()-d;d=(120>d?120:480>d?480:1080>d?1080:1920>d?1920:3E3>d?3E3:4320>d?4320:1960*mk(d/1960))-d;if(10a?16:a;if(null===xk)var d=!1;else{a=xk;xk=null;yk=0;if(0!==(K&6))throw Error(p(331));var e=K;K|=4;for(V=a.current;null!==V;){var f=V,g=f.child;if(0!==(V.flags&16)){var h=f.deletions;if(null!==h){for(var k=0;kB()-gk?Lk(a,0):sk|=c);Ek(a,b)}function Zk(a,b){0===b&&(0===(a.mode&1)?b=1:(b=sc,sc<<=1,0===(sc&130023424)&&(sc=4194304)));var c=L();a=Zg(a,b);null!==a&&(Ac(a,b,c),Ek(a,c))}function vj(a){var b=a.memoizedState,c=0;null!==b&&(c=b.retryLane);Zk(a,c)} +function ck(a,b){var c=0;switch(a.tag){case 13:var d=a.stateNode;var e=a.memoizedState;null!==e&&(c=e.retryLane);break;case 19:d=a.stateNode;break;default:throw Error(p(314));}null!==d&&d.delete(b);Zk(a,c)}var Wk; +Wk=function(a,b,c){if(null!==a)if(a.memoizedProps!==b.pendingProps||Wf.current)Ug=!0;else{if(0===(a.lanes&c)&&0===(b.flags&128))return Ug=!1,zj(a,b,c);Ug=0!==(a.flags&131072)?!0:!1}else Ug=!1,I&&0!==(b.flags&1048576)&&ug(b,ng,b.index);b.lanes=0;switch(b.tag){case 2:var d=b.type;jj(a,b);a=b.pendingProps;var e=Yf(b,H.current);Tg(b,c);e=Xh(null,b,d,a,e,c);var f=bi();b.flags|=1;"object"===typeof e&&null!==e&&"function"===typeof e.render&&void 0===e.$$typeof?(b.tag=1,b.memoizedState=null,b.updateQueue= +null,Zf(d)?(f=!0,cg(b)):f=!1,b.memoizedState=null!==e.state&&void 0!==e.state?e.state:null,ah(b),e.updater=nh,b.stateNode=e,e._reactInternals=b,rh(b,d,a,c),b=kj(null,b,d,!0,f,c)):(b.tag=0,I&&f&&vg(b),Yi(null,b,e,c),b=b.child);return b;case 16:d=b.elementType;a:{jj(a,b);a=b.pendingProps;e=d._init;d=e(d._payload);b.type=d;e=b.tag=$k(d);a=Lg(d,a);switch(e){case 0:b=dj(null,b,d,a,c);break a;case 1:b=ij(null,b,d,a,c);break a;case 11:b=Zi(null,b,d,a,c);break a;case 14:b=aj(null,b,d,Lg(d.type,a),c);break a}throw Error(p(306, +d,""));}return b;case 0:return d=b.type,e=b.pendingProps,e=b.elementType===d?e:Lg(d,e),dj(a,b,d,e,c);case 1:return d=b.type,e=b.pendingProps,e=b.elementType===d?e:Lg(d,e),ij(a,b,d,e,c);case 3:a:{lj(b);if(null===a)throw Error(p(387));d=b.pendingProps;f=b.memoizedState;e=f.element;bh(a,b);gh(b,d,null,c);var g=b.memoizedState;d=g.element;if(f.isDehydrated)if(f={element:d,isDehydrated:!1,cache:g.cache,pendingSuspenseBoundaries:g.pendingSuspenseBoundaries,transitions:g.transitions},b.updateQueue.baseState= +f,b.memoizedState=f,b.flags&256){e=Ki(Error(p(423)),b);b=mj(a,b,d,c,e);break a}else if(d!==e){e=Ki(Error(p(424)),b);b=mj(a,b,d,c,e);break a}else for(yg=Lf(b.stateNode.containerInfo.firstChild),xg=b,I=!0,zg=null,c=Ch(b,null,d,c),b.child=c;c;)c.flags=c.flags&-3|4096,c=c.sibling;else{Ig();if(d===e){b=$i(a,b,c);break a}Yi(a,b,d,c)}b=b.child}return b;case 5:return Kh(b),null===a&&Eg(b),d=b.type,e=b.pendingProps,f=null!==a?a.memoizedProps:null,g=e.children,Ef(d,e)?g=null:null!==f&&Ef(d,f)&&(b.flags|=32), +hj(a,b),Yi(a,b,g,c),b.child;case 6:return null===a&&Eg(b),null;case 13:return pj(a,b,c);case 4:return Ih(b,b.stateNode.containerInfo),d=b.pendingProps,null===a?b.child=Bh(b,null,d,c):Yi(a,b,d,c),b.child;case 11:return d=b.type,e=b.pendingProps,e=b.elementType===d?e:Lg(d,e),Zi(a,b,d,e,c);case 7:return Yi(a,b,b.pendingProps,c),b.child;case 8:return Yi(a,b,b.pendingProps.children,c),b.child;case 12:return Yi(a,b,b.pendingProps.children,c),b.child;case 10:a:{d=b.type._context;e=b.pendingProps;f=b.memoizedProps; +g=e.value;G(Mg,d._currentValue);d._currentValue=g;if(null!==f)if(He(f.value,g)){if(f.children===e.children&&!Wf.current){b=$i(a,b,c);break a}}else for(f=b.child,null!==f&&(f.return=b);null!==f;){var h=f.dependencies;if(null!==h){g=f.child;for(var k=h.firstContext;null!==k;){if(k.context===d){if(1===f.tag){k=ch(-1,c&-c);k.tag=2;var l=f.updateQueue;if(null!==l){l=l.shared;var m=l.pending;null===m?k.next=k:(k.next=m.next,m.next=k);l.pending=k}}f.lanes|=c;k=f.alternate;null!==k&&(k.lanes|=c);Sg(f.return, +c,b);h.lanes|=c;break}k=k.next}}else if(10===f.tag)g=f.type===b.type?null:f.child;else if(18===f.tag){g=f.return;if(null===g)throw Error(p(341));g.lanes|=c;h=g.alternate;null!==h&&(h.lanes|=c);Sg(g,c,b);g=f.sibling}else g=f.child;if(null!==g)g.return=f;else for(g=f;null!==g;){if(g===b){g=null;break}f=g.sibling;if(null!==f){f.return=g.return;g=f;break}g=g.return}f=g}Yi(a,b,e.children,c);b=b.child}return b;case 9:return e=b.type,d=b.pendingProps.children,Tg(b,c),e=Vg(e),d=d(e),b.flags|=1,Yi(a,b,d,c), +b.child;case 14:return d=b.type,e=Lg(d,b.pendingProps),e=Lg(d.type,e),aj(a,b,d,e,c);case 15:return cj(a,b,b.type,b.pendingProps,c);case 17:return d=b.type,e=b.pendingProps,e=b.elementType===d?e:Lg(d,e),jj(a,b),b.tag=1,Zf(d)?(a=!0,cg(b)):a=!1,Tg(b,c),ph(b,d,e),rh(b,d,e,c),kj(null,b,d,!0,a,c);case 19:return yj(a,b,c);case 22:return ej(a,b,c)}throw Error(p(156,b.tag));};function Gk(a,b){return ac(a,b)} +function al(a,b,c,d){this.tag=a;this.key=c;this.sibling=this.child=this.return=this.stateNode=this.type=this.elementType=null;this.index=0;this.ref=null;this.pendingProps=b;this.dependencies=this.memoizedState=this.updateQueue=this.memoizedProps=null;this.mode=d;this.subtreeFlags=this.flags=0;this.deletions=null;this.childLanes=this.lanes=0;this.alternate=null}function Bg(a,b,c,d){return new al(a,b,c,d)}function bj(a){a=a.prototype;return!(!a||!a.isReactComponent)} +function $k(a){if("function"===typeof a)return bj(a)?1:0;if(void 0!==a&&null!==a){a=a.$$typeof;if(a===Da)return 11;if(a===Ga)return 14}return 2} +function wh(a,b){var c=a.alternate;null===c?(c=Bg(a.tag,b,a.key,a.mode),c.elementType=a.elementType,c.type=a.type,c.stateNode=a.stateNode,c.alternate=a,a.alternate=c):(c.pendingProps=b,c.type=a.type,c.flags=0,c.subtreeFlags=0,c.deletions=null);c.flags=a.flags&14680064;c.childLanes=a.childLanes;c.lanes=a.lanes;c.child=a.child;c.memoizedProps=a.memoizedProps;c.memoizedState=a.memoizedState;c.updateQueue=a.updateQueue;b=a.dependencies;c.dependencies=null===b?null:{lanes:b.lanes,firstContext:b.firstContext}; +c.sibling=a.sibling;c.index=a.index;c.ref=a.ref;return c} +function yh(a,b,c,d,e,f){var g=2;d=a;if("function"===typeof a)bj(a)&&(g=1);else if("string"===typeof a)g=5;else a:switch(a){case ya:return Ah(c.children,e,f,b);case za:g=8;e|=8;break;case Aa:return a=Bg(12,c,b,e|2),a.elementType=Aa,a.lanes=f,a;case Ea:return a=Bg(13,c,b,e),a.elementType=Ea,a.lanes=f,a;case Fa:return a=Bg(19,c,b,e),a.elementType=Fa,a.lanes=f,a;case Ia:return qj(c,e,f,b);default:if("object"===typeof a&&null!==a)switch(a.$$typeof){case Ba:g=10;break a;case Ca:g=9;break a;case Da:g=11; +break a;case Ga:g=14;break a;case Ha:g=16;d=null;break a}throw Error(p(130,null==a?a:typeof a,""));}b=Bg(g,c,b,e);b.elementType=a;b.type=d;b.lanes=f;return b}function Ah(a,b,c,d){a=Bg(7,a,d,b);a.lanes=c;return a}function qj(a,b,c,d){a=Bg(22,a,d,b);a.elementType=Ia;a.lanes=c;a.stateNode={isHidden:!1};return a}function xh(a,b,c){a=Bg(6,a,null,b);a.lanes=c;return a} +function zh(a,b,c){b=Bg(4,null!==a.children?a.children:[],a.key,b);b.lanes=c;b.stateNode={containerInfo:a.containerInfo,pendingChildren:null,implementation:a.implementation};return b} +function bl(a,b,c,d,e){this.tag=b;this.containerInfo=a;this.finishedWork=this.pingCache=this.current=this.pendingChildren=null;this.timeoutHandle=-1;this.callbackNode=this.pendingContext=this.context=null;this.callbackPriority=0;this.eventTimes=zc(0);this.expirationTimes=zc(-1);this.entangledLanes=this.finishedLanes=this.mutableReadLanes=this.expiredLanes=this.pingedLanes=this.suspendedLanes=this.pendingLanes=0;this.entanglements=zc(0);this.identifierPrefix=d;this.onRecoverableError=e;this.mutableSourceEagerHydrationData= +null}function cl(a,b,c,d,e,f,g,h,k){a=new bl(a,b,c,h,k);1===b?(b=1,!0===f&&(b|=8)):b=0;f=Bg(3,null,null,b);a.current=f;f.stateNode=a;f.memoizedState={element:d,isDehydrated:c,cache:null,transitions:null,pendingSuspenseBoundaries:null};ah(f);return a}function dl(a,b,c){var d=3 { + + + +function checkDCE() { + /* global __REACT_DEVTOOLS_GLOBAL_HOOK__ */ + if ( + typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ === 'undefined' || + typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE !== 'function' + ) { + return; + } + if (false) {} + try { + // Verify that the code above has been dead code eliminated (DCE'd). + __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(checkDCE); + } catch (err) { + // DevTools shouldn't crash React, no matter what. + // We should still report in case we break this code. + console.error(err); + } +} + +if (true) { + // DCE check should happen before ReactDOM bundle executes so that + // DevTools can report bad minification during injection. + checkDCE(); + module.exports = __webpack_require__(448); +} else {} + + +/***/ }), + +/***/ 408: +/***/ ((__unused_webpack_module, exports) => { + +/** + * @license React + * react.production.min.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +var l=Symbol.for("react.element"),n=Symbol.for("react.portal"),p=Symbol.for("react.fragment"),q=Symbol.for("react.strict_mode"),r=Symbol.for("react.profiler"),t=Symbol.for("react.provider"),u=Symbol.for("react.context"),v=Symbol.for("react.forward_ref"),w=Symbol.for("react.suspense"),x=Symbol.for("react.memo"),y=Symbol.for("react.lazy"),z=Symbol.iterator;function A(a){if(null===a||"object"!==typeof a)return null;a=z&&a[z]||a["@@iterator"];return"function"===typeof a?a:null} +var B={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},C=Object.assign,D={};function E(a,b,e){this.props=a;this.context=b;this.refs=D;this.updater=e||B}E.prototype.isReactComponent={}; +E.prototype.setState=function(a,b){if("object"!==typeof a&&"function"!==typeof a&&null!=a)throw Error("setState(...): takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,a,b,"setState")};E.prototype.forceUpdate=function(a){this.updater.enqueueForceUpdate(this,a,"forceUpdate")};function F(){}F.prototype=E.prototype;function G(a,b,e){this.props=a;this.context=b;this.refs=D;this.updater=e||B}var H=G.prototype=new F; +H.constructor=G;C(H,E.prototype);H.isPureReactComponent=!0;var I=Array.isArray,J=Object.prototype.hasOwnProperty,K={current:null},L={key:!0,ref:!0,__self:!0,__source:!0}; +function M(a,b,e){var d,c={},k=null,h=null;if(null!=b)for(d in void 0!==b.ref&&(h=b.ref),void 0!==b.key&&(k=""+b.key),b)J.call(b,d)&&!L.hasOwnProperty(d)&&(c[d]=b[d]);var g=arguments.length-2;if(1===g)c.children=e;else if(1 { + + + +if (true) { + module.exports = __webpack_require__(408); +} else {} + + +/***/ }), + +/***/ 53: +/***/ ((__unused_webpack_module, exports) => { + +/** + * @license React + * scheduler.production.min.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +function f(a,b){var c=a.length;a.push(b);a:for(;0>>1,e=a[d];if(0>>1;dg(C,c))ng(x,C)?(a[d]=x,a[n]=c,d=n):(a[d]=C,a[m]=c,d=m);else if(ng(x,c))a[d]=x,a[n]=c,d=n;else break a}}return b} +function g(a,b){var c=a.sortIndex-b.sortIndex;return 0!==c?c:a.id-b.id}if("object"===typeof performance&&"function"===typeof performance.now){var l=performance;exports.unstable_now=function(){return l.now()}}else{var p=Date,q=p.now();exports.unstable_now=function(){return p.now()-q}}var r=[],t=[],u=1,v=null,y=3,z=!1,A=!1,B=!1,D="function"===typeof setTimeout?setTimeout:null,E="function"===typeof clearTimeout?clearTimeout:null,F="undefined"!==typeof setImmediate?setImmediate:null; +"undefined"!==typeof navigator&&void 0!==navigator.scheduling&&void 0!==navigator.scheduling.isInputPending&&navigator.scheduling.isInputPending.bind(navigator.scheduling);function G(a){for(var b=h(t);null!==b;){if(null===b.callback)k(t);else if(b.startTime<=a)k(t),b.sortIndex=b.expirationTime,f(r,b);else break;b=h(t)}}function H(a){B=!1;G(a);if(!A)if(null!==h(r))A=!0,I(J);else{var b=h(t);null!==b&&K(H,b.startTime-a)}} +function J(a,b){A=!1;B&&(B=!1,E(L),L=-1);z=!0;var c=y;try{G(b);for(v=h(r);null!==v&&(!(v.expirationTime>b)||a&&!M());){var d=v.callback;if("function"===typeof d){v.callback=null;y=v.priorityLevel;var e=d(v.expirationTime<=b);b=exports.unstable_now();"function"===typeof e?v.callback=e:v===h(r)&&k(r);G(b)}else k(r);v=h(r)}if(null!==v)var w=!0;else{var m=h(t);null!==m&&K(H,m.startTime-b);w=!1}return w}finally{v=null,y=c,z=!1}}var N=!1,O=null,L=-1,P=5,Q=-1; +function M(){return exports.unstable_now()-Qa||125d?(a.sortIndex=c,f(t,a),null===h(r)&&a===h(t)&&(B?(E(L),L=-1):B=!0,K(H,c-d))):(a.sortIndex=e,f(r,a),A||z||(A=!0,I(J)));return a}; +exports.unstable_shouldYield=M;exports.unstable_wrapCallback=function(a){var b=y;return function(){var c=y;y=b;try{return a.apply(this,arguments)}finally{y=c}}}; + + +/***/ }), + +/***/ 840: +/***/ ((module, __unused_webpack_exports, __webpack_require__) => { + + + +if (true) { + module.exports = __webpack_require__(53); +} else {} + + +/***/ }) + +}]); \ No newline at end of file diff --git a/browser/components/pocket/content/panels/js/vendor.bundle.js.LICENSE.txt b/browser/components/pocket/content/panels/js/vendor.bundle.js.LICENSE.txt new file mode 100644 index 0000000000..d5075bd9ca --- /dev/null +++ b/browser/components/pocket/content/panels/js/vendor.bundle.js.LICENSE.txt @@ -0,0 +1,32 @@ +/* +object-assign +(c) Sindre Sorhus +@license MIT +*/ + +/** @license React v0.20.2 + * scheduler.production.min.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +/** @license React v17.0.2 + * react-dom.production.min.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +/** @license React v17.0.2 + * react.production.min.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ diff --git a/browser/components/pocket/content/panels/license.txt b/browser/components/pocket/content/panels/license.txt new file mode 100644 index 0000000000..7f3f806bab --- /dev/null +++ b/browser/components/pocket/content/panels/license.txt @@ -0,0 +1,35 @@ + +Unless where otherwise noted, the following license applies to the files +within this directory and descendents of this directory. + +POCKET MARKS + +Notwithstanding the permitted uses of the Software (as defined below) pursuant +to the license set forth below, "Pocket," "Read It Later" and the Pocket icon +and logos (collectively, the “Pocket Marks”) are registered and common law +trademarks of Read It Later, Inc. This means that, while you have considerable +freedom to redistribute and modify the Software, there are tight restrictions +on your ability to use the Pocket Marks. This license does not grant you any +rights to use the Pocket Marks except as they are embodied in the Software. + +--- + +SOFTWARE + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/browser/components/pocket/content/panels/saved.html b/browser/components/pocket/content/panels/saved.html new file mode 100644 index 0000000000..7042a59c9e --- /dev/null +++ b/browser/components/pocket/content/panels/saved.html @@ -0,0 +1,20 @@ + + + + + + + + + Pocket: Page Saved + + + + + + + + diff --git a/browser/components/pocket/content/panels/signup.html b/browser/components/pocket/content/panels/signup.html new file mode 100644 index 0000000000..349d3432c3 --- /dev/null +++ b/browser/components/pocket/content/panels/signup.html @@ -0,0 +1,21 @@ + + + + + + + + + + Pocket: Sign Up + + + + + + + + diff --git a/browser/components/pocket/content/panels/style-guide.html b/browser/components/pocket/content/panels/style-guide.html new file mode 100644 index 0000000000..cdc3368178 --- /dev/null +++ b/browser/components/pocket/content/panels/style-guide.html @@ -0,0 +1,34 @@ + + + + + + + + + Pocket: Style Guide + + + + + + + +
    +
    +
    + + +
    +
    +

    + Save To Pocket:
    + Style Guide +

    +
    +
    + + diff --git a/browser/components/pocket/content/pktApi.sys.mjs b/browser/components/pocket/content/pktApi.sys.mjs new file mode 100644 index 0000000000..b63e0f8f65 --- /dev/null +++ b/browser/components/pocket/content/pktApi.sys.mjs @@ -0,0 +1,926 @@ +/* + * LICENSE + * + * POCKET MARKS + * + * Notwithstanding the permitted uses of the Software (as defined below) pursuant to the license set forth below, "Pocket," "Read It Later" and the Pocket icon and logos (collectively, the “Pocket Marks”) are registered and common law trademarks of Read It Later, Inc. This means that, while you have considerable freedom to redistribute and modify the Software, there are tight restrictions on your ability to use the Pocket Marks. This license does not grant you any rights to use the Pocket Marks except as they are embodied in the Software. + * + * --- + * + * SOFTWARE + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/* + * Pocket API module + * + * Public API Documentation: http://getpocket.com/developer/ + * + * + * Definition of keys stored in preferences to preserve user state: + * premium_status: Current premium status for logged in user if available + * Can be 0 for no premium and 1 for premium + * latestSince: Last timestamp a save happened + * tags: All tags for logged in user + * usedTags: All used tags from within the extension sorted by recency + */ + +import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; + +const lazy = {}; +ChromeUtils.defineESModuleGetters(lazy, { + IndexedDB: "resource://gre/modules/IndexedDB.sys.mjs", + NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs", + PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs", +}); + +XPCOMUtils.defineLazyPreferenceGetter( + lazy, + "gCookieFirstPartyIsolate", + "privacy.firstparty.isolate", + false +); + +const DB_NAME = "SaveToPocket"; +const STORE_NAME = "pktAPI"; +const DB_VERSION = 1; +const RECENT_SAVES_UPDATE_TIME = 5 * 60 * 1000; // 30 minutes + +/** + * Create a new connection to the database. + */ +function openDatabase() { + return lazy.IndexedDB.open(DB_NAME, DB_VERSION, db => { + db.createObjectStore(STORE_NAME); + }); +} + +/** + * Cache the database connection so that it is shared among multiple operations. + */ +let databasePromise; +function getDatabase() { + if (!databasePromise) { + databasePromise = openDatabase(); + } + return databasePromise; +} + +export var pktApi = (function () { + /** + * Configuration + */ + + // Base url for all api calls + var pocketSiteHost = Services.prefs.getCharPref("extensions.pocket.site"); // getpocket.com + + /** + * + */ + var prefBranch = Services.prefs.getBranch("extensions.pocket.settings."); + + /** + * Helper + */ + + var extend = function (out) { + out = out || {}; + + for (var i = 1; i < arguments.length; i++) { + if (!arguments[i]) { + continue; + } + + for (var key in arguments[i]) { + if (arguments[i].hasOwnProperty(key)) { + out[key] = arguments[i][key]; + } + } + } + return out; + }; + + var parseJSON = function (jsonString) { + try { + var o = JSON.parse(jsonString); + + // Handle non-exception-throwing cases: + // Neither JSON.parse(false) or JSON.parse(1234) throw errors, hence the type-checking, + // but... JSON.parse(null) returns 'null', and typeof null === "object", + // so we must check for that, too. + if (o && typeof o === "object" && o !== null) { + return o; + } + } catch (e) {} + + return undefined; + }; + + /** + * Settings + */ + + /** + * Wrapper for different plattforms to get settings for a given key + * @param {string} key A string containing the name of the key you want to + * retrieve the value of + * @return {string} String containing the value of the key. If the key + * does not exist, null is returned + */ + function getSetting(key) { + // TODO : Move this to sqlite or a local file so it's not editable (and is safer) + // https://developer.mozilla.org/en-US/Add-ons/Overlay_Extensions/XUL_School/Local_Storage + + if (!prefBranch.prefHasUserValue(key)) { + return undefined; + } + + return prefBranch.getStringPref(key); + } + + /** + * Wrapper for different plattforms to set a value for a given key in settings + * @param {string} key A string containing the name of the key you want + * to create/update. + * @param {string} value String containing the value you want to give + * the key you are creating/updating. + */ + function setSetting(key, value) { + // TODO : Move this to sqlite or a local file so it's not editable (and is safer) + // https://developer.mozilla.org/en-US/Add-ons/Overlay_Extensions/XUL_School/Local_Storage + + if (!value) { + prefBranch.clearUserPref(key); + } else { + // We use complexValue as tags can have utf-8 characters in them + prefBranch.setStringPref(key, value); + } + } + + /** + * Auth + */ + + /* + * All cookies from the Pocket domain + * The return format: { cookieName:cookieValue, cookieName:cookieValue, ... } + */ + function getCookiesFromPocket() { + var cookies = {}; + let oa = {}; + if (lazy.gCookieFirstPartyIsolate) { + oa.firstPartyDomain = pocketSiteHost; + } + oa.privateBrowsingId = lazy.PrivateBrowsingUtils.permanentPrivateBrowsing + ? 1 + : 0; + for (let cookie of Services.cookies.getCookiesFromHost( + pocketSiteHost, + oa + )) { + if (cookie.host === pocketSiteHost) { + cookies[cookie.name] = cookie.value; + } + } + return cookies; + } + + /** + * Returns access token or undefined if no logged in user was found + * @return {string | undefined} Access token for logged in user user + */ + function getAccessToken() { + var pocketCookies = getCookiesFromPocket(); + + // If no cookie was found just return undefined + if (typeof pocketCookies.ftv1 === "undefined") { + return undefined; + } + + // Check if a new user logged in in the meantime and clearUserData if so + var sessionId = pocketCookies.fsv1; + var lastSessionId = getSetting("fsv1"); + if (sessionId !== lastSessionId) { + clearUserData(); + setSetting("fsv1", sessionId); + } + + // Return access token + return pocketCookies.ftv1; + } + + /** + * Get the current premium status of the user + * @return {number | undefined} Premium status of user + */ + function getPremiumStatus() { + var premiumStatus = getSetting("premium_status"); + if (typeof premiumStatus === "undefined") { + // Premium status is not in settings try get it from cookie + var pocketCookies = getCookiesFromPocket(); + premiumStatus = pocketCookies.ps; + } + return premiumStatus; + } + + /** + * Helper method to check if a user is premium or not + * @return {Boolean} Boolean if user is premium or not + */ + function isPremiumUser() { + return getPremiumStatus() == 1; + } + + /** + * Returns users logged in status + * @return {Boolean} Users logged in status + */ + function isUserLoggedIn() { + return typeof getAccessToken() !== "undefined"; + } + + /** + * API + */ + + /** + * Helper function for executing api requests. It mainly configures the + * ajax call with default values like type, headers or dataType for an api call. + * This function is for internal usage only. + * @param {Object} options + * Possible keys: + * - {string} path: This should be the Pocket API + * endpoint to call. For example providing the path + * "/get" would result in a call to getpocket.com/v3/get + * - {Object|undefined} data: Gets passed on to the jQuery ajax + * call as data parameter + * - {function(Object data, XMLHttpRequest xhr) | undefined} success: + * A function to be called if the request succeeds. + * - {function(Error errorThrown, XMLHttpRequest xhr) | undefined} error: + * A function to be called if the request fails. + * @return {Boolean} Returns Boolean whether the api call started sucessfully + * + */ + function apiRequest(options, useBFF = false) { + let baseAPIUrl; + let oAuthConsumerKey; + + if (!useBFF) { + baseAPIUrl = `https://${Services.prefs.getCharPref( + "extensions.pocket.api" + )}/v3`; + + oAuthConsumerKey = Services.prefs.getCharPref( + "extensions.pocket.oAuthConsumerKey" + ); + } else { + baseAPIUrl = `https://${lazy.NimbusFeatures.saveToPocket.getVariable( + "bffApi" + )}/desktop/v1`; + + oAuthConsumerKey = lazy.NimbusFeatures.saveToPocket.getVariable( + "oAuthConsumerKeyBff" + ); + } + + if (typeof options === "undefined" || typeof options.path === "undefined") { + return false; + } + + var url = baseAPIUrl + options.path; + var data = options.data || {}; + data.locale_lang = Services.locale.appLocaleAsBCP47; + data.consumer_key = oAuthConsumerKey; + + var request = new XMLHttpRequest(); + + if (!useBFF) { + request.open("POST", url, true); + } else { + request.open("GET", url, true); + } + + request.onreadystatechange = function (e) { + if (request.readyState == 4) { + // "done" is a completed XHR regardless of success/error: + if (options.done) { + options.done(); + } + + if (request.status === 200) { + // There could still be an error if the response is no valid json + // or does not have status = 1 + var response = parseJSON(request.response); + + // BFF doesn't return an appended `status` code in the returned data + if (options.success && response && (response.status == 1 || useBFF)) { + options.success(response, request); + return; + } + } + + // Handle error case + if (options.error) { + // In case the user did revoke the access token or it's not + // valid anymore clear the user data + if (request.status === 401) { + clearUserData(); + } + + // Handle error message + var errorMessage; + if (request.status !== 200) { + errorMessage = + request.getResponseHeader("X-Error") || request.statusText; + errorMessage = JSON.parse('"' + errorMessage + '"'); + } + var error = { message: errorMessage }; + options.error(error, request); + } + } + }; + + // Set headers + request.setRequestHeader( + "Content-Type", + "application/x-www-form-urlencoded; charset=UTF-8" + ); + request.setRequestHeader("X-Accept", " application/json"); + + if (useBFF) { + let cookies = getCookiesFromPocket(); + let serializedCookies = ``; + + for (const key in cookies) { + serializedCookies += `${key}=${cookies[key]}; `; + } + + serializedCookies = serializedCookies.substring( + 0, + serializedCookies.length - 2 + ); + + request.setRequestHeader("Cookie", serializedCookies); + request.setRequestHeader("consumer_key", oAuthConsumerKey); + } + + // Serialize and Fire off the request + var str = []; + for (var p in data) { + if (data.hasOwnProperty(p)) { + str.push(encodeURIComponent(p) + "=" + encodeURIComponent(data[p])); + } + } + + request.send(str.join("&")); + + return true; + } + + /** + * Cleans all settings for the previously logged in user + */ + function clearUserData() { + // Clear stored information + setSetting("premium_status", undefined); + setSetting("latestSince", undefined); + setSetting("tags", undefined); + // An old pref that is no longer used, + // but the user data may still exist on some profiles. + // So best to clean it up just in case. + // Can probably remove this line in the future. + setSetting("usedTags", undefined); + + setSetting("fsv1", undefined); + + _clearRecentSavesCache(); + } + + /** + * Add a new link to Pocket + * @param {string} url URL of the link + * @param {Object | undefined} options Can provide a string-based title, a + * `success` callback and an `error` callback. + * @return {Boolean} Returns Boolean whether the api call started sucessfully + */ + function addLink(url, options) { + var since = getSetting("latestSince"); + var accessToken = getAccessToken(); + + var sendData = { + access_token: accessToken, + url, + since: since ? since : 0, + }; + + if (options.title) { + sendData.title = options.title; + } + + return apiRequest({ + path: "/firefox/save", + data: sendData, + success(data) { + // Update premium status, tags and since + var tags = data.tags; + if (typeof tags !== "undefined" && Array.isArray(tags)) { + // If a tagslist is in the response replace the tags + setSetting("tags", JSON.stringify(data.tags)); + } + + // Update premium status + var premiumStatus = data.premium_status; + if (typeof premiumStatus !== "undefined") { + // If a premium_status is in the response replace the premium_status + setSetting("premium_status", premiumStatus); + } + + // Save since value for further requests + setSetting("latestSince", data.since); + + // Define variant for ho2 + if (data.flags) { + var showHo2 = + Services.locale.appLocaleAsBCP47 === "en-US" + ? data.flags.show_ffx_mobile_prompt + : "control"; + setSetting("test.ho2", showHo2); + } + data.ho2 = getSetting("test.ho2"); + + _expireRecentSavesCache(); + if (options.success) { + options.success.apply(options, Array.apply(null, arguments)); + } + }, + error: options.error, + }); + } + + /** + * Gets a list of related recommendations for the item + * @param {string} itemId Item id of item + * @param {Object | undefined} options Can provide a string-based title, a + * `success` callback and an `error` callback. + * @return {Boolean} Returns Boolean whether the api call started sucessfully + */ + function getRecsForItem(itemId, options) { + return apiRequest({ + path: "/discover/recIt", + data: { + item_id: itemId, + module: "ff_plugin", + count: 3, + }, + success(data) { + if (options.success) { + options.success.apply(options, Array.apply(null, arguments)); + } + }, + error: options.error, + }); + } + + /** + * Get a preview for saved URL + * @param {string} url URL of the link + * @param {Object | undefined} options Can provide a `success` callback and an `error` callback. + * @return {Boolean} Returns Boolean whether the api call started sucessfully + */ + function getArticleInfo(url, options) { + return apiRequest({ + path: "/getItemPreview", + data: { + access_token: getAccessToken(), + url, + }, + success(data) { + if (options.success) { + options.success.apply(options, Array.apply(null, arguments)); + } + }, + error: options.error, + done: options.done, + }); + } + + /** + * Request a email for mobile apps + * @param {Object | undefined} options Can provide a `success` callback and an `error` callback. + * @return {Boolean} Returns Boolean whether the api call started sucessfully + */ + function getMobileDownload(options) { + return apiRequest({ + path: "/firefox/get-app", + data: { + access_token: getAccessToken(), + }, + success(data) { + if (options.success) { + options.success.apply(options, Array.apply(null, arguments)); + } + }, + error: options.error, + }); + } + + /** + * Delete an item identified by item id from the users list + * @param {string} itemId The id from the item we want to remove + * @param {Object | undefined} options Can provide an actionInfo object with + * further data to send to the API. Can + * have success and error callbacks + * @return {Boolean} Returns Boolean whether the api call started sucessfully + */ + function deleteItem(itemId, options) { + var action = { + action: "delete", + item_id: itemId, + }; + return sendAction(action, options); + } + + /** + * Archive an item identified by item id from the users list + * @param {string} itemId The id from the item we want to archive + * @param {Object | undefined} options Can provide an actionInfo object with + * further data to send to the API. Can + * have success and error callbacks + * @return {Boolean} Returns Boolean whether the api call started sucessfully + */ + function archiveItem(itemId, options) { + var action = { + action: "archive", + item_id: itemId, + }; + return sendAction(action, options); + } + + /** + * General function to send all kinds of actions like adding of links or + * removing of items via the API + * @param {Object} action Action object + * @param {Object | undefined} options Can provide an actionInfo object + * with further data to send to the + * API. Can have success and error + * callbacks + * @return {Boolean} Returns Boolean whether the api call started sucessfully + */ + function sendAction(action, options) { + // Options can have an 'actionInfo' object. This actionInfo object gets + // passed through to the action object that will be send to the API endpoint + if (typeof options.actionInfo !== "undefined") { + action = extend(action, options.actionInfo); + } + return sendActions([action], options); + } + + /** + * General function to send all kinds of actions like adding of links or + * removing of items via the API + * @param {Array} actions Array of action objects + * @param {Object | undefined} options Can have success and error callbacks + * @return {Boolean} Returns Boolean whether the api call started sucessfully + */ + function sendActions(actions, options) { + return apiRequest({ + path: "/send", + data: { + access_token: getAccessToken(), + actions: JSON.stringify(actions), + }, + success: options.success, + error: options.error, + }); + } + + /** + * Handling Tags + */ + + /** + * Add tags to the item identified by the url. Also updates the used tags + * list + * @param {string} itemId The item identifier by item id + * @param {Array} tags Tags adding to the item + * @param {Object | undefined} options Can provide an actionInfo object with + * further data to send to the API. Can + * have success and error callbacks + * @return {Boolean} Returns Boolean whether the api call started sucessfully + */ + function addTagsToItem(itemId, tags, options) { + return addTags({ item_id: itemId }, tags, options); + } + + /** + * Add tags to the item identified by the url. Also updates the used tags + * list + * @param {string} url The item identifier by url + * @param {Array} tags Tags adding to the item + * @param {Object} options Can provide an actionInfo object with further + * data to send to the API. Can have success and error + * callbacks + * @return {Boolean} Returns Boolean whether the api call started sucessfully + */ + function addTagsToURL(url, tags, options) { + return addTags({ url }, tags, options); + } + + /** + * Helper function to execute the add tags api call. Will be used from addTagsToURL + * and addTagsToItem but not exposed outside + * @param {string} actionPart Specific action part to add to action + * @param {Array} tags Tags adding to the item + * @param {Object | undefined} options Can provide an actionInfo object with + * further data to send to the API. Can + * have success and error callbacks + * @return {Boolean} Returns Boolean whether the api call started sucessfully + */ + function addTags(actionPart, tags, options) { + if (tags.length) { + addRecentTags(tags); + } + + // Tags add action + var action = { + action: "tags_add", + tags, + }; + action = extend(action, actionPart); + + // Execute the action + return sendAction(action, options); + } + + /** + * Return all cached tags and used tags. + */ + function getTags() { + var tagsFromSettings = function () { + var tagsJSON = getSetting("tags"); + if (typeof tagsJSON !== "undefined") { + return JSON.parse(tagsJSON); + } + return []; + }; + + return { + tags: tagsFromSettings(), + }; + } + + /** + * Return all recent tags. + */ + function getRecentTags() { + var tagsFromSettings = function () { + var tagsJSON = getSetting("recentTags"); + + if (typeof tagsJSON !== "undefined") { + let parsedTags; + + try { + parsedTags = JSON.parse(tagsJSON); + } catch { + parsedTags = []; + } + + return parsedTags; + } + + return []; + }; + + return { + recentTags: tagsFromSettings(), + }; + } + + /** + * Store recently used tags. + * @param {Array} tags Newly used tags to store + */ + function addRecentTags(tags) { + var newRecentTags = tags || []; + var cachedRecentTags = getRecentTags()?.recentTags; + var mergedRecentTags = []; + + cachedRecentTags.forEach(tag => { + if (!newRecentTags.includes(tag)) { + mergedRecentTags.push(tag); + } + }); + + mergedRecentTags = [...newRecentTags, ...mergedRecentTags]; + + // update recent tags pref to store + setSetting("recentTags", JSON.stringify(mergedRecentTags)); + } + + /** + * Fetch suggested tags for a given item id + * @param {string} itemId Item id of + * @param {Object | undefined} options Can provide an actionInfo object + * with further data to send to the API. + * Can have success and error callbacks + * @return {Boolean} Returns Boolean whether the api call started sucessfully + */ + function getSuggestedTagsForItem(itemId, options) { + return getSuggestedTags({ item_id: itemId }, options); + } + + /** + * Fetch suggested tags for a given URL + * @param {string} url (required) The item identifier by url + * @param {Object} options Can provide an actionInfo object with further + * data to send to the API. Can have success and error + * callbacks + * @return {Boolean} Returns Boolean whether the api call started sucessfully + */ + function getSuggestedTagsForURL(url, options) { + return getSuggestedTags({ url }, options); + } + + /** + * Helper function to get suggested tags + * @return {Boolean} Returns Boolean whether the api call started sucessfully + */ + function getSuggestedTags(data, options) { + data = data || {}; + options = options || {}; + + data.access_token = getAccessToken(); + + return apiRequest({ + path: "/getSuggestedTags", + data, + success: options.success, + error: options.error, + }); + } + + /** + * Helper function to get a user's pocket stories + * @return {Boolean} Returns Boolean whether the api call started sucessfully + */ + function retrieve(data = {}, options = {}) { + const requestData = Object.assign({}, data, { + access_token: getAccessToken(), + }); + + const useBFF = + lazy.NimbusFeatures.saveToPocket.getVariable("bffRecentSaves"); + + return apiRequest( + { + path: useBFF ? `/recent-saves?count=${data.count}` : `/firefox/get`, + data: requestData, + success: options.success, + error: options.error, + }, + useBFF + ); // Use BFF + } + + async function _getRecentSavesCache() { + const db = await getDatabase(); + return db.objectStore(STORE_NAME, "readonly").get("recentSaves"); + } + async function _setRecentSavesCache(data) { + const db = await getDatabase(); + db.objectStore(STORE_NAME, "readwrite").put(data, "recentSaves"); + } + // Clears the cache time, so the next get forces an update. + async function _expireRecentSavesCache() { + const cache = await _getRecentSavesCache(); + _setRecentSavesCache({ + ...cache, + lastUpdated: 0, + }); + } + // Clears the cache, for when a new user logs in. + async function _clearRecentSavesCache() { + const db = await getDatabase(); + db.objectStore(STORE_NAME, "readwrite").delete("recentSaves"); + } + + async function getRecentSavesCache() { + // Get cache + const cache = await _getRecentSavesCache(); + // Check age + if ( + cache?.lastUpdated && + Date.now() - cache.lastUpdated < RECENT_SAVES_UPDATE_TIME + ) { + // Return cache if it's not too old. + return cache.list; + } + return null; + } + + async function getRecentSaves(options = {}) { + pktApi.retrieve( + { count: 4 }, + { + success(data) { + const useBFF = + lazy.NimbusFeatures.saveToPocket.getVariable("bffRecentSaves"); + + // Don't try to parse bad or missing data + if ( + useBFF && + (typeof data !== `object` || typeof data?.data !== `object`) + ) { + return; + } + + try { + let list = useBFF ? [] : data.list; + + if (useBFF) { + // Transform BFF list item schema to existing api schema + data.data.forEach((item, index) => { + list[index] = { + item_id: item.id, + id: item.id, // This can probably be deprecated when the old API is + resolved_url: item.resolvedUrl, + given_url: item.givenUrl, + resolved_title: item.title, + excerpt: item.excerpt, + word_count: item.wordCount, + time_to_read: item.timeToRead, + top_image_url: item.topImageUrl, + }; + }); + } else { + // We want these to show up in the same order as they saved, + // so we need to do some work and sort. + list = Object.values(list) + .map(item => ({ + ...item, + id: parseInt(item.item_id || item.resolved_id, 10), + time_added: parseInt(item.time_added), + })) + .sort((a, b) => b.time_added - a.time_added); + } + + // Cache results + const results = { + lastUpdated: Date.now(), + list, + }; + + _setRecentSavesCache(results); + options.success?.(results.list); + } catch { + // If parsing fails, just leave existing recent saves cache intact + } + }, + error(error) { + options.error?.(error); + }, + } + ); + } + + /** + * Public functions + */ + return { + isUserLoggedIn, + clearUserData, + addLink, + getRecsForItem, + deleteItem, + archiveItem, + addTagsToItem, + addTagsToURL, + getTags, + getRecentTags, + isPremiumUser, + getSuggestedTagsForItem, + getSuggestedTagsForURL, + retrieve, + getRecentSavesCache, + getRecentSaves, + getArticleInfo, + getMobileDownload, + }; +})(); diff --git a/browser/components/pocket/content/pktTelemetry.sys.mjs b/browser/components/pocket/content/pktTelemetry.sys.mjs new file mode 100644 index 0000000000..602d3bda25 --- /dev/null +++ b/browser/components/pocket/content/pktTelemetry.sys.mjs @@ -0,0 +1,117 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; + +const lazy = {}; +ChromeUtils.defineModuleGetter( + lazy, + "PingCentre", + "resource:///modules/PingCentre.jsm" +); +ChromeUtils.defineESModuleGetters(lazy, { + pktApi: "chrome://pocket/content/pktApi.sys.mjs", + TelemetryEnvironment: "resource://gre/modules/TelemetryEnvironment.sys.mjs", +}); + +// List of namespaces for the structured ingestion system. +// They are defined in https://github.com/mozilla-services/mozilla-pipeline-schemas +const STRUCTURED_INGESTION_NAMESPACE_AS = "activity-stream"; +const STRUCTURED_INGESTION_ENDPOINT_PREF = + "browser.newtabpage.activity-stream.telemetry.structuredIngestion.endpoint"; +// This is the topic for telemetry pings, used inside PingCentre. +const POCKET_TELEMETRY_TOPIC = "pocket"; +const PREF_IMPRESSION_ID = "browser.newtabpage.activity-stream.impressionId"; + +XPCOMUtils.defineLazyGetter(lazy, "pingCentre", () => { + return new lazy.PingCentre({ topic: POCKET_TELEMETRY_TOPIC }); +}); + +export var pktTelemetry = { + get structuredIngestionEndpointBase() { + if (!this._structuredIngestionEndpointBase) { + this._structuredIngestionEndpointBase = Services.prefs.getStringPref( + STRUCTURED_INGESTION_ENDPOINT_PREF, + "" + ); + } + return this._structuredIngestionEndpointBase; + }, + + get impressionId() { + if (!this._impressionId) { + this._impressionId = this.getOrCreateImpressionId(); + } + return this._impressionId; + }, + + // Sets or gets the impression id that's use for Pocket impressions. + // The impression id cannot be tied to a client id. + // This is the same impression id used in newtab pocket impressions. + getOrCreateImpressionId() { + let impressionId = Services.prefs.getStringPref(PREF_IMPRESSION_ID, ""); + + if (!impressionId) { + impressionId = String(Services.uuid.generateUUID()); + Services.prefs.setStringPref(PREF_IMPRESSION_ID, impressionId); + } + return impressionId; + }, + + /** + * createPingPayload - Create a ping for an impression stats + * + * @param {ob} data The data object to be included in the ping. + * @return {obj} A telemetry ping + */ + createPingPayload(data) { + // experiments, locale, version, and release_channel are provided by pingCentre. + // model and events is provided in the data param. + return { + ...data, + impression_id: this.impressionId, + pocket_logged_in_status: lazy.pktApi.isUserLoggedIn(), + profile_creation_date: this._profileCreationDate(), + }; + }, + + _profileCreationDate() { + return ( + lazy.TelemetryEnvironment.currentEnvironment.profile.resetDate || + lazy.TelemetryEnvironment.currentEnvironment.profile.creationDate + ); + }, + + _generateUUID() { + return String(Services.uuid.generateUUID()); + }, + + /** + * Generates an endpoint for Structured Ingestion telemetry pipeline. Note that + * Structured Ingestion requires a different endpoint for each ping. See more + * details about endpoint schema at: + * https://github.com/mozilla/gcp-ingestion/blob/master/docs/edge.md#postput-request + */ + _generateStructuredIngestionEndpoint() { + const uuid = this._generateUUID(); + // Structured Ingestion does not support the UUID generated by gUUIDGenerator, + // because it contains leading and trailing braces. Need to trim them first. + const docID = uuid.slice(1, -1); + const extension = `${STRUCTURED_INGESTION_NAMESPACE_AS}/pocket-button/1/${docID}`; + return `${this.structuredIngestionEndpointBase}/${extension}`; + }, + + /** + * sendStructuredIngestionEvent - Sent a ping for an impression stats + * + * @param {ob} eventObject The data object to be included in the ping. + */ + sendStructuredIngestionEvent(eventObject) { + lazy.pingCentre.sendStructuredIngestionPing( + eventObject, + this._generateStructuredIngestionEndpoint(), + STRUCTURED_INGESTION_NAMESPACE_AS + ); + }, +}; diff --git a/browser/components/pocket/content/pktUI.js b/browser/components/pocket/content/pktUI.js new file mode 100644 index 0000000000..6baa304733 --- /dev/null +++ b/browser/components/pocket/content/pktUI.js @@ -0,0 +1,665 @@ +/* + * LICENSE + * + * POCKET MARKS + * + * Notwithstanding the permitted uses of the Software (as defined below) pursuant to the license set forth below, "Pocket," "Read It Later" and the Pocket icon and logos (collectively, the “Pocket Marks”) are registered and common law trademarks of Read It Later, Inc. This means that, while you have considerable freedom to redistribute and modify the Software, there are tight restrictions on your ability to use the Pocket Marks. This license does not grant you any rights to use the Pocket Marks except as they are embodied in the Software. + * + * --- + * + * SOFTWARE + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/* + * Pocket UI module + * + * Handles interactions with Pocket buttons, panels and menus. + * + */ + +// TODO : Get the toolbar icons from Firefox's build (Nikki needs to give us a red saved icon) +// TODO : [needs clarificaiton from Fx] Firefox's plan was to hide Pocket from context menus until the user logs in. Now that it's an extension I'm wondering if we still need to do this. +// TODO : [needs clarificaiton from Fx] Reader mode (might be a something they need to do since it's in html, need to investigate their code) +// TODO : [needs clarificaiton from Fx] Move prefs within pktApi.s to sqlite or a local file so it's not editable (and is safer) +// TODO : [nice to have] - Immediately save, buffer the actions in a local queue and send (so it works offline, works like our native extensions) + +/* eslint-disable no-shadow */ +/* eslint-env mozilla/browser-window */ + +ChromeUtils.defineESModuleGetters(this, { + ExperimentAPI: "resource://nimbus/ExperimentAPI.sys.mjs", + NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs", + pktApi: "chrome://pocket/content/pktApi.sys.mjs", + pktTelemetry: "chrome://pocket/content/pktTelemetry.sys.mjs", + PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs", + ReaderMode: "resource://gre/modules/ReaderMode.sys.mjs", + SaveToPocket: "chrome://pocket/content/SaveToPocket.sys.mjs", +}); + +const POCKET_ONSAVERECS_PREF = "extensions.pocket.onSaveRecs"; +const POCKET_ONSAVERECS_LOCLES_PREF = "extensions.pocket.onSaveRecs.locales"; +const POCKET_HOME_PREF = "extensions.pocket.showHome"; + +var pktUI = (function () { + let _titleToSave = ""; + let _urlToSave = ""; + + // Initial sizes are only here to help visual load jank before the panel is ready. + const initialPanelSize = { + signup: { + height: 315, + width: 328, + }, + saved: { + height: 110, + width: 350, + }, + home: { + height: 251, + width: 328, + }, + // This is for non English sizes, this is not for an AB experiment. + home_no_topics: { + height: 86, + width: 328, + }, + }; + + var onSaveRecsEnabledPref; + var onSaveRecsLocalesPref; + var pocketHomePref; + + function initPrefs() { + onSaveRecsEnabledPref = Services.prefs.getBoolPref( + POCKET_ONSAVERECS_PREF, + false + ); + onSaveRecsLocalesPref = Services.prefs.getStringPref( + POCKET_ONSAVERECS_LOCLES_PREF, + "" + ); + + pocketHomePref = Services.prefs.getBoolPref(POCKET_HOME_PREF); + } + initPrefs(); + + // -- Communication to API -- // + + /** + * Either save or attempt to log the user in + */ + function tryToSaveCurrentPage() { + tryToSaveUrl(getCurrentUrl(), getCurrentTitle()); + } + + function tryToSaveUrl(url, title) { + // Validate input parameter + if (typeof url !== "undefined" && url.startsWith("about:reader?url=")) { + url = ReaderMode.getOriginalUrl(url); + } + + // If the user is not logged in, show the logged-out state to prompt them to authenticate + if (!pktApi.isUserLoggedIn()) { + showSignUp(); + return; + } + + _titleToSave = title; + _urlToSave = url; + // If the user is logged in, and the url is valid, go ahead and save the current page + if (!pocketHomePref || isValidURL()) { + saveAndShowConfirmation(); + return; + } + showPocketHome(); + } + + // -- Panel UI -- // + + /** + * Show the sign-up panel + */ + function showSignUp() { + getFirefoxAccountSignedInUser(function (userdata) { + showPanel( + "about:pocket-signup?" + + "emailButton=" + + NimbusFeatures.saveToPocket.getVariable("emailButton"), + `signup` + ); + }); + } + + /** + * Get a list of recs for item and show them in the panel. + */ + function getAndShowRecsForItem(item, options) { + var onSaveRecsEnabled = + onSaveRecsEnabledPref && onSaveRecsLocalesPref.includes(getUILocale()); + + if ( + onSaveRecsEnabled && + item && + item.resolved_id && + item.resolved_id !== "0" + ) { + pktApi.getRecsForItem(item.resolved_id, options); + } + } + + /** + * Show the logged-out state / sign-up panel + */ + function saveAndShowConfirmation() { + getFirefoxAccountSignedInUser(function (userdata) { + showPanel( + "about:pocket-saved?premiumStatus=" + + (pktApi.isPremiumUser() ? "1" : "0") + + "&fxasignedin=" + + (typeof userdata == "object" && userdata !== null ? "1" : "0"), + `saved` + ); + }); + } + + /** + * Show the Pocket home panel state + */ + function showPocketHome() { + const hideRecentSaves = + NimbusFeatures.saveToPocket.getVariable("hideRecentSaves"); + const locale = getUILocale(); + let panel = `home_no_topics`; + if (locale.startsWith("en-")) { + panel = `home`; + } + showPanel(`about:pocket-home?hiderecentsaves=${hideRecentSaves}`, panel); + } + + /** + * Open a generic panel + */ + function showPanel(urlString, panel) { + const locale = getUILocale(); + const options = initialPanelSize[panel]; + + resizePanel({ + width: options.width, + height: options.height, + }); + + const saveToPocketExperiment = ExperimentAPI.getExperimentMetaData({ + featureId: "saveToPocket", + }); + + const saveToPocketRollout = ExperimentAPI.getRolloutMetaData({ + featureId: "saveToPocket", + }); + + const pocketNewtabExperiment = ExperimentAPI.getExperimentMetaData({ + featureId: "pocketNewtab", + }); + + const pocketNewtabRollout = ExperimentAPI.getRolloutMetaData({ + featureId: "pocketNewtab", + }); + + // We want to know if the user is in a Pocket related experiment or rollout, + // but we have 2 Pocket related features, so we prioritize the saveToPocket feature, + // and experiments over rollouts. + const experimentMetaData = + saveToPocketExperiment || + pocketNewtabExperiment || + saveToPocketRollout || + pocketNewtabRollout; + + let utmSource = "firefox_pocket_save_button"; + let utmCampaign = experimentMetaData?.slug; + let utmContent = experimentMetaData?.branch?.slug; + + const url = new URL(urlString); + // A set of params shared across all panels. + url.searchParams.append("utmSource", utmSource); + if (utmCampaign && utmContent) { + url.searchParams.append("utmCampaign", utmCampaign); + url.searchParams.append("utmContent", utmContent); + } + url.searchParams.append("locale", locale); + + // We don't have to hide and show the panel again if it's already shown + // as if the user tries to click again on the toolbar button the overlay + // will close instead of the button will be clicked + var frame = getPanelFrame(); + + // Load the frame + frame.setAttribute("src", url.href); + } + + function onShowSignup() { + // Ensure opening the signup panel clears the icon state from any previous sessions. + SaveToPocket.itemDeleted(); + // A successful button click, for logged out users. + pktTelemetry.sendStructuredIngestionEvent( + pktTelemetry.createPingPayload({ + events: [ + { + action: "click", + source: "save_button", + }, + ], + }) + ); + } + + async function onShowHome() { + // A successful home button click. + pktTelemetry.sendStructuredIngestionEvent( + pktTelemetry.createPingPayload({ + events: [ + { + action: "click", + source: "home_button", + }, + ], + }) + ); + + if (!NimbusFeatures.saveToPocket.getVariable("hideRecentSaves")) { + let recentSaves = await pktApi.getRecentSavesCache(); + if (recentSaves) { + // We have cache, so we can use those. + pktUIMessaging.sendMessageToPanel("PKT_renderRecentSaves", recentSaves); + } else { + // Let the client know we're loading fresh recs. + pktUIMessaging.sendMessageToPanel( + "PKT_loadingRecentSaves", + recentSaves + ); + // We don't have cache, so fetch fresh stories. + pktApi.getRecentSaves({ + success(data) { + pktUIMessaging.sendMessageToPanel("PKT_renderRecentSaves", data); + }, + error(error) { + pktUIMessaging.sendErrorMessageToPanel( + "PKT_renderRecentSaves", + error + ); + }, + }); + } + } + } + + function onShowSaved() { + var saveLinkMessageId = "PKT_saveLink"; + + // Send error message for invalid url + if (!isValidURL()) { + let errorData = { + localizedKey: "pocket-panel-saved-error-only-links", + }; + pktUIMessaging.sendErrorMessageToPanel(saveLinkMessageId, errorData); + return; + } + + // Check online state + if (!navigator.onLine) { + let errorData = { + localizedKey: "pocket-panel-saved-error-no-internet", + }; + pktUIMessaging.sendErrorMessageToPanel(saveLinkMessageId, errorData); + return; + } + + // A successful button click, for logged in users. + pktTelemetry.sendStructuredIngestionEvent( + pktTelemetry.createPingPayload({ + events: [ + { + action: "click", + source: "save_button", + }, + ], + }) + ); + + // Add url + var options = { + success(data, request) { + var item = data.item; + var ho2 = data.ho2; + var accountState = data.account_state; + var displayName = data.display_name; + var successResponse = { + status: "success", + accountState, + displayName, + item, + ho2, + }; + pktUIMessaging.sendMessageToPanel(saveLinkMessageId, successResponse); + SaveToPocket.itemSaved(); + + if (!NimbusFeatures.saveToPocket.getVariable("hideRecentSaves")) { + // Articles saved for the first time (by anyone) won't have a resolved_id + if (item?.resolved_id && item?.resolved_id !== "0") { + pktApi.getArticleInfo(item.resolved_url, { + success(data) { + pktUIMessaging.sendMessageToPanel( + "PKT_articleInfoFetched", + data + ); + }, + done() { + pktUIMessaging.sendMessageToPanel( + "PKT_getArticleInfoAttempted" + ); + }, + }); + } else { + pktUIMessaging.sendMessageToPanel("PKT_getArticleInfoAttempted"); + } + } + + getAndShowRecsForItem(item, { + success(data) { + pktUIMessaging.sendMessageToPanel("PKT_renderItemRecs", data); + if (data?.recommendations?.[0]?.experiment) { + const payload = pktTelemetry.createPingPayload({ + // This is the ML model used to recommend the story. + // Right now this value is the same for all three items returned together, + // so we can just use the first item's value for all. + model: data.recommendations[0].experiment, + // Create an impression event for each item rendered. + events: data.recommendations.map((item, index) => ({ + action: "impression", + position: index, + source: "on_save_recs", + })), + }); + // Send view impression ping. + pktTelemetry.sendStructuredIngestionEvent(payload); + } + }, + }); + }, + error(error, request) { + // If user is not authorized show singup page + if (request.status === 401) { + showSignUp(); + return; + } + + // For unknown server errors, use a generic catch-all error message + let errorData = { + localizedKey: "pocket-panel-saved-error-generic", + }; + + // Send error message to panel + pktUIMessaging.sendErrorMessageToPanel(saveLinkMessageId, errorData); + }, + }; + + // Add title if given + if (typeof _titleToSave !== "undefined") { + options.title = _titleToSave; + } + + // Send the link + pktApi.addLink(_urlToSave, options); + } + + /** + * Resize the panel + * options = { + * width: , + * height: , + * } + */ + function resizePanel(options = {}) { + var frame = getPanelFrame(); + + // Set an explicit size, panel will adapt. + frame.style.width = options.width + "px"; + frame.style.height = options.height + "px"; + } + + // -- Browser Navigation -- // + + /** + * Open a new tab with a given url and notify the frame panel that it was opened + */ + + function openTabWithUrl(url, aTriggeringPrincipal, aCsp) { + let recentWindow = Services.wm.getMostRecentWindow("navigator:browser"); + if (!recentWindow) { + console.error("Pocket: No open browser windows to openTabWithUrl"); + return; + } + closePanel(); + + // If the user is in permanent private browsing than this is not an issue, + // since the current window will always share the same cookie jar as the other + // windows. + if ( + !PrivateBrowsingUtils.isWindowPrivate(recentWindow) || + PrivateBrowsingUtils.permanentPrivateBrowsing + ) { + recentWindow.openWebLinkIn(url, "tab", { + triggeringPrincipal: aTriggeringPrincipal, + csp: aCsp, + }); + return; + } + + for (let win of Services.wm.getEnumerator("navigator:browser")) { + if (!PrivateBrowsingUtils.isWindowPrivate(win)) { + win.openWebLinkIn(url, "tab", { + triggeringPrincipal: aTriggeringPrincipal, + csp: aCsp, + }); + return; + } + } + + // If there were no non-private windows opened already. + recentWindow.openWebLinkIn(url, "window", { + triggeringPrincipal: aTriggeringPrincipal, + csp: aCsp, + }); + } + + // Open a new tab with a given url + function onOpenTabWithUrl(data, contentPrincipal, csp) { + try { + urlSecurityCheck( + data.url, + contentPrincipal, + Services.scriptSecurityManager.DISALLOW_INHERIT_PRINCIPAL + ); + } catch (ex) { + return; + } + + // We don't track every click, only clicks with a known source. + if (data.source) { + const { position, source, model } = data; + const payload = pktTelemetry.createPingPayload({ + ...(model ? { model } : {}), + events: [ + { + action: "click", + source, + // Add in position if needed, for example, topic links have a position. + ...(position || position === 0 ? { position } : {}), + }, + ], + }); + // Send click event ping. + pktTelemetry.sendStructuredIngestionEvent(payload); + } + + var url = data.url; + openTabWithUrl(url, contentPrincipal, csp); + } + + // Open a new tab with a Pocket story url + function onOpenTabWithPocketUrl(data, contentPrincipal, csp) { + try { + urlSecurityCheck( + data.url, + contentPrincipal, + Services.scriptSecurityManager.DISALLOW_INHERIT_PRINCIPAL + ); + } catch (ex) { + return; + } + + const { url, position, model } = data; + // Check to see if we need to and can fire valid telemetry. + if (model && (position || position === 0)) { + const payload = pktTelemetry.createPingPayload({ + model, + events: [ + { + action: "click", + position, + source: "on_save_recs", + }, + ], + }); + // Send click event ping. + pktTelemetry.sendStructuredIngestionEvent(payload); + } + + openTabWithUrl(url, contentPrincipal, csp); + } + + // -- Helper Functions -- // + + function getCurrentUrl() { + return gBrowser.currentURI.spec; + } + + function getCurrentTitle() { + return gBrowser.contentTitle; + } + + function closePanel() { + // The panel frame doesn't exist until the Pocket panel is showing. + // So we ensure it is open before attempting to hide it. + getPanelFrame()?.closest("panel")?.hidePopup(); + } + + var toolbarPanelFrame; + + function setToolbarPanelFrame(frame) { + toolbarPanelFrame = frame; + } + + function getPanelFrame() { + return toolbarPanelFrame; + } + + function isValidURL() { + return ( + typeof _urlToSave !== "undefined" && + (_urlToSave.startsWith("http") || _urlToSave.startsWith("https")) + ); + } + + function getFirefoxAccountSignedInUser(callback) { + fxAccounts + .getSignedInUser() + .then(userData => { + callback(userData); + }) + .then(null, error => { + callback(); + }); + } + + function getUILocale() { + return Services.locale.appLocaleAsBCP47; + } + + /** + * Public functions + */ + return { + setToolbarPanelFrame, + getPanelFrame, + initPrefs, + showPanel, + getUILocale, + + openTabWithUrl, + onOpenTabWithUrl, + onOpenTabWithPocketUrl, + onShowSaved, + onShowSignup, + onShowHome, + + getAndShowRecsForItem, + tryToSaveUrl, + tryToSaveCurrentPage, + resizePanel, + closePanel, + }; +})(); + +// -- Communication to Background -- // +var pktUIMessaging = (function () { + /** + * Send a message to the panel's frame + */ + function sendMessageToPanel(messageId, payload) { + var panelFrame = pktUI.getPanelFrame(); + if (!panelFrame) { + console.warn("Pocket panel frame is undefined"); + return; + } + + const aboutPocketActor = + panelFrame?.browsingContext?.currentWindowGlobal?.getActor("AboutPocket"); + + // Send message to panel + aboutPocketActor?.sendAsyncMessage(messageId, payload); + } + + /** + * Helper function to package an error object and send it to the panel + * frame as a message response + */ + function sendErrorMessageToPanel(messageId, error) { + var errorResponse = { status: "error", error }; + sendMessageToPanel(messageId, errorResponse); + } + + /** + * Public + */ + return { + sendMessageToPanel, + sendErrorMessageToPanel, + }; +})(); -- cgit v1.2.3