Skip to content

Commit a2024dc

Browse files
authored
Merge pull request #15 from kiing-dom/feat/filter-problem-difficulty
NEW FEATURE: PROBLEM DIFFICULTY FILTER
2 parents ac72ce5 + 2458353 commit a2024dc

File tree

6 files changed

+196
-6
lines changed

6 files changed

+196
-6
lines changed

content.js

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
// Import fetchLeetCodeTags from leetcodeApi.js
2-
// (Assumes leetcodeApi.js is loaded before this script as per manifest)
3-
41
async function getProblemData() {
52
const titleEl = document.querySelector('div[class*="text-title-large"]');
63
const title = titleEl ? titleEl.textContent.trim() : "Unknown Title";

manifest.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
{
22
"manifest_version": 2,
33
"name": "KeepCode",
4-
"version": "0.3.0",
5-
"description": "Prep smarter for coding interviews: KeepCode tracks your LeetCode progress so you can focus on solving.",
4+
"version": "0.3.5",
5+
"description": "Prep smarter for coding interviews! KeepCode tracks your LeetCode progress so you can focus on solving.",
66
"permissions": ["storage", "tabs", "activeTab", "https://leetcode.com/*"],
77
"background": {
88
"scripts": ["background.js"],

options/difficultyDropdown.js

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
class DifficultyDropdown {
2+
constructor(container, difficulties, onChange) {
3+
this.container = container;
4+
this.difficulties = difficulties;
5+
this.onChange = onChange;
6+
this.selectedDifficulty = 'all';
7+
this.filteredDifficulties = ['all', ...difficulties];
8+
this.createDropdown();
9+
}
10+
11+
createDropdown() {
12+
this.container.innerHTML = '';
13+
this.dropdown = document.createElement('div');
14+
this.dropdown.className = 'difficulty-dropdown';
15+
16+
this.selected = document.createElement('div');
17+
this.selected.className = 'difficulty-dropdown-selected';
18+
this.selected.tabIndex = 0;
19+
20+
const selectedText = document.createElement('span');
21+
selectedText.className = 'difficulty-dropdown-selected-text';
22+
selectedText.textContent = 'All Difficulties';
23+
this.selected.appendChild(selectedText);
24+
const chevron = document.createElement('i');
25+
chevron.className = 'ri-arrow-down-s-line difficulty-dropdown-chevron'
26+
this.selected.appendChild(chevron);
27+
this.selected.addEventListener('click', () => this.toggleList());
28+
this.selected.addEventListener('keydown', (e) => {
29+
if (e.key == 'Enter' || e.key == ' ') this.toggleList();
30+
});
31+
this.dropdown.appendChild(this.selected);
32+
33+
this.list = document.createElement('div');
34+
this.list.className = 'difficulty-dropdown-list'
35+
this.list.style.display = 'none'
36+
37+
this.searchInput = document.createElement('input');
38+
this.searchInput.type = 'text';
39+
this.searchInput.className = 'difficulty-dropdown-search';
40+
this.searchInput.placeholder = 'Search Difficulties...';
41+
this.searchInput.addEventListener('input', () => this.filterDifficulties());
42+
this.list.appendChild(this.searchInput);
43+
44+
this.optionsContainer = document.createElement('div');
45+
this.optionsContainer.className = 'difficulty-dropdown-options';
46+
this.list.appendChild(this.optionsContainer);
47+
48+
this.dropdown.appendChild(this.list);
49+
this.container.appendChild(this.dropdown);
50+
51+
this.renderOptions();
52+
document.addEventListener('click', (e) => {
53+
if (!this.dropdown.contains(e.target)) this.closeList();
54+
});
55+
}
56+
57+
renderOptions() {
58+
this.optionsContainer.innerHTML = '';
59+
this.filteredDifficulties.forEach(difficulty => {
60+
const opt = document.createElement('div');
61+
opt.className = 'difficulty-dropdown-option';
62+
opt.textContent = difficulty === 'all' ? 'All Difficulties' : difficulty;
63+
if (difficulty === this.selectedDifficulty) opt.classList.add('selected');
64+
opt.addEventListener('click', () => this.selectDifficulty(difficulty));
65+
this.optionsContainer.appendChild(opt);
66+
});
67+
}
68+
69+
filterDifficulties() {
70+
const val = this.searchInput.value.toLowerCase();
71+
this.filteredDifficulties = ['all', ...this.difficulties
72+
.filter(difficulty => difficulty
73+
.toLowerCase()
74+
.includes(val)
75+
)];
76+
this.renderOptions();
77+
}
78+
79+
selectDifficulty(difficulty) {
80+
this.selectedDifficulty = difficulty;
81+
82+
const textSpan = this.selected.querySelector('.difficulty-dropdown-selected-text');
83+
if (textSpan) textSpan.textContent = difficulty === 'all' ? 'All Difficulties' : difficulty;
84+
this.closeList();
85+
if (this.onChange) this.onChange(difficulty);
86+
}
87+
88+
toggleList() {
89+
this.list.style.display = this.list.style.display === 'none' ? 'block' : 'none';
90+
if (this.list.style.display === 'block') {
91+
this.searchInput.value = '';
92+
this.filterDifficulties();
93+
this.searchInput.focus();
94+
}
95+
}
96+
97+
closeList() {
98+
this.list.style.display = 'none';
99+
}
100+
101+
setDifficulty(difficulties) {
102+
this.difficulties = difficulties;
103+
this.filteredDifficulties = ['all', ...difficulties];
104+
this.renderOptions();
105+
}
106+
}
107+
window.DifficultyDropdown = DifficultyDropdown;

options/options.css

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,3 +274,70 @@ a:hover {
274274
outline: none;
275275
border-color: #8868bd;
276276
}
277+
278+
/* Difficulty Dropdown Styles (to match Tag Dropdown) */
279+
.difficulty-dropdown,
280+
.difficulty-dropdown-selected,
281+
.difficulty-dropdown-list {
282+
position: relative;
283+
font-size: 1em;
284+
}
285+
.difficulty-dropdown-selected {
286+
background: #fff;
287+
border: 1px solid #d1d5db;
288+
border-radius: 5px;
289+
padding: 7px 12px;
290+
cursor: pointer;
291+
user-select: none;
292+
min-width: 120px;
293+
transition: border-color 0.15s;
294+
}
295+
.difficulty-dropdown-selected:focus {
296+
outline: none;
297+
border-color: #8868bd;
298+
}
299+
.difficulty-dropdown-list {
300+
position: absolute;
301+
top: 110%;
302+
left: 0;
303+
right: 0;
304+
background: #fff;
305+
border: 1px solid #d1d5db;
306+
border-radius: 5px;
307+
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
308+
z-index: 10;
309+
padding: 0;
310+
}
311+
.difficulty-dropdown-search {
312+
width: 100%;
313+
margin: 0;
314+
border-radius: 5px 5px 0 0;
315+
border-bottom: 1px solid #e5e7eb;
316+
border-top: none;
317+
border-left: none;
318+
border-right: none;
319+
box-sizing: border-box;
320+
padding: 10px 12px;
321+
font-size: 1em;
322+
background: #fafbfc;
323+
}
324+
.difficulty-dropdown-options {
325+
max-height: 180px;
326+
overflow-y: auto;
327+
padding-top: 4px;
328+
}
329+
.difficulty-dropdown-list {
330+
border-radius: 5px;
331+
overflow: hidden;
332+
}
333+
.difficulty-dropdown-option {
334+
padding: 7px 16px;
335+
cursor: pointer;
336+
transition: background 0.13s;
337+
color: #374151;
338+
}
339+
.difficulty-dropdown-option.selected,
340+
.difficulty-dropdown-option:hover {
341+
background: #f3f4f6;
342+
color: #8868bd;
343+
}

options/options.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ <h2>All Solved Problems</h2>
3535
<option value="recent-desc">Most recently solved</option>
3636
<option value="recent-asc">Least recently solved</option>
3737
</select>
38+
<div id="difficultyDropdownContainer"></div>
3839
</div>
3940
<div id="problemsList"></div>
4041
</section>
@@ -64,6 +65,7 @@ <h2>FAQ / About</h2>
6465
</main>
6566
</div>
6667
<script src="tagDropdown.js"></script>
68+
<script src="difficultyDropdown.js"></script>
6769
<script src="options.js"></script>
6870
</body>
6971
</html>

options/options.js

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,20 @@ document.addEventListener('DOMContentLoaded', () => {
2020
const searchInput = document.getElementById('searchInput');
2121
const tagDropdownContainer = document.getElementById('tagDropdownContainer');
2222
let tagDropdownInstance = null;
23+
const difficultyDropdownContainer = document.getElementById('difficultyDropdownContainer');
24+
let difficultyDropdownInstance = null;
2325

2426
function renderProblems(problems, filterTag, searchTerm, sortOrder = 'recent-desc') {
2527
problemsList.innerHTML = '';
2628
let filtered = problems;
2729
if (filterTag && filterTag !== 'all') {
2830
filtered = filtered.filter(p => Array.isArray(p.tags) && p.tags.includes(filterTag));
2931
}
32+
33+
if (filterDifficulty && filterDifficulty == 'all') {
34+
filtered = filtered.filter(p => difficulty === filterDifficulty);
35+
}
36+
3037
if (searchTerm) {
3138
filtered = filtered.filter(p => p.title.toLowerCase().includes(searchTerm.toLowerCase()));
3239
}
@@ -81,20 +88,30 @@ document.addEventListener('DOMContentLoaded', () => {
8188
}
8289
});
8390
const allTags = Array.from(tagSet).sort((a, b) => a.localeCompare(b));
84-
let currentProblems = problems; // Store for re-sorting/filtering
91+
const allDifficulties = ['Easy', 'Medium', 'Hard'];
92+
let currentProblems = problems;
8593
function rerender() {
8694
renderProblems(
8795
currentProblems,
8896
tagDropdownInstance ? tagDropdownInstance.selectedTag : 'all',
97+
difficultyDropdownInstance ? difficultyDropdownInstance.selectedDifficulty : 'all',
8998
searchInput.value,
9099
sortDropdown.value
91100
);
92101
}
102+
93103
if (tagDropdownInstance) {
94104
tagDropdownInstance.setTags(allTags);
95105
} else {
96106
tagDropdownInstance = new window.TagDropdown(tagDropdownContainer, allTags, () => rerender());
97107
}
108+
109+
if (difficultyDropdownInstance) {
110+
difficultyDropdownInstance.setDifficulty(allDifficulties);
111+
} else {
112+
difficultyDropdownInstance = new window.DifficultyDropdown(difficultyDropdownContainer, allDifficulties, () => rerender());
113+
}
114+
98115
// Initial render
99116
rerender();
100117
// Event listeners

0 commit comments

Comments
 (0)