User:Tzusheng/Wikibench-Editquality.js

Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.
//<nowiki>

// Campaign Page

(function ($, mw) {
    $(document).ready(function() {
        var wgPageName = "User:Tzusheng/sandbox/Wikipedia:Wikibench/Campaign:Editquality";
        if (mw.config.get("wgPageName") === wgPageName && mw.config.get("wgAction") === "view") {
            mw.loader.using(["oojs-ui-core", "oojs-ui-widgets", "oojs-ui-windows"]).done(function() {

                // init
                var WIKIBENCH_PREFIX = "Tzusheng/sandbox/Wikipedia:Wikibench";
                var WIKIBENCH_NAMESPACE = 2;
                var entityType = "diff";
                var language = "en";
                var entityPageSplit = "-----";
                var facets = ["editDamage", "userIntent"];
                var facetNames = {
                    editDamage: "edit damage",
                    userIntent: "user intent"
                };
                var facetLabels = {
                    editDamage: ["damaging", "not damaging"],
                    userIntent: ["bad faith", "good faith"]
                };
                var facetColors = {
                    editDamage: ["#fee7e6", "#d5fdf4"],
                    userIntent: ["#fee7e6", "#d5fdf4"]
                };
                var userName = mw.config.get("wgUserName");
                var mwApi = new mw.Api();

                function calculateStandardDeviation(numbers) {
                    // Step 1: Calculate the mean
                    var sum = 0;
                    for (var i = 0; i < numbers.length; i++) {
                    sum += numbers[i];
                    }
                    var mean = sum / numbers.length;
                
                    // Step 2: Calculate the sum of squared differences
                    var squaredDifferencesSum = 0;
                    for (var j = 0; j < numbers.length; j++) {
                    var difference = numbers[j] - mean;
                    squaredDifferencesSum += difference * difference;
                    }
                
                    // Step 3: Calculate the variance
                    var variance = squaredDifferencesSum / numbers.length;
                
                    // Step 4: Calculate the standard deviation (square root of variance)
                    var standardDeviation = Math.sqrt(variance);
                
                    return standardDeviation;
                }

                function getPrefixedPages(entityType, queryContinue, deferred, results) {
                    deferred = deferred || $.Deferred();
                    queryContinue = queryContinue || {};
                    results = results || [];
                    var prefix = WIKIBENCH_PREFIX + "/Entity:" + entityType.charAt(0).toUpperCase() + entityType.slice(1) + "/";
                    var params = {
                        action: "query",
                        prop: "revisions",
                        rvprop: "content",
                        generator: "allpages",
                        gapprefix: prefix,
                        gaplimit: 500,
                        gapnamespace: WIKIBENCH_NAMESPACE,
                        format: "json",
                        formatversion: 2
                    };

                    Object.assign(params, queryContinue)

                    mwApi.get(params)
                        .done(function(data) {
                            var pages = data.query.pages;
                            pages.forEach(function(page){
                                // console.log(page);
                                // console.log(page.revisions);
                                if (page.revisions !== undefined) { // band-aid
                                    page['content'] = page.revisions[0].content;
                                    delete page['revisions'];
                                    results.push(page);
                                }
                            });
                            if(data.continue){
                                getPrefixedPages(entityType, data.continue, deferred, results);
                            }else{
                                deferred.resolve(results);
                            }
                        })
                        .fail(function(e) {
                            deferred.fail(e);
                        });

                    return deferred.promise();
                }

                getPrefixedPages(entityType).done(function(results) {
                    var label;
                    var tableDiv = $(".wikibench-data-table")
                    var tbody = tableDiv.find("tbody");
                    tbody.find("tr").remove(); // remove the empty line
                    function sortColumn(columnId, order) {
                        var header = tableDiv.find(columnId);
                        if (order === "ascending") {
                            if (header.attr("title") === "Sort ascending") { // note: title is not the current sorting state
                                header.click();
                            } else if (header.attr("title") === "Sort initial") {
                                header.click().click();
                            } else {
                                // do nothing because the column is already ascending
                            }
                        } else if (order === "descending") {
                            if (header.attr("title") === "Sort ascending") {
                                header.click().click();
                            } else if (header.attr("title") === "Sort descending") {
                                header.click();
                            } else {
                                // do nothing because the column is already descending
                            }
                        } else {
                            // do nothing
                        }
                    }
                    var button1 = new OO.ui.ButtonWidget({label: "provide more labels"});
                    button1.on("click", function() {
                        sortColumn("#table-header-editDamage-your-label", "ascending");
                        sortColumn("#table-header-label-count", "ascending");
                    });
                    var button2 = new OO.ui.ButtonWidget({label: "compare my labels"});
                    button2.on("click", function() {
                        sortColumn("#table-header-userIntent-primary-label", "descending");
                        sortColumn("#table-header-editDamage-primary-label", "descending");
                        sortColumn("#table-header-userIntent-your-label", "descending");
                        sortColumn("#table-header-editDamage-your-label", "descending");
                    });
                    var button3 = new OO.ui.ButtonWidget({label: "build consensus (edit damage)"});
                    button3.on("click", function() {
                        sortColumn("#table-header-label-count", "descending");
                        sortColumn("#table-header-editDamage-disagreement", "descending")
                    });
                    var button4 = new OO.ui.ButtonWidget({label: "build consensus (user intent)"});
                    button4.on("click", function() {
                        sortColumn("#table-header-label-count", "descending");
                        sortColumn("#table-header-userIntent-disagreement", "descending");
                    });
                    var layoutBefore = new OO.ui.HorizontalLayout({
                        items: [
                            new OO.ui.LabelWidget({ label: "I want to:" }),
                            button1,
                            button2,
                            button3,
                            button4
                        ]
                    });
                    tableDiv.before(layoutBefore.$element);

                    var searchInputDiff = new OO.ui.SearchInputWidget({
                        placeholder: "oldid/newid"
                    });
                    searchInputDiff.on("enter", function() {
                        window.open("/wiki/Special:Diff/" + searchInputDiff.getValue(), "_blank");
                    });
                    var layoutAfter = new OO.ui.HorizontalLayout({
                        items: [
                            new OO.ui.LabelWidget({ label: "Direct submission:" }),
                            searchInputDiff
                        ]
                    })
                    tableDiv.after(layoutAfter.$element);

                    var tableLabelColors = {};
                    for (var i = 0; i < facets.length; i++) {
                        tableLabelColors[facets[i]] = {};
                        for (var j = 0; j < facetLabels[facets[i]].length; j++) {
                            tableLabelColors[facets[i]][facetLabels[facets[i]][j]] = facetColors[facets[i]][j];
                        }
                    }

                    var primaryLabelCounts = {};
                    facets.forEach(function(f) {
                        primaryLabelCounts[f] = {};
                        facetLabels[f].forEach(function(l) {
                            primaryLabelCounts[f][l] = 0;
                        })
                    })

                    for (var r = 0; r < results.length; r++) {
                        label = JSON.parse(results[r].content.split(entityPageSplit)[1]);
                        var primaryLabel = {};
                        var userLabels = {};
                        var individualLabels = {};
                        var lowConfidenceIndicator = {};
                        for (var i = 0; i < facets.length; i++) {
                            var f = facets[i];
                            lowConfidenceIndicator[f] = false;
                            primaryLabel[f] = label.facets[f].primaryLabel.label;
                            userLabels[f] = "";
                            individualLabels[f] = [];
                            primaryLabelCounts[f][primaryLabel[f]] += 1;
                            for (var j = 0; j < label.facets[f].individualLabels.length; j++) {
                                // handle user labels
                                if (label.facets[f].individualLabels[j].userName === userName) {
                                    userLabels[f] = label.facets[f].individualLabels[j].label;
                                    if (label.facets[f].individualLabels[j].lowConfidence) {
                                        lowConfidenceIndicator[f] = true;
                                    }
                                }
                                // handle individual labels for disagreements
                                if (label.facets[f].individualLabels[j].label === facetLabels[f][0]) {
                                    if (label.facets[f].individualLabels[j].lowConfidence) {
                                        individualLabels[f].push(-0.5);
                                    }
                                    else {
                                        individualLabels[f].push(-1);
                                    }
                                }
                                if (label.facets[f].individualLabels[j].label === facetLabels[f][1]) {
                                    if (label.facets[f].individualLabels[j].lowConfidence) {
                                        individualLabels[f].push(0.5);
                                    }
                                    else {
                                        individualLabels[f].push(1);
                                    }
                                }
                            }
                        }

                        var hrefLink = "<a href=\"/wiki/User:" + WIKIBENCH_PREFIX + "/Entity:" + entityType.charAt(0).toUpperCase() + entityType.slice(1) + "/" + label.entityId.toString() + "\"></a>";

                        tbody.append($("<tr>")
                            .append($("<th>").text(label.entityId).wrapInner(hrefLink).attr("scope", "row"))
                            .append($("<td>").text(primaryLabel[facets[0]]).attr("bgcolor", tableLabelColors[facets[0]][primaryLabel[facets[0]]]))
                            .append($("<td>").text(userLabels[facets[0]] + ((lowConfidenceIndicator[facets[0]])?("*"):(""))).attr("bgcolor", tableLabelColors[facets[0]][userLabels[facets[0]]]))
                            .append($("<td>").text(calculateStandardDeviation(individualLabels[facets[0]]).toFixed(3)))
                            .append($("<td>").text(primaryLabel[facets[1]]).attr("bgcolor", tableLabelColors[facets[1]][primaryLabel[facets[1]]]))
                            .append($("<td>").text(userLabels[facets[1]] + ((lowConfidenceIndicator[facets[1]])?("*"):(""))).attr("bgcolor", tableLabelColors[facets[1]][userLabels[facets[1]]]))
                            .append($("<td>").text(calculateStandardDeviation(individualLabels[facets[1]]).toFixed(3)))
                            .append($("<td>").text(label.facets[facets[0]].individualLabels.length.toString()))
                        );
                    }

                    for (var i = 0; i < facets.length; i++) {
                        var f = facets[i];
                        var tmp0 = primaryLabelCounts[f][facetLabels[f][0]];
                        var tmp1 = primaryLabelCounts[f][facetLabels[f][1]];
                        mwApi.get({
                            action: "parse",
                            text: "{{Bar box" +
                                "|title=" + "Primary label distribution for " + facetNames[f] + 
                                "|titlebar=#DDD" +
                                // "|left1=label" +
                                // "|right1=quantity" +
                                "|width=400px" +
                                "|bars=" +
                                "{{bar percent|" + facetLabels[f][0] + "|#b32424|" + (tmp0/(tmp0+tmp1)*100).toString() + "|" + tmp0.toString() + " (" + Math.floor(tmp0/(tmp0+tmp1)*100).toString() + "%) }}" +
                                "{{bar percent|" + facetLabels[f][1] + "|#14866d|" + (tmp1/(tmp0+tmp1)*100).toString() + "|" + tmp1.toString() + " (" + Math.floor(tmp1/(tmp0+tmp1)*100).toString() + "%) }}" +
                                // "|caption=Some stuff displayed by quantity." +
                                "}}",
                            contentmodel: "wikitext"
                        }).done(function(ret) {
                            $("#wikibench-data-curation-charts").append(ret.parse.text["*"]);
                        });
                    }
                });
            });
        }
    });
})(jQuery, mediaWiki);

// Entity Page

(function ($, mw) {
    $(document).ready(function() {
        // init
        var wikibenchURL = "User:Tzusheng/sandbox/Wikipedia:Wikibench";
        var wikibenchTalkURL = "User_talk:Tzusheng/sandbox/Wikipedia:Wikibench"
        var campaignURL = wikibenchURL + "/Campaign:Editquality";
        var campaignTalkURL = wikibenchTalkURL + "/Campaign:Editquality";
        var entityType = "diff";
        var entityPagePrefix = wikibenchURL + "/Entity:" + entityType.charAt(0).toUpperCase() + entityType.slice(1) + "/";
        var entityPagePrefixArchive = wikibenchURL + "/Archive/Entity:" + entityType.charAt(0).toUpperCase() + entityType.slice(1) + "/";
        var entityPageHeader = "{{Warning |heading=Script installation is required for reading and editing |This page is part of the Wikibench project on the English Wikipedia. Please read the [[" + campaignURL + "|project page]] and install the script to see this page correctly rendered. Do not edit the source without installing the script.}}";
        var entityPageSplit = "-----";
        var language = "en";
        var facets = ["editDamage", "userIntent"];
        var facetNames = {
            editDamage: "edit damage",
            userIntent: "user intent"
        };
        var facetLabels = {
            editDamage: ["damaging", "not damaging"],
            userIntent: ["bad faith", "good faith"]
        };
        var facetIcons = {
            editDamage: ["alert", "success"],
            userIntent: ["alert", "success"]
        };
        var facetColors = {
            editDamage: ["#b32424", "#14866d"],
            userIntent: ["#b32424", "#14866d"]
        };

        // get config
        var wgPageName = mw.config.get("wgPageName");

        if((wgPageName.startsWith(entityPagePrefix) || (wgPageName.startsWith(entityPagePrefixArchive))) && mw.config.get("wgAction") === "view") {

            // widgets
            var divRender = $(".mw-parser-output");
            var diffTableHeader = "<table class=\"diff diff-contentalign-left diff-editfont-monospace\" data-mw=\"interface\"><colgroup><col class=\"diff-marker\"><col class=\"diff-content\"><col class=\"diff-marker\"><col class=\"diff-content\"></colgroup><tbody>";
            var diffTableFooter = "</tbody></table>";
            var diffTableContent, diffTableTitle;
            var windowManager;
            var noticeBox;
            var revdelBox;
            var newPageBox;
            var primaryFieldset;
            var userFieldset;
            var userLabel = {};
            var userLowConfidences = {};
            var userNote = {};
            var individualFieldset = {};
            var stackBars = {};
            var stackBarText = {};
            var lowConfidenceHtmlSnippet = "<font color=\"#72777d\">(low confidence)</font>"

            var mwApi = new mw.Api();
            var entityId = wgPageName.substring(entityPagePrefix.length);
            var userName = mw.config.get("wgUserName");
            var userId = mw.config.get("wgUserId");

            mwApi.get({
                action: "parse",
                page: wgPageName,
                prop: "wikitext"
            }).done(function(ret) {
                mw.loader.using(["oojs-ui-core", "oojs-ui-widgets", "oojs-ui-windows", "mediawiki.diff.styles"]).done(function(){
                    var label = JSON.parse(ret.parse.wikitext["*"].split(entityPageSplit)[1]);

                    /* ===== WINDOR MANAGER ===== */
                    windowManager = new OO.ui.WindowManager();
                    $(document.body).append(windowManager.$element);

                    noticeBox = new OO.ui.MessageWidget({
                        type: "notice",
                        label: new OO.ui.HtmlSnippet("Please do not directly edit the source of this page. To update the primary or your label, click the edit buttons below. To discuss, visit the talk page. To view the labeling progress of the campaign, visit the <a href=\"/wiki/" + campaignURL + "\">campaign page</a>.")
                    });

                    revdelBox = new OO.ui.MessageWidget({
                        type: "warning",
                        label: new OO.ui.HtmlSnippet("This diff has been <a href=\"/wiki/Wikipedia:Revision_deletion\">revdel\'d</a>. Please visit the <a href=\"/wiki/" + campaignTalkURL + "\">campaign talk page</a> for an ongoing discussion about handling revdel\'d diffs.")
                    });

                    newPageBox = new OO.ui.MessageWidget({
                        type: "warning",
                        label: new OO.ui.HtmlSnippet("This diff is a new page creation. Please visit the <a href=\"/wiki/" + campaignTalkURL + "\">campaign talk page</a> for an ongoing discussion about handling new pages.")
                    });
                    
                    /* ===== PRIMARY LABEL ===== */
                    
                    primaryFieldset = new OO.ui.FieldsetLayout({ 
                        label: "Primary label",
                        classes: ["wikibench-entity-primary-label"],
                    });

                    var labelColor = {};
                    for (var i = 0; i < facets.length; i++) {
                        labelColor[facets[i]] = {};
                        for (var j = 0; j < facetLabels[facets[i]].length; j++) {
                            labelColor[facets[i]][facetLabels[facets[i]][j]] = facetColors[facets[i]][j];
                        }
                    }

                    for (var i = 0; i < facets.length; i++) {
                        var f = facets[i];
                        var facetPrimaryLabel = new OO.ui.LabelWidget({
                            label: new OO.ui.HtmlSnippet("<font color=\"" + labelColor[f][label.facets[f].primaryLabel.label] + "\">" + label.facets[f].primaryLabel.label + "</font>")
                        });
                        primaryFieldset.addItems(
                            new OO.ui.FieldLayout(facetPrimaryLabel, {
                                label: facetNames[f].charAt(0).toUpperCase() + facetNames[f].slice(1),
                                align: "left"
                            })
                        )
                    }

                    primaryFieldset.addItems([
                        new OO.ui.FieldLayout(
                            new OO.ui.LabelWidget({
                                label: $("<a>")
                                    .attr("href","/wiki/User:"+label.facets[facets[0]].primaryLabel.lastModifier)
                                    .text(label.facets[facets[0]].primaryLabel.lastModifier)
                            }), {
                                label: "Last modifier",
                                align: "left"
                            }
                        ),
                        new OO.ui.FieldLayout(
                            new OO.ui.LabelWidget({
                                label: label.facets[facets[0]].primaryLabel.touched
                            }), {
                                label: "Last modified time",
                                align: "left"
                            }
                        )
                    ]);

                    var editPrimaryBtn = new OO.ui.ButtonWidget({
                        label: "Edit",
                        disabled: true // Change to false when doing demo
                    });

                    // Make a subclass of ProcessDialog for editing the primary label
                    function EditPrimaryDialog( config ) {
                        EditPrimaryDialog.super.call( this, config );
                    }
                    OO.inheritClass( EditPrimaryDialog, OO.ui.ProcessDialog );

                    // Specify a name for .addWindows()
                    EditPrimaryDialog.static.name = "editPrimaryDialog";
                    EditPrimaryDialog.static.title = "Edit primary label";
                    EditPrimaryDialog.static.actions = [
                        { 
                            flags: [ "primary", "progressive" ], 
                            label: "Publish changes", 
                            action: "publish" 
                        },
                        { 
                            flags: "safe", 
                            label: "Cancel" 
                        }
                    ];

                    // Customize the initialize() function to add content and layouts: 
                    EditPrimaryDialog.prototype.initialize = function () {
                        EditPrimaryDialog.super.prototype.initialize.call( this );
                        this.panel = new OO.ui.PanelLayout( { 
                            padded: true, 
                            expanded: false 
                        } );
                        this.content = new OO.ui.FieldsetLayout();
                        
                        this.primaryLabelMessage = new OO.ui.MessageWidget({
                            type: "warning",
                            label: new OO.ui.HtmlSnippet("When editing primary labels, <a href=\"/wiki/Wikipedia:Be_bold\">be bold</a> yet respectful of others' views. Wikibench will notify the last modifier on the <a href=\"/wiki/" + wikibenchTalkURL + "/Entity:" + entityType.charAt(0).toUpperCase() + entityType.slice(1) + "/" + entityId + "\">talk page</a> with <a href=\"/wiki/Wikipedia:Signatures#Using_four_tildes\">your signature</a> after publishing changes.")
                        });

                        this.content.addItems([this.primaryLabelMessage]);

                        this.primaryFacetBtns = {};
                        for (var i = 0 ; i < facets.length; i++) {
                            this.primaryFacetBtns[facets[i]] = new OO.ui.ButtonSelectWidget({
                                items: [
                                    new OO.ui.ButtonOptionWidget({
                                        data: facetLabels[facets[i]][0],
                                        label: facetLabels[facets[i]][0],
                                        icon: facetIcons[facets[i]][0]
                                    }),
                                    new OO.ui.ButtonOptionWidget({
                                        data: facetLabels[facets[i]][1],
                                        label: facetLabels[facets[i]][1],
                                        icon: facetIcons[facets[i]][1]
                                    })
                                ]
                            });
                            this.primaryFacetBtns[facets[i]].selectItemByLabel(label.facets[facets[i]].primaryLabel.label);
                            this.content.addItems([
                                new OO.ui.FieldLayout(this.primaryFacetBtns[facets[i]], {
                                    label: facetNames[facets[i]].charAt(0).toUpperCase() + facetNames[facets[i]].slice(1),
                                    align: "left"
                                })
                            ]);
                        }

                        this.primaryLabelSummary = new OO.ui.TextInputWidget({
                            placeholder: "Briefly describe your changes"
                        });

                        this.content.addItems([
                            new OO.ui.FieldLayout(this.primaryLabelSummary, {
                                label: "Edit summary",
                                align: "top"
                            })
                        ]);

                        this.panel.$element.append( this.content.$element );
                        this.$body.append( this.panel.$element );
                    };

                    EditPrimaryDialog.prototype.getBodyHeight = function () {
                        return this.panel.$element.outerHeight( true );
                    };

                    // Specify processes to handle the actions.
                    EditPrimaryDialog.prototype.getActionProcess = function ( action ) {
                        if ( action === "publish" ) {
                            // Create a new process to handle the action
                            return new OO.ui.Process( function () {
                                var primaryLabels = {}
                                var summary = this.primaryLabelSummary.getValue();
                                for (var i = 0; i < facets.length; i++) {
                                    primaryLabels[facets[i]] = this.primaryFacetBtns[facets[i]].findSelectedItem().getData();
                                }
                                mwApi.get({
                                    action: "query",
                                    prop: "revisions",
                                    rvprop: "content",
                                    titles: wgPageName,
                                    format: "json"
                                }).done(function(ret) {
                                    var revisions = ret.query.pages;
                                    var pageId = Object.keys(revisions)[0];
                                    var submitContent = JSON.parse(revisions[pageId].revisions[0]["*"].split(entityPageSplit)[1]);
                                    var lastModifier = submitContent.facets[facets[0]].primaryLabel.lastModifier;
                                    for (var i = 0; i < facets.length; i++) {
                                        submitContent.facets[facets[i]].primaryLabel.lastModifier = userName;
                                        submitContent.facets[facets[i]].primaryLabel.lastModifierId = userId;
                                        submitContent.facets[facets[i]].primaryLabel.label = primaryLabels[facets[i]];
                                        submitContent.facets[facets[i]].primaryLabel.touched = new Date(new Date().getTime()).toUTCString();
                                        submitContent.facets[facets[i]].primaryLabel.autolabeled = false;
                                    }
                                    mwApi.postWithToken("csrf",{
                                        action: "edit",
                                        title: wgPageName,
                                        section: 0,
                                        text: entityPageHeader + "\n" + entityPageSplit + "\n" + JSON.stringify(submitContent),
                                        summary: "Primary label change from the Wikibench entity page: " + summary,
                                    }).done(function(result,jqXHR) {
                                        // notify the previous labeler on the talk page
                                        mwApi.postWithToken("csrf", {
                                            action: "edit",
                                            title: wikibenchTalkURL + "/Entity:" + entityType.charAt(0).toUpperCase() + entityType.slice(1) + "/" + entityId,
                                            section: "new",
                                            sectiontitle: "The primary label has been edited",
                                            text: "{{Ping|" + lastModifier + "}} [[User:" + userName + "|" + userName + "]] edited the primary label that you previously submitted. If you disagree with the change, please kindly engage in a discussion on this talk page and consider seeking a third opinion if needed. ~~~~"
                                        }).done(function(result,jqXHR) {
                                            location.reload();
                                        });
                                    });
                                });
                            }, this );
                        }
                        // Fallback to parent handler
                        return EditPrimaryDialog.super.prototype.getActionProcess.call( this, action );
                    };


                    // Create a new process dialog window.
                    var editPrimaryDialog = new EditPrimaryDialog();

                    // Add the window to window manager using the addWindows() method.
                    windowManager.addWindows( [ editPrimaryDialog ] );

                    editPrimaryBtn.on("click", function() {
                        windowManager.openWindow( editPrimaryDialog );
                    });

                    var discussPrimaryBtn = new OO.ui.ButtonWidget({
                        label: "Talk"
                    });

                    discussPrimaryBtn.on("click", function() {
                        window.open("/wiki/" + wikibenchTalkURL + "/Entity:" + entityType.charAt(0).toUpperCase() + entityType.slice(1) + "/" + entityId, "_self");
                    })

                    // primaryFieldset.addItems(
                    //     new OO.ui.FieldLayout(editPrimaryBtn, {
                    //         align: "left"
                    //     })
                    // );
                    primaryFieldset.addItems(
                        new OO.ui.FieldLayout(new OO.ui.Widget({
                            content: [
                                new OO.ui.HorizontalLayout({
                                    items: [
                                        editPrimaryBtn,
                                        discussPrimaryBtn
                                    ]
                                })
                            ]
                        }), {
                            align: "left"
                        })
                    );

                    /* ===== USER LABEL ===== */
                    userFieldset = new OO.ui.FieldsetLayout({ 
                        label: "Your label (" + userName + ")",
                        classes: ["wikibench-entity-user-label"]
                    });

                    for (var i = 0; i < facets.length; i++) {
                        var f = facets[i];
                        userLabel[f] = undefined;
                        userNote[f] = "";
                        var displayLabelName = "undefined";
                        label.facets[f].individualLabels.forEach(function(l) {
                            if (userName === l.userName) {
                                userLabel[f] = l.label;
                                userLowConfidences[f] = l.lowConfidence;
                                userNote[f] = l.note;
                                displayLabelName = "<font color=\"" + labelColor[f][l.label] + "\">" + l.label + "</font>";
                                if (l.lowConfidence) {
                                    displayLabelName = displayLabelName + " " + lowConfidenceHtmlSnippet;
                                }
                            }
                        });
                        userFieldset.addItems(
                            new OO.ui.FieldLayout(
                                new OO.ui.LabelWidget({
                                    label: new OO.ui.HtmlSnippet(displayLabelName)
                                }), {
                                    label: facetNames[f].charAt(0).toUpperCase() + facetNames[f].slice(1),
                                    align: "left" 
                            })
                        );
                    }

                    var editUserBtn = new OO.ui.ButtonWidget({
                        label: "Edit",
                        disabled: true // Change to false when doing demo
                    });

                    // Make a subclass of Process Dialog for editing the user label
                    function EditUserLabelDialog(config) {
                        EditUserLabelDialog.super.call(this, config);
                    }
                    OO.inheritClass(EditUserLabelDialog, OO.ui.ProcessDialog);

                    EditUserLabelDialog.static.name = "editUserLabelDialog";
                    EditUserLabelDialog.static.title = "Edit your label";
                    EditUserLabelDialog.static.actions = [
                        {
                            flags: ["primary", "progressive"],
                            label: "Publish changes",
                            action: "publish"
                        },
                        {
                            flags: "safe",
                            label: "Cancel"
                        }
                    ];

                    EditUserLabelDialog.prototype.initialize = function() {
                        EditUserLabelDialog.super.prototype.initialize.call(this);
                        this.panel = new OO.ui.PanelLayout({
                            padded: true,
                            expanded: false
                        });
                        this.content = new OO.ui.FieldsetLayout();
                        this.userFacetBtns = {};
                        this.userFacetLowConfidenceCheckboxes = {};
                        this.userFacetNoteInputs = {};
                        for (var i = 0; i < facets.length; i++) {
                            var f = facets[i];
                            this.userFacetBtns[f] = new OO.ui.ButtonSelectWidget({
                                items: [
                                    new OO.ui.ButtonOptionWidget({
                                        data: facetLabels[f][0],
                                        label: facetLabels[f][0],
                                        icon: facetIcons[f][0]
                                    }),
                                    new OO.ui.ButtonOptionWidget({
                                        data: facetLabels[f][1],
                                        label: facetLabels[f][1],
                                        icon: facetIcons[f][1]
                                    })
                                ]
                            });
                            if (userLabel[f] !== undefined) {
                                this.userFacetBtns[f].selectItemByLabel(userLabel[f]);
                            }
                            this.userFacetLowConfidenceCheckboxes[f] = new OO.ui.CheckboxInputWidget();
                            if (userLowConfidences[f] === true) {
                                this.userFacetLowConfidenceCheckboxes[f].setSelected(true);
                            }
                            this.userFacetNoteInputs[f] = new OO.ui.TextInputWidget({
                                value: userNote[f]
                            })
                            this.content.addItems([
                                new OO.ui.FieldLayout(this.userFacetBtns[f], {
                                    label: facetNames[f].charAt(0).toUpperCase() + facetNames[f].slice(1),
                                    align: "left"
                                }),
                                new OO.ui.FieldLayout(new OO.ui.Widget({
                                    content: [
                                        new OO.ui.HorizontalLayout({
                                            items: [
                                                this.userFacetLowConfidenceCheckboxes[f],
                                                new OO.ui.LabelWidget({label: "low confidence"})
                                            ]
                                        })
                                    ]
                                }), {
                                    label: " ",
                                    align: "left"
                                }),
                                new OO.ui.FieldLayout(this.userFacetNoteInputs[f], {
                                    label: "Note for " + facetNames[f],
                                    align: "left"
                                })
                            ])
                        }

                        this.panel.$element.append(this.content.$element);
                        this.$body.append(this.panel.$element);
                    }

                    EditUserLabelDialog.prototype.getBodyHeight = function() {
                        return this.panel.$element.outerHeight(true);
                    }

                    EditUserLabelDialog.prototype.getActionProcess = function(action) {
                        if ( action === "publish" ) {
                            // Create a new process to handle the action
                            return new OO.ui.Process( function () {
                                var tmpUserLabel = {};
                                var tmpUserLowConfidences = {};
                                var tmpUserNote = {};
                                for (var i = 0; i < facets.length; i++) {
                                    tmpUserLabel[facets[i]] = this.userFacetBtns[facets[i]].findSelectedItem().getData();
                                    tmpUserLowConfidences[facets[i]] = this.userFacetLowConfidenceCheckboxes[facets[i]].isSelected();
                                    tmpUserNote[facets[i]] = this.userFacetNoteInputs[facets[i]].getValue();
                                }
                                mwApi.get({
                                    action: "query",
                                    prop: "revisions",
                                    rvprop: "content",
                                    titles: wgPageName,
                                    format: "json"
                                }).done(function(ret) {
                                    var revisions = ret.query.pages;
                                    var pageId = Object.keys(revisions)[0];
                                    var submitContent = JSON.parse(revisions[pageId].revisions[0]["*"].split(entityPageSplit)[1]);
                                    for (var i = 0; i < facets.length; i++) {
                                        var isUserLabelExist = false;
                                        var f = facets[i];
                                        var submitLabel = {
                                            "userName": userName,
                                            "userId": userId,
                                            "label": tmpUserLabel[f],
                                            "note": tmpUserNote[f],
                                            "origin": "wikibench-enwiki-entity-page",
                                            "created": new Date(new Date().getTime()).toUTCString(),
                                            "touched": new Date(new Date().getTime()).toUTCString(),
                                            "lowConfidence": tmpUserLowConfidences[f],
                                            "category": []
                                        }
                                        for (var j = 0; j < submitContent.facets[f].individualLabels.length; j++) {
                                            if (submitContent.facets[f].individualLabels[j].userName === userName) {
                                                submitLabel.created = submitContent.facets[f].individualLabels[j].created;
                                                submitContent.facets[f].individualLabels[j] = submitLabel;
                                                isUserLabelExist = true;
                                                break;
                                            }
                                        }
                                        if (!isUserLabelExist) {
                                            submitContent.facets[f].individualLabels.push(submitLabel);
                                        }
                                    }
                                    mwApi.postWithToken("csrf",{
                                        action: "edit",
                                        title: wgPageName,
                                        section: 0,
                                        text: entityPageHeader + "\n" + entityPageSplit + "\n" + JSON.stringify(submitContent),
                                        summary: "Individual label edit from the Wikibench entity page",
                                    }).done(function(result,jqXHR) {
                                        location.reload();
                                    })
                                });;
                            }, this );
                        }
                        // Fallback to parent handler
                        return EditUserLabelDialog.super.prototype.getActionProcess.call( this, action );
                    }
                    var editUserLabelDialog = new EditUserLabelDialog();
                    windowManager.addWindows([editUserLabelDialog]);
                    editUserBtn.on("click", function() {
                        windowManager.openWindow(editUserLabelDialog);
                    });

                    userFieldset.addItems([
                        new OO.ui.FieldLayout(editUserBtn, {
                            align: "left"
                        })
                    ]);

                    divRender.find("table").remove(); // remove warning message
                    divRender.find("hr").remove(); // remove horizontal line
                    divRender.find("p").remove(); // remove json content

                    divRender
                        .append(noticeBox.$element)
                        .append("<h2>Difference between revisions</h2>")
                        .append("<div id=\"wikibench-entity-page-diff-table\"></div>")
                        .append("<h2>Primary and your labels</h2>")
                        .append(primaryFieldset.$element)
                        .append(userFieldset.$element);


                    /* ===== INDIVIDUAL LABEL ===== */
                    for (var i = 0; i < facets.length; i++) {
                        var f = facets[i];
                        individualFieldset[f] = {};
                        var labelCount = {};
                        var labelLowConfidenceCount = {};
                        for (var j = 0; j < facetLabels[f].length; j++) {
                            var l = facetLabels[f][j];
                            labelCount[l] = 0;
                            labelLowConfidenceCount[l] = 0;
                            individualFieldset[f][l] = new OO.ui.FieldsetLayout({
                                icon: "expand",
                                classes: ["wikibench-entity-individual-labels"]
                            });
                            label.facets[f].individualLabels.forEach(function(individualLabel) {
                                if (individualLabel.label === l) {
                                    var labelText = "<a href=\"/wiki/User:" + individualLabel.userName + "\">" + individualLabel.userName + "</a>";
                                    labelCount[l]++;
                                    if (individualLabel.lowConfidence) {
                                        labelLowConfidenceCount[l]++;
                                        labelText = labelText + " " + lowConfidenceHtmlSnippet;
                                    }
                                    individualFieldset[f][l].addItems(
                                        new OO.ui.FieldLayout(
                                            new OO.ui.LabelWidget({label: individualLabel.note}),
                                            {label: new OO.ui.HtmlSnippet(labelText)}
                                        )
                                    );
                                }
                            });
                            individualFieldset[f][l].setLabel(l + " (" + labelCount[l].toString() + ")");
                            individualFieldset[f][l].$group.toggle(false);
                        }

                        // stack bars (assume binary labels)
                        var stackBarCounts = [
                            labelCount[facetLabels[f][0]] - labelLowConfidenceCount[facetLabels[f][0]],
                            labelLowConfidenceCount[facetLabels[f][0]],
                            labelLowConfidenceCount[facetLabels[f][1]],
                            labelCount[facetLabels[f][1]] - labelLowConfidenceCount[facetLabels[f][1]]
                        ];
                        var stackBarTotal = 0;
                        var stackBarNames = [
                            facetLabels[f][0],
                            facetLabels[f][0] + " (low confidence)",
                            facetLabels[f][1] + " (low confidence)",
                            facetLabels[f][1]
                        ];
                        var stackBarColors = ["#b32424", "#fee7e6", "#d5fdf4", "#14866d"]
                        stackBarText[f] = "{{Stacked bar|height=18px|";
                        for (var k = 0; k < 4; k++) {
                            if (stackBarCounts[k] > 0) {
                                var tmp = (k+1).toString();
                                stackBarText[f] += "A" + tmp + "=" + stackBarCounts[k].toString() + "|C" + tmp + "=" + stackBarColors[k] + "|T" + tmp + "=" + stackBarNames[k] + "|";
                            }
                            stackBarTotal += stackBarCounts[k];
                        }
                        stackBarText[f] += "Total=" + (stackBarTotal).toString() + "}}";

                        divRender
                                .append("<h2>" + facetNames[f].charAt(0).toUpperCase() + facetNames[f].slice(1) + " labels</h2>")
                                .append("<h3>Label distribution</h3>")
                                .append("<div id=\"Wikibench-StackBar-" + f + "\"></div>")
                                .append("<h3>Individual labels</h3>");
                        for (var j = 0; j < facetLabels[f].length; j++) {
                            var l = facetLabels[f][j];
                            divRender.append(individualFieldset[f][l].$element);
                            individualFieldset[f][l].$element.find(".oo-ui-fieldsetLayout-header").click(function(){
                                var header = $(this);
                                var content = header.next();
                                content.slideToggle(function () {
                                    header.children(".oo-ui-iconElement-icon").toggleClass("oo-ui-icon-collapse").toggleClass("oo-ui-icon-expand");
                                });
                            })
                        }
                    }

                    // get and append the stackbars outside the for loop to bypass the await time
                    // p.s. await is not available in ES5
                    facets.forEach(function(f) {
                        mwApi.get({
                            action: "parse",
                            text: stackBarText[f],
                            contentmodel: "wikitext"
                        }).done(function(ret) {
                            $("#Wikibench-StackBar-"+f).append(ret.parse.text["*"]);
                        });
                    });

                    // diff table
                    mwApi.get({
                        action: "compare",
                        fromrev: parseInt(entityId.split("/")[0]),
                        torev: parseInt(entityId.split("/")[1]),
                    }).done(function(ret) {
                        diffTableContent = ret.compare["*"];
                        diffTableTitle = 
                        "<tr class=\"diff-title\" lang=\"" + language + "\">" +
                            "<td colspan=\"2\" class=\"diff-otitle diff-side-deleted\">" +
                                "<div id=\"mw-diff-otitle1\">" + 
                                    "<strong><a href=\"/wiki/Special:Permalink/" + ret.compare.fromrevid + "\">Revision before edit</a></strong>" +
                                "</div>" +
                                "<div id=\"mw-diff-otitle3\"> <span class=\"comment\">Revision ID: " + ret.compare.fromrevid + "</span></div>" +
                                "<div id=\"mw-diff-otitle5\"></div>" +
                            "</td>" +
                            "<td colspan=\"2\" class=\"diff-ntitle diff-side-added\">" +
                                "<div id=\"mw-diff-ntitle1\">" +
                                    "<strong><a href=\"/wiki/Special:Permalink/" + ret.compare.torevid + "\">Revision after edit</a></strong>" +
                                    " (<a href=\"/wiki/Special:Diff/" + ret.compare.fromrevid + "/" + ret.compare.torevid + "\">diff page</a>)" +
                                "</div>" +
                                "<div id=\"mw-diff-ntitle3\"> <span class=\"comment\">Revision ID: " + ret.compare.torevid + "</span></div>" +
                                // "<div id=\"mw-diff-ntitle5\"></div>" +
                            "</td>" +
                        "</tr>" +
                        "<tr><td colspan=\"4\" class=\"diff-multi\" lang=\"" + language + "\">" + label.entityNote + "</td></tr>";
                        divRender.find("#wikibench-entity-page-diff-table").append(diffTableHeader + diffTableTitle + diffTableContent + diffTableFooter);
                    }).fail(function(e) {
                        if (entityId.split("/")[0] === "false") {
                            divRender.find("#wikibench-entity-page-diff-table").append(newPageBox.$element);
                        }
                        else {
                            divRender.find("#wikibench-entity-page-diff-table").append(revdelBox.$element);
                        }
                    });
                });
            });
        }
    });
})(jQuery, mediaWiki);

