Rename MarkReadOrUnreadCommand to MarkStatusCommand and make it handle starring/unstarring and deleting/undeleting. Also: add contextual menu for smart feeds in the sidebar.

This commit is contained in:
Brent Simmons 2018-02-11 12:59:35 -08:00
parent a13d21395e
commit c8d2fac9a6
6 changed files with 130 additions and 89 deletions

View File

@ -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<Article>
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)
}
}

View File

@ -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<Article>
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 }
}
}

View File

@ -49,7 +49,7 @@
846E773E1F6EF67A00A165E2 /* Account.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 846E773A1F6EF5D700A165E2 /* Account.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 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 */; }; 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, ); }; }; 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 */; }; 8472058120142E8900AD578B /* FeedInspectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8472058020142E8900AD578B /* FeedInspectorViewController.swift */; };
847FA121202BA34100BB56C8 /* SidebarContextualMenuDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847FA120202BA34100BB56C8 /* SidebarContextualMenuDelegate.swift */; }; 847FA121202BA34100BB56C8 /* SidebarContextualMenuDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847FA120202BA34100BB56C8 /* SidebarContextualMenuDelegate.swift */; };
848F6AE51FC29CFB002D422E /* FaviconDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848F6AE41FC29CFA002D422E /* FaviconDownloader.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 = "<group>"; }; 845F52EC1FB2B9FC00C10BF0 /* FeedPasteboardWriter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedPasteboardWriter.swift; sourceTree = "<group>"; };
846E77161F6EF5D000A165E2 /* Database.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Database.xcodeproj; path = Frameworks/Database/Database.xcodeproj; sourceTree = "<group>"; }; 846E77161F6EF5D000A165E2 /* Database.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Database.xcodeproj; path = Frameworks/Database/Database.xcodeproj; sourceTree = "<group>"; };
846E77301F6EF5D600A165E2 /* Account.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Account.xcodeproj; path = Frameworks/Account/Account.xcodeproj; sourceTree = "<group>"; }; 846E77301F6EF5D600A165E2 /* Account.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Account.xcodeproj; path = Frameworks/Account/Account.xcodeproj; sourceTree = "<group>"; };
84702AA31FA27AC0006B8943 /* MarkReadOrUnreadCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarkReadOrUnreadCommand.swift; sourceTree = "<group>"; }; 84702AA31FA27AC0006B8943 /* MarkStatusCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarkStatusCommand.swift; sourceTree = "<group>"; };
8472058020142E8900AD578B /* FeedInspectorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedInspectorViewController.swift; sourceTree = "<group>"; }; 8472058020142E8900AD578B /* FeedInspectorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedInspectorViewController.swift; sourceTree = "<group>"; };
847752FE2008879500D93690 /* CoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreServices.framework; path = System/Library/Frameworks/CoreServices.framework; sourceTree = SDKROOT; }; 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 = "<group>"; }; 847FA120202BA34100BB56C8 /* SidebarContextualMenuDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarContextualMenuDelegate.swift; sourceTree = "<group>"; };
@ -893,7 +893,7 @@
84702AB31FA27AE8006B8943 /* Commands */ = { 84702AB31FA27AE8006B8943 /* Commands */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
84702AA31FA27AC0006B8943 /* MarkReadOrUnreadCommand.swift */, 84702AA31FA27AC0006B8943 /* MarkStatusCommand.swift */,
84B99C9C1FAE83C600ECDEDB /* DeleteFromSidebarCommand.swift */, 84B99C9C1FAE83C600ECDEDB /* DeleteFromSidebarCommand.swift */,
84A1500220048D660046AD9A /* SendToCommand.swift */, 84A1500220048D660046AD9A /* SendToCommand.swift */,
84A14FF220048CA70046AD9A /* SendToMicroBlogCommand.swift */, 84A14FF220048CA70046AD9A /* SendToMicroBlogCommand.swift */,
@ -1896,7 +1896,7 @@
D553738B20186C20006D8857 /* Article+Scriptability.swift in Sources */, D553738B20186C20006D8857 /* Article+Scriptability.swift in Sources */,
8403E75B201C4A79007F7246 /* FeedListKeyboardDelegate.swift in Sources */, 8403E75B201C4A79007F7246 /* FeedListKeyboardDelegate.swift in Sources */,
845EE7C11FC2488C00854A1F /* SmartFeed.swift in Sources */, 845EE7C11FC2488C00854A1F /* SmartFeed.swift in Sources */,
84702AA41FA27AC0006B8943 /* MarkReadOrUnreadCommand.swift in Sources */, 84702AA41FA27AC0006B8943 /* MarkStatusCommand.swift in Sources */,
D5907D7F2004AC00005947E5 /* NSApplication+Scriptability.swift in Sources */, D5907D7F2004AC00005947E5 /* NSApplication+Scriptability.swift in Sources */,
849A979F1ED9F130007D329B /* SidebarCell.swift in Sources */, 849A979F1ED9F130007D329B /* SidebarCell.swift in Sources */,
849A97651ED9EB96007D329B /* SidebarTreeControllerDelegate.swift in Sources */, 849A97651ED9EB96007D329B /* SidebarTreeControllerDelegate.swift in Sources */,

View File

@ -19,17 +19,22 @@ extension SidebarViewController {
return menuForNoSelection() return menuForNoSelection()
} }
if objects.count == 1 { if objects.count > 1 {
if let feed = objects.first as? Feed { return menuForMultipleObjects(objects)
return menuForFeed(feed)
}
if let folder = objects.first as? Folder {
return menuForFolder(folder)
}
return nil
} }
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) let articles = unreadArticles(for: objects)
if articles.isEmpty { guard let undoManager = undoManager, let markReadCommand = MarkStatusCommand(initialArticles: Array(articles), markingRead: true, undoManager: undoManager) else {
return
}
guard let undoManager = undoManager, let markReadCommand = MarkReadOrUnreadCommand(initialArticles: Array(articles), markingRead: true, undoManager: undoManager) else {
return return
} }
runCommand(markReadCommand) runCommand(markReadCommand)
@ -160,11 +161,21 @@ private extension SidebarViewController {
return menu.numberOfItems > 0 ? menu : nil 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? { func menuForMultipleObjects(_ objects: [Any]) -> NSMenu? {
guard allObjectsAreFeedsAndOrFolders(objects) else { // guard allObjectsAreFeedsAndOrFolders(objects) else {
return nil // return nil
} // }
let menu = NSMenu(title: "") let menu = NSMenu(title: "")

View File

@ -72,7 +72,7 @@ private extension TimelineViewController {
guard let articlesToMark = read ? unreadArticles(from: articles) : readArticles(from: articles) else { guard let articlesToMark = read ? unreadArticles(from: articles) : readArticles(from: articles) else {
return 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 return
} }

View File

@ -157,7 +157,7 @@ class TimelineViewController: NSViewController, UndoableCommandRunner {
func markAllAsRead() { 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 return
} }
runCommand(markReadCommand) runCommand(markReadCommand)
@ -201,7 +201,7 @@ class TimelineViewController: NSViewController, UndoableCommandRunner {
@IBAction func markSelectedArticlesAsRead(_ sender: Any?) { @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 return
} }
runCommand(markReadCommand) runCommand(markReadCommand)
@ -209,7 +209,7 @@ class TimelineViewController: NSViewController, UndoableCommandRunner {
@IBAction func markSelectedArticlesAsUnread(_ sender: Any?) { @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 return
} }
runCommand(markUnreadCommand) runCommand(markUnreadCommand)
@ -237,7 +237,7 @@ class TimelineViewController: NSViewController, UndoableCommandRunner {
return 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 return
} }
runCommand(markReadCommand) runCommand(markReadCommand)