Skip to content

Commit d12ab07

Browse files
committed
fix: remove CSV, i18n diff errors, accessibility, and beforeunload
1 parent 26704af commit d12ab07

14 files changed

Lines changed: 54 additions & 68 deletions

File tree

src/App.tsx

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ import Toolbar from './components/Toolbar';
66
import Editor from './components/Editor';
77
import DiffView from './components/DiffView';
88
import Footer from './components/Footer';
9-
import { formatJson, validateJson, jsonToYaml, jsonToCsv } from './utils/json';
9+
import { formatJson, validateJson, jsonToYaml } from './utils/json';
10+
import { useBeforeUnload } from './hooks/useBeforeUnload';
1011

1112
type Mode = 'editor' | 'diff';
1213

@@ -19,6 +20,8 @@ export default function App() {
1920
const [mode, setMode] = useState<Mode>('editor');
2021
const [copied, setCopied] = useState(false);
2122

23+
useBeforeUnload(input.length > 0);
24+
2225
function handleFormat() {
2326
const { output: o, error: e } = formatJson(input);
2427
setOutput(o);
@@ -37,12 +40,6 @@ export default function App() {
3740
setError(e ? (e.line ? t('errorAtLine', { line: e.line, message: e.message }) : t('parseError', { message: e.message })) : '');
3841
}
3942

40-
function handleToCsv() {
41-
const { output: o, error: e } = jsonToCsv(input);
42-
setOutput(o);
43-
setError(e === 'csvNotSupported' ? t('csvNotSupported') : e || '');
44-
}
45-
4643
function handleCopy() {
4744
navigator.clipboard.writeText(output);
4845
setCopied(true);
@@ -57,7 +54,6 @@ export default function App() {
5754
onFormat={handleFormat}
5855
onValidate={handleValidate}
5956
onToYaml={handleToYaml}
60-
onToCsv={handleToCsv}
6157
onToggleDiff={() => setMode(mode === 'editor' ? 'diff' : 'editor')}
6258
/>
6359
<main className="flex-1 p-4 max-w-7xl mx-auto w-full">

src/components/DiffView.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,15 @@ export default function DiffView() {
4646
value={left}
4747
onChange={(e) => setLeft(e.target.value)}
4848
placeholder={t('diffLeftPlaceholder')}
49+
aria-label={t('diffLeftPlaceholder')}
4950
className="flex-1 min-h-[200px] p-3 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-slate-800 font-mono text-sm resize-none focus:outline-none focus:ring-2 focus:ring-amber-500"
5051
spellCheck={false}
5152
/>
5253
<textarea
5354
value={right}
5455
onChange={(e) => setRight(e.target.value)}
5556
placeholder={t('diffRightPlaceholder')}
57+
aria-label={t('diffRightPlaceholder')}
5658
className="flex-1 min-h-[200px] p-3 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-slate-800 font-mono text-sm resize-none focus:outline-none focus:ring-2 focus:ring-amber-500"
5759
spellCheck={false}
5860
/>
@@ -61,13 +63,15 @@ export default function DiffView() {
6163
<div className="flex items-center justify-center gap-3">
6264
<button
6365
onClick={handleDiff}
66+
aria-label={t('diff')}
6467
className="px-4 py-2 bg-amber-500 hover:bg-amber-600 text-white rounded font-medium transition-colors"
6568
>
6669
{t('diff')}
6770
</button>
6871
<div className="flex rounded-md border border-gray-300 dark:border-gray-600 overflow-hidden text-xs">
6972
<button
7073
onClick={() => setMode('keys')}
74+
aria-label={t('diffByKeys')}
7175
className={`px-3 py-1.5 transition-colors ${
7276
mode === 'keys'
7377
? 'bg-amber-500 text-white'
@@ -78,6 +82,7 @@ export default function DiffView() {
7882
</button>
7983
<button
8084
onClick={() => setMode('lines')}
85+
aria-label={t('diffByLines')}
8186
className={`px-3 py-1.5 transition-colors ${
8287
mode === 'lines'
8388
? 'bg-amber-500 text-white'
@@ -103,7 +108,7 @@ export default function DiffView() {
103108
</div>
104109

105110
{error ? (
106-
<div className="p-3 text-red-600 dark:text-red-400 text-sm">{error}</div>
111+
<div className="p-3 text-red-600 dark:text-red-400 text-sm">{t(error)}</div>
107112
) : mode === 'keys' && keyResult ? (
108113
keyResult.entries.every((e) => e.type === 'unchanged') ? (
109114
<div className="p-3 text-gray-500 text-sm">{t('noDifferences')}</div>

src/components/Editor.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export default function Editor({ input, output, error, copied, onInputChange, on
1919
value={input}
2020
onChange={(e) => onInputChange(e.target.value)}
2121
placeholder={t('inputPlaceholder')}
22+
aria-label={t('inputPlaceholder')}
2223
className="flex-1 min-h-[300px] w-full p-3 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-slate-800 text-sm resize-none focus:outline-none focus:ring-2 focus:ring-amber-500"
2324
spellCheck={false}
2425
/>
@@ -34,12 +35,14 @@ export default function Editor({ input, output, error, copied, onInputChange, on
3435
value={output}
3536
readOnly
3637
placeholder={t('outputPlaceholder')}
38+
aria-label={t('outputPlaceholder')}
3739
className="min-h-[300px] w-full h-full p-3 rounded-lg border border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-slate-800/50 text-sm resize-none focus:outline-none"
3840
spellCheck={false}
3941
/>
4042
{output && (
4143
<button
4244
onClick={onCopy}
45+
aria-label={t('copy')}
4346
className="absolute top-2 right-2 px-2 py-1 text-xs rounded bg-amber-500 hover:bg-amber-600 text-white transition-colors"
4447
>
4548
{copied ? t('copied') : t('copy')}

src/components/Header.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export default function Header({ dark, toggleDark }: Props) {
4040
<button
4141
onClick={toggleDark}
4242
className="p-2 rounded hover:bg-gray-100 dark:hover:bg-slate-700 transition-colors"
43-
aria-label="Toggle theme"
43+
aria-label={t('toggleTheme')}
4444
>
4545
{dark ? '☀️' : '🌙'}
4646
</button>

src/components/Toolbar.tsx

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,10 @@ interface Props {
55
onFormat: () => void;
66
onValidate: () => void;
77
onToYaml: () => void;
8-
onToCsv: () => void;
98
onToggleDiff: () => void;
109
}
1110

12-
export default function Toolbar({ mode, onFormat, onValidate, onToYaml, onToCsv, onToggleDiff }: Props) {
11+
export default function Toolbar({ mode, onFormat, onValidate, onToYaml, onToggleDiff }: Props) {
1312
const { t } = useTranslation();
1413

1514
const btn = 'px-3 py-1.5 text-sm font-medium rounded transition-colors';
@@ -20,11 +19,10 @@ export default function Toolbar({ mode, onFormat, onValidate, onToYaml, onToCsv,
2019
return (
2120
<div className="border-b border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-slate-800/50 px-4 py-2">
2221
<div className="max-w-7xl mx-auto flex flex-wrap gap-2">
23-
<button onClick={onFormat} className={primary} disabled={mode === 'diff'}>{t('format')}</button>
24-
<button onClick={onValidate} className={secondary} disabled={mode === 'diff'}>{t('validate')}</button>
25-
<button onClick={onToYaml} className={secondary} disabled={mode === 'diff'}>{t('toYaml')}</button>
26-
<button onClick={onToCsv} className={secondary} disabled={mode === 'diff'}>{t('toCsv')}</button>
27-
<button onClick={onToggleDiff} className={mode === 'diff' ? active : secondary}>{t('diff')}</button>
22+
<button onClick={onFormat} className={primary} disabled={mode === 'diff'} aria-label={t('format')}>{t('format')}</button>
23+
<button onClick={onValidate} className={secondary} disabled={mode === 'diff'} aria-label={t('validate')}>{t('validate')}</button>
24+
<button onClick={onToYaml} className={secondary} disabled={mode === 'diff'} aria-label={t('toYaml')}>{t('toYaml')}</button>
25+
<button onClick={onToggleDiff} className={mode === 'diff' ? active : secondary} aria-label={t('diff')}>{t('diff')}</button>
2826
</div>
2927
</div>
3028
);

src/hooks/useBeforeUnload.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { useEffect } from 'react';
2+
3+
export function useBeforeUnload(shouldBlock: boolean) {
4+
useEffect(() => {
5+
if (!shouldBlock) return;
6+
const handler = (e: BeforeUnloadEvent) => { e.preventDefault(); };
7+
window.addEventListener('beforeunload', handler);
8+
return () => window.removeEventListener('beforeunload', handler);
9+
}, [shouldBlock]);
10+
}

src/i18n/locales/cs.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
"format": "Formátovat",
66
"validate": "Ověřit",
77
"toYaml": "JSON → YAML",
8-
"toCsv": "JSON → CSV",
98
"diff": "Porovnat",
109
"inputPlaceholder": "Vložte JSON zde...",
1110
"outputPlaceholder": "Formátovaný výstup se zobrazí zde...",
@@ -18,7 +17,9 @@
1817
"invalidJson": "Neplatný JSON",
1918
"errorAtLine": "Chyba na řádku {{line}}: {{message}}",
2019
"parseError": "Chyba analýzy: {{message}}",
21-
"csvNotSupported": "Konverze CSV vyžaduje pole plochých objektů",
20+
"diffErrorLeft": "Vlevo: neplatný JSON",
21+
"diffErrorRight": "Vpravo: neplatný JSON",
22+
"toggleTheme": "Přepnout téma",
2223
"viewOnGithub": "Zobrazit na GitHubu",
2324
"buyMeACoffee": "Kup mi kávu",
2425
"language": "Jazyk",

src/i18n/locales/de.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
"format": "Formatieren",
66
"validate": "Validieren",
77
"toYaml": "JSON → YAML",
8-
"toCsv": "JSON → CSV",
98
"diff": "Vergleichen",
109
"inputPlaceholder": "JSON hier einfügen...",
1110
"outputPlaceholder": "Formatierte Ausgabe erscheint hier...",
@@ -18,7 +17,9 @@
1817
"invalidJson": "Ungültiges JSON",
1918
"errorAtLine": "Fehler in Zeile {{line}}: {{message}}",
2019
"parseError": "Analysefehler: {{message}}",
21-
"csvNotSupported": "CSV-Konvertierung erfordert ein Array flacher Objekte",
20+
"diffErrorLeft": "Links: Ungültiges JSON",
21+
"diffErrorRight": "Rechts: Ungültiges JSON",
22+
"toggleTheme": "Thema wechseln",
2223
"viewOnGithub": "Auf GitHub ansehen",
2324
"buyMeACoffee": "Kauf mir einen Kaffee",
2425
"language": "Sprache",

src/i18n/locales/en.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
"format": "Format",
66
"validate": "Validate",
77
"toYaml": "JSON → YAML",
8-
"toCsv": "JSON → CSV",
98
"diff": "Diff",
109
"inputPlaceholder": "Paste your JSON here...",
1110
"outputPlaceholder": "Formatted output will appear here...",
@@ -18,7 +17,9 @@
1817
"invalidJson": "Invalid JSON",
1918
"errorAtLine": "Error at line {{line}}: {{message}}",
2019
"parseError": "Parse error: {{message}}",
21-
"csvNotSupported": "CSV conversion requires an array of flat objects",
20+
"diffErrorLeft": "Left: invalid JSON",
21+
"diffErrorRight": "Right: invalid JSON",
22+
"toggleTheme": "Toggle theme",
2223
"viewOnGithub": "View on GitHub",
2324
"buyMeACoffee": "Buy me a coffee",
2425
"language": "Language",

src/i18n/locales/es.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
"format": "Formatear",
66
"validate": "Validar",
77
"toYaml": "JSON → YAML",
8-
"toCsv": "JSON → CSV",
98
"diff": "Comparar",
109
"inputPlaceholder": "Pega tu JSON aquí...",
1110
"outputPlaceholder": "La salida formateada aparecerá aquí...",
@@ -18,7 +17,9 @@
1817
"invalidJson": "JSON inválido",
1918
"errorAtLine": "Error en línea {{line}}: {{message}}",
2019
"parseError": "Error de análisis: {{message}}",
21-
"csvNotSupported": "La conversión CSV requiere un array de objetos planos",
20+
"diffErrorLeft": "Izquierda: JSON inválido",
21+
"diffErrorRight": "Derecha: JSON inválido",
22+
"toggleTheme": "Cambiar tema",
2223
"viewOnGithub": "Ver en GitHub",
2324
"buyMeACoffee": "Invítame un café",
2425
"language": "Idioma",

0 commit comments

Comments
 (0)