// ==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.3
// @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; }
#nav-drawer .list-group .list-group-item.custom-course-loading .media-body:after { display: inline-block; margin-left: 2px; width: 16px; height: 16px; content: ""; background: url(data:image/gif;base64,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); }`);
// 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'
};
// Initialize
let parser = new DOMParser();
let $courses = document.querySelectorAll(selectors.courses);
// Add loading indicator to each course element
$courses.forEach(ele => ele.classList.add('custom-course-loading'));
// Fetch all courses and process it
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))
.finally(() => $courses.forEach(ele => ele.classList.remove('custom-course-loading')));
// 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.