Permalink
Find file
Fetching contributors…
Cannot retrieve contributors at this time
228 lines (167 sloc) 6.62 KB
// ==UserScript==
// @name LW Comment Highlight
// @namespace http://www.github.com/bakkot
// @description Allows you to choose the time after which comments on a thread on Less Wrong are highlighted
// @match http://lesswrong.com/lw/*
// @match http://lesswrong.com/r/*/lw/*
// @version 1.1
// ==/UserScript==
/**************************************************
Tested only on recent versions of Firefox and Chrome.
Installation
------------
Save this file as something.user.js. Then:
Firefox users: Install Greasemonkey and open the saved file in Firefox.
Chrome users: Go to chrome://extensions and drag the saved file onto the main body of the window.
Feedback
--------
bakkot on github or LessWrong
**************************************************/
// Global variables are fun!
var lastGivenDate, commentCountText, commentsScroller;
// *** Inject some css used by the floating list
var styleEle = document.createElement('style');
styleEle.type = 'text/css';
styleEle.innerHTML = '.comments-floater { position: fixed; right: 4px; top: 4px; padding: 2px 5px; width: 230px; border: 1px solid #bbbcbf; font-size: small; background: rgba(247, 247, 248, 0.90); }' +
'.comments-scroller { word-wrap: break-word; max-height: 500px; overflow-y:scroll; }' +
'.comments-date { font-size: 11px; }' +
'.comments-list { margin-left: 23px; }' +
'.semantic-cell { display: table-cell; }' +
'.hidden-comment-ref { opacity: 0.5; }' +
'.cct-span { white-space: nowrap; }' +
'.date-input { width: 100%; box-sizing: border-box; }' +
'.input-span { width: 100%; padding-left: 5px; }' +
'.hider { position: absolute; left: -19px; top: 6px;}';
document.head.appendChild(styleEle);
// *** Create and insert the floating list of comments, and its contents
// The floating box.
var floatBox = document.createElement('div');
floatBox.className = 'comments-floater';
// Container for the text node below.
var cctSpan = document.createElement('span');
cctSpan.className = 'semantic-cell cct-span';
// The text node which says 'x comments since'
var commentCountText = document.createTextNode('');
// Container for the text box below.
var inputSpan = document.createElement('span');
inputSpan.className = 'semantic-cell input-span';
// The text box with the date.
var dateInput = document.createElement('input');
dateInput.className = 'date-input';
dateInput.addEventListener('blur', function(){
var newDate = Date.parse(dateInput.value);
if (isNaN(newDate)) {
alert('Given date not valid.');
dateInput.value = (new Date(lastGivenDate)).toLocaleString();
return;
}
border(newDate, false);
}, false);
dateInput.addEventListener('keypress', function(e){
if (e.keyCode === 13) {
dateInput.blur();
}
}, false);
// Container for the comments list and the '[-]'
var divDiv = document.createElement('div');
divDiv.style.display = 'none';
// The '[-]'
var hider = document.createElement('span');
hider.innerHTML = '[-]';
hider.className = 'hider';
hider.addEventListener('click', function(){
if (commentsScroller.style.display != 'none') {
commentsScroller.style.display = 'none';
}
else {
commentsScroller.style.display = '';
}
}, false);
// Scrollable container for the comments list
var commentsScroller = document.createElement('div');
commentsScroller.className = 'comments-scroller';
commentsScroller.style.display = 'none';
// Actual list of comments
var commentsList = document.createElement('ul');
commentsList.className = 'comments-list';
// Insert all the things we made into each other and ultimately the document.
cctSpan.appendChild(commentCountText);
floatBox.appendChild(cctSpan);
inputSpan.appendChild(dateInput);
floatBox.appendChild(inputSpan);
divDiv.appendChild(hider);
commentsScroller.appendChild(commentsList);
divDiv.appendChild(commentsScroller);
floatBox.appendChild(divDiv);
document.body.appendChild(floatBox);
// *** Extract post time from a comment element
function getTime(ele) {
return parseInt(ele.childNodes[3].querySelector('.comment-date').getAttribute('time'))*1000; // seconds->ms
}
// *** Find the latest comment without a highlight
function findLastUnreadTime() {
var lvEle = document.getElementById("lastViewed");
if(lvEle) {
return parseInt(lvEle.getAttribute("time"))*1000;
}
var mostRecent = 0;
var commentList = document.querySelectorAll('div.comment:not(.new-comment)');
for(var i = 0; i < commentList.length; ++i) {
var time = getTime(commentList[i]);
if(time > mostRecent) { // handily also deals with NaN case
mostRecent = time;
}
}
return mostRecent;
}
// *** Set up borders and populate comments list
function border(since, updateTitle) {
lastGivenDate = since;
var commentList = document.querySelectorAll('div.comment');
var newComments = [];
// Walk comments, setting borders as appropriate and saving new comments in a list
for(var i = 0; i < commentList.length; ++i) {
var postTime = getTime(commentList[i]);
if(isNaN(postTime)) console.log("what", commentList[i]);
if (postTime > since) {
commentList[i].classList.add('new-comment');
newComments.push({time: postTime, ele: commentList[i]});
}
else {
commentList[i].classList.remove('new-comment');
}
}
var newCount = newComments.length;
// Maybe add new comment count to title
if (updateTitle) {
document.title = '(' + newCount + ') ' + document.title;
}
// Populate the floating comment list
commentCountText.data = '' + newCount + ' comment' + (newCount == 1 ? '' : 's') + ' since ';
commentsList.innerHTML = '';
if (newCount > 0 ) {
divDiv.style.display = 'block';
newComments.sort(function(a, b){return a.time - b.time;});
for(i = 0; i < newCount; ++i) {
var ele = newComments[i].ele;
var newLi = document.createElement('li');
var newHTML = ele.querySelector('.author').textContent.replace(/\n/g, '') + ' <span class="comments-date">' + (new Date(newComments[i].time)).toLocaleString() + '</span>';
if(ele.offsetParent === null) { // ie, not currently displayed, due to a parent being below the point threshold
newHTML = '<span class="hidden-comment-ref">' + newHTML + '</span>';
}
newLi.innerHTML = newHTML;
newLi.addEventListener('click', function(ele){return function(){ele.scrollIntoView(true);};}(ele));
commentsList.appendChild(newLi);
}
}
else {
divDiv.style.display = 'none';
}
}
// *** Set up the comments list and the title
function setup() {
var mostRecentUnread = findLastUnreadTime();
dateInput.value = (new Date(mostRecentUnread)).toLocaleString();
border(mostRecentUnread, true); // for list population and title setting
}
setup();