Skip to content

Add Read Later feature - #113

Merged
WCY-dt merged 4 commits into
mainfrom
copilot/add-read-later-feature
Dec 3, 2025
Merged

WCY-dt merged 4 commits into
mainfrom
copilot/add-read-later-feature

Conversation

Copilot AI commented Dec 3, 2025 •

Copy link
Copy Markdown
Contributor

Description

Implements "Read Later" (稍后阅读) functionality, mirroring the existing Favorites feature. Articles marked for read later are automatically set to unread; reading them removes them from the list.

Type of Change

  • 🐛 Bug fix (non-breaking change which fixes an issue)
  • ✨ New feature (non-breaking change which adds functionality)
  • 💥 Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • 📝 Documentation update
  • 🎨 Style/UI change
  • ♻️ Code refactoring
  • ⚡ Performance improvement
  • ✅ Test addition/update

Related Issues

Fixes #113

Changes Made

Backend (Go):

  • Added is_read_later column with index and composite index for query optimization
  • Database migration for existing installations
  • ToggleReadLater and SetArticleReadLater operations
  • GetArticles supports readLater filter
  • /api/articles/toggle-read-later endpoint
  • Modified MarkArticleRead to auto-remove from read later
  • Cleanup logic preserves read-later articles
  • Rules engine: is_read_later condition + read_later/remove_read_later actions

Frontend (Vue.js):

  • Sidebar: "Read Later" nav item with PhClockCountdown icon
  • Article list: blue highlight + clock indicator for read-later items
  • Toolbar: toggle button in both compact and full toolbars
  • Context menu: add/remove read later option
  • Keyboard shortcuts: l (toggle), 4 (navigate to view)
  • Filter modal: is_read_later field option
  • Rules: read later conditions and actions
  • i18n: English and Chinese translations

Screenshots

Before

N/A - new feature

After

N/A - desktop application

Testing

  • Go tests pass (go test ./...)
  • Frontend builds and lints successfully
  • Frontend tests pass (npm run test)

Test Configuration

  • OS: Linux
  • Go Version: 1.24+
  • Node Version: 18+

Test Steps

  1. Build frontend and backend
  2. Add article to read later via context menu or l key
  3. Verify article shows blue highlight and clock icon
  4. Navigate to Read Later view via sidebar or 4 key
  5. Read the article; verify it's removed from read later

Checklist

  • My code follows the project's style guidelines
  • I have performed a self-review of my code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • Any dependent changes have been merged and published

Additional Notes

Auto-behavior summary:

  • Adding to read later → marks article unread
  • Reading article → removes from read later

Breaking Changes

