qubodup / Freesound Moderation Helper

// ==UserScript==
// @name         Freesound Moderation Helper
// @namespace    http://qubodup.net/
// @version      0.9
// @description  autoplay freesound moderation sounds upon clicking their name in your moderation queue
// @author       qubodup
// @copyright    2018-2020, qubodup (https://openuserjs.org/users/qubodup
// @license      MIT
// @match        https://freesound.org/tickets/moderation/assigned/*
// @grant        none
// ==/UserScript==

// This is from https://openuserjs.org/scripts/qubodup/Freesound_Moderation_Helper

/* Changelog
- 0.9 pre add bg color to selected sounds, matching current decision; Composition quickmod button added, taller [note: I'm not sure what this changelog means. I'm reading this years after but who cares they now update the offical styling it seems at least this way my CSS can be copied]
- 0.8 Recording button added; license indicator (cheap for now, might make it nicer later)
- 0.7 add select all accepted link; shorten "select all" text; add margin to decision buttons;
add color to decision buttons; onclick decision buttons; if #moderation-queue textarea height 80px,
set to 160px; code cleanup; decision radio buttons create color warning; sort sounds by user, not date;
censor button
- 0.6 Additional moderation button (device? and silence); works on Firefox now
- 0.5 Shortening moderation buttons; adding a moderation button (timeout?); added changelog
- 0.4 Elaborate re-styling of the rest of the page to make moderation more efficient (less cluttered)
- pre-0.4 see https://github.com/MTG/freesound/issues/1249 ; amongst others: shorten accepted/deferred
to a/deferred; color-code a/d rows; convert date to timer; write full name and abbreviate using CSS
rather than removing information; highlight picked user's username in queue (in StopWaiting)
*/

