Major 2.0 (#8)
* Migrated to React 18 and FluentUI 9 * Added Ukranian translation * Updated GitHub templates * Updated CI/CD - Added CodeQL and Dependabot pipelines - Removed Whitesource Bolt integration - Added PR pipeline - Update release pipeline to meet ReactJS - Added Edge publish to pipeline - Updated PR checklist * Updated repo docs * Moved dependabot yml to the right place * Update README.md * Added path filters to pipelines
@@ -3,8 +3,7 @@ name: Bug report
|
|||||||
about: Create a report to help us improve the extension
|
about: Create a report to help us improve the extension
|
||||||
title: ''
|
title: ''
|
||||||
labels: bug
|
labels: bug
|
||||||
assignees: ''
|
assignees: @XFox111
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Description
|
### Description
|
||||||
@@ -30,4 +29,6 @@ Please provide the following information:
|
|||||||
- Extension version: [e.g. 1.5]
|
- Extension version: [e.g. 1.5]
|
||||||
|
|
||||||
### Additional context
|
### Additional context
|
||||||
Add any other context about the problem here.
|
Add any other context about the problem here.
|
||||||
|
|
||||||
|
- [ ] Are you willing to submit a pull request to implement this feature yourself?
|
||||||
|
|||||||
@@ -3,8 +3,7 @@ name: Feature request
|
|||||||
about: Suggest an idea for this project
|
about: Suggest an idea for this project
|
||||||
title: ''
|
title: ''
|
||||||
labels: enhancement
|
labels: enhancement
|
||||||
assignees: ''
|
assignees: @XFox111
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**Is your feature request related to a problem? Please describe.**
|
**Is your feature request related to a problem? Please describe.**
|
||||||
@@ -17,4 +16,6 @@ A clear and concise description of what you want to happen.
|
|||||||
A clear and concise description of any alternative solutions or features you've considered.
|
A clear and concise description of any alternative solutions or features you've considered.
|
||||||
|
|
||||||
**Additional context**
|
**Additional context**
|
||||||
Add any other context or screenshots about the feature request here.
|
Add any other context or screenshots about the feature request here.
|
||||||
|
|
||||||
|
- [ ] Are you willing to submit a pull request to implement this feature yourself?
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
# 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: "main"
|
||||||
|
assignees:
|
||||||
|
- "xfox111"
|
||||||
|
reviewers:
|
||||||
|
- "xfox111"
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
||||||
|
|
||||||
|
- package-ecosystem: "github-actions"
|
||||||
|
directory: "/"
|
||||||
|
target-branch: "main"
|
||||||
|
assignees:
|
||||||
|
- "xfox111"
|
||||||
|
reviewers:
|
||||||
|
- "xfox111"
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
||||||
@@ -1,9 +1,14 @@
|
|||||||
fixes:
|
## Description
|
||||||
|
> Put short description of the pull request here
|
||||||
|
|
||||||
|
fixes: #issue_number
|
||||||
|
|
||||||
## Changelog
|
## Changelog
|
||||||
- Item 1
|
- Item 1
|
||||||
- Item 2
|
- Item 2
|
||||||
- Item 3
|
- Item 3
|
||||||
|
|
||||||
## PR Checklist
|
## PR Checklist (main branch)
|
||||||
- [ ] Change extension version in the manifest
|
- [ ] Update extension version in `package.json`
|
||||||
|
- [ ] [Post-merge] Create a release to publish the new version
|
||||||
|
- [ ] [Post-deploy] Update changelog for Firefox webstore
|
||||||
|
|||||||
@@ -0,0 +1,147 @@
|
|||||||
|
name: Release pipeline
|
||||||
|
|
||||||
|
on:
|
||||||
|
|
||||||
|
release:
|
||||||
|
types: [published]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
Build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Update manifest version
|
||||||
|
uses: Amadevus/pwsh-script@v2
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
[PSCustomObject] $package = Get-Content "package.json" | ConvertFrom-Json;
|
||||||
|
[PSCustomObject] $manifest = Get-Content "public/manifest.json" | ConvertFrom-Json;
|
||||||
|
|
||||||
|
$manifest.version = $package.version;
|
||||||
|
|
||||||
|
$manifest | ConvertTo-Json -Depth 10 | Out-File "public/manifest.json"
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: '16.x'
|
||||||
|
cache: 'yarn'
|
||||||
|
|
||||||
|
- run: yarn install
|
||||||
|
- run: yarn build
|
||||||
|
|
||||||
|
- name: 'Drop artifacts'
|
||||||
|
uses: actions/upload-artifacts@v3.0.0
|
||||||
|
with:
|
||||||
|
name: 'Build'
|
||||||
|
path: 'build'
|
||||||
|
|
||||||
|
Firefox:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- uses: actions/download-artifact@v3
|
||||||
|
with:
|
||||||
|
name: 'Build'
|
||||||
|
|
||||||
|
- name: Build Extension for Firefox
|
||||||
|
id: web-ext-build
|
||||||
|
uses: kewisch/action-web-ext@v1
|
||||||
|
with:
|
||||||
|
cmd: build
|
||||||
|
source: 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 build'
|
||||||
|
path: ${{ steps.web-ext-sign.outputs.target }}
|
||||||
|
|
||||||
|
- name: Upload artifact
|
||||||
|
uses: xresloader/upload-to-github-release@v1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
file: ${{ steps.web-ext-sign.outputs.target }}
|
||||||
|
tags: true
|
||||||
|
draft: false
|
||||||
|
|
||||||
|
Chrome:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- uses: actions/download-artifact@v3
|
||||||
|
with:
|
||||||
|
name: 'Build'
|
||||||
|
|
||||||
|
- name: Pack extension
|
||||||
|
uses: TheDoctor0/zip-release@0.4.1
|
||||||
|
with:
|
||||||
|
directory: Build
|
||||||
|
filename: ./PasswordGenerator.zip
|
||||||
|
|
||||||
|
- 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: Drop artifacts
|
||||||
|
uses: actions/upload-artifact@v2
|
||||||
|
with:
|
||||||
|
name: 'Chrome Artifacts'
|
||||||
|
path: ./PasswordGenerator.zip
|
||||||
|
|
||||||
|
- name: Upload artifact
|
||||||
|
uses: xresloader/upload-to-github-release@v1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
file: ./PasswordGenerator.zip
|
||||||
|
tags: true
|
||||||
|
draft: false
|
||||||
|
|
||||||
|
Edge:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- uses: actions/download-artifact@v3
|
||||||
|
with:
|
||||||
|
name: 'Build'
|
||||||
|
|
||||||
|
- name: Pack extension
|
||||||
|
uses: TheDoctor0/zip-release@0.4.1
|
||||||
|
with:
|
||||||
|
directory: Build
|
||||||
|
filename: ./PasswordGenerator.zip
|
||||||
|
|
||||||
|
- name: Publish to Edge Addons
|
||||||
|
uses: wdzeng/edge-addon@v1
|
||||||
|
with:
|
||||||
|
product-id: 0RDCKDGH3MWT
|
||||||
|
zip-path: ./PasswordGenerator.zip
|
||||||
|
client-id: ${{ secrets.EDGE_CLIENT_ID }}
|
||||||
|
client-secret: ${{ secrets.EDGE_CLIENT_SECRET }}
|
||||||
|
access-token-url: ${{ secrets.EDGE_ACCESS_TOKEN_URL }}
|
||||||
@@ -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" ]
|
||||||
|
paths-ignore:
|
||||||
|
- '**.md'
|
||||||
|
- 'LICENSE'
|
||||||
|
- 'PRIVACY'
|
||||||
|
- '**/cd_pipeline.yaml'
|
||||||
|
- '**/dependabot.yml'
|
||||||
|
- '**/pr_pipeline.yaml'
|
||||||
|
pull_request:
|
||||||
|
# The branches below must be a subset of the branches above
|
||||||
|
branches: [ "main" ]
|
||||||
|
paths-ignore:
|
||||||
|
- '**.md'
|
||||||
|
- 'LICENSE'
|
||||||
|
- 'PRIVACY'
|
||||||
|
- '**/cd_pipeline.yaml'
|
||||||
|
- '**/dependabot.yml'
|
||||||
|
- '**/pr_pipeline.yaml'
|
||||||
|
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: [ 'javascript', '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@v2
|
||||||
|
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@v2
|
||||||
|
|
||||||
|
# ℹ️ 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@v2
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
name: PR check pipeline
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
paths-ignore:
|
||||||
|
- '**.md'
|
||||||
|
- 'LICENSE'
|
||||||
|
- 'PRIVACY'
|
||||||
|
- '**/cd_pipeline.yaml'
|
||||||
|
- '**/dependabot.yml'
|
||||||
|
- '**/codeql-analysis.yml'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: '16.x'
|
||||||
|
cache: 'yarn'
|
||||||
|
|
||||||
|
- run: yarn install
|
||||||
|
- run: yarn build
|
||||||
|
|
||||||
|
- name: 'Drop artifacts'
|
||||||
|
uses: actions/upload-artifact@v3.0.0
|
||||||
|
with:
|
||||||
|
name: 'Package'
|
||||||
|
path: 'build'
|
||||||
@@ -1 +1,26 @@
|
|||||||
.vscode
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# IDE files
|
||||||
|
.vscode
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
/.pnp
|
||||||
|
.pnp.js
|
||||||
|
|
||||||
|
# testing
|
||||||
|
/coverage
|
||||||
|
|
||||||
|
# production
|
||||||
|
/build
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
.env.local
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"scanSettings": {
|
|
||||||
"baseBranches": []
|
|
||||||
},
|
|
||||||
"checkRunSettings": {
|
|
||||||
"vulnerableCheckRunConclusionLevel": "failure",
|
|
||||||
"displayMode": "diff"
|
|
||||||
},
|
|
||||||
"issueSettings": {
|
|
||||||
"minSeverityLevel": "LOW"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -68,9 +68,9 @@ Please include the following with each issue:
|
|||||||
|
|
||||||
### Final Checklist
|
### Final Checklist
|
||||||
Please remember to do the following:
|
Please remember to do the following:
|
||||||
- [ ] Search the issue repository to ensure your report is a new issue
|
- [*] Search the issue repository to ensure your report is a new issue
|
||||||
- [ ] Separate issues reports
|
- [*] Separate issues reports
|
||||||
- [ ] Include as much information as you can to your report
|
- [*] 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!
|
Don't feel bad if the developers can't reproduce the issue right away. They will simply ask for more information!
|
||||||
|
|
||||||
@@ -85,23 +85,34 @@ If you are interested in writing code to fix issues or implement new awesome fea
|
|||||||
```
|
```
|
||||||
git clone https://github.com/xfox111/PasswordGeneratorExtension.git
|
git clone https://github.com/xfox111/PasswordGeneratorExtension.git
|
||||||
```
|
```
|
||||||
|
2. Install [Node.js](https://nodejs.org/en/) and [Yarn](https://yarnpkg.com/)
|
||||||
|
3. Open terminal in project directory and run
|
||||||
|
```bash
|
||||||
|
yarn install
|
||||||
|
```
|
||||||
|
4. Build project
|
||||||
|
```bash
|
||||||
|
yarn build
|
||||||
|
```
|
||||||
2. Enable Developers mode on your browser extensions page
|
2. Enable Developers mode on your browser extensions page
|
||||||
3. Click "Load unpacked" button and navigate to the extension root folder (contains `manifest.json`)
|
3. Click "Load unpacked" button and navigate to the `build` folder of the repo (contains `manifest.json`)
|
||||||
4. Done!
|
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
|
To run extension as standalone web application you can use `yarn start` command
|
||||||
|
|
||||||
|
Next time you make any changes to the codebase, rebuild extension with `yarn build` and reload extension by toggling it off and on or by pressing "Reload" button on extensions list page
|
||||||
|
|
||||||
### Development workflow
|
### Development workflow
|
||||||
This section represents how contributors should interact with codebase implementing features and fixing bugs
|
This section represents how contributors should interact with codebase implementing features and fixing bugs
|
||||||
1. Getting assigned to the issue
|
1. Getting assigned to the issue
|
||||||
2. Creating a repository fork
|
2. Creating a repository fork
|
||||||
3. Making changes to codebase
|
3. Making changes to codebase
|
||||||
5. Creating a pull request to `master`
|
5. Creating a pull request to `main`
|
||||||
6. Reviewing & completing PR
|
6. Reviewing & completing PR
|
||||||
7. Done
|
7. Done
|
||||||
|
|
||||||
#### Release
|
#### 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
|
Next stage is release. Release performs on every push to main (which makes functional changes to the source code). Release performs manually by @XFox111 into: Chrome webstore, Edge webstore and GitHub releases
|
||||||
|
|
||||||
### Coding guidelines
|
### Coding guidelines
|
||||||
#### Indentation
|
#### Indentation
|
||||||
@@ -201,4 +212,4 @@ Pull requests that fix typos are welcomed but please make sure it doesn't touch
|
|||||||
Your contributions to open source, large or small, make great projects like this possible. Thank you for taking the time to contribute.
|
Your contributions to open source, large or small, make great projects like this possible. Thank you for taking the time to contribute.
|
||||||
|
|
||||||
## Attribution
|
## Attribution
|
||||||
This Contribution Guidelines are adapted from the [Contributing to VS Code](https://github.com/microsoft/vscode/blob/master/CONTRIBUTING.md)
|
This Contribution Guidelines are adapted from the [Contributing to VS Code](https://github.com/microsoft/vscode/blob/master/CONTRIBUTING.md)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2020 Michael Gordeev
|
Copyright (c) 2022 Eugene Fox
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
Password generator Extension Privacy Policy
|
# Password Generator Extension Privacy Policy
|
||||||
1. Developers of the extension don't affiliate with Google LLC, Mozilla Foundation or Microsoft Corporation in any way
|
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
|
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 doesn't share any personal data with third parties
|
3. This extension uses build-in cloud storage of your browser to store extension settings
|
||||||
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
|
|
||||||
|
|||||||
@@ -1,21 +1,12 @@
|
|||||||
# Password generator
|
# Password generator
|
||||||
[](https://github.com/xfox111/PasswordGeneratorExtension/releases/latest)
|
[](https://github.com/xfox111/PasswordGeneratorExtension/releases/latest)
|
||||||

|
[](https://github.com/xfox111/PasswordGeneratorExtension/commits/master)
|
||||||
[](https://addons.mozilla.org/firefox/addon/easy-password-generator/)
|
|
||||||
|
|
||||||
[](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://twitter.com/xfox111)
|
||||||
[](https://github.com/xfox111)
|
[](https://github.com/xfox111)
|
||||||
[](https://buymeacoffee.com/xfox111)
|
[](https://buymeacoffee.com/xfox111)
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
Extension for web browsers which helps you to easily generate strong passwords in one click
|
Extension for web browsers which helps you to easily generate strong passwords in one click
|
||||||
|
|
||||||
@@ -25,15 +16,23 @@ Extension for web browsers which helps you to easily generate strong passwords i
|
|||||||
- Clean and simple UI
|
- Clean and simple UI
|
||||||
- Dark mode
|
- Dark mode
|
||||||
|
|
||||||

|
<img width="1136" alt="Password generator v2.0" src="https://user-images.githubusercontent.com/28831743/188680034-a673b1b4-3153-4054-bb0d-949568de1748.png">
|
||||||
|
|
||||||
## Download
|
## Download
|
||||||
|
[](https://chrome.google.com/webstore/detail/jnjobgjobffgmgfnkpkjfjkkfhfikmfl)
|
||||||
|
[](https://addons.mozilla.org/firefox/addon/easy-password-generator/)
|
||||||
|
[](https://addons.mozilla.org/firefox/addon/easy-password-generator/)
|
||||||
|
|
||||||
- [Google Chrome Webstore](https://chrome.google.com/webstore/detail/jnjobgjobffgmgfnkpkjfjkkfhfikmfl)
|
- [Google Chrome Webstore](https://chrome.google.com/webstore/detail/jnjobgjobffgmgfnkpkjfjkkfhfikmfl)
|
||||||
- [Microsoft Edge Add-ons Webstore](https://microsoftedge.microsoft.com/addons/detail/manimdhobjbkfpeeehlhhneookiokpbj)
|
- [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/)
|
- [Firefox Add-ons](https://addons.mozilla.org/en-US/firefox/addon/easy-password-generator/)
|
||||||
- [GitHub Releases](https://github.com/xfox111/PasswordGeneratorExtension/releases/latest)
|
- [GitHub Releases](https://github.com/xfox111/PasswordGeneratorExtension/releases/latest)
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
[](https://github.com/xfox111/PasswordGeneratorExtension/issues)
|
||||||
|

|
||||||
|
[](https://github.com/xfox111/PasswordGeneratorExtension)
|
||||||
|
|
||||||
There are many ways in which you can participate in the project, for example:
|
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
|
- [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 [source code changes](https://github.com/xfox111/PasswordGeneratorExtension/pulls)
|
||||||
@@ -51,8 +50,6 @@ If you are interested in fixing issues and contributing directly to the code bas
|
|||||||
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)
|
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
|
## Copyrights
|
||||||
> ©2020 Michael "XFox" Gordeev
|
> ©2022 Eugene Fox
|
||||||
|
|
||||||
Font copyrights: Microsoft Corportation ©2019 (Additional ELUA applied)
|
[](https://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
Licensed under [MIT License](https://opensource.org/licenses/MIT)
|
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
# Security Policy
|
||||||
|
|
||||||
|
## Supported Versions
|
||||||
|
Password generator extension has a linear versioning system. The latest version is always the most secure one. 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.
|
||||||
|
|
||||||
|
### 1.x EoL
|
||||||
|
With 2.0 release bringing substantial changes to the extension, 1.x versions are no longer supported. Please update to the latest version.
|
||||||
|
|
||||||
|
If you are willing to continue using 1.x version, you can sideload it from the [releases page](https://github.com/XFox111/PasswordGeneratorExtension/releases/tag/v1.3). Use outdated versions at your own risk.
|
||||||
|
|
||||||
|
|
||||||
|
| Version | Supported |
|
||||||
|
| ------- | ------------------ |
|
||||||
|
| 2.x.x | :white_check_mark: |
|
||||||
|
| < 2.0.0 | :x: |
|
||||||
|
|
||||||
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
|
In case you find a security issue, please report it to us by [creating an issue](https://github.com/XFox111/PasswordGeneratorExtension/issues/new?assignees=xfox111&labels=bug&template=bug_report.md&title=)
|
||||||
@@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
// Craco config file
|
||||||
|
// Craco is used to separate content and background scripts from the main JS bundle
|
||||||
|
|
||||||
|
export default
|
||||||
|
{
|
||||||
|
webpack:
|
||||||
|
{
|
||||||
|
configure: (webpackConfig : any, { env, paths } : IEnvironment) =>
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
...webpackConfig,
|
||||||
|
entry:
|
||||||
|
{
|
||||||
|
main: [ env === "development" && require.resolve("react-dev-utils/webpackHotDevClient"), paths.appIndexJs ].filter(Boolean),
|
||||||
|
background: "./src/Services/BackgroundService.ts",
|
||||||
|
contentScript: "./src/Services/ContentService.ts"
|
||||||
|
},
|
||||||
|
output:
|
||||||
|
{
|
||||||
|
...webpackConfig.output,
|
||||||
|
filename: "static/js/[name].js",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IEnvironment
|
||||||
|
{
|
||||||
|
env: string;
|
||||||
|
paths:
|
||||||
|
{
|
||||||
|
[key: string]: string | string[]
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
{
|
||||||
|
"name": "password-generator",
|
||||||
|
"version": "2.0.0",
|
||||||
|
"private": true,
|
||||||
|
"dependencies":
|
||||||
|
{
|
||||||
|
"@craco/craco": "^6.4.5",
|
||||||
|
"@fluentui/react-components": "^9.2.0",
|
||||||
|
"@fluentui/react-icons": "^2.0.179",
|
||||||
|
"react": "^18.2.0",
|
||||||
|
"react-dom": "^18.2.0",
|
||||||
|
"sass": "^1.54.5",
|
||||||
|
"typescript": "^4.4.2"
|
||||||
|
},
|
||||||
|
"devDependencies":
|
||||||
|
{
|
||||||
|
"@testing-library/jest-dom": "^5.14.1",
|
||||||
|
"@testing-library/react": "^13.0.0",
|
||||||
|
"@testing-library/user-event": "^13.2.1",
|
||||||
|
"@types/chrome": "^0.0.195",
|
||||||
|
"@types/jest": "^27.0.1",
|
||||||
|
"@types/node": "^16.7.13",
|
||||||
|
"@types/react": "^18.0.0",
|
||||||
|
"@types/react-dom": "^18.0.0",
|
||||||
|
"react-scripts": "5.0.1"
|
||||||
|
},
|
||||||
|
"scripts":
|
||||||
|
{
|
||||||
|
"start": "react-scripts start",
|
||||||
|
"build": "INLINE_RUNTIME_CHUNK=false craco build",
|
||||||
|
"test": "react-scripts test",
|
||||||
|
"eject": "react-scripts eject"
|
||||||
|
},
|
||||||
|
"eslintConfig":
|
||||||
|
{
|
||||||
|
"extends":
|
||||||
|
[
|
||||||
|
"react-app",
|
||||||
|
"react-app/jest"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"browserslist":
|
||||||
|
{
|
||||||
|
"production":
|
||||||
|
[
|
||||||
|
">0.2%",
|
||||||
|
"not dead",
|
||||||
|
"not op_mini all"
|
||||||
|
],
|
||||||
|
"development":
|
||||||
|
[
|
||||||
|
"last 1 chrome version",
|
||||||
|
"last 1 firefox version",
|
||||||
|
"last 1 safari version"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,217 @@
|
|||||||
|
{
|
||||||
|
"name":
|
||||||
|
{
|
||||||
|
"message": "Password Generator",
|
||||||
|
"description": "manifest.json"
|
||||||
|
},
|
||||||
|
"description":
|
||||||
|
{
|
||||||
|
"message": "Password generator extension allows you to easily generate long and secure password in one click",
|
||||||
|
"description": "manifest.json"
|
||||||
|
},
|
||||||
|
"author":
|
||||||
|
{
|
||||||
|
"message": "Eugene Fox",
|
||||||
|
"description": "manifest.json"
|
||||||
|
},
|
||||||
|
"Password_generator":
|
||||||
|
{
|
||||||
|
"message": "Password generator",
|
||||||
|
"description": "App.tsx"
|
||||||
|
},
|
||||||
|
"Copy":
|
||||||
|
{
|
||||||
|
"message": "Copy",
|
||||||
|
"description": "PasswordView.tsx"
|
||||||
|
},
|
||||||
|
"Generate_new":
|
||||||
|
{
|
||||||
|
"message": "Generate new",
|
||||||
|
"description": "PasswordView.tsx"
|
||||||
|
},
|
||||||
|
"Exclude_special_symbols_one_time":
|
||||||
|
{
|
||||||
|
"message": "Generate password without special symbols",
|
||||||
|
"description": "PasswordView.tsx"
|
||||||
|
},
|
||||||
|
"Include_special_symbols_one_time":
|
||||||
|
{
|
||||||
|
"message": "Generate password with special symbols",
|
||||||
|
"description": "PasswordView.tsx"
|
||||||
|
},
|
||||||
|
"Settings":
|
||||||
|
{
|
||||||
|
"message": "Settings",
|
||||||
|
"description": "SettingsSection.tsx"
|
||||||
|
},
|
||||||
|
"Password_length":
|
||||||
|
{
|
||||||
|
"message": "Password length",
|
||||||
|
"description": "SettingsSection.tsx"
|
||||||
|
},
|
||||||
|
"Recommended_password_length":
|
||||||
|
{
|
||||||
|
"message": "Recommended password length",
|
||||||
|
"description": "SettingsSection.tsx"
|
||||||
|
},
|
||||||
|
"Character_options":
|
||||||
|
{
|
||||||
|
"message": "Character options",
|
||||||
|
"description": "SettingsSection.tsx"
|
||||||
|
},
|
||||||
|
"Include":
|
||||||
|
{
|
||||||
|
"message": "Include",
|
||||||
|
"description": "SettingsSection.tsx"
|
||||||
|
},
|
||||||
|
"Special_symbols":
|
||||||
|
{
|
||||||
|
"message": "Special symbols",
|
||||||
|
"description": "SettingsSection.tsx"
|
||||||
|
},
|
||||||
|
"Numeric":
|
||||||
|
{
|
||||||
|
"message": "Numeric",
|
||||||
|
"description": "SettingsSection.tsx"
|
||||||
|
},
|
||||||
|
"Uppercase":
|
||||||
|
{
|
||||||
|
"message": "Uppercase",
|
||||||
|
"description": "SettingsSection.tsx"
|
||||||
|
},
|
||||||
|
"Lowercase":
|
||||||
|
{
|
||||||
|
"message": "Lowercase",
|
||||||
|
"description": "SettingsSection.tsx"
|
||||||
|
},
|
||||||
|
"Exclude":
|
||||||
|
{
|
||||||
|
"message": "Exclude",
|
||||||
|
"description": "SettingsSection.tsx"
|
||||||
|
},
|
||||||
|
"Similar":
|
||||||
|
{
|
||||||
|
"message": "Similar",
|
||||||
|
"description": "SettingsSection.tsx"
|
||||||
|
},
|
||||||
|
"Ambiguous":
|
||||||
|
{
|
||||||
|
"message": "Ambiguous",
|
||||||
|
"description": "SettingsSection.tsx"
|
||||||
|
},
|
||||||
|
"Repeating":
|
||||||
|
{
|
||||||
|
"message": "Repeating",
|
||||||
|
"description": "SettingsSection.tsx"
|
||||||
|
},
|
||||||
|
"Add_shortcut_to_context_menu":
|
||||||
|
{
|
||||||
|
"message": "Add shortcut to context menu",
|
||||||
|
"description": "SettingsSection.tsx"
|
||||||
|
},
|
||||||
|
"Right_click_password_field_to_quickly_generate_password":
|
||||||
|
{
|
||||||
|
"message": "Right-click password field to quickly generate password",
|
||||||
|
"description": "SettingsSection.tsx"
|
||||||
|
},
|
||||||
|
"Automatically_copy_to_clipboard":
|
||||||
|
{
|
||||||
|
"message": "Automatically copy to clipboard",
|
||||||
|
"description": "SettingsSection.tsx"
|
||||||
|
},
|
||||||
|
"About":
|
||||||
|
{
|
||||||
|
"message": "About",
|
||||||
|
"description": "AboutSection.tsx"
|
||||||
|
},
|
||||||
|
"Developed_by_Eugene_Fox":
|
||||||
|
{
|
||||||
|
"message": "Developed by Eugene Fox",
|
||||||
|
"description": "AboutSection.tsx"
|
||||||
|
},
|
||||||
|
"Licensed_under":
|
||||||
|
{
|
||||||
|
"message": "Licensed under",
|
||||||
|
"description": "AboutSection.tsx"
|
||||||
|
},
|
||||||
|
"MIT_license":
|
||||||
|
{
|
||||||
|
"message": "MIT license",
|
||||||
|
"description": "AboutSection.tsx"
|
||||||
|
},
|
||||||
|
"Want_to_contribute_translation_for_your_language_":
|
||||||
|
{
|
||||||
|
"message": "Want to contribute translation for your language?",
|
||||||
|
"description": "AboutSection.tsx"
|
||||||
|
},
|
||||||
|
"Read_this_to_get_started":
|
||||||
|
{
|
||||||
|
"message": "Read this to get started",
|
||||||
|
"description": "AboutSection.tsx"
|
||||||
|
},
|
||||||
|
"My_website":
|
||||||
|
{
|
||||||
|
"message": "My website",
|
||||||
|
"description": "AboutSection.tsx"
|
||||||
|
},
|
||||||
|
"Source_code":
|
||||||
|
{
|
||||||
|
"message": "Source code",
|
||||||
|
"description": "AboutSection.tsx"
|
||||||
|
},
|
||||||
|
"Changelog":
|
||||||
|
{
|
||||||
|
"message": "Changelog",
|
||||||
|
"description": "AboutSection.tsx"
|
||||||
|
},
|
||||||
|
"Leave_feedback":
|
||||||
|
{
|
||||||
|
"message": "Leave feedback",
|
||||||
|
"description": "AboutSection.tsx"
|
||||||
|
},
|
||||||
|
"Buy_me_a_coffee":
|
||||||
|
{
|
||||||
|
"message": "Buy me a coffee",
|
||||||
|
"description": "AboutSection.tsx"
|
||||||
|
},
|
||||||
|
"Set_name":
|
||||||
|
{
|
||||||
|
"message": "Set name",
|
||||||
|
"description": "CharacterHelpDialog.tsx"
|
||||||
|
},
|
||||||
|
"Characters":
|
||||||
|
{
|
||||||
|
"message": "Characters",
|
||||||
|
"description": "CharacterHelpDialog.tsx"
|
||||||
|
},
|
||||||
|
"__etc_":
|
||||||
|
{
|
||||||
|
"message": ", etc.",
|
||||||
|
"description": "CharacterHelpDialog.tsx"
|
||||||
|
},
|
||||||
|
"OK":
|
||||||
|
{
|
||||||
|
"message": "OK",
|
||||||
|
"description": "CharacterHelpDialog.tsx"
|
||||||
|
},
|
||||||
|
"Either_lowercase_or_uppercase_characters_must_be_included":
|
||||||
|
{
|
||||||
|
"message": "Either lowercase or uppercase characters must be included",
|
||||||
|
"description": "Generator.tsx"
|
||||||
|
},
|
||||||
|
"Selected_length_is_too_long_to_exclude_repeating_characters":
|
||||||
|
{
|
||||||
|
"message": "Selected length is too long to exclude repeating characters",
|
||||||
|
"description": "Generator.tsx"
|
||||||
|
},
|
||||||
|
"Quick_generator_is_only_available_on_password_fields":
|
||||||
|
{
|
||||||
|
"message": "Quick generator is only available on password fields",
|
||||||
|
"description": "ContentService.tsx"
|
||||||
|
},
|
||||||
|
"Quick_generate_password":
|
||||||
|
{
|
||||||
|
"message": "Quick generate password",
|
||||||
|
"description": "BackgroundService.tsx"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,217 @@
|
|||||||
|
{
|
||||||
|
"name":
|
||||||
|
{
|
||||||
|
"message": "Генератор паролей",
|
||||||
|
"description": "manifest.json"
|
||||||
|
},
|
||||||
|
"description":
|
||||||
|
{
|
||||||
|
"message": "Расширение, позволяющее легко генерировать сложные и надежные пароли в один клик",
|
||||||
|
"description": "manifest.json"
|
||||||
|
},
|
||||||
|
"author":
|
||||||
|
{
|
||||||
|
"message": "Евгений Лис",
|
||||||
|
"description": "manifest.json"
|
||||||
|
},
|
||||||
|
"Password_generator":
|
||||||
|
{
|
||||||
|
"message": "Генератор паролей",
|
||||||
|
"description": "App.tsx"
|
||||||
|
},
|
||||||
|
"Copy":
|
||||||
|
{
|
||||||
|
"message": "Копировать",
|
||||||
|
"description": "PasswordView.tsx"
|
||||||
|
},
|
||||||
|
"Generate_new":
|
||||||
|
{
|
||||||
|
"message": "Создать новый",
|
||||||
|
"description": "PasswordView.tsx"
|
||||||
|
},
|
||||||
|
"Exclude_special_symbols_one_time":
|
||||||
|
{
|
||||||
|
"message": "Сгенерировать пароль без спецсимволов",
|
||||||
|
"description": "PasswordView.tsx"
|
||||||
|
},
|
||||||
|
"Include_special_symbols_one_time":
|
||||||
|
{
|
||||||
|
"message": "Сгенерировать пароль со спецсимволами",
|
||||||
|
"description": "PasswordView.tsx"
|
||||||
|
},
|
||||||
|
"Settings":
|
||||||
|
{
|
||||||
|
"message": "Настройки",
|
||||||
|
"description": "SettingsSection.tsx"
|
||||||
|
},
|
||||||
|
"Password_length":
|
||||||
|
{
|
||||||
|
"message": "Длина пароля",
|
||||||
|
"description": "SettingsSection.tsx"
|
||||||
|
},
|
||||||
|
"Recommended_password_length":
|
||||||
|
{
|
||||||
|
"message": "Рекомендуемая длина пароля",
|
||||||
|
"description": "SettingsSection.tsx"
|
||||||
|
},
|
||||||
|
"Character_options":
|
||||||
|
{
|
||||||
|
"message": "Настройки символов",
|
||||||
|
"description": "SettingsSection.tsx"
|
||||||
|
},
|
||||||
|
"Include":
|
||||||
|
{
|
||||||
|
"message": "Включить",
|
||||||
|
"description": "SettingsSection.tsx"
|
||||||
|
},
|
||||||
|
"Special_symbols":
|
||||||
|
{
|
||||||
|
"message": "Специальные символы",
|
||||||
|
"description": "SettingsSection.tsx"
|
||||||
|
},
|
||||||
|
"Numeric":
|
||||||
|
{
|
||||||
|
"message": "Цифры",
|
||||||
|
"description": "SettingsSection.tsx"
|
||||||
|
},
|
||||||
|
"Uppercase":
|
||||||
|
{
|
||||||
|
"message": "Прописные буквы",
|
||||||
|
"description": "SettingsSection.tsx"
|
||||||
|
},
|
||||||
|
"Lowercase":
|
||||||
|
{
|
||||||
|
"message": "Строчные буквы",
|
||||||
|
"description": "SettingsSection.tsx"
|
||||||
|
},
|
||||||
|
"Exclude":
|
||||||
|
{
|
||||||
|
"message": "Исключить",
|
||||||
|
"description": "SettingsSection.tsx"
|
||||||
|
},
|
||||||
|
"Similar":
|
||||||
|
{
|
||||||
|
"message": "Похожие",
|
||||||
|
"description": "SettingsSection.tsx"
|
||||||
|
},
|
||||||
|
"Ambiguous":
|
||||||
|
{
|
||||||
|
"message": "Особые",
|
||||||
|
"description": "SettingsSection.tsx"
|
||||||
|
},
|
||||||
|
"Repeating":
|
||||||
|
{
|
||||||
|
"message": "Повторяющиеся",
|
||||||
|
"description": "SettingsSection.tsx"
|
||||||
|
},
|
||||||
|
"Add_shortcut_to_context_menu":
|
||||||
|
{
|
||||||
|
"message": "Добавить расширение в контекстное меню",
|
||||||
|
"description": "SettingsSection.tsx"
|
||||||
|
},
|
||||||
|
"Right_click_password_field_to_quickly_generate_password":
|
||||||
|
{
|
||||||
|
"message": "Щелкните правой кнопкой мыши по полю ввода пароля, чтобы быстро сгенерировать пароль",
|
||||||
|
"description": "SettingsSection.tsx"
|
||||||
|
},
|
||||||
|
"Automatically_copy_to_clipboard":
|
||||||
|
{
|
||||||
|
"message": "Автоматически копировать в буфер обмена",
|
||||||
|
"description": "SettingsSection.tsx"
|
||||||
|
},
|
||||||
|
"About":
|
||||||
|
{
|
||||||
|
"message": "О расширении",
|
||||||
|
"description": "AboutSection.tsx"
|
||||||
|
},
|
||||||
|
"Developed_by_Eugene_Fox":
|
||||||
|
{
|
||||||
|
"message": "Разработчик Евгений Лис",
|
||||||
|
"description": "AboutSection.tsx"
|
||||||
|
},
|
||||||
|
"Licensed_under":
|
||||||
|
{
|
||||||
|
"message": "Лицензия",
|
||||||
|
"description": "AboutSection.tsx"
|
||||||
|
},
|
||||||
|
"MIT_license":
|
||||||
|
{
|
||||||
|
"message": "MIT",
|
||||||
|
"description": "AboutSection.tsx"
|
||||||
|
},
|
||||||
|
"Want_to_contribute_translation_for_your_language_":
|
||||||
|
{
|
||||||
|
"message": "Хотите помочь с переводом на свой язык?",
|
||||||
|
"description": "AboutSection.tsx"
|
||||||
|
},
|
||||||
|
"Read_this_to_get_started":
|
||||||
|
{
|
||||||
|
"message": "Прочтите эту статью",
|
||||||
|
"description": "AboutSection.tsx"
|
||||||
|
},
|
||||||
|
"My_website":
|
||||||
|
{
|
||||||
|
"message": "Мой сайт",
|
||||||
|
"description": "AboutSection.tsx"
|
||||||
|
},
|
||||||
|
"Source_code":
|
||||||
|
{
|
||||||
|
"message": "Исходный код",
|
||||||
|
"description": "AboutSection.tsx"
|
||||||
|
},
|
||||||
|
"Changelog":
|
||||||
|
{
|
||||||
|
"message": "Что нового",
|
||||||
|
"description": "AboutSection.tsx"
|
||||||
|
},
|
||||||
|
"Leave_feedback":
|
||||||
|
{
|
||||||
|
"message": "Оставить отзыв",
|
||||||
|
"description": "AboutSection.tsx"
|
||||||
|
},
|
||||||
|
"Buy_me_a_coffee":
|
||||||
|
{
|
||||||
|
"message": "Спонсировать",
|
||||||
|
"description": "AboutSection.tsx"
|
||||||
|
},
|
||||||
|
"Set_name":
|
||||||
|
{
|
||||||
|
"message": "Название набора",
|
||||||
|
"description": "CharacterHelpDialog.tsx"
|
||||||
|
},
|
||||||
|
"Characters":
|
||||||
|
{
|
||||||
|
"message": "Символы",
|
||||||
|
"description": "CharacterHelpDialog.tsx"
|
||||||
|
},
|
||||||
|
"__etc_":
|
||||||
|
{
|
||||||
|
"message": " и т.д.",
|
||||||
|
"description": "CharacterHelpDialog.tsx"
|
||||||
|
},
|
||||||
|
"OK":
|
||||||
|
{
|
||||||
|
"message": "ОК",
|
||||||
|
"description": "CharacterHelpDialog.tsx"
|
||||||
|
},
|
||||||
|
"Either_lowercase_or_uppercase_characters_must_be_included":
|
||||||
|
{
|
||||||
|
"message": "Должны быть включены строчные или прописные буквы",
|
||||||
|
"description": "Generator.tsx"
|
||||||
|
},
|
||||||
|
"Selected_length_is_too_long_to_exclude_repeating_characters":
|
||||||
|
{
|
||||||
|
"message": "Выбранная длина слишком велика для исключения повторяющихся символов",
|
||||||
|
"description": "Generator.tsx"
|
||||||
|
},
|
||||||
|
"Quick_generator_is_only_available_on_password_fields":
|
||||||
|
{
|
||||||
|
"message": "Быстрый генератор доступен только для полей ввода пароля",
|
||||||
|
"description": "ContentService.tsx"
|
||||||
|
},
|
||||||
|
"Quick_generate_password":
|
||||||
|
{
|
||||||
|
"message": "Сгенерировать пароль",
|
||||||
|
"description": "BackgroundService.tsx"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,217 @@
|
|||||||
|
{
|
||||||
|
"name":
|
||||||
|
{
|
||||||
|
"message": "Генератор паролів",
|
||||||
|
"description": "manifest.json"
|
||||||
|
},
|
||||||
|
"description":
|
||||||
|
{
|
||||||
|
"message": "Розширення, яке дозволяє легко генерувати складні та надійні паролі в один клік",
|
||||||
|
"description": "manifest.json"
|
||||||
|
},
|
||||||
|
"author":
|
||||||
|
{
|
||||||
|
"message": "Євген Лис",
|
||||||
|
"description": "manifest.json"
|
||||||
|
},
|
||||||
|
"Password_generator":
|
||||||
|
{
|
||||||
|
"message": "Генератор паролів",
|
||||||
|
"description": "App.tsx"
|
||||||
|
},
|
||||||
|
"Copy":
|
||||||
|
{
|
||||||
|
"message": "Копіювати",
|
||||||
|
"description": "PasswordView.tsx"
|
||||||
|
},
|
||||||
|
"Generate_new":
|
||||||
|
{
|
||||||
|
"message": "Генерувати новий",
|
||||||
|
"description": "PasswordView.tsx"
|
||||||
|
},
|
||||||
|
"Exclude_special_symbols_one_time":
|
||||||
|
{
|
||||||
|
"message": "Генерувати пароль без спеціальних символів",
|
||||||
|
"description": "PasswordView.tsx"
|
||||||
|
},
|
||||||
|
"Include_special_symbols_one_time":
|
||||||
|
{
|
||||||
|
"message": "Генерувати пароль з спеціальними символами",
|
||||||
|
"description": "PasswordView.tsx"
|
||||||
|
},
|
||||||
|
"Settings":
|
||||||
|
{
|
||||||
|
"message": "Налаштування",
|
||||||
|
"description": "SettingsSection.tsx"
|
||||||
|
},
|
||||||
|
"Password_length":
|
||||||
|
{
|
||||||
|
"message": "Довжина паролю",
|
||||||
|
"description": "SettingsSection.tsx"
|
||||||
|
},
|
||||||
|
"Recommended_password_length":
|
||||||
|
{
|
||||||
|
"message": "Рекомендована довжина паролю",
|
||||||
|
"description": "SettingsSection.tsx"
|
||||||
|
},
|
||||||
|
"Character_options":
|
||||||
|
{
|
||||||
|
"message": "Параметри символів",
|
||||||
|
"description": "SettingsSection.tsx"
|
||||||
|
},
|
||||||
|
"Include":
|
||||||
|
{
|
||||||
|
"message": "Включити",
|
||||||
|
"description": "SettingsSection.tsx"
|
||||||
|
},
|
||||||
|
"Special_symbols":
|
||||||
|
{
|
||||||
|
"message": "Спеціальні символи",
|
||||||
|
"description": "SettingsSection.tsx"
|
||||||
|
},
|
||||||
|
"Numeric":
|
||||||
|
{
|
||||||
|
"message": "Цифри",
|
||||||
|
"description": "SettingsSection.tsx"
|
||||||
|
},
|
||||||
|
"Uppercase":
|
||||||
|
{
|
||||||
|
"message": "Великі літери",
|
||||||
|
"description": "SettingsSection.tsx"
|
||||||
|
},
|
||||||
|
"Lowercase":
|
||||||
|
{
|
||||||
|
"message": "Малі літери",
|
||||||
|
"description": "SettingsSection.tsx"
|
||||||
|
},
|
||||||
|
"Exclude":
|
||||||
|
{
|
||||||
|
"message": "Виключити",
|
||||||
|
"description": "SettingsSection.tsx"
|
||||||
|
},
|
||||||
|
"Similar":
|
||||||
|
{
|
||||||
|
"message": "Схожі",
|
||||||
|
"description": "SettingsSection.tsx"
|
||||||
|
},
|
||||||
|
"Ambiguous":
|
||||||
|
{
|
||||||
|
"message": "Особливі",
|
||||||
|
"description": "SettingsSection.tsx"
|
||||||
|
},
|
||||||
|
"Repeating":
|
||||||
|
{
|
||||||
|
"message": "Повторювані",
|
||||||
|
"description": "SettingsSection.tsx"
|
||||||
|
},
|
||||||
|
"Add_shortcut_to_context_menu":
|
||||||
|
{
|
||||||
|
"message": "Додати розширення до контекстного меню",
|
||||||
|
"description": "SettingsSection.tsx"
|
||||||
|
},
|
||||||
|
"Right_click_password_field_to_quickly_generate_password":
|
||||||
|
{
|
||||||
|
"message": "Правий клік на поле вводу паролю для швидкого генерування паролю",
|
||||||
|
"description": "SettingsSection.tsx"
|
||||||
|
},
|
||||||
|
"Automatically_copy_to_clipboard":
|
||||||
|
{
|
||||||
|
"message": "Автоматично копіювати в буфер обміну",
|
||||||
|
"description": "SettingsSection.tsx"
|
||||||
|
},
|
||||||
|
"About":
|
||||||
|
{
|
||||||
|
"message": "Про розширення",
|
||||||
|
"description": "AboutSection.tsx"
|
||||||
|
},
|
||||||
|
"Developed_by_Eugene_Fox":
|
||||||
|
{
|
||||||
|
"message": "Розроблено Євгеном Лисом",
|
||||||
|
"description": "AboutSection.tsx"
|
||||||
|
},
|
||||||
|
"Licensed_under":
|
||||||
|
{
|
||||||
|
"message": "Ліцензовано під",
|
||||||
|
"description": "AboutSection.tsx"
|
||||||
|
},
|
||||||
|
"MIT_license":
|
||||||
|
{
|
||||||
|
"message": "MIT",
|
||||||
|
"description": "AboutSection.tsx"
|
||||||
|
},
|
||||||
|
"Want_to_contribute_translation_for_your_language_":
|
||||||
|
{
|
||||||
|
"message": "Хочете допомогти перекласти розширення на свою мову?",
|
||||||
|
"description": "AboutSection.tsx"
|
||||||
|
},
|
||||||
|
"Read_this_to_get_started":
|
||||||
|
{
|
||||||
|
"message": "Прочитайте цю статтю",
|
||||||
|
"description": "AboutSection.tsx"
|
||||||
|
},
|
||||||
|
"My_website":
|
||||||
|
{
|
||||||
|
"message": "Моя веб-сторінка",
|
||||||
|
"description": "AboutSection.tsx"
|
||||||
|
},
|
||||||
|
"Source_code":
|
||||||
|
{
|
||||||
|
"message": "Вихідний код",
|
||||||
|
"description": "AboutSection.tsx"
|
||||||
|
},
|
||||||
|
"Changelog":
|
||||||
|
{
|
||||||
|
"message": "Що нового",
|
||||||
|
"description": "AboutSection.tsx"
|
||||||
|
},
|
||||||
|
"Leave_feedback":
|
||||||
|
{
|
||||||
|
"message": "Залишити відгук",
|
||||||
|
"description": "AboutSection.tsx"
|
||||||
|
},
|
||||||
|
"Buy_me_a_coffee":
|
||||||
|
{
|
||||||
|
"message": "Підтримати",
|
||||||
|
"description": "AboutSection.tsx"
|
||||||
|
},
|
||||||
|
"Set_name":
|
||||||
|
{
|
||||||
|
"message": "Назва набору",
|
||||||
|
"description": "CharacterHelpDialog.tsx"
|
||||||
|
},
|
||||||
|
"Characters":
|
||||||
|
{
|
||||||
|
"message": "Символи",
|
||||||
|
"description": "CharacterHelpDialog.tsx"
|
||||||
|
},
|
||||||
|
"__etc_":
|
||||||
|
{
|
||||||
|
"message": " і т.д.",
|
||||||
|
"description": "CharacterHelpDialog.tsx"
|
||||||
|
},
|
||||||
|
"OK":
|
||||||
|
{
|
||||||
|
"message": "OK",
|
||||||
|
"description": "CharacterHelpDialog.tsx"
|
||||||
|
},
|
||||||
|
"Either_lowercase_or_uppercase_characters_must_be_included":
|
||||||
|
{
|
||||||
|
"message": "Повинні бути включені малі або великі літери",
|
||||||
|
"description": "Generator.tsx"
|
||||||
|
},
|
||||||
|
"Selected_length_is_too_long_to_exclude_repeating_characters":
|
||||||
|
{
|
||||||
|
"message": "Вибрана довжина занадто велика для виключення повторюваних символів",
|
||||||
|
"description": "Generator.tsx"
|
||||||
|
},
|
||||||
|
"Quick_generator_is_only_available_on_password_fields":
|
||||||
|
{
|
||||||
|
"message": "Швидкий генератор доступний тільки для полів вводу паролів",
|
||||||
|
"description": "ContentService.tsx"
|
||||||
|
},
|
||||||
|
"Quick_generate_password":
|
||||||
|
{
|
||||||
|
"message": "Згенерувати пароль",
|
||||||
|
"description": "BackgroundService.tsx"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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,15 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>Password Generator</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
|
<div id="root"></div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -1,32 +1,40 @@
|
|||||||
{
|
{
|
||||||
|
"$schema": "https://json.schemastore.org/chrome-manifest.json",
|
||||||
|
"manifest_version": 3,
|
||||||
|
|
||||||
"name": "__MSG_name__",
|
"name": "__MSG_name__",
|
||||||
"version": "1.3",
|
|
||||||
"manifest_version": 2,
|
|
||||||
"description": "__MSG_description__",
|
"description": "__MSG_description__",
|
||||||
"author": "__MSG_author__",
|
"author": "__MSG_author__",
|
||||||
|
|
||||||
|
"version": "2.0.0",
|
||||||
"default_locale": "en",
|
"default_locale": "en",
|
||||||
|
|
||||||
"permissions":
|
"permissions":
|
||||||
[
|
[
|
||||||
"storage",
|
"storage",
|
||||||
"<all_urls>",
|
|
||||||
"contextMenus",
|
"contextMenus",
|
||||||
"clipboardWrite"
|
"clipboardWrite"
|
||||||
],
|
],
|
||||||
|
|
||||||
|
"background":
|
||||||
|
{
|
||||||
|
"service_worker": "./static/js/background.js",
|
||||||
|
"type": "module"
|
||||||
|
},
|
||||||
"content_scripts":
|
"content_scripts":
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"matches": [ "<all_urls>" ],
|
"matches": [ "<all_urls>" ],
|
||||||
"js": [ "js/script.js" ],
|
"js": [ "./static/js/contentScript.js" ],
|
||||||
"run_at": "document_idle",
|
"run_at": "document_idle",
|
||||||
"all_frames": true
|
"all_frames": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"background":
|
|
||||||
|
"action":
|
||||||
{
|
{
|
||||||
"scripts": [ "js/background.js", "js/script.js" ],
|
"default_popup": "index.html",
|
||||||
"persistent": false
|
"default_title": "__MSG_name__"
|
||||||
},
|
},
|
||||||
|
|
||||||
"icons":
|
"icons":
|
||||||
@@ -36,11 +44,6 @@
|
|||||||
"32": "icons/icon-32.png",
|
"32": "icons/icon-32.png",
|
||||||
"16": "icons/icon-16.png"
|
"16": "icons/icon-16.png"
|
||||||
},
|
},
|
||||||
"browser_action":
|
|
||||||
{
|
|
||||||
"default_icon": "icons/icon-32.png",
|
|
||||||
"default_popup": "options.html"
|
|
||||||
},
|
|
||||||
|
|
||||||
"browser_specific_settings":
|
"browser_specific_settings":
|
||||||
{
|
{
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
main
|
||||||
|
{
|
||||||
|
width: 400px;
|
||||||
|
|
||||||
|
h1, h2, h3, h4, h5, h6, p
|
||||||
|
{
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Buy me a coffee button style
|
||||||
|
.fui-Button.bmc
|
||||||
|
{
|
||||||
|
background-color: var(--colorPaletteDarkOrangeBorder2) !important;
|
||||||
|
|
||||||
|
&:hover
|
||||||
|
{
|
||||||
|
background-color: var(--colorPaletteDarkOrangeForeground1) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active
|
||||||
|
{
|
||||||
|
background-color: var(--colorPaletteDarkOrangeBackground2) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overrides for default FluentUI styles
|
||||||
|
a.fui-Button
|
||||||
|
{
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fui-Accordion
|
||||||
|
{
|
||||||
|
section
|
||||||
|
{
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fui-AccordionHeader__expandIcon > svg
|
||||||
|
{
|
||||||
|
transition-duration: .5s;
|
||||||
|
transition-timing-function: var(--curveDecelerateMax);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.scaleUpIn
|
||||||
|
{
|
||||||
|
animation-name: scaleUpInAnim;
|
||||||
|
animation-timing-function: var(--curveEasyEaseMax);
|
||||||
|
animation-duration: .5s;
|
||||||
|
}
|
||||||
|
@keyframes scaleUpInAnim
|
||||||
|
{
|
||||||
|
from
|
||||||
|
{
|
||||||
|
transform: scale(.5);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to
|
||||||
|
{
|
||||||
|
transform: scale(1);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.spin
|
||||||
|
{
|
||||||
|
animation-name: spinAnim;
|
||||||
|
animation-timing-function: var(--curveEasyEaseMax);
|
||||||
|
animation-duration: .5s;
|
||||||
|
}
|
||||||
|
@keyframes spinAnim
|
||||||
|
{
|
||||||
|
from
|
||||||
|
{
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
to
|
||||||
|
{
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.fadeIn
|
||||||
|
{
|
||||||
|
animation-name: fadeInAnim;
|
||||||
|
animation-timing-function: var(--curveDecelerateMin);
|
||||||
|
animation-duration: .5s;
|
||||||
|
}
|
||||||
|
@keyframes fadeInAnim
|
||||||
|
{
|
||||||
|
from
|
||||||
|
{
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to
|
||||||
|
{
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.stack
|
||||||
|
{
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
&.horizontal
|
||||||
|
{
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.gap
|
||||||
|
{
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { Accordion, FluentProvider, Text, Theme, Title2, webDarkTheme, webLightTheme } from "@fluentui/react-components";
|
||||||
|
import "./App.scss";
|
||||||
|
import SettingsSection from "./Components/SettingsSection";
|
||||||
|
import AboutSection from "./Components/AboutSection";
|
||||||
|
import Package from "../package.json";
|
||||||
|
import PasswordView from "./Components/PasswordView";
|
||||||
|
import Settings from "./Utils/Settings";
|
||||||
|
import GeneratorOptions from "./Utils/GeneratorOptions";
|
||||||
|
import { loc } from "./Utils/Localization";
|
||||||
|
|
||||||
|
interface IStates
|
||||||
|
{
|
||||||
|
theme: Theme;
|
||||||
|
generatorOptions: GeneratorOptions;
|
||||||
|
settings: Settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IProps
|
||||||
|
{
|
||||||
|
settings: Settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class App extends React.Component<IProps, IStates>
|
||||||
|
{
|
||||||
|
constructor(props: IProps)
|
||||||
|
{
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state =
|
||||||
|
{
|
||||||
|
theme: this.UpdateTheme(),
|
||||||
|
generatorOptions: new GeneratorOptions(),
|
||||||
|
settings: props.settings
|
||||||
|
};
|
||||||
|
|
||||||
|
Settings.OnChanged = (changes) => this.setState({ settings: { ...this.state.settings, ...changes } });
|
||||||
|
GeneratorOptions.OnChanged = (changes) => this.setState({ generatorOptions: { ...this.state.generatorOptions, ...changes } });
|
||||||
|
}
|
||||||
|
|
||||||
|
public async componentDidMount(): Promise<void>
|
||||||
|
{
|
||||||
|
window
|
||||||
|
.matchMedia("(prefers-color-scheme: dark)")
|
||||||
|
.addEventListener("change",
|
||||||
|
(arg: MediaQueryListEvent) =>
|
||||||
|
this.setState({ theme: this.UpdateTheme(arg.matches) })
|
||||||
|
);
|
||||||
|
|
||||||
|
this.setState({ generatorOptions: await GeneratorOptions.Init() });
|
||||||
|
}
|
||||||
|
|
||||||
|
private UpdateTheme(isDark?: boolean): Theme
|
||||||
|
{
|
||||||
|
let theme: Theme = (isDark ?? window.matchMedia("(prefers-color-scheme: dark)").matches) ? webDarkTheme : webLightTheme;
|
||||||
|
document.body.style.backgroundColor = theme.colorNeutralBackground1;
|
||||||
|
|
||||||
|
return theme;
|
||||||
|
}
|
||||||
|
|
||||||
|
public render(): JSX.Element
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
<FluentProvider theme={ this.state.theme }>
|
||||||
|
<main className="stack gap">
|
||||||
|
<header className="stack horizontal gap">
|
||||||
|
<Title2 as="h1">{ loc("Password generator") }</Title2>
|
||||||
|
<Text as="span">v{ Package.version }</Text>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<PasswordView settings={ this.state.settings } generatorOptions={ this.state.generatorOptions } />
|
||||||
|
|
||||||
|
<Accordion collapsible>
|
||||||
|
<SettingsSection
|
||||||
|
generatorOptions={ this.state.generatorOptions }
|
||||||
|
settings={ this.state.settings } />
|
||||||
|
<AboutSection />
|
||||||
|
</Accordion>
|
||||||
|
</main>
|
||||||
|
</FluentProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 8.1 KiB |
@@ -0,0 +1,54 @@
|
|||||||
|
import { AccordionItem, AccordionHeader, AccordionPanel, Link, Text, Button } from "@fluentui/react-components";
|
||||||
|
import { InfoRegular, PersonFeedbackRegular } from "@fluentui/react-icons";
|
||||||
|
import { ReactComponent as BuyMeACoffee } from "../Assets/BuyMeACoffee.svg";
|
||||||
|
import React from "react";
|
||||||
|
import { loc } from "../Utils/Localization";
|
||||||
|
|
||||||
|
export default class AboutSection extends React.Component
|
||||||
|
{
|
||||||
|
public render(): JSX.Element
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
<AccordionItem value="about">
|
||||||
|
<AccordionHeader as="h2" icon={ <InfoRegular /> }>{ loc("About") }</AccordionHeader>
|
||||||
|
<AccordionPanel>
|
||||||
|
<section className="stack gap fadeIn">
|
||||||
|
<Text as="p">
|
||||||
|
{ loc("Developed by Eugene Fox") } (<Link href="https://twitter.com/xfox111" target="_blank">@xfox111</Link>)
|
||||||
|
<br />
|
||||||
|
{ loc("Licensed under") } <Link href="https://github.com/XFox111/PasswordGeneratorExtension/blob/master/LICENSE" target="_blank">{ loc("MIT license") }</Link>
|
||||||
|
</Text>
|
||||||
|
<Text as="p">
|
||||||
|
{ loc("Want to contribute translation for your language?") } <Link href="https://github.com/XFox111/PasswordGeneratorExtension/blob/master/CONTRIBUTING.md" target="_blank">{ loc("Read this to get started") }</Link>
|
||||||
|
</Text>
|
||||||
|
<Text as="p">
|
||||||
|
<Link href="https://xfox111.net/" target="_blank">{ loc("My website") }</Link>
|
||||||
|
<br />
|
||||||
|
<Link href="https://github.com/xfox111/PasswordGeneratorExtension" target="_blank">{ loc("Source code") }</Link>
|
||||||
|
<br />
|
||||||
|
<Link href="https://github.com/XFox111/PasswordGeneratorExtension/releases/latest" target="_blank">{ loc("Changelog") }</Link>
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<div className="stack horizontal gap">
|
||||||
|
<Button
|
||||||
|
as="a" target="_blank"
|
||||||
|
href="mailto:feedback@xfox111.net"
|
||||||
|
appearance="primary" icon={ <PersonFeedbackRegular /> }>
|
||||||
|
|
||||||
|
{ loc("Leave feedback") }
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
as="a" target="_blank"
|
||||||
|
href="https://buymeacoffee.com/xfox111"
|
||||||
|
className="bmc" appearance="primary" icon={ <BuyMeACoffee /> }>
|
||||||
|
|
||||||
|
{ loc("Buy me a coffee") }
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</AccordionPanel>
|
||||||
|
</AccordionItem>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
import { Button, Text } from "@fluentui/react-components";
|
||||||
|
import { Dialog, DialogTrigger, DialogSurface, DialogTitle, DialogBody, Table, TableHeader, TableRow, TableHeaderCell, TableBody, TableCell, DialogActions } from "@fluentui/react-components/unstable";
|
||||||
|
import { QuestionCircleRegular } from "@fluentui/react-icons";
|
||||||
|
import React from "react";
|
||||||
|
import Generator from "../Utils/Generator";
|
||||||
|
import { loc } from "../Utils/Localization";
|
||||||
|
|
||||||
|
export default class CharacterHelpDialog extends React.Component
|
||||||
|
{
|
||||||
|
public render(): JSX.Element
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
<Dialog>
|
||||||
|
<DialogTrigger>
|
||||||
|
<Button appearance="subtle" style={ { marginLeft: 5 } } icon={ <QuestionCircleRegular /> } />
|
||||||
|
</DialogTrigger>
|
||||||
|
<DialogSurface aria-label="label">
|
||||||
|
<DialogTitle>{ loc("Character options") }</DialogTitle>
|
||||||
|
<DialogBody>
|
||||||
|
<Table>
|
||||||
|
<TableHeader>
|
||||||
|
<TableRow>
|
||||||
|
<TableHeaderCell>{ loc("Set_name") }</TableHeaderCell>
|
||||||
|
<TableHeaderCell>{ loc("Characters") }</TableHeaderCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>{ loc("Lowercase") }</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Text font="monospace">{ Generator.Lowercase.substring(0, 10) }{ loc(", etc.") }</Text>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>{ loc("Uppercase") }</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Text font="monospace">{ Generator.Uppercase.substring(0, 10) }{ loc(", etc.") }</Text>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>{ loc("Numeric") }</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Text font="monospace">{ Generator.Numeric }</Text>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>{ loc("Special symbols") }</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Text font="monospace">{ Generator.SpecialCharacters }</Text>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>{ loc("Ambiguous") }</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Text font="monospace">{ Generator.AmbiguousCharacters }</Text>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell>{ loc("Similar") }</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Text font="monospace">{ Generator.SimilarCharacters }</Text>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</DialogBody>
|
||||||
|
<DialogActions>
|
||||||
|
<DialogTrigger>
|
||||||
|
<Button appearance="secondary">{ loc("OK") }</Button>
|
||||||
|
</DialogTrigger>
|
||||||
|
</DialogActions>
|
||||||
|
</DialogSurface>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
import { Input, Button, Link, Text, Tooltip } from "@fluentui/react-components";
|
||||||
|
import { Alert } from "@fluentui/react-components/unstable";
|
||||||
|
import { ArrowClockwiseRegular, CheckmarkRegular, CopyRegular } from "@fluentui/react-icons";
|
||||||
|
import React from "react";
|
||||||
|
import Generator from "../Utils/Generator";
|
||||||
|
import GeneratorOptions from "../Utils/GeneratorOptions";
|
||||||
|
import { loc } from "../Utils/Localization";
|
||||||
|
import Settings from "../Utils/Settings";
|
||||||
|
|
||||||
|
interface IStates
|
||||||
|
{
|
||||||
|
password: string;
|
||||||
|
error?: string;
|
||||||
|
copyIcon: JSX.Element;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IProps
|
||||||
|
{
|
||||||
|
generatorOptions: GeneratorOptions;
|
||||||
|
settings: Settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class PasswordView extends React.Component<IProps, IStates>
|
||||||
|
{
|
||||||
|
constructor(props: IProps)
|
||||||
|
{
|
||||||
|
super(props);
|
||||||
|
this.state =
|
||||||
|
{
|
||||||
|
password: Generator.GeneratePassword(props.generatorOptions),
|
||||||
|
error: Generator.ValidateProps(props.generatorOptions),
|
||||||
|
copyIcon: <CopyRegular className="scaleUpIn" />,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private OnCopyPassword(password : string): void
|
||||||
|
{
|
||||||
|
console.log("PasswordView.OnCopyPassword");
|
||||||
|
|
||||||
|
if (!document.hasFocus())
|
||||||
|
return;
|
||||||
|
|
||||||
|
window.navigator.clipboard.writeText(password);
|
||||||
|
|
||||||
|
this.setState({ copyIcon: <CheckmarkRegular className="scaleUpIn" /> });
|
||||||
|
setTimeout(() => this.setState({ copyIcon: <CopyRegular className="scaleUpIn" /> }), 3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
private OnRefreshPassword(): void
|
||||||
|
{
|
||||||
|
console.log("PasswordView.OnRefreshPassword");
|
||||||
|
|
||||||
|
let password : string = Generator.GeneratePassword(this.props.generatorOptions);
|
||||||
|
|
||||||
|
this.setState({ password });
|
||||||
|
|
||||||
|
document.querySelector("svg#refresh-btn")?.classList.add("spin");
|
||||||
|
setTimeout(() => document.querySelector("svg#refresh-btn")?.classList.remove("spin"), 600);
|
||||||
|
|
||||||
|
if (this.props.settings.Autocopy)
|
||||||
|
this.OnCopyPassword(password);
|
||||||
|
}
|
||||||
|
|
||||||
|
public componentDidUpdate(prevProps: Readonly<IProps>): void
|
||||||
|
{
|
||||||
|
console.log("PasswordView.componentDidUpdate");
|
||||||
|
|
||||||
|
// Converting to JSON is the easiest way to compare objects
|
||||||
|
if (JSON.stringify(prevProps.generatorOptions) === JSON.stringify(this.props.generatorOptions))
|
||||||
|
return;
|
||||||
|
|
||||||
|
let error : string = Generator.ValidateProps(this.props.generatorOptions);
|
||||||
|
let password = Generator.GeneratePassword(this.props.generatorOptions);
|
||||||
|
|
||||||
|
this.setState({ password, error });
|
||||||
|
|
||||||
|
if (!error && this.props.settings.Autocopy)
|
||||||
|
this.OnCopyPassword(password);
|
||||||
|
}
|
||||||
|
|
||||||
|
private AlterSpecialsOnce(useSpecials : boolean) : void
|
||||||
|
{
|
||||||
|
console.log("PasswordView.AlterSpecialsOnce", `useSpecials: ${useSpecials}`);
|
||||||
|
|
||||||
|
let options : GeneratorOptions = { ...this.props.generatorOptions, Special: useSpecials, ExcludeAmbiguous: true };
|
||||||
|
|
||||||
|
let error : string = Generator.ValidateProps(options);
|
||||||
|
let password : string = Generator.GeneratePassword(options);
|
||||||
|
|
||||||
|
this.setState({ password, error });
|
||||||
|
|
||||||
|
if (error)
|
||||||
|
setTimeout(() => this.OnRefreshPassword(), 3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
public render(): JSX.Element
|
||||||
|
{
|
||||||
|
return this.state.error ?
|
||||||
|
<Alert intent="error" className="fadeIn">{ this.state.error }</Alert>
|
||||||
|
:
|
||||||
|
<section className="stack fadeIn">
|
||||||
|
<Input
|
||||||
|
value={ this.state.password } readOnly
|
||||||
|
contentAfter={
|
||||||
|
<>
|
||||||
|
<Tooltip content={ loc("Copy") } relationship="label">
|
||||||
|
<Button onClick={ () => this.OnCopyPassword(this.state.password) } appearance="subtle" icon={ this.state.copyIcon } />
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
<Tooltip content={ loc("Generate new") } relationship="label">
|
||||||
|
<Button onClick={ () => this.OnRefreshPassword() } appearance="subtle" icon={ <ArrowClockwiseRegular id="refresh-btn" /> } />
|
||||||
|
</Tooltip>
|
||||||
|
</>
|
||||||
|
} />
|
||||||
|
<Text>
|
||||||
|
{ this.props.generatorOptions.Special ?
|
||||||
|
<Link onClick={ () => this.AlterSpecialsOnce(false) }>
|
||||||
|
{ loc("Exclude special symbols one time") }
|
||||||
|
</Link>
|
||||||
|
:
|
||||||
|
<Link onClick={ () => this.AlterSpecialsOnce(true) }>
|
||||||
|
{ loc("Include special symbols one time") }
|
||||||
|
</Link>
|
||||||
|
}
|
||||||
|
</Text>
|
||||||
|
</section>
|
||||||
|
;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
import { AccordionItem, AccordionHeader, AccordionPanel, Label, Text, Input, Divider, Checkbox, Tooltip } from "@fluentui/react-components";
|
||||||
|
import { QuestionCircleRegular, SettingsRegular } from "@fluentui/react-icons";
|
||||||
|
import React from "react";
|
||||||
|
import GeneratorOptions from "../Utils/GeneratorOptions";
|
||||||
|
import { loc } from "../Utils/Localization";
|
||||||
|
import Settings from "../Utils/Settings";
|
||||||
|
import CharacterHelpDialog from "./CharacterHelpDialog";
|
||||||
|
|
||||||
|
interface IProps
|
||||||
|
{
|
||||||
|
generatorOptions: GeneratorOptions;
|
||||||
|
settings: Settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class SettingsSection extends React.Component<IProps>
|
||||||
|
{
|
||||||
|
public render(): JSX.Element
|
||||||
|
{
|
||||||
|
let options : GeneratorOptions = this.props.generatorOptions;
|
||||||
|
let settings : Settings = this.props.settings;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AccordionItem value="settings">
|
||||||
|
<AccordionHeader as="h2" icon={ <SettingsRegular /> }>{ loc("Settings") }</AccordionHeader>
|
||||||
|
<AccordionPanel>
|
||||||
|
<section className="stack gap fadeIn">
|
||||||
|
<Label weight="semibold" htmlFor="pwd-length">{ loc("Password length") }</Label>
|
||||||
|
<div className="stack">
|
||||||
|
<Input
|
||||||
|
id="pwd-length"
|
||||||
|
value={ options.Length.toString() }
|
||||||
|
onChange={ (_, e) => GeneratorOptions.Update({ Length: parseInt(e.value) }) }
|
||||||
|
type="number" min={ 4 } />
|
||||||
|
<Text size={ 200 }>{ loc("Recommended password length") } <b>16-32</b></Text>
|
||||||
|
</div>
|
||||||
|
<Divider />
|
||||||
|
<div className="stack">
|
||||||
|
<Text as="h3" weight="semibold">
|
||||||
|
{ loc("Character options") }
|
||||||
|
<CharacterHelpDialog />
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Text as="h4">{ loc("Include") }</Text>
|
||||||
|
<div className="stack horizontal">
|
||||||
|
<Checkbox label={ loc("Special symbols") }
|
||||||
|
checked={ options.Special } onChange={ (_, e) => GeneratorOptions.Update({ Special: e.checked as boolean }) } />
|
||||||
|
<Checkbox label={ loc("Numeric") }
|
||||||
|
checked={ options.Numeric } onChange={ (_, e) => GeneratorOptions.Update({ Numeric: e.checked as boolean }) } />
|
||||||
|
<Checkbox label={ loc("Uppercase") }
|
||||||
|
checked={ options.Uppercase } onChange={ (_, e) => GeneratorOptions.Update({ Uppercase: e.checked as boolean }) } />
|
||||||
|
<Checkbox label={ loc("Lowercase") }
|
||||||
|
checked={ options.Lowercase } onChange={ (_, e) => GeneratorOptions.Update({ Lowercase: e.checked as boolean }) } />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Text as="h4">{ loc("Exclude") }</Text>
|
||||||
|
<div className="stack horizontal">
|
||||||
|
<Checkbox label={ loc("Similar") }
|
||||||
|
checked={ options.ExcludeSimilar } onChange={ (_, e) => GeneratorOptions.Update({ ExcludeSimilar: e.checked as boolean }) } />
|
||||||
|
<Checkbox label={ loc("Ambiguous") }
|
||||||
|
checked={ options.ExcludeAmbiguous } onChange={ (_, e) => GeneratorOptions.Update({ ExcludeAmbiguous: e.checked as boolean }) } />
|
||||||
|
<Checkbox label={ loc("Repeating") }
|
||||||
|
checked={ options.ExcludeRepeating } onChange={ (_, e) => GeneratorOptions.Update({ ExcludeRepeating: e.checked as boolean }) } />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Divider />
|
||||||
|
<div className="stack">
|
||||||
|
<div>
|
||||||
|
<Tooltip content={ loc("Right-click password field to quickly generate password") } relationship="description">
|
||||||
|
<Checkbox label={ <Text>{loc("Add shortcut to context menu")} <QuestionCircleRegular /></Text> }
|
||||||
|
checked={ settings.AddContext } onChange={ (_, e) => Settings.Update({ AddContext: e.checked as boolean }) } />
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
<Checkbox label={ loc("Automatically copy to clipboard") }
|
||||||
|
checked={ settings.Autocopy } onChange={ (_, e) => Settings.Update({ Autocopy: e.checked as boolean }) } />
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</AccordionPanel>
|
||||||
|
</AccordionItem>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
// BackgroundService.ts
|
||||||
|
// Background script that handles the context menu visibility
|
||||||
|
|
||||||
|
import { loc } from "../Utils/Localization";
|
||||||
|
|
||||||
|
function UpdateContextMenu(isEnabled: boolean) : void
|
||||||
|
{
|
||||||
|
console.log("BackgroundService.UpdateContextMenu", isEnabled);
|
||||||
|
chrome.contextMenus.update("generatePassword", { visible: isEnabled });
|
||||||
|
}
|
||||||
|
|
||||||
|
async function OnContextClick(info : chrome.contextMenus.OnClickData) : Promise<void>
|
||||||
|
{
|
||||||
|
console.log("BackgroundService.OnContextClick", info);
|
||||||
|
|
||||||
|
let tabInfo : chrome.tabs.Tab[] = await chrome.tabs.query({ active: true, currentWindow: true });
|
||||||
|
|
||||||
|
console.log("BackgroundService.OnContextClick", tabInfo);
|
||||||
|
|
||||||
|
chrome.tabs.sendMessage<string>(tabInfo[0].id, info.menuItemId as string);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!chrome.runtime.onInstalled.hasListeners())
|
||||||
|
chrome.runtime.onInstalled.addListener(async () =>
|
||||||
|
{
|
||||||
|
console.log("[BackgroundService] chrome.runtime.onInstalled");
|
||||||
|
chrome.contextMenus.removeAll();
|
||||||
|
|
||||||
|
chrome.contextMenus.create(
|
||||||
|
{
|
||||||
|
title: loc("Quick generate password"),
|
||||||
|
contexts: [ "editable" ],
|
||||||
|
id: "generatePassword"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
let settings : { [key : string]: any } = await chrome.storage.sync.get({ AddContext: true });
|
||||||
|
|
||||||
|
UpdateContextMenu(settings.AddContext);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!chrome.contextMenus.onClicked.hasListeners())
|
||||||
|
chrome.contextMenus.onClicked.addListener(OnContextClick);
|
||||||
|
|
||||||
|
if (!chrome.storage.sync.onChanged.hasListeners())
|
||||||
|
chrome.storage.sync.onChanged.addListener(changes =>
|
||||||
|
{
|
||||||
|
console.log("[BackgroundService] chrome.storage.sync.onChanged", changes);
|
||||||
|
if (changes.AddContext?.newValue !== undefined)
|
||||||
|
UpdateContextMenu(changes.AddContext.newValue);
|
||||||
|
});
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
// ContentService.ts
|
||||||
|
// Content script that handles quick password generation through context menu
|
||||||
|
|
||||||
|
import Generator from "../Utils/Generator";
|
||||||
|
import GeneratorOptions from "../Utils/GeneratorOptions";
|
||||||
|
import { loc } from "../Utils/Localization";
|
||||||
|
|
||||||
|
if (!chrome.runtime.onMessage.hasListeners())
|
||||||
|
chrome.runtime.onMessage.addListener(async message =>
|
||||||
|
{
|
||||||
|
console.log("[ContentService] chrome.runtime.onMessage", message);
|
||||||
|
|
||||||
|
if (message === "generatePassword")
|
||||||
|
{
|
||||||
|
let generatorOptions : GeneratorOptions = await GeneratorOptions.Init();
|
||||||
|
let password : string = Generator.GeneratePassword(generatorOptions);
|
||||||
|
|
||||||
|
let input : HTMLInputElement = document.activeElement as HTMLInputElement;
|
||||||
|
|
||||||
|
if (![ "INPUT", "TEXTAREA" ].includes(input.tagName))
|
||||||
|
return;
|
||||||
|
|
||||||
|
console.log("[ContentService] chrome.runtime.onMessage", input);
|
||||||
|
|
||||||
|
if (input.tagName !== "INPUT" || input.readOnly || ![ "text", "password" ].includes(input.type))
|
||||||
|
{
|
||||||
|
window.alert(loc("Quick generator is only available on password fields"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
input.focus();
|
||||||
|
input.value = password;
|
||||||
|
window.navigator.clipboard.writeText(password);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("[ContentService] Loaded");
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
import GeneratorOptions from "./GeneratorOptions";
|
||||||
|
import { loc } from "./Localization";
|
||||||
|
|
||||||
|
export default class Generator
|
||||||
|
{
|
||||||
|
public static Uppercase = "ABCDEFGHJKMNPQRSTUVWXYZ";
|
||||||
|
public static Lowercase = this.Uppercase.toLowerCase();
|
||||||
|
public static Numeric = "23456789";
|
||||||
|
public static SpecialCharacters = "!#$%&*+-=?@^_";
|
||||||
|
public static AmbiguousCharacters = "{}[]()/\\'\"`~,;:.<>";
|
||||||
|
public static SimilarCharacters = "il1Lo0O";
|
||||||
|
|
||||||
|
public static GeneratePassword(props : GeneratorOptions) : string
|
||||||
|
{
|
||||||
|
// Validating parameters
|
||||||
|
if (this.ValidateProps(props))
|
||||||
|
return "";
|
||||||
|
|
||||||
|
// Generating password
|
||||||
|
let availableCharacters : string = this.GetAvailableCharacters(props);
|
||||||
|
let requiredCharacters : string = this.GetRequiredCharacters(props);
|
||||||
|
|
||||||
|
let password : string = "";
|
||||||
|
|
||||||
|
for (let i = 0; i < props.Length; i++)
|
||||||
|
{
|
||||||
|
let char : string = this.PickRandomFromArray(availableCharacters);
|
||||||
|
|
||||||
|
if (props.ExcludeRepeating && password.includes(char))
|
||||||
|
i--;
|
||||||
|
else
|
||||||
|
password += char;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < requiredCharacters.length; i++)
|
||||||
|
{
|
||||||
|
if (props.ExcludeRepeating && password.includes(requiredCharacters[i]))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
password = password.replace(this.PickRandomFromArray(password), requiredCharacters[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return password;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ValidateProps(props : GeneratorOptions): string
|
||||||
|
{
|
||||||
|
if (!props.Lowercase && !props.Uppercase)
|
||||||
|
return loc("Either lowercase or uppercase characters must be included");
|
||||||
|
|
||||||
|
let availableCharacters : string = this.GetAvailableCharacters(props);
|
||||||
|
|
||||||
|
if (props.ExcludeRepeating && availableCharacters.length < props.Length)
|
||||||
|
return loc("Selected length is too long to exclude repeating characters");
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static GetAvailableCharacters(props : GeneratorOptions) : string
|
||||||
|
{
|
||||||
|
let availableCharacters : string = "";
|
||||||
|
|
||||||
|
if (props.Special)
|
||||||
|
availableCharacters += this.SpecialCharacters;
|
||||||
|
if (props.Numeric)
|
||||||
|
availableCharacters += this.Numeric;
|
||||||
|
if (props.Lowercase)
|
||||||
|
availableCharacters += this.Lowercase;
|
||||||
|
if (props.Uppercase)
|
||||||
|
availableCharacters += this.Uppercase;
|
||||||
|
|
||||||
|
if (!props.ExcludeAmbiguous)
|
||||||
|
availableCharacters += this.AmbiguousCharacters;
|
||||||
|
if (!props.ExcludeSimilar)
|
||||||
|
availableCharacters += this.SimilarCharacters;
|
||||||
|
|
||||||
|
return availableCharacters;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static GetRequiredCharacters(props : GeneratorOptions) : string
|
||||||
|
{
|
||||||
|
let requiredCharacters : string = "";
|
||||||
|
|
||||||
|
if (props.Special)
|
||||||
|
requiredCharacters += this.PickRandomFromArray(this.SpecialCharacters);
|
||||||
|
if (props.Numeric)
|
||||||
|
requiredCharacters += this.PickRandomFromArray(this.Numeric);
|
||||||
|
if (props.Lowercase)
|
||||||
|
requiredCharacters += this.PickRandomFromArray(this.Lowercase);
|
||||||
|
if (props.Uppercase)
|
||||||
|
requiredCharacters += this.PickRandomFromArray(this.Uppercase);
|
||||||
|
|
||||||
|
if (!props.ExcludeAmbiguous)
|
||||||
|
requiredCharacters += this.PickRandomFromArray(this.AmbiguousCharacters);
|
||||||
|
if (!props.ExcludeSimilar)
|
||||||
|
requiredCharacters += this.PickRandomFromArray(this.SimilarCharacters);
|
||||||
|
|
||||||
|
return requiredCharacters;
|
||||||
|
}
|
||||||
|
|
||||||
|
// See https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Math/random
|
||||||
|
// min is inclusive, max is exclusive
|
||||||
|
private static GetRandomInt(min : number, max : number) : number
|
||||||
|
{
|
||||||
|
return Math.floor(Math.random() * (max - min)) + min;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static PickRandomFromArray(array : string) : string
|
||||||
|
{
|
||||||
|
return array[this.GetRandomInt(0, array.length)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
export default class GeneratorOptions
|
||||||
|
{
|
||||||
|
public Length: number = 16;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
public static OnChanged : (changes : Partial<GeneratorOptions>) => void;
|
||||||
|
|
||||||
|
public static async Init() : Promise<GeneratorOptions>
|
||||||
|
{
|
||||||
|
let fallbackOptions : GeneratorOptions = new GeneratorOptions();
|
||||||
|
|
||||||
|
if (!chrome?.storage?.sync) // Extension is running as a standalone app
|
||||||
|
return fallbackOptions;
|
||||||
|
|
||||||
|
let props : { [key: string]: any } = await chrome.storage.sync.get(fallbackOptions);
|
||||||
|
|
||||||
|
chrome.storage.sync.onChanged.addListener(GeneratorOptions.OnStorageChanged);
|
||||||
|
|
||||||
|
return props as GeneratorOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Update(changes : Partial<GeneratorOptions>) : Promise<void>
|
||||||
|
{
|
||||||
|
if (chrome?.storage?.sync)
|
||||||
|
await chrome?.storage?.sync?.set(changes);
|
||||||
|
else
|
||||||
|
GeneratorOptions.OnChanged(changes);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static OnStorageChanged(changes : { [key: string]: chrome.storage.StorageChange }) : void
|
||||||
|
{
|
||||||
|
let propsList : string[] = Object.keys(new GeneratorOptions());
|
||||||
|
let options : { [key: string]: any } = { };
|
||||||
|
|
||||||
|
Object.entries(changes)
|
||||||
|
.filter(i => propsList.includes(i[0]))
|
||||||
|
.map(i => options[i[0]] = i[1].newValue);
|
||||||
|
|
||||||
|
if (GeneratorOptions.OnChanged)
|
||||||
|
GeneratorOptions.OnChanged(options as Partial<GeneratorOptions>);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
export default class Localization
|
||||||
|
{
|
||||||
|
public static GetString(key : string) : string
|
||||||
|
{
|
||||||
|
let sanitizedKey : string = key
|
||||||
|
.replaceAll(".", "_")
|
||||||
|
.replaceAll(",", "_")
|
||||||
|
.replaceAll(" ", "_")
|
||||||
|
.replaceAll("-", "_")
|
||||||
|
.replaceAll("?", "_")
|
||||||
|
.replaceAll("!", "_");
|
||||||
|
|
||||||
|
let str : string = chrome?.i18n?.getMessage(sanitizedKey);
|
||||||
|
|
||||||
|
return str ?? key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function loc(key : string) : string
|
||||||
|
{
|
||||||
|
return Localization.GetString(key);
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
export default class Settings
|
||||||
|
{
|
||||||
|
public AddContext : boolean = true;
|
||||||
|
public Autocopy : boolean = true;
|
||||||
|
|
||||||
|
public static OnChanged : (changes : Partial<Settings>) => void;
|
||||||
|
|
||||||
|
public static async Init() : Promise<Settings>
|
||||||
|
{
|
||||||
|
let fallbackOptions = new Settings();
|
||||||
|
|
||||||
|
if (!chrome?.storage?.sync)
|
||||||
|
return fallbackOptions;
|
||||||
|
|
||||||
|
let props : { [key: string]: any } = await chrome.storage.sync.get(fallbackOptions);
|
||||||
|
|
||||||
|
chrome.storage.sync.onChanged.addListener(Settings.OnStorageChanged);
|
||||||
|
|
||||||
|
return props as Settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Update(changes : Partial<Settings>) : Promise<void>
|
||||||
|
{
|
||||||
|
if (chrome?.storage?.sync)
|
||||||
|
await chrome?.storage?.sync?.set(changes);
|
||||||
|
else
|
||||||
|
Settings.OnChanged(changes);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static OnStorageChanged(changes : { [key: string]: chrome.storage.StorageChange }) : void
|
||||||
|
{
|
||||||
|
let propsList : string[] = Object.keys(new Settings());
|
||||||
|
let settings : { [key: string]: any } = { };
|
||||||
|
|
||||||
|
Object.entries(changes)
|
||||||
|
.filter(i => propsList.includes(i[0]))
|
||||||
|
.map(i => settings[i[0]] = i[1].newValue);
|
||||||
|
|
||||||
|
if (Settings.OnChanged)
|
||||||
|
Settings.OnChanged(settings as Partial<Settings>);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import ReactDOM from "react-dom/client";
|
||||||
|
import App from "./App";
|
||||||
|
import Settings from "./Utils/Settings";
|
||||||
|
|
||||||
|
Settings.Init().then(settings =>
|
||||||
|
ReactDOM
|
||||||
|
.createRoot(document.querySelector("#root") as HTMLElement)
|
||||||
|
.render(<App settings={ settings } />));
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
/// <reference types="react-scripts" />
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions":
|
||||||
|
{
|
||||||
|
"target": "es5",
|
||||||
|
"lib":
|
||||||
|
[
|
||||||
|
"dom",
|
||||||
|
"dom.iterable",
|
||||||
|
"esnext"
|
||||||
|
],
|
||||||
|
"allowJs": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"strict": true,
|
||||||
|
"strictNullChecks": false,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "react-jsx"
|
||||||
|
},
|
||||||
|
"include": [ "src" ]
|
||||||
|
}
|
||||||