Skip to content

Instantly share code, notes, and snippets.

@wilmtang
Last active May 13, 2026 12:58
Show Gist options
  • Select an option

  • Save wilmtang/8f88d70b85438362dcfc4c7e1cc6e3ad to your computer and use it in GitHub Desktop.

Select an option

Save wilmtang/8f88d70b85438362dcfc4c7e1cc6e3ad to your computer and use it in GitHub Desktop.
Add a button in neetcode to open the equivalent leetcode question
// ==UserScript==
// @name NeetCode -> LeetCode Button
// @namespace http://tampermonkey.net/
// @version 4.3
// @description Adds a button on NeetCode problem pages that opens the equivalent LeetCode problem
// @author You
// @match https://neetcode.io/problems/*
// @grant none
// ==/UserScript==
(function () {
'use strict';
const BUTTON_ID = 'nc-open-leetcode-btn';
function getLeetCodeUrl() {
// Approach 1: Extract the slug directly from the URL path.
// NeetCode slugs map 1:1 with LeetCode slugs.
const match = location.pathname.match(/\/problems\/([^\/]+)/);
if (match && match[1]) {
return `https://leetcode.com/problems/${match[1]}/`;
}
// Approach 2 (Fallback): Parse it from the new H1 title element
const h1 = findTitleEl();
if (!h1) return null;
const slug = h1.textContent
.trim()
.toLowerCase()
.replace(/[^a-z0-9\s-]/g, '')
.replace(/\s+/g, '-');
return `https://leetcode.com/problems/${slug}/`;
}
function findTitleEl() {
return document.querySelector('h1.problem-title') || document.querySelector('h1');
}
function openUrl() {
const url = getLeetCodeUrl();
if (url) window.open(url, '_blank');
}
function createButton() {
const btn = document.createElement('button');
btn.id = BUTTON_ID;
btn.textContent = '🔗 Open on LeetCode';
btn.style.cssText = `
display: inline-flex;
align-items: center;
margin-left: 12px;
padding: 4px 12px;
font-size: 13px;
font-weight: 500;
cursor: pointer;
background: transparent;
border: 1px solid #f89f1b;
border-radius: 6px;
color: #f89f1b;
transition: background 0.15s, color 0.15s;
line-height: 1.5;
white-space: nowrap;
`;
btn.addEventListener('mouseenter', () => {
btn.style.background = '#f89f1b';
btn.style.color = '#1a1a1a';
});
btn.addEventListener('mouseleave', () => {
btn.style.background = 'transparent';
btn.style.color = '#f89f1b';
});
// Left-click
btn.addEventListener('click', (e) => {
e.stopPropagation();
openUrl();
});
// Middle-click (button 1 = middle)
btn.addEventListener('mousedown', (e) => {
if (e.button === 1) {
e.preventDefault(); // prevent auto-scroll cursor
e.stopPropagation();
openUrl();
}
});
return btn;
}
let lastPathname = null;
function injectButton() {
if (!/\/problems\//.test(location.pathname)) return;
const titleEl = findTitleEl();
if (!titleEl) return; // Wait until h1 is rendered
if (lastPathname === location.pathname && document.getElementById(BUTTON_ID)) return;
document.getElementById(BUTTON_ID)?.remove();
if (!getLeetCodeUrl()) return;
titleEl.insertAdjacentElement('afterend', createButton());
lastPathname = location.pathname;
}
const _pushState = history.pushState.bind(history);
history.pushState = function (...args) {
_pushState(...args);
lastPathname = null;
};
window.addEventListener('popstate', () => { lastPathname = null; });
const observer = new MutationObserver(() => injectButton());
observer.observe(document.body, { childList: true, subtree: true });
window.addEventListener('load', injectButton);
setTimeout(injectButton, 1000);
setTimeout(injectButton, 3000);
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment