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 " 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
(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 + + 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, not1 - 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.
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 = () => { /* ... */ };Declarative buttons (
data-act) - exactly one click listener, ondocument, 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-
onclickversion.Live conversion (
data-live) -data-live="faq"re-runsRUN.faq150 ms after you stop typing (debounced, so a 5,000-word paste doesn't re-parse per keystroke). Anyinputorselectinside a.toolbaralso re-runs its panel's tool on change.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
Tab button
<button class="tab" data-tab="case">π Case Converter</button>Panel -
idmust betab-+ thedata-tabvalue. Copy an existing panel's structure; the only wiring that matters isdata-liveon the input,data-stats-forin 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>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')); };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.