Skip to content

Commit fbbe265

Browse files
committed
feat: fleshed out the options page
users can now view full list of problems, filter and search from the options page
1 parent a837913 commit fbbe265

File tree

3 files changed

+244
-13
lines changed

3 files changed

+244
-13
lines changed

options/options.css

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,115 @@ a {
2626
a:hover {
2727
text-decoration: underline;
2828
}
29+
30+
/* Layout */
31+
.options-container {
32+
display: flex;
33+
min-height: 100vh;
34+
background: #f8fafc;
35+
}
36+
37+
.sidebar {
38+
width: 200px;
39+
background: #fff;
40+
border-right: 1.5px solid #e5e7eb;
41+
padding-top: 32px;
42+
min-height: 100vh;
43+
}
44+
.sidebar ul {
45+
list-style: none;
46+
padding: 0;
47+
margin: 0;
48+
}
49+
.nav-item {
50+
display: flex;
51+
align-items: center;
52+
gap: 12px;
53+
padding: 14px 24px;
54+
cursor: pointer;
55+
color: #374151;
56+
font-size: 1.08em;
57+
border-left: 4px solid transparent;
58+
transition: background 0.15s, border-color 0.15s;
59+
}
60+
.nav-item.active, .nav-item:hover {
61+
background: #f3f4f6;
62+
border-left: 4px solid #8868bd;
63+
color: #8868bd;
64+
}
65+
66+
.main-content {
67+
flex: 1;
68+
padding: 32px 40px;
69+
background: #f8fafc;
70+
}
71+
.section {
72+
display: none;
73+
}
74+
.section.active {
75+
display: block;
76+
}
77+
78+
/* Problems Section */
79+
.problems-toolbar {
80+
display: flex;
81+
gap: 12px;
82+
margin-bottom: 18px;
83+
}
84+
#searchInput {
85+
flex: 1;
86+
padding: 7px 12px;
87+
border: 1px solid #d1d5db;
88+
border-radius: 5px;
89+
font-size: 1em;
90+
}
91+
#tagFilter {
92+
padding: 7px 12px;
93+
border: 1px solid #d1d5db;
94+
border-radius: 5px;
95+
font-size: 1em;
96+
}
97+
#problemsList {
98+
margin-top: 8px;
99+
}
100+
.problem-item {
101+
display: flex;
102+
align-items: center;
103+
justify-content: space-between;
104+
background: #e0e7ef;
105+
border-radius: 6px;
106+
padding: 8px 12px;
107+
margin-bottom: 8px;
108+
}
109+
.problem-item a {
110+
color: #1e293b;
111+
text-decoration: none;
112+
font-weight: 500;
113+
}
114+
.difficulty {
115+
padding: 2px 8px;
116+
border-radius: 4px;
117+
font-size: 0.9em;
118+
font-weight: bold;
119+
}
120+
.difficulty.easy {
121+
background: #d1fae5;
122+
color: #059669;
123+
}
124+
.difficulty.medium {
125+
background: #fef3c7;
126+
color: #b45309;
127+
}
128+
.difficulty.hard {
129+
background: #fee2e2;
130+
color: #b91c1c;
131+
}
132+
133+
/* About/FAQ links */
134+
.section a {
135+
color: #2563eb;
136+
text-decoration: none;
137+
}
138+
.section a:hover {
139+
text-decoration: underline;
140+
}

options/options.html

Lines changed: 47 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,55 @@
44
<meta charset="UTF-8">
55
<title>KeepCode Options</title>
66
<link rel="stylesheet" href="options.css">
7+
<!-- Remix Icon CDN for icons -->
8+
<link href="https://cdn.jsdelivr.net/npm/remixicon@3.5.0/fonts/remixicon.css" rel="stylesheet">
79
</head>
810
<body>
9-
<h1>KeepCode Extension Settings</h1>
10-
<section>
11-
<h2>Settings (Coming Soon)</h2>
12-
<p>Here you’ll be able to customize your experience in future updates.</p>
13-
</section>
14-
<section>
15-
<h2>About &amp; Support</h2>
16-
<p>
17-
Made by <a href="https://linktr.ee/267dngi" target="_blank">@_dngi</a>.<br>
18-
Star the project on <a href="https://github.com/kiing-dom/leetcode-tracker" target="_blank">GitHub</a>!<br>
19-
If you find this useful, consider <a href="https://ko-fi.com/267dngi" target="_blank">buying me a kebab 😏</a>.
20-
</p>
21-
</section>
11+
<div class="options-container">
12+
<nav class="sidebar">
13+
<ul>
14+
<li class="nav-item active" data-section="problems">
15+
<i class="ri-list-check-2 ri-lg"></i>
16+
<span>All Problems</span>
17+
</li>
18+
<li class="nav-item" data-section="settings">
19+
<i class="ri-settings-3-line ri-lg"></i>
20+
<span>Settings</span>
21+
</li>
22+
<li class="nav-item" data-section="about">
23+
<i class="ri-question-line ri-lg"></i>
24+
<span>FAQ / About</span>
25+
</li>
26+
</ul>
27+
</nav>
28+
<main class="main-content">
29+
<section id="section-problems" class="section active">
30+
<h2>All Solved Problems</h2>
31+
<div class="problems-toolbar">
32+
<input type="text" id="searchInput" placeholder="Search by title...">
33+
<select id="tagFilter">
34+
<option value="all">All Tags</option>
35+
</select>
36+
</div>
37+
<div id="problemsList"></div>
38+
</section>
39+
<section id="section-settings" class="section">
40+
<h2>Settings</h2>
41+
<p>Settings will be available here in a future update.</p>
42+
</section>
43+
<section id="section-about" class="section">
44+
<h2>FAQ / About</h2>
45+
<p>
46+
Made by <a href="https://linktr.ee/267dngi" target="_blank">@dngi</a>.<br>
47+
Star the project on <a href="https://github.com/kiing-dom/leetcode-tracker" target="_blank">GitHub</a>!<br>
48+
If you find this useful, consider <a href="https://www.ko-fi/267dngi" target="_blank"> buying me a kebab 😏</a>.<br><br>
49+
<strong>FAQ:</strong><br>
50+
<b>Q:</b> How do I use this extension?<br>
51+
<b>A:</b> Just solve problems on LeetCode as usual! Your progress is tracked automatically.<br>
52+
</p>
53+
</section>
54+
</main>
55+
</div>
2256
<script src="options.js"></script>
2357
</body>
2458
</html>

