QuickComment
quick SE comments for quick SE people
Description
Tired of typing the same thing over and over again? Now, with QuickComment, you can type ";;linkonly
" (for example) and have it expand to a detailed comment to a user explaining why link-only answers are bad. Inspired by AutoReviewComments.
Download
- Available on Github
- Full source code included below (may be out of date)
Current version is 1.0.2.
How to use
Install as a normal userscript (for Chrome, drag and drop into chrome://extensions
, or just see here for a detailed guide for all browsers). Then go to any SE site, open a comment box, and type ;;welcome
and press the space bar for a short introduction.
Features
some of this list is copied from AutoReviewComments - Pro-forma comments for SE
Read your comment before you post it!
Inserting a QuickComment only inserts the text; it doesn't submit the comment. This is so that you can check the comment before posting!
Insert as many templates as you would like
Multiple commands in a single comment are supported; for example:
;;welcome ;;linkonly ;;thanks
. Simply type them one after the other!Full customization
You can change the prefix (
;;
by default) and the commands, which also support regexp matching for site hostnames (ex.stackapps.com
) to customize your QuickComments to specific sites.Easy import/export and simple editing via a JSON editor
The format for customizing your QuickComments is JSON, which is easy to copy/paste wherever you want, including into an IDE to edit as you please.
Sublime-esque "match-anywhere" searcher
The script doesn't just search for substrings of the commands: it also searches for subsequences. For example, if you want to access the
;;welcome
command, you can just type;;wlc
. Exact matches (ex.;;welc
) will still show up first in the list of suggestions.Access to data about the post being commented on
QuickComment will automatically replace
$STIENAME
with the site's name (ex. Stack Apps),$SITEURL
with its URL (ex. stackapps.com), and$USERNAME
with the name of the user whose post you are commenting on. added in 1.0.1Easy access on all sites / computers by storing information in profile
For example, just add this at the end of your profile:
<!--stackexchange-quickcomment prefix: ";;", commands: [ {"sites": ".", "cmd": "welcome", "text": "Welcome to $SITENAME!"} ] -->
Now just click the "(auto)" link (next to the "qc" link) on any site or any computer, and you'll have access to an easy
;;welcome
command. added in 1.0.2
Code
Here is the full code of stackexchange-quickcomment.user.js
. Warning: it may be outdated; see the Github repo (in the Download section above) for the newest version.
// ==UserScript==
// @name stackexchange-quickcomment
// @namespace http://keyboardfire.com/
// @license MIT
// @description Quick SE comments for quick SE people
// @version 1.0.2
// @match *://*.stackexchange.com/*
// @match *://*.stackoverflow.com/*
// @match *://*.superuser.com/*
// @match *://*.serverfault.com/*
// @match *://*.askububtu.com/*
// @match *://*.stackapps.com/*
// @match *://*.mathoverflow.net/*
// ==/UserScript==
var userscript = function($) {
if (!localStorage.__stackexchange_quickcomment_data) {
localStorage.__stackexchange_quickcomment_data = JSON.stringify({
prefix: ';;',
commands: [
{sites: '.', cmd: 'welcome', text: 'Welcome to stackexchange-quickcomment! ' +
'To use, simply click the "qc" link to the right of this comment box to edit your quickcomments. The format is JSON. ' +
'The `sites` field is a regexp that tests against the hostname (ex. "$SITEURL") so as to only pertain to a specific site or sites. ' +
'The `cmd` field is the command that you can type in after the prefix to access the quickcomment. ' +
'Finally, the text field is the text that will be inserted when you use the quickcomment. Enjoy!'}
]
});
}
var data = JSON.parse(localStorage.__stackexchange_quickcomment_data);
var commentModes = {NORMAL: 0, COMMAND: 1}, commentMode, focusedComment, commentPopup, commentPopupSelectionIndex;
$(document).on('focus', 'textarea[name="comment"]', function() {
var currentComment = $(this).closest('form').attr('id');
if (focusedComment != currentComment) {
destroyPopup();
focusedComment = currentComment;
}
}).on('keydown keyup', 'textarea[name="comment"]', function(e) { // 'keydown keyup' to prevent weird timing issues with typing really fast
switch (commentMode) {
case commentModes.NORMAL:
if (new RegExp(data.prefix + '$').test(this.value)) {
commentMode = commentModes.COMMAND;
createPopup(this);
}
break;
case commentModes.COMMAND:
if (e.which == 27) { // esc
destroyPopup();
} else if (e.which == 32) { // space
this.value = this.value.replace(new RegExp('^(.*)' + data.prefix + '.*$'), function(m, g1) { return g1; })
+ parseText(data.commands[parseInt(commentPopup.children().eq(commentPopupSelectionIndex).attr('data-cmdindex'))].text, this);
destroyPopup();
} else if (e.which == 37 || e.which == 39) { // left / right respectively
if (e.type == 'keydown') {
var delta = e.which - 38; // this just happened to work out perfectly
commentPopup.children().eq(commentPopupSelectionIndex).css('font-weight', 'normal');
commentPopupSelectionIndex = Math.max(0, Math.min(commentPopup.children().length-1, commentPopupSelectionIndex + delta));
commentPopup.children().eq(commentPopupSelectionIndex).css('font-weight', 'bold');
}
} else {
if (e.which == 8 && !new RegExp(data.prefix).test(this.value)) { // backspace
destroyPopup();
} else {
updatePopup(this.value.match(new RegExp('.*' + data.prefix + '(.*)$'))[1]);
}
}
break;
}
});
$(document).on('click', '.comments-link', function() {
var td = $(this).parent();
var intr = setInterval(function() {
var helpLink = $('.comment-help-link', td);
if (helpLink.length) {
if ($('.stackexchange-quickcomment-link', td).length === 0) {
helpLink.parent().append($('<span>').text(' | ').addClass('lsep'))
.append($('<a>').text('qc').addClass('stackexchange-quickcomment-link').click(function() {
var tr;
$(this).closest('tr').parent().append(tr = $('<tr>').append(
$('<td>').append($('<textarea>').attr({cols: 68, rows: 3, id: 'stackexchange-quickcomment-editor'}).css('height', '20em').val(prettyJSON(data)))
).append(
$('<td>').append($('<input>').attr({type: 'submit'}).val('Save').click(function(e){
e.preventDefault();
data = JSON.parse('{' + $('textarea', tr).val() + '}');
localStorage.__stackexchange_quickcomment_data = JSON.stringify(data);
tr.remove();
}))
));
})).append($('<span>').text(' | ').addClass('lsep'))
.append($('<a>').text('(auto)').click(function() {
$.ajax({
url: (window.location.origin + '/users/edit/' + $('.topbar a.profile-me').attr('href').match(/\d+/)[0]),
success: function(resp) {
var profileData = unHTMLEncode(resp.match(/<!--stackexchange-quickcomment([\s\S]+?)-->/)[1]);
data = JSON.parse('{' + profileData + '}');
localStorage.__stackexchange_quickcomment_data = JSON.stringify(data);
alert('Data successfully pulled from profile');
}
});
}));
}
clearInterval(intr);
}
}, 5);
});
function createPopup(el) {
commentPopup = $('<div>');
$(el).parent().prepend(commentPopup);
}
function updatePopup(txt) {
commentPopup.empty();
commentPopupSelectionIndex = 0;
for (var i = 0; i < data.commands.length; i++) {
if (new RegExp(data.commands[i].sites).test(window.location.hostname) && extMatch(txt, data.commands[i].cmd)) {
commentPopup[data.commands[i].cmd.indexOf(txt) > -1 ? 'prepend' : 'append']
($('<span>').text(data.commands[i].cmd).attr('data-cmdindex', '' + i).css({
padding: '2px'
}));
}
}
commentPopup.children().eq(0).css('font-weight', 'bold');
}
function destroyPopup() {
commentMode = commentModes.NORMAL;
if (commentPopup) commentPopup.remove();
}
function parseText(text, commentBox) {
return text
.replace(/\$SITENAME/g, $('.site-icon.favicon').attr('title'))
.replace(/\$SITEURL/g, window.location.hostname)
.replace(/\$USERNAME/g, $(commentBox).closest('div[class="question"],div[class="answer"]').find('.post-signature:last .user-details a').text())
}
function prettyJSON(obj) {
return JSON.stringify(obj)
.replace(/^\{|\}$/g, '')
.replace(/"\},/g, '"},\n ') // this will break if string contains the substring '"},', but that's incredibly unlikely
.replace(':[', ': [\n ')
.replace(']', '\n]')
.replace(/:"/g, ': "')
.replace(/,"/g, ', "')
}
function unHTMLEncode(text) {
// todo: make this work for all texts (this is the lazy version)
return text
.replace(/"/g, '"')
.replace(/'/g, '\'')
.replace(/&/g, '&') // this must come last
}
function extMatch(needle, haystack) {
return new RegExp(needle.split('').join('.*')).test(haystack);
}
};
var el = document.createElement('script');
el.type = 'text/javascript';
el.text = '(' + userscript + ')(jQuery);';
document.head.appendChild(el);