- // ==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://greatest.deepsurf.us/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'
- ]
- } );
-
- })();