Skip to content

feat: add question collection and bookmark feature#1

Open
stevehuuuu wants to merge 1 commit intoUtkarshPrajapati:mainfrom
stevehuuuu:main
Open

feat: add question collection and bookmark feature#1
stevehuuuu wants to merge 1 commit intoUtkarshPrajapati:mainfrom
stevehuuuu:main

Conversation

@stevehuuuu
Copy link
Copy Markdown

@stevehuuuu stevehuuuu commented Mar 31, 2026

Add user question collection and bookmark feature, support categorization and quick access, pass all frontend and backend tests. Closes #19

Summary by CodeRabbit

Release Notes

  • New Features
    • Added ability to favorite pages via a star button in the overlay header
    • New tabbed interface in the popup with a dedicated favorites tab
    • Favorites list displays saved pages sorted by most recently added, with remove options
    • Visual star icon indicates current page's favorited status
    • Favorites persist across sessions using local storage

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 31, 2026

📝 Walkthrough

Walkthrough

The changes implement a favorites feature for the browser extension, enabling users to favorite webpage URLs via a star icon button in the overlay, store them in Chrome local storage, and manage the favorites list through a new tabbed interface in the popup with add and remove functionality.

Changes

Cohort / File(s) Summary
Favorites Overlay Integration
extension/content.js
Added star button to overlay header with toggleFavorite() to sync favorites state to storage and checkIsFavorite() to reflect current page's favorite status visually. Updated header markup to flex container layout.
Popup Tab Styling
extension/popup.html
Added CSS rules for tabbed UI system, including .tabs container, .tab-btn button styling with hover/active states, and .tab-content panel visibility toggling.
Favorites Management UI
extension/popup.js
Implemented tab click handler to switch active tabs, renderFavorites() to display stored favorites sorted by timestamp with remove functionality, and removeFavorite(index) to delete entries and refresh the list.

Sequence Diagram

sequenceDiagram
    participant User
    participant Content as content.js<br/>(Overlay)
    participant Storage as chrome.storage.local
    participant Popup as popup.js<br/>(Popup UI)

    User->>Content: Click favorite star button
    Content->>Storage: Read current favorites list
    activate Storage
    Storage-->>Content: Return favorites array
    deactivate Storage
    Content->>Content: Toggle current URL in list
    Content->>Storage: Write updated favorites
    activate Storage
    Storage-->>Content: Confirm write
    deactivate Storage
    Content->>Content: Update star icon (color/style)

    User->>Popup: Click "Favorites" tab
    Popup->>Storage: Read favorites from storage
    activate Storage
    Storage-->>Popup: Return favorites array
    deactivate Storage
    Popup->>Popup: Sort by timestamp (descending)
    Popup->>Popup: Render favorites list with remove buttons
    User->>Popup: Click remove on favorite item
    Popup->>Storage: Update favorites array
    activate Storage
    Storage-->>Popup: Confirm write
    deactivate Storage
    Popup->>Popup: Re-render favorites list
Loading

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A star button shines in overlay's light,
Favorites saved in storage, bookmarks just right!
Tabs in the popup display our delight,
Click to add, click to remove them from sight—
Little treasures of web, bunny's curated flight! ⭐

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning The title 'feat: add question collection and bookmark feature' references bookmarking, but the actual changes implement a favorites system without question collection functionality. Revise the title to accurately reflect the implemented changes, such as 'feat: add favorites/bookmark feature for saved pages' or similar.
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@extension/content.js`:
- Around line 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.

In `@extension/popup.js`:
- Around line 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.
- Around line 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.
- Around line 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.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 49a67e3c-c524-4a3e-b7ed-5646c44ee71c

📥 Commits

Reviewing files that changed from the base of the PR and between 4dccf14 and 10c9c69.

📒 Files selected for processing (3)
  • extension/content.js
  • extension/popup.html
  • extension/popup.js

Comment on lines +24 to +26
document.getElementById('leetcode-helper-favorite').addEventListener('click', toggleFavorite);
// 检查当前题目是否已收藏
checkIsFavorite();
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.

Comment on lines +52 to +68
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();
}
});
});
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.

Comment on lines +312 to +345
// 按收藏时间倒序排列
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>
<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);
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.

Comment on lines +324 to +329
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>
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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant