So, I've created my first Chrome extension! What this does is it highlights every post on Reddit that comes from a source of dubious quality. It provides a few sources by default and allows the user to add or remove from this list. The user also has the choice between either highlighting the offending post or hiding it altogether.
All settings are stored in sync storage which allows them to be used across chrome instances.
Project site: https://github.com/Vannevelj/RedditCrap
Chrome Store: https://chrome.google.com/webstore/detail/redditcrap/kkbdfmnmdigakifakfglcokgohgolgan
Yes, it looks ugly. I take PRs.
redditcrap.js Github
// Register an event listener to check for crap when the stored sites change
chrome.storage.onChanged.addListener(function(changes, area) {
checkCrap();
})
// Finds all the appropriate domains in the DOM, checks whether they're blacklisted and acts accordingly
function checkCrap() {
var domains = document.getElementsByClassName('domain');
chrome.storage.sync.get(['crappySites', 'crappyAction'], function(data) {
for (var i = 0; i < domains.length; i++) {
// urls are in the form of '(url)'
// (surrounded by brackets)
var url = domains[i].textContent.substring(1, domains[i].textContent.length - 1);
var parentNode = getParentByClass(domains[i], 'title');
var entryNode = getThingNode(parentNode);
if (parentNode.style.backgroundColor === 'red' ||
entryNode.style.display === 'none') {
parentNode.style.backgroundColor = '';
entryNode.style.display = '';
}
for (var index = 0; index < data.crappySites.length; index++) {
var shouldBeColoured = data.crappySites[index].indexOf(url) > -1;
if (shouldBeColoured) {
if (data.crappyAction === 1) {
parentNode.style.backgroundColor = 'red';
}
if (data.crappyAction === 2) {
entryNode.style.display = 'none';
}
break;
}
}
}
});
}
function getParentByClass(currentNode, className) {
while (currentNode = currentNode.parentElement) {
if (currentNode.classList.contains(className)) {
return currentNode;
}
}
}
function getThingNode(parentNode) {
while (parentNode = parentNode.parentElement) {
if (parentNode.getAttribute('data-fullname')) {
return parentNode;
}
}
}
// In this section we perform crapchecks when the page has changed
// This is important in the case of NeverEndingReddit which allows a user to scroll down to load a new page
// By introducing a short delay we prevent the page from being crapchecked for every modification (which is problematic if they come in quick succession)
var lastTimeModified;
var timeDelay = 2000; // 2 seconds
// When the subtree is modified for the first time we do an initial crapcheck.
// This allows us to look responsive while we discard all but one closely subsequent events
// After the last event we call checkCrap() again to make sure nothing has been missed due to the subsequent modification events
// Afterwards we reset the value so this can be repeated for other grouped subtree modification events
var performedFastCheck = false;
document.addEventListener('DOMSubtreeModified', function() {
if (!performedFastCheck) {
checkCrap();
performedFastCheck = true;
}
lastTimeModified = new Date().getTime();
setTimeout(function() {
startTimer();
}, timeDelay);
});
function startTimer() {
if (lastTimeModified + timeDelay > new Date().getTime()) {
clearTimeout();
setTimeout(function() {
startTimer()
}, timeDelay);
} else {
checkCrap();
performedFastCheck = false;
}
};
popup.html Github
<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" href="../css/popup.css">
<title>RedditCrap</title>
</head>
<body>
<fieldset>
<legend> Settings </legend>
<form id="actionForm">
<select id="crappyAction">
<option value='none' disabled selected>What happens to blacklisted entries?</option>
<option value="colour">Colourize</option>
<option value="hide">Hide</option>
</select>
</form>
</fieldset>
<form id="siteForm">
<label for="siteInput"> Enter site: </label>
<input type="text" id="siteInput">
<input type="submit" value="Add to blacklist">
</form>
<ul id="crappySites">
</ul>
<button id='reset'> Reset to default blacklist </button>
</body>
<script src="../js/popup.js"></script>
</html>
popup.css Github
body {
font-family: "Segoe UI", "Lucida Grande", Tahoma, sans-serif;
width: 300px;
height: 100%;
}
input {
padding: 5px;
margin: 2px;
border-radius: 3px;
}
li {
font-size: 1.5em;
}
fieldset {
padding: 5px;
margin: 10px;
border-radius: 5px;
}
popup.js Github
var shittyCrapSites = ['dailymail.co.uk', 'independent.co.uk', 'ibtimes.co.uk', 'huffingtonpost.com', 'rt.com', 'express.co.uk', 'telegraph.co.uk'];
// We seed the storage with crappy sites if none are found yet
chrome.storage.sync.get(['crappySites', 'crappyAction'], function (data) {
if (!data.crappySites || data.crappySites.length === 0) {
chrome.storage.sync.set({ 'crappySites': shittyCrapSites }, function () {});
}
var selectElement = document.getElementById('crappyAction');
if (!data.crappyAction) {
chrome.storage.sync.set({ 'crappyAction': 1 }, function () {});
} else {
selectElement.selectedIndex = data.crappyAction;
}
});
document.addEventListener('DOMContentLoaded', function () {
displayExistingFilters();
document.getElementById('siteForm').addEventListener('submit', add);
document.getElementById('reset').addEventListener('click', reset);
document.getElementById('crappyAction').addEventListener('change', actionSettingChanged);
});
function displayExistingFilters() {
// Get the existing filters from storage
chrome.storage.sync.get('crappySites', function (data) {
// Add them to the list
data.crappySites.forEach(function (element) {
addListElement(element);
}, this);
});
}
function addListElement(value) {
if (!value) {
return;
}
var resultList = document.getElementById('crappySites');
var inputField = document.getElementById('siteInput');
var newItem = document.createElement('li');
newItem.setAttribute('id', value);
newItem.appendChild(document.createTextNode(value + ' '));
var newCancelImage = document.createElement('img');
newCancelImage.setAttribute('src', '../res/cross.png');
newCancelImage.addEventListener('click', function () { remove(value); });
newItem.appendChild(newCancelImage);
resultList.appendChild(newItem);
inputField.value = '';
}
function remove(value) {
chrome.storage.sync.get('crappySites', function (data) {
var sites = data.crappySites;
console.log('trying to remove ' + value);
var newSites = [];
for (var i = 0; i < sites.length; i++) {
if (sites[i] !== value) {
newSites.push(sites[i]);
}
}
console.log('new list: ' + newSites);
chrome.storage.sync.set({ 'crappySites': newSites }, function () {});
var resultList = document.getElementById('crappySites');
resultList.removeChild(document.getElementById(value));
});
}
function add(e) {
e.preventDefault();
var inputField = document.getElementById('siteInput');
var value = inputField.value;
addListElement(value);
chrome.storage.sync.get('crappySites', function (data) {
var sites = data.crappySites;
sites.push(value);
chrome.storage.sync.set({ 'crappySites': sites }, function () {});
});
}
function reset() {
chrome.storage.sync.set({ 'crappySites': shittyCrapSites }, function () {});
var resultList = document.getElementById('crappySites');
var newResultList = document.createElement('ul');
newResultList.setAttribute('id', 'crappySites');
resultList.parentElement.replaceChild(newResultList, resultList);
shittyCrapSites.forEach(function (element) {
addListElement(element);
}, this);
}
function actionSettingChanged() {
var selectElement = document.getElementById('crappyAction');
var selectedOption = selectElement.selectedIndex;
chrome.storage.sync.set({ 'crappyAction': selectedOption }, function () {});
}
manifest.json Github
{
"manifest_version": 2,
"name": "RedditCrap",
"version": "1.0",
"description": "This extension highlights all submissions on reddit.com that point to a crappy clickbait source.",
"author": "Jeroen Vannevel",
"permissions": [
"http://www.reddit.com/*",
"https://www.reddit.com/*",
"storage"
],
"content_scripts": [
{
"matches": [
"http://www.reddit.com/*",
"https://www.reddit.com/*"
],
"js": [
"app/js/redditcrap.js"
],
"run_at": "document_end"
}
],
"browser_action": {
"default_icon": "app/res/icon16.png",
"default_popup": "app/html/popup.html",
"default_title": "RedditCrap"
},
"icons": {
"16": "app/res/icon16.png",
"48": "app/res/icon48.png",
"128": "app/res/icon128.png"
}
}