├── .babelrc ├── .circleci └── config.yml ├── .dir-locals.el ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .github └── ISSUE_TEMPLATE │ └── config.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── bin ├── deploy-addon.sh └── install-test-dependencies.sh ├── docs ├── CODEOWNERS ├── README.md ├── SECURITY.md ├── code_of_conduct.md ├── contributing.md ├── css │ └── extra.css ├── developer │ ├── directory-structure.md │ ├── install.md │ ├── releases.md │ ├── test-plan-accessibility.md │ ├── test-plan-telemetry.md │ └── test-plan.md ├── index.md ├── metrics.md ├── pull_request_template.md ├── release-notes.md └── user-guide.md ├── karma.conf.js ├── l10n.toml ├── mkdocs.yml ├── package-lock.json ├── package.json ├── src ├── .dir-locals.el ├── background │ ├── README.md │ ├── browser-action.js │ ├── clipboard.js │ ├── datastore.js │ ├── environment.js │ ├── index.js │ ├── message-ports.js │ ├── profile.js │ ├── telemetry.js │ └── views.js ├── common.js ├── experiments │ ├── logins │ │ ├── api.js │ │ └── schema.json │ ├── sync │ │ ├── api.js │ │ └── schema.json │ └── temptelemetry │ │ ├── api.js │ │ └── schema.json ├── fonts │ └── fira-mono-regular.woff ├── icons │ ├── add-icon.svg │ ├── arrow-dropdown.svg │ ├── arrowhead-left-16.svg │ ├── chevron-right.svg │ ├── clear.svg │ ├── close.svg │ ├── copy.svg │ ├── delete.svg │ ├── edit.svg │ ├── external-link.svg │ ├── hide.svg │ ├── icon-account.svg │ ├── icon-desktop.svg │ ├── icon-download.svg │ ├── icon-faq.svg │ ├── icon-feedback.svg │ ├── icon-lockwise.svg │ ├── icon-mobile.svg │ ├── icon-sync.svg │ ├── icon-toolbar.svg │ ├── info-dark.svg │ ├── info.svg │ ├── localized │ │ ├── LICENSE │ │ └── en │ │ │ ├── icon-app-store.svg │ │ │ └── icon-play-store.png │ ├── search-focused.svg │ ├── search.svg │ ├── show.svg │ ├── warning-light.svg │ └── warning.svg ├── images │ ├── intro-step-1.png │ ├── intro-step-2.png │ ├── intro-step-3.png │ ├── logged-out-avatar.png │ ├── logo-lockwise.svg │ └── nessie_v2.svg ├── l10n.js ├── list │ ├── README.md │ ├── actions.js │ ├── common.js │ ├── components │ │ ├── duplicate-notification.js │ │ ├── error-notification.js │ │ ├── item-fields.css │ │ ├── item-fields.js │ │ ├── item-list.css │ │ ├── item-list.js │ │ ├── item-summary.css │ │ ├── item-summary.js │ │ ├── notification.css │ │ └── sync-notification.js │ ├── containers │ │ ├── connected-sync-notification.js │ │ ├── item-filter.js │ │ └── no-matching-placeholder.js │ ├── filter.js │ ├── manage │ │ ├── components │ │ │ ├── app.css │ │ │ ├── app.js │ │ │ ├── edit-item-details.js │ │ │ ├── homepage.css │ │ │ ├── homepage.js │ │ │ ├── intro-page.css │ │ │ ├── intro-page.js │ │ │ ├── item-details.css │ │ │ ├── item-details.js │ │ │ ├── item-list-panel.css │ │ │ ├── item-list-panel.js │ │ │ ├── list-counter.js │ │ │ ├── list-sort.css │ │ │ ├── list-sort.js │ │ │ ├── promote-banner.css │ │ │ └── promote-banner.js │ │ ├── containers │ │ │ ├── add-item.css │ │ │ ├── add-item.js │ │ │ ├── all-items.js │ │ │ ├── app-header.css │ │ │ ├── app-header.js │ │ │ ├── app-panes.js │ │ │ ├── connected-promote-banner.js │ │ │ ├── current-selection.js │ │ │ ├── list-sort.js │ │ │ ├── modals.css │ │ │ └── modals.js │ │ ├── index.js │ │ ├── reducers.js │ │ ├── sort-middleware.js │ │ └── telemetry.js │ ├── message-ports.js │ ├── open-link-middleware.js │ ├── popup │ │ ├── components │ │ │ ├── app.css │ │ │ ├── app.js │ │ │ ├── item-details-panel.css │ │ │ ├── item-details-panel.js │ │ │ ├── item-list-panel.css │ │ │ └── item-list-panel.js │ │ ├── containers │ │ │ ├── all-items-panel.js │ │ │ ├── current-selection.js │ │ │ └── no-entries-placeholder.js │ │ ├── index.js │ │ ├── reducers.js │ │ └── telemetry.js │ ├── reducers.js │ ├── sort.js │ └── telemetry.js ├── locales │ ├── README.md │ ├── cak │ │ ├── list.ftl │ │ └── widgets.ftl │ ├── cs │ │ ├── list.ftl │ │ └── widgets.ftl │ ├── cy │ │ ├── list.ftl │ │ └── widgets.ftl │ ├── de │ │ ├── list.ftl │ │ └── widgets.ftl │ ├── el │ │ ├── list.ftl │ │ └── widgets.ftl │ ├── en-CA │ │ ├── list.ftl │ │ └── widgets.ftl │ ├── en-US │ │ ├── list.ftl │ │ └── widgets.ftl │ ├── fr │ │ ├── list.ftl │ │ └── widgets.ftl │ ├── fy-NL │ │ ├── list.ftl │ │ └── widgets.ftl │ ├── hu │ │ ├── list.ftl │ │ └── widgets.ftl │ ├── ia │ │ ├── list.ftl │ │ └── widgets.ftl │ ├── id │ │ ├── list.ftl │ │ └── widgets.ftl │ ├── it │ │ ├── list.ftl │ │ └── widgets.ftl │ ├── kab │ │ ├── list.ftl │ │ └── widgets.ftl │ ├── locales.json │ ├── nl │ │ ├── list.ftl │ │ └── widgets.ftl │ ├── nn-NO │ │ ├── list.ftl │ │ └── widgets.ftl │ ├── pt-BR │ │ ├── list.ftl │ │ └── widgets.ftl │ ├── ro │ │ ├── list.ftl │ │ └── widgets.ftl │ ├── sl │ │ ├── list.ftl │ │ └── widgets.ftl │ ├── sv-SE │ │ ├── list.ftl │ │ └── widgets.ftl │ ├── vi │ │ ├── list.ftl │ │ └── widgets.ftl │ ├── zh-CN │ │ ├── list.ftl │ │ └── widgets.ftl │ └── zh-TW │ │ ├── list.ftl │ │ └── widgets.ftl ├── manifest.json.tpl ├── telemetry.js ├── template.ejs └── widgets │ ├── README.md │ ├── banner.css │ ├── banner.js │ ├── breadcrumbs.css │ ├── breadcrumbs.js │ ├── button.css │ ├── button.js │ ├── copy-to-clipboard-button.css │ ├── copy-to-clipboard-button.js │ ├── dialog-box.css │ ├── dialog-box.js │ ├── field-text.js │ ├── filter-input.js │ ├── input.css │ ├── input.js │ ├── label-text.js │ ├── link.css │ ├── link.js │ ├── modal-root.css │ ├── modal-root.js │ ├── panel.css │ ├── panel.js │ ├── password-input.js │ ├── password-text.js │ ├── scrolling-list.css │ ├── scrolling-list.js │ ├── stack.css │ ├── stack.js │ ├── text-area.css │ ├── text-area.js │ ├── toolbar.css │ └── toolbar.js ├── stylelint.config.js ├── test ├── .dir-locals.el ├── .eslintrc.js ├── integration │ ├── a11y-test.js │ ├── chai-axe.js │ ├── driver.js │ ├── functional-test.js │ ├── helper.js │ ├── logins-api-test.js │ ├── sync-api-test.js │ └── test-pages │ │ ├── logins-api.html │ │ ├── logins-api.js │ │ ├── sync-api.html │ │ └── sync-api.js └── unit │ ├── background │ ├── browser-action-test.js │ ├── clipboard-test.js │ ├── datastore-test.js │ ├── environment-test.js │ ├── message-ports-test.js │ ├── profile-test.js │ ├── telemetry-test.js │ └── views-test.js │ ├── chai-focus.js │ ├── common-test.js │ ├── common.js │ ├── constants.js │ ├── enzyme.js │ ├── l10n-test.js │ ├── list │ ├── actions-test.js │ ├── components │ │ ├── item-list-test.js │ │ ├── item-summary-test.js │ │ └── sync-notification-test.js │ ├── containers │ │ ├── item-filter-test.js │ │ └── no-matching-placeholder-test.js │ ├── filter-test.js │ ├── manage │ │ ├── components │ │ │ ├── app-test.js │ │ │ ├── edit-item-details-test.js │ │ │ ├── homepage-test.js │ │ │ ├── intro-page-test.js │ │ │ ├── item-details-test.js │ │ │ ├── item-list-panel-test.js │ │ │ ├── list-counter-test.js │ │ │ ├── list-sort-test.js │ │ │ └── promote-banner-test.js │ │ ├── containers │ │ │ ├── add-item-test.js │ │ │ ├── all-items-test.js │ │ │ ├── app-header-test.js │ │ │ ├── connected-promote-banner-test.js │ │ │ ├── current-selection-test.js │ │ │ ├── list-sort-test.js │ │ │ └── modals-test.js │ │ ├── mock-redux-state.js │ │ ├── reducers-test.js │ │ └── telemetry-test.js │ ├── message-ports-test.js │ ├── open-link-middleware-test.js │ ├── popup │ │ ├── components │ │ │ ├── app-test.js │ │ │ ├── item-details-panel-test.js │ │ │ └── item-list-panel-test.js │ │ ├── containers │ │ │ ├── all-items-panel-test.js │ │ │ ├── current-selection-test.js │ │ │ └── no-entries-placeholder-test.js │ │ ├── mock-redux-state.js │ │ └── telemetry-test.js │ ├── reducers-test.js │ ├── sort-test.js │ └── telemetry-test.js │ ├── mocks │ ├── browser.js │ └── l10n.js │ ├── telemetry-test.js │ └── widgets │ ├── banner-test.js │ ├── breadcrumbs-test.js │ ├── button-test.js │ ├── copy-to-clipboard-button-test.js │ ├── dialog-box-test.js │ ├── field-text-test.js │ ├── filter-input-test.js │ ├── input-test.js │ ├── label-text-test.js │ ├── link-test.js │ ├── panel-test.js │ ├── password-input-test.js │ ├── scrolling-list-test.js │ ├── stack-test.js │ ├── text-area-test.js │ └── toolbar-test.js └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-react", 4 | [ 5 | "@babel/preset-env", 6 | { 7 | "targets": { 8 | "firefox": "63", 9 | "node": "10", 10 | } 11 | } 12 | ] 13 | ], 14 | "plugins": [ 15 | "@babel/plugin-syntax-object-rest-spread", 16 | "@babel/plugin-syntax-async-generators" 17 | ], 18 | "env": { 19 | "test": { 20 | "plugins": [ 21 | "istanbul", 22 | "rewire" 23 | ] 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /.dir-locals.el: -------------------------------------------------------------------------------- 1 | ; Use rjsx-mode instead of js2-mode for files in this directory. 2 | ((js2-mode . ((mode . rjsx)))) 3 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | 5 | root = true 6 | 7 | [*] 8 | charset = utf-8 9 | end_of_line = lf 10 | indent_style = space 11 | insert_final_newline = true 12 | 13 | [.{babelrc,editorconfig,eslintrc.js}] 14 | indent_size = 2 15 | 16 | [**.{js,json,yml}] 17 | indent_size = 2 18 | 19 | [docs/**.md] 20 | indent_size = 2 21 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | !.eslintrc.js 2 | coverage 3 | dist 4 | node_modules 5 | site 6 | temp 7 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | let extraPlugins = []; 8 | if (process.env.STRICT_LINT !== "1") { 9 | extraPlugins.push("only-warn"); 10 | } 11 | 12 | module.exports = { 13 | "parserOptions": { 14 | "ecmaVersion": 2018, 15 | "sourceType": "module", 16 | "ecmaFeatures": { 17 | "jsx": true, 18 | }, 19 | }, 20 | "extends": [ 21 | "eslint:recommended", 22 | "plugin:mozilla/recommended", 23 | "plugin:react/recommended", 24 | ], 25 | "env": { 26 | "browser": true, 27 | "node": true, 28 | "webextensions": true, 29 | "es6": true, 30 | }, 31 | "plugins": [ 32 | "mozilla", 33 | "react", 34 | ...extraPlugins, 35 | ], 36 | "rules": { 37 | "comma-dangle": ["error", "always-multiline"], 38 | "curly": "error", 39 | "no-console": "warn", 40 | "semi": "error", 41 | "mozilla/no-define-cc-etc": "off", 42 | "mozilla/use-chromeutils-import": "off", 43 | }, 44 | "settings": { 45 | "react": { 46 | "version": "16", 47 | }, 48 | }, 49 | }; 50 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | contact_links: 2 | - name: Firefox Desktop Password Management UI Bugs 3 | url: https://bugzilla.mozilla.org/enter_bug.cgi?product=Firefox&component=about:logins 4 | about: Issues or enhancements managing Logins and Passwords (about:logins) in Firefox Desktop 5 | - name: Firefox Password Manager Website Compatibility Bugs 6 | url: https://bugzilla.mozilla.org/enter_bug.cgi?product=Toolkit&component=Password%20Manager:%20Site%20Compatibility 7 | about: Issues with the Firefox password manager working on a specific website. One website per bug please. 8 | - name: Firefox Password Manager Bugs 9 | url: https://bugzilla.mozilla.org/enter_bug.cgi?product=Toolkit&component=Password%20Manager 10 | about: Issues or enhancements filling or saving logins, not specific to a certain website. 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | /coverage/ 3 | /dist/ 4 | /docs/api.md 5 | /node_modules/ 6 | /site/ 7 | /temp/ 8 | /addons/ 9 | /.web-ext-artifacts/ 10 | .DS_Store 11 | .cache 12 | .tox 13 | *.log 14 | *.pyc 15 | .pytest_cache 16 | geckodriver.exe 17 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Community Participation Guidelines 2 | 3 | This repository is governed by Mozilla's code of conduct and etiquette guidelines. 4 | For more details, please read the 5 | [Mozilla Community Participation Guidelines](https://www.mozilla.org/about/governance/policies/participation/). 6 | 7 | ## How to Report 8 | For more information on how to report violations of the Community Participation Guidelines, please read our '[How to Report](https://www.mozilla.org/about/governance/policies/participation/reporting/)' page. 9 | 10 | 16 | -------------------------------------------------------------------------------- /bin/deploy-addon.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | # Deploys a signed extension as a GitHub Release 4 | 5 | VERSION=$(cat package.json | grep version | head -1 | awk -F: '{ print $2 }' | sed 's/[",]//g' | tr -d '[[:space:]]') 6 | echo "Publish ${VERSION}" 7 | ghr -t ${GITHUB_TOKEN} -u ${CIRCLE_PROJECT_USERNAME} -r ${CIRCLE_PROJECT_REPONAME} -c ${CIRCLE_SHA1} -recreate ${VERSION} ./addons/lockwise-signed.xpi 8 | -------------------------------------------------------------------------------- /bin/install-test-dependencies.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -ex 3 | 4 | ## Copied from https://github.com/mozilla/testpilot/blob/master/bin/circleci/install-test-dependencies.sh 5 | 6 | GECKODRIVER_URL=$( 7 | curl -s 'https://api.github.com/repos/mozilla/geckodriver/releases/latest' | 8 | python -c "import sys, json; r = json.load(sys.stdin); print([a for a in r['assets'] if 'linux64' in a['name']][0]['browser_download_url']);" 9 | ); 10 | 11 | curl -L -o geckodriver.tar.gz $GECKODRIVER_URL 12 | gunzip -c geckodriver.tar.gz | tar xopf - 13 | chmod +x geckodriver 14 | sudo mv geckodriver /bin 15 | geckodriver --version 16 | # Install pip 17 | sudo apt-get install python-pip python-dev build-essential 18 | sudo pip install --upgrade pip 19 | 20 | sudo pip install tox mozdownload mozinstall==1.15 21 | 22 | mkdir -p ~/project/firefox-downloads/ 23 | find ~/project/firefox-downloads/ -type f -mtime +90 -delete 24 | mozdownload --version latest --destination ~/project/firefox-downloads/firefox/ 25 | mozdownload --version latest-beta --destination ~/project/firefox-downloads/firefox_dev/ 26 | mozdownload --version latest --type daily --destination ~/project/firefox-downloads/firefox_nightly/ 27 | 28 | # Dependencies for firefox 29 | sudo apt-get update && sudo apt-get install -y libgtk3.0-cil-dev libasound2 libasound2 libdbus-glib-1-2 libdbus-1-3 30 | 31 | # install firefox-dev 32 | mozinstall $(ls -t ~/project/firefox-downloads/firefox_dev/*.tar.bz2 | head -1) 33 | alias firefox=~/project/firefox/firefox 34 | firefox --version 35 | -------------------------------------------------------------------------------- /docs/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # lockwise-extension code owners 2 | # https://help.github.com/articles/about-codeowners/ 3 | 4 | # test plans are maintained by PI 5 | /docs/developer/test-plan* @mozilla-lockwise/product-integrity 6 | 7 | # metrics docs are maintained by Leif 8 | /docs/metrics.md @irrationalagent 9 | 10 | # tests and related config are maintained by engineering 11 | /test/ @mozilla-lockwise/desktop-engineering 12 | 13 | # source code and all other docs are maintained by engineering 14 | /src/ @mozilla-lockwise/desktop-engineering 15 | /docs/ @mozilla-lockwise/desktop-engineering 16 | 17 | # release notes are maintained by the product owners 18 | /docs/release-notes.md @mozilla-lockwise/product 19 | 20 | # include Flod for l10n string changes 21 | /src/locales/en-US/*.ftl @flodolo 22 | 23 | # otherwise, everything is to be reviewed by the desktop team 24 | * @mozilla-lockwise/desktop-engineering 25 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Welcome to Lockwise's extension documentation! 2 | 3 | You can read this documentation [online][online-docs-link] or by browsing the 4 | [`docs` directory on GitHub][repo-docs-link]. 5 | 6 | [online-docs-link]: https://mozilla-lockwise.github.io/lockwise-addon/ 7 | [repo-docs-link]: https://github.com/mozilla-lockwise/lockwise-addon/tree/master/docs 8 | -------------------------------------------------------------------------------- /docs/SECURITY.md: -------------------------------------------------------------------------------- 1 | # Mozilla Security # 2 | 3 | - Mozilla cares about privacy and security. For more information please see: [https://www.mozilla.org/security/](https://www.mozilla.org/security/) 4 | 5 | - If you believe that you've found a security vulnerability, please report it by sending email to the addresses: [security@mozilla.org](mailto:security@mozilla.org) and [lockbox-dev@mozilla.com](mailto:lockbox-dev@mozilla.com) 6 | -------------------------------------------------------------------------------- /docs/code_of_conduct.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | This repository is governed by Mozilla's code of conduct and etiquette guidelines. For more details please see the [Mozilla Community Participation Guidelines](https://www.mozilla.org/about/governance/policies/participation/) and [Developer Etiquette Guidelines](https://bugzilla.mozilla.org/page.cgi?id=etiquette.html). 4 | -------------------------------------------------------------------------------- /docs/css/extra.css: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | h1 { 6 | margin-bottom: 1em; 7 | } 8 | 9 | a.button-link { 10 | display: block; 11 | margin: .5em auto; 12 | padding: 0.5em; 13 | width: 12em; 14 | color: white; 15 | background-color: #717171; 16 | border: 1px solid #717171; 17 | border-radius: 4px; 18 | text-align: center; 19 | } 20 | 21 | div.right { 22 | float: right; 23 | clear: right; 24 | width: 40%; 25 | padding-left: 2em; 26 | } 27 | 28 | .rst-content .admonition-title::before { 29 | display: none !important; 30 | } 31 | -------------------------------------------------------------------------------- /docs/developer/directory-structure.md: -------------------------------------------------------------------------------- 1 | # Directory Structure 2 | 3 | The following provides a brief outline of the directory structure of this project. 4 | 5 | ## `docs/` 6 | 7 | Documentation that gets built and deploy to GitHub pages (you're reading it!). 8 | 9 | ## `src/` 10 | 11 | This is where all of the source code for this project lives. 12 | 13 | #### `src/background/` 14 | 15 | Any code meant to run in a background page lives here. For more information, see the [`README.md`][background-readme] in this directory. 16 | 17 | #### `src/list/` 18 | 19 | Common code for all the list views in the extension, as well as the specific views themselves (in subdirectories). For more information, see the [`README.md`][list-readme] in this directory. 20 | 21 | ##### `src/list/manage/` 22 | 23 | The full-tab management UI. 24 | 25 | ##### `src/list/popup/` 26 | 27 | The "doorhanger" UI. 28 | 29 | #### `src/locales/` 30 | 31 | All the strings for each locale, in subdirectories with the locale name. For more information, see the [`README.md`][locales-readme] in this directory. 32 | 33 | #### `src/widgets/` 34 | 35 | All the common UI widgets used throughout the source. For more information, see the [`README.md`][widgets-readme] in this directory. 36 | 37 | ## `test/` 38 | 39 | This is where all of the tests for this project live. 40 | 41 | ### `test/integration/` 42 | 43 | End-to-end integration tests operating on a compiled version of the add-on running in Firefox. These tests are written in Javascript using Mocha and `webextensions-geckodriver`. 44 | 45 | ### `test/unit/` 46 | 47 | Unit tests testing individual components of the source code. These are writen in Javascript using Mocha/Chai and run via Karma. The subdirectories of this directory match the subdirectories of `src/webextension`. 48 | 49 | [background-readme]: https://github.com/mozilla-lockwise/lockwise-addon/blob/master/src/background/README.md 50 | [list-readme]: https://github.com/mozilla-lockwise/lockwise-addon/blob/master/src/list/README.md 51 | [locales-readme]: https://github.com/mozilla-lockwise/lockwise-addon/blob/master/src/locales/README.md 52 | [widgets-readme]: https://github.com/mozilla-lockwise/lockwise-addon/blob/master/src/widgets/README.md 53 | -------------------------------------------------------------------------------- /docs/developer/test-plan-telemetry.md: -------------------------------------------------------------------------------- 1 | # Telemetry Test Plan: Lockwise-extension 2 | 3 | Given the importance the Lockwise team places on actionable user telemetry, ensuring confidence in the fidelity of that data is vital. Coverage aspects are broken into two distinct areas: test automation and manual testing. 4 | 5 | ## Unit tests 6 | 7 | When a new feature is developed or an additional user action needs to be recorded, the developer implementing the new telemetry event will add a unit test to ensure the action creates a ping. 8 | 9 | ## Manual tests 10 | 11 | ### New telemetry events 12 | 13 | Once a new telemetry event is added to a feature and that event has a unit test, the telemetry ping will be manually inspected by either Leif Oines, Product Data Scientist or Product Integrity. The goal of this exercise it to ensure the new event and accompanying unit test are capturing the correct user behavior. 14 | 15 | ### Regression testing 16 | 17 | A small handful of smoke tests will manually be run prior to each release. Note, the team will rely on the underlying unit tests for comprehensive testing of event verification. 18 | 19 | Product Integrity will investigate options for automating these tests into the integration test suite. 20 | 21 | ## Metrics being gathered 22 | 23 | For a comprehensive list of metrics being gathered review the [Lockwise Telemetry Plan](/metrics/). 24 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # Lockbox for desktop 2 | 3 | Welcome to the Lockbox desktop addon documentation! 4 | -------------------------------------------------------------------------------- /docs/pull_request_template.md: -------------------------------------------------------------------------------- 1 | Fixes #??? 2 | 3 | _(**Required**: this reference (one or many) will be closed upon merge. Ideally it has the acceptance criteria and designs for features or fixes related to the work in this Pull Request.)_ 4 | 5 | Connected to #??? 6 | 7 | _(Optional: other issues or pull requests related to this, but merging should not close it)_ 8 | 9 | ## Testing and Review Notes 10 | 11 | _(**Required**: steps to take to confirm this works as expected or other guidance for code, UX, and any other reviewers)_ 12 | 13 | 14 | ## Screenshots or Videos 15 | 16 | _(Optional: to clearly demonstrate the feature or fix to help with testing and reviews)_ 17 | 18 | 19 | ## To Do 20 | 21 | - add “WIP” to the PR title if pushing up but not complete nor ready for review 22 | - [ ] double check the original issue to confirm it is fully satisfied 23 | - [ ] add testing notes and screenshots in PR description to help guide reviewers 24 | - [ ] add unit tests 25 | - optional: consider adding integration tests 26 | - [ ] request the "UX" team perform a design review (if/when applicable) 27 | - [ ] make sure CI builds are passing (e.g.: fix lint and other errors) 28 | - [ ] check on the [accessibility](https://mozilla-lockbox.github.io/lockbox-addon/developer/test-plan-accessibility/) of any added UI 29 | - [ ] make sure any new UI has telemetry events wired up and documented in docs/metrics.md, and verify that the events are recording properly (visit `about:telemetry#events-tab`, then choose 'dynamic' from the dropdown menu at top right, to view events fired by the addon 30 | -------------------------------------------------------------------------------- /docs/user-guide.md: -------------------------------------------------------------------------------- 1 | # Lockbox User Guide 2 | 3 | First things first, [install the extension](developer/install.md). 4 | 5 | ## Commands 6 | 7 | * `Ctrl-Shift-L`: open Lockbox editor (item management) 8 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | module.exports = (config) => { 8 | config.set({ 9 | browsers: ["FirefoxHeadless"], 10 | customLaunchers: { 11 | FirefoxHeadless: { 12 | base: "Firefox", 13 | flags: ["-headless"], 14 | }, 15 | }, 16 | files: [ 17 | "test/unit/**/*-test.js", 18 | ], 19 | preprocessors: { 20 | "**/*.js": ["webpack", "sourcemap"], 21 | }, 22 | 23 | frameworks: ["mocha"], 24 | reporters: ["mocha", "coverage"], 25 | coverageReporter: { 26 | dir: "coverage/", 27 | reporters: [ 28 | { type: "text" }, 29 | { type: "lcov" }, 30 | { type: "html", subdir: "html" }, 31 | ], 32 | }, 33 | mochaReporter: { 34 | showDiff: true, 35 | }, 36 | 37 | webpack: require("./webpack.config"), 38 | webpackMiddleware: { 39 | stats: "errors-only", 40 | }, 41 | }); 42 | }; 43 | -------------------------------------------------------------------------------- /l10n.toml: -------------------------------------------------------------------------------- 1 | basepath = "." 2 | 3 | [[paths]] 4 | reference = "src/locales/en-US/*.ftl" 5 | l10n = "src/locales/{locale}/*.ftl" 6 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: Lockbox Extension 2 | site_description: Documentation for the Firefox extension 3 | theme: readthedocs 4 | repo_url: https://github.com/mozilla-lockbox/lockbox-addon 5 | 6 | extra_css: 7 | - css/extra.css 8 | 9 | markdown_extensions: 10 | - attr_list 11 | - admonition 12 | 13 | pages: 14 | - 'Introduction': 'index.md' 15 | - 'Release Notes': 'release-notes.md' 16 | - 'Contribute': 'contributing.md' 17 | - 'Telemetry and Metrics': 'metrics.md' 18 | - 'Mozilla Security': 'SECURITY.md' 19 | - 'Developer Guides': 20 | - 'Build and Install': 'developer/install.md' 21 | - 'Release Instructions': 'developer/releases.md' 22 | - 'Directory Structure': 'developer/directory-structure.md' 23 | - 'Test Plan': 'developer/test-plan.md' 24 | - 'Test Plan - Accessibility': 'developer/test-plan-accessibility.md' 25 | - 'Test Plan - Telemetry': 'developer/test-plan-telemetry.md' 26 | -------------------------------------------------------------------------------- /src/.dir-locals.el: -------------------------------------------------------------------------------- 1 | ; Use rjsx-mode instead of js2-mode for files in this directory. 2 | ((js2-mode . ((mode . rjsx)))) 3 | -------------------------------------------------------------------------------- /src/background/README.md: -------------------------------------------------------------------------------- 1 | # Background Scripts 2 | 3 | All of the logic working with the datastore occurs in the background scripts. Front-end pages communicate with these scripts via message ports. 4 | 5 | ## `open_view` 6 | 7 | Open the view named `name` in a new tab. Returns an empty object. 8 | 9 | ## `close_view` 10 | 11 | Close the view named `name` if it's open. Returns an empty object. 12 | 13 | ## `open_site` 14 | 15 | Open website passed as `url` in a new tab. Returns an empty object. 16 | 17 | ## `list_items` 18 | 19 | List all the items in the datastore. Returns an array of summaries of the items in the `items` field. 20 | 21 | ## `add_item` 22 | 23 | Add an item (in the `item` attribute) to the datastore. Returns the updated item in the `item` field with its `id` field filled out. 24 | 25 | ## `update_item` 26 | 27 | Update an existing item (in the `item` attribute) in the datastore. Returns the updated item in the `item` field. 28 | 29 | ## `remove_item` 30 | 31 | Remove the item with ID `id` from the datastore. Returns an empty object. 32 | 33 | ## `get_item` 34 | 35 | Fetches the item with ID `id` from the datastore. Returns the item object in the `item` field. 36 | 37 | ## `telemetry_event` 38 | 39 | Record a telemetry event with method `method`, object `object`, and extra `extra` (value is `null`). 40 | 41 | ## `telemetry_add` 42 | 43 | Increment a telemetry counter with `name` by some `value`. 44 | -------------------------------------------------------------------------------- /src/background/browser-action.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | let popup; 6 | 7 | function installPopup(path) { 8 | popup = browser.extension.getURL(path); 9 | browser.browserAction.setPopup({ 10 | popup, 11 | }); 12 | } 13 | 14 | function uninstallPopup() { 15 | if (popup) { 16 | browser.browserAction.setPopup({ popup: "" }); 17 | } 18 | popup = null; 19 | } 20 | 21 | function installEntriesAction() { 22 | return installPopup("list/popup.html"); 23 | } 24 | 25 | export default async function updateBrowserAction() { 26 | // clear listener 27 | // XXXX: be more efficient with this? 28 | uninstallPopup(); 29 | 30 | return installEntriesAction(); 31 | } 32 | -------------------------------------------------------------------------------- /src/background/clipboard.js: -------------------------------------------------------------------------------- 1 | const CLIPBOARD_CLEAR_DELAY = 1 * 60 * 1000; // 1 minute 2 | 3 | let clearClipboardTimer = null; 4 | 5 | export async function copyToClipboard(field, toCopy, navigatorClipboard = navigator.clipboard) { 6 | if (clearClipboardTimer) { 7 | // Clear any existing timer before scheduling a new one. 8 | window.clearTimeout(clearClipboardTimer); 9 | } 10 | await navigatorClipboard.writeText(toCopy); 11 | clearClipboardTimer = window.setTimeout( 12 | async () => { 13 | clearClipboardTimer = null; 14 | // Only clear the clipboard if it still holds the value set earlier. 15 | const inClipboard = await navigatorClipboard.readText(); 16 | if (inClipboard === toCopy) { 17 | navigatorClipboard.writeText(""); 18 | } 19 | }, 20 | CLIPBOARD_CLEAR_DELAY 21 | ); 22 | } 23 | 24 | export default { copyToClipboard }; 25 | -------------------------------------------------------------------------------- /src/background/environment.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | export async function initializeEnvironment() { 6 | // Issue #57: Disable login saving for extension pages. 7 | const origin = browser.extension.getURL("").slice(0, -1); 8 | const savingEnabled = await browser.experiments.logins 9 | .getLoginSavingEnabled(origin); 10 | if (savingEnabled) { 11 | await browser.experiments.logins 12 | .setLoginSavingEnabled(origin, false); 13 | } 14 | 15 | // NOTE: %DOMAIN% is replaced by the browser with the context's location: 16 | // * context menu -> "Fill Password" / "Fill Login" -> "Saved Logins" 17 | // * autofill dropdown -> "Saved logins" 18 | // %DOMAIN* is "" if there is no context (e.g., from main menu -> "Passwords and Logins") 19 | const mgmtURI = browser.extension.getURL("/list/manage.html?filter=%DOMAIN%"); 20 | await browser.experiments.logins.setManagementURI(mgmtURI); 21 | } 22 | -------------------------------------------------------------------------------- /src/background/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | import updateBrowserAction from "./browser-action"; 8 | import initializeMessagePorts from "./message-ports"; 9 | import { initializeTelemetry } from "./telemetry"; 10 | import { initializeDataStore } from "./datastore"; 11 | import { initializeEnvironment } from "./environment"; 12 | import { initializeProfileInfo } from "./profile"; 13 | 14 | Promise.resolve().then(async () => { 15 | await initializeEnvironment(); 16 | 17 | // The `shouldUseEmbedded()` check tells us if FF version is less than 68. It 18 | // turns out there is another bug (1555734) that has nothing to do with whether 19 | // or not we need to use the embedded `recordEvent`, but does require that we 20 | // only enable telemetry for 68+. Hence, despite the misleading name, it's the 21 | // right function to call here. 22 | const enableTelemetry = !(await browser.experiments.temptelemetry.shouldUseEmbedded()); 23 | initializeTelemetry(enableTelemetry); 24 | initializeMessagePorts(); 25 | await initializeDataStore(); 26 | await initializeProfileInfo(); 27 | await updateBrowserAction(); 28 | }); 29 | -------------------------------------------------------------------------------- /src/background/profile.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | import { broadcast } from "./message-ports"; 6 | 7 | let profileInfo = null; 8 | 9 | export function updateProfileInfo(update) { 10 | profileInfo = update; 11 | broadcast({ type: "profile_info", profile: profileInfo }); 12 | } 13 | 14 | export async function initializeProfileInfo() { 15 | const { sync } = browser.experiments; 16 | updateProfileInfo(await sync.getUserProfileInfo()); 17 | sync.onUserProfileChanged.addListener(updateProfileInfo); 18 | } 19 | 20 | export function getProfileInfo() { 21 | return profileInfo; 22 | } 23 | 24 | export default initializeProfileInfo; 25 | -------------------------------------------------------------------------------- /src/background/views.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | export class SingletonView { 6 | constructor(path) { 7 | this.path = path; 8 | this.id = undefined; 9 | } 10 | 11 | async open() { 12 | let windowId, 13 | offPath = false, 14 | url = browser.extension.getURL(this.path); 15 | if (this.id !== undefined) { 16 | // verify tab still exists 17 | try { 18 | let tabInfo = await browser.tabs.get(this.id); 19 | windowId = tabInfo.windowId; 20 | offPath = !tabInfo.url.startsWith(url); 21 | } catch (err) { 22 | // does not exist, forget existing info 23 | this.id = undefined; 24 | } 25 | } 26 | 27 | if (!offPath && this.id !== undefined) { 28 | // focus owning window and activate tab 29 | await browser.windows.update(windowId, { 30 | focused: true, 31 | }); 32 | await browser.tabs.update(this.id, { 33 | active: true, 34 | }); 35 | } else { 36 | // create a tab in the current window 37 | let tabInfo = await browser.tabs.create({ 38 | url, 39 | }); 40 | this.id = tabInfo.id; 41 | } 42 | return this; 43 | } 44 | 45 | async close() { 46 | try { 47 | (this.id !== undefined) && await browser.tabs.remove(this.id); 48 | } catch (err) { 49 | // Q: loggit? 50 | // eslint-disable-next-line no-console 51 | console.error(`could not close ${this.path} view: ${err.message}`); 52 | } 53 | this.id = undefined; 54 | return this; 55 | } 56 | } 57 | 58 | const views = { 59 | manage: new SingletonView("/list/manage.html"), 60 | }; 61 | 62 | export async function openView(name) { 63 | const v = views[name]; 64 | return v.open(); 65 | } 66 | 67 | export async function closeView(name) { 68 | if (!name) { 69 | let pending = Object.keys(views).map((n) => views[n].close()); 70 | await Promise.all(pending); 71 | return {}; 72 | } 73 | const v = views[name]; 74 | return v.close(); 75 | } 76 | -------------------------------------------------------------------------------- /src/common.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | export function makeItemSummary(item) { 6 | return { 7 | title: item.title, 8 | id: item.id, 9 | origins: item.origins, 10 | username: item.entry.username, 11 | timeLastUsed: item.timeLastUsed, 12 | timePasswordChanged: item.timePasswordChanged, 13 | }; 14 | } 15 | 16 | export function classNames(classNames) { 17 | return classNames.filter((i) => i).join(" "); 18 | } 19 | -------------------------------------------------------------------------------- /src/fonts/fira-mono-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla-lockwise/lockwise-addon/56b75ccb8c9b7b36230c0e3ea51913e40086a30f/src/fonts/fira-mono-regular.woff -------------------------------------------------------------------------------- /src/icons/add-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/arrow-dropdown.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/icons/arrowhead-left-16.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/icons/chevron-right.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/clear.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/icons/close.svg: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /src/icons/copy.svg: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /src/icons/delete.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/icons/edit.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/icons/external-link.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/icons/hide.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/icons/icon-account.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/icon-desktop.svg: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /src/icons/icon-download.svg: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /src/icons/icon-faq.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/icon-feedback.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/icons/icon-mobile.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/icons/icon-sync.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/icons/icon-toolbar.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/icons/info-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/info.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/localized/LICENSE: -------------------------------------------------------------------------------- 1 | Icons in these subdirectories are not MPL licensed, and product names, logos, and brands are property of their respective owners: 2 | 3 | Google Play and the Google Play logo are trademarks of Google LLC. 4 | 5 | App Store and the App Store logo are trademarks of Apple Inc. 6 | -------------------------------------------------------------------------------- /src/icons/localized/en/icon-play-store.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla-lockwise/lockwise-addon/56b75ccb8c9b7b36230c0e3ea51913e40086a30f/src/icons/localized/en/icon-play-store.png -------------------------------------------------------------------------------- /src/icons/search-focused.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/search.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/icons/show.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/icons/warning-light.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | -------------------------------------------------------------------------------- /src/icons/warning.svg: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /src/images/intro-step-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla-lockwise/lockwise-addon/56b75ccb8c9b7b36230c0e3ea51913e40086a30f/src/images/intro-step-1.png -------------------------------------------------------------------------------- /src/images/intro-step-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla-lockwise/lockwise-addon/56b75ccb8c9b7b36230c0e3ea51913e40086a30f/src/images/intro-step-2.png -------------------------------------------------------------------------------- /src/images/intro-step-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla-lockwise/lockwise-addon/56b75ccb8c9b7b36230c0e3ea51913e40086a30f/src/images/intro-step-3.png -------------------------------------------------------------------------------- /src/images/logged-out-avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mozilla-lockwise/lockwise-addon/56b75ccb8c9b7b36230c0e3ea51913e40086a30f/src/images/logged-out-avatar.png -------------------------------------------------------------------------------- /src/list/README.md: -------------------------------------------------------------------------------- 1 | # List Views 2 | 3 | This directory contains the list views for the extension (the doorhanger - in `popup/` - and the full management UI - in `manage/`). Code in other directories is shared between both views. 4 | 5 | ## Redux storage 6 | 7 | The list views are the primary users of Redux in the extension. This section describes how the state is stored at a high level. 8 | 9 | ### Cache reducer (common) 10 | 11 | This reducer contains a local cache of the necessary portions of the Lockwise datastore. In particular, it contains a summary of all the items in the store, plus full details of the currently-selected item. Item summaries have the following fields: 12 | 13 | ``` 14 | title 15 | id 16 | origins 17 | username 18 | ``` 19 | 20 | ### List reducer (common) 21 | 22 | This reducer contains the logic for the actual item list. It stores the ID of the currently-selected item as well as any filter to be applied to the list. 23 | 24 | ### Editor reducer (manager) 25 | 26 | This reducer tracks the state of the editor components. It stores whether the editor is active and if the user has changed any of the fields. `hideHome` is a bit more subtle; it's used when selecting another item when the editor is open, and prevents the home view from appearing while that item is loading. 27 | 28 | ### Modal reducer (manager) 29 | 30 | This reducer contains the state for any modal dialogs to be shown in the manager. It contains the ID of the modal and any props that should be passed to the modal when it's displayed. 31 | -------------------------------------------------------------------------------- /src/list/common.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | // This is a string instead of a symbol since we need to be able to use it as a 6 | // React `key` inside a list. 7 | export const NEW_ITEM_ID = "new-item"; 8 | 9 | export function unflattenItem(item, id) { 10 | let origins = []; 11 | if (item.origin) { 12 | origins.push(item.origin); 13 | } 14 | if (item.formURL) { 15 | origins.push(item.formURL); 16 | } 17 | return { 18 | id, 19 | title: item.title, 20 | origins, 21 | realm: item.realm || null, 22 | timeCreated: item.timeCreated, 23 | timeLastUsed: item.timeLastUsed, 24 | timePasswordChanged: item.timePasswordChanged, 25 | entry: { 26 | kind: "login", 27 | username: item.username, 28 | password: item.password, 29 | }, 30 | }; 31 | } 32 | 33 | export function flattenItem(item) { 34 | return { 35 | title: item.title, 36 | origin: item.origins[0] || "", 37 | formURL: item.origins[1] || "", 38 | realm: item.realm || "", 39 | timeCreated: item.timeCreated, 40 | timeLastUsed: item.timeLastUsed, 41 | timePasswordChanged: item.timePasswordChanged, 42 | username: item.entry.username, 43 | password: item.entry.password, 44 | }; 45 | } 46 | 47 | export const openWebsite = (url, closeWindow = true) => { 48 | browser.runtime.sendMessage({ 49 | type: "open_site", 50 | url, 51 | }); 52 | if (closeWindow) { 53 | window.close(); 54 | } 55 | }; 56 | -------------------------------------------------------------------------------- /src/list/components/duplicate-notification.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | import { Localized } from "fluent-react"; 6 | import PropTypes from "prop-types"; 7 | import React from "react"; 8 | 9 | import ErrorNotification from "./error-notification"; 10 | 11 | import styles from "./notification.css"; 12 | 13 | export default class DuplicateNotification extends React.Component { 14 | static get propTypes() { 15 | return { 16 | title: PropTypes.string.isRequired, 17 | }; 18 | } 19 | 20 | static get defaultProps() { 21 | return { 22 | title: "", 23 | }; 24 | } 25 | 26 | render() { 27 | const { title } = this.props; 28 | 29 | return ( 30 | 31 |

}> 32 |

33 | aN eNTRy fOr {title} wITh tHAt uSERNAMe aLREADy eXISTs. 34 |

35 |
36 |
37 | ); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/list/components/error-notification.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | import PropTypes from "prop-types"; 6 | import React from "react"; 7 | 8 | import styles from "./notification.css"; 9 | import { classNames } from "../../common"; 10 | 11 | export default function ErrorNotification({ className, children }) { 12 | const fullClassName = classNames([ styles.errorNotification, className ]); 13 | 14 | return
{children}
; 15 | } 16 | 17 | ErrorNotification.propTypes = { 18 | className: PropTypes.string, 19 | children: PropTypes.node, 20 | }; 21 | -------------------------------------------------------------------------------- /src/list/components/item-list.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | import PropTypes from "prop-types"; 6 | import React from "react"; 7 | 8 | import { classNames } from "../../common"; 9 | import ItemSummary from "./item-summary"; 10 | import ScrollingList from "../../widgets/scrolling-list"; 11 | 12 | import styles from "./item-list.css"; 13 | 14 | export default function ItemList({items, itemClassName, panel, ...props}) { 15 | return ( 16 | 20 | {({id, title, username}) => { 21 | return ( 22 | 24 | ); 25 | }} 26 | 27 | ); 28 | } 29 | 30 | ItemList.propTypes = { 31 | items: PropTypes.arrayOf( 32 | PropTypes.shape({ 33 | id: PropTypes.string.isRequired, 34 | title: PropTypes.string.isRequired, 35 | username: PropTypes.string.isRequired, 36 | }).isRequired 37 | ).isRequired, 38 | itemClassName: PropTypes.string, 39 | panel: PropTypes.bool, 40 | sort: PropTypes.string, 41 | }; 42 | 43 | ItemList.defaultProps = { 44 | itemClassName: "", 45 | sort: "name", 46 | }; 47 | 48 | export function ItemListPlaceholder({className, children}) { 49 | const fullClassName = classNames(["itemListEmpty", styles.empty, className]); 50 | return ( 51 |
52 | {children} 53 |
54 | ); 55 | } 56 | 57 | ItemListPlaceholder.propTypes = { 58 | className: PropTypes.string, 59 | children: PropTypes.node, 60 | }; 61 | 62 | ItemListPlaceholder.defaultProps = { 63 | className: "", 64 | }; 65 | -------------------------------------------------------------------------------- /src/list/components/item-summary.css: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | .item-summary-container { 6 | cursor: pointer; 7 | } 8 | 9 | .item-summary { 10 | padding: 9px 15px; 11 | position: relative; 12 | } 13 | 14 | .info { 15 | background: url(/icons/chevron-right.svg) no-repeat; 16 | position: absolute; 17 | right: 12px; 18 | top: 25px; 19 | height: 9px; 20 | width: 9px; 21 | } 22 | 23 | li[data-selected] .info { 24 | background-image: none; 25 | } 26 | 27 | .info.panel { 28 | right: 0px; 29 | top: -3px; 30 | width: 18px; 31 | height: 18px; 32 | margin: 20px; 33 | margin-right: 12px; 34 | background-image: url(/icons/info.svg); 35 | background-repeat: no-repeat; 36 | background-size: 18px 18px; 37 | background-position: 50%; 38 | padding: 7px; 39 | border-radius: 2px; 40 | } 41 | 42 | .info.panel:hover { 43 | background-color: rgba(12, 12, 13, 0.1); 44 | } 45 | 46 | .item-summary:hover .info.panel { 47 | background-image: url(/icons/info-dark.svg); 48 | } 49 | 50 | .title, 51 | .subtitle { 52 | line-height: 1.5; 53 | letter-spacing: 0.2px; 54 | overflow: hidden; 55 | white-space: nowrap; 56 | text-overflow: ellipsis; 57 | } 58 | 59 | .title { 60 | font-size: 14px; 61 | font-weight: 500; 62 | color: #0c0c0d; 63 | } 64 | 65 | .subtitle { 66 | font-size: 12px; 67 | font-weight: 300; 68 | color: #737373; 69 | } 70 | -------------------------------------------------------------------------------- /src/list/components/item-summary.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | import { Localized } from "fluent-react"; 6 | import PropTypes from "prop-types"; 7 | import React from "react"; 8 | 9 | import { classNames } from "../../common"; 10 | import { NEW_ITEM_ID } from "../common"; 11 | 12 | import styles from "./item-summary.css"; 13 | 14 | export default function ItemSummary({className, id, title, username, panel}) { 15 | const trimmedTitle = title.trim(); 16 | const trimmedUsername = username.trim(); 17 | 18 | const isNew = id === NEW_ITEM_ID; 19 | const idModifier = (subject) => { 20 | if (isNew) { 21 | return "new-"; 22 | } 23 | if (!subject.length) { 24 | return "no-"; 25 | } 26 | return ""; 27 | }; 28 | 29 | const titleId = `item-summary-${idModifier(trimmedTitle)}title`; 30 | const usernameId = `item-summary-${idModifier(trimmedUsername)}username`; 31 | return ( 32 |
33 |
34 | 35 |
{title || "nO tITLe"}
36 |
37 | 38 |
{username || "nO uSERNAMe"}
39 |
40 | {!isNew && panel && 41 | 42 | } 43 |
44 |
45 | ); 46 | } 47 | 48 | ItemSummary.propTypes = { 49 | className: PropTypes.string, 50 | id: PropTypes.string, 51 | title: PropTypes.string, 52 | username: PropTypes.string, 53 | panel: PropTypes.bool, 54 | }; 55 | 56 | ItemSummary.defaultProps = { 57 | className: "", 58 | id: null, 59 | title: "", 60 | username: "", 61 | showInfo: false, 62 | }; 63 | -------------------------------------------------------------------------------- /src/list/components/notification.css: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | .error-notification { 6 | background: #ffe900; 7 | padding: 8px 5px; 8 | } 9 | 10 | .error-notification button { 11 | border-radius: 10px; 12 | border: 1px solid #989898; 13 | color: black; 14 | cursor: pointer; 15 | float: right; 16 | font-size: 11px; 17 | font-weight: 500; 18 | height: 19px; 19 | margin: 0px 10px; 20 | } 21 | 22 | .warning-notification { 23 | background: #a4000f; 24 | margin-bottom: 40px; 25 | color: #ffffff; 26 | } 27 | 28 | .warning-message, 29 | .warning-message-light { 30 | display: inline-block; 31 | margin: 0 10px; 32 | font-size: 12px; 33 | } 34 | 35 | .warning-message:before, 36 | .warning-message-light:before { 37 | background-image: url(/icons/warning.svg); 38 | content: ''; 39 | display: inline-block; 40 | height: 16px; 41 | margin-right: 5px; 42 | margin-bottom: -2px; 43 | width: 16px; 44 | } 45 | 46 | .warning-message-light:before { 47 | background-image: url(/icons/warning-light.svg); 48 | } 49 | 50 | .close-icon { 51 | background-image: url(/icons/close.svg); 52 | cursor: pointer; 53 | display: inline-block; 54 | float: right; 55 | height: 16px; 56 | width: 16px; 57 | } 58 | -------------------------------------------------------------------------------- /src/list/components/sync-notification.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | import { Localized } from "fluent-react"; 6 | import PropTypes from "prop-types"; 7 | import React from "react"; 8 | 9 | import ErrorNotification from "./error-notification"; 10 | 11 | import styles from "./notification.css"; 12 | 13 | export default class SyncNotification extends React.Component { 14 | static get propTypes() { 15 | return { 16 | // profile not set as required, since the default is null 17 | // which tells us that the user has never signed in. 18 | hasProfileNeedsAttn: PropTypes.bool.isRequired, 19 | isPanel: PropTypes.bool, 20 | reconnectToSync: PropTypes.func.isRequired, 21 | }; 22 | } 23 | 24 | static get defaultProps() { 25 | return { 26 | hasProfileNeedsAttn: false, 27 | isPanel: false, 28 | reconnectToSync: () => {}, 29 | }; 30 | } 31 | 32 | constructor(props) { 33 | super(props); 34 | this.remove = this.remove.bind(this); 35 | this.state = {hideNotification: false}; 36 | } 37 | 38 | remove() { 39 | this.setState({ 40 | hideNotification: true, 41 | }); 42 | } 43 | 44 | render() { 45 | const { hideNotification } = this.state; 46 | const { isPanel, reconnectToSync, hasProfileNeedsAttn } = this.props; 47 | const shouldShow = hasProfileNeedsAttn && !hideNotification; 48 | 49 | return (<> 50 | {shouldShow && 51 | 52 |

Unable to sync logins.

53 |
54 | {!isPanel && } 55 | 56 | 57 | 58 |
} 59 | ); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/list/containers/connected-sync-notification.js: -------------------------------------------------------------------------------- 1 | import { connect } from "react-redux"; 2 | import { openSyncPrefs } from "../actions"; 3 | 4 | import SyncNotification from "../components/sync-notification"; 5 | 6 | export default connect(({ 7 | app: { 8 | profileWrap: { 9 | hasProfileNeedsAttn, 10 | }, 11 | }, 12 | }) => ({ 13 | hasProfileNeedsAttn, 14 | }), 15 | dispatch => ({ 16 | reconnectToSync: () => dispatch(openSyncPrefs()), 17 | }) 18 | )(SyncNotification); 19 | -------------------------------------------------------------------------------- /src/list/containers/item-filter.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | import { Localized } from "fluent-react"; 6 | import PropTypes from "prop-types"; 7 | import React from "react"; 8 | import { connect } from "react-redux"; 9 | 10 | import { filterItems } from "../actions"; 11 | import FilterInput from "../../widgets/filter-input"; 12 | 13 | function ItemFilter({inputRef, ...props}) { 14 | return ( 15 | 18 | 20 | 21 | ); 22 | } 23 | 24 | ItemFilter.propTypes = { 25 | inputRef: PropTypes.func, 26 | }; 27 | 28 | export default connect( 29 | (state) => ({ 30 | value: state.list.filter.query, 31 | disabled: state.cache.items.length === 0, 32 | }), 33 | (dispatch) => ({ 34 | onChange: (value) => { dispatch(filterItems(value)); }, 35 | }) 36 | )(ItemFilter); 37 | -------------------------------------------------------------------------------- /src/list/containers/no-matching-placeholder.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | import { Localized } from "fluent-react"; 8 | import PropTypes from "prop-types"; 9 | import React from "react"; 10 | import { connect } from "react-redux"; 11 | 12 | import { ItemListPlaceholder } from "../components/item-list"; 13 | import { Link } from "../../widgets/link"; 14 | import { openFAQ } from "../actions"; 15 | 16 | export function BaseNoMatchingPlaceholder({ 17 | withTitle, 18 | className, 19 | onLearnMore, 20 | }) { 21 | const header = (withTitle) ? 22 | 23 |

nO rESULTs!

24 |
: 25 | null; 26 | return 27 | {header} 28 | 29 |

nO rESULTs

30 |
31 | 34 | } 35 | > 36 |

lEARn mORe

37 |
38 |
; 39 | } 40 | 41 | BaseNoMatchingPlaceholder.propTypes = { 42 | withTitle: PropTypes.bool, 43 | className: PropTypes.string, 44 | onLearnMore: PropTypes.func.isRequired, 45 | }; 46 | 47 | BaseNoMatchingPlaceholder.defaultProps = { 48 | className: "", 49 | }; 50 | 51 | export default connect( 52 | (state, ownProps) => ({ 53 | ...ownProps, 54 | }), 55 | (dispatch, ownProps) => ({ 56 | // since doorhanger does not render a in-placeholder title ... 57 | // .. (ab)using the inverse of `withTitle` for the `close` flag 58 | onLearnMore: () => dispatch(openFAQ(null, !ownProps.withTitle)), 59 | }) 60 | )(BaseNoMatchingPlaceholder); 61 | -------------------------------------------------------------------------------- /src/list/filter.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | export function parseFilterString(filter) { 6 | filter = filter.trim(); 7 | if (!filter) { 8 | return []; 9 | } 10 | return filter.split(/\s+/).map((i) => i.toLocaleLowerCase()); 11 | } 12 | 13 | function match(filterElement, value) { 14 | return value.includes(filterElement); 15 | } 16 | 17 | function matchAny(filterElement, values) { 18 | for (let i of values) { 19 | if (match(filterElement, i)) { 20 | return true; 21 | } 22 | } 23 | return false; 24 | } 25 | 26 | export function filterItem(filter, item) { 27 | const title = item.title.toLocaleLowerCase(); 28 | const username = item.username.toLocaleLowerCase(); 29 | const origins = item.origins.map((i) => i.toLocaleLowerCase()); 30 | 31 | for (let i of filter) { 32 | if (!match(i, title) && !match(i, username) && !matchAny(i, origins)) { 33 | return false; 34 | } 35 | } 36 | return true; 37 | } 38 | -------------------------------------------------------------------------------- /src/list/manage/components/app.css: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | body { 6 | padding: 0; 7 | margin: 0; 8 | color: #0c0c0d; 9 | background-color: #f9f9fa; 10 | font: caption; 11 | font-size: 13px; 12 | -moz-user-select: none; 13 | overflow: hidden; 14 | } 15 | 16 | html, 17 | body, 18 | body > main, 19 | .app { 20 | height: 100%; 21 | } 22 | 23 | .app { 24 | display: flex; 25 | flex-flow: column nowrap; 26 | } 27 | 28 | .app-main { 29 | display: grid; 30 | height: 100%; 31 | grid-template-columns: 1fr 3fr; 32 | overflow: hidden; 33 | } 34 | 35 | .app-main article { 36 | padding-top: 28px; 37 | } 38 | 39 | .navigation { 40 | height: 48px; 41 | padding-inline-start: 4.6em; 42 | } 43 | 44 | .navigation > * + * { 45 | margin-inline-start: 40px; 46 | } 47 | 48 | .aside { 49 | grid-column: 1; 50 | min-height: 0; 51 | display: flex; 52 | flex-grow: 0; 53 | flex-direction: column; 54 | border-inline-end: 1px solid #d7d7db; 55 | } 56 | 57 | .app-main > article { 58 | grid-column: 2; 59 | background-color: #f9f9fa; 60 | overflow: auto; 61 | display: flex; 62 | flex-flow: column nowrap; 63 | } 64 | -------------------------------------------------------------------------------- /src/list/manage/components/app.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | import { Localized } from "fluent-react"; 6 | import React from "react"; 7 | import DocumentTitle from "react-document-title"; 8 | 9 | import SyncNotification from "../../containers/connected-sync-notification"; 10 | import AppHeader from "../containers/app-header"; 11 | import AllItems from "../containers/all-items"; 12 | import CurrentSelection from "../containers/current-selection"; 13 | import ModalRoot from "../containers/modals"; 14 | import CurrentPromotionBanner from "../containers/connected-promote-banner"; 15 | 16 | 17 | import styles from "./app.css"; 18 | 19 | export default class App extends React.Component { 20 | componentDidMount() { 21 | this._filterField.focus(true); 22 | } 23 | 24 | render() { 25 | return ( 26 | 27 | 28 |
29 | 30 | { 31 | this._filterField = element; 32 | }}/> 33 |
34 | 35 |
36 | 37 |
38 |
39 | 40 | 41 |
42 |
43 |
44 | ); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/list/manage/components/homepage.css: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | .homepage { 6 | padding: 1em 4.6em 4.6em; 7 | flex: 1 1 auto; 8 | display: flex; 9 | flex-flow: column nowrap; 10 | align-items: center; 11 | } 12 | 13 | .promotion { 14 | flex: 1 1 auto; 15 | display: flex; 16 | flex-flow: row nowrap; 17 | align-items: end; 18 | align-self: stretch; 19 | } 20 | -------------------------------------------------------------------------------- /src/list/manage/components/homepage.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | import React from "react"; 6 | 7 | import styles from "./homepage.css"; 8 | 9 | export default function Homepage() { 10 | return ( 11 |
12 |
13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /src/list/manage/components/intro-page.css: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | .intro-page { 6 | color: #0c0c0d; 7 | text-align: center; 8 | margin: 0 95px; 9 | min-width: 500px; 10 | } 11 | 12 | .intro-page footer h2, 13 | .intro-page header h2 { 14 | font-size: 22px; 15 | font-weight: lighter; 16 | } 17 | 18 | .intro-page footer { 19 | margin-top: 40px; 20 | } 21 | 22 | .intro-page footer p, 23 | .intro-page header p { 24 | color: #737373; 25 | font-size: 15px; 26 | } 27 | 28 | .intro-main { 29 | display: grid; 30 | grid-auto-flow: column; 31 | grid-template-columns: 1fr 1fr; 32 | grid-template-rows: auto auto; 33 | grid-column-gap: 4em; 34 | 35 | font-size: 13px; 36 | margin-bottom: 40px; 37 | } 38 | 39 | .intro-main h3 { 40 | font-size: 15px; 41 | font-weight: bold; 42 | } 43 | 44 | .intro-main article { 45 | padding: 0 !important; 46 | text-align: left; 47 | } 48 | 49 | .intro-page hr { 50 | color: #ededf0; 51 | width: 200px; 52 | } 53 | 54 | .intro-page footer button { 55 | font-size: inherit; 56 | } 57 | -------------------------------------------------------------------------------- /src/list/manage/components/item-details.css: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | .item-details { 6 | padding: 1em 4.6em 4.6em; 7 | background-color: #f9f9fa; 8 | display: flex; 9 | flex: 1 1 auto; 10 | flex-flow: column nowrap; 11 | overflow: auto; 12 | min-width: 500px; 13 | } 14 | 15 | .item-details header { 16 | display: flex; 17 | flex-flow: row nowrap; 18 | align-items: baseline; 19 | border-bottom: 1px solid #d7d7db; 20 | height: 50px; 21 | padding-bottom: 8px; 22 | } 23 | .item-details header h1 { 24 | flex: 1 1 auto; 25 | margin: 0px; 26 | display: inline; 27 | font-size: 33px; 28 | line-height: 1.5; 29 | font-weight: 300; 30 | } 31 | .item-details header menu { 32 | flex: 0 1 auto; 33 | } 34 | 35 | .clear { 36 | clear: both; 37 | } 38 | 39 | .item-details .actions { 40 | padding: 40px 0px 0px; 41 | } 42 | 43 | .item-details menu > button { 44 | margin-top: 0px; 45 | } 46 | 47 | .edit-button, .delete-button { 48 | font-size: 15px; 49 | } 50 | .edit-button::before, .delete-button::before { 51 | content: ""; 52 | display: inline-block; 53 | width: 16px; 54 | height: 16px; 55 | background-repeat: no-repeat; 56 | padding-right: 8px; 57 | vertical-align: sub; 58 | } 59 | .edit-button::before { 60 | background-image: url(/icons/edit.svg); 61 | } 62 | 63 | .delete-button::before { 64 | background-image: url(/icons/delete.svg); 65 | background-size: 105% 100%; 66 | } 67 | 68 | hr { 69 | color: #d7d7db; 70 | } 71 | 72 | .metadata { 73 | color: #737373; 74 | font-size: 13px; 75 | } 76 | 77 | .metadata hr { 78 | width: 40px; 79 | color: #d7d7db; 80 | float: left; 81 | margin-top: 50px; 82 | } 83 | -------------------------------------------------------------------------------- /src/list/manage/components/item-list-panel.css: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | .filter-toolbar { 6 | flex-direction: column; 7 | align-items: stretch; 8 | } 9 | 10 | .panel-header { 11 | composes: panel-header from "../../../widgets/panel.css"; 12 | background: #f9f9fa; 13 | height: auto; 14 | padding: 0; 15 | } 16 | 17 | .first-row, .second-row { 18 | display: flex; 19 | flex-flow: row nowrap; 20 | } 21 | 22 | .first-row > * + * { 23 | margin-inline-start: 8px; 24 | } 25 | 26 | .second-row { 27 | background-color: #f9f9fa; 28 | font-weight: 300; 29 | display: flex; 30 | justify-content: space-between; 31 | margin-inline-start: 0; 32 | line-height: 32px; 33 | height: 32px; 34 | margin: 0; 35 | padding: 0 10px; 36 | } 37 | 38 | .item-filter { 39 | composes: item-filter from "../../../widgets/panel.css"; 40 | background: #f3f3f5; 41 | height: 32px; 42 | } 43 | 44 | .flex-spacer { 45 | flex-grow: 1; 46 | } 47 | 48 | .list-counter { 49 | flex: 1 1 auto; 50 | overflow: hidden; 51 | text-overflow: ellipsis; 52 | font-size: 12px; 53 | color: #4a4a4f; 54 | text-align: right; 55 | } 56 | 57 | .empty { 58 | border-top: 1px solid #d7d7db; 59 | } 60 | 61 | .panel-body ul { 62 | /* Ensure the promo banner stuck to the bottom of the viewport doesn't 63 | totally overlap the bottom of the list. */ 64 | max-height: calc(100% - 85px); 65 | } 66 | -------------------------------------------------------------------------------- /src/list/manage/components/list-counter.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | import { Localized } from "fluent-react"; 6 | import PropTypes from "prop-types"; 7 | import React from "react"; 8 | 9 | export default function ListCounter({count, className}) { 10 | return ( 11 | 12 |
{count} eNTRIEs
13 |
14 | ); 15 | } 16 | 17 | ListCounter.propTypes = { 18 | count: PropTypes.number, 19 | className: PropTypes.string, 20 | }; 21 | 22 | ListCounter.defaultProps = { 23 | count: 0, 24 | className: "", 25 | }; 26 | -------------------------------------------------------------------------------- /src/list/manage/components/list-sort.css: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | .label { 6 | color: #4a4a4f; 7 | font-size: 12px; 8 | flex: 0 0 auto; 9 | } 10 | 11 | .select { 12 | flex: 0 1 auto; 13 | color: #0c0c0d; 14 | text-overflow: ellipsis; 15 | appearance: none; 16 | -moz-appearance: none; 17 | border: none; 18 | font-size: 12px; 19 | font-weight: bold; 20 | background: url(/icons/arrow-dropdown.svg) right center no-repeat; 21 | padding-inline-end: 18px; 22 | border-radius: 0; 23 | padding-left: 3px; 24 | min-width: 65px; 25 | } 26 | 27 | .select option { 28 | font-weight: normal; 29 | } 30 | 31 | .select[disabled] { 32 | cursor: not-allowed; 33 | } 34 | -------------------------------------------------------------------------------- /src/list/manage/components/promote-banner.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019 Matthew A. Miller 3 | * 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | */ 8 | 9 | .promotion { 10 | display: flex; 11 | flex-flow: row nowrap; 12 | align-items: center; 13 | justify-content: center; 14 | min-width: 500px; 15 | } 16 | .promotion .content { 17 | flex: 0 1 auto; 18 | flex-flow: row; 19 | text-align: center; 20 | font-size: 15px; 21 | margin: 0; 22 | margin-right: 15px; 23 | } 24 | 25 | .promotion .content strong { 26 | font-weight: 500; 27 | } 28 | 29 | .promotion .action { 30 | margin: 0; 31 | } 32 | 33 | /* TODO: include app store and google play icons for other languages */ 34 | .action.ios { 35 | background: url(/icons/localized/en/icon-app-store.svg) no-repeat center center; 36 | height: 40px; 37 | } 38 | 39 | .action.android { 40 | background: url(/icons/localized/en/icon-play-store.png) no-repeat center center/150px; 41 | height: 40px; 42 | } 43 | 44 | .promotion .action.ios:hover, 45 | .promotion .action.android:hover { 46 | background-color: white; 47 | cursor: pointer; 48 | } 49 | 50 | .promotion .action.ios:focus, 51 | .promotion .action.android:focus { 52 | box-shadow: none; 53 | } 54 | -------------------------------------------------------------------------------- /src/list/manage/containers/add-item.css: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | .add-item { 6 | flex: 0 0 auto; 7 | margin-left: 2em; 8 | padding: 0px 2em; 9 | min-width: 120px; 10 | } 11 | -------------------------------------------------------------------------------- /src/list/manage/containers/add-item.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | import { Localized } from "fluent-react"; 6 | import PropTypes from "prop-types"; 7 | import React from "react"; 8 | import { connect } from "react-redux"; 9 | 10 | import Button from "../../../widgets/button"; 11 | import { startNewItem } from "../../actions"; 12 | 13 | import styles from "./add-item.css"; 14 | 15 | function AddItem({disabled, onAddItem}) { 16 | return ( 17 | 18 | 22 | 23 | ); 24 | } 25 | 26 | AddItem.propTypes = { 27 | disabled: PropTypes.bool.isRequired, 28 | onAddItem: PropTypes.func.isRequired, 29 | }; 30 | 31 | export default connect( 32 | (state) => ({ 33 | disabled: state.editor.editing, 34 | }), 35 | (dispatch) => ({ 36 | onAddItem: () => { dispatch(startNewItem()); }, 37 | }) 38 | )(AddItem); 39 | -------------------------------------------------------------------------------- /src/list/manage/containers/all-items.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | import { connect } from "react-redux"; 6 | 7 | import ItemListPanel from "../components/item-list-panel"; 8 | import { requestSelectItem } from "../../actions"; 9 | import { parseFilterString, filterItem } from "../../filter"; 10 | import { getSort } from "../../sort"; 11 | import { NEW_ITEM_ID } from "../../common"; 12 | 13 | export default connect( 14 | (state, ownProps) => { 15 | const totalItemCount = state.cache.items.length; 16 | const filter = parseFilterString(state.list.filter.query); 17 | const selected = state.list.selectedItemId; 18 | const sort = state.cache.sort; 19 | const sortFn = getSort(state.cache.sort); 20 | const items = state.cache.items 21 | .filter((i) => filterItem(filter, i)) 22 | .sort(sortFn); 23 | const count = items.length; 24 | 25 | if (selected === NEW_ITEM_ID) { 26 | items.unshift({id: NEW_ITEM_ID, title: "", username: ""}); 27 | } 28 | return {...ownProps, totalItemCount, items, selected, count, sort}; 29 | }, 30 | (dispatch) => ({ 31 | onChange: (id) => dispatch(requestSelectItem(id)), 32 | }), 33 | )(ItemListPanel); 34 | -------------------------------------------------------------------------------- /src/list/manage/containers/app-panes.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | import PropTypes from "prop-types"; 6 | import { connect } from "react-redux"; 7 | 8 | export function AppPanes({ 9 | children, 10 | selectedTab = "logins", 11 | }) { 12 | return children[selectedTab]; 13 | } 14 | 15 | AppPanes.propTypes = { 16 | children: PropTypes.object.isRequired, 17 | selectedTab: PropTypes.string.isRequired, 18 | }; 19 | 20 | export default connect( 21 | ({ app: { tabs: { selectedTab }} }) => ({ selectedTab }), 22 | )(AppPanes); 23 | -------------------------------------------------------------------------------- /src/list/manage/containers/connected-promote-banner.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | import PropTypes from "prop-types"; 8 | import React from "react"; 9 | import { connect } from "react-redux"; 10 | 11 | import { PromoteDeviceBanner, PromoteFxABanner } from "../components/promote-banner"; 12 | import { openSyncPrefs, openAppStore, openPlayStore } from "../../actions"; 13 | 14 | export function CurrentPromotionBanner({hasProfile, openAppStore, openPlayStore, onClickFxABanner}) { 15 | if (hasProfile) { 16 | return ; 18 | } 19 | return ; 20 | } 21 | 22 | CurrentPromotionBanner.propTypes = { 23 | hasProfile: PropTypes.bool, 24 | openAppStore: PropTypes.func.isRequired, 25 | openPlayStore: PropTypes.func.isRequired, 26 | onClickFxABanner: PropTypes.func.isRequired, 27 | }; 28 | 29 | export default connect( 30 | (state) => ({ 31 | hasProfile: state.app.profileWrap.hasProfile, 32 | }), 33 | (dispatch) => ({ 34 | openPlayStore: () => dispatch(openPlayStore()), 35 | openAppStore: () => dispatch(openAppStore()), 36 | onClickFxABanner: () => dispatch(openSyncPrefs()), 37 | }), 38 | )(CurrentPromotionBanner); 39 | -------------------------------------------------------------------------------- /src/list/manage/containers/list-sort.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | import { connect } from "react-redux"; 6 | 7 | import ListSort from "../components/list-sort"; 8 | import { getAction } from "../../sort"; 9 | 10 | export default connect( 11 | (state) => ({ 12 | sort: state.cache.sort, 13 | disabled: !state.cache.items.length, 14 | }), 15 | (dispatch) => ({ 16 | onChange: (newSort) => { 17 | dispatch({ type: getAction(newSort) }); 18 | }, 19 | }) 20 | )(ListSort); 21 | -------------------------------------------------------------------------------- /src/list/manage/reducers.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | import { combineReducers } from "redux"; 6 | 7 | import * as actions from "../actions"; 8 | import { cacheReducer, listReducer, profileReducer, 9 | } from "../reducers"; 10 | 11 | export function appReducer(state, action) { 12 | return combineReducers({ 13 | profileWrap: profileReducer, 14 | })(state, action); 15 | } 16 | 17 | export function editorReducer(state = { 18 | editing: false, changed: false, hideHome: false, 19 | }, action) { 20 | switch (action.type) { 21 | case actions.ADD_ITEM_FAILED: 22 | case actions.UPDATE_ITEM_FAILED: 23 | return {...state, editing: true, hideHome: false, error: action.error}; 24 | case actions.ADD_ITEM_COMPLETED: 25 | case actions.UPDATE_ITEM_COMPLETED: 26 | if (action.interactive) { 27 | return {...state, editing: false, changed: false}; 28 | } 29 | return state; 30 | case actions.SELECT_ITEM_STARTING: 31 | return {...state, editing: false, changed: false, hideHome: state.editing}; 32 | case actions.SELECT_ITEM_COMPLETED: 33 | return {...state, hideHome: false}; 34 | case actions.START_NEW_ITEM: 35 | case actions.EDIT_CURRENT_ITEM: 36 | return {...state, editing: true, error: null}; 37 | case actions.EDITOR_CHANGED: 38 | return {...state, changed: true}; 39 | case actions.CANCEL_EDITING: 40 | case actions.REMOVE_ITEM_STARTING: 41 | return {...state, editing: false, changed: false}; 42 | default: 43 | return state; 44 | } 45 | } 46 | 47 | export function modalReducer(state = {id: null, props: null}, action) { 48 | switch (action.type) { 49 | case actions.SHOW_MODAL: 50 | return {...state, id: action.id, props: action.props}; 51 | case actions.HIDE_MODAL: 52 | return {...state, id: null, props: null}; 53 | default: 54 | return state; 55 | } 56 | } 57 | 58 | export default combineReducers({ 59 | app: appReducer, 60 | cache: cacheReducer, 61 | list: listReducer, 62 | editor: editorReducer, 63 | modal: modalReducer, 64 | }); 65 | -------------------------------------------------------------------------------- /src/list/manage/sort-middleware.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | import { sortActions, getName } from "../sort"; 6 | 7 | // This could grow into a generic localStorage middleware, but for now, it just 8 | // saves the current sort option in the manage page UI, so that the user's 9 | // preferred sort is persisted across page reloads. 10 | 11 | // Cache in memory to minimize the number of localStorage calls. 12 | let currentSort; 13 | 14 | export const saveSort = (store) => (next) => (action) => { 15 | if (sortActions.includes(action.type)) { 16 | const sort = getName(action.type); 17 | if (currentSort !== sort) { 18 | currentSort = sort; 19 | browser.storage.local.set({ sort }) 20 | .catch(console.error); // eslint-disable-line no-console 21 | } 22 | } 23 | return next(action); 24 | }; 25 | 26 | export const loadSort = async () => { 27 | let storage; 28 | try { 29 | storage = await browser.storage.local.get(); 30 | } catch (ex) { 31 | // eslint-disable-next-line no-console 32 | console.error("loadSort failed: ", ex); 33 | } 34 | currentSort = storage.sort; 35 | return storage.sort; 36 | }; 37 | -------------------------------------------------------------------------------- /src/list/message-ports.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | import { 6 | addedItem, 7 | updatedItem, 8 | removedItem, 9 | removedAllItems, 10 | updatedProfile, 11 | } from "./actions"; 12 | 13 | let messagePort; 14 | 15 | export default function initializeMessagePorts(store) { 16 | // Listen for changes to the datastore from other sources and dispatch actions 17 | // to sync those changes with our Redux store. 18 | messagePort = browser.runtime.connect(); 19 | messagePort.onMessage.addListener((message) => { 20 | switch (message.type) { 21 | case "added_item": 22 | store.dispatch(addedItem(message.item)); 23 | break; 24 | case "updated_item": 25 | store.dispatch(updatedItem(message.item)); 26 | break; 27 | case "removed_item": 28 | store.dispatch(removedItem(message.id)); 29 | break; 30 | case "removed_all": 31 | store.dispatch(removedAllItems()); 32 | break; 33 | case "profile_info": 34 | store.dispatch(updatedProfile(message.profile)); 35 | break; 36 | } 37 | }); 38 | } 39 | -------------------------------------------------------------------------------- /src/list/open-link-middleware.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | import * as actions from "./actions"; 6 | import { version } from "../../package"; 7 | import { openWebsite } from "./common"; 8 | 9 | // TODO: Update these links? 10 | const urls = { 11 | feedback: "https://www.surveygizmo.com/s3/5036102/Lockwise-feedback?ver=" + version, 12 | faq: "https://lockwise.firefox.com/faq.html", 13 | homepage: "https://lockwise.firefox.com", 14 | appStore: "https://app.adjust.com/eu4xdqg?redirect=https://itunes.apple.com/us/app/firefox-lockbox/id1314000270?mt=8", 15 | androidStore: "https://app.adjust.com/eu4xdqg?redirect=https://play.google.com/store/apps/details?id=mozilla.lockbox", 16 | }; 17 | 18 | function openTargetedWebsite(url, target, close) { 19 | const location = (target) ? 20 | `${url}#${target}` : 21 | url; 22 | openWebsite(location, close); 23 | } 24 | 25 | export default (store) => (next) => (action) => { 26 | switch (action.type) { 27 | case actions.OPEN_FAQ: 28 | openTargetedWebsite(urls.faq, action.target, action.close); 29 | break; 30 | case actions.OPEN_FEEDBACK: 31 | openWebsite(urls.feedback, false); 32 | break; 33 | case actions.OPEN_HOMEPAGE: 34 | openWebsite(urls.homepage, false); 35 | break; 36 | case actions.OPEN_SYNC_PREFS: 37 | browser.experiments.sync.openPreferences("lockbox-addon"); 38 | break; 39 | case actions.OPEN_APP_STORE: 40 | openWebsite(urls.appStore, false); 41 | break; 42 | case actions.OPEN_PLAY_STORE: 43 | openWebsite(urls.androidStore, false); 44 | break; 45 | case actions.OPEN_WEBSITE: 46 | const url = action.item.origins[0]; 47 | openWebsite(url, action.close); 48 | break; 49 | } 50 | return next(action); 51 | }; 52 | -------------------------------------------------------------------------------- /src/list/popup/components/app.css: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | body { 6 | padding: 0; 7 | margin: 0; 8 | width: 100%; 9 | height: 339px; 10 | color: #0c0c0d; 11 | background-color: #ffffff; 12 | font: caption; 13 | font-size: 13px; 14 | -moz-user-select: none; 15 | } 16 | 17 | @media (max-width: 300px) { 18 | body { 19 | width: 300px; 20 | } 21 | } 22 | 23 | body > main, 24 | .app { 25 | height: 100%; 26 | } 27 | -------------------------------------------------------------------------------- /src/list/popup/components/app.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | import { Localized } from "fluent-react"; 6 | import React from "react"; 7 | import DocumentTitle from "react-document-title"; 8 | 9 | import CurrentSelection from "../containers/current-selection"; 10 | 11 | import styles from "./app.css"; 12 | 13 | export default class App extends React.Component { 14 | componentDidMount() { 15 | this._filterField.focus(true); 16 | } 17 | 18 | render() { 19 | return ( 20 | 21 | 22 |
23 | { 24 | this._filterField = element; 25 | }}/> 26 |
27 |
28 |
29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/list/popup/components/item-details-panel.css: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | .panel-body { 6 | padding: 1em; 7 | background: #F9F9FA; 8 | } 9 | 10 | .panel-header { 11 | background: #fff; 12 | text-align: center; 13 | } 14 | 15 | .panel-header .edit-btn { 16 | display: block; 17 | background-image: url(/icons/edit.svg); 18 | background-repeat: no-repeat; 19 | background-size: 16px 16px; 20 | height: 16px; 21 | width: 16px; 22 | position: absolute; 23 | top: 16px; 24 | right: 8px; 25 | } 26 | 27 | .panel-footer-button { 28 | background: #2a2a2e !important; 29 | color: #f9f9fa; 30 | } 31 | -------------------------------------------------------------------------------- /src/list/popup/components/item-details-panel.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | import { Localized } from "fluent-react"; 6 | import React from "react"; 7 | import PropTypes from "prop-types"; 8 | 9 | import Panel, { PanelHeader, PanelBody, PanelFooter, 10 | PanelFooterButton } from "../../../widgets/panel"; 11 | import { ItemFields } from "../../components/item-fields"; 12 | 13 | import styles from "./item-details-panel.css"; 14 | 15 | export default function ItemDetailsPanel({fields, showPassword, onCopy, onBack, onReveal, onOpenWebsite}) { 16 | return ( 17 | 18 | 19 | 20 | lOGIn dETAILs 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 35 | 36 | oPEn wEBSITe 37 | 38 | 39 | 40 | 41 | ); 42 | } 43 | 44 | ItemDetailsPanel.propTypes = { 45 | ...ItemFields.propTypes, 46 | onCopy: PropTypes.func.isRequired, 47 | onBack: PropTypes.func.isRequired, 48 | onReveal: PropTypes.func.isRequired, 49 | onOpenWebsite: PropTypes.func.isRequired, 50 | }; 51 | -------------------------------------------------------------------------------- /src/list/popup/components/item-list-panel.css: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | .empty { 6 | padding: 20px; 7 | } 8 | 9 | .panel-footer-button { 10 | color: #f9f9fa; 11 | cursor: pointer; 12 | background: #4a4a4f; 13 | font-size: 13px; 14 | } 15 | 16 | .panel-footer-button:hover { 17 | color: #f9f9fa; 18 | background: #2a2a2e !important; 19 | } 20 | 21 | .panel-header { 22 | background: #ffffff; 23 | padding: 12px; 24 | } 25 | 26 | .panel-banner { 27 | background: #d7d7db; 28 | color: #2a2a2e; 29 | font-size: 12px; 30 | font-weight: normal; 31 | } 32 | 33 | .filter-panel { 34 | background: #ffffff; 35 | border: 0; 36 | } 37 | 38 | .filter-panel::before, 39 | .filter-panel:focus-within::before { 40 | background-image: url(/icons/icon-lockwise.svg); 41 | } 42 | 43 | .filter-panel .input-wrapper { 44 | border: none; 45 | } 46 | 47 | .filter-panel input { 48 | background: #ffffff; 49 | font-size: 15px; 50 | font-weight: normal; 51 | color: #171718; 52 | border: none; 53 | } 54 | 55 | input::placeholder { 56 | color: #0C0C0D; 57 | } 58 | -------------------------------------------------------------------------------- /src/list/popup/containers/all-items-panel.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | import { connect } from "react-redux"; 6 | 7 | import ItemListPanel from "../components/item-list-panel"; 8 | import { selectItem } from "../../actions"; 9 | import { parseFilterString, filterItem } from "../../filter"; 10 | import { byLastUsed } from "../../sort"; 11 | 12 | export default connect( 13 | (state, ownProps) => { 14 | const totalItemCount = state.cache.items.length; 15 | const filter = parseFilterString(state.list.filter.query); 16 | const items = state.cache.items 17 | .filter((i) => filterItem(filter, i)) 18 | .sort(byLastUsed); 19 | const isFiltering = state.list.filter.query.length; 20 | const currProps = {...ownProps, totalItemCount}; 21 | if (items.length || state.list.filter.userEntered) { 22 | return {...currProps, items, noResultsBanner: false, isFiltering}; 23 | } 24 | // An autogenerated filter that produced no results; show everything, plus 25 | // a banner informing users. 26 | return {...currProps, items: state.cache.items, noResultsBanner: true}; 27 | }, 28 | (dispatch) => ({ 29 | onClick: (id) => { dispatch(selectItem(id)); }, 30 | }), 31 | )(ItemListPanel); 32 | -------------------------------------------------------------------------------- /src/list/popup/containers/current-selection.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | import PropTypes from "prop-types"; 6 | import React from "react"; 7 | import { connect } from "react-redux"; 8 | 9 | import { flattenItem } from "../../common"; 10 | import { selectItem, copiedField, openWebsite } from "../../actions"; 11 | import AllItemsPanel from "./all-items-panel"; 12 | import ItemDetailsPanel from "../components/item-details-panel"; 13 | import { concealPassword, revealPassword } from "../../actions"; 14 | 15 | const ConnectedItemDetailsPanel = connect( 16 | (state, ownProps) => ({ 17 | fields: flattenItem(ownProps.item), 18 | showPassword: state.list.showPassword, 19 | }), 20 | (dispatch, ownProps) => ({ 21 | onCopy: (field, toCopy) => { dispatch(copiedField(field, toCopy, ownProps.item)); }, 22 | onBack: () => { dispatch(selectItem(null)); }, 23 | onReveal: (show) => { 24 | const id = ownProps.item && ownProps.item.id; 25 | return dispatch(show ? revealPassword(id) : concealPassword(id)); 26 | }, 27 | onOpenWebsite: () => { dispatch(openWebsite(ownProps.item, true)); }, 28 | }) 29 | )(ItemDetailsPanel); 30 | 31 | function CurrentSelection({item, inputRef, onReveal}) { 32 | if (item) { 33 | return ; 34 | } 35 | return ; 36 | } 37 | 38 | CurrentSelection.propTypes = { 39 | item: PropTypes.object, 40 | inputRef: PropTypes.func, 41 | onReveal: PropTypes.func, 42 | }; 43 | 44 | export default connect( 45 | (state) => ({ 46 | item: state.cache.currentItem, 47 | }), 48 | )(CurrentSelection); 49 | -------------------------------------------------------------------------------- /src/list/popup/containers/no-entries-placeholder.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | import { Localized } from "fluent-react"; 8 | import PropTypes from "prop-types"; 9 | import React from "react"; 10 | import { connect } from "react-redux"; 11 | 12 | import { ItemListPlaceholder } from "../../components/item-list"; 13 | import { Link } from "../../../widgets/link"; 14 | import { openFAQ } from "../../actions"; 15 | 16 | export function BaseNoEntriesPlaceholder({className, onLearnMore}) { 17 | return 18 | 19 |

wHEn yOu cREATe an eNTRy...

20 |
21 | 24 | }> 25 |

fINd oUt wHy

26 |
27 |
; 28 | } 29 | 30 | BaseNoEntriesPlaceholder.propTypes = { 31 | className: PropTypes.string, 32 | onLearnMore: PropTypes.func.isRequired, 33 | }; 34 | 35 | BaseNoEntriesPlaceholder.defaultProps = { 36 | className: "", 37 | }; 38 | 39 | export default connect( 40 | (state, ownProps) => ({ 41 | ...ownProps, 42 | }), 43 | (dispatch) => ({ 44 | onLearnMore: () => dispatch(openFAQ("how-do-i-get-my-saved-logins-into-firefox-lockbox", true)), 45 | }), 46 | )(BaseNoEntriesPlaceholder); 47 | -------------------------------------------------------------------------------- /src/list/popup/index.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | import React from "react"; 6 | import ReactDOM from "react-dom"; 7 | import { Provider } from "react-redux"; 8 | import { createStore, applyMiddleware } from "redux"; 9 | import thunk from "redux-thunk"; 10 | 11 | import AppLocalizationProvider from "../../l10n"; 12 | import App from "./components/app"; 13 | import { filterItems, listItems, getProfile } from "../actions"; 14 | import reducer from "./reducers"; 15 | import initializeMessagePorts from "../message-ports"; 16 | import telemetryLogger from "./telemetry"; 17 | import openLink from "../open-link-middleware"; 18 | 19 | const store = createStore(reducer, undefined, applyMiddleware( 20 | thunk, telemetryLogger, openLink, 21 | )); 22 | 23 | // Pre-fill the URL of the current tab. 24 | // XXX: Eventually, we'll probably want to put this in another file. 25 | browser.tabs.query({ active: true, currentWindow: true }).then((result) => { 26 | const validProtocols = ["http:", "https:"]; 27 | const url = new URL(result[0].url); 28 | if (validProtocols.includes(url.protocol)) { 29 | store.dispatch(filterItems(url.hostname, false)); 30 | } 31 | }); 32 | 33 | store.dispatch(listItems()); 34 | initializeMessagePorts(store); 35 | store.dispatch(getProfile()); 36 | 37 | ReactDOM.render( 38 | 39 | 41 | 42 | 43 | , 44 | document.getElementById("content") 45 | ); 46 | -------------------------------------------------------------------------------- /src/list/popup/reducers.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | import { combineReducers } from "redux"; 6 | 7 | import { cacheReducer, listReducer, profileReducer } from "../reducers"; 8 | 9 | export default combineReducers({ 10 | // we use combineReducers here to add another layer 11 | // in the object in order to match the pattern used 12 | // in manage/reducers.js 13 | app: combineReducers({profileWrap: profileReducer}), 14 | cache: cacheReducer, 15 | list: listReducer, 16 | }); 17 | -------------------------------------------------------------------------------- /src/list/popup/telemetry.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | import * as actions from "../actions"; 6 | import helpers from "../telemetry"; 7 | 8 | export default (store) => (next) => (action) => { 9 | try { 10 | switch (action.type) { 11 | case actions.CONCEAL_PASSWORD: 12 | helpers.passwordConcealed("itemDetailDoorhanger"); 13 | break; 14 | case actions.COPIED_FIELD_COMPLETED: 15 | helpers.itemCopied(action, "itemDetailDoorhanger"); 16 | break; 17 | case actions.LIST_ITEMS_COMPLETED: 18 | // Accessing item info from the store requires waiting a turn. 19 | setTimeout(() => { 20 | const state = store.getState(); 21 | helpers.listShown("itemListDoorhanger", state.cache.items); 22 | }, 0); 23 | break; 24 | case actions.OPEN_WEBSITE: 25 | helpers.websiteOpened("itemDetailDoorhanger"); 26 | break; 27 | case actions.REVEAL_PASSWORD: 28 | helpers.passwordRevealed("itemDetailDoorhanger"); 29 | break; 30 | case actions.SELECT_ITEM_COMPLETED: 31 | if (action.item) { 32 | helpers.itemShown("itemDetailDoorhanger"); 33 | } 34 | break; 35 | case actions.SELECT_ITEM_STARTING: 36 | if (action.id) { 37 | helpers.itemSelected("doorhanger"); 38 | } 39 | break; 40 | } 41 | } catch (e) { 42 | // eslint-disable-next-line no-console 43 | console.error("Unable to record telemetry event", e); 44 | } 45 | 46 | return next(action); 47 | }; 48 | -------------------------------------------------------------------------------- /src/list/sort.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | import { SORT_BY_NAME, SORT_BY_LAST_USED, SORT_BY_LAST_CHANGED } from "./actions"; 6 | 7 | const collator = new Intl.Collator(); 8 | 9 | export const sortActions = [ 10 | SORT_BY_NAME, 11 | SORT_BY_LAST_USED, 12 | SORT_BY_LAST_CHANGED, 13 | ]; 14 | 15 | export const byName = (a, b) => collator.compare(a.title, b.title); 16 | 17 | export const byLastUsed = (a, b) => a.timeLastUsed < b.timeLastUsed; 18 | 19 | export const byLastChanged = (a, b) => a.timePasswordChanged < b.timePasswordChanged; 20 | 21 | export const getAction = (name) => { 22 | switch (name) { 23 | case "last-changed": 24 | return SORT_BY_LAST_CHANGED; 25 | case "last-used": 26 | return SORT_BY_LAST_USED; 27 | case "name": 28 | default: 29 | return SORT_BY_NAME; 30 | } 31 | }; 32 | 33 | export const getSortForAction = (action) => { 34 | switch (action) { 35 | case SORT_BY_LAST_CHANGED: 36 | return byLastChanged; 37 | case SORT_BY_LAST_USED: 38 | return byLastUsed; 39 | case SORT_BY_NAME: 40 | default: 41 | return byName; 42 | } 43 | }; 44 | 45 | export const getName = (action) => { 46 | switch (action) { 47 | case SORT_BY_LAST_CHANGED: 48 | return "last-changed"; 49 | case SORT_BY_LAST_USED: 50 | return "last-used"; 51 | case SORT_BY_NAME: 52 | default: 53 | return "name"; 54 | } 55 | }; 56 | 57 | export const getSort = (name) => { 58 | switch (name) { 59 | case "last-changed": 60 | return byLastChanged; 61 | case "last-used": 62 | return byLastUsed; 63 | case "name": 64 | default: 65 | return byName; 66 | } 67 | }; 68 | -------------------------------------------------------------------------------- /src/locales/README.md: -------------------------------------------------------------------------------- 1 | # Adding Locales 2 | 3 | To add a locale, just create a new directory with the name of your locale and 4 | add the necessary translations to it. For development builds, this locale will 5 | automatically be included. However, for production builds, you should **add your 6 | locale to locales.json**. This allows you to gradually work on adding 7 | translations to your locale without having to have it production-ready 8 | immediately. 9 | -------------------------------------------------------------------------------- /src/locales/cak/widgets.ftl: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | 5 | copy-to-clipboard-button = Tiwachib'ëx 6 | copy-to-clipboard-copied = ✔ Xwachib'ëx 7 | filter-input-clear = 8 | .title = Tijosq'ïx 9 | modal-root = 10 | .contentLabel = Modal ch'owen 11 | password-input-show = 12 | .title = Tik'ut 13 | password-input-hide = 14 | .title = Tewäx 15 | panel-back-button = 16 | .alt = Titzolin 17 | -------------------------------------------------------------------------------- /src/locales/cs/widgets.ftl: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | 5 | copy-to-clipboard-button = Zkopírovat 6 | copy-to-clipboard-copied = ✔ Zkopírováno 7 | filter-input-clear = 8 | .title = Vymazat 9 | modal-root = 10 | .contentLabel = Modální dialog 11 | password-input-show = 12 | .title = Zobrazit 13 | password-input-hide = 14 | .title = Skrýt 15 | panel-back-button = 16 | .alt = Přejít zpět 17 | -------------------------------------------------------------------------------- /src/locales/cy/widgets.ftl: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | 5 | copy-to-clipboard-button = Copïo 6 | copy-to-clipboard-copied = ✔ Copïwyd 7 | filter-input-clear = 8 | .title = Clirio 9 | modal-root = 10 | .contentLabel = Deialog moddol 11 | password-input-show = 12 | .title = Dangos 13 | password-input-hide = 14 | .title = Cuddio 15 | panel-back-button = 16 | .alt = Nôl 17 | -------------------------------------------------------------------------------- /src/locales/de/widgets.ftl: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | 5 | copy-to-clipboard-button = Kopieren 6 | copy-to-clipboard-copied = ✔ Kopiert 7 | filter-input-clear = 8 | .title = Leeren 9 | modal-root = 10 | .contentLabel = Modaler Dialog 11 | password-input-show = 12 | .title = Anzeigen 13 | password-input-hide = 14 | .title = Ausblenden 15 | panel-back-button = 16 | .alt = Zurück 17 | -------------------------------------------------------------------------------- /src/locales/el/widgets.ftl: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | 5 | copy-to-clipboard-button = Αντιγραφή 6 | copy-to-clipboard-copied = ✔ Αντιγράφηκε 7 | filter-input-clear = 8 | .title = Απαλοιφή 9 | modal-root = 10 | .contentLabel = Πλαίσιο διαλόγου 11 | password-input-show = 12 | .title = Εμφάνιση 13 | password-input-hide = 14 | .title = Απόκρυψη 15 | panel-back-button = 16 | .alt = Επιστροφή 17 | -------------------------------------------------------------------------------- /src/locales/en-CA/widgets.ftl: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | 5 | copy-to-clipboard-button = Copy 6 | copy-to-clipboard-copied = ✔ Copied 7 | filter-input-clear = 8 | .title = Clear 9 | modal-root = 10 | .contentLabel = Modal dialog 11 | password-input-show = 12 | .title = Show 13 | password-input-hide = 14 | .title = Hide 15 | panel-back-button = 16 | .alt = Go Back 17 | -------------------------------------------------------------------------------- /src/locales/en-US/widgets.ftl: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | 5 | copy-to-clipboard-button = Copy 6 | copy-to-clipboard-copied = ✔ Copied 7 | 8 | filter-input-clear = 9 | .title = Clear 10 | 11 | modal-root = 12 | .contentLabel = Modal dialog 13 | 14 | password-input-show = 15 | .title = Show 16 | password-input-hide = 17 | .title = Hide 18 | 19 | panel-back-button = 20 | .alt = Go Back 21 | -------------------------------------------------------------------------------- /src/locales/fr/widgets.ftl: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | 5 | copy-to-clipboard-button = Copier 6 | copy-to-clipboard-copied = ✔ Copié 7 | filter-input-clear = 8 | .title = Effacer 9 | modal-root = 10 | .contentLabel = Boîte de dialogue 11 | password-input-show = 12 | .title = Afficher 13 | password-input-hide = 14 | .title = Masquer 15 | panel-back-button = 16 | .alt = Retour 17 | -------------------------------------------------------------------------------- /src/locales/fy-NL/widgets.ftl: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | 5 | copy-to-clipboard-button = Kopiearje 6 | copy-to-clipboard-copied = ✔ Kopiearre 7 | filter-input-clear = 8 | .title = Wiskje 9 | modal-root = 10 | .contentLabel = Modaal dialoochfinster 11 | password-input-show = 12 | .title = Toane 13 | password-input-hide = 14 | .title = Ferstopje 15 | panel-back-button = 16 | .alt = Tebek 17 | -------------------------------------------------------------------------------- /src/locales/hu/widgets.ftl: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | 5 | copy-to-clipboard-button = Másolás 6 | copy-to-clipboard-copied = ✔ Másolva 7 | filter-input-clear = 8 | .title = Törlés 9 | modal-root = 10 | .contentLabel = Modális párbeszédablak 11 | password-input-show = 12 | .title = Megjelenítés 13 | password-input-hide = 14 | .title = Elrejtés 15 | panel-back-button = 16 | .alt = Ugrás vissza 17 | -------------------------------------------------------------------------------- /src/locales/ia/widgets.ftl: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | 5 | copy-to-clipboard-button = Copiar 6 | copy-to-clipboard-copied = ✔ Copiate 7 | filter-input-clear = 8 | .title = Clarar 9 | password-input-show = 10 | .title = Monstrar 11 | password-input-hide = 12 | .title = Celar 13 | panel-back-button = 14 | .alt = Regreder 15 | -------------------------------------------------------------------------------- /src/locales/id/widgets.ftl: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | 5 | copy-to-clipboard-button = Salin 6 | copy-to-clipboard-copied = ✔ Disalin 7 | filter-input-clear = 8 | .title = Bersihkan 9 | modal-root = 10 | .contentLabel = Dialog modal 11 | password-input-show = 12 | .title = Tampilkan 13 | password-input-hide = 14 | .title = Sembunyikan 15 | panel-back-button = 16 | .alt = Kembali 17 | -------------------------------------------------------------------------------- /src/locales/it/widgets.ftl: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | 5 | copy-to-clipboard-button = Copia 6 | copy-to-clipboard-copied = ✔ Copiato 7 | filter-input-clear = 8 | .title = Cancella 9 | modal-root = 10 | .contentLabel = Finestra di dialogo modale 11 | password-input-show = 12 | .title = Mostra 13 | password-input-hide = 14 | .title = Nascondi 15 | panel-back-button = 16 | .alt = Torna indietro 17 | -------------------------------------------------------------------------------- /src/locales/kab/widgets.ftl: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | 5 | copy-to-clipboard-button = Nɣel 6 | copy-to-clipboard-copied = Yenɣel 7 | filter-input-clear = 8 | .title = Sfeḍ 9 | modal-root = 10 | .contentLabel = Tanaka n udiwenni 11 | password-input-show = 12 | .title = Sken 13 | password-input-hide = 14 | .title = Ffer 15 | panel-back-button = 16 | .alt = Uɣal 17 | -------------------------------------------------------------------------------- /src/locales/locales.json: -------------------------------------------------------------------------------- 1 | ["en-US", "cak", "cs", "cy", "de", "en-CA", "fr", "fy-NL", "hu", "id", "it", "kab", "nl", "nn-NO", "pt-BR", "ro", "sv-SE", "vi", "zh-CN", "zh-TW"] 2 | -------------------------------------------------------------------------------- /src/locales/nl/widgets.ftl: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | 5 | copy-to-clipboard-button = Kopiëren 6 | copy-to-clipboard-copied = ✔ Gekopieerd 7 | filter-input-clear = 8 | .title = Wissen 9 | modal-root = 10 | .contentLabel = Modaal dialoogvenster 11 | password-input-show = 12 | .title = Tonen 13 | password-input-hide = 14 | .title = Verbergen 15 | panel-back-button = 16 | .alt = Terug 17 | -------------------------------------------------------------------------------- /src/locales/nn-NO/widgets.ftl: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | 5 | copy-to-clipboard-button = Kopier 6 | copy-to-clipboard-copied = ✔ Kopiert 7 | filter-input-clear = 8 | .title = Tøm 9 | modal-root = 10 | .contentLabel = Modal dialog 11 | password-input-show = 12 | .title = Vis 13 | password-input-hide = 14 | .title = Gøym 15 | panel-back-button = 16 | .alt = Gå tilbake 17 | -------------------------------------------------------------------------------- /src/locales/pt-BR/widgets.ftl: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | 5 | copy-to-clipboard-button = Copiar 6 | copy-to-clipboard-copied = ✔ Copiado 7 | filter-input-clear = 8 | .title = Limpar 9 | modal-root = 10 | .contentLabel = Diálogo modal 11 | password-input-show = 12 | .title = Mostrar 13 | password-input-hide = 14 | .title = Esconder 15 | panel-back-button = 16 | .alt = Voltar 17 | -------------------------------------------------------------------------------- /src/locales/ro/widgets.ftl: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | 5 | copy-to-clipboard-button = Copiază 6 | copy-to-clipboard-copied = ✔ Copiat 7 | filter-input-clear = 8 | .title = Șterge 9 | modal-root = 10 | .contentLabel = Fereastră de dialog modal 11 | password-input-show = 12 | .title = Afișează 13 | password-input-hide = 14 | .title = Ascunde 15 | panel-back-button = 16 | .alt = Înapoi 17 | -------------------------------------------------------------------------------- /src/locales/sl/widgets.ftl: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | 5 | copy-to-clipboard-button = Kopiraj 6 | copy-to-clipboard-copied = ✔ Kopirano 7 | filter-input-clear = 8 | .title = Počisti 9 | modal-root = 10 | .contentLabel = Modalno pogovorno okno 11 | password-input-show = 12 | .title = Prikaži 13 | password-input-hide = 14 | .title = Skrij 15 | panel-back-button = 16 | .alt = Nazaj 17 | -------------------------------------------------------------------------------- /src/locales/sv-SE/widgets.ftl: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | 5 | copy-to-clipboard-button = Kopiera 6 | copy-to-clipboard-copied = ✔ Kopierad 7 | filter-input-clear = 8 | .title = Rensa 9 | modal-root = 10 | .contentLabel = Modal dialog 11 | password-input-show = 12 | .title = Visa 13 | password-input-hide = 14 | .title = Dölj 15 | panel-back-button = 16 | .alt = Gå tillbaka 17 | -------------------------------------------------------------------------------- /src/locales/vi/widgets.ftl: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | 5 | copy-to-clipboard-button = Sao chép 6 | copy-to-clipboard-copied = ✔ Đã sao chép 7 | filter-input-clear = 8 | .title = Xóa 9 | modal-root = 10 | .contentLabel = Hộp thoại 11 | password-input-show = 12 | .title = Hiện 13 | password-input-hide = 14 | .title = Ẩn 15 | panel-back-button = 16 | .alt = Quay lại 17 | -------------------------------------------------------------------------------- /src/locales/zh-CN/widgets.ftl: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | 5 | copy-to-clipboard-button = 复制 6 | copy-to-clipboard-copied = ✔ 已复制 7 | filter-input-clear = 8 | .title = 清除 9 | modal-root = 10 | .contentLabel = 对话框 11 | password-input-show = 12 | .title = 显示 13 | password-input-hide = 14 | .title = 隐藏 15 | panel-back-button = 16 | .alt = 返回 17 | -------------------------------------------------------------------------------- /src/locales/zh-TW/widgets.ftl: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | 5 | copy-to-clipboard-button = 複製 6 | copy-to-clipboard-copied = 已複製 ✔ 7 | filter-input-clear = 8 | .title = 清除 9 | modal-root = 10 | .contentLabel = 對話框 11 | password-input-show = 12 | .title = 顯示 13 | password-input-hide = 14 | .title = 隱藏 15 | panel-back-button = 16 | .alt = 返回 17 | -------------------------------------------------------------------------------- /src/manifest.json.tpl: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "name": "{{title}}", 4 | "short_name": "{{name}}", 5 | "version": "{{version}}", 6 | "description": "{{description}}", 7 | "icons": { 8 | "48": "icons/icon-lockwise.svg", 9 | "96": "icons/icon-lockwise.svg" 10 | }, 11 | 12 | 13 | "content_security_policy": "script-src 'self' {{testing_csp_scripts}} ; object-src 'self' {{testing_csp_objects}}", 14 | 15 | "browser_specific_settings": { 16 | "gecko": { 17 | "id": "{{id}}", 18 | "strict_min_version": "67.0", 19 | "strict_max_version": "69.*", 20 | "update_url": "https://lockwise.firefox.com/addon/updates.json" 21 | } 22 | }, 23 | 24 | "background": { 25 | "scripts": ["background.js"] 26 | }, 27 | 28 | "browser_action": { 29 | "default_icon": { 30 | "32": "icons/icon-toolbar.svg" 31 | }, 32 | "browser_style": false 33 | }, 34 | 35 | "commands": { 36 | "_execute_browser_action": { 37 | "suggested_key": { 38 | "default": "Ctrl+Shift+L" 39 | } 40 | } 41 | }, 42 | 43 | "experiment_apis": { 44 | "logins": { 45 | "schema": "experiments/logins/schema.json", 46 | "parent": { 47 | "scopes": ["addon_parent"], 48 | "script": "experiments/logins/api.js", 49 | "paths": [["experiments", "logins"]] 50 | } 51 | }, 52 | "sync": { 53 | "schema": "experiments/sync/schema.json", 54 | "parent": { 55 | "scopes": ["addon_parent"], 56 | "script": "experiments/sync/api.js", 57 | "paths": [["experiments", "sync"]] 58 | } 59 | }, 60 | "temptelemetry": { 61 | "schema": "experiments/temptelemetry/schema.json", 62 | "parent": { 63 | "scopes": ["addon_parent"], 64 | "script": "experiments/temptelemetry/api.js", 65 | "paths": [["experiments", "temptelemetry"]] 66 | } 67 | } 68 | }, 69 | 70 | "permissions": [ 71 | "tabs", 72 | "clipboardRead", 73 | "clipboardWrite", 74 | "mozillaAddons", 75 | "storage", 76 | "telemetry" 77 | ] 78 | } 79 | -------------------------------------------------------------------------------- /src/telemetry.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | export async function recordEvent({ method, object, extra = null, value = null}) { 6 | return browser.runtime.sendMessage({ 7 | type: "telemetry_event", 8 | data: { method, object, extra, value }, 9 | }); 10 | } 11 | 12 | export async function scalarAdd({ name, value }) { 13 | return browser.runtime.sendMessage({ 14 | type: "telemetry_add", 15 | data: { name, value }, 16 | }); 17 | } 18 | 19 | -------------------------------------------------------------------------------- /src/template.ejs: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | <% for (let css of htmlWebpackPlugin.files.css) { %> 9 | 10 | <% } for (let js of htmlWebpackPlugin.files.js) { %> 11 | 12 | <% } %> 13 | 14 | 15 | 16 |
17 | 18 | 19 | -------------------------------------------------------------------------------- /src/widgets/README.md: -------------------------------------------------------------------------------- 1 | # Common Widgets 2 | 3 | This directory contains the common widgets used throughout the extension. If you need a general-purpose UI widget, it should probably go in here. 4 | 5 | ## Visuals 6 | 7 | All the widgets here are designed to follow the [Photon Design System][photon], though we occasionally make modifications to better suit our needs. 8 | 9 | ## APIs 10 | 11 | Widgets should be designed as React components (no Redux, since that would add extra requirements to how a view's Redux store is designed), and should generally contain a few common options/methods to allow users of the widget to work with it as needed. 12 | 13 | ### Custom classNames 14 | 15 | Widgets should have a `className` argument in their constructor that allows users to customize the styling of the widget for a particular instance. 16 | 17 | ### focus() method 18 | 19 | Focusable widgets (buttons, form elements, links, etc) should have a `focus()` method that focuses the relevant HTML element. If the widget also contains selectable text, `focus()` should take an optional boolean value (defaulting to `false`) to focus *and* select the text. 20 | 21 | ### Extra props 22 | 23 | Finally, widgets should accept extra props in their constructor and apply them to the most-relevant subcomponent. This allows users to apply things like ARIA labels and other props to the widget. 24 | 25 | [photon]: https://design.firefox.com/photon/welcome.html 26 | -------------------------------------------------------------------------------- /src/widgets/banner.css: -------------------------------------------------------------------------------- 1 | /** 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | .banner { 8 | border: solid 1px #d7d7db; 9 | background-color: #ffffff; 10 | padding: 24px 20px; 11 | } 12 | -------------------------------------------------------------------------------- /src/widgets/banner.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This Source Code Form is subject to the terms of the Mozilla Public 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | */ 6 | 7 | import PropTypes from "prop-types"; 8 | import React from "react"; 9 | 10 | import styles from "./banner.css"; 11 | import { classNames } from "../common"; 12 | 13 | // TODO: add "close banner" hooks (https://github.com/mozilla-lockbox/lockbox-addon/issues/95) 14 | 15 | export default function Banner({ className, children }) { 16 | const fullClassName = classNames([ styles.banner, className ]); 17 | 18 | return
{children}
; 19 | } 20 | 21 | Banner.propTypes = { 22 | className: PropTypes.string, 23 | children: PropTypes.node, 24 | }; 25 | -------------------------------------------------------------------------------- /src/widgets/breadcrumbs.css: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | .breadcrumbs { 6 | font-size: 15px; 7 | } 8 | 9 | .crumb { 10 | font-size: 15px; 11 | } 12 | 13 | .crumb:not(:first-child)::before { 14 | content: ""; 15 | display: inline-block; 16 | width: 5px; 17 | height: 9px; 18 | background-image: url(/icons/chevron-right.svg); 19 | background-repeat: no-repeat; 20 | background-size: 5px 9px; 21 | margin: 0 16px; 22 | } 23 | -------------------------------------------------------------------------------- /src/widgets/breadcrumbs.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | import PropTypes from "prop-types"; 6 | import React from "react"; 7 | 8 | import { classNames } from "../common"; 9 | import { Link } from "./link"; 10 | 11 | import styles from "./breadcrumbs.css"; 12 | 13 | export default function Breadcrumbs({className, ...props}) { 14 | return ( 15 |
16 | ); 17 | } 18 | 19 | Breadcrumbs.propTypes = { 20 | className: PropTypes.string, 21 | }; 22 | 23 | Breadcrumbs.defaultProps = { 24 | className: "", 25 | }; 26 | 27 | export function Crumb({className, onClick, ...props}) { 28 | const finalClassName = classNames([styles.crumb, className]); 29 | if (onClick) { 30 | return ( 31 | 32 | ); 33 | } 34 | 35 | return ( 36 | 37 | ); 38 | } 39 | 40 | Crumb.propTypes = { 41 | className: PropTypes.string, 42 | onClick: PropTypes.func, 43 | }; 44 | 45 | Crumb.defaultProps = { 46 | className: "", 47 | }; 48 | -------------------------------------------------------------------------------- /src/widgets/button.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | import PropTypes from "prop-types"; 6 | import React from "react"; 7 | 8 | import { classNames } from "../common"; 9 | 10 | import styles from "./button.css"; 11 | 12 | const THEME_CLASS_NAME = { 13 | primary: `${styles.primaryTheme}`, 14 | normal: `${styles.normalTheme}`, 15 | ghost: `${styles.ghostTheme}`, 16 | danger: `${styles.dangerTheme}`, 17 | }; 18 | 19 | const SIZE_CLASS_NAME = { 20 | puffy: `${styles.puffySize}`, 21 | normal: `${styles.normalSize}`, 22 | micro: `${styles.microSize}`, 23 | wide: `${styles.normalSize} ${styles.wideSize}`, 24 | }; 25 | 26 | export default class Button extends React.Component { 27 | static get propTypes() { 28 | return { 29 | theme: PropTypes.oneOf(Object.keys(THEME_CLASS_NAME)), 30 | size: PropTypes.oneOf(Object.keys(SIZE_CLASS_NAME)), 31 | id: PropTypes.string, 32 | className: PropTypes.string, 33 | }; 34 | } 35 | 36 | static get defaultProps() { 37 | return { 38 | theme: "normal", 39 | size: "normal", 40 | id: null, 41 | className: "", 42 | }; 43 | } 44 | 45 | focus() { 46 | this.buttonElement.focus(); 47 | } 48 | 49 | render() { 50 | const {id, className, theme, size, ...props} = this.props; 51 | return ( 52 | 45 | ); 46 | } 47 | } 48 | 49 | // XXX: External links go to a real URL, and we should probably indicate that to 50 | // the user (by Firefox showing the URL in the bottom of the window), even if 51 | // the actual loading of the URL happens in a Redux action. 52 | 53 | export class ExternalLink extends Link { 54 | get baseClassName() { 55 | return classNames([styles.link, styles.external]); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/widgets/modal-root.css: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | .modal { 6 | border-radius: 4px; 7 | box-shadow: 0 4px 16px 0 rgba(12, 12, 13, 0.1); 8 | min-width: 300px; 9 | color: #0c0c0d; 10 | background-color: #f9f9fa; 11 | } 12 | 13 | .overlay { 14 | z-index: 100; 15 | position: fixed; 16 | top: 0; 17 | bottom: 0; 18 | left: 0; 19 | right: 0; 20 | display: flex; 21 | justify-content: center; 22 | align-items: center; 23 | background-color: rgba(0, 0, 0, 0.5); 24 | } 25 | -------------------------------------------------------------------------------- /src/widgets/modal-root.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | import { Localized } from "fluent-react"; 6 | import PropTypes from "prop-types"; 7 | import React from "react"; 8 | import Modal from "react-modal"; 9 | 10 | import styles from "./modal-root.css"; 11 | 12 | export default function ModalRoot({modals, modalId, modalProps, onClose}) { 13 | let modal = null; 14 | if (modalId) { 15 | const CurrentModal = modals[modalId]; 16 | modal = ; 17 | } 18 | 19 | return ( 20 | 21 | 23 | {modal} 24 | 25 | 26 | ); 27 | } 28 | 29 | ModalRoot.propTypes = { 30 | modals: PropTypes.objectOf(PropTypes.func).isRequired, 31 | modalId: PropTypes.string, 32 | modalProps: PropTypes.object, 33 | onClose: PropTypes.func.isRequired, 34 | }; 35 | -------------------------------------------------------------------------------- /src/widgets/password-text.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | import { Localized } from "fluent-react"; 6 | import PropTypes from "prop-types"; 7 | import React from "react"; 8 | 9 | import { classNames } from "../common"; 10 | 11 | import styles from "./input.css"; 12 | 13 | const PASSWORD_DOT = "\u2022"; 14 | 15 | export default class PasswordText extends React.Component { 16 | static get propTypes() { 17 | return { 18 | className: PropTypes.string, 19 | value: PropTypes.string, 20 | showPassword: PropTypes.bool, 21 | onReveal: PropTypes.func.isRequired, 22 | }; 23 | } 24 | 25 | static get defaultProps() { 26 | return { 27 | className: "", 28 | value: "", 29 | }; 30 | } 31 | 32 | constructor(props) { 33 | super(props); 34 | } 35 | 36 | render() { 37 | const {className, value, showPassword, onReveal, ...props} = this.props; 38 | 39 | return ( 40 |
45 | {showPassword ? ( 46 | 47 | {value} 48 | 49 |
63 | ); 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/widgets/scrolling-list.css: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | .scrolling-list { 6 | margin: 0; 7 | padding: 0; 8 | list-style: none; 9 | overflow-x: hidden; 10 | overflow-y: auto; 11 | } 12 | 13 | .scrolling-list:-moz-focusring { 14 | outline-style: none; 15 | } 16 | 17 | .styled-item { 18 | color: #0c0c0d; 19 | background-color: #f9f9fa; 20 | } 21 | 22 | .scrolling-list > .styled-item[data-selected] { 23 | background-color: #b1b1b3; 24 | } 25 | 26 | .scrolling-list:focus > .styled-item[data-selected] { 27 | color: #ffffff; 28 | background-color: #0060df; 29 | } 30 | -------------------------------------------------------------------------------- /src/widgets/stack.css: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | /* XXX: Sadly, I can't come up with a better way to make something that works 6 | like a than by just using XUL's box model... */ 7 | 8 | .stack { 9 | display: -moz-stack; 10 | } 11 | 12 | .stretch { 13 | height: 100%; 14 | width: 100%; 15 | } 16 | 17 | .stack-item { 18 | display: -moz-box; 19 | visibility: hidden; 20 | } 21 | 22 | .stack-item[data-selected] { 23 | visibility: visible; 24 | } 25 | 26 | .stack-item > * { 27 | -moz-box-flex: 1; 28 | } 29 | -------------------------------------------------------------------------------- /src/widgets/stack.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | import PropTypes from "prop-types"; 6 | import React from "react"; 7 | 8 | import { classNames } from "../common"; 9 | 10 | import styles from "./stack.css"; 11 | 12 | export default class Stack extends React.Component { 13 | static get propTypes() { 14 | return { 15 | children: PropTypes.node, 16 | selectedIndex: PropTypes.number, 17 | stretch: PropTypes.bool, 18 | }; 19 | } 20 | 21 | static get defaultProps() { 22 | return { 23 | selectedIndex: 0, 24 | stretch: false, 25 | }; 26 | } 27 | 28 | render() { 29 | const {children, selectedIndex, stretch, ...props} = this.props; 30 | return ( 31 |
32 |
33 | {React.Children.map(children, (child, i) => { 34 | const extraProps = {}; 35 | if (i === selectedIndex) { 36 | extraProps["data-selected"] = true; 37 | } 38 | 39 | return ( 40 | 41 | {child} 42 | 43 | ); 44 | })} 45 |
46 |
47 | ); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/widgets/text-area.css: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | .text-area { 6 | padding: 8px; 7 | font: caption; 8 | font-size: 13px; 9 | color: #0c0c0d; 10 | background-color: #ffffff; 11 | border: 1px solid rgba(12, 12, 13, 0.3); 12 | border-radius: 2px; 13 | } 14 | 15 | .text-area:hover { 16 | border-color: rgba(12, 12, 13, 0.5); 17 | } 18 | 19 | .text-area:focus { 20 | border-color: #0a84ff; 21 | /* Note: This is adapted from the Photon button spec, since the input fields 22 | spec doesn't define this. */ 23 | box-shadow: 0 0 0 3px rgba(10, 132, 255, 0.3); 24 | } 25 | -------------------------------------------------------------------------------- /src/widgets/text-area.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | import PropTypes from "prop-types"; 6 | import React from "react"; 7 | 8 | import { classNames } from "../common"; 9 | 10 | import styles from "./text-area.css"; 11 | 12 | export default class TextArea extends React.Component { 13 | static get propTypes() { 14 | return { 15 | className: PropTypes.string, 16 | }; 17 | } 18 | 19 | static get defaultProps() { 20 | return { 21 | className: "", 22 | }; 23 | } 24 | 25 | focus(select = false) { 26 | this.textAreaElement.focus(); 27 | if (select) { 28 | this.textAreaElement.setSelectionRange( 29 | 0, this.textAreaElement.value.length 30 | ); 31 | } 32 | } 33 | 34 | render() { 35 | const {className, ...props} = this.props; 36 | return ( 37 |