├── .babelrc
├── .dir-locals.el
├── .eslintignore
├── .eslintrc.js
├── .github
└── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
├── .gitignore
├── .pyup.yml
├── .travis.yml
├── LICENSE
├── README.md
├── dir-list-webpack-plugin.js
├── 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
├── faqs.md
├── images
│ ├── tour-01.welcome.png
│ ├── tour-02.create-entry.png
│ ├── tour-03.doorhanger-search.png
│ └── tour-04.signup-fxa.png
├── index.md
├── metrics.md
├── release-notes.md
└── user-guide.md
├── jpm-prefs.json
├── json-webpack-plugin.js
├── karma.conf.js
├── mkdocs.yml
├── mocha-webpack.opts
├── package-lock.json
├── package.json
├── requirements
├── flake8.txt
└── tests.txt
├── setup.cfg
├── src
├── bootstrap.js
├── icon.png
├── install.rdf.ejs
├── template.ejs
└── webextension
│ ├── .dir-locals.el
│ ├── background
│ ├── README.md
│ ├── accounts
│ │ ├── configs.json
│ │ └── index.js
│ ├── browser-action.js
│ ├── datastore.js
│ ├── index.js
│ ├── message-ports.js
│ ├── telemetry.js
│ └── views.js
│ ├── common.js
│ ├── firstrun
│ ├── components
│ │ ├── app.css
│ │ ├── app.js
│ │ ├── intro.css
│ │ ├── intro.js
│ │ ├── using.css
│ │ └── using.js
│ └── index.js
│ ├── fonts
│ └── fira-mono-regular.woff
│ ├── icons
│ ├── account.svg
│ ├── arrowhead-left-16.svg
│ ├── chevron-right.svg
│ ├── clear.svg
│ ├── default-avatar.svg
│ ├── external-link.svg
│ ├── hide.svg
│ ├── lb_locked.svg
│ ├── lb_unlocked.svg
│ ├── options.svg
│ ├── search.svg
│ ├── show.svg
│ └── signout.svg
│ ├── images
│ ├── intro-step-1.png
│ ├── intro-step-2.png
│ ├── intro-step-3.png
│ └── nessie_v2.svg
│ ├── l10n.js
│ ├── list
│ ├── README.md
│ ├── actions.js
│ ├── common.js
│ ├── components
│ │ ├── item-fields.css
│ │ ├── item-fields.js
│ │ ├── item-list.css
│ │ ├── item-list.js
│ │ ├── item-summary.css
│ │ └── item-summary.js
│ ├── containers
│ │ └── item-filter.js
│ ├── filter.js
│ ├── manage
│ │ ├── components
│ │ │ ├── account-linked.css
│ │ │ ├── account-linked.js
│ │ │ ├── account-summary.css
│ │ │ ├── account-summary.js
│ │ │ ├── app.css
│ │ │ ├── app.js
│ │ │ ├── edit-item-details.js
│ │ │ ├── home-breadcrumbs.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
│ │ │ ├── link-account.css
│ │ │ └── link-account.js
│ │ ├── containers
│ │ │ ├── account-details.js
│ │ │ ├── add-item.css
│ │ │ ├── add-item.js
│ │ │ ├── all-items.js
│ │ │ ├── current-account-summary.js
│ │ │ ├── current-breadcrumbs.js
│ │ │ ├── current-selection.js
│ │ │ ├── modals.js
│ │ │ ├── open-faq.css
│ │ │ ├── open-faq.js
│ │ │ ├── send-feedback.css
│ │ │ └── send-feedback.js
│ │ ├── index.js
│ │ ├── reducers.js
│ │ └── telemetry.js
│ ├── message-ports.js
│ ├── popup
│ │ ├── components
│ │ │ ├── app.css
│ │ │ ├── app.js
│ │ │ ├── item-details-panel.css
│ │ │ ├── item-details-panel.js
│ │ │ └── item-list-panel.js
│ │ ├── containers
│ │ │ ├── all-items-panel.js
│ │ │ └── current-selection.js
│ │ ├── index.js
│ │ ├── reducers.js
│ │ └── telemetry.js
│ └── reducers.js
│ ├── locales
│ ├── README.md
│ ├── en-US
│ │ ├── common.ftl
│ │ ├── firstrun.ftl
│ │ ├── list.ftl
│ │ ├── settings.ftl
│ │ ├── unlock.ftl
│ │ └── widgets.ftl
│ ├── fr-FR
│ │ ├── common.ftl
│ │ ├── firstrun.ftl
│ │ ├── list.ftl
│ │ ├── settings.ftl
│ │ ├── unlock.ftl
│ │ └── widgets.ftl
│ └── locales.json
│ ├── manifest.json.tpl
│ ├── settings
│ ├── actions.js
│ ├── components
│ │ ├── app.css
│ │ ├── app.js
│ │ └── local-reset.js
│ ├── containers
│ │ └── modals.js
│ ├── index.js
│ └── reducers.js
│ ├── telemetry.js
│ ├── unlock
│ ├── components
│ │ ├── app.css
│ │ └── app.js
│ └── index.js
│ └── widgets
│ ├── README.md
│ ├── 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
│ ├── 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.json
├── integration
│ ├── README.md
│ ├── conftest.py
│ ├── pages
│ │ ├── __init__.py
│ │ ├── base.py
│ │ ├── door_hanger.py
│ │ ├── home.py
│ │ ├── login.py
│ │ └── util
│ │ │ ├── __init__.py
│ │ │ └── util.py
│ └── tests
│ │ ├── __init__.py
│ │ ├── test_accessibility.py
│ │ ├── test_account_creation.py
│ │ ├── test_doorhanger.py
│ │ └── test_guest_mode.py
└── unit
│ ├── background
│ ├── accounts-test.js
│ ├── browser-action-test.js
│ ├── message-ports-test.js
│ ├── telemetry-test.js
│ └── views-test.js
│ ├── bootstrap-test.js
│ ├── chai-focus.js
│ ├── common.js
│ ├── enzyme.js
│ ├── firstrun
│ └── components
│ │ ├── app-test.js
│ │ ├── intro-test.js
│ │ └── using-test.js
│ ├── index.js
│ ├── l10n-test.js
│ ├── list
│ ├── actions-test.js
│ ├── components
│ │ ├── item-list-test.js
│ │ └── item-summary-test.js
│ ├── containers
│ │ └── item-filter-test.js
│ ├── filter-test.js
│ ├── manage
│ │ ├── components
│ │ │ ├── account-linked-test.js
│ │ │ ├── account-summary-test.js
│ │ │ ├── app-test.js
│ │ │ ├── edit-item-details-test.js
│ │ │ ├── home-breadcrumbs-test.js
│ │ │ ├── homepage-test.js
│ │ │ ├── item-details-test.js
│ │ │ ├── item-list-panel-test.js
│ │ │ └── link-account-test.js
│ │ ├── containers
│ │ │ ├── account-details-test.js
│ │ │ ├── add-item-test.js
│ │ │ ├── all-items-test.js
│ │ │ ├── current-account-summary-test.js
│ │ │ ├── current-breadcrumbs-test.js
│ │ │ ├── current-selection-test.js
│ │ │ ├── modals-test.js
│ │ │ ├── open-faq-test.js
│ │ │ └── send-feedback-test.js
│ │ ├── mock-redux-state.js
│ │ └── reducers-test.js
│ ├── message-ports-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
│ │ └── mock-redux-state.js
│ └── reducers-test.js
│ ├── mocks
│ ├── browser.js
│ ├── l10n.js
│ └── xpcom.js
│ ├── settings
│ ├── actions-test.js
│ ├── components
│ │ ├── app-test.js
│ │ └── local-reset-test.js
│ ├── containers
│ │ └── modals-test.js
│ ├── mock-redux-state.js
│ └── reducers-test.js
│ ├── telemetry-test.js
│ ├── unlock
│ └── components
│ │ └── app-test.js
│ └── widgets
│ ├── 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
├── tox.ini
├── webpack.config.babel.js
└── webpack.config.test.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "@babel/preset-react",
4 | ["@babel/preset-env", {
5 | "targets": {"browsers": ["firefox >= 56"]},
6 | "useBuiltins": true,
7 | "modules": false
8 | }]
9 | ],
10 | "plugins": [
11 | "@babel/plugin-syntax-object-rest-spread",
12 | "@babel/plugin-syntax-async-generators"
13 | ],
14 | "env": {
15 | "production": {
16 | "presets": [
17 | "minify"
18 | ]
19 | },
20 | "test": {
21 | "plugins": [
22 | "istanbul",
23 | "rewire"
24 | ]
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/.dir-locals.el:
--------------------------------------------------------------------------------
1 | ((markdown-mode
2 | (mode . visual-line)))
3 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | !.eslintrc.js
2 | coverage
3 | dist
4 | node_modules
5 | site
6 | temp
7 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | let extraPlugins = [];
2 | if (process.env.STRICT_LINT !== "1") {
3 | extraPlugins.push("only-warn");
4 | }
5 |
6 | module.exports = {
7 | "parserOptions": {
8 | "ecmaVersion": 2017,
9 | "sourceType": "module",
10 | "ecmaFeatures": {
11 | "jsx": true,
12 | "experimentalObjectRestSpread": true,
13 | },
14 | },
15 | "extends": [
16 | "eslint:recommended",
17 | "plugin:mozilla/recommended",
18 | "plugin:react/recommended",
19 | ],
20 | "env": {
21 | "browser": true,
22 | "node": true,
23 | "webextensions": true,
24 | "es6": true,
25 | },
26 | "plugins": [
27 | "mozilla",
28 | "react",
29 | ...extraPlugins,
30 | ],
31 | "rules": {
32 | "comma-dangle": ["error", "always-multiline"],
33 | "curly": "error",
34 | "no-console": "warn",
35 | "semi": "error",
36 | "mozilla/no-define-cc-etc": "off",
37 | "mozilla/use-chromeutils-import": "off",
38 | },
39 | };
40 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a bug report to help us improve
4 |
5 | ---
6 |
7 | *Please note: this project is not currently or actively planning to fix non-critical (data loss, security related) bugs. This is an experimental prototype.*
8 |
9 | **Describe the bug**
10 | A clear and concise description of what the bug is.
11 |
12 | **To Reproduce**
13 | Steps to reproduce the behavior:
14 | 1. Go to '...'
15 | 2. Click on '....'
16 | 3. Scroll down to '....'
17 | 4. See error
18 |
19 | **Expected behavior**
20 | A clear and concise description of what you expected to happen.
21 |
22 | **Screenshots**
23 | If applicable, add screenshots to help explain your problem.
24 |
25 | **Desktop (please complete the following information):**
26 | - OS: [e.g. iOS]
27 | - Browser version [e.g. 62, Nightly 63.a.01)
28 | - Version [e.g. 22]
29 |
30 | **Additional context**
31 | Add any other context about the problem here.
32 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 |
5 | ---
6 |
7 | *Please note: your idea may have already been considered and you may find it in an issue by searching this repository. Also, this project is not currently or actively planning to implement new features as it is an experimental prototype.*
8 |
9 | **Is your feature request related to a problem? Please describe.**
10 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
11 |
12 | **Describe the solution you'd like**
13 | A clear and concise description of what you want to happen.
14 |
15 | **Describe alternatives you've considered**
16 | A clear and concise description of any alternative solutions or features you've considered.
17 |
18 | **Additional context**
19 | Add any other context or screenshots about the feature request here.
20 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | __pycache__
2 | /coverage/
3 | /dist/
4 | /docs/api.md
5 | /node_modules/
6 | /site/
7 | /temp/
8 | /*.xpi
9 | /addon.env
10 | .DS_Store
11 | .cache
12 | .tox
13 | *.log
14 | *.pyc
15 | .pytest_cache
16 |
--------------------------------------------------------------------------------
/.pyup.yml:
--------------------------------------------------------------------------------
1 | # see https://pyup.io/docs/configuration/ for all available options
2 |
3 | schedule: every week
4 |
--------------------------------------------------------------------------------
/docs/CODEOWNERS:
--------------------------------------------------------------------------------
1 | # lockbox-extension code owners
2 | # https://help.github.com/articles/about-codeowners/
3 |
4 | # integration tests and related config are maintained by PI
5 | /requirements/ @mozilla-lockbox/product-integrity
6 | /test/integration/ @mozilla-lockbox/product-integrity
7 | /.pyup.ini @mozilla-lockbox/product-integrity
8 | /setup.cfg @mozilla-lockbox/product-integrity
9 | /tox.ini @mozilla-lockbox/product-integrity
10 |
11 | # test plans are maintained by PI
12 | /docs/developer/test-plan* @mozilla-lockbox/product-integrity
13 |
14 | # metrics docs are maintained by Leif
15 | /docs/metrics.md @irrationalagent
16 |
17 | # unit tests and related config are maintained by engineering
18 | /test/unit/ @mozilla-lockbox/desktop-engineering
19 | /test/ @mozilla-lockbox/desktop-engineering
20 |
21 | # source code and all other docs are maintained by engineering
22 | /src/ @mozilla-lockbox/desktop-engineering
23 | /docs/ @mozilla-lockbox/desktop-engineering
24 |
25 | # release notes are maintained by the product owners
26 | /docs/release-notes.md @mozilla-lockbox/product
27 |
28 | # include Flod for l10n string changes
29 | # @todo uncomment below after initial string freeze
30 | #src/webextension/locales/en-US/*.ftl @flodolo
31 |
32 | # otherwise, everything is to be reviewed by the desktop team
33 | * @mozilla-lockbox/desktop-engineering
34 |
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 | # Welcome to Lockbox'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-lockbox.github.io/lockbox-extension/
7 | [repo-docs-link]: https://github.com/mozilla-lockbox/lockbox-extension/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/
4 |
5 | - If you believe that you've found a security vulnerability, please report it by sending email to the addresses: security@mozilla.org and 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/test-plan-telemetry.md:
--------------------------------------------------------------------------------
1 | # Telemetry Test Plan: Lockbox-extension
2 |
3 | Given the importance the Lockbox 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 | 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.
7 |
8 | ## Manual tests
9 | ### New telemetry events
10 | 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.
11 |
12 | ### Regression testing
13 | 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.
14 |
15 | Product Integrity will investigate options for automating these tests into the integration test suite.
16 |
17 | ## Metrics being gathered
18 |
19 | For a comprehensive list of metrics being gathered review the [Lockbox Telemetry Plan](https://github.com/mozilla-lockbox/lockbox-extension/blob/telemetry_leif/docs/metrics.md#list-of-events-currently-recorded).
--------------------------------------------------------------------------------
/docs/images/tour-01.welcome.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla-lockwise/lockbox-extension/c202738f6adbaa43f161fc1f65af1dc012d8fad6/docs/images/tour-01.welcome.png
--------------------------------------------------------------------------------
/docs/images/tour-02.create-entry.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla-lockwise/lockbox-extension/c202738f6adbaa43f161fc1f65af1dc012d8fad6/docs/images/tour-02.create-entry.png
--------------------------------------------------------------------------------
/docs/images/tour-03.doorhanger-search.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla-lockwise/lockbox-extension/c202738f6adbaa43f161fc1f65af1dc012d8fad6/docs/images/tour-03.doorhanger-search.png
--------------------------------------------------------------------------------
/docs/images/tour-04.signup-fxa.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla-lockwise/lockbox-extension/c202738f6adbaa43f161fc1f65af1dc012d8fad6/docs/images/tour-04.signup-fxa.png
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | # Lockbox for desktop
2 |
3 | !!! right "Install the extension"
4 | [Install Lockbox][install-link]{: .button-link }
5 | Have questions about how Lockbox works? [Check out the FAQs][faq-link]
6 |
7 | !!! right "Contribute"
8 | You can also contribute by:
9 |
10 | - Developing code
11 | - Reporting bugs
12 |
13 | [Learn how to get started][contribute-link]
14 |
15 | The Lockbox extension is a simple, stand-alone password manager that works
16 | with Firefox for desktop. It’s the first of several planned experiments
17 | designed to help us test and improve password management and online
18 | security.
19 |
20 | **Lockbox is an experimental prototype and is not currently or actively planning to fix non-critical (data loss, security related) bugs or implement new features.**
21 |
22 | Install it and sign in with your Firefox Account to encrypt your data with
23 | tamper-resistant block cipher technology. Then [share feedback
24 | here][feedback-link].
25 |
26 | ## Get Started
27 |
28 | 1. Install Lockbox, and it will automatically disable Firefox’s password manager.
29 | 
30 |
31 | 2. Create an entry with a website name, URL, username, and password.
32 | 
33 |
34 | 3. Search or browse in the toolbar menu or on the full tab to find the password you need.
35 | 
36 |
37 | 4. Sign up or sign in with a Firefox Account to encrypt your entries.
38 | 
39 |
40 | _This is just one component of the Lockbox product. Please see the [Lockbox
41 | website][website-link] for more documentation and context._
42 |
43 | [install-link]: https://testpilot.firefox.com/files/lockbox@mozilla.com/latest
44 | [faq-link]: /faqs/
45 | [contribute-link]: /contributing/
46 | [website-link]: https://mozilla-lockbox.github.io/
47 | [feedback-link]: https://qsurvey.mozilla.com/s3/Lockbox-Input
48 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/jpm-prefs.json:
--------------------------------------------------------------------------------
1 | {
2 | "toolkit.telemetry.enabled": true,
3 | "toolkit.telemetry.server": "https://127.0.0.1/telemetry-dummy/",
4 | "xpinstall.signatures.required": false,
5 | "extensions.legacy.enabled": true
6 | }
7 |
--------------------------------------------------------------------------------
/json-webpack-plugin.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 | const fs = require("fs");
6 | const path = require("path");
7 | const renderObject = require("json-templater/object");
8 |
9 | module.exports = class JSONWebpackPlugin {
10 | constructor(options) {
11 | this.options = {
12 | template: undefined,
13 | filename: undefined,
14 | data: {},
15 | ...options,
16 | };
17 | }
18 |
19 | apply(compiler) {
20 | compiler.plugin("emit", (compilation, callback) => {
21 | if (!this.options.template) {
22 | compilation.errors.push("JSONWebpackPlugin template undefined");
23 | callback();
24 | return;
25 | }
26 | if (!this.options.filename) {
27 | compilation.errors.push("JSONWebpackPlugin filename undefined");
28 | callback();
29 | return;
30 | }
31 |
32 | const template = path.resolve(compiler.context, this.options.template);
33 | fs.readFile(template, {encoding: "utf8"}, (err, data) => {
34 | if (err) {
35 | compilation.errors.push("JSONWebpackPlugin couldn't read template");
36 | callback();
37 | return;
38 | }
39 |
40 | const rendered = renderObject(data, this.options.data);
41 | compilation.assets[this.options.filename] = {
42 | source() {
43 | return rendered;
44 | },
45 | size() {
46 | return rendered.length;
47 | },
48 | };
49 | callback();
50 | });
51 | });
52 | }
53 | };
54 |
--------------------------------------------------------------------------------
/karma.conf.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 | module.exports = function(config) {
6 | config.set({
7 | browsers: ["FirefoxHeadless"],
8 | customLaunchers: {
9 | FirefoxHeadless: {
10 | base: "Firefox",
11 | flags: ["-headless"],
12 | },
13 | },
14 |
15 | basePath: "",
16 | files: [
17 | "test/unit/index.js",
18 | ],
19 | exclude: [],
20 | preprocessors: {
21 | "test/unit/index.js": ["webpack", "sourcemap"],
22 | },
23 |
24 | frameworks: ["mocha"],
25 | reporters: ["mocha", "coverage"],
26 |
27 | webpack: require("./webpack.config.test.js"),
28 | webpackMiddleware: {
29 | stats: "errors-only",
30 | },
31 |
32 | coverageReporter: {
33 | dir: "coverage/",
34 | reporters: [
35 | { type: "text" },
36 | { type: "lcov" },
37 | { type: "html", subdir: "html" },
38 | ],
39 | },
40 |
41 | port: 9876,
42 | colors: true,
43 | logLevel: config.LOG_INFO,
44 | autoWatch: true,
45 | singleRun: true,
46 | concurrency: Infinity,
47 | });
48 | };
49 |
--------------------------------------------------------------------------------
/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-extension
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 | - 'FAQs': 'faqs.md'
17 | - 'Contribute': 'contributing.md'
18 | - 'Telemetry and Metrics': 'metrics.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 |
--------------------------------------------------------------------------------
/mocha-webpack.opts:
--------------------------------------------------------------------------------
1 | --colors
2 | --recursive
3 | --require ignore-styles
4 | --require babel-core/register
5 | --require jsdom-global/register
6 |
--------------------------------------------------------------------------------
/requirements/flake8.txt:
--------------------------------------------------------------------------------
1 | flake8==3.5.0
2 | flake8-isort==2.5
3 | flake8-docstrings==1.3.0
4 |
--------------------------------------------------------------------------------
/requirements/tests.txt:
--------------------------------------------------------------------------------
1 | fxapom==1.10.2
2 | PyPOM==2.0.0
3 | pytest==3.7.1
4 | pytest-selenium==1.13.0
5 | pytest-xdist==1.22.5
6 | selenium==3.14.0
7 | axe-selenium-python==2.0.4
8 | pytest-axe==1.0.0
9 |
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [isort]
2 |
--------------------------------------------------------------------------------
/src/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla-lockwise/lockbox-extension/c202738f6adbaa43f161fc1f65af1dc012d8fad6/src/icon.png
--------------------------------------------------------------------------------
/src/install.rdf.ejs:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 | <% const package = htmlWebpackPlugin.options.package; %>
6 | <%- package.id %>
7 | 2
8 | true
9 | <%- package.title %> (unmaintained)
10 | <%- package.version %>
11 | Mozilla
12 | <%- package.author %>
13 | <%- package.description %>
14 | <%- package.homepage %>
15 | true
16 | true
17 | https://testpilot.firefox.com/files/<%= package.id %>/updates.json
18 |
19 |
20 | {ec8030f7-c20a-464f-9b0e-13a3a9e97384}
21 | 57.0
22 | 59.0a1
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/src/template.ejs:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 | <% const path = require("path");
9 | const dir = path.dirname(htmlWebpackPlugin.options.filename);
10 | for (let css of htmlWebpackPlugin.files.css) {
11 | %>
12 |
13 | <% } for (let js of htmlWebpackPlugin.files.js) { %>
14 |
15 | <% } %>
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/src/webextension/.dir-locals.el:
--------------------------------------------------------------------------------
1 | ; Use rjsx-mode instead of js2-mode for files in this directory.
2 | ((js2-mode . ((mode . rjsx))))
3 |
--------------------------------------------------------------------------------
/src/webextension/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 | ## `get_account_details`
6 |
7 | Returns the JSON for the user's account. Contains the following fields:
8 |
9 | ```
10 | mode
11 | uid
12 | email
13 | displayName
14 | avatar
15 | ```
16 |
17 | ## `open_view`
18 |
19 | Open the view named `name` in a new tab. Returns an empty object.
20 |
21 | ## `close_view`
22 |
23 | Close the view named `name` if it's open. Returns an empty object.
24 |
25 | ## `initialize`
26 |
27 | Initialize the datastore and open the view named `view`, if specified. Returns an empty object.
28 |
29 | ## `upgrade_account`
30 |
31 | Upgrade a local account to an FxA-connected account.
32 |
33 | ## `reset`
34 |
35 | Reset the local state and re-open the first run page. Returns an empty object.
36 |
37 | ## `signin`
38 |
39 | Sign in to the user's Firefox Account (i.e. unlock the datastore), and open the view named `view`, if specified. Returns an empty object.
40 |
41 | ## `signout`
42 |
43 | Sign out of the user's Firefox Account (i.e. lock the datastore), and close the management view if open. Returns an empty object.
44 |
45 | ## `list_items`
46 |
47 | List all the items in the datastore. Returns an array of summaries of the items in the `items` field.
48 |
49 | ## `add_item`
50 |
51 | Add an item (in the `item` attribute) to the datastore. Returns the updated item in the `item` field with its `id` field filled out.
52 |
53 | ## `update_item`
54 |
55 | Update an existing item (in the `item` attribute) in the datastore. Returns the updated item in the `item` field.
56 |
57 | ## `remove_item`
58 |
59 | Remove the item with ID `id` from the datastore. Returns an empty object.
60 |
61 | ## `get_item`
62 |
63 | Fetches the item with ID `id` from the datastore. Returns the item object in the `item` field.
64 |
65 | ## `proxy_telemetry_event`
66 |
67 | Record a telemetry event with method `method`, object `object`, and extra `extra`.
68 |
--------------------------------------------------------------------------------
/src/webextension/background/accounts/configs.json:
--------------------------------------------------------------------------------
1 | {
2 | "production": {
3 | "client_id": "1b024772203a0849",
4 | "redirect_uri": "https://2aa95473a5115d5f3deb36bb6875cf76f05e4c4d.extensions.allizom.org/",
5 | "authorization_endpoint": "https://oauth.accounts.firefox.com/v1/authorization",
6 | "token_endpoint": "https://oauth.accounts.firefox.com/v1/token",
7 | "userinfo_endpoint": "https://profile.accounts.firefox.com/v1/profile",
8 | "scopes": ["openid", "profile", "https://identity.mozilla.com/apps/lockbox"],
9 | "pkce": true,
10 | "app_keys": true
11 | },
12 | "dev-latest": {
13 | "client_id": "f69b2d16e724b432",
14 | "client_secret": "32052e102892dd07b1885c38887849d8283d1407350ee009b6377b8f542a272c",
15 | "redirect_uri": "https://2aa95473a5115d5f3deb36bb6875cf76f05e4c4d.extensions.allizom.org/",
16 | "authorization_endpoint": "https://oauth-latest.dev.lcip.org/v1/authorization",
17 | "token_endpoint": "https://oauth-latest.dev.lcip.org/v1/token",
18 | "userinfo_endpoint": "https://latest.dev.lcip.org/profile/v1/profile",
19 | "scopes": ["openid", "profile", "https://identity.mozilla.com/apps/lockbox"],
20 | "pkce": false
21 | },
22 | "scoped-keys": {
23 | "client_id": "37fdfa37698f251a",
24 | "redirect_uri": "https://2aa95473a5115d5f3deb36bb6875cf76f05e4c4d.extensions.allizom.org/",
25 | "authorization_endpoint": "https://oauth-latest-keys.dev.lcip.org/v1/authorization",
26 | "token_endpoint": "https://oauth-latest-keys.dev.lcip.org/v1/token",
27 | "userinfo_endpoint": "https://latest-keys.dev.lcip.org/profile/v1/profile",
28 | "scopes": ["openid", "profile", "https://identity.mozilla.com/apps/lockbox"],
29 | "pkce": true,
30 | "app_keys": true
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/webextension/background/datastore.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 DataStore from "lockbox-datastore";
6 | import * as telemetry from "./telemetry";
7 |
8 | let datastore;
9 |
10 | async function recordMetric(method, itemid, fields) {
11 | let extra = {
12 | itemid,
13 | };
14 | if (fields) {
15 | extra = {
16 | ...extra,
17 | fields,
18 | };
19 | }
20 | telemetry.recordEvent(method, "datastore", extra);
21 | }
22 |
23 | export const DEFAULT_APP_KEY = {
24 | "kty": "oct",
25 | "kid": "L9-eBkDrYHdPdXV_ymuzy_u9n3drkQcSw5pskrNl4pg",
26 | "k": "WsTdZ2tjji2W36JN9vk9s2AYsvp8eYy1pBbKPgcSLL4",
27 | };
28 |
29 | export function clearDataStore() {
30 | datastore = undefined;
31 | }
32 |
33 | export default async function openDataStore(cfg = {}) {
34 | if (!datastore) {
35 | datastore = await DataStore.open({
36 | ...cfg,
37 | recordMetric,
38 | });
39 | }
40 | return datastore;
41 | }
42 |
--------------------------------------------------------------------------------
/src/webextension/background/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 openDataStore, { DEFAULT_APP_KEY } from "./datastore";
6 | import { openAccount, GUEST } from "./accounts";
7 | import initializeMessagePorts from "./message-ports";
8 | import updateBrowserAction from "./browser-action";
9 |
10 | openAccount(browser.storage.local).then(async (account) => {
11 | let datastore = await openDataStore({ salt: account.uid });
12 | if (datastore.initialized && account.mode === GUEST) {
13 | try {
14 | await datastore.unlock(DEFAULT_APP_KEY);
15 | } catch (err) {
16 | // eslint-disable-next-line no-console
17 | console.error("datastore is in an inconsistent state.");
18 | }
19 | }
20 |
21 | initializeMessagePorts();
22 | await updateBrowserAction({account, datastore});
23 | });
24 |
--------------------------------------------------------------------------------
/src/webextension/background/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) {
6 | return browser.runtime.sendMessage({
7 | type: "telemetry_event", method, object, extra,
8 | });
9 | }
10 |
11 | export async function setScalar(name, value) {
12 | return browser.runtime.sendMessage({
13 | type: "telemetry_scalar", name, value,
14 | });
15 | }
16 |
--------------------------------------------------------------------------------
/src/webextension/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 | };
12 | }
13 |
14 | export function classNames(classNames) {
15 | return classNames.filter((i) => i).join(" ");
16 | }
17 |
--------------------------------------------------------------------------------
/src/webextension/firstrun/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 | }
14 |
15 | .firstrun {
16 | width: 612px;
17 | margin: 3em auto;
18 | display: flex;
19 | flex-flow: column nowrap;
20 | align-items: center;
21 | }
22 |
23 | .firstrun > img {
24 | width: 300px;
25 | }
26 |
--------------------------------------------------------------------------------
/src/webextension/firstrun/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 Intro from "./intro";
10 | import StartUsing from "./using";
11 |
12 | import styles from "./app.css";
13 |
14 | export default function App() {
15 | const imgSrc = browser.extension.getURL("/images/nessie_v2.svg");
16 |
17 | return (
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | );
28 | }
29 |
--------------------------------------------------------------------------------
/src/webextension/firstrun/components/intro.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 {
6 | display: flex;
7 | flex-flow: column nowrap;
8 | justify-content: center;
9 | align-items: center;
10 | }
11 |
12 | .intro > h1,
13 | .intro > h2,
14 | .intro > h3,
15 | .intro > p {
16 | text-align: center;
17 | line-height: 1.5;
18 | letter-spacing: 0.2px;
19 | }
20 |
21 | .intro > h1 {
22 | font-size: 33px;
23 | font-weight: 300;
24 | margin: 0;
25 | }
26 |
27 | .intro > h2 {
28 | font-size: 17px;
29 | font-weight: 300;
30 | font-style: italic;
31 | margin: 0px;
32 | text-transform: lowercase;
33 | }
34 |
35 | .intro > p {
36 | font-size: 15px;
37 | }
38 |
--------------------------------------------------------------------------------
/src/webextension/firstrun/components/intro.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 |
8 | import styles from "./intro.css";
9 |
10 | export default function Intro() {
11 | return (
12 |
13 |
14 |
wELCOMe
15 |
16 |
17 |
mORe wELCOMe
18 |
19 |
20 |
uNMAINTAINEd dISCLAIMEr
21 |
22 |
23 | );
24 | }
25 |
--------------------------------------------------------------------------------
/src/webextension/firstrun/components/using.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 | .using {
6 | display: flex;
7 | flex-flow: column nowrap;
8 | align-items: center;
9 | }
10 |
11 | .using h1,
12 | .using h2,
13 | .using p {
14 | text-align: center;
15 | }
16 |
17 | .using > .actions {
18 | margin: 0;
19 | display: flex;
20 | flex-flow: column nowrap;
21 | align-items: stretch;
22 | }
23 |
24 | .using > .actions > h2 {
25 | margin: 1em 0 .5em;
26 | font-size: 22px;
27 | font-weight: 300;
28 | line-height: 1.5;
29 | letter-spacing: 0.2px;
30 | }
31 |
32 | .using > .actions > button {
33 | margin: 0;
34 | font-size: 15px;
35 | }
36 |
--------------------------------------------------------------------------------
/src/webextension/firstrun/components/using.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 |
8 | import Button from "../../widgets/button";
9 | import * as telemetry from "../../telemetry";
10 |
11 | import styles from "./using.css";
12 |
13 | export default function StartUsing() {
14 | const doGuest = async () => {
15 | telemetry.recordEvent("click", "welcomeGuest");
16 | browser.runtime.sendMessage({
17 | type: "initialize",
18 | view: "manage",
19 | });
20 | browser.runtime.sendMessage({
21 | type: "close_view",
22 | name: "firstrun",
23 | });
24 | };
25 | const doReturning = async () => {
26 | telemetry.recordEvent("click", "welcomeSignin");
27 | browser.runtime.sendMessage({
28 | type: "upgrade_account",
29 | view: "manage",
30 | });
31 | browser.runtime.sendMessage({
32 | type: "close_view",
33 | name: "firstrun",
34 | });
35 | };
36 |
37 | return (
38 |
39 |
40 |
41 |
gUESt
42 |
43 |
44 |
47 |
48 |
49 |
rETURNINg
50 |
51 |
52 |
53 |
54 |
55 |
56 | );
57 | }
58 |
--------------------------------------------------------------------------------
/src/webextension/firstrun/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 |
8 | import AppLocalizationProvider from "../l10n";
9 | import App from "./components/app";
10 | import * as telemetry from "../telemetry";
11 |
12 | telemetry.recordEvent("render", "firstrun");
13 |
14 | ReactDOM.render(
15 |
17 |
18 | ,
19 | document.getElementById("content")
20 | );
21 |
--------------------------------------------------------------------------------
/src/webextension/fonts/fira-mono-regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla-lockwise/lockbox-extension/c202738f6adbaa43f161fc1f65af1dc012d8fad6/src/webextension/fonts/fira-mono-regular.woff
--------------------------------------------------------------------------------
/src/webextension/icons/account.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/webextension/icons/arrowhead-left-16.svg:
--------------------------------------------------------------------------------
1 |
4 |
7 |
--------------------------------------------------------------------------------
/src/webextension/icons/chevron-right.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/webextension/icons/clear.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/webextension/icons/default-avatar.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/webextension/icons/external-link.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/webextension/icons/hide.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/src/webextension/icons/lb_locked.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/webextension/icons/lb_unlocked.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/webextension/icons/options.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/webextension/icons/search.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/webextension/icons/show.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/src/webextension/icons/signout.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/webextension/images/intro-step-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla-lockwise/lockbox-extension/c202738f6adbaa43f161fc1f65af1dc012d8fad6/src/webextension/images/intro-step-1.png
--------------------------------------------------------------------------------
/src/webextension/images/intro-step-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla-lockwise/lockbox-extension/c202738f6adbaa43f161fc1f65af1dc012d8fad6/src/webextension/images/intro-step-2.png
--------------------------------------------------------------------------------
/src/webextension/images/intro-step-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mozilla-lockwise/lockbox-extension/c202738f6adbaa43f161fc1f65af1dc012d8fad6/src/webextension/images/intro-step-3.png
--------------------------------------------------------------------------------
/src/webextension/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 Lockbox 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/webextension/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 | return {
11 | id,
12 | title: item.title,
13 | origins: item.origin ? [item.origin] : [],
14 | entry: {
15 | kind: "login",
16 | username: item.username,
17 | password: item.password,
18 | notes: item.notes,
19 | },
20 | };
21 | }
22 |
23 | export function flattenItem(item) {
24 | return {
25 | title: item.title,
26 | origin: item.origins[0] || "",
27 | username: item.entry.username,
28 | password: item.entry.password,
29 | notes: item.entry.notes,
30 | };
31 | }
32 |
--------------------------------------------------------------------------------
/src/webextension/list/components/item-fields.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-fields {
6 | display: grid;
7 | grid-template-columns: minmax(200px, 1fr);
8 | max-width: 360px;
9 | }
10 |
11 | .item-fields > label,
12 | .item-fields > .field {
13 | display: contents;
14 | }
15 |
16 | .item-fields textarea {
17 | resize: none;
18 | }
19 |
20 | .input {
21 | max-width: 242px;
22 | }
23 |
24 | .password {
25 | max-width: 258px;
26 | }
27 |
28 | .notes {
29 | min-height: 60px;
30 | }
31 |
32 | .notes-read-only {
33 | min-height: 73px;
34 | }
35 |
36 | .first-label {
37 | margin-top: 0;
38 | }
39 |
40 | .inline-button {
41 | display: flex;
42 | }
43 |
44 | .inline-button > *:first-child {
45 | flex: 1;
46 | }
47 |
--------------------------------------------------------------------------------
/src/webextension/list/components/item-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 | .empty {
6 | text-align: center;
7 | white-space: pre-wrap;
8 | padding: 40px;
9 | overflow: hidden;
10 | overflow-wrap: break-word;
11 | color: #4a4a4f;
12 | font-size: 15px;
13 | line-height: 1.5;
14 | letter-spacing: 0.2px;
15 | }
16 |
17 | .item {
18 | color: #0c0c0d;
19 | border-bottom: 1px solid #d7d7db;
20 | background-color: #ffffff;
21 | }
22 |
23 | .item:first-child {
24 | border-top: 1px solid #d7d7db;
25 | }
26 |
27 | .item .item-summary:hover {
28 | background-color: rgba(10, 132, 255, 0.07);
29 | }
30 |
31 | .item .item-summary:active {
32 | background-color: rgba(10, 132, 255, 0.12);
33 | }
34 |
35 | .item[data-selected] .item-summary {
36 | position: relative;
37 | background-color: rgba(10, 132, 255, 0.12);
38 | box-shadow: 0 -1px 0 rgba(10, 132, 255, 0.4), 0 1px 0 rgba(10, 132, 255, 0.4);
39 | }
40 |
41 | /* Provide borders at the top and bottom of selected items. These are ::before
42 | and ::after pseudoelements so that we can give them their own z-index. This
43 | lets us have other elements (e.g. headers, footers) around the list that are
44 | drawn on top of the list, but to have the selected borders here go on top of
45 | *those*. */
46 |
47 | .item[data-selected] .item-summary::before,
48 | .item[data-selected] .item-summary::after {
49 | content: "";
50 | display: block;
51 | position: absolute;
52 | left: 0;
53 | right: 0;
54 | width: 100%;
55 | height: 1px;
56 | background-color: #99bff2;
57 | z-index: 10;
58 | }
59 |
60 | .item[data-selected] .item-summary::before {
61 | top: -1px;
62 | }
63 |
64 | .item[data-selected] .item-summary::after {
65 | bottom: -1px;
66 | }
67 |
68 | .item-list:focus .item[data-selected] .item-summary::before,
69 | .item-list:focus .item[data-selected] .item-summary::after {
70 | background-color: #0a84ff;
71 | }
72 |
73 | .verbose + .verbose {
74 | margin-top: 4px;
75 | border-top: 1px solid #d7d7db;
76 | }
77 |
--------------------------------------------------------------------------------
/src/webextension/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, verbose, onCopy,
15 | ...props}) {
16 | return (
17 |
21 | {({id, title, username}) => {
22 | return (
23 |
25 | );
26 | }}
27 |
28 | );
29 | }
30 |
31 | ItemList.propTypes = {
32 | items: PropTypes.arrayOf(
33 | PropTypes.shape({
34 | id: PropTypes.string.isRequired,
35 | title: PropTypes.string.isRequired,
36 | username: PropTypes.string.isRequired,
37 | }).isRequired
38 | ).isRequired,
39 | itemClassName: PropTypes.string,
40 | verbose: PropTypes.bool,
41 | onCopy: PropTypes.func,
42 | };
43 |
44 | ItemList.defaultProps = {
45 | itemClassName: "",
46 | verbose: false,
47 | };
48 |
49 | export function ItemListPlaceholder({children}) {
50 | return (
51 |
52 | {children}
53 |
54 | );
55 | }
56 |
57 | ItemListPlaceholder.propTypes = {
58 | children: PropTypes.node,
59 | };
60 |
--------------------------------------------------------------------------------
/src/webextension/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::before {
6 | content: "";
7 | float: right;
8 | position: relative;
9 | width: 5px;
10 | height: 9px;
11 | margin: 32px 20px;
12 | background-image: url(/icons/chevron-right.svg);
13 | background-repeat: no-repeat;
14 | background-size: 5px 9px;
15 | z-index: 2;
16 | }
17 |
18 | .item-summary {
19 | padding: 16px 20px;
20 | font-size: 15px;
21 | }
22 |
23 | .title,
24 | .subtitle {
25 | line-height: 1.5;
26 | letter-spacing: 0.2px;
27 | overflow: hidden;
28 | white-space: nowrap;
29 | text-overflow: ellipsis;
30 | }
31 |
32 | .title {
33 | font-weight: bold;
34 | }
35 |
36 | .subtitle {
37 | font-size: 13px;
38 | font-weight: 400;
39 | }
40 |
41 | .copy-buttons {
42 | border-top: 1px solid #d7d7db;
43 | display: flex;
44 | }
45 |
46 | .copy-button {
47 | flex: 1;
48 | height: 40px;
49 | }
50 |
51 | .copy-button + .copy-button {
52 | border-inline-start: 1px solid #d7d7db;
53 | }
54 |
55 | .copy-button-inner {
56 | border-radius: 0;
57 | }
58 |
59 | .copy-button-inner:focus {
60 | box-shadow: inset 0 0 0 2px #0a84ff;
61 | }
62 |
63 | .copy-button-inner:hover {
64 | background-color: #ededf0;
65 | }
66 |
67 | .copy-button-inner:active:hover {
68 | background-color: #d7d7db;
69 | }
70 |
--------------------------------------------------------------------------------
/src/webextension/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/webextension/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/webextension/list/manage/components/account-linked.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 | .linked {
6 | margin: 2em auto;
7 | padding: 0;
8 | display: flex;
9 | flex-flow: column nowrap;
10 | align-items: center;
11 | }
12 |
13 | .linked > h2,
14 | .linked > p {
15 | text-align: center;
16 | letter-spacing: 0.2px;
17 | line-height: 1.5;
18 | }
19 |
20 | .linked > h2 {
21 | margin: 0 0 .5em;
22 | padding: 0;
23 | font-size: 17px;
24 | font-weight: 600;
25 | line-height: 1.32;
26 | color: #058b00;
27 | }
28 |
29 | .linked > p {
30 | margin-top: 0;
31 | font-size: 15px;
32 | white-space: pre-line;
33 | }
34 |
--------------------------------------------------------------------------------
/src/webextension/list/manage/components/account-linked.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 |
8 | import styles from "./account-linked.css";
9 |
10 | export default function AccountLinked() {
11 | return (
12 |
13 |
14 |
aCCOUNt lINKEd
15 |
16 |
17 |
Praesent commodo cursus magna, vel scelerisque nisl consectetur et.
18 |
19 |
20 | );
21 | }
22 |
--------------------------------------------------------------------------------
/src/webextension/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-main {
24 | display: grid;
25 | height: 100%;
26 | grid-template-columns: 1fr 3fr;
27 | }
28 |
29 | .navigation {
30 | height: 48px;
31 | padding-inline-start: 4.6em;
32 | }
33 |
34 | .navigation > * + * {
35 | margin-inline-start: 40px;
36 | }
37 |
38 | .aside {
39 | grid-column: 1;
40 | min-width: 150px;
41 | min-height: 0;
42 | display: flex;
43 | flex-direction: column;
44 | border-inline-end: 1px solid #d7d7db;
45 | }
46 |
47 | .app-main > article {
48 | grid-column: 2;
49 | background-color: #f9f9fa;
50 | overflow: auto;
51 | }
52 |
--------------------------------------------------------------------------------
/src/webextension/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 AllItems from "../containers/all-items";
10 | import CurrentAccountSummary from "../containers/current-account-summary";
11 | import CurrentBreadcrumbs from "../containers/current-breadcrumbs";
12 | import CurrentSelection from "../containers/current-selection";
13 | import ModalRoot from "../containers/modals";
14 | import OpenFAQ from "../containers/open-faq";
15 | import Toolbar, { ToolbarSpace } from "../../../widgets/toolbar";
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 |
27 |
28 |
29 | );
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/webextension/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 | }
8 |
--------------------------------------------------------------------------------
/src/webextension/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 } from "../../../widgets/panel";
10 | import { ItemFields } from "../../components/item-fields";
11 |
12 | import styles from "./item-details-panel.css";
13 |
14 | export default function ItemDetailsPanel({fields, onCopy, onBack}) {
15 | return (
16 |
17 |
18 |
19 | eNTRy dETAILs
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | );
28 | }
29 |
30 | ItemDetailsPanel.propTypes = {
31 | ...ItemFields.propTypes,
32 | onCopy: PropTypes.func.isRequired,
33 | onBack: PropTypes.func.isRequired,
34 | };
35 |
--------------------------------------------------------------------------------
/src/webextension/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, copiedField } from "../../actions";
9 | import { parseFilterString, filterItem } from "../../filter";
10 | const collator = new Intl.Collator();
11 |
12 | export default connect(
13 | (state, ownProps) => {
14 | const filter = parseFilterString(state.list.filter.query);
15 | const items = state.cache.items
16 | .filter((i) => filterItem(filter, i))
17 | .sort((a, b) => collator.compare(a.title, b.title));
18 | if (items.length || state.list.filter.userEntered) {
19 | return {...ownProps, items, noResultsBanner: false};
20 | }
21 | // An autogenerated filter that produced no results; show everything, plus
22 | // a banner informing users.
23 | return {...ownProps, items: state.cache.items, noResultsBanner: true};
24 | },
25 | (dispatch) => ({
26 | onClick: (id) => { dispatch(selectItem(id)); },
27 | onCopy: (field) => { dispatch(copiedField(field)); },
28 | }),
29 | )(ItemListPanel);
30 |
--------------------------------------------------------------------------------
/src/webextension/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 } from "../../actions";
11 | import AllItemsPanel from "./all-items-panel";
12 | import ItemDetailsPanel from "../components/item-details-panel";
13 |
14 | const ConnectedItemDetailsPanel = connect(
15 | (state, ownProps) => ({
16 | fields: flattenItem(ownProps.item),
17 | }),
18 | (dispatch) => ({
19 | onCopy: (field) => { dispatch(copiedField(field)); },
20 | onBack: () => { dispatch(selectItem(null)); },
21 | })
22 | )(ItemDetailsPanel);
23 |
24 | function CurrentSelection({item, inputRef}) {
25 | if (item) {
26 | return ;
27 | }
28 | return ;
29 | }
30 |
31 | CurrentSelection.propTypes = {
32 | item: PropTypes.object,
33 | inputRef: PropTypes.func,
34 | };
35 |
36 | export default connect(
37 | (state) => ({
38 | item: state.cache.currentItem,
39 | })
40 | )(CurrentSelection);
41 |
--------------------------------------------------------------------------------
/src/webextension/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 } from "../actions";
14 | import reducer from "./reducers";
15 | import initializeMessagePorts from "../message-ports";
16 | import * as telemetry from "../../telemetry";
17 | import telemetryLogger from "./telemetry";
18 |
19 | const store = createStore(reducer, undefined, applyMiddleware(
20 | thunk, telemetryLogger
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 |
36 | telemetry.recordEvent("render", "doorhanger");
37 |
38 | ReactDOM.render(
39 |
40 |
42 |
43 |
44 | ,
45 | document.getElementById("content")
46 | );
47 |
--------------------------------------------------------------------------------
/src/webextension/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 } from "../reducers";
8 |
9 | export default combineReducers({
10 | cache: cacheReducer,
11 | list: listReducer,
12 | });
13 |
--------------------------------------------------------------------------------
/src/webextension/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 * as telemetry from "../../telemetry";
7 |
8 | export default (store) => (next) => (action) => {
9 | try {
10 | switch (action.type) {
11 | case actions.SELECT_ITEM_COMPLETED:
12 | if (action.item) {
13 | telemetry.recordEvent("itemSelected", "doorhanger",
14 | {itemid: action.item.id});
15 | }
16 | break;
17 | case actions.COPIED_FIELD:
18 | telemetry.recordEvent(`${action.field}Copied`, "doorhanger");
19 | break;
20 | }
21 | } catch (e) {
22 | // eslint-disable-next-line no-console
23 | console.error("Unable to record telemetry event", e);
24 | }
25 |
26 | return next(action);
27 | };
28 |
--------------------------------------------------------------------------------
/src/webextension/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/webextension/locales/en-US/common.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 | product-title = Lockbox
6 | product-tagline = The simple way to store, retrieve and manage website login info
7 |
8 | product-disclaimer = This is just a prototype and not officially supported. Any data stored or functionality is not guaranteed to remain.
9 |
10 | # when Kinto is supported, the above should be changed to:
11 | # Creating a Firefox account - or adding Lockbox to an existing account - protects your logins
12 | # with the strongest encryption available and syncs your Lockbox info across accounts.
13 |
14 |
15 | product-action-signin = Sign In
16 | product-action-prefs = Preferences
17 |
--------------------------------------------------------------------------------
/src/webextension/locales/en-US/firstrun.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 | document =
6 | .title = Welcome to Lockbox
7 |
8 | firstrun-intro-title = Welcome to { product-title }
9 | firstrun-intro-tagline = { product-tagline }
10 |
11 | firstrun-using-guest-title = New to Lockbox?
12 |
13 | firstrun-using-guest-action = Get Started
14 |
15 | firstrun-using-returning-title = Returning User?
16 |
17 | firstrun-using-returning-action = Sign in to your Firefox Account
18 |
--------------------------------------------------------------------------------
/src/webextension/locales/en-US/settings.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 | settings-local-reset-title = Reset Lockbox
6 | settings-local-reset-description = This resets Lockbox to its uninitialized state. Once performed, it cannot be undone.
7 | settings-local-reset-button = 💥💣 Reset 💣💥
8 |
9 | modal-local-reset = Are you sure you wish to reset Lockbox?
10 | .confirmLabel = Yes, reset it
11 | .cancelLabel = No, do not reset
12 |
--------------------------------------------------------------------------------
/src/webextension/locales/en-US/unlock.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 | document =
6 | .title = Unlock Lockbox
7 |
8 | unlock-title = { product-title }
9 | unlock-tagline = { product-tagline }
10 |
11 | unlock-action-signin = { product-action-signin }
12 | unlock-action-prefs = { product-action-prefs }
13 |
--------------------------------------------------------------------------------
/src/webextension/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/webextension/locales/fr-FR/common.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 | product-title = Lockbox
6 | product-tagline = Une façon simple de stocker, retrouver et de gérer les infos de connexion de site web
7 |
8 | # Lors ce que Kinto est supporté, le texte ci-dessus devrait être modifié comme suit:
9 | # Créer un compte Firefox - ou ajouter Lockbox à un compte existant - protège vos identifiants
10 | # avec le chiffrage le plus puissant disponible et synchronisations de vos infos Lockbox à travers vos comptes.
11 |
12 |
13 | product-action-signin = S'identifier
14 | product-action-prefs = Réglages
15 |
--------------------------------------------------------------------------------
/src/webextension/locales/fr-FR/firstrun.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 | document
6 | .title = Bienvenue dans Lockbox
7 |
8 | firstrun-intro-title = Bienvenue dans { product-title }
9 | firstrun-intro-tagline = { product-tagline }
10 |
11 | firstrun-using-guest-title = Nouveau dans Lockbox?
12 |
13 | firstrun-using-guest-action = Commençons
14 |
15 | firstrun-using-returning-title = Retour utilisateurs ?
16 |
17 | firstrun-using-returning-action = Connectez-vous à votre compte Firefox
18 |
--------------------------------------------------------------------------------
/src/webextension/locales/fr-FR/settings.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 | settings-local-reset-title = Réinitialiser Lockbox
6 | settings-local-reset-description = Celà va réinitialiser Lockbox à son état initial. Une fois celà fait, il est impossible d'annuler l'action.
7 | settings-local-reset-button = 💥💣 Réinitialisation 💣💥
8 |
9 | modal-local-reset = Êtes-vous sûr de vouloir procéder à une réinitialisation de Lockbox?
10 | .confirmLabel = Oui, le réinitialiser
11 | .cancelLabel = Non, ne pas réinitialiser
12 |
--------------------------------------------------------------------------------
/src/webextension/locales/fr-FR/unlock.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 | document =
6 | .title = Débloquer Lockbox
7 |
8 | unlock-title = { product-title }
9 | unlock-tagline = { product-tagline }
10 |
11 | unlock-action-signin = { product-action-signin }
12 | unlock-action-prefs = { product-action-prefs }
13 |
--------------------------------------------------------------------------------
/src/webextension/locales/fr-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 = Copie
6 | copy-to-clipboard-copied = ✔ Copié
7 |
8 | filter-input-clear =
9 | .title = Éffacer
10 |
11 | modal-root =
12 | .contentLabel = Dialogue modal
13 |
14 | password-input-show =
15 | .title = Afficher
16 | password-input-hide =
17 | .title = Masquer
18 |
19 | panel-back-button =
20 | .alt = Retour arrière
21 |
--------------------------------------------------------------------------------
/src/webextension/locales/locales.json:
--------------------------------------------------------------------------------
1 | ["en-US", "fr-FR"]
2 |
--------------------------------------------------------------------------------
/src/webextension/manifest.json.tpl:
--------------------------------------------------------------------------------
1 | {
2 | "manifest_version": 2,
3 | "name": "{{title}}",
4 | "version": "{{version}}",
5 |
6 | "description": "{{description}}",
7 |
8 | "applications": {
9 | "gecko": {
10 | "id": "{{id}}"
11 | }
12 | },
13 |
14 | "background": {
15 | "scripts": ["background.js"]
16 | },
17 |
18 | "browser_action": {
19 | "default_icon": {
20 | "32": "icons/lb_locked.svg"
21 | },
22 | "default_title": "Lockbox",
23 | "browser_style": false
24 | },
25 |
26 | "commands": {
27 | "_execute_browser_action": {
28 | "suggested_key": {
29 | "default": "Ctrl+Shift+L"
30 | }
31 | }
32 | },
33 |
34 | "permissions": [
35 | "identity",
36 | "storage",
37 | "tabs",
38 | "clipboardWrite"
39 | ],
40 |
41 | "options_ui": {
42 | "page": "settings/index.html",
43 | "browser_style": false
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/webextension/settings/actions.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 telemetry from "../telemetry";
6 |
7 | export const LOCAL_RESET_STARTING = Symbol("LOCAL_RESET_STARTING");
8 | export const LOCAL_RESET_COMPLETED = Symbol("LOCAL_RESET_COMPLETED");
9 |
10 | export const SHOW_MODAL = Symbol("SHOW_MODAL");
11 | export const HIDE_MODAL = Symbol("HIDE_MODAL");
12 |
13 | let nextActionId = 0;
14 |
15 | // Reset actions
16 |
17 | export function localReset() {
18 | return async (dispatch) => {
19 | const actionId = nextActionId++;
20 | dispatch(localResetStarting(actionId));
21 | await browser.runtime.sendMessage({
22 | type: "reset",
23 | });
24 | dispatch(localResetCompleted(actionId));
25 | };
26 | }
27 |
28 | function localResetStarting(actionId) {
29 | return {
30 | type: LOCAL_RESET_STARTING,
31 | actionId,
32 | };
33 | }
34 |
35 | function localResetCompleted(actionId) {
36 | telemetry.recordEvent("resetCompleted", "settings");
37 | return {
38 | type: LOCAL_RESET_COMPLETED,
39 | actionId,
40 | };
41 | }
42 |
43 | export function requestLocalReset() {
44 | telemetry.recordEvent("resetRequested", "settings");
45 | return showModal("local-reset");
46 | }
47 |
48 | // Modal actions
49 |
50 | function showModal(id, props = {}) {
51 | return {
52 | type: SHOW_MODAL,
53 | id,
54 | props,
55 | };
56 | }
57 |
58 | export function hideModal() {
59 | return {
60 | type: HIDE_MODAL,
61 | };
62 | }
63 |
--------------------------------------------------------------------------------
/src/webextension/settings/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 | html {
6 | display: flex;
7 | }
8 |
9 | body {
10 | padding: 0;
11 | margin: 1em;
12 | min-height: 150px;
13 | color: #0c0c0d;
14 | background-color: #f9f9fa;
15 | font: caption;
16 | font-size: 13px;
17 | -moz-user-select: none;
18 | display: flex;
19 | }
20 |
--------------------------------------------------------------------------------
/src/webextension/settings/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 |
8 | import LocalReset from "./local-reset";
9 | import ModalRoot from "../containers/modals";
10 |
11 | import "./app.css";
12 |
13 | export default function App() {
14 | return (
15 |
16 |
17 |
nOTe: tHIs eXTENSION Is nOt mAINTAINEd
18 |
19 |
20 |
21 |
22 | );
23 | }
24 |
--------------------------------------------------------------------------------
/src/webextension/settings/components/local-reset.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 {
11 | requestLocalReset,
12 | } from "../actions";
13 | import Button from "../../widgets/button";
14 |
15 | export function LocalReset({onReset}) {
16 | return (
17 |
18 |
19 |
rESEt lOCKBOx
20 |
21 |
22 |
dO tHe fACTORy rESEt
23 |
24 |
25 |
26 |
27 |
28 | );
29 | }
30 | LocalReset.propTypes = {
31 | onReset: PropTypes.func.isRequired,
32 | };
33 |
34 | export default connect(
35 | (state) => ({
36 | }),
37 | (dispatch) => ({
38 | onReset: () => { dispatch(requestLocalReset()); },
39 | })
40 | )(LocalReset);
41 |
--------------------------------------------------------------------------------
/src/webextension/settings/containers/modals.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 ModalRoot from "../../widgets/modal-root";
8 | import { LocalizedConfirmDialog } from "../../widgets/dialog-box";
9 | import { hideModal, localReset } from "../actions";
10 |
11 | export const LocalResetModal = connect(
12 | (state) => ({
13 | l10nId: "modal-local-reset",
14 | theme: "danger",
15 | }),
16 | (dispatch) => ({
17 | onConfirm: () => { dispatch(localReset()); },
18 | })
19 | )(LocalizedConfirmDialog);
20 |
21 | const MODALS = {
22 | "local-reset": LocalResetModal,
23 | };
24 |
25 | export default connect(
26 | (state) => ({
27 | modals: MODALS,
28 | modalId: state.modal.id,
29 | modalProps: state.modal.props,
30 | }),
31 | (dispatch) => ({
32 | onClose: () => { dispatch(hideModal()); },
33 | })
34 | )(ModalRoot);
35 |
--------------------------------------------------------------------------------
/src/webextension/settings/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 reducer from "./reducers";
14 |
15 | const store = createStore(reducer, undefined, applyMiddleware(thunk));
16 |
17 | ReactDOM.render(
18 |
19 |
21 |
22 |
23 | ,
24 | document.getElementById("content")
25 | );
26 |
--------------------------------------------------------------------------------
/src/webextension/settings/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 | // keep in sync with
6 |
7 | import { combineReducers } from "redux";
8 |
9 | import {
10 | SHOW_MODAL, HIDE_MODAL,
11 | } from "./actions";
12 |
13 | export function modalReducer(state = {id: null, props: null}, action) {
14 | switch (action.type) {
15 | case SHOW_MODAL:
16 | return {...state, id: action.id, props: action.props};
17 | case HIDE_MODAL:
18 | return {...state, id: null, props: null};
19 | default:
20 | return state;
21 | }
22 | }
23 |
24 | const reducer = combineReducers({
25 | modal: modalReducer,
26 | });
27 |
28 | export default reducer;
29 |
--------------------------------------------------------------------------------
/src/webextension/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) {
6 | return browser.runtime.sendMessage({
7 | type: "proxy_telemetry_event", method, object, extra,
8 | });
9 | }
10 |
--------------------------------------------------------------------------------
/src/webextension/unlock/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: 280px;
9 | height: 360px;
10 | color: #0c0c0d;
11 | background-color: #ededf0;
12 | font: caption;
13 | font-size: 13px;
14 | -moz-user-select: none;
15 | }
16 |
17 | body > main {
18 | height: 100%;
19 | }
20 |
21 | .lockie {
22 | display: block;
23 | margin: 4em auto 0;
24 | width: 200px;
25 | }
26 |
27 | .unlock-content {
28 | margin: 1em auto 3em;
29 | width: 240px;
30 | display: flex;
31 | flex-flow: column nowrap;
32 | align-items: center;
33 | }
34 |
35 | .unlock-content > h1 {
36 | font-size: 2.5em;
37 | font-weight: 300;
38 | text-align: center;
39 | margin: 0 0 0.25em;
40 | }
41 |
42 | .unlock-content > h2 {
43 | font-size: 1.15em;
44 | font-weight: 300;
45 | font-style: italic;
46 | text-align: center;
47 | text-transform: lowercase;
48 | margin: 0;
49 | }
50 |
--------------------------------------------------------------------------------
/src/webextension/unlock/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 Panel, { PanelBody, PanelFooter, PanelFooterButton }
10 | from "../../widgets/panel";
11 | import * as telemetry from "../../telemetry";
12 |
13 | import styles from "./app.css";
14 |
15 | export default function App() {
16 | const imgSrc = browser.extension.getURL("/images/nessie_v2.svg");
17 |
18 | const doSignIn = async () => {
19 | telemetry.recordEvent("click", "unlockSignin");
20 | browser.runtime.sendMessage({
21 | type: "signin",
22 | view: "manage",
23 | });
24 | };
25 | const doPrefs = async () => {
26 | browser.runtime.openOptionsPage();
27 | window.close();
28 | };
29 |
30 | return (
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
lOCKBOx
39 |
40 |
41 |
lOCKBOx tAGLINe
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 | sIGn iN
50 |
51 |
52 |
53 |
54 | pREFs
55 |
56 |
57 |
58 |
59 |
60 |
61 | );
62 | }
63 |
--------------------------------------------------------------------------------
/src/webextension/unlock/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 |
8 | import AppLocalizationProvider from "../l10n";
9 | import App from "./components/app";
10 | import * as telemetry from "../telemetry";
11 |
12 | // This is the closest approximation we can get to a toolbar click when the
13 | // popup is registered.
14 | telemetry.recordEvent("render", "popupUnlock");
15 |
16 | ReactDOM.render(
17 |
19 |
20 | ,
21 | document.getElementById("content")
22 | );
23 |
--------------------------------------------------------------------------------
/src/webextension/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/webextension/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/webextension/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/webextension/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 | className: PropTypes.string,
32 | };
33 | }
34 |
35 | static get defaultProps() {
36 | return {
37 | theme: "normal",
38 | size: "normal",
39 | className: "",
40 | };
41 | }
42 |
43 | focus() {
44 | this.buttonElement.focus();
45 | }
46 |
47 | render() {
48 | const {className, theme, size, ...props} = this.props;
49 | return (
50 |