I came up with an idea for a userscript to simplify writing answers on Stack Exchange while writing a previous answer.
This userscript adds "Review"-links on the top of each code segment. When clicked, it adds checkboxes in front of every code line. You select the checkboxes for the lines you want to comment about, click the link again - which has now changed to "Add to answer", and voilà - the code will be copied to your answer.
I believe this userscript can significantly reduce the scrolling up and down where you consistently copy some code from the question, paste it in the answer, scroll up again, copy code, scroll down, paste, scroll up copy, etc... Now you can just go through the code from top to bottom and select the lines you want to comment on.
I have tested this userscript using Google Chrome + Tampermonkey, but I believe it should also work with Firefox + Greasemonkey, and with other browser/extension combinations that support userscripts.
The code is also available on GitHub.
If you are able to include a userscript from a link, use this link.
// ==UserScript==
// @name Auto-Review
// @author Simon Forsberg
// @namespace zomis
// @homepage https://www.github.com/Zomis/Auto-Review
// @description Adds checkboxes for copying code in a post to an answer.
// @include http://stackoverflow.com/*
// @include http://meta.stackoverflow.com/*
// @include http://superuser.com/*
// @include http://serverfault.com/*
// @include http://meta.superuser.com/*
// @include http://meta.serverfault.com/*
// @include http://stackapps.com/*
// @include http://askubuntu.com/*
// @include http://*.stackexchange.com/*
// @exclude http://chat.stackexchange.com/*
// ==/UserScript==
function embedFunction(name, theFunction) {
var script = document.createElement('script');
script.type = 'text/javascript';
script.textContent = theFunction.toString().replace(/function ?/, 'function ' + name);
document.getElementsByTagName('head')[0].appendChild(script);
}
embedFunction('showAutoreviewButtons', function(clickedObject) {
var i;
if ($(clickedObject).data('review')) {
var answer = $("#wmd-input");
var answer_text = answer.val();
var added_lines = 0;
var added_blocks = 0;
// loop through checkboxes and prepare answer
var checkboxes = $("input.autoreview");
var block = [];
for (i = 0; i < checkboxes.length; i++) {
if (!$(checkboxes[i]).prop('checked')) {
continue;
}
var checkbox = $(checkboxes[i]);
var line_data = (checkbox).data('line');
block.push(line_data);
if ((i === checkboxes.length - 1) || !$(checkboxes[i + 1]).prop('checked')) {
// add block
var block_line;
var cut_count = 1000;
for (block_line = 0; block_line < block.length; block_line++) {
var cut_this = block[block_line].indexOf(block[block_line].trim());
if (cut_count > cut_this) {
cut_count = cut_this;
}
}
for (block_line = 0; block_line < block.length; block_line++) {
answer_text = answer_text + "\n " + block[block_line].substr(cut_count);
}
answer_text += "\n\n---\n";
added_lines += block.length;
added_blocks++;
block = [];
}
}
answer.val(answer_text);
alert(added_lines + " lines in " + added_blocks + " blocks added to answer.");
return;
}
$(clickedObject).data('review', true);
$(clickedObject).text("Add to answer");
var spans = $("code span", $(clickedObject).next());
console.log(spans.length);
var count = spans.length;
var line = "";
var first = null;
for (i = 0; i < count; i++) {
var element = $(spans[i]);
if (first === null) {
first = element;
}
if (element.text().indexOf("\n") !== -1) {
console.log(i + " line: " + line);
var lines = element.text().split("\n");
element.text("");
for (var line_index = 1; line_index < lines.length; line_index++) {
var current_line = lines[line_index];
var prev_line = lines[line_index - 1];
var span;
// Add the last part of the previous line
if (line_index == 1) {
line += prev_line;
span = $('<span class="pln zomis before">' + prev_line + '\n</span>');
element.after(span);
element = span;
}
// Add the checkbox for the previous line
if (line.length > 0) {
var dataProperty = 'data-line="' + line + '" ';
var checkbox = $('<input type="checkbox" ' + dataProperty + ' class="autoreview"></input>');
first.before(checkbox);
first = null;
}
// Add the beginning <span> element for the current line
if (line_index < lines.length - 1) {
current_line += "\n";
}
span = $('<span class="pln zomis after">' + current_line + '</span>');
element.after(span);
first = span;
element = span;
line = current_line;
}
}
else {
line += element.text().replace(/\\/g, '\\\\').replace(/"/g, '\\"');
}
}
if (line.length > 0) {
dataProperty = 'data-line="' + line + '" ';
checkbox = $('<input type="checkbox" ' + dataProperty + ' class="autoreview"></input>');
first.before(checkbox);
}
});
$('pre code').parent().before('<a href="javascript:void(0);" onclick="showAutoreviewButtons($(this))">(Review)</a>');
Primary questions:
- Am I following JavaScript conventions? I have seen
people.coding( "like this" );
with extra spaces around each parameter, and I've also seen people declaring all variables at the beginning of a method. Is this some official convention? Am I break any official convention here? I feel that I am using Java conventions in my JavaScript code. - Can the code be cleaned up, and utility methods and stuff be used, while still keeping browser compatibility? For example, in the code to figure out the indentation for a block I was thinking of how I would do it in Java, where I would use
block.stream().mapToInt(str -> str.indexOf(str.trim())).min()
, can something similar be done here? - Any other comments welcome!
I bet there are many things that can be improved. The fact that it's a userscript made me feel a bit restricted on what I could do, but perhaps that's just because I am not aware of how to do them for userscripts.
I plan on posting this userscript to http://www.stackapps.com when I'm happy enough with it, any feature-requests / UI suggestions can be added as issues on github, or you can ping me in The 2nd Monitor.
Auto-upvote to anyone who is using the script while writing their answer!