Read feed directory data from disk.
This commit is contained in:
parent
0960477be6
commit
44461af07a
|
@ -79,6 +79,9 @@
|
||||||
84B06FFE1ED3818D00F0B54B /* RSTree.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 84B06FFA1ED3818000F0B54B /* RSTree.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
84B06FFE1ED3818D00F0B54B /* RSTree.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 84B06FFA1ED3818000F0B54B /* RSTree.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||||
84B0700A1ED3822600F0B54B /* RSTextDrawing.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84B070071ED3821900F0B54B /* RSTextDrawing.framework */; };
|
84B0700A1ED3822600F0B54B /* RSTextDrawing.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84B070071ED3821900F0B54B /* RSTextDrawing.framework */; };
|
||||||
84B0700B1ED3822600F0B54B /* RSTextDrawing.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 84B070071ED3821900F0B54B /* RSTextDrawing.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
84B0700B1ED3822600F0B54B /* RSTextDrawing.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 84B070071ED3821900F0B54B /* RSTextDrawing.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||||
|
84B99C671FAE35E600ECDEDB /* FeedListTreeControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B99C661FAE35E600ECDEDB /* FeedListTreeControllerDelegate.swift */; };
|
||||||
|
84B99C691FAE36B800ECDEDB /* FeedListFolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B99C681FAE36B800ECDEDB /* FeedListFolder.swift */; };
|
||||||
|
84B99C6B1FAE370B00ECDEDB /* FeedListFeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B99C6A1FAE370B00ECDEDB /* FeedListFeed.swift */; };
|
||||||
84BB4B771F11753300858766 /* Data.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84BB4B681F1174D400858766 /* Data.framework */; };
|
84BB4B771F11753300858766 /* Data.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84BB4B681F1174D400858766 /* Data.framework */; };
|
||||||
84BB4B781F11753300858766 /* Data.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 84BB4B681F1174D400858766 /* Data.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
84BB4B781F11753300858766 /* Data.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 84BB4B681F1174D400858766 /* Data.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||||
84DAEE301F86CAFE0058304B /* OPMLImporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84DAEE2F1F86CAFE0058304B /* OPMLImporter.swift */; };
|
84DAEE301F86CAFE0058304B /* OPMLImporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84DAEE2F1F86CAFE0058304B /* OPMLImporter.swift */; };
|
||||||
|
@ -446,6 +449,9 @@
|
||||||
84B06FE01ED3803200F0B54B /* RSFeedFinder.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RSFeedFinder.xcodeproj; path = Frameworks/RSFeedFinder/RSFeedFinder.xcodeproj; sourceTree = "<group>"; };
|
84B06FE01ED3803200F0B54B /* RSFeedFinder.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RSFeedFinder.xcodeproj; path = Frameworks/RSFeedFinder/RSFeedFinder.xcodeproj; sourceTree = "<group>"; };
|
||||||
84B06FF41ED3818000F0B54B /* RSTree.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RSTree.xcodeproj; path = Frameworks/RSTree/RSTree.xcodeproj; sourceTree = "<group>"; };
|
84B06FF41ED3818000F0B54B /* RSTree.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RSTree.xcodeproj; path = Frameworks/RSTree/RSTree.xcodeproj; sourceTree = "<group>"; };
|
||||||
84B070011ED3821800F0B54B /* RSTextDrawing.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RSTextDrawing.xcodeproj; path = Frameworks/RSTextDrawing/RSTextDrawing.xcodeproj; sourceTree = "<group>"; };
|
84B070011ED3821800F0B54B /* RSTextDrawing.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RSTextDrawing.xcodeproj; path = Frameworks/RSTextDrawing/RSTextDrawing.xcodeproj; sourceTree = "<group>"; };
|
||||||
|
84B99C661FAE35E600ECDEDB /* FeedListTreeControllerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedListTreeControllerDelegate.swift; sourceTree = "<group>"; };
|
||||||
|
84B99C681FAE36B800ECDEDB /* FeedListFolder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedListFolder.swift; sourceTree = "<group>"; };
|
||||||
|
84B99C6A1FAE370B00ECDEDB /* FeedListFeed.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedListFeed.swift; sourceTree = "<group>"; };
|
||||||
84BB4B611F1174D400858766 /* Data.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Data.xcodeproj; path = Frameworks/Data/Data.xcodeproj; sourceTree = "<group>"; };
|
84BB4B611F1174D400858766 /* Data.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Data.xcodeproj; path = Frameworks/Data/Data.xcodeproj; sourceTree = "<group>"; };
|
||||||
84DAEE2F1F86CAFE0058304B /* OPMLImporter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OPMLImporter.swift; sourceTree = "<group>"; };
|
84DAEE2F1F86CAFE0058304B /* OPMLImporter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OPMLImporter.swift; sourceTree = "<group>"; };
|
||||||
84DAEE311F870B390058304B /* DockBadge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = DockBadge.swift; path = Evergreen/DockBadge.swift; sourceTree = "<group>"; };
|
84DAEE311F870B390058304B /* DockBadge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = DockBadge.swift; path = Evergreen/DockBadge.swift; sourceTree = "<group>"; };
|
||||||
|
@ -644,6 +650,9 @@
|
||||||
84F204CD1FAACB660076E152 /* FeedListViewController.swift */,
|
84F204CD1FAACB660076E152 /* FeedListViewController.swift */,
|
||||||
84F204DD1FAACB8B0076E152 /* FeedListTimelineViewController.swift */,
|
84F204DD1FAACB8B0076E152 /* FeedListTimelineViewController.swift */,
|
||||||
84513F8F1FAA63950023A1A9 /* FeedListControlsView.swift */,
|
84513F8F1FAA63950023A1A9 /* FeedListControlsView.swift */,
|
||||||
|
84B99C661FAE35E600ECDEDB /* FeedListTreeControllerDelegate.swift */,
|
||||||
|
84B99C681FAE36B800ECDEDB /* FeedListFolder.swift */,
|
||||||
|
84B99C6A1FAE370B00ECDEDB /* FeedListFeed.swift */,
|
||||||
84E95CF61FABB3C800552D99 /* FeedList.plist */,
|
84E95CF61FABB3C800552D99 /* FeedList.plist */,
|
||||||
);
|
);
|
||||||
name = "Feed List";
|
name = "Feed List";
|
||||||
|
@ -1224,11 +1233,14 @@
|
||||||
849A976E1ED9EBC8007D329B /* TimelineViewController.swift in Sources */,
|
849A976E1ED9EBC8007D329B /* TimelineViewController.swift in Sources */,
|
||||||
849A978D1ED9EE4D007D329B /* FeedListWindowController.swift in Sources */,
|
849A978D1ED9EE4D007D329B /* FeedListWindowController.swift in Sources */,
|
||||||
849A97771ED9EC04007D329B /* TimelineCellData.swift in Sources */,
|
849A97771ED9EC04007D329B /* TimelineCellData.swift in Sources */,
|
||||||
|
84B99C6B1FAE370B00ECDEDB /* FeedListFeed.swift in Sources */,
|
||||||
849A97781ED9EC04007D329B /* TimelineCellLayout.swift in Sources */,
|
849A97781ED9EC04007D329B /* TimelineCellLayout.swift in Sources */,
|
||||||
849A976C1ED9EBC8007D329B /* TimelineTableRowView.swift in Sources */,
|
849A976C1ED9EBC8007D329B /* TimelineTableRowView.swift in Sources */,
|
||||||
849A977B1ED9EC04007D329B /* UnreadIndicatorView.swift in Sources */,
|
849A977B1ED9EC04007D329B /* UnreadIndicatorView.swift in Sources */,
|
||||||
849A97541ED9EAC0007D329B /* AddFeedWindowController.swift in Sources */,
|
849A97541ED9EAC0007D329B /* AddFeedWindowController.swift in Sources */,
|
||||||
849A976D1ED9EBC8007D329B /* TimelineTableView.swift in Sources */,
|
849A976D1ED9EBC8007D329B /* TimelineTableView.swift in Sources */,
|
||||||
|
84B99C671FAE35E600ECDEDB /* FeedListTreeControllerDelegate.swift in Sources */,
|
||||||
|
84B99C691FAE36B800ECDEDB /* FeedListFolder.swift in Sources */,
|
||||||
84F204DE1FAACB8B0076E152 /* FeedListTimelineViewController.swift in Sources */,
|
84F204DE1FAACB8B0076E152 /* FeedListTimelineViewController.swift in Sources */,
|
||||||
849A97A31ED9F180007D329B /* FolderTreeControllerDelegate.swift in Sources */,
|
849A97A31ED9F180007D329B /* FolderTreeControllerDelegate.swift in Sources */,
|
||||||
849A97851ED9ECCD007D329B /* PreferencesWindowController.swift in Sources */,
|
849A97851ED9ECCD007D329B /* PreferencesWindowController.swift in Sources */,
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<key>Mac and iOS</key>
|
<key>Mac and iOS</key>
|
||||||
<array>
|
<array>
|
||||||
<dict>
|
<dict>
|
||||||
<key>editedName</key>
|
<key>name</key>
|
||||||
<string>TidBITS</string>
|
<string>TidBITS</string>
|
||||||
<key>homePageURL</key>
|
<key>homePageURL</key>
|
||||||
<string>http://tidbits.org/</string>
|
<string>http://tidbits.org/</string>
|
||||||
|
@ -13,7 +13,7 @@
|
||||||
<string>http://tidbits.org/feeds/tidbits_blurb.rss</string>
|
<string>http://tidbits.org/feeds/tidbits_blurb.rss</string>
|
||||||
</dict>
|
</dict>
|
||||||
<dict>
|
<dict>
|
||||||
<key>editedName</key>
|
<key>name</key>
|
||||||
<string>Michael Tsai</string>
|
<string>Michael Tsai</string>
|
||||||
<key>homePageURL</key>
|
<key>homePageURL</key>
|
||||||
<string>https://mjtsai.com/blog/</string>
|
<string>https://mjtsai.com/blog/</string>
|
||||||
|
@ -24,7 +24,7 @@
|
||||||
<key>Blogs</key>
|
<key>Blogs</key>
|
||||||
<array>
|
<array>
|
||||||
<dict>
|
<dict>
|
||||||
<key>editedName</key>
|
<key>name</key>
|
||||||
<string>Jason Kottke</string>
|
<string>Jason Kottke</string>
|
||||||
<key>homePageURL</key>
|
<key>homePageURL</key>
|
||||||
<string>https://kottke.org/</string>
|
<string>https://kottke.org/</string>
|
||||||
|
@ -35,7 +35,7 @@
|
||||||
<key>News</key>
|
<key>News</key>
|
||||||
<array>
|
<array>
|
||||||
<dict>
|
<dict>
|
||||||
<key>editedName</key>
|
<key>name</key>
|
||||||
<string>Talking Points Memo</string>
|
<string>Talking Points Memo</string>
|
||||||
<key>homePageURL</key>
|
<key>homePageURL</key>
|
||||||
<string>https://talkingpointsmemo.com/</string>
|
<string>https://talkingpointsmemo.com/</string>
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
//
|
||||||
|
// FeedListFeed.swift
|
||||||
|
// Evergreen
|
||||||
|
//
|
||||||
|
// Created by Brent Simmons on 11/4/17.
|
||||||
|
// Copyright © 2017 Ranchero Software. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
final class FeedListFeed: Hashable {
|
||||||
|
|
||||||
|
let name: String
|
||||||
|
let url: String
|
||||||
|
let homePageURL: String
|
||||||
|
let hashValue: Int
|
||||||
|
|
||||||
|
init(name: String, url: String, homePageURL: String) {
|
||||||
|
|
||||||
|
self.name = name
|
||||||
|
self.url = url
|
||||||
|
self.homePageURL = homePageURL
|
||||||
|
self.hashValue = url.hashValue
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct Key {
|
||||||
|
static let name = "name"
|
||||||
|
static let editedName = "editedName" // Used in DefaultFeeds.plist
|
||||||
|
static let url = "url"
|
||||||
|
static let homePageURL = "homePageURL"
|
||||||
|
}
|
||||||
|
|
||||||
|
convenience init(dictionary: [String: String]) {
|
||||||
|
|
||||||
|
let name = (dictionary[Key.name] ?? dictionary[Key.editedName])!
|
||||||
|
let url = dictionary[Key.url]!
|
||||||
|
let homePageURL = dictionary[Key.homePageURL]!
|
||||||
|
|
||||||
|
self.init(name: name, url: url, homePageURL: homePageURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func ==(lhs: FeedListFeed, rhs: FeedListFeed) -> Bool {
|
||||||
|
|
||||||
|
return lhs.hashValue == rhs.hashValue && lhs.url == rhs.url && lhs.name == rhs.name && lhs.homePageURL == rhs.homePageURL
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
//
|
||||||
|
// FeedListFolder.swift
|
||||||
|
// Evergreen
|
||||||
|
//
|
||||||
|
// Created by Brent Simmons on 11/4/17.
|
||||||
|
// Copyright © 2017 Ranchero Software. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
final class FeedListFolder: Hashable {
|
||||||
|
|
||||||
|
let name: String
|
||||||
|
let feeds: Set<FeedListFeed>
|
||||||
|
let hashValue: Int
|
||||||
|
|
||||||
|
init(name: String, feeds: Set<FeedListFeed>) {
|
||||||
|
|
||||||
|
self.name = name
|
||||||
|
self.feeds = feeds
|
||||||
|
self.hashValue = name.hashValue
|
||||||
|
}
|
||||||
|
|
||||||
|
static func ==(lhs: FeedListFolder, rhs: FeedListFolder) -> Bool {
|
||||||
|
|
||||||
|
return lhs === rhs
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,93 @@
|
||||||
|
//
|
||||||
|
// FeedListTreeControllerDelegate.swift
|
||||||
|
// Evergreen
|
||||||
|
//
|
||||||
|
// Created by Brent Simmons on 11/4/17.
|
||||||
|
// Copyright © 2017 Ranchero Software. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import RSTree
|
||||||
|
|
||||||
|
// 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.)
|
||||||
|
// 2. Default feeds for new users — see DefaultFeeds.plist.
|
||||||
|
// 3. FeedList.plist — the main directory. Its top level is all folders. There are no sub-folders.
|
||||||
|
// It’s okay if there’s overlap: a feed may appear in multiple places.
|
||||||
|
// If there’s any problem with the data (wrong types), this will crash. By design.
|
||||||
|
|
||||||
|
final class FeedListTreeControllerDelegate: TreeControllerDelegate {
|
||||||
|
|
||||||
|
let topLevelFeeds: Set<FeedListFeed>
|
||||||
|
let defaultFeeds: Set<FeedListFeed>
|
||||||
|
let folders: Set<FeedListFolder>
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
|
func treeController(treeController: TreeController, childNodesFor node: Node) -> [Node]? {
|
||||||
|
|
||||||
|
// if node.isRoot {
|
||||||
|
// return childNodesForRootNode(node)
|
||||||
|
// }
|
||||||
|
// if node.representedObject is FeedListFolder {
|
||||||
|
// return childNodesForFolderNode(node)
|
||||||
|
// }
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//private extension FeedListTreeControllerDelegate {
|
||||||
|
//
|
||||||
|
// func childNodesForRootNode(_ rootNode: Node) -> [Node]? {
|
||||||
|
//
|
||||||
|
// return childNodesForContainerNode(rootNode, AccountManager.shared.localAccount.children)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
//}
|
||||||
|
|
||||||
|
// MARK: - Loading from Disk
|
||||||
|
|
||||||
|
private struct FeedListReader {
|
||||||
|
|
||||||
|
static func folders() -> Set<FeedListFolder> {
|
||||||
|
|
||||||
|
return Set(foldersDictionary().map { (arg: (key: String, value: [[String : String]])) -> FeedListFolder in
|
||||||
|
|
||||||
|
let (name, feedDictionaries) = arg
|
||||||
|
return FeedListFolder(name: name, feeds: feeds(with: feedDictionaries))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
static func defaultFeeds() -> Set<FeedListFeed> {
|
||||||
|
|
||||||
|
return feeds(with: defaultFeedDictionaries())
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func defaultFeedDictionaries() -> [[String: String]] {
|
||||||
|
|
||||||
|
let f = Bundle.main.path(forResource: "DefaultFeeds", ofType: "plist")!
|
||||||
|
return NSArray(contentsOfFile: f)! as! [[String: String]]
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func foldersDictionary() -> [String: [[String: String]]] {
|
||||||
|
|
||||||
|
let f = Bundle.main.path(forResource: "FeedList", ofType: "plist")!
|
||||||
|
return NSDictionary(contentsOfFile: f)! as! [String: [[String: String]]]
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func feeds(with dictionaries: [[String: String]]) -> Set<FeedListFeed> {
|
||||||
|
|
||||||
|
return Set(dictionaries.map { FeedListFeed(dictionary: $0) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -7,8 +7,42 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import Cocoa
|
import Cocoa
|
||||||
|
import RSTree
|
||||||
|
|
||||||
final class FeedListViewController: NSViewController {
|
final class FeedListViewController: NSViewController {
|
||||||
|
|
||||||
|
@IBOutlet var outlineView: NSOutlineView!
|
||||||
|
private let treeControllerDelegate = FeedListTreeControllerDelegate()
|
||||||
|
lazy var treeController: TreeController = {
|
||||||
|
TreeController(delegate: treeControllerDelegate)
|
||||||
|
}()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - NSOutlineViewDataSource
|
||||||
|
|
||||||
|
extension FeedListViewController: NSOutlineViewDataSource {
|
||||||
|
|
||||||
|
func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int {
|
||||||
|
|
||||||
|
return nodeForItem(item as AnyObject?).numberOfChildNodes
|
||||||
|
}
|
||||||
|
|
||||||
|
func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any {
|
||||||
|
|
||||||
|
return nodeForItem(item as AnyObject?).childNodes![index]
|
||||||
|
}
|
||||||
|
|
||||||
|
func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool {
|
||||||
|
|
||||||
|
return nodeForItem(item as AnyObject?).canHaveChildNodes
|
||||||
|
}
|
||||||
|
|
||||||
|
private func nodeForItem(_ item: AnyObject?) -> Node {
|
||||||
|
|
||||||
|
if item == nil {
|
||||||
|
return treeController.rootNode
|
||||||
|
}
|
||||||
|
return item as! Node
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue