我想出了一个用户脚本的想法,它可以简化在Stack Exchange上编写答案的过程,同时编写先前的答案。

此用户脚本在每个代码段的顶部添加了“ Review”链接。单击后,它将在每个代码行的前面添加复选框。您选中要评论的行的复选框,再次单击链接-现在已更改为“添加到答案”,然后点击链接-代码将复制到您的答案中。





我相信该用户脚本可以显着减少上下滚动,在该位置中,您始终可以从问题中复制一些代码,将其粘贴到答案中,然后再次向上滚动,然后复制代码,向下滚动,粘贴,向上复制等...现在,您只需从上至下浏览代码,然后选择要评论的行即可。

我已经测试了此用户脚本使用Google Chrome + Tampermonkey,但我认为它也应与Firefox + Greasemonkey以及支持用户脚本的其他浏览器/扩展组合一起使用。

该代码也可在GitHub上获得。

如果可以从链接中包含用户脚本,请使用此链接。

// ==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>');


主要问题:


我是否遵循JavaScript约定?我看到people.coding( "like this" );每个参数周围都有多余的空格,而且我还看到人们在方法开始时声明所有变量。这是一些官方惯例吗?我在这里违反任何正式约定吗?我觉得我在JavaScript代码中使用了Java约定。
在保持浏览器兼容性的同时,是否可以清理代码,使用实用程序方法和内容?例如,在代码中找出一个块的缩进量时,我正在考虑如何在Java中使用block.stream().mapToInt(str -> str.indexOf(str.trim())).min()进行操作,可以在此处执行类似的操作吗?
欢迎任何其他评论!

我敢打赌,有很多事情可以改进。它是用户脚本这一事实使我对可以做的事情感到有些限制,但这也许只是因为我不知道如何为用户脚本做这些操作。

我打算将此用户脚本发布到当我对此感到满意时,可以在http://www.stackapps.com上将任何功能请求/ UI建议添加为github上的问题,也可以在The 2nd Monitor中对我进行ping操作。

自动为任何在编写答案时使用脚本的人提供帮助!

评论

我不喜欢您必须单击每行一个复选框。目前,我只是用鼠标中键点击编辑并复制了相关部分。当我必须处理12行以上的代码时,您的工具又如何更快?

@Pimgd当前可能不是,但是我正在寻找改善方法。例如按住shift键并单击一个复选框以从先前选择的复选框中选择所有行?

是的,这样做。会帮助吨。

#1 楼

命名

我知道命名可能很难,但是'Auto-Review'的名称与StackExchange上现有的UserScript叫AutoReviewComments
协议
的名称有点冲突
您有很多用户脚本约定无法遵守。

首先,对于Firefox,您需要将标头部分包装到“保留的”注释块中:

/** @preserve
// ==UserScript==
.....
// ==/UserScript==
*/


处理javascript后,需要保留该内容以保留注释块。这样,“编译”版本就可以保留引用,并让FireFox知道它的含义。

其他浏览器可能没有相同的要求。

此外,您还需要指定@grant权限,以便GreaseMonkey满意。在您的情况下,none是合适的:

// @grant   none


一旦进行了这些修改,用户脚本就会很好地加载到FireFox中。

可用性

,我建议您进行四项用户体验增强:


没有弹出框-弹出窗口令人分心,
滚动到插入内容-插入代码块后,滚动到编辑点并使它在屏幕上可见
在答案输入框上触发更改事件-这将更新答案的“预览”。目前,您必须在答案框中手动更改某些内容才能更新预览。
处理复选框后,应清除它们。如果以后需要复制不同的块,则必须取消选中每个框都是PITA。

审阅




双数组解引用是不必要的微优化。您具有以下代码(使用用户脚本在此处复制):


