Skip to content

Commit 80d2a04

Browse files
committed
fix: keyboard shortcuts dialog z-index and add shortcuts for all tools
- Fixed dialog z-index (1000/1001) to appear above navigation (z-index: 100) - Added keyboard shortcuts for all 30+ tools - Simplified shortcuts: Alt+1-0 for main tools, Alt+letter for utilities - Added KeyboardShortcutsDialog to docs, favorites, and recent pages - Now works from all pages, not just home and dashboard
1 parent 3eb0fef commit 80d2a04

18 files changed

Lines changed: 386 additions & 418 deletions

app/components/keyboard-shortcuts-dialog.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,19 +22,19 @@ function formatShortcut(shortcut: (typeof KEYBOARD_SHORTCUTS)[0]): string[] {
2222
const groupedShortcuts = [
2323
{
2424
title: "Navigation",
25-
shortcuts: KEYBOARD_SHORTCUTS.filter((s) => !s.alt && s.action !== "shortcuts"),
25+
shortcuts: KEYBOARD_SHORTCUTS.filter((s) => s.ctrl && !s.alt),
2626
},
2727
{
2828
title: "Security Tools",
29-
shortcuts: KEYBOARD_SHORTCUTS.filter((s) => s.alt && /^[1-4]$/.test(s.key)),
29+
shortcuts: KEYBOARD_SHORTCUTS.filter((s) => s.alt && !s.ctrl && /^[1-4]$/.test(s.key)),
3030
},
3131
{
3232
title: "Encryption Tools",
33-
shortcuts: KEYBOARD_SHORTCUTS.filter((s) => s.alt && /^[5-7]$/.test(s.key)),
33+
shortcuts: KEYBOARD_SHORTCUTS.filter((s) => s.alt && !s.ctrl && /^[5-7]$/.test(s.key)),
3434
},
3535
{
3636
title: "Analysis Tools",
37-
shortcuts: KEYBOARD_SHORTCUTS.filter((s) => s.alt && /^[890]$/.test(s.key)),
37+
shortcuts: KEYBOARD_SHORTCUTS.filter((s) => s.alt && !s.ctrl && /^[890]$/.test(s.key)),
3838
},
3939
{
4040
title: "Utility Tools",

app/components/ui/dialog/dialog.module.css

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
.overlay {
22
position: fixed;
33
inset: 0;
4-
z-index: 5;
4+
z-index: 1000;
55
background: color-mix(in srgb, var(--color-neutral-1) 80%, transparent);
66
}
77

@@ -17,7 +17,7 @@
1717
position: fixed;
1818
left: 50%;
1919
top: 50%;
20-
z-index: 5;
20+
z-index: 1001;
2121
display: grid;
2222
transform: translate(-50%, -50%);
2323
gap: var(--space-4);

app/hooks/use-keyboard-shortcuts.ts

Lines changed: 38 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -11,39 +11,52 @@ export interface KeyboardShortcut {
1111
}
1212

1313
export const KEYBOARD_SHORTCUTS: KeyboardShortcut[] = [
14+
// Navigation Shortcuts
1415
{ key: "k", ctrl: true, description: "Focus search", action: "search" },
1516
{ key: "d", ctrl: true, description: "Dashboard", action: "/dashboard" },
1617
{ key: "h", ctrl: true, description: "Home", action: "/" },
1718
{ key: "/", ctrl: true, description: "Keyboard shortcuts", action: "shortcuts" },
19+
{ key: "f", ctrl: true, alt: true, description: "Favorites", action: "/favorites" },
20+
{ key: "e", ctrl: true, alt: true, description: "Recent Tools", action: "/recent" },
1821

19-
// Security Tools
20-
{ key: "1", ctrl: true, alt: true, description: "JWT Debugger", action: "/jwt-debugger" },
21-
{ key: "2", ctrl: true, alt: true, description: "Password Strength Checker", action: "/password-checker" },
22-
{ key: "3", ctrl: true, alt: true, description: "Hash Generator", action: "/hash-tools" },
23-
{ key: "4", ctrl: true, alt: true, description: "Full Security Audit", action: "/analyze" },
22+
// Security Tools (Alt+1-4)
23+
{ key: "1", alt: true, description: "JWT Debugger", action: "/jwt-debugger" },
24+
{ key: "2", alt: true, description: "Password Strength", action: "/password-checker" },
25+
{ key: "3", alt: true, description: "Hash Generator", action: "/hash-tools" },
26+
{ key: "4", alt: true, description: "Security Audit", action: "/analyze" },
2427

25-
// Encryption Tools
26-
{ key: "5", ctrl: true, alt: true, description: "AES Encryption", action: "/aes-encryption" },
27-
{ key: "6", ctrl: true, alt: true, description: "RSA Key Generator", action: "/rsa-generator" },
28-
{ key: "7", ctrl: true, alt: true, description: "Base64 Encoder/Decoder", action: "/base64-tools" },
28+
// Encryption Tools (Alt+5-7)
29+
{ key: "5", alt: true, description: "AES Encryption", action: "/aes-encryption" },
30+
{ key: "6", alt: true, description: "RSA Key Generator", action: "/rsa-generator" },
31+
{ key: "7", alt: true, description: "Base64 Tools", action: "/base64-tools" },
2932

30-
// Analysis Tools
31-
{ key: "8", ctrl: true, alt: true, description: "SSL Certificate Inspector", action: "/ssl-inspector" },
32-
{ key: "9", ctrl: true, alt: true, description: "JWT Analyzer", action: "/jwt-analyzer" },
33-
{ key: "0", ctrl: true, alt: true, description: "CORS Checker", action: "/cors-checker" },
33+
// Analysis Tools (Alt+8-0)
34+
{ key: "8", alt: true, description: "SSL Inspector", action: "/ssl-inspector" },
35+
{ key: "9", alt: true, description: "JWT Analyzer", action: "/jwt-analyzer" },
36+
{ key: "0", alt: true, description: "CORS Checker", action: "/cors-checker" },
3437

35-
// General Tools
36-
{ key: "a", ctrl: true, alt: true, description: "API Security", action: "/api-security" },
37-
{ key: "c", ctrl: true, alt: true, description: "Certificate Decoder", action: "/certificate-decoder" },
38-
{ key: "s", ctrl: true, alt: true, description: "Data Sanitizer", action: "/data-sanitizer" },
39-
{ key: "n", ctrl: true, alt: true, description: "DNS Lookup", action: "/dns-lookup" },
40-
{ key: "b", ctrl: true, alt: true, description: "HTTP Builder", action: "/http-builder" },
41-
{ key: "j", ctrl: true, alt: true, description: "JWT Best Practices", action: "/jwt-best-practices" },
42-
{ key: "g", ctrl: true, alt: true, description: "JWT Generator", action: "/jwt-generator" },
43-
{ key: "p", ctrl: true, alt: true, description: "Privacy Analyzer", action: "/privacy-analyzer" },
44-
{ key: "r", ctrl: true, alt: true, description: "Regex Tester", action: "/regex-tester" },
45-
{ key: "f", ctrl: true, alt: true, description: "Favorites", action: "/favorites" },
46-
{ key: "e", ctrl: true, alt: true, description: "Recent Tools", action: "/recent" },
38+
// Utility Tools (Alt+Letter)
39+
{ key: "a", alt: true, description: "API Security", action: "/api-security" },
40+
{ key: "b", alt: true, description: "Base Converter", action: "/base-converter" },
41+
{ key: "c", alt: true, description: "Certificate Decoder", action: "/certificate-decoder" },
42+
{ key: "g", alt: true, description: "CSP Generator", action: "/csp-generator" },
43+
{ key: "i", alt: true, description: "Hash Identifier", action: "/hash-identifier" },
44+
{ key: "j", alt: true, description: "JSON Formatter", action: "/json-formatter" },
45+
{ key: "l", alt: true, description: "Color Converter", action: "/color-converter" },
46+
{ key: "m", alt: true, description: "HMAC Generator", action: "/hmac-generator" },
47+
{ key: "n", alt: true, description: "DNS Lookup", action: "/dns-lookup" },
48+
{ key: "o", alt: true, description: "TOTP Generator", action: "/totp-generator" },
49+
{ key: "p", alt: true, description: "Password Generator", action: "/password-generator" },
50+
{ key: "q", alt: true, description: "URL Parser", action: "/url-parser" },
51+
{ key: "r", alt: true, description: "Regex Tester", action: "/regex-tester" },
52+
{ key: "s", alt: true, description: "Data Sanitizer", action: "/data-sanitizer" },
53+
{ key: "t", alt: true, description: "Timestamp Converter", action: "/timestamp-converter" },
54+
{ key: "u", alt: true, description: "UUID Generator", action: "/uuid-generator" },
55+
{ key: "v", alt: true, description: "Privacy Analyzer", action: "/privacy-analyzer" },
56+
{ key: "w", alt: true, description: "HTTP Builder", action: "/http-builder" },
57+
{ key: "x", alt: true, description: "XOR Cipher", action: "/xor-cipher" },
58+
{ key: "y", alt: true, description: "Caesar Cipher", action: "/caesar-cipher" },
59+
{ key: "z", alt: true, description: "Text Diff", action: "/text-diff" },
4760
];
4861

4962
export function useKeyboardShortcuts(onShowShortcuts?: () => void) {

app/routes/aes-encryption.tsx

Lines changed: 37 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
import { useState } from 'react';
2-
import { Lock, Unlock, Key, Copy, Check } from 'lucide-react';
3-
import { ToolHeader } from '../components/tool-header';
4-
import { Button } from '../components/ui/button/button';
5-
import { Card } from '../components/ui/card/card';
6-
import { Textarea } from '../components/ui/textarea/textarea';
7-
import { Label } from '../components/ui/label/label';
8-
import { Input } from '../components/ui/input/input';
9-
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../components/ui/select/select';
10-
import styles from './aes-encryption.module.css';
1+
import { useState } from "react";
2+
import { Lock, Unlock, Key, Copy, Check } from "lucide-react";
3+
import { ToolHeader } from "../components/tool-header";
4+
import { Button } from "../components/ui/button/button";
5+
import { Card } from "../components/ui/card/card";
6+
import { Textarea } from "../components/ui/textarea/textarea";
7+
import { Label } from "../components/ui/label/label";
8+
import { Input } from "../components/ui/input/input";
9+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../components/ui/select/select";
10+
import styles from "./aes-encryption.module.css";
1111

1212
export function meta() {
1313
return [
@@ -17,27 +17,27 @@ export function meta() {
1717
}
1818

1919
export default function AESEncryption() {
20-
const [mode, setMode] = useState<'encrypt' | 'decrypt'>('encrypt');
21-
const [algorithm, setAlgorithm] = useState('AES-GCM');
22-
const [keySize, setKeySize] = useState('256');
23-
const [input, setInput] = useState('');
24-
const [key, setKey] = useState('');
25-
const [iv, setIv] = useState('');
26-
const [output, setOutput] = useState('');
20+
const [mode, setMode] = useState<"encrypt" | "decrypt">("encrypt");
21+
const [algorithm, setAlgorithm] = useState("AES-GCM");
22+
const [keySize, setKeySize] = useState("256");
23+
const [input, setInput] = useState("");
24+
const [key, setKey] = useState("");
25+
const [iv, setIv] = useState("");
26+
const [output, setOutput] = useState("");
2727
const [processing, setProcessing] = useState(false);
2828
const [copied, setCopied] = useState(false);
2929

3030
const generateKey = () => {
3131
const size = parseInt(keySize) / 8;
3232
const array = new Uint8Array(size);
3333
crypto.getRandomValues(array);
34-
setKey(Array.from(array, byte => byte.toString(16).padStart(2, '0')).join(''));
34+
setKey(Array.from(array, (byte) => byte.toString(16).padStart(2, "0")).join(""));
3535
};
3636

3737
const generateIV = () => {
3838
const array = new Uint8Array(16);
3939
crypto.getRandomValues(array);
40-
setIv(Array.from(array, byte => byte.toString(16).padStart(2, '0')).join(''));
40+
setIv(Array.from(array, (byte) => byte.toString(16).padStart(2, "0")).join(""));
4141
};
4242

4343
const hexToBytes = (hex: string) => {
@@ -49,51 +49,51 @@ export default function AESEncryption() {
4949
};
5050

5151
const bytesToHex = (bytes: Uint8Array): string => {
52-
return Array.from(bytes, byte => byte.toString(16).padStart(2, '0')).join('');
52+
return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
5353
};
5454

5555
const processData = async () => {
5656
if (!input || !key) return;
57-
57+
5858
setProcessing(true);
5959
try {
6060
const keyBytes = hexToBytes(key);
6161
const cryptoKey = await crypto.subtle.importKey(
62-
'raw',
62+
"raw",
6363
keyBytes,
6464
{ name: algorithm, length: parseInt(keySize) },
6565
false,
66-
[mode === 'encrypt' ? 'encrypt' : 'decrypt']
66+
[mode === "encrypt" ? "encrypt" : "decrypt"],
6767
);
6868

69-
if (mode === 'encrypt') {
69+
if (mode === "encrypt") {
7070
const ivBytes = iv ? hexToBytes(iv) : crypto.getRandomValues(new Uint8Array(16));
7171
const encoded = new TextEncoder().encode(input);
7272
const encrypted = await crypto.subtle.encrypt(
7373
{ name: algorithm, iv: new Uint8Array(ivBytes.buffer.slice(0)) },
7474
cryptoKey,
75-
encoded
75+
encoded,
7676
);
7777
const result = bytesToHex(new Uint8Array(encrypted));
7878
setOutput(`IV: ${bytesToHex(ivBytes)}\nCiphertext: ${result}`);
7979
if (!iv) setIv(bytesToHex(ivBytes));
8080
} else {
8181
if (!iv) {
82-
setOutput('Error: IV is required for decryption');
82+
setOutput("Error: IV is required for decryption");
8383
return;
8484
}
8585
const ivBytes = hexToBytes(iv);
86-
const ciphertext = hexToBytes(input.replace(/^IV:.*\nCiphertext:\s*/i, '').trim());
86+
const ciphertext = hexToBytes(input.replace(/^IV:.*\nCiphertext:\s*/i, "").trim());
8787
const decrypted = await crypto.subtle.decrypt(
8888
{ name: algorithm, iv: new Uint8Array(ivBytes.buffer.slice(0)) },
8989
cryptoKey,
90-
new Uint8Array(ciphertext.buffer.slice(0))
90+
new Uint8Array(ciphertext.buffer.slice(0)),
9191
);
9292
const result = new TextDecoder().decode(decrypted);
9393
setOutput(result);
9494
}
9595
} catch (error) {
96-
setOutput(`Error: ${error instanceof Error ? error.message : 'Encryption/decryption failed'}`);
96+
setOutput(`Error: ${error instanceof Error ? error.message : "Encryption/decryption failed"}`);
9797
} finally {
9898
setProcessing(false);
9999
}
@@ -119,7 +119,7 @@ export default function AESEncryption() {
119119
<div className={styles.row}>
120120
<div className={styles.field}>
121121
<Label>Mode</Label>
122-
<Select value={mode} onValueChange={(v) => setMode(v as 'encrypt' | 'decrypt')}>
122+
<Select value={mode} onValueChange={(v) => setMode(v as "encrypt" | "decrypt")}>
123123
<SelectTrigger>
124124
<SelectValue />
125125
</SelectTrigger>
@@ -192,38 +192,33 @@ export default function AESEncryption() {
192192
</div>
193193

194194
<div className={styles.field}>
195-
<Label>{mode === 'encrypt' ? 'Plaintext' : 'Ciphertext (Hex)'}</Label>
195+
<Label>{mode === "encrypt" ? "Plaintext" : "Ciphertext (Hex)"}</Label>
196196
<Textarea
197197
value={input}
198198
onChange={(e) => setInput(e.target.value)}
199-
placeholder={mode === 'encrypt' ? 'Enter text to encrypt...' : 'Paste ciphertext to decrypt...'}
199+
placeholder={mode === "encrypt" ? "Enter text to encrypt..." : "Paste ciphertext to decrypt..."}
200200
rows={6}
201201
className={styles.textarea}
202202
/>
203203
</div>
204204

205205
<Button onClick={processData} disabled={processing || !input || !key} className={styles.processButton}>
206-
{mode === 'encrypt' ? <Lock size={18} /> : <Unlock size={18} />}
207-
{processing ? 'Processing...' : mode === 'encrypt' ? 'Encrypt Data' : 'Decrypt Data'}
206+
{mode === "encrypt" ? <Lock size={18} /> : <Unlock size={18} />}
207+
{processing ? "Processing..." : mode === "encrypt" ? "Encrypt Data" : "Decrypt Data"}
208208
</Button>
209209
</div>
210210
</Card>
211211

212212
{output && (
213213
<Card className={styles.card}>
214214
<div className={styles.outputHeader}>
215-
<Label>{mode === 'encrypt' ? 'Encrypted Output' : 'Decrypted Output'}</Label>
215+
<Label>{mode === "encrypt" ? "Encrypted Output" : "Decrypted Output"}</Label>
216216
<Button onClick={copyOutput} variant="ghost" size="sm">
217217
{copied ? <Check size={16} /> : <Copy size={16} />}
218-
{copied ? 'Copied!' : 'Copy'}
218+
{copied ? "Copied!" : "Copy"}
219219
</Button>
220220
</div>
221-
<Textarea
222-
value={output}
223-
readOnly
224-
rows={8}
225-
className={styles.output}
226-
/>
221+
<Textarea value={output} readOnly rows={8} className={styles.output} />
227222
</Card>
228223
)}
229224
</div>

0 commit comments

Comments
 (0)