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
20 changes: 20 additions & 0 deletions options.css
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,7 @@ input:focus + .slider {
padding: 12px 16px;
border-top: 1px solid #e5e7eb;
transition: background 0.2s ease;
gap: 8px;
}

.problem-item:hover {
Expand Down Expand Up @@ -505,3 +506,22 @@ input:focus + .slider {
color: #3b82f6;
font-weight: bold;
}

.neetcode-video-link {
margin-left: 8px;
font-size: 16px;
text-decoration: none;
opacity: 0.7;
transition: opacity 0.2s ease, transform 0.2s ease;
display: inline-flex;
align-items: center;
justify-content: center;
width: 24px;
height: 24px;
}

.neetcode-video-link:hover {
opacity: 1;
transform: scale(1.1);
}

17 changes: 17 additions & 0 deletions options.html
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,23 @@ <h2>Display Preferences</h2>
</label>
</div>
</div>

<div class="form-group">
<div class="toggle-setting">
<div class="toggle-info">
<label for="sortByDifficultyToggle" class="toggle-label">
Sort Problems by Difficulty
</label>
<p class="toggle-description">
Sort problems within each category by difficulty (Easy → Medium → Hard) instead of the original problemset order
</p>
</div>
<label class="switch">
<input type="checkbox" id="sortByDifficultyToggle">
<span class="slider"></span>
</label>
</div>
</div>
</section>

<section class="option-section">
Expand Down
103 changes: 100 additions & 3 deletions options.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
// Leetcode Buddy - Options Page Script

const ALIASES_PATH = "src/assets/data/problemAliases.json";

const problemSetSelect = document.getElementById("problemSetSelect");
const totalProblems = document.getElementById("totalProblems");
const totalCategories = document.getElementById("totalCategories");
Expand All @@ -9,6 +11,65 @@ const resetConfirm = document.getElementById("resetConfirm");
const confirmReset = document.getElementById("confirmReset");
const cancelReset = document.getElementById("cancelReset");
const celebrationToggle = document.getElementById("celebrationToggle");
const sortByDifficultyToggle = document.getElementById("sortByDifficultyToggle");

// Load aliases for NeetCode URL resolution
let problemAliases = {};

async function loadAliases() {
try {
const response = await fetch(chrome.runtime.getURL(ALIASES_PATH));
problemAliases = await response.json();
return problemAliases;
} catch (error) {
console.error("Failed to load aliases:", error);
return {};
}
}

// Find alias for a canonical slug (reverse lookup)
function findAliasForSlug(canonicalSlug) {
for (const [alias, canonical] of Object.entries(problemAliases)) {
if (canonical === canonicalSlug) {
return alias;
}
}
return null;
}

// Get NeetCode slug for URL
function getNeetCodeSlug(slug) {
// First check if the slug itself is an alias
if (problemAliases[slug]) {
return slug;
}

// Check if there's an alias for this canonical slug
const alias = findAliasForSlug(slug);
if (alias) {
return alias;
}

// No alias found, use original slug
return slug;
}

// Get NeetCode solution URL
function getNeetCodeUrl(slug) {
const neetcodeSlug = getNeetCodeSlug(slug);
return `https://neetcode.io/solutions/${neetcodeSlug}`;
}

// Sort problems by difficulty
function sortProblemsByDifficulty(problems) {
const difficultyOrder = { Easy: 0, Medium: 1, Hard: 2 };

return [...problems].sort((a, b) => {
const diffA = difficultyOrder[a.difficulty] ?? 999;
const diffB = difficultyOrder[b.difficulty] ?? 999;
return diffA - diffB;
});
}

// Load current settings
async function loadSettings() {
Expand All @@ -24,14 +85,19 @@ async function loadSettings() {
// Load selected problem set
const result = await chrome.storage.sync.get([
"selectedProblemSet",
"celebrationEnabled"
"celebrationEnabled",
"sortByDifficulty"
]);
const selectedSet = result.selectedProblemSet || "neetcode250";
problemSetSelect.value = selectedSet;

// Load celebration toggle setting (default: true)
const celebrationEnabled = result.celebrationEnabled !== false;
celebrationToggle.checked = celebrationEnabled;

// Load sort by difficulty toggle setting (default: false)
const sortByDifficulty = result.sortByDifficulty === true;
sortByDifficultyToggle.checked = sortByDifficulty;
}
} catch (error) {
console.error("Failed to load settings:", error);
Expand Down Expand Up @@ -104,6 +170,27 @@ celebrationToggle.addEventListener("change", async () => {
}
});

// Handle sort by difficulty toggle
sortByDifficultyToggle.addEventListener("change", async () => {
const enabled = sortByDifficultyToggle.checked;

try {
await chrome.storage.sync.set({ sortByDifficulty: enabled });
console.log("Sort by difficulty:", enabled ? "enabled" : "disabled");

// Recompute next problem to reflect the new ordering
await chrome.runtime.sendMessage({ type: "REFRESH_STATUS" });

// Re-render category accordion with new sorting
await renderCategoryAccordion();

// Reload settings to show updated current problem
await loadSettings();
} catch (error) {
console.error("Failed to save sort by difficulty setting:", error);
}
});

// Render category accordion
async function renderCategoryAccordion() {
const container = document.getElementById('categoryAccordion');
Expand Down Expand Up @@ -161,7 +248,7 @@ function createCategoryAccordionItem(category) {
const content = document.createElement('div');
content.className = 'category-content';

// Add problems
// Add problems (already sorted by message handler if needed)
category.problems.forEach(problem => {
const problemDiv = document.createElement('div');
problemDiv.className = `problem-item ${problem.isCurrent ? 'problem-current' : ''}`;
Expand All @@ -184,9 +271,18 @@ function createCategoryAccordionItem(category) {
difficultyDiv.className = `problem-difficulty difficulty-${problem.difficulty.toLowerCase()}`;
difficultyDiv.textContent = problem.difficulty;

// Add NeetCode video icon
const videoLink = document.createElement('a');
videoLink.href = getNeetCodeUrl(problem.slug);
videoLink.target = '_blank';
videoLink.className = 'neetcode-video-link';
videoLink.title = 'View NeetCode solution';
videoLink.innerHTML = '▶️';

problemDiv.appendChild(statusDiv);
problemDiv.appendChild(titleDiv);
problemDiv.appendChild(difficultyDiv);
problemDiv.appendChild(videoLink);

content.appendChild(problemDiv);
});
Expand All @@ -204,7 +300,8 @@ function createCategoryAccordionItem(category) {
}

// Initialize on load
document.addEventListener("DOMContentLoaded", () => {
document.addEventListener("DOMContentLoaded", async () => {
await loadAliases();
loadSettings();
renderCategoryAccordion();
});
Expand Down
25 changes: 25 additions & 0 deletions popup.css
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,13 @@ header h1 {
color: #991b1b;
}

.problem-links {
display: flex;
align-items: center;
gap: 12px;
margin-top: 8px;
}

.problem-link {
display: inline-block;
color: #667eea;
Expand All @@ -174,6 +181,24 @@ header h1 {
color: #764ba2;
}

.neetcode-video-link {
font-size: 18px;
text-decoration: none;
opacity: 0.7;
transition: opacity 0.2s ease, transform 0.2s ease;
display: inline-flex;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
line-height: 1;
}

.neetcode-video-link:hover {
opacity: 1;
transform: scale(1.15);
}

.category-stats-section {
padding: 24px 20px;
border-bottom: 1px solid #e5e7eb;
Expand Down
27 changes: 19 additions & 8 deletions popup.html
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,25 @@ <h3>Current Problem:</h3>
<div class="problem-category" id="currentCategory">Arrays & Hashing</div>
<div id="currentProblemName" class="problem-name">Loading...</div>
<div class="problem-difficulty" id="currentDifficulty">Medium</div>
<a
id="currentProblemLink"
href="#"
target="_blank"
class="problem-link"
>
Open on LeetCode →
</a>
<div class="problem-links">
<a
id="currentProblemLink"
href="#"
target="_blank"
class="problem-link"
>
Open on LeetCode →
</a>
<a
id="neetcodeVideoLink"
href="#"
target="_blank"
class="neetcode-video-link"
title="View NeetCode solution"
>
▶️
</a>
</div>
</div>
</div>

Expand Down
54 changes: 53 additions & 1 deletion popup.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
// Leetcode Buddy - Popup Script with Category Stats

const ALIASES_PATH = "src/assets/data/problemAliases.json";

// DOM elements
const progressFill = document.getElementById("progressFill");
const solvedCount = document.getElementById("solvedCount");
Expand All @@ -8,7 +10,55 @@ const currentProblemName = document.getElementById("currentProblemName");
const currentCategory = document.getElementById("currentCategory");
const currentDifficulty = document.getElementById("currentDifficulty");
const currentProblemLink = document.getElementById("currentProblemLink");
const neetcodeVideoLink = document.getElementById("neetcodeVideoLink");
const dailyStatus = document.getElementById("dailyStatus");

// Load aliases for NeetCode URL resolution
let problemAliases = {};

async function loadAliases() {
try {
const response = await fetch(chrome.runtime.getURL(ALIASES_PATH));
problemAliases = await response.json();
return problemAliases;
} catch (error) {
console.error("Failed to load aliases:", error);
return {};
}
}

// Find alias for a canonical slug (reverse lookup)
function findAliasForSlug(canonicalSlug) {
for (const [alias, canonical] of Object.entries(problemAliases)) {
if (canonical === canonicalSlug) {
return alias;
}
}
return null;
}

// Get NeetCode slug for URL
function getNeetCodeSlug(slug) {
// First check if the slug itself is an alias
if (problemAliases[slug]) {
return slug;
}

// Check if there's an alias for this canonical slug
const alias = findAliasForSlug(slug);
if (alias) {
return alias;
}

// No alias found, use original slug
return slug;
}

// Get NeetCode solution URL
function getNeetCodeUrl(slug) {
const neetcodeSlug = getNeetCodeSlug(slug);
return `https://neetcode.io/solutions/${neetcodeSlug}`;
}
const categoryList = document.getElementById("categoryList");
const toggleCategories = document.getElementById("toggleCategories");
const bypassActive = document.getElementById("bypassActive");
Expand Down Expand Up @@ -118,6 +168,7 @@ async function updateStatus() {
problem.difficulty
)}`;
currentProblemLink.href = `https://leetcode.com/problems/${problem.slug}/`;
neetcodeVideoLink.href = getNeetCodeUrl(problem.slug);
}

// Update daily solve status with celebration animation
Expand Down Expand Up @@ -285,7 +336,8 @@ optionsButton.addEventListener("click", () => {
});

// Initialize on load
document.addEventListener("DOMContentLoaded", () => {
document.addEventListener("DOMContentLoaded", async () => {
await loadAliases();
updateStatus();

// Refresh status every 30 seconds
Expand Down
Loading
Loading