diff --git a/Frameworks/Account/Account.swift b/Frameworks/Account/Account.swift
index c8c826ef4..79c28212a 100644
--- a/Frameworks/Account/Account.swift
+++ b/Frameworks/Account/Account.swift
@@ -63,6 +63,8 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
private let feedsPath: ODBPath
private let feedsTable: ODBTable
+ private let opmlFilePath: String
+
private struct SettingsKey {
static let unreadCount = "unreadCount"
}
@@ -115,6 +117,8 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
self.settingsFile = settingsFile
self.dataFolder = dataFolder
+ self.opmlFilePath = (dataFolder as NSString).appendingPathComponent("Subscriptions.opml")
+
let databaseFilePath = (dataFolder as NSString).appendingPathComponent("DB.sqlite3")
self.database = ArticlesDatabase(databaseFilePath: databaseFilePath, accountID: accountID)
@@ -417,6 +421,32 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
flattenedFeeds().forEach { $0.unreadCount = 0 }
}
+ public func opmlDocument() -> String {
+ let escapedTitle = nameForDisplay.rs_stringByEscapingSpecialXMLCharacters()
+ let openingText =
+ """
+
+
+
+
+ \(escapedTitle)
+
+
+
+ """
+
+ let middleText = OPMLString(indentLevel: 0)
+
+ let closingText =
+ """
+
+
+ """
+
+ let opml = openingText + middleText + closingText
+ return opml
+ }
+
// MARK: - Debug
public func debugDropConditionalGetInfo() {
@@ -585,6 +615,8 @@ private extension Account {
func saveToDisk() {
+ dirty = false
+
let d = diskDictionary()
do {
try RSPlist.write(d, filePath: settingsFile)
@@ -592,7 +624,15 @@ private extension Account {
catch let error as NSError {
NSApplication.shared.presentError(error)
}
- dirty = false
+
+ let opmlDocumentString = opmlDocument()
+ do {
+ let url = URL(fileURLWithPath: opmlFilePath)
+ try opmlDocumentString.write(to: url, atomically: true, encoding: .utf8)
+ }
+ catch let error as NSError {
+ NSApplication.shared.presentError(error)
+ }
}
}
diff --git a/Frameworks/Account/Feed.swift b/Frameworks/Account/Feed.swift
index 78194e085..33741421d 100644
--- a/Frameworks/Account/Feed.swift
+++ b/Frameworks/Account/Feed.swift
@@ -116,7 +116,6 @@ public final class Feed: DisplayNameProvider, UnreadCountProvider, Hashable {
self.faviconURL = dictionary[Key.faviconURL] as? String
self.name = dictionary[Key.name] as? String
self.editedName = dictionary[Key.editedName] as? String
-// self.contentHash = dictionary[Key.contentHash] as? String
if let conditionalGetInfoDictionary = dictionary[Key.conditionalGetInfo] as? [String: String] {
self.conditionalGetInfo = HTTPConditionalGetInfo(dictionary: conditionalGetInfoDictionary)
@@ -160,9 +159,6 @@ public final class Feed: DisplayNameProvider, UnreadCountProvider, Hashable {
if let authorsArray = authors?.diskArray() {
d[Key.authors] = authorsArray
}
-// if let contentHash = contentHash {
-// d[Key.contentHash] = contentHash
-// }
if let conditionalGetInfo = conditionalGetInfo {
d[Key.conditionalGetInfo] = conditionalGetInfo.dictionary
}
diff --git a/Frameworks/Account/LocalAccount/LocalAccountRefresher.swift b/Frameworks/Account/LocalAccount/LocalAccountRefresher.swift
index 618093110..fbdf1d1e6 100644
--- a/Frameworks/Account/LocalAccount/LocalAccountRefresher.swift
+++ b/Frameworks/Account/LocalAccount/LocalAccountRefresher.swift
@@ -80,7 +80,6 @@ extension LocalAccountRefresher: DownloadSessionDelegate {
}
feed.contentHash = dataHash
- feed.account?.dirty = true
}
}
}
diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj
index 4e8bee760..029e361d3 100644
--- a/NetNewsWire.xcodeproj/project.pbxproj
+++ b/NetNewsWire.xcodeproj/project.pbxproj
@@ -8,6 +8,9 @@
/* Begin PBXBuildFile section */
519B8D332143397200FA689C /* SharingServiceDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519B8D322143397200FA689C /* SharingServiceDelegate.swift */; };
+ 51EC114C2149FE3300B296E3 /* FolderTreeMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51EC114B2149FE3300B296E3 /* FolderTreeMenu.swift */; };
+ 51EC11A1214A94AD00B296E3 /* AddFeedFromListSheet.xib in Resources */ = {isa = PBXBuildFile; fileRef = 51EC1193214A94AC00B296E3 /* AddFeedFromListSheet.xib */; };
+ 51EC11A3214A990000B296E3 /* AddFeedFromListWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51EC11A2214A990000B296E3 /* AddFeedFromListWindowController.swift */; };
6581C73820CED60100F4AD34 /* SafariExtensionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6581C73720CED60100F4AD34 /* SafariExtensionHandler.swift */; };
6581C73A20CED60100F4AD34 /* SafariExtensionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6581C73920CED60100F4AD34 /* SafariExtensionViewController.swift */; };
6581C73D20CED60100F4AD34 /* SafariExtensionViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6581C73B20CED60100F4AD34 /* SafariExtensionViewController.xib */; };
@@ -480,6 +483,9 @@
/* Begin PBXFileReference section */
519B8D322143397200FA689C /* SharingServiceDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharingServiceDelegate.swift; sourceTree = ""; };
+ 51EC114B2149FE3300B296E3 /* FolderTreeMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = FolderTreeMenu.swift; path = AddFeed/FolderTreeMenu.swift; sourceTree = ""; };
+ 51EC1194214A94AC00B296E3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = NetNewsWire/Base.lproj/AddFeedFromListSheet.xib; sourceTree = SOURCE_ROOT; };
+ 51EC11A2214A990000B296E3 /* AddFeedFromListWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AddFeedFromListWindowController.swift; path = AddFeed/AddFeedFromListWindowController.swift; sourceTree = ""; };
6581C73320CED60000F4AD34 /* Subscribe to Feed.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "Subscribe to Feed.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
6581C73420CED60100F4AD34 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; };
6581C73720CED60100F4AD34 /* SafariExtensionHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafariExtensionHandler.swift; sourceTree = ""; };
@@ -948,10 +954,13 @@
849A97551ED9EAC3007D329B /* Add Feed */ = {
isa = PBXGroup;
children = (
+ 51EC1193214A94AC00B296E3 /* AddFeedFromListSheet.xib */,
+ 51EC11A2214A990000B296E3 /* AddFeedFromListWindowController.swift */,
849A97A71ED9F9AA007D329B /* AddFeedSheet.xib */,
849A97511ED9EAC0007D329B /* AddFeedController.swift */,
849A97521ED9EAC0007D329B /* AddFeedWindowController.swift */,
849A97A01ED9F180007D329B /* InitialFeedDownloader.swift */,
+ 51EC114B2149FE3300B296E3 /* FolderTreeMenu.swift */,
849A97A11ED9F180007D329B /* FolderTreeControllerDelegate.swift */,
);
name = "Add Feed";
@@ -1528,8 +1537,8 @@
ORGANIZATIONNAME = "Ranchero Software";
TargetAttributes = {
6581C73220CED60000F4AD34 = {
- DevelopmentTeam = M8L2WTLA8W;
- ProvisioningStyle = Manual;
+ DevelopmentTeam = SHJK2V3AJG;
+ ProvisioningStyle = Automatic;
};
840D617B2029031C009BC708 = {
CreatedOnToolsVersion = 9.3;
@@ -1550,12 +1559,12 @@
};
849C645F1ED37A5D003D8FC0 = {
CreatedOnToolsVersion = 8.2.1;
- DevelopmentTeam = M8L2WTLA8W;
- ProvisioningStyle = Manual;
+ DevelopmentTeam = SHJK2V3AJG;
+ ProvisioningStyle = Automatic;
};
849C64701ED37A5D003D8FC0 = {
CreatedOnToolsVersion = 8.2.1;
- DevelopmentTeam = 9C84TZ7Q6Z;
+ DevelopmentTeam = SHJK2V3AJG;
ProvisioningStyle = Automatic;
TestTargetID = 849C645F1ED37A5D003D8FC0;
};
@@ -1811,6 +1820,7 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ 51EC11A1214A94AD00B296E3 /* AddFeedFromListSheet.xib in Resources */,
84EB381F1FBA8B9F000D2111 /* KeyboardShortcuts.html in Resources */,
849A97951ED9EF7A007D329B /* IndeterminateProgressWindow.xib in Resources */,
844B5B651FEA11F200C7C76A /* GlobalKeyboardShortcuts.plist in Resources */,
@@ -1940,6 +1950,7 @@
8426118A1FCB67AA0086A189 /* FeedIconDownloader.swift in Sources */,
84162A152038C12C00035290 /* MarkCommandValidationStatus.swift in Sources */,
84E95D241FB1087500552D99 /* ArticlePasteboardWriter.swift in Sources */,
+ 51EC11A3214A990000B296E3 /* AddFeedFromListWindowController.swift in Sources */,
849A975B1ED9EB0D007D329B /* ArticleUtilities.swift in Sources */,
84DAEE301F86CAFE0058304B /* OPMLImporter.swift in Sources */,
849A975C1ED9EB0D007D329B /* DefaultFeedsImporter.swift in Sources */,
@@ -2005,6 +2016,7 @@
84B99C691FAE36B800ECDEDB /* FeedListFolder.swift in Sources */,
84411E711FE5FBFA004B527F /* SmallIconProvider.swift in Sources */,
844B5B591FE9FE4F00C7C76A /* SidebarKeyboardDelegate.swift in Sources */,
+ 51EC114C2149FE3300B296E3 /* FolderTreeMenu.swift in Sources */,
849A97A31ED9F180007D329B /* FolderTreeControllerDelegate.swift in Sources */,
84A1500320048D660046AD9A /* SendToCommand.swift in Sources */,
845A29091FC74B8E007B49E3 /* SingleFaviconDownloader.swift in Sources */,
@@ -2113,6 +2125,14 @@
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
+ 51EC1193214A94AC00B296E3 /* AddFeedFromListSheet.xib */ = {
+ isa = PBXVariantGroup;
+ children = (
+ 51EC1194214A94AC00B296E3 /* Base */,
+ );
+ name = AddFeedFromListSheet.xib;
+ sourceTree = "";
+ };
6581C73B20CED60100F4AD34 /* SafariExtensionViewController.xib */ = {
isa = PBXVariantGroup;
children = (
diff --git a/NetNewsWire/AppDelegate.swift b/NetNewsWire/AppDelegate.swift
index a94d8405c..8e38abf8a 100644
--- a/NetNewsWire/AppDelegate.swift
+++ b/NetNewsWire/AppDelegate.swift
@@ -49,6 +49,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations,
private var readerWindows = [NSWindowController]()
private var feedListWindowController: NSWindowController?
private var addFeedController: AddFeedController?
+ private var addFeedFromListController: AddFeedFromListWindowController?
private var addFolderWindowController: AddFolderWindowController?
private var keyboardShortcutsWindowController: WebViewWindowController?
private var inspectorWindowController: InspectorWindowController?
@@ -101,6 +102,18 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations,
addFeedController = AddFeedController(hostWindow: window)
addFeedController?.showAddFeedSheet(urlString, name)
}
+
+ func showAddFeedFromListOnMainWindow(_ feedListFeeds: [FeedListFeed]) {
+
+ addFeedFromListController = AddFeedFromListWindowController(feedListFeeds)
+
+ createAndShowMainWindow()
+
+ let isDisplayingSheet = mainWindowController?.isDisplayingSheet ?? false
+ if !isDisplayingSheet, let mainWindow = mainWindowController?.window {
+ addFeedFromListController!.runSheetOnWindow(mainWindow)
+ }
+ }
// MARK: - NSApplicationDelegate
diff --git a/NetNewsWire/Base.lproj/AddFeedFromListSheet.xib b/NetNewsWire/Base.lproj/AddFeedFromListSheet.xib
new file mode 100644
index 000000000..a622517e9
--- /dev/null
+++ b/NetNewsWire/Base.lproj/AddFeedFromListSheet.xib
@@ -0,0 +1,107 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/NetNewsWire/Base.lproj/MainWindow.storyboard b/NetNewsWire/Base.lproj/MainWindow.storyboard
index 76c995b0b..b44312305 100644
--- a/NetNewsWire/Base.lproj/MainWindow.storyboard
+++ b/NetNewsWire/Base.lproj/MainWindow.storyboard
@@ -1,8 +1,8 @@
-
+
-
+
@@ -120,7 +120,7 @@
-
+
@@ -349,11 +349,11 @@
-
+
-
+
@@ -517,11 +517,11 @@
-
+
-
+
diff --git a/NetNewsWire/FeedList/FeedListViewController.swift b/NetNewsWire/FeedList/FeedListViewController.swift
index 6d7ca1790..1557180ca 100644
--- a/NetNewsWire/FeedList/FeedListViewController.swift
+++ b/NetNewsWire/FeedList/FeedListViewController.swift
@@ -76,8 +76,10 @@ extension FeedListViewController {
}
@IBAction func addToFeeds(_ sender: Any?) {
-
+ let selectedFeeds = selectedObjects.map { $0 as! FeedListFeed }
+ appDelegate.showAddFeedFromListOnMainWindow(selectedFeeds)
}
+
}
// MARK: - NSOutlineViewDataSource
diff --git a/NetNewsWire/MainWindow/AddFeed/AddFeedFromListWindowController.swift b/NetNewsWire/MainWindow/AddFeed/AddFeedFromListWindowController.swift
new file mode 100644
index 000000000..d27cd8e5c
--- /dev/null
+++ b/NetNewsWire/MainWindow/AddFeed/AddFeedFromListWindowController.swift
@@ -0,0 +1,122 @@
+//
+// AddFeedFromListWindowController.swift
+// NetNewsWire
+//
+// Created by Maurice Parker on 9/13/18.
+// Copyright © 2018 Ranchero Software, LLC. All rights reserved.
+//
+
+import AppKit
+import RSCore
+import RSTree
+import Articles
+import Account
+
+
+class AddFeedFromListWindowController : NSWindowController {
+
+ @IBOutlet weak var addFeedTextField: NSTextField!
+ @IBOutlet weak var folderPopupButton: NSPopUpButton!
+
+ private var feedListFeeds: [FeedListFeed]?
+ private var hostWindow: NSWindow!
+ private var folderTreeController: TreeController?
+
+ convenience init(_ feedListFeeds: [FeedListFeed]) {
+ self.init(windowNibName: NSNib.Name(rawValue: "AddFeedFromListSheet"))
+ self.feedListFeeds = feedListFeeds
+ }
+
+ func runSheetOnWindow(_ w: NSWindow) {
+ hostWindow = w
+ if let sheetWindow = self.window {
+ hostWindow.beginSheet(sheetWindow) { (returnCode: NSApplication.ModalResponse) -> Void in
+ }
+ }
+ }
+
+ override func windowDidLoad() {
+
+ guard let feedListFeeds = feedListFeeds else {
+ assertionFailure("Feeds should have been passed in the initializer")
+ return
+ }
+
+ if feedListFeeds.count == 1 {
+ addFeedTextField.stringValue = "Add \"\(feedListFeeds.first!.nameForDisplay)\"?"
+ } else {
+ addFeedTextField.stringValue = "Add \(feedListFeeds.count) feeds?"
+ }
+
+ let rootNode = Node(representedObject: AccountManager.shared.localAccount, parent: nil)
+ rootNode.canHaveChildNodes = true
+ folderTreeController = TreeController(delegate: FolderTreeControllerDelegate(), rootNode: rootNode)
+
+ folderPopupButton.menu = FolderTreeMenu.createFolderPopupMenu(with: folderTreeController!.rootNode)
+
+ }
+
+
+ // MARK: Actions
+
+ @IBAction func cancel(_ sender: Any?) {
+ if let sheetWindow = window {
+ hostWindow.endSheet(sheetWindow, returnCode: NSApplication.ModalResponse.cancel)
+ }
+ }
+
+ @IBAction func addFeed(_ sender: Any?) {
+
+ guard let container = folderPopupButton.selectedItem?.representedObject as? Container else {
+ assertionFailure("Expected the folderPopupButton to have a container.")
+ return
+ }
+
+ guard let feedListFeeds = feedListFeeds else {
+ assertionFailure("Feeds should have been passed in the initializer")
+ return
+ }
+
+ var account: Account?
+ var folder: Folder?
+ if container is Folder {
+ folder = (container as! Folder)
+ account = folder!.account
+ } else {
+ account = (container as! Account)
+ }
+
+ for feedListFeed in feedListFeeds {
+
+ if account!.hasFeed(withURL: feedListFeed.url) {
+ continue
+ }
+
+ guard let feed = account!.createFeed(with: feedListFeed.nameForDisplay, editedName: nil, url: feedListFeed.url) else {
+ continue
+ }
+
+ guard let url = URL(string: feedListFeed.url) else {
+ assertionFailure("Malformed URL string: \(feedListFeed.url).")
+ continue
+ }
+
+ if account!.addFeed(feed, to: folder) {
+ NotificationCenter.default.post(name: .UserDidAddFeed, object: self, userInfo: [UserInfoKey.feed: feed])
+ }
+
+ InitialFeedDownloader.download(url) { (parsedFeed) in
+ if let parsedFeed = parsedFeed {
+ account!.update(feed, with: parsedFeed, {})
+ }
+ }
+
+ }
+
+ if let sheetWindow = window {
+ hostWindow.endSheet(sheetWindow, returnCode: NSApplication.ModalResponse.OK)
+ }
+
+ }
+
+}
diff --git a/NetNewsWire/MainWindow/AddFeed/AddFeedWindowController.swift b/NetNewsWire/MainWindow/AddFeed/AddFeedWindowController.swift
index 78e3f92b0..451f801d7 100644
--- a/NetNewsWire/MainWindow/AddFeed/AddFeedWindowController.swift
+++ b/NetNewsWire/MainWindow/AddFeed/AddFeedWindowController.swift
@@ -68,7 +68,7 @@ class AddFeedWindowController : NSWindowController {
nameTextField.stringValue = initialName
}
- folderPopupButton.menu = createFolderPopupMenu()
+ folderPopupButton.menu = FolderTreeMenu.createFolderPopupMenu(with: folderTreeController.rootNode)
updateUI()
}
@@ -139,35 +139,4 @@ private extension AddFeedWindowController {
return folderPopupButton.selectedItem?.representedObject as? Container
}
- func createFolderPopupMenu() -> NSMenu {
-
- let menu = NSMenu(title: "Folders")
-
- let menuItem = NSMenuItem(title: NSLocalizedString("Top Level", comment: "Add Feed Sheet"), action: nil, keyEquivalent: "")
- menuItem.representedObject = folderTreeController.rootNode.representedObject
- menu.addItem(menuItem)
-
- let childNodes = folderTreeController.rootNode.childNodes
- addFolderItemsToMenuWithNodes(menu: menu, nodes: childNodes, indentationLevel: 1)
-
- return menu
- }
-
- func addFolderItemsToMenuWithNodes(menu: NSMenu, nodes: [Node], indentationLevel: Int) {
-
- nodes.forEach { (oneNode) in
-
- if let nameProvider = oneNode.representedObject as? DisplayNameProvider {
-
- let menuItem = NSMenuItem(title: nameProvider.nameForDisplay, action: nil, keyEquivalent: "")
- menuItem.indentationLevel = indentationLevel
- menuItem.representedObject = oneNode.representedObject
- menu.addItem(menuItem)
-
- if oneNode.numberOfChildNodes > 0 {
- addFolderItemsToMenuWithNodes(menu: menu, nodes: oneNode.childNodes, indentationLevel: indentationLevel + 1)
- }
- }
- }
- }
}
diff --git a/NetNewsWire/MainWindow/AddFeed/FolderTreeMenu.swift b/NetNewsWire/MainWindow/AddFeed/FolderTreeMenu.swift
new file mode 100644
index 000000000..5b32307d3
--- /dev/null
+++ b/NetNewsWire/MainWindow/AddFeed/FolderTreeMenu.swift
@@ -0,0 +1,47 @@
+//
+// FolderTreeMenu.swift
+// NetNewsWire
+//
+// Created by Maurice Parker on 9/12/18.
+// Copyright © 2018 Ranchero Software, LLC. All rights reserved.
+//
+
+import AppKit
+import RSCore
+import RSTree
+
+class FolderTreeMenu {
+
+ static func createFolderPopupMenu(with rootNode: Node) -> NSMenu {
+
+ let menu = NSMenu(title: "Folders")
+
+ let menuItem = NSMenuItem(title: NSLocalizedString("Top Level", comment: "Add Feed Sheet"), action: nil, keyEquivalent: "")
+ menuItem.representedObject = rootNode.representedObject
+ menu.addItem(menuItem)
+
+ let childNodes = rootNode.childNodes
+ addFolderItemsToMenuWithNodes(menu: menu, nodes: childNodes, indentationLevel: 1)
+
+ return menu
+ }
+
+ private static func addFolderItemsToMenuWithNodes(menu: NSMenu, nodes: [Node], indentationLevel: Int) {
+
+ nodes.forEach { (oneNode) in
+
+ if let nameProvider = oneNode.representedObject as? DisplayNameProvider {
+
+ let menuItem = NSMenuItem(title: nameProvider.nameForDisplay, action: nil, keyEquivalent: "")
+ menuItem.indentationLevel = indentationLevel
+ menuItem.representedObject = oneNode.representedObject
+ menu.addItem(menuItem)
+
+ if oneNode.numberOfChildNodes > 0 {
+ addFolderItemsToMenuWithNodes(menu: menu, nodes: oneNode.childNodes, indentationLevel: indentationLevel + 1)
+ }
+ }
+ }
+ }
+
+}
diff --git a/NetNewsWire/MainWindow/MainWindowController.swift b/NetNewsWire/MainWindow/MainWindowController.swift
index 15f065fac..5adbe168a 100644
--- a/NetNewsWire/MainWindow/MainWindowController.swift
+++ b/NetNewsWire/MainWindow/MainWindowController.swift
@@ -129,8 +129,8 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations {
return canMarkAllAsRead()
}
- if item.action == #selector(markRead(_:)) {
- return canMarkRead()
+ if item.action == #selector(toggleRead(_:)) {
+ return validateToggleRead(item)
}
if item.action == #selector(toggleStarred(_:)) {
@@ -223,9 +223,9 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations {
timelineViewController?.markAllAsRead()
}
- @IBAction func markRead(_ sender: Any?) {
+ @IBAction func toggleRead(_ sender: Any?) {
- timelineViewController?.markSelectedArticlesAsRead(sender)
+ timelineViewController?.toggleReadStatusForSelectedArticles()
}
@IBAction func markUnread(_ sender: Any?) {
@@ -407,9 +407,35 @@ private extension MainWindowController {
return timelineViewController?.canMarkAllAsRead() ?? false
}
- func canMarkRead() -> Bool {
+ func validateToggleRead(_ item: NSValidatedUserInterfaceItem) -> Bool {
- return timelineViewController?.canMarkSelectedArticlesAsRead() ?? false
+ let validationStatus = timelineViewController?.markReadCommandStatus() ?? .canDoNothing
+ let markingRead: Bool
+ let result: Bool
+
+ switch validationStatus {
+ case .canMark:
+ markingRead = true
+ result = true
+ case .canUnmark:
+ markingRead = false
+ result = true
+ case .canDoNothing:
+ markingRead = true
+ result = false
+ }
+
+ let commandName = markingRead ? NSLocalizedString("Mark as Read", comment: "Command") : NSLocalizedString("Mark as Unread", comment: "Command")
+
+ if let toolbarItem = item as? NSToolbarItem {
+ toolbarItem.toolTip = commandName
+ }
+
+ if let menuItem = item as? NSMenuItem {
+ menuItem.title = commandName
+ }
+
+ return result
}
func canMarkOlderArticlesAsRead() -> Bool {
diff --git a/NetNewsWire/MainWindow/Sidebar/SidebarViewController.swift b/NetNewsWire/MainWindow/Sidebar/SidebarViewController.swift
index 3d39cbb50..a306facec 100644
--- a/NetNewsWire/MainWindow/Sidebar/SidebarViewController.swift
+++ b/NetNewsWire/MainWindow/Sidebar/SidebarViewController.swift
@@ -194,6 +194,8 @@ import RSCore
NSCursor.setHiddenUntilMouseMoves(true)
outlineView.selectRowIndexes(IndexSet([row]), byExtendingSelection: false)
+ outlineView.scrollTo(row: row)
+
}
func focus() {
diff --git a/NetNewsWire/MainWindow/Timeline/TimelineViewController.swift b/NetNewsWire/MainWindow/Timeline/TimelineViewController.swift
index 046cef10a..97e7ca65d 100644
--- a/NetNewsWire/MainWindow/Timeline/TimelineViewController.swift
+++ b/NetNewsWire/MainWindow/Timeline/TimelineViewController.swift
@@ -208,6 +208,29 @@ class TimelineViewController: NSViewController, UndoableCommandRunner {
NSPasteboard.general.copyObjects(selectedArticles)
}
+ func toggleReadStatusForSelectedArticles() {
+
+ // If any one of the selected articles is unread, then mark them as read.
+ // If all articles are read, then mark them as unread them.
+
+ let commandStatus = markReadCommandStatus()
+ let markingRead: Bool
+ switch commandStatus {
+ case .canMark:
+ markingRead = true
+ case .canUnmark:
+ markingRead = false
+ case .canDoNothing:
+ return
+ }
+
+ guard let undoManager = undoManager, let markStarredCommand = MarkStatusCommand(initialArticles: selectedArticles, markingRead: markingRead, undoManager: undoManager) else {
+ return
+ }
+
+ runCommand(markStarredCommand)
+ }
+
func toggleStarredStatusForSelectedArticles() {
// If any one of the selected articles is not starred, then star them.