White Space Remover / Cleaner

import React, { useEffect, useMemo, useState } from "react"; /* Whitespace Cleaner — Single-file React + TypeScript component - Tailwind CSS classes used for styling (no imports required) - Features: * Multiple cleaning modes: Trim, Collapse spaces, Remove all whitespace, Normalize newlines, Remove zero-width and control characters * Live preview and stats (characters, words, lines) * Copy to clipboard, Download .txt, Undo (history), Clear * Keyboard shortcuts: Ctrl+Enter = Clean, Ctrl+Z = Undo, Ctrl+S = Download * Autosave input to localStorage * Accessibility: labels, aria attributes How to use: - Drop into a React + Tailwind project (TypeScript). This is a single component file. - It exports a default React component Ideas for extending: - Add server-side API for batch file processing - Provide CLI version (Node/Go/Rust) for heavy files - Add file upload and drag-drop */ type Mode = | "trim" | "collapse" | "remove_all" | "normalize_newlines" | "remove_zero_width" | "custom"; const STORAGE_KEY = "whitespace_cleaner_input_v1"; const HISTORY_KEY = "whitespace_cleaner_history_v1"; function normalizeUnicode(s: string) { // Use NFC for common normalization (covers composed chars) try { return s.normalize("NFC"); } catch (e) { return s; } } function removeZeroWidthAndControls(s: string) { // Remove zero-width joiner (\u200D), zero-width space, BOM, and other invisible controls return s.replace(/\u200B|\u200C|\u200D|\uFEFF|\p{C}/gu, ""); } function collapseSpaces(s: string) { // Replace sequences of spaces and tabs with a single space return s.replace(/[ \t\u00A0\u1680\u2000-\u200A\u202F\u205F\u3000]+/g, " "); } function removeAllWhitespace(s: string) { // Remove all spaces, tabs, newlines and other whitespace characters return s.replace(/\s+/g, ""); } function trimLines(s: string) { // Trim leading and trailing whitespace on each line return s .split(/\r?\n/) .map((l) => l.trim()) .join("\n"); } function normalizeNewlines(s: string, newline: "\n" | "\r\n") { // Convert CRLF, CR to desired newline and collapse repeated blank lines const converted = s.replace(/\r\n|\r/g, "\n"); // collapse 3+ newlines to two const collapsed = converted.replace(/\n{3,}/g, "\n\n"); return newline === "\r\n" ? collapsed.replace(/\n/g, "\r\n") : collapsed; } function statsOf(s: string) { const characters = s.length; const words = s.trim().length === 0 ? 0 : s.trim().split(/\s+/).length; const lines = s.split(/\r?\n/).length; return { characters, words, lines }; } export default function WhitespaceCleaner(): JSX.Element { const [input, setInput] = useState(""); const [mode, setMode] = useState("collapse"); const [normalizeNl, setNormalizeNl] = useState<"\n" | "\r\n">("\n"); const [removeZeroWidth, setRemoveZeroWidth] = useState(true); const [history, setHistory] = useState([]); useEffect(() => { const saved = localStorage.getItem(STORAGE_KEY); if (saved) setInput(saved); const h = localStorage.getItem(HISTORY_KEY); if (h) { try { setHistory(JSON.parse(h)); } catch {} } }, []); useEffect(() => { localStorage.setItem(STORAGE_KEY, input); }, [input]); useEffect(() => { localStorage.setItem(HISTORY_KEY, JSON.stringify(history.slice(-50))); }, [history]); useEffect(() => { function handler(e: KeyboardEvent) { if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "enter") { e.preventDefault(); doClean(); } if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "z") { e.preventDefault(); undo(); } if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "s") { e.preventDefault(); downloadOutput(); } } window.addEventListener("keydown", handler); return () => window.removeEventListener("keydown", handler); }, [input, mode, normalizeNl, removeZeroWidth, history]); const output = useMemo(() => { let s = input; s = normalizeUnicode(s); if (removeZeroWidth) s = removeZeroWidthAndControls(s); switch (mode) { case "trim": s = s.trim(); break; case "collapse": s = collapseSpaces(s); s = trimLines(s); break; case "remove_all": s = removeAllWhitespace(s); break; case "normalize_newlines": s = normalizeNewlines(s, normalizeNl); break; case "remove_zero_width": s = removeZeroWidthAndControls(s); break; case "custom": // keep input as is — user can use custom regex in future break; } return s; }, [input, mode, normalizeNl, removeZeroWidth]); const statsIn = statsOf(input); const statsOut = statsOf(output); function doClean() { if (input === output) return; setHistory((h) => [...h.slice(-49), input]); setInput(output); } function undo() { setHistory((h) => { if (h.length === 0) return h; const last = h[h.length - 1]; setInput(last); return h.slice(0, -1); }); } function clearAll() { setHistory((h) => [...h.slice(-49), input]); setInput(""); } function copyOutput() { navigator.clipboard .writeText(output) .then(() => { // small visual confirmation could be added }) .catch(() => {}); } function downloadOutput() { const blob = new Blob([output], { type: "text/plain;charset=utf-8" }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = "cleaned.txt"; document.body.appendChild(a); a.click(); a.remove(); URL.revokeObjectURL(url); } function loadExample(kind: "small" | "large") { const text = kind === "small" ? " This is an example text.\nNew line.\n\n Many spaces.\n" : Array.from({ length: 2000 }, (_, i) => `Line ${i + 1}: example text `).join("\n"); setInput(text); } return (

Whitespace Cleaner

Modern, fast whitespace removal and normalization tool.