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
301 changes: 160 additions & 141 deletions popup/popup.js
Original file line number Diff line number Diff line change
@@ -1,166 +1,185 @@
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 })
.then((data) => {
if (!data) {
document.body.innerHTML = "<p>Not a leetcode problem page</p>";
return;
}
const container = document.getElementById("popupContent");

container.innerHTML = "";
const titleEl = document.createElement("h3");
titleEl.textContent = `Problem: ${data.title}`;
const diffEl = document.createElement("p");
diffEl.textContent = `Difficulty: ${data.difficulty} `;
const statusEl = document.createElement("p");
statusEl.id = "status";
const strongEl = document.createElement("strong");
strongEl.textContent = data.status || "Unsolved";
statusEl.appendChild(strongEl);
container.appendChild(titleEl);
container.appendChild(diffEl);
container.appendChild(statusEl);

// 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 ✅";

const container = document.getElementById("popupContent");

browser.storage.local.get(slug).then((result) => {
let data = result[slug];
if (data) {
renderCurrentProblem(data);
} else {

browser.runtime
.sendMessage({ type: "GET_PROBLEM_DATA", slug })
.then((data) => {
if (data) {
renderCurrentProblem(data);
} else {
container.innerHTML = "<p>To track a problem please visit a leetcode problem page</p>";
}
});
})
.catch((err) => {
document.body.innerHTML = "<p>Unable to read this page</p>";
console.error(err);
})
.catch((err) => {
container.innerHTML = "<p>Unable to read this page.</p>";
console.error(err);
});
}
});

function renderCurrentProblem(data) {
container.innerHTML = "";
const titleEl = document.createElement("h3");
titleEl.textContent = `Problem: ${data.title}`;
const diffEl = document.createElement("p");
diffEl.textContent = `Difficulty: ${data.difficulty} `;
const statusEl = document.createElement("p");
statusEl.id = "status";
const strongEl = document.createElement("strong");
strongEl.textContent = data.status || "Unsolved";
statusEl.appendChild(strongEl);
container.appendChild(titleEl);
container.appendChild(diffEl);
container.appendChild(statusEl);

// 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 ✅";
}
});
}
});
});

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));

// 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);
});
} else {
const opt = document.createElement("option");
opt.value = "none";
opt.textContent = "No tags";
tagFilter.appendChild(opt);
}
}
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"
);

// 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 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);
});
}
problems.sort((a, b) => (b.solvedAt || 0) - (a.solvedAt || 0));

if (tagFilter) {
tagFilter.addEventListener("change", renderProblems);
}
renderProblems();
const tagSet = new Set();
problems.forEach((p) => {
if (Array.isArray(p.tags)) {
p.tags.forEach((tag) => tagSet.add(tag));
}
});
const tags = Array.from(tagSet);

// 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");
}
// 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);
}
}

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()
: "";

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 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);
});
}

if (tagFilter) {
tagFilter.addEventListener("change", renderProblems);
}
renderProblems();
});

const viewAllLink = document.getElementById("viewAllLink");
if (viewAllLink) {
viewAllLink.addEventListener("click", (e) => {
e.preventDefault();
if (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?.openOptionsPage) {
browser.runtime.openOptionsPage();
} else {
window.open("../options/options.html", "_blank");
}
});
}
});