Major 4.0 (#380)
- Migrated to WXT - Migrated to NPM - Added Insert & copy action - Added ESLint
@@ -21,5 +21,5 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
"postCreateCommand": "yarn install"
|
"postCreateCommand": "npm install"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,57 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
"settings": {
|
|
||||||
"react": {
|
|
||||||
"version": "detect",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"env": {
|
|
||||||
"browser": true,
|
|
||||||
"es2021": true
|
|
||||||
},
|
|
||||||
"extends": [
|
|
||||||
"eslint:recommended",
|
|
||||||
"plugin:@typescript-eslint/recommended",
|
|
||||||
"plugin:react/recommended"
|
|
||||||
],
|
|
||||||
"overrides": [
|
|
||||||
{
|
|
||||||
"env": {
|
|
||||||
"node": true
|
|
||||||
},
|
|
||||||
"files": [
|
|
||||||
".eslintrc.{js,cjs}"
|
|
||||||
],
|
|
||||||
"parserOptions": {
|
|
||||||
"sourceType": "script"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"parser": "@typescript-eslint/parser",
|
|
||||||
"parserOptions": {
|
|
||||||
"ecmaVersion": "latest",
|
|
||||||
"sourceType": "module"
|
|
||||||
},
|
|
||||||
"plugins": [
|
|
||||||
"@typescript-eslint",
|
|
||||||
"react"
|
|
||||||
],
|
|
||||||
"rules": {
|
|
||||||
"indent": [
|
|
||||||
"error",
|
|
||||||
"tab"
|
|
||||||
],
|
|
||||||
"linebreak-style": [
|
|
||||||
"error",
|
|
||||||
"unix"
|
|
||||||
],
|
|
||||||
"quotes": [
|
|
||||||
"error",
|
|
||||||
"double"
|
|
||||||
],
|
|
||||||
"semi": [
|
|
||||||
"error",
|
|
||||||
"always"
|
|
||||||
],
|
|
||||||
"react/react-in-jsx-scope": "off"
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
# yaml-language-server: $schema=https://json.schemastore.org/github-issue-config.json
|
||||||
|
|
||||||
blank_issues_enabled: true
|
blank_issues_enabled: true
|
||||||
contact_links:
|
contact_links:
|
||||||
- name: Questions & Discussions
|
- name: Questions & Discussions
|
||||||
|
|||||||
@@ -31,3 +31,15 @@ updates:
|
|||||||
interval: monthly
|
interval: monthly
|
||||||
rebase-strategy: disabled
|
rebase-strategy: disabled
|
||||||
open-pull-requests-limit: 20
|
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
|
||||||
|
|||||||
@@ -43,34 +43,24 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@main
|
- uses: actions/checkout@main
|
||||||
|
|
||||||
- run: yarn install
|
- run: npm install
|
||||||
- run: yarn lint
|
- run: npm run zip -- -b ${{ matrix.target }}
|
||||||
- run: TARGET=${{ matrix.target }} yarn build
|
- run: npm audit
|
||||||
|
|
||||||
- name: Drop build artifacts (${{ matrix.target }})
|
- name: Drop build artifacts (${{ matrix.target }})
|
||||||
uses: actions/upload-artifact@main
|
uses: actions/upload-artifact@main
|
||||||
with:
|
with:
|
||||||
name: ${{ matrix.target }}
|
name: ${{ matrix.target }}
|
||||||
path: dist
|
path: ./.output/password-generator-*-${{ matrix.target }}.zip
|
||||||
|
include-hidden-files: true
|
||||||
|
|
||||||
- name: web-ext lint
|
- name: web-ext lint
|
||||||
if: ${{ matrix.target == 'firefox' }}
|
if: ${{ matrix.target == 'firefox' }}
|
||||||
uses: freaktechnik/web-ext-lint@main
|
uses: freaktechnik/web-ext-lint@main
|
||||||
with:
|
with:
|
||||||
extension-root: dist
|
extension-root: ./.output/firefox-mv3
|
||||||
self-hosted: false
|
self-hosted: false
|
||||||
|
|
||||||
- uses: cardinalby/webext-buildtools-pack-extension-dir-action@1.0.9
|
|
||||||
with:
|
|
||||||
extensionDir: dist
|
|
||||||
zipFilePath: PasswordGenerator-${{ matrix.target }}.zip
|
|
||||||
|
|
||||||
- name: Drop packed artifacts (${{ matrix.target }})
|
|
||||||
uses: actions/upload-artifact@main
|
|
||||||
with:
|
|
||||||
name: packed-${{ matrix.target }}
|
|
||||||
path: PasswordGenerator-${{ matrix.target }}.zip
|
|
||||||
|
|
||||||
publish-github:
|
publish-github:
|
||||||
needs: build
|
needs: build
|
||||||
if: ${{ github.event_name == 'release' || github.event.inputs.gh-release == 'true' }}
|
if: ${{ github.event_name == 'release' || github.event.inputs.gh-release == 'true' }}
|
||||||
@@ -83,14 +73,14 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/download-artifact@main
|
- uses: actions/download-artifact@main
|
||||||
with:
|
with:
|
||||||
name: packed-${{ matrix.target }}
|
name: ${{ matrix.target }}
|
||||||
|
|
||||||
- name: Attach build to release
|
- name: Attach build to release
|
||||||
uses: xresloader/upload-to-github-release@v1
|
uses: xresloader/upload-to-github-release@v1
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
with:
|
with:
|
||||||
file: PasswordGenerator-${{ matrix.target }}.zip
|
file: password-generator-*-${{ matrix.target }}.zip
|
||||||
draft: false
|
draft: false
|
||||||
overwrite: true
|
overwrite: true
|
||||||
update_latest_release: true
|
update_latest_release: true
|
||||||
@@ -103,12 +93,12 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/download-artifact@main
|
- uses: actions/download-artifact@main
|
||||||
with:
|
with:
|
||||||
name: packed-chrome
|
name: chrome
|
||||||
|
|
||||||
- uses: wdzeng/chrome-extension@v1.2.4
|
- uses: wdzeng/chrome-extension@v1.2.4
|
||||||
with:
|
with:
|
||||||
extension-id: jnjobgjobffgmgfnkpkjfjkkfhfikmfl
|
extension-id: jnjobgjobffgmgfnkpkjfjkkfhfikmfl
|
||||||
zip-path: PasswordGenerator-chrome.zip
|
zip-path: password-generator-*-chrome.zip
|
||||||
client-id: ${{ secrets.CHROME_CLIENT_ID }}
|
client-id: ${{ secrets.CHROME_CLIENT_ID }}
|
||||||
client-secret: ${{ secrets.CHROME_CLIENT_SECRET }}
|
client-secret: ${{ secrets.CHROME_CLIENT_SECRET }}
|
||||||
refresh-token: ${{ secrets.CHROME_REFRESH_TOKEN }}
|
refresh-token: ${{ secrets.CHROME_REFRESH_TOKEN }}
|
||||||
@@ -121,12 +111,12 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/download-artifact@main
|
- uses: actions/download-artifact@main
|
||||||
with:
|
with:
|
||||||
name: packed-chrome
|
name: chrome
|
||||||
|
|
||||||
- uses: wdzeng/edge-addon@v1.2.4
|
- uses: wdzeng/edge-addon@v1.2.4
|
||||||
with:
|
with:
|
||||||
product-id: ${{ secrets.EDGE_PRODUCT_ID }}
|
product-id: ${{ secrets.EDGE_PRODUCT_ID }}
|
||||||
zip-path: PasswordGenerator-chrome.zip
|
zip-path: password-generator-*-chrome.zip
|
||||||
client-id: ${{ secrets.EDGE_CLIENT_ID }}
|
client-id: ${{ secrets.EDGE_CLIENT_ID }}
|
||||||
client-secret: ${{ secrets.EDGE_CLIENT_SECRET }}
|
client-secret: ${{ secrets.EDGE_CLIENT_SECRET }}
|
||||||
access-token-url: ${{ secrets.EDGE_ACCESS_TOKEN_URL }}
|
access-token-url: ${{ secrets.EDGE_ACCESS_TOKEN_URL }}
|
||||||
@@ -139,11 +129,11 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/download-artifact@main
|
- uses: actions/download-artifact@main
|
||||||
with:
|
with:
|
||||||
name: packed-firefox
|
name: firefox
|
||||||
|
|
||||||
- uses: wdzeng/firefox-addon@v1.0.5
|
- uses: wdzeng/firefox-addon@v1.0.5
|
||||||
with:
|
with:
|
||||||
addon-guid: ${{ secrets.FIREFOX_EXT_UUID }}
|
addon-guid: ${{ secrets.FIREFOX_EXT_UUID }}
|
||||||
xpi-path: PasswordGenerator-firefox.zip
|
xpi-path: password-generator-*-firefox.zip
|
||||||
jwt-issuer: ${{ secrets.FIREFOX_API_KEY }}
|
jwt-issuer: ${{ secrets.FIREFOX_API_KEY }}
|
||||||
jwt-secret: ${{ secrets.FIREFOX_CLIENT_SECRET }}
|
jwt-secret: ${{ secrets.FIREFOX_CLIENT_SECRET }}
|
||||||
|
|||||||
@@ -37,19 +37,20 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@main
|
- uses: actions/checkout@main
|
||||||
|
|
||||||
- run: yarn install
|
- run: npm install
|
||||||
- run: yarn lint
|
- run: npm run zip -- -b ${{ matrix.target }}
|
||||||
- run: TARGET=${{ matrix.target }} yarn build
|
- run: npm audit
|
||||||
|
|
||||||
- name: Drop artifacts (${{ matrix.target }})
|
- name: Drop artifacts (${{ matrix.target }})
|
||||||
uses: actions/upload-artifact@main
|
uses: actions/upload-artifact@main
|
||||||
with:
|
with:
|
||||||
name: ${{ matrix.target }}
|
name: ${{ matrix.target }}
|
||||||
path: dist
|
path: ./.output/password-generator-*-${{ matrix.target }}.zip
|
||||||
|
include-hidden-files: true
|
||||||
|
|
||||||
- name: web-ext lint
|
- name: web-ext lint
|
||||||
if: ${{ matrix.target == 'firefox' }}
|
if: ${{ matrix.target == 'firefox' }}
|
||||||
uses: freaktechnik/web-ext-lint@main
|
uses: freaktechnik/web-ext-lint@main
|
||||||
with:
|
with:
|
||||||
extension-root: dist
|
extension-root: ./.output/firefox-mv3
|
||||||
self-hosted: false
|
self-hosted: false
|
||||||
|
|||||||
@@ -8,9 +8,11 @@ pnpm-debug.log*
|
|||||||
lerna-debug.log*
|
lerna-debug.log*
|
||||||
|
|
||||||
node_modules
|
node_modules
|
||||||
dist
|
.output
|
||||||
dist-ssr
|
stats.html
|
||||||
*.local
|
stats-*.json
|
||||||
|
.wxt
|
||||||
|
web-ext.config.ts
|
||||||
|
|
||||||
# Editor directories and files
|
# Editor directories and files
|
||||||
.idea
|
.idea
|
||||||
@@ -20,7 +22,3 @@ dist-ssr
|
|||||||
*.njsproj
|
*.njsproj
|
||||||
*.sln
|
*.sln
|
||||||
*.sw?
|
*.sw?
|
||||||
|
|
||||||
# Config files
|
|
||||||
.webextrc
|
|
||||||
.webextrc.*
|
|
||||||
|
|||||||
@@ -1,15 +1,11 @@
|
|||||||
{
|
{
|
||||||
"recommendations": [
|
"recommendations": [
|
||||||
"DNEK.auto-region-folder",
|
"bierner.github-markdown-preview",
|
||||||
"eamodio.gitlens",
|
|
||||||
"jock.svg",
|
|
||||||
"firefox-devtools.vscode-firefox-debug",
|
|
||||||
"dbaeumer.vscode-eslint",
|
"dbaeumer.vscode-eslint",
|
||||||
"github.vscode-github-actions",
|
"github.vscode-github-actions",
|
||||||
"GitHub.vscode-pull-request-github",
|
|
||||||
"bierner.github-markdown-preview",
|
|
||||||
"mrmlnc.vscode-scss",
|
|
||||||
"Gruntfuggly.todo-tree",
|
"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": [
|
"editor.rulers": [
|
||||||
{
|
{
|
||||||
"column": 120
|
"column": 120
|
||||||
@@ -29,14 +21,6 @@
|
|||||||
"css.lint.float": "warning",
|
"css.lint.float": "warning",
|
||||||
"css.lint.unknownVendorSpecificProperties": "warning",
|
"css.lint.unknownVendorSpecificProperties": "warning",
|
||||||
"css.lint.zeroUnits": "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.maxPreserveNewLines": 3,
|
||||||
"html.format.wrapAttributes": "preserve",
|
"html.format.wrapAttributes": "preserve",
|
||||||
"javascript.format.placeOpenBraceOnNewLineForControlBlocks": true,
|
"javascript.format.placeOpenBraceOnNewLineForControlBlocks": true,
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"tasks": [
|
"tasks": [
|
||||||
{
|
{
|
||||||
"type": "shell",
|
"type": "npm",
|
||||||
"command": "yarn build",
|
"script": "build -- -b chrome",
|
||||||
"group":
|
"group":
|
||||||
{
|
{
|
||||||
"kind": "build",
|
"kind": "build",
|
||||||
@@ -12,67 +12,32 @@
|
|||||||
"label": "Build: Chromium"
|
"label": "Build: Chromium"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "shell",
|
"type": "npm",
|
||||||
"command": "yarn build",
|
"script": "build -- -b firefox",
|
||||||
"group":
|
"group": "build",
|
||||||
{
|
"label": "Build: Firefox"
|
||||||
"kind": "build",
|
|
||||||
"isDefault": false
|
|
||||||
},
|
|
||||||
"label": "Build: Firefox",
|
|
||||||
"options": {
|
|
||||||
"env": {
|
|
||||||
"TARGET": "firefox"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "shell",
|
"type": "npm",
|
||||||
"command": "yarn build",
|
"script": "dev -- -b chrome",
|
||||||
"args": [
|
|
||||||
"--watch",
|
|
||||||
"--mode",
|
|
||||||
"development"
|
|
||||||
],
|
|
||||||
"group": "test",
|
"group": "test",
|
||||||
"label": "Watch: Chromium"
|
"label": "Dev: Google Chrome"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "shell",
|
"type": "npm",
|
||||||
"command": "yarn build",
|
"script": "dev -- -b firefox",
|
||||||
"args": [
|
|
||||||
"--watch",
|
|
||||||
"--mode",
|
|
||||||
"development"
|
|
||||||
],
|
|
||||||
"group": "test",
|
"group": "test",
|
||||||
"label": "Watch: Firefox",
|
"label": "Dev: Firefox"
|
||||||
"options": {
|
|
||||||
"env": {
|
|
||||||
"TARGET": "firefox"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "shell",
|
"type": "npm",
|
||||||
"command": "yarn dev",
|
"script": "dev -- -b edge",
|
||||||
"group": "test",
|
"group": "test",
|
||||||
"label": "Dev: Chromium"
|
"label": "Dev: Microsoft Edge"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "shell",
|
"type": "npm",
|
||||||
"command": "yarn dev",
|
"script": "install",
|
||||||
"group": "test",
|
|
||||||
"label": "Dev: Firefox",
|
|
||||||
"options": {
|
|
||||||
"env": {
|
|
||||||
"TARGET": "firefox"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "shell",
|
|
||||||
"command": "yarn install",
|
|
||||||
"label": "Restore dependencies",
|
"label": "Restore dependencies",
|
||||||
"group": "build"
|
"group": "build"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,75 +2,133 @@
|
|||||||
|
|
||||||
## Our Pledge
|
## Our Pledge
|
||||||
|
|
||||||
In the interest of fostering an open and welcoming environment, we as
|
We as members, contributors, and leaders pledge to make participation in our
|
||||||
contributors and maintainers pledge to making participation in our project and
|
community a harassment-free experience for everyone, regardless of age, body
|
||||||
our community a harassment-free experience for everyone, regardless of age, body
|
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||||
size, disability, ethnicity, sex characteristics, gender identity and expression,
|
identity and expression, level of experience, education, socio-economic status,
|
||||||
level of experience, education, socio-economic status, nationality, personal
|
nationality, personal appearance, race, caste, color, religion, or sexual
|
||||||
appearance, race, religion, or sexual identity and orientation.
|
identity and orientation.
|
||||||
|
|
||||||
|
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||||
|
diverse, inclusive, and healthy community.
|
||||||
|
|
||||||
## Our Standards
|
## Our Standards
|
||||||
|
|
||||||
Examples of behavior that contributes to creating a positive environment
|
Examples of behavior that contributes to a positive environment for our
|
||||||
include:
|
community include:
|
||||||
|
|
||||||
* Using welcoming and inclusive language
|
* Demonstrating empathy and kindness toward other people
|
||||||
* Being respectful of differing viewpoints and experiences
|
* Being respectful of differing opinions, viewpoints, and experiences
|
||||||
* Gracefully accepting constructive criticism
|
* Giving and gracefully accepting constructive feedback
|
||||||
* Focusing on what is best for the community
|
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||||
* Showing empathy towards other community members
|
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
|
* The use of sexualized language or imagery, and sexual attention or advances of
|
||||||
advances
|
any kind
|
||||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||||
* Public or private harassment
|
* Public or private harassment
|
||||||
* Publishing others' private information, such as a physical or electronic
|
* Publishing others' private information, such as a physical or email address,
|
||||||
address, without explicit permission
|
without their explicit permission
|
||||||
* Other conduct which could reasonably be considered inappropriate in a
|
* 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
|
Community leaders are responsible for clarifying and enforcing our standards of
|
||||||
behavior and are expected to take appropriate and fair corrective action in
|
acceptable behavior and will take appropriate and fair corrective action in
|
||||||
response to any instances of unacceptable behavior.
|
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||||
|
or harmful.
|
||||||
|
|
||||||
Project maintainers have the right and responsibility to remove, edit, or
|
Community leaders have the right and responsibility to remove, edit, or reject
|
||||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||||
permanently any contributor for other behaviors that they deem inappropriate,
|
decisions when appropriate.
|
||||||
threatening, offensive, or harmful.
|
|
||||||
|
|
||||||
## Scope
|
## Scope
|
||||||
|
|
||||||
This Code of Conduct applies both within project spaces and in public spaces
|
This Code of Conduct applies within all community spaces, and also applies when
|
||||||
when an individual is representing the project or its community. Examples of
|
an individual is officially representing the community in public spaces.
|
||||||
representing a project or community include using an official project e-mail
|
Examples of representing our community include using an official email address,
|
||||||
address, posting via an official social media account, or acting as an appointed
|
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
|
representative at an online or offline event.
|
||||||
further defined and clarified by project maintainers.
|
|
||||||
|
|
||||||
## Enforcement
|
## Enforcement
|
||||||
|
|
||||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||||
reported by contacting the project team at opensource@xfox111.net. All
|
reported to the community leaders responsible for enforcement at
|
||||||
complaints will be reviewed and investigated and will result in a response that
|
[opensource@xfox111.net](mailto:opensource@xfox111.net).
|
||||||
is deemed necessary and appropriate to the circumstances. The project team is
|
All complaints will be reviewed and investigated promptly and fairly.
|
||||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
|
||||||
Further details of specific enforcement policies may be posted separately.
|
|
||||||
|
|
||||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
All community leaders are obligated to respect the privacy and security of the
|
||||||
faith may face temporary or permanent repercussions as determined by other
|
reporter of any incident.
|
||||||
members of the project's leadership.
|
|
||||||
|
## 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
|
## Attribution
|
||||||
|
|
||||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||||
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
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
|
[homepage]: https://www.contributor-covenant.org
|
||||||
|
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
|
||||||
For answers to common questions about this code of conduct, see
|
[Mozilla CoC]: https://github.com/mozilla/diversity
|
||||||
https://www.contributor-covenant.org/faq
|
[FAQ]: https://www.contributor-covenant.org/faq
|
||||||
|
[translations]: https://www.contributor-covenant.org/translations
|
||||||
|
|||||||
@@ -1,2 +1,4 @@
|
|||||||
# Contribution Guidelines
|
# 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)
|
||||||
|
|||||||
@@ -12,10 +12,12 @@
|
|||||||
Extension for web browsers which helps you to easily generate strong passwords in one click
|
Extension for web browsers which helps you to easily generate strong passwords in one click
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
- Create strong passwords in one click
|
|
||||||
- Customizable generator
|
- Customizable generator
|
||||||
- Clean and simple UI
|
- Clean and simple UI
|
||||||
- Dark mode
|
- Dark mode
|
||||||
|
- **NEW:** Insert and copy generated password in one click
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
## Languages
|
## Languages
|
||||||
- English
|
- 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
|
> 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
|
> 2. Download attached archive for Chromium and unpack it
|
||||||
> 3. Go to "chrome://extensions"
|
> 3. Go to `chrome://extensions`
|
||||||
> 4. Enable "Developer mode"
|
> 4. Enable "Developer mode"
|
||||||
> 5. Click the "Load unpacked" button and navigate to the extension's root folder (contains `manifest.json`)
|
> 5. Click the "Load unpacked" button and navigate to the extension's root folder (contains `manifest.json`)
|
||||||
> 6. Done!
|
> 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
|
> 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
|
> 2. Download attached archive for Firefox and unpack it
|
||||||
> 3. Go to "about:debugging#/runtime/this-firefox"
|
> 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
|
> 4. Click the "Load Temporary Add-on..." button and select `manifest.json` file in the root folder
|
||||||
> 5. Done!
|
> 5. Done!
|
||||||
|
|
||||||
> **Important!**
|
> **Important!**
|
||||||
|
|||||||
@@ -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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -1,14 +1,14 @@
|
|||||||
|
import { StorageProvider } from "@/utils/storage";
|
||||||
|
import { useTheme } from "@/utils/useTheme";
|
||||||
import { Accordion, FluentProvider, Spinner } from "@fluentui/react-components";
|
import { Accordion, FluentProvider, Spinner } from "@fluentui/react-components";
|
||||||
import { useStyles } from "./App.styles";
|
|
||||||
import AboutSection from "./Components/AboutSection";
|
|
||||||
import GeneratorView from "./Components/GeneratorView";
|
|
||||||
import SettingsSection from "./Components/SettingsSection";
|
|
||||||
import Specials from "./Specials/Specials";
|
|
||||||
import { StorageProvider } from "./Utils/Storage";
|
|
||||||
import { useTheme } from "./Utils/Theme";
|
|
||||||
import { useState } from "react";
|
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";
|
||||||
|
|
||||||
export default function App(): JSX.Element
|
const App: React.FC = () =>
|
||||||
{
|
{
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const cls = useStyles();
|
const cls = useStyles();
|
||||||
@@ -22,14 +22,17 @@ export default function App(): JSX.Element
|
|||||||
<Accordion
|
<Accordion
|
||||||
openItems={ selection }
|
openItems={ selection }
|
||||||
onToggle={ (_, e) => setSelection(e.openItems as string[]) }
|
onToggle={ (_, e) => setSelection(e.openItems as string[]) }
|
||||||
collapsible
|
collapsible>
|
||||||
className={ cls.accordionAnimation }>
|
|
||||||
<SettingsSection />
|
<SettingsSection />
|
||||||
<AboutSection />
|
<AboutSection />
|
||||||
</Accordion>
|
</Accordion>
|
||||||
</StorageProvider>
|
</StorageProvider>
|
||||||
<Specials />
|
|
||||||
|
<Snow />
|
||||||
</main>
|
</main>
|
||||||
</FluentProvider>
|
</FluentProvider>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default App;
|
||||||
@@ -1,17 +1,16 @@
|
|||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="utf-8" />
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Password generator</title>
|
<title>Password generator</title>
|
||||||
|
<meta name="manifest.type" content="browser_action" />
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
<script type="module" src="./main.tsx"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
<script type="module" src="./popup.tsx"></script>
|
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import ReactDOM from "react-dom/client";
|
import ReactDOM from "react-dom/client";
|
||||||
import App from "./App";
|
import App from "./App";
|
||||||
import "./popup.css";
|
import "./style.css";
|
||||||
|
|
||||||
ReactDOM.createRoot(document.getElementById("root")!).render(
|
ReactDOM.createRoot(document.getElementById("root")!).render(
|
||||||
<React.StrictMode>
|
<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,13 +10,6 @@ export const useStyles = makeStyles({
|
|||||||
{
|
{
|
||||||
fontFamily: tokens.fontFamilyMonospace,
|
fontFamily: tokens.fontFamilyMonospace,
|
||||||
},
|
},
|
||||||
lengthContainer:
|
|
||||||
{
|
|
||||||
display: "grid",
|
|
||||||
gridTemplateColumns: "1fr auto",
|
|
||||||
alignItems: "center",
|
|
||||||
paddingRight: tokens.spacingHorizontalM,
|
|
||||||
},
|
|
||||||
copyIcon:
|
copyIcon:
|
||||||
{
|
{
|
||||||
animationName: "scaleUpIn",
|
animationName: "scaleUpIn",
|
||||||
@@ -33,15 +26,4 @@ export const useStyles = makeStyles({
|
|||||||
{
|
{
|
||||||
...shorthands.padding(tokens.spacingVerticalMNudge, tokens.spacingHorizontalM),
|
...shorthands.padding(tokens.spacingVerticalMNudge, tokens.spacingHorizontalM),
|
||||||
},
|
},
|
||||||
options:
|
|
||||||
{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
...shorthands.padding(tokens.spacingVerticalS, tokens.spacingHorizontalS),
|
|
||||||
},
|
|
||||||
characterOptionsContainer:
|
|
||||||
{
|
|
||||||
display: "flex",
|
|
||||||
...shorthands.gap(tokens.spacingHorizontalXS),
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
@@ -0,0 +1,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;
|
||||||
|
}
|
||||||
@@ -1,122 +1,138 @@
|
|||||||
|
import { CharacterHints } from "@/utils/PasswordGenerator";
|
||||||
|
import { ExtensionOptions, GeneratorOptions, useStorage } from "@/utils/storage";
|
||||||
import * as fui from "@fluentui/react-components";
|
import * as fui from "@fluentui/react-components";
|
||||||
import { ArrowUndoRegular, SettingsRegular } from "@fluentui/react-icons";
|
import { ArrowUndoRegular, SettingsRegular } from "@fluentui/react-icons";
|
||||||
import ExtensionOptions from "../Models/ExtensionOptions";
|
|
||||||
import GeneratorOptions from "../Models/GeneratorOptions";
|
|
||||||
import { GetLocaleString as loc } from "../Utils/Localization";
|
|
||||||
import { CharacterHints } from "../Utils/PasswordGenerator";
|
|
||||||
import { useStorage } from "../Utils/Storage";
|
|
||||||
import { useStyles } from "./SettingsSection.styles";
|
import { useStyles } from "./SettingsSection.styles";
|
||||||
|
|
||||||
// FIXME: Remove ts-ignore comments once slots override fix is released
|
// FIXME: Remove ts-ignore comments once slots override fix is released
|
||||||
// Tracker: https://github.com/microsoft/fluentui/issues/27090
|
// 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>
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
export default function SettingsSection(): JSX.Element
|
const defaultOptions =
|
||||||
|
{
|
||||||
|
generator: new GeneratorOptions(),
|
||||||
|
extension: new ExtensionOptions()
|
||||||
|
};
|
||||||
|
|
||||||
|
const SettingsSection: React.FC = () =>
|
||||||
{
|
{
|
||||||
const { extOptions, generatorOptions, updateStorage } = useStorage();
|
const { extOptions, generatorOptions, updateStorage } = useStorage();
|
||||||
const cls = useStyles();
|
const cls = useStyles();
|
||||||
|
|
||||||
const infoLabel = (content: string, hint: string) => ({
|
const resetRange = useCallback(() =>
|
||||||
children: (_: unknown, slotProps: fui.LabelProps) => (
|
{
|
||||||
<fui.InfoLabel { ...slotProps } info={ hint }>{ content }</fui.InfoLabel>
|
updateStorage({
|
||||||
)
|
MinLength: defaultOptions.extension.MinLength,
|
||||||
});
|
MaxLength: defaultOptions.extension.MaxLength
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
const setOption = (option: keyof (GeneratorOptions & ExtensionOptions)) =>
|
const setOption = (option: keyof (GeneratorOptions & ExtensionOptions)) =>
|
||||||
(_: unknown, args: fui.CheckboxOnChangeData) =>
|
(_: unknown, args: fui.CheckboxOnChangeData) =>
|
||||||
updateStorage({ [option]: args.checked } );
|
updateStorage({ [option]: args.checked });
|
||||||
|
|
||||||
const updateNumberField = (key: keyof (ExtensionOptions & GeneratorOptions), defaultValue: number) =>
|
const updateNumberField = (key: keyof (ExtensionOptions & GeneratorOptions), defaultValue: number) =>
|
||||||
(_: unknown, e: fui.InputOnChangeData): void =>
|
(_: unknown, e: fui.InputOnChangeData): void =>
|
||||||
{
|
{
|
||||||
if (e.value.length < 1)
|
if (e.value.length >= 1)
|
||||||
{
|
{
|
||||||
updateStorage({ [key]: defaultValue });
|
const value = parseInt(e.value);
|
||||||
return;
|
|
||||||
|
if (!isNaN(value) && value >= 0)
|
||||||
|
updateStorage({ [key]: value });
|
||||||
}
|
}
|
||||||
|
else
|
||||||
const value = parseInt(e.value);
|
updateStorage({ [key]: defaultValue });
|
||||||
|
|
||||||
if (!isNaN(value) && value >= 0)
|
|
||||||
updateStorage({ [key]: value });
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<fui.AccordionItem value="settings">
|
<fui.AccordionItem value="settings">
|
||||||
<fui.AccordionHeader as="h2" icon={ <SettingsRegular /> }>{ loc("settings@title") }</fui.AccordionHeader>
|
<fui.AccordionHeader as="h2" icon={ <SettingsRegular /> }>{ i18n.t("settings.title") }</fui.AccordionHeader>
|
||||||
|
|
||||||
<fui.AccordionPanel className={ cls.root }>
|
<fui.AccordionPanel className={ cls.root }>
|
||||||
|
|
||||||
<fui.Field label={ loc("settings@length") } hint={ loc("settings@length__hint") }>
|
<fui.Field label={ i18n.t("settings.length.title") } hint={ i18n.t("settings.length.hint") }>
|
||||||
<fui.Input
|
<fui.Input
|
||||||
value={ generatorOptions.Length.toString() }
|
value={ generatorOptions.Length.toString() }
|
||||||
onChange={ updateNumberField("Length", 0) } />
|
onChange={ updateNumberField("Length", 0) } />
|
||||||
</fui.Field>
|
</fui.Field>
|
||||||
|
|
||||||
<fui.Field label={ loc("settings@lengthRange") }>
|
<fui.Field label={ i18n.t("settings.quick_range") }>
|
||||||
<div className={ cls.rangeContainer }>
|
<div className={ cls.rangeContainer }>
|
||||||
<fui.Input
|
<fui.Input
|
||||||
input={ { className: cls.rangeInput } }
|
input={ { className: cls.rangeInput } }
|
||||||
value={ extOptions.MinLength.toString() }
|
value={ extOptions.MinLength.toString() }
|
||||||
onChange={ updateNumberField("MinLength", 4) } />
|
onChange={ updateNumberField("MinLength", defaultOptions.extension.MinLength) } />
|
||||||
|
|
||||||
<fui.Divider />
|
<fui.Divider />
|
||||||
|
|
||||||
<fui.Input
|
<fui.Input
|
||||||
input={ { className: cls.rangeInput } }
|
input={ { className: cls.rangeInput } }
|
||||||
value={ extOptions.MaxLength.toString() }
|
value={ extOptions.MaxLength.toString() }
|
||||||
onChange={ updateNumberField("MaxLength", 32) } />
|
onChange={ updateNumberField("MaxLength", defaultOptions.extension.MaxLength) } />
|
||||||
|
|
||||||
<fui.Tooltip relationship="label" content={ loc("generator@reset") }>
|
<fui.Tooltip relationship="label" content={ i18n.t("common.reset") }>
|
||||||
<fui.Button
|
<fui.Button
|
||||||
appearance="subtle" icon={ <ArrowUndoRegular /> }
|
appearance="subtle" icon={ <ArrowUndoRegular /> }
|
||||||
onClick={ () => updateStorage({ MinLength: 6, MaxLength: 32 }) } />
|
onClick={ resetRange } />
|
||||||
</fui.Tooltip>
|
</fui.Tooltip>
|
||||||
</div>
|
</div>
|
||||||
</fui.Field>
|
</fui.Field>
|
||||||
|
|
||||||
<fui.Divider />
|
<fui.Divider />
|
||||||
|
|
||||||
<fui.Text>{ loc("settings@include") }</fui.Text>
|
<fui.Text>{ i18n.t("settings.include.title") }</fui.Text>
|
||||||
<div className={ cls.checkboxContainer }>
|
<div className={ cls.checkboxContainer }>
|
||||||
<fui.Checkbox label={ loc("settings@uppercase") }
|
<fui.Checkbox label={ i18n.t("settings.include.uppercase") }
|
||||||
checked={ generatorOptions.Uppercase }
|
checked={ generatorOptions.Uppercase }
|
||||||
onChange={ setOption("Uppercase") } />
|
onChange={ setOption("Uppercase") } />
|
||||||
<fui.Checkbox
|
<fui.Checkbox
|
||||||
label={ loc("settings@lowercase") }
|
label={ i18n.t("settings.include.lowercase") }
|
||||||
checked={ generatorOptions.Lowercase }
|
checked={ generatorOptions.Lowercase }
|
||||||
onChange={ setOption("Lowercase") } />
|
onChange={ setOption("Lowercase") } />
|
||||||
<fui.Checkbox
|
<fui.Checkbox
|
||||||
label={ loc("settings@numeric") }
|
label={ i18n.t("settings.include.numeric") }
|
||||||
checked={ generatorOptions.Numeric }
|
checked={ generatorOptions.Numeric }
|
||||||
onChange={ setOption("Numeric") } />
|
onChange={ setOption("Numeric") } />
|
||||||
<fui.Checkbox
|
<fui.Checkbox
|
||||||
label={ loc("settings@special") }
|
label={ i18n.t("settings.include.special") }
|
||||||
checked={ generatorOptions.Special }
|
checked={ generatorOptions.Special }
|
||||||
onChange={ setOption("Special") } />
|
onChange={ setOption("Special") } />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<fui.Text>{ loc("settings@exclude") }</fui.Text>
|
<fui.Text>{ i18n.t("settings.exclude.title") }</fui.Text>
|
||||||
<div className={ cls.checkboxContainer }>
|
<div className={ cls.checkboxContainer }>
|
||||||
<fui.Checkbox
|
<fui.Checkbox
|
||||||
// @ts-expect-error See line 11
|
// @ts-expect-error See FIXME
|
||||||
label={ infoLabel(loc("settings@similar"), CharacterHints.Similar) }
|
label={ infoLabel(i18n.t("settings.exclude.similar"), CharacterHints.Similar) }
|
||||||
checked={ generatorOptions.ExcludeSimilar }
|
checked={ generatorOptions.ExcludeSimilar }
|
||||||
onChange={ setOption("ExcludeSimilar") } />
|
onChange={ setOption("ExcludeSimilar") } />
|
||||||
<fui.Checkbox
|
<fui.Checkbox
|
||||||
// @ts-expect-error See line 11
|
// @ts-expect-error See FIXME
|
||||||
label={ infoLabel(loc("settings@ambiguous"), CharacterHints.Ambiguous) }
|
label={ infoLabel(i18n.t("settings.exclude.ambiguous"), CharacterHints.Ambiguous) }
|
||||||
disabled={ !generatorOptions.Special }
|
disabled={ !generatorOptions.Special }
|
||||||
checked={ generatorOptions.ExcludeAmbiguous }
|
checked={ generatorOptions.ExcludeAmbiguous }
|
||||||
onChange={ setOption("ExcludeAmbiguous") } />
|
onChange={ setOption("ExcludeAmbiguous") } />
|
||||||
<fui.Checkbox
|
<fui.Checkbox
|
||||||
// @ts-expect-error See line 11
|
// @ts-expect-error See FIXME
|
||||||
label={ infoLabel(loc("settings@repeating"), loc("settings@repeating__hint")) }
|
label={ infoLabel(i18n.t("settings.exclude.repeating.title"), i18n.t("settings.exclude.repeating.hint")) }
|
||||||
checked={ generatorOptions.ExcludeRepeating }
|
checked={ generatorOptions.ExcludeRepeating }
|
||||||
onChange={ setOption("ExcludeRepeating") } />
|
onChange={ setOption("ExcludeRepeating") } />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<fui.Checkbox
|
||||||
|
label={ i18n.t("settings.context_menu") }
|
||||||
|
checked={ extOptions.ContextMenu }
|
||||||
|
onChange={ setOption("ContextMenu") } />
|
||||||
|
|
||||||
</fui.AccordionPanel>
|
</fui.AccordionPanel>
|
||||||
|
|
||||||
</fui.AccordionItem>
|
</fui.AccordionItem>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default SettingsSection;
|
||||||
@@ -1,12 +1,14 @@
|
|||||||
import { GriffelStyle, makeStyles, shorthands, tokens } from "@fluentui/react-components";
|
import { GriffelStyle, makeStyles, tokens } from "@fluentui/react-components";
|
||||||
|
|
||||||
const random = (max: number) => Math.floor(Math.random() * max);
|
const random = (max: number): number => Math.floor(Math.random() * max);
|
||||||
|
|
||||||
export const useStyles = (count: number) => makeStyles({
|
export const SNOWFLAKES_NUM: number = 100;
|
||||||
|
|
||||||
|
export const useStyles = makeStyles({
|
||||||
snow:
|
snow:
|
||||||
{
|
{
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
...shorthands.overflow("hidden"),
|
overflow: "hidden",
|
||||||
pointerEvents: "none",
|
pointerEvents: "none",
|
||||||
|
|
||||||
top: 0,
|
top: 0,
|
||||||
@@ -20,16 +22,16 @@ export const useStyles = (count: number) => makeStyles({
|
|||||||
width: "var(--size)",
|
width: "var(--size)",
|
||||||
height: "var(--size)",
|
height: "var(--size)",
|
||||||
backgroundColor: tokens.colorScrollbarOverlay,
|
backgroundColor: tokens.colorScrollbarOverlay,
|
||||||
...shorthands.borderRadius(tokens.borderRadiusCircular),
|
borderRadius: tokens.borderRadiusCircular,
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
top: "-5px",
|
top: "-5px",
|
||||||
},
|
},
|
||||||
...[...Array(count)].reduce(
|
...[...Array(SNOWFLAKES_NUM)].reduce(
|
||||||
(acc, _, i): Record<string, GriffelStyle> => ({
|
(acc, _, i): Record<string, GriffelStyle> => ({
|
||||||
...acc,
|
...acc,
|
||||||
[`snowflake-${i}`]: {
|
[`snowflake-${i}`]: {
|
||||||
"--size": `${random(5)}px`,
|
"--size": `${random(5)}px`,
|
||||||
"--left-ini": `${random(20) - 10}vw`,
|
"--left-start": `${random(20) - 10}vw`,
|
||||||
"--left-end": `${random(20) - 10}vw`,
|
"--left-end": `${random(20) - 10}vw`,
|
||||||
left: `${random(100)}vw`,
|
left: `${random(100)}vw`,
|
||||||
animationName: "snowfall",
|
animationName: "snowfall",
|
||||||
@@ -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;
|
||||||
@@ -6,6 +6,9 @@ body
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
|
||||||
|
max-height: 600px;
|
||||||
|
overflow-y: hidden;
|
||||||
|
|
||||||
-moz-user-select: none;
|
-moz-user-select: none;
|
||||||
-webkit-user-select: none;
|
-webkit-user-select: none;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
@@ -22,39 +25,53 @@ p, ul, ol, li
|
|||||||
from
|
from
|
||||||
{
|
{
|
||||||
transform: scale(0.5);
|
transform: scale(0.5);
|
||||||
opacity: 0;
|
filter: opacity(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
to
|
to
|
||||||
{
|
{
|
||||||
transform: scale(1);
|
transform: scale(1);
|
||||||
opacity: 1;
|
filter: opacity(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes spin
|
@keyframes spin
|
||||||
{
|
{
|
||||||
from { transform: rotate(0deg); }
|
from
|
||||||
to { transform: rotate(360deg); }
|
{
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
to
|
||||||
|
{
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes fadeIn
|
@keyframes fadeIn
|
||||||
{
|
{
|
||||||
from { opacity: 0; }
|
from
|
||||||
to { opacity: 1; }
|
{
|
||||||
|
filter: opacity(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
to
|
||||||
|
{
|
||||||
|
filter: opacity(1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes snowfall
|
@keyframes snowfall
|
||||||
{
|
{
|
||||||
0%
|
0%
|
||||||
{
|
{
|
||||||
transform: translate3d(var(--left-ini), 0, 0);
|
transform: translate3d(var(--left-start), 0, 0);
|
||||||
opacity: .6;
|
filter: opacity(.6);
|
||||||
}
|
}
|
||||||
|
|
||||||
100%
|
100%
|
||||||
{
|
{
|
||||||
transform: translate3d(var(--left-end), 610px, 0);
|
transform: translate3d(var(--left-end), 610px, 0);
|
||||||
opacity: 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 contextual"
|
||||||
|
|
||||||
|
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,39 +1,37 @@
|
|||||||
{
|
{
|
||||||
"name": "password-generator",
|
"name": "password-generator",
|
||||||
"version": "3.1.4",
|
"version": "4.0.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "wxt",
|
||||||
"build": "tsc && vite build",
|
"build": "wxt build --mv3",
|
||||||
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0"
|
"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": {
|
"dependencies": {
|
||||||
"@fluentui/react-components": "^9.53.0",
|
"@fluentui/react-components": "^9.54.14",
|
||||||
"@fluentui/react-icons": "^2.0.242",
|
"@fluentui/react-icons": "^2.0.258",
|
||||||
|
"@wxt-dev/i18n": "^0.1.0",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1"
|
"react-dom": "^18.3.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/react": "^18.3.3",
|
"@eslint/js": "^9.10.0",
|
||||||
|
"@types/react": "^18.3.5",
|
||||||
"@types/react-dom": "^18.3.0",
|
"@types/react-dom": "^18.3.0",
|
||||||
"@types/webextension-polyfill": "^0.10.7",
|
"@typescript-eslint/eslint-plugin": "^8.6.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^7.0.0",
|
"@typescript-eslint/parser": "^8.6.0",
|
||||||
"@typescript-eslint/parser": "^6.21.0",
|
"@wxt-dev/module-react": "^1.1.1",
|
||||||
"@vitejs/plugin-react-swc": "^3.7.0",
|
"eslint": "^9.10.0",
|
||||||
"eslint": "^8.57.0",
|
"eslint-plugin-react": "^7.36.1",
|
||||||
"eslint-plugin-react": "^7.34.2",
|
"globals": "^15.9.0",
|
||||||
"eslint-plugin-react-hooks": "^4.6.2",
|
"typescript": "^5.6.2",
|
||||||
"eslint-plugin-react-refresh": "^0.4.7",
|
"wxt": "^0.19.9"
|
||||||
"typescript": "^5.4.5",
|
|
||||||
"vite": "^5.2.12",
|
|
||||||
"vite-plugin-static-copy": "^1.0.5",
|
|
||||||
"vite-plugin-svgr": "^4.2.0",
|
|
||||||
"vite-plugin-web-extension": "^4.1.4",
|
|
||||||
"webextension-polyfill": "^0.12.0"
|
|
||||||
},
|
|
||||||
"resolutions": {
|
|
||||||
"scheduler": "^0.20.0",
|
|
||||||
"@floating-ui/dom": "^1.5.4"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 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,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 bmcTheme = useTheme(bmcLightTheme, bmcDarkTheme);
|
|
||||||
const cls = useStyles();
|
|
||||||
|
|
||||||
const link = (text: string, href: string): JSX.Element => (
|
|
||||||
<fui.Link target="_blank" href={ href }>{ text }</fui.Link>
|
|
||||||
);
|
|
||||||
|
|
||||||
const buttonProps = (href: string, icon: JSX.Element): fui.ButtonProps => (
|
|
||||||
{
|
|
||||||
as: "a", target: "_blank", href,
|
|
||||||
appearance: "primary", icon
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<fui.AccordionItem value="about">
|
|
||||||
<fui.AccordionHeader as="h2" icon={ <InfoRegular /> }>{ loc("about@title") }</fui.AccordionHeader>
|
|
||||||
<fui.AccordionPanel className={ cls.root }>
|
|
||||||
<header className={ cls.horizontalContainer }>
|
|
||||||
<fui.Subtitle1 as="h1">{ loc("name") }</fui.Subtitle1>
|
|
||||||
<fui.Caption1 as="span">v{ Package.version }</fui.Caption1>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<fui.Text as="p">
|
|
||||||
{ loc("about@developedBy") } ({ link("@xfox111", PersonalLink.Twitter) })
|
|
||||||
<br />
|
|
||||||
{ loc("about@licensedUnder") } { link(loc("about@mitLicense"), GithubLink.License) }
|
|
||||||
</fui.Text>
|
|
||||||
|
|
||||||
<fui.Text as="p">
|
|
||||||
{ loc("about@translationCta") }<br />
|
|
||||||
{ link(loc("about@translationCtaButton"), GithubLink.TranslationGuide) }
|
|
||||||
</fui.Text>
|
|
||||||
|
|
||||||
<fui.Text as="p">
|
|
||||||
{ link(loc("about@website"), PersonalLink.Website) } <br />
|
|
||||||
{ link(loc("about@sourceCode"), GithubLink.Repository) } <br />
|
|
||||||
{ link(loc("about@changelog"), GithubLink.Changelog) }
|
|
||||||
</fui.Text>
|
|
||||||
|
|
||||||
<div className={ cls.horizontalContainer }>
|
|
||||||
<fui.Button { ...buttonProps(GetFeedbackLink(), <PersonFeedbackRegular />) }>
|
|
||||||
{ loc("about@feedback") }
|
|
||||||
</fui.Button>
|
|
||||||
<fui.FluentProvider theme={ bmcTheme }>
|
|
||||||
<fui.Button { ...buttonProps(PersonalLink.BuyMeACoffee, <BuyMeACoffee />) }>
|
|
||||||
{ loc("about@sponsor") }
|
|
||||||
</fui.Button>
|
|
||||||
</fui.FluentProvider>
|
|
||||||
</div>
|
|
||||||
</fui.AccordionPanel>
|
|
||||||
</fui.AccordionItem>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,171 +0,0 @@
|
|||||||
import * as fui from "@fluentui/react-components";
|
|
||||||
import * as Icons from "@fluentui/react-icons";
|
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import GeneratorOptions from "../Models/GeneratorOptions";
|
|
||||||
import { GetLocaleString as loc } from "../Utils/Localization";
|
|
||||||
import { GeneratePassword } from "../Utils/PasswordGenerator";
|
|
||||||
import { useStorage } from "../Utils/Storage";
|
|
||||||
import { useTimeout } from "../Utils/Timeout";
|
|
||||||
import { useStyles } from "./GeneratorView.styles";
|
|
||||||
|
|
||||||
export default function GeneratorView(props: { collapse: boolean; }): JSX.Element
|
|
||||||
{
|
|
||||||
const { generatorOptions, extOptions } = useStorage();
|
|
||||||
const [password, setPassword] = useState<string>("");
|
|
||||||
const [quickOpts, _setOpts] = useState<GeneratorOptions>(generatorOptions);
|
|
||||||
const [error, setError] = useState<string | null>(null);
|
|
||||||
|
|
||||||
const [refreshTimer, copyTimer] = [useTimeout(310), useTimeout(1000)];
|
|
||||||
const checkedOptions = Object.keys(quickOpts).filter(k => quickOpts[k as keyof GeneratorOptions] as boolean);
|
|
||||||
const cls = useStyles();
|
|
||||||
|
|
||||||
const IncludeIcon: Icons.FluentIcon = Icons.bundleIcon(Icons.AddCircleFilled, Icons.AddCircleRegular);
|
|
||||||
const ExcludeIcon: Icons.FluentIcon = Icons.bundleIcon(Icons.SubtractCircleFilled, Icons.SubtractCircleRegular);
|
|
||||||
|
|
||||||
const resetOptions = (): void =>
|
|
||||||
_setOpts(generatorOptions);
|
|
||||||
|
|
||||||
const setOptions = (opts: Partial<GeneratorOptions>) =>
|
|
||||||
_setOpts({ ...quickOpts, ...opts });
|
|
||||||
|
|
||||||
function RefreshPassword(): void
|
|
||||||
{
|
|
||||||
setError(null);
|
|
||||||
const options: GeneratorOptions = { ...generatorOptions, ...quickOpts };
|
|
||||||
|
|
||||||
try { setPassword(GeneratePassword(options)); }
|
|
||||||
catch (e) { setError((e as Error).message); }
|
|
||||||
}
|
|
||||||
|
|
||||||
async function CopyPassword(): Promise<void>
|
|
||||||
{
|
|
||||||
await window.navigator.clipboard.writeText(password);
|
|
||||||
copyTimer.trigger();
|
|
||||||
}
|
|
||||||
|
|
||||||
function OnCheckedValueChange(_: unknown, e: fui.MenuCheckedValueChangeData): void
|
|
||||||
{
|
|
||||||
const opts: Partial<Omit<GeneratorOptions, "Length">> = {};
|
|
||||||
|
|
||||||
let keys = Object.keys(quickOpts).filter(i => i !== "Length") as (keyof Omit<GeneratorOptions, "Length">)[];
|
|
||||||
|
|
||||||
if (e.name === "include")
|
|
||||||
keys = keys.filter(i => !i.startsWith("Exclude"));
|
|
||||||
else
|
|
||||||
keys = keys.filter(i => i.startsWith("Exclude"));
|
|
||||||
|
|
||||||
for (const key of keys)
|
|
||||||
opts[key] = e.checkedItems.includes(key);
|
|
||||||
|
|
||||||
setOptions(opts);
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(resetOptions, [generatorOptions]);
|
|
||||||
useEffect(RefreshPassword, [generatorOptions, quickOpts]);
|
|
||||||
useEffect(refreshTimer.trigger, [password]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section className={ cls.root }>
|
|
||||||
{ error ?
|
|
||||||
<fui.MessageBar intent="warning" className={ cls.msgBar }>
|
|
||||||
<fui.MessageBarBody>{ error }</fui.MessageBarBody>
|
|
||||||
</fui.MessageBar>
|
|
||||||
:
|
|
||||||
<fui.Input
|
|
||||||
className={ cls.input }
|
|
||||||
readOnly value={ password }
|
|
||||||
contentAfter={ <>
|
|
||||||
<fui.Tooltip content={ loc("generator@copy") } relationship="label">
|
|
||||||
<fui.Button
|
|
||||||
appearance="subtle" onClick={ CopyPassword }
|
|
||||||
icon={
|
|
||||||
copyTimer.isActive ?
|
|
||||||
<Icons.CheckmarkRegular className={ cls.copyIcon } /> :
|
|
||||||
<Icons.CopyRegular className={ cls.copyIcon } />
|
|
||||||
} />
|
|
||||||
</fui.Tooltip>
|
|
||||||
|
|
||||||
<fui.Tooltip content={ loc("generator@refresh") } relationship="label">
|
|
||||||
<fui.Button
|
|
||||||
appearance="subtle" onClick={ RefreshPassword }
|
|
||||||
icon={
|
|
||||||
<Icons.ArrowClockwiseRegular className={ fui.mergeClasses(refreshTimer.isActive && cls.refreshIcon) } />
|
|
||||||
} />
|
|
||||||
</fui.Tooltip>
|
|
||||||
</> } />
|
|
||||||
}
|
|
||||||
|
|
||||||
{ !props.collapse &&
|
|
||||||
<div className={ cls.options }>
|
|
||||||
<fui.InfoLabel info={ loc("generator@quickOptions__hint") }>
|
|
||||||
{ loc("generator@quickOptions") }
|
|
||||||
</fui.InfoLabel>
|
|
||||||
|
|
||||||
<div className={ cls.lengthContainer }>
|
|
||||||
<fui.Slider
|
|
||||||
min={ extOptions.MinLength } max={ Math.max(extOptions.MaxLength, generatorOptions.Length) }
|
|
||||||
value={ quickOpts.Length } onChange={ (_, e) => setOptions({ Length: e.value }) } />
|
|
||||||
<fui.Text>{ quickOpts.Length }</fui.Text>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={ cls.characterOptionsContainer }>
|
|
||||||
<fui.Menu
|
|
||||||
positioning="after" hasCheckmarks
|
|
||||||
checkedValues={ { include: checkedOptions } }
|
|
||||||
onCheckedValueChange={ OnCheckedValueChange }>
|
|
||||||
|
|
||||||
<fui.MenuTrigger disableButtonEnhancement>
|
|
||||||
<fui.MenuButton appearance="subtle" icon={ <IncludeIcon /> }>{ loc("generator@include") }</fui.MenuButton>
|
|
||||||
</fui.MenuTrigger>
|
|
||||||
|
|
||||||
<fui.MenuPopover>
|
|
||||||
<fui.MenuList>
|
|
||||||
<fui.MenuItemCheckbox name="include" value="Uppercase" icon={ <Icons.TextCaseUppercaseRegular /> }>
|
|
||||||
{ loc("settings@uppercase") }
|
|
||||||
</fui.MenuItemCheckbox>
|
|
||||||
<fui.MenuItemCheckbox name="include" value="Lowercase" icon={ <Icons.TextCaseLowercaseRegular /> }>
|
|
||||||
{ loc("settings@lowercase") }
|
|
||||||
</fui.MenuItemCheckbox>
|
|
||||||
<fui.MenuItemCheckbox name="include" value="Numeric" icon={ <Icons.NumberSymbolRegular /> }>
|
|
||||||
{ loc("settings@numeric") }
|
|
||||||
</fui.MenuItemCheckbox>
|
|
||||||
<fui.MenuItemCheckbox name="include" value="Special" icon={ <Icons.MathSymbolsRegular /> }>
|
|
||||||
{ loc("settings@special") }
|
|
||||||
</fui.MenuItemCheckbox>
|
|
||||||
</fui.MenuList>
|
|
||||||
</fui.MenuPopover>
|
|
||||||
</fui.Menu>
|
|
||||||
|
|
||||||
<fui.Menu
|
|
||||||
positioning="before"
|
|
||||||
checkedValues={ { exclude: checkedOptions } }
|
|
||||||
onCheckedValueChange={ OnCheckedValueChange }>
|
|
||||||
|
|
||||||
<fui.MenuTrigger disableButtonEnhancement>
|
|
||||||
<fui.MenuButton appearance="subtle" icon={ <ExcludeIcon /> }>{ loc("generator@exclude") }</fui.MenuButton>
|
|
||||||
</fui.MenuTrigger>
|
|
||||||
|
|
||||||
<fui.MenuPopover>
|
|
||||||
<fui.MenuList>
|
|
||||||
<fui.MenuItemCheckbox name="exclude" value="ExcludeSimilar">
|
|
||||||
{ loc("settings@similar") }
|
|
||||||
</fui.MenuItemCheckbox>
|
|
||||||
<fui.MenuItemCheckbox name="exclude" value="ExcludeAmbiguous" disabled={ !quickOpts.Special }>
|
|
||||||
{ loc("settings@ambiguous") }
|
|
||||||
</fui.MenuItemCheckbox>
|
|
||||||
<fui.MenuItemCheckbox name="exclude" value="ExcludeRepeating">
|
|
||||||
{ loc("settings@repeating") }
|
|
||||||
</fui.MenuItemCheckbox>
|
|
||||||
</fui.MenuList>
|
|
||||||
</fui.MenuPopover>
|
|
||||||
</fui.Menu>
|
|
||||||
|
|
||||||
<fui.Tooltip content={ loc("generator@reset") } relationship="label">
|
|
||||||
<fui.Button appearance="subtle" icon={ <Icons.ArrowUndoRegular /> } onClick={ resetOptions } />
|
|
||||||
</fui.Tooltip>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -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("/wiki/Contribution-Guidelines#contributing-to-translations"),
|
|
||||||
License: getGithub("/blob/main/LICENSE")
|
|
||||||
};
|
|
||||||
|
|
||||||
export const GetFeedbackLink = () =>
|
|
||||||
{
|
|
||||||
if (__BROWSER__ === "firefox")
|
|
||||||
return WebstoreLink.Firefox;
|
|
||||||
|
|
||||||
const manifest: Manifest.WebExtensionManifest = browser.runtime.getManifest();
|
|
||||||
|
|
||||||
const updateUrl: URL = new URL((manifest as unknown as Record<string, unknown>).update_url as string ?? "about:blank");
|
|
||||||
|
|
||||||
if (updateUrl.host === "edge.microsoft.com")
|
|
||||||
return WebstoreLink.Edge;
|
|
||||||
if (updateUrl.host === "clients2.google.com")
|
|
||||||
return WebstoreLink.Chrome;
|
|
||||||
|
|
||||||
return "mailto:feedback@xfox111.net";
|
|
||||||
};
|
|
||||||
@@ -1,186 +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": "Default password length",
|
|
||||||
"description": "Password length label in settings section"
|
|
||||||
},
|
|
||||||
"settings@length__hint":
|
|
||||||
{
|
|
||||||
"message": "Recommended length: 8–16",
|
|
||||||
"description": "Password length recommendatin in settings section"
|
|
||||||
},
|
|
||||||
"settings@lengthRange":
|
|
||||||
{
|
|
||||||
"message": "Quick adjustment length range",
|
|
||||||
"description": "Quick adjustment length range label in settings section"
|
|
||||||
},
|
|
||||||
"settings@include":
|
|
||||||
{
|
|
||||||
"message": "Include symbols",
|
|
||||||
"description": "Include character sets section"
|
|
||||||
},
|
|
||||||
"settings@uppercase":
|
|
||||||
{
|
|
||||||
"message": "Uppercase",
|
|
||||||
"description": "Uppercase character set label in settings section"
|
|
||||||
},
|
|
||||||
"settings@lowercase":
|
|
||||||
{
|
|
||||||
"message": "Lowercase",
|
|
||||||
"description": "Lowercase character set label in settings section"
|
|
||||||
},
|
|
||||||
"settings@numeric":
|
|
||||||
{
|
|
||||||
"message": "Numeric",
|
|
||||||
"description": "Numbers character set label in settings section"
|
|
||||||
},
|
|
||||||
"settings@special":
|
|
||||||
{
|
|
||||||
"message": "Special",
|
|
||||||
"description": "Special characters character set label in settings section"
|
|
||||||
},
|
|
||||||
"settings@exclude":
|
|
||||||
{
|
|
||||||
"message": "Exclude symbols",
|
|
||||||
"description": "Exclude character sets section"
|
|
||||||
},
|
|
||||||
"settings@similar":
|
|
||||||
{
|
|
||||||
"message": "Similar",
|
|
||||||
"description": "Similar characters character set label in settings section"
|
|
||||||
},
|
|
||||||
"settings@ambiguous":
|
|
||||||
{
|
|
||||||
"message": "Ambiguous",
|
|
||||||
"description": "Ambiguous characters character set label in settings section"
|
|
||||||
},
|
|
||||||
"settings@repeating":
|
|
||||||
{
|
|
||||||
"message": "Repeating",
|
|
||||||
"description": "Repeating characters character set label in settings section"
|
|
||||||
},
|
|
||||||
"settings@repeating__hint":
|
|
||||||
{
|
|
||||||
"message": "Each character in the password will be unique",
|
|
||||||
"description": "Repeating characters character set hint in settings section"
|
|
||||||
},
|
|
||||||
"generator@copy":
|
|
||||||
{
|
|
||||||
"message": "Copy",
|
|
||||||
"description": "Copy button label"
|
|
||||||
},
|
|
||||||
"generator@reset":
|
|
||||||
{
|
|
||||||
"message": "Reset",
|
|
||||||
"description": "Reset button label"
|
|
||||||
},
|
|
||||||
"generator@refresh":
|
|
||||||
{
|
|
||||||
"message": "Generate new",
|
|
||||||
"description": "Generate new password button label"
|
|
||||||
},
|
|
||||||
"generator@quickOptions":
|
|
||||||
{
|
|
||||||
"message": "Quick adjustments",
|
|
||||||
"description": "Quick options section title"
|
|
||||||
},
|
|
||||||
"generator@quickOptions__hint":
|
|
||||||
{
|
|
||||||
"message": "These adjustments will not be saved",
|
|
||||||
"description": "Quick options hint"
|
|
||||||
},
|
|
||||||
"generator@include":
|
|
||||||
{
|
|
||||||
"message": "Include",
|
|
||||||
"description": "Include characters button label"
|
|
||||||
},
|
|
||||||
"generator@exclude":
|
|
||||||
{
|
|
||||||
"message": "Exclude",
|
|
||||||
"description": "Exclude characters button label"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,186 +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": "Domyślna długość hasła",
|
|
||||||
"description": "Password length label in settings section"
|
|
||||||
},
|
|
||||||
"settings@length__hint":
|
|
||||||
{
|
|
||||||
"message": "Zalecana długość: 8–16",
|
|
||||||
"description": "Password length recommendatin in settings section"
|
|
||||||
},
|
|
||||||
"settings@lengthRange":
|
|
||||||
{
|
|
||||||
"message": "Zakres długości dla szybkich ustawień",
|
|
||||||
"description": "Quick adjustment length range label in settings section"
|
|
||||||
},
|
|
||||||
"settings@include":
|
|
||||||
{
|
|
||||||
"message": "Dodaj znaki",
|
|
||||||
"description": "Include character sets section"
|
|
||||||
},
|
|
||||||
"settings@uppercase":
|
|
||||||
{
|
|
||||||
"message": "Wielkie",
|
|
||||||
"description": "Uppercase character set label in settings section"
|
|
||||||
},
|
|
||||||
"settings@lowercase":
|
|
||||||
{
|
|
||||||
"message": "Małe",
|
|
||||||
"description": "Lowercase character set label in settings section"
|
|
||||||
},
|
|
||||||
"settings@numeric":
|
|
||||||
{
|
|
||||||
"message": "Cyfry",
|
|
||||||
"description": "Numbers character set label in settings section"
|
|
||||||
},
|
|
||||||
"settings@special":
|
|
||||||
{
|
|
||||||
"message": "Specjalne",
|
|
||||||
"description": "Special characters character set label in settings section"
|
|
||||||
},
|
|
||||||
"settings@exclude":
|
|
||||||
{
|
|
||||||
"message": "Wyklucz znaki",
|
|
||||||
"description": "Exclude character sets section"
|
|
||||||
},
|
|
||||||
"settings@similar":
|
|
||||||
{
|
|
||||||
"message": "Podobne",
|
|
||||||
"description": "Similar characters character set label in settings section"
|
|
||||||
},
|
|
||||||
"settings@ambiguous":
|
|
||||||
{
|
|
||||||
"message": "Niebezpieczne",
|
|
||||||
"description": "Ambiguous characters character set label in settings section"
|
|
||||||
},
|
|
||||||
"settings@repeating":
|
|
||||||
{
|
|
||||||
"message": "Powtarzające się",
|
|
||||||
"description": "Repeating characters character set label in settings section"
|
|
||||||
},
|
|
||||||
"settings@repeating__hint":
|
|
||||||
{
|
|
||||||
"message": "Każdy znak hasła będzie unikalny",
|
|
||||||
"description": "Repeating characters character set hint in settings section"
|
|
||||||
},
|
|
||||||
"generator@copy":
|
|
||||||
{
|
|
||||||
"message": "Kopiuj",
|
|
||||||
"description": "Copy button label"
|
|
||||||
},
|
|
||||||
"generator@reset":
|
|
||||||
{
|
|
||||||
"message": "Resetuj",
|
|
||||||
"description": "Reset button label"
|
|
||||||
},
|
|
||||||
"generator@refresh":
|
|
||||||
{
|
|
||||||
"message": "Wygeneruj nowe",
|
|
||||||
"description": "Generate new password button label"
|
|
||||||
},
|
|
||||||
"generator@quickOptions":
|
|
||||||
{
|
|
||||||
"message": "Szybkie ustawienia",
|
|
||||||
"description": "Quick options section title"
|
|
||||||
},
|
|
||||||
"generator@quickOptions__hint":
|
|
||||||
{
|
|
||||||
"message": "Te ustawienia nie zostaną zapisane",
|
|
||||||
"description": "Quick options hint"
|
|
||||||
},
|
|
||||||
"generator@include":
|
|
||||||
{
|
|
||||||
"message": "Dodaj",
|
|
||||||
"description": "Include characters button label"
|
|
||||||
},
|
|
||||||
"generator@exclude":
|
|
||||||
{
|
|
||||||
"message": "Wyklucz",
|
|
||||||
"description": "Exclude characters button label"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,186 +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": "É necessário selecionar pelo menos um conjunto de caracteres",
|
|
||||||
"description": "Error message when no character set is selected"
|
|
||||||
},
|
|
||||||
"error@tooLong": {
|
|
||||||
"message": "O comprimento é muito longo para gerar uma senha com caracteres únicos",
|
|
||||||
"description": "Error message when password length is too long to generate password with unique characters"
|
|
||||||
},
|
|
||||||
"about@title":
|
|
||||||
{
|
|
||||||
"message": "Sobre",
|
|
||||||
"description": "About section title"
|
|
||||||
},
|
|
||||||
"about@developedBy":
|
|
||||||
{
|
|
||||||
"message": "Desenvolvido por Eugênio Fox",
|
|
||||||
"description": "Developer credits in about section"
|
|
||||||
},
|
|
||||||
"about@licensedUnder":
|
|
||||||
{
|
|
||||||
"message": "Licenciado sob",
|
|
||||||
"description": "License info in about section"
|
|
||||||
},
|
|
||||||
"about@mitLicense":
|
|
||||||
{
|
|
||||||
"message": "Licença MIT",
|
|
||||||
"description": "License name"
|
|
||||||
},
|
|
||||||
"about@translationCta":
|
|
||||||
{
|
|
||||||
"message": "Encontrou um erro ou quer uma tradução no seu idioma?",
|
|
||||||
"description": "Translation CTA in about section"
|
|
||||||
},
|
|
||||||
"about@translationCtaButton":
|
|
||||||
{
|
|
||||||
"message": "Comece aqui",
|
|
||||||
"description": "Translation CTA button in about section"
|
|
||||||
},
|
|
||||||
"about@website":
|
|
||||||
{
|
|
||||||
"message": "Meu site",
|
|
||||||
"description": "Website link in about section"
|
|
||||||
},
|
|
||||||
"about@sourceCode":
|
|
||||||
{
|
|
||||||
"message": "Código fonte",
|
|
||||||
"description": "Source code link in about section"
|
|
||||||
},
|
|
||||||
"about@changelog":
|
|
||||||
{
|
|
||||||
"message": "Registro de alterações",
|
|
||||||
"description": "Changelog link in about section"
|
|
||||||
},
|
|
||||||
"about@feedback":
|
|
||||||
{
|
|
||||||
"message": "Enviar comentários",
|
|
||||||
"description": "Feedback link in about section"
|
|
||||||
},
|
|
||||||
"about@sponsor":
|
|
||||||
{
|
|
||||||
"message": "Pague um café",
|
|
||||||
"description": "Buy me a coffee donation link in about section"
|
|
||||||
},
|
|
||||||
"settings@title":
|
|
||||||
{
|
|
||||||
"message": "Configurações",
|
|
||||||
"description": "Settings section title"
|
|
||||||
},
|
|
||||||
"settings@length":
|
|
||||||
{
|
|
||||||
"message": "Comprimento padrão da senha",
|
|
||||||
"description": "Password length label in settings section"
|
|
||||||
},
|
|
||||||
"settings@length__hint":
|
|
||||||
{
|
|
||||||
"message": "Comprimento recomendado: 8–16",
|
|
||||||
"description": "Password length recommendatin in settings section"
|
|
||||||
},
|
|
||||||
"settings@lengthRange":
|
|
||||||
{
|
|
||||||
"message": "Intervalo do comprimento de ajuste rápido",
|
|
||||||
"description": "Quick adjustment length range label in settings section"
|
|
||||||
},
|
|
||||||
"settings@include":
|
|
||||||
{
|
|
||||||
"message": "Incluir símbolos",
|
|
||||||
"description": "Include character sets section"
|
|
||||||
},
|
|
||||||
"settings@uppercase":
|
|
||||||
{
|
|
||||||
"message": "Maiúsculos",
|
|
||||||
"description": "Uppercase character set label in settings section"
|
|
||||||
},
|
|
||||||
"settings@lowercase":
|
|
||||||
{
|
|
||||||
"message": "Minúsculos",
|
|
||||||
"description": "Lowercase character set label in settings section"
|
|
||||||
},
|
|
||||||
"settings@numeric":
|
|
||||||
{
|
|
||||||
"message": "Numéricos",
|
|
||||||
"description": "Numbers character set label in settings section"
|
|
||||||
},
|
|
||||||
"settings@special":
|
|
||||||
{
|
|
||||||
"message": "Especiais",
|
|
||||||
"description": "Special characters character set label in settings section"
|
|
||||||
},
|
|
||||||
"settings@exclude":
|
|
||||||
{
|
|
||||||
"message": "Excluir símbolos",
|
|
||||||
"description": "Exclude character sets section"
|
|
||||||
},
|
|
||||||
"settings@similar":
|
|
||||||
{
|
|
||||||
"message": "Semelhantes",
|
|
||||||
"description": "Similar characters character set label in settings section"
|
|
||||||
},
|
|
||||||
"settings@ambiguous":
|
|
||||||
{
|
|
||||||
"message": "Ambíguos",
|
|
||||||
"description": "Ambiguous characters character set label in settings section"
|
|
||||||
},
|
|
||||||
"settings@repeating":
|
|
||||||
{
|
|
||||||
"message": "Repetidos",
|
|
||||||
"description": "Repeating characters character set label in settings section"
|
|
||||||
},
|
|
||||||
"settings@repeating__hint":
|
|
||||||
{
|
|
||||||
"message": "Cada caractere na senha será único",
|
|
||||||
"description": "Repeating characters character set hint in settings section"
|
|
||||||
},
|
|
||||||
"generator@copy":
|
|
||||||
{
|
|
||||||
"message": "Copiar",
|
|
||||||
"description": "Copy button label"
|
|
||||||
},
|
|
||||||
"generator@reset":
|
|
||||||
{
|
|
||||||
"message": "Redefinir",
|
|
||||||
"description": "Reset button label"
|
|
||||||
},
|
|
||||||
"generator@refresh":
|
|
||||||
{
|
|
||||||
"message": "Gerar nova",
|
|
||||||
"description": "Generate new password button label"
|
|
||||||
},
|
|
||||||
"generator@quickOptions":
|
|
||||||
{
|
|
||||||
"message": "Ajustes rápidos",
|
|
||||||
"description": "Quick options section title"
|
|
||||||
},
|
|
||||||
"generator@quickOptions__hint":
|
|
||||||
{
|
|
||||||
"message": "Esses ajustes não serão salvos",
|
|
||||||
"description": "Quick options hint"
|
|
||||||
},
|
|
||||||
"generator@include":
|
|
||||||
{
|
|
||||||
"message": "Incluir",
|
|
||||||
"description": "Include characters button label"
|
|
||||||
},
|
|
||||||
"generator@exclude":
|
|
||||||
{
|
|
||||||
"message": "Excluir",
|
|
||||||
"description": "Exclude characters button label"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,186 +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@lengthRange":
|
|
||||||
{
|
|
||||||
"message": "Диапазон длины для быстрых настроек",
|
|
||||||
"description": "Quick adjustment length range label in settings section"
|
|
||||||
},
|
|
||||||
"settings@include":
|
|
||||||
{
|
|
||||||
"message": "Добавить символы",
|
|
||||||
"description": "Include character sets section"
|
|
||||||
},
|
|
||||||
"settings@uppercase":
|
|
||||||
{
|
|
||||||
"message": "Прописные",
|
|
||||||
"description": "Uppercase character set label in settings section"
|
|
||||||
},
|
|
||||||
"settings@lowercase":
|
|
||||||
{
|
|
||||||
"message": "Строчные",
|
|
||||||
"description": "Lowercase character set label in settings section"
|
|
||||||
},
|
|
||||||
"settings@numeric":
|
|
||||||
{
|
|
||||||
"message": "Числовые",
|
|
||||||
"description": "Numbers character set label in settings section"
|
|
||||||
},
|
|
||||||
"settings@special":
|
|
||||||
{
|
|
||||||
"message": "Специальные",
|
|
||||||
"description": "Special characters character set label in settings section"
|
|
||||||
},
|
|
||||||
"settings@exclude":
|
|
||||||
{
|
|
||||||
"message": "Исключить символы",
|
|
||||||
"description": "Exclude character sets section"
|
|
||||||
},
|
|
||||||
"settings@similar":
|
|
||||||
{
|
|
||||||
"message": "Похожие",
|
|
||||||
"description": "Similar characters character set label in settings section"
|
|
||||||
},
|
|
||||||
"settings@ambiguous":
|
|
||||||
{
|
|
||||||
"message": "Особые",
|
|
||||||
"description": "Ambiguous characters character set label in settings section"
|
|
||||||
},
|
|
||||||
"settings@repeating":
|
|
||||||
{
|
|
||||||
"message": "Повторяющиеся",
|
|
||||||
"description": "Repeating characters character set label in settings section"
|
|
||||||
},
|
|
||||||
"settings@repeating__hint":
|
|
||||||
{
|
|
||||||
"message": "Каждый символ пароля будет уникальным",
|
|
||||||
"description": "Repeating characters character set hint in settings section"
|
|
||||||
},
|
|
||||||
"generator@copy":
|
|
||||||
{
|
|
||||||
"message": "Копировать",
|
|
||||||
"description": "Copy button label"
|
|
||||||
},
|
|
||||||
"generator@reset":
|
|
||||||
{
|
|
||||||
"message": "Сбросить",
|
|
||||||
"description": "Reset button label"
|
|
||||||
},
|
|
||||||
"generator@refresh":
|
|
||||||
{
|
|
||||||
"message": "Сгенерировать новый",
|
|
||||||
"description": "Generate new password button label"
|
|
||||||
},
|
|
||||||
"generator@quickOptions":
|
|
||||||
{
|
|
||||||
"message": "Быстрые настройки",
|
|
||||||
"description": "Quick options section title"
|
|
||||||
},
|
|
||||||
"generator@quickOptions__hint":
|
|
||||||
{
|
|
||||||
"message": "Эти настройки не будут сохранены",
|
|
||||||
"description": "Quick options hint"
|
|
||||||
},
|
|
||||||
"generator@include":
|
|
||||||
{
|
|
||||||
"message": "Добавить",
|
|
||||||
"description": "Include characters button label"
|
|
||||||
},
|
|
||||||
"generator@exclude":
|
|
||||||
{
|
|
||||||
"message": "Исключить",
|
|
||||||
"description": "Exclude characters button label"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,186 +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@lengthRange":
|
|
||||||
{
|
|
||||||
"message": "Діапазон довжини для швидких налаштувань",
|
|
||||||
"description": "Quick adjustment length range label in settings section"
|
|
||||||
},
|
|
||||||
"settings@include":
|
|
||||||
{
|
|
||||||
"message": "Додати символи",
|
|
||||||
"description": "Include character sets section"
|
|
||||||
},
|
|
||||||
"settings@uppercase":
|
|
||||||
{
|
|
||||||
"message": "Великі",
|
|
||||||
"description": "Uppercase character set label in settings section"
|
|
||||||
},
|
|
||||||
"settings@lowercase":
|
|
||||||
{
|
|
||||||
"message": "Малі",
|
|
||||||
"description": "Lowercase character set label in settings section"
|
|
||||||
},
|
|
||||||
"settings@numeric":
|
|
||||||
{
|
|
||||||
"message": "Числові",
|
|
||||||
"description": "Numbers character set label in settings section"
|
|
||||||
},
|
|
||||||
"settings@special":
|
|
||||||
{
|
|
||||||
"message": "Спеціальні",
|
|
||||||
"description": "Special characters character set label in settings section"
|
|
||||||
},
|
|
||||||
"settings@exclude":
|
|
||||||
{
|
|
||||||
"message": "Виключити символи",
|
|
||||||
"description": "Exclude character sets section"
|
|
||||||
},
|
|
||||||
"settings@similar":
|
|
||||||
{
|
|
||||||
"message": "Схожі",
|
|
||||||
"description": "Similar characters character set label in settings section"
|
|
||||||
},
|
|
||||||
"settings@ambiguous":
|
|
||||||
{
|
|
||||||
"message": "Особливі",
|
|
||||||
"description": "Ambiguous characters character set label in settings section"
|
|
||||||
},
|
|
||||||
"settings@repeating":
|
|
||||||
{
|
|
||||||
"message": "Повторювані",
|
|
||||||
"description": "Repeating characters character set label in settings section"
|
|
||||||
},
|
|
||||||
"settings@repeating__hint":
|
|
||||||
{
|
|
||||||
"message": "Кожен символ пароля буде унікальним",
|
|
||||||
"description": "Repeating characters character set hint in settings section"
|
|
||||||
},
|
|
||||||
"generator@copy":
|
|
||||||
{
|
|
||||||
"message": "Копіювати",
|
|
||||||
"description": "Copy button label"
|
|
||||||
},
|
|
||||||
"generator@reset":
|
|
||||||
{
|
|
||||||
"message": "Скинути",
|
|
||||||
"description": "Reset button label"
|
|
||||||
},
|
|
||||||
"generator@refresh":
|
|
||||||
{
|
|
||||||
"message": "Згенерувати новий",
|
|
||||||
"description": "Generate new password button label"
|
|
||||||
},
|
|
||||||
"generator@quickOptions":
|
|
||||||
{
|
|
||||||
"message": "Швидкі налаштування",
|
|
||||||
"description": "Quick options section title"
|
|
||||||
},
|
|
||||||
"generator@quickOptions__hint":
|
|
||||||
{
|
|
||||||
"message": "Ці налаштування не будуть збережені",
|
|
||||||
"description": "Quick options hint"
|
|
||||||
},
|
|
||||||
"generator@include":
|
|
||||||
{
|
|
||||||
"message": "Додати",
|
|
||||||
"description": "Include characters button label"
|
|
||||||
},
|
|
||||||
"generator@exclude":
|
|
||||||
{
|
|
||||||
"message": "Виключити",
|
|
||||||
"description": "Exclude characters button label"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
import { mergeClasses } from "@fluentui/react-components";
|
|
||||||
import { useStyles } from "./Snow.styles";
|
|
||||||
|
|
||||||
const SNOWFLAKES_NUM: number = 100;
|
|
||||||
|
|
||||||
export default function Snow(): JSX.Element
|
|
||||||
{
|
|
||||||
const cls = useStyles(SNOWFLAKES_NUM)();
|
|
||||||
|
|
||||||
if (![0, 11].includes(new Date().getMonth()))
|
|
||||||
return <></>;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={ cls.snow }>
|
|
||||||
{ [...Array(SNOWFLAKES_NUM)].map((_, i) => <div key={ i } className={ mergeClasses(cls.snowflake, cls[`snowflake-${i}`]) } />) }
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -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,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": {
|
"compilerOptions": {
|
||||||
"target": "ESNext",
|
"allowImportingTsExtensions": true,
|
||||||
"useDefineForClassFields": true,
|
"jsx": "react-jsx"
|
||||||
"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"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 { GeneratorOptions } from "./storage";
|
||||||
import { GetLocaleString as loc } from "./Localization";
|
|
||||||
|
|
||||||
const Characters =
|
const Characters =
|
||||||
{
|
{
|
||||||
@@ -49,15 +48,15 @@ export function GeneratePassword(options: GeneratorOptions): string
|
|||||||
export function ValidateOptions(options: GeneratorOptions): void
|
export function ValidateOptions(options: GeneratorOptions): void
|
||||||
{
|
{
|
||||||
if (options.Length < 4)
|
if (options.Length < 4)
|
||||||
throw new Error(loc("error@moreCharacters"));
|
throw new Error(i18n.t("errors.too_short"));
|
||||||
|
|
||||||
const availableCharacters: string = GetAvailableCharacters(options);
|
const availableCharacters: string = GetAvailableCharacters(options);
|
||||||
|
|
||||||
if (availableCharacters.length < 1)
|
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)
|
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
|
// 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";
|
||||||
|
};
|
||||||
@@ -2,4 +2,5 @@ export default class ExtensionOptions
|
|||||||
{
|
{
|
||||||
public MinLength: number = 4;
|
public MinLength: number = 4;
|
||||||
public MaxLength: number = 32;
|
public MaxLength: number = 32;
|
||||||
|
public ContextMenu: boolean = true;
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
import React, { createContext, useContext, useEffect, useState } from "react";
|
import React, { createContext, useContext, useEffect, useState } from "react";
|
||||||
import browser from "webextension-polyfill";
|
import ExtensionOptions from "./ExtensionOptions";
|
||||||
import ExtensionOptions from "../Models/ExtensionOptions";
|
import GeneratorOptions from "./GeneratorOptions";
|
||||||
import GeneratorOptions from "../Models/GeneratorOptions";
|
|
||||||
|
|
||||||
const defaultOptions: IStorage =
|
const defaultOptions: IStorage =
|
||||||
{
|
{
|
||||||
@@ -14,7 +13,7 @@ const Storage = createContext<IStorage>(defaultOptions);
|
|||||||
|
|
||||||
export const useStorage = () => useContext<IStorage>(Storage);
|
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);
|
const [storage, setStorage] = useState<IStorage | null>(null);
|
||||||
|
|
||||||
@@ -37,7 +36,7 @@ export function StorageProvider(props: IStorageProviderProps): JSX.Element
|
|||||||
{ storage ? props.children : props.loader }
|
{ storage ? props.children : props.loader }
|
||||||
</Storage.Provider>
|
</Storage.Provider>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
// #region Types
|
// #region Types
|
||||||
interface IStorage
|
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";
|
import { useState } from "react";
|
||||||
|
|
||||||
export function useTimeout(timeout: number)
|
export default function useTimeout(timeout: number)
|
||||||
{
|
{
|
||||||
const [isActive, setActive] = useState<boolean>(false);
|
const [isActive, setActive] = useState<boolean>(false);
|
||||||
|
|
||||||
@@ -1,61 +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"
|
|
||||||
),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
build:
|
|
||||||
{
|
|
||||||
chunkSizeWarningLimit: 1000,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
});
|
||||||