From c8d2fac9a6fd9cc01f5a83120c1e61c6734535d5 Mon Sep 17 00:00:00 2001 From: Brent Simmons Date: Sun, 11 Feb 2018 12:59:35 -0800 Subject: [PATCH] Rename MarkReadOrUnreadCommand to MarkStatusCommand and make it handle starring/unstarring and deleting/undeleting. Also: add contextual menu for smart feeds in the sidebar. --- Commands/MarkReadOrUnreadCommand.swift | 63 ------------- Commands/MarkStatusCommand.swift | 93 +++++++++++++++++++ Evergreen.xcodeproj/project.pbxproj | 8 +- ...idebarViewController+ContextualMenus.swift | 45 +++++---- ...melineViewController+ContextualMenus.swift | 2 +- .../Timeline/TimelineViewController.swift | 8 +- 6 files changed, 130 insertions(+), 89 deletions(-) delete mode 100644 Commands/MarkReadOrUnreadCommand.swift create mode 100644 Commands/MarkStatusCommand.swift diff --git a/Commands/MarkReadOrUnreadCommand.swift b/Commands/MarkReadOrUnreadCommand.swift deleted file mode 100644 index 0fb4122ec..000000000 --- a/Commands/MarkReadOrUnreadCommand.swift +++ /dev/null @@ -1,63 +0,0 @@ -// -// MarkReadOrUnreadCommand.swift -// Evergreen -// -// Created by Brent Simmons on 10/26/17. -// Copyright © 2017 Ranchero Software. All rights reserved. -// - -import Foundation -import RSCore -import Data - -final class MarkReadOrUnreadCommand: UndoableCommand { - - static private let markReadActionName = NSLocalizedString("Mark Read", comment: "command") - static private let markUnreadActionName = NSLocalizedString("Mark Unread", comment: "command") - let undoActionName: String - let redoActionName: String - let articles: Set
- let undoManager: UndoManager - let markingRead: Bool - - init?(initialArticles: [Article], markingRead: Bool, undoManager: UndoManager) { - - // Filter out articles already read. - let articlesToMark = initialArticles.filter { markingRead ? !$0.status.read : $0.status.read } - if articlesToMark.isEmpty { - return nil - } - self.articles = Set(articlesToMark) - - self.markingRead = markingRead - - self.undoManager = undoManager - - if markingRead { - self.undoActionName = MarkReadOrUnreadCommand.markReadActionName - self.redoActionName = MarkReadOrUnreadCommand.markReadActionName - } - else { - self.undoActionName = MarkReadOrUnreadCommand.markUnreadActionName - self.redoActionName = MarkReadOrUnreadCommand.markUnreadActionName - } - } - - func perform() { - mark(read: markingRead) - registerUndo() - } - - func undo() { - mark(read: !markingRead) - registerRedo() - } -} - -private extension MarkReadOrUnreadCommand { - - func mark(read: Bool) { - - markArticles(articles, statusKey: .read, flag: read) - } -} diff --git a/Commands/MarkStatusCommand.swift b/Commands/MarkStatusCommand.swift new file mode 100644 index 000000000..b3a655e85 --- /dev/null +++ b/Commands/MarkStatusCommand.swift @@ -0,0 +1,93 @@ +// +// MarkStatusCommand.swift +// Evergreen +// +// Created by Brent Simmons on 10/26/17. +// Copyright © 2017 Ranchero Software. All rights reserved. +// + +import Foundation +import RSCore +import Data + +// Mark articles read/unread, starred/unstarred, deleted/undeleted. + +final class MarkStatusCommand: UndoableCommand { + + let undoActionName: String + let redoActionName: String + let articles: Set
+ let undoManager: UndoManager + let flag: Bool + let statusKey: ArticleStatus.Key + + init?(initialArticles: [Article], statusKey: ArticleStatus.Key, flag: Bool, undoManager: UndoManager) { + + // Filter out articles that already have the desired status. + let articlesToMark = MarkStatusCommand.filteredArticles(initialArticles, statusKey, flag) + if articlesToMark.isEmpty { + return nil + } + self.articles = Set(articlesToMark) + + self.flag = flag + self.statusKey = statusKey + self.undoManager = undoManager + + let actionName = MarkStatusCommand.actionName(statusKey, flag) + self.undoActionName = actionName + self.redoActionName = actionName + } + + convenience init?(initialArticles: [Article], markingRead: Bool, undoManager: UndoManager) { + + self.init(initialArticles: initialArticles, statusKey: .read, flag: markingRead, undoManager: undoManager) + } + + convenience init?(initialArticles: [Article], markingStarred: Bool, undoManager: UndoManager) { + + self.init(initialArticles: initialArticles, statusKey: .starred, flag: markingStarred, undoManager: undoManager) + } + + func perform() { + mark(statusKey, flag) + registerUndo() + } + + func undo() { + mark(statusKey, !flag) + registerRedo() + } +} + +private extension MarkStatusCommand { + + func mark(_ statusKey: ArticleStatus.Key, _ flag: Bool) { + + markArticles(articles, statusKey: statusKey, flag: flag) + } + + static private let markReadActionName = NSLocalizedString("Mark Read", comment: "command") + static private let markUnreadActionName = NSLocalizedString("Mark Unread", comment: "command") + static private let markStarredActionName = NSLocalizedString("Mark Starred", comment: "command") + static private let markUnstarredActionName = NSLocalizedString("Mark Unstarred", comment: "command") + static private let markDeletedActionName = NSLocalizedString("Delete", comment: "command") + static private let markUndeletedActionName = NSLocalizedString("Undelete", comment: "command") + + static func actionName(_ statusKey: ArticleStatus.Key, _ flag: Bool) -> String { + + switch statusKey { + case .read: + return flag ? markReadActionName : markUnreadActionName + case .starred: + return flag ? markStarredActionName : markUnstarredActionName + case .userDeleted: + return flag ? markDeletedActionName : markUndeletedActionName + } + } + + static func filteredArticles(_ articles: [Article], _ statusKey: ArticleStatus.Key, _ flag: Bool) -> [Article] { + + return articles.filter{ $0.status.boolStatus(forKey: statusKey) != flag } + } +} diff --git a/Evergreen.xcodeproj/project.pbxproj b/Evergreen.xcodeproj/project.pbxproj index 5d46feaf2..7671a145a 100644 --- a/Evergreen.xcodeproj/project.pbxproj +++ b/Evergreen.xcodeproj/project.pbxproj @@ -49,7 +49,7 @@ 846E773E1F6EF67A00A165E2 /* Account.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 846E773A1F6EF5D700A165E2 /* Account.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 846E77411F6EF6A100A165E2 /* Database.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 846E77211F6EF5D100A165E2 /* Database.framework */; }; 846E77421F6EF6A100A165E2 /* Database.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 846E77211F6EF5D100A165E2 /* Database.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - 84702AA41FA27AC0006B8943 /* MarkReadOrUnreadCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84702AA31FA27AC0006B8943 /* MarkReadOrUnreadCommand.swift */; }; + 84702AA41FA27AC0006B8943 /* MarkStatusCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84702AA31FA27AC0006B8943 /* MarkStatusCommand.swift */; }; 8472058120142E8900AD578B /* FeedInspectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8472058020142E8900AD578B /* FeedInspectorViewController.swift */; }; 847FA121202BA34100BB56C8 /* SidebarContextualMenuDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847FA120202BA34100BB56C8 /* SidebarContextualMenuDelegate.swift */; }; 848F6AE51FC29CFB002D422E /* FaviconDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848F6AE41FC29CFA002D422E /* FaviconDownloader.swift */; }; @@ -562,7 +562,7 @@ 845F52EC1FB2B9FC00C10BF0 /* FeedPasteboardWriter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedPasteboardWriter.swift; sourceTree = ""; }; 846E77161F6EF5D000A165E2 /* Database.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Database.xcodeproj; path = Frameworks/Database/Database.xcodeproj; sourceTree = ""; }; 846E77301F6EF5D600A165E2 /* Account.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Account.xcodeproj; path = Frameworks/Account/Account.xcodeproj; sourceTree = ""; }; - 84702AA31FA27AC0006B8943 /* MarkReadOrUnreadCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarkReadOrUnreadCommand.swift; sourceTree = ""; }; + 84702AA31FA27AC0006B8943 /* MarkStatusCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarkStatusCommand.swift; sourceTree = ""; }; 8472058020142E8900AD578B /* FeedInspectorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedInspectorViewController.swift; sourceTree = ""; }; 847752FE2008879500D93690 /* CoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreServices.framework; path = System/Library/Frameworks/CoreServices.framework; sourceTree = SDKROOT; }; 847FA120202BA34100BB56C8 /* SidebarContextualMenuDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarContextualMenuDelegate.swift; sourceTree = ""; }; @@ -893,7 +893,7 @@ 84702AB31FA27AE8006B8943 /* Commands */ = { isa = PBXGroup; children = ( - 84702AA31FA27AC0006B8943 /* MarkReadOrUnreadCommand.swift */, + 84702AA31FA27AC0006B8943 /* MarkStatusCommand.swift */, 84B99C9C1FAE83C600ECDEDB /* DeleteFromSidebarCommand.swift */, 84A1500220048D660046AD9A /* SendToCommand.swift */, 84A14FF220048CA70046AD9A /* SendToMicroBlogCommand.swift */, @@ -1896,7 +1896,7 @@ D553738B20186C20006D8857 /* Article+Scriptability.swift in Sources */, 8403E75B201C4A79007F7246 /* FeedListKeyboardDelegate.swift in Sources */, 845EE7C11FC2488C00854A1F /* SmartFeed.swift in Sources */, - 84702AA41FA27AC0006B8943 /* MarkReadOrUnreadCommand.swift in Sources */, + 84702AA41FA27AC0006B8943 /* MarkStatusCommand.swift in Sources */, D5907D7F2004AC00005947E5 /* NSApplication+Scriptability.swift in Sources */, 849A979F1ED9F130007D329B /* SidebarCell.swift in Sources */, 849A97651ED9EB96007D329B /* SidebarTreeControllerDelegate.swift in Sources */, diff --git a/Evergreen/MainWindow/Sidebar/SidebarViewController+ContextualMenus.swift b/Evergreen/MainWindow/Sidebar/SidebarViewController+ContextualMenus.swift index 70e79fac6..133c6c30e 100644 --- a/Evergreen/MainWindow/Sidebar/SidebarViewController+ContextualMenus.swift +++ b/Evergreen/MainWindow/Sidebar/SidebarViewController+ContextualMenus.swift @@ -19,17 +19,22 @@ extension SidebarViewController { return menuForNoSelection() } - if objects.count == 1 { - if let feed = objects.first as? Feed { - return menuForFeed(feed) - } - if let folder = objects.first as? Folder { - return menuForFolder(folder) - } - return nil + if objects.count > 1 { + return menuForMultipleObjects(objects) } - return menuForMultipleObjects(objects) + let object = objects.first! + + switch object { + case is Feed: + return menuForFeed(object as! Feed) + case is Folder: + return menuForFolder(object as! Folder) + case is PseudoFeed: + return menuForSmartFeed(object as! PseudoFeed) + default: + return nil + } } } @@ -60,11 +65,7 @@ extension SidebarViewController { } let articles = unreadArticles(for: objects) - if articles.isEmpty { - return - } - - guard let undoManager = undoManager, let markReadCommand = MarkReadOrUnreadCommand(initialArticles: Array(articles), markingRead: true, undoManager: undoManager) else { + guard let undoManager = undoManager, let markReadCommand = MarkStatusCommand(initialArticles: Array(articles), markingRead: true, undoManager: undoManager) else { return } runCommand(markReadCommand) @@ -160,11 +161,21 @@ private extension SidebarViewController { return menu.numberOfItems > 0 ? menu : nil } + func menuForSmartFeed(_ smartFeed: PseudoFeed) -> NSMenu? { + + let menu = NSMenu(title: "") + + if smartFeed.unreadCount > 0 { + menu.addItem(markAllReadMenuItem([smartFeed])) + } + return menu.numberOfItems > 0 ? menu : nil + } + func menuForMultipleObjects(_ objects: [Any]) -> NSMenu? { - guard allObjectsAreFeedsAndOrFolders(objects) else { - return nil - } +// guard allObjectsAreFeedsAndOrFolders(objects) else { +// return nil +// } let menu = NSMenu(title: "") diff --git a/Evergreen/MainWindow/Timeline/TimelineViewController+ContextualMenus.swift b/Evergreen/MainWindow/Timeline/TimelineViewController+ContextualMenus.swift index 3a0df2348..44aff4df0 100644 --- a/Evergreen/MainWindow/Timeline/TimelineViewController+ContextualMenus.swift +++ b/Evergreen/MainWindow/Timeline/TimelineViewController+ContextualMenus.swift @@ -72,7 +72,7 @@ private extension TimelineViewController { guard let articlesToMark = read ? unreadArticles(from: articles) : readArticles(from: articles) else { return } - guard let undoManager = undoManager, let markReadCommand = MarkReadOrUnreadCommand(initialArticles: Array(articlesToMark), markingRead: read, undoManager: undoManager) else { + guard let undoManager = undoManager, let markReadCommand = MarkStatusCommand(initialArticles: Array(articlesToMark), markingRead: read, undoManager: undoManager) else { return } diff --git a/Evergreen/MainWindow/Timeline/TimelineViewController.swift b/Evergreen/MainWindow/Timeline/TimelineViewController.swift index 993a8d8ba..328b8131b 100644 --- a/Evergreen/MainWindow/Timeline/TimelineViewController.swift +++ b/Evergreen/MainWindow/Timeline/TimelineViewController.swift @@ -157,7 +157,7 @@ class TimelineViewController: NSViewController, UndoableCommandRunner { func markAllAsRead() { - guard let undoManager = undoManager, let markReadCommand = MarkReadOrUnreadCommand(initialArticles: articles, markingRead: true, undoManager: undoManager) else { + guard let undoManager = undoManager, let markReadCommand = MarkStatusCommand(initialArticles: articles, markingRead: true, undoManager: undoManager) else { return } runCommand(markReadCommand) @@ -201,7 +201,7 @@ class TimelineViewController: NSViewController, UndoableCommandRunner { @IBAction func markSelectedArticlesAsRead(_ sender: Any?) { - guard let undoManager = undoManager, let markReadCommand = MarkReadOrUnreadCommand(initialArticles: selectedArticles, markingRead: true, undoManager: undoManager) else { + guard let undoManager = undoManager, let markReadCommand = MarkStatusCommand(initialArticles: selectedArticles, markingRead: true, undoManager: undoManager) else { return } runCommand(markReadCommand) @@ -209,7 +209,7 @@ class TimelineViewController: NSViewController, UndoableCommandRunner { @IBAction func markSelectedArticlesAsUnread(_ sender: Any?) { - guard let undoManager = undoManager, let markUnreadCommand = MarkReadOrUnreadCommand(initialArticles: selectedArticles, markingRead: false, undoManager: undoManager) else { + guard let undoManager = undoManager, let markUnreadCommand = MarkStatusCommand(initialArticles: selectedArticles, markingRead: false, undoManager: undoManager) else { return } runCommand(markUnreadCommand) @@ -237,7 +237,7 @@ class TimelineViewController: NSViewController, UndoableCommandRunner { return } - guard let undoManager = undoManager, let markReadCommand = MarkReadOrUnreadCommand(initialArticles: articlesToMark, markingRead: true, undoManager: undoManager) else { + guard let undoManager = undoManager, let markReadCommand = MarkStatusCommand(initialArticles: articlesToMark, markingRead: true, undoManager: undoManager) else { return } runCommand(markReadCommand)