// ==UserScript==
// @name TU Berlin - New posts badge
// @description Shows badge for new posts next to visible courses in sidebar
// @namespace https://tu-berlin.de/
// @version 0.2
// @author David
// @match https://isis.tu-berlin.de/*
// @grant GM_addStyle
// @run-at document-idle
// ==/UserScript==
(function() {
'use strict';
const cachedFetch = async (url, options) => {
let expiry = 10 * 60; // 10 min default
if (typeof options === 'number') {
expiry = options;
options = undefined;
} else if (typeof options === 'object') {
expiry = options.seconds || expiry;
}
// Use the URL as the cache key to sessionStorage
let cacheKey = url;
let cached = localStorage.getItem(cacheKey);
let whenCached = localStorage.getItem(cacheKey + ':ts');
if (cached !== null && whenCached !== null) {
let age = (Date.now() - whenCached) / 1000;
if (age < expiry) {
let response = new Response(new Blob([cached]));
return Promise.resolve(response);
} else {
// We need to clean up this old key
localStorage.removeItem(cacheKey);
localStorage.removeItem(cacheKey + ':ts');
}
}
return fetch(url, options).then(response => {
if (response.status === 200) {
let ct = response.headers.get('Content-Type');
if (ct && (ct.match(/application\/json/i) || ct.match(/text\//i))) {
response.clone().text().then(content => {
localStorage.setItem(cacheKey, content);
localStorage.setItem(cacheKey + ':ts', Date.now());
});
}
}
return response;
});
};
const deleteFetchedItem = (url) => localStorage.removeItem(url) && localStorage.removeItem(url + ':ts');
// Add some custom CSS
GM_addStyle(`#nav-drawer .list-group .list-group-item.custom-course-alert { background-color: rgba(0, 0, 0, 0.1); }
#nav-drawer .list-group .list-group-item.custom-course-alert:hover { background-color: #c50e1f; color: #ffffff; }
#nav-drawer .list-group .list-group-item.custom-course-alert .media-body:after { content: " \uD83D\uDD14(" attr(data-unread-count) ")"; font-size: smaller; white-space: nowrap; }`);
// DOM selectors
const selectors = {
courses: '#nav-drawer a.list-group-item[data-parent-key="mycourses"]',
courseLink: '#page-navbar .breadcrumb-item:last-child a',
unreadNotificationLink: '#page-content .activityinstance .unread a',
courseLinkFooter: '#page-footer .homelink a',
unreadLinks: '#region-main thead .replies a, #region-main tbody .replies span.unread'
};
// Fetch all courses and process it
let parser = new DOMParser();
let $courses = document.querySelectorAll(selectors.courses);
Promise.all([...$courses].map(ele => ele.href).map(url => cachedFetch(url).then(resp => resp.text())))
.then(htmls => [...htmls].map(html => parser.parseFromString(html, 'text/html'))
.filter(doc => doc.querySelector(selectors.unreadNotificationLink))
.map(doc => [
doc.querySelector(selectors.courseLink),
doc.querySelectorAll(selectors.unreadNotificationLink)
])
.map(arr => [
new URLSearchParams(arr.shift().search).get('id'),
[...arr.shift()].map(link => link.innerText.split(' ').shift())
.map(count => parseInt(count, 10))
.reduce((sum, n) => sum + n)
]))
.then(posts => posts.forEach(post => {
let $course = document.querySelector(selectors.courses + '[data-key="' + post.shift() + '"]');
$course.classList.add('custom-course-alert');
$course.querySelector('.media-body').dataset.unreadCount = post.shift();
}))
.catch(err => console.error('Error:', err));
// Unread links/discussion view => Clear cache storage
if (document.URL.indexOf('mod/forum/discuss') > 0) deleteFetchedItem(document.querySelector(selectors.courseLinkFooter).href);
document.querySelectorAll(selectors.unreadLinks).forEach(ele => ele.addEventListener('click', deleteFetchedItem(document.querySelector(selectors.courseLinkFooter).href), false));
})();
Be the first to comment
You can use [html][/html], [css][/css], [php][/php] and more to embed the code. Urls are automatically hyperlinked. Line breaks and paragraphs are automatically generated.