返回首頁 

LeetCode Helper for JavaScript

try to take over the world!


Install this script?
// ==UserScript==// @name         LeetCode Helper for JavaScript// @namespace    http://tampermonkey.net/// @version      0.10// @description  try to take over the world!// @author       You// @match        https://leetcode-cn.com/problems/*// @match        https://leetcode-cn.com/contest/*/problems/*// @match        https://leetcode.cn/problems/*// @match        https://leetcode.cn/contest/*/problems/*// @match        https://leetcode.com/problems/*// @match        https://leetcode.com/contest/*/problems/*// @grant        GM_setValue// @grant        GM_getValue// @license      MIT// ==/UserScript==(function() {console.log('loading leetcode helper');const STYLE = `.leetcode-helper {position: fixed;background: rgba(255,255,255,1);z-index: ####;width: 400px;min-height: 50px;top: 0;left: 0;border: .5px solid rgb(255, 109, 0);max-height: 500px;overflow-y: auto;}.leetcode-helper-header {font-weight: bold;color: white;background: rgb(255, 164, 97);padding: 2px 7px;position: sticky;top: 0;}.leetcode-helper-body {padding: 5px 10px;}.leetcode-helper-body .case {display: flex;justify-content: space-between;}.leetcode-helper-body .case>div {flex: 0 1 30%;overflow: auto;}.leetcode-helper-body section>div:last-child {flex: 0 1 60%;}.leetcode-helper-body section p {margin-bottom: 0px;}.leetcode-helper-body label {color: rgb(255, 109, 0); margin-right: 5px;'}.leetcode-helper-status {margin-left: 5px;}.leetcode-helper .case .title button {line-height: 12px;font-size: 12px;}.leetcode-helper .case textarea {width: 100%;overflow: auto;white-space: nowrap;border: 1px solid black;border-left: none;font-family: monospace;}.leetcode-helper .case div:first-child textarea {border-left: 1px solid black;}.leetcode-helper .success {background-color: lightgreen;}.leetcode-helper .error {background-color: #ff9090;}.leetcode-helper .message {white-space: pre;font-family: monospace;line-height: 1.2;padding: 2px 5px;max-height: 20em;overflow: auto;}.leetcode-helper .operations {margin-top: 5px;}`main();async function main() {insertStyleSheets();const panel = createPanel()console.log('panel created:', panel);setDraggable(panel.querySelector('.leetcode-helper-header'), panel);document.body.appendChild(panel);}function getEditorText() {if (typeof monaco !== 'undefined') { // window is not the window, so window.monaco wont't workreturn monaco.editor.getModels()[0].getValue()}const el1 = document.querySelector('.editor-scrollable')if (el1) return el1.innerTextconst el2 = document.querySelector('.CodeMirror')if (el2) return el2.CodeMirror.getValue()return 'editor not found'}function getResolver(log) {const body = getEditorText()const match = /var\s+([a-zA-Z_$][\w$]*)\s*=/.exec(body)if (!match) throw new Error('resolver var xxx = function(){} not found')const fn = new Function(`console`, `${body}\n    return ${match[1]}`)return fn({log: function(...args) {log(args.map(serialize).join(' '))},error: function(...args) {log(args.map(serialize).join(' '))}})}function lineOffset() {try {const fn = new Function('console', 'throw new Error(314)')fn()} catch(e){const match = /(\d+):\d+\)($|\n)/.exec(e.stack)return match ? +match[1] - 1 : 2}}function insertStyleSheets() {const style = document.createElement('style')style.innerHTML = STYLEdocument.body.appendChild(style)}function getDescription() {return new Promise((resolve, reject) => {const interval = setInterval(() => {const el1 = document.querySelector('[data-key=description-content]')const el2 = document.querySelector('.question-content')const content = el1 && el1.innerText || el2 && el2.innerTextif (!content) returnclearInterval(interval)resolve(content)}, 300);})}function setDraggable(handle, panel) {let dragging = falselet initXlet initYlet initMarginXlet initMarginYhandle.addEventListener('mousedown', e => {dragging = trueinitX = e.clientX - (panel.style.left.slice(0, -2) || 0)initY = e.clientY - (panel.style.top.slice(0, -2) || 0)// console.log(mousedown, recording (${initX}, ${initY}))})window.addEventListener('mousemove', e => {if (!dragging) returnconst l = Math.min(window.innerWidth - 15, Math.max(0, e.clientX - initX))const t = Math.min(window.innerHeight - 15, Math.max(0, e.clientY - initY))// console.log(moving to (${l}, ${r}));panel.style.left = l + 'px'panel.style.top = t + 'px'})window.addEventListener('mouseup', e => {dragging = falseGM_setValue('pos', [+panel.style.left.slice(0, -2), +panel.style.top.slice(0, -2)])})}function renderCases (ios, caseList) {for(const io of ios) {caseList.append(createCase(io.input, io.expected))}}function loadCases () {let iostry {ios = JSON.parse(GM_getValue('leetcode.io:' + location.href))} catch (err) {return false}if (!ios) return falsereturn ios}async function saveCases () {const sections = document.querySelectorAll('.leetcode-helper .case-list .case')const ios = [...sections].map(section => ({input: section.querySelector('.input').value,expected: section.querySelector('.expected').value}))GM_setValue('leetcode.io:' + location.href, JSON.stringify(ios))console.log('cases saved', ios)}async function parseIO(caseList) {console.log('parsing IO from HTML...')const desc = await getDescription();const ios = parse(desc);console.log('parsed sample input/expected', ios);renderCases(ios, caseList);if (ios.length === 0) info('sample input/output not found')else saveCases(ios)}function createPanel() {const panel = document.createElement('div');panel.setAttribute('class', 'leetcode-helper');const pos = GM_getValue('pos')if (pos) {panel.style.left = Math.min(pos[0], window.innerWidth - 50) + 'px'panel.style.top = Math.min(pos[1], window.innerHeight - 50) + 'px'}const header = document.createElement('div');header.innerText = 'LeetCode Helper';header.setAttribute('class', 'leetcode-helper-header');panel.appendChild(header);const body = document.createElement('div');body.setAttribute('class', 'leetcode-helper-body');panel.appendChild(body);const caseList = document.createElement('div')caseList.classList.add('case-list')body.appendChild(caseList)window.messageEl = document.createElement('div')window.messageEl.classList.add('message')body.appendChild(window.messageEl);const operations = document.createElement('div')operations.classList.add('operations')operations.appendChild(createButton('RunAll', x => runAll(caseList.querySelectorAll('.case'))))operations.appendChild(createButton('AddCase', () => caseList.append(createCase())))operations.appendChild(createButton('Refresh', () => {caseList.innerHTML = ''parseIO(caseList)}))body.appendChild(operations)const ios = loadCases()if (ios) renderCases(ios, caseList);else parseIO(caseList);return panel;}function createCase(input = '', expected = '') {const section = document.createElement('section')section.classList.add('case')section.appendChild(createData('Input', input))section.appendChild(createData('Expected', expected))const output = createData('Output', '')output.querySelector('.title').appendChild(createButton('Run', () => run(section)))output.querySelector('.title').appendChild(createButton('Delete', () => section.remove()))section.appendChild(output)return section}function run(section) {const input = section.querySelector('.input').valueconst expected = section.querySelector('.expected').valueconst outputEl = section.querySelector('.output')info('Running...', section)requestAnimationFrame(() => requestAnimationFrame(() => {let argstry {args = input.split('\n').map(parseArg)} catch (err) {outputEl.value = err.stack.split('\n').map(x => x.replace(/\([^:]*:[^:]*:/, '(')).join('\n')console.error(err)return error(outputEl.value, section)}console.log('calling resolver with', ...args)clear(section)let r###lt = nulllet resolvertry {fn = getResolver(x => info(x, section))} catch (err) {outputEl.value = err.stack.split('\n').slice(0, -3).join('\n')console.error(err)return error(outputEl.value, section)}try {r###lt = fn(...args)console.log('r###lt:', r###lt)} catch(err) {const offset = lineOffset();const fixLineNumber = line => line.replace(/(\d+):(\d+)\)$/, (match, line, col) => `${line - offset}:${col})`)outputEl.value = err.stack.split('\n').slice(0, -2).map(x => x.replace(/eval at [^)]*\), <[^>]*>:/, '')).map(fixLineNumber).join('\n')console.error(err)return error(outputEl.value, section)}const output = serialize(r###lt)outputEl.value = outputif (output === expected) {success('Accepted', section)} else {error('Wrong Answer', section)console.error(`Failed:\nExpected: ${expected}\nOutput: ${output}`)}}))}function runAll(sections) {for(const section of sections) run(section)}function clear(section) {const outputEl = section.querySelector('.output')outputEl.classList.remove('error')outputEl.classList.remove('success')const messageEl = window.messageElmessageEl.innerText = ''messageEl.classList.remove('info')messageEl.classList.remove('error')messageEl.classList.remove('success')}function success(msg, section) {const outputEl = section.querySelector('.output')outputEl.classList.add('success')const messageEl = window.messageElmessageEl.innerText += msg + '\n'messageEl.classList.add('success')}function info(msg, section) {console.log(msg)const messageEl = window.messageElmessageEl.innerText += msg + '\n'messageEl.classList.add('info')}function error(msg, section) {const outputEl = section.querySelector('.output')outputEl.classList.add('error')const messageEl = window.messageElmessageEl.innerText += msg + '\n'messageEl.classList.add('error')}function serialize(r###lt) {return JSON.stringify(r###lt, (k, v) => {if (Number.isNaN(v) || v === Infinity || v === -Infinity || v === undefined || typeof v === 'bigint') return '' + vreturn v})}function parseArg(arg) {return JSON.parse(arg.trim())}function createButton(text, onClick) {const btn = document.createElement('button')btn.innerText = textbtn.addEventListener('click', onClick)return btn}function createData(labelText, str = '') {const div = document.createElement('div');const p = document.createElement('p')p.classList.add('title')const label = document.createElement('label')label.innerText = labelTextp.appendChild(label);div.appendChild(p);const textarea = document.createElement('textarea')textarea.setAttribute('class', labelText.toLowerCase())textarea.value = str;textarea.addEventListener('blur', () => saveCases())div.appendChild(textarea)return div}function parse(text) {const r = /(?:输入|Input)[::]([\s\S]*?)(?:输出|Output)[::][\s\n]*(?:.*:)?(.*)(\n|$)/igconst ans = []let matchwhile(match = r.exec(text)) {const [, input, expected] = matchans.push({input: parseInput(input.trim()),expected: parseExpected(expected)})}return ans}function parseExpected(expected) {try {return JSON.stringify(JSON.parse(expected))} catch (err) {return expected}}function parseInput(input) {const args = []const pair = {"'": "'",'"': '"','[': ']','{': '}','(': ')'}let state = 'input'let stacklet argfor(let i = 0; i < input.length; i++) {const char = input.charAt(i)if (state === 'input') {if (char === '=') {state = 'expr'arg = ''stack = []}} else if (state === 'expr') {if ('"\'[]{}()'.includes(char) && input[i - 1] !== '\\') {if (pair[stack[stack.length - 1]] === char) stack.pop()else stack.push(char)arg += char} else if (stack.length) {arg += char} else if ((char === ',' || char === '\n') && stack.length === 0) {state = 'input'args.push(arg)arg = ''} else {arg += char}}}if (arg === undefined) args.push(input)else if (arg) args.push(arg)return args.map(x => x.split('\n').map(l => l.trim()).join(' ').trim()).join('\n')}})();