زیادکردنی فلتەر و کارت پیشرفت بۆ LeetCode.
// ==UserScript==
// @name LeetCode Problem Hider and Filter Panel
// @name:en LeetCode Problem Hider and Filter Panel
// @name:es Panel de filtros y ocultación de problemas de LeetCode
// @name:fr Panneau de filtrage et de masquage des problèmes LeetCode
// @name:de LeetCode Problem-Filter- und Ausblendungs-Panel
// @name:it Pannello filtri e nascondi problemi LeetCode
// @name:pt-BR Painel de filtros e ocultação de problemas do LeetCode
// @name:ru Панель фильтров и скрытия задач LeetCode
// @name:ar لوحة تصفية وإخفاء مسائل LeetCode
// @name:be Панэль фільтраў і ўтойвання задач LeetCode
// @name:nb LeetCode problemfilter- og skjulepanel
// @name:bg Панел за филтриране и скриване на задачи LeetCode
// @name:zh-CN LeetCode 题目筛选与隐藏面板
// @name:zh-TW LeetCode 題目篩選與隱藏面板
// @name:hr LeetCode panel za filtriranje i skrivanje zadataka
// @name:cs Panel pro filtrování a skrývání úloh LeetCode
// @name:da LeetCode filter- og skjulepanel
// @name:nl LeetCode probleemfilter- en verbergpaneel
// @name:eo LeetCode-problemo-filtrilo kaj kaŝpanelo
// @name:fi LeetCode-tehtävien suodatus- ja piilopaneeli
// @name:fr-CA Panneau de filtrage et de masquage LeetCode
// @name:ka LeetCode ამოცანების ფილტრი და დამალვის პანელი
// @name:el Πίνακας φίλτρων και απόκρυψης προβλημάτων LeetCode
// @name:he לוח סינון והסתרת בעיות של LeetCode
// @name:hu LeetCode feladat szűrő és elrejtő panel
// @name:id Panel filter dan sembunyikan soal LeetCode
// @name:ja LeetCode 問題フィルター&非表示パネル
// @name:ko 리트코드 문제 필터 및 숨김 패널
// @name:mr LeetCode समस्या फिल्टर आणि लपविणे पॅनेल
// @name:pl Panel filtrów i ukrywania zadań LeetCode
// @name:ro Panou de filtrare și ascundere a problemelor LeetCode
// @name:sr LeetCode панел за филтрирање и скривање задатака
// @name:sk Panel na filtrovanie a skrývanie úloh LeetCode
// @name:sv LeetCode filter- och döljpanel
// @name:th แผงกรองและซ่อนปัญหา LeetCode
// @name:tr LeetCode problem filtreleme ve gizleme paneli
// @name:ug LeetCode مەسىلە سۈزۈش ۋە يوشۇرۇش تاختىسى
// @name:uk Панель фільтрів і приховування задач LeetCode
// @name:vi Bảng lọc và ẩn bài toán LeetCode
// @name:ckb پانێلی فلتەرکردن و شاردنەوەی کێشەکانی LeetCode
// @version 1.4
// @description Adds advanced filtering for LeetCode problemset including difficulty filters (Easy/Medium/Hard), hiding locked/completed/daily questions, and a live progress card in the sidebar.
// @description:en Adds advanced filtering for LeetCode problemset including difficulty filters (Easy/Medium/Hard), hiding locked/completed/daily questions, and a live progress card in the sidebar.
// @description:es Añade filtros avanzados para LeetCode incluyendo dificultad (Fácil/Medio/Difícil), ocultar problemas bloqueados/completados/diarios y una tarjeta de progreso en la barra lateral.
// @description:fr Ajoute des filtres avancés pour LeetCode incluant difficulté (Facile/Moyen/Difficile), masquage des problèmes verrouillés/terminés/quotidiens et une carte de progression.
// @description:de Fügt erweiterte Filter für LeetCode hinzu einschließlich Schwierigkeit (Leicht/Mittel/Schwer), Ausblenden gesperrter/gelöster/täglicher Aufgaben und Fortschrittskarte.
// @description:it Aggiunge filtri avanzati per LeetCode con difficoltà (Facile/Medio/Difficile), nascondere problemi bloccati/completati/quotidiani e scheda progresso.
// @description:pt-BR Adiciona filtros avançados ao LeetCode incluindo dificuldade (Fácil/Médio/Difícil), ocultar bloqueados/concluídos/diários e cartão de progresso.
// @description:ru Добавляет фильтры LeetCode: сложность (Easy/Medium/Hard), скрытие заблокированных/решённых/ежедневных задач и карточку прогресса.
// @description:ar يضيف فلاتر متقدمة لـ LeetCode تشمل الصعوبة وإخفاء الأسئلة وبطاقة التقدم.
// @description:be Дадае фільтры LeetCode з утойваннем задач і карткай прагрэсу.
// @description:nb Legger til filtre for LeetCode med progresjonskort.
// @description:bg Добавя филтри за LeetCode и карта за прогрес.
// @description:zh-CN 添加 LeetCode 高级筛选(难度/隐藏题目/进度卡片)。
// @description:zh-TW 添加 LeetCode 進階篩選(難度/隱藏題目/進度卡片)。
// @description:hr Dodaje LeetCode filtere i karticu napretka.
// @description:cs Přidává filtry LeetCode a kartu postupu.
// @description:da Tilføjer filtre og progresskort til LeetCode.
// @description:nl Voegt LeetCode filters en voortgangskaart toe.
// @description:eo Aldonas filtrilojn kaj progreskarton por LeetCode.
// @description:fi Lisää LeetCode-suodattimet ja etenemiskortin.
// @description:fr-CA Ajoute filtres et carte de progression pour LeetCode.
// @description:ka ამატებს LeetCode ფილტრებს და პროგრესის ბარათს.
// @description:el Προσθέτει φίλτρα και κάρτα προόδου στο LeetCode.
// @description:he מוסיף מסננים וכרטיס התקדמות ל-LeetCode.
// @description:hu Szűrőket és haladási kártyát ad a LeetCode-hoz.
// @description:id Menambahkan filter dan kartu progres LeetCode.
// @description:ja LeetCodeのフィルターと進捗カードを追加。
// @description:ko LeetCode 필터 및 진행 카드 추가.
// @description:mr LeetCode साठी फिल्टर आणि प्रगती कार्ड जोडतो.
// @description:pl Dodaje filtry i kartę postępu LeetCode.
// @description:ro Adaugă filtre și card de progres pentru LeetCode.
// @description:sr Додаје филтере и картицу напретка.
// @description:sk Pridáva filtre a kartu postupu.
// @description:sv Lägger till filter och progresskort.
// @description:th เพิ่มตัวกรองและการ์ดความคืบหน้า LeetCode.
// @description:tr LeetCode için filtreler ve ilerleme kartı ekler.
// @description:ug LeetCode سۈزگۈچ ۋە ئىلگىرىلەش كارتىسى قوشىدۇ.
// @description:uk Додає фільтри та картку прогресу.
// @description:vi Thêm bộ lọc và thẻ tiến độ LeetCode.
// @description:ckb زیادکردنی فلتەر و کارت پیشرفت بۆ LeetCode.
// @match https://leetcode.com/problemset/*
// @grant none
// @run-at document-idle
// @namespace https://greasyfork.org/users/1591397
// ==/UserScript==
/*
====================================================================
LEETCODE FILTER + PROGRESS USERSCRIPT
====================================================================
PURPOSE:
This script enhances LeetCode’s problem list by adding:
1. Filters (Easy / Medium / Hard)
2. Hide Locked Problems (Premium)
3. Hide Completed Problems
4. Hide Daily Challenge
5. Live progress circle (Solved / Total)
6. Sidebar control panel UI
SAFETY:
- No external network requests
- No data collection
- Only reads local page DOM
- Only stores user settings in localStorage
- Runs fully client-side
====================================================================
*/
(function () {
'use strict';
/* ------------------------------------------------------------
PREVENT DUPLICATE EXECUTION
------------------------------------------------------------
LeetCode is a Single Page Application (SPA).
This script may be injected multiple times.
This flag ensures it only runs once.
------------------------------------------------------------ */
if (window.__lc_ui_suite_opt) return;
window.__lc_ui_suite_opt = true;
/* ------------------------------------------------------------
CONSTANTS
------------------------------------------------------------ */
// CSS class used to hide elements
const HIDDEN = 'lc-hidden';
// Key used in browser localStorage
const STORE_KEY = 'lc_pro_suite_settings_v6';
/* ------------------------------------------------------------
DEFAULT USER SETTINGS
------------------------------------------------------------
These values are used if user has never saved settings.
------------------------------------------------------------ */
const defaultSettings = {
hideLocked: true, // hide premium locked problems
hideCompleted: false, // hide solved problems
hideDaily: false, // hide daily challenge
easy: true, // show easy problems
medium: true, // show medium problems
hard: true, // show hard problems
collapsed: false // sidebar open/closed
};
/* ------------------------------------------------------------
LOAD SAVED SETTINGS
------------------------------------------------------------ */
let settings =
JSON.parse(localStorage.getItem(STORE_KEY) || 'null') ||
defaultSettings;
/* Save settings whenever user changes a toggle */
function saveSettings() {
localStorage.setItem(STORE_KEY, JSON.stringify(settings));
}
/* ------------------------------------------------------------
STYLE INJECTION (CSS)
------------------------------------------------------------
We inject CSS dynamically so:
- No manual installation needed
- Works instantly on page load
------------------------------------------------------------ */
const style = document.createElement('style');
style.textContent = `
/* Hidden rows */
.${HIDDEN}{display:none!important}
/* Sidebar container */
#lc-suite{
margin-top:10px;
border-radius:14px;
background:rgba(255,255,255,0.04);
border:1px solid rgba(255,255,255,0.06);
overflow:hidden;
}
/* Header bar */
#lc-header{
padding:12px;
cursor:pointer;
display:flex;
justify-content:space-between;
font-weight:700;
font-size:13px;
color:#e5e7eb;
}
/* Panel body */
#lc-body{padding:12px}
/* Collapsed state hides body */
#lc-suite.collapsed #lc-body{display:none}
/* Toggle button styling */
.lc-btn{
display:flex;
justify-content:space-between;
padding:10px;
margin:6px 0;
border-radius:10px;
cursor:pointer;
background:rgba(255,255,255,0.03);
transition:0.15s;
user-select:none;
}
.lc-btn:hover{transform:translateY(-1px)}
.lc-active{background:rgba(34,197,94,0.18)}
/* Progress card container */
.lc-progress-card{
border-radius:13px;
border:1px solid rgba(255,255,255,0.08);
background:rgba(255,255,255,0.03);
width:100%;
padding:12px;
margin:6px 0 10px 0;
display:block;
text-decoration:none;
color:inherit;
}
/* Layout inside progress card */
.lc-progress-inner{
display:flex;
align-items:center;
gap:10px;
}
/* Progress text */
.lc-progress-text{
font-size:13px;
color:#9ca3af;
font-weight:500;
}
`;
document.head.appendChild(style);
/* ------------------------------------------------------------
ROW SELECTION SYSTEM
------------------------------------------------------------
We select ALL possible problem rows because LeetCode
uses different layouts depending on page state.
------------------------------------------------------------ */
const ROW_SELECTOR =
'[role="row"], tr, [data-cy="question-card"], a[href*="/problems/"]';
/* ------------------------------------------------------------
DIFFICULTY DETECTION
------------------------------------------------------------ */
function getDifficulty(el) {
if (!el) return null;
// Best case: LeetCode uses explicit CSS classes
if (el.querySelector('.text-sd-easy')) return 'easy';
if (el.querySelector('.text-sd-medium')) return 'medium';
if (el.querySelector('.text-sd-hard')) return 'hard';
// Fallback: text-based detection
const t = el.textContent;
if (!t) return null;
const lower = t.toLowerCase();
if (lower.includes('easy')) return 'easy';
if (lower.includes('med')) return 'medium';
if (lower.includes('hard')) return 'hard';
return null;
}
/* Detect locked premium problems */
function isLocked(el) {
return !!el.querySelector('svg[data-icon="lock"]');
}
/* Detect solved problems */
function isCompleted(el) {
return !!el.querySelector('svg[data-icon="check"], [class*="check"]');
}
/* Detect daily challenge rows */
function isDaily(el) {
return !!el.querySelector('svg[data-icon="calendar"]');
}
/* ------------------------------------------------------------
FILTER ENGINE
------------------------------------------------------------
This decides whether a row should be hidden or shown.
------------------------------------------------------------ */
function shouldHide(row) {
if (isDaily(row) && settings.hideDaily) return true;
const diff = getDifficulty(row);
if (diff === 'easy' && !settings.easy) return true;
if (diff === 'medium' && !settings.medium) return true;
if (diff === 'hard' && !settings.hard) return true;
if (settings.hideLocked && isLocked(row)) return true;
if (settings.hideCompleted && isCompleted(row)) return true;
return false;
}
/* Apply visibility changes to a row */
function applyRow(row) {
if (!row) return;
const target =
row.closest?.('[role="row"], tr, [data-cy="question-card"]') || row;
const hide = shouldHide(target);
// Avoid unnecessary DOM updates (performance optimization)
if (target.classList.contains(HIDDEN) === hide) return;
target.classList.toggle(HIDDEN, hide);
}
/* ------------------------------------------------------------
FULL PAGE SCAN
------------------------------------------------------------ */
function scanAll() {
const rows = document.querySelectorAll(ROW_SELECTOR);
for (let i = 0; i < rows.length; i++) {
applyRow(rows[i]);
}
}
/* ------------------------------------------------------------
MUTATION OBSERVER
------------------------------------------------------------
LeetCode loads content dynamically.
This watches for new problems being added.
------------------------------------------------------------ */
const observer = new MutationObserver((mutations) => {
for (let i = 0; i < mutations.length; i++) {
const m = mutations[i];
for (let j = 0; j < m.addedNodes.length; j++) {
const node = m.addedNodes[j];
if (!(node instanceof HTMLElement)) continue;
if (node.matches?.(ROW_SELECTOR)) applyRow(node);
const children = node.querySelectorAll?.(ROW_SELECTOR);
if (children?.length) {
for (let k = 0; k < children.length; k++) {
applyRow(children[k]);
}
}
}
}
});
function startObserver() {
observer.observe(document.body, {
childList: true,
subtree: true
});
}
/* ------------------------------------------------------------
PROGRESS PARSER
------------------------------------------------------------
Reads text like:
"123 / 350 Solved"
------------------------------------------------------------ */
function parseProgress() {
const el =
document.querySelector('a[href="/progress/"] .text-sd-muted-foreground') ||
document.querySelector('a[href="/progress/"] div');
const text = el?.innerText?.trim() || '';
const match = text.match(/(\d+)\s*\/\s*(\d+)/);
if (!match) return { solved: 0, total: 0, raw: text };
return {
solved: +match[1],
total: +match[2],
raw: text
};
}
/* ------------------------------------------------------------
PROGRESS CIRCLE UI
------------------------------------------------------------ */
function createProgressCard() {
const a = document.createElement('a');
a.href = '/progress/';
a.className = 'lc-progress-card';
const wrap = document.createElement('div');
wrap.className = 'lc-progress-inner';
// SVG setup
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.setAttribute('viewBox', '0 0 100 100');
svg.style.width = '42px';
svg.style.height = '42px';
const r = 42;
const C = 2 * Math.PI * r; // circumference formula
// Background circle (empty track)
const bg = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
bg.setAttribute('cx', '50');
bg.setAttribute('cy', '50');
bg.setAttribute('r', '42');
bg.setAttribute('fill', 'none');
bg.setAttribute('stroke', '#2a2a2a');
bg.setAttribute('stroke-width', '10');
// Foreground progress circle
const fg = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
fg.setAttribute('cx', '50');
fg.setAttribute('cy', '50');
fg.setAttribute('r', '42');
fg.setAttribute('fill', 'none');
fg.setAttribute('stroke', '#22c55e');
fg.setAttribute('stroke-width', '10');
fg.setAttribute('stroke-linecap', 'round');
// Rotate so progress starts from top
fg.style.transform = 'rotate(-90deg)';
fg.style.transformOrigin = '50% 50%';
// Stroke-based progress system
fg.setAttribute('stroke-dasharray', String(C));
fg.setAttribute('stroke-dashoffset', String(C));
svg.appendChild(bg);
svg.appendChild(fg);
const text = document.createElement('div');
text.className = 'lc-progress-text';
let last = '';
function update() {
const { solved, total, raw } = parseProgress();
if (!total) {
if (raw !== last) text.textContent = raw || 'Loading...';
return;
}
const pct = solved / total;
// Update circular progress
fg.style.strokeDashoffset = C * (1 - pct);
const newText = `${solved}/${total} Solved`;
if (newText !== last) {
text.textContent = newText;
last = newText;
}
}
update();
setInterval(update, 2500);
wrap.append(svg, text);
a.appendChild(wrap);
return a;
}
/* ------------------------------------------------------------
UI BUTTON SYSTEM
------------------------------------------------------------ */
function btn(label, key) {
const el = document.createElement('div');
el.className = 'lc-btn';
const render = () => {
el.textContent = '';
el.innerHTML = `<span>${label}</span><b>${settings[key] ? 'ON' : 'OFF'}</b>`;
el.classList.toggle('lc-active', settings[key]);
};
el.onclick = () => {
settings[key] = !settings[key];
// prevent disabling all difficulty filters
if (['easy', 'medium', 'hard'].includes(key)) {
if (!settings.easy && !settings.medium && !settings.hard) {
settings[key] = true;
}
}
saveSettings();
render();
scanAll();
};
render();
return el;
}
/* ------------------------------------------------------------
SIDEBAR INJECTION
------------------------------------------------------------ */
function insertSidebar() {
if (document.getElementById('lc-suite')) return true;
const sidebar = document.querySelector('#sidebarWidthContainer');
if (!sidebar) return false;
const wrap = document.createElement('div');
wrap.id = 'lc-suite';
const header = document.createElement('div');
header.id = 'lc-header';
header.textContent = 'LeetCode Panel';
header.onclick = () => {
settings.collapsed = !settings.collapsed;
wrap.classList.toggle('collapsed');
saveSettings();
};
const body = document.createElement('div');
body.id = 'lc-body';
body.append(
createProgressCard(),
btn('Hide Locked', 'hideLocked'),
btn('Hide Completed', 'hideCompleted'),
btn('Hide Daily', 'hideDaily'),
btn('Easy', 'easy'),
btn('Medium', 'medium'),
btn('Hard', 'hard')
);
wrap.append(header, body);
sidebar.prepend(wrap);
return true;
}
/* ------------------------------------------------------------
INITIALIZATION FLOW
------------------------------------------------------------ */
function waitForSidebarThenInit() {
const tryInit = () => {
const sidebar = document.querySelector('#sidebarWidthContainer');
if (sidebar) {
scanAll();
startObserver();
insertSidebar(); // try immediately
return true;
}
return false;
};
// Try immediately first
if (tryInit()) return;
// Then watch DOM until sidebar appears (THIS is the fix)
const bootObserver = new MutationObserver(() => {
if (tryInit()) {
bootObserver.disconnect();
}
});
bootObserver.observe(document.documentElement, {
childList: true,
subtree: true
});
}
function init() {
waitForSidebarThenInit();
}
// start script
if (document.body) init();
else window.addEventListener('DOMContentLoaded', init);
})();