(function() {
    'use strict';

    //// ONLY RUN CODE ONCE PAGE HAS STARTED LOADING

    $(document).ready(function(){

        //// CSS

        // style additions
        $(`<style type='text/css'>
#header { height: 30px; width: 950px; }
body, #header, #account_nav { background: none !important; }
.button, #container { border: none !important; }
.sample_player_small { width: 440px; }
#sounds_info { overflow-y: auto !important; overflow-x: hidden !important; }
#container { padding: 0px; }
#ticket_menu { padding: 0px; width: 100%; }
#ticket_menu .button.selected { background-color: white; color: #CB3D44; }
#ticket_menu li { margin: 0; }
#ticket_menu li a { padding: 10px 60px !important; }
#ticket_menu li a:hover { background-color: #f3f3f3 !important; }
#wrapper, ul#site_nav li, ul#site_nav li a { background: white !important; }
#footer_wrapper, #search, #logo, h1 { display: none !important; }
#upload_button { width: auto; height: auto; background: none; display: inline; }
tr.row-accept.alternate-row-odd { background-color: #dfffdf; }
tr.row-accept.alternate-row-even { background-color: #cfffcf; }
tr.row-defer.alternate-row-odd { background-color: #ffdfdf; }
tr.row-defer.alternate-row-even { background-color: #ffcfcf; }
td { background-color: transparent !important; color: inherit !important;
border: none;
white-space: nowrap; text-overflow: ellipsis; overflow: hidden; }
.mod-selected-row td:nth-child(2) { font-weight: bold; }
td:nth-child(2) a { color: black !important; }
.mod-selected-row td.selected-user { font-weight: bold; }
tr.alternate-row-odd  td.selected-user { background-color: #d5f3ff !important; }
tr.alternate-row-even td.selected-user { background-color: #d0eeff !important; }
td.user { background-color: transparent !important; }
td:nth-child(2) { max-width: 190px; }
td:nth-child(3) { max-width: 125px; }
table#assigned-tickets-table { width: 448px; }
#id_action li { background-color: white; padding: 2px; margin-bottom: 6px; }
#id_action li:nth-child(1):hover, .decision-approve #moderation-form, .decision-approve #moderation-form #id_action li:nth-child(1),
.decision-approve .mod-selected-row td:nth-child(2), .decision-approve .mod-selected-row td:nth-child(4), .decision-approve .mod-selected-row td:nth-child(5) { background-color:lightgreen !important; }
#id_action li:nth-child(2):hover, .decision-delete #moderation-form, .decision-delete #moderation-form #id_action li:nth-child(2),
.decision-delete .mod-selected-row td:nth-child(2), .decision-delete .mod-selected-row td:nth-child(4), .decision-delete .mod-selected-row td:nth-child(5) { background-color:coral !important; }
#id_action li:nth-child(3):hover, .decision-defer #moderation-form, .decision-defer #moderation-form #id_action li:nth-child(3),
.decision-defer .mod-selected-row td:nth-child(2), .decision-defer .mod-selected-row td:nth-child(4),.decision-defer .mod-selected-row td:nth-child(5) { background-color:PaleGoldenrod !important; }
#id_action li:nth-child(4):hover, .decision-return #moderation-form, .decision-return #moderation-form #id_action li:nth-child(4),
.decision-return .mod-selected-row td:nth-child(2), .decision-return .mod-selected-row td:nth-child(4), .decision-return .mod-selected-row td:nth-child(5) { background-color:lightgrey !important; }
#id_action li:nth-child(5):hover, .decision-whitelist #moderation-form, .decision-whitelist #moderation-form #id_action li:nth-child(5),
.decision-whitelist .mod-selected-row td:nth-child(2), .decision-whitelist .mod-selected-row td:nth-child(4), .decision-whitelist .mod-selected-row td:nth-child(5) { background-color:white !important; }
#moderation-decision-form p:nth-child(4), #moderation-form form div:nth-child(3) p:nth-child(4) { float: right; background-color: white; padding: 2px 2px 0px 2px; border: 1px solid white; }
#moderation-decision-form p.selected, #moderation-form form div:nth-child(3) p.selected { background-color: pink; border: 1px solid black; }

#assigned-tickets-table-wrapper { height: inherit; }

#moderation-form form { background-color: inherit; border: none; }
#sounds_info { background-color: white; }
#select-accepted { font-size: 12px; cursor: pointer; }
#moderation_options_ input { margin-bottom: .5em; }
#quick-wrong, #quick-illegal, #quick-silent { background-color: coral; }
#quick-recording, #quick-composition, #quick-description, #quick-language, #quick-timeout, #quick-tags { background-color: PaleGoldenrod; }
#quick-device { background-color: lightgreen; }
#censor { color: white !important; }
.censored { filter: blur(8px) !important; }
.sample_information { border-width: 0px !important; float: inherit !important; display: initial !important; }
.sample_information a, .sample_information br, .sample_information span { display: none; }
.sample_information img { float: right !important; margin-top: -90px !important; }
</style>`).appendTo("head");

        //// HEADER AND GENERAL NON-MODERATION STYLE CHANGES

        // minimize upload button
        $("#upload_button").html("Upload");

        //// MODERATION QUEUE SELECTION BUTTONS

        // minimize "select all by user even deferred" link and add warning
        $("#select-all-same-user").html("select user");
        $("#select-all-same-user").attr('title', 'warning: selects deferred sounds too');

        // add "select all by user but only active" link
        $("#select-all-same-user-wrapper").after(" | <a id='select-accepted' title='warning: does not deselect selected deferred sounds'>select user accepted</a>");
        $("#select-accepted").click(function(){
            var selectedUserIds = {};
            $('.ticket-check:checked').each(function () {
                var ticket_id = $(this).data('id');
                var user_id = $("#row_" + ticket_id).data('sender-id');
                selectedUserIds[user_id] = true;
            });
            if (Object.keys(selectedUserIds).length == 1){
                var target_user_id = Object.keys(selectedUserIds)[0];
                $('.ticket-check').each(function () {
                    var ticket_id = $(this).data('id');
                    var t_user_id = $("#row_" + ticket_id).data('sender-id');
                    var t_user_active = $("#row_" + ticket_id).hasClass('row-accept');
                    if (t_user_id == target_user_id && t_user_active){
                        $("#row_" + ticket_id).find('.ticket-check').attr('checked', true);
                    }
                });
            }
            updateSelectedTickets();
        });

        //// MODERATION QUEUE DISPLAY OF CONTENT STREAMLINING

        // constants
        var TODAY = new Date();

        // for each sound ticket
        $('#assigned-tickets-table-wrapper table#assigned-tickets-table tr').each(function (i, row) {

            // get ticket id
            var ticket = $(this).attr("data-messages-url").toString().split("tickets/")[1].split("/')")[0];

            // get status ("Accepted" or "Deferred"), trim for safety(?)
            var status = $(this).children("td:nth-child(5)").text().trim();

            // make status strings compact
            // turn status indicators into ticket links to save additional space
            // add classes according to status
            if ( status == "Accepted" ) {
                $(this).children("td:nth-child(5)").html("<a target='_blank' href='http://freesound.org/tickets/" + ticket + "' title='Accepted ticket'>A</a>");
                $(this).addClass('row-accept');
            } else if ( status == "Deferred" ) {
                $(this).children("td:nth-child(5)").html("<a target='_blank' href='http://freesound.org/tickets/" + ticket + "' title='Deferred ticket'>D</a>");
                $(this).addClass('row-defer');
            }

            // convert date to days, to save space and sanity
            var sound_date_array = $(row).children("td:nth-child(4)").text().split("/");
            var sound_date = new Date(sound_date_array[2], sound_date_array[1] - 1, sound_date_array[0]);
            var sound_days = Math.floor((TODAY - sound_date) / 1000 / 60 / 60 / 24);
            $(this).children("td:nth-child(4)").text(sound_days + "d");

            // get username from data-sound-url
            var username = $(this).attr("data-sound-url").split("people/")[1].split("/")[0];

            // replace table username with sourced username to make use of available space
            $(this).children("td:nth-child(3)").text(username);
        });

        // sort (group) sounds in queue by user, not simply sort by date.
        // this way, it's less likely to accidentally apply decisions to sounds below the bottom border,
        // as intuitively the system makes us used to sounds being grouped by user.

        // store user names in order of ocurence
        var usersOrder = new Array();

        // fill list of user names
        $('#assigned-tickets-table-wrapper table#assigned-tickets-table tr').each(function (i, row) {
            var username = $(this).attr("data-sound-url").split("people/")[1].split("/")[0];
            if (!usersOrder.includes(username)){
                usersOrder.push(username);
            }
        });

        // for each user
        usersOrder.forEach(function(name, i) {

            // debug
            //console.log(i,name);

            // track whether user has occured yet or not
            var nameOccured = false;

            // track at which index a different name appeared
            var gapIndex = null;

            // for each row
            $('#assigned-tickets-table-wrapper table#assigned-tickets-table tr').each(function (j, row) {
                // get row's user name
                var nameRow = $(this).attr("data-sound-url").split("people/")[1].split("/")[0];

                // register if current name occurs for first time
                if (!nameOccured && name == nameRow) {
                    nameOccured = true;
                } else

                // if current name has occurred, remember which row had different name first for later swapping
                if (gapIndex == null && nameOccured && name != nameRow) {
                    gapIndex = j
                } else

                // move (don't move deferred rows)
                if ($(this).hasClass('row-accept') && gapIndex != null && nameOccured && name == nameRow) {

                    $(row).insertBefore($('#assigned-tickets-table-wrapper table#assigned-tickets-table tr')[gapIndex]);

                    // the gap just moved by one
                    gapIndex += 1;
                }
            });
        });

        // re-assign odd/even row classes
        var odd = true;

        $('#assigned-tickets-table-wrapper table#assigned-tickets-table tr').each(function (j, row) {
            $(this).removeClass("alternate-row-odd").removeClass("alternate-row-even");
            if(odd) {
                $(this).addClass("alternate-row-odd");
            } else {
                $(this).addClass("alternate-row-even");
            }
            odd = !odd;
        });


        //// FUNCTION FOR HIGHLIGHTING ROWS WITH SOUNDS BY CURRENT SELECTED USER's SOUND

        function MarkUser(userId) {
            // uses sender ID, as different usernames could have same abbreviation (which is used by default)
            // and user info box has unnecessary load delay

            $('#assigned-tickets-table-wrapper table#assigned-tickets-table tr').each(function (i, row) {
                var userNameField = $(this).children("td:nth-child(3)");
                var rowUserId = $(this).attr("data-sender-id");
                if (userId == rowUserId) {
                    $(userNameField).addClass("selected-user");
                    $(userNameField).removeClass("user");
                } else {
                    $(userNameField).removeClass("selected-user");
                    $(userNameField).addClass("user");
                }
            });
        }

        // paint selected user without having to click (need to get first item's userId for that)
        MarkUser($("table tbody tr:nth-child(1)").attr("data-sender-id"));


        //// FUNCTIONS FOR AUTO-PLAY OF SOUNDS WHEN CLICKING SOUND NAME IN QUEUE

        function StopWaiting() {
            $("#sound_0 a.toggle.play")[0].click();
        }

        function StartWaiting() {
            //console.log("loading");
            var loadCheck = setInterval(function(){
                if ($("#sound_0 a.toggle.play").length){
                    //console.log("loaded");
                    StopWaiting();
                    clearInterval(loadCheck);
                }
            }, 32);
        }

        // Let's actually use these functions
        // sound playback and loading class are controlled by the click event
        $('#assigned-tickets-table-wrapper table#assigned-tickets-table tr').find('td:nth-child(2) a').bind('click', function(){
            StartWaiting();
            var userId = $(this).parent().parent().attr("data-sender-id");
            MarkUser(userId);
        });


        //// QUICK MODERATION BUTTONS, RADIO BUTTONS, TEXT BOX

        // Shorten quick moderation button texts
        $("#moderation_options_ input:nth-child(1)").attr("value", "Tags").attr('id', 'quick-tags');
        $("#moderation_options_ input:nth-child(2)").attr("value", "Description").attr('id', 'quick-description');
        $("#moderation_options_ input:nth-child(3)").attr("value", "Language").attr('id', 'quick-language');
        $("#moderation_options_ input:nth-child(4)").attr('id', 'quick-illegal');
        $("#moderation_options_ input:nth-child(5)").attr("value", "Wrong").attr('id', 'quick-wrong');

        // Add quick moderation buttons
        $("#moderation_options_ input:nth-child(1)").before("<input id='quick-device' type='button' class='moderation_text' value='Device' onclick=\"$('#id_message').get(0).value += 'In the future, please write the recording device used, if you can. Thanks for sharing!\\n';\"> ");
        $("#moderation_options_ input:nth-child(2)").before("<input id='quick-timeout' type='button' class='moderation_text' value='Timeout' onclick=\"$('#id_message').get(0).value += '\\nThis ticket will time out in two weeks.';\"> ");
        $("#moderation_options_ input:nth-child(6)").before("<input id='quick-silent' type='button' class='moderation_text' value='Silent' onclick=\"$('#id_message').get(0).value += 'Please re-upload the sound without long sections of complete silence in the file.\\n';\"> ");
        $("#moderation_options_ input:nth-child(3)").before("<input id='quick-recording' type='button' class='moderation_text' value='Recording' onclick=\"$('#id_message').get(0).value += 'Thank you for sharing. Is this your own recording or taken from a website/sound library?\\nIf yours: could you please add to the description what device was used to record it?\\nIf not yours: please let us know from where.';\"> ");
        $("#moderation_options_ input:nth-child(4)").before("<input id='quick-composition' type='button' class='moderation_text' value='Composition' onclick=\"$('#id_message').get(0).value += 'Thank you for sharing. Is this your own composition or taken from a website/music track/music library?\\nIf yours: could you please briefly add to the description how it was made?\\nIf not yours: please let us know from where.';\"> ");

        // When clicking decision buttons, resize text box if it's default height (80px)
        $("#moderation_options_ input").click(function(){
            if ($("textarea#id_message").height() == 80){
                $("textarea#id_message").height(160);
            }
        });


        // Use decision radio buttons to change color as a warning
        function decisionWarning(option) {
            $("#moderation-queue").removeClass();
            switch(option) {
                case 'Approve' :
                    $("#moderation-queue").addClass("decision-approve");
                    break;
                case 'Delete' :
                    $("#moderation-queue").addClass("decision-delete");
                    break;
                case 'Defer' :
                    $("#moderation-queue").addClass("decision-defer");
                    break;
                case 'Return' :
                    $("#moderation-queue").addClass("decision-return");
                    break;
                case 'Whitelist' :
                    $("#moderation-queue").addClass("decision-whitelist");
                    break;
            }
        }

        // scan standard decision
        decisionWarning("Approve")

        // scan decision each time decision radio buttons are pressed
        $('#id_action input[type=radio][name=action]').change(function(){decisionWarning($(this).val());});

        // explicit and mod-only checkboxes selected warnings
        $("#moderation-decision-form p:nth-child(4) input, #moderation-form form div:nth-child(3) p:nth-child(4) input").change(function(){
            if ($(this).is(':checked')) {
                $(this).parent().addClass('selected');
            } else {
                $(this).parent().removeClass('selected');
            }
        });

        //// CENSORE TOGGLE

        $("#account_nav").append("<a id='censor'>C</a>");

        $('#censor').toggle(function () {
            $("table").addClass("censored");
            $("#account_user").addClass("censored");
            $("#sounds_info").addClass("censored");
            $("#user-annotations-section").addClass("censored");
            $("#moderate-form-title-label").addClass("censored");
        }, function () {
            $("table").removeClass("censored");
            $("#account_user").removeClass("censored");
            $("#sounds_info").removeClass("censored");
            $("#user-annotations-section").removeClass("censored");
            $("#moderate-form-title-label").removeClass("censored");
        });

    }); // end document ready
})(); // end (function() { which tampermonkey prescribed