├── .babelrc ├── .github └── ISSUE_TEMPLATE │ └── bug_report.md ├── .gitignore ├── README.md ├── content-script.css ├── content-script.js ├── deployment-zip-maker.sh ├── docs ├── HowToGetAPIKey.md ├── LICENSE └── termsNCondition.md ├── icons ├── close_eye.png ├── eye_open.png ├── github.png ├── linkedin.png ├── logo-128.png ├── logo-96.png ├── logo.png └── mail.png ├── images ├── api-key-img.png ├── cohere-login-page.png ├── lock-hide.gif ├── logoFull.png ├── solve-highlight.gif ├── ui-1280x800-dark.png ├── ui-640x400.png ├── ui-dark-new.png ├── ui-dark.png ├── ui.png ├── ui1-m.png ├── ui2-1280x800.png ├── ui2-m.png ├── ui3-m.png ├── ui3.png ├── ui4-1280x800.png └── ui4.png ├── manifest-chrome.json ├── manifest-firefox.json ├── popup.css ├── popup.css.map ├── popup.html ├── popup.js ├── popup.scss ├── scripts ├── constants.js ├── content-script-main.js ├── debugger.js ├── feature-strategies.js ├── message-publisher.js ├── mode.js ├── mutation-observer.js ├── page-checker.js ├── popup-main.js ├── service-worker-main.js ├── strategies │ ├── base-strategy.js │ ├── coding-area-strategy.js │ ├── contest-strategy.js │ ├── problem-set-strategy.js │ └── strategy-factory.js └── utils.js └── service-worker.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "env" 4 | ] 5 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 1. Is there any specific feature that is not working or the entire extension failed? 11 | 12 | 2. Which version of extension are you using? 13 | 14 | 3. Which browser are you using? Have you tried changing it (As the extension is also available for firefox and edge) 15 | 16 | 4. On which url of leetcode issue is encountered? 17 | 18 | 5. Are you using the old or new UI of leetcode? 19 | 20 | 6. Please add any screenshots or additional information that might help 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # MacOS Files 2 | .DS_Store 3 | 4 | # TODO: Add More Relevant Ones 5 | popup.css.map 6 | 7 | # Deployment files 8 | deployment/ 9 | *.zip -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | logo full 2 |
3 | 4 | ![License: MIT](https://img.shields.io/badge/License-MIT-green.svg) 5 | 6 | **A cross-browser extension to improve :rocket: productivity on 7 | Leetcode**. 8 | 9 | It is a light-weight browser extension which can be pinned in menu bar. It boosts your productivity by hiding unnecessary visual elements from the screen, enabling you to focus on what is important. You can change your preferences form the popup menu and find out what best suits your needs. 10 | 11 |
12 | 13 |
14 | 15 |
16 | 17 |
18 | 19 | ## :heavy_check_mark: Features 20 | * Hide Difficulty 21 | * Hide Locked Problems 22 | * Highlight Solved Problems 23 | * Hide Solved Problems 24 | * Hide / Show Problems Table Columns (incl. Difficulty, Acceptance etc.) 25 | 26 | **Note**: The extension functions on the following URLs : 27 | * https://leetcode.com/problemset/ 28 | * https://leetcode.com/problems/* 29 | * https://leetcode.com/contest/ 30 | 31 | ## :star2: Installation 32 | ### Google Chrome 33 | * To install this extension from Chrome Web Store click here. 34 | 35 | * After installation, you should pin this extension to use its features. 36 | ### Mozilla Firefox 37 | * To install this extension from Firefox Browser Addons click here. 38 | 39 | ### Microsoft Edge 40 | * To install this extension from Microsoft Edge Addons click here. 41 | 42 |
43 | mozilla page action extension location 44 |
45 | 46 | * In case of Mozilla Firefox, the icon will appear in the address bar as this is a page specific extension, this makes sure the extension is running, only when you need it and not all the time. 47 | 48 | ## :crown: For developers 49 | 50 | ### 1. Clone the source code locally: 51 | 52 | Would really appreciate if you can :star: the repository 53 | 54 | ```sh 55 | $ git clone https://github.com/loveshdongre/leetcode-enhancer/ 56 | $ cd leetcode-enhancer 57 | ``` 58 | 59 | ### 2. Building the Extension locally 60 | 61 | 1. Install Live Sass Compiler (Ritwick Dey) extension on VS code or any other compiler of you choice and make sure before testing the compiler is watching and converting you scss file into css if making any changes in scss file. It is required to convert the file popup.scss to popup.css only. 62 | 2. Install browserify globally 63 | `npm i browserify -g` 64 | 3. Once code changes are done run `deployment-zip-maker.sh` file. 65 | >a. This take content-script-main.js, popup-main.js and service-worker-main.js and convert them to content-script.js, popup.js and service-worker.js respectively. 66 | 67 | >b. Creates the `deployment` folder which will be used to load the extension in the browser 68 | 69 | ### 3. Load Extension in the Browser 70 | #### Google Chrome 71 | * Go to `chrome://extensions/` from url search and enable `Developer mode` 72 | * Click on `load unpacked` and select the folder containing `manifest.json` file 73 | * Pin this extension to use it. 74 | 75 | #### Mozilla Firefox 76 | * Go to `about:debugging` from url search and click on `This Firefox` (if you are not already there) 77 | * Click on `Load Temporary Add-on...` and select the `manifest.json` file 78 | * The addon will appear next to the address bar only when you are on leetcode 79 | 80 | #### Microsoft Edge 81 | * follow steps in this [link](https://www.windowscentral.com/how-install-non-store-extensions-microsoft-edge) 82 | 83 | 84 | ## :file_folder: File Description 85 | - .github: Hidden directory that contains git template for submitting an issue. 86 | - deployment: It's gets generated when user run file `deployment-zip-maker.sh`. It is used to build browser specific directory for testing and creating zip for deployment in extension app stores. 87 | - docs: Contains terms and condition, Licence and guide to obtain API key of Cohere 88 | - icons: contains app logo, custom checkmark logos, etc 89 | - images: contains display images for web stores 90 | - scripts: this is the main source folder where all the scripts are present. This code is mostly modified for development. 91 | - .babelrc: babel configuration file 92 | - .gitignore: files ignored by git 93 | - content-script.css: for injecting custom style sheet. 94 | - deployment-zip-maker.sh: bash file to create zip file of the only files important for deployment 95 | - manifest.json: Heart of the extension which contains all the configurations. (tip: understand this first) 96 | - popup.css: style sheet for extension pop 97 | - popup.css.map: intermediate file generated while converting .scss file to .css file with Live Sass Compiler extension (Vscode) 98 | - popup.html: html file for extension pop 99 | - popup.js: js file to handle changes like storaging checkbox preferences. 100 | - popup.scss: Scss file for styling popup.html file. 101 | - README.md: Guide for the entire extension 102 | - service-worker.js (background script): One important function is listens event from the content-script and enables extension icon in the browser 103 | 104 | ## :handshake: Contribution 105 | Contributions are always welcomed, here is what you need to do: 106 | 1. Work on an existing issue or create a new one [here](https://github.com/loveshdongre/leetcode-enhancer/issues) 107 | 2. Create a pull request linked to that issue 108 | 109 | Note: Please set up your [git config](https://git-scm.com/book/en/v2/Customizing-Git-Git-Configuration) for user and email so your name gets updated as a contributor 110 | 111 | Know more about [pull request](https://docs.github.com/en/free-pro-team@latest/github/collaborating-with-issues-and-pull-requests/about-pull-requests). 112 | 113 | ## :pencil: Feedback 114 | In order to add more useful features and keep the extension bug free your feedback is important. It will be really appreciated, if you could take out some time to write a review so that this extension can be improved ASAP. 115 | 116 | * Google Chrome users can leave a feedback [here](https://chrome.google.com/webstore/detail/leetcode-enhancer/gcmncppaaebldbkgkcbojghpmpjkdlmp) 117 | * Mozilla Firefox users can leave a feedback [here](https://addons.mozilla.org/en-US/firefox/addon/leetcode-enhancer/) 118 | 119 | 120 | ## :earth_asia: Development Env Tools 121 | * MacOS Sequoia: 15.4.1 122 | * Mozilla Firefox: 138.0.1 123 | * Google Chrome / Brave / Edge: (Chromium) 136.0.7103.93 124 | 125 | ## :clipboard: License 126 | MIT © Lovesh Dongre 127 | -------------------------------------------------------------------------------- /content-script.css: -------------------------------------------------------------------------------- 1 | .add-bg-light_leetcode-enhancer { 2 | background-color: #a1ffa5 !important; 3 | } 4 | 5 | .add-bg-dark_leetcode-enhancer { 6 | background-color: rgb(115, 115, 115) !important; 7 | } 8 | 9 | .hide_leetcode-enhancer { 10 | display: none !important; 11 | } 12 | 13 | .visible_leetcode-enhancer { 14 | display: block !important; 15 | } 16 | 17 | .remove_diff_color_leetcode-enhancer { 18 | color: black !important; 19 | background-color: grey !important; 20 | border-color: grey !important; 21 | } 22 | 23 | .color-alfa0 { 24 | color: rgba(0, 0, 0, 0); 25 | } 26 | 27 | .blur { 28 | border-color: gray !important; 29 | color: gray !important; 30 | background-color: white !important; 31 | } -------------------------------------------------------------------------------- /content-script.js: -------------------------------------------------------------------------------- 1 | (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i el.classList[checked ? 'remove' : 'add']('hide_leetcode-enhancer')); 328 | } 329 | } 330 | 331 | hideDiffOfSimilarProb(checked) { 332 | const allAnchors = document.querySelectorAll('a'); 333 | if (!allAnchors || allAnchors.length === 0) return; 334 | 335 | const urlProb = "https://leetcode.com/problems/"; 336 | const curUrl = urlProb + window.location.pathname.split("/")[2] + "/"; 337 | 338 | allAnchors.forEach(anchor => { 339 | if (anchor.href.startsWith(urlProb) && !anchor.href.startsWith(curUrl)) { 340 | const diffElement = anchor.parentElement?.parentElement?.parentElement?.nextElementSibling; 341 | if (diffElement) { 342 | diffElement.classList[checked ? 'remove' : 'add']('hide_leetcode-enhancer'); 343 | } 344 | } 345 | }); 346 | } 347 | 348 | hideStatus(checked) { 349 | const solvedMarkParent = document.querySelector('[data-track-load="description_content"]')?.parentNode?.previousSibling?.previousSibling?.lastChild; 350 | if (solvedMarkParent && solvedMarkParent.classList.contains('text-body')) { 351 | solvedMarkParent.classList[checked ? 'remove' : 'add']('hide_leetcode-enhancer'); 352 | } 353 | } 354 | 355 | hideAcceptance(checked) { 356 | const parentElement = document.querySelector('[data-track-load="description_content"]')?.parentElement?.nextSibling; 357 | if (!parentElement) return; 358 | 359 | const acceptanceRateElement = [...parentElement.children] 360 | .find(child => child.textContent.toLowerCase().includes('acceptance'))?.lastElementChild; 361 | 362 | if (acceptanceRateElement) { 363 | acceptanceRateElement.classList[checked ? 'remove' : 'add']('hide_leetcode-enhancer'); 364 | } 365 | } 366 | 367 | hideSave(checked) { 368 | const saveButton = document.querySelector("svg[data-icon='star']"); 369 | if (saveButton) { 370 | saveButton.classList[checked ? 'remove' : 'add']('hide_leetcode-enhancer'); 371 | } 372 | } 373 | 374 | toggleByColName(colName, checked) { 375 | if (colName === 'difficulty') { 376 | this.hideSolvedDiff(checked); 377 | this.hideDiffOfSimilarProb(checked); 378 | } else if (colName === 'status') { 379 | this.hideStatus(checked); 380 | } else if (colName === 'acceptance') { 381 | this.hideAcceptance(checked); 382 | } else if(colName === 'save') { 383 | this.hideSave(checked); 384 | } 385 | } 386 | 387 | getUserCode() { 388 | const codeEditor = document.querySelector('div.editor-scrollable'); 389 | return codeEditor ? codeEditor.textContent : ''; 390 | } 391 | } 392 | 393 | module.exports = CodingAreaStrategy; 394 | },{"./base-strategy":9}],11:[function(require,module,exports){ 395 | const FeatureStrategy = require('./base-strategy'); 396 | 397 | class ContestStrategy extends FeatureStrategy { 398 | hideDiffFromContest(checked) { 399 | 400 | // old UI 401 | const diffLabel = document.querySelectorAll('.contest-question-info .list-group .list-group-item:nth-child(5) .label')[0]; 402 | if (diffLabel) { 403 | diffLabel.style.visibility = checked ? 'visible_leetcode-enhancer' : 'hidden'; 404 | return; 405 | } 406 | 407 | // new UI 408 | const easyDiffLabel = document.querySelector('.text-difficulty-easy'); 409 | if (easyDiffLabel) { 410 | easyDiffLabel.classList[checked ? 'remove' : 'add']('hide_leetcode-enhancer'); 411 | } 412 | 413 | const mediumDiffLabel = document.querySelector('.text-difficulty-medium'); 414 | if (mediumDiffLabel) { 415 | mediumDiffLabel.classList[checked ? 'remove' : 'add']('hide_leetcode-enhancer'); 416 | } 417 | 418 | const hardDiffLabel = document.querySelector('.text-difficulty-hard'); 419 | if (hardDiffLabel) { 420 | hardDiffLabel.classList[checked ? 'remove' : 'add']('hide_leetcode-enhancer'); 421 | } 422 | 423 | // Handle multiple elements in sidebar 424 | const easyDiffLabelsSideBar = document.querySelectorAll('.text-sd-easy'); 425 | easyDiffLabelsSideBar.forEach(label => { 426 | label.classList[checked ? 'remove' : 'add']('hide_leetcode-enhancer'); 427 | }); 428 | 429 | const mediumDiffLabelsSideBar = document.querySelectorAll('.text-sd-medium'); 430 | mediumDiffLabelsSideBar.forEach(label => { 431 | label.classList[checked ? 'remove' : 'add']('hide_leetcode-enhancer'); 432 | }); 433 | 434 | const hardDiffLabelsSideBar = document.querySelectorAll('.text-sd-hard'); 435 | hardDiffLabelsSideBar.forEach(label => { 436 | label.classList[checked ? 'remove' : 'add']('hide_leetcode-enhancer'); 437 | }); 438 | } 439 | 440 | toggleByColName(colName, checked) { 441 | if (colName === 'difficulty') { 442 | this.hideDiffFromContest(checked); 443 | } 444 | } 445 | } 446 | 447 | module.exports = ContestStrategy; 448 | },{"./base-strategy":9}],12:[function(require,module,exports){ 449 | const FeatureStrategy = require('./base-strategy'); 450 | 451 | class NewProblemSetStrategy extends FeatureStrategy { 452 | 453 | hideLockedProblems(checked) { 454 | const temp = document.querySelectorAll('a[id]'); 455 | 456 | temp.forEach(row => { 457 | const cell = row.querySelector(`div>div:nth-child(1)>svg`); 458 | if (cell && cell.getAttribute('data-icon') == 'lock') { 459 | row.classList[checked ? 'remove' : 'add']('hide_leetcode-enhancer'); 460 | } 461 | }); 462 | } 463 | 464 | highlightSolvedProblems(checked) { 465 | const temp = document.querySelectorAll('a[id]'); 466 | const isDarkMode = document.querySelector('html').classList.contains('dark'); 467 | temp.forEach(row => { 468 | const cell = row.querySelector(`div>div:nth-child(1)>svg`); 469 | if (cell && cell.getAttribute('data-icon') == 'check') { 470 | if(!checked) { 471 | row.classList.add(isDarkMode ? 'add-bg-dark_leetcode-enhancer' : 'add-bg-light_leetcode-enhancer'); 472 | } 473 | else { 474 | row.classList.remove('add-bg-dark_leetcode-enhancer'); 475 | row.classList.remove('add-bg-light_leetcode-enhancer'); 476 | } 477 | } 478 | }); 479 | } 480 | 481 | hideSolvedProb(checked) { 482 | const temp = document.querySelectorAll('a[id]'); 483 | 484 | temp.forEach(row => { 485 | const cell = row.querySelector(`div>div:nth-child(1)>svg`); 486 | if (cell && cell.getAttribute('data-icon') == 'check') { 487 | row.classList[checked ? 'remove' : 'add']('hide_leetcode-enhancer'); 488 | } 489 | }); 490 | } 491 | 492 | toggleByColName(colName, checked) { 493 | const temp = document.querySelectorAll('a[id]'); 494 | 495 | if(colName == 'status') { 496 | temp.forEach(row => { 497 | const cell = row.querySelector(`div>div:nth-child(1)`); 498 | if (cell) { 499 | cell.classList[checked ? 'remove' : 'add']('hide_leetcode-enhancer'); 500 | } 501 | }); 502 | } 503 | if(colName == 'acceptance') { 504 | temp.forEach(row => { 505 | const cell = row.querySelector(`div>div:nth-child(2)>div:nth-child(2)`); 506 | if (cell) { 507 | cell.classList[checked ? 'remove' : 'add']('hide_leetcode-enhancer'); 508 | } 509 | }); 510 | } 511 | else if(colName == 'difficulty') { 512 | temp.forEach(row => { 513 | const cell = row.querySelector(`div>div:nth-child(2)>p:nth-child(3)`); 514 | if (cell) { 515 | cell.classList[checked ? 'remove' : 'add']('hide_leetcode-enhancer'); 516 | } 517 | }); 518 | } 519 | else if(colName == 'frequency') { 520 | temp.forEach(row => { 521 | const cell = row.querySelector(`div>div:nth-child(3)`); 522 | if (cell) { 523 | cell.classList[checked ? 'remove' : 'add']('hide_leetcode-enhancer'); 524 | } 525 | }); 526 | } 527 | else if(colName == 'save') { 528 | temp.forEach(row => { 529 | const cell = row.querySelector(`div>div:nth-child(4)>div`); 530 | if (cell) { 531 | cell.classList[checked ? 'remove' : 'add']('hide_leetcode-enhancer'); 532 | } 533 | }); 534 | } 535 | } 536 | } 537 | 538 | module.exports = NewProblemSetStrategy; 539 | },{"./base-strategy":9}],13:[function(require,module,exports){ 540 | const { Mode } = require('../mode.js'); 541 | const ProblemSetStrategy = require('./problem-set-strategy.js'); 542 | const CodingAreaStrategy = require('./coding-area-strategy.js'); 543 | const ContestStrategy = require('./contest-strategy.js'); 544 | 545 | class FeatureStrategyFactory { 546 | static getStrategy(mode) { 547 | switch (mode) { 548 | case Mode.PROBLEM_SET: 549 | return new ProblemSetStrategy(); 550 | case Mode.CODING_AREA: 551 | return new CodingAreaStrategy(); 552 | case Mode.CONTEST: 553 | return new ContestStrategy(); 554 | default: 555 | console.log('No strategy found for mode:', mode); 556 | return null; 557 | } 558 | } 559 | } 560 | 561 | module.exports = FeatureStrategyFactory; 562 | },{"../mode.js":6,"./coding-area-strategy.js":10,"./contest-strategy.js":11,"./problem-set-strategy.js":12}],14:[function(require,module,exports){ 563 | const print = require('./debugger.js'); 564 | let browser = window.browser || window.chrome; 565 | 566 | /** 567 | * Checks if an object is iterable. 568 | * @param {Object} obj - The object to check. 569 | * @returns {boolean} - True if the object is iterable, false otherwise. 570 | */ 571 | function isIterable(obj) { 572 | if(obj != null && typeof obj[Symbol.iterator] === 'function') 573 | return true; 574 | print(`${JSON.stringify(obj)} is not iterable`) 575 | } 576 | 577 | function storeDataWithObjectWrapping(key, value) { 578 | const data = {}; 579 | data[key] = value; 580 | storeData(data); 581 | } 582 | 583 | function storeData(data) { 584 | browser.storage.local.set(data); 585 | } 586 | 587 | 588 | function getData(key, callback) { 589 | try { 590 | browser.storage.local.get([key], result => callback(result[key])); 591 | } 592 | catch (err) { 593 | print("Error while retrieving key"); 594 | } 595 | } 596 | 597 | async function getDataAsUint8Array(key) { 598 | return new Uint8Array((await browser.storage.local.get(key))[key] || []); 599 | } 600 | 601 | // Export the isIterable function using CommonJS syntax 602 | module.exports = {isIterable, storeDataWithObjectWrapping, getData, getDataAsUint8Array, storeData}; 603 | },{"./debugger.js":3}]},{},[2]); 604 | -------------------------------------------------------------------------------- /deployment-zip-maker.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "Deleting existing deployment directory..." 4 | rm -rf deployment 5 | 6 | # Run browserify to bundle the scripts 7 | echo "Running browserify..." 8 | 9 | # Run the Browserify command 10 | browserify scripts/content-script-main.js -o content-script.js 11 | browserify scripts/popup-main.js -o popup.js 12 | browserify scripts/service-worker-main.js -o service-worker.js 13 | 14 | # Confirm success or output an error if Browserify is not installed 15 | if [ $? -eq 0 ]; then 16 | echo "Browserify succeeded" 17 | else 18 | echo "Error: Browserify command failed. Ensure Browserify is installed and try again." 19 | fi 20 | 21 | # Create deployment directories 22 | echo "Creating deployment directories..." 23 | mkdir -p deployment/chrome 24 | mkdir -p deployment/firefox 25 | 26 | # Define files and folders to copy 27 | FOLDERS=("icons") 28 | FILES=("content-script.css" "content-script.js" "service-worker.js" "popup.html" "popup.css" "popup.js") 29 | 30 | # Create Chrome deployment 31 | echo "Creating Chrome deployment..." 32 | # Copy folders 33 | for folder in "${FOLDERS[@]}"; do 34 | cp -r "$folder" deployment/chrome/ 35 | done 36 | 37 | # Copy files 38 | for file in "${FILES[@]}"; do 39 | cp "$file" deployment/chrome/ 40 | done 41 | 42 | # Copy Chrome manifest 43 | cp manifest-chrome.json deployment/chrome/manifest.json 44 | 45 | # Verify Chrome deployment 46 | echo "Verifying Chrome deployment..." 47 | ls -la deployment/chrome/ 48 | echo "Chrome deployment files copied successfully!" 49 | 50 | # Create Firefox deployment 51 | echo "Creating Firefox deployment..." 52 | # Copy folders 53 | for folder in "${FOLDERS[@]}"; do 54 | cp -r "$folder" deployment/firefox/ 55 | done 56 | 57 | # Copy files 58 | for file in "${FILES[@]}"; do 59 | cp "$file" deployment/firefox/ 60 | done 61 | 62 | # Copy Firefox manifest 63 | cp manifest-firefox.json deployment/firefox/manifest.json 64 | 65 | # Verify Firefox deployment 66 | echo "Verifying Firefox deployment..." 67 | ls -la deployment/firefox/ 68 | echo "Firefox deployment files copied successfully!" 69 | 70 | # Create zip files 71 | echo "Creating zip files..." 72 | cd deployment 73 | zip -r chrome.zip chrome/ 74 | cd firefox 75 | zip -r ../firefox.zip ./* 76 | cd ../.. 77 | 78 | echo "Deployment packages created in deployment directory!" 79 | 80 | echo " 81 | **********************[IMPORTANT]************************** 82 | *********************************************************** 83 | *********************************************************** 84 | Hope you have set debugger value to FALSE before deploying 85 | *********************************************************** 86 | *********************************************************** 87 | **********************[IMPORTANT]**************************" 88 | -------------------------------------------------------------------------------- /docs/HowToGetAPIKey.md: -------------------------------------------------------------------------------- 1 | # Guide to Obtaining an API Key from Cohere 2 | 3 | To integrate your Cohere API key with the extension, follow the steps below. Please note that a trial API key may already be generated upon account creation. 4 | 5 | ## Steps 6 | 7 | 1. **Access the Cohere Dashboard** 8 | - Visit [Cohere’s login page](https://dashboard.cohere.com/welcome/login) and log in to your account. 9 | - If you are a new user, register by providing the necessary details to create an account. 10 | 11 | ![alt text](../images/cohere-login-page.png) 12 | 13 | 2. **Complete Account Profile (if required)** 14 | - After logging in, you may be prompted to add additional profile details. Complete any required fields as instructed on the dashboard. 15 | 16 | 3. **Navigate to the API Key Section** 17 | - On the left-hand sidebar, locate and click on the **API Key** section. This is where you can view, manage, or generate API keys associated with your account. 18 | 19 | 4. **Retrieve the Trial API Key** 20 | - If a trial API key has already been generated, it will appear in the API Key section. Locate the key marked for trial usage and **copy the API key** for use with the extension. 21 | - If no API key is visible, you may have the option to generate one by following any prompts in the API Key section. 22 | 23 | ![alt text](../images/api-key-img.png) 24 | This API key enables you to connect to Cohere’s services with limited free access, subject to any usage limits or charges that may apply. 25 | 26 | -------------------------------------------------------------------------------- /docs/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Lovesh Dongre 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/termsNCondition.md: -------------------------------------------------------------------------------- 1 | # Terms and Conditions 2 | 3 | ## 1. Introduction 4 | Thank you for using our browser extension. By obtaining and using an API key from the [Cohere](https://cohere.com/) for use with this extension, you agree to the following terms and conditions. 5 | 6 | ## 2. API Key Usage 7 | - The API key you obtain from Cohere is **personal** to you and should be used responsibly. 8 | - This extension allows you to utilize your Cohere API key to interact with Cohere's services directly. 9 | 10 | ## 3. Liability for Usage Charges 11 | - **You are solely responsible for charges if any** that may be incurred through the usage of your Cohere API key. 12 | - We recommend reviewing Cohere's pricing and usage limits to avoid unexpected charges. 13 | - This extension does not assume any responsibility for charges resulting from API usage. 14 | 15 | ## 4. Data Privacy 16 | - We do not store, collect, or have access to your API key or any data sent or received through Cohere’s services. 17 | - Your API key and usage data are handled directly by Cohere, and we encourage you to review Cohere's privacy policy regarding their data handling practices. 18 | 19 | ## 5. Changes to Terms 20 | - We reserve the right to update these terms and conditions at any time. Continued use of the extension indicates acceptance of the updated terms. 21 | 22 | ## 6. Contact 23 | For any questions regarding these terms and conditions, please reach out to us via our support channels. 24 | 25 | **Last Updated:** 16 Oct 2024 26 | -------------------------------------------------------------------------------- /icons/close_eye.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loveshdongre/leetcode-enhancer/a743a31cd9d649253295880673a70acbb7cd2def/icons/close_eye.png -------------------------------------------------------------------------------- /icons/eye_open.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loveshdongre/leetcode-enhancer/a743a31cd9d649253295880673a70acbb7cd2def/icons/eye_open.png -------------------------------------------------------------------------------- /icons/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loveshdongre/leetcode-enhancer/a743a31cd9d649253295880673a70acbb7cd2def/icons/github.png -------------------------------------------------------------------------------- /icons/linkedin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loveshdongre/leetcode-enhancer/a743a31cd9d649253295880673a70acbb7cd2def/icons/linkedin.png -------------------------------------------------------------------------------- /icons/logo-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loveshdongre/leetcode-enhancer/a743a31cd9d649253295880673a70acbb7cd2def/icons/logo-128.png -------------------------------------------------------------------------------- /icons/logo-96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loveshdongre/leetcode-enhancer/a743a31cd9d649253295880673a70acbb7cd2def/icons/logo-96.png -------------------------------------------------------------------------------- /icons/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loveshdongre/leetcode-enhancer/a743a31cd9d649253295880673a70acbb7cd2def/icons/logo.png -------------------------------------------------------------------------------- /icons/mail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loveshdongre/leetcode-enhancer/a743a31cd9d649253295880673a70acbb7cd2def/icons/mail.png -------------------------------------------------------------------------------- /images/api-key-img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loveshdongre/leetcode-enhancer/a743a31cd9d649253295880673a70acbb7cd2def/images/api-key-img.png -------------------------------------------------------------------------------- /images/cohere-login-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loveshdongre/leetcode-enhancer/a743a31cd9d649253295880673a70acbb7cd2def/images/cohere-login-page.png -------------------------------------------------------------------------------- /images/lock-hide.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loveshdongre/leetcode-enhancer/a743a31cd9d649253295880673a70acbb7cd2def/images/lock-hide.gif -------------------------------------------------------------------------------- /images/logoFull.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loveshdongre/leetcode-enhancer/a743a31cd9d649253295880673a70acbb7cd2def/images/logoFull.png -------------------------------------------------------------------------------- /images/solve-highlight.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loveshdongre/leetcode-enhancer/a743a31cd9d649253295880673a70acbb7cd2def/images/solve-highlight.gif -------------------------------------------------------------------------------- /images/ui-1280x800-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loveshdongre/leetcode-enhancer/a743a31cd9d649253295880673a70acbb7cd2def/images/ui-1280x800-dark.png -------------------------------------------------------------------------------- /images/ui-640x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loveshdongre/leetcode-enhancer/a743a31cd9d649253295880673a70acbb7cd2def/images/ui-640x400.png -------------------------------------------------------------------------------- /images/ui-dark-new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loveshdongre/leetcode-enhancer/a743a31cd9d649253295880673a70acbb7cd2def/images/ui-dark-new.png -------------------------------------------------------------------------------- /images/ui-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loveshdongre/leetcode-enhancer/a743a31cd9d649253295880673a70acbb7cd2def/images/ui-dark.png -------------------------------------------------------------------------------- /images/ui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loveshdongre/leetcode-enhancer/a743a31cd9d649253295880673a70acbb7cd2def/images/ui.png -------------------------------------------------------------------------------- /images/ui1-m.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loveshdongre/leetcode-enhancer/a743a31cd9d649253295880673a70acbb7cd2def/images/ui1-m.png -------------------------------------------------------------------------------- /images/ui2-1280x800.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loveshdongre/leetcode-enhancer/a743a31cd9d649253295880673a70acbb7cd2def/images/ui2-1280x800.png -------------------------------------------------------------------------------- /images/ui2-m.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loveshdongre/leetcode-enhancer/a743a31cd9d649253295880673a70acbb7cd2def/images/ui2-m.png -------------------------------------------------------------------------------- /images/ui3-m.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loveshdongre/leetcode-enhancer/a743a31cd9d649253295880673a70acbb7cd2def/images/ui3-m.png -------------------------------------------------------------------------------- /images/ui3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loveshdongre/leetcode-enhancer/a743a31cd9d649253295880673a70acbb7cd2def/images/ui3.png -------------------------------------------------------------------------------- /images/ui4-1280x800.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loveshdongre/leetcode-enhancer/a743a31cd9d649253295880673a70acbb7cd2def/images/ui4-1280x800.png -------------------------------------------------------------------------------- /images/ui4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loveshdongre/leetcode-enhancer/a743a31cd9d649253295880673a70acbb7cd2def/images/ui4.png -------------------------------------------------------------------------------- /manifest-chrome.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 3, 3 | "name": "Leetcode Enhancer", 4 | "version": "1.72", 5 | "author": "Lovesh Dongre", 6 | "description": "A browser extension to improve productivity on Leetcode.", 7 | "icons": { 8 | "48": "icons/logo.png", 9 | "96": "icons/logo-96.png" 10 | }, 11 | "content_scripts": [{ 12 | "matches": ["https://leetcode.com/*"], 13 | "css": ["content-script.css"], 14 | "js": ["content-script.js"] 15 | }], 16 | 17 | "background": { 18 | "service_worker": "service-worker.js" 19 | }, 20 | "action": { 21 | "browser_style": true, 22 | "default_icon": { 23 | "48": "icons/logo.png", 24 | "96": "icons/logo-96.png" 25 | }, 26 | "default_title": "Leetcode Enhancer", 27 | "default_popup": "popup.html", 28 | "theme_icons": [{ 29 | "light": "icons/logo.png", 30 | "dark": "icons/logo.png", 31 | "size": 48 32 | }], 33 | "show_matches": ["https://leetcode.com/*"] 34 | }, 35 | "permissions": ["activeTab", "storage"], 36 | "optional_host_permissions": ["https://api.cohere.ai/*"] 37 | } -------------------------------------------------------------------------------- /manifest-firefox.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 3, 3 | "name": "Leetcode Enhancer", 4 | "version": "1.72", 5 | "author": "Lovesh Dongre", 6 | "description": "A browser extension to improve productivity on Leetcode.", 7 | "icons": { 8 | "48": "icons/logo.png", 9 | "96": "icons/logo-96.png" 10 | }, 11 | "content_scripts": [{ 12 | "matches": ["https://leetcode.com/*"], 13 | "css": ["content-script.css"], 14 | "js": ["content-script.js"] 15 | }], 16 | 17 | "background": { 18 | "scripts": ["service-worker.js"] 19 | }, 20 | "action": { 21 | "default_icon": { 22 | "48": "icons/logo.png", 23 | "96": "icons/logo-96.png" 24 | }, 25 | "default_title": "Leetcode Enhancer", 26 | "default_popup": "popup.html", 27 | "theme_icons": [{ 28 | "light": "icons/logo.png", 29 | "dark": "icons/logo.png", 30 | "size": 48 31 | }] 32 | }, 33 | "permissions": ["storage"], 34 | "optional_host_permissions": ["https://api.cohere.ai/*"], 35 | "browser_specific_settings": { 36 | "gecko": { 37 | "id": "{ca2d6de6-3a5e-4ab3-afae-2ef6e1f241f0}" 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /popup.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | margin: 0; 4 | padding: 0; 5 | outline: 0; 6 | vertical-align: baseline; 7 | background: transparent; 8 | } 9 | 10 | .wrapper { 11 | max-width: 1000px; 12 | min-width: 280px; 13 | } 14 | 15 | .wrapper .link { 16 | font-size: 12px; 17 | text-align: center; 18 | } 19 | .wrapper .brand { 20 | background-color: rgb(40, 42, 46); 21 | font-size: 20px; 22 | padding: 10px; 23 | color: rgba(255, 255, 255, 0.9); 24 | box-shadow: rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0.25) 0px 4px 4px 0px, rgba(0, 0, 0, 0.3) 0px 1px 2px 0px; 25 | } 26 | .wrapper .brand .title { 27 | text-decoration: none; 28 | color: rgba(255, 255, 255, 0.9); 29 | } 30 | .wrapper .brand .title:hover { 31 | color: grey; 32 | } 33 | .wrapper .logo { 34 | height: 50px; 35 | width: 50px; 36 | border-radius: 10px; 37 | } 38 | .wrapper .github { 39 | margin-top: 10px; 40 | filter: invert(1); 41 | } 42 | .wrapper .brand span { 43 | position: absolute; 44 | padding-left: 10px; 45 | } 46 | .wrapper .notice { 47 | padding: 10px; 48 | color: white; 49 | line-height: 15px; 50 | background-color: rgb(60, 60, 61); 51 | font-size: 12px; 52 | } 53 | .wrapper .options { 54 | padding: 10px; 55 | background-color: rgb(34, 35, 37); 56 | color: rgba(255, 255, 255, 0.9); 57 | } 58 | .wrapper .options div { 59 | margin-top: 10px; 60 | margin-bottom: 10px; 61 | } 62 | .wrapper .options div input { 63 | float: right; 64 | } 65 | .wrapper .options div input:hover { 66 | cursor: pointer; 67 | } 68 | .wrapper .options input[type=checkbox] { 69 | display: none; 70 | } 71 | .wrapper .footer { 72 | background-color: rgb(40, 42, 46); 73 | padding: 5px 10px; 74 | } 75 | .wrapper .footer a { 76 | text-decoration: none; 77 | } 78 | .wrapper .footer span { 79 | position: relative; 80 | color: rgba(255, 255, 255, 0.9); 81 | } 82 | .wrapper .footer #fBtn span { 83 | text-decoration: underline; 84 | } 85 | .wrapper .footer #deBtn { 86 | float: right; 87 | cursor: pointer; 88 | } 89 | .wrapper .details { 90 | color: rgba(255, 255, 255, 0.9); 91 | background-color: rgb(40, 42, 46); 92 | padding: 5px 10px; 93 | line-height: 15px; 94 | box-shadow: rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0.25) 0px 4px 4px 0px, rgba(0, 0, 0, 0.3) 0px 1px 2px 0px; 95 | } 96 | .wrapper .details a { 97 | color: rgba(255, 255, 255, 0.9); 98 | } 99 | .wrapper .creator { 100 | color: rgba(255, 255, 255, 0.9); 101 | background-color: rgb(40, 42, 46); 102 | padding: 5px 10px; 103 | text-align: center; 104 | } 105 | .wrapper .creator .linkedin { 106 | height: 20px; 107 | width: 20px; 108 | margin-bottom: -5px; 109 | } 110 | .wrapper .contributors { 111 | padding: 5px 10px; 112 | text-align: center; 113 | } 114 | .wrapper .contributors a { 115 | color: rgba(255, 255, 255, 0.9); 116 | } 117 | 118 | /* Checkbox UI enhancement */ 119 | .checkbox-label { 120 | display: inline-block; 121 | width: 1.2rem; 122 | height: 1.2rem; 123 | background-image: url("/icons/close_eye.png"); 124 | background-size: contain; 125 | } 126 | 127 | input[type=checkbox]:checked + .checkbox-label { 128 | background-image: url("/icons/eye_open.png"); 129 | } 130 | 131 | .option-div { 132 | width: 100%; 133 | display: flex; 134 | justify-content: space-between; 135 | } 136 | 137 | .ai-box { 138 | background-color: rgb(34, 35, 37); 139 | color: rgba(255, 255, 255, 0.9); 140 | padding: 10px; 141 | } 142 | .ai-box .ai-terms { 143 | margin-bottom: 5px; 144 | } 145 | .ai-box .ai-terms a { 146 | color: #F0F0F0; 147 | } 148 | .ai-box #api-key { 149 | width: 99%; 150 | margin-top: 5px; 151 | margin-bottom: 5px; 152 | } 153 | .ai-box .ai-inputs { 154 | margin-top: 5px; 155 | margin-bottom: 5px; 156 | } 157 | .ai-box .ai-getKey { 158 | font-size: 12px; 159 | text-decoration: underline; 160 | cursor: pointer; 161 | } 162 | .ai-box .ai-getKey :hover { 163 | color: rgb(14, 115, 222); 164 | -webkit-text-decoration: rgb(14, 115, 222); 165 | text-decoration: rgb(14, 115, 222); 166 | } 167 | .ai-box .ai-getKey a { 168 | color: #F0F0F0; 169 | } 170 | .ai-box .output { 171 | height: 300px; 172 | overflow: auto; 173 | border-radius: 5px; 174 | color: rgb(40, 42, 46); 175 | font-size: 12px; 176 | font-family: monospace; 177 | white-space: pre-wrap; /* Preserve both spaces and line breaks */ 178 | } 179 | .ai-box .ai-button { 180 | margin-left: 5px; 181 | } 182 | .ai-box .ai-button:hover { 183 | cursor: pointer; 184 | }/*# sourceMappingURL=popup.css.map */ -------------------------------------------------------------------------------- /popup.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["popup.scss","popup.css"],"names":[],"mappings":"AASA;;EAEI,SAAA;EACA,UAAA;EACA,UAAA;EACA,wBAAA;EACA,uBAAA;ACRJ;;ADWA;EACI,iBAAA;EACA,gBAAA;ACRJ;;ADYI;EACI,eAxBH;EAyBG,kBAAA;ACTR;ADYI;EACI,iCA3BE;EA4BF,eA/BH;EAgCG,aAAA;EACA,+BA3BE;EAuCF,uJAAA;ACrBR;ADWQ;EACI,qBAAA;EACA,+BA/BF;ACsBV;ADWY;EACI,WAAA;ACThB;ADoBI;EACI,YAAA;EACA,WAAA;EACA,mBAAA;AClBR;ADqBI;EACI,gBAAA;EACA,iBAAA;ACnBR;ADsBI;EACI,kBAAA;EACA,kBAAA;ACpBR;ADuBI;EACI,aAAA;EACA,YAAA;EACA,iBAAA;EACA,iCAlEG;EAmEH,eAvEH;ACkDL;ADwBI;EACI,aAAA;EACA,iCAzEG;EA0EH,+BAxEE;ACkDV;ADwBQ;EACI,gBAAA;EACA,mBAAA;ACtBZ;ADwBY;EACI,YAAA;ACtBhB;ADwBgB;EACI,eAAA;ACtBpB;AD2BQ;EACI,aAAA;ACzBZ;AD6BI;EACI,iCAhGE;EAiGF,iBAAA;AC3BR;AD6BQ;EACI,qBAAA;AC3BZ;AD8BQ;EACI,kBAAA;EACA,+BAtGF;AC0EV;AD+BQ;EACI,0BAAA;AC7BZ;ADoCQ;EACI,YAAA;EACA,eAAA;AClCZ;ADsCI;EACI,+BAxHE;EAyHF,iCA5HE;EA6HF,iBAAA;EACA,iBAAA;EACA,uJAAA;ACpCR;ADsCQ;EACI,+BA/HF;AC2FV;ADyCI;EACI,+BArIE;EAsIF,iCAzIE;EA0IF,iBAAA;EACA,kBAAA;ACvCR;ADyCQ;EACI,YAAA;EACA,WAAA;EACA,mBAAA;ACvCZ;AD2CI;EAKI,iBAAA;EACA,kBAAA;AC7CR;ADwCQ;EACI,+BAnJF;AC6GV;;AD+CA,4BAAA;AAEA;EACI,qBAAA;EACA,aAAA;EACA,cAAA;EACA,6CAAA;EACA,wBAAA;AC7CJ;;ADgDA;EACI,4CAAA;AC7CJ;;ADiDA;EACI,WAAA;EACA,aAAA;EACA,8BAAA;AC9CJ;;ADiDA;EACI,iCApLO;EAqLP,+BAnLM;EAoLN,aAAA;AC9CJ;ADgDI;EACI,kBAAA;AC9CR;ADgDQ;EACI,cA9LP;ACgJL;ADkDI;EACI,UAAA;EACA,eAAA;EACA,kBAAA;AChDR;ADmDI;EACI,eAAA;EACA,kBAAA;ACjDR;ADoDI;EACI,eA/MH;EAgNG,0BAAA;EACA,eAAA;AClDR;ADoDQ;EACI,wBA9MC;EA+MD,0CA/MC;UA+MD,kCA/MC;AC6Jb;ADqDQ;EACI,cAxNP;ACqKL;ADuDI;EACI,aAAA;EACA,cAAA;EACA,kBAAA;EACA,sBA/NE;EAgOF,eAlOH;EAmOG,sBAAA;EACA,qBAAA,EAAA,yCAAA;ACrDR;ADwDI;EACI,gBAAA;ACtDR;ADwDQ;EACI,eAAA;ACtDZ","file":"popup.css"} -------------------------------------------------------------------------------- /popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 |
13 | 14 | 15 | Leetcode 16 |
enhancer
17 | 18 | 19 | github 20 | 21 |
22 | 23 |
24 |
25 | 26 | 27 | 28 |
29 |
30 | 31 | 32 | 33 |
34 |
35 | 36 | 37 | 38 |
39 | 40 |
41 | Problem Set Table Columns 42 |
43 | 44 | 45 | 46 |
47 |
48 | 49 | 50 | 51 |
52 |
53 | 54 | 55 | 56 |
57 |
58 | 59 | 60 | 61 |
62 |
63 | 64 | 65 | 66 |
67 |
68 |
69 | 70 |
71 |
72 | Use AI to Find Time & Space Complexity 73 |
74 | 75 | 76 |
77 | 80 |
81 | 82 | 83 | 84 |
85 | 86 | 87 |
88 | 89 |
90 | 91 |
92 |
93 | 94 | 99 | 100 | 119 |
120 | 121 | 122 | 123 | 124 | 125 | -------------------------------------------------------------------------------- /popup.js: -------------------------------------------------------------------------------- 1 | (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i chb.addEventListener('change', onCheckboxChange)); 120 | } 121 | 122 | function loadSavedSettings() { 123 | getData(KEY_NAME_OPTIONS, updateCheckboxes); 124 | } 125 | 126 | function updateCheckboxes(options) { 127 | if (isIterable(options)) { 128 | options.forEach(option => { 129 | const chb = document.getElementById(option.optionName); 130 | chb.checked = option.checked; 131 | }); 132 | } 133 | } 134 | 135 | function onCheckboxChange() { 136 | browser.tabs.query({}, function(tabs) { 137 | const options = []; 138 | const checkboxes = document.querySelectorAll('input[name=settings]'); 139 | checkboxes.forEach(chb => options.push({ optionName: chb.value, checked: chb.checked })); 140 | storeDataWithObjectWrapping('options', options); 141 | 142 | const response = { options }; 143 | try { 144 | tabs.forEach(tab => browser.tabs.sendMessage(tab.id, response)); 145 | } 146 | catch { 147 | print("error while sending the message"); 148 | } 149 | }); 150 | } 151 | 152 | /* ==================== Feedback and Details Buttons ==================== */ 153 | 154 | function initFeedbackButtons() { 155 | document.getElementById('fBtn').addEventListener("click", function() { 156 | const isFirefox = typeof InstallTrigger !== 'undefined'; 157 | const isEdge = /Edge/.test(navigator.userAgent); 158 | if (isFirefox) { 159 | this.href = "https://addons.mozilla.org/en-US/firefox/addon/leetcode-enhancer/"; 160 | } else if (isEdge) { 161 | this.href = "https://microsoftedge.microsoft.com/addons/detail/leetcode-enhancer/dgddijgkneackjhmijacbopefpladfia"; 162 | } 163 | }); 164 | } 165 | 166 | function initDetailsButton() { 167 | document.getElementById('deBtn').addEventListener("click", function() { 168 | const isClose = document.getElementById('deBtn').innerHTML == '►'; 169 | document.getElementById('msg').style.display = isClose ? "block" : "none"; 170 | document.getElementById('deBtn').innerHTML = isClose ? '▼' : '►'; 171 | }); 172 | } 173 | 174 | /* ==================== AI Section for API Key ==================== */ 175 | 176 | function initAISection() { 177 | const apiKeyInput = document.getElementById('api-key'); 178 | const deleteApiKeyButton = document.getElementById('delete-api-key'); 179 | const triggerActionButton = document.getElementById('trigger-action'); 180 | 181 | loadApiKey().then((decryptedApiKey) => { 182 | if (decryptedApiKey) { 183 | apiKeyInput.value = decryptedApiKey; 184 | } else { 185 | print("No API key found in storage."); 186 | } 187 | }); 188 | 189 | apiKeyInput.addEventListener('input', () => { 190 | const apiKey = apiKeyInput.value.trim(); 191 | if (apiKey.length > 0) { 192 | storeApiKey(apiKey); 193 | print("API Key saved."); 194 | } 195 | }); 196 | 197 | deleteApiKeyButton.addEventListener('click', () => { 198 | apiKeyInput.value = ''; 199 | browser.storage.local.remove(['encryptedApiKey', 'aesIV', 'aesKey']); 200 | print("API Key deleted."); 201 | }); 202 | 203 | triggerActionButton.addEventListener('click', async () => { 204 | if(!isTermsCheckboxChecked()) { 205 | alert("Please accept the terms and condition"); 206 | return; 207 | } 208 | const apiKey = apiKeyInput.value; 209 | if (apiKey) { 210 | sendMessageToContentScriptToGetCode(apiKey); 211 | } else { 212 | alert("Please provide API Key"); 213 | } 214 | }); 215 | 216 | const termsCheckbox = document.getElementById('terms-checkbox'); 217 | 218 | loadTermsCheckboxState(); 219 | 220 | termsCheckbox.addEventListener('change', function() { 221 | storeTermsCheckboxState(termsCheckbox.checked); 222 | }); 223 | 224 | getData('outputDivCode', displayDataInOutputDiv); 225 | 226 | function displayDataInOutputDiv(outputData) { 227 | if(outputData) { 228 | document.getElementById("output").innerHTML = outputData; 229 | } 230 | } 231 | } 232 | 233 | function storeTermsCheckboxState(isChecked) { 234 | storeData({ termsAccepted: isChecked }) 235 | } 236 | 237 | function loadTermsCheckboxState() { 238 | try { 239 | browser.storage.local.get('termsAccepted', (result) => { 240 | const isChecked = result.termsAccepted; 241 | const termsCheckbox = document.getElementById('terms-checkbox'); 242 | if (termsCheckbox) { 243 | termsCheckbox.checked = !!isChecked; 244 | } 245 | }); 246 | } 247 | catch (err) { 248 | print("Error while reading termsAccepted"); 249 | } 250 | } 251 | 252 | function isTermsCheckboxChecked() { 253 | const termsCheckbox = document.getElementById('terms-checkbox'); 254 | return termsCheckbox && termsCheckbox.checked; 255 | } 256 | 257 | function sendMessageToContentScriptToGetCode(apiKey) { 258 | browser.tabs.query({ active: true, currentWindow: true }, (tabs) => { 259 | if (!tabs || tabs.length === 0) { 260 | alert("No active tab found. Please make sure you're on a LeetCode page."); 261 | return; 262 | } 263 | 264 | const activeTab = tabs[0]; 265 | 266 | if (!activeTab.url.includes("leetcode.com")) { 267 | alert("Please navigate to a LeetCode page first."); 268 | return; 269 | } 270 | 271 | try { 272 | browser.tabs.sendMessage(activeTab.id, { action: MESSAGE_GET_CODE }) 273 | .then(response => { 274 | if (response && response.code) { 275 | const code = response.code; 276 | const question = "Provide time and space complexity of the code.\n"; 277 | requestCoherePermissionIfNeeded(makeCohereRequest, apiKey, question + code); 278 | } else if (response && response.error) { 279 | alert(response.error); 280 | } else { 281 | alert("Failed to get code from the page. Please make sure you're on a LeetCode problem page."); 282 | } 283 | }) 284 | .catch(error => { 285 | print(`Error: ${error.message}`); 286 | alert("Failed to communicate with the page. Please refresh the page and try again."); 287 | }); 288 | } catch (err) { 289 | print(`Error while sending message: ${err.message}`); 290 | alert("An error occurred. Please refresh the page and try again."); 291 | } 292 | }); 293 | } 294 | 295 | /* ==================== Cohere API Call ==================== */ 296 | 297 | function requestCoherePermissionIfNeeded(callback, apiKey, payload) { 298 | browser.permissions.contains( 299 | { origins: ["https://api.cohere.ai/*"] }, 300 | async (hasPermission) => { 301 | if (hasPermission) { 302 | // Permission already granted, proceed with the API call 303 | callback(apiKey, payload); 304 | } else { 305 | // Permission not granted, request it 306 | await browser.permissions.request( 307 | { origins: ["https://api.cohere.ai/*"] }, 308 | (granted) => { 309 | if (granted) { 310 | // Permission granted, proceed with the API call 311 | callback(apiKey, payload); 312 | } else { 313 | // Permission denied, handle accordingly 314 | print("Permission denied for Cohere API access."); 315 | alert("Permission denied for Cohere API access. Please grant permission to use the Cohere API."); 316 | } 317 | } 318 | ); 319 | } 320 | } 321 | ); 322 | } 323 | 324 | function makeCohereRequest(apiKey, question) { 325 | actionButtonUnusable(); 326 | const apiUrl = 'https://api.cohere.ai/v1/generate'; 327 | const data = { 328 | model: 'command', 329 | prompt: question, 330 | temperature: 0.7, 331 | k: 0, 332 | p: 0.1, 333 | frequency_penalty: 0, 334 | presence_penalty: 0, 335 | stop_sequences: [] 336 | }; 337 | 338 | fetch(apiUrl, { 339 | method: 'POST', 340 | headers: { 341 | 'Authorization': `Bearer ${apiKey}`, 342 | 'Content-Type': 'application/json' 343 | }, 344 | body: JSON.stringify(data) 345 | }) 346 | .then(response => response.json()) 347 | .then(data => { 348 | const outputDiv = document.getElementById('output'); 349 | outputDiv.innerHTML = data.generations[0].text.replace(/\n/g, '
').replace(/```(.*?)```/gs, '
$1
'); 350 | storeDataWithObjectWrapping("outputDivCode", outputDiv.innerHTML); 351 | }) 352 | .catch(error => { 353 | alert("Operation failed. Please check your api key."); 354 | print("error while sending request to cohere" + error); 355 | }).finally( () => { 356 | actionButtonUsable(); 357 | }) 358 | } 359 | 360 | function actionButtonUsable() { 361 | const triggerButton = document.getElementById('trigger-action'); 362 | triggerButton.innerText="TC & SC" 363 | triggerButton.style.backgroundColor = 'buttonface'; // Set background to green when usable 364 | triggerButton.disabled = false; // Enable the button 365 | } 366 | 367 | function actionButtonUnusable() { 368 | const triggerButton = document.getElementById('trigger-action'); 369 | triggerButton.innerText="processing.." 370 | triggerButton.style.backgroundColor = 'yellow'; // Set background to yellow when unusable 371 | triggerButton.disabled = true; // Disable the button 372 | } 373 | },{"./constants.js":1,"./debugger.js":2,"./utils.js":4}],4:[function(require,module,exports){ 374 | const print = require('./debugger.js'); 375 | let browser = window.browser || window.chrome; 376 | 377 | /** 378 | * Checks if an object is iterable. 379 | * @param {Object} obj - The object to check. 380 | * @returns {boolean} - True if the object is iterable, false otherwise. 381 | */ 382 | function isIterable(obj) { 383 | if(obj != null && typeof obj[Symbol.iterator] === 'function') 384 | return true; 385 | print(`${JSON.stringify(obj)} is not iterable`) 386 | } 387 | 388 | function storeDataWithObjectWrapping(key, value) { 389 | const data = {}; 390 | data[key] = value; 391 | storeData(data); 392 | } 393 | 394 | function storeData(data) { 395 | browser.storage.local.set(data); 396 | } 397 | 398 | 399 | function getData(key, callback) { 400 | try { 401 | browser.storage.local.get([key], result => callback(result[key])); 402 | } 403 | catch (err) { 404 | print("Error while retrieving key"); 405 | } 406 | } 407 | 408 | async function getDataAsUint8Array(key) { 409 | return new Uint8Array((await browser.storage.local.get(key))[key] || []); 410 | } 411 | 412 | // Export the isIterable function using CommonJS syntax 413 | module.exports = {isIterable, storeDataWithObjectWrapping, getData, getDataAsUint8Array, storeData}; 414 | },{"./debugger.js":2}]},{},[3]); 415 | -------------------------------------------------------------------------------- /popup.scss: -------------------------------------------------------------------------------- 1 | $f1: 20px; 2 | $f2: 12px; 3 | $c1: #F0F0F0; 4 | $bg-dark: rgba(40, 42, 46, 1); 5 | $bg2-dark: rgba(34, 35, 37, 1); 6 | $bg3-dark: rgb(60, 60, 61); 7 | $c1-dark: rgba(255, 255, 255, 0.9); 8 | $link-color: rgb(14, 115, 222); 9 | 10 | html, 11 | body { 12 | margin: 0; 13 | padding: 0; 14 | outline: 0; 15 | vertical-align: baseline; 16 | background: transparent; 17 | } 18 | 19 | .wrapper { 20 | max-width: 1000px; 21 | min-width: 280px; 22 | } 23 | 24 | .wrapper { 25 | .link { 26 | font-size: $f2; 27 | text-align: center; 28 | } 29 | 30 | .brand { 31 | background-color: $bg-dark; 32 | font-size: $f1; 33 | padding: 10px; 34 | color: $c1-dark; 35 | 36 | .title { 37 | text-decoration: none; 38 | color: $c1-dark; 39 | 40 | &:hover { 41 | color: grey; 42 | } 43 | } 44 | 45 | ; 46 | box-shadow: rgba(0, 0, 0, 0) 0px 0px 0px 0px, 47 | rgba(0, 0, 0, 0) 0px 0px 0px 0px, 48 | rgba(0, 0, 0, 0.25) 0px 4px 4px 0px, 49 | rgba(0, 0, 0, 0.3) 0px 1px 2px 0px; 50 | } 51 | 52 | .logo { 53 | height: 50px; 54 | width: 50px; 55 | border-radius: 10px; 56 | } 57 | 58 | .github { 59 | margin-top: 10px; 60 | filter: invert(1); 61 | } 62 | 63 | .brand span { 64 | position: absolute; 65 | padding-left: 10px; 66 | } 67 | 68 | .notice { 69 | padding: 10px; 70 | color: white; 71 | line-height: 15px; 72 | background-color: $bg3-dark; 73 | font-size: $f2; 74 | } 75 | 76 | .options { 77 | padding: 10px; 78 | background-color: $bg2-dark; 79 | color: $c1-dark; 80 | 81 | div { 82 | margin-top: 10px; 83 | margin-bottom: 10px; 84 | 85 | input { 86 | float: right; 87 | 88 | &:hover { 89 | cursor: pointer; 90 | } 91 | } 92 | } 93 | 94 | input[type="checkbox"] { 95 | display: none; 96 | } 97 | } 98 | 99 | .footer { 100 | background-color: $bg-dark; 101 | padding: 5px 10px; 102 | 103 | a { 104 | text-decoration: none; 105 | } 106 | 107 | span { 108 | position: relative; 109 | color: $c1-dark; 110 | } 111 | 112 | #fBtn span { 113 | text-decoration: underline; 114 | } 115 | 116 | // img { 117 | // height: 20px; 118 | // width: auto; 119 | // } 120 | #deBtn { 121 | float: right; 122 | cursor: pointer; 123 | } 124 | } 125 | 126 | .details { 127 | color: $c1-dark; 128 | background-color: $bg-dark; 129 | padding: 5px 10px; 130 | line-height: 15px; 131 | box-shadow: rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0.25) 0px 4px 4px 0px, rgba(0, 0, 0, 0.3) 0px 1px 2px 0px; 132 | 133 | a { 134 | color: $c1-dark; 135 | } 136 | 137 | } 138 | 139 | .creator { 140 | color: $c1-dark; 141 | background-color: $bg-dark; 142 | padding: 5px 10px; 143 | text-align: center; 144 | 145 | .linkedin { 146 | height: 20px; 147 | width: 20px; 148 | margin-bottom: -5px; 149 | } 150 | } 151 | 152 | .contributors { 153 | a { 154 | color: $c1-dark; 155 | } 156 | 157 | padding: 5px 10px; 158 | text-align: center; 159 | } 160 | 161 | } 162 | 163 | /* Checkbox UI enhancement */ 164 | 165 | .checkbox-label { 166 | display: inline-block; 167 | width: 1.2rem; 168 | height: 1.2rem; 169 | background-image: url('/icons/close_eye.png'); 170 | background-size: contain; 171 | } 172 | 173 | input[type="checkbox"]:checked+.checkbox-label { 174 | background-image: url('/icons/eye_open.png'); 175 | } 176 | 177 | 178 | .option-div { 179 | width: 100%; 180 | display: flex; 181 | justify-content: space-between; 182 | } 183 | 184 | .ai-box { 185 | background-color: $bg2-dark; 186 | color: $c1-dark; 187 | padding:10px; 188 | 189 | .ai-terms { 190 | margin-bottom: 5px; 191 | 192 | a { 193 | color: $c1; 194 | } 195 | } 196 | 197 | #api-key { 198 | width: 99%; 199 | margin-top: 5px; 200 | margin-bottom: 5px; 201 | } 202 | 203 | .ai-inputs { 204 | margin-top: 5px; 205 | margin-bottom: 5px; 206 | } 207 | 208 | .ai-getKey { 209 | font-size: $f2; 210 | text-decoration: underline; 211 | cursor: pointer; 212 | 213 | :hover { 214 | color: $link-color; 215 | text-decoration: $link-color; 216 | } 217 | 218 | a { 219 | color: $c1; 220 | } 221 | } 222 | 223 | .output { 224 | height: 300px; 225 | overflow: auto; 226 | border-radius: 5px; // Optional: rounded corners 227 | color: $bg-dark; // Text color (modify as needed) 228 | font-size: $f2; // Font size (modify as needed) 229 | font-family: monospace; 230 | white-space: pre-wrap; /* Preserve both spaces and line breaks */ 231 | } 232 | 233 | .ai-button { 234 | margin-left: 5px; 235 | 236 | &:hover { 237 | cursor: pointer; // Change cursor on hover 238 | } 239 | } 240 | } 241 | -------------------------------------------------------------------------------- /scripts/constants.js: -------------------------------------------------------------------------------- 1 | const KEY_NAME_OPTIONS = "options"; 2 | const APP_NAME = "Leetcode Enhancer"; 3 | const MESSAGE_ACTIVATE_ICON = 'activate_icon'; 4 | const MESSAGE_GET_CODE = 'get_code' 5 | const FIREFOX_APP_PAGE_URL = "https://addons.mozilla.org/en-US/firefox/addon/leetcode-enhancer/"; 6 | const CHROME_APP_PAGE_URL = "https://chrome.google.com/webstore/detail/leetcode-enhancer/gcmncppaaebldbkgkcbojghpmpjkdlmp"; 7 | 8 | module.exports = {KEY_NAME_OPTIONS, APP_NAME, MESSAGE_ACTIVATE_ICON, FIREFOX_APP_PAGE_URL, CHROME_APP_PAGE_URL, MESSAGE_GET_CODE}; -------------------------------------------------------------------------------- /scripts/content-script-main.js: -------------------------------------------------------------------------------- 1 | const sendMessage = require('./message-publisher.js'); 2 | const findMode = require('./page-checker.js'); 3 | const initMutationObserver = require('./mutation-observer.js'); 4 | const {isIterable} = require('./utils.js'); 5 | const Mode = require('./mode.js'); 6 | const print = require('./debugger.js'); 7 | const { MESSAGE_ACTIVATE_ICON, MESSAGE_GET_CODE } = require('./constants.js'); 8 | const { FeatureStrategyFactory } = require('./feature-strategies.js'); 9 | 10 | var browser = browser || chrome; 11 | let mode = findMode(); 12 | let currentStrategy = FeatureStrategyFactory.getStrategy(mode); 13 | 14 | // publish message to background script (i.e. service worker) for activating icon 15 | sendMessage({ message: MESSAGE_ACTIVATE_ICON }); 16 | 17 | // init mutation observer 18 | initMutationObserver(browser, mode, modifyThenApplyChanges); 19 | 20 | function modifyThenApplyChanges(options) { 21 | if(!isIterable(options.options)) 22 | return; 23 | applyChanges(options.options); 24 | } 25 | 26 | // ################### EVENT LISTENER ##################### 27 | browser.runtime.onMessage.addListener(function(request, sender, sendResponse) { 28 | print(`Received Notification in content script: ${JSON.stringify(request)}`); 29 | if(request.options) { 30 | ops = [] 31 | for (option of request.options) { 32 | ops.push(option); 33 | } 34 | applyChanges(ops); 35 | } 36 | 37 | if (request.action === MESSAGE_GET_CODE) { 38 | const code = getUserCode(); 39 | print(`code obtained: ${code}`); 40 | sendResponse({ code: code }); 41 | } 42 | }); 43 | 44 | function applyChanges(options) { 45 | if (!currentStrategy) { 46 | print('No strategy found for current mode'); 47 | return; 48 | } 49 | 50 | for (option of options) { 51 | let name = option.optionName; 52 | if (name === 'locked') { 53 | currentStrategy.hideLockedProblems(option.checked); 54 | } else if (name === 'highlight') { 55 | currentStrategy.highlightSolvedProblems(option.checked); 56 | } else if (name === 'solved') { 57 | currentStrategy.hideSolvedProb(option.checked); 58 | } else if(name === 'disUsers') { 59 | currentStrategy.setSolutionsUsers(option.checked); 60 | } else { 61 | currentStrategy.toggleByColName(name, option.checked); 62 | } 63 | } 64 | } 65 | 66 | function getUserCode() { 67 | if (!currentStrategy) { 68 | print('Unable to read the code. If you are seeing this error in code editor page, please report this issue.'); 69 | return ''; 70 | } 71 | return currentStrategy.getUserCode(); 72 | } -------------------------------------------------------------------------------- /scripts/debugger.js: -------------------------------------------------------------------------------- 1 | // debugger.js 2 | const {APP_NAME} = require('./constants'); 3 | // Set the debug mode (true to enable debugging) 4 | const debug = false; 5 | 6 | /** 7 | * Prints a message to the console if debugging is enabled. 8 | * @param {string} message - The message to print. 9 | */ 10 | function print(message) { 11 | if (debug) { 12 | console.log(`[${APP_NAME}]: ${message}`); 13 | } 14 | } 15 | 16 | // Export the print function 17 | module.exports = print; 18 | -------------------------------------------------------------------------------- /scripts/feature-strategies.js: -------------------------------------------------------------------------------- 1 | const FeatureStrategy = require('./strategies/base-strategy'); 2 | const FeatureStrategyFactory = require('./strategies/strategy-factory'); 3 | 4 | module.exports = { 5 | FeatureStrategy, 6 | FeatureStrategyFactory 7 | }; -------------------------------------------------------------------------------- /scripts/message-publisher.js: -------------------------------------------------------------------------------- 1 | const print = require('./debugger.js'); 2 | 3 | /** 4 | * Sends a message to the background script (service-worker) or other parts of the extension. 5 | * @param {Object} payload - The message payload to send. 6 | */ 7 | function sendMessage(payload) { 8 | var browser = browser || chrome; 9 | if (!browser || !browser.runtime) { 10 | print('Browser API not available'); 11 | return; 12 | } 13 | 14 | try { 15 | browser.runtime.sendMessage(payload); 16 | } catch (err) { 17 | print(`Failed to send message: ${JSON.stringify(payload)} because of the following error: ${err}`); 18 | } 19 | } 20 | 21 | // Export the sendMessage function 22 | module.exports = sendMessage; 23 | -------------------------------------------------------------------------------- /scripts/mode.js: -------------------------------------------------------------------------------- 1 | // ################### MODES ##################### 2 | /* 3 | 1 - problem set 4 | 5 - contest 5 | 6 - coding area 6 | */ 7 | 8 | const Mode = { 9 | PROBLEM_SET: 'PROBLEM_SET', 10 | CODING_AREA: 'CODING_AREA', 11 | CONTEST: 'CONTEST' 12 | }; 13 | 14 | module.exports = { Mode }; -------------------------------------------------------------------------------- /scripts/mutation-observer.js: -------------------------------------------------------------------------------- 1 | const print = require('./debugger.js'); 2 | const { Mode } = require('./mode.js'); 3 | const { KEY_NAME_OPTIONS } = require('./constants.js'); 4 | 5 | const ERROR_IN_MUTATION_OBSERVER_CALLBACK = "Error in MutationObserver callback"; 6 | const ERROR_PROBLEM_SET_UI_NOT_FOUND = "Problem Set UI element not found."; 7 | const ERROR_CODING_AREA_NOT_FOUND = "Coding Area element not found."; 8 | const ERROR_BASE_CONTENT_NOT_FOUND = "Base content element and qd-content element not found."; 9 | const ERROR_NO_VALID_MODE = "No valid mode found yet to observe."; 10 | 11 | // More specific selectors for better targeting 12 | const SELECTOR_PROBLEM_SET_UI = '#__next'; // Using ID selector for better specificity 13 | const SELECTOR_CODING_AREA = '#__next'; // Using ID selector for better specificity 14 | const SELECTOR_BASE_CONTENT = 'base_content'; 15 | const SELECTOR_NEW_CONTEST_PAGE = 'qd-content'; 16 | 17 | /** 18 | * Initialize the Mutation Observer. 19 | */ 20 | function initMutationObserver(browser, mode, modifyThenApplyChanges) { 21 | const observer = new MutationObserver(function (mutations) { 22 | try { 23 | if (mutations.length) { 24 | browser.storage.local.get([KEY_NAME_OPTIONS], modifyThenApplyChanges); 25 | } 26 | } catch (error) { 27 | print(`${ERROR_IN_MUTATION_OBSERVER_CALLBACK}: ${error}`); 28 | } 29 | }); 30 | 31 | // Start observing based on mode 32 | switch (mode) { 33 | case Mode.PROBLEM_SET: 34 | const ui = document.querySelector(SELECTOR_PROBLEM_SET_UI); 35 | if (ui) { 36 | observer.observe(ui, { 37 | childList: true, 38 | subtree: true 39 | }); 40 | } else { 41 | print(ERROR_PROBLEM_SET_UI_NOT_FOUND); 42 | } 43 | break; 44 | 45 | case Mode.CODING_AREA: 46 | const code_ui = document.querySelector(SELECTOR_CODING_AREA); 47 | if (code_ui) { 48 | observer.observe(code_ui, { 49 | childList: true, 50 | subtree: true 51 | }); 52 | } else { 53 | print(ERROR_CODING_AREA_NOT_FOUND); 54 | } 55 | break; 56 | 57 | case Mode.CONTEST: 58 | // Handle old contest page 59 | const old_contest_page = document.getElementById(SELECTOR_BASE_CONTENT); 60 | if (old_contest_page) { 61 | observer.observe(old_contest_page, { 62 | childList: true, 63 | subtree: true 64 | }); 65 | break; 66 | } 67 | 68 | // Handle new contest page 69 | const new_contest_page = document.getElementById(SELECTOR_NEW_CONTEST_PAGE); 70 | if (new_contest_page) { 71 | observer.observe(new_contest_page, { 72 | childList: true, 73 | subtree: true 74 | }); 75 | } 76 | 77 | // Handle new contest page sidebar (loads after 2-3 seconds) 78 | const next_root = document.querySelector('#__next'); 79 | if (next_root) { 80 | observer.observe(next_root.parentElement, { 81 | childList: true, 82 | subtree: true 83 | }); 84 | } 85 | 86 | if (!old_contest_page && !new_contest_page && !next_root) { 87 | print(ERROR_BASE_CONTENT_NOT_FOUND); 88 | } 89 | break; 90 | 91 | default: 92 | print(ERROR_NO_VALID_MODE); 93 | break; 94 | } 95 | return observer; 96 | } 97 | 98 | // Export the function to initialize the observer 99 | module.exports = initMutationObserver; 100 | -------------------------------------------------------------------------------- /scripts/page-checker.js: -------------------------------------------------------------------------------- 1 | const { Mode } = require('./mode.js'); 2 | const print = require('./debugger.js'); 3 | 4 | // Constant URLs 5 | const PROBLEMSET_URL = '/problemset/'; 6 | const PROBLEMS_URL = '/problems/'; 7 | const CONTEST_URL = '/contest/'; 8 | 9 | /** 10 | * Check if the current URL is for the problem set page. 11 | * @returns {boolean} 12 | */ 13 | function isProblemSetPage() { 14 | return window.location.href.includes(PROBLEMSET_URL); 15 | } 16 | 17 | /** 18 | * Check if the current URL is for the coding area. 19 | * @returns {boolean} 20 | */ 21 | function isCodingArea() { 22 | return window.location.href.includes(PROBLEMS_URL); 23 | } 24 | 25 | /** 26 | * Check if the current URL is for contests. 27 | * @returns {boolean} 28 | */ 29 | function isContest() { 30 | return window.location.href.includes(CONTEST_URL); 31 | } 32 | 33 | /** 34 | * Determine the current mode based on the page type. 35 | * @returns {Mode} - The detected mode. 36 | */ 37 | function findMode() { 38 | let mode; 39 | if (isContest()) { 40 | mode = Mode.CONTEST; 41 | } else if (isCodingArea()) { 42 | mode = Mode.CODING_AREA; 43 | } else if (isProblemSetPage()) { 44 | mode = Mode.PROBLEM_SET; 45 | } 46 | print(`Current mode value = ${mode}`); 47 | return mode; 48 | } 49 | 50 | module.exports = findMode; 51 | -------------------------------------------------------------------------------- /scripts/popup-main.js: -------------------------------------------------------------------------------- 1 | const {KEY_NAME_OPTIONS, MESSAGE_GET_CODE} = require('./constants.js'); 2 | const print = require('./debugger.js'); 3 | const {isIterable, storeDataWithObjectWrapping, getData, getDataAsUint8Array, storeData} = require('./utils.js'); 4 | 5 | /* ==================== Compatibility Between Chrome and Firefox ==================== */ 6 | var browser = browser || chrome 7 | 8 | /* ==================== Initialization ==================== */ 9 | document.addEventListener('DOMContentLoaded', initExtension); 10 | 11 | function initExtension() { 12 | attachCheckboxEventListeners(); 13 | loadSavedSettings(); 14 | initFeedbackButtons(); 15 | initDetailsButton(); 16 | initAISection(); 17 | } 18 | 19 | /* ==================== AES Encryption and Decryption Functions ==================== */ 20 | 21 | async function generateAESKey() { 22 | return window.crypto.subtle.generateKey( 23 | { name: "AES-GCM", length: 256 }, 24 | true, 25 | ["encrypt", "decrypt"] 26 | ); 27 | } 28 | 29 | async function encryptData(data, aesKey) { 30 | const iv = window.crypto.getRandomValues(new Uint8Array(12)); 31 | const encodedData = new TextEncoder().encode(data); 32 | 33 | const encryptedData = await window.crypto.subtle.encrypt( 34 | { name: "AES-GCM", iv }, 35 | aesKey, 36 | encodedData 37 | ); 38 | 39 | return { encryptedData: new Uint8Array(encryptedData), iv }; 40 | } 41 | 42 | async function decryptData(encryptedData, aesKey, iv) { 43 | const decryptedData = await window.crypto.subtle.decrypt( 44 | { name: "AES-GCM", iv }, 45 | aesKey, 46 | encryptedData 47 | ); 48 | 49 | return new TextDecoder().decode(decryptedData); 50 | } 51 | 52 | /* ==================== API Key Handling ==================== */ 53 | 54 | async function storeApiKey(apiKey) { 55 | const aesKey = await generateAESKey(); 56 | const { encryptedData, iv } = await encryptData(apiKey, aesKey); 57 | 58 | storeDataWithObjectWrapping('encryptedApiKey', Array.from(encryptedData)); 59 | storeDataWithObjectWrapping('aesIV', Array.from(iv)); 60 | 61 | const exportedKey = await window.crypto.subtle.exportKey("raw", aesKey); 62 | storeDataWithObjectWrapping('aesKey', Array.from(new Uint8Array(exportedKey))); 63 | } 64 | 65 | async function loadApiKey() { 66 | const encryptedData = await getDataAsUint8Array('encryptedApiKey'); 67 | const iv = await getDataAsUint8Array('aesIV'); 68 | const aesKeyBytes = await getDataAsUint8Array('aesKey'); 69 | 70 | if (!encryptedData || !iv || !aesKeyBytes) { 71 | print("No API key or encryption details found."); 72 | return null; 73 | } 74 | 75 | const aesKey = await window.crypto.subtle.importKey( 76 | "raw", 77 | aesKeyBytes, 78 | { name: "AES-GCM" }, 79 | false, 80 | ["decrypt"] 81 | ); 82 | 83 | return await decryptData(encryptedData, aesKey, iv); 84 | } 85 | 86 | /* ==================== Checkbox Settings Handling ==================== */ 87 | 88 | function attachCheckboxEventListeners() { 89 | const checkboxes = document.querySelectorAll("input[name=settings]"); 90 | checkboxes.forEach(chb => chb.addEventListener('change', onCheckboxChange)); 91 | } 92 | 93 | function loadSavedSettings() { 94 | getData(KEY_NAME_OPTIONS, updateCheckboxes); 95 | } 96 | 97 | function updateCheckboxes(options) { 98 | if (isIterable(options)) { 99 | options.forEach(option => { 100 | const chb = document.getElementById(option.optionName); 101 | chb.checked = option.checked; 102 | }); 103 | } 104 | } 105 | 106 | function onCheckboxChange() { 107 | browser.tabs.query({}, function(tabs) { 108 | const options = []; 109 | const checkboxes = document.querySelectorAll('input[name=settings]'); 110 | checkboxes.forEach(chb => options.push({ optionName: chb.value, checked: chb.checked })); 111 | storeDataWithObjectWrapping('options', options); 112 | 113 | const response = { options }; 114 | try { 115 | tabs.forEach(tab => browser.tabs.sendMessage(tab.id, response)); 116 | } 117 | catch { 118 | print("error while sending the message"); 119 | } 120 | }); 121 | } 122 | 123 | /* ==================== Feedback and Details Buttons ==================== */ 124 | 125 | function initFeedbackButtons() { 126 | document.getElementById('fBtn').addEventListener("click", function() { 127 | const isFirefox = typeof InstallTrigger !== 'undefined'; 128 | const isEdge = /Edge/.test(navigator.userAgent); 129 | if (isFirefox) { 130 | this.href = "https://addons.mozilla.org/en-US/firefox/addon/leetcode-enhancer/"; 131 | } else if (isEdge) { 132 | this.href = "https://microsoftedge.microsoft.com/addons/detail/leetcode-enhancer/dgddijgkneackjhmijacbopefpladfia"; 133 | } 134 | }); 135 | } 136 | 137 | function initDetailsButton() { 138 | document.getElementById('deBtn').addEventListener("click", function() { 139 | const isClose = document.getElementById('deBtn').innerHTML == '►'; 140 | document.getElementById('msg').style.display = isClose ? "block" : "none"; 141 | document.getElementById('deBtn').innerHTML = isClose ? '▼' : '►'; 142 | }); 143 | } 144 | 145 | /* ==================== AI Section for API Key ==================== */ 146 | 147 | function initAISection() { 148 | const apiKeyInput = document.getElementById('api-key'); 149 | const deleteApiKeyButton = document.getElementById('delete-api-key'); 150 | const triggerActionButton = document.getElementById('trigger-action'); 151 | 152 | loadApiKey().then((decryptedApiKey) => { 153 | if (decryptedApiKey) { 154 | apiKeyInput.value = decryptedApiKey; 155 | } else { 156 | print("No API key found in storage."); 157 | } 158 | }); 159 | 160 | apiKeyInput.addEventListener('input', () => { 161 | const apiKey = apiKeyInput.value.trim(); 162 | if (apiKey.length > 0) { 163 | storeApiKey(apiKey); 164 | print("API Key saved."); 165 | } 166 | }); 167 | 168 | deleteApiKeyButton.addEventListener('click', () => { 169 | apiKeyInput.value = ''; 170 | browser.storage.local.remove(['encryptedApiKey', 'aesIV', 'aesKey']); 171 | print("API Key deleted."); 172 | }); 173 | 174 | triggerActionButton.addEventListener('click', async () => { 175 | if(!isTermsCheckboxChecked()) { 176 | alert("Please accept the terms and condition"); 177 | return; 178 | } 179 | const apiKey = apiKeyInput.value; 180 | if (apiKey) { 181 | sendMessageToContentScriptToGetCode(apiKey); 182 | } else { 183 | alert("Please provide API Key"); 184 | } 185 | }); 186 | 187 | const termsCheckbox = document.getElementById('terms-checkbox'); 188 | 189 | loadTermsCheckboxState(); 190 | 191 | termsCheckbox.addEventListener('change', function() { 192 | storeTermsCheckboxState(termsCheckbox.checked); 193 | }); 194 | 195 | getData('outputDivCode', displayDataInOutputDiv); 196 | 197 | function displayDataInOutputDiv(outputData) { 198 | if(outputData) { 199 | document.getElementById("output").innerHTML = outputData; 200 | } 201 | } 202 | } 203 | 204 | function storeTermsCheckboxState(isChecked) { 205 | storeData({ termsAccepted: isChecked }) 206 | } 207 | 208 | function loadTermsCheckboxState() { 209 | try { 210 | browser.storage.local.get('termsAccepted', (result) => { 211 | const isChecked = result.termsAccepted; 212 | const termsCheckbox = document.getElementById('terms-checkbox'); 213 | if (termsCheckbox) { 214 | termsCheckbox.checked = !!isChecked; 215 | } 216 | }); 217 | } 218 | catch (err) { 219 | print("Error while reading termsAccepted"); 220 | } 221 | } 222 | 223 | function isTermsCheckboxChecked() { 224 | const termsCheckbox = document.getElementById('terms-checkbox'); 225 | return termsCheckbox && termsCheckbox.checked; 226 | } 227 | 228 | function sendMessageToContentScriptToGetCode(apiKey) { 229 | browser.tabs.query({ active: true, currentWindow: true }, (tabs) => { 230 | if (!tabs || tabs.length === 0) { 231 | alert("No active tab found. Please make sure you're on a LeetCode page."); 232 | return; 233 | } 234 | 235 | const activeTab = tabs[0]; 236 | 237 | if (!activeTab.url.includes("leetcode.com")) { 238 | alert("Please navigate to a LeetCode page first."); 239 | return; 240 | } 241 | 242 | try { 243 | browser.tabs.sendMessage(activeTab.id, { action: MESSAGE_GET_CODE }) 244 | .then(response => { 245 | if (response && response.code) { 246 | const code = response.code; 247 | const question = "Provide time and space complexity of the code.\n"; 248 | requestCoherePermissionIfNeeded(makeCohereRequest, apiKey, question + code); 249 | } else if (response && response.error) { 250 | alert(response.error); 251 | } else { 252 | alert("Failed to get code from the page. Please make sure you're on a LeetCode problem page."); 253 | } 254 | }) 255 | .catch(error => { 256 | print(`Error: ${error.message}`); 257 | alert("Failed to communicate with the page. Please refresh the page and try again."); 258 | }); 259 | } catch (err) { 260 | print(`Error while sending message: ${err.message}`); 261 | alert("An error occurred. Please refresh the page and try again."); 262 | } 263 | }); 264 | } 265 | 266 | /* ==================== Cohere API Call ==================== */ 267 | 268 | function requestCoherePermissionIfNeeded(callback, apiKey, payload) { 269 | browser.permissions.contains( 270 | { origins: ["https://api.cohere.ai/*"] }, 271 | async (hasPermission) => { 272 | if (hasPermission) { 273 | // Permission already granted, proceed with the API call 274 | callback(apiKey, payload); 275 | } else { 276 | // Permission not granted, request it 277 | await browser.permissions.request( 278 | { origins: ["https://api.cohere.ai/*"] }, 279 | (granted) => { 280 | if (granted) { 281 | // Permission granted, proceed with the API call 282 | callback(apiKey, payload); 283 | } else { 284 | // Permission denied, handle accordingly 285 | print("Permission denied for Cohere API access."); 286 | alert("Permission denied for Cohere API access. Please grant permission to use the Cohere API."); 287 | } 288 | } 289 | ); 290 | } 291 | } 292 | ); 293 | } 294 | 295 | function makeCohereRequest(apiKey, question) { 296 | actionButtonUnusable(); 297 | const apiUrl = 'https://api.cohere.ai/v1/generate'; 298 | const data = { 299 | model: 'command', 300 | prompt: question, 301 | temperature: 0.7, 302 | k: 0, 303 | p: 0.1, 304 | frequency_penalty: 0, 305 | presence_penalty: 0, 306 | stop_sequences: [] 307 | }; 308 | 309 | fetch(apiUrl, { 310 | method: 'POST', 311 | headers: { 312 | 'Authorization': `Bearer ${apiKey}`, 313 | 'Content-Type': 'application/json' 314 | }, 315 | body: JSON.stringify(data) 316 | }) 317 | .then(response => response.json()) 318 | .then(data => { 319 | const outputDiv = document.getElementById('output'); 320 | outputDiv.innerHTML = data.generations[0].text.replace(/\n/g, '
').replace(/```(.*?)```/gs, '
$1
'); 321 | storeDataWithObjectWrapping("outputDivCode", outputDiv.innerHTML); 322 | }) 323 | .catch(error => { 324 | alert("Operation failed. Please check your api key."); 325 | print("error while sending request to cohere" + error); 326 | }).finally( () => { 327 | actionButtonUsable(); 328 | }) 329 | } 330 | 331 | function actionButtonUsable() { 332 | const triggerButton = document.getElementById('trigger-action'); 333 | triggerButton.innerText="TC & SC" 334 | triggerButton.style.backgroundColor = 'buttonface'; // Set background to green when usable 335 | triggerButton.disabled = false; // Enable the button 336 | } 337 | 338 | function actionButtonUnusable() { 339 | const triggerButton = document.getElementById('trigger-action'); 340 | triggerButton.innerText="processing.." 341 | triggerButton.style.backgroundColor = 'yellow'; // Set background to yellow when unusable 342 | triggerButton.disabled = true; // Disable the button 343 | } -------------------------------------------------------------------------------- /scripts/service-worker-main.js: -------------------------------------------------------------------------------- 1 | const { CHROME_APP_PAGE_URL, FIREFOX_APP_PAGE_URL, MESSAGE_ACTIVATE_ICON } = require("./constants"); 2 | 3 | // Detect browser type 4 | const isFirefox = typeof browser !== 'undefined'; 5 | 6 | // Get the appropriate API 7 | var browser = browser || chrome; 8 | 9 | // enable popup button when content script sends notification 10 | browser.runtime.onMessage.addListener( 11 | function(request, sender, sendResponse) { 12 | if (request.message === MESSAGE_ACTIVATE_ICON) { 13 | if (isFirefox) { 14 | browser.action.show(sender.tab.id); 15 | } else { 16 | browser.action.enable(sender.tab.id); 17 | } 18 | } 19 | } 20 | ); 21 | 22 | // uninstall feedback redirect 23 | if(isFirefox) { 24 | browser.runtime.setUninstallURL(FIREFOX_APP_PAGE_URL); 25 | } else { 26 | browser.runtime.setUninstallURL(CHROME_APP_PAGE_URL); 27 | } 28 | -------------------------------------------------------------------------------- /scripts/strategies/base-strategy.js: -------------------------------------------------------------------------------- 1 | class FeatureStrategy { 2 | hideLockedProblems(checked) {} 3 | highlightSolvedProblems(checked) {} 4 | hideSolvedProb(checked) {} 5 | setSolutionsUsers(checked) {} 6 | toggleByColName(colName, checked) {} 7 | getUserCode() { return ''; } 8 | } 9 | 10 | module.exports = FeatureStrategy; -------------------------------------------------------------------------------- /scripts/strategies/coding-area-strategy.js: -------------------------------------------------------------------------------- 1 | const FeatureStrategy = require('./base-strategy'); 2 | 3 | class CodingAreaStrategy extends FeatureStrategy { 4 | 5 | hideSolvedDiff(checked) { 6 | const diffCodingArea = document.querySelector('[data-track-load="description_content"]')?.parentElement?.previousElementSibling?.firstChild; 7 | const diffNext = document.querySelectorAll("a[rel ='noopener noreferrer'] div"); 8 | 9 | if (diffCodingArea) { 10 | diffCodingArea.classList[checked ? 'remove' : 'add']('hide_leetcode-enhancer'); 11 | } 12 | 13 | if (diffNext) { 14 | diffNext.forEach(el => el.classList[checked ? 'remove' : 'add']('hide_leetcode-enhancer')); 15 | } 16 | } 17 | 18 | hideDiffOfSimilarProb(checked) { 19 | const allAnchors = document.querySelectorAll('a'); 20 | if (!allAnchors || allAnchors.length === 0) return; 21 | 22 | const urlProb = "https://leetcode.com/problems/"; 23 | const curUrl = urlProb + window.location.pathname.split("/")[2] + "/"; 24 | 25 | allAnchors.forEach(anchor => { 26 | if (anchor.href.startsWith(urlProb) && !anchor.href.startsWith(curUrl)) { 27 | const diffElement = anchor.parentElement?.parentElement?.parentElement?.nextElementSibling; 28 | if (diffElement) { 29 | diffElement.classList[checked ? 'remove' : 'add']('hide_leetcode-enhancer'); 30 | } 31 | } 32 | }); 33 | } 34 | 35 | hideStatus(checked) { 36 | const solvedMarkParent = document.querySelector('[data-track-load="description_content"]')?.parentNode?.previousSibling?.previousSibling?.lastChild; 37 | if (solvedMarkParent && solvedMarkParent.classList.contains('text-body')) { 38 | solvedMarkParent.classList[checked ? 'remove' : 'add']('hide_leetcode-enhancer'); 39 | } 40 | } 41 | 42 | hideAcceptance(checked) { 43 | const parentElement = document.querySelector('[data-track-load="description_content"]')?.parentElement?.nextSibling; 44 | if (!parentElement) return; 45 | 46 | const acceptanceRateElement = [...parentElement.children] 47 | .find(child => child.textContent.toLowerCase().includes('acceptance'))?.lastElementChild; 48 | 49 | if (acceptanceRateElement) { 50 | acceptanceRateElement.classList[checked ? 'remove' : 'add']('hide_leetcode-enhancer'); 51 | } 52 | } 53 | 54 | hideSave(checked) { 55 | const saveButton = document.querySelector("svg[data-icon='star']"); 56 | if (saveButton) { 57 | saveButton.classList[checked ? 'remove' : 'add']('hide_leetcode-enhancer'); 58 | } 59 | } 60 | 61 | toggleByColName(colName, checked) { 62 | if (colName === 'difficulty') { 63 | this.hideSolvedDiff(checked); 64 | this.hideDiffOfSimilarProb(checked); 65 | } else if (colName === 'status') { 66 | this.hideStatus(checked); 67 | } else if (colName === 'acceptance') { 68 | this.hideAcceptance(checked); 69 | } else if(colName === 'save') { 70 | this.hideSave(checked); 71 | } 72 | } 73 | 74 | getUserCode() { 75 | const codeEditor = document.querySelector('div.editor-scrollable'); 76 | return codeEditor ? codeEditor.textContent : ''; 77 | } 78 | } 79 | 80 | module.exports = CodingAreaStrategy; -------------------------------------------------------------------------------- /scripts/strategies/contest-strategy.js: -------------------------------------------------------------------------------- 1 | const FeatureStrategy = require('./base-strategy'); 2 | 3 | class ContestStrategy extends FeatureStrategy { 4 | hideDiffFromContest(checked) { 5 | 6 | // old UI 7 | const diffLabel = document.querySelectorAll('.contest-question-info .list-group .list-group-item:nth-child(5) .label')[0]; 8 | if (diffLabel) { 9 | diffLabel.style.visibility = checked ? 'visible_leetcode-enhancer' : 'hidden'; 10 | return; 11 | } 12 | 13 | // new UI 14 | const easyDiffLabel = document.querySelector('.text-difficulty-easy'); 15 | if (easyDiffLabel) { 16 | easyDiffLabel.classList[checked ? 'remove' : 'add']('hide_leetcode-enhancer'); 17 | } 18 | 19 | const mediumDiffLabel = document.querySelector('.text-difficulty-medium'); 20 | if (mediumDiffLabel) { 21 | mediumDiffLabel.classList[checked ? 'remove' : 'add']('hide_leetcode-enhancer'); 22 | } 23 | 24 | const hardDiffLabel = document.querySelector('.text-difficulty-hard'); 25 | if (hardDiffLabel) { 26 | hardDiffLabel.classList[checked ? 'remove' : 'add']('hide_leetcode-enhancer'); 27 | } 28 | 29 | // Handle multiple elements in sidebar 30 | const easyDiffLabelsSideBar = document.querySelectorAll('.text-sd-easy'); 31 | easyDiffLabelsSideBar.forEach(label => { 32 | label.classList[checked ? 'remove' : 'add']('hide_leetcode-enhancer'); 33 | }); 34 | 35 | const mediumDiffLabelsSideBar = document.querySelectorAll('.text-sd-medium'); 36 | mediumDiffLabelsSideBar.forEach(label => { 37 | label.classList[checked ? 'remove' : 'add']('hide_leetcode-enhancer'); 38 | }); 39 | 40 | const hardDiffLabelsSideBar = document.querySelectorAll('.text-sd-hard'); 41 | hardDiffLabelsSideBar.forEach(label => { 42 | label.classList[checked ? 'remove' : 'add']('hide_leetcode-enhancer'); 43 | }); 44 | } 45 | 46 | toggleByColName(colName, checked) { 47 | if (colName === 'difficulty') { 48 | this.hideDiffFromContest(checked); 49 | } 50 | } 51 | } 52 | 53 | module.exports = ContestStrategy; -------------------------------------------------------------------------------- /scripts/strategies/problem-set-strategy.js: -------------------------------------------------------------------------------- 1 | const FeatureStrategy = require('./base-strategy'); 2 | 3 | class NewProblemSetStrategy extends FeatureStrategy { 4 | 5 | hideLockedProblems(checked) { 6 | const temp = document.querySelectorAll('a[id]'); 7 | 8 | temp.forEach(row => { 9 | const cell = row.querySelector(`div>div:nth-child(1)>svg`); 10 | if (cell && cell.getAttribute('data-icon') == 'lock') { 11 | row.classList[checked ? 'remove' : 'add']('hide_leetcode-enhancer'); 12 | } 13 | }); 14 | } 15 | 16 | highlightSolvedProblems(checked) { 17 | const temp = document.querySelectorAll('a[id]'); 18 | const isDarkMode = document.querySelector('html').classList.contains('dark'); 19 | temp.forEach(row => { 20 | const cell = row.querySelector(`div>div:nth-child(1)>svg`); 21 | if (cell && cell.getAttribute('data-icon') == 'check') { 22 | if(!checked) { 23 | row.classList.add(isDarkMode ? 'add-bg-dark_leetcode-enhancer' : 'add-bg-light_leetcode-enhancer'); 24 | } 25 | else { 26 | row.classList.remove('add-bg-dark_leetcode-enhancer'); 27 | row.classList.remove('add-bg-light_leetcode-enhancer'); 28 | } 29 | } 30 | }); 31 | } 32 | 33 | hideSolvedProb(checked) { 34 | const temp = document.querySelectorAll('a[id]'); 35 | 36 | temp.forEach(row => { 37 | const cell = row.querySelector(`div>div:nth-child(1)>svg`); 38 | if (cell && cell.getAttribute('data-icon') == 'check') { 39 | row.classList[checked ? 'remove' : 'add']('hide_leetcode-enhancer'); 40 | } 41 | }); 42 | } 43 | 44 | toggleByColName(colName, checked) { 45 | const temp = document.querySelectorAll('a[id]'); 46 | 47 | if(colName == 'status') { 48 | temp.forEach(row => { 49 | const cell = row.querySelector(`div>div:nth-child(1)`); 50 | if (cell) { 51 | cell.classList[checked ? 'remove' : 'add']('hide_leetcode-enhancer'); 52 | } 53 | }); 54 | } 55 | if(colName == 'acceptance') { 56 | temp.forEach(row => { 57 | const cell = row.querySelector(`div>div:nth-child(2)>div:nth-child(2)`); 58 | if (cell) { 59 | cell.classList[checked ? 'remove' : 'add']('hide_leetcode-enhancer'); 60 | } 61 | }); 62 | } 63 | else if(colName == 'difficulty') { 64 | temp.forEach(row => { 65 | const cell = row.querySelector(`div>div:nth-child(2)>p:nth-child(3)`); 66 | if (cell) { 67 | cell.classList[checked ? 'remove' : 'add']('hide_leetcode-enhancer'); 68 | } 69 | }); 70 | } 71 | else if(colName == 'frequency') { 72 | temp.forEach(row => { 73 | const cell = row.querySelector(`div>div:nth-child(3)`); 74 | if (cell) { 75 | cell.classList[checked ? 'remove' : 'add']('hide_leetcode-enhancer'); 76 | } 77 | }); 78 | } 79 | else if(colName == 'save') { 80 | temp.forEach(row => { 81 | const cell = row.querySelector(`div>div:nth-child(4)>div`); 82 | if (cell) { 83 | cell.classList[checked ? 'remove' : 'add']('hide_leetcode-enhancer'); 84 | } 85 | }); 86 | } 87 | } 88 | } 89 | 90 | module.exports = NewProblemSetStrategy; -------------------------------------------------------------------------------- /scripts/strategies/strategy-factory.js: -------------------------------------------------------------------------------- 1 | const { Mode } = require('../mode.js'); 2 | const ProblemSetStrategy = require('./problem-set-strategy.js'); 3 | const CodingAreaStrategy = require('./coding-area-strategy.js'); 4 | const ContestStrategy = require('./contest-strategy.js'); 5 | 6 | class FeatureStrategyFactory { 7 | static getStrategy(mode) { 8 | switch (mode) { 9 | case Mode.PROBLEM_SET: 10 | return new ProblemSetStrategy(); 11 | case Mode.CODING_AREA: 12 | return new CodingAreaStrategy(); 13 | case Mode.CONTEST: 14 | return new ContestStrategy(); 15 | default: 16 | console.log('No strategy found for mode:', mode); 17 | return null; 18 | } 19 | } 20 | } 21 | 22 | module.exports = FeatureStrategyFactory; -------------------------------------------------------------------------------- /scripts/utils.js: -------------------------------------------------------------------------------- 1 | const print = require('./debugger.js'); 2 | let browser = window.browser || window.chrome; 3 | 4 | /** 5 | * Checks if an object is iterable. 6 | * @param {Object} obj - The object to check. 7 | * @returns {boolean} - True if the object is iterable, false otherwise. 8 | */ 9 | function isIterable(obj) { 10 | if(obj != null && typeof obj[Symbol.iterator] === 'function') 11 | return true; 12 | print(`${JSON.stringify(obj)} is not iterable`) 13 | } 14 | 15 | function storeDataWithObjectWrapping(key, value) { 16 | const data = {}; 17 | data[key] = value; 18 | storeData(data); 19 | } 20 | 21 | function storeData(data) { 22 | browser.storage.local.set(data); 23 | } 24 | 25 | 26 | function getData(key, callback) { 27 | try { 28 | browser.storage.local.get([key], result => callback(result[key])); 29 | } 30 | catch (err) { 31 | print("Error while retrieving key"); 32 | } 33 | } 34 | 35 | async function getDataAsUint8Array(key) { 36 | return new Uint8Array((await browser.storage.local.get(key))[key] || []); 37 | } 38 | 39 | // Export the isIterable function using CommonJS syntax 40 | module.exports = {isIterable, storeDataWithObjectWrapping, getData, getDataAsUint8Array, storeData}; -------------------------------------------------------------------------------- /service-worker.js: -------------------------------------------------------------------------------- 1 | (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i