Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions content.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
// Import fetchLeetCodeTags from leetcodeApi.js
// (Assumes leetcodeApi.js is loaded before this script as per manifest)

async function getProblemData() {
const titleEl = document.querySelector('div[class*="text-title-large"]');
const title = titleEl ? titleEl.textContent.trim() : "Unknown Title";
Expand Down
4 changes: 2 additions & 2 deletions manifest.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"manifest_version": 2,
"name": "KeepCode",
"version": "0.3.0",
"description": "Prep smarter for coding interviews: KeepCode tracks your LeetCode progress so you can focus on solving.",
"version": "0.3.5",
"description": "Prep smarter for coding interviews! KeepCode tracks your LeetCode progress so you can focus on solving.",
"permissions": ["storage", "tabs", "activeTab", "https://leetcode.com/*"],
"background": {
"scripts": ["background.js"],
Expand Down
107 changes: 107 additions & 0 deletions options/difficultyDropdown.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
class DifficultyDropdown {
constructor(container, difficulties, onChange) {
this.container = container;
this.difficulties = difficulties;
this.onChange = onChange;
this.selectedDifficulty = 'all';
this.filteredDifficulties = ['all', ...difficulties];
this.createDropdown();
}

createDropdown() {
this.container.innerHTML = '';
this.dropdown = document.createElement('div');
this.dropdown.className = 'difficulty-dropdown';

this.selected = document.createElement('div');
this.selected.className = 'difficulty-dropdown-selected';
this.selected.tabIndex = 0;

const selectedText = document.createElement('span');
selectedText.className = 'difficulty-dropdown-selected-text';
selectedText.textContent = 'All Difficulties';
this.selected.appendChild(selectedText);
const chevron = document.createElement('i');
chevron.className = 'ri-arrow-down-s-line difficulty-dropdown-chevron'
this.selected.appendChild(chevron);
this.selected.addEventListener('click', () => this.toggleList());
this.selected.addEventListener('keydown', (e) => {
if (e.key == 'Enter' || e.key == ' ') this.toggleList();
});
this.dropdown.appendChild(this.selected);

this.list = document.createElement('div');
this.list.className = 'difficulty-dropdown-list'
this.list.style.display = 'none'

this.searchInput = document.createElement('input');
this.searchInput.type = 'text';
this.searchInput.className = 'difficulty-dropdown-search';
this.searchInput.placeholder = 'Search Difficulties...';
this.searchInput.addEventListener('input', () => this.filterDifficulties());
this.list.appendChild(this.searchInput);

this.optionsContainer = document.createElement('div');
this.optionsContainer.className = 'difficulty-dropdown-options';
this.list.appendChild(this.optionsContainer);

this.dropdown.appendChild(this.list);
this.container.appendChild(this.dropdown);

this.renderOptions();
document.addEventListener('click', (e) => {
if (!this.dropdown.contains(e.target)) this.closeList();
});
}

renderOptions() {
this.optionsContainer.innerHTML = '';
this.filteredDifficulties.forEach(difficulty => {
const opt = document.createElement('div');
opt.className = 'difficulty-dropdown-option';
opt.textContent = difficulty === 'all' ? 'All Difficulties' : difficulty;
if (difficulty === this.selectedDifficulty) opt.classList.add('selected');
opt.addEventListener('click', () => this.selectDifficulty(difficulty));
this.optionsContainer.appendChild(opt);
});
}

filterDifficulties() {
const val = this.searchInput.value.toLowerCase();
this.filteredDifficulties = ['all', ...this.difficulties
.filter(difficulty => difficulty
.toLowerCase()
.includes(val)
)];
this.renderOptions();
}

selectDifficulty(difficulty) {
this.selectedDifficulty = difficulty;

const textSpan = this.selected.querySelector('.difficulty-dropdown-selected-text');
if (textSpan) textSpan.textContent = difficulty === 'all' ? 'All Difficulties' : difficulty;
this.closeList();
if (this.onChange) this.onChange(difficulty);
}

toggleList() {
this.list.style.display = this.list.style.display === 'none' ? 'block' : 'none';
if (this.list.style.display === 'block') {
this.searchInput.value = '';
this.filterDifficulties();
this.searchInput.focus();
}
}

closeList() {
this.list.style.display = 'none';
}

setDifficulty(difficulties) {
this.difficulties = difficulties;
this.filteredDifficulties = ['all', ...difficulties];
this.renderOptions();
}
}
window.DifficultyDropdown = DifficultyDropdown;
67 changes: 67 additions & 0 deletions options/options.css
Original file line number Diff line number Diff line change
Expand Up @@ -274,3 +274,70 @@ a:hover {
outline: none;
border-color: #8868bd;
}

/* Difficulty Dropdown Styles (to match Tag Dropdown) */
.difficulty-dropdown,
.difficulty-dropdown-selected,
.difficulty-dropdown-list {
position: relative;
font-size: 1em;
}
.difficulty-dropdown-selected {
background: #fff;
border: 1px solid #d1d5db;
border-radius: 5px;
padding: 7px 12px;
cursor: pointer;
user-select: none;
min-width: 120px;
transition: border-color 0.15s;
}
.difficulty-dropdown-selected:focus {
outline: none;
border-color: #8868bd;
}
.difficulty-dropdown-list {
position: absolute;
top: 110%;
left: 0;
right: 0;
background: #fff;
border: 1px solid #d1d5db;
border-radius: 5px;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
z-index: 10;
padding: 0;
}
.difficulty-dropdown-search {
width: 100%;
margin: 0;
border-radius: 5px 5px 0 0;
border-bottom: 1px solid #e5e7eb;
border-top: none;
border-left: none;
border-right: none;
box-sizing: border-box;
padding: 10px 12px;
font-size: 1em;
background: #fafbfc;
}
.difficulty-dropdown-options {
max-height: 180px;
overflow-y: auto;
padding-top: 4px;
}
.difficulty-dropdown-list {
border-radius: 5px;
overflow: hidden;
}
.difficulty-dropdown-option {
padding: 7px 16px;
cursor: pointer;
transition: background 0.13s;
color: #374151;
}
.difficulty-dropdown-option.selected,
.difficulty-dropdown-option:hover {
background: #f3f4f6;
color: #8868bd;
}
2 changes: 2 additions & 0 deletions options/options.html
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ <h2>All Solved Problems</h2>
<option value="recent-desc">Most recently solved</option>
<option value="recent-asc">Least recently solved</option>
</select>
<div id="difficultyDropdownContainer"></div>
</div>
<div id="problemsList"></div>
</section>
Expand Down Expand Up @@ -64,6 +65,7 @@ <h2>FAQ / About</h2>
</main>
</div>
<script src="tagDropdown.js"></script>
<script src="difficultyDropdown.js"></script>
<script src="options.js"></script>
</body>
</html>
19 changes: 18 additions & 1 deletion options/options.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,20 @@ document.addEventListener('DOMContentLoaded', () => {
const searchInput = document.getElementById('searchInput');
const tagDropdownContainer = document.getElementById('tagDropdownContainer');
let tagDropdownInstance = null;
const difficultyDropdownContainer = document.getElementById('difficultyDropdownContainer');
let difficultyDropdownInstance = null;

function renderProblems(problems, filterTag, searchTerm, sortOrder = 'recent-desc') {
problemsList.innerHTML = '';
let filtered = problems;
if (filterTag && filterTag !== 'all') {
filtered = filtered.filter(p => Array.isArray(p.tags) && p.tags.includes(filterTag));
}

if (filterDifficulty && filterDifficulty == 'all') {
filtered = filtered.filter(p => difficulty === filterDifficulty);
}

if (searchTerm) {
filtered = filtered.filter(p => p.title.toLowerCase().includes(searchTerm.toLowerCase()));
}
Expand Down Expand Up @@ -81,20 +88,30 @@ document.addEventListener('DOMContentLoaded', () => {
}
});
const allTags = Array.from(tagSet).sort((a, b) => a.localeCompare(b));
let currentProblems = problems; // Store for re-sorting/filtering
const allDifficulties = ['Easy', 'Medium', 'Hard'];
let currentProblems = problems;
function rerender() {
renderProblems(
currentProblems,
tagDropdownInstance ? tagDropdownInstance.selectedTag : 'all',
difficultyDropdownInstance ? difficultyDropdownInstance.selectedDifficulty : 'all',
searchInput.value,
sortDropdown.value
);
}

if (tagDropdownInstance) {
tagDropdownInstance.setTags(allTags);
} else {
tagDropdownInstance = new window.TagDropdown(tagDropdownContainer, allTags, () => rerender());
}

if (difficultyDropdownInstance) {
difficultyDropdownInstance.setDifficulty(allDifficulties);
} else {
difficultyDropdownInstance = new window.DifficultyDropdown(difficultyDropdownContainer, allDifficulties, () => rerender());
}

// Initial render
rerender();
// Event listeners
Expand Down