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.

  1. // ==UserScript==
  2. // @name Tool for leetcode.com
  3. // @match https://leetcode.com/problems/*
  4. // @match https://leetcode.com/submissions/detail/*
  5. // @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.
  6. // @version 1.3
  7. // @git f890188d315661aae24c2b05abfb6feac286c9fe
  8. // @namespace https://greatest.deepsurf.us/users/7949
  9. // ==/UserScript==
  10.  
  11. // @todo table sorting, diff functionality
  12.  
  13.  
  14. (function () {
  15.  
  16. // dependancies inserted here
  17.  
  18. // BEGIN INCLUDE sql_table.js
  19. /**
  20. * @file This module works with leetcode's json representable of tables.
  21. */
  22.  
  23. var sql_table = (function ( factory ) {
  24. var modulize = function ( factory, args ) {
  25. var callable = function () {
  26. return modulize( factory, arguments );
  27. };
  28.  
  29. var obj = factory.apply( null, args );
  30. for ( var prop in obj ) {
  31. if ( obj.hasOwnProperty( prop ) ) {
  32. callable[ prop ] = obj[ prop ];
  33. }
  34. }
  35.  
  36. return callable;
  37. };
  38.  
  39. return function () {
  40. return modulize( factory, arguments );
  41. }
  42. })( function ( $, _ ) {
  43.  
  44. /**
  45. @typedef {Object} Table
  46. @property {string} name The name of table
  47. @property {string[]} headers List of headers
  48. @property {Array[]} values Rows of table
  49. */
  50.  
  51.  
  52. /**
  53. * Create table element from a leetcode json
  54. * @param {Table} obj Parsed json from "output" or "expected" field
  55. * @returns {jQuery} The table object
  56. */
  57. var create_table_elem = function ( obj ) {
  58.  
  59. /**
  60. * Return the HTML representation of value wrap in <td>
  61. * @param value
  62. * @return {jQuery}
  63. */
  64. var repr_cell = function ( value ) {
  65. if ( _.isNull( value ) ) {
  66. return $( '<td>' )
  67. .append( $( '<em>' ).text( 'NULL' ) );
  68. } else if ( _.isString( value ) ) {
  69. return $( '<td>' ).text( JSON.stringify( value ) );
  70. } else if ( _.isNumber( value ) ) {
  71. return $( '<td>' )
  72. .text( value )
  73. .css( 'text-align', 'right' );
  74. } else {
  75. // unknown type
  76. return $( '<td>' ).text( value );
  77. }
  78. };
  79.  
  80. /**
  81. * Wrap a list of text with tag
  82. * @param {string[]} arr
  83. * @param {string} tag
  84. * @returns {jQuery[]} Array of wrapping element
  85. */
  86. var wrap_text = function ( arr, tag ) {
  87. return _( arr ).map( function ( txt ) {
  88. return $( tag ).text( txt );
  89. } );
  90. };
  91.  
  92. return $( '<table>' )
  93. .append( $( '<caption>' ).text( obj.name ) )
  94. .append( $( '<thead>' )
  95. .append( $( '<tr>' ).append( wrap_text( obj.headers, '<th>' ) ) ) )
  96. .append( $( '<tbody>' )
  97. .append( _( obj.values ).map( function ( row ) {
  98. return $( '<tr>' ).append( _( row ).map( repr_cell ) );
  99. } ) ) );
  100. };
  101.  
  102.  
  103. /**
  104. * Split the input json from leetcode to multiple table objects
  105. * @param {Object} obj Parsed json from "input" field
  106. * @param {Object.<string, string[]>} obj.headers Table name -> list of headers
  107. * @param {Object.<string, Array>} obj.rows Table name -> list of rows
  108. * @returns {Object.<string, Table>} Table name -> table object
  109. */
  110. var split_input_table = function ( obj ) {
  111. var tables = {};
  112. _( obj.headers ).each( function ( headers, table_name ) {
  113. var table = {};
  114. table.name = table_name;
  115. table.headers = headers;
  116. table.values = obj.rows[ table_name ];
  117. tables[ table_name ] = table;
  118. } );
  119.  
  120. return tables;
  121. };
  122.  
  123.  
  124. return {
  125. create_table_elem: create_table_elem,
  126. split_input_table: split_input_table
  127. };
  128. } );
  129. // END INCLUDE sql_table.js
  130.  
  131.  
  132. // BEGIN INCLUDE bootstrap.js
  133. /**
  134. * @file Dependancy loader for user scripts.
  135. */
  136.  
  137.  
  138. // ref: https://gist.github.com/cyranix/6180495
  139.  
  140. /**
  141. * @typedef {Object} JSModule
  142. * @property {string} url
  143. * @property {Function} has
  144. * @property {Function} get
  145. */
  146.  
  147. /**
  148. * Dependancy loader for user scripts.
  149. * @param {Function} main Main function of a user script,
  150. * call with loaded js modules
  151. * @param {Object} opts Required JS, CSS
  152. * @param {JSModule[]} [opts.modules=[]] Array of JS module specifiers
  153. * @param {string[]} [opts.css=[]] Array of URLs to CSS dependencies
  154. */
  155. var bootstrap = function ( main, opts ) {
  156. /**
  157. * Load a js url and invoke callback
  158. * @param {string} url
  159. * @param {function} callback
  160. */
  161. var load_js = function ( url, callback ) {
  162. var script = document.createElement( 'script' );
  163. script.src = url;
  164. script.addEventListener( 'load', callback );
  165. document.body.appendChild( script );
  166. };
  167.  
  168. /**
  169. * Load a list of css urls
  170. * @param {string[]} url_list
  171. */
  172. var load_css_multi = function ( url_list ) {
  173. while ( url_list.length > 0 ) {
  174. var head = document.getElementsByTagName( 'head' )[ 0 ];
  175. var link = document.createElement( 'link' );
  176. link.rel = 'stylesheet';
  177. link.type = 'text/css';
  178. link.href = url_list.shift();
  179. link.media = 'all';
  180. head.appendChild( link );
  181. }
  182. };
  183.  
  184. /**
  185. * Load a list of JS modules and invoke callback with a list of loaded module objects
  186. * @param {JSModule[]} mod_list
  187. * @param {Function} done
  188. * @param {Object[]} [loaded]
  189. */
  190. var load_js_modules = function ( mod_list, done, loaded ) {
  191. loaded = loaded || [];
  192. if ( mod_list.length > 0 ) {
  193. var mod_specifier = mod_list.shift();
  194. var existed = mod_specifier.has();
  195. if ( existed ) {
  196. loaded.push( existed );
  197. load_js_modules( mod_list, done, loaded );
  198. } else {
  199. load_js( mod_specifier.url, function () {
  200. loaded.push( mod_specifier.get() );
  201. load_js_modules( mod_list, done, loaded );
  202. } );
  203. }
  204. } else {
  205. done( loaded );
  206. }
  207. };
  208.  
  209. var load_all = function () {
  210. var css_urls = opts.css || {};
  211. var modules = opts.modules || [];
  212.  
  213. load_css_multi( css_urls );
  214. load_js_modules( modules, function ( mod_objs ) {
  215. // start main function
  216. main.apply( null, mod_objs );
  217. } );
  218. };
  219.  
  220. load_all();
  221. };
  222. // END INCLUDE bootstrap.js
  223.  
  224.  
  225. // BEGIN INCLUDE main.js
  226. /**
  227. * @file Main function of this user script
  228. */
  229.  
  230. // Arguments correspond to the dependency references.
  231. // @see {@link bootstrap} for further information.
  232. var main = function ( $, _, sql_table ) {
  233. // Is this a sql problem or sql submission
  234. var has_sql = $( '*[ng-switch-when=mysql]' ).length > 0 // sql problem
  235. || (window.pageData && window.pageData.getLangDisplay === 'mysql'); // sql submission
  236. if ( !has_sql ) {
  237. return;
  238. }
  239.  
  240. // load sql_table module
  241. sql_table = sql_table( $, _ );
  242.  
  243. // get json from these elements
  244. var input_id = '#result_wa_testcase_input';
  245. var output_id = '#result_wa_testcase_output';
  246. var expected_id = '#result_wa_testcase_expected';
  247. var last_exe_id = '#last_executed_testcase_output';
  248.  
  249. // styles for table
  250. var table_classes = [ 'pure-table', 'pure-table-bordered', 'pure-table-striped' ];
  251.  
  252. /**
  253. * Create table element from "input" field
  254. * @param {jQuery} el
  255. * @return {jQuery[]} Array of table element
  256. */
  257. var get_input_tables = function ( el ) {
  258. // workaround: leetcode is replacing ',' with '\n'
  259. var json = JSON.parse( el.text().replace( /\n/g, ', ' ) );
  260. return _( sql_table.split_input_table( json ) ).map( function ( table ) {
  261. return sql_table.create_table_elem( table )
  262. .addClass( table_classes.join( ' ' ) );
  263. } );
  264. };
  265.  
  266. /**
  267. * Create table element from "output" or "expected" field
  268. * @param {jQuery} el
  269. * @return {jQuery} The table element
  270. */
  271. var get_output_table = function ( el ) {
  272. var json = JSON.parse( el.text() );
  273. return sql_table.create_table_elem( json )
  274. .addClass( table_classes.join( ' ' ) );
  275. };
  276.  
  277. /**
  278. * Render tables after "Wrong Answer" encountered.
  279. */
  280. var show_table = function () {
  281. var table_ctn = $( '<div>' )
  282. .append( '<hr>' )
  283. .append( $( '<div>' ).text( 'Inputs:' ) )
  284. .append( get_input_tables( $( input_id ) ) )
  285.  
  286. .append( '<hr>' )
  287. .append( $( '<div class="pure-g">' )
  288. .append( $( '<div class="pure-u-1-2">' )
  289. .css( { color: 'red' } )
  290. .append( $( '<div>' ).text( 'Output:' ) )
  291. .append( get_output_table( $( output_id ) ) ) )
  292. .append( $( '<div class="pure-u-1-2">' )
  293. .css( { color: 'green' } )
  294. .append( $( '<div>' ).text( 'Expected:' ) )
  295. .append( get_output_table( $( expected_id ) ) ) ) );
  296.  
  297. var wa_output = $( '#wa_output' );
  298. // remove prevous tables
  299. wa_output.children().first().nextAll().remove();
  300. wa_output.append( table_ctn );
  301. };
  302.  
  303. /**
  304. * Render tables after "Runtime Error" encountered.
  305. */
  306. var show_le_table = function () {
  307. var table_ctn = $( '<div>' )
  308. .append( '<hr>' )
  309. .append( $( '<div>' ).text( 'Inputs:' ) )
  310. .append( get_input_tables( $( last_exe_id ) ) );
  311.  
  312. var last_exe = $( '#last_executed_testcase_output_row' );
  313. // remove prevous tables
  314. last_exe.children().first().nextAll().remove();
  315. last_exe.append( table_ctn );
  316. };
  317.  
  318. var create_show_table_btn = function () {
  319. var btn = $( '<button>' )
  320. .text( 'Tablize!' )
  321. .addClass( 'pure-button' )
  322. .css( { 'margin-left': '16px' } )
  323. .click( function () {
  324. show_table();
  325. } );
  326. $( '#more-details' ).after( btn );
  327. };
  328.  
  329. /**
  330. * Invoke callback when element is visible. Using MutationObserver
  331. * @param {jQuery} elem
  332. * @param {function} func
  333. */
  334. var setup_observer = function ( elem, func ) {
  335. var on_attr_changes = function () {
  336. if ( elem.is( ':visible' ) ) {
  337. func();
  338. }
  339. };
  340.  
  341. // elem may be visible already
  342. on_attr_changes();
  343. // observe style attribute changes
  344. var observer = new MutationObserver( on_attr_changes );
  345. observer.observe( elem.get( 0 ), { attributes: true } );
  346. };
  347.  
  348. /**
  349. * Invoke callback when element is visible. Using setInterval()
  350. * @param {jQuery} elem
  351. * @param {function} func
  352. */
  353. var setup_poller = function ( elem, func ) {
  354. var is_showing = false;
  355. var check = function () {
  356. if ( elem.is( ':visible' ) ) {
  357. if ( !is_showing ) {
  358. func();
  359. is_showing = true;
  360. }
  361. } else {
  362. is_showing = false;
  363. }
  364. };
  365.  
  366. window.setInterval( check, 500 );
  367. };
  368.  
  369. var setup;
  370. if ( !window.MutationObserver ) {
  371. setup = setup_poller;
  372. } else {
  373. setup = setup_observer;
  374. }
  375.  
  376. // show tables after wrong answer appeared
  377. setup( $( '#wa_output' ), show_table );
  378. // show tables after a runtime errer
  379. setup( $( '#last_executed_testcase_output_row' ), show_le_table );
  380. };
  381. // END INCLUDE main.js
  382.  
  383.  
  384. bootstrap( main, {
  385. modules: [ {
  386. // jQuery
  387. url: '//cdnjs.cloudflare.com/ajax/libs/jquery/1.12.4/jquery.min.js',
  388. has: function () {
  389. return window.jQuery;
  390. },
  391. get: function () {
  392. return window.$.noConflict();
  393. }
  394. }, {
  395. // underscore
  396. url: '//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js',
  397. has: function () {
  398. return undefined;
  399. },
  400. get: function () {
  401. return window._.noConflict();
  402. }
  403. }, {
  404. // sql_table
  405. has: function () {
  406. return sql_table; // direct reference
  407. }
  408. } ],
  409. css: [
  410. // pure.css
  411. '//cdnjs.cloudflare.com/ajax/libs/pure/0.6.0/pure-min.css'
  412. ]
  413. } );
  414.  
  415. })();