SHARE
TWEET

AutoReviewComments

a guest Feb 24th, 2014 57 Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. // ==UserScript==
  2. // @name           AutoReviewComments
  3. // @namespace      benjol
  4. // @version        1.3.0izzy2
  5. // @description    Add pro-forma comments dialog for reviewing (pre-flag)
  6. // @grant          none
  7. // @include        http://*stackoverflow.com/questions*
  8. // @include        http://*stackoverflow.com/review*
  9. // @include        http://*stackoverflow.com/admin/dashboard*
  10. // @include        http://*stackoverflow.com/tools*
  11. // @include        http://*serverfault.com/questions*
  12. // @include        http://*serverfault.com/review*
  13. // @include        http://*serverfault.com/admin/dashboard*
  14. // @include        http://*serverfault.com/tools*
  15. // @include        http://*superuser.com/questions*
  16. // @include        http://*superuser.com/review*
  17. // @include        http://*superuser.com/admin/dashboard*
  18. // @include        http://*superuser.com/tools*
  19. // @include        http://*stackexchange.com/questions*
  20. // @include        http://*stackexchange.com/review*
  21. // @include        http://*stackexchange.com/admin/dashboard*
  22. // @include        http://*stackexchange.com/tools*
  23. // @include        http://*askubuntu.com/questions*
  24. // @include        http://*askubuntu.com/review*
  25. // @include        http://*askubuntu.com/admin/dashboard*
  26. // @include        http://*askubuntu.com/tools*
  27. // @include        http://*answers.onstartups.com/questions*
  28. // @include        http://*answers.onstartups.com/review*
  29. // @include        http://*answers.onstartups.com/admin/dashboard*
  30. // @include        http://*answers.onstartups.com/tools*
  31. // @include        http://*mathoverflow.net/questions*
  32. // @include        http://*mathoverflow.net/review*
  33. // @include        http://*mathoverflow.net/admin/dashboard*
  34. // @include        http://*mathoverflow.net/tools*
  35. // @include        http://discuss.area51.stackexchange.com/questions/*
  36. // @include        http://discuss.area51.stackexchange.com/review*
  37. // @include        http://discuss.area51.stackexchange.com/admin/dashboard*
  38. // @include        http://discuss.area51.stackexchange.com/tools*
  39. // @include        http://stackapps.com/questions*
  40. // @include        http://stackapps.com/review*
  41. // @include        http://stackapps.com/admin/dashboard*
  42. // @include        http://stackapps.com/tools*
  43. // ==/UserScript==
  44.  
  45. function with_jquery(f) {
  46.   var script = document.createElement("script");
  47.   script.type = "text/javascript";
  48.   script.textContent = "(" + f.toString() + ")(jQuery)";
  49.   document.body.appendChild(script);
  50. };
  51.  
  52. with_jquery(function ($) {
  53.   StackExchange.ready(function () {
  54.     //**selfupdatingscript starts here (see https://gist.github.com/raw/874058/selfupdatingscript.user.js)
  55.     var VERSION = '1.3.0izzy2';  //<<<<<<<<<<<<*********************** DON'T FORGET TO UPDATE THIS!!!! *************************
  56.     var URL = "https://gist.github.com/raw/842025/autoreviewcomments.user.js";
  57.  
  58.     if(window["selfUpdaterCallback:" + URL]) {
  59.       window["selfUpdaterCallback:" + URL](VERSION);
  60.       return;
  61.     }
  62.  
  63.     function updateCheck(notifier) {
  64.       window["selfUpdaterCallback:" + URL] = function (newver) {
  65.         if(newver > VERSION) notifier(newver, VERSION, URL);
  66.       }
  67.       $("<script />").attr("src", URL).appendTo("head");
  68.     }
  69.     //**selfupdatingscript ends here (except for call to updateCheck further down in code)
  70.  
  71.     //autoreviewcomments script starts here
  72.     var siteurl = window.location.hostname;
  73.     var arr = document.title.split(' - ');
  74.     var sitename = arr[arr.length - 1];
  75.     var username = 'user';
  76.     var myuserid = getLoggedInUserId();
  77.     var OP = 'OP';
  78.     var prefix = "AutoReviewComments-"; //prefix to avoid clashes in localstorage
  79.  
  80.     sitename.replace(/ Stack Exchange/,'');
  81.     var greeting = 'Welcome to  ' + sitename.replace(/ Stack Exchange/,'') + '! ';
  82.     var showGreeting = false;
  83.  
  84.     var markupTemplate = '                                                                            \
  85.    <div id="popup" class="popup" style="width:690px; position: absolute; display: block">            \
  86.       <div id="close" class="popup-close"><a title="close this popup (or hit Esc)">&#215;</a></div>  \
  87.       <h2 class="handle">Which review comment to insert?</h2>                                        \
  88.       <div style="overflow:hidden" id="main">                                                        \
  89.         <div class="popup-active-pane">                                                              \
  90.           <div id="userinfo" style="padding:5px;background:#EAEFEF">                                 \
  91.              <img src="http://sstatic.net/img/progress-dots.gif"/>                                   \
  92.           </div>                                                                                     \
  93.          <ul class="action-list" style="height:440;overflow-y:auto" >                                \
  94.          </ul>                                                                                       \
  95.         </div>                                                                                       \
  96.         <div style="display:none" class="share-tip" id="remote-popup">                               \
  97.            enter url for remote source of comments (use import/export to create jsonp)               \
  98.            <input id="remoteurl" type="text" style="display: block; width: 400px;"\>                 \
  99.            <img id="throbber1" style="display:none" src="http://sstatic.net/img/progress-dots.gif"/> \
  100.            <span id="remoteerror1" style="color:red"/>                                               \
  101.            <div style="float:left">                                                                  \
  102.              <input type="checkbox" id="remoteauto"\>                                                \
  103.              <label title="get from remote on every page refresh" for="remoteauto">auto-get</label>  \
  104.            </div>                                                                                    \
  105.            <div style="float:right">                                                                 \
  106.              <a class="remote-get">get now</a>                                                       \
  107.              <span class="lsep"> | </span>                                                           \
  108.              <a class="remote-save">save</a>                                                         \
  109.              <span class="lsep"> | </span>                                                           \
  110.              <a class="remote-cancel">cancel</a>                                                     \
  111.            </div>                                                                                    \
  112.        </div>                                                                                        \                                                                                      \
  113.         <div style="display:none" class="share-tip" id="welcome-popup">                              \
  114.            configure "welcome" message (empty=none):                                                 \
  115.            <div>                                                                                     \
  116.              <input id="customwelcome" type="text" style="width: 300px;"\>                           \
  117.            </div>                                                                                    \
  118.            <div style="float:right">                                                                 \
  119.              <a class="welcome-force">force</a>                                                      \
  120.              <span class="lsep"> | </span>                                                           \
  121.              <a class="welcome-save">save</a>                                                        \
  122.              <span class="lsep"> | </span>                                                           \
  123.              <a class="welcome-cancel">cancel</a>                                                    \
  124.            </div>                                                                                    \
  125.        </div>                                                                                        \
  126.         <div class="popup-actions">                                                                  \
  127.          <div style="float: left; margin-top: 18px;">                                                \
  128.            <a title="close this popup (or hit Esc)" class="popup-actions-cancel">cancel</a>          \
  129.            <span class="lsep"> | </span>                                                             \
  130.            <a title="see info about this popup" class="popup-actions-help" href="http://stackapps.com/q/2116" target="_blank">info</a>  \
  131.            <span class="lsep"> | </span>                                                             \
  132.            <a class="popup-actions-see">see-through</a>                                              \
  133.            <span class="lsep"> | </span>                                                             \
  134.            <a title="reset any custom comments" class="popup-actions-reset">reset</a>                \
  135.            <span class="lsep"> | </span>                                                             \
  136.            <a title="use this to import/export all comments" class="popup-actions-impexp">import/export</a>    \
  137.            <span class="lsep"> | </span>                                                             \
  138.            <a title="use this to hide/show all comments" class="popup-actions-toggledesc">show/hide desc</a>    \
  139.            <span class="lsep"> | </span>                                                             \
  140.            <a title="setup remote source" class="popup-actions-remote">remote</a>                    \
  141.            <img id="throbber2" style="display:none" src="http://sstatic.net/img/progress-dots.gif"/> \
  142.            <span id="remoteerror2" style="color:red"/>                                               \
  143.            <span class="lsep"> | </span>                                                             \
  144.            <a title="configure welcome" class="popup-actions-welcome">welcome</a>                    \
  145.          </div>                                                                                      \
  146.          <div style="float:right">                                                                   \
  147.            <input class="popup-submit" type="button" disabled="disabled" style="float:none; margin-left: 5px" value="Insert">  \
  148.          </div>                                                                                      \
  149.         </div>                                                                                       \
  150.       </div>                                                                                         \
  151.    </div>';
  152.  
  153.     var messageTemplate = '                                                                                                              \
  154.    <div id="announcement" style="background:orange;padding:7px;margin-bottom:10px;font-size:15px">                               \
  155.      <span class="notify-close" style="border:2px solid black;cursor:pointer;display:block;float:right;margin:0 4px;padding:0 4px;line-height:17px">  \
  156.         <a title="dismiss this notification" style="color:black;text-decoration:none;font-weight:bold;font-size:16px">x</a>      \
  157.      </span>                                                                                                                     \
  158.      <strong>$TITLE$</strong> $BODY$                                                                                           \
  159.    </div>';
  160.  
  161.     var optionTemplate = '                                                          \
  162.    <li>                                                                    \
  163.      <input id="comment-$ID$" type="radio" name="commentreview"/>          \
  164.      <label for="comment-$ID$">                                            \
  165.        <span id="name-$ID$" class="action-name">$NAME$</span>              \
  166.        <span id="desc-$ID$" class="action-desc">$DESCRIPTION$</span>       \
  167.      </label>                                                              \
  168.    </li>';
  169.  
  170.     //default comments
  171.     var defaultcomments = [
  172.      { Name: "[Q]App recommendation", Description: "Please note that recommendations like *Is there an app for X* are off-topic here (see [What topics can I ask about here?](http://$SITEURL$/help/on-topic) for details). For where your question might fit better, you might want to look into [Where can I ask questions that aren't Android Enthusiast questions?](http://meta.android.stackexchange.com/q/371/$MYUSERID$)", Site: "android.stackexchange.com" },
  173.      { Name: "[Q]Low question quality", Description: "We will need much more information to give good recommendations here. Please take a look at [What is required for a question to contain \"enough information\"?](http://meta.softwarerecs.stackexchange.com/q/336/185) Then please [edit] your question and see if you can incorporate some of these improvements.", Site: "softwarerecs.stackexchange.com" },
  174.      { Name: "[Q]Development question", Description: "This site is for *users* of Android, which means that questions about development/programming are off-topic here (see the [What topics can I ask about here?](http://android.stackexchange.com/help/on-topic)). Development questions are on-topic on our sister site [Stack Overflow](http://stackoverflow.com/questions/tagged/android). You might also wish to consult [Where can I ask questions that aren\'t Android Enthusiast questions?](http://meta.android.stackexchange.com/q/371/$MYUSERID$) for a fitting place to your question.", Site: "android.stackexchange.com" },
  175.      { Name: "[Q]More than one question asked", Description: "The question-and-answer format of this site works best if you put each question in a separate question post. Please [edit] your post down to one question, and create new posts to ask any further questions. You\'ll get better answers that way."},  
  176.      { Name: "[Q]OP providing facts in a comment", Description: "The best way to add additional information to your question is by editing it, with the [edit] link. It is better visible that way, and comments are mainly for secondary, temporary purposes. Comments are removed under a variety of circumstances. Anything important to your question should be in the question itself."},  
  177.      { Name: "[Q]Frequent Question (use Search)", Description: "This happens to be a question frequently asked on our site. Have you tried our on-site search? See [How do I search?](http://$SITEURL$/help/searching) for help using it."},
  178.      { Name: "[A]OP adding a new question as an answer", Description: "Remember this is a Q&A site, so keep on [edit]ing your question with new information – this section is for actual answers. If you have another question, please ask it by clicking the [Ask Question](http://$SITEURL$/questions/ask) button."},
  179.      { Name: "[A]OP using an answer for further information", Description: "This is a question-and-answer site, not a forum. Please use the *Post answer* button only if you have a solution to the problem, so that other users can see your question is not yet answered. You can click *edit* on the question to add more information to it."},
  180.      { Name: "[A]Answers just to say Thanks!", Description: "This is a question-and-answer site, not a forum – so please don\'t add \"thanks\" as answers. Invest some time in the site and you will gain sufficient [privileges](http://$SITEURL$/privileges) to upvote answers you like, which is the $SITENAME$ way of saying thank you." },
  181.      { Name: "[A]Nothing but a URL (and isn\'t spam)", Description: "Whilst this may theoretically answer the question, [it would be preferable](http://meta.stackoverflow.com/q/8259) to include the essential parts of the answer here, and provide the link for reference. Otherwise, your answer becomes useless in case the link dies." },
  182.      { Name: "[A]Comments as an answer (new users)", Description: "This is a question-and-answer site, not a forum: please don\'t add comments as answers. Invest some time in the site and you will gain sufficient [privileges](http://$SITEURL$/privileges) to upvote answers you like, or to add actual comments when seeking clarification of any issues."},
  183.      { Name: "[A]Comments as an answer (experienced users)", Description: "This is a question-and-answer site, not a forum: please don\'t add comments as answers. Use actual comments when seeking clarification of any issues."},
  184.      { Name: "[A]Answer that is a question", Description: "This is a question-and-answer site, not a forum. Please use the *Post answer* button only if you have a solution to the problem. If you have another question, please ask it by clicking the [Ask Question](http://$SITEURL$/questions/ask) button. Include a link to this question if it helps provide context."},
  185.      { Name: "[A]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](http://$SITEURL$/privileges/comment). For the moment I\'ve added the comment for you, and I\'m flagging this post for deletion." },
  186.      { Name: "[A]Another user adding a \'Me too!\'", Description: "This is a question-and-answer site, not a forum. Please use the *Post answer* button only if you have a solution to the problem. If you have a NEW question, please ask it by clicking the [Ask Question](http://$SITEURL$/questions/ask) button. If you have sufficient reputation, [you may upvote](http://$SITEURL$/privileges/vote-up) the question. Alternatively, \"star\" it as a favorite and you will be notified of any new answers." },
  187.      { Name: "[A]Low quality answer", Description: "This post does not contain enough information to be considered a high quality answer. Please [read our discussion on what makes an answer high quality](http://meta.softwarerecs.stackexchange.com/q/356/$MYUSERID$) to see if you can incorporate some of these improvements into your answer, otherwise it will be removed.", Site: "softwarerecs.stackexchange.com" }
  188.     ];
  189.  
  190.     var weekday_name = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
  191.     var minute = 60, hour = 3600, day = 86400, sixdays = 518400, week = 604800, month = 2592000, year = 31536000;
  192.  
  193.     //Wrap local storage access so that we avoid collisions with other scripts
  194. //    function GetStorage(key) { return localStorage[prefix + key]; }
  195.     function GetStorage(key) { var st = localStorage[prefix + key]; if (st==undefined) return ''; return st; }
  196.     function SetStorage(key, val) { localStorage[prefix + key] = val; }
  197.     function RemoveStorage(key) { localStorage.removeItem(prefix + key); }
  198.     function ClearStorage(startsWith) {
  199.       for(var i = localStorage.length - 1; i >= 0; i--) {
  200.         var key = localStorage.key(i);
  201.         if(key.indexOf(prefix + startsWith) == 0) localStorage.removeItem(key);
  202.       }
  203.     }
  204.  
  205.     //Calculate and format datespan for "Member since/for"
  206.     function datespan(date) {
  207.       var now = new Date() / 1000;
  208.       var then = new Date(date * 1000);
  209.       var today = new Date().setHours(0, 0, 0) / 1000;
  210.       var nowseconds = now - today;
  211.       var elapsedSeconds = now - date;
  212.       var strout = "";
  213.       if(elapsedSeconds < nowseconds) strout = "since today";
  214.       else if(elapsedSeconds < day + nowseconds) strout = "since yesterday";
  215.       else if(elapsedSeconds < sixdays) strout = "since " + weekday_name[then.getDay()];
  216.       else if(elapsedSeconds > year) {
  217.         strout = "for " + Math.round((elapsedSeconds) / year) + " years";
  218.         if(((elapsedSeconds) % year) > month) strout += ", " + Math.round(((elapsedSeconds) % year) / month) + " months";
  219.       }
  220.       else if(elapsedSeconds > month) {
  221.         strout = "for " + Math.round((elapsedSeconds) / month) + " months";
  222.         if(((elapsedSeconds) % month) > week) strout += ", " + Math.round(((elapsedSeconds) % month) / week) + " weeks";
  223.       }
  224.       else {
  225.         strout = "for " + Math.round((elapsedSeconds) / week) + " weeks";
  226.       }
  227.       return strout;
  228.     }
  229.  
  230.     //Calculate and format datespan for "Last seen"
  231.     function lastseen(date) {
  232.       var now = new Date() / 1000;
  233.       var today = new Date().setHours(0, 0, 0) / 1000;
  234.       var nowseconds = now - today;
  235.       var elapsedSeconds = now - date;
  236.       if(elapsedSeconds < minute) return (Math.round(elapsedSeconds) + " seconds ago");
  237.       if(elapsedSeconds < hour) return (Math.round((elapsedSeconds) / minute) + " minutes ago");
  238.       if(elapsedSeconds < nowseconds) return (Math.round((elapsedSeconds) / hour) + " hours ago");
  239.       if(elapsedSeconds < day + nowseconds) return ("yesterday");
  240.       var then = new Date(date * 1000);
  241.       if(elapsedSeconds < sixdays) return ("on " + weekday_name[then.getDay()]);
  242.       return then.toDateString();
  243.     }
  244.  
  245.     //Format reputation string
  246.     function repNumber(r) {
  247.       if(r < 1E4) return r;
  248.       else if(r < 1E5) {
  249.         var d = Math.floor(Math.round(r / 100) / 10);
  250.         r = Math.round((r - d * 1E3) / 100);
  251.         return d + (r > 0 ? "." + r : "") + "k"
  252.       }
  253.       else return Math.round(r / 1E3) + "k"
  254.     }
  255.  
  256.     // Get the Id of the logged-in user
  257.     function getLoggedInUserId() {
  258.       if ( document.getElementsByClassName('profile-me')[0].href.match(/\/users\/(\d+)\/.*/i) ) {
  259.         return RegExp.$1;
  260.       } else {
  261.         return '';
  262.       }
  263.     }
  264.  
  265.     //Get userId for post
  266.     function getUserId(el) {
  267.       // a bit complicated, but we have to avoid edits (:last), not trip on CW questions (:not([id])), and not bubble
  268.       //  out of post scope for deleted users (first()).
  269.       var userlink = el.parents('div').find('.post-signature:last').first().find('.user-details > a:not([id])');
  270.       if(userlink.length) return userlink.attr('href').split('/')[2];
  271.       return "[NULL]";
  272.     }
  273.     function isNewUser(date) {
  274.       return (new Date() / 1000) - date < week
  275.     }
  276.     function getOP() {
  277.       var userlink = $('#question').find('.owner').find('.user-details > a:not([id])');
  278.       if(userlink.length) return userlink.text();
  279.       var user = $('#question').find('.owner').find('.user-details'); //for deleted users
  280.       if(user.length) return user.text();
  281.       return "[NULL]";
  282.     }
  283.  
  284.     //Ajax to Stack Exchange api to get basic user info, and paste into userinfo element
  285.     //http://soapi.info/code/js/stable/soapi-explore-beta.htm
  286.     function getUserInfo(userid, container) {
  287.       var userinfo = container.find('#userinfo');
  288.       if(isNaN(userid)) {
  289.         userinfo.fadeOutAndRemove();
  290.         return;
  291.       }
  292.       $.ajax({
  293.         type: "GET",
  294.         url: 'http://api.stackexchange.com/2.2/users/' + userid + '?site=' + siteurl + '&jsonp=?',
  295.         dataType: "jsonp",
  296.         timeout: 2000,
  297.         success: function (data) {
  298.           if(data['items'].length > 0) {
  299.             var user = data['items'][0];
  300.             if(isNewUser(user['creation_date'])) {
  301.               showGreeting = true;
  302.               container.find('.action-desc').prepend(greeting);
  303.             }
  304.             username = user['display_name'];
  305.             var usertype = user['user_type'].charAt(0).toUpperCase() + user['user_type'].slice(1);
  306.             var html = usertype + ' user <strong><a href="/users/' + userid + '" target="_blank">' + username + '</a></strong>,     \
  307.                            member <strong>' + datespan(user['creation_date']) + '</strong>,                                        \
  308.                            last seen <strong>' + lastseen(user['last_access_date']) + '</strong>,                                  \
  309.                            reputation <strong>' + repNumber(user['reputation']) + '</strong>';
  310.  
  311.             userinfo.html(html.replace(/ +/g, ' '));
  312.           }
  313.           else userinfo.fadeOutAndRemove();
  314.         },
  315.         error: function () { userinfo.fadeOutAndRemove(); }
  316.       });
  317.     }
  318.  
  319.     //Show textarea in front of popup to import/export all comments (for other sites or for posting somewhere)
  320.     function ImportExport(popup) {
  321.       var tohide = popup.find('#main');
  322.       var div = $('<div><textarea/><a class="jsonp">jsonp</a><span class="lsep"> | </span><a class="save">save</a><span class="lsep"> | </span><a class="cancel">cancel</a></div>');
  323.       //Painful, but shortest way I've found to position div over the tohide element
  324.       div.css({ position: 'absolute', left: tohide.position().left, top: tohide.position().top,
  325.         width: tohide.css('width'), height: tohide.css('height'), background: 'white'
  326.       });
  327.  
  328.       var txt = '';
  329.       for(var i = 0; i < GetStorage("commentcount"); i++) {
  330.         var name = GetStorage('name-' + i);
  331.         var desc = GetStorage('desc-' + i);
  332.         var site = GetStorage('site-' + i);
  333.         if (site==undefined) site = '';
  334.         txt += '###' + name + '\n' + '§§§' + site + '\n' + htmlToMarkDown(desc) + '\n\n'; //the leading ### makes prettier if pasting to markdown, and differentiates names from descriptions
  335.       }
  336.  
  337.       div.find('textarea').width('100%').height('95%').val(txt);
  338.       div.find('.jsonp').click(function () {
  339.         var txt = 'callback(\n[\n';
  340.         for(var i = 0; i < GetStorage("commentcount"); i++) {
  341.           txt += '{ "name": "' + GetStorage('name-' + i) + '", "description": "' + GetStorage('desc-' + i).replace(/"/g, '\\"') + '"},\n\n';
  342.         }
  343.         div.find('textarea').val(txt + ']\n)');
  344.         div.find('a:lt(2)').remove(); div.find('.lsep:lt(2)').remove();
  345.       });
  346.       div.find('.cancel').click(function () { div.fadeOutAndRemove(); });
  347.       div.find('.save').click(function () { DoImport(div.find('textarea').val()); WriteComments(popup); div.fadeOutAndRemove(); });
  348.  
  349.       popup.append(div);
  350.     }
  351.  
  352.     //Import complete text into comments
  353.     function DoImport(text) {
  354.       //clear out any existing stuff
  355.       ClearStorage("name-"); ClearStorage("desc-"); ClearStorage("site-");
  356.       var arr = text.split('\n');
  357.       var nameIndex = 0, descIndex = 0, siteIndex = 0;
  358.       for(var i = 0; i < arr.length; i++) {
  359.         var line = $.trim(arr[i]);
  360.         if(line.indexOf('#') == 0) {
  361.           SetStorage('name-' + nameIndex, line.substr(3));
  362.           nameIndex++;
  363.         }
  364.         else if(line.indexOf('§') == 0) {
  365.           SetStorage('site-' + siteIndex, line.substr(3));
  366.           siteIndex++;
  367.         }
  368.         else if(line.length > 0) {
  369.           var desc = markDownToHtml(line);
  370.           SetStorage('desc-' + descIndex, Tag(desc));
  371.           descIndex++;
  372.         }
  373.       }
  374.       //This is de-normalised, but I don't care.
  375.       SetStorage("commentcount", Math.min(nameIndex, descIndex));
  376.     }
  377.  
  378.     function htmlToMarkDown(html) {
  379.       markdown = html.replace(/<a href="(.+?)">(.+?)<\/a>/g, '[$2]($1)').replace(/&amp;/g, '&');
  380.       return markdown.replace(/<em>(.+?)<\/em>/g, '*$1*').replace(/<strong>(.+?)<\/strong>/g, '**$1**');
  381.     }
  382.  
  383.     function markDownToHtml(markdown) {
  384.       html = markdown.replace(/\[([^\]]+)\]\((.+?)\)/g, '<a href="$2">$1</a>');
  385.       return html.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>').replace(/\*([^`]+?)\*/g, '<em>$1</em>');
  386.     }
  387.  
  388.     function UnTag(text) {
  389.       return text.replace(/\$SITENAME\$/g, sitename).replace(/\$SITEURL\$/g, siteurl).replace(/\$MYUSERID\$/g, myuserid);
  390.     }
  391.  
  392.     function Tag(html) {
  393.       //put tags back in
  394.       var regname = new RegExp(sitename, "g"), regurl = new RegExp('http://' + siteurl, "g"), reguid = new RegExp('/' + myuserid + '[)]', "g");
  395.       return html.replace(regname, '$SITENAME$').replace(regurl, 'http://$SITEURL$').replace(reguid, '/$MYUSERID$)'); // myuserid/$MYUSERID$ ???
  396.     }
  397.  
  398.     //Replace contents of element with a textarea (containing markdown of contents), and save/cancel buttons
  399.     function ToEditable(el) {
  400.       var backup = el.html();
  401.       var html = Tag(el.html().replace(greeting, ''));  //remove greeting before editing..
  402.       if(html.indexOf('<textarea') > -1) return; //don't want to create a new textarea inside this one!
  403.       var txt = $('<textarea />').css('height', 2 * el.height())
  404.                 .css('width', el.css('width'))
  405.                 .val(htmlToMarkDown(html));
  406.  
  407.       BorkFor(el); //this is a hack
  408.       //save/cancel links to add to textarea
  409.       var commands = $('<a>save</a>').click(function () { SaveEditable($(this).parent()); UnborkFor(el); })
  410.                       .add('<span class="lsep"> | </span>')
  411.                       .add($('<a>cancel</a>').click(function () { CancelEditable($(this).parent(), backup); UnborkFor(el); }));
  412.       //set contents of element to textarea with links
  413.       el.html(txt.add(commands));
  414.     }
  415.  
  416.     //This is to stop the input pinching focus when I click inside textarea
  417.     //Could have done something clever with contentEditable, but this is evil, and it annoys Yi :P
  418.     function BorkFor(el) {
  419.       var label = el.parent('label');
  420.       label.attr('for', 'borken');
  421.     }
  422.     function UnborkFor(el) {
  423.       var label = el.parent('label');
  424.       label.attr('for', label.prev().attr('id'));
  425.     }
  426.     //Save textarea contents, replace element html with new edited content
  427.     function SaveEditable(el) {
  428.       var html = markDownToHtml(el.find('textarea').val());
  429.       SetStorage(el.attr('id'), Tag(html));
  430.       el.html((showGreeting ? greeting : "") + UnTag(html));
  431.     }
  432.  
  433.     function CancelEditable(el, backup) {
  434.       el.html(backup);
  435.     }
  436.  
  437.     //Empty all custom comments from storage and rewrite to ui
  438.     function ResetComments() {
  439.       ClearStorage("name-"); ClearStorage("desc-"); ClearStorage("site-");
  440.       $.each(defaultcomments, function (index, value) {
  441.         SetStorage('name-' + index, value["Name"]);
  442.         SetStorage('desc-' + index, value["Description"]);
  443.         if ( value["Site"] == undefined ) SetStorage('site-' + index, '');
  444.         else SetStorage('site-' + index, value["Site"]);
  445.       });
  446.       SetStorage("commentcount", defaultcomments.length);
  447.     }
  448.  
  449.     //rewrite all comments to ui (typically after import or reset)
  450.     function WriteComments(popup) {
  451.       if(!GetStorage("commentcount")) ResetComments();
  452.       var ul = popup.find('.action-list');
  453.       ul.empty();
  454.       for(var i = 0; i < GetStorage("commentcount"); i++) {
  455.         var commenttype = GetCommentType(GetStorage('name-' + i));
  456.         if(commenttype == "any" || (commenttype == popup.posttype)) {
  457.           var tsite = GetStorage('site-' + i);
  458.           if ( !(tsite == undefined || tsite == '' || tsite == siteurl) ) continue;
  459.           var desc = GetStorage('desc-' + i).replace(/\$SITENAME\$/g, sitename).replace(/\$SITEURL\$/g, siteurl).replace(/\$MYUSERID\$/g, myuserid).replace(/\$/g, "$$$");
  460.           var opt = optionTemplate.replace(/\$ID\$/g, i)
  461.                           .replace("$NAME$", GetStorage('name-' + i).replace(/\$/g, "$$$"))
  462.                           .replace("$DESCRIPTION$", (showGreeting ? greeting : "") + desc);
  463.           ul.append(opt);
  464.         }
  465.       }
  466.       ShowHideDescriptions(popup);
  467.       AddOptionEventHandlers(popup);
  468.     }
  469.  
  470.     function GetCommentType(comment) {
  471.       if(comment.indexOf('[Q]') > -1) return "question";
  472.       if(comment.indexOf('[A]') > -1) return "answer";
  473.       return "any";
  474.     }
  475.  
  476.     function AddOptionEventHandlers(popup) {
  477.       popup.find('label > span').dblclick(function () { ToEditable($(this)); });
  478.       //add click handler to radio buttons
  479.       popup.find('input:radio').click(function () {
  480.         popup.find('.popup-submit').removeAttr("disabled"); //enable submit button
  481.         //unset/set selected class, hide others if necessary
  482.         $(this).parents('ul').find('.action-selected').removeClass('action-selected');
  483.         if(GetStorage('hide-desc') == "hide") {
  484.           $(this).parents('ul').find('.action-desc').hide();
  485.         }
  486.         $(this).parent().addClass('action-selected')
  487.                         .find('.action-desc').show();
  488.       });
  489.       popup.find('input:radio').keyup(function (event) {
  490.         if(event.which == 13) {
  491.           event.preventDefault();
  492.           popup.find('.popup-submit').trigger('click');
  493.         }
  494.       });
  495.     }
  496.  
  497.     //Adjust the descriptions so they show or hide based on the user's preference.
  498.     function ShowHideDescriptions(popup) {
  499.       //get list of all descriptions except the currently selected one
  500.       var descriptions = popup.find("ul.action-list li:not(.action-selected) span[id*='desc-']");
  501.  
  502.       if(GetStorage('hide-desc') == "hide") {
  503.         descriptions.hide();
  504.       }
  505.       else {
  506.         descriptions.show();
  507.       }
  508.     }
  509.  
  510.     //Show a message (like notify.show) inside popup
  511.     function ShowMessage(popup, title, body, callback) {
  512.       var html = body.replace(/\n/g, '<BR/>');
  513.       var message = $(messageTemplate.replace("$TITLE$", title)
  514.                           .replace('$BODY$', html));
  515.       message.find('.notify-close').click(function () {
  516.         $(this).parent().fadeOutAndRemove();
  517.         callback();
  518.       });
  519.       popup.find('h2').before(message);
  520.     }
  521.  
  522.     //We only show announcement once for each version
  523.     function CheckForAnnouncement(popup) {
  524.       var previous = GetStorage("LastMessage");
  525.       GetRemote('http://dl.dropbox.com/u/2835366/SO/announcement.json', function (announcement) {
  526.         if(previous != announcement.id) {
  527.           ShowMessage(popup, "Service announcement", announcement.message, function () { SetStorage("LastMessage", announcement.id); });
  528.         }
  529.       });
  530.     }
  531.  
  532.     //Get remote content via ajax, target url must contain valid json wrapped in callback() function
  533.     function GetRemote(url, callback, onerror) {
  534.       $.ajax({ type: "GET", url: url + '?jsonp=?', dataType: "jsonp", jsonpCallback: "callback", timeout: 2000, success: callback, error: onerror, async: false });
  535.     }
  536.  
  537.     //Check to see if a new version has become available since last check
  538.     // only checks once a day
  539.     function CheckForNewVersion(popup) {
  540.       var today = (new Date().setHours(0, 0, 0, 0));
  541.       var lastCheck = GetStorage("LastUpdateCheckDay");
  542.       if(lastCheck == null) { //first time visitor
  543.         ShowMessage(popup, "Please read this!", 'Thanks for installing this script. \
  544.                            Please note that you can EDIT the texts inline by double-clicking them. \
  545.                            For other options, please read the full text <a href="http://stackapps.com/q/2116" target="_blank">here</a>.',
  546.                             function () { });
  547.       }
  548.       if(lastCheck != null && lastCheck != today) {
  549.         var lastVersion = GetStorage("LastVersionAcknowledged");
  550.         updateCheck(function (newver, oldver, url) {
  551.           if(newver != lastVersion) {
  552.             ShowMessage(popup, "New Version!", 'A new version (' + newver + ') of the <a href="http://stackapps.com/q/2116">AutoReviewComments</a> userscript is now available (this notification will only appear once per new version, and per site).',
  553.               function () { SetStorage("LastVersionAcknowledged", newver); });
  554.           }
  555.         });
  556.       }
  557.       SetStorage("LastUpdateCheckDay", today);
  558.     }
  559.  
  560.     //customise welcome
  561.     //reverse compatible!
  562.     function LoadFromRemote(url, done, error) {
  563.       GetRemote(url, function (data) {
  564.         SetStorage("commentcount", data.length);
  565.         ClearStorage("name-"); ClearStorage("desc-");
  566.         $.each(data, function (index, value) {
  567.           SetStorage('name-' + index, value.name);
  568.           SetStorage('desc-' + index, markDownToHtml(value.description));
  569.           if ( value.Site == undefined ) SetStorage('site-' + index, '');
  570.           else SetStorage('site-' + index, value.Site);
  571.         });
  572.         done();
  573.       }, error);
  574.     }
  575.  
  576.     //Factored out from main popu creation, just because it's too long
  577.     function SetupRemoteBox(popup) {
  578.       var remote = popup.find('#remote-popup');
  579.       var remoteerror = remote.find('#remoteerror1');
  580.       var urlfield = remote.find('#remoteurl');
  581.       var autofield = remote.find('#remoteauto');
  582.       var throbber = remote.find("#throbber1");
  583.  
  584.       popup.find('.popup-actions-remote').click(function () {
  585.         urlfield.val(GetStorage("RemoteUrl"));
  586.         autofield.prop('checked', GetStorage("AutoRemote") == 'true');
  587.         remote.show();
  588.       });
  589.  
  590.       popup.find('.remote-cancel').click(function () {
  591.         throbber.hide();
  592.         remoteerror.text("");
  593.         remote.hide();
  594.       });
  595.  
  596.       popup.find('.remote-save').click(function () {
  597.         SetStorage("RemoteUrl", urlfield.val());
  598.         SetStorage("AutoRemote", autofield.prop('checked'));
  599.         remote.hide();
  600.       });
  601.  
  602.       popup.find('.remote-get').click(function () {
  603.         throbber.show();
  604.         LoadFromRemote(urlfield.val(), function () {
  605.           WriteComments(popup);
  606.           throbber.hide();
  607.         }, function (d, msg) {
  608.           remoteerror.text(msg);
  609.         });
  610.       });
  611.     }
  612.  
  613.     function SetupWelcomeBox(popup) {
  614.       var welcome = popup.find('#welcome-popup');
  615.       var custom = welcome.find('#customwelcome');
  616.  
  617.       popup.find('.popup-actions-welcome').click(function () {
  618.         custom.val(greeting);
  619.         welcome.show();
  620.       });
  621.  
  622.       popup.find('.welcome-cancel').click(function () {
  623.         welcome.hide();
  624.       });
  625.  
  626.       popup.find('.welcome-force').click(function () {
  627.         showGreeting = true;
  628.         WriteComments(popup);
  629.         welcome.hide();
  630.       });
  631.  
  632.       popup.find('.welcome-save').click(function () {
  633.         var msg = custom.val() == "" ? "NONE" : custom.val();
  634.         SetStorage("WelcomeMessage", msg);
  635.         greeting = custom.val();
  636.         welcome.hide();
  637.       });
  638.     }
  639.  
  640.     //This is where the real work starts - add the 'auto' link next to each comment 'help' link
  641.     //use most local root-nodes possible (have to exist on page load) - #questions is for review pages
  642.     $("#content").delegate(".comments-link", "click", function () {
  643.       var divid = $(this).attr('id').replace('-link', '');
  644.       var posttype = $(this).parents(".question, .answer").attr("class").split(' ')[0]; //slightly fragile
  645.  
  646.       if($('#' + divid).find('.comment-auto-link').length > 0) return; //don't create auto link if already there
  647.       var newspan = $('<span class="lsep"> | </span>').add($('<a class="comment-auto-link">auto</a>').click(function () {
  648.         //Create popup and wire-up the functionality
  649.         var popup = $(markupTemplate);
  650.         popup.find('.popup-close').click(function () { popup.fadeOutAndRemove(); });
  651.         popup.posttype = posttype;
  652.  
  653.         //Reset this, otherwise we get the greeting twice...
  654.         showGreeting = false;
  655.  
  656.         //create/add options
  657.         WriteComments(popup);
  658.  
  659.         //Add handlers for command links
  660.         popup.find('.popup-actions-cancel').click(function () { popup.fadeOutAndRemove(); });
  661.         popup.find('.popup-actions-reset').click(function () { ResetComments(); WriteComments(popup); });
  662.         popup.find('.popup-actions-see').hover(function () {
  663.           popup.fadeTo('fast', '0.4').children().not('#close').fadeTo('fast', '0.0')
  664.         }, function () {
  665.           popup.fadeTo('fast', '1.0').children().not('#close').fadeTo('fast', '1.0')
  666.         });
  667.         popup.find('.popup-actions-impexp').click(function () { ImportExport(popup); });
  668.         popup.find('.popup-actions-toggledesc').click(function () {
  669.           var hideDesc = GetStorage('hide-desc') || "show";
  670.           SetStorage('hide-desc', hideDesc == "show" ? "hide" : "show");
  671.           ShowHideDescriptions(popup);
  672.         });
  673.         //Handle remote url & welcome
  674.         SetupRemoteBox(popup);
  675.         SetupWelcomeBox(popup);
  676.  
  677.         //on submit, convert html to markdown and copy to comment textarea
  678.         popup.find('.popup-submit').click(function () {
  679.           var selected = popup.find('input:radio:checked');
  680.           var markdown = htmlToMarkDown(selected.parent().find('.action-desc').html()).replace(/\[username\]/g, username).replace(/\[OP\]/g, OP);
  681.           $('#' + divid).find('textarea').val(markdown).focus();  //focus provokes character count test
  682.           var caret = markdown.indexOf('[type here]')
  683.           if(caret >= 0) $('#' + divid).find('textarea')[0].setSelectionRange(caret, caret + '[type here]'.length);
  684.           popup.fadeOutAndRemove();
  685.         });
  686.  
  687.         //Auto-load from remote if required
  688.         if(!window.VersionChecked && GetStorage("AutoRemote") == 'true') {
  689.           var throbber = popup.find("#throbber2");
  690.           var remoteerror = popup.find('#remoteerror2');
  691.           throbber.show();
  692.           LoadFromRemote(GetStorage("RemoteUrl"),
  693.             function () { WriteComments(popup); throbber.hide(); },
  694.             function (d, msg) { remoteerror.text(msg); });
  695.         }
  696.  
  697.         //check if we need to show announcement
  698.         //Timing issues here: if we put this before remote code, data from announcement and remote get mixed up
  699.         //if(!window.VersionChecked) CheckForAnnouncement(popup); //commented out, this has to be dismissed on every site - not a good idea!
  700.  
  701.         //add popup and center on screen
  702.         $('#' + divid).append(popup);
  703.         popup.center();
  704.         StackExchange.helpers.bindMovablePopups();
  705.  
  706.         //Get user info and inject
  707.         var userid = getUserId($(this));
  708.         getUserInfo(userid, popup);
  709.         OP = getOP();
  710.  
  711.         //We only actually perform the updates check when someone clicks, this should make it less costly, and more timely
  712.         //also wrap it so that it only gets called the *FIRST* time we open this dialog on any given page (not much of an optimisation).
  713.         if(!window.VersionChecked) { CheckForNewVersion(popup); window.VersionChecked = true; }
  714.       }));
  715.       //$('#' + divid).find('.comment-help-link').parent().append(newspan);
  716.       setTimeout(function() {
  717.         $('#' + divid).find('.comment-help-link').parent().append(newspan);
  718.       }, 15);
  719.     });
  720.   });
  721. });
RAW Paste Data
We use cookies for various purposes including analytics. By continuing to use Pastebin, you agree to our use of cookies as described in the Cookies Policy. OK, I Understand
 
Top