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:
parent
a13d21395e
commit
c8d2fac9a6
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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 }
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 */,
|
||||||
|
|
|
@ -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: "")
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue