Text

LINE TOOLS

Sort, deduplicate, reverse, shuffle, trim, and number lines of text. Chain multiple operations. Real-time output.

Input 0 lines
Output 0 lines
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();