Skip to content

Instantly share code, notes, and snippets.

@wilmtang
Created March 6, 2026 05:20
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.0
// @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';
// Extract LeetCode slug from the og:description meta tag.
// The tag looks like: "Leetcode 235. Lowest Common Ancestor of a Binary Search Tree\n\n..."
// We grab the title after "Leetcode NNN." and slugify it.
function getLeetCodeUrl() {
const meta = document.querySelector('meta[name="og:description"]');
if (!meta) return null;
const match = meta.getAttribute('content').match(/^Leetcode\s+\d+\.\s+(.+?)(?:\n|$)/i);
if (!match) return null;
const slug = match[1]
.trim()
.toLowerCase()
.replace(/[^a-z0-9\s-]/g, '') // strip special chars
.replace(/\s+/g, '-'); // spaces to hyphens
return `https://leetcode.com/problems/${slug}/`;
}
function findTitleEl() {
const h1 = document.querySelector('h1');
if (h1 && h1.textContent.trim().length > 2) return h1;
return null;
}
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-top: 8px;
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';
});
btn.addEventListener('click', (e) => {
e.stopPropagation();
const url = getLeetCodeUrl();
if (url) window.open(url, '_blank');
});
return btn;
}
let lastPathname = null;
function injectButton() {
if (!/\/problems\//.test(location.pathname)) return;
if (lastPathname === location.pathname && document.getElementById(BUTTON_ID)) return;
document.getElementById(BUTTON_ID)?.remove();
// Wait until the meta tag is present (Angular may render it async)
if (!getLeetCodeUrl()) return;
const titleEl = findTitleEl();
if (!titleEl) return;
titleEl.insertAdjacentElement('afterend', createButton());
lastPathname = location.pathname;
}
// SPA navigation
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