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
2 changes: 1 addition & 1 deletion manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"manifest_version": 2,
"name": "KeepCode",
"version": "0.1.0",
"version": "0.2.0",
"description": "Focus on answering problems, let KeepCode handle the rest",
"permissions": ["storage", "tabs", "activeTab", "https://leetcode.com/*"],
"background": {
Expand Down
239 changes: 125 additions & 114 deletions popup/popup.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
document.addEventListener("DOMContentLoaded", () => {
browser.tabs.query({ active: true, currentWindow: true }).then((tabs) => {
const tab = tabs[0];
// Get the problem slug from the URL

const url = new URL(tab.url);
const pathParts = url.pathname.split("/").filter(Boolean);
const slug = pathParts[1] || null;
if (!slug) {
document.body.innerHTML = "<p>Not a leetcode problem page</p>";
document.getElementById("popupContent").innerHTML = "<p>To track a problem, please visit a leetcode problem page</p>";
return;
}
// Ask background for the latest problem data
browser.runtime.sendMessage({ type: "GET_PROBLEM_DATA", slug })

browser.runtime
.sendMessage({ type: "GET_PROBLEM_DATA", slug })
.then((data) => {
const container = document.getElementById("popupContent");
if (!data) {
document.body.innerHTML = "<p>Not a leetcode problem page</p>";
container.innerHTML = "<p>To track a problem please visit a leetcode problem page</p>";
return;
}
const container = document.getElementById("popupContent");

container.innerHTML = "";
const titleEl = document.createElement("h3");
Expand All @@ -34,133 +35,143 @@ document.addEventListener("DOMContentLoaded", () => {

// Listen for problem solved message
browser.runtime.onMessage.addListener((msg) => {
if (msg.type === "PROBLEM_SOLVED" && msg.slug === data.slug) {
const statusEl = document.getElementById("status");
if (statusEl) statusEl.textContent = "Solved ✅";
}
if (msg.type === "PROBLEM_SOLVED" && msg.slug === data.slug) {
const statusEl = document.getElementById("status");
if (statusEl) statusEl.textContent = "Solved ✅";
}
});
})
.catch((err) => {
document.body.innerHTML = "<p>Unable to read this page</p>";
document.getElementById("popupContent").innerHTML = "<p>Unable to read this page</p>";
console.error(err);
});
});
});

document.addEventListener("DOMContentLoaded", () => {
browser.storage.local.get(null).then((allData) => {
// Filter out invalid/undefined problems before displaying
const problems = Object.values(allData).filter(p =>
p &&
typeof p.title === 'string' && p.title !== 'Unknown Title' &&
typeof p.slug === 'string' && p.slug !== 'unknown-problem' &&
typeof p.difficulty === 'string' && p.difficulty !== 'Unknown Difficulty' &&
p.status === "Solved"
);
// Sort by solvedAt descending (most recent first)
problems.sort((a, b) => (b.solvedAt || 0) - (a.solvedAt || 0));
browser.storage.local.get(null).then((allData) => {
// Filter out invalid/undefined problems before displaying
const problems = Object.values(allData).filter(
(p) =>
p &&
typeof p.title === "string" &&
p.title !== "Unknown Title" &&
typeof p.slug === "string" &&
p.slug !== "unknown-problem" &&
typeof p.difficulty === "string" &&
p.difficulty !== "Unknown Difficulty" &&
p.status === "Solved"
);
// Sort by solvedAt descending (most recent first)
problems.sort((a, b) => (b.solvedAt || 0) - (a.solvedAt || 0));

// Collect all unique tags (flattened)
const tagSet = new Set();
problems.forEach(p => {
if (Array.isArray(p.tags)) {
p.tags.forEach(tag => tagSet.add(tag));
}
// Collect all unique tags (flattened)
const tagSet = new Set();
problems.forEach((p) => {
if (Array.isArray(p.tags)) {
p.tags.forEach((tag) => tagSet.add(tag));
}
});
const tags = Array.from(tagSet);

// Populate tag filter dropdown
const tagFilter = document.getElementById("tagFilter");
if (tagFilter) {
// Remove old options except 'All'
tagFilter.innerHTML = '<option value="all">All</option>';
if (tags.length > 0) {
tags.forEach((tag) => {
const opt = document.createElement("option");
opt.value = tag;
opt.textContent = tag;
tagFilter.appendChild(opt);
});
const tags = Array.from(tagSet);
} else {
const opt = document.createElement("option");
opt.value = "none";
opt.textContent = "No tags";
tagFilter.appendChild(opt);
}
}

// Populate tag filter dropdown
const tagFilter = document.getElementById("tagFilter");
if (tagFilter) {
// Remove old options except 'All'
tagFilter.innerHTML = '<option value="all">All</option>';
if (tags.length > 0) {
tags.forEach(tag => {
const opt = document.createElement("option");
opt.value = tag;
opt.textContent = tag;
tagFilter.appendChild(opt);
});
} else {
const opt = document.createElement("option");
opt.value = "none";
opt.textContent = "No tags";
tagFilter.appendChild(opt);
}
}
// Render problems (filtered and limited)
function renderProblems() {
const list = document.getElementById("solvedList");
list.innerHTML = "";
let filtered = problems;
const selectedTag = tagFilter ? tagFilter.value : "all";
if (selectedTag && selectedTag !== "all" && selectedTag !== "none") {
filtered = problems.filter(
(p) => Array.isArray(p.tags) && p.tags.includes(selectedTag)
);
}
const toShow = filtered.slice(0, 5);
if (toShow.length === 0) {
list.innerHTML = "<p>No solved problems yet</p>";
}
toShow.forEach((problem) => {
const item = document.createElement("div");
item.className = "problem-item";
const difficultyClass = problem.difficulty
? problem.difficulty.toLowerCase()
: "";
// Use DOM methods instead of innerHTML for safety
const link = document.createElement("a");
link.href = problem.url;
link.target = "_blank";
link.textContent = problem.title;

// Render problems (filtered and limited)
function renderProblems() {
const list = document.getElementById("solvedList");
list.innerHTML = "";
let filtered = problems;
const selectedTag = tagFilter ? tagFilter.value : "all";
if (selectedTag && selectedTag !== "all" && selectedTag !== "none") {
filtered = problems.filter(p => Array.isArray(p.tags) && p.tags.includes(selectedTag));
}
const toShow = filtered.slice(0, 5);
if (toShow.length === 0) {
list.innerHTML = "<p>No solved problems yet</p>";
}
toShow.forEach(problem => {
const item = document.createElement("div");
item.className = "problem-item";
const difficultyClass = problem.difficulty ? problem.difficulty.toLowerCase() : "";
// Use DOM methods instead of innerHTML for safety
const link = document.createElement('a');
link.href = problem.url;
link.target = '_blank';
link.textContent = problem.title;
const diffSpan = document.createElement("span");
diffSpan.className = `difficulty ${difficultyClass}`;
diffSpan.textContent = problem.difficulty;

const diffSpan = document.createElement('span');
diffSpan.className = `difficulty ${difficultyClass}`;
diffSpan.textContent = problem.difficulty;
const tagsSpan = document.createElement("span");
tagsSpan.style.fontSize = "0.85em";
tagsSpan.style.color =
problem.tags && problem.tags.length > 0 ? "#666" : "#bbb";
tagsSpan.style.marginLeft = "8px";
tagsSpan.textContent =
problem.tags && problem.tags.length > 0
? `[${problem.tags.join(", ")}]`
: "[No tags]";

const tagsSpan = document.createElement('span');
tagsSpan.style.fontSize = '0.85em';
tagsSpan.style.color = problem.tags && problem.tags.length > 0 ? '#666' : '#bbb';
tagsSpan.style.marginLeft = '8px';
tagsSpan.textContent = problem.tags && problem.tags.length > 0
? `[${problem.tags.join(', ')}]`
: '[No tags]';
item.appendChild(link);
item.appendChild(diffSpan);
item.appendChild(tagsSpan);
list.appendChild(item);
});
}

item.appendChild(link);
item.appendChild(diffSpan);
item.appendChild(tagsSpan);
list.appendChild(item);
});
}
if (tagFilter) {
tagFilter.addEventListener("change", renderProblems);
}
renderProblems();
});

if (tagFilter) {
tagFilter.addEventListener("change", renderProblems);
}
renderProblems();
// View All link opens options page
const viewAllLink = document.getElementById("viewAllLink");
if (viewAllLink) {
viewAllLink.addEventListener("click", (e) => {
e.preventDefault();
if (browser.runtime && browser.runtime.openOptionsPage) {
browser.runtime.openOptionsPage();
} else {
window.open("../options/options.html", "_blank");
}
});

// View All link opens options page
const viewAllLink = document.getElementById("viewAllLink");
if (viewAllLink) {
viewAllLink.addEventListener("click", (e) => {
e.preventDefault();
if (browser.runtime && browser.runtime.openOptionsPage) {
browser.runtime.openOptionsPage();
} else {
window.open("../options/options.html", "_blank");
}
});
}
}
});

document.addEventListener("DOMContentLoaded", () => {
const optionsBtn = document.getElementById("optionsBtn");
if (optionsBtn) {
optionsBtn.addEventListener("click", () => {
if (browser.runtime && browser.runtime.openOptionsPage) {
browser.runtime.openOptionsPage();
} else {
// fallback for browsers that don't support openOptionsPage
window.open("../options/options.html", "_blank");
}
});
}
const optionsBtn = document.getElementById("optionsBtn");
if (optionsBtn) {
optionsBtn.addEventListener("click", () => {
if (browser.runtime && browser.runtime.openOptionsPage) {
browser.runtime.openOptionsPage();
} else {
// fallback for browsers that don't support openOptionsPage
window.open("../options/options.html", "_blank");
}
});
}
});