Stack Apps is a question and answer site for apps, scripts, and development with the Stack Exchange API. Join them; it only takes a minute:

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top


quick SE comments for quick SE people


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.


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.


some of this list is copied from AutoReviewComments - Pro-forma comments for SE

  1. 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!

  2. 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!

  3. Full customization

    You can change the prefix (;; by default) and the commands, which also support regexp matching for site hostnames (ex. to customize your QuickComments to specific sites.

  4. 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.

  5. 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.

  6. 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., and $USERNAME with the name of the user whose post you are commenting on. added in 1.0.1

  7. Easy access on all sites / computers by storing information in profile

    For example, just add this at the end of your profile:

    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


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
// @license MIT
// @description Quick SE comments for quick SE people
// @version 1.0.2
// @match *://**
// @match *://**
// @match *://**
// @match *://**
// @match *://**
// @match *://**
// @match *://**
// ==/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) {
        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;
    case commentModes.COMMAND:
        if (e.which == 27) { // esc
        } 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);
        } 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
            } else {
                updatePopup(this.value.match(new RegExp('.*' + data.prefix + '(.*)$'))[1]);

$(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)))
                            $('<td>').append($('<input>').attr({type: 'submit'}).val('Save').click(function(e){
                                data = JSON.parse('{' + $('textarea', tr).val() + '}');
                                localStorage.__stackexchange_quickcomment_data = JSON.stringify(data);
                    })).append($('<span>').text(' | ').addClass('lsep'))
                    .append($('<a>').text('(auto)').click(function() {
                            url: (window.location.origin + '/users/edit/' + $('.topbar a.profile-me').attr('href').match(/\d+/)[0]),
                            success: function(resp) {
                                var profileData = unHTMLEncode(resp.match(/&lt;!--stackexchange-quickcomment([\s\S]+?)--&gt;/)[1]);
                                data = JSON.parse('{' + profileData + '}');
                                localStorage.__stackexchange_quickcomment_data = JSON.stringify(data);
                                alert('Data successfully pulled from profile');
    }, 5);

function createPopup(el) {
    commentPopup = $('<div>');

function updatePopup(txt) {
    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(/&quot;/g, '"')
        .replace(/&#39;/g, '\'')
        .replace(/&amp;/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);';
share|improve this question

(v1.0.1): metadata referring to the post being commented on

For example,

  • $SITENAME: the name of the current site (ex. Stack Apps)
  • $SITEURL: the URL of the current site (ex.
  • $USERNAME: the owner of the post being commented on
share|improve this answer

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .