Skip to content
Open
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
63 changes: 62 additions & 1 deletion extension/content.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ function initializeExtension() {
document.getElementById('leetcode-helper-get-hint').addEventListener('click', getHint);
document.getElementById('leetcode-helper-get-hint-advanced').addEventListener('click', getHintAdvanced);
document.getElementById('leetcode-helper-toggle').addEventListener('click', toggleOverlay);
document.getElementById('leetcode-helper-favorite').addEventListener('click', toggleFavorite);
// 检查当前题目是否已收藏
checkIsFavorite();
Comment on lines +24 to +26
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Re-sync the star when favorites changes outside the page.

This only checks storage once during initialization. If the popup removes the current problem from favorites (extension/popup.js Lines 362-369), the overlay icon stays filled and the next click can add the bookmark back instead of removing it.

Suggested fix
     document.getElementById('leetcode-helper-favorite').addEventListener('click', toggleFavorite);
-    // 检查当前题目是否已收藏
     checkIsFavorite();
+    chrome.storage.onChanged.addListener((changes, areaName) => {
+      if (areaName === 'local' && changes.favorites) {
+        checkIsFavorite();
+      }
+    });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
document.getElementById('leetcode-helper-favorite').addEventListener('click', toggleFavorite);
// 检查当前题目是否已收藏
checkIsFavorite();
document.getElementById('leetcode-helper-favorite').addEventListener('click', toggleFavorite);
checkIsFavorite();
chrome.storage.onChanged.addListener((changes, areaName) => {
if (areaName === 'local' && changes.favorites) {
checkIsFavorite();
}
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@extension/content.js` around lines 24 - 26, The overlay only calls
checkIsFavorite() once at init so the star UI doesn't update if the shared
"favorites" storage is modified elsewhere; add a storage change listener that
watches chrome.storage (or browser.storage) changes for the "favorites" key and
re-run checkIsFavorite() (or a small helper that updates the element with id
'leetcode-helper-favorite' and its filled/unfilled state) when the favorites
value changes, ensuring toggleFavorite() continues to work correctly after
external updates.

} catch (error) {
console.error("Error during extension initialization:", error);
displayErrorMessage("Error: LeetCode's page structure has changed. The extension may not work correctly.");
Expand Down Expand Up @@ -84,7 +87,12 @@ function createOverlay() {
<i class="fa-solid fa-puzzle-piece" style="margin-right: 8px;"></i>
<h3>LeetCode Helper</h3>
</div>
<button id="leetcode-helper-toggle" class="leetcode-helper-button"><i class="fa-solid fa-plus"></i></button>
<div style="display: flex; gap: 8px;">
<button id="leetcode-helper-favorite" class="leetcode-helper-button" title="Add to favorites">
<i class="fa-regular fa-star"></i>
</button>
<button id="leetcode-helper-toggle" class="leetcode-helper-button"><i class="fa-solid fa-plus"></i></button>
</div>
</div>
<div class="leetcode-helper-content collapsed" id="leetcode-helper-content">
<p><i class="fa-solid fa-lightbulb" style="color: #f1c40f; margin-right: 5px;"></i> Need help with your solution? Click a button below!</p>
Expand Down Expand Up @@ -436,4 +444,57 @@ function formatTextWithCodeBlocks(text) {
}

return text;
}

// 收藏功能
function toggleFavorite() {
const problemUrl = window.location.href;
const favoriteBtn = document.getElementById('leetcode-helper-favorite');
const icon = favoriteBtn.querySelector('i');

chrome.storage.local.get('favorites', (result) => {
let favorites = result.favorites || [];
const existingIndex = favorites.findIndex(item => item.url === problemUrl);

if (existingIndex >= 0) {
// 取消收藏
favorites.splice(existingIndex, 1);
icon.classList.remove('fa-solid');
icon.classList.add('fa-regular');
icon.style.color = '';
} else {
// 添加收藏
favorites.push({
title: problemTitle,
url: problemUrl,
timestamp: Date.now()
});
icon.classList.remove('fa-regular');
icon.classList.add('fa-solid');
icon.style.color = '#f1c40f';
}

chrome.storage.local.set({favorites: favorites});
});
}

function checkIsFavorite() {
const problemUrl = window.location.href;
const favoriteBtn = document.getElementById('leetcode-helper-favorite');
const icon = favoriteBtn.querySelector('i');

chrome.storage.local.get('favorites', (result) => {
const favorites = result.favorites || [];
const isFavorite = favorites.some(item => item.url === problemUrl);

if (isFavorite) {
icon.classList.remove('fa-regular');
icon.classList.add('fa-solid');
icon.style.color = '#f1c40f';
} else {
icon.classList.remove('fa-solid');
icon.classList.add('fa-regular');
icon.style.color = '';
}
});
}
41 changes: 41 additions & 0 deletions extension/popup.html
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,47 @@
h2 {
font-size: 20px;
}

/* Tab Styles */
.tabs {
display: flex;
gap: 8px;
margin-bottom: 16px;
background: var(--dark-surface);
padding: 4px;
border-radius: 8px;
}

.tab-btn {
flex: 1;
padding: 10px 12px;
background: transparent;
border: none;
border-radius: 6px;
color: var(--medium-text);
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
font-family: 'Roboto', sans-serif;
}

.tab-btn.active {
background: var(--primary-color);
color: white;
}

.tab-btn:hover:not(.active) {
background: var(--dark-card);
}

.tab-content {
display: none;
}

.tab-content.active {
display: block;
}

h3 {
font-size: 16px;
Expand Down
97 changes: 97 additions & 0 deletions extension/popup.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,25 @@ function setupEventListeners() {
const aboutTab = document.getElementById('about-tab');
const settingsContent = document.getElementById('settings-content');
const aboutContent = document.getElementById('about-content');

// 收藏Tab功能
const tabBtns = document.querySelectorAll('.tab-btn');
const tabContents = document.querySelectorAll('.tab-content');
tabBtns.forEach(btn => {
btn.addEventListener('click', () => {
const tab = btn.dataset.tab;
// 切换按钮状态
tabBtns.forEach(b => b.classList.remove('active'));
btn.classList.add('active');
// 切换内容
tabContents.forEach(content => content.classList.remove('active'));
document.getElementById(`${tab}-tab`).classList.add('active');
// 如果是收藏tab,加载收藏列表
if (tab === 'favorites') {
renderFavorites();
}
});
});
Comment on lines +52 to +68
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

The new favorites tab wiring does not match the popup DOM.

popup.html still renders .tab buttons and #settings-content / #about-content panels, so this loop never attaches today and renderFavorites() is unreachable. There is also no #favorites-tab container to render into. Even after adding .tab-btn, document.getElementById(\${tab}-tab`)` would hit the button IDs for settings/about instead of the content panels.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@extension/popup.js` around lines 52 - 68, The click wiring uses selectors and
ID patterns that don't match popup.html, so event handlers never attach and
renderFavorites() can't run; update the button selector and content ID
convention to match the DOM (use document.querySelectorAll('.tab') or ensure
buttons have .tab-btn) and change the content lookup from
document.getElementById(`${tab}-tab`) to
document.getElementById(`${tab}-content`) (or otherwise map data-tab values to
the corresponding content IDs), ensure the favorites panel exists (create an
element with id="favorites-content") and that the favorites button has
data-tab="favorites", and keep the renderFavorites() call when tab ===
'favorites' so the list renders into the new `#favorites-content` container.


if (settingsTab && aboutTab && settingsContent && aboutContent) {
settingsTab.addEventListener('click', function() {
Expand Down Expand Up @@ -270,4 +289,82 @@ function showErrorMessage(message) {
} catch (error) {
console.error('Error showing error message:', error);
}
}

// 渲染收藏列表
function renderFavorites() {
const favoritesTab = document.getElementById('favorites-tab');
if (!favoritesTab) return;

chrome.storage.local.get('favorites', (result) => {
const favorites = result.favorites || [];
if (favorites.length === 0) {
favoritesTab.innerHTML = `
<div class="empty-state" style="text-align: center; padding: 40px 20px; color: var(--medium-text);">
<i class="fas fa-star" style="font-size: 48px; margin-bottom: 16px; opacity: 0.3;"></i>
<p>No favorite problems yet</p>
<p style="font-size: 14px; margin-top: 8px;">Star problems on LeetCode pages to save them here</p>
</div>
`;
return;
}

// 按收藏时间倒序排列
favorites.sort((a, b) => b.timestamp - a.timestamp);

let html = `
<div style="margin-bottom: 16px;">
<h3 style="margin-bottom: 8px;"><i class="fas fa-star" style="color: #f1c40f; margin-right: 6px;"></i> Your Favorites</h3>
<p style="font-size: 14px; color: var(--medium-text);">${favorites.length} saved problems</p>
</div>
<div style="max-height: 400px; overflow-y: auto; padding-right: 4px;">
`;

favorites.forEach((item, index) => {
html += `
<div class="favorite-item" style="background: var(--dark-card); border: 1px solid var(--dark-border); border-radius: 8px; padding: 12px; margin-bottom: 8px; display: flex; align-items: center; justify-content: space-between;">
<a href="${item.url}" target="_blank" style="color: var(--light-text); text-decoration: none; flex: 1; margin-right: 8px;">
<div style="font-weight: 500; margin-bottom: 4px;">${item.title}</div>
<div style="font-size: 12px; color: var(--medium-text);">${new Date(item.timestamp).toLocaleDateString()}</div>
</a>
Comment on lines +324 to +329
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Do not inject stored favorites into innerHTML.

item.title and item.url are persisted from page DOM / window.location.href in extension/content.js. Rendering them through template strings lets a crafted title or URL break out of the markup and execute in extension context. Build these rows with DOM APIs (textContent, href, setAttribute) and validate the URL before linking.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@extension/popup.js` around lines 324 - 329, The current code builds favorite
rows by concatenating into the html string (the `html += \`...\`` block that
injects `item.title` and `item.url`) which allows stored values to break out of
markup; change the renderer in extension/popup.js to create elements with DOM
APIs instead of template strings: use document.createElement for the wrapper div
and anchor, set anchor.href via setAttribute only after validating the URL (use
the URL constructor and allow only http/https), set text nodes via textContent
for the title and date (no innerHTML), and append children with appendChild; if
URL validation fails, do not set a clickable href (or fall back to a safe
action) so untrusted data cannot execute script.

<button class="remove-favorite" data-index="${index}" style="background: transparent; border: none; color: var(--error-color); cursor: pointer; padding: 4px 8px; border-radius: 4px; transition: background 0.2s;">
<i class="fas fa-trash"></i>
</button>
</div>
`;
});

html += '</div>';
favoritesTab.innerHTML = html;

// 添加删除收藏事件
document.querySelectorAll('.remove-favorite').forEach(btn => {
btn.addEventListener('click', (e) => {
e.stopPropagation();
const index = parseInt(btn.dataset.index);
removeFavorite(index);
Comment on lines +312 to +345
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Delete favorites by a stable identifier, not the sorted row index.

The list is rendered after sorting by timestamp, but removeFavorite() reloads the unsorted storage array and splices the same index. Removing the first visible row can delete a different favorite from storage.

Suggested fix
-    favorites.forEach((item, index) => {
+    favorites.forEach((item) => {
       html += `
         <div class="favorite-item" style="background: var(--dark-card); border: 1px solid var(--dark-border); border-radius: 8px; padding: 12px; margin-bottom: 8px; display: flex; align-items: center; justify-content: space-between;">
           <a href="${item.url}" target="_blank" style="color: var(--light-text); text-decoration: none; flex: 1; margin-right: 8px;">
             <div style="font-weight: 500; margin-bottom: 4px;">${item.title}</div>
             <div style="font-size: 12px; color: var(--medium-text);">${new Date(item.timestamp).toLocaleDateString()}</div>
           </a>
-          <button class="remove-favorite" data-index="${index}" style="background: transparent; border: none; color: var(--error-color); cursor: pointer; padding: 4px 8px; border-radius: 4px; transition: background 0.2s;">
+          <button class="remove-favorite" data-url="${encodeURIComponent(item.url)}" data-timestamp="${item.timestamp}" style="background: transparent; border: none; color: var(--error-color); cursor: pointer; padding: 4px 8px; border-radius: 4px; transition: background 0.2s;">
             <i class="fas fa-trash"></i>
           </button>
         </div>
       `;
     });
@@
     document.querySelectorAll('.remove-favorite').forEach(btn => {
       btn.addEventListener('click', (e) => {
         e.stopPropagation();
-        const index = parseInt(btn.dataset.index);
-        removeFavorite(index);
+        removeFavorite(decodeURIComponent(btn.dataset.url), Number(btn.dataset.timestamp));
       });
     });
@@
-function removeFavorite(index) {
+function removeFavorite(url, timestamp) {
   chrome.storage.local.get('favorites', (result) => {
-    let favorites = result.favorites || [];
-    favorites.splice(index, 1);
+    const favorites = (result.favorites || []).filter(
+      item => !(item.url === url && item.timestamp === timestamp)
+    );
     chrome.storage.local.set({favorites: favorites}, () => {
       renderFavorites();
     });
   });
 }

Also applies to: 362-368

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@extension/popup.js` around lines 312 - 345, The current UI sorts the
favorites array (favorites.sort(...)) but sets data-index on each
.remove-favorite button, then calls removeFavorite(index), causing mismatches
between the displayed order and underlying storage; change the buttons to store
a stable identifier (e.g., item.id or item.url) in a data attribute (data-id),
update the click handler to read that id and call removeFavoriteById(id) (or
modify removeFavorite to accept an id), and implement removal by finding and
splicing the stored array entry that matches the id (instead of using the sorted
row index); if favorites objects lack a stable id, add one when saving (UUID or
use url) and use that for matching.

});
});

// 添加悬停效果
document.querySelectorAll('.favorite-item').forEach(item => {
item.addEventListener('mouseenter', () => {
item.style.borderColor = 'var(--primary-color)';
});
item.addEventListener('mouseleave', () => {
item.style.borderColor = 'var(--dark-border)';
});
});
});
}

// 删除收藏
function removeFavorite(index) {
chrome.storage.local.get('favorites', (result) => {
let favorites = result.favorites || [];
favorites.splice(index, 1);
chrome.storage.local.set({favorites: favorites}, () => {
renderFavorites();
});
});
}