diff --git a/Evergreen/Base.lproj/MainWindow.storyboard b/Evergreen/Base.lproj/MainWindow.storyboard
index ea7e91e46..d47facebf 100644
--- a/Evergreen/Base.lproj/MainWindow.storyboard
+++ b/Evergreen/Base.lproj/MainWindow.storyboard
@@ -134,7 +134,7 @@
-
+
diff --git a/Evergreen/MainWindow/MainWindowController.swift b/Evergreen/MainWindow/MainWindowController.swift
index 46063dfaa..da44085c6 100644
--- a/Evergreen/MainWindow/MainWindowController.swift
+++ b/Evergreen/MainWindow/MainWindowController.swift
@@ -12,6 +12,11 @@ import Account
private let kWindowFrameKey = "MainWindow"
+extension NSImage.Name {
+ static let star = NSImage.Name(rawValue: "star")
+ static let unstar = NSImage.Name(rawValue: "unstar")
+}
+
class MainWindowController : NSWindowController, NSUserInterfaceValidations {
var isOpen: Bool {
@@ -152,6 +157,10 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations {
return canMarkRead()
}
+ if item.action == #selector(toggleStarred(_:)) {
+ return validateToggleStarred(item)
+ }
+
if item.action == #selector(markOlderArticlesAsRead(_:)) {
return canMarkOlderArticlesAsRead()
}
@@ -178,6 +187,31 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations {
return true
}
+ private func validateToggleStarred(_ item: NSValidatedUserInterfaceItem) -> Bool {
+
+ let validationStatus = timelineViewController?.markStarredCommandStatus() ?? .canDoNothing
+ let showStar: Bool
+ let result: Bool
+
+ switch validationStatus {
+ case .canMark:
+ showStar = true
+ result = true
+ case .canUnmark:
+ showStar = false
+ result = true
+ case .canDoNothing:
+ showStar = true
+ result = false
+ }
+
+ if let button = (item as? NSToolbarItem)?.view as? NSButton {
+ button.image = NSImage(named: showStar ? .star : .unstar)
+ }
+
+ return result
+ }
+
// MARK: - Actions
@IBAction func scrollOrGoToNextUnread(_ sender: Any?) {
@@ -267,6 +301,11 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations {
timelineViewController?.markSelectedArticlesAsUnread(sender)
}
+ @IBAction func toggleStarred(_ sender: Any?) {
+
+ timelineViewController?.toggleStarredStatusForSelectedArticles()
+ }
+
@IBAction func markAllAsReadAndGoToNextUnread(_ sender: Any?) {
markAllAsRead(sender)
diff --git a/Evergreen/MainWindow/Timeline/TimelineViewController.swift b/Evergreen/MainWindow/Timeline/TimelineViewController.swift
index 4de75c8db..ea3bd1e7c 100644
--- a/Evergreen/MainWindow/Timeline/TimelineViewController.swift
+++ b/Evergreen/MainWindow/Timeline/TimelineViewController.swift
@@ -12,6 +12,19 @@ import RSTextDrawing
import Data
import Account
+enum MarkCommandValidationStatus {
+
+ case canMark, canUnmark, canDoNothing
+
+ static func statusFor(_ articles: ArticleArray, _ canMarkTest: ((ArticleArray) -> Bool)) -> MarkCommandValidationStatus {
+
+ if articles.isEmpty {
+ return .canDoNothing
+ }
+ return canMarkTest(articles) ? .canMark : .canUnmark
+ }
+}
+
class TimelineViewController: NSViewController, UndoableCommandRunner {
@IBOutlet var tableView: TimelineTableView!
@@ -191,7 +204,7 @@ class TimelineViewController: NSViewController, UndoableCommandRunner {
markSelectedArticlesAsUnread(sender)
}
}
-
+
@IBAction func markSelectedArticlesAsRead(_ sender: Any?) {
guard let undoManager = undoManager, let markReadCommand = MarkStatusCommand(initialArticles: selectedArticles, markingRead: true, undoManager: undoManager) else {
@@ -213,6 +226,38 @@ class TimelineViewController: NSViewController, UndoableCommandRunner {
NSPasteboard.general.copyObjects(selectedArticles)
}
+ func toggleStarredStatusForSelectedArticles() {
+
+ // If any one of the selected articles is not starred, then star them.
+ // If all articles are starred, then unstar them.
+
+ let commandStatus = markStarredCommandStatus()
+ let starring: Bool
+ switch commandStatus {
+ case .canMark:
+ starring = true
+ case .canUnmark:
+ starring = false
+ case .canDoNothing:
+ return
+ }
+
+ guard let undoManager = undoManager, let markStarredCommand = MarkStatusCommand(initialArticles: selectedArticles, markingStarred: starring, undoManager: undoManager) else {
+ return
+ }
+ runCommand(markStarredCommand)
+ }
+
+ func markStarredCommandStatus() -> MarkCommandValidationStatus {
+
+ return MarkCommandValidationStatus.statusFor(selectedArticles) { $0.anyArticleIsUnstarred() }
+ }
+
+ func markReadCommandStatus() -> MarkCommandValidationStatus {
+
+ return MarkCommandValidationStatus.statusFor(selectedArticles) { $0.anyArticleIsUnread() }
+ }
+
func markOlderArticlesAsRead() {
// Mark articles the same age or older than the selected article(s) as read.