Compare commits
47 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f2683e37b2 | |||
| f6ff9cf4c9 | |||
| 88b954fcd0 | |||
| 59d993ae4a | |||
| d808bffff2 | |||
| 01d048e298 | |||
| 0b47bf93aa | |||
| 53856a8b3b | |||
| 2c70ca2490 | |||
| 2731ccbff2 | |||
| cf99eb3b52 | |||
| 59d57d99a9 | |||
| 16613253ad | |||
| db8c99be0a | |||
| cfe8099ae5 | |||
| 992949f797 | |||
| e7599ea2a1 | |||
| 7e7b6f71a4 | |||
| 3e137f6955 | |||
| 47888e4774 | |||
| 1c378978c1 | |||
| 2e4e49e0db | |||
| 4a11ebbb45 | |||
| 86c4e0498b | |||
| c7869010b6 | |||
| 6f9c13587d | |||
| 5d4088dd1a | |||
| 917884b883 | |||
| 33b3df7433 | |||
| 408d417c3f | |||
| f8a80d4a4c | |||
| 32bcdef7ec | |||
| 68aace475e | |||
| 4f578da227 | |||
| 5c49fc6b97 | |||
| 9ade276977 | |||
| ed9ae670d8 | |||
| 3f38322056 | |||
| 5e4a9f1699 | |||
| 4c2669632d | |||
| 19882e7907 | |||
| 928ec924c0 | |||
| c01773f9ff | |||
| 5831dd3dc5 | |||
| d31bc4090e | |||
| 03d74f93d6 | |||
| 8991884494 |
@@ -0,0 +1,19 @@
|
||||
FROM mcr.microsoft.com/devcontainers/base:focal
|
||||
|
||||
RUN apt update && apt upgrade -y
|
||||
|
||||
RUN apt install -y software-properties-common apt-transport-https ca-certificates curl gnupg
|
||||
|
||||
RUN mkdir -p /etc/apt/keyrings
|
||||
RUN curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
|
||||
|
||||
RUN echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_20.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list
|
||||
RUN apt update && apt install -y nodejs
|
||||
|
||||
RUN corepack enable
|
||||
|
||||
RUN echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google-chrome.list
|
||||
RUN curl -fSsL https://dl.google.com/linux/linux_signing_key.pub | sudo gpg --dearmor | sudo tee /usr/share/keyrings/google-chrome.gpg >> /dev/null
|
||||
RUN echo deb [arch=amd64 signed-by=/usr/share/keyrings/google-chrome.gpg] http://dl.google.com/linux/chrome/deb/ stable main | sudo tee /etc/apt/sources.list.d/google-chrome.list
|
||||
|
||||
RUN apt update && apt install -y google-chrome-stable firefox
|
||||
@@ -0,0 +1,25 @@
|
||||
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
|
||||
// README at: https://github.com/devcontainers/templates/tree/main/src/typescript-node
|
||||
{
|
||||
"name": "PasswordGeneratorExtension",
|
||||
"build": {
|
||||
"dockerfile": "Dockerfile"
|
||||
},
|
||||
|
||||
// Configure tool-specific properties.
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"extensions": [
|
||||
"dbaeumer.vscode-eslint",
|
||||
"github.vscode-github-actions",
|
||||
"GitHub.vscode-pull-request-github",
|
||||
"bierner.github-markdown-preview",
|
||||
"mrmlnc.vscode-scss",
|
||||
"Gruntfuggly.todo-tree",
|
||||
"redhat.vscode-yaml"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
"postCreateCommand": "yarn install"
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
module.exports = {
|
||||
"settings": {
|
||||
"react": {
|
||||
"version": "detect",
|
||||
},
|
||||
},
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es2021": true
|
||||
},
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:react/recommended"
|
||||
],
|
||||
"overrides": [
|
||||
{
|
||||
"env": {
|
||||
"node": true
|
||||
},
|
||||
"files": [
|
||||
".eslintrc.{js,cjs}"
|
||||
],
|
||||
"parserOptions": {
|
||||
"sourceType": "script"
|
||||
}
|
||||
}
|
||||
],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": "latest",
|
||||
"sourceType": "module"
|
||||
},
|
||||
"plugins": [
|
||||
"@typescript-eslint",
|
||||
"react"
|
||||
],
|
||||
"rules": {
|
||||
"indent": [
|
||||
"error",
|
||||
"tab"
|
||||
],
|
||||
"linebreak-style": [
|
||||
"error",
|
||||
"unix"
|
||||
],
|
||||
"quotes": [
|
||||
"error",
|
||||
"double"
|
||||
],
|
||||
"semi": [
|
||||
"error",
|
||||
"always"
|
||||
],
|
||||
"react/react-in-jsx-scope": "off"
|
||||
}
|
||||
};
|
||||
@@ -1,33 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve the extension
|
||||
title: ''
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
### Description
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
### Reproduction steps
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '...'
|
||||
3. Scroll down to '...'
|
||||
4. See error
|
||||
|
||||
### Expected behavior
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
### Screenshots
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
### Environment
|
||||
Please provide the following information:
|
||||
- Operating System: [e.g. Windows 10 Pro 1909 (10.0.18363)]
|
||||
- Browser: [e.g. Microsoft Edge 83.0.478.56]
|
||||
- Extension version: [e.g. 1.5]
|
||||
|
||||
### Additional context
|
||||
Add any other context about the problem here.
|
||||
@@ -0,0 +1,107 @@
|
||||
name: "🐞 Bug Report"
|
||||
description: Create a report to help us improve the extension
|
||||
title: "[Bug]: "
|
||||
labels: ["bug", "needs-triage"]
|
||||
assignees:
|
||||
- xfox111
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to fill out this bug report!
|
||||
|
||||
- type: textarea
|
||||
id: desc
|
||||
attributes:
|
||||
label: Description
|
||||
description: A clear and concise description of what the bug is.
|
||||
placeholder: e.g. Sometimes when generating a password not all character sets are included
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Reproduction steps
|
||||
description: Precisely describe minimal number of steps that make the bug to appear
|
||||
placeholder: |
|
||||
1. Go to '...'
|
||||
2. Click on '...'
|
||||
3. Scroll down to '...'
|
||||
4. See '...'
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Expected behavior
|
||||
description: A clear and concise description of what you expected to happen.
|
||||
placeholder: e.g. Generated password should include at least one character from every enabled character set
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Screenshot
|
||||
description: If applicable, add screenshots to help explain your problem.
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: dropdown
|
||||
id: os
|
||||
attributes:
|
||||
label: Operating system
|
||||
options:
|
||||
- "Windows 10 and newer"
|
||||
- "Windows 8/8.1"
|
||||
- "Windows 7 and older"
|
||||
- "MacOS"
|
||||
- "Debian or Debian-based"
|
||||
- "Other"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: browser
|
||||
attributes:
|
||||
label: Browser name and version
|
||||
placeholder: e.g. Microsoft Edge 119.0.2151.58
|
||||
description: Put here your browser's name and version
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: Extension version
|
||||
placeholder: e.g. 3.0.0
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: context
|
||||
attributes:
|
||||
label: Additional context
|
||||
description: Add any other context about the problem here.
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: dropdown
|
||||
id: requested-help
|
||||
attributes:
|
||||
label: Are you willing to submit a PR for this issue?
|
||||
options:
|
||||
- "yes"
|
||||
- "no"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: checkboxes
|
||||
id: checkboxes
|
||||
attributes:
|
||||
label: Validations
|
||||
description: Before submitting the issue, please make sure you do the following
|
||||
options:
|
||||
- label: Check that there isn't already an issue that reports the same bug to avoid creating a duplicate.
|
||||
required: true
|
||||
- label: The provided reproduction is a minimal reproducible example of the bug.
|
||||
required: true
|
||||
@@ -0,0 +1,5 @@
|
||||
blank_issues_enabled: true
|
||||
contact_links:
|
||||
- name: Questions & Discussions
|
||||
url: https://github.com/XFox111/PasswordGeneratorExtension/discussions
|
||||
about: Use GitHub discussions for message-board style questions and discussions.
|
||||
@@ -1,20 +0,0 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: enhancement
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when '...'
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
@@ -0,0 +1,62 @@
|
||||
name: "🚀 New feature proposal"
|
||||
description: Suggest a feature idea for this project
|
||||
title: "[Feature]: "
|
||||
labels: ["feature", "needs-triage"]
|
||||
assignees:
|
||||
- xfox111
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for your interest in the project and taking the time to fill out this feature report!
|
||||
|
||||
- type: textarea
|
||||
id: proposition
|
||||
attributes:
|
||||
label: Proposed solution
|
||||
description: Describe the solution you'd like
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: justification
|
||||
attributes:
|
||||
label: Justification
|
||||
description: Is your feature request related to a problem? Please describe.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: alts
|
||||
attributes:
|
||||
label: Alternatives
|
||||
description: Describe alternatives you've considered.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: context
|
||||
attributes:
|
||||
label: Additional context
|
||||
description: Add any other context or screenshots about the feature request here.
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: dropdown
|
||||
id: requested-help
|
||||
attributes:
|
||||
label: Are you willing to submit a PR for this issue?
|
||||
options:
|
||||
- "yes"
|
||||
- "no"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: checkboxes
|
||||
id: checkboxes
|
||||
attributes:
|
||||
label: Validations
|
||||
description: Before submitting the issue, please make sure you do the following
|
||||
options:
|
||||
- label: Check that there isn't already an issue that request the same feature to avoid creating a duplicate.
|
||||
required: true
|
||||
@@ -0,0 +1,33 @@
|
||||
# To get started with Dependabot version updates, you'll need to specify which
|
||||
# package ecosystems to update and where the package manifests are located.
|
||||
# Please see the documentation for all configuration options:
|
||||
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||
|
||||
# yaml-language-server: $schema=https://json.schemastore.org/dependabot-2.0.json
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
|
||||
- package-ecosystem: "npm" # See documentation for possible values
|
||||
directory: "/" # Location of package manifests
|
||||
target-branch: "next"
|
||||
assignees:
|
||||
- "xfox111"
|
||||
reviewers:
|
||||
- "xfox111"
|
||||
schedule:
|
||||
interval: monthly
|
||||
rebase-strategy: disabled
|
||||
open-pull-requests-limit: 20
|
||||
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
target-branch: "next"
|
||||
assignees:
|
||||
- "xfox111"
|
||||
reviewers:
|
||||
- "xfox111"
|
||||
schedule:
|
||||
interval: monthly
|
||||
rebase-strategy: disabled
|
||||
open-pull-requests-limit: 20
|
||||
@@ -1,9 +1,31 @@
|
||||
fixes:
|
||||
## Description
|
||||
<!--Put short description of the pull request here-->
|
||||
|
||||
Resolves: #issue_number
|
||||
|
||||
<!-- ------------------------------------- -->
|
||||
<!-- FOR REPOSITORY MAINTAINERS' PRS ONLY! -->
|
||||
<!-- DO NOT INCLUDE FOLLOWING IN YOUR PR!! -->
|
||||
<!-- ------------------------------------- -->
|
||||
|
||||
<!-- > ## 🚀 Patch Tuesday update
|
||||
> This pull request is a part of our new initiative!
|
||||
From now on we are starting to roll out updates on every first Tuesday of the month, which will include bugfixes, security and dependency updates to keep the project's security and stability up to date!
|
||||
|
||||
## Description
|
||||
Dependencies update and security fixes
|
||||
|
||||
## Changelog
|
||||
- Item 1
|
||||
- Item 2
|
||||
- Item 3
|
||||
### Dependency bumps
|
||||
- #
|
||||
### Fixed security vulnerabilities
|
||||
- [CWE-20](https://cwe.mitre.org/data/definitions/20.html) (#)
|
||||
- CVE-2022-25883 (#)
|
||||
|
||||
## PR Checklist
|
||||
- [ ] Change extension version in the manifest
|
||||
- [ ] Update version in `package.json`
|
||||
- [ ] [Post-merge] Review and publish GitHub release
|
||||
- [ ] Update Discussions
|
||||
- [ ] [Post-deploy] Update changelog for Firefox webstore
|
||||
- [ ] Reset `next` branch to be in sync with `main`
|
||||
-->
|
||||
|
||||
@@ -1,22 +1,14 @@
|
||||
<!-- > ## 🚀 Patch Tuesday update
|
||||
> This release is a part of our new initiative!
|
||||
From now on we are starting to roll out updates on every first Tuesday of the month, which will include bugfixes, security and dependency updates to keep the project's security and stability up to date!
|
||||
-->
|
||||
|
||||
## What's new
|
||||
-
|
||||
<!-- - Dependency updates and security patches (#) -->
|
||||
|
||||
## Installation
|
||||
### From extension webstore (recommended)
|
||||
- [Google Chrome Webstore](https://chrome.google.com/webstore/detail/jnjobgjobffgmgfnkpkjfjkkfhfikmfl)
|
||||
- [Microsoft Edge Add-ons Webstore](https://microsoftedge.microsoft.com/addons/detail/manimdhobjbkfpeeehlhhneookiokpbj)
|
||||
- [Firefox Add-ons](https://addons.mozilla.org/en-US/firefox/addon/easy-password-generator/)
|
||||
- [GitHub Releases](https://github.com/xfox111/PasswordGeneratorExtension/releases/latest)
|
||||
<!-- ### Fixed security issues in this update
|
||||
- [CWE-20](https://cwe.mitre.org/data/definitions/20.html)
|
||||
- CVE-2022-25883
|
||||
-->
|
||||
|
||||
Note that version published on these webstores can differ from this release
|
||||
### Sideloading (for testing purposes only)
|
||||
1. Download attached archive and unpack it
|
||||
2. Enable Developers mode on your browser extensions page
|
||||
3. Click "Load unpacked" button and navigate to the extension root folder (contains `manifest.json`)
|
||||
4. Done!
|
||||
|
||||
*On Firefox you should open manifest file instead of extension's folder
|
||||
|
||||
**Note:** If you delete extension folder it will disappear from your browser
|
||||
|
||||
_Sideloaded extensions don't replace officially installed ones_
|
||||
Refer to [Download section of the README.md](https://github.com/XFox111/PasswordGeneratorExtension#download) for sideloading instructions and download links
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
name: Release pipeline
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [ released ]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
targets:
|
||||
description: Targets
|
||||
required: true
|
||||
default: '["chrome","firefox"]'
|
||||
type: choice
|
||||
options:
|
||||
- '["chrome","firefox"]'
|
||||
- '["chrome"]'
|
||||
- '["firefox"]'
|
||||
firefox:
|
||||
description: Deploy Firefox
|
||||
type: boolean
|
||||
default: true
|
||||
chrome:
|
||||
description: Deploy Chrome
|
||||
type: boolean
|
||||
default: true
|
||||
edge:
|
||||
description: Deploy Edge
|
||||
type: boolean
|
||||
default: true
|
||||
gh-release:
|
||||
description: Attach to GitHub release
|
||||
type: boolean
|
||||
default: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
container: node:20
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target: ${{ fromJSON(github.event.inputs.targets || '["chrome","firefox"]') }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@main
|
||||
|
||||
- run: yarn install
|
||||
- run: yarn lint
|
||||
- run: TARGET=${{ matrix.target }} yarn build
|
||||
|
||||
- name: Drop build artifacts (${{ matrix.target }})
|
||||
uses: actions/upload-artifact@main
|
||||
with:
|
||||
name: ${{ matrix.target }}
|
||||
path: dist
|
||||
|
||||
- name: web-ext lint
|
||||
if: ${{ matrix.target == 'firefox' }}
|
||||
uses: freaktechnik/web-ext-lint@main
|
||||
with:
|
||||
extension-root: dist
|
||||
self-hosted: false
|
||||
|
||||
- uses: cardinalby/webext-buildtools-pack-extension-dir-action@1.0.9
|
||||
with:
|
||||
extensionDir: dist
|
||||
zipFilePath: PasswordGenerator-${{ matrix.target }}.zip
|
||||
|
||||
- name: Drop packed artifacts (${{ matrix.target }})
|
||||
uses: actions/upload-artifact@main
|
||||
with:
|
||||
name: packed-${{ matrix.target }}
|
||||
path: PasswordGenerator-${{ matrix.target }}.zip
|
||||
|
||||
publish-github:
|
||||
needs: build
|
||||
if: ${{ github.event_name == 'release' || github.event.inputs.gh-release == 'true' }}
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target: ${{ fromJSON(github.event.inputs.targets || '["chrome","firefox"]') }}
|
||||
|
||||
steps:
|
||||
- uses: actions/download-artifact@main
|
||||
with:
|
||||
name: packed-${{ matrix.target }}
|
||||
|
||||
- name: Attach build to release
|
||||
uses: xresloader/upload-to-github-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
file: PasswordGenerator-${{ matrix.target }}.zip
|
||||
draft: false
|
||||
overwrite: true
|
||||
update_latest_release: true
|
||||
|
||||
publish-chrome:
|
||||
needs: build
|
||||
if: ${{ github.event_name == 'release' || (github.event.inputs.chrome == 'true' && contains(github.event.inputs.targets, 'chrome')) }}
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/download-artifact@main
|
||||
with:
|
||||
name: packed-chrome
|
||||
|
||||
- uses: wdzeng/chrome-extension@v1.2.4
|
||||
with:
|
||||
extension-id: jnjobgjobffgmgfnkpkjfjkkfhfikmfl
|
||||
zip-path: PasswordGenerator-chrome.zip
|
||||
client-id: ${{ secrets.CHROME_CLIENT_ID }}
|
||||
client-secret: ${{ secrets.CHROME_CLIENT_SECRET }}
|
||||
refresh-token: ${{ secrets.CHROME_REFRESH_TOKEN }}
|
||||
|
||||
publish-edge:
|
||||
needs: build
|
||||
if: ${{ github.event_name == 'release' || (github.event.inputs.edge == 'true' && contains(github.event.inputs.targets, 'chrome')) }}
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/download-artifact@main
|
||||
with:
|
||||
name: packed-chrome
|
||||
|
||||
- uses: wdzeng/edge-addon@v1.2.4
|
||||
with:
|
||||
product-id: ${{ secrets.EDGE_PRODUCT_ID }}
|
||||
zip-path: PasswordGenerator-chrome.zip
|
||||
client-id: ${{ secrets.EDGE_CLIENT_ID }}
|
||||
client-secret: ${{ secrets.EDGE_CLIENT_SECRET }}
|
||||
access-token-url: ${{ secrets.EDGE_ACCESS_TOKEN_URL }}
|
||||
|
||||
publish-firefox:
|
||||
needs: build
|
||||
if: ${{ github.event_name == 'release' || (github.event.inputs.firefox == 'true' && contains(github.event.inputs.targets, 'firefox')) }}
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/download-artifact@main
|
||||
with:
|
||||
name: packed-firefox
|
||||
|
||||
- uses: wdzeng/firefox-addon@v1.0.5
|
||||
with:
|
||||
addon-guid: ${{ secrets.FIREFOX_EXT_UUID }}
|
||||
xpi-path: PasswordGenerator-firefox.zip
|
||||
jwt-issuer: ${{ secrets.FIREFOX_API_KEY }}
|
||||
jwt-secret: ${{ secrets.FIREFOX_CLIENT_SECRET }}
|
||||
@@ -1,70 +0,0 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
Firefox:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Build Extension for Firefox
|
||||
id: web-ext-build
|
||||
uses: kewisch/action-web-ext@v1
|
||||
with:
|
||||
cmd: build
|
||||
|
||||
- name: 'Sign & publish'
|
||||
id: web-ext-sign
|
||||
uses: kewisch/action-web-ext@v1
|
||||
with:
|
||||
cmd: sign
|
||||
channel: listed
|
||||
source: ${{ steps.web-ext-build.outputs.target }}
|
||||
apiKey: ${{ secrets.FIREFOX_API_KEY }}
|
||||
apiSecret: ${{ secrets.FIREFOX_CLIENT_SECRET }}
|
||||
|
||||
- name: Drop artifacts
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: 'Firefox Artefacts'
|
||||
path: ${{ steps.web-ext-build.outputs.target }}
|
||||
|
||||
Chrome:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Pack extension
|
||||
uses: TheDoctor0/zip-release@0.4.1
|
||||
with:
|
||||
filename: ./PasswordGenerator.zip
|
||||
exclusions: '.git/* .vscode/* .github/* *.md'
|
||||
|
||||
- name: Publish to Chrome Webstore
|
||||
uses: SebastienGllmt/chrome-addon@v3
|
||||
with:
|
||||
extension: jnjobgjobffgmgfnkpkjfjkkfhfikmfl
|
||||
zip: ./PasswordGenerator.zip
|
||||
client-id: ${{ secrets.CHROME_CLIENT_ID }}
|
||||
client-secret: ${{ secrets.CHROME_CLIENT_SECRET }}
|
||||
refresh-token: ${{ secrets.CHROME_REFRESH_TOKEN }}
|
||||
|
||||
- name: Upload artifact
|
||||
uses: xresloader/upload-to-github-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
file: ./PasswordGenerator.zip
|
||||
tags: true
|
||||
draft: false
|
||||
|
||||
- name: Drop artifacts
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: 'Chrome Artifacts'
|
||||
path: ./PasswordGenerator.zip
|
||||
@@ -0,0 +1,86 @@
|
||||
# For most projects, this workflow file will not need changing; you simply need
|
||||
# to commit it to your repository.
|
||||
#
|
||||
# You may wish to alter this file to override the set of languages analyzed,
|
||||
# or to provide custom queries or build logic.
|
||||
#
|
||||
# ******** NOTE ********
|
||||
# We have attempted to detect the languages in your repository. Please check
|
||||
# the `language` matrix defined below to confirm you have the correct set of
|
||||
# supported CodeQL languages.
|
||||
#
|
||||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main", "next" ]
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- 'LICENSE'
|
||||
- '**/cd_pipeline.yaml'
|
||||
- '**/dependabot.yml'
|
||||
- '**/pr_pipeline.yaml'
|
||||
- '.vscode/*'
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [ "main", "next" ]
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- 'LICENSE'
|
||||
- '**/cd_pipeline.yaml'
|
||||
- '**/dependabot.yml'
|
||||
- '**/pr_pipeline.yaml'
|
||||
- '.vscode/*'
|
||||
schedule:
|
||||
- cron: '24 7 * * 3'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: [ 'typescript' ]
|
||||
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
|
||||
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
|
||||
# Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
|
||||
# queries: security-extended,security-and-quality
|
||||
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v3
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||
|
||||
# If the Autobuild fails above, remove it and uncomment the following three lines.
|
||||
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
|
||||
|
||||
# - run: |
|
||||
# echo "Run, Build Application using script"
|
||||
# ./location_of_script_within_repo/buildscript.sh
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v3
|
||||
@@ -0,0 +1,31 @@
|
||||
name: PR next workflow
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
paths:
|
||||
- 'package.json'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
create-release-draft:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@main
|
||||
|
||||
- name: Get version from package.json
|
||||
id: get_version
|
||||
run: |
|
||||
extver=`jq -r ".version" package.json`
|
||||
echo "version=$extver" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- uses: dev-build-deploy/release-me@v0.16.3
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
prefix: v
|
||||
draft: true
|
||||
version: v${{ steps.get_version.outputs.version }}
|
||||
release-notes: .github/release_description_template.md
|
||||
@@ -0,0 +1,55 @@
|
||||
name: PR check pipeline
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [ "main", "next" ]
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- 'LICENSE'
|
||||
- 'PRIVACY'
|
||||
- '**/cd_pipeline.yaml'
|
||||
- '**/dependabot.yml'
|
||||
- '**/codeql-analysis.yml'
|
||||
- '**/pr_next.yaml'
|
||||
- '.vscode/*'
|
||||
- '.devcontainer/*'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
targets:
|
||||
description: Targets
|
||||
required: true
|
||||
default: '["chrome","firefox"]'
|
||||
type: choice
|
||||
options:
|
||||
- '["chrome","firefox"]'
|
||||
- '["chrome"]'
|
||||
- '["firefox"]'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
container: node:20
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target: ${{ fromJSON(github.event.inputs.targets || '["chrome","firefox"]') }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@main
|
||||
|
||||
- run: yarn install
|
||||
- run: yarn lint
|
||||
- run: TARGET=${{ matrix.target }} yarn build
|
||||
|
||||
- name: Drop artifacts (${{ matrix.target }})
|
||||
uses: actions/upload-artifact@main
|
||||
with:
|
||||
name: ${{ matrix.target }}
|
||||
path: dist
|
||||
|
||||
- name: web-ext lint
|
||||
if: ${{ matrix.target == 'firefox' }}
|
||||
uses: freaktechnik/web-ext-lint@main
|
||||
with:
|
||||
extension-root: dist
|
||||
self-hosted: false
|
||||
@@ -1 +1,26 @@
|
||||
.vscode
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
# Config files
|
||||
.webextrc
|
||||
.webextrc.*
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"DNEK.auto-region-folder",
|
||||
"eamodio.gitlens",
|
||||
"jock.svg",
|
||||
"firefox-devtools.vscode-firefox-debug",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"github.vscode-github-actions",
|
||||
"GitHub.vscode-pull-request-github",
|
||||
"bierner.github-markdown-preview",
|
||||
"mrmlnc.vscode-scss",
|
||||
"Gruntfuggly.todo-tree",
|
||||
"redhat.vscode-yaml"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
{
|
||||
"json.schemas": [
|
||||
{
|
||||
"fileMatch": [
|
||||
"/messages.json"
|
||||
],
|
||||
"url": "https://gist.github.com/XFox111/9528b76f9f02704d620d4edbf421e06b/raw/e77197276f0aa2994cceae4ddf4dcfcabdce9dcb/webext-locale-schema.json"
|
||||
}
|
||||
],
|
||||
"editor.rulers": [
|
||||
{
|
||||
"column": 120
|
||||
}
|
||||
],
|
||||
"editor.insertSpaces": false,
|
||||
"files.insertFinalNewline": true,
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.organizeImports": "explicit"
|
||||
},
|
||||
"files.eol": "\n",
|
||||
"files.trimFinalNewlines": true,
|
||||
"files.trimTrailingWhitespace": true,
|
||||
"css.format.braceStyle": "expand",
|
||||
"css.format.maxPreserveNewLines": 3,
|
||||
"css.format.spaceAroundSelectorSeparator": true,
|
||||
"css.format.newlineBetweenSelectors": false,
|
||||
"css.lint.compatibleVendorPrefixes": "warning",
|
||||
"css.lint.duplicateProperties": "warning",
|
||||
"css.lint.float": "warning",
|
||||
"css.lint.unknownVendorSpecificProperties": "warning",
|
||||
"css.lint.zeroUnits": "warning",
|
||||
"scss.format.braceStyle": "expand",
|
||||
"scss.format.maxPreserveNewLines": 3,
|
||||
"scss.format.spaceAroundSelectorSeparator": true,
|
||||
"scss.lint.compatibleVendorPrefixes": "warning",
|
||||
"scss.lint.duplicateProperties": "warning",
|
||||
"scss.lint.float": "warning",
|
||||
"scss.lint.unknownVendorSpecificProperties": "warning",
|
||||
"scss.lint.zeroUnits": "warning",
|
||||
"html.format.maxPreserveNewLines": 3,
|
||||
"html.format.wrapAttributes": "preserve",
|
||||
"javascript.format.placeOpenBraceOnNewLineForControlBlocks": true,
|
||||
"javascript.format.placeOpenBraceOnNewLineForFunctions": true,
|
||||
"javascript.format.semicolons": "insert",
|
||||
"javascript.preferences.quoteStyle": "double",
|
||||
"javascript.format.insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces": true,
|
||||
"typescript.format.placeOpenBraceOnNewLineForControlBlocks": true,
|
||||
"typescript.format.placeOpenBraceOnNewLineForFunctions": true,
|
||||
"typescript.format.semicolons": "insert",
|
||||
"typescript.preferences.quoteStyle": "double",
|
||||
"typescript.format.insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces": true
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"type": "shell",
|
||||
"command": "yarn build",
|
||||
"group":
|
||||
{
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
},
|
||||
"label": "Build: Chromium"
|
||||
},
|
||||
{
|
||||
"type": "shell",
|
||||
"command": "yarn build",
|
||||
"group":
|
||||
{
|
||||
"kind": "build",
|
||||
"isDefault": false
|
||||
},
|
||||
"label": "Build: Firefox",
|
||||
"options": {
|
||||
"env": {
|
||||
"TARGET": "firefox"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "shell",
|
||||
"command": "yarn build",
|
||||
"args": [
|
||||
"--watch",
|
||||
"--mode",
|
||||
"development"
|
||||
],
|
||||
"group": "test",
|
||||
"label": "Watch: Chromium"
|
||||
},
|
||||
{
|
||||
"type": "shell",
|
||||
"command": "yarn build",
|
||||
"args": [
|
||||
"--watch",
|
||||
"--mode",
|
||||
"development"
|
||||
],
|
||||
"group": "test",
|
||||
"label": "Watch: Firefox",
|
||||
"options": {
|
||||
"env": {
|
||||
"TARGET": "firefox"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "shell",
|
||||
"command": "yarn dev",
|
||||
"group": "test",
|
||||
"label": "Dev: Chromium"
|
||||
},
|
||||
{
|
||||
"type": "shell",
|
||||
"command": "yarn dev",
|
||||
"group": "test",
|
||||
"label": "Dev: Firefox",
|
||||
"options": {
|
||||
"env": {
|
||||
"TARGET": "firefox"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "shell",
|
||||
"command": "yarn install",
|
||||
"label": "Restore dependencies",
|
||||
"group": "build"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,204 +1,2 @@
|
||||
# Contribution Guidelines
|
||||
Welcome, and thank you for your interest in contributing to my project!
|
||||
|
||||
There are many ways in which you can contribute, beyond writing code. The goal of this document is to provide a high-level overview of how you can get involved.
|
||||
|
||||
## Table of Contents
|
||||
- [Contribution Guidelines](#contribution-guidelines)
|
||||
- [Table of Contents](#table-of-contents)
|
||||
- [Asking Questions](#asking-questions)
|
||||
- [Providing Feedback](#providing-feedback)
|
||||
- [Reporting Issues](#reporting-issues)
|
||||
- [Look For an Existing Issue](#look-for-an-existing-issue)
|
||||
- [Writing Good Bug Reports and Feature Requests](#writing-good-bug-reports-and-feature-requests)
|
||||
- [Final Checklist](#final-checklist)
|
||||
- [Follow Your Issue](#follow-your-issue)
|
||||
- [Contributing to codebase](#contributing-to-codebase)
|
||||
- [Deploy test version on your browser](#deploy-test-version-on-your-browser)
|
||||
- [Development workflow](#development-workflow)
|
||||
- [Release](#release)
|
||||
- [Coding guidelines](#coding-guidelines)
|
||||
- [Indentation](#indentation)
|
||||
- [Names](#names)
|
||||
- [Comments](#comments)
|
||||
- [Strings](#strings)
|
||||
- [Style](#style)
|
||||
- [Finding an issue to work on](#finding-an-issue-to-work-on)
|
||||
- [Contributing to translations](#contributing-to-translations)
|
||||
- [Submitting pull requests](#submitting-pull-requests)
|
||||
- [Spell check errors](#spell-check-errors)
|
||||
- [Thank You!](#thank-you)
|
||||
- [Attribution](#attribution)
|
||||
|
||||
## Asking Questions
|
||||
Have a question? Rather than opening an issue, please ask me directly on opensource@xfox111.net.
|
||||
|
||||
## Providing Feedback
|
||||
Your comments and feedback are welcome.
|
||||
You can leave your feedbak on feedback@xfox111.net or do it on [Microsoft Edge Add-ons Webstore](https://microsoftedge.microsoft.com/addons/detail/manimdhobjbkfpeeehlhhneookiokpbj), [Chrome Extensions Webstore](https://chrome.google.com/webstore/detail/jnjobgjobffgmgfnkpkjfjkkfhfikmfl) or [Mozilla Add-ons Webstore](https://addons.mozilla.org/en-US/firefox/addon/easy-password-generator/)
|
||||
|
||||
## Reporting Issues
|
||||
Have you identified a reproducible problem in the extension? Have a feature request? I'd like to hear it! Here's how you can make reporting your issue as effective as possible.
|
||||
|
||||
### Look For an Existing Issue
|
||||
Before you create a new issue, please do a search in [open issues](https://github.com/xfox111/PasswordGeneratorExtension/issues) to see if the issue or feature request has already been filed.
|
||||
|
||||
Be sure to scan through the [feature requests](https://github.com/XFox111/PasswordGeneratorExtension/issues?q=is%3Aissue+is%3Aopen+label%3Aenhancement).
|
||||
|
||||
If you find your issue already exists, make relevant comments and add your [reaction](https://github.com/blog/2119-add-reactions-to-pull-requests-issues-and-comments). Use a reaction in place of a "+1" comment:
|
||||
|
||||
* 👍 - upvote
|
||||
* 👎 - downvote
|
||||
|
||||
If you cannot find an existing issue that describes your bug or feature, create a new issue using the guidelines below.
|
||||
|
||||
### Writing Good Bug Reports and Feature Requests
|
||||
File a single issue per problem and feature request. Do not enumerate multiple bugs or feature requests in the same issue.
|
||||
|
||||
Do not add your issue as a comment to an existing issue unless they are the same ones. Many issues look similar, but have different causes.
|
||||
|
||||
The more information you can provide, the more likely someone will be successful at reproducing the issue and finding a solution.
|
||||
|
||||
Please include the following with each issue:
|
||||
- Current version of the extension
|
||||
- Your current browser and OS name
|
||||
- Reproducible steps (1... 2... 3...) that cause the issue
|
||||
- What you expected to see, versus what you actually saw
|
||||
- Images, animations, or a link to a video showing the issue occurring
|
||||
|
||||
### Final Checklist
|
||||
Please remember to do the following:
|
||||
- [ ] Search the issue repository to ensure your report is a new issue
|
||||
- [ ] Separate issues reports
|
||||
- [ ] Include as much information as you can to your report
|
||||
|
||||
Don't feel bad if the developers can't reproduce the issue right away. They will simply ask for more information!
|
||||
|
||||
### Follow Your Issue
|
||||
Once your report is submitted, be sure to stay in touch with the devs in case they need more help from you.
|
||||
|
||||
## Contributing to codebase
|
||||
If you are interested in writing code to fix issues or implement new awesome features you can follow this guidelines to get a better result
|
||||
|
||||
### Deploy test version on your browser
|
||||
1. Clone repository to local storage using [Git](https://guides.github.com/introduction/git-handbook/)
|
||||
```
|
||||
git clone https://github.com/xfox111/PasswordGeneratorExtension.git
|
||||
```
|
||||
2. Enable Developers mode on your browser extensions page
|
||||
3. Click "Load unpacked" button and navigate to the extension root folder (contains `manifest.json`)
|
||||
4. Done!
|
||||
|
||||
Next time you make any changes to the codebase, reload extension by toggling it off and on or by pressing "Reload" button on extensions list page
|
||||
|
||||
### Development workflow
|
||||
This section represents how contributors should interact with codebase implementing features and fixing bugs
|
||||
1. Getting assigned to the issue
|
||||
2. Creating a repository fork
|
||||
3. Making changes to codebase
|
||||
5. Creating a pull request to `master`
|
||||
6. Reviewing & completing PR
|
||||
7. Done
|
||||
|
||||
#### Release
|
||||
Next stage is release. Release performs on every push to master (which makes functional changes to the source code). Release performs manually by @XFox111 into: Chrome webstore, Edge webstore and GitHub releases
|
||||
|
||||
### Coding guidelines
|
||||
#### Indentation
|
||||
We use tabs, not spaces.
|
||||
|
||||
#### Names
|
||||
The project naming rules inherit [.NET Naming Guidelines](https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/naming-guidelines). Nevertheless there'is some distinction with the guidelines as well as additions to the one:
|
||||
- Use `camelCase` for variables instead of `CamelCase` stated in [Capitalization Conventions](https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/capitalization-conventions#capitalization-rules-for-identifiers)
|
||||
- Use `snake_case` for file names
|
||||
|
||||
#### Comments
|
||||
Leave as more comments as you can. Remember: the more detailed documentation your code has the less programmers will curse you in the future
|
||||
|
||||
#### Strings
|
||||
Use "double quotes" wherever it's possible
|
||||
|
||||
#### Style
|
||||
- Prefer to use lambda functions
|
||||
- Put curly braces on new lines
|
||||
- Wrong:
|
||||
```
|
||||
if (condition) {
|
||||
...
|
||||
}
|
||||
```
|
||||
- Correct:
|
||||
```
|
||||
if (condition)
|
||||
{
|
||||
...
|
||||
}
|
||||
```
|
||||
- Put spaces between operators and before braces in methods declarations, conditionals and loops
|
||||
- Wrong:
|
||||
- `y=k*x+b`
|
||||
- `function FunctionName()`
|
||||
- Correct:
|
||||
- `y = k * x + b`
|
||||
- `function FunctionName ()`
|
||||
- Use ternary conditionals wherever it's possible
|
||||
- Wrong:
|
||||
```
|
||||
var s;
|
||||
if (condition)
|
||||
s = "Life";
|
||||
else
|
||||
s = "Death"
|
||||
```
|
||||
- Correct:
|
||||
```
|
||||
var s = condition ? "Life" : "Death";
|
||||
```
|
||||
- Do not surround loop and conditional bodies with curly braces if they can be avoided
|
||||
- Wrong:
|
||||
```
|
||||
if (condition)
|
||||
{
|
||||
console.log("Hello, World!");
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
```
|
||||
- Correct
|
||||
```
|
||||
if (condition)
|
||||
console.log("Hello, World!");
|
||||
else
|
||||
return;
|
||||
```
|
||||
|
||||
### Finding an issue to work on
|
||||
Check out the [full issues list](https://github.com/XFox111/PasswordGeneratorExtension/issues?utf8=%E2%9C%93&q=is%3Aopen+is%3Aissue) for a list of all potential areas for contributions. **Note** that just because an issue exists in the repository does not mean we will accept a contribution. There are several reasons we may not accept a pull request like:
|
||||
|
||||
- Performance - One of project's core values is to deliver a lightweight extension, that means it should perform well in both real and test environments.
|
||||
- User experience - Since we want to deliver a lightweight extension, the UX should feel lightweight as well and not be cluttered. Most changes to the UI should go through the issue owner and project owner (@XFox111).
|
||||
- Architectural - Project owner needs to agree with any architectural impact a change may make. Such things must be discussed with and agreed upon by the project owner.
|
||||
|
||||
To improve the chances to get a pull request merged you should select an issue that is labelled with the `help-wanted` or `bug` labels. If the issue you want to work on is not labelled with `help-wanted` or `bug`, you can start a conversation with the project owner asking whether an external contribution will be considered.
|
||||
|
||||
To avoid multiple pull requests resolving the same issue, let others know you are working on it by saying so in a comment.
|
||||
|
||||
### Contributing to translations
|
||||
If you want to help us to translate this extension into other languages, please read [this article](https://developer.chrome.com/extensions/i18n)
|
||||
|
||||
**Note** that whatever you want to contribute to the codebase, you should do it only after you got assigned on an issue
|
||||
|
||||
### Submitting pull requests
|
||||
To enable us to quickly review and accept your pull requests, always create one pull request per issue and [link the issue in the pull request](https://github.com/blog/957-introducing-issue-mentions). Never merge multiple requests in one unless they have the same root cause. Be sure to follow our [Coding Guidelines](#coding-guidelines) and keep code changes as small as possible. Avoid pure formatting changes to code that has not been modified otherwise. Pull requests should contain tests whenever possible. Fill pull request content according to its template. Deviations from template are not recommended
|
||||
|
||||
#### Spell check errors
|
||||
Pull requests that fix typos are welcomed but please make sure it doesn't touch multiple feature areas, otherwise it will be difficult to review. Pull requests only fixing spell check errors in source code are not recommended.
|
||||
|
||||
## Thank You!
|
||||
|
||||
Your contributions to open source, large or small, make great projects like this possible. Thank you for taking the time to contribute.
|
||||
|
||||
## Attribution
|
||||
This Contribution Guidelines are adapted from the [Contributing to VS Code](https://github.com/microsoft/vscode/blob/master/CONTRIBUTING.md)
|
||||
This article has been moved to the [project's Wiki section](https://github.com/XFox111/PasswordGeneratorExtension/wiki/Contribution-Guidelines)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 Michael Gordeev
|
||||
Copyright (c) 2024 Eugene Fox
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
Password generator Extension Privacy Policy
|
||||
1. Developers of the extension don't affiliate with Google LLC, Mozilla Foundation or Microsoft Corporation in any way
|
||||
2. This extension doesn't transfer any personal data (data which may be used to track your location or reveal your identity) to any remote or local server
|
||||
3. This extension doesn't share any personal data with third parties
|
||||
4. This extension stores following personal data:
|
||||
- Browser tabs which have been saved for later by user via this extension (if user click "Set current tabs aside" button). This includes titles, favicons and web links
|
||||
5. User can delete all saved personal data by removing this extension from his Browser
|
||||
@@ -0,0 +1,5 @@
|
||||
# Password generator extension Privacy policy
|
||||
1. Developers of the extension don't affiliate with Google LLC, Mozilla Foundation or Microsoft Corporation in any way.
|
||||
2. This extension doesn't store any personal data (data which may be used to track your location or reveal your identity).
|
||||
3. This extension uses cloud storage built into your browser to store its settings.
|
||||
4. Refer to your browser's developer regarding the privacy policy of the cloud storage used by your browser.
|
||||
@@ -1,21 +1,13 @@
|
||||
# Password generator
|
||||
<!-- # Password generator -->
|
||||
|
||||
[](https://github.com/xfox111/PasswordGeneratorExtension/releases/latest)
|
||||

|
||||
[](https://addons.mozilla.org/firefox/addon/easy-password-generator/)
|
||||
[](https://github.com/XFox111/PasswordGeneratorExtension/commits/main)
|
||||
|
||||
[](https://chrome.google.com/webstore/detail/jnjobgjobffgmgfnkpkjfjkkfhfikmfl)
|
||||
[](https://addons.mozilla.org/firefox/addon/easy-password-generator/)
|
||||
|
||||
[](https://github.com/xfox111/PasswordGeneratorExtension/issues)
|
||||
[](https://github.com/xfox111/PasswordGeneratorExtension/commits/master)
|
||||
[](https://github.com/xfox111/PasswordGeneratorExtension)
|
||||
[](https://opensource.org/licenses/MIT)
|
||||
|
||||
[](https://twitter.com/xfox111)
|
||||
[](https://github.com/xfox111)
|
||||
[](https://buymeacoffee.com/xfox111)
|
||||
|
||||

|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://cdn.xfox111.net/projects/pwdgen/PasswordGeneratorExtension-dark.webp">
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://cdn.xfox111.net/projects/pwdgen/PasswordGeneratorExtension-light.webp">
|
||||
<img alt="Password generator">
|
||||
</picture>
|
||||
|
||||
Extension for web browsers which helps you to easily generate strong passwords in one click
|
||||
|
||||
@@ -25,34 +17,77 @@ Extension for web browsers which helps you to easily generate strong passwords i
|
||||
- Clean and simple UI
|
||||
- Dark mode
|
||||
|
||||

|
||||
## Languages
|
||||
- English
|
||||
- Ukrainian
|
||||
- Polish
|
||||
- Russian
|
||||
- Portuguese (Brazil)
|
||||
|
||||
## Download
|
||||
[](https://chrome.google.com/webstore/detail/jnjobgjobffgmgfnkpkjfjkkfhfikmfl)
|
||||
[](https://addons.mozilla.org/firefox/addon/easy-password-generator/)
|
||||
|
||||
- [Google Chrome Webstore](https://chrome.google.com/webstore/detail/jnjobgjobffgmgfnkpkjfjkkfhfikmfl)
|
||||
- [Microsoft Edge Add-ons Webstore](https://microsoftedge.microsoft.com/addons/detail/manimdhobjbkfpeeehlhhneookiokpbj)
|
||||
- [Firefox Add-ons](https://addons.mozilla.org/en-US/firefox/addon/easy-password-generator/)
|
||||
- [GitHub Releases](https://github.com/xfox111/PasswordGeneratorExtension/releases/latest)
|
||||
|
||||
### Sideloading (for testing purposes only)
|
||||
|
||||
<details>
|
||||
<summary>Click to expand</summary>
|
||||
|
||||
---
|
||||
|
||||
<details>
|
||||
<summary><b>Chromium-based browsers (Edge, Chrome, etc.)</b></summary>
|
||||
|
||||
> 1. Go to [Releases](https://github.com/XFox111/PasswordGeneratorExtension/releases) and select a release to download
|
||||
> 2. Download attached archive for Chromium and unpack it
|
||||
> 3. Go to "chrome://extensions"
|
||||
> 4. Enable "Developer mode"
|
||||
> 5. Click the "Load unpacked" button and navigate to the extension's root folder (contains `manifest.json`)
|
||||
> 6. Done!
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>Firefox</b></summary>
|
||||
|
||||
> 1. Go to [Releases](https://github.com/XFox111/PasswordGeneratorExtension/releases) and select a release to download
|
||||
> 2. Download attached archive for Firefox and unpack it
|
||||
> 3. Go to "about:debugging#/runtime/this-firefox"
|
||||
> 4. Click the "Load Temporary Add-on..." button and select `manifest.josn` file in the root folder
|
||||
> 5. Done!
|
||||
|
||||
> **Important!**
|
||||
This will _replace_ officialy installed version if you have one.
|
||||
If you want to sideload it without replacing to run both versions at the same time - before loading add-on, open `manifest.json` in a text editor and change `id` key (it's `passwordgenerator@xfox111.net` by default) to something else
|
||||
|
||||
</details>
|
||||
|
||||
> **Note:** If you delete the extension folder it will disappear from your browser
|
||||
---
|
||||
|
||||
</details>
|
||||
|
||||
## Contributing
|
||||
[](https://github.com/xfox111/PasswordGeneratorExtension/issues)
|
||||
[](https://github.com/XFox111/PasswordGeneratorExtension/actions/workflows/cd_pipeline.yaml)
|
||||
[](https://github.com/xfox111/PasswordGeneratorExtension)
|
||||
|
||||
There are many ways in which you can participate in the project, for example:
|
||||
- [Submit bugs and feature requests](https://github.com/xfox111/PasswordGeneratorExtension/issues), and help us verify as they are checked in
|
||||
- Review [source code changes](https://github.com/xfox111/PasswordGeneratorExtension/pulls)
|
||||
- Review documentation and make pull requests for anything from typos to new content
|
||||
|
||||
If you are interested in fixing issues and contributing directly to the code base, please see the [Contribution Guidelines](https://github.com/XFox111/PasswordGeneratorExtension/blob/master/CONTRIBUTING.md), which covers the following:
|
||||
- [How to deploy the extension on your browser](https://github.com/XFox111/PasswordGeneratorExtension/blob/master/CONTRIBUTING.md#deploy-test-version-on-your-browser)
|
||||
- [The development workflow](https://github.com/XFox111/PasswordGeneratorExtension/blob/master/CONTRIBUTING.md#development-workflow), including debugging and running tests
|
||||
- [Coding guidelines](https://github.com/XFox111/PasswordGeneratorExtension/blob/master/CONTRIBUTING.md#coding-guidelines)
|
||||
- [Submitting pull requests](https://github.com/XFox111/PasswordGeneratorExtension/blob/master/CONTRIBUTING.md#submitting-pull-requests)
|
||||
- [Finding an issue to work on](https://github.com/XFox111/PasswordGeneratorExtension/blob/master/CONTRIBUTING.md#finding-an-issue-to-work-on)
|
||||
- [Contributing to translations](https://github.com/XFox111/PasswordGeneratorExtension/blob/master/CONTRIBUTING.md#contributing-to-translations)
|
||||
If you are interested in fixing issues and contributing directly to the code base, please refer to the [Contribution Guidelines](https://github.com/XFox111/PasswordGeneratorExtension/wiki/Contribution-Guidelines)
|
||||
|
||||
## Code of Conduct
|
||||
This project has adopted the Contributor Covenant. For more information see the [Code of Conduct](https://github.com/XFox111/PasswordGeneratorExtension/blob/master/CODE_OF_CONDUCT.md)
|
||||
---
|
||||
|
||||
## Copyrights
|
||||
> ©2020 Michael "XFox" Gordeev
|
||||
[](https://twitter.com/xfox111)
|
||||
[](https://github.com/xfox111)
|
||||
[](https://buymeacoffee.com/xfox111)
|
||||
|
||||
Font copyrights: Microsoft Corportation ©2019 (Additional ELUA applied)
|
||||
|
||||
Licensed under [MIT License](https://opensource.org/licenses/MIT)
|
||||
> ©2024 Eugene Fox
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
Password generator extension has a linear versioning system. The latest version is always the most secure one. This is applied to major versions as well. If you are using an older version, please update it to the latest one.
|
||||
|
||||
We regularly run security audits and fix any security issues that are found. If you find a security issue, please report it to us as described below.
|
||||
|
||||
If you are willing to continue using an older version, you can sideload it from the [releases page](https://github.com/XFox111/PasswordGeneratorExtension/releases). Use outdated versions at your own risk.
|
||||
|
||||
## Reporting a Vulnerability
|
||||
You can report a security issue by clicking "Report a vulnerabilty" button at the top-right of this page, or by going through [this link](https://github.com/XFox111/PasswordGeneratorExtension/security/advisories/new)
|
||||
@@ -1,160 +0,0 @@
|
||||
{
|
||||
"name":
|
||||
{
|
||||
"message": "Password generator",
|
||||
"description": "Extension name. Displayed in the manifest and pane header"
|
||||
},
|
||||
"description":
|
||||
{
|
||||
"message": "Password generator extension allows you to easily generate long and secure password in one click",
|
||||
"description": "Extension description"
|
||||
},
|
||||
"author":
|
||||
{
|
||||
"message": "Michael \"XFox\" Gordeev",
|
||||
"description": "Author's name"
|
||||
},
|
||||
"generate":
|
||||
{
|
||||
"message": "Generate password",
|
||||
"description": "Main action label"
|
||||
},
|
||||
"success":
|
||||
{
|
||||
"message": "Password has been generated and copied to your clipboard",
|
||||
"description": "Message which is shown after successful creation of a password"
|
||||
},
|
||||
"fail":
|
||||
{
|
||||
"message": "Invalid generator settings. No password was generated",
|
||||
"description": "Message which is shown after a creation of password failed"
|
||||
},
|
||||
"notEnoughChars":
|
||||
{
|
||||
"message": "With your current settings, your password length should not exceed %MIN_CHARS% characters",
|
||||
"description": "Message which is shown if there's no enough characters to create a password"
|
||||
},
|
||||
"lengthPrompt":
|
||||
{
|
||||
"message": "Set password length. Press OK to use default password length (%LEN% characters)",
|
||||
"description": "A prompt message when user generates a password"
|
||||
},
|
||||
|
||||
"generatorOptions":
|
||||
{
|
||||
"message": "Generator options",
|
||||
"description": "Generator options group header"
|
||||
},
|
||||
"length":
|
||||
{
|
||||
"message": "Password length",
|
||||
"description": "Input field header"
|
||||
},
|
||||
"hint":
|
||||
{
|
||||
"message": "Reccommended password length",
|
||||
"description": "Password length field hint"
|
||||
},
|
||||
"includeSymbols":
|
||||
{
|
||||
"message": "Include symbols (e.g.",
|
||||
"description": "Option checkbox label"
|
||||
},
|
||||
"includeNumbers":
|
||||
{
|
||||
"message": "Include numbers (e.g.",
|
||||
"description": "Option checkbox label"
|
||||
},
|
||||
"includeLowercase":
|
||||
{
|
||||
"message": "Include lowercase characters (e.g.",
|
||||
"description": "Option checkbox label"
|
||||
},
|
||||
"includeUppercase":
|
||||
{
|
||||
"message": "Include uppercase characters (e.g.",
|
||||
"description": "Option checkbox label"
|
||||
},
|
||||
"excludeSimilar":
|
||||
{
|
||||
"message": "Exclude similar characters (e.g.",
|
||||
"description": "Option checkbox label"
|
||||
},
|
||||
"excludeSpecial":
|
||||
{
|
||||
"message": "Exclude ambiguous characters (e.g.",
|
||||
"description": "Option checkbox label"
|
||||
},
|
||||
"dontRepeatChars":
|
||||
{
|
||||
"message": "Do not repeat characters",
|
||||
"description": "Option checkbox label"
|
||||
},
|
||||
|
||||
"extOptions":
|
||||
{
|
||||
"message": "Extension options",
|
||||
"description": "Extension options group header"
|
||||
},
|
||||
"showButton":
|
||||
{
|
||||
"message": "Insert \"Generate password\" button below password fields",
|
||||
"description": "Option checkbox label"
|
||||
},
|
||||
"showContext":
|
||||
{
|
||||
"message": "Add \"Generate password\" action to context menu",
|
||||
"description": "Option checkbox label"
|
||||
},
|
||||
"hideAlert":
|
||||
{
|
||||
"message": "Hide successful password generation message",
|
||||
"description": "Option checkbox label"
|
||||
},
|
||||
"promptForLength":
|
||||
{
|
||||
"message": "Prompt for password length every time before generating",
|
||||
"description": "Option checkbox label"
|
||||
},
|
||||
"more":
|
||||
{
|
||||
"message": "More",
|
||||
"description": "About section toggle button caption"
|
||||
},
|
||||
|
||||
"about":
|
||||
{
|
||||
"message": "About",
|
||||
"description": "About info header"
|
||||
},
|
||||
"developed":
|
||||
{
|
||||
"message": "Developed by",
|
||||
"description": ""
|
||||
},
|
||||
"website":
|
||||
{
|
||||
"message": "My website",
|
||||
"description": "My website link caption"
|
||||
},
|
||||
"github":
|
||||
{
|
||||
"message": "Source code",
|
||||
"description": "GitHub repo link caption"
|
||||
},
|
||||
"changelog":
|
||||
{
|
||||
"message": "Changelog",
|
||||
"description": "Changelog link caption"
|
||||
},
|
||||
"feedback":
|
||||
{
|
||||
"message": "Leave feedback",
|
||||
"description": "Feedback CTA button"
|
||||
},
|
||||
"bmc":
|
||||
{
|
||||
"message": "Buy me a coffee",
|
||||
"description": "BMC CTA button cation"
|
||||
}
|
||||
}
|
||||
@@ -1,160 +0,0 @@
|
||||
{
|
||||
"name":
|
||||
{
|
||||
"message": "Генератор паролей",
|
||||
"description": "Extension name. Displayed in the manifest and pane header"
|
||||
},
|
||||
"description":
|
||||
{
|
||||
"message": "Расширение, позволяющее вам легко генерировать сложные и надежные пароли в один клик",
|
||||
"description": "Extension description"
|
||||
},
|
||||
"author":
|
||||
{
|
||||
"message": "Михаил \"XFox\" Гордеев",
|
||||
"description": "Author's name"
|
||||
},
|
||||
"generate":
|
||||
{
|
||||
"message": "Сгенерировать пароль",
|
||||
"description": "Main action label"
|
||||
},
|
||||
"success":
|
||||
{
|
||||
"message": "Пароль сгенерирован и скопирован в ваш буфер обмена",
|
||||
"description": "Message which is shown after successful creation of a password"
|
||||
},
|
||||
"fail":
|
||||
{
|
||||
"message": "Установлены неправильные настройки генератора. Ничего не было сгенерировано",
|
||||
"description": "Message which is shown after a creation of password failed"
|
||||
},
|
||||
"notEnoughChars":
|
||||
{
|
||||
"message": "При текущих параметрах длина создаваемого пароля должна быть не больше %MIN_CHARS% знаков",
|
||||
"description": "Message which is shown if there's no enough characters to create a password"
|
||||
},
|
||||
"lengthPrompt":
|
||||
{
|
||||
"message": "Укажите длину пароля. Нажмите ОК чтобы использовать длину по умолчанию (%LEN% символов)",
|
||||
"description": "A prompt message when user generates a password"
|
||||
},
|
||||
|
||||
"generatorOptions":
|
||||
{
|
||||
"message": "Настройки генератора",
|
||||
"description": "Generator options group header"
|
||||
},
|
||||
"length":
|
||||
{
|
||||
"message": "Длина пароля",
|
||||
"description": "Input field header"
|
||||
},
|
||||
"hint":
|
||||
{
|
||||
"message": "Рекомендованная длина пароля",
|
||||
"description": "Password length field hint"
|
||||
},
|
||||
"includeSymbols":
|
||||
{
|
||||
"message": "Включить символы (например:",
|
||||
"description": "Option checkbox label"
|
||||
},
|
||||
"includeNumbers":
|
||||
{
|
||||
"message": "Включить цифры (например:",
|
||||
"description": "Option checkbox label"
|
||||
},
|
||||
"includeLowercase":
|
||||
{
|
||||
"message": "Включить буквы нижнего регистра (например:",
|
||||
"description": "Option checkbox label"
|
||||
},
|
||||
"includeUppercase":
|
||||
{
|
||||
"message": "Включить буквы верхнего регистра (например:",
|
||||
"description": "Option checkbox label"
|
||||
},
|
||||
"excludeSimilar":
|
||||
{
|
||||
"message": "Исключить похожие символы (например:",
|
||||
"description": "Option checkbox label"
|
||||
},
|
||||
"excludeSpecial":
|
||||
{
|
||||
"message": "Исключить специальные символы (например:",
|
||||
"description": "Option checkbox label"
|
||||
},
|
||||
"dontRepeatChars":
|
||||
{
|
||||
"message": "Не повторять символы",
|
||||
"description": "Option checkbox label"
|
||||
},
|
||||
|
||||
"extOptions":
|
||||
{
|
||||
"message": "Настройки расширения",
|
||||
"description": "Extension options group header"
|
||||
},
|
||||
"showButton":
|
||||
{
|
||||
"message": "Добавлять кнопку \"Сгенерировать пароль\" рядом с каждым полем ввода пароля",
|
||||
"description": "Option checkbox label"
|
||||
},
|
||||
"showContext":
|
||||
{
|
||||
"message": "Добавить действие \"Сгенерировать пароль\" в контекстное меню браузера",
|
||||
"description": "Option checkbox label"
|
||||
},
|
||||
"hideAlert":
|
||||
{
|
||||
"message": "Не показывать сообщение при генерации пароля",
|
||||
"description": "Option checkbox label"
|
||||
},
|
||||
"promptForLength":
|
||||
{
|
||||
"message": "Спрашивать длину пароля перед генерацией",
|
||||
"description": "Option checkbox label"
|
||||
},
|
||||
"more":
|
||||
{
|
||||
"message": "Больше",
|
||||
"description": "About section toggle button caption"
|
||||
},
|
||||
|
||||
"about":
|
||||
{
|
||||
"message": "О расширении",
|
||||
"description": "About info header"
|
||||
},
|
||||
"developed":
|
||||
{
|
||||
"message": "Разработал:",
|
||||
"description": ""
|
||||
},
|
||||
"website":
|
||||
{
|
||||
"message": "Мой веб-сайт",
|
||||
"description": "My website link caption"
|
||||
},
|
||||
"github":
|
||||
{
|
||||
"message": "Исходный код",
|
||||
"description": "GitHub repo link caption"
|
||||
},
|
||||
"changelog":
|
||||
{
|
||||
"message": "Список изменений",
|
||||
"description": "Changelog link caption"
|
||||
},
|
||||
"feedback":
|
||||
{
|
||||
"message": "Оставить отзыв",
|
||||
"description": "Feedback CTA button"
|
||||
},
|
||||
"bmc":
|
||||
{
|
||||
"message": "Поддержать автора",
|
||||
"description": "BMC CTA button cation"
|
||||
}
|
||||
}
|
||||
@@ -1,238 +0,0 @@
|
||||
/* Fluent UI styles */
|
||||
@font-face
|
||||
{
|
||||
font-family: "Segoe UI";
|
||||
src: local("Segoe UI"),
|
||||
url("fonts/segoeui.ttf") format("truetype"),
|
||||
}
|
||||
|
||||
@font-face
|
||||
{
|
||||
font-family: "Segoe MDL2 Assets";
|
||||
src: local("Segoe MDL2 Assets"),
|
||||
url("fonts/segoemdl2.ttf") format("truetype"),
|
||||
}
|
||||
|
||||
body
|
||||
{
|
||||
font-family: "Segoe UI";
|
||||
user-select: none;
|
||||
white-space: nowrap;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: rgb(50, 49, 48);
|
||||
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
h1, h2, h3
|
||||
{
|
||||
font-weight: 400;
|
||||
margin: 0px;
|
||||
}
|
||||
h1
|
||||
{
|
||||
font-size: 28px;
|
||||
font-weight: 400;
|
||||
line-height: 36px;
|
||||
}
|
||||
|
||||
h2
|
||||
{
|
||||
font-size: 20px;
|
||||
font-weight: 400;
|
||||
line-height: 28px;
|
||||
}
|
||||
|
||||
.group
|
||||
{
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.group > *
|
||||
{
|
||||
display: block;
|
||||
}
|
||||
|
||||
button.accent, a.button
|
||||
{
|
||||
font-weight: 600;
|
||||
background-color: #0078d4;
|
||||
cursor: pointer;
|
||||
color: white !important;
|
||||
border: 1px solid #0078d4;
|
||||
border-radius: 2px;
|
||||
padding: 0px 16px;
|
||||
|
||||
height: 32px;
|
||||
min-width: 80px;
|
||||
text-align: center;
|
||||
outline: none !important;
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
button.accent:hover, a.button:hover
|
||||
{
|
||||
background-color: rgb(16, 110, 190);
|
||||
border: 1px solid rgb(16, 110, 190);
|
||||
}
|
||||
|
||||
button.accent:active, a.button:active
|
||||
{
|
||||
background-color: rgb(0, 90, 158);
|
||||
border: 1px solid rgb(0, 90, 158);
|
||||
}
|
||||
|
||||
a.button
|
||||
{
|
||||
display: inline-block;
|
||||
line-height: 28px;
|
||||
vertical-align: bottom;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
button.link, a, a:visited
|
||||
{
|
||||
color: rgb(0, 120, 212);
|
||||
background: none;
|
||||
outline: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
button.link:hover, a:hover, a:visited:hover
|
||||
{
|
||||
color: rgb(0, 69, 120);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
button.link:hover
|
||||
{
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
button.link:hover span
|
||||
{
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
button.link i
|
||||
{
|
||||
font-family: "Segoe MDL2 Assets";
|
||||
font-style: normal;
|
||||
font-size: 16px;
|
||||
margin-left: 4px;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
input[type=number]
|
||||
{
|
||||
padding-left: 9px;
|
||||
padding-right: 9px;
|
||||
height: 32px;
|
||||
width: 100%;
|
||||
|
||||
border: 1px solid rgb(96, 94, 92);
|
||||
box-sizing: border-box;
|
||||
outline: none;
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
input[type=number]:active, input[type=number]:focus
|
||||
{
|
||||
border: 2px solid rgb(0, 120, 212);
|
||||
}
|
||||
|
||||
p
|
||||
{
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
.control.checkbox
|
||||
{
|
||||
position: relative;
|
||||
padding-left: 32px;
|
||||
cursor: pointer;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.control.checkbox input
|
||||
{
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
.control.checkbox input + span
|
||||
{
|
||||
position: absolute;
|
||||
left: 0;
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
border: 1px solid rgb(50, 49, 48);
|
||||
border-radius: 2px;
|
||||
|
||||
transition-property: background, border, border-color;
|
||||
transition-duration: 200ms;
|
||||
transition-timing-function: cubic-bezier(0.4, 0, 0.23, 1);
|
||||
}
|
||||
.control.checkbox input + span::after
|
||||
{
|
||||
position: absolute;
|
||||
color: rgb(96, 94, 92);
|
||||
|
||||
font-family: "Segoe MDL2 Assets";
|
||||
content: "\E001";
|
||||
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
text-align: center;
|
||||
|
||||
display: none;
|
||||
}
|
||||
.control.checkbox input:checked + span::after
|
||||
{
|
||||
display: block;
|
||||
color: white;
|
||||
}
|
||||
.control.checkbox:hover input + span::after
|
||||
{
|
||||
display: block;
|
||||
}
|
||||
.control.checkbox input:checked + span
|
||||
{
|
||||
background-color: rgb(0, 120, 212);
|
||||
border-color: rgb(0, 120, 212);
|
||||
}
|
||||
.control.checkbox:hover input:checked + span
|
||||
{
|
||||
background-color: rgb(0, 90, 158);
|
||||
border-color: rgb(0, 90, 158);
|
||||
}
|
||||
|
||||
/* Custom CSS */
|
||||
.bmc
|
||||
{
|
||||
display: inline-grid !important;
|
||||
grid-template-columns: auto 1fr;
|
||||
grid-column-gap: 10px;
|
||||
align-items: center;
|
||||
|
||||
background-color: #FF813F !important;
|
||||
border-color: #FF813F !important;
|
||||
color: white !important;
|
||||
|
||||
text-decoration: none;
|
||||
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.bmc svg
|
||||
{
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.bmc span
|
||||
{
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
body, input
|
||||
{
|
||||
color: #EDEBE9;
|
||||
background-color: rgb(51, 51, 51);
|
||||
}
|
||||
|
||||
input[type=number]:active, input[type=number]:focus
|
||||
{
|
||||
border-color: rgb(40, 153, 245);
|
||||
}
|
||||
|
||||
button.link, a, a:visited
|
||||
{
|
||||
color: rgb(40, 153, 245);
|
||||
}
|
||||
button.link:hover, a:hover, a:visited:hover
|
||||
{
|
||||
color: #7DBAE9;
|
||||
}
|
||||
|
||||
.control.checkbox input + span
|
||||
{
|
||||
border-color: rgb(243, 242, 241);
|
||||
}
|
||||
.control.checkbox input:checked + span
|
||||
{
|
||||
background-color: rgb(40, 153, 245);
|
||||
border-color: rgb(40, 153, 245);
|
||||
}
|
||||
.control.checkbox:hover input:checked + span
|
||||
{
|
||||
background-color: rgb(108, 184, 246);
|
||||
border-color: rgb(108, 184, 246);
|
||||
}
|
||||
.control.checkbox:hover input + span:after
|
||||
{
|
||||
color: rgb(161, 159, 157);
|
||||
}
|
||||
.control.checkbox input:checked + span:after
|
||||
{
|
||||
color: rgb(27, 26, 25);
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
You may use the Segoe MDL2 Assets and Segoe UI fonts or glyphs included in this file (“Software”) solely to design, develop and test your programs for Microsoft Office. Microsoft Office includes but is not limited to any software product or service branded by trademark, trade dress, copyright or some other recognized means, as a product or service of Microsoft Office. This license does not grant you the right to distribute or sublicense all or part of the Software to any third party. By using the Software you agree to these terms. If you do not agree to these terms, do not use the Segoe MDL2 and Segoe UI Fonts or Glyphs.
|
||||
@@ -1,35 +0,0 @@
|
||||
function AddContextMenu()
|
||||
{
|
||||
chrome.contextMenus.create(
|
||||
{
|
||||
id: "generate",
|
||||
contexts: [ "all" ],
|
||||
title: chrome.i18n.getMessage("generate")
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Setting up context menu event listener
|
||||
chrome.contextMenus.onClicked.addListener(() => GeneratePassword(null));
|
||||
|
||||
// Adding context menu entry if needed
|
||||
chrome.runtime.onInstalled.addListener(() =>
|
||||
{
|
||||
// Adding context menu option
|
||||
chrome.storage.sync.get({ showContext: true }, (settings) =>
|
||||
{
|
||||
if (settings.showContext !== false)
|
||||
AddContextMenu();
|
||||
});
|
||||
});
|
||||
|
||||
chrome.storage.onChanged.addListener((changes, area) =>
|
||||
{
|
||||
if (area != "sync" || changes["showContext"] == null)
|
||||
return;
|
||||
|
||||
if (changes["showContext"].newValue === false)
|
||||
chrome.contextMenus.removeAll();
|
||||
else
|
||||
AddContextMenu();
|
||||
});
|
||||
@@ -1,73 +0,0 @@
|
||||
// Loading input fields states
|
||||
chrome.storage.sync.get(
|
||||
{
|
||||
// Set of settings keys we retrieve and their default values
|
||||
// Generator settings
|
||||
length: 16,
|
||||
includeSymbols: true,
|
||||
includeNumbers: true,
|
||||
includeLowercase: true,
|
||||
includeUppercase: true,
|
||||
excludeSimilar: true,
|
||||
excludeSpecial: true,
|
||||
dontRepeatChars: false,
|
||||
|
||||
// Extension settings
|
||||
showButton: true,
|
||||
showContext: true,
|
||||
hideAlert: false,
|
||||
promptForLength: false
|
||||
},
|
||||
(settings) =>
|
||||
{
|
||||
if (window.matchMedia("(prefers-color-scheme: dark)").matches) // Doesn't work on Fiefox
|
||||
document.querySelector("#darkStylesheet").removeAttribute("disabled");
|
||||
|
||||
document.querySelector("#length").value = settings.length; // Setting length value
|
||||
|
||||
// Setting checkboxes
|
||||
[
|
||||
"includeSymbols",
|
||||
"includeNumbers",
|
||||
"includeLowercase",
|
||||
"includeUppercase",
|
||||
"excludeSimilar",
|
||||
"excludeSpecial",
|
||||
"dontRepeatChars",
|
||||
|
||||
"showButton",
|
||||
"showContext",
|
||||
"hideAlert",
|
||||
"promptForLength"
|
||||
].forEach(i => document.querySelector("#" + i).checked = settings[i]);
|
||||
|
||||
SetupEventHandlers();
|
||||
document.querySelector("#version").textContent = "v" + chrome.runtime.getManifest()["version"]; // Updating display version
|
||||
document.querySelectorAll("*[loc]").forEach(i => i.textContent = chrome.i18n.getMessage(i.getAttribute("loc"))); // Updating localization
|
||||
});
|
||||
|
||||
function SetupEventHandlers()
|
||||
{
|
||||
document.querySelectorAll("input").forEach(i =>
|
||||
i.addEventListener(
|
||||
"input",
|
||||
() => chrome.storage.sync.set(JSON.parse("{ \"" + i.id + "\": " + (i.type == "checkbox" ? i.checked : i.value) + " }"))
|
||||
)
|
||||
);
|
||||
|
||||
document.querySelector("#generate").addEventListener("click", () => GeneratePassword(null, true));
|
||||
document.querySelector("#more").addEventListener("click", (s) =>
|
||||
{
|
||||
let group = document.querySelector("#about");
|
||||
if (group.hasAttribute("hidden"))
|
||||
{
|
||||
group.removeAttribute("hidden");
|
||||
s.currentTarget.querySelector("i").textContent = "\uE010";
|
||||
}
|
||||
else
|
||||
{
|
||||
group.setAttribute("hidden", "");
|
||||
s.currentTarget.querySelector("i").textContent = "\uE011";
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1,156 +0,0 @@
|
||||
// Some constants
|
||||
const upperCase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
const lowerCase = upperCase.toLowerCase();
|
||||
const numbers = "1234567890";
|
||||
const specialCharacters = "!#$%&*+-=?@^_";
|
||||
const ambiguousCharacters = "{}[]()/\\'\"`~,;:.<>";
|
||||
const similarCharacters = "il1Lo0O";
|
||||
|
||||
chrome.storage.sync.get({ showButton: true },
|
||||
(settings) =>
|
||||
{
|
||||
if (settings.showButton)
|
||||
InsertButtons();
|
||||
}
|
||||
);
|
||||
|
||||
// Adding button below every password field
|
||||
function InsertButtons()
|
||||
{
|
||||
document.querySelectorAll("input[type=password]").forEach(i =>
|
||||
{
|
||||
let actionLink = document.createElement("a");
|
||||
|
||||
actionLink.innerText = chrome.i18n.getMessage("generate");
|
||||
actionLink.style.margin = "5px";
|
||||
actionLink.style.display = "block";
|
||||
|
||||
// Since anchor without 'href' attrubute isn't shown as a hyperlink and '#' link potentially can break some sites logic, we add empty JS function
|
||||
actionLink.href = "javascript:void(0);";
|
||||
|
||||
actionLink.addEventListener("click", GeneratePassword);
|
||||
|
||||
i.insertAdjacentElement("afterend", actionLink); // Adding button after a password field
|
||||
});
|
||||
}
|
||||
|
||||
function GeneratePassword(e, useDefaultLength = false)
|
||||
{
|
||||
// Generating password
|
||||
let availableCharacters = ""; // Set of available characters to generate a password from
|
||||
|
||||
chrome.storage.sync.get(
|
||||
{
|
||||
length: 16,
|
||||
includeSymbols: true,
|
||||
includeNumbers: true,
|
||||
includeLowercase: true,
|
||||
includeUppercase: true,
|
||||
excludeSimilar: true,
|
||||
excludeSpecial: true,
|
||||
hideAlert: false,
|
||||
promptForLength: false,
|
||||
dontRepeatChars: false,
|
||||
},
|
||||
(settings) =>
|
||||
{
|
||||
// Adding or excluding characters from the set
|
||||
if (settings.includeSymbols)
|
||||
availableCharacters += specialCharacters;
|
||||
if (settings.includeNumbers)
|
||||
availableCharacters += numbers;
|
||||
if (settings.includeLowercase)
|
||||
availableCharacters += lowerCase;
|
||||
if (settings.includeUppercase)
|
||||
availableCharacters += upperCase;
|
||||
if (settings.excludeSimilar)
|
||||
similarCharacters.split("").forEach(i => availableCharacters = availableCharacters.replace(i, ""));
|
||||
if (settings.excludeSpecial === false)
|
||||
availableCharacters += ambiguousCharacters;
|
||||
|
||||
if (availableCharacters.length < 1)
|
||||
{
|
||||
alert(chrome.i18n.getMessage("fail"));
|
||||
return;
|
||||
}
|
||||
|
||||
var pwdLength = settings.length;
|
||||
if (settings.promptForLength && !useDefaultLength)
|
||||
while(true)
|
||||
{
|
||||
var response = prompt(chrome.i18n.getMessage("lengthPrompt").replace("%LEN%", settings.length));
|
||||
if (response === null) // If user clicked 'Cancel'
|
||||
return;
|
||||
|
||||
if (parseInt(response) && response >= 6)
|
||||
{
|
||||
pwdLength = response;
|
||||
break;
|
||||
}
|
||||
else if (!response) // Continue with default length if no response is provided. Try again if input is invalid
|
||||
break;
|
||||
}
|
||||
|
||||
if (settings.dontRepeatChars && availableCharacters.length < pwdLength)
|
||||
{
|
||||
alert(chrome.i18n.getMessage("notEnoughChars").replace("%MIN_CHARS%", availableCharacters.length));
|
||||
return;
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
var password = "";
|
||||
var leftCharacters = availableCharacters;
|
||||
for (k = 0; k < pwdLength; k++)
|
||||
{
|
||||
password += leftCharacters[GetRandomInt(0, leftCharacters.length)]; // Picking random characters
|
||||
if (settings.dontRepeatChars)
|
||||
leftCharacters = leftCharacters.replace(password[k], "");
|
||||
}
|
||||
}
|
||||
while (!((!settings.includeSymbols || ContainsAny(password, specialCharacters)) &&
|
||||
(!settings.includeNumbers || ContainsAny(password, numbers)) &&
|
||||
(!settings.includeLowercase || ContainsAny(password, lowerCase)) &&
|
||||
(!settings.includeUppercase || ContainsAny(password, upperCase))));
|
||||
|
||||
let field = e?.target.previousElementSibling;
|
||||
// Creating a hidden field if called as standalone
|
||||
if (!field)
|
||||
{
|
||||
field = document.createElement("input");
|
||||
document.body.appendChild(field);
|
||||
}
|
||||
|
||||
field.value = password; // Setting generated password to the field
|
||||
|
||||
field.setAttribute("type", "text"); // Since we cannot copy text from a password field, we'll make it temporarly simple field
|
||||
|
||||
// Some JS clipboard copying stuff
|
||||
field.select();
|
||||
document.execCommand("copy");
|
||||
|
||||
// Setting field type back to 'password'
|
||||
field.setAttribute("type", "password");
|
||||
|
||||
if (!e)
|
||||
field.remove();
|
||||
|
||||
if (settings.hideAlert === false)
|
||||
alert(chrome.i18n.getMessage("success"));
|
||||
});
|
||||
}
|
||||
|
||||
// See https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Math/random
|
||||
function GetRandomInt(min, max)
|
||||
{
|
||||
return Math.floor(Math.random() * (max - min)) + min;
|
||||
}
|
||||
|
||||
function ContainsAny(array1, array2)
|
||||
{
|
||||
for(var k = 0; k < array2.length; k++)
|
||||
if (array1.includes(array2[k]))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
{
|
||||
"name": "__MSG_name__",
|
||||
"version": "1.3",
|
||||
"manifest_version": 2,
|
||||
"description": "__MSG_description__",
|
||||
"author": "__MSG_author__",
|
||||
"default_locale": "en",
|
||||
|
||||
"permissions":
|
||||
[
|
||||
"storage",
|
||||
"<all_urls>",
|
||||
"contextMenus",
|
||||
"clipboardWrite"
|
||||
],
|
||||
|
||||
"content_scripts":
|
||||
[
|
||||
{
|
||||
"matches": [ "<all_urls>" ],
|
||||
"js": [ "js/script.js" ],
|
||||
"run_at": "document_idle",
|
||||
"all_frames": true
|
||||
}
|
||||
],
|
||||
"background":
|
||||
{
|
||||
"scripts": [ "js/background.js", "js/script.js" ],
|
||||
"persistent": false
|
||||
},
|
||||
|
||||
"icons":
|
||||
{
|
||||
"128": "icons/icon-128.png",
|
||||
"48": "icons/icon-48.png",
|
||||
"32": "icons/icon-32.png",
|
||||
"16": "icons/icon-16.png"
|
||||
},
|
||||
"browser_action":
|
||||
{
|
||||
"default_icon": "icons/icon-32.png",
|
||||
"default_popup": "options.html"
|
||||
},
|
||||
|
||||
"browser_specific_settings":
|
||||
{
|
||||
"gecko":
|
||||
{
|
||||
"id": "passwordgenerator@xfox111.net",
|
||||
"strict_min_version": "58.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"name": "password-generator",
|
||||
"version": "3.1.4",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build",
|
||||
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fluentui/react-components": "^9.53.0",
|
||||
"@fluentui/react-icons": "^2.0.242",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.3.3",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@types/webextension-polyfill": "^0.10.7",
|
||||
"@typescript-eslint/eslint-plugin": "^7.0.0",
|
||||
"@typescript-eslint/parser": "^6.21.0",
|
||||
"@vitejs/plugin-react-swc": "^3.7.0",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-plugin-react": "^7.34.2",
|
||||
"eslint-plugin-react-hooks": "^4.6.2",
|
||||
"eslint-plugin-react-refresh": "^0.4.7",
|
||||
"typescript": "^5.4.5",
|
||||
"vite": "^5.2.12",
|
||||
"vite-plugin-static-copy": "^1.0.5",
|
||||
"vite-plugin-svgr": "^4.2.0",
|
||||
"vite-plugin-web-extension": "^4.1.4",
|
||||
"webextension-polyfill": "^0.12.0"
|
||||
},
|
||||
"resolutions": {
|
||||
"scheduler": "^0.20.0",
|
||||
"@floating-ui/dom": "^1.5.4"
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 101 KiB After Width: | Height: | Size: 101 KiB |
|
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 6.4 KiB |
|
Before Width: | Height: | Size: 641 B After Width: | Height: | Size: 641 B |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
@@ -0,0 +1,30 @@
|
||||
import { makeStyles, shorthands, tokens } from "@fluentui/react-components";
|
||||
|
||||
export const useStyles = makeStyles({
|
||||
root:
|
||||
{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
...shorthands.padding(tokens.spacingVerticalM, tokens.spacingHorizontalS),
|
||||
},
|
||||
spinner:
|
||||
{
|
||||
alignSelf: "center",
|
||||
...shorthands.margin(tokens.spacingVerticalXXXL, 0),
|
||||
},
|
||||
accordionAnimation:
|
||||
{
|
||||
"> .fui-AccordionItem .fui-AccordionHeader__expandIcon > svg":
|
||||
{
|
||||
transitionProperty: "transform",
|
||||
transitionDuration: tokens.durationNormal,
|
||||
transitionTimingFunction: tokens.curveDecelerateMax,
|
||||
},
|
||||
"> .fui-AccordionItem > .fui-AccordionPanel":
|
||||
{
|
||||
animationName: "fadeIn",
|
||||
animationDuration: tokens.durationSlow,
|
||||
animationTimingFunction: tokens.curveDecelerateMin,
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,35 @@
|
||||
import { Accordion, FluentProvider, Spinner } from "@fluentui/react-components";
|
||||
import { useStyles } from "./App.styles";
|
||||
import AboutSection from "./Components/AboutSection";
|
||||
import GeneratorView from "./Components/GeneratorView";
|
||||
import SettingsSection from "./Components/SettingsSection";
|
||||
import Specials from "./Specials/Specials";
|
||||
import { StorageProvider } from "./Utils/Storage";
|
||||
import { useTheme } from "./Utils/Theme";
|
||||
import { useState } from "react";
|
||||
|
||||
export default function App(): JSX.Element
|
||||
{
|
||||
const theme = useTheme();
|
||||
const cls = useStyles();
|
||||
const [selection, setSelection] = useState<string[]>([]);
|
||||
|
||||
return (
|
||||
<FluentProvider theme={ theme }>
|
||||
<main className={ cls.root }>
|
||||
<StorageProvider loader={ <Spinner size="large" className={ cls.spinner } /> }>
|
||||
<GeneratorView collapse={ selection.includes("settings") } />
|
||||
<Accordion
|
||||
openItems={ selection }
|
||||
onToggle={ (_, e) => setSelection(e.openItems as string[]) }
|
||||
collapsible
|
||||
className={ cls.accordionAnimation }>
|
||||
<SettingsSection />
|
||||
<AboutSection />
|
||||
</Accordion>
|
||||
</StorageProvider>
|
||||
<Specials />
|
||||
</main>
|
||||
</FluentProvider>
|
||||
);
|
||||
}
|
||||
|
After Width: | Height: | Size: 8.0 KiB |
@@ -0,0 +1,16 @@
|
||||
import { makeStyles, shorthands, tokens } from "@fluentui/react-components";
|
||||
|
||||
export const useStyles = makeStyles({
|
||||
root:
|
||||
{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
...shorthands.gap(tokens.spacingVerticalM),
|
||||
paddingBottom: tokens.spacingVerticalS,
|
||||
},
|
||||
horizontalContainer:
|
||||
{
|
||||
display: "flex",
|
||||
...shorthands.gap(tokens.spacingHorizontalSNudge),
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,66 @@
|
||||
import * as fui from "@fluentui/react-components";
|
||||
import { InfoRegular, PersonFeedbackRegular } from "@fluentui/react-icons";
|
||||
import Package from "../../package.json";
|
||||
import BuyMeACoffee from "../Assets/BuyMeACoffee.svg?react";
|
||||
import { bmcDarkTheme, bmcLightTheme } from "../Data/BmcTheme";
|
||||
import { GetFeedbackLink, GithubLink, PersonalLink } from "../Data/Links";
|
||||
import { GetLocaleString as loc } from "../Utils/Localization";
|
||||
import { useTheme } from "../Utils/Theme";
|
||||
import { useStyles } from "./AboutSection.styles";
|
||||
|
||||
export default function AboutSection(): JSX.Element
|
||||
{
|
||||
const bmcTheme = useTheme(bmcLightTheme, bmcDarkTheme);
|
||||
const cls = useStyles();
|
||||
|
||||
const link = (text: string, href: string): JSX.Element => (
|
||||
<fui.Link target="_blank" href={ href }>{ text }</fui.Link>
|
||||
);
|
||||
|
||||
const buttonProps = (href: string, icon: JSX.Element): fui.ButtonProps => (
|
||||
{
|
||||
as: "a", target: "_blank", href,
|
||||
appearance: "primary", icon
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<fui.AccordionItem value="about">
|
||||
<fui.AccordionHeader as="h2" icon={ <InfoRegular /> }>{ loc("about@title") }</fui.AccordionHeader>
|
||||
<fui.AccordionPanel className={ cls.root }>
|
||||
<header className={ cls.horizontalContainer }>
|
||||
<fui.Subtitle1 as="h1">{ loc("name") }</fui.Subtitle1>
|
||||
<fui.Caption1 as="span">v{ Package.version }</fui.Caption1>
|
||||
</header>
|
||||
|
||||
<fui.Text as="p">
|
||||
{ loc("about@developedBy") } ({ link("@xfox111", PersonalLink.Twitter) })
|
||||
<br />
|
||||
{ loc("about@licensedUnder") } { link(loc("about@mitLicense"), GithubLink.License) }
|
||||
</fui.Text>
|
||||
|
||||
<fui.Text as="p">
|
||||
{ loc("about@translationCta") }<br />
|
||||
{ link(loc("about@translationCtaButton"), GithubLink.TranslationGuide) }
|
||||
</fui.Text>
|
||||
|
||||
<fui.Text as="p">
|
||||
{ link(loc("about@website"), PersonalLink.Website) } <br />
|
||||
{ link(loc("about@sourceCode"), GithubLink.Repository) } <br />
|
||||
{ link(loc("about@changelog"), GithubLink.Changelog) }
|
||||
</fui.Text>
|
||||
|
||||
<div className={ cls.horizontalContainer }>
|
||||
<fui.Button { ...buttonProps(GetFeedbackLink(), <PersonFeedbackRegular />) }>
|
||||
{ loc("about@feedback") }
|
||||
</fui.Button>
|
||||
<fui.FluentProvider theme={ bmcTheme }>
|
||||
<fui.Button { ...buttonProps(PersonalLink.BuyMeACoffee, <BuyMeACoffee />) }>
|
||||
{ loc("about@sponsor") }
|
||||
</fui.Button>
|
||||
</fui.FluentProvider>
|
||||
</div>
|
||||
</fui.AccordionPanel>
|
||||
</fui.AccordionItem>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import { makeStyles, shorthands, tokens } from "@fluentui/react-components";
|
||||
|
||||
export const useStyles = makeStyles({
|
||||
root:
|
||||
{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
},
|
||||
input:
|
||||
{
|
||||
fontFamily: tokens.fontFamilyMonospace,
|
||||
},
|
||||
lengthContainer:
|
||||
{
|
||||
display: "grid",
|
||||
gridTemplateColumns: "1fr auto",
|
||||
alignItems: "center",
|
||||
paddingRight: tokens.spacingHorizontalM,
|
||||
},
|
||||
copyIcon:
|
||||
{
|
||||
animationName: "scaleUpIn",
|
||||
animationDuration: tokens.durationSlow,
|
||||
animationTimingFunction: tokens.curveEasyEaseMax,
|
||||
},
|
||||
refreshIcon:
|
||||
{
|
||||
animationName: "spin",
|
||||
animationDuration: tokens.durationSlow,
|
||||
animationTimingFunction: tokens.curveEasyEaseMax,
|
||||
},
|
||||
msgBar:
|
||||
{
|
||||
...shorthands.padding(tokens.spacingVerticalMNudge, tokens.spacingHorizontalM),
|
||||
},
|
||||
options:
|
||||
{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
...shorthands.padding(tokens.spacingVerticalS, tokens.spacingHorizontalS),
|
||||
},
|
||||
characterOptionsContainer:
|
||||
{
|
||||
display: "flex",
|
||||
...shorthands.gap(tokens.spacingHorizontalXS),
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,171 @@
|
||||
import * as fui from "@fluentui/react-components";
|
||||
import * as Icons from "@fluentui/react-icons";
|
||||
import { useEffect, useState } from "react";
|
||||
import GeneratorOptions from "../Models/GeneratorOptions";
|
||||
import { GetLocaleString as loc } from "../Utils/Localization";
|
||||
import { GeneratePassword } from "../Utils/PasswordGenerator";
|
||||
import { useStorage } from "../Utils/Storage";
|
||||
import { useTimeout } from "../Utils/Timeout";
|
||||
import { useStyles } from "./GeneratorView.styles";
|
||||
|
||||
export default function GeneratorView(props: { collapse: boolean; }): JSX.Element
|
||||
{
|
||||
const { generatorOptions, extOptions } = useStorage();
|
||||
const [password, setPassword] = useState<string>("");
|
||||
const [quickOpts, _setOpts] = useState<GeneratorOptions>(generatorOptions);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const [refreshTimer, copyTimer] = [useTimeout(310), useTimeout(1000)];
|
||||
const checkedOptions = Object.keys(quickOpts).filter(k => quickOpts[k as keyof GeneratorOptions] as boolean);
|
||||
const cls = useStyles();
|
||||
|
||||
const IncludeIcon: Icons.FluentIcon = Icons.bundleIcon(Icons.AddCircleFilled, Icons.AddCircleRegular);
|
||||
const ExcludeIcon: Icons.FluentIcon = Icons.bundleIcon(Icons.SubtractCircleFilled, Icons.SubtractCircleRegular);
|
||||
|
||||
const resetOptions = (): void =>
|
||||
_setOpts(generatorOptions);
|
||||
|
||||
const setOptions = (opts: Partial<GeneratorOptions>) =>
|
||||
_setOpts({ ...quickOpts, ...opts });
|
||||
|
||||
function RefreshPassword(): void
|
||||
{
|
||||
setError(null);
|
||||
const options: GeneratorOptions = { ...generatorOptions, ...quickOpts };
|
||||
|
||||
try { setPassword(GeneratePassword(options)); }
|
||||
catch (e) { setError((e as Error).message); }
|
||||
}
|
||||
|
||||
async function CopyPassword(): Promise<void>
|
||||
{
|
||||
await window.navigator.clipboard.writeText(password);
|
||||
copyTimer.trigger();
|
||||
}
|
||||
|
||||
function OnCheckedValueChange(_: unknown, e: fui.MenuCheckedValueChangeData): void
|
||||
{
|
||||
const opts: Partial<Omit<GeneratorOptions, "Length">> = {};
|
||||
|
||||
let keys = Object.keys(quickOpts).filter(i => i !== "Length") as (keyof Omit<GeneratorOptions, "Length">)[];
|
||||
|
||||
if (e.name === "include")
|
||||
keys = keys.filter(i => !i.startsWith("Exclude"));
|
||||
else
|
||||
keys = keys.filter(i => i.startsWith("Exclude"));
|
||||
|
||||
for (const key of keys)
|
||||
opts[key] = e.checkedItems.includes(key);
|
||||
|
||||
setOptions(opts);
|
||||
}
|
||||
|
||||
useEffect(resetOptions, [generatorOptions]);
|
||||
useEffect(RefreshPassword, [generatorOptions, quickOpts]);
|
||||
useEffect(refreshTimer.trigger, [password]);
|
||||
|
||||
return (
|
||||
<section className={ cls.root }>
|
||||
{ error ?
|
||||
<fui.MessageBar intent="warning" className={ cls.msgBar }>
|
||||
<fui.MessageBarBody>{ error }</fui.MessageBarBody>
|
||||
</fui.MessageBar>
|
||||
:
|
||||
<fui.Input
|
||||
className={ cls.input }
|
||||
readOnly value={ password }
|
||||
contentAfter={ <>
|
||||
<fui.Tooltip content={ loc("generator@copy") } relationship="label">
|
||||
<fui.Button
|
||||
appearance="subtle" onClick={ CopyPassword }
|
||||
icon={
|
||||
copyTimer.isActive ?
|
||||
<Icons.CheckmarkRegular className={ cls.copyIcon } /> :
|
||||
<Icons.CopyRegular className={ cls.copyIcon } />
|
||||
} />
|
||||
</fui.Tooltip>
|
||||
|
||||
<fui.Tooltip content={ loc("generator@refresh") } relationship="label">
|
||||
<fui.Button
|
||||
appearance="subtle" onClick={ RefreshPassword }
|
||||
icon={
|
||||
<Icons.ArrowClockwiseRegular className={ fui.mergeClasses(refreshTimer.isActive && cls.refreshIcon) } />
|
||||
} />
|
||||
</fui.Tooltip>
|
||||
</> } />
|
||||
}
|
||||
|
||||
{ !props.collapse &&
|
||||
<div className={ cls.options }>
|
||||
<fui.InfoLabel info={ loc("generator@quickOptions__hint") }>
|
||||
{ loc("generator@quickOptions") }
|
||||
</fui.InfoLabel>
|
||||
|
||||
<div className={ cls.lengthContainer }>
|
||||
<fui.Slider
|
||||
min={ extOptions.MinLength } max={ Math.max(extOptions.MaxLength, generatorOptions.Length) }
|
||||
value={ quickOpts.Length } onChange={ (_, e) => setOptions({ Length: e.value }) } />
|
||||
<fui.Text>{ quickOpts.Length }</fui.Text>
|
||||
</div>
|
||||
|
||||
<div className={ cls.characterOptionsContainer }>
|
||||
<fui.Menu
|
||||
positioning="after" hasCheckmarks
|
||||
checkedValues={ { include: checkedOptions } }
|
||||
onCheckedValueChange={ OnCheckedValueChange }>
|
||||
|
||||
<fui.MenuTrigger disableButtonEnhancement>
|
||||
<fui.MenuButton appearance="subtle" icon={ <IncludeIcon /> }>{ loc("generator@include") }</fui.MenuButton>
|
||||
</fui.MenuTrigger>
|
||||
|
||||
<fui.MenuPopover>
|
||||
<fui.MenuList>
|
||||
<fui.MenuItemCheckbox name="include" value="Uppercase" icon={ <Icons.TextCaseUppercaseRegular /> }>
|
||||
{ loc("settings@uppercase") }
|
||||
</fui.MenuItemCheckbox>
|
||||
<fui.MenuItemCheckbox name="include" value="Lowercase" icon={ <Icons.TextCaseLowercaseRegular /> }>
|
||||
{ loc("settings@lowercase") }
|
||||
</fui.MenuItemCheckbox>
|
||||
<fui.MenuItemCheckbox name="include" value="Numeric" icon={ <Icons.NumberSymbolRegular /> }>
|
||||
{ loc("settings@numeric") }
|
||||
</fui.MenuItemCheckbox>
|
||||
<fui.MenuItemCheckbox name="include" value="Special" icon={ <Icons.MathSymbolsRegular /> }>
|
||||
{ loc("settings@special") }
|
||||
</fui.MenuItemCheckbox>
|
||||
</fui.MenuList>
|
||||
</fui.MenuPopover>
|
||||
</fui.Menu>
|
||||
|
||||
<fui.Menu
|
||||
positioning="before"
|
||||
checkedValues={ { exclude: checkedOptions } }
|
||||
onCheckedValueChange={ OnCheckedValueChange }>
|
||||
|
||||
<fui.MenuTrigger disableButtonEnhancement>
|
||||
<fui.MenuButton appearance="subtle" icon={ <ExcludeIcon /> }>{ loc("generator@exclude") }</fui.MenuButton>
|
||||
</fui.MenuTrigger>
|
||||
|
||||
<fui.MenuPopover>
|
||||
<fui.MenuList>
|
||||
<fui.MenuItemCheckbox name="exclude" value="ExcludeSimilar">
|
||||
{ loc("settings@similar") }
|
||||
</fui.MenuItemCheckbox>
|
||||
<fui.MenuItemCheckbox name="exclude" value="ExcludeAmbiguous" disabled={ !quickOpts.Special }>
|
||||
{ loc("settings@ambiguous") }
|
||||
</fui.MenuItemCheckbox>
|
||||
<fui.MenuItemCheckbox name="exclude" value="ExcludeRepeating">
|
||||
{ loc("settings@repeating") }
|
||||
</fui.MenuItemCheckbox>
|
||||
</fui.MenuList>
|
||||
</fui.MenuPopover>
|
||||
</fui.Menu>
|
||||
|
||||
<fui.Tooltip content={ loc("generator@reset") } relationship="label">
|
||||
<fui.Button appearance="subtle" icon={ <Icons.ArrowUndoRegular /> } onClick={ resetOptions } />
|
||||
</fui.Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { makeStyles, shorthands, tokens } from "@fluentui/react-components";
|
||||
|
||||
export const useStyles = makeStyles({
|
||||
root:
|
||||
{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
...shorthands.gap(tokens.spacingVerticalS),
|
||||
},
|
||||
checkboxContainer:
|
||||
{
|
||||
display: "flex",
|
||||
flexWrap: "wrap",
|
||||
},
|
||||
rangeContainer:
|
||||
{
|
||||
display: "grid",
|
||||
gridTemplateColumns: "1fr auto 1fr auto",
|
||||
alignItems: "center",
|
||||
...shorthands.gap(tokens.spacingHorizontalS),
|
||||
},
|
||||
rangeInput:
|
||||
{
|
||||
width: "100%",
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,122 @@
|
||||
import * as fui from "@fluentui/react-components";
|
||||
import { ArrowUndoRegular, SettingsRegular } from "@fluentui/react-icons";
|
||||
import ExtensionOptions from "../Models/ExtensionOptions";
|
||||
import GeneratorOptions from "../Models/GeneratorOptions";
|
||||
import { GetLocaleString as loc } from "../Utils/Localization";
|
||||
import { CharacterHints } from "../Utils/PasswordGenerator";
|
||||
import { useStorage } from "../Utils/Storage";
|
||||
import { useStyles } from "./SettingsSection.styles";
|
||||
|
||||
// FIXME: Remove ts-ignore comments once slots override fix is released
|
||||
// Tracker: https://github.com/microsoft/fluentui/issues/27090
|
||||
|
||||
export default function SettingsSection(): JSX.Element
|
||||
{
|
||||
const { extOptions, generatorOptions, updateStorage } = useStorage();
|
||||
const cls = useStyles();
|
||||
|
||||
const infoLabel = (content: string, hint: string) => ({
|
||||
children: (_: unknown, slotProps: fui.LabelProps) => (
|
||||
<fui.InfoLabel { ...slotProps } info={ hint }>{ content }</fui.InfoLabel>
|
||||
)
|
||||
});
|
||||
|
||||
const setOption = (option: keyof (GeneratorOptions & ExtensionOptions)) =>
|
||||
(_: unknown, args: fui.CheckboxOnChangeData) =>
|
||||
updateStorage({ [option]: args.checked } );
|
||||
|
||||
const updateNumberField = (key: keyof (ExtensionOptions & GeneratorOptions), defaultValue: number) =>
|
||||
(_: unknown, e: fui.InputOnChangeData): void =>
|
||||
{
|
||||
if (e.value.length < 1)
|
||||
{
|
||||
updateStorage({ [key]: defaultValue });
|
||||
return;
|
||||
}
|
||||
|
||||
const value = parseInt(e.value);
|
||||
|
||||
if (!isNaN(value) && value >= 0)
|
||||
updateStorage({ [key]: value });
|
||||
};
|
||||
|
||||
return (
|
||||
<fui.AccordionItem value="settings">
|
||||
<fui.AccordionHeader as="h2" icon={ <SettingsRegular /> }>{ loc("settings@title") }</fui.AccordionHeader>
|
||||
|
||||
<fui.AccordionPanel className={ cls.root }>
|
||||
|
||||
<fui.Field label={ loc("settings@length") } hint={ loc("settings@length__hint") }>
|
||||
<fui.Input
|
||||
value={ generatorOptions.Length.toString() }
|
||||
onChange={ updateNumberField("Length", 0) } />
|
||||
</fui.Field>
|
||||
|
||||
<fui.Field label={ loc("settings@lengthRange") }>
|
||||
<div className={ cls.rangeContainer }>
|
||||
<fui.Input
|
||||
input={ { className: cls.rangeInput } }
|
||||
value={ extOptions.MinLength.toString() }
|
||||
onChange={ updateNumberField("MinLength", 4) } />
|
||||
|
||||
<fui.Divider />
|
||||
|
||||
<fui.Input
|
||||
input={ { className: cls.rangeInput } }
|
||||
value={ extOptions.MaxLength.toString() }
|
||||
onChange={ updateNumberField("MaxLength", 32) } />
|
||||
|
||||
<fui.Tooltip relationship="label" content={ loc("generator@reset") }>
|
||||
<fui.Button
|
||||
appearance="subtle" icon={ <ArrowUndoRegular /> }
|
||||
onClick={ () => updateStorage({ MinLength: 6, MaxLength: 32 }) } />
|
||||
</fui.Tooltip>
|
||||
</div>
|
||||
</fui.Field>
|
||||
|
||||
<fui.Divider />
|
||||
|
||||
<fui.Text>{ loc("settings@include") }</fui.Text>
|
||||
<div className={ cls.checkboxContainer }>
|
||||
<fui.Checkbox label={ loc("settings@uppercase") }
|
||||
checked={ generatorOptions.Uppercase }
|
||||
onChange={ setOption("Uppercase") } />
|
||||
<fui.Checkbox
|
||||
label={ loc("settings@lowercase") }
|
||||
checked={ generatorOptions.Lowercase }
|
||||
onChange={ setOption("Lowercase") } />
|
||||
<fui.Checkbox
|
||||
label={ loc("settings@numeric") }
|
||||
checked={ generatorOptions.Numeric }
|
||||
onChange={ setOption("Numeric") } />
|
||||
<fui.Checkbox
|
||||
label={ loc("settings@special") }
|
||||
checked={ generatorOptions.Special }
|
||||
onChange={ setOption("Special") } />
|
||||
</div>
|
||||
|
||||
<fui.Text>{ loc("settings@exclude") }</fui.Text>
|
||||
<div className={ cls.checkboxContainer }>
|
||||
<fui.Checkbox
|
||||
// @ts-expect-error See line 11
|
||||
label={ infoLabel(loc("settings@similar"), CharacterHints.Similar) }
|
||||
checked={ generatorOptions.ExcludeSimilar }
|
||||
onChange={ setOption("ExcludeSimilar") } />
|
||||
<fui.Checkbox
|
||||
// @ts-expect-error See line 11
|
||||
label={ infoLabel(loc("settings@ambiguous"), CharacterHints.Ambiguous) }
|
||||
disabled={ !generatorOptions.Special }
|
||||
checked={ generatorOptions.ExcludeAmbiguous }
|
||||
onChange={ setOption("ExcludeAmbiguous") } />
|
||||
<fui.Checkbox
|
||||
// @ts-expect-error See line 11
|
||||
label={ infoLabel(loc("settings@repeating"), loc("settings@repeating__hint")) }
|
||||
checked={ generatorOptions.ExcludeRepeating }
|
||||
onChange={ setOption("ExcludeRepeating") } />
|
||||
</div>
|
||||
|
||||
</fui.AccordionPanel>
|
||||
|
||||
</fui.AccordionItem>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { BrandVariants, Theme, createDarkTheme, createLightTheme } from "@fluentui/react-components";
|
||||
|
||||
const bmcBrandRamp: BrandVariants =
|
||||
{
|
||||
"10": "#050201",
|
||||
"20": "#20140C",
|
||||
"30": "#372014",
|
||||
"40": "#492918",
|
||||
"50": "#5C321D",
|
||||
"60": "#6F3C21",
|
||||
"70": "#834525",
|
||||
"80": "#984F2A",
|
||||
"90": "#AD5A2E",
|
||||
"100": "#C36433",
|
||||
"110": "#D96E37",
|
||||
"120": "#EF793C",
|
||||
"130": "#FF884A",
|
||||
"140": "#FFA170",
|
||||
"150": "#FFB792",
|
||||
"160": "#FFCCB3"
|
||||
};
|
||||
|
||||
export const bmcLightTheme: Theme =
|
||||
{
|
||||
...createLightTheme(bmcBrandRamp),
|
||||
colorBrandBackground: bmcBrandRamp[110],
|
||||
};
|
||||
|
||||
export const bmcDarkTheme: Theme =
|
||||
{
|
||||
...createDarkTheme(bmcBrandRamp),
|
||||
colorBrandBackground: bmcBrandRamp[120],
|
||||
colorBrandForeground1: bmcBrandRamp[110],
|
||||
colorBrandForeground2: bmcBrandRamp[120]
|
||||
};
|
||||
@@ -0,0 +1,43 @@
|
||||
import browser, { Manifest } from "webextension-polyfill";
|
||||
|
||||
export const PersonalLink =
|
||||
{
|
||||
Website: "https://xfox111.net",
|
||||
Twitter: "https://twitter.com/xfox111",
|
||||
BuyMeACoffee: "https://buymeacoffee.com/xfox111"
|
||||
};
|
||||
|
||||
export const WebstoreLink =
|
||||
{
|
||||
Chrome: "https://chrome.google.com/webstore/detail/password-generator/jnjobgjobffgmgfnkpkjfjkkfhfikmfl",
|
||||
Edge: "https://microsoftedge.microsoft.com/addons/detail/password-generator/manimdhobjbkfpeeehlhhneookiokpbj",
|
||||
Firefox: "https://addons.mozilla.org/firefox/addon/easy-password-generator"
|
||||
};
|
||||
|
||||
const getGithub = (path?: string): string =>
|
||||
`https://github.com/xfox111/PasswordGeneratorExtension${path ?? ""}`;
|
||||
|
||||
export const GithubLink =
|
||||
{
|
||||
Repository: getGithub(),
|
||||
Changelog: getGithub("/releases/latest"),
|
||||
TranslationGuide: getGithub("/wiki/Contribution-Guidelines#contributing-to-translations"),
|
||||
License: getGithub("/blob/main/LICENSE")
|
||||
};
|
||||
|
||||
export const GetFeedbackLink = () =>
|
||||
{
|
||||
if (__BROWSER__ === "firefox")
|
||||
return WebstoreLink.Firefox;
|
||||
|
||||
const manifest: Manifest.WebExtensionManifest = browser.runtime.getManifest();
|
||||
|
||||
const updateUrl: URL = new URL((manifest as unknown as Record<string, unknown>).update_url as string ?? "about:blank");
|
||||
|
||||
if (updateUrl.host === "edge.microsoft.com")
|
||||
return WebstoreLink.Edge;
|
||||
if (updateUrl.host === "clients2.google.com")
|
||||
return WebstoreLink.Chrome;
|
||||
|
||||
return "mailto:feedback@xfox111.net";
|
||||
};
|
||||
@@ -0,0 +1,186 @@
|
||||
{
|
||||
"name": {
|
||||
"message": "Password Generator",
|
||||
"description": "Extension name"
|
||||
},
|
||||
"description": {
|
||||
"message": "Password generator extension allows you to easily generate long and secure password in one click",
|
||||
"description": "Extension description"
|
||||
},
|
||||
"author": {
|
||||
"message": "Eugene Fox",
|
||||
"description": "Extension author"
|
||||
},
|
||||
"error@moreCharacters": {
|
||||
"message": "Password length must be at least 4 characters",
|
||||
"description": "Error message when password length is less than 4 characters"
|
||||
},
|
||||
"error@noCharacters": {
|
||||
"message": "At least one set of characters must be selected",
|
||||
"description": "Error message when no character set is selected"
|
||||
},
|
||||
"error@tooLong": {
|
||||
"message": "Length is too long to generate password with unique characters",
|
||||
"description": "Error message when password length is too long to generate password with unique characters"
|
||||
},
|
||||
"about@title":
|
||||
{
|
||||
"message": "About",
|
||||
"description": "About section title"
|
||||
},
|
||||
"about@developedBy":
|
||||
{
|
||||
"message": "Developed by Eugene Fox",
|
||||
"description": "Developer credits in about section"
|
||||
},
|
||||
"about@licensedUnder":
|
||||
{
|
||||
"message": "Licensed under",
|
||||
"description": "License info in about section"
|
||||
},
|
||||
"about@mitLicense":
|
||||
{
|
||||
"message": "MIT License",
|
||||
"description": "License name"
|
||||
},
|
||||
"about@translationCta":
|
||||
{
|
||||
"message": "Found a typo or want a translation for your language?",
|
||||
"description": "Translation CTA in about section"
|
||||
},
|
||||
"about@translationCtaButton":
|
||||
{
|
||||
"message": "Get started here",
|
||||
"description": "Translation CTA button in about section"
|
||||
},
|
||||
"about@website":
|
||||
{
|
||||
"message": "My website",
|
||||
"description": "Website link in about section"
|
||||
},
|
||||
"about@sourceCode":
|
||||
{
|
||||
"message": "Source code",
|
||||
"description": "Source code link in about section"
|
||||
},
|
||||
"about@changelog":
|
||||
{
|
||||
"message": "Changelog",
|
||||
"description": "Changelog link in about section"
|
||||
},
|
||||
"about@feedback":
|
||||
{
|
||||
"message": "Leave feedback",
|
||||
"description": "Feedback link in about section"
|
||||
},
|
||||
"about@sponsor":
|
||||
{
|
||||
"message": "Buy me a coffee",
|
||||
"description": "Buy me a coffee donation link in about section"
|
||||
},
|
||||
"settings@title":
|
||||
{
|
||||
"message": "Settings",
|
||||
"description": "Settings section title"
|
||||
},
|
||||
"settings@length":
|
||||
{
|
||||
"message": "Default password length",
|
||||
"description": "Password length label in settings section"
|
||||
},
|
||||
"settings@length__hint":
|
||||
{
|
||||
"message": "Recommended length: 8–16",
|
||||
"description": "Password length recommendatin in settings section"
|
||||
},
|
||||
"settings@lengthRange":
|
||||
{
|
||||
"message": "Quick adjustment length range",
|
||||
"description": "Quick adjustment length range label in settings section"
|
||||
},
|
||||
"settings@include":
|
||||
{
|
||||
"message": "Include symbols",
|
||||
"description": "Include character sets section"
|
||||
},
|
||||
"settings@uppercase":
|
||||
{
|
||||
"message": "Uppercase",
|
||||
"description": "Uppercase character set label in settings section"
|
||||
},
|
||||
"settings@lowercase":
|
||||
{
|
||||
"message": "Lowercase",
|
||||
"description": "Lowercase character set label in settings section"
|
||||
},
|
||||
"settings@numeric":
|
||||
{
|
||||
"message": "Numeric",
|
||||
"description": "Numbers character set label in settings section"
|
||||
},
|
||||
"settings@special":
|
||||
{
|
||||
"message": "Special",
|
||||
"description": "Special characters character set label in settings section"
|
||||
},
|
||||
"settings@exclude":
|
||||
{
|
||||
"message": "Exclude symbols",
|
||||
"description": "Exclude character sets section"
|
||||
},
|
||||
"settings@similar":
|
||||
{
|
||||
"message": "Similar",
|
||||
"description": "Similar characters character set label in settings section"
|
||||
},
|
||||
"settings@ambiguous":
|
||||
{
|
||||
"message": "Ambiguous",
|
||||
"description": "Ambiguous characters character set label in settings section"
|
||||
},
|
||||
"settings@repeating":
|
||||
{
|
||||
"message": "Repeating",
|
||||
"description": "Repeating characters character set label in settings section"
|
||||
},
|
||||
"settings@repeating__hint":
|
||||
{
|
||||
"message": "Each character in the password will be unique",
|
||||
"description": "Repeating characters character set hint in settings section"
|
||||
},
|
||||
"generator@copy":
|
||||
{
|
||||
"message": "Copy",
|
||||
"description": "Copy button label"
|
||||
},
|
||||
"generator@reset":
|
||||
{
|
||||
"message": "Reset",
|
||||
"description": "Reset button label"
|
||||
},
|
||||
"generator@refresh":
|
||||
{
|
||||
"message": "Generate new",
|
||||
"description": "Generate new password button label"
|
||||
},
|
||||
"generator@quickOptions":
|
||||
{
|
||||
"message": "Quick adjustments",
|
||||
"description": "Quick options section title"
|
||||
},
|
||||
"generator@quickOptions__hint":
|
||||
{
|
||||
"message": "These adjustments will not be saved",
|
||||
"description": "Quick options hint"
|
||||
},
|
||||
"generator@include":
|
||||
{
|
||||
"message": "Include",
|
||||
"description": "Include characters button label"
|
||||
},
|
||||
"generator@exclude":
|
||||
{
|
||||
"message": "Exclude",
|
||||
"description": "Exclude characters button label"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
{
|
||||
"name": {
|
||||
"message": "Generator haseł",
|
||||
"description": "manifest.json"
|
||||
},
|
||||
"description": {
|
||||
"message": "Rozszerzenie, które pozwala na łatwe generowanie trudnych i bezpiecznych haseł w jednym kliknięciu",
|
||||
"description": "manifest.json"
|
||||
},
|
||||
"author": {
|
||||
"message": "Jewgienij Lis",
|
||||
"description": "manifest.json"
|
||||
},
|
||||
"error@moreCharacters": {
|
||||
"message": "Minimalna długość hasła to 4 znaki",
|
||||
"description": "Error message when password length is less than 4 characters"
|
||||
},
|
||||
"error@noCharacters": {
|
||||
"message": "Musisz wybrać przynajmniej jeden rodzaj znaków",
|
||||
"description": "Error message when no character set is selected"
|
||||
},
|
||||
"error@tooLong": {
|
||||
"message": "Wybrana długość jest zbyt duża, aby wygenerować unikalne znaki",
|
||||
"description": "Error message when password length is too long to generate password with unique characters"
|
||||
},
|
||||
"about@title":
|
||||
{
|
||||
"message": "O rozszerzeniu",
|
||||
"description": "About section title"
|
||||
},
|
||||
"about@developedBy":
|
||||
{
|
||||
"message": "Autor: Jewgienij Lis",
|
||||
"description": "Developer credits in about section"
|
||||
},
|
||||
"about@licensedUnder":
|
||||
{
|
||||
"message": "",
|
||||
"description": "License info in about section"
|
||||
},
|
||||
"about@mitLicense":
|
||||
{
|
||||
"message": "Licencja MIT",
|
||||
"description": "License name"
|
||||
},
|
||||
"about@translationCta":
|
||||
{
|
||||
"message": "Znalazłeś błąd lub potrzebujesz tłumaczenia na swój język?",
|
||||
"description": "Translation CTA in about section"
|
||||
},
|
||||
"about@translationCtaButton":
|
||||
{
|
||||
"message": "Zacznij tutaj",
|
||||
"description": "Translation CTA button in about section"
|
||||
},
|
||||
"about@website":
|
||||
{
|
||||
"message": "Moja strona internetowa",
|
||||
"description": "Website link in about section"
|
||||
},
|
||||
"about@sourceCode":
|
||||
{
|
||||
"message": "Kod źródłowy",
|
||||
"description": "Source code link in about section"
|
||||
},
|
||||
"about@changelog":
|
||||
{
|
||||
"message": "Lista zmian",
|
||||
"description": "Changelog link in about section"
|
||||
},
|
||||
"about@feedback":
|
||||
{
|
||||
"message": "Zostaw opinię",
|
||||
"description": "Feedback link in about section"
|
||||
},
|
||||
"about@sponsor":
|
||||
{
|
||||
"message": "Wesprzyj",
|
||||
"description": "Buy me a coffee donation link in about section"
|
||||
},
|
||||
"settings@title":
|
||||
{
|
||||
"message": "Ustawienia",
|
||||
"description": "Settings section title"
|
||||
},
|
||||
"settings@length":
|
||||
{
|
||||
"message": "Domyślna długość hasła",
|
||||
"description": "Password length label in settings section"
|
||||
},
|
||||
"settings@length__hint":
|
||||
{
|
||||
"message": "Zalecana długość: 8–16",
|
||||
"description": "Password length recommendatin in settings section"
|
||||
},
|
||||
"settings@lengthRange":
|
||||
{
|
||||
"message": "Zakres długości dla szybkich ustawień",
|
||||
"description": "Quick adjustment length range label in settings section"
|
||||
},
|
||||
"settings@include":
|
||||
{
|
||||
"message": "Dodaj znaki",
|
||||
"description": "Include character sets section"
|
||||
},
|
||||
"settings@uppercase":
|
||||
{
|
||||
"message": "Wielkie",
|
||||
"description": "Uppercase character set label in settings section"
|
||||
},
|
||||
"settings@lowercase":
|
||||
{
|
||||
"message": "Małe",
|
||||
"description": "Lowercase character set label in settings section"
|
||||
},
|
||||
"settings@numeric":
|
||||
{
|
||||
"message": "Cyfry",
|
||||
"description": "Numbers character set label in settings section"
|
||||
},
|
||||
"settings@special":
|
||||
{
|
||||
"message": "Specjalne",
|
||||
"description": "Special characters character set label in settings section"
|
||||
},
|
||||
"settings@exclude":
|
||||
{
|
||||
"message": "Wyklucz znaki",
|
||||
"description": "Exclude character sets section"
|
||||
},
|
||||
"settings@similar":
|
||||
{
|
||||
"message": "Podobne",
|
||||
"description": "Similar characters character set label in settings section"
|
||||
},
|
||||
"settings@ambiguous":
|
||||
{
|
||||
"message": "Niebezpieczne",
|
||||
"description": "Ambiguous characters character set label in settings section"
|
||||
},
|
||||
"settings@repeating":
|
||||
{
|
||||
"message": "Powtarzające się",
|
||||
"description": "Repeating characters character set label in settings section"
|
||||
},
|
||||
"settings@repeating__hint":
|
||||
{
|
||||
"message": "Każdy znak hasła będzie unikalny",
|
||||
"description": "Repeating characters character set hint in settings section"
|
||||
},
|
||||
"generator@copy":
|
||||
{
|
||||
"message": "Kopiuj",
|
||||
"description": "Copy button label"
|
||||
},
|
||||
"generator@reset":
|
||||
{
|
||||
"message": "Resetuj",
|
||||
"description": "Reset button label"
|
||||
},
|
||||
"generator@refresh":
|
||||
{
|
||||
"message": "Wygeneruj nowe",
|
||||
"description": "Generate new password button label"
|
||||
},
|
||||
"generator@quickOptions":
|
||||
{
|
||||
"message": "Szybkie ustawienia",
|
||||
"description": "Quick options section title"
|
||||
},
|
||||
"generator@quickOptions__hint":
|
||||
{
|
||||
"message": "Te ustawienia nie zostaną zapisane",
|
||||
"description": "Quick options hint"
|
||||
},
|
||||
"generator@include":
|
||||
{
|
||||
"message": "Dodaj",
|
||||
"description": "Include characters button label"
|
||||
},
|
||||
"generator@exclude":
|
||||
{
|
||||
"message": "Wyklucz",
|
||||
"description": "Exclude characters button label"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
{
|
||||
"name": {
|
||||
"message": "Gerador de Senhas",
|
||||
"description": "Extension name"
|
||||
},
|
||||
"description": {
|
||||
"message": "A extensão do gerador de senhas permite gerar facilmente uma senha longa e segura em um clique",
|
||||
"description": "Extension description"
|
||||
},
|
||||
"author": {
|
||||
"message": "Eugênio Fox",
|
||||
"description": "Extension author"
|
||||
},
|
||||
"error@moreCharacters": {
|
||||
"message": "O comprimento da senha deve ter pelo menos 4 caracteres",
|
||||
"description": "Error message when password length is less than 4 characters"
|
||||
},
|
||||
"error@noCharacters": {
|
||||
"message": "É necessário selecionar pelo menos um conjunto de caracteres",
|
||||
"description": "Error message when no character set is selected"
|
||||
},
|
||||
"error@tooLong": {
|
||||
"message": "O comprimento é muito longo para gerar uma senha com caracteres únicos",
|
||||
"description": "Error message when password length is too long to generate password with unique characters"
|
||||
},
|
||||
"about@title":
|
||||
{
|
||||
"message": "Sobre",
|
||||
"description": "About section title"
|
||||
},
|
||||
"about@developedBy":
|
||||
{
|
||||
"message": "Desenvolvido por Eugênio Fox",
|
||||
"description": "Developer credits in about section"
|
||||
},
|
||||
"about@licensedUnder":
|
||||
{
|
||||
"message": "Licenciado sob",
|
||||
"description": "License info in about section"
|
||||
},
|
||||
"about@mitLicense":
|
||||
{
|
||||
"message": "Licença MIT",
|
||||
"description": "License name"
|
||||
},
|
||||
"about@translationCta":
|
||||
{
|
||||
"message": "Encontrou um erro ou quer uma tradução no seu idioma?",
|
||||
"description": "Translation CTA in about section"
|
||||
},
|
||||
"about@translationCtaButton":
|
||||
{
|
||||
"message": "Comece aqui",
|
||||
"description": "Translation CTA button in about section"
|
||||
},
|
||||
"about@website":
|
||||
{
|
||||
"message": "Meu site",
|
||||
"description": "Website link in about section"
|
||||
},
|
||||
"about@sourceCode":
|
||||
{
|
||||
"message": "Código fonte",
|
||||
"description": "Source code link in about section"
|
||||
},
|
||||
"about@changelog":
|
||||
{
|
||||
"message": "Registro de alterações",
|
||||
"description": "Changelog link in about section"
|
||||
},
|
||||
"about@feedback":
|
||||
{
|
||||
"message": "Enviar comentários",
|
||||
"description": "Feedback link in about section"
|
||||
},
|
||||
"about@sponsor":
|
||||
{
|
||||
"message": "Pague um café",
|
||||
"description": "Buy me a coffee donation link in about section"
|
||||
},
|
||||
"settings@title":
|
||||
{
|
||||
"message": "Configurações",
|
||||
"description": "Settings section title"
|
||||
},
|
||||
"settings@length":
|
||||
{
|
||||
"message": "Comprimento padrão da senha",
|
||||
"description": "Password length label in settings section"
|
||||
},
|
||||
"settings@length__hint":
|
||||
{
|
||||
"message": "Comprimento recomendado: 8–16",
|
||||
"description": "Password length recommendatin in settings section"
|
||||
},
|
||||
"settings@lengthRange":
|
||||
{
|
||||
"message": "Intervalo do comprimento de ajuste rápido",
|
||||
"description": "Quick adjustment length range label in settings section"
|
||||
},
|
||||
"settings@include":
|
||||
{
|
||||
"message": "Incluir símbolos",
|
||||
"description": "Include character sets section"
|
||||
},
|
||||
"settings@uppercase":
|
||||
{
|
||||
"message": "Maiúsculos",
|
||||
"description": "Uppercase character set label in settings section"
|
||||
},
|
||||
"settings@lowercase":
|
||||
{
|
||||
"message": "Minúsculos",
|
||||
"description": "Lowercase character set label in settings section"
|
||||
},
|
||||
"settings@numeric":
|
||||
{
|
||||
"message": "Numéricos",
|
||||
"description": "Numbers character set label in settings section"
|
||||
},
|
||||
"settings@special":
|
||||
{
|
||||
"message": "Especiais",
|
||||
"description": "Special characters character set label in settings section"
|
||||
},
|
||||
"settings@exclude":
|
||||
{
|
||||
"message": "Excluir símbolos",
|
||||
"description": "Exclude character sets section"
|
||||
},
|
||||
"settings@similar":
|
||||
{
|
||||
"message": "Semelhantes",
|
||||
"description": "Similar characters character set label in settings section"
|
||||
},
|
||||
"settings@ambiguous":
|
||||
{
|
||||
"message": "Ambíguos",
|
||||
"description": "Ambiguous characters character set label in settings section"
|
||||
},
|
||||
"settings@repeating":
|
||||
{
|
||||
"message": "Repetidos",
|
||||
"description": "Repeating characters character set label in settings section"
|
||||
},
|
||||
"settings@repeating__hint":
|
||||
{
|
||||
"message": "Cada caractere na senha será único",
|
||||
"description": "Repeating characters character set hint in settings section"
|
||||
},
|
||||
"generator@copy":
|
||||
{
|
||||
"message": "Copiar",
|
||||
"description": "Copy button label"
|
||||
},
|
||||
"generator@reset":
|
||||
{
|
||||
"message": "Redefinir",
|
||||
"description": "Reset button label"
|
||||
},
|
||||
"generator@refresh":
|
||||
{
|
||||
"message": "Gerar nova",
|
||||
"description": "Generate new password button label"
|
||||
},
|
||||
"generator@quickOptions":
|
||||
{
|
||||
"message": "Ajustes rápidos",
|
||||
"description": "Quick options section title"
|
||||
},
|
||||
"generator@quickOptions__hint":
|
||||
{
|
||||
"message": "Esses ajustes não serão salvos",
|
||||
"description": "Quick options hint"
|
||||
},
|
||||
"generator@include":
|
||||
{
|
||||
"message": "Incluir",
|
||||
"description": "Include characters button label"
|
||||
},
|
||||
"generator@exclude":
|
||||
{
|
||||
"message": "Excluir",
|
||||
"description": "Exclude characters button label"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
{
|
||||
"name": {
|
||||
"message": "Генератор паролей",
|
||||
"description": "Extension name"
|
||||
},
|
||||
"description": {
|
||||
"message": "Расширение, позволяющее легко генерировать сложные и надежные пароли в один клик",
|
||||
"description": "Extension description"
|
||||
},
|
||||
"author": {
|
||||
"message": "Евгений Лис",
|
||||
"description": "Extension author"
|
||||
},
|
||||
"error@moreCharacters": {
|
||||
"message": "Минимальная длина пароля — 4 символа",
|
||||
"description": "Error message when password length is less than 4 characters"
|
||||
},
|
||||
"error@noCharacters": {
|
||||
"message": "Необходимо выбрать хотя бы один вид символов",
|
||||
"description": "Error message when no character set is selected"
|
||||
},
|
||||
"error@tooLong": {
|
||||
"message": "Выбранная длина слишком большая, для генерации уникальных символов",
|
||||
"description": "Error message when password length is too long to generate password with unique characters"
|
||||
},
|
||||
"about@title":
|
||||
{
|
||||
"message": "О расширении",
|
||||
"description": "About section title"
|
||||
},
|
||||
"about@developedBy":
|
||||
{
|
||||
"message": "Разработчк: Евгений Лис",
|
||||
"description": "Developer credits in about section"
|
||||
},
|
||||
"about@licensedUnder":
|
||||
{
|
||||
"message": "",
|
||||
"description": "License info in about section"
|
||||
},
|
||||
"about@mitLicense":
|
||||
{
|
||||
"message": "Лицензия MIT",
|
||||
"description": "License name"
|
||||
},
|
||||
"about@translationCta":
|
||||
{
|
||||
"message": "Нашли опечатку или хотите перевод на ваш язык?",
|
||||
"description": "Translation CTA in about section"
|
||||
},
|
||||
"about@translationCtaButton":
|
||||
{
|
||||
"message": "Начните здесь",
|
||||
"description": "Translation CTA button in about section"
|
||||
},
|
||||
"about@website":
|
||||
{
|
||||
"message": "Мой веб-сайт",
|
||||
"description": "Website link in about section"
|
||||
},
|
||||
"about@sourceCode":
|
||||
{
|
||||
"message": "Исходный код",
|
||||
"description": "Source code link in about section"
|
||||
},
|
||||
"about@changelog":
|
||||
{
|
||||
"message": "Список изменений",
|
||||
"description": "Changelog link in about section"
|
||||
},
|
||||
"about@feedback":
|
||||
{
|
||||
"message": "Оставить отзыв",
|
||||
"description": "Feedback link in about section"
|
||||
},
|
||||
"about@sponsor":
|
||||
{
|
||||
"message": "Поддержать",
|
||||
"description": "Buy me a coffee donation link in about section"
|
||||
},
|
||||
"settings@title":
|
||||
{
|
||||
"message": "Настройки",
|
||||
"description": "Settings section title"
|
||||
},
|
||||
"settings@length":
|
||||
{
|
||||
"message": "Длина пароля по умолчанию",
|
||||
"description": "Password length label in settings section"
|
||||
},
|
||||
"settings@length__hint":
|
||||
{
|
||||
"message": "Рекомендованная длина: 8–16",
|
||||
"description": "Password length recommendatin in settings section"
|
||||
},
|
||||
"settings@lengthRange":
|
||||
{
|
||||
"message": "Диапазон длины для быстрых настроек",
|
||||
"description": "Quick adjustment length range label in settings section"
|
||||
},
|
||||
"settings@include":
|
||||
{
|
||||
"message": "Добавить символы",
|
||||
"description": "Include character sets section"
|
||||
},
|
||||
"settings@uppercase":
|
||||
{
|
||||
"message": "Прописные",
|
||||
"description": "Uppercase character set label in settings section"
|
||||
},
|
||||
"settings@lowercase":
|
||||
{
|
||||
"message": "Строчные",
|
||||
"description": "Lowercase character set label in settings section"
|
||||
},
|
||||
"settings@numeric":
|
||||
{
|
||||
"message": "Числовые",
|
||||
"description": "Numbers character set label in settings section"
|
||||
},
|
||||
"settings@special":
|
||||
{
|
||||
"message": "Специальные",
|
||||
"description": "Special characters character set label in settings section"
|
||||
},
|
||||
"settings@exclude":
|
||||
{
|
||||
"message": "Исключить символы",
|
||||
"description": "Exclude character sets section"
|
||||
},
|
||||
"settings@similar":
|
||||
{
|
||||
"message": "Похожие",
|
||||
"description": "Similar characters character set label in settings section"
|
||||
},
|
||||
"settings@ambiguous":
|
||||
{
|
||||
"message": "Особые",
|
||||
"description": "Ambiguous characters character set label in settings section"
|
||||
},
|
||||
"settings@repeating":
|
||||
{
|
||||
"message": "Повторяющиеся",
|
||||
"description": "Repeating characters character set label in settings section"
|
||||
},
|
||||
"settings@repeating__hint":
|
||||
{
|
||||
"message": "Каждый символ пароля будет уникальным",
|
||||
"description": "Repeating characters character set hint in settings section"
|
||||
},
|
||||
"generator@copy":
|
||||
{
|
||||
"message": "Копировать",
|
||||
"description": "Copy button label"
|
||||
},
|
||||
"generator@reset":
|
||||
{
|
||||
"message": "Сбросить",
|
||||
"description": "Reset button label"
|
||||
},
|
||||
"generator@refresh":
|
||||
{
|
||||
"message": "Сгенерировать новый",
|
||||
"description": "Generate new password button label"
|
||||
},
|
||||
"generator@quickOptions":
|
||||
{
|
||||
"message": "Быстрые настройки",
|
||||
"description": "Quick options section title"
|
||||
},
|
||||
"generator@quickOptions__hint":
|
||||
{
|
||||
"message": "Эти настройки не будут сохранены",
|
||||
"description": "Quick options hint"
|
||||
},
|
||||
"generator@include":
|
||||
{
|
||||
"message": "Добавить",
|
||||
"description": "Include characters button label"
|
||||
},
|
||||
"generator@exclude":
|
||||
{
|
||||
"message": "Исключить",
|
||||
"description": "Exclude characters button label"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
{
|
||||
"name": {
|
||||
"message": "Генератор паролів",
|
||||
"description": "Extension name"
|
||||
},
|
||||
"description": {
|
||||
"message": "Розширення, яке дозволяє легко генерувати складні та надійні паролі в один клік",
|
||||
"description": "Extension description"
|
||||
},
|
||||
"author": {
|
||||
"message": "Євген Лис",
|
||||
"description": "Extension author"
|
||||
},
|
||||
"error@moreCharacters": {
|
||||
"message": "Мінімальна довжина пароля — 4 символи",
|
||||
"description": "Error message when password length is less than 4 characters"
|
||||
},
|
||||
"error@noCharacters": {
|
||||
"message": "Необхідно вибрати хоча б один вид символів",
|
||||
"description": "Error message when no character set is selected"
|
||||
},
|
||||
"error@tooLong": {
|
||||
"message": "Вибрана довжина занадто велика, для генерації унікальних символів",
|
||||
"description": "Error message when password length is too long to generate password with unique characters"
|
||||
},
|
||||
"about@title":
|
||||
{
|
||||
"message": "Про розширення",
|
||||
"description": "About section title"
|
||||
},
|
||||
"about@developedBy":
|
||||
{
|
||||
"message": "Розробник: Євген Лис",
|
||||
"description": "Developer credits in about section"
|
||||
},
|
||||
"about@licensedUnder":
|
||||
{
|
||||
"message": "",
|
||||
"description": "License info in about section"
|
||||
},
|
||||
"about@mitLicense":
|
||||
{
|
||||
"message": "Ліцензія MIT",
|
||||
"description": "License name"
|
||||
},
|
||||
"about@translationCta":
|
||||
{
|
||||
"message": "Знайшли помилку або хочете переклад на вашу мову?",
|
||||
"description": "Translation CTA in about section"
|
||||
},
|
||||
"about@translationCtaButton":
|
||||
{
|
||||
"message": "Почніть тут",
|
||||
"description": "Translation CTA button in about section"
|
||||
},
|
||||
"about@website":
|
||||
{
|
||||
"message": "Мій веб-сайт",
|
||||
"description": "Website link in about section"
|
||||
},
|
||||
"about@sourceCode":
|
||||
{
|
||||
"message": "Вихідний код",
|
||||
"description": "Source code link in about section"
|
||||
},
|
||||
"about@changelog":
|
||||
{
|
||||
"message": "Список змін",
|
||||
"description": "Changelog link in about section"
|
||||
},
|
||||
"about@feedback":
|
||||
{
|
||||
"message": "Залишити відгук",
|
||||
"description": "Feedback link in about section"
|
||||
},
|
||||
"about@sponsor":
|
||||
{
|
||||
"message": "Підтримати",
|
||||
"description": "Buy me a coffee donation link in about section"
|
||||
},
|
||||
"settings@title":
|
||||
{
|
||||
"message": "Налаштування",
|
||||
"description": "Settings section title"
|
||||
},
|
||||
"settings@length":
|
||||
{
|
||||
"message": "Довжина пароля за замовчуванням",
|
||||
"description": "Password length label in settings section"
|
||||
},
|
||||
"settings@length__hint":
|
||||
{
|
||||
"message": "Рекомендована довжина: 8–16",
|
||||
"description": "Password length recommendatin in settings section"
|
||||
},
|
||||
"settings@lengthRange":
|
||||
{
|
||||
"message": "Діапазон довжини для швидких налаштувань",
|
||||
"description": "Quick adjustment length range label in settings section"
|
||||
},
|
||||
"settings@include":
|
||||
{
|
||||
"message": "Додати символи",
|
||||
"description": "Include character sets section"
|
||||
},
|
||||
"settings@uppercase":
|
||||
{
|
||||
"message": "Великі",
|
||||
"description": "Uppercase character set label in settings section"
|
||||
},
|
||||
"settings@lowercase":
|
||||
{
|
||||
"message": "Малі",
|
||||
"description": "Lowercase character set label in settings section"
|
||||
},
|
||||
"settings@numeric":
|
||||
{
|
||||
"message": "Числові",
|
||||
"description": "Numbers character set label in settings section"
|
||||
},
|
||||
"settings@special":
|
||||
{
|
||||
"message": "Спеціальні",
|
||||
"description": "Special characters character set label in settings section"
|
||||
},
|
||||
"settings@exclude":
|
||||
{
|
||||
"message": "Виключити символи",
|
||||
"description": "Exclude character sets section"
|
||||
},
|
||||
"settings@similar":
|
||||
{
|
||||
"message": "Схожі",
|
||||
"description": "Similar characters character set label in settings section"
|
||||
},
|
||||
"settings@ambiguous":
|
||||
{
|
||||
"message": "Особливі",
|
||||
"description": "Ambiguous characters character set label in settings section"
|
||||
},
|
||||
"settings@repeating":
|
||||
{
|
||||
"message": "Повторювані",
|
||||
"description": "Repeating characters character set label in settings section"
|
||||
},
|
||||
"settings@repeating__hint":
|
||||
{
|
||||
"message": "Кожен символ пароля буде унікальним",
|
||||
"description": "Repeating characters character set hint in settings section"
|
||||
},
|
||||
"generator@copy":
|
||||
{
|
||||
"message": "Копіювати",
|
||||
"description": "Copy button label"
|
||||
},
|
||||
"generator@reset":
|
||||
{
|
||||
"message": "Скинути",
|
||||
"description": "Reset button label"
|
||||
},
|
||||
"generator@refresh":
|
||||
{
|
||||
"message": "Згенерувати новий",
|
||||
"description": "Generate new password button label"
|
||||
},
|
||||
"generator@quickOptions":
|
||||
{
|
||||
"message": "Швидкі налаштування",
|
||||
"description": "Quick options section title"
|
||||
},
|
||||
"generator@quickOptions__hint":
|
||||
{
|
||||
"message": "Ці налаштування не будуть збережені",
|
||||
"description": "Quick options hint"
|
||||
},
|
||||
"generator@include":
|
||||
{
|
||||
"message": "Додати",
|
||||
"description": "Include characters button label"
|
||||
},
|
||||
"generator@exclude":
|
||||
{
|
||||
"message": "Виключити",
|
||||
"description": "Exclude characters button label"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export default class ExtensionOptions
|
||||
{
|
||||
public MinLength: number = 4;
|
||||
public MaxLength: number = 32;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
export default class GeneratorOptions
|
||||
{
|
||||
public Length: number = 8;
|
||||
|
||||
public Special: boolean = true;
|
||||
public Numeric: boolean = true;
|
||||
public Lowercase: boolean = true;
|
||||
public Uppercase: boolean = true;
|
||||
|
||||
public ExcludeSimilar: boolean = true;
|
||||
public ExcludeAmbiguous: boolean = true;
|
||||
|
||||
public ExcludeRepeating: boolean = false;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import { GriffelStyle, makeStyles, shorthands, tokens } from "@fluentui/react-components";
|
||||
|
||||
const random = (max: number) => Math.floor(Math.random() * max);
|
||||
|
||||
export const useStyles = (count: number) => makeStyles({
|
||||
snow:
|
||||
{
|
||||
position: "absolute",
|
||||
...shorthands.overflow("hidden"),
|
||||
pointerEvents: "none",
|
||||
|
||||
top: 0,
|
||||
left: 0,
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
},
|
||||
snowflake:
|
||||
{
|
||||
"--size": "1px",
|
||||
width: "var(--size)",
|
||||
height: "var(--size)",
|
||||
backgroundColor: tokens.colorScrollbarOverlay,
|
||||
...shorthands.borderRadius(tokens.borderRadiusCircular),
|
||||
position: "absolute",
|
||||
top: "-5px",
|
||||
},
|
||||
...[...Array(count)].reduce(
|
||||
(acc, _, i): Record<string, GriffelStyle> => ({
|
||||
...acc,
|
||||
[`snowflake-${i}`]: {
|
||||
"--size": `${random(5)}px`,
|
||||
"--left-ini": `${random(20) - 10}vw`,
|
||||
"--left-end": `${random(20) - 10}vw`,
|
||||
left: `${random(100)}vw`,
|
||||
animationName: "snowfall",
|
||||
animationDuration: `${5 + random(10)}s`,
|
||||
animationTimingFunction: "linear",
|
||||
animationIterationCount: "infinite",
|
||||
animationDelay: `-${random(10)}s`,
|
||||
},
|
||||
}),
|
||||
{},
|
||||
),
|
||||
});
|
||||
@@ -0,0 +1,18 @@
|
||||
import { mergeClasses } from "@fluentui/react-components";
|
||||
import { useStyles } from "./Snow.styles";
|
||||
|
||||
const SNOWFLAKES_NUM: number = 100;
|
||||
|
||||
export default function Snow(): JSX.Element
|
||||
{
|
||||
const cls = useStyles(SNOWFLAKES_NUM)();
|
||||
|
||||
if (![0, 11].includes(new Date().getMonth()))
|
||||
return <></>;
|
||||
|
||||
return (
|
||||
<div className={ cls.snow }>
|
||||
{ [...Array(SNOWFLAKES_NUM)].map((_, i) => <div key={ i } className={ mergeClasses(cls.snowflake, cls[`snowflake-${i}`]) } />) }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import Snow from "./Snow";
|
||||
|
||||
const Specials = (): JSX.Element => <>
|
||||
<Snow />
|
||||
</>;
|
||||
|
||||
export default Specials;
|
||||
@@ -0,0 +1,11 @@
|
||||
import browser from "webextension-polyfill";
|
||||
import messages from "../Data/Locales/en/messages.json";
|
||||
|
||||
/**
|
||||
* Gets the localized string for the specified message. If the message is missing, this method returns an empty string (''). If the format of the call is wrong — for example, messageName is not a string or the substitutions array has more than 9 elements — this method returns .
|
||||
* @param key The name of the message, as specified in the file.
|
||||
* @param subustitutions Optional. Substitution strings, if the message requires any.
|
||||
* @returns Message localized for current locale.
|
||||
*/
|
||||
export const GetLocaleString = (key: keyof typeof messages, ...subustitutions: string[]): string =>
|
||||
browser.i18n.getMessage(key, subustitutions) ?? key;
|
||||
@@ -0,0 +1,129 @@
|
||||
import GeneratorOptions from "../Models/GeneratorOptions";
|
||||
import { GetLocaleString as loc } from "./Localization";
|
||||
|
||||
const Characters =
|
||||
{
|
||||
Uppercase: "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
|
||||
Lowercase: "abcdefghijklmnopqrstuvwxyz",
|
||||
Numeric: "1234567890",
|
||||
Special: "!#$%&*+-=?@^_"
|
||||
};
|
||||
|
||||
const Similar: string = "iIl1Lo0O";
|
||||
const Ambiguous: string = "{}[]()/\\'\"`~,;:.<>";
|
||||
|
||||
export const CharacterHints = { ...Characters, Similar, Ambiguous };
|
||||
|
||||
/**
|
||||
* Generates a random password
|
||||
* @param options Options for password generation
|
||||
* @returns Randomly generated password
|
||||
* @throws Error if options are invalid
|
||||
*/
|
||||
export function GeneratePassword(options: GeneratorOptions): string
|
||||
{
|
||||
ValidateOptions(options);
|
||||
|
||||
let password: string = GetRequiredCharacters(options);
|
||||
const availableCharacters: string = GetAvailableCharacters(options);
|
||||
|
||||
for (let i = password.length; i < options.Length; i++)
|
||||
{
|
||||
const character: string = PickRandomFromArray(availableCharacters);
|
||||
|
||||
if (options.ExcludeRepeating && password.includes(character))
|
||||
i--;
|
||||
else
|
||||
password += character;
|
||||
}
|
||||
|
||||
password = ShuffleString(password);
|
||||
return password;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates options for password generation
|
||||
* @param options Options for password generation
|
||||
* @throws Error if options are invalid
|
||||
*/
|
||||
export function ValidateOptions(options: GeneratorOptions): void
|
||||
{
|
||||
if (options.Length < 4)
|
||||
throw new Error(loc("error@moreCharacters"));
|
||||
|
||||
const availableCharacters: string = GetAvailableCharacters(options);
|
||||
|
||||
if (availableCharacters.length < 1)
|
||||
throw new Error(loc("error@noCharacters"));
|
||||
|
||||
if (options.ExcludeRepeating && options.Length > availableCharacters.length)
|
||||
throw new Error(loc("error@tooLong"));
|
||||
}
|
||||
|
||||
// Returns a string containing all characters that are available for password generation
|
||||
function GetAvailableCharacters(options: GeneratorOptions): string
|
||||
{
|
||||
let availableCharacters: string = "";
|
||||
|
||||
for (const [key, value] of Object.entries(Characters))
|
||||
if (options[key as keyof GeneratorOptions])
|
||||
availableCharacters += value;
|
||||
|
||||
if (options.ExcludeSimilar)
|
||||
availableCharacters = availableCharacters.replace(new RegExp(`[${Similar}]`, "g"), "");
|
||||
|
||||
if (options.Special && !options.ExcludeAmbiguous)
|
||||
availableCharacters += Ambiguous;
|
||||
|
||||
return availableCharacters;
|
||||
}
|
||||
|
||||
// Returns a string containing all characters from every available set that are required for password generation
|
||||
function GetRequiredCharacters(options: GeneratorOptions): string
|
||||
{
|
||||
let result: string = "";
|
||||
const characters: Record<string, string> = Object.assign({}, Characters);
|
||||
|
||||
if (!options.ExcludeAmbiguous)
|
||||
characters.Special += Ambiguous;
|
||||
|
||||
if (options.ExcludeSimilar)
|
||||
for (const key of Object.keys(characters))
|
||||
characters[key] = characters[key].replace(new RegExp(`[${Similar}]`, "g"), "");
|
||||
|
||||
for (const [key, value] of Object.entries(characters))
|
||||
if (options[key as keyof GeneratorOptions])
|
||||
result += PickRandomFromArray(value);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Picks a random character from a string
|
||||
function PickRandomFromArray(array: string): string
|
||||
{
|
||||
return array[GetRandomInt(0, array.length)];
|
||||
}
|
||||
|
||||
// See https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Math/random
|
||||
// min is inclusive, max is exclusive
|
||||
function GetRandomInt(min: number, max: number): number
|
||||
{
|
||||
const arr = new Uint16Array(1);
|
||||
crypto.getRandomValues(arr); // Using crypto instead of Math.random() as a CSPRNG
|
||||
return Math.floor((arr[0] / 65_536) * (max - min)) + min;
|
||||
}
|
||||
|
||||
// Shuffles a string using Fisher-Yates algorithm and CSPRNG
|
||||
// See https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle
|
||||
function ShuffleString(str: string): string
|
||||
{
|
||||
const arr = str.split("");
|
||||
|
||||
for (let i = arr.length - 1; i > 0; i--)
|
||||
{
|
||||
const j = GetRandomInt(0, i + 1);
|
||||
[arr[i], arr[j]] = [arr[j], arr[i]];
|
||||
}
|
||||
|
||||
return arr.join("");
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import React, { createContext, useContext, useEffect, useState } from "react";
|
||||
import browser from "webextension-polyfill";
|
||||
import ExtensionOptions from "../Models/ExtensionOptions";
|
||||
import GeneratorOptions from "../Models/GeneratorOptions";
|
||||
|
||||
const defaultOptions: IStorage =
|
||||
{
|
||||
generatorOptions: new GeneratorOptions(),
|
||||
extOptions: new ExtensionOptions(),
|
||||
updateStorage: async () => { }
|
||||
};
|
||||
|
||||
const Storage = createContext<IStorage>(defaultOptions);
|
||||
|
||||
export const useStorage = () => useContext<IStorage>(Storage);
|
||||
|
||||
export function StorageProvider(props: IStorageProviderProps): JSX.Element
|
||||
{
|
||||
const [storage, setStorage] = useState<IStorage | null>(null);
|
||||
|
||||
const getStorage = async () =>
|
||||
setStorage({
|
||||
extOptions: await browser.storage.sync.get(defaultOptions.extOptions) as ExtensionOptions,
|
||||
generatorOptions: await browser.storage.sync.get(defaultOptions.generatorOptions) as GeneratorOptions,
|
||||
updateStorage: async (options) => await browser.storage.sync.set(options)
|
||||
});
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
getStorage();
|
||||
browser.storage.sync.onChanged.addListener(getStorage);
|
||||
return () => browser.storage.sync.onChanged.removeListener(getStorage);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Storage.Provider value={ storage ?? defaultOptions }>
|
||||
{ storage ? props.children : props.loader }
|
||||
</Storage.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
// #region Types
|
||||
interface IStorage
|
||||
{
|
||||
generatorOptions: GeneratorOptions;
|
||||
extOptions: ExtensionOptions;
|
||||
updateStorage: (options: Partial<GeneratorOptions | ExtensionOptions>) => Promise<void>;
|
||||
}
|
||||
|
||||
interface IStorageProviderProps extends React.PropsWithChildren
|
||||
{
|
||||
loader?: JSX.Element;
|
||||
}
|
||||
// #endregion
|
||||
@@ -0,0 +1,21 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { Theme, webDarkTheme, webLightTheme } from "@fluentui/react-components";
|
||||
|
||||
export function useTheme(lightTheme?: Theme, darkTheme?: Theme): Theme
|
||||
{
|
||||
const media = window.matchMedia("(prefers-color-scheme: dark)");
|
||||
|
||||
const getTheme = (isDark: boolean) =>
|
||||
isDark ? (darkTheme ?? webDarkTheme) : (lightTheme ?? webLightTheme);
|
||||
|
||||
const [theme, setTheme] = useState<Theme>(getTheme(media.matches));
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
const updateTheme = (args: MediaQueryListEvent) => setTheme(getTheme(args.matches));
|
||||
media.addEventListener("change", updateTheme);
|
||||
return () => media.removeEventListener("change", updateTheme);
|
||||
}, []);
|
||||
|
||||
return theme;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { useState } from "react";
|
||||
|
||||
export function useTimeout(timeout: number)
|
||||
{
|
||||
const [isActive, setActive] = useState<boolean>(false);
|
||||
|
||||
const trigger = () =>
|
||||
{
|
||||
setActive(true);
|
||||
setTimeout(() => setActive(false), timeout);
|
||||
};
|
||||
|
||||
return { isActive, trigger };
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"manifest_version": 3,
|
||||
|
||||
"name": "__MSG_name__",
|
||||
"description": "__MSG_description__",
|
||||
"author": "__MSG_author__",
|
||||
"homepage_url": "https://github.com/xfox111/PasswordGeneratorExtension",
|
||||
|
||||
"default_locale": "en",
|
||||
|
||||
"icons": {
|
||||
"16": "icons/icon-16.png",
|
||||
"32": "icons/icon-32.png",
|
||||
"48": "icons/icon-48.png",
|
||||
"128": "icons/icon-128.png"
|
||||
},
|
||||
|
||||
"permissions": [
|
||||
"storage"
|
||||
],
|
||||
|
||||
"action": {
|
||||
"default_popup": "src/popup.html"
|
||||
},
|
||||
|
||||
"{{firefox}}.browser_specific_settings": {
|
||||
"gecko": {
|
||||
"id": "passwordgenerator@xfox111.net",
|
||||
"strict_min_version": "109.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
html,
|
||||
body
|
||||
{
|
||||
width: 400px;
|
||||
min-width: 400px;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
-moz-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6,
|
||||
p, ul, ol, li
|
||||
{
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@keyframes scaleUpIn
|
||||
{
|
||||
from
|
||||
{
|
||||
transform: scale(0.5);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
to
|
||||
{
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spin
|
||||
{
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
@keyframes fadeIn
|
||||
{
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
@keyframes snowfall
|
||||
{
|
||||
0%
|
||||
{
|
||||
transform: translate3d(var(--left-ini), 0, 0);
|
||||
opacity: .6;
|
||||
}
|
||||
|
||||
100%
|
||||
{
|
||||
transform: translate3d(var(--left-end), 610px, 0);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Password generator</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
|
||||
<script type="module" src="./popup.tsx"></script>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,10 @@
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import App from "./App";
|
||||
import "./popup.css";
|
||||
|
||||
ReactDOM.createRoot(document.getElementById("root")!).render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>
|
||||
);
|
||||
@@ -0,0 +1,5 @@
|
||||
/// <reference types="vite/client" />
|
||||
/// <reference types="vite-plugin-svgr/client" />
|
||||
|
||||
type SupportedBrowser = "chrome" | "firefox";
|
||||
declare const __BROWSER__: SupportedBrowser;
|
||||
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": [
|
||||
"DOM",
|
||||
"DOM.Iterable",
|
||||
"ESNext"
|
||||
],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "Node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"allowJs": false,
|
||||
"esModuleInterop": false,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.node.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Node",
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"include": [
|
||||
"vite.config.ts"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import { defineConfig } from "vite";
|
||||
import react from "@vitejs/plugin-react-swc";
|
||||
import webExtension, { readJsonFile } from "vite-plugin-web-extension";
|
||||
import path from "node:path";
|
||||
import svgr from "vite-plugin-svgr";
|
||||
import { viteStaticCopy } from "vite-plugin-static-copy";
|
||||
|
||||
const target: string = process.env.TARGET || "chrome";
|
||||
|
||||
function generateManifest()
|
||||
{
|
||||
const manifest = readJsonFile("src/manifest.json");
|
||||
const pkg = readJsonFile("package.json");
|
||||
return {
|
||||
version: pkg.version,
|
||||
...manifest,
|
||||
};
|
||||
}
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
define:
|
||||
{
|
||||
__BROWSER__: JSON.stringify(target)
|
||||
},
|
||||
plugins:
|
||||
[
|
||||
react(),
|
||||
svgr(),
|
||||
viteStaticCopy({
|
||||
targets: [
|
||||
{
|
||||
src: "src/Data/Locales",
|
||||
dest: ".",
|
||||
rename: "_locales"
|
||||
}
|
||||
]
|
||||
}),
|
||||
webExtension({
|
||||
manifest: generateManifest,
|
||||
browser: target,
|
||||
/* webExtConfig: {
|
||||
args: target === "firefox" ? [ ] : [ "--no-sandbox" ]
|
||||
} */
|
||||
}),
|
||||
],
|
||||
resolve:
|
||||
{
|
||||
alias:
|
||||
{
|
||||
// In dev mode, make sure fast refresh works
|
||||
"/@react-refresh": path.resolve(
|
||||
"node_modules/@vitejs/plugin-react-swc/refresh-runtime.js"
|
||||
),
|
||||
},
|
||||
},
|
||||
build:
|
||||
{
|
||||
chunkSizeWarningLimit: 1000,
|
||||
},
|
||||
});
|
||||