JS Tools β€” Five content utilities in one HTML file
DEV Community

JS Tools - Five content utilities in one HTML file

Quick start

  • Download jstools.html
  • Double-click it

No npm install, no bundler, no CDN links - all HTML, CSS and JS is inline.

Hosting (optional): drop it on GitHub Pages, Netlify, or a /tools/ folder on your own site. HTTPS unlocks the Paste buttons - see clipboard caveats.

Nothing persists between refreshes, so keep it in a pinned tab.

Interface

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚βš‘ JS ToolsπŸŒ™ Theme                  β”‚header
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚[FAQ] [Cleaner] [Links] [Slug] [Counter]β”‚tabs
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚β˜‘ optionβ˜‘ option[field][β–Ά Convert]β”‚toolbar
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚INPUTπŸ“‹ ⧉ βœ•β”‚OUTPUT ⇄ β­³ ⧉ βœ•β”‚
β”‚textarea                   β”‚textarea                      β”‚
β”‚120 chars Β· 22 words       β”‚340 chars Β· 48 wordsβ”‚live stats
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Same three-part layout everywhere: a toolbar (changing any option re-runs instantly - you never press Convert twice), an input card (Paste / Copy / Clear, plus a count pill on some tools), and an output card (Swap-to-input / Download / Copy / Clear). Every textarea has a live chars Β· words Β· lines footer.

Tool 1 - FAQ β†’ Accordion

Plain Q/A list β†’ WPBakery Page Builder shortcodes. A line is a question if it ends with ? or starts with Q:, Q., Q), Q- (case-insensitive). Every non-blank line after it, until the next question, becomes the answer - optional A: prefixes and leading list numbers (1., 2)) are stripped, blank lines ignored, so double-spaced documents work as-is. Press ✨ Sample to load a working example.

Input:

What is your return policy?
You can return any unused item within 30 days for a full refund.

Q: Do you ship internationally?
A: Yes - over 40 countries, 5-10 business days.

Output:

[vc_tta_accordion]
[vc_tta_section title="What is your return policy?" tab_id="what-is-your-return-policy"][vc_column_text]You can return any unused item within 30 days for a full refund.[/vc_column_text][/vc_tta_section]
[/vc_tta_accordion]
Option Effect
Shortcode vc_tta_accordion (default), vc_tta_tabs or vc_tta_tour. Inner sections are identical, so you can switch layout without redoing content.
Single line output On by default - WPBakery's classic editor injects stray <br> tags into multi-line shortcodes. Turn off for readable, indented output.
Wrap answer in <p> For themes whose accordion body has no default paragraph spacing.
tab_id prefix faq β†’ faq-what-is-your-return-policy. Essential with two accordions on one page - duplicate tab_ids break open/close behaviour.

tab_id generation: accent-folded ( HΓ©llo β†’ Hello ), depunctuated, lowercased, spaces β†’ hyphens, with a numeric suffix ( -2, -3 ) on collisions. Double quotes in questions are escaped to &quot; so they can't break out of title="".

The input pill shows how many Q/A pairs were found. 0 items means your questions don't end in ? or start with Q:. Questions with no answer beneath them are discarded.

Tool 2 - HTML Cleaner

Strips the junk out of HTML pasted from Word, Google Docs, Outlook or another CMS. Independent toggles, applied in this fixed order - order matters, e.g. removing attributes after stripping all tags is pointless.

  • Smart quotes β†’ plain (off) - curly quotes β†’ straight, en/em dashes β†’ - / --, … β†’ .... For CSV imports, older PHP templates, some email clients.
  • Remove &nbsp; (on) - both the entity and the literal U+00A0. The single biggest source of "why is there a weird gap in my paragraph".
  • Remove style attrs (on) + Remove class/id (off) - one pass via DOMParser, not regex, so attributes inside comments or strings can't be mangled and malformed markup is normalised rather than corrupted. Class/id removal is aggressive, hence off by default.
  • Remove <p> tags (on) - drops opening tags, converts </p> to a newline so paragraph breaks survive. For fields that add their own wrapping (wpautop, most page builders).
  • Strip ALL tags (off) - nuclear option, leaves plain text.
  • Drop empty lines (on) - cleans up after the steps above.
  • Single line (off) - collapses whitespace runs, for shortcode attributes and JSON fields.

Presets: Word/Docs cleanup = style + &nbsp; + empty lines Β· Plain text = strip all + smart quotes + empty lines Β· Page-builder prep = <p> + style + class/id Β· One-line value = strip all + single line

