Text
LINE TOOLS
Sort, deduplicate, reverse, shuffle, trim, and number lines of text. Chain multiple operations. Real-time output.
Input
Output
Developer Reference
Core Algorithm & Standalone Script
Standalone, zero-dependency JavaScript implementation powering this tool. Free to inspect, copy, and build upon.
const OPS = [
{ id:'sortAZ', label:'Sort A → Z', fn: lines => [...lines].sort((a,b)=>a.localeCompare(b)) },
{ id:'sortZA', label:'Sort Z → A', fn: lines => [...lines].sort((a,b)=>b.localeCompare(a)) },
{ id:'sortLenAsc', label:'Sort by length ↑', fn: lines => [...lines].sort((a,b)=>a.length-b.length) },
{ id:'sortLenDesc',label:'Sort by length ↓', fn: lines => [...lines].sort((a,b)=>b.length-a.length) },
{ id:'dedup', label:'Remove duplicates', fn: lines => [...new Set(lines)] },
{ id:'dedupCI', label:'Dedup (ignore case)',fn: lines => {
const seen = new Set();
return lines.filter(l => { const k=l.toLowerCase(); if(seen.has(k)) return false; seen.add(k); return true; });
}},
{ id:'reverse', label:'Reverse order', fn: lines => [...lines].reverse() },
{ id:'shuffle', label:'Shuffle', fn: lines => { const a=[...lines]; for(let i=a.length-1;i>0;i--){const j=Math.floor(Math.random()*(i+1));[a[i],a[j]]=[a[j],a[i]];}return a; } },
{ id:'trimWS', label:'Trim whitespace', fn: lines => lines.map(l=>l.trim()) },
{ id:'noEmpty', label:'Remove empty lines',fn: lines => lines.filter(l=>l.trim()!=='') },
{ id:'lower', label:'Lowercase', fn: lines => lines.map(l=>l.toLowerCase()) },
{ id:'upper', label:'Uppercase', fn: lines => lines.map(l=>l.toUpperCase()) },
{ id:'number', label:'Number lines', fn: lines => lines.map((l,i)=>`${i+1}. ${l}`) },
{ id:'unnumber',label:'Remove line numbers',fn: lines => lines.map(l=>l.replace(/^\d+[.)]\s*/,'')) },
{ id:'unique', label:'Keep unique only', fn: lines => { const cnt={}; lines.forEach(l=>cnt[l]=(cnt[l]||0)+1); return lines.filter(l=>cnt[l]===1); } },
];
const activeOps = new Set();
function initOps() {
const grid = document.getElementById('opsGrid');
OPS.forEach(op => {
const btn = document.createElement('button');
btn.className = 'op-btn';
btn.textContent = op.label;
btn.dataset.id = op.id;
btn.onclick = () => toggleOp(op.id, btn);
grid.appendChild(btn);
});
}
function toggleOp(id, btn) {
if (activeOps.has(id)) { activeOps.delete(id); btn.classList.remove('active'); }
else { activeOps.add(id); btn.classList.add('active'); }
processAll();
}
function processAll() {
const raw = document.getElementById('inputText').value;
const lines = raw === '' ? [] : raw.split('\n');
document.getElementById('inMeta').textContent = lines.length + ' line' + (lines.length !== 1 ? 's' : '');
let result = lines;
// Apply ops in defined order (not toggle order) for predictability
OPS.forEach(op => { if (activeOps.has(op.id)) result = op.fn(result); });
document.getElementById('outputText').value = result.join('\n');
document.getElementById('outMeta').textContent = result.length + ' line' + (result.length !== 1 ? 's' : '');
}
function copyOutput() {
const val = document.getElementById('outputText').value;
if (!val) return;
navigator.clipboard.writeText(val).catch(() => {});
const btn = document.getElementById('btnCopyOut');
btn.textContent = 'copied!'; btn.classList.add('copied');
setTimeout(() => { btn.textContent = 'copy output'; btn.classList.remove('copied'); }, 2000);
}
function swapToInput() {
const out = document.getElementById('outputText').value;
document.getElementById('inputText').value = out;
processAll();
}
initOps();