mirror of
https://github.com/XFox111/PasswordGeneratorExtension.git
synced 2026-07-02 19:52:43 +03:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 01d048e298 | |||
| 0b47bf93aa | |||
| 53856a8b3b | |||
| 2c70ca2490 | |||
| 2731ccbff2 | |||
| cf99eb3b52 | |||
| 59d57d99a9 | |||
| 16613253ad | |||
| db8c99be0a |
@@ -16,7 +16,6 @@ body:
|
||||
label: Description
|
||||
description: A clear and concise description of what the bug is.
|
||||
placeholder: e.g. Sometimes when generating a password not all character sets are included
|
||||
render: Markdown
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@@ -29,7 +28,6 @@ body:
|
||||
2. Click on '...'
|
||||
3. Scroll down to '...'
|
||||
4. See '...'
|
||||
render: Markdown
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@@ -38,7 +36,6 @@ body:
|
||||
label: Expected behavior
|
||||
description: A clear and concise description of what you expected to happen.
|
||||
placeholder: e.g. Generated password should include at least one character from every enabled character set
|
||||
render: Markdown
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@@ -46,7 +43,6 @@ body:
|
||||
attributes:
|
||||
label: Screenshot
|
||||
description: If applicable, add screenshots to help explain your problem.
|
||||
render: Markdown
|
||||
validations:
|
||||
required: false
|
||||
|
||||
@@ -55,7 +51,7 @@ body:
|
||||
attributes:
|
||||
label: Operating system
|
||||
options:
|
||||
- "Windows 10+"
|
||||
- "Windows 10 and newer"
|
||||
- "Windows 8/8.1"
|
||||
- "Windows 7 and older"
|
||||
- "MacOS"
|
||||
@@ -86,7 +82,6 @@ body:
|
||||
attributes:
|
||||
label: Additional context
|
||||
description: Add any other context about the problem here.
|
||||
render: Markdown
|
||||
validations:
|
||||
required: false
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@ body:
|
||||
attributes:
|
||||
label: Proposed solution
|
||||
description: Describe the solution you'd like
|
||||
render: Markdown
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@@ -24,7 +23,6 @@ body:
|
||||
attributes:
|
||||
label: Justification
|
||||
description: Is your feature request related to a problem? Please describe.
|
||||
render: Markdown
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@@ -33,7 +31,6 @@ body:
|
||||
attributes:
|
||||
label: Alternatives
|
||||
description: Describe alternatives you've considered.
|
||||
render: Markdown
|
||||
validations:
|
||||
required: true
|
||||
|
||||
@@ -42,7 +39,6 @@ body:
|
||||
attributes:
|
||||
label: Additional context
|
||||
description: Add any other context or screenshots about the feature request here.
|
||||
render: Markdown
|
||||
validations:
|
||||
required: false
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ updates:
|
||||
schedule:
|
||||
interval: monthly
|
||||
rebase-strategy: disabled
|
||||
open-pull-requests-limit: 20
|
||||
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
@@ -29,3 +30,4 @@ updates:
|
||||
schedule:
|
||||
interval: monthly
|
||||
rebase-strategy: disabled
|
||||
open-pull-requests-limit: 20
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
<!-->> ## 🚀 Patch Tuesday update
|
||||
<!-- > ## 🚀 Patch Tuesday update
|
||||
> This release is a part of our new initiative!
|
||||
From now on we are starting to roll out updates on every first Tuesday of the month, which will include bugfixes, security and dependency updates to keep the project's security and stability up to date!
|
||||
-->
|
||||
|
||||
## What's new
|
||||
<!-- - Dependency updates and security patches (#) -->
|
||||
|
||||
<!-->### Fixed security issues in this update
|
||||
<!-- ### Fixed security issues in this update
|
||||
- [CWE-20](https://cwe.mitre.org/data/definitions/20.html)
|
||||
- CVE-2022-25883
|
||||
-->
|
||||
|
||||
@@ -60,16 +60,16 @@ jobs:
|
||||
extension-root: dist
|
||||
self-hosted: false
|
||||
|
||||
- uses: TheDoctor0/zip-release@main
|
||||
- uses: cardinalby/webext-buildtools-pack-extension-dir-action@1.0.8
|
||||
with:
|
||||
filename: packed-${{ matrix.target }}.zip
|
||||
path: dist
|
||||
extensionDir: dist
|
||||
zipFilePath: PasswordGenerator-${{ matrix.target }}.zip
|
||||
|
||||
- name: Drop packed artifacts (${{ matrix.target }})
|
||||
uses: actions/upload-artifact@main
|
||||
with:
|
||||
name: packed-${{ matrix.target }}
|
||||
path: packed-${{ matrix.target }}.zip
|
||||
path: PasswordGenerator-${{ matrix.target }}.zip
|
||||
|
||||
publish-github:
|
||||
needs: build
|
||||
@@ -105,10 +105,10 @@ jobs:
|
||||
with:
|
||||
name: packed-chrome
|
||||
|
||||
- uses: wdzeng/chrome-extension@main
|
||||
- uses: wdzeng/chrome-extension@v1.2.2
|
||||
with:
|
||||
extension-id: jnjobgjobffgmgfnkpkjfjkkfhfikmfl
|
||||
zip-path: packed-chrome.zip
|
||||
zip-path: PasswordGenerator-chrome.zip
|
||||
client-id: ${{ secrets.CHROME_CLIENT_ID }}
|
||||
client-secret: ${{ secrets.CHROME_CLIENT_SECRET }}
|
||||
refresh-token: ${{ secrets.CHROME_REFRESH_TOKEN }}
|
||||
@@ -123,10 +123,10 @@ jobs:
|
||||
with:
|
||||
name: packed-chrome
|
||||
|
||||
- uses: wdzeng/edge-addon@main
|
||||
- uses: wdzeng/edge-addon@v1.2.3
|
||||
with:
|
||||
product-id: ${{ secrets.EDGE_PRODUCT_ID }}
|
||||
zip-path: packed-chrome.zip
|
||||
zip-path: PasswordGenerator-chrome.zip
|
||||
client-id: ${{ secrets.EDGE_CLIENT_ID }}
|
||||
client-secret: ${{ secrets.EDGE_CLIENT_SECRET }}
|
||||
access-token-url: ${{ secrets.EDGE_ACCESS_TOKEN_URL }}
|
||||
@@ -141,9 +141,9 @@ jobs:
|
||||
with:
|
||||
name: packed-firefox
|
||||
|
||||
- uses: wdzeng/firefox-addon@main
|
||||
- uses: wdzeng/firefox-addon@v1.0.4
|
||||
with:
|
||||
addon-guid: ${{ secrets.FIREFOX_EXT_UUID }}
|
||||
xpi-path: packed-firefox.zip
|
||||
xpi-path: PasswordGenerator-firefox.zip
|
||||
jwt-issuer: ${{ secrets.FIREFOX_API_KEY }}
|
||||
jwt-secret: ${{ secrets.FIREFOX_CLIENT_SECRET }}
|
||||
|
||||
@@ -56,7 +56,7 @@ jobs:
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
uses: github/codeql-action/init@v3
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
@@ -70,7 +70,7 @@ jobs:
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
uses: github/codeql-action/autobuild@v3
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||
@@ -83,4 +83,4 @@ jobs:
|
||||
# ./location_of_script_within_repo/buildscript.sh
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
||||
uses: github/codeql-action/analyze@v3
|
||||
|
||||
@@ -22,10 +22,10 @@ jobs:
|
||||
extver=`jq -r ".version" package.json`
|
||||
echo "version=$extver" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- uses: dev-build-deploy/release-me@v0.15.0
|
||||
- uses: dev-build-deploy/release-me@v0.15.2
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
prefix: v
|
||||
draft: true
|
||||
version: ${{ steps.get_version.outputs.version }}
|
||||
version: v${{ steps.get_version.outputs.version }}
|
||||
release-notes: .github/release_description_template.md
|
||||
|
||||
Vendored
+1
-1
@@ -15,7 +15,7 @@
|
||||
"editor.insertSpaces": false,
|
||||
"files.insertFinalNewline": true,
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.organizeImports": true
|
||||
"source.organizeImports": "explicit"
|
||||
},
|
||||
"files.eol": "\n",
|
||||
"files.trimFinalNewlines": true,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 Eugene Fox
|
||||
Copyright (c) 2024 Eugene Fox
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -90,4 +90,4 @@ If you are interested in fixing issues and contributing directly to the code bas
|
||||
[](https://github.com/xfox111)
|
||||
[](https://buymeacoffee.com/xfox111)
|
||||
|
||||
> ©2023 Eugene Fox
|
||||
> ©2024 Eugene Fox
|
||||
|
||||
+18
-18
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "password-generator",
|
||||
"version": "3.0.0",
|
||||
"version": "3.1.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
@@ -9,32 +9,32 @@
|
||||
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fluentui/react-components": "^9.39.0",
|
||||
"@fluentui/react-icons": "^2.0.222",
|
||||
"@fluentui/react-components": "^9.46.4",
|
||||
"@fluentui/react-icons": "^2.0.226",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.2.37",
|
||||
"@types/react-dom": "^18.2.15",
|
||||
"@types/webextension-polyfill": "^0.10.6",
|
||||
"@typescript-eslint/eslint-plugin": "^6.10.0",
|
||||
"@typescript-eslint/parser": "^6.10.0",
|
||||
"@vitejs/plugin-react-swc": "^3.4.1",
|
||||
"eslint": "^8.53.0",
|
||||
"@types/react": "^18.2.55",
|
||||
"@types/react-dom": "^18.2.19",
|
||||
"@types/webextension-polyfill": "^0.10.7",
|
||||
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
||||
"@typescript-eslint/parser": "^6.21.0",
|
||||
"@vitejs/plugin-react-swc": "^3.6.0",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-plugin-react": "^7.33.2",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.4",
|
||||
"sass": "^1.69.5",
|
||||
"typescript": "^5.2.2",
|
||||
"vite": "^4.5.0",
|
||||
"vite-plugin-static-copy": "^0.17.0",
|
||||
"vite-plugin-svgr": "^4.1.0",
|
||||
"vite-plugin-web-extension": "^3.0.1",
|
||||
"eslint-plugin-react-refresh": "^0.4.5",
|
||||
"typescript": "^5.3.3",
|
||||
"vite": "^5.1.0",
|
||||
"vite-plugin-static-copy": "^1.0.1",
|
||||
"vite-plugin-svgr": "^4.2.0",
|
||||
"vite-plugin-web-extension": "^4.1.1",
|
||||
"webextension-polyfill": "^0.10.0"
|
||||
},
|
||||
"resolutions": {
|
||||
"postcss": "^8.4.31",
|
||||
"tough-cookie": "^4.1.3"
|
||||
"tough-cookie": "^4.1.3",
|
||||
"scheduler": "^0.20.0"
|
||||
}
|
||||
}
|
||||
|
||||
+8
-2
@@ -6,18 +6,24 @@ import SettingsSection from "./Components/SettingsSection";
|
||||
import Specials from "./Specials/Specials";
|
||||
import { StorageProvider } from "./Utils/Storage";
|
||||
import { useTheme } from "./Utils/Theme";
|
||||
import { useState } from "react";
|
||||
|
||||
export default function App(): JSX.Element
|
||||
{
|
||||
const theme = useTheme();
|
||||
const cls = useStyles();
|
||||
const [selection, setSelection] = useState<string[]>([]);
|
||||
|
||||
return (
|
||||
<FluentProvider theme={ theme }>
|
||||
<main className={ cls.root }>
|
||||
<StorageProvider loader={ <Spinner size="large" className={ cls.spinner } /> }>
|
||||
<GeneratorView />
|
||||
<Accordion collapsible className={ cls.accordionAnimation }>
|
||||
<GeneratorView collapse={ selection.includes("settings") } />
|
||||
<Accordion
|
||||
openItems={ selection }
|
||||
onToggle={ (_, e) => setSelection(e.openItems as string[]) }
|
||||
collapsible
|
||||
className={ cls.accordionAnimation }>
|
||||
<SettingsSection />
|
||||
<AboutSection />
|
||||
</Accordion>
|
||||
|
||||
@@ -10,7 +10,7 @@ import { useStyles } from "./AboutSection.styles";
|
||||
|
||||
export default function AboutSection(): JSX.Element
|
||||
{
|
||||
const theme = useTheme(bmcLightTheme, bmcDarkTheme);
|
||||
const bmcTheme = useTheme(bmcLightTheme, bmcDarkTheme);
|
||||
const cls = useStyles();
|
||||
|
||||
const link = (text: string, href: string): JSX.Element => (
|
||||
@@ -54,7 +54,7 @@ export default function AboutSection(): JSX.Element
|
||||
<fui.Button { ...buttonProps(GetFeedbackLink(), <PersonFeedbackRegular />) }>
|
||||
{ loc("about@feedback") }
|
||||
</fui.Button>
|
||||
<fui.FluentProvider theme={ theme }>
|
||||
<fui.FluentProvider theme={ bmcTheme }>
|
||||
<fui.Button { ...buttonProps(PersonalLink.BuyMeACoffee, <BuyMeACoffee />) }>
|
||||
{ loc("about@sponsor") }
|
||||
</fui.Button>
|
||||
|
||||
@@ -17,17 +17,6 @@ export const useStyles = makeStyles({
|
||||
alignItems: "center",
|
||||
paddingRight: tokens.spacingHorizontalM,
|
||||
},
|
||||
optionsSpacing:
|
||||
{
|
||||
...shorthands.padding(tokens.spacingVerticalS, tokens.spacingHorizontalS),
|
||||
},
|
||||
optionsLabel:
|
||||
{
|
||||
"> div[role=note].fui-InfoButton__info":
|
||||
{
|
||||
zIndex: 1,
|
||||
},
|
||||
},
|
||||
copyIcon:
|
||||
{
|
||||
animationName: "scaleUpIn",
|
||||
@@ -39,5 +28,20 @@ export const useStyles = makeStyles({
|
||||
animationName: "spin",
|
||||
animationDuration: tokens.durationSlow,
|
||||
animationTimingFunction: tokens.curveEasyEaseMax,
|
||||
}
|
||||
},
|
||||
msgBar:
|
||||
{
|
||||
...shorthands.padding(tokens.spacingVerticalMNudge, tokens.spacingHorizontalM),
|
||||
},
|
||||
options:
|
||||
{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
...shorthands.padding(tokens.spacingVerticalS, tokens.spacingHorizontalS),
|
||||
},
|
||||
characterOptionsContainer:
|
||||
{
|
||||
display: "flex",
|
||||
...shorthands.gap(tokens.spacingHorizontalXS),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Button, Checkbox, Input, Slider, Text, Tooltip, mergeClasses } from "@fluentui/react-components";
|
||||
import { Alert, InfoLabel } from "@fluentui/react-components/unstable";
|
||||
import { ArrowClockwiseRegular, ArrowUndoRegular, CheckmarkRegular, CopyRegular } from "@fluentui/react-icons";
|
||||
import * 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";
|
||||
@@ -9,22 +8,24 @@ import { useStorage } from "../Utils/Storage";
|
||||
import { useTimeout } from "../Utils/Timeout";
|
||||
import { useStyles } from "./GeneratorView.styles";
|
||||
|
||||
type QuickOptions = Pick<GeneratorOptions, "Length" | "Special" | "ExcludeAmbiguous">;
|
||||
|
||||
export default function GeneratorView(): JSX.Element
|
||||
export default function GeneratorView(props: { collapse: boolean; }): JSX.Element
|
||||
{
|
||||
const { generatorOptions } = useStorage();
|
||||
const { generatorOptions, extOptions } = useStorage();
|
||||
const [password, setPassword] = useState<string>("");
|
||||
const [quickOpts, _setOpts] = useState<QuickOptions>(generatorOptions);
|
||||
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<QuickOptions>) =>
|
||||
const setOptions = (opts: Partial<GeneratorOptions>) =>
|
||||
_setOpts({ ...quickOpts, ...opts });
|
||||
|
||||
function RefreshPassword(): void
|
||||
@@ -42,64 +43,129 @@ export default function GeneratorView(): JSX.Element
|
||||
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]);
|
||||
|
||||
const actionButtons: JSX.Element = <>
|
||||
<Tooltip content={ loc("generator@copy") } relationship="label">
|
||||
<Button
|
||||
appearance="subtle" onClick={ CopyPassword }
|
||||
icon={
|
||||
copyTimer.isActive ?
|
||||
<CheckmarkRegular className={ cls.copyIcon } /> :
|
||||
<CopyRegular className={ cls.copyIcon } />
|
||||
} />
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip content={ loc("generator@refresh") } relationship="label">
|
||||
<Button
|
||||
appearance="subtle" onClick={ RefreshPassword }
|
||||
icon={
|
||||
<ArrowClockwiseRegular className={ mergeClasses(refreshTimer.isActive && cls.refreshIcon) } />
|
||||
} />
|
||||
</Tooltip>
|
||||
</>;
|
||||
|
||||
return (
|
||||
<section className={ cls.root }>
|
||||
{ error ?
|
||||
<Alert intent="warning">{ error }</Alert> :
|
||||
<Input readOnly contentAfter={ actionButtons } value={ password } className={ cls.input } />
|
||||
<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>
|
||||
</> } />
|
||||
}
|
||||
|
||||
<div className={ mergeClasses(cls.root, cls.optionsSpacing) }>
|
||||
<InfoLabel info={ loc("generator@quickOptions__hint") } className={ cls.optionsLabel }>
|
||||
{ loc("generator@quickOptions") }
|
||||
</InfoLabel>
|
||||
{ !props.collapse &&
|
||||
<div className={ cls.options }>
|
||||
<fui.InfoLabel info={ loc("generator@quickOptions__hint") }>
|
||||
{ loc("generator@quickOptions") }
|
||||
</fui.InfoLabel>
|
||||
|
||||
<div className={ cls.lengthContainer }>
|
||||
<Slider
|
||||
min={ 6 } max={ Math.max(32, generatorOptions.Length) }
|
||||
value={ quickOpts.Length } onChange={ (_, e) => setOptions({ Length: e.value }) } />
|
||||
<Text>{ quickOpts.Length }</Text>
|
||||
<div 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>
|
||||
|
||||
<div>
|
||||
<Checkbox
|
||||
label={ loc("settings@special") }
|
||||
checked={ quickOpts.Special }
|
||||
onChange={ (_, e) => setOptions({ Special: e.checked as boolean }) } />
|
||||
<Checkbox
|
||||
label={ loc("settings@ambiguous") } disabled={ !quickOpts.Special }
|
||||
checked={ !quickOpts.ExcludeAmbiguous }
|
||||
onChange={ (_, e) => setOptions({ ExcludeAmbiguous: !e.checked }) } />
|
||||
|
||||
<Tooltip content={ loc("generator@reset") } relationship="label">
|
||||
<Button appearance="subtle" icon={ <ArrowUndoRegular /> } onClick={ resetOptions } />
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -12,4 +12,15 @@ export const useStyles = makeStyles({
|
||||
display: "flex",
|
||||
flexWrap: "wrap",
|
||||
},
|
||||
rangeContainer:
|
||||
{
|
||||
display: "grid",
|
||||
gridTemplateColumns: "1fr auto 1fr auto",
|
||||
alignItems: "center",
|
||||
...shorthands.gap(tokens.spacingHorizontalS),
|
||||
},
|
||||
rangeInput:
|
||||
{
|
||||
width: "100%",
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import * as fui from "@fluentui/react-components";
|
||||
import { InfoLabel } from "@fluentui/react-components/unstable";
|
||||
import { 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";
|
||||
@@ -13,12 +12,12 @@ import { useStyles } from "./SettingsSection.styles";
|
||||
|
||||
export default function SettingsSection(): JSX.Element
|
||||
{
|
||||
const { generatorOptions, updateStorage } = useStorage();
|
||||
const { extOptions, generatorOptions, updateStorage } = useStorage();
|
||||
const cls = useStyles();
|
||||
|
||||
const infoLabel = (content: string, hint: string) => ({
|
||||
children: (_: unknown, slotProps: fui.LabelProps) => (
|
||||
<InfoLabel { ...slotProps } info={ hint }>{ content }</InfoLabel>
|
||||
<fui.InfoLabel { ...slotProps } info={ hint }>{ content }</fui.InfoLabel>
|
||||
)
|
||||
});
|
||||
|
||||
@@ -26,6 +25,21 @@ export default function SettingsSection(): JSX.Element
|
||||
(_: unknown, args: fui.CheckboxOnChangeData) =>
|
||||
updateStorage({ [option]: args.checked } );
|
||||
|
||||
const updateNumberField = (key: keyof (ExtensionOptions & GeneratorOptions), defaultValue: number) =>
|
||||
(_: unknown, e: fui.InputOnChangeData): void =>
|
||||
{
|
||||
if (e.value.length < 1)
|
||||
{
|
||||
updateStorage({ [key]: defaultValue });
|
||||
return;
|
||||
}
|
||||
|
||||
const value = parseInt(e.value);
|
||||
|
||||
if (!isNaN(value) && value >= 0)
|
||||
updateStorage({ [key]: value });
|
||||
};
|
||||
|
||||
return (
|
||||
<fui.AccordionItem value="settings">
|
||||
<fui.AccordionHeader as="h2" icon={ <SettingsRegular /> }>{ loc("settings@title") }</fui.AccordionHeader>
|
||||
@@ -34,9 +48,30 @@ export default function SettingsSection(): JSX.Element
|
||||
|
||||
<fui.Field label={ loc("settings@length") } hint={ loc("settings@length__hint") }>
|
||||
<fui.Input
|
||||
type="number" min={ 6 }
|
||||
value={ generatorOptions.Length.toString() }
|
||||
onChange={ (_, e) => updateStorage({ Length: parseInt(e.value) }) } />
|
||||
onChange={ updateNumberField("Length", 0) } />
|
||||
</fui.Field>
|
||||
|
||||
<fui.Field label={ loc("settings@lengthRange") }>
|
||||
<div className={ cls.rangeContainer }>
|
||||
<fui.Input
|
||||
input={ { className: cls.rangeInput } }
|
||||
value={ extOptions.MinLength.toString() }
|
||||
onChange={ updateNumberField("MinLength", 4) } />
|
||||
|
||||
<fui.Divider />
|
||||
|
||||
<fui.Input
|
||||
input={ { className: cls.rangeInput } }
|
||||
value={ extOptions.MaxLength.toString() }
|
||||
onChange={ updateNumberField("MaxLength", 32) } />
|
||||
|
||||
<fui.Tooltip relationship="label" content={ loc("generator@reset") }>
|
||||
<fui.Button
|
||||
appearance="subtle" icon={ <ArrowUndoRegular /> }
|
||||
onClick={ () => updateStorage({ MinLength: 6, MaxLength: 32 }) } />
|
||||
</fui.Tooltip>
|
||||
</div>
|
||||
</fui.Field>
|
||||
|
||||
<fui.Divider />
|
||||
|
||||
+2
-2
@@ -15,13 +15,13 @@ export const WebstoreLink =
|
||||
};
|
||||
|
||||
const getGithub = (path?: string): string =>
|
||||
`https://github.com/xfox111/PasswordGeneratorExtension${path}`;
|
||||
`https://github.com/xfox111/PasswordGeneratorExtension${path ?? ""}`;
|
||||
|
||||
export const GithubLink =
|
||||
{
|
||||
Repository: getGithub(),
|
||||
Changelog: getGithub("/releases/latest"),
|
||||
TranslationGuide: getGithub("/blob/main/CONTRIBUTING.md#contributing-to-translations"),
|
||||
TranslationGuide: getGithub("/wiki/Contribution-Guidelines#contributing-to-translations"),
|
||||
License: getGithub("/blob/main/LICENSE")
|
||||
};
|
||||
|
||||
|
||||
@@ -85,14 +85,19 @@
|
||||
},
|
||||
"settings@length":
|
||||
{
|
||||
"message": "Password length",
|
||||
"message": "Default password length",
|
||||
"description": "Password length label in settings section"
|
||||
},
|
||||
"settings@length__hint":
|
||||
{
|
||||
"message": "Recommended length: 8—16",
|
||||
"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",
|
||||
@@ -167,5 +172,15 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,14 +85,19 @@
|
||||
},
|
||||
"settings@length":
|
||||
{
|
||||
"message": "Długość hasła",
|
||||
"message": "Domyślna długość hasła",
|
||||
"description": "Password length label in settings section"
|
||||
},
|
||||
"settings@length__hint":
|
||||
{
|
||||
"message": "Zalecana długość: 8—16",
|
||||
"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",
|
||||
@@ -167,5 +172,15 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,11 +16,11 @@
|
||||
"description": "Error message when password length is less than 4 characters"
|
||||
},
|
||||
"error@noCharacters": {
|
||||
"message": "Pelo menos um conjunto de caracteres deve ser selecionado",
|
||||
"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 exclusivos",
|
||||
"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":
|
||||
@@ -45,7 +45,7 @@
|
||||
},
|
||||
"about@translationCta":
|
||||
{
|
||||
"message": "Encontrou um erro de digitação ou deseja uma tradução para o seu idioma?",
|
||||
"message": "Encontrou um erro ou quer uma tradução no seu idioma?",
|
||||
"description": "Translation CTA in about section"
|
||||
},
|
||||
"about@translationCtaButton":
|
||||
@@ -55,7 +55,7 @@
|
||||
},
|
||||
"about@website":
|
||||
{
|
||||
"message": "Meu website",
|
||||
"message": "Meu site",
|
||||
"description": "Website link in about section"
|
||||
},
|
||||
"about@sourceCode":
|
||||
@@ -70,12 +70,12 @@
|
||||
},
|
||||
"about@feedback":
|
||||
{
|
||||
"message": "Deixe um comentário",
|
||||
"message": "Enviar comentários",
|
||||
"description": "Feedback link in about section"
|
||||
},
|
||||
"about@sponsor":
|
||||
{
|
||||
"message": "Doar",
|
||||
"message": "Pague um café",
|
||||
"description": "Buy me a coffee donation link in about section"
|
||||
},
|
||||
"settings@title":
|
||||
@@ -85,14 +85,19 @@
|
||||
},
|
||||
"settings@length":
|
||||
{
|
||||
"message": "Comprimento da senha",
|
||||
"message": "Comprimento padrão da senha",
|
||||
"description": "Password length label in settings section"
|
||||
},
|
||||
"settings@length__hint":
|
||||
{
|
||||
"message": "Comprimento recomendado: 8—16",
|
||||
"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",
|
||||
@@ -167,5 +172,15 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,14 +85,19 @@
|
||||
},
|
||||
"settings@length":
|
||||
{
|
||||
"message": "Длина пароля",
|
||||
"message": "Длина пароля по умолчанию",
|
||||
"description": "Password length label in settings section"
|
||||
},
|
||||
"settings@length__hint":
|
||||
{
|
||||
"message": "Рекомендованная длина: 8—16",
|
||||
"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": "Добавить символы",
|
||||
@@ -167,5 +172,15 @@
|
||||
{
|
||||
"message": "Эти настройки не будут сохранены",
|
||||
"description": "Quick options hint"
|
||||
},
|
||||
"generator@include":
|
||||
{
|
||||
"message": "Добавить",
|
||||
"description": "Include characters button label"
|
||||
},
|
||||
"generator@exclude":
|
||||
{
|
||||
"message": "Исключить",
|
||||
"description": "Exclude characters button label"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,14 +85,19 @@
|
||||
},
|
||||
"settings@length":
|
||||
{
|
||||
"message": "Довжина пароля",
|
||||
"message": "Довжина пароля за замовчуванням",
|
||||
"description": "Password length label in settings section"
|
||||
},
|
||||
"settings@length__hint":
|
||||
{
|
||||
"message": "Рекомендована довжина: 8—16",
|
||||
"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": "Додати символи",
|
||||
@@ -167,5 +172,15 @@
|
||||
{
|
||||
"message": "Ці налаштування не будуть збережені",
|
||||
"description": "Quick options hint"
|
||||
},
|
||||
"generator@include":
|
||||
{
|
||||
"message": "Додати",
|
||||
"description": "Include characters button label"
|
||||
},
|
||||
"generator@exclude":
|
||||
{
|
||||
"message": "Виключити",
|
||||
"description": "Exclude characters button label"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1,5 @@
|
||||
export default class ExtensionOptions { }
|
||||
export default class ExtensionOptions
|
||||
{
|
||||
public MinLength: number = 4;
|
||||
public MaxLength: number = 32;
|
||||
}
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
.snow
|
||||
{
|
||||
position: absolute;
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.snowflake
|
||||
{
|
||||
--size: 1px;
|
||||
width: var(--size);
|
||||
height: var(--size);
|
||||
background: white;
|
||||
border-radius: 50%;
|
||||
position: absolute;
|
||||
top: -5px;
|
||||
|
||||
@media (prefers-color-scheme: light)
|
||||
{
|
||||
background: var(--colorPalettePlatinumBorderActive);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes snowfall
|
||||
{
|
||||
0%
|
||||
{
|
||||
transform: translate3d(var(--left-ini), 0, 0);
|
||||
opacity: .6;
|
||||
}
|
||||
|
||||
100%
|
||||
{
|
||||
transform: translate3d(var(--left-end), 610px, 0);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@for $i from 1 through 50
|
||||
{
|
||||
.snowflake:nth-child(#{$i})
|
||||
{
|
||||
--size: #{random(5)}px;
|
||||
--left-ini: #{random(20) - 10}vw;
|
||||
--left-end: #{random(20) - 10}vw;
|
||||
left: #{random(100)}vw;
|
||||
animation: snowfall #{5 + random(10)}s linear infinite;
|
||||
animation-delay: -#{random(10)}s;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import { GriffelStyle, makeStyles, shorthands, tokens } from "@fluentui/react-components";
|
||||
|
||||
const random = (max: number) => Math.floor(Math.random() * max);
|
||||
|
||||
export const useStyles = (count: number) => makeStyles({
|
||||
snow:
|
||||
{
|
||||
position: "absolute",
|
||||
...shorthands.overflow("hidden"),
|
||||
pointerEvents: "none",
|
||||
|
||||
top: 0,
|
||||
left: 0,
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
},
|
||||
snowflake:
|
||||
{
|
||||
"--size": "1px",
|
||||
width: "var(--size)",
|
||||
height: "var(--size)",
|
||||
backgroundColor: tokens.colorScrollbarOverlay,
|
||||
...shorthands.borderRadius(tokens.borderRadiusCircular),
|
||||
position: "absolute",
|
||||
top: "-5px",
|
||||
},
|
||||
...[...Array(count)].reduce(
|
||||
(acc, _, i): Record<string, GriffelStyle> => ({
|
||||
...acc,
|
||||
[`snowflake-${i}`]: {
|
||||
"--size": `${random(5)}px`,
|
||||
"--left-ini": `${random(20) - 10}vw`,
|
||||
"--left-end": `${random(20) - 10}vw`,
|
||||
left: `${random(100)}vw`,
|
||||
animationName: "snowfall",
|
||||
animationDuration: `${5 + random(10)}s`,
|
||||
animationTimingFunction: "linear",
|
||||
animationIterationCount: "infinite",
|
||||
animationDelay: `-${random(10)}s`,
|
||||
},
|
||||
}),
|
||||
{},
|
||||
),
|
||||
});
|
||||
+15
-8
@@ -1,11 +1,18 @@
|
||||
import "./Snow.scss";
|
||||
import { mergeClasses } from "@fluentui/react-components";
|
||||
import { useStyles } from "./Snow.styles";
|
||||
|
||||
const Snow = (): JSX.Element => (
|
||||
![0, 11].includes(new Date().getMonth()) ? <></> : // Only shows in December and January
|
||||
const SNOWFLAKES_NUM: number = 100;
|
||||
|
||||
<div className="snow">
|
||||
{ [...Array(50)].map((_, i) => <div key={ i } className="snowflake" />) }
|
||||
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>
|
||||
);
|
||||
|
||||
export default Snow;
|
||||
);
|
||||
}
|
||||
|
||||
@@ -43,3 +43,18 @@ p, ul, ol, li
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
@keyframes snowfall
|
||||
{
|
||||
0%
|
||||
{
|
||||
transform: translate3d(var(--left-ini), 0, 0);
|
||||
opacity: .6;
|
||||
}
|
||||
|
||||
100%
|
||||
{
|
||||
transform: translate3d(var(--left-end), 610px, 0);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user