"You point at a news article, ClipDeck pulls out the headline, byline, and body — no ads, no related posts, no cookie banners. Lesson 2 is wiring Mozilla's Readability.js into the content script so 'save the whole article' becomes a single button."
Readability.js, Plainly
github.com/mozilla/readability is the JavaScript library that powers Firefox's Reader Mode. Same engine, MPL-licensed, no Firefox required to use it. The shape:
- Input: a cloned
document. Don't passdocumentdirectly — Readability mutates the tree it's given, and you do not want to mutate the page the user is reading. - Output:
{ title, byline, dir, content, textContent, length, excerpt, siteName, lang }.contentis sanitized HTML;textContentis plain text. - Cost: about 50 KB minified. Fits in a content script. Adds maybe 50–100 ms to parse a typical article.
Vendoring vs Bundling
Two reasonable ways to ship Readability with ClipDeck:
- Vendor: download
Readability.jsfrom the GitHub release, drop it underclipdeck/vendor/Readability.js, declare it incontent_scripts.jsarray BEFOREcontent.js. The library exposes a globalReadability. Simple. Auditable. - Bundle:
npm install @mozilla/readability, import in your TS/JS source, bundle with esbuild/Rollup into a single content.js. Cleaner long-term. Required if you want tree-shaking or TypeScript.
ClipDeck v1 vendors. We'll move to bundling in Track 8.
The Cloning Trick
The standard incantation:
const documentClone = document.cloneNode(true);
const reader = new Readability(documentClone);
const article = reader.parse();
document.cloneNode(true) creates a deep copy. Readability strips out everything it considers non-article (nav, sidebars, footer, comments, ads) by physically removing those nodes from the clone. The user's page is untouched.
Reading the Result
Common fields:
title— the article title, cleaned up. Often missing the site-name suffix thatdocument.titlecarries.byline— author. Best-effort; often null on small blogs.content— sanitized HTML. Safe to render in an extension iframe; still pass through a sanitizer like DOMPurify before injecting into the user's page.textContent— plain text version ofcontent. Use this for ClipDeck if you want clean text storage; usecontentif you want to preserve heading/paragraph structure.length— character count oftextContent. Useful for sanity-checking the extraction (very short = probably failed).excerpt— first paragraph or so. Good for previews.siteName,lang,dir— metadata.
If reader.parse() returns null, Readability decided the page is not article-shaped (too short, too cluttered, no clear main content). Fall back to the selection if there is one, or to the page title + URL for context.
The ClipDeck 'Save Full Article' Action
Wire it as a separate trigger from selection-save:
- Popup button: "Save full article from this page."
- Keyboard shortcut option (if you have a free combo).
- Context menu item with
contexts: ['page']: "Save this page to ClipDeck."
The handler messages the content script with {type: 'readArticle'}; the content script runs Readability and returns the article object; the SW builds a clip with the article's text, a flag isArticle: true, and the URL + title — same shape as a selection clip, just with more body.
Performance Note
Readability runs in tens of milliseconds on most pages and up to a few hundred ms on giant ones (long Wikipedia articles, archived forum threads). Run it inside the content script's main thread; users won't notice. If you ever needed to keep the page responsive during parse, you could offload to a Web Worker, but for one-shot per click it's overkill.