NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @name Golem.de Forum+ // @author Bccc1 // @version 1.0.1 // @license MIT // @homepageURL https://github.com/Bccc1/golem-plus-webextension // @supportURL https://github.com/Bccc1/golem-plus-webextension/issues // @grant none // @match https://forum.golem.de/kommentare/*read.html* // @match https://forum.golem.de/read.php?* // @match https://forum.golem.de/sonstiges/bolzplatz/*read.html* // ==/UserScript== /* TODO div.golem-forum_flattr > div.social-bar muss irgendwie optisch angepasst werden. ist zu fett. Mindestens auf golem-forum_flattr aus dem style margin-bottom:1em rausnehmen. besser flattr in action bar verschieben. Werbung zwischen den Kommentaren erkennen. (Momentan werden die mit einer depth von 0 dargestellt) shortcuts reparieren */ /* * Detect if logged in. This is needed for the viewmode detection. */ var isLoggedIn = true; if(document.querySelector("input[value=Login]")) isLoggedIn = false; console.log("is logged in: "+isLoggedIn); /* * Detect view mode. only do stuff if viewmode is indented. * Otherwise just display a info that this userscript requires indented view. */ var correctViewmode = true; var preThreadLinks = document.querySelectorAll("p#pre-thread > a"); if(preThreadLinks){ var changeViewModeLink; for(var i=0; i<preThreadLinks.length; i++){ if(preThreadLinks[i].textContent == "Ansicht wechseln"){ changeViewModeLink = preThreadLinks[i]; break; } } if(changeViewModeLink){ //extract number var nextViewMode = changeViewModeLink.href.slice(changeViewModeLink.href.lastIndexOf("#")-1,changeViewModeLink.href.lastIndexOf("#")); if(isLoggedIn){ //logged in 3, links to 2 if(nextViewMode == "2"){ correctViewmode = true; }else { correctViewmode = false; var correctChangeViewModeLink = ""; correctChangeViewModeLink += changeViewModeLink.href.slice(0,changeViewModeLink.href.lastIndexOf("#")-1); correctChangeViewModeLink += "3"; correctChangeViewModeLink += changeViewModeLink.href.slice(changeViewModeLink.href.lastIndexOf("#"), changeViewModeLink.href.length); addChangeViewModeButton(correctChangeViewModeLink); } //p#pre-thread > a (innerHTML is Ansicht wechseln). access href. "https://forum.golem.de/read.php?115321,5007333,5007333,sv=2#msg-5007333" //extract viewmode changeViewModeLink.href.lastIndexOf("#") }else { //anonymous 2, links to 1 if(nextViewMode == "1"){ correctViewmode = true; }else { correctViewmode = false; var correctChangeViewModeLink = ""; correctChangeViewModeLink += changeViewModeLink.href.slice(0,changeViewModeLink.href.lastIndexOf("#")-1); correctChangeViewModeLink += "2"; correctChangeViewModeLink += changeViewModeLink.href.slice(changeViewModeLink.href.lastIndexOf("#"), changeViewModeLink.href.length); addChangeViewModeButton(correctChangeViewModeLink); } //"https://forum.golem.de/read.php?115321,5007333,5007333,anonymous_threaded_read=2#msg-5007333" //extract viewmode } } } console.log("correct Viewmode: "+correctViewmode); function addChangeViewModeButton(link){ console.log("called addChangeViewModeButton "); //link should already be the correct one var newNode = document.createElement("div"); newNode.style.background = "#e3f1f6"; newNode.style.border = "1px solid #d2e5ec"; newNode.style.color = "#00abea"; newNode.style.padding ="15px 20px"; var textElement = document.createElement("p"); var boldElement = document.createElement("b"); var textContent = document.createTextNode("Das Golem.de Forum+ UserScript funktioniert nicht in dieser Ansicht."); var linkContent = document.createTextNode("Um zur passenden Ansicht zu wechseln, klicken Sie hier."); var linkElement = document.createElement("a"); linkElement.style.color = "#00abea"; linkElement.href=link; linkElement.appendChild(linkContent); textElement.appendChild(boldElement); boldElement.appendChild(textContent); newNode.appendChild(textElement); newNode.appendChild(linkElement); var referenceNode = document.querySelector("ol.list-pages"); referenceNode.parentNode.insertBefore(newNode, referenceNode); } //for shortcut navigation var currPos = -1; var headerList = document.querySelectorAll(".message-new"); /* * Assign Depth for each post * div thread-detail ol li padding-left / 20 => depth */ var threadDetail = document.getElementById("thread-detail"); var liItems = threadDetail.querySelectorAll("div#thread-detail > ol > li[data-user-id]"); //threadDetail.getElementsByTagName("li"); var httpRequestRequired = false; /* * Add optical highlights */ var colorLookup = []; colorLookupDefault = "transparent"; colorLookup[0] = "#1ccdff"; colorLookup[1] = "#54ff00"; colorLookup[2] = "#ffc300"; colorLookup[3] = "#ff0000"; colorLookup[4] = "#c700ff"; // loop through 1 to 5 / second to last /* * Stuff in here is the actual userscript. */ if(correctViewmode){ for(var i=0; i < liItems.length; i++) { var depth = parseInt(liItems[i].style.paddingLeft,10) / 20; liItems[i].setAttribute("depth", depth); if(i>0 && depth == 1){ //only the root element i=0 should have a depth of 1 //if this condition is true, an overflow happened and a depth of 16 was interpreted as 1 httpRequestRequired = true; } } // increase overall width by 10px; // document.getElementById("screen").style.width = "990px"; // document.getElementById("forum-main").style.width = "630px"; threadDetail.style.borderWidth="0px"; threadDetail.style.backgroundColor="transparent"; if(httpRequestRequired){ requestThreadAsView(1,requestDepthsFromThreadViewListener); //the stuff from the else branch is called in a callback of requestDepthsFromThreadView }else { applyDepthAndAddActionBar(); insertActionBarStyles(); } //Shortcut Section if (document.addEventListener ){ document.addEventListener('keydown', function(e) { // only bind event to text-accepting elements, if they have been explicitly selected if ( this !== e.target && ( /textarea|select/i.test( e.target.nodeName ) || e.target.type === "text") ) { return; } if (e.which == 74) jumpToNext(); }); } if (document.addEventListener ){ document.addEventListener('keydown', function(e) { // only bind event to text-accepting elements, if they have been explicitly selected if ( this !== e.target && ( /textarea|select/i.test( e.target.nodeName ) || e.target.type === "text") ) { return; } if (e.which == 75) jumpToPrevious(); }); } } function applyDepthAndAddActionBar(){ for(var i=0; i < liItems.length; i++) { var depth = parseInt(liItems[i].getAttribute("depth"),10); liItems[i].style.paddingLeft="20px"; liItems[i].style.marginLeft= (depth-1)*5+"px"; liItems[i].style.backgroundColor="rgb(249, 249, 249)"; //liItems[i].style.borderLeftColor="orange"; liItems[i].style.borderLeftWidth="5px"; liItems[i].style.borderLeftStyle="solid"; if(depth == 1){ liItems[i].style.borderLeftColor=colorLookupDefault; }else{ var colorLookupIndex = (depth - 2) % colorLookup.length; liItems[i].style.borderLeftColor=colorLookup[colorLookupIndex]; } // move links to actionBar, which is only visible on :hover var links = liItems[i].getElementsByClassName("links")[0]; var actionBar = document.createElement("div"); if(links){ actionBar.innerHTML=links.innerHTML; actionBar.classList.add("links"); actionBar.classList.add("post-hover-class"); actionBar.style.cssText=liItems[i].style.cssText; actionBar.style.backgroundColor="rgb(255, 249, 214)"; links.parentNode.removeChild(links); //jump to parent actionBar.innerHTML=actionBar.innerHTML+"|"; //spacer var toParentBtn = document.createElement("a"); toParentBtn.innerHTML="▲"; toParentBtn.href="javascript:void(0);"; toParentBtn.onclick=jumpToParent; toParentBtn.classList.add("link-class"); actionBar.appendChild(toParentBtn); insertAfter(actionBar, liItems[i]); }else{ //there are no links. Probably not logged in. //actionBar.style.backgroundColor="rgb(255, 0, 0)"; //actionBar.innerHTML="Fehler: Das \"links\" Element konnte nicht gefunden werden."; } } } function insertActionBarStyles(){ // add styleclass for .post-hover-class insertCss('.post-hover-class { display: none; }'); insertCss('.post-hover-class a { color: rgb(76, 76, 76); }'); insertCss('li:hover + .post-hover-class { display: block; padding: 10px 20px; }'); insertCss('.post-hover-class:hover { display: block; padding: 10px 20px; }'); insertCss('.link-class:hover { text-decoration: none; }'); } /** view is a number indicating the viewmode, with 1=thread view, 2=just the posts, 3=the posts indendented. The listener is an optional callback that is called, once the page loaded. The page content can be accessed in the listener with this.responseXML */ function requestThreadAsView(view,listener){ var url = window.location.href; var appendix = url.slice(url.lastIndexOf("/")+1,url.length); //after that appendix is "115321,5007333,5007333,read.html#msg-5007333" if(isLoggedIn){ appendix = appendix.replace("read.html", "sv="+view); //after that appendix is "115321,5007333,5007333,sv=1#msg-5007333" }else { //if not logged in, the url should use anonymous_threaded_read instead of sv //and the viewmode numbers are different: 1 thread, 2 indented, 3 plain switch (view) { case 2: view = 3; break; case 3: view = 2; break; default: view = 1; } appendix = appendix.replace("read.html", "anonymous_threaded_read="+view); } var oReq = new XMLHttpRequest(); if(listener){ oReq.addEventListener("load", listener); oReq.open("GET", "https://forum.golem.de/read.php\?"+appendix); oReq.responseType = "document"; }else { oReq.open("HEAD", "https://forum.golem.de/read.php\?"+appendix); } oReq.send(); } function requestDepthsFromThreadViewListener () { var tableListthread = this.responseXML.getElementsByClassName("table-listthread")[0]; var nodes = tableListthread.querySelectorAll("tbody > tr:not(:first-child)"); // table.table-listthread > tbody > alle tr außer dem ersten sind die nodes //die anzahl der img objecte in node > td > div.indent scheint die depth zu sein //defaultView: li > h4 die id vom h4 ist msg-5007355 //thread view: tr > td > h3 die id vom h3 ist msg-5007355 //Performance wise this is shit, but I'm lazy. //go trough all li items, search for the message id in the nodes, apply depth. if messageId can't be found in nodes, mark the post and log it. for(var l=0; l < liItems.length; l++) { var header = liItems[l].getElementsByTagName("h4")[0]; var msgId = header.id; var found = false; for(var n=0; n < nodes.length; n++) { var nodeMsgId = nodes[n].querySelectorAll("td > h3")[0].id; if(nodeMsgId == msgId){ var imgNodes = nodes[n].querySelectorAll("td > div.indent > img"); var depth = imgNodes.length; liItems[l].setAttribute("depth", depth); found = true; break; } } if(!found){ console.log("msgId "+msgId+" could not be mapped"); } } requestThreadAsView(3); //reset view to correct mode applyDepthAndAddActionBar(); insertActionBarStyles(); } function insertCss( code ) { var style = document.createElement('style'); style.type = 'text/css'; if (style.styleSheet) { // IE style.styleSheet.cssText = code; } else { // Other browsers style.innerHTML = code; } document.getElementsByTagName("head")[0].appendChild( style ); } function insertAfter(newNode, referenceNode) { referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling); } function jumpToParent(){ var li = this.parentElement.parentElement; var depthOfThis = parseInt(li.getAttribute("depth"),10); if(depthOfThis==1) {return;} //if first post, do nothing var msgId = li.getElementsByClassName("head2")[0].getAttribute("id"); var index = -1; for(var i=0; i < liItems.length; i++) { if(liItems[i].getElementsByClassName("head2")[0].getAttribute("id")==msgId){ index=i; break; } } for(var i=index-1; i >= 0; i--){ var tmpDepth = parseInt(liItems[i].getAttribute("depth"),10); if(tmpDepth+1==depthOfThis){ liItems[i].scrollIntoView(); break; } } } function clamp(num, min, max) { return num <= min ? min : num >= max ? max : num; } jumpToNext = function() { currPos++; headerList = document.querySelectorAll(".message-new"); currPos = clamp(currPos,0,headerList.length-1); //console.warn(list.length); var currElem = headerList[currPos]; highlightEntry(currElem); currElem.scrollIntoView(); //window.scrollBy(0, -60); } jumpToPrevious = function() { currPos--; headerList = document.querySelectorAll(".message-new"); currPos = clamp(currPos,0,headerList.length-1); //console.warn(list.length); var currElem = headerList[currPos]; highlightEntry(currElem); currElem.scrollIntoView(); //window.scrollBy(0, -60); } function JumpToNextComment(){ //thread id = first messageId of thread threadId = document.querySelector("h4:first-of-type").id; forumId = document.querySelector("#gsu").elements["forum_id"].value; lastVisitedMessageKey = "lastVisitedMessage,f:"+forumId+",t:"+threadId; if(sessionStorage[lastVisitedMessageKey]){ //if this is a page refresh, ignore sessionStorage.lastVisitedMessage and do the same as in the else branch var lastVisitedMessageHeader = document.getElementById(sessionStorage[lastVisitedMessageKey]); if(lastVisitedMessageHeader != null && lastVisitedMessageHeader.classList.contains("message-new")){ //normal behaviour, go to next message-new var headerList = document.querySelectorAll(".message-new"); for (var i = 0; i < headerList.length; ++i) { var item = headerList[i]; if(item.id==sessionStorage[lastVisitedMessageKey]){ console.log("found something"); var header = headerList[i+1]; if(header){ sessionStorage[lastVisitedMessageKey] = header.id; header.scrollIntoView(); HighlightMessage(header.id); } break; } } }else{ //this is a page refresh, ignore sessionStorage.lastVisitedMessage and do the same as in the else branch var header = document.querySelector(".message-new"); sessionStorage[lastVisitedMessageKey] = header.id; header.scrollIntoView(); HighlightMessage(header.id); } }else{ var header = document.querySelector(".message-new"); sessionStorage[lastVisitedMessageKey] = header.id; header.scrollIntoView(); HighlightMessage(header.id); highlightEntry(header); } } function HighlightMessage(messageId){ alert("highlight #".concat(messageId)); } highlightEntry = function(element) { element.style.color = "red"; var parent = element.parentNode; parent.style.backgroundColor = "#fffcd8"; origBGColor = {r:249, g:249, b:249}; yellowishColor = {r:255, g:252, b:216}; whiteColor = {r:255, g:255, b:255}; blackColor = {r: 0, g:0, b: 0}; redColor = {r:255, g:0, b: 0}; blueishColor = {r: 51, g:122, b:183}; setTimeout(function() { fade(parent, 'background-color', yellowishColor, origBGColor, 1000); }, 500); setTimeout(function() { fade(element, 'color', redColor, blackColor, 1000); }, 500); } // linear interpolation between two values a and b // u controls amount of a/b and is in range [0.0,1.0] lerp = function(a, b, u) { return (1 - u) * a + u * b; }; fade = function(element, property, start, end, duration) { var interval = 10; var steps = duration / interval; var step_u = 1.0 / steps; var u = 0.0; var theInterval = setInterval(function() { if (u >= 1.0) { clearInterval(theInterval) } var r = parseInt(lerp(start.r, end.r, u)); var g = parseInt(lerp(start.g, end.g, u)); var b = parseInt(lerp(start.b, end.b, u)); var colorname = 'rgb(' + r + ',' + g + ',' + b + ')'; element.style.setProperty(property, colorname); u += step_u; }, interval); };