Tool 3 - Link Tool

Applies classes, target and rel to every <a> in a block of HTML at once. Also DOM-parsed and re-serialised, so it can never emit a broken tag.

Option Effect
Add class Space-separated, e.g. btn btn-primary. Added via classList.add() - duplicates impossible, existing classes preserved.
Replace existing classes Wipes class before adding yours. For re-skinning links carrying an old theme's classes.
target="_blank" Added to every matched link.
rel noopener noreferrer (best practice for _blank) Β· nofollow noopener (paid/untrusted) Β· nofollow sponsored noopener (Google's sponsored markup) Β· ugc noopener Β· - keep - leaves existing values alone.
External only Limits changes to hrefs starting http:// or https://.
domain field With External only: links containing this domain count as internal and are skipped. Enter bare, e.g. mysite.com - leading www. stripped automatically.

The pill reads 12 links Β· 4 updated. That second number is the sanity check - 0 updated with External-only on means your links are relative paths (/about), or your domain filter is matching everything.

Typical run: paste a legacy post, tick External only, enter your domain, pick nofollow sponsored noopener, tick target="_blank", Apply, copy back. Thirty links fixed in five seconds.

Tool 4 - Slug Maker

Titles β†’ clean, URL-safe slugs. NFD-normalises and strips combining marks (HΓ©llo WΓΆrld β†’ Hello World, not hll-wrld), removes HTML entities, removes anything that isn't a word character / whitespace / hyphen, collapses separator runs, trims the ends.

Option Effect
Separator - (SEO standard) or _ (file names, some CMS taxonomies).
Lowercase On by default. Off preserves casing for case-sensitive asset paths.
One slug per line Batch mode. Off = whole input squashed into one slug.
Max length 0 = unlimited. Otherwise truncates then trims the trailing separator, so you never get my-great-post-.

HΓ©llo WΓΆrld - My Great Post! (2024) β†’ hello-world-my-great-post-2024

Tool 5 - Counter

Live text metrics plus SEO length guides - one full-width input with a stats dashboard beneath.

  • Characters - including spaces and newlines
  • No spaces - all whitespace removed
  • Chars + offset - count plus a configurable Offset (default 22). For when a template appends a fixed suffix (site name in a title tag, tracking string, prefix code) and you need to budget for it while writing.
  • Words - whitespace-delimited tokens
  • Lines - empty box reports 0, not 1
  • Sentences - runs ending ., ! or ?. Approximate; e.g. Dr. inflates it.
  • Read time - 200 wpm, seconds under a minute, otherwise Xm Ys

Two progress bars track the conventional truncation limits - SEO title 60 and meta description 160. Fill is green within budget, red the moment you exceed it, capped at 100% width so long strings don't blow out the layout. Both read the same textarea, so check title and description one at a time.

Shared controls

Every control is a real <button> - focusable, keyboard-activatable, driven by one delegated click handler. No dead hrefs.

Button Behaviour
πŸ“‹ Paste Reads the clipboard and re-runs the tool. Needs a secure context; if blocked, focuses the field and toasts "use Ctrl+V".
⧉ Copy Async Clipboard API with a hidden-textarea execCommand fallback, so it works on file:// too. Flashes βœ“ Copied for 1.2 s; warns on an empty field rather than doing nothing.
βœ• Clear Empties, re-runs, refocuses the textarea.
⇄ To input Sends output back to input - chain operations or re-run with different settings.
β­³ Save Downloads .txt / .html via a Blob URL. Nothing touches a server.
✨ Sample (FAQ) Loads demo content.
β–Ά Convert / Clean / Apply / Slugify Forces a run. Rarely needed - useful after a paste that didn't fire an input event.
πŸŒ™ Theme Dark/light, stored in localStorage as jstools-theme.

Toasts appear bottom-right for 2.2 s: success (green), warn (amber), error (red), info (indigo).

Internals

~250 lines of JavaScript held together by four conventions. Learn these and you can change anything.

  1. The RUN registry - every tool is a plain function on one object. Nothing else knows what a tool does; callers just invoke RUN[name]().

    const RUN = {};
    RUN.faq = () => { /* read inputs, write output */ };
    RUN.clean = () => { /* ... */ };
    
  2. Declarative buttons (data-act) - exactly one click listener, on document, reading attributes off whatever was clicked:

    <button data-act="copy" data-target="faqOut">⧉ Copy</button>
    <button data-act="swap" data-from="faqOut" data-to="faqIn">⇄</button>
    <button data-act="download" data-target="faqOut" data-file="accordion.txt">β­³</button>
    <button data-act="run" data-run="faq">β–Ά Convert</button>
    

    Event delegation means buttons added later work automatically, and a typo in one can't break the others - the core failure mode of the original inline-onclick version.

  3. Live conversion (data-live) - data-live="faq" re-runs RUN.faq 150 ms after you stop typing (debounced, so a 5,000-word paste doesn't re-parse per keystroke). Any input or select inside a .toolbar also re-runs its panel's tool on change.

  4. Stats (data-stats-for) - <span data-stats-for="faqIn"> is auto-populated with #faqIn's char/word/line count. Add the span, get the stats.

Plus:

  • No dependencies - jQuery, Bootstrap, toastr and CKEditor are gone (Bootstrap's grid β†’ ~40 lines of CSS Grid, toastr β†’ a 6-line toast(), jQuery β†’ $ / $$ helpers).
  • DOM parsing over regex - regex-based HTML editing is exactly what produces the mangled markup these tools exist to fix.
  • CSS custom properties for theming.

Add your own tool

  1. Tab button

    <button class="tab" data-tab="case">πŸ”  Case Converter</button>
    
  2. Panel - id must be tab- + the data-tab value. Copy an existing panel's structure; the only wiring that matters is data-live on the input, data-stats-for in the footers, and matching ids:

    <section class="panel" id="tab-case">
      <div class="toolbar">
        <div class="field">
          Style <select id="cs_mode">
            <option value="upper">UPPERCASE</option>
            <option value="title">Title Case</option>
          </select>
        </div>
        <div class="btns" style="margin-left:auto">
          <button class="btn primary" data-act="run" data-run="case">β–Ά Convert</button>
        </div>
      </div>
      <div class="grid">
        <!-- input card -->
        <textarea id="caseIn" class="editor" data-live="case"></textarea>
        <span data-stats-for="caseIn">0 chars</span>
        <!-- output card -->
        <textarea id="caseOut" class="editor"></textarea>
        <span data-stats-for="caseOut">0 chars</span>
      </div>
    </section>
    
  3. Register the function

    RUN.case = () => {
      const v = $('#caseIn').value;
      $('#caseOut').value = $('#cs_mode').value === 'upper'
        ? v.toUpperCase()
        : v.replace(/\w\S*/g, w => w[0].toUpperCase() + w.slice(1).toLowerCase());
      stats($('#caseOut'));
    };
    
  4. There is no step 4. Tabs, buttons, live typing, stats and toasts all pick it up from the markup.

⚠️ The data-live / data-stats-for wiring runs once at load. Inject a panel at runtime and you must re-run the two $$(...) setup loops.

Theming

Colours are CSS variables declared twice - :root (dark) and html[data-theme="light"]:

:root {
  --bg: #0e1320;
  --bg2: #141b2b;
  --card: #19212f;
  --line: #2a3446;
  --txt: #e7edf7;
  --muted: #93a1b8;
  --accent: #6366f1;
  --accent2: #22d3ee;
  --ok: #22c55e;
  --warn: #f59e0b;
  --err: #ef4444;
  --radius: 14px;
}

To rebrand, change --accent / --accent2 and the .app-header gradient. For a light-only build, set data-theme="light" on <html> and delete the toggle.

Browser support & clipboard caveats

Current Chrome, Edge, Firefox and Safari. Uses DOMParser, classList and template literals - ES2019, no transpilation, no IE.

Copy works everywhere: navigator.clipboard.writeText() first, hidden textarea + document.execCommand('copy') as fallback, explicit "press Ctrl+V" toast if both fail.

Paste is conditional - navigator.clipboard.readText() is far more restricted:

Context Result
https:// in Chrome/Edge βœ… Works (may prompt once)
http://localhost βœ… Works
file:// (double-clicked) ❌ Insecure context
Firefox ❌ Blocked for regular pages by design
Safari ⚠️ Genuine user gesture only; often still blocked

On failure the app focuses the textarea and toasts "Browser blocked paste - use Ctrl+V". Manual pasting always works and fires the same live conversion, so nothing is lost. Host over HTTPS if you want the Paste buttons.

Limitations

  • Counter's two SEO bars read one field - check title and description separately.
  • Sentence counting is naΓ―ve - e.g., Dr. and decimals each count as a sentence end.

Comments

No comments yet. Start the discussion.