Greasy Fork is available in English.

Tool for leetcode.com

This tool shows tables on database problems after you submit a wrong answer, so you don't need to read their unreadable JSON representation of tables.

// ==UserScript==
// @name            Tool for leetcode.com
// @match           https://leetcode.com/problems/*
// @match           https://leetcode.com/submissions/detail/*
// @description     This tool shows tables on database problems after you submit a wrong answer, so you don't need to read their unreadable JSON representation of tables.
// @version         1.3
// @git             f890188d315661aae24c2b05abfb6feac286c9fe
// @namespace https://greasyfork.org/users/7949
// ==/UserScript==

// @todo table sorting, diff functionality


(function () {

    // dependancies inserted here

    // BEGIN INCLUDE sql_table.js
    /**
     * @file This module works with leetcode's json representable of tables.
     */

    var sql_table = (function ( factory ) {
        var modulize = function ( factory, args ) {
            var callable = function () {
                return modulize( factory, arguments );
            };

            var obj = factory.apply( null, args );
            for ( var prop in obj ) {
                if ( obj.hasOwnProperty( prop ) ) {
                    callable[ prop ] = obj[ prop ];
                }
            }

            return callable;
        };

        return function () {
            return modulize( factory, arguments );
        }
    })( function ( $, _ ) {

        /**
         @typedef {Object} Table
         @property {string}     name    The name of table
         @property {string[]}   headers List of headers
         @property {Array[]}    values  Rows of table
         */


        /**
         * Create table element from a leetcode json
         * @param {Table} obj   Parsed json from "output" or "expected" field
         * @returns {jQuery}    The table object
         */
        var create_table_elem = function ( obj ) {

            /**
             * Return the HTML representation of value wrap in <td>
             * @param value
             * @return {jQuery}
             */
            var repr_cell = function ( value ) {
                if ( _.isNull( value ) ) {
                    return $( '<td>' )
                        .append( $( '<em>' ).text( 'NULL' ) );
                } else if ( _.isString( value ) ) {
                    return $( '<td>' ).text( JSON.stringify( value ) );
                } else if ( _.isNumber( value ) ) {
                    return $( '<td>' )
                        .text( value )
                        .css( 'text-align', 'right' );
                } else {
                    // unknown type
                    return $( '<td>' ).text( value );
                }
            };

            /**
             * Wrap a list of text with tag
             * @param {string[]}    arr
             * @param {string}      tag
             * @returns {jQuery[]}  Array of wrapping element
             */
            var wrap_text = function ( arr, tag ) {
                return _( arr ).map( function ( txt ) {
                    return $( tag ).text( txt );
                } );
            };

            return $( '<table>' )
                .append( $( '<caption>' ).text( obj.name ) )
                .append( $( '<thead>' )
                    .append( $( '<tr>' ).append( wrap_text( obj.headers, '<th>' ) ) ) )
                .append( $( '<tbody>' )
                    .append( _( obj.values ).map( function ( row ) {
                        return $( '<tr>' ).append( _( row ).map( repr_cell ) );
                    } ) ) );
        };


        /**
         * Split the input json from leetcode to multiple table objects
         * @param {Object}                    obj           Parsed json from "input" field
         * @param {Object.<string, string[]>} obj.headers   Table name -> list of headers
         * @param {Object.<string, Array>}    obj.rows      Table name -> list of rows
         * @returns {Object.<string, Table>}                Table name -> table object
         */
        var split_input_table = function ( obj ) {
            var tables = {};
            _( obj.headers ).each( function ( headers, table_name ) {
                var table = {};
                table.name = table_name;
                table.headers = headers;
                table.values = obj.rows[ table_name ];
                tables[ table_name ] = table;
            } );

            return tables;
        };


        return {
            create_table_elem: create_table_elem,
            split_input_table: split_input_table
        };
    } );
    // END INCLUDE sql_table.js


    // BEGIN INCLUDE bootstrap.js
    /**
     * @file Dependancy loader for user scripts.
     */


    // ref: https://gist.github.com/cyranix/6180495

    /**
     * @typedef {Object}    JSModule
     * @property {string}   url
     * @property {Function} has
     * @property {Function} get
     */

    /**
     * Dependancy loader for user scripts.
     * @param {Function}    main                Main function of a user script,
     *                                              call with loaded js modules
     * @param {Object}      opts                Required JS, CSS
     * @param {JSModule[]}  [opts.modules=[]]   Array of JS module specifiers
     * @param {string[]}    [opts.css=[]]       Array of URLs to CSS dependencies
     */
    var bootstrap = function ( main, opts ) {
        /**
         * Load a js url and invoke callback
         * @param {string}      url
         * @param {function}    callback
         */
        var load_js = function ( url, callback ) {
            var script = document.createElement( 'script' );
            script.src = url;
            script.addEventListener( 'load', callback );
            document.body.appendChild( script );
        };

        /**
         * Load a list of css urls
         * @param {string[]} url_list
         */
        var load_css_multi = function ( url_list ) {
            while ( url_list.length > 0 ) {
                var head = document.getElementsByTagName( 'head' )[ 0 ];
                var link = document.createElement( 'link' );
                link.rel = 'stylesheet';
                link.type = 'text/css';
                link.href = url_list.shift();
                link.media = 'all';
                head.appendChild( link );
            }
        };

        /**
         * Load a list of JS modules and invoke callback with a list of loaded module objects
         * @param {JSModule[]}  mod_list
         * @param {Function}    done
         * @param {Object[]}    [loaded]
         */
        var load_js_modules = function ( mod_list, done, loaded ) {
            loaded = loaded || [];
            if ( mod_list.length > 0 ) {
                var mod_specifier = mod_list.shift();
                var existed = mod_specifier.has();
                if ( existed ) {
                    loaded.push( existed );
                    load_js_modules( mod_list, done, loaded );
                } else {
                    load_js( mod_specifier.url, function () {
                        loaded.push( mod_specifier.get() );
                        load_js_modules( mod_list, done, loaded );
                    } );
                }
            } else {
                done( loaded );
            }
        };

        var load_all = function () {
            var css_urls = opts.css || {};
            var modules = opts.modules || [];

            load_css_multi( css_urls );
            load_js_modules( modules, function ( mod_objs ) {
                // start main function
                main.apply( null, mod_objs );
            } );
        };

        load_all();
    };
    // END INCLUDE bootstrap.js


    // BEGIN INCLUDE main.js
    /**
     * @file Main function of this user script
     */

    // Arguments correspond to the dependency references.
    // @see {@link bootstrap} for further information.
    var main = function ( $, _, sql_table ) {
        // Is this a sql problem or sql submission
        var has_sql = $( '*[ng-switch-when=mysql]' ).length > 0 // sql problem
            || (window.pageData && window.pageData.getLangDisplay === 'mysql'); // sql submission
        if ( !has_sql ) {
            return;
        }

        // load sql_table module
        sql_table = sql_table( $, _ );

        // get json from these elements
        var input_id = '#result_wa_testcase_input';
        var output_id = '#result_wa_testcase_output';
        var expected_id = '#result_wa_testcase_expected';
        var last_exe_id = '#last_executed_testcase_output';

        // styles for table
        var table_classes = [ 'pure-table', 'pure-table-bordered', 'pure-table-striped' ];

        /**
         * Create table element from "input" field
         * @param {jQuery} el
         * @return {jQuery[]} Array of table element
         */
        var get_input_tables = function ( el ) {
            // workaround: leetcode is replacing ',' with '\n'
            var json = JSON.parse( el.text().replace( /\n/g, ', ' ) );
            return _( sql_table.split_input_table( json ) ).map( function ( table ) {
                return sql_table.create_table_elem( table )
                    .addClass( table_classes.join( ' ' ) );
            } );
        };

        /**
         * Create table element from "output" or "expected" field
         * @param {jQuery} el
         * @return {jQuery} The table element
         */
        var get_output_table = function ( el ) {
            var json = JSON.parse( el.text() );
            return sql_table.create_table_elem( json )
                .addClass( table_classes.join( ' ' ) );
        };

        /**
         * Render tables after "Wrong Answer" encountered.
         */
        var show_table = function () {
            var table_ctn = $( '<div>' )
                .append( '<hr>' )
                .append( $( '<div>' ).text( 'Inputs:' ) )
                .append( get_input_tables( $( input_id ) ) )

                .append( '<hr>' )
                .append( $( '<div class="pure-g">' )
                    .append( $( '<div class="pure-u-1-2">' )
                        .css( { color: 'red' } )
                        .append( $( '<div>' ).text( 'Output:' ) )
                        .append( get_output_table( $( output_id ) ) ) )
                    .append( $( '<div class="pure-u-1-2">' )
                        .css( { color: 'green' } )
                        .append( $( '<div>' ).text( 'Expected:' ) )
                        .append( get_output_table( $( expected_id ) ) ) ) );

            var wa_output = $( '#wa_output' );
            // remove prevous tables
            wa_output.children().first().nextAll().remove();
            wa_output.append( table_ctn );
        };

        /**
         * Render tables after "Runtime Error" encountered.
         */
        var show_le_table = function () {
            var table_ctn = $( '<div>' )
                .append( '<hr>' )
                .append( $( '<div>' ).text( 'Inputs:' ) )
                .append( get_input_tables( $( last_exe_id ) ) );

            var last_exe = $( '#last_executed_testcase_output_row' );
            // remove prevous tables
            last_exe.children().first().nextAll().remove();
            last_exe.append( table_ctn );
        };

        var create_show_table_btn = function () {
            var btn = $( '<button>' )
                .text( 'Tablize!' )
                .addClass( 'pure-button' )
                .css( { 'margin-left': '16px' } )
                .click( function () {
                    show_table();
                } );
            $( '#more-details' ).after( btn );
        };

        /**
         * Invoke callback when element is visible. Using MutationObserver
         * @param {jQuery} elem
         * @param {function} func
         */
        var setup_observer = function ( elem, func ) {
            var on_attr_changes = function () {
                if ( elem.is( ':visible' ) ) {
                    func();
                }
            };

            // elem may be visible already
            on_attr_changes();
            // observe style attribute changes
            var observer = new MutationObserver( on_attr_changes );
            observer.observe( elem.get( 0 ), { attributes: true } );
        };

        /**
         * Invoke callback when element is visible. Using setInterval()
         * @param {jQuery} elem
         * @param {function} func
         */
        var setup_poller = function ( elem, func ) {
            var is_showing = false;
            var check = function () {
                if ( elem.is( ':visible' ) ) {
                    if ( !is_showing ) {
                        func();
                        is_showing = true;
                    }
                } else {
                    is_showing = false;
                }
            };

            window.setInterval( check, 500 );
        };

        var setup;
        if ( !window.MutationObserver ) {
            setup = setup_poller;
        } else {
            setup = setup_observer;
        }

        // show tables after wrong answer appeared
        setup( $( '#wa_output' ), show_table );
        // show tables after a runtime errer
        setup( $( '#last_executed_testcase_output_row' ), show_le_table );
    };
    // END INCLUDE main.js


    bootstrap( main, {
        modules: [ {
            // jQuery
            url: '//cdnjs.cloudflare.com/ajax/libs/jquery/1.12.4/jquery.min.js',
            has: function () {
                return window.jQuery;
            },
            get: function () {
                return window.$.noConflict();
            }
        }, {
            // underscore
            url: '//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js',
            has: function () {
                return undefined;
            },
            get: function () {
                return window._.noConflict();
            }
        }, {
            // sql_table
            has: function () {
                return sql_table;   // direct reference
            }
        } ],
        css: [
            // pure.css
            '//cdnjs.cloudflare.com/ajax/libs/pure/0.6.0/pure-min.css'
        ]
    } );

})();