for (i = 0; i < checkboxes.length; i++) {
    if (!$(checkboxes[i]).prop('checked')) {
        continue;
    }

    var checkbox = $(checkboxes[i]);
    var line_data = (checkbox).data('line');



该代码重复引用了$(checkboxes[i])。我想这是因为如果不检查变量,您不想承担该变量的开销。这是早期优化。该代码将更简单地为:

for (i = 0; i < checkboxes.length; i++) {
    var checkbox = $(checkboxes[i]);
    if (!checkbox.prop('checked')) {
        continue;
    }

    var line_data = (checkbox).data('line');


var声明。 JavaScript将所有变量声明“提升”到包含它的函数的顶部。与其他语言不同,JavaScript不应使用“块本地”声明进行编码。最佳做法是将所有变量声明移到函数的第一项。这个堆栈溢出的答案比我做的要好得多。


评论


\ $ \ begingroup \ $
虽然所有var声明都已挂起,但我仍然认为避免在函数顶部使用var声明的怪物块会减少两种危害。我宁愿不小心将一个标识符声明为var两次,也不愿由于声明和定义之间的分离而根本不声明它。
\ $ \ endgroup \ $
– 200_success
15年1月27日在5:42

#2 楼

编码风格

我不是JavaScript专家,但是我很确定编码风格指南比Java严格得多,例如。
>在运算符之间使用空格的方式,
以及像Java中的大括号的放置方式似乎很常见,
我从未听说过有人反对。
还有其他一些样式,例如您提到的那些,
,但它们很少见。
有些人也更喜欢使用2个空格而不是4个空格的缩进,
但这在很大程度上似乎是一种品味问题。

作为参考,
PyCharm是由JetBrains(与IntelliJ相同的名称)进行Python + Web开发的出色IDE,
对JavaScript具有良好的支持,
也不反对您的代码就风格而言。
我认为这是一个好兆头。
对于people.coding( "like this" );之类的代码,
自动格式化功能会删除括号内的空格。

错误的做法

首先,将代码粘贴到http://jshint.com/中并亲自查看:


一个重复的变量定义:checkbox

一些超出范围的变量

此外,
我发现奇怪的是,您没有充分利用embedFunction中的jQuery。
由于您已经在脚本中的其他地方使用jQuery了很多,因此
例如,而不是:
>

您可以将其简化为:原样”。我会尝试找出原因。同时,我的反对意见仍然存在:在同一脚本中混合使用经典JavaScript和jQuery有点奇怪,好像是由两个不同的人或同一个人在不同的时间完成的一样。我认为最好保持一致并要么不依赖jQuery就编写经典JavaScript,要么完全拥抱jQuery。

缓存$(...)查找的结果

DOM查找并不便宜。
因此,只要您重复查找,就可以考虑在变量中进行缓存,例如在这里:


   document.getElementsByTagName('head')[0].appendChild(script);



以上是如果先分配给checkbox更好,然后再分配if,则更好。当然也可以很好地缓存它。

奇怪的元素

在这一行上:


$('head').append(script);



checkboxes[i]周围的括号很...好奇...
您不需要它们,
可能会使读者误以为它是jQuery并被误删了$(clickedObject)

扩展分配

此分配可以替换为checkbox扩展分配:


if (!$(checkboxes[i]).prop('checked')) {
    continue;
}
var checkbox = $(checkboxes[i]);



评论


\ $ \ begingroup \ $
$('head')。append(script);不起作用,我收到了很好的错误消息“未定义不是函数”。似乎由于某种原因未定义JQuery。 (我想它还没有被加载)
\ $ \ endgroup \ $
–西蒙·福斯伯格
15年1月27日,11:35



\ $ \ begingroup \ $
这让我感到惊讶,因为如果未加载jQuery,则在embedFunction(...)调用之后立即拥有的$('pre code')应该首先给出相同的错误。今晚我将尝试使用您的脚本。我现在在评论中对此进行了小幅更新。
\ $ \ endgroup \ $
– janos
15年1月27日在12:29

#3 楼

我知道这段代码已经存在大约5年了,但是尽管这些站点上的DOM发生了一些变化,但看起来仍然可以正常工作。自发布此书以来,您无疑已经学到了很多东西,但是我觉得还有其他事情尚未提及/并入github脚本中,可以改善/简化代码。

我最近已经习惯了github风格的UI,它通过单击和拖动选择来选择要注释的行。我考虑过制作这样的用户脚本,并且可能会利用其中的一些代码。我开始修改它,发现它在一个巨大的功能中有很多代码,以及一些大循环。我建议为事件处理程序使用单独的功能,例如一个用于单击按钮以添加复选框,另一个用于单击按钮以将选中的行添加到评论。我知道这不是单一责任原则的直接应用,但感觉很相关。

jQuery :checked选择器可以简化代码,以便从以下位置复制到答案时找到
复选框:


var checkboxes = $("input.autoreview");
for (i = 0; i < checkboxes.length; i++) {
    if (!$(checkboxes[i]).prop('checked')) {
        continue;
    }
    var checkbox = $(checkboxes[i]);



对此:

var checkboxes = $("input.autoreview:checked");
for (i = 0; i < checkboxes.length; i++) {
    var checkbox = $(checkboxes[i]);


可以使用功能性方法而是使用Array.reduce()。 jQuery确实具有类似的功能方法,例如.map().each()的过滤方法,它们类似于Array.map()的本机数组方法,但是请注意jQuery等效项之间的参数差异。


例如,在代码来弄清楚我正在考虑如何在Java中使用block.stream().mapToInt(str -> str.indexOf(str.trim())).min()的块的缩进,可以在此处执行类似的操作吗?


使用ecmascript- 6个功能可以简化诸如取消引用数组之类的事情,即使用for...of循环。在回顾了您的Ur代码的Royal游戏之后,我知道您熟悉箭头功能。它们可以与Array.map()Array.filter()Array.reduce()等功能方法一起使用,以简化语法。

更新:PR#4已创建并合并,其中包含来自上面的许多建议。

评论


\ $ \ begingroup \ $
确实有很多好处!随时提出请求请求或其他任何内容,或使用我为其他项目编写的代码。这不是我打算维护的任何内容。
\ $ \ endgroup \ $
–西蒙·福斯伯格
20年4月21日在10:00