None. Database migration handles existing installations automatically.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • nonexistent-feed-url-12345.com
    • Triggering command: /tmp/go-build2096446897/b293/discovery.test /tmp/go-build2096446897/b293/discovery.test -test.testlogfile=/tmp/go-build2096446897/b293/testlog.txt -test.paniconexit0 -test.timeout=10m0s (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Original prompt

This section details on the original issue you should resolve

<issue_title>Support read later</issue_title>
<issue_description>添加稍后阅读功能,该功能和收藏功能基本相同,包括:

  • 主页最左侧添加稍后阅读的列表按钮;
  • 文章列表右键菜单添加稍后阅读按钮;
  • 主页右上角添加稍后阅读按钮;
  • 文章列表中,设置了稍后阅读的文章增加特殊样式;
  • 新增稍后阅读的快捷键,并在设置页的快捷键tab增加该项;
  • 主页中的过滤功能、设置页中的规则功能都要增加稍后阅读的相关功能;
  • 设置了稍后阅读的文章,其状态改为未读。如果再次阅读该文章,将其移出稍后阅读,并将状态改为已读。</issue_description>

Comments on the Issue (you are @copilot in this section)


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

@WCY-dt WCY-dt added the ✨enhancement New feature or request label Dec 3, 2025
Copilot AI and others added 2 commits December 3, 2025 16:19
Co-authored-by: WCY-dt <55525165+WCY-dt@users.noreply.github.com>
Co-authored-by: WCY-dt <55525165+WCY-dt@users.noreply.github.com>
Copilot AI changed the title [WIP] Add support for read later functionality Add Read Later feature Dec 3, 2025
Copilot AI requested a review from WCY-dt December 3, 2025 16:29
@WCY-dt WCY-dt added the 🤖github actions Modification of GitHub Actions label Dec 3, 2025
@WCY-dt
WCY-dt marked this pull request as ready for review December 3, 2025 16:45
Copilot AI review requested due to automatic review settings December 3, 2025 16:45
@WCY-dt
WCY-dt merged commit 8590a06 into main Dec 3, 2025
13 checks passed
@WCY-dt
WCY-dt deleted the copilot/add-read-later-feature branch December 3, 2025 16:47

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR implements a comprehensive "Read Later" feature for MrRSS, mirroring the existing Favorites functionality. The feature allows users to mark articles for reading later, with automatic state management where adding an article to read later marks it as unread, and reading an article removes it from the read later list.

Key Changes:

  • Added is_read_later column to the database with appropriate indexing and migration support
  • Implemented backend API endpoints and business logic for read later operations
  • Created frontend UI components, keyboard shortcuts (l, 4), and visual indicators (blue highlight, clock icon)
  • Integrated read later into rules engine and filter system
  • Added full internationalization support (English and Chinese)

Reviewed changes

Copilot reviewed 32 out of 32 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
main.go Added /api/articles/toggle-read-later endpoint registration
internal/rules/engine.go Added is_read_later condition and read_later/remove_read_later actions to rules engine
internal/models/models.go Added IsReadLater field to Article model
internal/handlers/article/article_filters.go Added is_read_later filter condition support
internal/handlers/article/article_basic.go Implemented HandleToggleReadLater HTTP handler
internal/database/db.go Added database migration for is_read_later column and created index before schema init
internal/database/cleanup_db.go Updated cleanup logic to preserve read later articles alongside favorites
internal/database/article_db.go Implemented ToggleReadLater, SetArticleReadLater, and updated MarkArticleRead to auto-clear read later status (with critical bug)
frontend/src/types/models.ts Added is_read_later field to Article interface and RuleAction types
frontend/src/stores/app.ts Added readLater to Filter type
frontend/src/i18n/types.ts Added read later translation keys
frontend/src/i18n/locales/zh.ts Added Chinese translations for read later feature
frontend/src/i18n/locales/en.ts Added English translations for read later feature
frontend/src/composables/ui/useKeyboardShortcuts.ts Added keyboard shortcuts (l, 4) and toggleCurrentArticleReadLater function (with error handling bug)
frontend/src/composables/rules/useRuleOptions.ts Added read later to rule field and action options
frontend/src/composables/filter/useFilterFields.ts Added is_read_later to filter fields
frontend/src/composables/article/useArticleDetail.ts Added toggleReadLater function with optimistic updates (with error handling bug)
frontend/src/composables/article/useArticleActions.ts Added read later context menu action (with error handling bug)
frontend/src/components/sidebar/SidebarNavItem.vue Added PhClockCountdown icon import and readLater icon type
frontend/src/components/sidebar/Sidebar.vue Added "Read Later" navigation item to sidebar
frontend/src/components/modals/settings/shortcuts/ShortcutsTab.vue Added read later keyboard shortcuts to settings UI
frontend/src/components/common/ContextMenu.vue Added ph-clock-countdown icon to icon map
frontend/src/components/article/ArticleToolbar.vue Added read later toggle button to article toolbar
frontend/src/components/article/ArticleItem.vue Added blue highlight styling and clock icon indicator for read later articles
frontend/src/components/article/ArticleDetailToolbar.vue Added read later toggle button to detail toolbar
frontend/src/components/article/ArticleDetail.vue Wired up read later toggle to detail view
README_zh.md Updated build instructions to use Makefile or -skipbindings flag
README.md Updated build instructions to use Makefile or -skipbindings flag
Makefile Added -skipbindings flag to wails build command
CONTRIBUTING.md Updated build instructions to use Makefile or -skipbindings flag
AGENTS.md Updated build instructions and documented -skipbindings requirement
.github/copilot-instructions.md Added build command reference with -skipbindings flag

Comment on lines 133 to 140
isRead := 0
if read {
isRead = 1
// When marking as read, also remove from read later
_, err := db.Exec("UPDATE articles SET is_read = 1, is_read_later = 0 WHERE id = ?", id)
return err
}
_, err := db.Exec("UPDATE articles SET is_read = ? WHERE id = ?", isRead, id)

Copilot AI Dec 3, 2025

Copy link

Choose a reason for hiding this comment

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

The MarkArticleRead function has a logic flaw. When read is false (marking as unread), the code still uses isRead = 0 but then updates with the parameterized isRead variable. This is redundant. More importantly, the code path for marking as read (lines 134-138) returns early, so the code on lines 140-141 is only executed when marking as unread. This means the unread path doesn't go through, making it unreachable.

Suggested fix:

func (db *DB) MarkArticleRead(id int64, read bool) error {
	db.WaitForReady()
	if read {
		// When marking as read, also remove from read later
		_, err := db.Exec("UPDATE articles SET is_read = 1, is_read_later = 0 WHERE id = ?", id)
		return err
	}
	// When marking as unread, only update is_read
	_, err := db.Exec("UPDATE articles SET is_read = 0 WHERE id = ?", id)
	return err
}
Suggested change
isRead := 0
if read {
isRead = 1
// When marking as read, also remove from read later
_, err := db.Exec("UPDATE articles SET is_read = 1, is_read_later = 0 WHERE id = ?", id)
return err
}
_, err := db.Exec("UPDATE articles SET is_read = ? WHERE id = ?", isRead, id)
if read {
// When marking as read, also remove from read later
_, err := db.Exec("UPDATE articles SET is_read = 1, is_read_later = 0 WHERE id = ?", id)
return err
}
// When marking as unread, only update is_read
_, err := db.Exec("UPDATE articles SET is_read = 0 WHERE id = ?", id)

Copilot uses AI. Check for mistakes.
Comment on lines +141 to +157
function toggleCurrentArticleReadLater(): void {
const article = store.articles.find((a) => a.id === store.currentArticleId);
if (!article) return;

const newState = !article.is_read_later;
article.is_read_later = newState;
// When adding to read later, also mark as unread
if (newState) {
article.is_read = false;
}
fetch(`/api/articles/toggle-read-later?id=${article.id}`, { method: 'POST' })
.then(() => store.fetchUnreadCounts())
.catch((e) => {
console.error('Error toggling read later:', e);
article.is_read_later = !newState;
});
}

Copilot AI Dec 3, 2025

Copy link

Choose a reason for hiding this comment

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

When an error occurs after optimistically updating both is_read_later and is_read, only is_read_later is reverted (line 155). If newState was true, we also set is_read = false (line 149), but this change isn't reverted on error, leaving the UI in an inconsistent state.

Suggested fix:

function toggleCurrentArticleReadLater(): void {
  const article = store.articles.find((a) => a.id === store.currentArticleId);
  if (!article) return;

  const newState = !article.is_read_later;
  const oldReadState = article.is_read; // Save the old read state
  article.is_read_later = newState;
  // When adding to read later, also mark as unread
  if (newState) {
    article.is_read = false;
  }
  fetch(`/api/articles/toggle-read-later?id=${article.id}`, { method: 'POST' })
    .then(() => store.fetchUnreadCounts())
    .catch((e) => {
      console.error('Error toggling read later:', e);
      article.is_read_later = !newState;
      article.is_read = oldReadState; // Restore the old read state
    });
}

Copilot uses AI. Check for mistakes.
Comment on lines +103 to +119
async function toggleReadLater() {
if (!article.value) return;
const newState = !article.value.is_read_later;
article.value.is_read_later = newState;
// When adding to read later, also mark as unread
if (newState) {
article.value.is_read = false;
}
try {
await fetch(`/api/articles/toggle-read-later?id=${article.value.id}`, { method: 'POST' });
store.fetchUnreadCounts();
} catch (e) {
console.error('Error toggling read later:', e);
// Revert on error
article.value.is_read_later = !newState;
}
}

Copilot AI Dec 3, 2025

Copy link

Choose a reason for hiding this comment

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

The error handling in toggleReadLater has the same issue as in useKeyboardShortcuts.ts. When an error occurs after optimistically updating both is_read_later and is_read, only is_read_later is reverted (line 117). If newState was true, we also set is_read = false (line 109), but this change isn't reverted on error.

Suggested fix:

async function toggleReadLater() {
  if (!article.value) return;
  const newState = !article.value.is_read_later;
  const oldReadState = article.value.is_read; // Save the old read state
  article.value.is_read_later = newState;
  // When adding to read later, also mark as unread
  if (newState) {
    article.value.is_read = false;
  }
  try {
    await fetch(`/api/articles/toggle-read-later?id=${article.value.id}`, { method: 'POST' });
    store.fetchUnreadCounts();
  } catch (e) {
    console.error('Error toggling read later:', e);
    // Revert on error
    article.value.is_read_later = !newState;
    article.value.is_read = oldReadState; // Restore the old read state
  }
}

Copilot uses AI. Check for mistakes.
Comment on lines +106 to +124
} else if (action === 'toggleReadLater') {
const newState = !article.is_read_later;
article.is_read_later = newState;
// When adding to read later, also mark as unread
if (newState) {
article.is_read = false;
}
try {
await fetch(`/api/articles/toggle-read-later?id=${article.id}`, { method: 'POST' });
// Update unread counts after toggling read later status
if (onReadStatusChange) {
onReadStatusChange();
}
} catch (e) {
console.error('Error toggling read later:', e);
// Revert the state change on error
article.is_read_later = !newState;
window.showToast(t('errorSavingSettings'), 'error');
}

Copilot AI Dec 3, 2025

Copy link

Choose a reason for hiding this comment

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

The error handling in the toggleReadLater action has the same issue. When an error occurs after optimistically updating both is_read_later and is_read, only is_read_later is reverted (line 122). If newState was true, we also set is_read = false (line 111), but this change isn't reverted on error.

Suggested fix:

} else if (action === 'toggleReadLater') {
  const newState = !article.is_read_later;
  const oldReadState = article.is_read; // Save the old read state
  article.is_read_later = newState;
  // When adding to read later, also mark as unread
  if (newState) {
    article.is_read = false;
  }
  try {
    await fetch(`/api/articles/toggle-read-later?id=${article.id}`, { method: 'POST' });
    // Update unread counts after toggling read later status
    if (onReadStatusChange) {
      onReadStatusChange();
    }
  } catch (e) {
    console.error('Error toggling read later:', e);
    // Revert the state change on error
    article.is_read_later = !newState;
    article.is_read = oldReadState; // Restore the old read state
    window.showToast(t('errorSavingSettings'), 'error');
  }
}

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨enhancement New feature or request 🤖github actions Modification of GitHub Actions

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add Read Later feature

3 participants