options/options.js

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
// options.js
2+
// Handles navigation and basic filtering for the options page
3+
4+
document.addEventListener('DOMContentLoaded', () => {
5+
// Sidebar navigation
6+
const navItems = document.querySelectorAll('.nav-item');
7+
const sections = document.querySelectorAll('.section');
8+
navItems.forEach(item => {
9+
item.addEventListener('click', () => {
10+
navItems.forEach(i => i.classList.remove('active'));
11+
item.classList.add('active');
12+
const sectionId = 'section-' + item.dataset.section;
13+
sections.forEach(sec => sec.classList.remove('active'));
14+
const activeSection = document.getElementById(sectionId);
15+
if (activeSection) activeSection.classList.add('active');
16+
});
17+
});
18+
19+
// All Problems: load and render problems
20+
const problemsList = document.getElementById('problemsList');
21+
const searchInput = document.getElementById('searchInput');
22+
const tagFilter = document.getElementById('tagFilter');
23+
24+
function renderProblems(problems, filterTag, searchTerm) {
25+
problemsList.innerHTML = '';
26+
let filtered = problems;
27+
if (filterTag && filterTag !== 'all') {
28+
filtered = filtered.filter(p => Array.isArray(p.tags) && p.tags.includes(filterTag));
29+
}
30+
if (searchTerm) {
31+
filtered = filtered.filter(p => p.title.toLowerCase().includes(searchTerm.toLowerCase()));
32+
}
33+
if (filtered.length === 0) {
34+
problemsList.innerHTML = '<p>No problems found.</p>';
35+
}
36+
filtered.forEach(problem => {
37+
const item = document.createElement('div');
38+
item.className = 'problem-item';
39+
const difficultyClass = problem.difficulty ? problem.difficulty.toLowerCase() : '';
40+
const tagsHtml = Array.isArray(problem.tags) && problem.tags.length > 0
41+
? `<span style='font-size:0.85em; color:#666; margin-left:8px;'>[${problem.tags.join(", ")}]</span>`
42+
: "<span style='font-size:0.85em; color:#bbb; margin-left:8px;'>[No tags]</span>";
43+
item.innerHTML = `
44+
<a href="${problem.url}" target="_blank">${problem.title}</a>
45+
<span class="difficulty ${difficultyClass}">${problem.difficulty}</span>
46+
${tagsHtml}
47+
`;
48+
problemsList.appendChild(item);
49+
});
50+
}
51+
52+
// Load problems from storage
53+
browser.storage.local.get(null).then((allData) => {
54+
const problems = Object.values(allData).filter(p =>
55+
p &&
56+
typeof p.title === 'string' && p.title !== 'Unknown Title' &&
57+
typeof p.slug === 'string' && p.slug !== 'unknown-problem' &&
58+
typeof p.difficulty === 'string' && p.difficulty !== 'Unknown Difficulty'
59+
);
60+
problems.sort((a, b) => (b.solvedAt || 0) - (a.solvedAt || 0));
61+
// Populate tag filter
62+
const tagSet = new Set();
63+
problems.forEach(p => {
64+
if (Array.isArray(p.tags)) {
65+
p.tags.forEach(tag => tagSet.add(tag));
66+
}
67+
});
68+
tagFilter.innerHTML = '<option value="all">All Tags</option>';
69+
Array.from(tagSet).forEach(tag => {
70+
const opt = document.createElement('option');
71+
opt.value = tag;
72+
opt.textContent = tag;
73+
tagFilter.appendChild(opt);
74+
});
75+
// Initial render
76+
renderProblems(problems, tagFilter.value, searchInput.value);
77+
// Event listeners
78+
tagFilter.addEventListener('change', () => {
79+
renderProblems(problems, tagFilter.value, searchInput.value);
80+
});
81+
searchInput.addEventListener('input', () => {
82+
renderProblems(problems, tagFilter.value, searchInput.value);
83+
});
84+
});
85+
});

0 commit comments

Comments
 (0)