From ef1d95341b2dbfd579c2aa5912780d50d37f60ca Mon Sep 17 00:00:00 2001 From: Brent Simmons Date: Sat, 4 Nov 2017 14:53:21 -0700 Subject: [PATCH] Show feeds in Feed Directory sidebar. --- Evergreen/Base.lproj/FeedList.storyboard | 27 +++---- Evergreen/Extensions/Node-Extensions.swift | 1 + Evergreen/FeedList/FeedListFeed.swift | 9 ++- Evergreen/FeedList/FeedListFolder.swift | 10 ++- .../FeedListTreeControllerDelegate.swift | 78 ++++++++++++++----- .../FeedList/FeedListViewController.swift | 53 +++++++++++++ .../Sidebar/SidebarViewController.swift | 1 + Frameworks/Account/Folder.swift | 1 + .../Data/Data.xcodeproj/project.pbxproj | 8 -- .../RSCore/RSCore.xcodeproj/project.pbxproj | 12 +++ .../RSCore}/DisplayNameProvider.swift | 0 .../RSCore}/OPMLRepresentable.swift | 0 12 files changed, 156 insertions(+), 44 deletions(-) rename Frameworks/{Data => RSCore/RSCore}/DisplayNameProvider.swift (100%) rename Frameworks/{Data => RSCore/RSCore}/OPMLRepresentable.swift (100%) diff --git a/Evergreen/Base.lproj/FeedList.storyboard b/Evergreen/Base.lproj/FeedList.storyboard index d206c0c5b..851ee605e 100644 --- a/Evergreen/Base.lproj/FeedList.storyboard +++ b/Evergreen/Base.lproj/FeedList.storyboard @@ -150,7 +150,7 @@ - + @@ -199,19 +199,19 @@ - + - + - + - - - - + + + + @@ -219,13 +219,17 @@ - - + + + + + + @@ -337,7 +341,4 @@ - - - diff --git a/Evergreen/Extensions/Node-Extensions.swift b/Evergreen/Extensions/Node-Extensions.swift index 03ffa5fcc..e7445fe35 100644 --- a/Evergreen/Extensions/Node-Extensions.swift +++ b/Evergreen/Extensions/Node-Extensions.swift @@ -9,6 +9,7 @@ import Foundation import RSTree import Data +import RSCore extension Node { diff --git a/Evergreen/FeedList/FeedListFeed.swift b/Evergreen/FeedList/FeedListFeed.swift index cde60f337..8a0b658ad 100644 --- a/Evergreen/FeedList/FeedListFeed.swift +++ b/Evergreen/FeedList/FeedListFeed.swift @@ -7,14 +7,21 @@ // import Foundation +import RSCore -final class FeedListFeed: Hashable { +final class FeedListFeed: Hashable, DisplayNameProvider { let name: String let url: String let homePageURL: String let hashValue: Int + var nameForDisplay: String { // DisplayNameProvider + get { + return name + } + } + init(name: String, url: String, homePageURL: String) { self.name = name diff --git a/Evergreen/FeedList/FeedListFolder.swift b/Evergreen/FeedList/FeedListFolder.swift index 8883707a3..2f791b872 100644 --- a/Evergreen/FeedList/FeedListFolder.swift +++ b/Evergreen/FeedList/FeedListFolder.swift @@ -7,13 +7,21 @@ // import Foundation +import RSCore +import Data -final class FeedListFolder: Hashable { +final class FeedListFolder: Hashable, DisplayNameProvider { let name: String let feeds: Set let hashValue: Int + var nameForDisplay: String { // DisplayNameProvider + get { + return name + } + } + init(name: String, feeds: Set) { self.name = name diff --git a/Evergreen/FeedList/FeedListTreeControllerDelegate.swift b/Evergreen/FeedList/FeedListTreeControllerDelegate.swift index 59bdc4a1e..f04ae16da 100644 --- a/Evergreen/FeedList/FeedListTreeControllerDelegate.swift +++ b/Evergreen/FeedList/FeedListTreeControllerDelegate.swift @@ -8,6 +8,7 @@ import Foundation import RSTree +import RSCore // Folders and feeds that appear in the Feed Directory are pulled from three sources: // 1. Feeds added in code here. (Evergreen News should be the only one.) @@ -18,42 +19,77 @@ import RSTree final class FeedListTreeControllerDelegate: TreeControllerDelegate { - let topLevelFeeds: Set - let defaultFeeds: Set - let folders: Set + private let topLevelFeeds: Set + private let folders: Set init() { let evergreenNewsFeed = FeedListFeed(name: "Evergreen News", url: "https://ranchero.com/evergreen/feed.json", homePageURL: "https://ranchero.com/evergreen/blog/") self.topLevelFeeds = Set([evergreenNewsFeed]) - self.defaultFeeds = FeedListReader.defaultFeeds() - self.folders = FeedListReader.folders() + let defaultFeeds = FeedListReader.defaultFeeds() + let defaultFeedsFolder = FeedListFolder(name: NSLocalizedString("Default Feeds (for new users)", comment: "Feed Directory"), feeds: defaultFeeds) + + self.folders = Set(FeedListReader.folders() + [defaultFeedsFolder]) } func treeController(treeController: TreeController, childNodesFor node: Node) -> [Node]? { -// if node.isRoot { -// return childNodesForRootNode(node) -// } -// if node.representedObject is FeedListFolder { -// return childNodesForFolderNode(node) -// } - - return nil + if node.isRoot { + return childNodesForRootNode(node) + } + return childNodesForFolderNode(node) } } +// MARK: - Private +private extension FeedListTreeControllerDelegate { -//private extension FeedListTreeControllerDelegate { -// -// func childNodesForRootNode(_ rootNode: Node) -> [Node]? { -// -// return childNodesForContainerNode(rootNode, AccountManager.shared.localAccount.children) -// } -// -//} + func childNodesForRootNode(_ rootNode: Node) -> [Node]? { + + let children = Array(topLevelFeeds) as [AnyObject] + Array(folders) as [AnyObject] + return childNodesForContainerNode(rootNode, children) + } + + func childNodesForFolderNode(_ folderNode: Node) -> [Node]? { + + let folder = folderNode.representedObject as! FeedListFolder + return childNodesForContainerNode(folderNode, Array(folder.feeds)) + } + + func childNodesForContainerNode(_ containerNode: Node, _ children: [AnyObject]) -> [Node]? { + + let nodes = unsortedNodes(parent: containerNode, children: children) + return Node.nodesSortedAlphabeticallyWithFoldersAtEnd(nodes) + } + + func unsortedNodes(parent: Node, children: [AnyObject]) -> [Node] { + + return children.map{ createNode(child: $0, parent: parent) } + } + + func createNode(child: AnyObject, parent: Node) -> Node { + + if let feed = child as? FeedListFeed { + return createNode(feed: feed, parent: parent) + } + let folder = child as! FeedListFolder + return createNode(folder: folder, parent: parent) + } + + func createNode(feed: FeedListFeed, parent: Node) -> Node { + + return Node(representedObject: feed, parent: parent) + } + + func createNode(folder: FeedListFolder, parent: Node) -> Node { + + let node = Node(representedObject: folder, parent: parent) + node.canHaveChildNodes = true + return node + } +} // MARK: - Loading from Disk diff --git a/Evergreen/FeedList/FeedListViewController.swift b/Evergreen/FeedList/FeedListViewController.swift index cc4cf5b91..00760eaf6 100644 --- a/Evergreen/FeedList/FeedListViewController.swift +++ b/Evergreen/FeedList/FeedListViewController.swift @@ -8,6 +8,7 @@ import Cocoa import RSTree +import RSCore final class FeedListViewController: NSViewController { @@ -46,3 +47,55 @@ extension FeedListViewController: NSOutlineViewDataSource { return item as! Node } } + +// MARK: - NSOutlineViewDelegate + +extension FeedListViewController: NSOutlineViewDelegate { + + func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? { + + let cell = outlineView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "FeedListCell"), owner: self) as! SidebarCell + + let node = item as! Node + configure(cell, node) + + return cell + } + + func outlineViewSelectionDidChange(_ notification: Notification) { + +// // TODO: support multiple selection +// +// let selectedRow = self.outlineView.selectedRow +// +// if selectedRow < 0 || selectedRow == NSNotFound { +// postSidebarSelectionDidChangeNotification(nil) +// return +// } +// +// if let selectedNode = self.outlineView.item(atRow: selectedRow) as? Node { +// postSidebarSelectionDidChangeNotification([selectedNode.representedObject]) +// } + } + + private func configure(_ cell: SidebarCell, _ node: Node) { + + cell.objectValue = node + cell.name = nameFor(node) + cell.image = imageFor(node) + } + + func imageFor(_ node: Node) -> NSImage? { + + return nil + } + + func nameFor(_ node: Node) -> String { + + if let displayNameProvider = node.representedObject as? DisplayNameProvider { + return displayNameProvider.nameForDisplay + } + return "" + } + +} diff --git a/Evergreen/MainWindow/Sidebar/SidebarViewController.swift b/Evergreen/MainWindow/Sidebar/SidebarViewController.swift index 152f5a5f5..2500c7ca1 100644 --- a/Evergreen/MainWindow/Sidebar/SidebarViewController.swift +++ b/Evergreen/MainWindow/Sidebar/SidebarViewController.swift @@ -10,6 +10,7 @@ import Cocoa import RSTree import Data import Account +import RSCore @objc class SidebarViewController: NSViewController, NSOutlineViewDelegate, NSOutlineViewDataSource { diff --git a/Frameworks/Account/Folder.swift b/Frameworks/Account/Folder.swift index ad912fbb6..1c5d0cb63 100644 --- a/Frameworks/Account/Folder.swift +++ b/Frameworks/Account/Folder.swift @@ -8,6 +8,7 @@ import Foundation import Data +import RSCore public final class Folder: DisplayNameProvider, Container, UnreadCountProvider { diff --git a/Frameworks/Data/Data.xcodeproj/project.pbxproj b/Frameworks/Data/Data.xcodeproj/project.pbxproj index 64c079032..f50f29323 100644 --- a/Frameworks/Data/Data.xcodeproj/project.pbxproj +++ b/Frameworks/Data/Data.xcodeproj/project.pbxproj @@ -9,8 +9,6 @@ /* Begin PBXBuildFile section */ 840405CA1F1A8E4300DF0296 /* DatabaseID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840405C91F1A8E4300DF0296 /* DatabaseID.swift */; }; 8419741C1F6DD613006346C4 /* UnreadCountProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8419741B1F6DD613006346C4 /* UnreadCountProvider.swift */; }; - 841974201F6DD672006346C4 /* DisplayNameProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8419741F1F6DD672006346C4 /* DisplayNameProvider.swift */; }; - 841974231F6DD804006346C4 /* OPMLRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 841974221F6DD804006346C4 /* OPMLRepresentable.swift */; }; 843079FA1F0AB57F00B4B7F7 /* RSCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 844BEEA31F0AB512004AB7CD /* RSCore.framework */; }; 844BEE651F0AB3C9004AB7CD /* Data.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 844BEE5B1F0AB3C8004AB7CD /* Data.framework */; }; 844BEE6A1F0AB3C9004AB7CD /* DataTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844BEE691F0AB3C9004AB7CD /* DataTests.swift */; }; @@ -56,8 +54,6 @@ /* Begin PBXFileReference section */ 840405C91F1A8E4300DF0296 /* DatabaseID.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatabaseID.swift; sourceTree = ""; }; 8419741B1F6DD613006346C4 /* UnreadCountProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnreadCountProvider.swift; sourceTree = ""; }; - 8419741F1F6DD672006346C4 /* DisplayNameProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisplayNameProvider.swift; sourceTree = ""; }; - 841974221F6DD804006346C4 /* OPMLRepresentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OPMLRepresentable.swift; sourceTree = ""; }; 844BEE5B1F0AB3C8004AB7CD /* Data.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Data.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 844BEE641F0AB3C9004AB7CD /* DataTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = DataTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 844BEE691F0AB3C9004AB7CD /* DataTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataTests.swift; sourceTree = ""; }; @@ -103,8 +99,6 @@ 844BEE841F0AB4DB004AB7CD /* ArticleStatus.swift */, 840405C91F1A8E4300DF0296 /* DatabaseID.swift */, 8419741B1F6DD613006346C4 /* UnreadCountProvider.swift */, - 8419741F1F6DD672006346C4 /* DisplayNameProvider.swift */, - 841974221F6DD804006346C4 /* OPMLRepresentable.swift */, 844BEE761F0AB444004AB7CD /* Info.plist */, 844BEE681F0AB3C9004AB7CD /* DataTests */, 844BEE5C1F0AB3C8004AB7CD /* Products */, @@ -293,10 +287,8 @@ files = ( 844BEE7F1F0AB4CA004AB7CD /* Article.swift in Sources */, 844BEE7D1F0AB4C4004AB7CD /* Feed.swift in Sources */, - 841974231F6DD804006346C4 /* OPMLRepresentable.swift in Sources */, 844BEE831F0AB4D6004AB7CD /* Attachment.swift in Sources */, 8419741C1F6DD613006346C4 /* UnreadCountProvider.swift in Sources */, - 841974201F6DD672006346C4 /* DisplayNameProvider.swift in Sources */, 844BEE811F0AB4D0004AB7CD /* Author.swift in Sources */, 840405CA1F1A8E4300DF0296 /* DatabaseID.swift in Sources */, 844BEE851F0AB4DB004AB7CD /* ArticleStatus.swift in Sources */, diff --git a/Frameworks/RSCore/RSCore.xcodeproj/project.pbxproj b/Frameworks/RSCore/RSCore.xcodeproj/project.pbxproj index 342e08c6e..3640b4360 100755 --- a/Frameworks/RSCore/RSCore.xcodeproj/project.pbxproj +++ b/Frameworks/RSCore/RSCore.xcodeproj/project.pbxproj @@ -89,6 +89,10 @@ 849BF8BA1C9130150071D1DA /* DiskSaver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849BF8B91C9130150071D1DA /* DiskSaver.swift */; }; 84A8358A1D4EC7B80004C598 /* PlistProviderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A835891D4EC7B80004C598 /* PlistProviderProtocol.swift */; }; 84B890561C59CF1600D8BF23 /* NSString+ExtrasTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 84B890551C59CF1600D8BF23 /* NSString+ExtrasTests.m */; }; + 84B99C941FAE64D500ECDEDB /* DisplayNameProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B99C931FAE64D400ECDEDB /* DisplayNameProvider.swift */; }; + 84B99C951FAE64D500ECDEDB /* DisplayNameProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B99C931FAE64D400ECDEDB /* DisplayNameProvider.swift */; }; + 84B99C9A1FAE650100ECDEDB /* OPMLRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B99C991FAE650100ECDEDB /* OPMLRepresentable.swift */; }; + 84B99C9B1FAE650100ECDEDB /* OPMLRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B99C991FAE650100ECDEDB /* OPMLRepresentable.swift */; }; 84BB45431D6909C700B48537 /* NSMutableDictionary-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84BB45421D6909C700B48537 /* NSMutableDictionary-Extensions.swift */; }; 84CFF4FA1AC3C69700CEA6C8 /* RSCore.h in Headers */ = {isa = PBXBuildFile; fileRef = 84CFF4F91AC3C69700CEA6C8 /* RSCore.h */; settings = {ATTRIBUTES = (Public, ); }; }; 84CFF5001AC3C69700CEA6C8 /* RSCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84CFF4F41AC3C69700CEA6C8 /* RSCore.framework */; }; @@ -187,6 +191,8 @@ 849BF8B91C9130150071D1DA /* DiskSaver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = DiskSaver.swift; path = RSCore/DiskSaver.swift; sourceTree = ""; }; 84A835891D4EC7B80004C598 /* PlistProviderProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PlistProviderProtocol.swift; path = RSCore/PlistProviderProtocol.swift; sourceTree = ""; }; 84B890551C59CF1600D8BF23 /* NSString+ExtrasTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+ExtrasTests.m"; sourceTree = ""; }; + 84B99C931FAE64D400ECDEDB /* DisplayNameProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = DisplayNameProvider.swift; path = RSCore/DisplayNameProvider.swift; sourceTree = ""; }; + 84B99C991FAE650100ECDEDB /* OPMLRepresentable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = OPMLRepresentable.swift; path = RSCore/OPMLRepresentable.swift; sourceTree = ""; }; 84BB45421D6909C700B48537 /* NSMutableDictionary-Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSMutableDictionary-Extensions.swift"; sourceTree = ""; }; 84CFF4F41AC3C69700CEA6C8 /* RSCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = RSCore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 84CFF4F81AC3C69700CEA6C8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = RSCore/Info.plist; sourceTree = ""; }; @@ -306,6 +312,8 @@ 844C915A1B65753E0051FC1B /* RSPlist.m */, 8453F7DC1BDF337800B1C8ED /* RSMacroProcessor.h */, 8453F7DD1BDF337800B1C8ED /* RSMacroProcessor.m */, + 84B99C931FAE64D400ECDEDB /* DisplayNameProvider.swift */, + 84B99C991FAE650100ECDEDB /* OPMLRepresentable.swift */, 849BF8B91C9130150071D1DA /* DiskSaver.swift */, 84A835891D4EC7B80004C598 /* PlistProviderProtocol.swift */, 842E45CB1ED623C7000A8B52 /* UniqueIdentifier.swift */, @@ -640,6 +648,7 @@ 842DD7D01E14995C00E061EB /* RSMacroProcessor.m in Sources */, 842DD7E51E14996300E061EB /* NSMutableDictionary+RSCore.m in Sources */, 842DD7F41E14996B00E061EB /* Set+Extensions.swift in Sources */, + 84B99C9B1FAE650100ECDEDB /* OPMLRepresentable.swift in Sources */, 842DD7D71E14996300E061EB /* NSArray+RSCore.m in Sources */, 842DD7CC1E14995C00E061EB /* RSBinaryCache.m in Sources */, 842DD7F31E14996B00E061EB /* NSMutableDictionary-Extensions.swift in Sources */, @@ -649,6 +658,7 @@ 842DD7E11E14996300E061EB /* NSFileManager+RSCore.m in Sources */, 842DD7C61E14995C00E061EB /* RSBlocks.m in Sources */, 842DD7DD1E14996300E061EB /* NSDate+RSCore.m in Sources */, + 84B99C951FAE64D500ECDEDB /* DisplayNameProvider.swift in Sources */, 842DD7E91E14996300E061EB /* NSNotificationCenter+RSCore.m in Sources */, 842DD7E71E14996300E061EB /* NSMutableSet+RSCore.m in Sources */, 842DD7E31E14996300E061EB /* NSMutableArray+RSCore.m in Sources */, @@ -700,12 +710,14 @@ 84CFF56A1AC3D1B000CEA6C8 /* RSScaling.m in Sources */, 84FEB4AC1D19D7F4004727E5 /* Date+Extensions.swift in Sources */, 8461387F1DB3F5BE00048B83 /* RSToolbarItem.swift in Sources */, + 84B99C941FAE64D500ECDEDB /* DisplayNameProvider.swift in Sources */, 84BB45431D6909C700B48537 /* NSMutableDictionary-Extensions.swift in Sources */, 845DE0F41B80477100D1571B /* NSSet+RSCore.m in Sources */, 842635591D7FA24800196285 /* NSOutlineView+Extensions.swift in Sources */, 844C915C1B65753E0051FC1B /* RSPlist.m in Sources */, 84CFF5231AC3C89D00CEA6C8 /* NSObject+RSCore.m in Sources */, 8414CBA71C95F2EA00333C12 /* Set+Extensions.swift in Sources */, + 84B99C9A1FAE650100ECDEDB /* OPMLRepresentable.swift in Sources */, 84E34DA61F9FA1070077082F /* UndoableCommand.swift in Sources */, 844F91D61D90D86100820C48 /* RSTransparentContainerView.m in Sources */, 84CFF56E1AC3D20A00CEA6C8 /* NSImage+RSCore.m in Sources */, diff --git a/Frameworks/Data/DisplayNameProvider.swift b/Frameworks/RSCore/RSCore/DisplayNameProvider.swift similarity index 100% rename from Frameworks/Data/DisplayNameProvider.swift rename to Frameworks/RSCore/RSCore/DisplayNameProvider.swift diff --git a/Frameworks/Data/OPMLRepresentable.swift b/Frameworks/RSCore/RSCore/OPMLRepresentable.swift similarity index 100% rename from Frameworks/Data/OPMLRepresentable.swift rename to Frameworks/RSCore/RSCore/OPMLRepresentable.swift