问题描述

我想出了一个用户脚本的想法,以便在编写 a previous answer 时简化在 Stack Exchange 上的写作答案。

此用户脚本在每个代码段的顶部添加 「评论」 链接。点击后,它会在每个代码行前添加复选框。您选择要注释的行的复选框,再次单击该链接 – 现在已更改为 「添加到应答」,并且代码将被复制到您的答案。

我相信这个用户脚本可以大大减少上下滚动,您始终从问题中复制一些代码,将其粘贴到答案中,再次向上滚动,复制代码,向下滚动,粘贴,向上滚动等等… 现在你可以从上到下浏览代码,并选择要注释的行。

我已经使用 Google Chrome + Tampermonkey 测试了这个用户名,但我相信它也可以使用 Firefox + Greasemonkey 以及支持用户脚本的其他浏览器/扩展组合。

该代码也可在 GitHub 上获得。

如果您能够从链接中添加用户脚本 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 += "nn---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,当我很高兴的时候,任何 feature-requests /UI 建议都可以添加为 github 的问题,或者你可以在 The 2nd Monitor 中 ping 我。

任何人在编写他们的答案时使用脚本的人都是 Auto-upvote!

最佳解决方案

Naming

我认识到命名可能很难,但是’Auto-Review’ 是一个与 StackExchange 上退出的 UserScript 冲突的名称叫做 AutoReviewComments

Protocols

有许多 UserScript 约定您没有遵守。

首先,对于 Firefox,您需要将标题部分包含到”preserved” 注释块中:

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

在处理 JavaScript 之后,保留需要保留注释块。这允许’compiled’ 版本保留引用,FireFox 可以知道它是什么。

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

此外,您还需要指定 @grant 权限,以使 GreaseMonkey 快乐。在你的情况下,none 是适当的:

// @grant   none

一旦我做了这些修改,用户脚本在我的 FireFox 中很好地加载。

Usability

我建议有四种 user-experience 增强功能:

  1. 没有 POPUPS – 弹出窗口是一个可怕的分心

  2. Scroll-to-inserted 内容 – 插入代码块后,滚动到编辑点并使其在屏幕上可见

  3. 在答案输入框中触发 changed-event – 这将更新答案的’preview’ 。目前,您必须在答案框中手动更改某些内容以进行预览更新。

  4. 处理后,您应该清除复选框。如果您需要稍后复制不同的块,un-check 每个盒子都是一个 PITA 。

Review

  • double-array-dereferencing 是不必要的 micro-optimization 。您有以下代码 (在此使用您的 userscript 复制):

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

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

    那代码 double-dereferences $(checkboxes[i])。我认为这是因为你不想携带变量的开销,如果没有检查。这是一个 early-optimization 。代码将更简单:

    for (i = 0; i < checkboxes.length; i++) {
        var checkbox = $(checkboxes[i]);
        if (!checkbox.prop('checked')) {
            continue;
        }
    
        var line_data = (checkbox).data('line');
    
  • var 声明。 JavaScript ‘hoists’ 所有变量声明到包含它的函数的顶部。与其他语言不同,JavaScript 不应使用’block-local’ 声明进行编码。最好的做法是将所有变量声明移动到函数中的第一个项目中。 This Stack Overflow answer 比我更好地解释它。

次佳解决方案

编码风格

我不是一个 JavaScript 大师,但我相当肯定编码风格指南比 Java 更加严格。在运算符周围使用间距的方式,像 Java 一样的大括号的放置似乎很常见,我从来没有听说有人反对。还有一些其他的风格,像你提到的,但它们是罕见的。有些人也喜欢使用 2 个空格而不是 4 个缩进,但这似乎在很大程度上是一个味道的问题。

作为参考,PyCharm 是 JetBrains(与 IntelliJ 相同的品种) 的 Python + Web 开发的梦幻 IDE,它对 JavaScript 有很好的支持,并且它不反对您的代码的风格。我会把它作为一个好兆头。对于像 people.coding( "like this" ); 这样的代码,auto-format 函数会删除括号内的空格。

坏习惯

首先,将代码粘贴到 http://jshint.com/中,看看你自己:

  • 一个重复的变量定义:checkbox

  • 使用范围以外的几个变量

另外,我觉得奇怪的是,你不能在 embedFunction 中充分利用 jQuery 。由于您已经在脚本中的其他位置使用了 jQuery,而不是:

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

你可以简化为:

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

更新:看来 (从你的评论),这个建议不适用于你”as-is” 。我会试着弄清楚为什么。在此期间,我的反对意见仍然存在:在同一个脚本中混合使用经典的 JavaScript 和 jQuery 是有点奇怪,就好像它是由不同的人或同一个人在不同的时间完成的。我认为更好地保持一致,并且编写经典的 JavaScript 而不依赖于 jQuery,或者完全拥抱 jQuery 。

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

DOM 查找不是很便宜。所以每当你重复查找时,考虑缓存一个变量,例如:

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

如果您首先分配给 checkbox,然后执行 if,以上更好。它也减少了 checkboxes[i]的重复。

后来我在代码中看到 $(clickedObject)多次。当然也可以缓存。

奇怪的元素

在这一行:

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

checkbox 周围的括号是… 好奇… 你不需要它们,可能会让读者误以为 jQuery 被错误地省略了 $(它不是) 。

增强作业

这个赋值可以用+=来代替扩充赋值:

answer_text = answer_text + "n    " + block[block_line].substr(cut_count); 

参考文献

注:本文内容整合自 Google/Baidu/Bing 辅助翻译的英文资料结果。如果您对结果不满意,可以加入我们改善翻译效果:薇晓朵技术论坛。