// Plug-In
/*
(function ($, mw) {
    $(document).ready(function() {
        if(mw.config.get("wgDiffNewId") !== null) {

            // init
            var wikibenchURL = "User:Tzusheng/sandbox/Wikipedia:Wikibench";
            var campaignURL = "User:Tzusheng/sandbox/Wikipedia:Wikibench/Campaign:Editquality";
            var entityType = "diff";
            var entityPageSplit = "-----";
            var facets = ["editDamage", "userIntent"];
            var facetNames = {
                editDamage: "edit damage",
                userIntent: "user intent"
            };
            var facetHelp = {
                editDamage: "Please check the <a href=\"/wiki/" + campaignURL + "#Label_definitions\">campaign page</a> for the definition of edit damage. The optional checkbox on the right lets you specify that you provide the label with lower confidence when you're not so sure.",
                userIntent: "Please check the <a href=\"/wiki/" + campaignURL + "#Label_definitions\">campaign page</a> for the definition of user intent. The optional checkbox on the right lets you specify that you provide the label with lower confidence when you're not so sure."
            };
            var facetLabels = {
                editDamage: ["damaging", "not damaging"],
                userIntent: ["bad faith", "good faith"]
            };
            var facetIcons = {
                editDamage: ["alert", "success"],
                userIntent: ["alert", "success"]
            }
            var successMessage = "Your submission has been recorded";
            var warningMessage = "Your submission has been recorded but is different from the primary label";
            
            // get config
            var mwApi = new mw.Api();
            var diffNewId = mw.config.get("wgDiffNewId");
            var diffOldId = mw.config.get("wgDiffOldId");
            var userName = mw.config.get("wgUserName");
            var userId = mw.config.get("wgUserId");
            var revisionId; // revision ID of the entity page, not the diff page
            var entityPageTitle = wikibenchURL + "/Entity:" + entityType.charAt(0).toUpperCase() + entityType.slice(1) + "/" + diffOldId.toString() + "/" + diffNewId.toString();
            var entityPageHeader = "{{Warning |heading=Script installation is required for reading and editing |This page is part of the Wikibench project on the English Wikipedia. Please read the [[" + campaignURL + "|project page]] and install the script to see this page correctly rendered. Do not edit the source without installing the script.}}";
            var routingMessage = "You are welcome to review other Wikipedians' labels on the <a href=\"/wiki/" + entityPageTitle + "\">entity page of this " + entityType + "</a> or close this message for resubmission.";

            // labels
            var primaryLabels = {};
            var autolabeled = {};
            var userLabels = {};
            var userLowConfidences = {};
            var userNotes = {};

            // widgets
            var facetBtns = {};
            var facetLowConfidenceCheckboxes = {};
            var facetWidgets = {};
            var facetNoteInputs = {}; // assume notes might be different
            var submitBtn;
            var submitMessage;

            var getEntityPage = mwApi.get({ // get the entity page. if it doesn't exist, return revisionId = "-1"
                action: "query",
                prop: "revisions",
                rvprop: "content",
                titles: entityPageTitle,
                format: "json"
            });

            var parseEntityPage = getEntityPage.then(function(ret) {
                revisionId = Object.keys(ret.query.pages)[0];
                if (revisionId !== "-1") { // the entity page already exists                    
                    var revisions = ret.query.pages;
                    var pageId = Object.keys(revisions)[0];
                    var entityPageContent = JSON.parse(revisions[pageId].revisions[0]["*"].split(entityPageSplit)[1]);
                    facets.forEach(function(f) {
                        primaryLabels[f] = entityPageContent.facets[f].primaryLabel.label;
                        autolabeled[f] = entityPageContent.facets[f].primaryLabel.autolabeled;
                        entityPageContent.facets[f].individualLabels.forEach(function(l) {
                            if (userName === l.userName) {
                                userLabels[f] = l.label;
                                userLowConfidences[f] = l.lowConfidence;
                                userNotes[f] = l.note;
                            }
                        });
                    });
                }
            });

            var renderPlugIn = parseEntityPage.then(function() {
                mw.loader.using(["oojs-ui-core", "oojs-ui-widgets", "oojs-ui-windows"]).done(function(){
                    facets.forEach(function(f) {
                        facetBtns[f] = new OO.ui.ButtonSelectWidget({
                            items: [
                                new OO.ui.ButtonOptionWidget({
                                    data: facetLabels[f][0],
                                    label: facetLabels[f][0],
                                    icon: facetIcons[f][0]
                                }),
                                new OO.ui.ButtonOptionWidget({
                                    data: facetLabels[f][1],
                                    label: facetLabels[f][1],
                                    icon: facetIcons[f][1]
                                })
                            ]
                        });
                        
                        if (!$.isEmptyObject(userLabels)) {
                            facetBtns[f].selectItemByLabel(userLabels[f]);
                        }

                        facetBtns[f].on("choose", function(item){
                            userLabels[f] = item.getData();
                        });

                        facetLowConfidenceCheckboxes[f] = new OO.ui.CheckboxInputWidget();

                        if (!$.isEmptyObject(userLowConfidences[f])) {
                            facetLowConfidenceCheckboxes[f].setSelected(userLowConfidences[f]);
                        }

                        facetWidgets[f] = new OO.ui.Widget({
                            content: [
                                new OO.ui.HorizontalLayout({
                                    items: [
                                        facetBtns[f],
                                        facetLowConfidenceCheckboxes[f],
                                        new OO.ui.LabelWidget({label: "low confidence"})
                                    ]
                                })
                            ]
                        });

                        facetNoteInputs[f] = new OO.ui.TextInputWidget({});

                        if (!$.isEmptyObject(userNotes)){
                            facetNoteInputs[f].setValue(userNotes[f]);
                        }
                    });

                    submitBtn = new OO.ui.ButtonWidget({
                        label: "Submit",
                        flags: ["primary", "progressive"]
                    });

                    submitMessage = new OO.ui.MessageWidget({
                        showClose: true
                    });

                    submitMessage.toggle(false);

                    var fieldset = new OO.ui.FieldsetLayout({ 
                        label: new OO.ui.HtmlSnippet("Wikibench Plug-In"),
                        id: "wikibench-diff-plugin",
                        help: new OO.ui.HtmlSnippet("<a href=\"/wiki/" + campaignURL + "\">Editquality Campaign</a>"),
                        helpInline: true
                    });

                    // if user label already exists, compare it with the primary label and update submitMessage accordingly
                    if (!$.isEmptyObject(userLabels)) {
                        var isSameAsPrimary = true;
                        if (!$.isEmptyObject(primaryLabels)) { // make sure the primary label exist
                            facets.forEach(function(f) {
                                if (userLabels[f] !== primaryLabels[f]) {
                                    isSameAsPrimary = false;
                                }
                            });
                        }
                        if (isSameAsPrimary) {
                            submitMessage.setType("success");
                            submitMessage.setLabel(new OO.ui.HtmlSnippet("<strong>" + successMessage + "</strong>" + "<br>" + routingMessage));
                        }
                        else {
                            var primaryLabelMessage = "("
                            for (var i = 0; i < facets.length; i++) {
                                primaryLabelMessage += primaryLabels[facets[i]];
                                primaryLabelMessage += ", ";
                            }
                            primaryLabelMessage = primaryLabelMessage.slice(0,-2) + ")";
                            submitMessage.setType("warning");
                            submitMessage.setLabel(new OO.ui.HtmlSnippet("<strong>" + warningMessage + " " + primaryLabelMessage + "</strong>" + "<br>" + routingMessage));
                        }
                        submitMessage.toggle(true);
                        submitBtn.setDisabled(true);
                        submitBtn.toggle(false);
                    }

                    // add all the widgets to feildset
                    fieldset.addItems(
                        new OO.ui.FieldLayout(
                            new OO.ui.LabelWidget({label: diffOldId.toString() + "/" + diffNewId.toString()}), {
                            label: "Diff IDs",
                            align: "left"
                        })
                    );

                    for (var i = 0; i < facets.length; i++) {
                        fieldset.addItems(
                            new OO.ui.FieldLayout(facetWidgets[facets[i]], {
                                label: facetNames[facets[i]].charAt(0).toUpperCase() + facetNames[facets[i]].slice(1),
                                align: "left",
                                help: new OO.ui.HtmlSnippet(facetHelp[facets[i]])
                            })
                        );
                    }

                    for (var i = 0; i < facets.length; i++) {
                        fieldset.addItems(
                            new OO.ui.FieldLayout(facetNoteInputs[facets[i]], {
                                label: "Note for " + facetNames[facets[i]].toLowerCase(),
                                align: "left",
                                help: "An optional note that explains the labels you provide and, in case of low confidence, why so."
                            })
                        );
                    }

                    fieldset.addItems([
                        new OO.ui.FieldLayout(submitBtn, {}),
                        new OO.ui.FieldLayout(submitMessage, {})
                    ]);

                    //$("#mw-oldid").after(fieldset.$element);
                    //$(".mw-revslider-container").after(fieldset.$element);
                    $("#contentSub").after(fieldset.$element);

                    submitBtn.on("click", function() {

                        // labels and user login are required for submission
                        var isLabelUndefined = false;
                        facets.forEach(function(f) {
                            if (userLabels[f] === undefined) {
                                isLabelUndefined = true;
                            }
                        });
                        if (isLabelUndefined) { 
                            OO.ui.alert("Labels are required for submission.");
                        }
                        else if (userName === null){
                            OO.ui.alert("User login is required for submission.");
                        }
                        else {

                            // TODO: consider autolabel here

                            mwApi.get({ // get primary label again in case there are any changes from others
                                action: "query",
                                prop: "revisions",
                                rvprop: "content",
                                titles: entityPageTitle,
                                format: "json"
                            }).done(function(ret) {
                                var submitLabels = {};
                                var submitContent;
                                facets.forEach(function(f) {
                                    submitLabels[f] = {
                                        "userName": userName,
                                        "userId": userId,
                                        "label": userLabels[f],
                                        "note": facetNoteInputs[f].getValue(),
                                        "origin": "wikibench-enwiki-diff-plugin",
                                        "created": new Date(new Date().getTime()).toUTCString(),
                                        "touched": new Date(new Date().getTime()).toUTCString(),
                                        "lowConfidence": facetLowConfidenceCheckboxes[f].isSelected(),
                                        "category": []
                                    }
                                });
                                if (Object.keys(ret.query.pages)[0] === "-1") { // entity page doesn't exist
                                    submitContent = {
                                        "entityType": entityType,
                                        "entityId": diffOldId.toString() + "/" + diffNewId.toString(),
                                        "entityNote": $("td.diff-multi").text(),
                                        "facets": {}
                                    }
                                    facets.forEach(function(f) {
                                        submitContent.facets[f] = {};
                                        submitContent.facets[f]["primaryLabel"] = {
                                            "lastModifier": userName,
                                            "lastModifierId": userId,
                                            "label": userLabels[f],
                                            "touched": submitLabels[f].touched,
                                            "autolabeled": false
                                        };
                                        submitContent.facets[f]["individualLabels"] = [submitLabels[f]];  
                                    });
                                }
                                else { // entity page already exists
                                    var revisions = ret.query.pages;
                                    var pageId = Object.keys(revisions)[0];
                                    submitContent = JSON.parse(revisions[pageId].revisions[0]["*"].split(entityPageSplit)[1]);
                                    for (var i = 0; i < facets.length; i++) {
                                        var f = facets[i];
                                        var isUserLabelExist = false;
                                        primaryLabels[f] = submitContent.facets[f].primaryLabel.label; // get primary label again in case someone updates it in between
                                        for (var j = 0; j < submitContent.facets[f].individualLabels.length; j++) {
                                            if (submitContent.facets[f].individualLabels[j].userName === userName) {
                                                submitLabels[f].created = submitContent.facets[f].individualLabels[j].created;
                                                submitContent.facets[f].individualLabels[j] = submitLabels[f];
                                                isUserLabelExist = true;
                                                break;
                                            }
                                        }
                                        if (isUserLabelExist && (submitContent.facets[f].individualLabels.length === 1) && (submitContent.facets[f].primaryLabel.lastModifier === userName)) {
                                            // allow changing the primary label from the plug-in if the user is the only laber submitter
                                            submitContent.facets[f]["primaryLabel"] = {
                                                "lastModifier": userName,
                                                "lastModifierId": userId,
                                                "label": userLabels[f],
                                                "touched": submitLabels[f].touched,
                                                "autolabeled": false
                                            };
                                            primaryLabels[f] = submitContent.facets[f]["primaryLabel"].label; // update primary label for submitMessage
                                        }
                                        if (!isUserLabelExist) {
                                            submitContent.facets[f]["individualLabels"].push(submitLabels[f]);
                                        }
                                    }
                                }

                                mwApi.postWithToken("csrf",{
                                    action: "edit",
                                    title: entityPageTitle,
                                    section: 0,
                                    text: entityPageHeader + "\n" + entityPageSplit + "\n" + JSON.stringify(submitContent),
                                    summary: "Label submission from the Wikibench diff plug-in",
                                }).done(function(result,jqXHR){

                                    var isSameAsPrimary = true;
                                    if (!$.isEmptyObject(primaryLabels)) { // primary label already exist
                                        facets.forEach(function(f) {
                                            if (userLabels[f] !== primaryLabels[f]) {
                                                isSameAsPrimary = false;
                                            }
                                        });
                                    }

                                    if (isSameAsPrimary) {
                                        submitMessage.setType("success");
                                        submitMessage.setLabel(new OO.ui.HtmlSnippet("<strong>" + successMessage + "</strong>" + "<br>" + routingMessage));
                                    }
                                    else {
                                        var primaryLabelMessage = "("
                                        facets.forEach(function(f) {
                                            primaryLabelMessage += primaryLabels[f];
                                            primaryLabelMessage += ", ";
                                        });
                                        primaryLabelMessage = primaryLabelMessage.slice(0,-2) + ")";
                                        submitMessage.setType("warning");
                                        submitMessage.setLabel(new OO.ui.HtmlSnippet("<strong>" + warningMessage + " " + primaryLabelMessage + "</strong>" + "<br>" + routingMessage));
                                    }
                                    submitMessage.toggle(true);
                                    submitBtn.setDisabled(true);
                                    submitBtn.toggle(false);

                                }).fail(function(code,result){
                                    if ( code === "http" ) {
                                        mw.log( "HTTP error: " + result.textStatus ); // result.xhr contains the jqXHR object
                                    } else if ( code === "ok-but-empty" ) {
                                        mw.log( "Got an empty response from the server" );
                                    } else {
                                        mw.log( "API error: " + code );
                                    }
                                });
                            }); 
                        }
                    });

                    submitMessage.on("close", function() {
                        submitMessage.toggle(false);
                        submitBtn.setDisabled(false);
                        submitBtn.toggle(true);
                    });
                });
            });

            $.when(renderPlugIn).done(function(data) {
                //console.log("done");
            });
        }
    });
})(jQuery, mediaWiki);
*/

