Skip to content

Instantly share code, notes, and snippets.

@Munawwar
Last active June 30, 2024 12:29
Show Gist options
  • Save Munawwar/50091fb9f77b6565930047f0de818484 to your computer and use it in GitHub Desktop.
Save Munawwar/50091fb9f77b6565930047f0de818484 to your computer and use it in GitHub Desktop.
Firefox reader mode bookmarklet

Create a bookmark with the contents of bookmarklet.js. And then when you are on an distracting page, click the bookmark and poof .. clean page to read.

I used https://caiorss.github.io/bookmarklet-maker/ to convert source.js to be bookmark compatible.

javascript:(function()%7Bimport('https%3A%2F%2Fesm.sh%2F%40mozilla%2Freadability').then((%7B%20Readability%20%7D)%20%3D%3E%20%7B%0A%20%20var%20result%20%3D%20new%20Readability(document.cloneNode(true)).parse()%3B%0A%20%20const%20%7B%0A%20%20%20%20title%2C%0A%20%20%20%20byline%2C%0A%20%20%20%20dir%2C%0A%20%20%20%20lang%2C%0A%20%20%20%20content%2C%0A%20%20%20%20textContent%2C%0A%20%20%20%20siteName%2C%0A%20%20%20%20publishedTime%2C%0A%20%20%7D%20%3D%20result%3B%0A%0A%20%20let%20publishedDate%20%3D%20new%20Date(publishedTime)%3B%0A%20%20publishedDate%20%3D%20Number.isNaN(publishedDate.getTime())%0A%20%20%20%20%3F%20undefined%0A%20%20%20%20%3A%20publishedDate.toLocaleDateString('en-US'%2C%20%7B%0A%20%20%20%20%20%20%20%20year%3A%20'numeric'%2C%0A%20%20%20%20%20%20%20%20month%3A%20'long'%2C%0A%20%20%20%20%20%20%20%20day%3A%20'numeric'%2C%0A%20%20%20%20%20%20%7D)%3B%0A%0A%20%20function%20countAndApproximateWords(text)%20%7B%0A%20%20%20%20const%20words%20%3D%20text.split(%2F%5B%5E%5Cp%7BL%7D%5Cp%7BN%7D%5D%2B%2Fu).filter(word%20%3D%3E%20word.length%20%3E%200)%3B%0A%20%20%20%20const%20wordCount%20%3D%20words.length%3B%0A%20%20%0A%20%20%20%20if%20(wordCount%20%3C%20100)%20%7B%0A%20%20%20%20%20%20return%20%22%3C100%20words%22%3B%0A%20%20%20%20%7D%20else%20%7B%0A%20%20%20%20%20%20const%20approximateCount%20%3D%20Math.round(wordCount%20%2F%20100)%20*%20100%3B%0A%20%20%20%20%20%20return%20%60%24%7BapproximateCount%7D%20words%60%3B%0A%20%20%20%20%7D%0A%20%20%7D%0A%0A%20%20document.write(%2F*%20html%20*%2F%60%0A%20%20%20%20%3Chtml%20lang%3D%22%24%7Blang%7D%22%20dir%3D%22%24%7Bdir%7D%22%3E%0A%20%20%20%20%20%20%3Chead%3E%0A%20%20%20%20%20%20%20%20%3Ctitle%3E%24%7Btitle%7D%3C%2Ftitle%3E%0A%20%20%20%20%20%20%20%20%3Clink%20rel%3D%22preconnect%22%20href%3D%22https%3A%2F%2Ffonts.googleapis.com%22%3E%0A%20%20%20%20%20%20%20%20%3Clink%20rel%3D%22preconnect%22%20href%3D%22https%3A%2F%2Ffonts.gstatic.com%22%20crossorigin%3E%0A%20%20%20%20%20%20%20%20%3Clink%20href%3D%22https%3A%2F%2Ffonts.googleapis.com%2Fcss2%3Ffamily%3DOpen%2BSans%3Aital%2Cwght%400%2C300..800%3B1%2C300..800%26display%3Dswap%22%20rel%3D%22stylesheet%22%3E%0A%20%20%20%20%20%20%20%20%3Cstyle%3E%0A%20%20%20%20%20%20%20%20%20%20body%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20font-family%3A%20'Open%20Sans'%2C%20sans-serif%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20font-size%3A%201.5rem%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20line-height%3A%201.7%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20width%3A%20960px%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20margin-right%3A%20auto%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20margin-left%3A%20auto%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20transition%3A%20background-color%200.3s%2C%20color%200.3s%3B%0A%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20pre%3Ahas(%3E%20code)%2C%20code%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20max-width%3A%20100%25%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20overflow-x%3A%20auto%3B%0A%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20body.dark-mode%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20background-color%3A%20%231a1a1a%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20color%3A%20%23f0f0f0%3B%0A%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20.dark-mode-toggle%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20position%3A%20fixed%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20top%3A%2020px%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20right%3A%2020px%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20padding%3A%2010px%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20background-color%3A%20%23333%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20color%3A%20%23fff%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20border%3A%20none%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20border-radius%3A%205px%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20cursor%3A%20pointer%3B%0A%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%3C%2Fstyle%3E%0A%20%20%20%20%20%20%3C%2Fhead%3E%0A%20%20%20%20%20%20%3Cbody%3E%0A%20%20%20%20%20%20%20%20%3Cbutton%20class%3D%22dark-mode-toggle%22%20onclick%3D%22toggleDarkMode()%22%3EToggle%20Dark%20Mode%3C%2Fbutton%3E%0A%20%20%20%20%20%20%20%20%3Ch1%3E%24%7Btitle%7D%3C%2Fh1%3E%0A%20%20%20%20%20%20%20%20%3Caside%3E%24%7B%5Bbyline%2C%20publishedDate%2C%20countAndApproximateWords(textContent)%5D.filter(Boolean).join('%20%7C%20')%7D%3C%2Faside%3E%0A%20%20%20%20%20%20%20%20%24%7Bcontent%7D%0A%20%20%20%20%20%20%20%20%3Cscript%3E%0A%20%20%20%20%20%20%20%20%20%20function%20setDarkMode(isDark)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20(isDark)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20document.body.classList.add('dark-mode')%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20(document.getElementById('highlight-theme'))%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20document.getElementById('highlight-theme').href%20%3D%20'https%3A%2F%2Fcdnjs.cloudflare.com%2Fajax%2Flibs%2Fhighlight.js%2F11.7.0%2Fstyles%2Fgithub-dark.min.css'%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%20else%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20document.body.classList.remove('dark-mode')%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20(document.getElementById('highlight-theme'))%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20document.getElementById('highlight-theme').href%20%3D%20'https%3A%2F%2Fcdnjs.cloudflare.com%2Fajax%2Flibs%2Fhighlight.js%2F11.7.0%2Fstyles%2Fgithub.min.css'%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%7D%0A%0A%0A%20%20%20%20%20%20%20%20%20%20function%20toggleDarkMode()%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20document.body.classList.toggle('dark-mode')%3B%0A%20%20%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%20%20%20%20%2F%2F%20Check%20system%20preference%20and%20set%20initial%20mode%0A%20%20%20%20%20%20%20%20%20%20if%20(window.matchMedia%20%26%26%20window.matchMedia('(prefers-color-scheme%3A%20dark)').matches)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20setDarkMode(true)%3B%0A%20%20%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%20%20%20%20%2F%2F%20Listen%20for%20changes%20in%20system%20preference%0A%20%20%20%20%20%20%20%20%20%20window.matchMedia('(prefers-color-scheme%3A%20dark)').addEventListener('change'%2C%20e%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20setDarkMode(e.matches)%3B%0A%20%20%20%20%20%20%20%20%20%20%7D)%3B%0A%20%20%20%20%20%20%20%20%3C%2Fscript%3E%0A%20%20%20%20%20%20%3C%2Fbody%3E%0A%20%20%20%20%3C%2Fhtml%3E%0A%20%20%60)%3B%0A%7D)%3B%7D)()%3B
import('https://esm.sh/@mozilla/readability').then(({ Readability }) => {
var result = new Readability(document.cloneNode(true)).parse();
const {
title,
byline,
dir,
lang,
content,
textContent,
siteName,
publishedTime,
} = result;
let publishedDate = new Date(publishedTime);
publishedDate = Number.isNaN(publishedDate.getTime())
? undefined
: publishedDate.toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric',
});
function countAndApproximateWords(text) {
const words = text.split(/[^\p{L}\p{N}]+/u).filter(word => word.length > 0);
const wordCount = words.length;
if (wordCount < 100) {
return "<100 words";
} else {
const approximateCount = Math.round(wordCount / 100) * 100;
return `${approximateCount} words`;
}
}
document.write(/* html */`
<html lang="${lang}" dir="${dir}">
<head>
<title>${title}</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,300..800;1,300..800&display=swap" rel="stylesheet">
<style>
body {
font-family: 'Open Sans', sans-serif;
font-size: 1.5rem;
line-height: 1.7;
width: 960px;
margin-right: auto;
margin-left: auto;
transition: background-color 0.3s, color 0.3s;
}
pre:has(> code), code {
max-width: 100%;
overflow-x: auto;
}
body.dark-mode {
background-color: #1a1a1a;
color: #f0f0f0;
}
.dark-mode-toggle {
position: fixed;
top: 20px;
right: 20px;
padding: 10px;
background-color: #333;
color: #fff;
border: none;
border-radius: 5px;
cursor: pointer;
}
</style>
</head>
<body>
<button class="dark-mode-toggle" onclick="toggleDarkMode()">Toggle Dark Mode</button>
<h1>${title}</h1>
<aside>${[byline, publishedDate, countAndApproximateWords(textContent)].filter(Boolean).join(' | ')}</aside>
${content}
<script>
function setDarkMode(isDark) {
if (isDark) {
document.body.classList.add('dark-mode');
if (document.getElementById('highlight-theme')) {
document.getElementById('highlight-theme').href = 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/styles/github-dark.min.css';
}
} else {
document.body.classList.remove('dark-mode');
if (document.getElementById('highlight-theme')) {
document.getElementById('highlight-theme').href = 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/styles/github.min.css';
}
}
}
function toggleDarkMode() {
document.body.classList.toggle('dark-mode');
}
// Check system preference and set initial mode
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
setDarkMode(true);
}
// Listen for changes in system preference
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => {
setDarkMode(e.matches);
});
</script>
</body>
</html>
`);
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment