/** @preserve // ==UserScript== // @name AutoReviewComments // @namespace benjol // @author benjol, Machavity // @version 1.6.1 // @description No more re-typing the same comments over and over! // @homepage https://github.com/Benjol/SE-AutoReviewComments // @updateURL https://github.com/Benjol/SE-AutoReviewComments/raw/master/dist/autoreviewcomments.user.js // @downloadURL https://github.com/Benjol/SE-AutoReviewComments/raw/master/dist/autoreviewcomments.user.js // @grant none // @include /^https?:\/\/(.*\.)?stackoverflow\.com/.*$/ // @include /^https?:\/\/(.*\.)?serverfault\.com/.*$/ // @include /^https?:\/\/(.*\.)?superuser\.com/.*$/ // @include /^https?:\/\/(.*\.)?stackexchange\.com/.*$/ // @include /^https?:\/\/(.*\.)?askubuntu\.com/.*$/ // @include /^https?:\/\/(.*\.)?mathoverflow\.net/.*$/ // @include /^https?:\/\/discuss\.area51\.stackexchange\.com/.*$/ // @include /^https?:\/\/stackapps\.com/.*$/ // @exclude *://chat.stackexchange.com/* // @exclude *://chat.stackoverflow.com/* // @exclude *://chat.meta.stackexchange.com/* // @exclude *://chat.meta.stackexchange.com/* // @exclude *://data.stackexchange.com/* // ==/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() { /* 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 sitename = StackExchange.options.site.name || ""; var username = "user"; var OP = "OP"; var prefix = "AutoReviewComments-"; //prefix to avoid clashes in localstorage var myuserid = getLoggedInUserId(); 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; // These are injection markers and MUST use single-quotes. // The injected strings use double-quotes themselves, so that would result in parser errors. var cssTemplate = ''; var markupTemplate = ''; var messageTemplate = '
x $TITLE$ $BODY$
'; var optionTemplate = '
  • '; /** * All the different "targets" a comment can be placed on. * The given values are used as prefixes in the comment titles, to make it easy for the user to change the targets, * by simply adding the prefix to their comment title. */ var Target = { // A regular expression to match the possible targets in a string. MATCH_ALL: new RegExp("\\[(E?[AQ]|C)(?:,(E?[AQ]|C))*\\]"), Closure: "C", CommentQuestion: "Q", CommentAnswer: "A", EditSummaryAnswer: "EA", EditSummaryQuestion: "EQ" }; //default comments var defaultcomments = [ { Target: [Target.CommentQuestion], Name: "More than one question asked", Description: "It is preferred if you can post separate questions instead of combining your questions into one. That way, it helps the people answering your question and also others hunting for at least one of your questions. Thanks!" }, { Target: [Target.CommentQuestion], Name: "Duplicate Closure", Description: "This question will probably be closed as a duplicate soon. If the answers from the duplicates don't fully address your question please edit it to include why and flag this for re-opening. Thanks!" }, { Target: [Target.CommentAnswer], 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." }, { Target: [Target.CommentAnswer], 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." }, { Target: [Target.CommentAnswer], 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." }, { Target: [Target.CommentAnswer], 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." }, { Target: [Target.CommentAnswer], Name: "OP adding a new question as an answer", Description: "If you have another question, please ask it by clicking the Ask Question button." }, { Target: [Target.CommentAnswer], 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." }, { Target: [Target.Closure], Name: "Too localized", Description: "This question appears to be off-topic because it is too localized." }, { Target: [Target.EditSummaryQuestion], Name: "Improper tagging", Description: "The tags you were using are not appropriate for this question. Please review What are tags, and how should I use them?" } ]; 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() { return StackExchange.options && StackExchange.options.user ? StackExchange.options.user.userId : ""; } //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: "//api.stackexchange.com/2.2/users/" + userid + "?site=" + siteurl + '&key=5J)5cHN9KbyhE9Yf9S*G)g((' + "&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 = $("