// Evaluation page

(function ($, mw) {
    $(document).ready(function() {
        var wgPageName = "User:Tzusheng/sandbox/Wikipedia:Wikibench/Evaluation:Editquality";
        if (mw.config.get("wgPageName") === wgPageName && mw.config.get("wgAction") === "view") {
            mw.loader.using(["oojs-ui-core", "oojs-ui-widgets", "oojs-ui-windows"]).done(function() {

                // init
                var WIKIBENCH_PREFIX = "Tzusheng/sandbox/Wikipedia:Wikibench";
                var WIKIBENCH_NAMESPACE = 2;
                var RATE_LIMIT = 10;
                var ROUND_PRECISION = 2;
                var entityType = "diff";
                var language = "en";
                var entityPageSplit = "-----";
                var facets = ["editDamage", "userIntent"];
                var facetNames = {
                    editDamage: "edit damage",
                    userIntent: "user intent"
                };
                var facetLabels = {
                    editDamage: ["damaging", "not damaging"],
                    userIntent: ["bad faith", "good faith"]
                };
                var facetColors = {
                    editDamage: ["#fee7e6", "#d5fdf4"],
                    userIntent: ["#fee7e6", "#d5fdf4"]
                };
                var mwApi = new mw.Api();

                var tableDiv = $("#enwiki-evaluation-editquality");
                var tbody = tableDiv.find("tbody");

                var progressBar = new OO.ui.ProgressBarWidget( {
                    progress: false
                });
                tableDiv.after(progressBar.$element);
                
                // var loadDataBtn = new OO.ui.ButtonWidget({
                //     label: "Load " + RATE_LIMIT.toString() + " more edits"
                // });

                var requestLimitMessage = new OO.ui.MessageWidget({
                    type: "error",
                    showClose: true,
                    label: new OO.ui.HtmlSnippet("The table isn't loading due to rate limits imposed by ORES and LiftWing. Please try again later in a few seconds by refreshing your browser.")
                });
                requestLimitMessage.toggle(false);
                requestLimitMessage.on("close", function() {
                    requestLimitMessage.toggle(false);
                    // loadDataBtn.toggle(true);
                });
                tableDiv.after(requestLimitMessage.$element);

                var tableLabelColors = {};
                for (var i = 0; i < facets.length; i++) {
                    tableLabelColors[facets[i]] = {};
                    for (var j = 0; j < facetLabels[facets[i]].length; j++) {
                        tableLabelColors[facets[i]][facetLabels[facets[i]][j]] = facetColors[facets[i]][j];
                    }
                }
                tableLabelColors["reverted"] = "#fee7e6";
                tableLabelColors["not reverted"] = "#d5fdf4";
                var probColors = "#72777d";

                var tableRows = {};
                // var revids = [];
                var promises = [];
                var liftwing = {};
                var ores = {};

                function calculateStandardDeviation(numbers) {
                    // Step 1: Calculate the mean
                    var sum = 0;
                    for (var i = 0; i < numbers.length; i++) {
                    sum += numbers[i];
                    }
                    var mean = sum / numbers.length;
                
                    // Step 2: Calculate the sum of squared differences
                    var squaredDifferencesSum = 0;
                    for (var j = 0; j < numbers.length; j++) {
                    var difference = numbers[j] - mean;
                    squaredDifferencesSum += difference * difference;
                    }
                
                    // Step 3: Calculate the variance
                    var variance = squaredDifferencesSum / numbers.length;
                
                    // Step 4: Calculate the standard deviation (square root of variance)
                    var standardDeviation = Math.sqrt(variance);
                
                    return standardDeviation;
                }

                function sortColumn(columnId, order) {
                    var header = tableDiv.find(columnId);
                    if (order === "ascending") {
                        if (header.attr("title") === "Sort ascending") { // note: title is not the current sorting state
                            header.click();
                        } else if (header.attr("title") === "Sort initial") {
                            header.click().click();
                        } else {
                            // do nothing because the column is already ascending
                        }
                    } else if (order === "descending") {
                        if (header.attr("title") === "Sort ascending") {
                            header.click().click();
                        } else if (header.attr("title") === "Sort descending") {
                            header.click();
                        } else {
                            // do nothing because the column is already descending
                        }
                    } else {
                        // do nothing
                    }
                }

                var button1 = new OO.ui.ButtonWidget({label: "Wikibench and ORES"});
                button1.on("click", function() {
                    sortColumn("#table-header-userIntent-ores", "descending");
                    sortColumn("#table-header-editDamage-ores", "descending");
                    sortColumn("#table-header-userIntent-wikibench", "descending");
                    sortColumn("#table-header-editDamage-wikibench", "descending");
                });
                var button2 = new OO.ui.ButtonWidget({label: "Wikibench and LiftWing"});
                button2.on("click", function() {
                    sortColumn("#table-header-reverted-liftwing", "descending");
                    sortColumn("#table-header-userIntent-wikibench", "descending");
                    sortColumn("#table-header-editDamage-wikibench", "descending");
                });
                var button3 = new OO.ui.ButtonWidget({label: "ORES and LiftWing"});
                button3.on("click", function() {
                    sortColumn("#table-header-reverted-liftwing", "descending");
                    sortColumn("#table-header-userIntent-ores", "descending");
                    sortColumn("#table-header-editDamage-ores", "descending");
                });
                var layoutSortBtns = new OO.ui.HorizontalLayout({
                    items: [
                        new OO.ui.LabelWidget({ label: "I want to compare:" }),
                        button1,
                        button2,
                        button3
                    ]
                });
                tableDiv.before(layoutSortBtns.$element);
                layoutSortBtns.toggle(false);

                function getPrefixedPages(entityType, queryContinue, deferred, results) {
                    deferred = deferred || $.Deferred();
                    queryContinue = queryContinue || {};
                    results = results || [];
                    var prefix = WIKIBENCH_PREFIX + "/Entity:" + entityType.charAt(0).toUpperCase() + entityType.slice(1) + "/";
                    var params = {
                        action: "query",
                        prop: "revisions",
                        rvprop: "content",
                        generator: "allpages",
                        gapprefix: prefix,
                        gaplimit: 500,
                        gapnamespace: WIKIBENCH_NAMESPACE,
                        format: "json",
                        formatversion: 2
                    };

                    Object.assign(params, queryContinue)

                    mwApi.get(params)
                        .done(function(data) {
                            var pages = data.query.pages;
                            pages.forEach(function(page){
                                if (page.revisions !== undefined) {
                                    page['content'] = page.revisions[0].content;
                                    delete page['revisions'];
                                    results.push(page);
                                }
                            });
                            if(data.continue){
                                getPrefixedPages(entityType, data.continue, deferred, results);
                            }else{
                                deferred.resolve(results);
                            }
                        })
                        .fail(function(e) {
                            deferred.fail(e);
                        });

                    return deferred.promise();
                }

                getPrefixedPages(entityType).done(function(results) {

                    // tableDiv.after(loadDataBtn.$element);

                    function shuffle(array) {
                        let currentIndex = array.length,  randomIndex;
                      
                        // While there remain elements to shuffle.
                        while (currentIndex != 0) {
                      
                          // Pick a remaining element.
                          randomIndex = Math.floor(Math.random() * currentIndex);
                          currentIndex--;
                      
                          // And swap it with the current element.
                          [array[currentIndex], array[randomIndex]] = [
                            array[randomIndex], array[currentIndex]];
                        }
                      
                        return array;
                      }
                    shuffle(results);

                    var startIndex = 0;

                    // loadDataBtn.on("click", function() {

                        var renderCount = 0;
                        var revids = [];

                        for (var i = startIndex; i < results.length; i++) {
                            if (renderCount >= RATE_LIMIT) {
                                break;
                            }
                            label = JSON.parse(results[i].content.split(entityPageSplit)[1]);
                            if ((label.entityId.split("/")[0] !== "false") && (label.entityNote === "")) { // exclude new page and multiple changes
                                var newId = label.entityId.split("/")[1];
                                tableRows[newId] = {};
                                tableRows[newId]["entityId"] = label.entityId;
                                tableRows[newId]["wikibench"] = {}
                                tableRows[newId]["ores"] = {};
                                facets.forEach(function(f) {
                                    tableRows[newId]["wikibench"][f] = label.facets[f];
                                })
                                revids.push(newId);
                                var request1 = $.ajax({
                                    url: 'https://api.wikimedia.org/service/lw/inference/v1/models/revertrisk-language-agnostic:predict',
                                    crossDomain: true,
                                    method: 'post',
                                    contentType: 'application/x-www-form-urlencoded',
                                    data: '{"rev_id":' + newId + ', "lang": "en"}'
                                }).done( function ( result, textStatus, jqXHR ) {
                                    liftwing[result.revision_id.toString()] = result["output"];
                                });
                                promises.push(request1);

                                // var request2 = $.ajax({
                                //     url: "https://ores.wikimedia.org/v3/scores/enwiki",
                                //     data: {
                                //         "context": "enwiki",
                                //         "models": "damaging|goodfaith",
                                //         "revids": newId.toString()
                                //     },
                                //     timeout: 30 * 1000, // 30 seconds
                                //     dataType: "json",
                                //     type: "GET"
                                // }).done(function ( result, textStatus, jqXHR ){
                                //     ores[Object.keys(result["enwiki"]["scores"])[0]] = Object.values(result["enwiki"]["scores"])[0]
                                // });
                                // promises.push(request2);
                                renderCount++;
                            }
                        }
                        var request2 = $.ajax({
                            url: "https://ores.wikimedia.org/v3/scores/enwiki",
                            data: {
                                "context": "enwiki",
                                "models": "damaging|goodfaith",
                                "revids": revids.join("|")
                            },
                            timeout: 30 * 1000, // 30 seconds
                            dataType: "json",
                            type: "GET"
                        }).done(function ( result, textStatus, jqXHR ){
                            for (var r = 0; r < revids.length; r++) {
                                ores[revids[r].toString()] = result["enwiki"]["scores"][revids[r].toString()];
                            }
                        });
                        promises.push(request2);

                        $.when.apply(null, promises).done(function() {
                            if (startIndex === 0) {
                                tbody.find("tr").remove(); // remove the empty line
                            }
                            for (var r = 0; r < revids.length; r++) {
                                var row = tableRows[revids[r]];
                                var liftwing_prediction = liftwing[revids[r]]["prediction"] ? "reverted" : "not reverted";
                                var ores_prediction_editDamage = ores[revids[r]]["damaging"]["score"]["prediction"] ? "damaging" : "not damaging";
                                var ores_prediction_userIntent = ores[revids[r]]["goodfaith"]["score"]["prediction"] ? "good faith" : "bad faith";
                                var individualLabels = {};
                                // calculate wikibench agreement
                                for (var i = 0; i < facets.length; i++) {
                                    var f = facets[i];
                                    individualLabels[f] = [];
                                    for (var j = 0; j < row["wikibench"][f].individualLabels.length; j++) {
                                        // handle individual labels for disagreements
                                        var individualLabel = row["wikibench"][f].individualLabels[j];
                                        if (individualLabel.label === facetLabels[f][0]) {
                                            if (individualLabel.lowConfidence) {
                                                individualLabels[f].push(-0.5);
                                            }
                                            else {
                                                individualLabels[f].push(-1);
                                            }
                                        }
                                        if (individualLabel.label === facetLabels[f][1]) {
                                            if (individualLabel.lowConfidence) {
                                                individualLabels[f].push(0.5);
                                            }
                                            else {
                                                individualLabels[f].push(1);
                                            }
                                        }
                                    }
                                }                            

                                var hrefLink = "<a href=\"/wiki/User:" + WIKIBENCH_PREFIX + "/Entity:" + entityType.charAt(0).toUpperCase() + entityType.slice(1) + "/" + row["entityId"] + "\"></a>";
                                tbody.append($("<tr>")
                                    .append($("<th>").text(row["entityId"]).wrapInner(hrefLink).attr("scope", "row"))
                                    .append($("<td>").html(row["wikibench"][facets[0]].primaryLabel.label + " <span style=\"color:" + probColors + "\">(" + (1 - calculateStandardDeviation(individualLabels[facets[0]])).toFixed(ROUND_PRECISION).toString() + ")</span>").attr("bgcolor", tableLabelColors[facets[0]][row["wikibench"][facets[0]].primaryLabel.label]))
                                    .append($("<td>").html(ores_prediction_editDamage + " <span style=\"color:" + probColors + "\">(" + ores[revids[r]]["damaging"]["score"]["probability"][ores[revids[r]]["damaging"]["score"]["prediction"].toString()].toFixed(ROUND_PRECISION).toString() + ")</span>").attr("bgcolor", tableLabelColors[facets[0]][ores_prediction_editDamage]))
                                    .append($("<td>").html(row["wikibench"][facets[1]].primaryLabel.label + " <span style=\"color:" + probColors + "\">(" + (1 - calculateStandardDeviation(individualLabels[facets[1]])).toFixed(ROUND_PRECISION).toString() + ")</span>").attr("bgcolor", tableLabelColors[facets[1]][row["wikibench"][facets[1]].primaryLabel.label]))
                                    .append($("<td>").html(ores_prediction_userIntent + " <span style=\"color:" + probColors + "\">(" + ores[revids[r]]["goodfaith"]["score"]["probability"][ores[revids[r]]["goodfaith"]["score"]["prediction"].toString()].toFixed(ROUND_PRECISION).toString() + ")</span>").attr("bgcolor", tableLabelColors[facets[1]][ores_prediction_userIntent]))
                                    .append($("<td>").html(liftwing_prediction + " <span style=\"color:" + probColors + "\">(" + liftwing[revids[r]]["probabilities"][liftwing[revids[r]]["prediction"].toString()].toFixed(ROUND_PRECISION).toString() + ")</span>").attr("bgcolor", tableLabelColors[liftwing_prediction]))
                                );
                            }
                            progressBar.toggle(false);
                            layoutSortBtns.toggle(true);
                            startIndex += renderCount;
                            requestLimitMessage.toggle(false);
                        }).fail(function(e) {
                            // loadDataBtn.toggle(false);
                            requestLimitMessage.toggle(true);
                            progressBar.toggle(false);
                        });
                    // });
                });
            });
        }
    });
})(jQuery, mediaWiki);

// Import CSS
mw.loader.load("//meta.wikimedia.org/w/index.php?title=User:Tzusheng/Wikibench.css&action=raw&ctype=text/css", 'text/css');

// QOL
$.when( mw.loader.using( [ 'mediawiki.util' ] ), $.ready ).then( function () {
	mw.util.addPortletLink( 
	    'p-personal',
	    'https://en.wiki.x.io/wiki/User:Tzusheng/sandbox/Wikipedia:Wikibench/Campaign:Editquality',
	    'Wikibench',
	    'pt-wikibench',
	    'wikibench', 
	    null,
		'#pt-preferences'
	);
} );

//</nowiki>