Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f4674265e4 | |||
| b4117e430f | |||
| 3ecb6c4a31 | |||
| f2683e37b2 | |||
| f6ff9cf4c9 | |||
| 88b954fcd0 | |||
| 59d993ae4a | |||
| d808bffff2 | |||
| 01d048e298 | |||
| 0b47bf93aa | |||
| 53856a8b3b | |||
| 2c70ca2490 | |||
| 2731ccbff2 | |||
| cf99eb3b52 | |||
| 59d57d99a9 | |||
| 16613253ad | |||
| db8c99be0a |
@@ -21,5 +21,5 @@
|
||||
}
|
||||
},
|
||||
|
||||
"postCreateCommand": "yarn install"
|
||||
"postCreateCommand": "npm install"
|
||||
}
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
module.exports = {
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es2021": true
|
||||
},
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:react/recommended"
|
||||
],
|
||||
"overrides": [
|
||||
{
|
||||
"env": {
|
||||
"node": true
|
||||
},
|
||||
"files": [
|
||||
".eslintrc.{js,cjs}"
|
||||
],
|
||||
"parserOptions": {
|
||||
"sourceType": "script"
|
||||
}
|
||||
}
|
||||
],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": "latest",
|
||||
"sourceType": "module"
|
||||
},
|
||||
"plugins": [
|
||||
"@typescript-eslint",
|
||||
"react"
|
||||
],
|
||||
"rules": {
|
||||
"indent": [
|
||||
"error",
|
||||
"tab"
|
||||
],
|
||||
"linebreak-style": [
|
||||
"error",
|
||||
"unix"
|
||||
],
|
||||
"quotes": [
|
||||
"error",
|
||||
"double"
|
||||
],
|
||||
"semi": [
|
||||
"error",
|
||||
"always"
|
||||
],
|
||||
"react/react-in-jsx-scope": "off"
|
||||
}
|
||||
};
|
||||
@@ -16,7 +16,6 @@ body:
|
||||
label: Description
|
||||
description: A clear and concise description of what the bug is.
|
||||
placeholder: e.g. Sometimes when generating a password not all character sets are included
|
||||
render: Markdown
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@@ -29,7 +28,6 @@ body:
|
||||
2. Click on '...'
|
||||
3. Scroll down to '...'
|
||||
4. See '...'
|
||||
render: Markdown
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@@ -38,7 +36,6 @@ body:
|
||||
label: Expected behavior
|
||||
description: A clear and concise description of what you expected to happen.
|
||||
placeholder: e.g. Generated password should include at least one character from every enabled character set
|
||||
render: Markdown
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@@ -46,7 +43,6 @@ body:
|
||||
attributes:
|
||||
label: Screenshot
|
||||
description: If applicable, add screenshots to help explain your problem.
|
||||
render: Markdown
|
||||
validations:
|
||||
required: false
|
||||
|
||||
@@ -55,7 +51,7 @@ body:
|
||||
attributes:
|
||||
label: Operating system
|
||||
options:
|
||||
- "Windows 10+"
|
||||
- "Windows 10 and newer"
|
||||
- "Windows 8/8.1"
|
||||
- "Windows 7 and older"
|
||||
- "MacOS"
|
||||
@@ -86,7 +82,6 @@ body:
|
||||
attributes:
|
||||
label: Additional context
|
||||
description: Add any other context about the problem here.
|
||||
render: Markdown
|
||||
validations:
|
||||
required: false
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# yaml-language-server: $schema=https://json.schemastore.org/github-issue-config.json
|
||||
|
||||
blank_issues_enabled: true
|
||||
contact_links:
|
||||
- name: Questions & Discussions
|
||||
|
||||
@@ -15,7 +15,6 @@ body:
|
||||
attributes:
|
||||
label: Proposed solution
|
||||
description: Describe the solution you'd like
|
||||
render: Markdown
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@@ -24,7 +23,6 @@ body:
|
||||
attributes:
|
||||
label: Justification
|
||||
description: Is your feature request related to a problem? Please describe.
|
||||
render: Markdown
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@@ -33,7 +31,6 @@ body:
|
||||
attributes:
|
||||
label: Alternatives
|
||||
description: Describe alternatives you've considered.
|
||||
render: Markdown
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@@ -42,7 +39,6 @@ body:
|
||||
attributes:
|
||||
label: Additional context
|
||||
description: Add any other context or screenshots about the feature request here.
|
||||
render: Markdown
|
||||
validations:
|
||||
required: false
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ updates:
|
||||
schedule:
|
||||
interval: monthly
|
||||
rebase-strategy: disabled
|
||||
open-pull-requests-limit: 20
|
||||
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
@@ -29,3 +30,16 @@ updates:
|
||||
schedule:
|
||||
interval: monthly
|
||||
rebase-strategy: disabled
|
||||
open-pull-requests-limit: 20
|
||||
|
||||
- package-ecosystem: "devcontainers"
|
||||
directory: "/"
|
||||
target-branch: "next"
|
||||
assignees:
|
||||
- "xfox111"
|
||||
reviewers:
|
||||
- "xfox111"
|
||||
schedule:
|
||||
interval: monthly
|
||||
rebase-strategy: disabled
|
||||
open-pull-requests-limit: 20
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
<!-- ⚠️ Make sure that you create this PR against `next` branch and not `main` -->
|
||||
|
||||
## Description
|
||||
<!--Put short description of the pull request here-->
|
||||
|
||||
@@ -25,5 +27,7 @@ Dependencies update and security fixes
|
||||
## PR Checklist
|
||||
- [ ] Update version in `package.json`
|
||||
- [ ] [Post-merge] Review and publish GitHub release
|
||||
- [ ] Update Discussions
|
||||
- [ ] [Post-deploy] Update changelog for Firefox webstore
|
||||
- [ ] Reset `next` branch to be in sync with `main`
|
||||
-->
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
<!-->> ## 🚀 Patch Tuesday update
|
||||
<!-- > ## 🚀 Patch Tuesday update
|
||||
> This release is a part of our new initiative!
|
||||
From now on we are starting to roll out updates on every first Tuesday of the month, which will include bugfixes, security and dependency updates to keep the project's security and stability up to date!
|
||||
-->
|
||||
|
||||
## What's new
|
||||
<!-- - Dependency updates and security patches (#) -->
|
||||
|
||||
<!-->### Fixed security issues in this update
|
||||
<!-- ### Fixed security issues in this update
|
||||
- [CWE-20](https://cwe.mitre.org/data/definitions/20.html)
|
||||
- CVE-2022-25883
|
||||
-->
|
||||
|
||||
@@ -43,34 +43,24 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@main
|
||||
|
||||
- run: yarn install
|
||||
- run: yarn lint
|
||||
- run: TARGET=${{ matrix.target }} yarn build
|
||||
- run: npm install
|
||||
- run: npm run zip -- -b ${{ matrix.target }}
|
||||
- run: npm audit
|
||||
|
||||
- name: Drop build artifacts (${{ matrix.target }})
|
||||
uses: actions/upload-artifact@main
|
||||
with:
|
||||
name: ${{ matrix.target }}
|
||||
path: dist
|
||||
path: ./.output/password-generator-*-${{ matrix.target }}.zip
|
||||
include-hidden-files: true
|
||||
|
||||
- name: web-ext lint
|
||||
if: ${{ matrix.target == 'firefox' }}
|
||||
uses: freaktechnik/web-ext-lint@main
|
||||
with:
|
||||
extension-root: dist
|
||||
extension-root: ./.output/firefox-mv3
|
||||
self-hosted: false
|
||||
|
||||
- uses: TheDoctor0/zip-release@main
|
||||
with:
|
||||
filename: packed-${{ matrix.target }}.zip
|
||||
path: dist
|
||||
|
||||
- name: Drop packed artifacts (${{ matrix.target }})
|
||||
uses: actions/upload-artifact@main
|
||||
with:
|
||||
name: packed-${{ matrix.target }}
|
||||
path: packed-${{ matrix.target }}.zip
|
||||
|
||||
publish-github:
|
||||
needs: build
|
||||
if: ${{ github.event_name == 'release' || github.event.inputs.gh-release == 'true' }}
|
||||
@@ -83,14 +73,14 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/download-artifact@main
|
||||
with:
|
||||
name: packed-${{ matrix.target }}
|
||||
name: ${{ matrix.target }}
|
||||
|
||||
- name: Attach build to release
|
||||
uses: xresloader/upload-to-github-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
file: PasswordGenerator-${{ matrix.target }}.zip
|
||||
file: password-generator-*-${{ matrix.target }}.zip
|
||||
draft: false
|
||||
overwrite: true
|
||||
update_latest_release: true
|
||||
@@ -103,12 +93,12 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/download-artifact@main
|
||||
with:
|
||||
name: packed-chrome
|
||||
name: chrome
|
||||
|
||||
- uses: wdzeng/chrome-extension@main
|
||||
- uses: wdzeng/chrome-extension@v1.2.4
|
||||
with:
|
||||
extension-id: jnjobgjobffgmgfnkpkjfjkkfhfikmfl
|
||||
zip-path: packed-chrome.zip
|
||||
zip-path: password-generator-*-chrome.zip
|
||||
client-id: ${{ secrets.CHROME_CLIENT_ID }}
|
||||
client-secret: ${{ secrets.CHROME_CLIENT_SECRET }}
|
||||
refresh-token: ${{ secrets.CHROME_REFRESH_TOKEN }}
|
||||
@@ -121,12 +111,12 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/download-artifact@main
|
||||
with:
|
||||
name: packed-chrome
|
||||
name: chrome
|
||||
|
||||
- uses: wdzeng/edge-addon@main
|
||||
- uses: wdzeng/edge-addon@v1.2.5
|
||||
with:
|
||||
product-id: ${{ secrets.EDGE_PRODUCT_ID }}
|
||||
zip-path: packed-chrome.zip
|
||||
zip-path: password-generator-*-chrome.zip
|
||||
client-id: ${{ secrets.EDGE_CLIENT_ID }}
|
||||
client-secret: ${{ secrets.EDGE_CLIENT_SECRET }}
|
||||
access-token-url: ${{ secrets.EDGE_ACCESS_TOKEN_URL }}
|
||||
@@ -139,11 +129,11 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/download-artifact@main
|
||||
with:
|
||||
name: packed-firefox
|
||||
name: firefox
|
||||
|
||||
- uses: wdzeng/firefox-addon@main
|
||||
- uses: wdzeng/firefox-addon@v1.0.5
|
||||
with:
|
||||
addon-guid: ${{ secrets.FIREFOX_EXT_UUID }}
|
||||
xpi-path: packed-firefox.zip
|
||||
xpi-path: password-generator-*-firefox.zip
|
||||
jwt-issuer: ${{ secrets.FIREFOX_API_KEY }}
|
||||
jwt-secret: ${{ secrets.FIREFOX_CLIENT_SECRET }}
|
||||
|
||||
@@ -56,7 +56,7 @@ jobs:
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
uses: github/codeql-action/init@v3
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
@@ -70,7 +70,7 @@ jobs:
|
||||
# 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
|
||||
uses: github/codeql-action/autobuild@v3
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||
@@ -83,4 +83,4 @@ jobs:
|
||||
# ./location_of_script_within_repo/buildscript.sh
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
||||
uses: github/codeql-action/analyze@v3
|
||||
|
||||
@@ -22,10 +22,10 @@ jobs:
|
||||
extver=`jq -r ".version" package.json`
|
||||
echo "version=$extver" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- uses: dev-build-deploy/release-me@v0.15.0
|
||||
- uses: dev-build-deploy/release-me@v0.17.0
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
prefix: v
|
||||
draft: true
|
||||
version: ${{ steps.get_version.outputs.version }}
|
||||
version: v${{ steps.get_version.outputs.version }}
|
||||
release-notes: .github/release_description_template.md
|
||||
|
||||
@@ -37,19 +37,20 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@main
|
||||
|
||||
- run: yarn install
|
||||
- run: yarn lint
|
||||
- run: TARGET=${{ matrix.target }} yarn build
|
||||
- run: npm install
|
||||
- run: npm run zip -- -b ${{ matrix.target }}
|
||||
- run: npm audit
|
||||
|
||||
- name: Drop artifacts (${{ matrix.target }})
|
||||
uses: actions/upload-artifact@main
|
||||
with:
|
||||
name: ${{ matrix.target }}
|
||||
path: dist
|
||||
path: ./.output/password-generator-*-${{ matrix.target }}.zip
|
||||
include-hidden-files: true
|
||||
|
||||
- name: web-ext lint
|
||||
if: ${{ matrix.target == 'firefox' }}
|
||||
uses: freaktechnik/web-ext-lint@main
|
||||
with:
|
||||
extension-root: dist
|
||||
extension-root: ./.output/firefox-mv3
|
||||
self-hosted: false
|
||||
|
||||
@@ -8,9 +8,11 @@ pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
.output
|
||||
stats.html
|
||||
stats-*.json
|
||||
.wxt
|
||||
web-ext.config.ts
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
@@ -20,7 +22,3 @@ dist-ssr
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
# Config files
|
||||
.webextrc
|
||||
.webextrc.*
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"DNEK.auto-region-folder",
|
||||
"eamodio.gitlens",
|
||||
"jock.svg",
|
||||
"firefox-devtools.vscode-firefox-debug",
|
||||
"bierner.github-markdown-preview",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"github.vscode-github-actions",
|
||||
"GitHub.vscode-pull-request-github",
|
||||
"bierner.github-markdown-preview",
|
||||
"mrmlnc.vscode-scss",
|
||||
"Gruntfuggly.todo-tree",
|
||||
"redhat.vscode-yaml"
|
||||
"jock.svg",
|
||||
"ms-azuretools.vscode-docker",
|
||||
"saeris.markdown-github-alerts"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,12 +1,4 @@
|
||||
{
|
||||
"json.schemas": [
|
||||
{
|
||||
"fileMatch": [
|
||||
"/messages.json"
|
||||
],
|
||||
"url": "https://gist.github.com/XFox111/9528b76f9f02704d620d4edbf421e06b/raw/e77197276f0aa2994cceae4ddf4dcfcabdce9dcb/webext-locale-schema.json"
|
||||
}
|
||||
],
|
||||
"editor.rulers": [
|
||||
{
|
||||
"column": 120
|
||||
@@ -15,7 +7,7 @@
|
||||
"editor.insertSpaces": false,
|
||||
"files.insertFinalNewline": true,
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.organizeImports": true
|
||||
"source.organizeImports": "explicit"
|
||||
},
|
||||
"files.eol": "\n",
|
||||
"files.trimFinalNewlines": true,
|
||||
@@ -29,14 +21,6 @@
|
||||
"css.lint.float": "warning",
|
||||
"css.lint.unknownVendorSpecificProperties": "warning",
|
||||
"css.lint.zeroUnits": "warning",
|
||||
"scss.format.braceStyle": "expand",
|
||||
"scss.format.maxPreserveNewLines": 3,
|
||||
"scss.format.spaceAroundSelectorSeparator": true,
|
||||
"scss.lint.compatibleVendorPrefixes": "warning",
|
||||
"scss.lint.duplicateProperties": "warning",
|
||||
"scss.lint.float": "warning",
|
||||
"scss.lint.unknownVendorSpecificProperties": "warning",
|
||||
"scss.lint.zeroUnits": "warning",
|
||||
"html.format.maxPreserveNewLines": 3,
|
||||
"html.format.wrapAttributes": "preserve",
|
||||
"javascript.format.placeOpenBraceOnNewLineForControlBlocks": true,
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"type": "shell",
|
||||
"command": "yarn build",
|
||||
"type": "npm",
|
||||
"script": "build -- -b chrome",
|
||||
"group":
|
||||
{
|
||||
"kind": "build",
|
||||
@@ -12,67 +12,32 @@
|
||||
"label": "Build: Chromium"
|
||||
},
|
||||
{
|
||||
"type": "shell",
|
||||
"command": "yarn build",
|
||||
"group":
|
||||
{
|
||||
"kind": "build",
|
||||
"isDefault": false
|
||||
},
|
||||
"label": "Build: Firefox",
|
||||
"options": {
|
||||
"env": {
|
||||
"TARGET": "firefox"
|
||||
}
|
||||
}
|
||||
"type": "npm",
|
||||
"script": "build -- -b firefox",
|
||||
"group": "build",
|
||||
"label": "Build: Firefox"
|
||||
},
|
||||
{
|
||||
"type": "shell",
|
||||
"command": "yarn build",
|
||||
"args": [
|
||||
"--watch",
|
||||
"--mode",
|
||||
"development"
|
||||
],
|
||||
"type": "npm",
|
||||
"script": "dev -- -b chrome",
|
||||
"group": "test",
|
||||
"label": "Watch: Chromium"
|
||||
"label": "Dev: Google Chrome"
|
||||
},
|
||||
{
|
||||
"type": "shell",
|
||||
"command": "yarn build",
|
||||
"args": [
|
||||
"--watch",
|
||||
"--mode",
|
||||
"development"
|
||||
],
|
||||
"type": "npm",
|
||||
"script": "dev -- -b firefox",
|
||||
"group": "test",
|
||||
"label": "Watch: Firefox",
|
||||
"options": {
|
||||
"env": {
|
||||
"TARGET": "firefox"
|
||||
}
|
||||
}
|
||||
"label": "Dev: Firefox"
|
||||
},
|
||||
{
|
||||
"type": "shell",
|
||||
"command": "yarn dev",
|
||||
"type": "npm",
|
||||
"script": "dev -- -b edge",
|
||||
"group": "test",
|
||||
"label": "Dev: Chromium"
|
||||
"label": "Dev: Microsoft Edge"
|
||||
},
|
||||
{
|
||||
"type": "shell",
|
||||
"command": "yarn dev",
|
||||
"group": "test",
|
||||
"label": "Dev: Firefox",
|
||||
"options": {
|
||||
"env": {
|
||||
"TARGET": "firefox"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "shell",
|
||||
"command": "yarn install",
|
||||
"type": "npm",
|
||||
"script": "install",
|
||||
"label": "Restore dependencies",
|
||||
"group": "build"
|
||||
}
|
||||
|
||||
@@ -2,75 +2,133 @@
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to making participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, sex characteristics, gender identity and expression,
|
||||
level of experience, education, socio-economic status, nationality, personal
|
||||
appearance, race, religion, or sexual identity and orientation.
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, caste, color, religion, or sexual
|
||||
identity and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the overall
|
||||
community
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* The use of sexualized language or imagery, and sexual attention or advances of
|
||||
any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Publishing others' private information, such as a physical or email address,
|
||||
without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official email address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at opensource@xfox111.net. All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
reported to the community leaders responsible for enforcement at
|
||||
[opensource@xfox111.net](mailto:opensource@xfox111.net).
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series of
|
||||
actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or permanent
|
||||
ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within the
|
||||
community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.1, available at
|
||||
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
|
||||
|
||||
Community Impact Guidelines were inspired by
|
||||
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
|
||||
[https://www.contributor-covenant.org/translations][translations].
|
||||
|
||||
> Contributor Covenant is released under the [Creative Commons Attribution 4.0 International Public License](https://github.com/EthicalSource/contributor_covenant/blob/release/LICENSE.md).
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see
|
||||
https://www.contributor-covenant.org/faq
|
||||
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
|
||||
[Mozilla CoC]: https://github.com/mozilla/diversity
|
||||
[FAQ]: https://www.contributor-covenant.org/faq
|
||||
[translations]: https://www.contributor-covenant.org/translations
|
||||
|
||||
@@ -1,2 +1,4 @@
|
||||
# Contribution Guidelines
|
||||
This article has been moved to the [project's Wiki section](https://github.com/XFox111/PasswordGeneratorExtension/wiki/Contribution-Guidelines)
|
||||
|
||||
> [!IMPORTANT]
|
||||
> This article has been moved to the [project's Wiki section](https://github.com/XFox111/PasswordGeneratorExtension/wiki/Contribution-Guidelines)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 Eugene Fox
|
||||
Copyright (c) 2024 Eugene Fox
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -12,10 +12,12 @@
|
||||
Extension for web browsers which helps you to easily generate strong passwords in one click
|
||||
|
||||
## Features
|
||||
- Create strong passwords in one click
|
||||
- Customizable generator
|
||||
- Clean and simple UI
|
||||
- Dark mode
|
||||
- **NEW:** Insert and copy generated password in one click
|
||||
|
||||

|
||||
|
||||
## Languages
|
||||
- English
|
||||
@@ -45,7 +47,7 @@ Extension for web browsers which helps you to easily generate strong passwords i
|
||||
|
||||
> 1. Go to [Releases](https://github.com/XFox111/PasswordGeneratorExtension/releases) and select a release to download
|
||||
> 2. Download attached archive for Chromium and unpack it
|
||||
> 3. Go to "chrome://extensions"
|
||||
> 3. Go to `chrome://extensions`
|
||||
> 4. Enable "Developer mode"
|
||||
> 5. Click the "Load unpacked" button and navigate to the extension's root folder (contains `manifest.json`)
|
||||
> 6. Done!
|
||||
@@ -57,8 +59,8 @@ Extension for web browsers which helps you to easily generate strong passwords i
|
||||
|
||||
> 1. Go to [Releases](https://github.com/XFox111/PasswordGeneratorExtension/releases) and select a release to download
|
||||
> 2. Download attached archive for Firefox and unpack it
|
||||
> 3. Go to "about:debugging#/runtime/this-firefox"
|
||||
> 4. Click the "Load Temporary Add-on..." button and select `manifest.josn` file in the root folder
|
||||
> 3. Go to `about:debugging#/runtime/this-firefox`
|
||||
> 4. Click the "Load Temporary Add-on..." button and select `manifest.json` file in the root folder
|
||||
> 5. Done!
|
||||
|
||||
> **Important!**
|
||||
@@ -90,4 +92,4 @@ If you are interested in fixing issues and contributing directly to the code bas
|
||||
[](https://github.com/xfox111)
|
||||
[](https://buymeacoffee.com/xfox111)
|
||||
|
||||
> ©2023 Eugene Fox
|
||||
> ©2024 Eugene Fox. Licensed under [MIT license](https://github.com/XFox111/PasswordGeneratorExtension/blob/main/LICENSE)
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
export default defineBackground(() => main());
|
||||
|
||||
async function main(): Promise<void>
|
||||
{
|
||||
await browser.contextMenus.removeAll();
|
||||
browser.contextMenus.onClicked.addListener(() => browser.action.openPopup());
|
||||
|
||||
const showMenu: boolean = (await storage.getItem<boolean>("sync:ContextMenu", { fallback: true }))!;
|
||||
updateMenus(showMenu);
|
||||
|
||||
storage.watch<boolean>("sync:ContextMenu", e => updateMenus(e!));
|
||||
}
|
||||
|
||||
async function updateMenus(showMenus: boolean): Promise<void>
|
||||
{
|
||||
await browser.contextMenus.removeAll();
|
||||
|
||||
if (showMenus)
|
||||
browser.contextMenus.create({
|
||||
id: "password-generator",
|
||||
title: i18n.t("manifest.name"),
|
||||
contexts: ["all"],
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
export default defineContentScript({
|
||||
matches: ["<all_urls>"],
|
||||
runAt: "document_idle",
|
||||
main()
|
||||
{
|
||||
console.log("Password Generator: script loaded");
|
||||
|
||||
browser.runtime.onMessage.addListener((message: string, _, sendResponse) =>
|
||||
{
|
||||
if (message === "probe")
|
||||
// @ts-expect-error sendResponse has incorrect signature
|
||||
sendResponse(document.querySelectorAll("form input[type=password]").length);
|
||||
else
|
||||
document
|
||||
.querySelectorAll("form input[type=password]")
|
||||
.forEach(el => {
|
||||
(el as HTMLInputElement).value = message;
|
||||
(el as HTMLInputElement).focus();
|
||||
});
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,38 @@
|
||||
import { StorageProvider } from "@/utils/storage";
|
||||
import { useTheme } from "@/utils/useTheme";
|
||||
import { Accordion, FluentProvider, Spinner } from "@fluentui/react-components";
|
||||
import { useState } from "react";
|
||||
import { useStyles } from "./App.styles";
|
||||
import AboutSection from "./sections/AboutSection";
|
||||
import GeneratorView from "./sections/GeneratorView";
|
||||
import SettingsSection from "./sections/SettingsSection";
|
||||
import Snow from "./specials/Snow";
|
||||
|
||||
const App: React.FC = () =>
|
||||
{
|
||||
const theme = useTheme();
|
||||
const cls = useStyles();
|
||||
const [selection, setSelection] = useState<string[]>([]);
|
||||
|
||||
return (
|
||||
<FluentProvider theme={ theme }>
|
||||
<main className={ cls.root }>
|
||||
<StorageProvider loader={ <Spinner size="large" className={ cls.spinner } /> }>
|
||||
<GeneratorView collapse={ selection.includes("settings") } />
|
||||
<Accordion
|
||||
openItems={ selection }
|
||||
onToggle={ (_, e) => setSelection(e.openItems as string[]) }
|
||||
collapsible>
|
||||
|
||||
<SettingsSection />
|
||||
<AboutSection />
|
||||
</Accordion>
|
||||
</StorageProvider>
|
||||
|
||||
<Snow />
|
||||
</main>
|
||||
</FluentProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
||||
@@ -1,17 +1,16 @@
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Password generator</title>
|
||||
<meta name="manifest.type" content="browser_action" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="./main.tsx"></script>
|
||||
</body>
|
||||
|
||||
<script type="module" src="./popup.tsx"></script>
|
||||
|
||||
</html>
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import App from "./App";
|
||||
import "./popup.css";
|
||||
import "./style.css";
|
||||
|
||||
ReactDOM.createRoot(document.getElementById("root")!).render(
|
||||
<React.StrictMode>
|
||||
@@ -0,0 +1,66 @@
|
||||
import { bmcDarkTheme, bmcLightTheme } from "@/utils/BmcTheme";
|
||||
import { getFeedbackLink, githubLinks, personalLinks } from "@/utils/links";
|
||||
import { useTheme } from "@/utils/useTheme";
|
||||
import * as fui from "@fluentui/react-components";
|
||||
import { InfoRegular, PersonFeedbackRegular } from "@fluentui/react-icons";
|
||||
import { useStyles } from "./AboutSection.styles";
|
||||
import { ReactNode } from "react";
|
||||
|
||||
const link = (text: string, href: string): ReactNode => (
|
||||
<fui.Link target="_blank" href={ href }>{ text }</fui.Link>
|
||||
);
|
||||
|
||||
const buttonProps = (href: string, icon: JSX.Element): fui.ButtonProps => (
|
||||
{
|
||||
as: "a", target: "_blank", href,
|
||||
appearance: "primary", icon
|
||||
}
|
||||
);
|
||||
|
||||
const AboutSection: React.FC = () =>
|
||||
{
|
||||
const bmcTheme = useTheme(bmcLightTheme, bmcDarkTheme);
|
||||
const cls = useStyles();
|
||||
|
||||
return (
|
||||
<fui.AccordionItem value="about">
|
||||
<fui.AccordionHeader as="h2" icon={ <InfoRegular /> }>{ i18n.t("about.title") }</fui.AccordionHeader>
|
||||
<fui.AccordionPanel className={ cls.root }>
|
||||
<header className={ cls.horizontalContainer }>
|
||||
<fui.Subtitle1 as="h1">{ i18n.t("manifest.name") }</fui.Subtitle1>
|
||||
<fui.Caption1 as="span">v{ browser.runtime.getManifest().version }</fui.Caption1>
|
||||
</header>
|
||||
|
||||
<fui.Text as="p">
|
||||
{ i18n.t("about.developed_by") } ({ link("@xfox111", personalLinks.twitter) })
|
||||
<br />
|
||||
{ i18n.t("about.lincensed_under") } { link(i18n.t("about.mit_license"), githubLinks.license) }
|
||||
</fui.Text>
|
||||
|
||||
<fui.Text as="p">
|
||||
{ i18n.t("about.translation_cta.text") }<br />
|
||||
{ link(i18n.t("about.translation_cta.button"), githubLinks.translationGuide) }
|
||||
</fui.Text>
|
||||
|
||||
<fui.Text as="p">
|
||||
{ link(i18n.t("about.links.website"), personalLinks.website) } <br />
|
||||
{ link(i18n.t("about.links.source"), githubLinks.repository) } <br />
|
||||
{ link(i18n.t("about.links.changelog"), githubLinks.changelog) }
|
||||
</fui.Text>
|
||||
|
||||
<div className={ cls.horizontalContainer }>
|
||||
<fui.Button { ...buttonProps(getFeedbackLink(), <PersonFeedbackRegular />) }>
|
||||
{ i18n.t("about.cta.feedback") }
|
||||
</fui.Button>
|
||||
<fui.FluentProvider theme={ bmcTheme }>
|
||||
<fui.Button { ...buttonProps(personalLinks.donation, <img style={ { height: 20 } } src="bmc.svg" />) }>
|
||||
{ i18n.t("about.cta.sponsor") }
|
||||
</fui.Button>
|
||||
</fui.FluentProvider>
|
||||
</div>
|
||||
</fui.AccordionPanel>
|
||||
</fui.AccordionItem>
|
||||
);
|
||||
};
|
||||
|
||||
export default AboutSection;
|
||||
@@ -10,24 +10,6 @@ export const useStyles = makeStyles({
|
||||
{
|
||||
fontFamily: tokens.fontFamilyMonospace,
|
||||
},
|
||||
lengthContainer:
|
||||
{
|
||||
display: "grid",
|
||||
gridTemplateColumns: "1fr auto",
|
||||
alignItems: "center",
|
||||
paddingRight: tokens.spacingHorizontalM,
|
||||
},
|
||||
optionsSpacing:
|
||||
{
|
||||
...shorthands.padding(tokens.spacingVerticalS, tokens.spacingHorizontalS),
|
||||
},
|
||||
optionsLabel:
|
||||
{
|
||||
"> div[role=note].fui-InfoButton__info":
|
||||
{
|
||||
zIndex: 1,
|
||||
},
|
||||
},
|
||||
copyIcon:
|
||||
{
|
||||
animationName: "scaleUpIn",
|
||||
@@ -39,5 +21,9 @@ export const useStyles = makeStyles({
|
||||
animationName: "spin",
|
||||
animationDuration: tokens.durationSlow,
|
||||
animationTimingFunction: tokens.curveEasyEaseMax,
|
||||
}
|
||||
},
|
||||
msgBar:
|
||||
{
|
||||
...shorthands.padding(tokens.spacingVerticalMNudge, tokens.spacingHorizontalM),
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,112 @@
|
||||
import { GeneratePassword } from "@/utils/PasswordGenerator";
|
||||
import { GeneratorOptions, useStorage } from "@/utils/storage";
|
||||
import useTimeout from "@/utils/useTimeout";
|
||||
import * as fui from "@fluentui/react-components";
|
||||
import * as ic from "@fluentui/react-icons";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useStyles } from "./GeneratorView.styles";
|
||||
import QuickOptions from "./QuickOptions";
|
||||
|
||||
const GeneratorView: React.FC<{ collapse: boolean }> = props =>
|
||||
{
|
||||
const { generatorOptions } = useStorage();
|
||||
const [options, setOptions] = useState<GeneratorOptions>(generatorOptions);
|
||||
const [showInsert, setShowInsert] = useState<boolean>(false);
|
||||
const [password, setPassword] = useState<string>("");
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const [refreshTimer, copyTimer, insertTimer] = [useTimeout(310), useTimeout(1000), useTimeout(1000)];
|
||||
const cls = useStyles();
|
||||
|
||||
const refresh = useCallback(() =>
|
||||
{
|
||||
setError(null);
|
||||
try { setPassword(GeneratePassword(options)); }
|
||||
catch (e) { setError((e as Error).message); }
|
||||
}, [options]);
|
||||
|
||||
const copy = useCallback(async () =>
|
||||
{
|
||||
await window.navigator.clipboard.writeText(password);
|
||||
copyTimer.trigger();
|
||||
}, [password]);
|
||||
|
||||
const insert = useCallback(async () =>
|
||||
{
|
||||
const tabId: number = (await browser.tabs.query({ active: true, currentWindow: true }))[0].id!;
|
||||
await browser.tabs.sendMessage(tabId, password);
|
||||
insertTimer.trigger();
|
||||
copy();
|
||||
}, [password]);
|
||||
|
||||
useEffect(() => setOptions(generatorOptions), [generatorOptions]);
|
||||
useEffect(refresh, [options]);
|
||||
useEffect(refreshTimer.trigger, [password]);
|
||||
|
||||
useEffect(() =>
|
||||
{
|
||||
(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
const tabId: number = (await browser.tabs.query({ active: true, currentWindow: true }))[0].id!;
|
||||
const fieldCount: number = await browser.tabs.sendMessage(tabId, "probe");
|
||||
|
||||
if (fieldCount > 0)
|
||||
setShowInsert(true);
|
||||
}
|
||||
catch { }
|
||||
})();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<section className={ cls.root }>
|
||||
{ error ?
|
||||
<fui.MessageBar intent="warning" className={ cls.msgBar }>
|
||||
<fui.MessageBarBody>{ error }</fui.MessageBarBody>
|
||||
</fui.MessageBar>
|
||||
:
|
||||
<fui.Input
|
||||
className={ cls.input }
|
||||
readOnly value={ password }
|
||||
contentAfter={ <>
|
||||
<fui.Tooltip content={ i18n.t("common.copy") } relationship="label">
|
||||
<fui.Button
|
||||
appearance="subtle" onClick={ copy }
|
||||
icon={
|
||||
copyTimer.isActive ?
|
||||
<ic.CheckmarkRegular className={ cls.copyIcon } /> :
|
||||
<ic.CopyRegular className={ cls.copyIcon } />
|
||||
} />
|
||||
</fui.Tooltip>
|
||||
|
||||
<fui.Tooltip content={ i18n.t("generator.refresh") } relationship="label">
|
||||
<fui.Button
|
||||
appearance="subtle" onClick={ refresh }
|
||||
icon={
|
||||
<ic.ArrowClockwiseRegular className={ fui.mergeClasses(refreshTimer.isActive && cls.refreshIcon) } />
|
||||
} />
|
||||
</fui.Tooltip>
|
||||
|
||||
{ showInsert &&
|
||||
<fui.Tooltip content={ i18n.t("generator.insert") } relationship="label">
|
||||
<fui.Button
|
||||
appearance="subtle" onClick={ insert }
|
||||
icon={
|
||||
insertTimer.isActive ?
|
||||
<ic.CheckmarkRegular className={ cls.copyIcon } /> :
|
||||
<ic.ArrowRightRegular className={ cls.copyIcon } />
|
||||
} />
|
||||
</fui.Tooltip>
|
||||
}
|
||||
</> } />
|
||||
}
|
||||
|
||||
{ !props.collapse &&
|
||||
<QuickOptions onChange={ e => setOptions(e) } />
|
||||
}
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default GeneratorView;
|
||||
@@ -0,0 +1,22 @@
|
||||
import { makeStyles, shorthands, tokens } from "@fluentui/react-components";
|
||||
|
||||
export const useStyles = makeStyles({
|
||||
characterOptionsContainer:
|
||||
{
|
||||
display: "flex",
|
||||
...shorthands.gap(tokens.spacingHorizontalXS),
|
||||
},
|
||||
options:
|
||||
{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
...shorthands.padding(tokens.spacingVerticalS, tokens.spacingHorizontalS),
|
||||
},
|
||||
lengthContainer:
|
||||
{
|
||||
display: "grid",
|
||||
gridTemplateColumns: "1fr auto",
|
||||
alignItems: "center",
|
||||
paddingRight: tokens.spacingHorizontalM,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,121 @@
|
||||
import { GeneratorOptions, useStorage } from "@/utils/storage";
|
||||
import * as fui from "@fluentui/react-components";
|
||||
import * as ic from "@fluentui/react-icons";
|
||||
import React from "react";
|
||||
import { useStyles } from "./QuickOptions.styles";
|
||||
|
||||
const QuickOptions: React.FC<IProps> = ({ onChange }) =>
|
||||
{
|
||||
const { extOptions, generatorOptions } = useStorage();
|
||||
const [quickOpts, setOptions] = useState<GeneratorOptions>(generatorOptions);
|
||||
const checkedOptions = useMemo(
|
||||
() => Object.keys(quickOpts).filter(k => quickOpts[k as keyof GeneratorOptions] as boolean),
|
||||
[quickOpts]
|
||||
);
|
||||
|
||||
const onCheckedValueChange = useCallback((_: unknown, e: fui.MenuCheckedValueChangeData): void =>
|
||||
{
|
||||
const opts: Partial<Omit<GeneratorOptions, "Length">> = {};
|
||||
|
||||
let keys = Object.keys(quickOpts).filter(i => i !== "Length") as (keyof Omit<GeneratorOptions, "Length">)[];
|
||||
|
||||
if (e.name === "include")
|
||||
keys = keys.filter(i => !i.startsWith("Exclude"));
|
||||
else
|
||||
keys = keys.filter(i => i.startsWith("Exclude"));
|
||||
|
||||
for (const key of keys)
|
||||
opts[key] = e.checkedItems.includes(key);
|
||||
|
||||
setOptions({ ...generatorOptions, ...quickOpts, ...opts });
|
||||
}, [quickOpts]);
|
||||
|
||||
useEffect(() => onChange(quickOpts), [onChange, quickOpts]);
|
||||
|
||||
const IncludeIcon: ic.FluentIcon = ic.bundleIcon(ic.AddCircleFilled, ic.AddCircleRegular);
|
||||
const ExcludeIcon: ic.FluentIcon = ic.bundleIcon(ic.SubtractCircleFilled, ic.SubtractCircleRegular);
|
||||
const cls = useStyles();
|
||||
|
||||
return (
|
||||
<div className={ cls.options }>
|
||||
<fui.InfoLabel info={ i18n.t("generator.options.hint") }>
|
||||
{ i18n.t("generator.options.title") }
|
||||
</fui.InfoLabel>
|
||||
|
||||
<div className={ cls.lengthContainer }>
|
||||
<fui.Slider
|
||||
min={ extOptions.MinLength } max={ Math.max(extOptions.MaxLength, generatorOptions.Length) }
|
||||
value={ quickOpts.Length } onChange={ (_, e) => setOptions({ ...quickOpts, Length: e.value }) } />
|
||||
<fui.Text>{ quickOpts.Length }</fui.Text>
|
||||
</div>
|
||||
|
||||
<div className={ cls.characterOptionsContainer }>
|
||||
<fui.Menu
|
||||
positioning="after" hasCheckmarks
|
||||
checkedValues={ { include: checkedOptions } }
|
||||
onCheckedValueChange={ onCheckedValueChange }>
|
||||
|
||||
<fui.MenuTrigger disableButtonEnhancement>
|
||||
<fui.MenuButton appearance="subtle" icon={ <IncludeIcon /> }>
|
||||
{ i18n.t("generator.options.include") }
|
||||
</fui.MenuButton>
|
||||
</fui.MenuTrigger>
|
||||
|
||||
<fui.MenuPopover>
|
||||
<fui.MenuList>
|
||||
<fui.MenuItemCheckbox name="include" value="Uppercase" icon={ <ic.TextCaseUppercaseRegular /> }>
|
||||
{ i18n.t("settings.include.uppercase") }
|
||||
</fui.MenuItemCheckbox>
|
||||
<fui.MenuItemCheckbox name="include" value="Lowercase" icon={ <ic.TextCaseLowercaseRegular /> }>
|
||||
{ i18n.t("settings.include.lowercase") }
|
||||
</fui.MenuItemCheckbox>
|
||||
<fui.MenuItemCheckbox name="include" value="Numeric" icon={ <ic.NumberSymbolRegular /> }>
|
||||
{ i18n.t("settings.include.numeric") }
|
||||
</fui.MenuItemCheckbox>
|
||||
<fui.MenuItemCheckbox name="include" value="Special" icon={ <ic.MathSymbolsRegular /> }>
|
||||
{ i18n.t("settings.include.special") }
|
||||
</fui.MenuItemCheckbox>
|
||||
</fui.MenuList>
|
||||
</fui.MenuPopover>
|
||||
</fui.Menu>
|
||||
|
||||
<fui.Menu
|
||||
positioning="before"
|
||||
checkedValues={ { exclude: checkedOptions } }
|
||||
onCheckedValueChange={ onCheckedValueChange }>
|
||||
|
||||
<fui.MenuTrigger disableButtonEnhancement>
|
||||
<fui.MenuButton appearance="subtle" icon={ <ExcludeIcon /> }>
|
||||
{ i18n.t("generator.options.exclude") }
|
||||
</fui.MenuButton>
|
||||
</fui.MenuTrigger>
|
||||
|
||||
<fui.MenuPopover>
|
||||
<fui.MenuList>
|
||||
<fui.MenuItemCheckbox name="exclude" value="ExcludeSimilar">
|
||||
{ i18n.t("settings.exclude.similar") }
|
||||
</fui.MenuItemCheckbox>
|
||||
<fui.MenuItemCheckbox name="exclude" value="ExcludeAmbiguous" disabled={ !quickOpts.Special }>
|
||||
{ i18n.t("settings.exclude.ambiguous") }
|
||||
</fui.MenuItemCheckbox>
|
||||
<fui.MenuItemCheckbox name="exclude" value="ExcludeRepeating">
|
||||
{ i18n.t("settings.exclude.repeating.title") }
|
||||
</fui.MenuItemCheckbox>
|
||||
</fui.MenuList>
|
||||
</fui.MenuPopover>
|
||||
</fui.Menu>
|
||||
|
||||
<fui.Tooltip content={ i18n.t("common.reset") } relationship="label">
|
||||
<fui.Button appearance="subtle" icon={ <ic.ArrowUndoRegular /> } onClick={ () => setOptions(generatorOptions) } />
|
||||
</fui.Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default QuickOptions;
|
||||
|
||||
interface IProps
|
||||
{
|
||||
onChange: (value: GeneratorOptions) => void;
|
||||
}
|
||||
@@ -12,4 +12,15 @@ export const useStyles = makeStyles({
|
||||
display: "flex",
|
||||
flexWrap: "wrap",
|
||||
},
|
||||
rangeContainer:
|
||||
{
|
||||
display: "grid",
|
||||
gridTemplateColumns: "1fr auto 1fr auto",
|
||||
alignItems: "center",
|
||||
...shorthands.gap(tokens.spacingHorizontalS),
|
||||
},
|
||||
rangeInput:
|
||||
{
|
||||
width: "100%",
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,138 @@
|
||||
import { CharacterHints } from "@/utils/PasswordGenerator";
|
||||
import { ExtensionOptions, GeneratorOptions, useStorage } from "@/utils/storage";
|
||||
import * as fui from "@fluentui/react-components";
|
||||
import { ArrowUndoRegular, SettingsRegular } from "@fluentui/react-icons";
|
||||
import { useStyles } from "./SettingsSection.styles";
|
||||
|
||||
// FIXME: Remove ts-ignore comments once slots override fix is released
|
||||
// Tracker: https://github.com/microsoft/fluentui/issues/27090
|
||||
const infoLabel = (content: string, hint: string) => ({
|
||||
children: (_: unknown, slotProps: fui.LabelProps) => (
|
||||
<fui.InfoLabel { ...slotProps } info={ hint }>{ content }</fui.InfoLabel>
|
||||
)
|
||||
});
|
||||
|
||||
const defaultOptions =
|
||||
{
|
||||
generator: new GeneratorOptions(),
|
||||
extension: new ExtensionOptions()
|
||||
};
|
||||
|
||||
const SettingsSection: React.FC = () =>
|
||||
{
|
||||
const { extOptions, generatorOptions, updateStorage } = useStorage();
|
||||
const cls = useStyles();
|
||||
|
||||
const resetRange = useCallback(() =>
|
||||
{
|
||||
updateStorage({
|
||||
MinLength: defaultOptions.extension.MinLength,
|
||||
MaxLength: defaultOptions.extension.MaxLength
|
||||
});
|
||||
}, []);
|
||||
|
||||
const setOption = (option: keyof (GeneratorOptions & ExtensionOptions)) =>
|
||||
(_: unknown, args: fui.CheckboxOnChangeData) =>
|
||||
updateStorage({ [option]: args.checked });
|
||||
|
||||
const updateNumberField = (key: keyof (ExtensionOptions & GeneratorOptions), defaultValue: number) =>
|
||||
(_: unknown, e: fui.InputOnChangeData): void =>
|
||||
{
|
||||
if (e.value.length >= 1)
|
||||
{
|
||||
const value = parseInt(e.value);
|
||||
|
||||
if (!isNaN(value) && value >= 0)
|
||||
updateStorage({ [key]: value });
|
||||
}
|
||||
else
|
||||
updateStorage({ [key]: defaultValue });
|
||||
};
|
||||
|
||||
return (
|
||||
<fui.AccordionItem value="settings">
|
||||
<fui.AccordionHeader as="h2" icon={ <SettingsRegular /> }>{ i18n.t("settings.title") }</fui.AccordionHeader>
|
||||
|
||||
<fui.AccordionPanel className={ cls.root }>
|
||||
|
||||
<fui.Field label={ i18n.t("settings.length.title") } hint={ i18n.t("settings.length.hint") }>
|
||||
<fui.Input
|
||||
value={ generatorOptions.Length.toString() }
|
||||
onChange={ updateNumberField("Length", 0) } />
|
||||
</fui.Field>
|
||||
|
||||
<fui.Field label={ i18n.t("settings.quick_range") }>
|
||||
<div className={ cls.rangeContainer }>
|
||||
<fui.Input
|
||||
input={ { className: cls.rangeInput } }
|
||||
value={ extOptions.MinLength.toString() }
|
||||
onChange={ updateNumberField("MinLength", defaultOptions.extension.MinLength) } />
|
||||
|
||||
<fui.Divider />
|
||||
|
||||
<fui.Input
|
||||
input={ { className: cls.rangeInput } }
|
||||
value={ extOptions.MaxLength.toString() }
|
||||
onChange={ updateNumberField("MaxLength", defaultOptions.extension.MaxLength) } />
|
||||
|
||||
<fui.Tooltip relationship="label" content={ i18n.t("common.reset") }>
|
||||
<fui.Button
|
||||
appearance="subtle" icon={ <ArrowUndoRegular /> }
|
||||
onClick={ resetRange } />
|
||||
</fui.Tooltip>
|
||||
</div>
|
||||
</fui.Field>
|
||||
|
||||
<fui.Divider />
|
||||
|
||||
<fui.Text>{ i18n.t("settings.include.title") }</fui.Text>
|
||||
<div className={ cls.checkboxContainer }>
|
||||
<fui.Checkbox label={ i18n.t("settings.include.uppercase") }
|
||||
checked={ generatorOptions.Uppercase }
|
||||
onChange={ setOption("Uppercase") } />
|
||||
<fui.Checkbox
|
||||
label={ i18n.t("settings.include.lowercase") }
|
||||
checked={ generatorOptions.Lowercase }
|
||||
onChange={ setOption("Lowercase") } />
|
||||
<fui.Checkbox
|
||||
label={ i18n.t("settings.include.numeric") }
|
||||
checked={ generatorOptions.Numeric }
|
||||
onChange={ setOption("Numeric") } />
|
||||
<fui.Checkbox
|
||||
label={ i18n.t("settings.include.special") }
|
||||
checked={ generatorOptions.Special }
|
||||
onChange={ setOption("Special") } />
|
||||
</div>
|
||||
|
||||
<fui.Text>{ i18n.t("settings.exclude.title") }</fui.Text>
|
||||
<div className={ cls.checkboxContainer }>
|
||||
<fui.Checkbox
|
||||
// @ts-expect-error See FIXME
|
||||
label={ infoLabel(i18n.t("settings.exclude.similar"), CharacterHints.Similar) }
|
||||
checked={ generatorOptions.ExcludeSimilar }
|
||||
onChange={ setOption("ExcludeSimilar") } />
|
||||
<fui.Checkbox
|
||||
// @ts-expect-error See FIXME
|
||||
label={ infoLabel(i18n.t("settings.exclude.ambiguous"), CharacterHints.Ambiguous) }
|
||||
disabled={ !generatorOptions.Special }
|
||||
checked={ generatorOptions.ExcludeAmbiguous }
|
||||
onChange={ setOption("ExcludeAmbiguous") } />
|
||||
<fui.Checkbox
|
||||
// @ts-expect-error See FIXME
|
||||
label={ infoLabel(i18n.t("settings.exclude.repeating.title"), i18n.t("settings.exclude.repeating.hint")) }
|
||||
checked={ generatorOptions.ExcludeRepeating }
|
||||
onChange={ setOption("ExcludeRepeating") } />
|
||||
</div>
|
||||
|
||||
<fui.Checkbox
|
||||
label={ i18n.t("settings.context_menu") }
|
||||
checked={ extOptions.ContextMenu }
|
||||
onChange={ setOption("ContextMenu") } />
|
||||
|
||||
</fui.AccordionPanel>
|
||||
|
||||
</fui.AccordionItem>
|
||||
);
|
||||
};
|
||||
|
||||
export default SettingsSection;
|
||||
@@ -0,0 +1,46 @@
|
||||
import { GriffelStyle, makeStyles, tokens } from "@fluentui/react-components";
|
||||
|
||||
const random = (max: number): number => Math.floor(Math.random() * max);
|
||||
|
||||
export const SNOWFLAKES_NUM: number = 100;
|
||||
|
||||
export const useStyles = makeStyles({
|
||||
snow:
|
||||
{
|
||||
position: "absolute",
|
||||
overflow: "hidden",
|
||||
pointerEvents: "none",
|
||||
|
||||
top: 0,
|
||||
left: 0,
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
},
|
||||
snowflake:
|
||||
{
|
||||
"--size": "1px",
|
||||
width: "var(--size)",
|
||||
height: "var(--size)",
|
||||
backgroundColor: tokens.colorScrollbarOverlay,
|
||||
borderRadius: tokens.borderRadiusCircular,
|
||||
position: "absolute",
|
||||
top: "-5px",
|
||||
},
|
||||
...[...Array(SNOWFLAKES_NUM)].reduce(
|
||||
(acc, _, i): Record<string, GriffelStyle> => ({
|
||||
...acc,
|
||||
[`snowflake-${i}`]: {
|
||||
"--size": `${random(5)}px`,
|
||||
"--left-start": `${random(20) - 10}vw`,
|
||||
"--left-end": `${random(20) - 10}vw`,
|
||||
left: `${random(100)}vw`,
|
||||
animationName: "snowfall",
|
||||
animationDuration: `${5 + random(10)}s`,
|
||||
animationTimingFunction: "linear",
|
||||
animationIterationCount: "infinite",
|
||||
animationDelay: `-${random(10)}s`,
|
||||
},
|
||||
}),
|
||||
{},
|
||||
),
|
||||
});
|
||||
@@ -0,0 +1,20 @@
|
||||
import { mergeClasses } from "@fluentui/react-components";
|
||||
import { SNOWFLAKES_NUM, useStyles } from "./Snow.styles";
|
||||
|
||||
const Snow: React.FC = () =>
|
||||
{
|
||||
const cls = useStyles();
|
||||
|
||||
if (![0, 11].includes(new Date().getMonth()))
|
||||
return null;
|
||||
|
||||
return (
|
||||
<div className={ cls.snow }>
|
||||
{ [...Array(SNOWFLAKES_NUM)].map((_, i) =>
|
||||
<div key={ i } className={ mergeClasses(cls.snowflake, cls[`snowflake-${i}`]) } />
|
||||
) }
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Snow;
|
||||
@@ -0,0 +1,77 @@
|
||||
html,
|
||||
body
|
||||
{
|
||||
width: 400px;
|
||||
min-width: 400px;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
max-height: 600px;
|
||||
overflow-y: hidden;
|
||||
|
||||
-moz-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6,
|
||||
p, ul, ol, li
|
||||
{
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@keyframes scaleUpIn
|
||||
{
|
||||
from
|
||||
{
|
||||
transform: scale(0.5);
|
||||
filter: opacity(0);
|
||||
}
|
||||
|
||||
to
|
||||
{
|
||||
transform: scale(1);
|
||||
filter: opacity(1)
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spin
|
||||
{
|
||||
from
|
||||
{
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
to
|
||||
{
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeIn
|
||||
{
|
||||
from
|
||||
{
|
||||
filter: opacity(0);
|
||||
}
|
||||
|
||||
to
|
||||
{
|
||||
filter: opacity(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes snowfall
|
||||
{
|
||||
0%
|
||||
{
|
||||
transform: translate3d(var(--left-start), 0, 0);
|
||||
filter: opacity(.6);
|
||||
}
|
||||
|
||||
100%
|
||||
{
|
||||
transform: translate3d(var(--left-end), 610px, 0);
|
||||
filter: opacity(0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import jsConfigs from "@eslint/js";
|
||||
import tsPlugin from "@typescript-eslint/eslint-plugin";
|
||||
import tsParser from "@typescript-eslint/parser";
|
||||
import reactPlugin from "eslint-plugin-react";
|
||||
import globals from "globals";
|
||||
import autoImports from "./.wxt/eslint-auto-imports.mjs";
|
||||
|
||||
export default [
|
||||
autoImports,
|
||||
jsConfigs.configs.recommended,
|
||||
reactPlugin.configs.flat.recommended,
|
||||
{
|
||||
ignores: [".wxt/", ".output/"],
|
||||
},
|
||||
{
|
||||
files: ["**/*.{ts,tsx,js,mjs,jsx}"],
|
||||
languageOptions:
|
||||
{
|
||||
parser: tsParser,
|
||||
globals: {
|
||||
React: true,
|
||||
JSX: true,
|
||||
...globals.browser
|
||||
},
|
||||
ecmaVersion: "latest",
|
||||
sourceType: "module",
|
||||
},
|
||||
settings: {
|
||||
react: {
|
||||
version: "detect"
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
"@typescript-eslint": tsPlugin
|
||||
}
|
||||
},
|
||||
{
|
||||
rules:
|
||||
{
|
||||
"no-unused-vars": "off",
|
||||
"no-undef": "error",
|
||||
"semi": ["error", "always"],
|
||||
"quotes": ["error", "double"],
|
||||
"indent": ["warn", "tab", { "SwitchCase": 1 }],
|
||||
"no-empty": "off",
|
||||
"react/prop-types": "off",
|
||||
"@typescript-eslint/no-unused-vars": ["error", {
|
||||
"argsIgnorePattern": "^_",
|
||||
"args": "none"
|
||||
}],
|
||||
}
|
||||
}
|
||||
];
|
||||
@@ -0,0 +1,59 @@
|
||||
manifest:
|
||||
name: "Password generator"
|
||||
description: "Password generator extension allows you to easily generate long and secure password in one click"
|
||||
author: "Eugene Fox"
|
||||
|
||||
common:
|
||||
reset: "Reset"
|
||||
copy: "Copy"
|
||||
|
||||
errors:
|
||||
too_short: "Password length must be at least 4 characters"
|
||||
no_characters: "At least one set of characters must be selected"
|
||||
too_long: "Length is too long to generate password with unique characters"
|
||||
|
||||
about:
|
||||
title: "About"
|
||||
developed_by: "Developed by Eugene Fox"
|
||||
lincensed_under: "Licensed under"
|
||||
mit_license: "MIT License"
|
||||
translation_cta:
|
||||
text: "Found a typo or want a translation for your language?"
|
||||
button: "Get started here"
|
||||
links:
|
||||
website: "My website"
|
||||
source: "Source code"
|
||||
changelog: "Changelog"
|
||||
cta:
|
||||
feedback: "Leave feedback"
|
||||
sponsor: "Buy me a coffee"
|
||||
|
||||
settings:
|
||||
title: "Settings"
|
||||
length:
|
||||
title: "Default password length"
|
||||
hint: "Recommended length: 8–16"
|
||||
quick_range: "Quick adjustment length range"
|
||||
include:
|
||||
title: "Include symbols"
|
||||
uppercase: "Uppercase"
|
||||
lowercase: "Lowercase"
|
||||
numeric: "Numeric"
|
||||
special: "Special"
|
||||
exclude:
|
||||
title: "Exclude symbols"
|
||||
similar: "Similar"
|
||||
ambiguous: "Ambiguous"
|
||||
repeating:
|
||||
title: "Repeating"
|
||||
hint: "Each character in the password will be unique"
|
||||
context_menu: "Show context menu"
|
||||
|
||||
generator:
|
||||
refresh: "Refresh"
|
||||
insert: "Insert and copy"
|
||||
options:
|
||||
title: "Quick adjustments"
|
||||
hint: "These adjustments will not be saved"
|
||||
include: "Include"
|
||||
exclude: "Exclude"
|
||||
@@ -0,0 +1,59 @@
|
||||
manifest:
|
||||
name: "Generator haseł"
|
||||
description: "Rozszerzenie, które pozwala na łatwe generowanie trudnych i bezpiecznych haseł w jednym kliknięciu"
|
||||
author: "Jewgienij Lis"
|
||||
|
||||
common:
|
||||
reset: "Resetuj"
|
||||
copy: "Kopiuj"
|
||||
|
||||
errors:
|
||||
too_short: "Minimalna długość hasła to 4 znaki"
|
||||
no_characters: "Musisz wybrać przynajmniej jeden rodzaj znaków"
|
||||
too_long: "Wybrana długość jest zbyt duża, aby wygenerować unikalne znaki"
|
||||
|
||||
about:
|
||||
title: "O rozszerzeniu"
|
||||
developed_by: "Autor: Jewgienij Lis"
|
||||
licensed_under: ""
|
||||
mit_license: "Licencja MIT"
|
||||
translation_cta:
|
||||
text: "Znalazłeś błąd lub potrzebujesz tłumaczenia na swój język?"
|
||||
button: "Zacznij tutaj"
|
||||
links:
|
||||
website: "Moja strona internetowa"
|
||||
source: "Kod źródłowy"
|
||||
changelog: "Lista zmian"
|
||||
cta:
|
||||
feedback: "Zostaw opinię"
|
||||
sponsor: "Wesprzyj"
|
||||
|
||||
settings:
|
||||
title: "Ustawienia"
|
||||
length:
|
||||
title: "Domyślna długość hasła"
|
||||
hint: "Zalecana długość: 8–16"
|
||||
quick_range: "Zakres długości dla szybkich ustawień"
|
||||
include:
|
||||
title: "Dodaj znaki"
|
||||
uppercase: "Wielkie"
|
||||
lowercase: "Małe"
|
||||
numeric: "Cyfry"
|
||||
special: "Specjalne"
|
||||
exclude:
|
||||
title: "Wyklucz znaki"
|
||||
similar: "Podobne"
|
||||
ambiguous: "Niebezpieczne"
|
||||
repeating:
|
||||
title: "Powtarzające się"
|
||||
hint: "Każdy znak hasła będzie unikalny"
|
||||
context_menu: "Pokaż menu kontekstowe"
|
||||
|
||||
generator:
|
||||
refresh: "Wygeneruj nowe"
|
||||
insert: "Wstaw i kopiuj"
|
||||
options:
|
||||
title: "Szybkie ustawienia"
|
||||
hint: "Te ustawienia nie zostaną zapisane"
|
||||
include: "Dodaj"
|
||||
exclude: "Wyklucz"
|
||||
@@ -0,0 +1,59 @@
|
||||
manifest:
|
||||
name: "Gerador de Senhas"
|
||||
description: "A extensão do gerador de senhas permite gerar facilmente uma senha longa e segura em um clique"
|
||||
author: "Eugênio Fox"
|
||||
|
||||
common:
|
||||
reset: "Redefinir"
|
||||
copy: "Copiar"
|
||||
|
||||
errors:
|
||||
too_short: "O comprimento da senha deve ter pelo menos 4 caracteres"
|
||||
no_characters: "É necessário selecionar pelo menos um conjunto de caracteres"
|
||||
too_long: "O comprimento é muito longo para gerar uma senha com caracteres únicos"
|
||||
|
||||
about:
|
||||
title: "Sobre"
|
||||
developed_by: "Desenvolvido por Eugênio Fox"
|
||||
licensed_under: "Licenciado sob"
|
||||
mit_license: "Licença MIT"
|
||||
translation_cta:
|
||||
text: "Encontrou um erro ou quer uma tradução no seu idioma?"
|
||||
button: "Comece aqui"
|
||||
links:
|
||||
website: "Meu site"
|
||||
source: "Código fonte"
|
||||
changelog: "Registro de alterações"
|
||||
cta:
|
||||
feedback: "Enviar comentários"
|
||||
sponsor: "Pague um café"
|
||||
|
||||
settings:
|
||||
title: "Configurações"
|
||||
length:
|
||||
title: "Comprimento padrão da senha"
|
||||
hint: "Comprimento recomendado: 8–16"
|
||||
quick_range: "Intervalo do comprimento de ajuste rápido"
|
||||
include:
|
||||
title: "Incluir símbolos"
|
||||
uppercase: "Maiúsculos"
|
||||
lowercase: "Minúsculos"
|
||||
numeric: "Numéricos"
|
||||
special: "Especiais"
|
||||
exclude:
|
||||
title: "Excluir símbolos"
|
||||
similar: "Semelhantes"
|
||||
ambiguous: "Ambíguos"
|
||||
repeating:
|
||||
title: "Repetidos"
|
||||
hint: "Cada caractere na senha será único"
|
||||
context_menu: "Mostrar menu de contexto"
|
||||
|
||||
generator:
|
||||
refresh: "Gerar nova"
|
||||
insert: "Inserir e copiar"
|
||||
options:
|
||||
title: "Ajustes rápidos"
|
||||
hint: "Esses ajustes não serão salvos"
|
||||
include: "Incluir"
|
||||
exclude: "Excluir"
|
||||
@@ -0,0 +1,59 @@
|
||||
manifest:
|
||||
name: "Генератор паролей"
|
||||
description: "Расширение, позволяющее легко генерировать сложные и надежные пароли в один клик"
|
||||
author: "Евгений Лис"
|
||||
|
||||
common:
|
||||
reset: "Сбросить"
|
||||
copy: "Копировать"
|
||||
|
||||
errors:
|
||||
too_short: "Минимальная длина пароля — 4 символа"
|
||||
no_characters: "Необходимо выбрать хотя бы один вид символов"
|
||||
too_long: "Выбранная длина слишком большая, для генерации уникальных символов"
|
||||
|
||||
about:
|
||||
title: "О расширении"
|
||||
developed_by: "Разработчк: Евгений Лис"
|
||||
licensed_under: ""
|
||||
mit_license: "Лицензия MIT"
|
||||
translation_cta:
|
||||
text: "Нашли опечатку или хотите перевод на ваш язык?"
|
||||
button: "Начните здесь"
|
||||
links:
|
||||
website: "Мой веб-сайт"
|
||||
source: "Исходный код"
|
||||
changelog: "Список изменений"
|
||||
cta:
|
||||
feedback: "Оставить отзыв"
|
||||
sponsor: "Поддержать"
|
||||
|
||||
settings:
|
||||
title: "Настройки"
|
||||
length:
|
||||
title: "Длина пароля по умолчанию"
|
||||
hint: "Рекомендованная длина: 8–16"
|
||||
quick_range: "Диапазон длины для быстрых настроек"
|
||||
include:
|
||||
title: "Добавить символы"
|
||||
uppercase: "Прописные"
|
||||
lowercase: "Строчные"
|
||||
numeric: "Числовые"
|
||||
special: "Специальные"
|
||||
exclude:
|
||||
title: "Исключить символы"
|
||||
similar: "Похожие"
|
||||
ambiguous: "Особые"
|
||||
repeating:
|
||||
title: "Повторяющиеся"
|
||||
hint: "Каждый символ пароля будет уникальным"
|
||||
context_menu: "Показать контекстное меню"
|
||||
|
||||
generator:
|
||||
refresh: "Сгенерировать новый"
|
||||
insert: "Вставить и копировать"
|
||||
options:
|
||||
title: "Быстрые настройки"
|
||||
hint: "Эти настройки не будут сохранены"
|
||||
include: "Добавить"
|
||||
exclude: "Исключить"
|
||||
@@ -0,0 +1,59 @@
|
||||
manifest:
|
||||
name: "Генератор паролів"
|
||||
description: "Розширення, яке дозволяє легко генерувати складні та надійні паролі в один клік"
|
||||
author: "Євген Лис"
|
||||
|
||||
common:
|
||||
reset: "Скинути"
|
||||
copy: "Копіювати"
|
||||
|
||||
errors:
|
||||
too_short: "Мінімальна довжина пароля — 4 символи"
|
||||
no_characters: "Необхідно вибрати хоча б один вид символів"
|
||||
too_long: "Вибрана довжина занадто велика, для генерації унікальних символів"
|
||||
|
||||
about:
|
||||
title: "Про розширення"
|
||||
developed_by: "Розробник: Євген Лис"
|
||||
licensed_under: ""
|
||||
mit_license: "Ліцензія MIT"
|
||||
translation_cta:
|
||||
text: "Знайшли помилку або хочете переклад на вашу мову?"
|
||||
button: "Почніть тут"
|
||||
links:
|
||||
website: "Мій веб-сайт"
|
||||
source: "Вихідний код"
|
||||
changelog: "Список змін"
|
||||
cta:
|
||||
feedback: "Залишити відгук"
|
||||
sponsor: "Підтримати"
|
||||
|
||||
settings:
|
||||
title: "Налаштування"
|
||||
length:
|
||||
title: "Довжина пароля за замовчуванням"
|
||||
hint: "Рекомендована довжина: 8–16"
|
||||
quick_range: "Діапазон довжини для швидких налаштувань"
|
||||
include:
|
||||
title: "Додати символи"
|
||||
uppercase: "Великі"
|
||||
lowercase: "Малі"
|
||||
numeric: "Числові"
|
||||
special: "Спеціальні"
|
||||
exclude:
|
||||
title: "Виключити символи"
|
||||
similar: "Схожі"
|
||||
ambiguous: "Особливі"
|
||||
repeating:
|
||||
title: "Повторювані"
|
||||
hint: "Кожен символ пароля буде унікальним"
|
||||
context_menu: "Показати контекстне меню"
|
||||
|
||||
generator:
|
||||
refresh: "Згенерувати новий"
|
||||
insert: "Вставити та копіювати"
|
||||
options:
|
||||
title: "Швидкі налаштування"
|
||||
hint: "Ці налаштування не будуть збережені"
|
||||
include: "Додати"
|
||||
exclude: "Виключити"
|
||||
@@ -1,40 +1,37 @@
|
||||
{
|
||||
"name": "password-generator",
|
||||
"version": "3.0.0",
|
||||
"version": "4.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build",
|
||||
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0"
|
||||
"dev": "wxt",
|
||||
"build": "wxt build --mv3",
|
||||
"zip": "wxt zip --mv3",
|
||||
"lint": "eslint . -c eslint.config.js",
|
||||
"prebuild": "npm run lint",
|
||||
"prezip": "npm run lint",
|
||||
"prepare": "wxt prepare",
|
||||
"compile": "tsc --noEmit",
|
||||
"postinstall": "wxt prepare"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fluentui/react-components": "^9.39.0",
|
||||
"@fluentui/react-icons": "^2.0.222",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
"@fluentui/react-components": "^9.54.16",
|
||||
"@fluentui/react-icons": "^2.0.258",
|
||||
"@wxt-dev/i18n": "^0.1.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.2.37",
|
||||
"@types/react-dom": "^18.2.15",
|
||||
"@types/webextension-polyfill": "^0.10.6",
|
||||
"@typescript-eslint/eslint-plugin": "^6.10.0",
|
||||
"@typescript-eslint/parser": "^6.10.0",
|
||||
"@vitejs/plugin-react-swc": "^3.4.1",
|
||||
"eslint": "^8.53.0",
|
||||
"eslint-plugin-react": "^7.33.2",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.4",
|
||||
"sass": "^1.69.5",
|
||||
"typescript": "^5.2.2",
|
||||
"vite": "^4.5.0",
|
||||
"vite-plugin-static-copy": "^0.17.0",
|
||||
"vite-plugin-svgr": "^4.1.0",
|
||||
"vite-plugin-web-extension": "^3.0.1",
|
||||
"webextension-polyfill": "^0.10.0"
|
||||
},
|
||||
"resolutions": {
|
||||
"postcss": "^8.4.31",
|
||||
"tough-cookie": "^4.1.3"
|
||||
"@eslint/js": "^9.10.0",
|
||||
"@types/react": "^18.3.9",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.7.0",
|
||||
"@typescript-eslint/parser": "^8.7.0",
|
||||
"@wxt-dev/module-react": "^1.1.1",
|
||||
"eslint": "^9.11.1",
|
||||
"eslint-plugin-react": "^7.36.1",
|
||||
"globals": "^15.9.0",
|
||||
"typescript": "^5.6.2",
|
||||
"wxt": "^0.19.10"
|
||||
}
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 8.0 KiB After Width: | Height: | Size: 8.0 KiB |
|
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 |
@@ -1,29 +0,0 @@
|
||||
import { Accordion, FluentProvider, Spinner } from "@fluentui/react-components";
|
||||
import { useStyles } from "./App.styles";
|
||||
import AboutSection from "./Components/AboutSection";
|
||||
import GeneratorView from "./Components/GeneratorView";
|
||||
import SettingsSection from "./Components/SettingsSection";
|
||||
import Specials from "./Specials/Specials";
|
||||
import { StorageProvider } from "./Utils/Storage";
|
||||
import { useTheme } from "./Utils/Theme";
|
||||
|
||||
export default function App(): JSX.Element
|
||||
{
|
||||
const theme = useTheme();
|
||||
const cls = useStyles();
|
||||
|
||||
return (
|
||||
<FluentProvider theme={ theme }>
|
||||
<main className={ cls.root }>
|
||||
<StorageProvider loader={ <Spinner size="large" className={ cls.spinner } /> }>
|
||||
<GeneratorView />
|
||||
<Accordion collapsible className={ cls.accordionAnimation }>
|
||||
<SettingsSection />
|
||||
<AboutSection />
|
||||
</Accordion>
|
||||
</StorageProvider>
|
||||
<Specials />
|
||||
</main>
|
||||
</FluentProvider>
|
||||
);
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
import * as fui from "@fluentui/react-components";
|
||||
import { InfoRegular, PersonFeedbackRegular } from "@fluentui/react-icons";
|
||||
import Package from "../../package.json";
|
||||
import BuyMeACoffee from "../Assets/BuyMeACoffee.svg?react";
|
||||
import { bmcDarkTheme, bmcLightTheme } from "../Data/BmcTheme";
|
||||
import { GetFeedbackLink, GithubLink, PersonalLink } from "../Data/Links";
|
||||
import { GetLocaleString as loc } from "../Utils/Localization";
|
||||
import { useTheme } from "../Utils/Theme";
|
||||
import { useStyles } from "./AboutSection.styles";
|
||||
|
||||
export default function AboutSection(): JSX.Element
|
||||
{
|
||||
const theme = useTheme(bmcLightTheme, bmcDarkTheme);
|
||||
const cls = useStyles();
|
||||
|
||||
const link = (text: string, href: string): JSX.Element => (
|
||||
<fui.Link target="_blank" href={ href }>{ text }</fui.Link>
|
||||
);
|
||||
|
||||
const buttonProps = (href: string, icon: JSX.Element): fui.ButtonProps => (
|
||||
{
|
||||
as: "a", target: "_blank", href,
|
||||
appearance: "primary", icon
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<fui.AccordionItem value="about">
|
||||
<fui.AccordionHeader as="h2" icon={ <InfoRegular /> }>{ loc("about@title") }</fui.AccordionHeader>
|
||||
<fui.AccordionPanel className={ cls.root }>
|
||||
<header className={ cls.horizontalContainer }>
|
||||
<fui.Subtitle1 as="h1">{ loc("name") }</fui.Subtitle1>
|
||||
<fui.Caption1 as="span">v{ Package.version }</fui.Caption1>
|
||||
</header>
|
||||
|
||||
<fui.Text as="p">
|
||||
{ loc("about@developedBy") } ({ link("@xfox111", PersonalLink.Twitter) })
|
||||
<br />
|
||||
{ loc("about@licensedUnder") } { link(loc("about@mitLicense"), GithubLink.License) }
|
||||
</fui.Text>
|
||||
|
||||
<fui.Text as="p">
|
||||
{ loc("about@translationCta") }<br />
|
||||
{ link(loc("about@translationCtaButton"), GithubLink.TranslationGuide) }
|
||||
</fui.Text>
|
||||
|
||||
<fui.Text as="p">
|
||||
{ link(loc("about@website"), PersonalLink.Website) } <br />
|
||||
{ link(loc("about@sourceCode"), GithubLink.Repository) } <br />
|
||||
{ link(loc("about@changelog"), GithubLink.Changelog) }
|
||||
</fui.Text>
|
||||
|
||||
<div className={ cls.horizontalContainer }>
|
||||
<fui.Button { ...buttonProps(GetFeedbackLink(), <PersonFeedbackRegular />) }>
|
||||
{ loc("about@feedback") }
|
||||
</fui.Button>
|
||||
<fui.FluentProvider theme={ theme }>
|
||||
<fui.Button { ...buttonProps(PersonalLink.BuyMeACoffee, <BuyMeACoffee />) }>
|
||||
{ loc("about@sponsor") }
|
||||
</fui.Button>
|
||||
</fui.FluentProvider>
|
||||
</div>
|
||||
</fui.AccordionPanel>
|
||||
</fui.AccordionItem>
|
||||
);
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
import { Button, Checkbox, Input, Slider, Text, Tooltip, mergeClasses } from "@fluentui/react-components";
|
||||
import { Alert, InfoLabel } from "@fluentui/react-components/unstable";
|
||||
import { ArrowClockwiseRegular, ArrowUndoRegular, CheckmarkRegular, CopyRegular } from "@fluentui/react-icons";
|
||||
import { useEffect, useState } from "react";
|
||||
import GeneratorOptions from "../Models/GeneratorOptions";
|
||||
import { GetLocaleString as loc } from "../Utils/Localization";
|
||||
import { GeneratePassword } from "../Utils/PasswordGenerator";
|
||||
import { useStorage } from "../Utils/Storage";
|
||||
import { useTimeout } from "../Utils/Timeout";
|
||||
import { useStyles } from "./GeneratorView.styles";
|
||||
|
||||
type QuickOptions = Pick<GeneratorOptions, "Length" | "Special" | "ExcludeAmbiguous">;
|
||||
|
||||
export default function GeneratorView(): JSX.Element
|
||||
{
|
||||
const { generatorOptions } = useStorage();
|
||||
const [password, setPassword] = useState<string>("");
|
||||
const [quickOpts, _setOpts] = useState<QuickOptions>(generatorOptions);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const [refreshTimer, copyTimer] = [useTimeout(310), useTimeout(1000)];
|
||||
const cls = useStyles();
|
||||
|
||||
const resetOptions = (): void =>
|
||||
_setOpts(generatorOptions);
|
||||
|
||||
const setOptions = (opts: Partial<QuickOptions>) =>
|
||||
_setOpts({ ...quickOpts, ...opts });
|
||||
|
||||
function RefreshPassword(): void
|
||||
{
|
||||
setError(null);
|
||||
const options: GeneratorOptions = { ...generatorOptions, ...quickOpts };
|
||||
|
||||
try { setPassword(GeneratePassword(options)); }
|
||||
catch (e) { setError((e as Error).message); }
|
||||
}
|
||||
|
||||
async function CopyPassword(): Promise<void>
|
||||
{
|
||||
await window.navigator.clipboard.writeText(password);
|
||||
copyTimer.trigger();
|
||||
}
|
||||
|
||||
useEffect(resetOptions, [generatorOptions]);
|
||||
useEffect(RefreshPassword, [generatorOptions, quickOpts]);
|
||||
useEffect(refreshTimer.trigger, [password]);
|
||||
|
||||
const actionButtons: JSX.Element = <>
|
||||
<Tooltip content={ loc("generator@copy") } relationship="label">
|
||||
<Button
|
||||
appearance="subtle" onClick={ CopyPassword }
|
||||
icon={
|
||||
copyTimer.isActive ?
|
||||
<CheckmarkRegular className={ cls.copyIcon } /> :
|
||||
<CopyRegular className={ cls.copyIcon } />
|
||||
} />
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip content={ loc("generator@refresh") } relationship="label">
|
||||
<Button
|
||||
appearance="subtle" onClick={ RefreshPassword }
|
||||
icon={
|
||||
<ArrowClockwiseRegular className={ mergeClasses(refreshTimer.isActive && cls.refreshIcon) } />
|
||||
} />
|
||||
</Tooltip>
|
||||
</>;
|
||||
|
||||
return (
|
||||
<section className={ cls.root }>
|
||||
{ error ?
|
||||
<Alert intent="warning">{ error }</Alert> :
|
||||
<Input readOnly contentAfter={ actionButtons } value={ password } className={ cls.input } />
|
||||
}
|
||||
|
||||
<div className={ mergeClasses(cls.root, cls.optionsSpacing) }>
|
||||
<InfoLabel info={ loc("generator@quickOptions__hint") } className={ cls.optionsLabel }>
|
||||
{ loc("generator@quickOptions") }
|
||||
</InfoLabel>
|
||||
|
||||
<div className={ cls.lengthContainer }>
|
||||
<Slider
|
||||
min={ 6 } max={ Math.max(32, generatorOptions.Length) }
|
||||
value={ quickOpts.Length } onChange={ (_, e) => setOptions({ Length: e.value }) } />
|
||||
<Text>{ quickOpts.Length }</Text>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Checkbox
|
||||
label={ loc("settings@special") }
|
||||
checked={ quickOpts.Special }
|
||||
onChange={ (_, e) => setOptions({ Special: e.checked as boolean }) } />
|
||||
<Checkbox
|
||||
label={ loc("settings@ambiguous") } disabled={ !quickOpts.Special }
|
||||
checked={ !quickOpts.ExcludeAmbiguous }
|
||||
onChange={ (_, e) => setOptions({ ExcludeAmbiguous: !e.checked }) } />
|
||||
|
||||
<Tooltip content={ loc("generator@reset") } relationship="label">
|
||||
<Button appearance="subtle" icon={ <ArrowUndoRegular /> } onClick={ resetOptions } />
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
import * as fui from "@fluentui/react-components";
|
||||
import { InfoLabel } from "@fluentui/react-components/unstable";
|
||||
import { SettingsRegular } from "@fluentui/react-icons";
|
||||
import ExtensionOptions from "../Models/ExtensionOptions";
|
||||
import GeneratorOptions from "../Models/GeneratorOptions";
|
||||
import { GetLocaleString as loc } from "../Utils/Localization";
|
||||
import { CharacterHints } from "../Utils/PasswordGenerator";
|
||||
import { useStorage } from "../Utils/Storage";
|
||||
import { useStyles } from "./SettingsSection.styles";
|
||||
|
||||
// FIXME: Remove ts-ignore comments once slots override fix is released
|
||||
// Tracker: https://github.com/microsoft/fluentui/issues/27090
|
||||
|
||||
export default function SettingsSection(): JSX.Element
|
||||
{
|
||||
const { generatorOptions, updateStorage } = useStorage();
|
||||
const cls = useStyles();
|
||||
|
||||
const infoLabel = (content: string, hint: string) => ({
|
||||
children: (_: unknown, slotProps: fui.LabelProps) => (
|
||||
<InfoLabel { ...slotProps } info={ hint }>{ content }</InfoLabel>
|
||||
)
|
||||
});
|
||||
|
||||
const setOption = (option: keyof (GeneratorOptions & ExtensionOptions)) =>
|
||||
(_: unknown, args: fui.CheckboxOnChangeData) =>
|
||||
updateStorage({ [option]: args.checked } );
|
||||
|
||||
return (
|
||||
<fui.AccordionItem value="settings">
|
||||
<fui.AccordionHeader as="h2" icon={ <SettingsRegular /> }>{ loc("settings@title") }</fui.AccordionHeader>
|
||||
|
||||
<fui.AccordionPanel className={ cls.root }>
|
||||
|
||||
<fui.Field label={ loc("settings@length") } hint={ loc("settings@length__hint") }>
|
||||
<fui.Input
|
||||
type="number" min={ 6 }
|
||||
value={ generatorOptions.Length.toString() }
|
||||
onChange={ (_, e) => updateStorage({ Length: parseInt(e.value) }) } />
|
||||
</fui.Field>
|
||||
|
||||
<fui.Divider />
|
||||
|
||||
<fui.Text>{ loc("settings@include") }</fui.Text>
|
||||
<div className={ cls.checkboxContainer }>
|
||||
<fui.Checkbox label={ loc("settings@uppercase") }
|
||||
checked={ generatorOptions.Uppercase }
|
||||
onChange={ setOption("Uppercase") } />
|
||||
<fui.Checkbox
|
||||
label={ loc("settings@lowercase") }
|
||||
checked={ generatorOptions.Lowercase }
|
||||
onChange={ setOption("Lowercase") } />
|
||||
<fui.Checkbox
|
||||
label={ loc("settings@numeric") }
|
||||
checked={ generatorOptions.Numeric }
|
||||
onChange={ setOption("Numeric") } />
|
||||
<fui.Checkbox
|
||||
label={ loc("settings@special") }
|
||||
checked={ generatorOptions.Special }
|
||||
onChange={ setOption("Special") } />
|
||||
</div>
|
||||
|
||||
<fui.Text>{ loc("settings@exclude") }</fui.Text>
|
||||
<div className={ cls.checkboxContainer }>
|
||||
<fui.Checkbox
|
||||
// @ts-expect-error See line 11
|
||||
label={ infoLabel(loc("settings@similar"), CharacterHints.Similar) }
|
||||
checked={ generatorOptions.ExcludeSimilar }
|
||||
onChange={ setOption("ExcludeSimilar") } />
|
||||
<fui.Checkbox
|
||||
// @ts-expect-error See line 11
|
||||
label={ infoLabel(loc("settings@ambiguous"), CharacterHints.Ambiguous) }
|
||||
disabled={ !generatorOptions.Special }
|
||||
checked={ generatorOptions.ExcludeAmbiguous }
|
||||
onChange={ setOption("ExcludeAmbiguous") } />
|
||||
<fui.Checkbox
|
||||
// @ts-expect-error See line 11
|
||||
label={ infoLabel(loc("settings@repeating"), loc("settings@repeating__hint")) }
|
||||
checked={ generatorOptions.ExcludeRepeating }
|
||||
onChange={ setOption("ExcludeRepeating") } />
|
||||
</div>
|
||||
|
||||
</fui.AccordionPanel>
|
||||
|
||||
</fui.AccordionItem>
|
||||
);
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
import browser, { Manifest } from "webextension-polyfill";
|
||||
|
||||
export const PersonalLink =
|
||||
{
|
||||
Website: "https://xfox111.net",
|
||||
Twitter: "https://twitter.com/xfox111",
|
||||
BuyMeACoffee: "https://buymeacoffee.com/xfox111"
|
||||
};
|
||||
|
||||
export const WebstoreLink =
|
||||
{
|
||||
Chrome: "https://chrome.google.com/webstore/detail/password-generator/jnjobgjobffgmgfnkpkjfjkkfhfikmfl",
|
||||
Edge: "https://microsoftedge.microsoft.com/addons/detail/password-generator/manimdhobjbkfpeeehlhhneookiokpbj",
|
||||
Firefox: "https://addons.mozilla.org/firefox/addon/easy-password-generator"
|
||||
};
|
||||
|
||||
const getGithub = (path?: string): string =>
|
||||
`https://github.com/xfox111/PasswordGeneratorExtension${path}`;
|
||||
|
||||
export const GithubLink =
|
||||
{
|
||||
Repository: getGithub(),
|
||||
Changelog: getGithub("/releases/latest"),
|
||||
TranslationGuide: getGithub("/blob/main/CONTRIBUTING.md#contributing-to-translations"),
|
||||
License: getGithub("/blob/main/LICENSE")
|
||||
};
|
||||
|
||||
export const GetFeedbackLink = () =>
|
||||
{
|
||||
if (__BROWSER__ === "firefox")
|
||||
return WebstoreLink.Firefox;
|
||||
|
||||
const manifest: Manifest.WebExtensionManifest = browser.runtime.getManifest();
|
||||
|
||||
const updateUrl: URL = new URL((manifest as unknown as Record<string, unknown>).update_url as string ?? "about:blank");
|
||||
|
||||
if (updateUrl.host === "edge.microsoft.com")
|
||||
return WebstoreLink.Edge;
|
||||
if (updateUrl.host === "clients2.google.com")
|
||||
return WebstoreLink.Chrome;
|
||||
|
||||
return "mailto:feedback@xfox111.net";
|
||||
};
|
||||
@@ -1,171 +0,0 @@
|
||||
{
|
||||
"name": {
|
||||
"message": "Password Generator",
|
||||
"description": "Extension name"
|
||||
},
|
||||
"description": {
|
||||
"message": "Password generator extension allows you to easily generate long and secure password in one click",
|
||||
"description": "Extension description"
|
||||
},
|
||||
"author": {
|
||||
"message": "Eugene Fox",
|
||||
"description": "Extension author"
|
||||
},
|
||||
"error@moreCharacters": {
|
||||
"message": "Password length must be at least 4 characters",
|
||||
"description": "Error message when password length is less than 4 characters"
|
||||
},
|
||||
"error@noCharacters": {
|
||||
"message": "At least one set of characters must be selected",
|
||||
"description": "Error message when no character set is selected"
|
||||
},
|
||||
"error@tooLong": {
|
||||
"message": "Length is too long to generate password with unique characters",
|
||||
"description": "Error message when password length is too long to generate password with unique characters"
|
||||
},
|
||||
"about@title":
|
||||
{
|
||||
"message": "About",
|
||||
"description": "About section title"
|
||||
},
|
||||
"about@developedBy":
|
||||
{
|
||||
"message": "Developed by Eugene Fox",
|
||||
"description": "Developer credits in about section"
|
||||
},
|
||||
"about@licensedUnder":
|
||||
{
|
||||
"message": "Licensed under",
|
||||
"description": "License info in about section"
|
||||
},
|
||||
"about@mitLicense":
|
||||
{
|
||||
"message": "MIT License",
|
||||
"description": "License name"
|
||||
},
|
||||
"about@translationCta":
|
||||
{
|
||||
"message": "Found a typo or want a translation for your language?",
|
||||
"description": "Translation CTA in about section"
|
||||
},
|
||||
"about@translationCtaButton":
|
||||
{
|
||||
"message": "Get started here",
|
||||
"description": "Translation CTA button in about section"
|
||||
},
|
||||
"about@website":
|
||||
{
|
||||
"message": "My website",
|
||||
"description": "Website link in about section"
|
||||
},
|
||||
"about@sourceCode":
|
||||
{
|
||||
"message": "Source code",
|
||||
"description": "Source code link in about section"
|
||||
},
|
||||
"about@changelog":
|
||||
{
|
||||
"message": "Changelog",
|
||||
"description": "Changelog link in about section"
|
||||
},
|
||||
"about@feedback":
|
||||
{
|
||||
"message": "Leave feedback",
|
||||
"description": "Feedback link in about section"
|
||||
},
|
||||
"about@sponsor":
|
||||
{
|
||||
"message": "Buy me a coffee",
|
||||
"description": "Buy me a coffee donation link in about section"
|
||||
},
|
||||
"settings@title":
|
||||
{
|
||||
"message": "Settings",
|
||||
"description": "Settings section title"
|
||||
},
|
||||
"settings@length":
|
||||
{
|
||||
"message": "Password length",
|
||||
"description": "Password length label in settings section"
|
||||
},
|
||||
"settings@length__hint":
|
||||
{
|
||||
"message": "Recommended length: 8—16",
|
||||
"description": "Password length recommendatin in settings section"
|
||||
},
|
||||
"settings@include":
|
||||
{
|
||||
"message": "Include symbols",
|
||||
"description": "Include character sets section"
|
||||
},
|
||||
"settings@uppercase":
|
||||
{
|
||||
"message": "Uppercase",
|
||||
"description": "Uppercase character set label in settings section"
|
||||
},
|
||||
"settings@lowercase":
|
||||
{
|
||||
"message": "Lowercase",
|
||||
"description": "Lowercase character set label in settings section"
|
||||
},
|
||||
"settings@numeric":
|
||||
{
|
||||
"message": "Numeric",
|
||||
"description": "Numbers character set label in settings section"
|
||||
},
|
||||
"settings@special":
|
||||
{
|
||||
"message": "Special",
|
||||
"description": "Special characters character set label in settings section"
|
||||
},
|
||||
"settings@exclude":
|
||||
{
|
||||
"message": "Exclude symbols",
|
||||
"description": "Exclude character sets section"
|
||||
},
|
||||
"settings@similar":
|
||||
{
|
||||
"message": "Similar",
|
||||
"description": "Similar characters character set label in settings section"
|
||||
},
|
||||
"settings@ambiguous":
|
||||
{
|
||||
"message": "Ambiguous",
|
||||
"description": "Ambiguous characters character set label in settings section"
|
||||
},
|
||||
"settings@repeating":
|
||||
{
|
||||
"message": "Repeating",
|
||||
"description": "Repeating characters character set label in settings section"
|
||||
},
|
||||
"settings@repeating__hint":
|
||||
{
|
||||
"message": "Each character in the password will be unique",
|
||||
"description": "Repeating characters character set hint in settings section"
|
||||
},
|
||||
"generator@copy":
|
||||
{
|
||||
"message": "Copy",
|
||||
"description": "Copy button label"
|
||||
},
|
||||
"generator@reset":
|
||||
{
|
||||
"message": "Reset",
|
||||
"description": "Reset button label"
|
||||
},
|
||||
"generator@refresh":
|
||||
{
|
||||
"message": "Generate new",
|
||||
"description": "Generate new password button label"
|
||||
},
|
||||
"generator@quickOptions":
|
||||
{
|
||||
"message": "Quick adjustments",
|
||||
"description": "Quick options section title"
|
||||
},
|
||||
"generator@quickOptions__hint":
|
||||
{
|
||||
"message": "These adjustments will not be saved",
|
||||
"description": "Quick options hint"
|
||||
}
|
||||
}
|
||||
@@ -1,171 +0,0 @@
|
||||
{
|
||||
"name": {
|
||||
"message": "Generator haseł",
|
||||
"description": "manifest.json"
|
||||
},
|
||||
"description": {
|
||||
"message": "Rozszerzenie, które pozwala na łatwe generowanie trudnych i bezpiecznych haseł w jednym kliknięciu",
|
||||
"description": "manifest.json"
|
||||
},
|
||||
"author": {
|
||||
"message": "Jewgienij Lis",
|
||||
"description": "manifest.json"
|
||||
},
|
||||
"error@moreCharacters": {
|
||||
"message": "Minimalna długość hasła to 4 znaki",
|
||||
"description": "Error message when password length is less than 4 characters"
|
||||
},
|
||||
"error@noCharacters": {
|
||||
"message": "Musisz wybrać przynajmniej jeden rodzaj znaków",
|
||||
"description": "Error message when no character set is selected"
|
||||
},
|
||||
"error@tooLong": {
|
||||
"message": "Wybrana długość jest zbyt duża, aby wygenerować unikalne znaki",
|
||||
"description": "Error message when password length is too long to generate password with unique characters"
|
||||
},
|
||||
"about@title":
|
||||
{
|
||||
"message": "O rozszerzeniu",
|
||||
"description": "About section title"
|
||||
},
|
||||
"about@developedBy":
|
||||
{
|
||||
"message": "Autor: Jewgienij Lis",
|
||||
"description": "Developer credits in about section"
|
||||
},
|
||||
"about@licensedUnder":
|
||||
{
|
||||
"message": "",
|
||||
"description": "License info in about section"
|
||||
},
|
||||
"about@mitLicense":
|
||||
{
|
||||
"message": "Licencja MIT",
|
||||
"description": "License name"
|
||||
},
|
||||
"about@translationCta":
|
||||
{
|
||||
"message": "Znalazłeś błąd lub potrzebujesz tłumaczenia na swój język?",
|
||||
"description": "Translation CTA in about section"
|
||||
},
|
||||
"about@translationCtaButton":
|
||||
{
|
||||
"message": "Zacznij tutaj",
|
||||
"description": "Translation CTA button in about section"
|
||||
},
|
||||
"about@website":
|
||||
{
|
||||
"message": "Moja strona internetowa",
|
||||
"description": "Website link in about section"
|
||||
},
|
||||
"about@sourceCode":
|
||||
{
|
||||
"message": "Kod źródłowy",
|
||||
"description": "Source code link in about section"
|
||||
},
|
||||
"about@changelog":
|
||||
{
|
||||
"message": "Lista zmian",
|
||||
"description": "Changelog link in about section"
|
||||
},
|
||||
"about@feedback":
|
||||
{
|
||||
"message": "Zostaw opinię",
|
||||
"description": "Feedback link in about section"
|
||||
},
|
||||
"about@sponsor":
|
||||
{
|
||||
"message": "Wesprzyj",
|
||||
"description": "Buy me a coffee donation link in about section"
|
||||
},
|
||||
"settings@title":
|
||||
{
|
||||
"message": "Ustawienia",
|
||||
"description": "Settings section title"
|
||||
},
|
||||
"settings@length":
|
||||
{
|
||||
"message": "Długość hasła",
|
||||
"description": "Password length label in settings section"
|
||||
},
|
||||
"settings@length__hint":
|
||||
{
|
||||
"message": "Zalecana długość: 8—16",
|
||||
"description": "Password length recommendatin in settings section"
|
||||
},
|
||||
"settings@include":
|
||||
{
|
||||
"message": "Dodaj znaki",
|
||||
"description": "Include character sets section"
|
||||
},
|
||||
"settings@uppercase":
|
||||
{
|
||||
"message": "Wielkie",
|
||||
"description": "Uppercase character set label in settings section"
|
||||
},
|
||||
"settings@lowercase":
|
||||
{
|
||||
"message": "Małe",
|
||||
"description": "Lowercase character set label in settings section"
|
||||
},
|
||||
"settings@numeric":
|
||||
{
|
||||
"message": "Cyfry",
|
||||
"description": "Numbers character set label in settings section"
|
||||
},
|
||||
"settings@special":
|
||||
{
|
||||
"message": "Specjalne",
|
||||
"description": "Special characters character set label in settings section"
|
||||
},
|
||||
"settings@exclude":
|
||||
{
|
||||
"message": "Wyklucz znaki",
|
||||
"description": "Exclude character sets section"
|
||||
},
|
||||
"settings@similar":
|
||||
{
|
||||
"message": "Podobne",
|
||||
"description": "Similar characters character set label in settings section"
|
||||
},
|
||||
"settings@ambiguous":
|
||||
{
|
||||
"message": "Niebezpieczne",
|
||||
"description": "Ambiguous characters character set label in settings section"
|
||||
},
|
||||
"settings@repeating":
|
||||
{
|
||||
"message": "Powtarzające się",
|
||||
"description": "Repeating characters character set label in settings section"
|
||||
},
|
||||
"settings@repeating__hint":
|
||||
{
|
||||
"message": "Każdy znak hasła będzie unikalny",
|
||||
"description": "Repeating characters character set hint in settings section"
|
||||
},
|
||||
"generator@copy":
|
||||
{
|
||||
"message": "Kopiuj",
|
||||
"description": "Copy button label"
|
||||
},
|
||||
"generator@reset":
|
||||
{
|
||||
"message": "Resetuj",
|
||||
"description": "Reset button label"
|
||||
},
|
||||
"generator@refresh":
|
||||
{
|
||||
"message": "Wygeneruj nowe",
|
||||
"description": "Generate new password button label"
|
||||
},
|
||||
"generator@quickOptions":
|
||||
{
|
||||
"message": "Szybkie ustawienia",
|
||||
"description": "Quick options section title"
|
||||
},
|
||||
"generator@quickOptions__hint":
|
||||
{
|
||||
"message": "Te ustawienia nie zostaną zapisane",
|
||||
"description": "Quick options hint"
|
||||
}
|
||||
}
|
||||
@@ -1,171 +0,0 @@
|
||||
{
|
||||
"name": {
|
||||
"message": "Gerador de Senhas",
|
||||
"description": "Extension name"
|
||||
},
|
||||
"description": {
|
||||
"message": "A extensão do gerador de senhas permite gerar facilmente uma senha longa e segura em um clique",
|
||||
"description": "Extension description"
|
||||
},
|
||||
"author": {
|
||||
"message": "Eugênio Fox",
|
||||
"description": "Extension author"
|
||||
},
|
||||
"error@moreCharacters": {
|
||||
"message": "O comprimento da senha deve ter pelo menos 4 caracteres",
|
||||
"description": "Error message when password length is less than 4 characters"
|
||||
},
|
||||
"error@noCharacters": {
|
||||
"message": "Pelo menos um conjunto de caracteres deve ser selecionado",
|
||||
"description": "Error message when no character set is selected"
|
||||
},
|
||||
"error@tooLong": {
|
||||
"message": "O comprimento é muito longo para gerar uma senha com caracteres exclusivos",
|
||||
"description": "Error message when password length is too long to generate password with unique characters"
|
||||
},
|
||||
"about@title":
|
||||
{
|
||||
"message": "Sobre",
|
||||
"description": "About section title"
|
||||
},
|
||||
"about@developedBy":
|
||||
{
|
||||
"message": "Desenvolvido por Eugênio Fox",
|
||||
"description": "Developer credits in about section"
|
||||
},
|
||||
"about@licensedUnder":
|
||||
{
|
||||
"message": "Licenciado sob",
|
||||
"description": "License info in about section"
|
||||
},
|
||||
"about@mitLicense":
|
||||
{
|
||||
"message": "Licença MIT",
|
||||
"description": "License name"
|
||||
},
|
||||
"about@translationCta":
|
||||
{
|
||||
"message": "Encontrou um erro de digitação ou deseja uma tradução para o seu idioma?",
|
||||
"description": "Translation CTA in about section"
|
||||
},
|
||||
"about@translationCtaButton":
|
||||
{
|
||||
"message": "Comece aqui",
|
||||
"description": "Translation CTA button in about section"
|
||||
},
|
||||
"about@website":
|
||||
{
|
||||
"message": "Meu website",
|
||||
"description": "Website link in about section"
|
||||
},
|
||||
"about@sourceCode":
|
||||
{
|
||||
"message": "Código fonte",
|
||||
"description": "Source code link in about section"
|
||||
},
|
||||
"about@changelog":
|
||||
{
|
||||
"message": "Registro de alterações",
|
||||
"description": "Changelog link in about section"
|
||||
},
|
||||
"about@feedback":
|
||||
{
|
||||
"message": "Deixe um comentário",
|
||||
"description": "Feedback link in about section"
|
||||
},
|
||||
"about@sponsor":
|
||||
{
|
||||
"message": "Doar",
|
||||
"description": "Buy me a coffee donation link in about section"
|
||||
},
|
||||
"settings@title":
|
||||
{
|
||||
"message": "Configurações",
|
||||
"description": "Settings section title"
|
||||
},
|
||||
"settings@length":
|
||||
{
|
||||
"message": "Comprimento da senha",
|
||||
"description": "Password length label in settings section"
|
||||
},
|
||||
"settings@length__hint":
|
||||
{
|
||||
"message": "Comprimento recomendado: 8—16",
|
||||
"description": "Password length recommendatin in settings section"
|
||||
},
|
||||
"settings@include":
|
||||
{
|
||||
"message": "Incluir símbolos",
|
||||
"description": "Include character sets section"
|
||||
},
|
||||
"settings@uppercase":
|
||||
{
|
||||
"message": "Maiúsculos",
|
||||
"description": "Uppercase character set label in settings section"
|
||||
},
|
||||
"settings@lowercase":
|
||||
{
|
||||
"message": "Minúsculos",
|
||||
"description": "Lowercase character set label in settings section"
|
||||
},
|
||||
"settings@numeric":
|
||||
{
|
||||
"message": "Numéricos",
|
||||
"description": "Numbers character set label in settings section"
|
||||
},
|
||||
"settings@special":
|
||||
{
|
||||
"message": "Especiais",
|
||||
"description": "Special characters character set label in settings section"
|
||||
},
|
||||
"settings@exclude":
|
||||
{
|
||||
"message": "Excluir símbolos",
|
||||
"description": "Exclude character sets section"
|
||||
},
|
||||
"settings@similar":
|
||||
{
|
||||
"message": "Semelhantes",
|
||||
"description": "Similar characters character set label in settings section"
|
||||
},
|
||||
"settings@ambiguous":
|
||||
{
|
||||
"message": "Ambíguos",
|
||||
"description": "Ambiguous characters character set label in settings section"
|
||||
},
|
||||
"settings@repeating":
|
||||
{
|
||||
"message": "Repetidos",
|
||||
"description": "Repeating characters character set label in settings section"
|
||||
},
|
||||
"settings@repeating__hint":
|
||||
{
|
||||
"message": "Cada caractere na senha será único",
|
||||
"description": "Repeating characters character set hint in settings section"
|
||||
},
|
||||
"generator@copy":
|
||||
{
|
||||
"message": "Copiar",
|
||||
"description": "Copy button label"
|
||||
},
|
||||
"generator@reset":
|
||||
{
|
||||
"message": "Redefinir",
|
||||
"description": "Reset button label"
|
||||
},
|
||||
"generator@refresh":
|
||||
{
|
||||
"message": "Gerar nova",
|
||||
"description": "Generate new password button label"
|
||||
},
|
||||
"generator@quickOptions":
|
||||
{
|
||||
"message": "Ajustes rápidos",
|
||||
"description": "Quick options section title"
|
||||
},
|
||||
"generator@quickOptions__hint":
|
||||
{
|
||||
"message": "Esses ajustes não serão salvos",
|
||||
"description": "Quick options hint"
|
||||
}
|
||||
}
|
||||
@@ -1,171 +0,0 @@
|
||||
{
|
||||
"name": {
|
||||
"message": "Генератор паролей",
|
||||
"description": "Extension name"
|
||||
},
|
||||
"description": {
|
||||
"message": "Расширение, позволяющее легко генерировать сложные и надежные пароли в один клик",
|
||||
"description": "Extension description"
|
||||
},
|
||||
"author": {
|
||||
"message": "Евгений Лис",
|
||||
"description": "Extension author"
|
||||
},
|
||||
"error@moreCharacters": {
|
||||
"message": "Минимальная длина пароля — 4 символа",
|
||||
"description": "Error message when password length is less than 4 characters"
|
||||
},
|
||||
"error@noCharacters": {
|
||||
"message": "Необходимо выбрать хотя бы один вид символов",
|
||||
"description": "Error message when no character set is selected"
|
||||
},
|
||||
"error@tooLong": {
|
||||
"message": "Выбранная длина слишком большая, для генерации уникальных символов",
|
||||
"description": "Error message when password length is too long to generate password with unique characters"
|
||||
},
|
||||
"about@title":
|
||||
{
|
||||
"message": "О расширении",
|
||||
"description": "About section title"
|
||||
},
|
||||
"about@developedBy":
|
||||
{
|
||||
"message": "Разработчк: Евгений Лис",
|
||||
"description": "Developer credits in about section"
|
||||
},
|
||||
"about@licensedUnder":
|
||||
{
|
||||
"message": "",
|
||||
"description": "License info in about section"
|
||||
},
|
||||
"about@mitLicense":
|
||||
{
|
||||
"message": "Лицензия MIT",
|
||||
"description": "License name"
|
||||
},
|
||||
"about@translationCta":
|
||||
{
|
||||
"message": "Нашли опечатку или хотите перевод на ваш язык?",
|
||||
"description": "Translation CTA in about section"
|
||||
},
|
||||
"about@translationCtaButton":
|
||||
{
|
||||
"message": "Начните здесь",
|
||||
"description": "Translation CTA button in about section"
|
||||
},
|
||||
"about@website":
|
||||
{
|
||||
"message": "Мой веб-сайт",
|
||||
"description": "Website link in about section"
|
||||
},
|
||||
"about@sourceCode":
|
||||
{
|
||||
"message": "Исходный код",
|
||||
"description": "Source code link in about section"
|
||||
},
|
||||
"about@changelog":
|
||||
{
|
||||
"message": "Список изменений",
|
||||
"description": "Changelog link in about section"
|
||||
},
|
||||
"about@feedback":
|
||||
{
|
||||
"message": "Оставить отзыв",
|
||||
"description": "Feedback link in about section"
|
||||
},
|
||||
"about@sponsor":
|
||||
{
|
||||
"message": "Поддержать",
|
||||
"description": "Buy me a coffee donation link in about section"
|
||||
},
|
||||
"settings@title":
|
||||
{
|
||||
"message": "Настройки",
|
||||
"description": "Settings section title"
|
||||
},
|
||||
"settings@length":
|
||||
{
|
||||
"message": "Длина пароля",
|
||||
"description": "Password length label in settings section"
|
||||
},
|
||||
"settings@length__hint":
|
||||
{
|
||||
"message": "Рекомендованная длина: 8—16",
|
||||
"description": "Password length recommendatin in settings section"
|
||||
},
|
||||
"settings@include":
|
||||
{
|
||||
"message": "Добавить символы",
|
||||
"description": "Include character sets section"
|
||||
},
|
||||
"settings@uppercase":
|
||||
{
|
||||
"message": "Прописные",
|
||||
"description": "Uppercase character set label in settings section"
|
||||
},
|
||||
"settings@lowercase":
|
||||
{
|
||||
"message": "Строчные",
|
||||
"description": "Lowercase character set label in settings section"
|
||||
},
|
||||
"settings@numeric":
|
||||
{
|
||||
"message": "Числовые",
|
||||
"description": "Numbers character set label in settings section"
|
||||
},
|
||||
"settings@special":
|
||||
{
|
||||
"message": "Специальные",
|
||||
"description": "Special characters character set label in settings section"
|
||||
},
|
||||
"settings@exclude":
|
||||
{
|
||||
"message": "Исключить символы",
|
||||
"description": "Exclude character sets section"
|
||||
},
|
||||
"settings@similar":
|
||||
{
|
||||
"message": "Похожие",
|
||||
"description": "Similar characters character set label in settings section"
|
||||
},
|
||||
"settings@ambiguous":
|
||||
{
|
||||
"message": "Особые",
|
||||
"description": "Ambiguous characters character set label in settings section"
|
||||
},
|
||||
"settings@repeating":
|
||||
{
|
||||
"message": "Повторяющиеся",
|
||||
"description": "Repeating characters character set label in settings section"
|
||||
},
|
||||
"settings@repeating__hint":
|
||||
{
|
||||
"message": "Каждый символ пароля будет уникальным",
|
||||
"description": "Repeating characters character set hint in settings section"
|
||||
},
|
||||
"generator@copy":
|
||||
{
|
||||
"message": "Копировать",
|
||||
"description": "Copy button label"
|
||||
},
|
||||
"generator@reset":
|
||||
{
|
||||
"message": "Сбросить",
|
||||
"description": "Reset button label"
|
||||
},
|
||||
"generator@refresh":
|
||||
{
|
||||
"message": "Сгенерировать новый",
|
||||
"description": "Generate new password button label"
|
||||
},
|
||||
"generator@quickOptions":
|
||||
{
|
||||
"message": "Быстрые настройки",
|
||||
"description": "Quick options section title"
|
||||
},
|
||||
"generator@quickOptions__hint":
|
||||
{
|
||||
"message": "Эти настройки не будут сохранены",
|
||||
"description": "Quick options hint"
|
||||
}
|
||||
}
|
||||
@@ -1,171 +0,0 @@
|
||||
{
|
||||
"name": {
|
||||
"message": "Генератор паролів",
|
||||
"description": "Extension name"
|
||||
},
|
||||
"description": {
|
||||
"message": "Розширення, яке дозволяє легко генерувати складні та надійні паролі в один клік",
|
||||
"description": "Extension description"
|
||||
},
|
||||
"author": {
|
||||
"message": "Євген Лис",
|
||||
"description": "Extension author"
|
||||
},
|
||||
"error@moreCharacters": {
|
||||
"message": "Мінімальна довжина пароля — 4 символи",
|
||||
"description": "Error message when password length is less than 4 characters"
|
||||
},
|
||||
"error@noCharacters": {
|
||||
"message": "Необхідно вибрати хоча б один вид символів",
|
||||
"description": "Error message when no character set is selected"
|
||||
},
|
||||
"error@tooLong": {
|
||||
"message": "Вибрана довжина занадто велика, для генерації унікальних символів",
|
||||
"description": "Error message when password length is too long to generate password with unique characters"
|
||||
},
|
||||
"about@title":
|
||||
{
|
||||
"message": "Про розширення",
|
||||
"description": "About section title"
|
||||
},
|
||||
"about@developedBy":
|
||||
{
|
||||
"message": "Розробник: Євген Лис",
|
||||
"description": "Developer credits in about section"
|
||||
},
|
||||
"about@licensedUnder":
|
||||
{
|
||||
"message": "",
|
||||
"description": "License info in about section"
|
||||
},
|
||||
"about@mitLicense":
|
||||
{
|
||||
"message": "Ліцензія MIT",
|
||||
"description": "License name"
|
||||
},
|
||||
"about@translationCta":
|
||||
{
|
||||
"message": "Знайшли помилку або хочете переклад на вашу мову?",
|
||||
"description": "Translation CTA in about section"
|
||||
},
|
||||
"about@translationCtaButton":
|
||||
{
|
||||
"message": "Почніть тут",
|
||||
"description": "Translation CTA button in about section"
|
||||
},
|
||||
"about@website":
|
||||
{
|
||||
"message": "Мій веб-сайт",
|
||||
"description": "Website link in about section"
|
||||
},
|
||||
"about@sourceCode":
|
||||
{
|
||||
"message": "Вихідний код",
|
||||
"description": "Source code link in about section"
|
||||
},
|
||||
"about@changelog":
|
||||
{
|
||||
"message": "Список змін",
|
||||
"description": "Changelog link in about section"
|
||||
},
|
||||
"about@feedback":
|
||||
{
|
||||
"message": "Залишити відгук",
|
||||
"description": "Feedback link in about section"
|
||||
},
|
||||
"about@sponsor":
|
||||
{
|
||||
"message": "Підтримати",
|
||||
"description": "Buy me a coffee donation link in about section"
|
||||
},
|
||||
"settings@title":
|
||||
{
|
||||
"message": "Налаштування",
|
||||
"description": "Settings section title"
|
||||
},
|
||||
"settings@length":
|
||||
{
|
||||
"message": "Довжина пароля",
|
||||
"description": "Password length label in settings section"
|
||||
},
|
||||
"settings@length__hint":
|
||||
{
|
||||
"message": "Рекомендована довжина: 8—16",
|
||||
"description": "Password length recommendatin in settings section"
|
||||
},
|
||||
"settings@include":
|
||||
{
|
||||
"message": "Додати символи",
|
||||
"description": "Include character sets section"
|
||||
},
|
||||
"settings@uppercase":
|
||||
{
|
||||
"message": "Великі",
|
||||
"description": "Uppercase character set label in settings section"
|
||||
},
|
||||
"settings@lowercase":
|
||||
{
|
||||
"message": "Малі",
|
||||
"description": "Lowercase character set label in settings section"
|
||||
},
|
||||
"settings@numeric":
|
||||
{
|
||||
"message": "Числові",
|
||||
"description": "Numbers character set label in settings section"
|
||||
},
|
||||
"settings@special":
|
||||
{
|
||||
"message": "Спеціальні",
|
||||
"description": "Special characters character set label in settings section"
|
||||
},
|
||||
"settings@exclude":
|
||||
{
|
||||
"message": "Виключити символи",
|
||||
"description": "Exclude character sets section"
|
||||
},
|
||||
"settings@similar":
|
||||
{
|
||||
"message": "Схожі",
|
||||
"description": "Similar characters character set label in settings section"
|
||||
},
|
||||
"settings@ambiguous":
|
||||
{
|
||||
"message": "Особливі",
|
||||
"description": "Ambiguous characters character set label in settings section"
|
||||
},
|
||||
"settings@repeating":
|
||||
{
|
||||
"message": "Повторювані",
|
||||
"description": "Repeating characters character set label in settings section"
|
||||
},
|
||||
"settings@repeating__hint":
|
||||
{
|
||||
"message": "Кожен символ пароля буде унікальним",
|
||||
"description": "Repeating characters character set hint in settings section"
|
||||
},
|
||||
"generator@copy":
|
||||
{
|
||||
"message": "Копіювати",
|
||||
"description": "Copy button label"
|
||||
},
|
||||
"generator@reset":
|
||||
{
|
||||
"message": "Скинути",
|
||||
"description": "Reset button label"
|
||||
},
|
||||
"generator@refresh":
|
||||
{
|
||||
"message": "Згенерувати новий",
|
||||
"description": "Generate new password button label"
|
||||
},
|
||||
"generator@quickOptions":
|
||||
{
|
||||
"message": "Швидкі налаштування",
|
||||
"description": "Quick options section title"
|
||||
},
|
||||
"generator@quickOptions__hint":
|
||||
{
|
||||
"message": "Ці налаштування не будуть збережені",
|
||||
"description": "Quick options hint"
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export default class ExtensionOptions { }
|
||||
@@ -1,55 +0,0 @@
|
||||
.snow
|
||||
{
|
||||
position: absolute;
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.snowflake
|
||||
{
|
||||
--size: 1px;
|
||||
width: var(--size);
|
||||
height: var(--size);
|
||||
background: white;
|
||||
border-radius: 50%;
|
||||
position: absolute;
|
||||
top: -5px;
|
||||
|
||||
@media (prefers-color-scheme: light)
|
||||
{
|
||||
background: var(--colorPalettePlatinumBorderActive);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes snowfall
|
||||
{
|
||||
0%
|
||||
{
|
||||
transform: translate3d(var(--left-ini), 0, 0);
|
||||
opacity: .6;
|
||||
}
|
||||
|
||||
100%
|
||||
{
|
||||
transform: translate3d(var(--left-end), 610px, 0);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@for $i from 1 through 50
|
||||
{
|
||||
.snowflake:nth-child(#{$i})
|
||||
{
|
||||
--size: #{random(5)}px;
|
||||
--left-ini: #{random(20) - 10}vw;
|
||||
--left-end: #{random(20) - 10}vw;
|
||||
left: #{random(100)}vw;
|
||||
animation: snowfall #{5 + random(10)}s linear infinite;
|
||||
animation-delay: -#{random(10)}s;
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
import "./Snow.scss";
|
||||
|
||||
const Snow = (): JSX.Element => (
|
||||
![0, 11].includes(new Date().getMonth()) ? <></> : // Only shows in December and January
|
||||
|
||||
<div className="snow">
|
||||
{ [...Array(50)].map((_, i) => <div key={ i } className="snowflake" />) }
|
||||
</div>
|
||||
);
|
||||
|
||||
export default Snow;
|
||||
@@ -1,7 +0,0 @@
|
||||
import Snow from "./Snow";
|
||||
|
||||
const Specials = (): JSX.Element => <>
|
||||
<Snow />
|
||||
</>;
|
||||
|
||||
export default Specials;
|
||||
@@ -1,11 +0,0 @@
|
||||
import browser from "webextension-polyfill";
|
||||
import messages from "../Data/Locales/en/messages.json";
|
||||
|
||||
/**
|
||||
* Gets the localized string for the specified message. If the message is missing, this method returns an empty string (''). If the format of the call is wrong — for example, messageName is not a string or the substitutions array has more than 9 elements — this method returns .
|
||||
* @param key The name of the message, as specified in the file.
|
||||
* @param subustitutions Optional. Substitution strings, if the message requires any.
|
||||
* @returns Message localized for current locale.
|
||||
*/
|
||||
export const GetLocaleString = (key: keyof typeof messages, ...subustitutions: string[]): string =>
|
||||
browser.i18n.getMessage(key, subustitutions) ?? key;
|
||||
@@ -1,32 +0,0 @@
|
||||
{
|
||||
"manifest_version": 3,
|
||||
|
||||
"name": "__MSG_name__",
|
||||
"description": "__MSG_description__",
|
||||
"author": "__MSG_author__",
|
||||
"homepage_url": "https://github.com/xfox111/PasswordGeneratorExtension",
|
||||
|
||||
"default_locale": "en",
|
||||
|
||||
"icons": {
|
||||
"16": "icons/icon-16.png",
|
||||
"32": "icons/icon-32.png",
|
||||
"48": "icons/icon-48.png",
|
||||
"128": "icons/icon-128.png"
|
||||
},
|
||||
|
||||
"permissions": [
|
||||
"storage"
|
||||
],
|
||||
|
||||
"action": {
|
||||
"default_popup": "src/popup.html"
|
||||
},
|
||||
|
||||
"{{firefox}}.browser_specific_settings": {
|
||||
"gecko": {
|
||||
"id": "passwordgenerator@xfox111.net",
|
||||
"strict_min_version": "109.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
html,
|
||||
body
|
||||
{
|
||||
width: 400px;
|
||||
min-width: 400px;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
-moz-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6,
|
||||
p, ul, ol, li
|
||||
{
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@keyframes scaleUpIn
|
||||
{
|
||||
from
|
||||
{
|
||||
transform: scale(0.5);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
to
|
||||
{
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spin
|
||||
{
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
@keyframes fadeIn
|
||||
{
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
/// <reference types="vite/client" />
|
||||
/// <reference types="vite-plugin-svgr/client" />
|
||||
|
||||
type SupportedBrowser = "chrome" | "firefox";
|
||||
declare const __BROWSER__: SupportedBrowser;
|
||||
@@ -1,36 +1,7 @@
|
||||
{
|
||||
"extends": "./.wxt/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": [
|
||||
"DOM",
|
||||
"DOM.Iterable",
|
||||
"ESNext"
|
||||
],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "Node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"allowJs": false,
|
||||
"esModuleInterop": false,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.node.json"
|
||||
}
|
||||
]
|
||||
"allowImportingTsExtensions": true,
|
||||
"jsx": "react-jsx"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Node",
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"include": [
|
||||
"vite.config.ts"
|
||||
]
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
import GeneratorOptions from "../Models/GeneratorOptions";
|
||||
import { GetLocaleString as loc } from "./Localization";
|
||||
import { GeneratorOptions } from "./storage";
|
||||
|
||||
const Characters =
|
||||
{
|
||||
@@ -49,15 +48,15 @@ export function GeneratePassword(options: GeneratorOptions): string
|
||||
export function ValidateOptions(options: GeneratorOptions): void
|
||||
{
|
||||
if (options.Length < 4)
|
||||
throw new Error(loc("error@moreCharacters"));
|
||||
throw new Error(i18n.t("errors.too_short"));
|
||||
|
||||
const availableCharacters: string = GetAvailableCharacters(options);
|
||||
|
||||
if (availableCharacters.length < 1)
|
||||
throw new Error(loc("error@noCharacters"));
|
||||
throw new Error(i18n.t("errors.no_characters"));
|
||||
|
||||
if (options.ExcludeRepeating && options.Length > availableCharacters.length)
|
||||
throw new Error(loc("error@tooLong"));
|
||||
throw new Error(i18n.t("errors.too_long"));
|
||||
}
|
||||
|
||||
// Returns a string containing all characters that are available for password generation
|
||||
@@ -0,0 +1,42 @@
|
||||
import { Manifest } from "webextension-polyfill";
|
||||
|
||||
export const personalLinks =
|
||||
{
|
||||
website: "https://xfox111.net",
|
||||
twitter: "https://twitter.com/xfox111",
|
||||
donation: "https://buymeacoffee.com/xfox111"
|
||||
};
|
||||
|
||||
export const storeLinks =
|
||||
{
|
||||
chrome: "https://chrome.google.com/webstore/detail/password-generator/jnjobgjobffgmgfnkpkjfjkkfhfikmfl",
|
||||
edge: "https://microsoftedge.microsoft.com/addons/detail/password-generator/manimdhobjbkfpeeehlhhneookiokpbj",
|
||||
firefox: "https://addons.mozilla.org/firefox/addon/easy-password-generator"
|
||||
};
|
||||
|
||||
const getGithub = (path?: string): string =>
|
||||
new URL(path ?? "", "https://github.com/xfox111/PasswordGeneratorExtension/").href;
|
||||
|
||||
export const githubLinks =
|
||||
{
|
||||
repository: getGithub(),
|
||||
changelog: getGithub("releases/latest"),
|
||||
translationGuide: getGithub("wiki/Contribution-Guidelines#contributing-to-translations"),
|
||||
license: getGithub("blob/main/LICENSE")
|
||||
};
|
||||
|
||||
export const getFeedbackLink = () =>
|
||||
{
|
||||
if (import.meta.env.FIREFOX)
|
||||
return storeLinks.firefox;
|
||||
|
||||
const manifest: Manifest.WebExtensionManifest = browser.runtime.getManifest();
|
||||
const updateUrl: URL = new URL((manifest as unknown as Record<string, unknown>).update_url as string ?? "about:blank");
|
||||
|
||||
if (updateUrl.host === "edge.microsoft.com")
|
||||
return storeLinks.edge;
|
||||
if (updateUrl.host === "clients2.google.com")
|
||||
return storeLinks.chrome;
|
||||
|
||||
return "mailto:feedback@xfox111.net";
|
||||
};
|
||||
@@ -0,0 +1,6 @@
|
||||
export default class ExtensionOptions
|
||||
{
|
||||
public MinLength: number = 4;
|
||||
public MaxLength: number = 32;
|
||||
public ContextMenu: boolean = true;
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
import React, { createContext, useContext, useEffect, useState } from "react";
|
||||
import browser from "webextension-polyfill";
|
||||
import ExtensionOptions from "../Models/ExtensionOptions";
|
||||
import GeneratorOptions from "../Models/GeneratorOptions";
|
||||
import ExtensionOptions from "./ExtensionOptions";
|
||||
import GeneratorOptions from "./GeneratorOptions";
|
||||
|
||||
const defaultOptions: IStorage =
|
||||
{
|
||||
@@ -14,7 +13,7 @@ const Storage = createContext<IStorage>(defaultOptions);
|
||||
|
||||
export const useStorage = () => useContext<IStorage>(Storage);
|
||||
|
||||
export function StorageProvider(props: IStorageProviderProps): JSX.Element
|
||||
export const StorageProvider: React.FC<IStorageProviderProps> = (props) =>
|
||||
{
|
||||
const [storage, setStorage] = useState<IStorage | null>(null);
|
||||
|
||||
@@ -37,7 +36,7 @@ export function StorageProvider(props: IStorageProviderProps): JSX.Element
|
||||
{ storage ? props.children : props.loader }
|
||||
</Storage.Provider>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// #region Types
|
||||
interface IStorage
|
||||
@@ -0,0 +1,5 @@
|
||||
import ExtensionOptions from "./ExtensionOptions";
|
||||
import GeneratorOptions from "./GeneratorOptions";
|
||||
|
||||
export { useStorage, StorageProvider } from "./Storage";
|
||||
export { ExtensionOptions, GeneratorOptions };
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useState } from "react";
|
||||
|
||||
export function useTimeout(timeout: number)
|
||||
export default function useTimeout(timeout: number)
|
||||
{
|
||||
const [isActive, setActive] = useState<boolean>(false);
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
import { defineConfig } from "vite";
|
||||
import react from "@vitejs/plugin-react-swc";
|
||||
import webExtension, { readJsonFile } from "vite-plugin-web-extension";
|
||||
import path from "node:path";
|
||||
import svgr from "vite-plugin-svgr";
|
||||
import { viteStaticCopy } from "vite-plugin-static-copy";
|
||||
|
||||
const target: string = process.env.TARGET || "chrome";
|
||||
|
||||
function generateManifest()
|
||||
{
|
||||
const manifest = readJsonFile("src/manifest.json");
|
||||
const pkg = readJsonFile("package.json");
|
||||
return {
|
||||
version: pkg.version,
|
||||
...manifest,
|
||||
};
|
||||
}
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
define:
|
||||
{
|
||||
__BROWSER__: JSON.stringify(target)
|
||||
},
|
||||
plugins:
|
||||
[
|
||||
react(),
|
||||
svgr(),
|
||||
viteStaticCopy({
|
||||
targets: [
|
||||
{
|
||||
src: "src/Data/Locales",
|
||||
dest: ".",
|
||||
rename: "_locales"
|
||||
}
|
||||
]
|
||||
}),
|
||||
webExtension({
|
||||
manifest: generateManifest,
|
||||
browser: target,
|
||||
/* webExtConfig: {
|
||||
args: target === "firefox" ? [ ] : [ "--no-sandbox" ]
|
||||
} */
|
||||
}),
|
||||
],
|
||||
resolve:
|
||||
{
|
||||
alias:
|
||||
{
|
||||
// In dev mode, make sure fast refresh works
|
||||
"/@react-refresh": path.resolve(
|
||||
"node_modules/@vitejs/plugin-react-swc/refresh-runtime.js"
|
||||
),
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,42 @@
|
||||
import { defineConfig, WxtViteConfig } from "wxt";
|
||||
|
||||
// See https://wxt.dev/api/config.html
|
||||
export default defineConfig({
|
||||
modules: ["@wxt-dev/module-react", "@wxt-dev/i18n/module"],
|
||||
vite: (): WxtViteConfig => ({
|
||||
build:
|
||||
{
|
||||
chunkSizeWarningLimit: 1000
|
||||
}
|
||||
}),
|
||||
imports: {
|
||||
eslintrc: {
|
||||
enabled: 9
|
||||
},
|
||||
},
|
||||
manifest: ({ browser }) => ({
|
||||
name: "__MSG_manifest_name__",
|
||||
description: "__MSG_manifest_description__",
|
||||
author: "__MSG_manifest_author__",
|
||||
homepage_url: "https://github.com/xfox111/PasswordGeneratorExtension",
|
||||
|
||||
default_locale: "en",
|
||||
permissions: ["storage", "contextMenus", "activeTab"],
|
||||
|
||||
icons:
|
||||
{
|
||||
16: "/icons/16.png",
|
||||
32: "/icons/32.png",
|
||||
48: "/icons/48.png",
|
||||
128: "/icons/128.png",
|
||||
1024: "/icons/1024.png"
|
||||
},
|
||||
|
||||
browser_specific_settings: browser !== "firefox" ? undefined : ({
|
||||
gecko: {
|
||||
id: "passwordgenerator@xfox111.net",
|
||||
strict_min_version: "109.0"
|
||||
}
|
||||
})
|
||||
})
|
||||
});
|
||||