diff --git a/Mac/Base.lproj/Main.storyboard b/Mac/Base.lproj/Main.storyboard index 5774c150e..dbbb3e8d3 100644 --- a/Mac/Base.lproj/Main.storyboard +++ b/Mac/Base.lproj/Main.storyboard @@ -452,9 +452,15 @@ - + + - + + + + + + diff --git a/Mac/MainWindow/MainWindowController.swift b/Mac/MainWindow/MainWindowController.swift index 06ddbf865..55a17e6b0 100644 --- a/Mac/MainWindow/MainWindowController.swift +++ b/Mac/MainWindow/MainWindowController.swift @@ -198,8 +198,12 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations { return validateToggleStarred(item) } - if item.action == #selector(markOlderArticlesAsRead(_:)) { - return canMarkOlderArticlesAsRead() + if item.action == #selector(markAboveArticlesAsRead(_:)) { + return canMarkAboveArticlesAsRead() + } + + if item.action == #selector(markBelowArticlesAsRead(_:)) { + return canMarkBelowArticlesAsRead() } if item.action == #selector(toggleArticleExtractor(_:)) { @@ -360,8 +364,12 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations { splitViewController!.toggleSidebar(sender) } - @IBAction func markOlderArticlesAsRead(_ sender: Any?) { - currentTimelineViewController?.markOlderArticlesRead() + @IBAction func markAboveArticlesAsRead(_ sender: Any?) { + currentTimelineViewController?.markAboveArticlesRead() + } + + @IBAction func markBelowArticlesAsRead(_ sender: Any?) { + currentTimelineViewController?.markBelowArticlesRead() } @IBAction func navigateToTimeline(_ sender: Any?) { @@ -808,11 +816,14 @@ private extension MainWindowController { return true } - func canMarkOlderArticlesAsRead() -> Bool { - - return currentTimelineViewController?.canMarkOlderArticlesAsRead() ?? false + func canMarkAboveArticlesAsRead() -> Bool { + return currentTimelineViewController?.canMarkAboveArticlesAsRead() ?? false } + func canMarkBelowArticlesAsRead() -> Bool { + return currentTimelineViewController?.canMarkBelowArticlesAsRead() ?? false + } + func canShowShareMenu() -> Bool { guard let selectedArticles = selectedArticles else { diff --git a/Mac/MainWindow/Timeline/TimelineViewController+ContextualMenus.swift b/Mac/MainWindow/Timeline/TimelineViewController+ContextualMenus.swift index 5f29d39e4..1df39dca4 100644 --- a/Mac/MainWindow/Timeline/TimelineViewController+ContextualMenus.swift +++ b/Mac/MainWindow/Timeline/TimelineViewController+ContextualMenus.swift @@ -33,39 +33,31 @@ extension TimelineViewController { extension TimelineViewController { @objc func markArticlesReadFromContextualMenu(_ sender: Any?) { - - guard let articles = articles(from: sender) else { - return - } + guard let articles = articles(from: sender) else { return } markArticles(articles, read: true) } @objc func markArticlesUnreadFromContextualMenu(_ sender: Any?) { - - guard let articles = articles(from: sender) else { - return - } + guard let articles = articles(from: sender) else { return } markArticles(articles, read: false) } - @objc func markOlderArticlesReadFromContextualMenu(_ sender: Any?) { + @objc func markAboveArticlesReadFromContextualMenu(_ sender: Any?) { + guard let articles = articles(from: sender) else { return } + markAboveArticlesRead(articles) + } - guard let articles = articles(from: sender) else { - return - } - markOlderArticlesRead(articles) + @objc func markBelowArticlesReadFromContextualMenu(_ sender: Any?) { + guard let articles = articles(from: sender) else { return } + markBelowArticlesRead(articles) } @objc func markArticlesStarredFromContextualMenu(_ sender: Any?) { - - guard let articles = articles(from: sender) else { - return - } + guard let articles = articles(from: sender) else { return } markArticles(articles, starred: true) } @objc func markArticlesUnstarredFromContextualMenu(_ sender: Any?) { - guard let articles = articles(from: sender) else { return } @@ -111,17 +103,14 @@ extension TimelineViewController { private extension TimelineViewController { func markArticles(_ articles: [Article], read: Bool) { - markArticles(articles, statusKey: .read, flag: read) } func markArticles(_ articles: [Article], starred: Bool) { - markArticles(articles, statusKey: .starred, flag: starred) } func markArticles(_ articles: [Article], statusKey: ArticleStatus.Key, flag: Bool) { - guard let undoManager = undoManager, let markStatusCommand = MarkStatusCommand(initialArticles: articles, statusKey: statusKey, flag: flag, undoManager: undoManager) else { return } @@ -130,24 +119,20 @@ private extension TimelineViewController { } func unreadArticles(from articles: [Article]) -> [Article]? { - let filteredArticles = articles.filter { !$0.status.read } return filteredArticles.isEmpty ? nil : filteredArticles } func readArticles(from articles: [Article]) -> [Article]? { - let filteredArticles = articles.filter { $0.status.read } return filteredArticles.isEmpty ? nil : filteredArticles } func articles(from sender: Any?) -> [Article]? { - return (sender as? NSMenuItem)?.representedObject as? [Article] } func menu(for articles: [Article]) -> NSMenu? { - let menu = NSMenu(title: "") if articles.anyArticleIsUnread() { @@ -162,8 +147,11 @@ private extension TimelineViewController { if articles.anyArticleIsStarred() { menu.addItem(markUnstarredMenuItem(articles)) } - if articles.count > 0 { - menu.addItem(markOlderReadMenuItem(articles)) + if let first = articles.first, self.articles.articlesAbove(article: first).canMarkAllAsRead() { + menu.addItem(markAboveReadMenuItem(articles)) + } + if let last = articles.last, self.articles.articlesBelow(article: last).canMarkAllAsRead() { + menu.addItem(markBelowReadMenuItem(articles)) } menu.addSeparatorIfNeeded() @@ -239,8 +227,12 @@ private extension TimelineViewController { return menuItem(NSLocalizedString("Mark as Unstarred", comment: "Command"), #selector(markArticlesUnstarredFromContextualMenu(_:)), articles) } - func markOlderReadMenuItem(_ articles: [Article]) -> NSMenuItem { - return menuItem(NSLocalizedString("Mark Older as Read", comment: "Command"), #selector(markOlderArticlesReadFromContextualMenu(_:)), articles) + func markAboveReadMenuItem(_ articles: [Article]) -> NSMenuItem { + return menuItem(NSLocalizedString("Mark Above as Read", comment: "Command"), #selector(markAboveArticlesReadFromContextualMenu(_:)), articles) + } + + func markBelowReadMenuItem(_ articles: [Article]) -> NSMenuItem { + return menuItem(NSLocalizedString("Mark Below as Read", comment: "Command"), #selector(markBelowArticlesReadFromContextualMenu(_:)), articles) } func selectFeedInSidebarMenuItem(_ feed: WebFeed) -> NSMenuItem { diff --git a/Mac/MainWindow/Timeline/TimelineViewController.swift b/Mac/MainWindow/Timeline/TimelineViewController.swift index d88c5721b..b3d637f51 100644 --- a/Mac/MainWindow/Timeline/TimelineViewController.swift +++ b/Mac/MainWindow/Timeline/TimelineViewController.swift @@ -435,35 +435,38 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr return .canDoNothing } - func markOlderArticlesRead() { - markOlderArticlesRead(selectedArticles) + func markAboveArticlesRead() { + markAboveArticlesRead(selectedArticles) } - func canMarkOlderArticlesAsRead() -> Bool { - return !selectedArticles.isEmpty + func markBelowArticlesRead() { + markBelowArticlesRead(selectedArticles) } - func markOlderArticlesRead(_ selectedArticles: [Article]) { - // Mark articles older than the selectedArticles(s) as read. + func canMarkAboveArticlesAsRead() -> Bool { + guard let first = selectedArticles.first else { return false } + return articles.articlesAbove(article: first).canMarkAllAsRead() + } - var cutoffDate: Date? = nil - for article in selectedArticles { - if cutoffDate == nil { - cutoffDate = article.logicalDatePublished - } - else if cutoffDate! > article.logicalDatePublished { - cutoffDate = article.logicalDatePublished - } - } - if cutoffDate == nil { - return - } - - let articlesToMark = articles.filter { $0.logicalDatePublished < cutoffDate! } - if articlesToMark.isEmpty { + func canMarkBelowArticlesAsRead() -> Bool { + guard let last = selectedArticles.last else { return false } + return articles.articlesBelow(article: last).canMarkAllAsRead() + } + + func markAboveArticlesRead(_ selectedArticles: [Article]) { + guard let first = selectedArticles.first else { return } + let articlesToMark = articles.articlesAbove(article: first) + guard !articlesToMark.isEmpty else { return } + guard let undoManager = undoManager, let markReadCommand = MarkStatusCommand(initialArticles: articlesToMark, markingRead: true, undoManager: undoManager) else { return } + runCommand(markReadCommand) + } + func markBelowArticlesRead(_ selectedArticles: [Article]) { + guard let last = selectedArticles.last else { return } + let articlesToMark = articles.articlesBelow(article: last) + guard !articlesToMark.isEmpty else { return } guard let undoManager = undoManager, let markReadCommand = MarkStatusCommand(initialArticles: articlesToMark, markingRead: true, undoManager: undoManager) else { return }