TU Berlin ISIS - Userscript

// ==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.1 // @author David // @match https://isis.tu-berlin.de/* // @grant GM_addStyle // @run-at document-idle // ==/UserScript== (function() { 'use strict'; const cachedFetch = (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.