/** @preserve
// ==UserScript==
// @name AutoReviewComments
// @namespace benjol
// @version 1.3.3.2
// @description No more re-typing the same comments over and over!
// @homepage https://github.com/Benjol/SE-AutoReviewComments
// @grant none
// @include http*://*stackoverflow.com/questions*
// @include http*://*stackoverflow.com/review*
// @include http*://*stackoverflow.com/admin/dashboard*
// @include http*://*stackoverflow.com/tools*
// @include http*://*serverfault.com/questions*
// @include http*://*serverfault.com/review*
// @include http*://*serverfault.com/admin/dashboard*
// @include http*://*serverfault.com/tools*
// @include http*://*superuser.com/questions*
// @include http*://*superuser.com/review*
// @include http*://*superuser.com/admin/dashboard*
// @include http*://*superuser.com/tools*
// @include http*://*stackexchange.com/questions*
// @include http*://*stackexchange.com/review*
// @include http*://*stackexchange.com/admin/dashboard*
// @include http*://*stackexchange.com/tools*
// @include http*://*askubuntu.com/questions*
// @include http*://*askubuntu.com/review*
// @include http*://*askubuntu.com/admin/dashboard*
// @include http*://*askubuntu.com/tools*
// @include http*://*answers.onstartups.com/questions*
// @include http*://*answers.onstartups.com/review*
// @include http*://*answers.onstartups.com/admin/dashboard*
// @include http*://*answers.onstartups.com/tools*
// @include http*://*mathoverflow.net/questions*
// @include http*://*mathoverflow.net/review*
// @include http*://*mathoverflow.net/admin/dashboard*
// @include http*://*mathoverflow.net/tools*
// @include http*://discuss.area51.stackexchange.com/questions/*
// @include http*://discuss.area51.stackexchange.com/review*
// @include http*://discuss.area51.stackexchange.com/admin/dashboard*
// @include http*://discuss.area51.stackexchange.com/tools*
// @include http*://stackapps.com/questions*
// @include http*://stackapps.com/review*
// @include http*://stackapps.com/admin/dashboard*
// @include http*://stackapps.com/tools*
// ==/UserScript==
*/
function with_jquery(f) {
var script = document.createElement("script");
script.type = "text/javascript";
script.textContent = "(" + f.toString() + ")(jQuery)";
document.body.appendChild(script);
};
with_jquery(function ($) {
StackExchange.ready(function () {
//// Self Updating Userscript, see https://gist.github.com/Benjol/874058
// (the first line of this template _must_ be a comment!)
var VERSION = '1.3.3.2';
var URL = "https://raw.github.com/alerque/SE-AutoReviewComments/auto_site_remotes/dist/autoreviewcomments.user.js";
// This hack is necessary to bring people up from the last working auto-uptate gist
// release if they manually installed the latest version. (can be removed after some
// time has passed and last released version is at least 1.3.4)
for (var key in window) {
if (key.indexOf('selfUpdaterCallback') != -1) {
window[key](VERSION);
return;
}
}
// End hack
if(window["AutoReviewComments_AutoUpdateCallback"]) {
window["AutoReviewComments_AutoUpdateCallback"](VERSION);
return;
}
function updateCheck(notifier) {
window["AutoReviewComments_AutoUpdateCallback"] = function (newver) {
if(newver > VERSION) notifier(newver, VERSION, URL);
}
$("").attr("src", URL).appendTo("head");
}
// Check to see if a new version has become available since last check
// - only checks once a day
// - does not check for first time visitors, shows them a welcome message instead
// - called at the end of the main script if function exists
function CheckForNewVersion(popup) {
var today = (new Date().setHours(0, 0, 0, 0));
var LastUpdateCheckDay = GetStorage("LastUpdateCheckDay");
if(LastUpdateCheckDay == null) { //first time visitor
ShowMessage(popup, "Please read this!", 'Thanks for installing this script. \
Please note that you can EDIT the texts inline by double-clicking them. \
For other options, please see the README at here.',
function () { });
} else if ( LastUpdateCheckDay != today) {
updateCheck(function (newver, oldver, install_url) {
if(newver != GetStorage("LastVersionAcknowledged")) {
ShowMessage(popup, "New Version!", 'A new version (' + newver + ') of the AutoReviewComments userscript is now available, see the release notes for details or click here to install now.',
function () { SetStorage("LastVersionAcknowledged", newver); });
}
});
}
SetStorage("LastUpdateCheckDay", today);
}
/* How does this work?
1. The installed script loads first, and sets the local VERSION variable with the currently installed version number
2. window["AutoReviewComments_AutoUpdateCallback"] is not defined, so this is skipped
3. When updateCheck() is called, it defines window["AutoReviewComments_AutoUpdateCallback"], which retains the installed version number in VERSION (closure)
4. updateCheck() then loads the external version of the script into the page header
5. when the external version of the script loads, it defines its own local VERSION with the external (potentially new) version number
6. window["AutoReviewComments_AutoUpdateCallback"] is now defined, so it is invoked, and the external version number is passed in
7. if the external version number (ver) is greater than the installed version (VERSION), the notification is invoked
*/
var siteurl = window.location.hostname;
var arr = document.title.split(' - ');
var sitename = arr[arr.length - 1];
var username = 'user';
var OP = 'OP';
var prefix = "AutoReviewComments-"; //prefix to avoid clashes in localstorage
var myuserid = getLoggedInUserId();
if(sitename == "Stack Exchange") sitename = arr[arr.length - 2]; //workaround for some SE sites (e.g. Area 51, Stack Apps)
sitename = sitename.replace(/ ?Stack Exchange/, ''); //same for others ("Android Enthusiasts Stack Exchange", SR, and more)
if(!GetStorage("WelcomeMessage")) SetStorage("WelcomeMessage", 'Welcome to ' + sitename + '! ');
var greeting = GetStorage("WelcomeMessage") == "NONE" ? "" : GetStorage("WelcomeMessage");
var showGreeting = false;
var markupTemplate = '
';
//default comments
var defaultcomments = [
{ Name: "Answers just to say Thanks!", Description: 'Please don\'t add "thanks" as answers. Invest some time in the site and you will gain sufficient privileges to upvote answers you like, which is the $SITENAME$ way of saying thank you.' },
{ Name: "Nothing but a URL (and isn't spam)", Description: 'Whilst this may theoretically answer the question, it would be preferable to include the essential parts of the answer here, and provide the link for reference.' },
{ Name: "Requests to OP for further information", Description: 'This is really a comment, not an answer. With a bit more rep, you will be able to post comments. For the moment I\'ve added the comment for you, and I\'m flagging this post for deletion.' },
{ Name: "OP using an answer for further information", Description: 'Please use the Post answer button only for actual answers. You should modify your original question to add additional information.' },
{ Name: "OP adding a new question as an answer", Description: 'If you have another question, please ask it by clicking the Ask Question button.' },
{ Name: "Another user adding a 'Me too!'", Description: 'If you have a NEW question, please ask it by clicking the Ask Question button. If you have sufficient reputation, you may upvote the question. Alternatively, "star" it as a favorite and you will be notified of any new answers.' }
];
var weekday_name = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
var minute = 60, hour = 3600, day = 86400, sixdays = 518400, week = 604800, month = 2592000, year = 31536000;
//Wrap local storage access so that we avoid collisions with other scripts
function GetStorage(key) { return localStorage[prefix + key]; }
function SetStorage(key, val) { localStorage[prefix + key] = val; }
function RemoveStorage(key) { localStorage.removeItem(prefix + key); }
function ClearStorage(startsWith) {
for(var i = localStorage.length - 1; i >= 0; i--) {
var key = localStorage.key(i);
if(key.indexOf(prefix + startsWith) == 0) localStorage.removeItem(key);
}
}
//Calculate and format datespan for "Member since/for"
function datespan(date) {
var now = new Date() / 1000;
var then = new Date(date * 1000);
var today = new Date().setHours(0, 0, 0) / 1000;
var nowseconds = now - today;
var elapsedSeconds = now - date;
var strout = "";
if(elapsedSeconds < nowseconds) strout = "since today";
else if(elapsedSeconds < day + nowseconds) strout = "since yesterday";
else if(elapsedSeconds < sixdays) strout = "since " + weekday_name[then.getDay()];
else if(elapsedSeconds > year) {
strout = "for " + Math.round((elapsedSeconds) / year) + " years";
if(((elapsedSeconds) % year) > month) strout += ", " + Math.round(((elapsedSeconds) % year) / month) + " months";
}
else if(elapsedSeconds > month) {
strout = "for " + Math.round((elapsedSeconds) / month) + " months";
if(((elapsedSeconds) % month) > week) strout += ", " + Math.round(((elapsedSeconds) % month) / week) + " weeks";
}
else {
strout = "for " + Math.round((elapsedSeconds) / week) + " weeks";
}
return strout;
}
//Calculate and format datespan for "Last seen"
function lastseen(date) {
var now = new Date() / 1000;
var today = new Date().setHours(0, 0, 0) / 1000;
var nowseconds = now - today;
var elapsedSeconds = now - date;
if(elapsedSeconds < minute) return (Math.round(elapsedSeconds) + " seconds ago");
if(elapsedSeconds < hour) return (Math.round((elapsedSeconds) / minute) + " minutes ago");
if(elapsedSeconds < nowseconds) return (Math.round((elapsedSeconds) / hour) + " hours ago");
if(elapsedSeconds < day + nowseconds) return ("yesterday");
var then = new Date(date * 1000);
if(elapsedSeconds < sixdays) return ("on " + weekday_name[then.getDay()]);
return then.toDateString();
}
//Format reputation string
function repNumber(r) {
if(r < 1E4) return r;
else if(r < 1E5) {
var d = Math.floor(Math.round(r / 100) / 10);
r = Math.round((r - d * 1E3) / 100);
return d + (r > 0 ? "." + r : "") + "k"
}
else return Math.round(r / 1E3) + "k"
}
// Get the Id of the logged-in user
function getLoggedInUserId() {
if ( document.getElementsByClassName('profile-me')[0].href.match(/\/users\/(\d+)\/.*/i) ) {
return RegExp.$1;
} else {
return '';
}
}
//Get userId for post
function getUserId(el) {
// a bit complicated, but we have to avoid edits (:last), not trip on CW questions (:not([id])), and not bubble
// out of post scope for deleted users (first()).
var userlink = el.parents('div').find('.post-signature:last').first().find('.user-details > a:not([id])');
if(userlink.length) return userlink.attr('href').split('/')[2];
return "[NULL]";
}
function isNewUser(date) {
return (new Date() / 1000) - date < week
}
function getOP() {
var userlink = $('#question').find('.owner').find('.user-details > a:not([id])');
if(userlink.length) return userlink.text();
var user = $('#question').find('.owner').find('.user-details'); //for deleted users
if(user.length) return user.text();
return "[NULL]";
}
//Ajax to Stack Exchange api to get basic user info, and paste into userinfo element
//http://soapi.info/code/js/stable/soapi-explore-beta.htm
function getUserInfo(userid, container) {
var userinfo = container.find('#userinfo');
if(isNaN(userid)) {
userinfo.fadeOutAndRemove();
return;
}
$.ajax({
type: "GET",
url: location.protocol + '//api.stackexchange.com/2.2/users/' + userid + '?site=' + siteurl + '&jsonp=?',
dataType: "jsonp",
timeout: 2000,
success: function (data) {
if(data['items'].length > 0) {
var user = data['items'][0];
if(isNewUser(user['creation_date'])) {
showGreeting = true;
container.find('.action-desc').prepend(greeting);
}
username = user['display_name'];
var usertype = user['user_type'].charAt(0).toUpperCase() + user['user_type'].slice(1);
var html = usertype + ' user ' + username + ', \
member ' + datespan(user['creation_date']) + ', \
last seen ' + lastseen(user['last_access_date']) + ', \
reputation ' + repNumber(user['reputation']) + '';
userinfo.html(html.replace(/ +/g, ' '));
}
else userinfo.fadeOutAndRemove();
},
error: function () { userinfo.fadeOutAndRemove(); }
});
}
//Show textarea in front of popup to import/export all comments (for other sites or for posting somewhere)
function ImportExport(popup) {
var tohide = popup.find('#main');
var div = $('
');
//Painful, but shortest way I've found to position div over the tohide element
div.css({ position: 'absolute', left: tohide.position().left, top: tohide.position().top,
width: tohide.css('width'), height: tohide.css('height'), background: 'white'
});
var txt = '';
for(var i = 0; i < GetStorage("commentcount"); i++) {
var name = GetStorage('name-' + i);
var desc = GetStorage('desc-' + i);
txt += '###' + name + '\n' + htmlToMarkDown(desc) + '\n\n'; //the leading ### makes prettier if pasting to markdown, and differentiates names from descriptions
}
div.find('textarea').width('100%').height('95%').val(txt);
div.find('.jsonp').click(function () {
var txt = 'callback(\n[\n';
for(var i = 0; i < GetStorage("commentcount"); i++) {
txt += '{ "name": "' + GetStorage('name-' + i) + '", "description": "' + GetStorage('desc-' + i).replace(/"/g, '\\"') + '"},\n\n';
}
div.find('textarea').val(txt + ']\n)');
div.find('a:lt(2)').remove(); div.find('.lsep:lt(2)').remove();
});
div.find('.cancel').click(function () { div.fadeOutAndRemove(); });
div.find('.save').click(function () { DoImport(div.find('textarea').val()); WriteComments(popup); div.fadeOutAndRemove(); });
popup.append(div);
}
//Import complete text into comments
function DoImport(text) {
//clear out any existing stuff
ClearStorage("name-"); ClearStorage("desc-");
var arr = text.split('\n');
var nameIndex = 0, descIndex = 0;
for(var i = 0; i < arr.length; i++) {
var line = $.trim(arr[i]);
if(line.indexOf('#') == 0) {
var name = line.replace(/^#+/g, '');
SetStorage('name-' + nameIndex, name);
nameIndex++;
}
else if(line.length > 0) {
var desc = markDownToHtml(line);
SetStorage('desc-' + descIndex, Tag(desc));
descIndex++;
}
}
//This is de-normalised, but I don't care.
SetStorage("commentcount", Math.min(nameIndex, descIndex));
}
function htmlToMarkDown(html) {
markdown = html.replace(/(.+?)<\/a>/g, '[$2]($1)').replace(/&/g, '&');
return markdown.replace(/(.+?)<\/em>/g, '*$1*').replace(/(.+?)<\/strong>/g, '**$1**');
}
function markDownToHtml(markdown) {
html = markdown.replace(/\[([^\]]+)\]\((.+?)\)/g, '$1');
return html.replace(/\*\*(.+?)\*\*/g, '$1').replace(/\*([^`]+?)\*/g, '$1');
}
function UnTag(text) {
return text.replace(/\$SITENAME\$/g, sitename).replace(/\$SITEURL\$/g, siteurl).replace(/\$MYUSERID\$/g, myuserid);
}
function Tag(html) {
//put tags back in
var regname = new RegExp(sitename, "g"), regurl = new RegExp('http://' + siteurl, "g"), reguid = new RegExp('/' + myuserid + '[)]', "g");
return html.replace(regname, '$SITENAME$').replace(regurl, 'http://$SITEURL$').replace(reguid, '/$MYUSERID$)');
}
//Replace contents of element with a textarea (containing markdown of contents), and save/cancel buttons
function ToEditable(el) {
var backup = el.html();
var html = Tag(el.html().replace(greeting, '')); //remove greeting before editing..
if(html.indexOf('