Renamed Feed and related classes/instances to WebFeed
This commit is contained in:
parent
f5cd5d7067
commit
06bd5b3a6f
@ -29,7 +29,7 @@ public extension Notification.Name {
|
||||
static let AccountDidDownloadArticles = Notification.Name(rawValue: "AccountDidDownloadArticles")
|
||||
static let AccountStateDidChange = Notification.Name(rawValue: "AccountStateDidChange")
|
||||
static let StatusesDidChange = Notification.Name(rawValue: "StatusesDidChange")
|
||||
static let FeedMetadataDidChange = Notification.Name(rawValue: "FeedMetadataDidChange")
|
||||
static let WebFeedMetadataDidChange = Notification.Name(rawValue: "WebFeedMetadataDidChange")
|
||||
}
|
||||
|
||||
public enum AccountType: Int {
|
||||
@ -48,7 +48,7 @@ public enum FetchType {
|
||||
case unread
|
||||
case today
|
||||
case unreadForFolder(Folder)
|
||||
case feed(Feed)
|
||||
case webFeed(WebFeed)
|
||||
case articleIDs(Set<String>)
|
||||
case search(String)
|
||||
case searchWithArticleIDs(String, Set<String>)
|
||||
@ -62,7 +62,7 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
public static let updatedArticles = "updatedArticles" // AccountDidDownloadArticles
|
||||
public static let statuses = "statuses" // StatusesDidChange
|
||||
public static let articles = "articles" // StatusesDidChange
|
||||
public static let feeds = "feeds" // AccountDidDownloadArticles, StatusesDidChange
|
||||
public static let webFeeds = "webFeeds" // AccountDidDownloadArticles, StatusesDidChange
|
||||
}
|
||||
|
||||
public static let defaultLocalAccountName: String = {
|
||||
@ -126,15 +126,15 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
}
|
||||
}
|
||||
|
||||
public var topLevelFeeds = Set<Feed>()
|
||||
public var topLevelWebFeeds = Set<WebFeed>()
|
||||
public var folders: Set<Folder>? = Set<Folder>()
|
||||
private var feedDictionaryNeedsUpdate = true
|
||||
private var _idToFeedDictionary = [String: Feed]()
|
||||
var idToFeedDictionary: [String: Feed] {
|
||||
if feedDictionaryNeedsUpdate {
|
||||
rebuildFeedDictionaries()
|
||||
private var webFeedDictionaryNeedsUpdate = true
|
||||
private var _idToWebFeedDictionary = [String: WebFeed]()
|
||||
var idToWebFeedDictionary: [String: WebFeed] {
|
||||
if webFeedDictionaryNeedsUpdate {
|
||||
rebuildWebFeedDictionaries()
|
||||
}
|
||||
return _idToFeedDictionary
|
||||
return _idToWebFeedDictionary
|
||||
}
|
||||
|
||||
var username: String? {
|
||||
@ -169,8 +169,8 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
|
||||
private var unreadCounts = [String: Int]() // [feedID: Int]
|
||||
|
||||
private var _flattenedFeeds = Set<Feed>()
|
||||
private var flattenedFeedsNeedUpdate = true
|
||||
private var _flattenedWebFeeds = Set<WebFeed>()
|
||||
private var flattenedWebFeedsNeedUpdate = true
|
||||
|
||||
private lazy var opmlFile = OPMLFile(filename: (dataFolder as NSString).appendingPathComponent("Subscriptions.opml"), account: self)
|
||||
private lazy var metadataFile = AccountMetadataFile(filename: (dataFolder as NSString).appendingPathComponent("Settings.plist"), account: self)
|
||||
@ -180,9 +180,9 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
}
|
||||
}
|
||||
|
||||
private lazy var feedMetadataFile = FeedMetadataFile(filename: (dataFolder as NSString).appendingPathComponent("FeedMetadata.plist"), account: self)
|
||||
typealias FeedMetadataDictionary = [String: FeedMetadata]
|
||||
var feedMetadata = FeedMetadataDictionary()
|
||||
private lazy var webFeedMetadataFile = WebFeedMetadataFile(filename: (dataFolder as NSString).appendingPathComponent("FeedMetadata.plist"), account: self)
|
||||
typealias WebFeedMetadataDictionary = [String: WebFeedMetadata]
|
||||
var webFeedMetadata = WebFeedMetadataDictionary()
|
||||
|
||||
var startingUp = true
|
||||
|
||||
@ -259,11 +259,11 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(childrenDidChange(_:)), name: .ChildrenDidChange, object: nil)
|
||||
|
||||
metadataFile.load()
|
||||
feedMetadataFile.load()
|
||||
webFeedMetadataFile.load()
|
||||
opmlFile.load()
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.database.cleanupDatabaseAtStartup(subscribedToFeedIDs: self.flattenedFeeds().feedIDs())
|
||||
self.database.cleanupDatabaseAtStartup(subscribedToWebFeedIDs: self.flattenedWebFeeds().webFeedIDs())
|
||||
self.fetchAllUnreadCounts()
|
||||
}
|
||||
|
||||
@ -395,17 +395,17 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
|
||||
public func save() {
|
||||
metadataFile.save()
|
||||
feedMetadataFile.save()
|
||||
webFeedMetadataFile.save()
|
||||
opmlFile.save()
|
||||
}
|
||||
|
||||
func loadOPMLItems(_ items: [RSOPMLItem], parentFolder: Folder?) {
|
||||
var feedsToAdd = Set<Feed>()
|
||||
var feedsToAdd = Set<WebFeed>()
|
||||
|
||||
items.forEach { (item) in
|
||||
|
||||
if let feedSpecifier = item.feedSpecifier {
|
||||
let feed = newFeed(with: feedSpecifier)
|
||||
let feed = newWebFeed(with: feedSpecifier)
|
||||
feedsToAdd.insert(feed)
|
||||
return
|
||||
}
|
||||
@ -428,22 +428,22 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
|
||||
if let parentFolder = parentFolder {
|
||||
for feed in feedsToAdd {
|
||||
parentFolder.addFeed(feed)
|
||||
parentFolder.addWebFeed(feed)
|
||||
}
|
||||
} else {
|
||||
for feed in feedsToAdd {
|
||||
addFeed(feed)
|
||||
addWebFeed(feed)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public func resetFeedMetadataAndUnreadCounts() {
|
||||
for feed in flattenedFeeds() {
|
||||
feed.metadata = feedMetadata(feedURL: feed.url, feedID: feed.feedID)
|
||||
public func resetWebFeedMetadataAndUnreadCounts() {
|
||||
for feed in flattenedWebFeeds() {
|
||||
feed.metadata = webFeedMetadata(feedURL: feed.url, webFeedID: feed.webFeedID)
|
||||
}
|
||||
fetchAllUnreadCounts()
|
||||
NotificationCenter.default.post(name: .FeedMetadataDidChange, object: self, userInfo: nil)
|
||||
NotificationCenter.default.post(name: .WebFeedMetadataDidChange, object: self, userInfo: nil)
|
||||
}
|
||||
|
||||
public func markArticles(_ articles: Set<Article>, statusKey: ArticleStatus.Key, flag: Bool) -> Set<Article>? {
|
||||
@ -484,10 +484,10 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
return folders?.first(where: { $0.nameForDisplay == displayName })
|
||||
}
|
||||
|
||||
func newFeed(with opmlFeedSpecifier: RSOPMLFeedSpecifier) -> Feed {
|
||||
func newWebFeed(with opmlFeedSpecifier: RSOPMLFeedSpecifier) -> WebFeed {
|
||||
let feedURL = opmlFeedSpecifier.feedURL
|
||||
let metadata = feedMetadata(feedURL: feedURL, feedID: feedURL)
|
||||
let feed = Feed(account: self, url: opmlFeedSpecifier.feedURL, metadata: metadata)
|
||||
let metadata = webFeedMetadata(feedURL: feedURL, webFeedID: feedURL)
|
||||
let feed = WebFeed(account: self, url: opmlFeedSpecifier.feedURL, metadata: metadata)
|
||||
if let feedTitle = opmlFeedSpecifier.title {
|
||||
if feed.name == nil {
|
||||
feed.name = feedTitle
|
||||
@ -496,37 +496,37 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
return feed
|
||||
}
|
||||
|
||||
public func addFeed(_ feed: Feed, to container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
delegate.addFeed(for: self, with: feed, to: container, completion: completion)
|
||||
public func addWebFeed(_ feed: WebFeed, to container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
delegate.addWebFeed(for: self, with: feed, to: container, completion: completion)
|
||||
}
|
||||
|
||||
public func createFeed(url: String, name: String?, container: Container, completion: @escaping (Result<Feed, Error>) -> Void) {
|
||||
delegate.createFeed(for: self, url: url, name: name, container: container, completion: completion)
|
||||
public func createWebFeed(url: String, name: String?, container: Container, completion: @escaping (Result<WebFeed, Error>) -> Void) {
|
||||
delegate.createWebFeed(for: self, url: url, name: name, container: container, completion: completion)
|
||||
}
|
||||
|
||||
func createFeed(with name: String?, url: String, feedID: String, homePageURL: String?) -> Feed {
|
||||
let metadata = feedMetadata(feedURL: url, feedID: feedID)
|
||||
let feed = Feed(account: self, url: url, metadata: metadata)
|
||||
func createWebFeed(with name: String?, url: String, webFeedID: String, homePageURL: String?) -> WebFeed {
|
||||
let metadata = webFeedMetadata(feedURL: url, webFeedID: webFeedID)
|
||||
let feed = WebFeed(account: self, url: url, metadata: metadata)
|
||||
feed.name = name
|
||||
feed.homePageURL = homePageURL
|
||||
|
||||
return feed
|
||||
}
|
||||
|
||||
public func removeFeed(_ feed: Feed, from container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
delegate.removeFeed(for: self, with: feed, from: container, completion: completion)
|
||||
public func removeWebFeed(_ feed: WebFeed, from container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
delegate.removeWebFeed(for: self, with: feed, from: container, completion: completion)
|
||||
}
|
||||
|
||||
public func moveFeed(_ feed: Feed, from: Container, to: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
delegate.moveFeed(for: self, with: feed, from: from, to: to, completion: completion)
|
||||
public func moveWebFeed(_ feed: WebFeed, from: Container, to: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
delegate.moveWebFeed(for: self, with: feed, from: from, to: to, completion: completion)
|
||||
}
|
||||
|
||||
public func renameFeed(_ feed: Feed, to name: String, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
delegate.renameFeed(for: self, with: feed, to: name, completion: completion)
|
||||
public func renameWebFeed(_ feed: WebFeed, to name: String, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
delegate.renameWebFeed(for: self, with: feed, to: name, completion: completion)
|
||||
}
|
||||
|
||||
public func restoreFeed(_ feed: Feed, container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
delegate.restoreFeed(for: self, feed: feed, container: container, completion: completion)
|
||||
public func restoreWebFeed(_ feed: WebFeed, container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
delegate.restoreWebFeed(for: self, feed: feed, container: container, completion: completion)
|
||||
}
|
||||
|
||||
public func addFolder(_ name: String, completion: @escaping (Result<Folder, Error>) -> Void) {
|
||||
@ -545,8 +545,8 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
delegate.restoreFolder(for: self, folder: folder, completion: completion)
|
||||
}
|
||||
|
||||
func clearFeedMetadata(_ feed: Feed) {
|
||||
feedMetadata[feed.url] = nil
|
||||
func clearWebFeedMetadata(_ feed: WebFeed) {
|
||||
webFeedMetadata[feed.url] = nil
|
||||
}
|
||||
|
||||
func addFolder(_ folder: Folder) {
|
||||
@ -555,15 +555,15 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
structureDidChange()
|
||||
}
|
||||
|
||||
public func updateUnreadCounts(for feeds: Set<Feed>) {
|
||||
if feeds.isEmpty {
|
||||
public func updateUnreadCounts(for webFeeds: Set<WebFeed>) {
|
||||
if webFeeds.isEmpty {
|
||||
return
|
||||
}
|
||||
|
||||
database.fetchUnreadCounts(for: feeds.feedIDs()) { (unreadCountDictionary) in
|
||||
for feed in feeds {
|
||||
if let unreadCount = unreadCountDictionary[feed.feedID] {
|
||||
feed.unreadCount = unreadCount
|
||||
database.fetchUnreadCounts(for: webFeeds.webFeedIDs()) { (unreadCountDictionary) in
|
||||
for webFeed in webFeeds {
|
||||
if let unreadCount = unreadCountDictionary[webFeed.webFeedID] {
|
||||
webFeed.unreadCount = unreadCount
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -579,8 +579,8 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
return fetchTodayArticles()
|
||||
case .unreadForFolder(let folder):
|
||||
return fetchArticles(folder: folder)
|
||||
case .feed(let feed):
|
||||
return fetchArticles(feed: feed)
|
||||
case .webFeed(let webFeed):
|
||||
return fetchArticles(webFeed: webFeed)
|
||||
case .articleIDs(let articleIDs):
|
||||
return fetchArticles(articleIDs: articleIDs)
|
||||
case .search(let searchString):
|
||||
@ -600,8 +600,8 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
fetchTodayArticlesAsync(callback)
|
||||
case .unreadForFolder(let folder):
|
||||
fetchArticlesAsync(folder: folder, callback)
|
||||
case .feed(let feed):
|
||||
fetchArticlesAsync(feed: feed, callback)
|
||||
case .webFeed(let webFeed):
|
||||
fetchArticlesAsync(webFeed: webFeed, callback)
|
||||
case .articleIDs(let articleIDs):
|
||||
fetchArticlesAsync(articleIDs: articleIDs, callback)
|
||||
case .search(let searchString):
|
||||
@ -612,11 +612,11 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
}
|
||||
|
||||
public func fetchUnreadCountForToday(_ callback: @escaping (Int) -> Void) {
|
||||
database.fetchUnreadCountForToday(for: flattenedFeeds().feedIDs(), callback: callback)
|
||||
database.fetchUnreadCountForToday(for: flattenedWebFeeds().webFeedIDs(), callback: callback)
|
||||
}
|
||||
|
||||
public func fetchUnreadCountForStarredArticles(_ callback: @escaping (Int) -> Void) {
|
||||
database.fetchStarredAndUnreadCount(for: flattenedFeeds().feedIDs(), callback: callback)
|
||||
database.fetchStarredAndUnreadCount(for: flattenedWebFeeds().webFeedIDs(), callback: callback)
|
||||
}
|
||||
|
||||
public func fetchUnreadArticleIDs() -> Set<String> {
|
||||
@ -631,12 +631,12 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
return database.fetchArticleIDsForStatusesWithoutArticles()
|
||||
}
|
||||
|
||||
public func unreadCount(for feed: Feed) -> Int {
|
||||
return unreadCounts[feed.feedID] ?? 0
|
||||
public func unreadCount(for webFeed: WebFeed) -> Int {
|
||||
return unreadCounts[webFeed.webFeedID] ?? 0
|
||||
}
|
||||
|
||||
public func setUnreadCount(_ unreadCount: Int, for feed: Feed) {
|
||||
unreadCounts[feed.feedID] = unreadCount
|
||||
public func setUnreadCount(_ unreadCount: Int, for webFeed: WebFeed) {
|
||||
unreadCounts[webFeed.webFeedID] = unreadCount
|
||||
}
|
||||
|
||||
public func structureDidChange() {
|
||||
@ -645,36 +645,36 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
if !startingUp {
|
||||
opmlFile.markAsDirty()
|
||||
}
|
||||
flattenedFeedsNeedUpdate = true
|
||||
feedDictionaryNeedsUpdate = true
|
||||
flattenedWebFeedsNeedUpdate = true
|
||||
webFeedDictionaryNeedsUpdate = true
|
||||
}
|
||||
|
||||
func update(_ feed: Feed, with parsedFeed: ParsedFeed, _ completion: @escaping (() -> Void)) {
|
||||
func update(_ webFeed: WebFeed, with parsedFeed: ParsedFeed, _ completion: @escaping (() -> Void)) {
|
||||
// Used only by an On My Mac account.
|
||||
feed.takeSettings(from: parsedFeed)
|
||||
let feedIDsAndItems = [feed.feedID: parsedFeed.items]
|
||||
update(feedIDsAndItems: feedIDsAndItems, defaultRead: false, completion: completion)
|
||||
webFeed.takeSettings(from: parsedFeed)
|
||||
let webFeedIDsAndItems = [webFeed.webFeedID: parsedFeed.items]
|
||||
update(webFeedIDsAndItems: webFeedIDsAndItems, defaultRead: false, completion: completion)
|
||||
}
|
||||
|
||||
func update(feedIDsAndItems: [String: Set<ParsedItem>], defaultRead: Bool, completion: @escaping (() -> Void)) {
|
||||
func update(webFeedIDsAndItems: [String: Set<ParsedItem>], defaultRead: Bool, completion: @escaping (() -> Void)) {
|
||||
assert(Thread.isMainThread)
|
||||
guard !feedIDsAndItems.isEmpty else {
|
||||
guard !webFeedIDsAndItems.isEmpty else {
|
||||
completion()
|
||||
return
|
||||
}
|
||||
database.update(feedIDsAndItems: feedIDsAndItems, defaultRead: defaultRead) { (newArticles, updatedArticles) in
|
||||
database.update(webFeedIDsAndItems: webFeedIDsAndItems, defaultRead: defaultRead) { (newArticles, updatedArticles) in
|
||||
var userInfo = [String: Any]()
|
||||
let feeds = Set(feedIDsAndItems.compactMap { (key, _) -> Feed? in
|
||||
self.existingFeed(withFeedID: key)
|
||||
let webFeeds = Set(webFeedIDsAndItems.compactMap { (key, _) -> WebFeed? in
|
||||
self.existingWebFeed(withWebFeedID: key)
|
||||
})
|
||||
if let newArticles = newArticles, !newArticles.isEmpty {
|
||||
self.updateUnreadCounts(for: feeds)
|
||||
self.updateUnreadCounts(for: webFeeds)
|
||||
userInfo[UserInfoKey.newArticles] = newArticles
|
||||
}
|
||||
if let updatedArticles = updatedArticles, !updatedArticles.isEmpty {
|
||||
userInfo[UserInfoKey.updatedArticles] = updatedArticles
|
||||
}
|
||||
userInfo[UserInfoKey.feeds] = feeds
|
||||
userInfo[UserInfoKey.webFeeds] = webFeeds
|
||||
|
||||
completion()
|
||||
|
||||
@ -711,38 +711,38 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
|
||||
// MARK: - Container
|
||||
|
||||
public func flattenedFeeds() -> Set<Feed> {
|
||||
public func flattenedWebFeeds() -> Set<WebFeed> {
|
||||
assert(Thread.isMainThread)
|
||||
if flattenedFeedsNeedUpdate {
|
||||
updateFlattenedFeeds()
|
||||
if flattenedWebFeedsNeedUpdate {
|
||||
updateFlattenedWebFeeds()
|
||||
}
|
||||
return _flattenedFeeds
|
||||
return _flattenedWebFeeds
|
||||
}
|
||||
|
||||
public func removeFeed(_ feed: Feed) {
|
||||
topLevelFeeds.remove(feed)
|
||||
public func removeWebFeed(_ webFeed: WebFeed) {
|
||||
topLevelWebFeeds.remove(webFeed)
|
||||
structureDidChange()
|
||||
postChildrenDidChangeNotification()
|
||||
}
|
||||
|
||||
public func removeFeeds(_ feeds: Set<Feed>) {
|
||||
guard !feeds.isEmpty else {
|
||||
public func removeFeeds(_ webFeeds: Set<WebFeed>) {
|
||||
guard !webFeeds.isEmpty else {
|
||||
return
|
||||
}
|
||||
topLevelFeeds.subtract(feeds)
|
||||
topLevelWebFeeds.subtract(webFeeds)
|
||||
structureDidChange()
|
||||
postChildrenDidChangeNotification()
|
||||
}
|
||||
|
||||
public func addFeed(_ feed: Feed) {
|
||||
topLevelFeeds.insert(feed)
|
||||
public func addWebFeed(_ webFeed: WebFeed) {
|
||||
topLevelWebFeeds.insert(webFeed)
|
||||
structureDidChange()
|
||||
postChildrenDidChangeNotification()
|
||||
}
|
||||
|
||||
func addFeedIfNotInAnyFolder(_ feed: Feed) {
|
||||
if !flattenedFeeds().contains(feed) {
|
||||
addFeed(feed)
|
||||
func addFeedIfNotInAnyFolder(_ webFeed: WebFeed) {
|
||||
if !flattenedWebFeeds().contains(webFeed) {
|
||||
addWebFeed(webFeed)
|
||||
}
|
||||
}
|
||||
|
||||
@ -756,7 +756,7 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
|
||||
public func debugDropConditionalGetInfo() {
|
||||
#if DEBUG
|
||||
flattenedFeeds().forEach{ $0.debugDropConditionalGetInfo() }
|
||||
flattenedWebFeeds().forEach{ $0.debugDropConditionalGetInfo() }
|
||||
#endif
|
||||
}
|
||||
|
||||
@ -782,14 +782,14 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
}
|
||||
|
||||
@objc func unreadCountDidChange(_ note: Notification) {
|
||||
if let feed = note.object as? Feed, feed.account === self {
|
||||
if let feed = note.object as? WebFeed, feed.account === self {
|
||||
updateUnreadCount()
|
||||
}
|
||||
}
|
||||
|
||||
@objc func batchUpdateDidPerform(_ note: Notification) {
|
||||
flattenedFeedsNeedUpdate = true
|
||||
rebuildFeedDictionaries()
|
||||
flattenedWebFeedsNeedUpdate = true
|
||||
rebuildWebFeedDictionaries()
|
||||
updateUnreadCount()
|
||||
}
|
||||
|
||||
@ -835,11 +835,11 @@ extension Account: AccountMetadataDelegate {
|
||||
|
||||
// MARK: - FeedMetadataDelegate
|
||||
|
||||
extension Account: FeedMetadataDelegate {
|
||||
extension Account: WebFeedMetadataDelegate {
|
||||
|
||||
func valueDidChange(_ feedMetadata: FeedMetadata, key: FeedMetadata.CodingKeys) {
|
||||
feedMetadataFile.markAsDirty()
|
||||
guard let feed = existingFeed(withFeedID: feedMetadata.feedID) else {
|
||||
func valueDidChange(_ feedMetadata: WebFeedMetadata, key: WebFeedMetadata.CodingKeys) {
|
||||
webFeedMetadataFile.markAsDirty()
|
||||
guard let feed = existingWebFeed(withWebFeedID: feedMetadata.webFeedID) else {
|
||||
return
|
||||
}
|
||||
feed.postFeedSettingDidChangeNotification(key)
|
||||
@ -851,11 +851,11 @@ extension Account: FeedMetadataDelegate {
|
||||
private extension Account {
|
||||
|
||||
func fetchStarredArticles() -> Set<Article> {
|
||||
return database.fetchStarredArticles(flattenedFeeds().feedIDs())
|
||||
return database.fetchStarredArticles(flattenedWebFeeds().webFeedIDs())
|
||||
}
|
||||
|
||||
func fetchStarredArticlesAsync(_ callback: @escaping ArticleSetBlock) {
|
||||
database.fetchedStarredArticlesAsync(flattenedFeeds().feedIDs(), callback)
|
||||
database.fetchedStarredArticlesAsync(flattenedWebFeeds().webFeedIDs(), callback)
|
||||
}
|
||||
|
||||
func fetchUnreadArticles() -> Set<Article> {
|
||||
@ -867,11 +867,11 @@ private extension Account {
|
||||
}
|
||||
|
||||
func fetchTodayArticles() -> Set<Article> {
|
||||
return database.fetchTodayArticles(flattenedFeeds().feedIDs())
|
||||
return database.fetchTodayArticles(flattenedWebFeeds().webFeedIDs())
|
||||
}
|
||||
|
||||
func fetchTodayArticlesAsync(_ callback: @escaping ArticleSetBlock) {
|
||||
database.fetchTodayArticlesAsync(flattenedFeeds().feedIDs(), callback)
|
||||
database.fetchTodayArticlesAsync(flattenedWebFeeds().webFeedIDs(), callback)
|
||||
}
|
||||
|
||||
func fetchArticles(folder: Folder) -> Set<Article> {
|
||||
@ -882,21 +882,21 @@ private extension Account {
|
||||
fetchUnreadArticlesAsync(forContainer: folder, callback)
|
||||
}
|
||||
|
||||
func fetchArticles(feed: Feed) -> Set<Article> {
|
||||
let articles = database.fetchArticles(feed.feedID)
|
||||
validateUnreadCount(feed, articles)
|
||||
func fetchArticles(webFeed: WebFeed) -> Set<Article> {
|
||||
let articles = database.fetchArticles(webFeed.webFeedID)
|
||||
validateUnreadCount(webFeed, articles)
|
||||
return articles
|
||||
}
|
||||
|
||||
func fetchArticlesAsync(feed: Feed, _ callback: @escaping ArticleSetBlock) {
|
||||
database.fetchArticlesAsync(feed.feedID) { [weak self] (articles) in
|
||||
self?.validateUnreadCount(feed, articles)
|
||||
func fetchArticlesAsync(webFeed: WebFeed, _ callback: @escaping ArticleSetBlock) {
|
||||
database.fetchArticlesAsync(webFeed.webFeedID) { [weak self] (articles) in
|
||||
self?.validateUnreadCount(webFeed, articles)
|
||||
callback(articles)
|
||||
}
|
||||
}
|
||||
|
||||
func fetchArticlesMatching(_ searchString: String) -> Set<Article> {
|
||||
return database.fetchArticlesMatching(searchString, flattenedFeeds().feedIDs())
|
||||
return database.fetchArticlesMatching(searchString, flattenedWebFeeds().webFeedIDs())
|
||||
}
|
||||
|
||||
func fetchArticlesMatchingWithArticleIDs(_ searchString: String, _ articleIDs: Set<String>) -> Set<Article> {
|
||||
@ -904,7 +904,7 @@ private extension Account {
|
||||
}
|
||||
|
||||
func fetchArticlesMatchingAsync(_ searchString: String, _ callback: @escaping ArticleSetBlock) {
|
||||
database.fetchArticlesMatchingAsync(searchString, flattenedFeeds().feedIDs(), callback)
|
||||
database.fetchArticlesMatchingAsync(searchString, flattenedWebFeeds().webFeedIDs(), callback)
|
||||
}
|
||||
|
||||
func fetchArticlesMatchingWithArticleIDsAsync(_ searchString: String, _ articleIDs: Set<String>, _ callback: @escaping ArticleSetBlock) {
|
||||
@ -919,13 +919,13 @@ private extension Account {
|
||||
return database.fetchArticlesAsync(articleIDs: articleIDs, callback)
|
||||
}
|
||||
|
||||
func fetchUnreadArticles(feed: Feed) -> Set<Article> {
|
||||
let articles = database.fetchUnreadArticles(Set([feed.feedID]))
|
||||
validateUnreadCount(feed, articles)
|
||||
func fetchUnreadArticles(webFeed: WebFeed) -> Set<Article> {
|
||||
let articles = database.fetchUnreadArticles(Set([webFeed.webFeedID]))
|
||||
validateUnreadCount(webFeed, articles)
|
||||
return articles
|
||||
}
|
||||
|
||||
func fetchUnreadArticlesAsync(for feed: Feed, callback: @escaping (Set<Article>) -> Void) {
|
||||
func fetchUnreadArticlesAsync(for webFeed: WebFeed, callback: @escaping (Set<Article>) -> Void) {
|
||||
// database.fetchUnreadArticlesAsync(for: Set([feed.feedID])) { [weak self] (articles) in
|
||||
// self?.validateUnreadCount(feed, articles)
|
||||
// callback(articles)
|
||||
@ -934,48 +934,48 @@ private extension Account {
|
||||
|
||||
|
||||
func fetchUnreadArticles(forContainer container: Container) -> Set<Article> {
|
||||
let feeds = container.flattenedFeeds()
|
||||
let articles = database.fetchUnreadArticles(feeds.feedIDs())
|
||||
let feeds = container.flattenedWebFeeds()
|
||||
let articles = database.fetchUnreadArticles(feeds.webFeedIDs())
|
||||
validateUnreadCountsAfterFetchingUnreadArticles(feeds, articles)
|
||||
return articles
|
||||
}
|
||||
|
||||
func fetchUnreadArticlesAsync(forContainer container: Container, _ callback: @escaping ArticleSetBlock) {
|
||||
let feeds = container.flattenedFeeds()
|
||||
database.fetchUnreadArticlesAsync(feeds.feedIDs()) { [weak self] (articles) in
|
||||
self?.validateUnreadCountsAfterFetchingUnreadArticles(feeds, articles)
|
||||
let webFeeds = container.flattenedWebFeeds()
|
||||
database.fetchUnreadArticlesAsync(webFeeds.webFeedIDs()) { [weak self] (articles) in
|
||||
self?.validateUnreadCountsAfterFetchingUnreadArticles(webFeeds, articles)
|
||||
callback(articles)
|
||||
}
|
||||
}
|
||||
|
||||
func validateUnreadCountsAfterFetchingUnreadArticles(_ feeds: Set<Feed>, _ articles: Set<Article>) {
|
||||
func validateUnreadCountsAfterFetchingUnreadArticles(_ webFeeds: Set<WebFeed>, _ articles: Set<Article>) {
|
||||
// Validate unread counts. This was the site of a performance slowdown:
|
||||
// it was calling going through the entire list of articles once per feed:
|
||||
// feeds.forEach { validateUnreadCount($0, articles) }
|
||||
// Now we loop through articles exactly once. This makes a huge difference.
|
||||
|
||||
var unreadCountStorage = [String: Int]() // [FeedID: Int]
|
||||
var unreadCountStorage = [String: Int]() // [WebFeedID: Int]
|
||||
for article in articles where !article.status.read {
|
||||
unreadCountStorage[article.feedID, default: 0] += 1
|
||||
unreadCountStorage[article.webFeedID, default: 0] += 1
|
||||
}
|
||||
feeds.forEach { (feed) in
|
||||
let unreadCount = unreadCountStorage[feed.feedID, default: 0]
|
||||
feed.unreadCount = unreadCount
|
||||
webFeeds.forEach { (webFeed) in
|
||||
let unreadCount = unreadCountStorage[webFeed.webFeedID, default: 0]
|
||||
webFeed.unreadCount = unreadCount
|
||||
}
|
||||
}
|
||||
|
||||
func validateUnreadCount(_ feed: Feed, _ articles: Set<Article>) {
|
||||
func validateUnreadCount(_ webFeed: WebFeed, _ articles: Set<Article>) {
|
||||
// articles must contain all the unread articles for the feed.
|
||||
// The unread number should match the feed’s unread count.
|
||||
|
||||
let feedUnreadCount = articles.reduce(0) { (result, article) -> Int in
|
||||
if article.feed == feed && !article.status.read {
|
||||
if article.webFeed == webFeed && !article.status.read {
|
||||
return result + 1
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
feed.unreadCount = feedUnreadCount
|
||||
webFeed.unreadCount = feedUnreadCount
|
||||
}
|
||||
}
|
||||
|
||||
@ -983,37 +983,37 @@ private extension Account {
|
||||
|
||||
private extension Account {
|
||||
|
||||
func feedMetadata(feedURL: String, feedID: String) -> FeedMetadata {
|
||||
if let d = feedMetadata[feedURL] {
|
||||
func webFeedMetadata(feedURL: String, webFeedID: String) -> WebFeedMetadata {
|
||||
if let d = webFeedMetadata[feedURL] {
|
||||
assert(d.delegate === self)
|
||||
return d
|
||||
}
|
||||
let d = FeedMetadata(feedID: feedID)
|
||||
let d = WebFeedMetadata(webFeedID: webFeedID)
|
||||
d.delegate = self
|
||||
feedMetadata[feedURL] = d
|
||||
webFeedMetadata[feedURL] = d
|
||||
return d
|
||||
}
|
||||
|
||||
func updateFlattenedFeeds() {
|
||||
var feeds = Set<Feed>()
|
||||
feeds.formUnion(topLevelFeeds)
|
||||
func updateFlattenedWebFeeds() {
|
||||
var feeds = Set<WebFeed>()
|
||||
feeds.formUnion(topLevelWebFeeds)
|
||||
for folder in folders! {
|
||||
feeds.formUnion(folder.flattenedFeeds())
|
||||
feeds.formUnion(folder.flattenedWebFeeds())
|
||||
}
|
||||
|
||||
_flattenedFeeds = feeds
|
||||
flattenedFeedsNeedUpdate = false
|
||||
_flattenedWebFeeds = feeds
|
||||
flattenedWebFeedsNeedUpdate = false
|
||||
}
|
||||
|
||||
func rebuildFeedDictionaries() {
|
||||
var idDictionary = [String: Feed]()
|
||||
func rebuildWebFeedDictionaries() {
|
||||
var idDictionary = [String: WebFeed]()
|
||||
|
||||
flattenedFeeds().forEach { (feed) in
|
||||
idDictionary[feed.feedID] = feed
|
||||
flattenedWebFeeds().forEach { (feed) in
|
||||
idDictionary[feed.webFeedID] = feed
|
||||
}
|
||||
|
||||
_idToFeedDictionary = idDictionary
|
||||
feedDictionaryNeedsUpdate = false
|
||||
_idToWebFeedDictionary = idDictionary
|
||||
webFeedDictionaryNeedsUpdate = false
|
||||
}
|
||||
|
||||
func updateUnreadCount() {
|
||||
@ -1021,21 +1021,21 @@ private extension Account {
|
||||
return
|
||||
}
|
||||
var updatedUnreadCount = 0
|
||||
for feed in flattenedFeeds() {
|
||||
for feed in flattenedWebFeeds() {
|
||||
updatedUnreadCount += feed.unreadCount
|
||||
}
|
||||
unreadCount = updatedUnreadCount
|
||||
}
|
||||
|
||||
func noteStatusesForArticlesDidChange(_ articles: Set<Article>) {
|
||||
let feeds = Set(articles.compactMap { $0.feed })
|
||||
let feeds = Set(articles.compactMap { $0.webFeed })
|
||||
let statuses = Set(articles.map { $0.status })
|
||||
|
||||
// .UnreadCountDidChange notification will get sent to Folder and Account objects,
|
||||
// which will update their own unread counts.
|
||||
updateUnreadCounts(for: feeds)
|
||||
|
||||
NotificationCenter.default.post(name: .StatusesDidChange, object: self, userInfo: [UserInfoKey.statuses: statuses, UserInfoKey.articles: articles, UserInfoKey.feeds: feeds])
|
||||
NotificationCenter.default.post(name: .StatusesDidChange, object: self, userInfo: [UserInfoKey.statuses: statuses, UserInfoKey.articles: articles, UserInfoKey.webFeeds: feeds])
|
||||
}
|
||||
|
||||
func fetchAllUnreadCounts() {
|
||||
@ -1049,10 +1049,10 @@ private extension Account {
|
||||
return
|
||||
}
|
||||
|
||||
self.flattenedFeeds().forEach{ (feed) in
|
||||
self.flattenedWebFeeds().forEach{ (feed) in
|
||||
// When the unread count is zero, it won’t appear in unreadCountDictionary.
|
||||
|
||||
if let unreadCount = unreadCountDictionary[feed.feedID] {
|
||||
if let unreadCount = unreadCountDictionary[feed.webFeedID] {
|
||||
feed.unreadCount = unreadCount
|
||||
}
|
||||
else {
|
||||
@ -1070,8 +1070,8 @@ private extension Account {
|
||||
|
||||
extension Account {
|
||||
|
||||
public func existingFeed(withFeedID feedID: String) -> Feed? {
|
||||
return idToFeedDictionary[feedID]
|
||||
public func existingWebFeed(withWebFeedID webFeedID: String) -> WebFeed? {
|
||||
return idToWebFeedDictionary[webFeedID]
|
||||
}
|
||||
}
|
||||
|
||||
@ -1081,7 +1081,7 @@ extension Account: OPMLRepresentable {
|
||||
|
||||
public func OPMLString(indentLevel: Int, strictConformance: Bool) -> String {
|
||||
var s = ""
|
||||
for feed in topLevelFeeds {
|
||||
for feed in topLevelWebFeeds {
|
||||
s += feed.OPMLString(indentLevel: indentLevel + 1, strictConformance: strictConformance)
|
||||
}
|
||||
for folder in folders! {
|
||||
|
@ -11,7 +11,7 @@
|
||||
5107A09B227DE49500C7C3C5 /* TestAccountManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5107A09A227DE49500C7C3C5 /* TestAccountManager.swift */; };
|
||||
5107A09D227DE77700C7C3C5 /* TestTransport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5107A09C227DE77700C7C3C5 /* TestTransport.swift */; };
|
||||
510BD111232C3801002692E4 /* AccountMetadataFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510BD110232C3801002692E4 /* AccountMetadataFile.swift */; };
|
||||
510BD113232C3E9D002692E4 /* FeedMetadataFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510BD112232C3E9D002692E4 /* FeedMetadataFile.swift */; };
|
||||
510BD113232C3E9D002692E4 /* WebFeedMetadataFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510BD112232C3E9D002692E4 /* WebFeedMetadataFile.swift */; };
|
||||
511B9804237CD4270028BCAA /* ArticleFetcherType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 511B9803237CD4270028BCAA /* ArticleFetcherType.swift */; };
|
||||
513323082281070D00C30F19 /* AccountFeedbinSyncTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 513323072281070C00C30F19 /* AccountFeedbinSyncTest.swift */; };
|
||||
5133230A2281082F00C30F19 /* subscriptions_initial.json in Resources */ = {isa = PBXBuildFile; fileRef = 513323092281082F00C30F19 /* subscriptions_initial.json */; };
|
||||
@ -56,7 +56,7 @@
|
||||
841D4D702106B40400DD04E6 /* ArticlesDatabase.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 841D4D6F2106B40400DD04E6 /* ArticlesDatabase.framework */; };
|
||||
841D4D722106B40A00DD04E6 /* Articles.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 841D4D712106B40A00DD04E6 /* Articles.framework */; };
|
||||
84245C851FDDD8CB0074AFBB /* FeedbinSubscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84245C841FDDD8CB0074AFBB /* FeedbinSubscription.swift */; };
|
||||
844B297D2106C7EC004020B3 /* Feed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844B297C2106C7EC004020B3 /* Feed.swift */; };
|
||||
844B297D2106C7EC004020B3 /* WebFeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844B297C2106C7EC004020B3 /* WebFeed.swift */; };
|
||||
844B297F210CE37E004020B3 /* UnreadCountProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844B297E210CE37E004020B3 /* UnreadCountProvider.swift */; };
|
||||
844B2981210CE3BF004020B3 /* RSWeb.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 844B2980210CE3BF004020B3 /* RSWeb.framework */; };
|
||||
8469F81C1F6DD15E0084783E /* Account.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848935101F62486800CEBD24 /* Account.swift */; };
|
||||
@ -65,7 +65,7 @@
|
||||
846E77501F6EF9C400A165E2 /* LocalAccountRefresher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8419742D1F6DDE96006346C4 /* LocalAccountRefresher.swift */; };
|
||||
846E77541F6F00E300A165E2 /* AccountManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 846E77531F6F00E300A165E2 /* AccountManager.swift */; };
|
||||
848935001F62484F00CEBD24 /* Account.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 848934F61F62484F00CEBD24 /* Account.framework */; };
|
||||
84B2D4D02238CD8A00498ADA /* FeedMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B2D4CE2238C13D00498ADA /* FeedMetadata.swift */; };
|
||||
84B2D4D02238CD8A00498ADA /* WebFeedMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B2D4CE2238C13D00498ADA /* WebFeedMetadata.swift */; };
|
||||
84B99C9F1FAE8D3200ECDEDB /* ContainerPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B99C9E1FAE8D3200ECDEDB /* ContainerPath.swift */; };
|
||||
84C3654A1F899F3B001EC85C /* CombinedRefreshProgress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84C365491F899F3B001EC85C /* CombinedRefreshProgress.swift */; };
|
||||
84C8B3F41F89DE430053CCA6 /* DataExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84C8B3F31F89DE430053CCA6 /* DataExtensions.swift */; };
|
||||
@ -209,7 +209,7 @@
|
||||
5107A09A227DE49500C7C3C5 /* TestAccountManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestAccountManager.swift; sourceTree = "<group>"; };
|
||||
5107A09C227DE77700C7C3C5 /* TestTransport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestTransport.swift; sourceTree = "<group>"; };
|
||||
510BD110232C3801002692E4 /* AccountMetadataFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountMetadataFile.swift; sourceTree = "<group>"; };
|
||||
510BD112232C3E9D002692E4 /* FeedMetadataFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedMetadataFile.swift; sourceTree = "<group>"; };
|
||||
510BD112232C3E9D002692E4 /* WebFeedMetadataFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebFeedMetadataFile.swift; sourceTree = "<group>"; };
|
||||
511B9803237CD4270028BCAA /* ArticleFetcherType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleFetcherType.swift; sourceTree = "<group>"; };
|
||||
513323072281070C00C30F19 /* AccountFeedbinSyncTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountFeedbinSyncTest.swift; sourceTree = "<group>"; };
|
||||
513323092281082F00C30F19 /* subscriptions_initial.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = subscriptions_initial.json; sourceTree = "<group>"; };
|
||||
@ -258,7 +258,7 @@
|
||||
841D4D6F2106B40400DD04E6 /* ArticlesDatabase.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = ArticlesDatabase.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
841D4D712106B40A00DD04E6 /* Articles.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Articles.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
84245C841FDDD8CB0074AFBB /* FeedbinSubscription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbinSubscription.swift; sourceTree = "<group>"; };
|
||||
844B297C2106C7EC004020B3 /* Feed.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Feed.swift; sourceTree = "<group>"; };
|
||||
844B297C2106C7EC004020B3 /* WebFeed.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebFeed.swift; sourceTree = "<group>"; };
|
||||
844B297E210CE37E004020B3 /* UnreadCountProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnreadCountProvider.swift; sourceTree = "<group>"; };
|
||||
844B2980210CE3BF004020B3 /* RSWeb.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = RSWeb.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
846E77531F6F00E300A165E2 /* AccountManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountManager.swift; sourceTree = "<group>"; };
|
||||
@ -268,7 +268,7 @@
|
||||
848935061F62485000CEBD24 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
848935101F62486800CEBD24 /* Account.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Account.swift; sourceTree = "<group>"; };
|
||||
84AF4EA3222CFDD100F6A800 /* AccountMetadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountMetadata.swift; sourceTree = "<group>"; };
|
||||
84B2D4CE2238C13D00498ADA /* FeedMetadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedMetadata.swift; sourceTree = "<group>"; };
|
||||
84B2D4CE2238C13D00498ADA /* WebFeedMetadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebFeedMetadata.swift; sourceTree = "<group>"; };
|
||||
84B99C9E1FAE8D3200ECDEDB /* ContainerPath.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContainerPath.swift; sourceTree = "<group>"; };
|
||||
84C365491F899F3B001EC85C /* CombinedRefreshProgress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CombinedRefreshProgress.swift; sourceTree = "<group>"; };
|
||||
84C8B3F31F89DE430053CCA6 /* DataExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataExtensions.swift; sourceTree = "<group>"; };
|
||||
@ -532,9 +532,9 @@
|
||||
8419740D1F6DD25F006346C4 /* Container.swift */,
|
||||
84B99C9E1FAE8D3200ECDEDB /* ContainerPath.swift */,
|
||||
84C8B3F31F89DE430053CCA6 /* DataExtensions.swift */,
|
||||
844B297C2106C7EC004020B3 /* Feed.swift */,
|
||||
84B2D4CE2238C13D00498ADA /* FeedMetadata.swift */,
|
||||
510BD112232C3E9D002692E4 /* FeedMetadataFile.swift */,
|
||||
844B297C2106C7EC004020B3 /* WebFeed.swift */,
|
||||
84B2D4CE2238C13D00498ADA /* WebFeedMetadata.swift */,
|
||||
510BD112232C3E9D002692E4 /* WebFeedMetadataFile.swift */,
|
||||
841974001F6DD1EC006346C4 /* Folder.swift */,
|
||||
844B297E210CE37E004020B3 /* UnreadCountProvider.swift */,
|
||||
5165D71F22835E9800D9D53D /* FeedFinder */,
|
||||
@ -964,7 +964,7 @@
|
||||
84F73CF1202788D90000BCEF /* ArticleFetcher.swift in Sources */,
|
||||
9E713653233AD63E00765C84 /* FeedlySetUnreadArticlesOperation.swift in Sources */,
|
||||
841974251F6DDCE4006346C4 /* AccountDelegate.swift in Sources */,
|
||||
510BD113232C3E9D002692E4 /* FeedMetadataFile.swift in Sources */,
|
||||
510BD113232C3E9D002692E4 /* WebFeedMetadataFile.swift in Sources */,
|
||||
5165D73122837F3400D9D53D /* InitialFeedDownloader.swift in Sources */,
|
||||
9EEEF71F23545CB4009E9D80 /* FeedlySendArticleStatusesOperation.swift in Sources */,
|
||||
846E77541F6F00E300A165E2 /* AccountManager.swift in Sources */,
|
||||
@ -976,7 +976,7 @@
|
||||
9E84DC492359A73600D6E809 /* FeedlyCheckpointOperation.swift in Sources */,
|
||||
9E85C8EB236700E600D0F1F7 /* FeedlyGetEntriesOperation.swift in Sources */,
|
||||
9E1D154D233370D800F4944C /* FeedlySyncAllOperation.swift in Sources */,
|
||||
844B297D2106C7EC004020B3 /* Feed.swift in Sources */,
|
||||
844B297D2106C7EC004020B3 /* WebFeed.swift in Sources */,
|
||||
9E964EBA23754B4000A7AF2E /* OAuthAccountAuthorizationOperation.swift in Sources */,
|
||||
9E1D15572334355900F4944C /* FeedlyRequestStreamsOperation.swift in Sources */,
|
||||
9E1D15512334282100F4944C /* FeedlyMirrorCollectionsAsFoldersOperation.swift in Sources */,
|
||||
@ -987,7 +987,7 @@
|
||||
9EEAE075235D01C400E3FEE4 /* FeedlyMarkArticlesService.swift in Sources */,
|
||||
9EF1B10323584B4C000A486A /* FeedlySyncStreamContentsOperation.swift in Sources */,
|
||||
5154367B228EEB28005E1CDF /* FeedbinImportResult.swift in Sources */,
|
||||
84B2D4D02238CD8A00498ADA /* FeedMetadata.swift in Sources */,
|
||||
84B2D4D02238CD8A00498ADA /* WebFeedMetadata.swift in Sources */,
|
||||
9E84DC472359A23200D6E809 /* FeedlySyncUnreadStatusesOperation.swift in Sources */,
|
||||
9EAEC624233315F60085D7C9 /* FeedlyEntry.swift in Sources */,
|
||||
9EEAE073235D01AE00E3FEE4 /* FeedlyGetStreamIdsService.swift in Sources */,
|
||||
|
@ -33,13 +33,13 @@ protocol AccountDelegate {
|
||||
func renameFolder(for account: Account, with folder: Folder, to name: String, completion: @escaping (Result<Void, Error>) -> Void)
|
||||
func removeFolder(for account: Account, with folder: Folder, completion: @escaping (Result<Void, Error>) -> Void)
|
||||
|
||||
func createFeed(for account: Account, url: String, name: String?, container: Container, completion: @escaping (Result<Feed, Error>) -> Void)
|
||||
func renameFeed(for account: Account, with feed: Feed, to name: String, completion: @escaping (Result<Void, Error>) -> Void)
|
||||
func addFeed(for account: Account, with: Feed, to container: Container, completion: @escaping (Result<Void, Error>) -> Void)
|
||||
func removeFeed(for account: Account, with feed: Feed, from container: Container, completion: @escaping (Result<Void, Error>) -> Void)
|
||||
func moveFeed(for account: Account, with feed: Feed, from: Container, to: Container, completion: @escaping (Result<Void, Error>) -> Void)
|
||||
func createWebFeed(for account: Account, url: String, name: String?, container: Container, completion: @escaping (Result<WebFeed, Error>) -> Void)
|
||||
func renameWebFeed(for account: Account, with feed: WebFeed, to name: String, completion: @escaping (Result<Void, Error>) -> Void)
|
||||
func addWebFeed(for account: Account, with: WebFeed, to container: Container, completion: @escaping (Result<Void, Error>) -> Void)
|
||||
func removeWebFeed(for account: Account, with feed: WebFeed, from container: Container, completion: @escaping (Result<Void, Error>) -> Void)
|
||||
func moveWebFeed(for account: Account, with feed: WebFeed, from: Container, to: Container, completion: @escaping (Result<Void, Error>) -> Void)
|
||||
|
||||
func restoreFeed(for account: Account, feed: Feed, container: Container, completion: @escaping (Result<Void, Error>) -> Void)
|
||||
func restoreWebFeed(for account: Account, feed: WebFeed, container: Container, completion: @escaping (Result<Void, Error>) -> Void)
|
||||
func restoreFolder(for account: Account, folder: Folder, completion: @escaping (Result<Void, Error>) -> Void)
|
||||
|
||||
func markArticles(for account: Account, articles: Set<Article>, statusKey: ArticleStatus.Key, flag: Bool) -> Set<Article>?
|
||||
|
@ -207,7 +207,7 @@ public final class AccountManager: UnreadCountProvider {
|
||||
|
||||
public func anyAccountHasAtLeastOneFeed() -> Bool {
|
||||
for account in activeAccounts {
|
||||
if account.hasAtLeastOneFeed() {
|
||||
if account.hasAtLeastOneWebFeed() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@ -217,7 +217,7 @@ public final class AccountManager: UnreadCountProvider {
|
||||
|
||||
public func anyAccountHasFeedWithURL(_ urlString: String) -> Bool {
|
||||
for account in activeAccounts {
|
||||
if let _ = account.existingFeed(withURL: urlString) {
|
||||
if let _ = account.existingWebFeed(withURL: urlString) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
@ -33,8 +33,8 @@ class AccountFeedbinFolderContentsSyncTest: XCTestCase {
|
||||
waitForExpectations(timeout: 5, handler: nil)
|
||||
|
||||
let folder = account.folders?.filter { $0.name == "Developers" } .first!
|
||||
XCTAssertEqual(156, folder?.topLevelFeeds.count ?? 0)
|
||||
XCTAssertEqual(2, account.topLevelFeeds.count)
|
||||
XCTAssertEqual(156, folder?.topLevelWebFeeds.count ?? 0)
|
||||
XCTAssertEqual(2, account.topLevelWebFeeds.count)
|
||||
|
||||
// Test Adding a Feed to the folder
|
||||
testTransport.testFiles["https://api.feedbin.com/v2/taggings.json"] = "taggings_add.json"
|
||||
@ -45,8 +45,8 @@ class AccountFeedbinFolderContentsSyncTest: XCTestCase {
|
||||
}
|
||||
waitForExpectations(timeout: 5, handler: nil)
|
||||
|
||||
XCTAssertEqual(157, folder?.topLevelFeeds.count ?? 0)
|
||||
XCTAssertEqual(1, account.topLevelFeeds.count)
|
||||
XCTAssertEqual(157, folder?.topLevelWebFeeds.count ?? 0)
|
||||
XCTAssertEqual(1, account.topLevelWebFeeds.count)
|
||||
|
||||
// Test Deleting some Feeds from the folder
|
||||
testTransport.testFiles["https://api.feedbin.com/v2/taggings.json"] = "taggings_delete.json"
|
||||
@ -57,8 +57,8 @@ class AccountFeedbinFolderContentsSyncTest: XCTestCase {
|
||||
}
|
||||
waitForExpectations(timeout: 5, handler: nil)
|
||||
|
||||
XCTAssertEqual(153, folder?.topLevelFeeds.count ?? 0)
|
||||
XCTAssertEqual(5, account.topLevelFeeds.count)
|
||||
XCTAssertEqual(153, folder?.topLevelWebFeeds.count ?? 0)
|
||||
XCTAssertEqual(5, account.topLevelWebFeeds.count)
|
||||
|
||||
TestAccountManager.shared.deleteAccount(account)
|
||||
|
||||
|
@ -36,9 +36,9 @@ class AccountFeedbinSyncTest: XCTestCase {
|
||||
}
|
||||
waitForExpectations(timeout: 5, handler: nil)
|
||||
|
||||
XCTAssertEqual(224, account.flattenedFeeds().count)
|
||||
XCTAssertEqual(224, account.flattenedWebFeeds().count)
|
||||
|
||||
let daringFireball = account.idToFeedDictionary["1296379"]
|
||||
let daringFireball = account.idToWebFeedDictionary["1296379"]
|
||||
XCTAssertEqual("Daring Fireball", daringFireball!.name)
|
||||
XCTAssertEqual("https://daringfireball.net/feeds/json", daringFireball!.url)
|
||||
XCTAssertEqual("https://daringfireball.net/", daringFireball!.homePageURL)
|
||||
@ -57,9 +57,9 @@ class AccountFeedbinSyncTest: XCTestCase {
|
||||
}
|
||||
waitForExpectations(timeout: 5, handler: nil)
|
||||
|
||||
XCTAssertEqual(225, account.flattenedFeeds().count)
|
||||
XCTAssertEqual(225, account.flattenedWebFeeds().count)
|
||||
|
||||
let bPixels = account.idToFeedDictionary["1096623"]
|
||||
let bPixels = account.idToWebFeedDictionary["1096623"]
|
||||
XCTAssertEqual("Beautiful Pixels", bPixels?.name)
|
||||
XCTAssertEqual("https://feedpress.me/beautifulpixels", bPixels?.url)
|
||||
XCTAssertEqual("https://beautifulpixels.com/", bPixels?.homePageURL)
|
||||
|
@ -58,7 +58,7 @@ class FeedlyCreateFeedsForCollectionFoldersOperationTests: XCTestCase {
|
||||
completionExpectation.fulfill()
|
||||
}
|
||||
|
||||
XCTAssertTrue(account.flattenedFeeds().isEmpty, "Expected empty account.")
|
||||
XCTAssertTrue(account.flattenedWebFeeds().isEmpty, "Expected empty account.")
|
||||
|
||||
OperationQueue.main.addOperation(createFeeds)
|
||||
|
||||
@ -72,8 +72,8 @@ class FeedlyCreateFeedsForCollectionFoldersOperationTests: XCTestCase {
|
||||
.flatMap { $0 }
|
||||
.map { $0.title })
|
||||
|
||||
let accountFeeds = account.flattenedFeeds()
|
||||
let ingestedIds = Set(accountFeeds.map { $0.feedID })
|
||||
let accountFeeds = account.flattenedWebFeeds()
|
||||
let ingestedIds = Set(accountFeeds.map { $0.webFeedID })
|
||||
let ingestedTitles = Set(accountFeeds.map { $0.nameForDisplay })
|
||||
|
||||
let missingIds = feedIds.subtracting(ingestedIds)
|
||||
@ -91,7 +91,7 @@ class FeedlyCreateFeedsForCollectionFoldersOperationTests: XCTestCase {
|
||||
let ingestedFolderAndFeedIds = (account.folders ?? Set())
|
||||
.sorted { $0.externalID! < $1.externalID! }
|
||||
.compactMap { folder -> [String: [String]]? in
|
||||
return [folder.externalID!: folder.topLevelFeeds.map { $0.feedID }.sorted(by: <)]
|
||||
return [folder.externalID!: folder.topLevelWebFeeds.map { $0.webFeedID }.sorted(by: <)]
|
||||
}
|
||||
|
||||
XCTAssertEqual(expectedFolderAndFeedIds, ingestedFolderAndFeedIds, "Did not ingest feeds in their corresponding folders.")
|
||||
@ -129,7 +129,7 @@ class FeedlyCreateFeedsForCollectionFoldersOperationTests: XCTestCase {
|
||||
completionExpectation.fulfill()
|
||||
}
|
||||
|
||||
XCTAssertTrue(account.flattenedFeeds().isEmpty, "Expected empty account.")
|
||||
XCTAssertTrue(account.flattenedWebFeeds().isEmpty, "Expected empty account.")
|
||||
|
||||
OperationQueue.main.addOperation(createFeeds)
|
||||
|
||||
@ -165,8 +165,8 @@ class FeedlyCreateFeedsForCollectionFoldersOperationTests: XCTestCase {
|
||||
.flatMap { $0 }
|
||||
.map { $0.title })
|
||||
|
||||
let accountFeeds = account.flattenedFeeds()
|
||||
let ingestedIds = Set(accountFeeds.map { $0.feedID })
|
||||
let accountFeeds = account.flattenedWebFeeds()
|
||||
let ingestedIds = Set(accountFeeds.map { $0.webFeedID })
|
||||
let ingestedTitles = Set(accountFeeds.map { $0.nameForDisplay })
|
||||
|
||||
XCTAssertEqual(ingestedIds.count, feedIds.count)
|
||||
@ -187,7 +187,7 @@ class FeedlyCreateFeedsForCollectionFoldersOperationTests: XCTestCase {
|
||||
let ingestedFolderAndFeedIds = (account.folders ?? Set())
|
||||
.sorted { $0.externalID! < $1.externalID! }
|
||||
.compactMap { folder -> [String: [String]]? in
|
||||
return [folder.externalID!: folder.topLevelFeeds.map { $0.feedID }.sorted(by: <)]
|
||||
return [folder.externalID!: folder.topLevelWebFeeds.map { $0.webFeedID }.sorted(by: <)]
|
||||
}
|
||||
|
||||
XCTAssertEqual(expectedFolderAndFeedIds, ingestedFolderAndFeedIds, "Did not ingest feeds to their corresponding folders.")
|
||||
|
@ -183,7 +183,7 @@ class FeedlyMirrorCollectionsAsFoldersOperationTests: XCTestCase {
|
||||
|
||||
waitForExpectations(timeout: 2)
|
||||
|
||||
XCTAssertFalse(account.flattenedFeeds().isEmpty, "Expected account to have feeds.")
|
||||
XCTAssertFalse(account.flattenedWebFeeds().isEmpty, "Expected account to have feeds.")
|
||||
}
|
||||
|
||||
// Now that the folders are added, remove them all.
|
||||
@ -200,7 +200,7 @@ class FeedlyMirrorCollectionsAsFoldersOperationTests: XCTestCase {
|
||||
|
||||
waitForExpectations(timeout: 2)
|
||||
|
||||
let feeds = account.flattenedFeeds()
|
||||
let feeds = account.flattenedWebFeeds()
|
||||
|
||||
XCTAssertTrue(feeds.isEmpty)
|
||||
}
|
||||
|
@ -129,12 +129,12 @@ class FeedlyTestSupport {
|
||||
return
|
||||
}
|
||||
let collectionFeeds = collection["feeds"] as! [[String: Any]]
|
||||
let folderFeeds = folder.topLevelFeeds
|
||||
let folderFeeds = folder.topLevelWebFeeds
|
||||
|
||||
XCTAssertEqual(collectionFeeds.count, folderFeeds.count)
|
||||
|
||||
let collectionFeedIds = Set(collectionFeeds.map { $0["id"] as! String })
|
||||
let folderFeedIds = Set(folderFeeds.map { $0.feedID })
|
||||
let folderFeedIds = Set(folderFeeds.map { $0.webFeedID })
|
||||
let missingFeedIds = collectionFeedIds.subtracting(folderFeedIds)
|
||||
|
||||
XCTAssertTrue(missingFeedIds.isEmpty, "Feeds with these ids were not found in the \"\(label)\" folder.")
|
||||
@ -205,7 +205,7 @@ class FeedlyTestSupport {
|
||||
for item in articleItems where item.id == article.articleID {
|
||||
XCTAssertEqual(article.uniqueID, item.id)
|
||||
XCTAssertEqual(article.contentHTML, item.content)
|
||||
XCTAssertEqual(article.feedID, item.feedId)
|
||||
XCTAssertEqual(article.webFeedID, item.feedId)
|
||||
XCTAssertEqual(article.externalURL, item.externalUrl)
|
||||
}
|
||||
}
|
||||
|
@ -19,18 +19,18 @@ public protocol ArticleFetcher {
|
||||
func fetchUnreadArticlesAsync(_ callback: @escaping ArticleSetBlock)
|
||||
}
|
||||
|
||||
extension Feed: ArticleFetcher {
|
||||
extension WebFeed: ArticleFetcher {
|
||||
|
||||
public var articleFetcherType: ArticleFetcherType? {
|
||||
guard let accountID = account?.accountID else {
|
||||
assertionFailure("Expected feed.account, but got nil.")
|
||||
return nil
|
||||
}
|
||||
return ArticleFetcherType.feed(accountID, feedID)
|
||||
return ArticleFetcherType.webFeed(accountID, webFeedID)
|
||||
}
|
||||
|
||||
public func fetchArticles() -> Set<Article> {
|
||||
return account?.fetchArticles(.feed(self)) ?? Set<Article>()
|
||||
return account?.fetchArticles(.webFeed(self)) ?? Set<Article>()
|
||||
}
|
||||
|
||||
public func fetchArticlesAsync(_ callback: @escaping ArticleSetBlock) {
|
||||
@ -39,7 +39,7 @@ extension Feed: ArticleFetcher {
|
||||
callback(Set<Article>())
|
||||
return
|
||||
}
|
||||
account.fetchArticlesAsync(.feed(self), callback)
|
||||
account.fetchArticlesAsync(.webFeed(self), callback)
|
||||
}
|
||||
|
||||
public func fetchUnreadArticles() -> Set<Article> {
|
||||
@ -52,7 +52,7 @@ extension Feed: ArticleFetcher {
|
||||
callback(Set<Article>())
|
||||
return
|
||||
}
|
||||
account.fetchArticlesAsync(.feed(self)) { callback($0.unreadArticles()) }
|
||||
account.fetchArticlesAsync(.webFeed(self)) { callback($0.unreadArticles()) }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -12,7 +12,7 @@ public enum ArticleFetcherType: CustomStringConvertible {
|
||||
|
||||
case smartFeed(String) // String is a unique identifier
|
||||
case script(String) // String is a unique identifier
|
||||
case feed(String, String) // accountID, feedID
|
||||
case webFeed(String, String) // accountID, webFeedID
|
||||
case folder(String, String) // accountID, folderName
|
||||
|
||||
public var description: String {
|
||||
@ -21,8 +21,8 @@ public enum ArticleFetcherType: CustomStringConvertible {
|
||||
return "smartFeed: \(id)"
|
||||
case .script(let id):
|
||||
return "script: \(id)"
|
||||
case .feed(let accountID, let feedID):
|
||||
return "feed: \(accountID)_\(feedID)"
|
||||
case .webFeed(let accountID, let webFeedID):
|
||||
return "feed: \(accountID)_\(webFeedID)"
|
||||
case .folder(let accountID, let folderName):
|
||||
return "folder: \(accountID)_\(folderName)"
|
||||
}
|
||||
@ -40,11 +40,11 @@ public enum ArticleFetcherType: CustomStringConvertible {
|
||||
"type": "script",
|
||||
"id": id
|
||||
]
|
||||
case .feed(let accountID, let feedID):
|
||||
case .webFeed(let accountID, let webFeedID):
|
||||
return [
|
||||
"type": "feed",
|
||||
"accountID": accountID,
|
||||
"feedID": feedID
|
||||
"webFeedID": webFeedID
|
||||
]
|
||||
case .folder(let accountID, let folderName):
|
||||
return [
|
||||
@ -66,8 +66,8 @@ public enum ArticleFetcherType: CustomStringConvertible {
|
||||
guard let id = userInfo["id"] as? String else { return nil }
|
||||
self = ArticleFetcherType.script(id)
|
||||
case "feed":
|
||||
guard let accountID = userInfo["accountID"] as? String, let feedID = userInfo["feedID"] as? String else { return nil }
|
||||
self = ArticleFetcherType.feed(accountID, feedID)
|
||||
guard let accountID = userInfo["accountID"] as? String, let webFeedID = userInfo["webFeedID"] as? String else { return nil }
|
||||
self = ArticleFetcherType.webFeed(accountID, webFeedID)
|
||||
case "folder":
|
||||
guard let accountID = userInfo["accountID"] as? String, let folderName = userInfo["folderName"] as? String else { return nil }
|
||||
self = ArticleFetcherType.folder(accountID, folderName)
|
||||
|
@ -19,25 +19,25 @@ extension Notification.Name {
|
||||
public protocol Container: class {
|
||||
|
||||
var account: Account? { get }
|
||||
var topLevelFeeds: Set<Feed> { get set }
|
||||
var topLevelWebFeeds: Set<WebFeed> { get set }
|
||||
var folders: Set<Folder>? { get set }
|
||||
|
||||
func hasAtLeastOneFeed() -> Bool
|
||||
func hasAtLeastOneWebFeed() -> Bool
|
||||
func objectIsChild(_ object: AnyObject) -> Bool
|
||||
|
||||
func hasChildFolder(with: String) -> Bool
|
||||
func childFolder(with: String) -> Folder?
|
||||
|
||||
func removeFeed(_ feed: Feed)
|
||||
func addFeed(_ feed: Feed)
|
||||
func removeWebFeed(_ webFeed: WebFeed)
|
||||
func addWebFeed(_ webFeed: WebFeed)
|
||||
|
||||
//Recursive — checks subfolders
|
||||
func flattenedFeeds() -> Set<Feed>
|
||||
func has(_ feed: Feed) -> Bool
|
||||
func hasFeed(with feedID: String) -> Bool
|
||||
func hasFeed(withURL url: String) -> Bool
|
||||
func existingFeed(withFeedID: String) -> Feed?
|
||||
func existingFeed(withURL url: String) -> Feed?
|
||||
func flattenedWebFeeds() -> Set<WebFeed>
|
||||
func has(_ webFeed: WebFeed) -> Bool
|
||||
func hasWebFeed(with webFeedID: String) -> Bool
|
||||
func hasWebFeed(withURL url: String) -> Bool
|
||||
func existingWebFeed(withWebFeedID: String) -> WebFeed?
|
||||
func existingWebFeed(withURL url: String) -> WebFeed?
|
||||
func existingFolder(with name: String) -> Folder?
|
||||
func existingFolder(withID: Int) -> Folder?
|
||||
|
||||
@ -46,8 +46,8 @@ public protocol Container: class {
|
||||
|
||||
public extension Container {
|
||||
|
||||
func hasAtLeastOneFeed() -> Bool {
|
||||
return topLevelFeeds.count > 0
|
||||
func hasAtLeastOneWebFeed() -> Bool {
|
||||
return topLevelWebFeeds.count > 0
|
||||
}
|
||||
|
||||
func hasChildFolder(with name: String) -> Bool {
|
||||
@ -67,8 +67,8 @@ public extension Container {
|
||||
}
|
||||
|
||||
func objectIsChild(_ object: AnyObject) -> Bool {
|
||||
if let feed = object as? Feed {
|
||||
return topLevelFeeds.contains(feed)
|
||||
if let feed = object as? WebFeed {
|
||||
return topLevelWebFeeds.contains(feed)
|
||||
}
|
||||
if let folder = object as? Folder {
|
||||
return folders?.contains(folder) ?? false
|
||||
@ -76,40 +76,40 @@ public extension Container {
|
||||
return false
|
||||
}
|
||||
|
||||
func flattenedFeeds() -> Set<Feed> {
|
||||
var feeds = Set<Feed>()
|
||||
feeds.formUnion(topLevelFeeds)
|
||||
func flattenedWebFeeds() -> Set<WebFeed> {
|
||||
var feeds = Set<WebFeed>()
|
||||
feeds.formUnion(topLevelWebFeeds)
|
||||
if let folders = folders {
|
||||
for folder in folders {
|
||||
feeds.formUnion(folder.flattenedFeeds())
|
||||
feeds.formUnion(folder.flattenedWebFeeds())
|
||||
}
|
||||
}
|
||||
return feeds
|
||||
}
|
||||
|
||||
func hasFeed(with feedID: String) -> Bool {
|
||||
return existingFeed(withFeedID: feedID) != nil
|
||||
func hasWebFeed(with webFeedID: String) -> Bool {
|
||||
return existingWebFeed(withWebFeedID: webFeedID) != nil
|
||||
}
|
||||
|
||||
func hasFeed(withURL url: String) -> Bool {
|
||||
return existingFeed(withURL: url) != nil
|
||||
func hasWebFeed(withURL url: String) -> Bool {
|
||||
return existingWebFeed(withURL: url) != nil
|
||||
}
|
||||
|
||||
func has(_ feed: Feed) -> Bool {
|
||||
return flattenedFeeds().contains(feed)
|
||||
func has(_ webFeed: WebFeed) -> Bool {
|
||||
return flattenedWebFeeds().contains(webFeed)
|
||||
}
|
||||
|
||||
func existingFeed(withFeedID feedID: String) -> Feed? {
|
||||
for feed in flattenedFeeds() {
|
||||
if feed.feedID == feedID {
|
||||
func existingWebFeed(withWebFeedID webFeedID: String) -> WebFeed? {
|
||||
for feed in flattenedWebFeeds() {
|
||||
if feed.webFeedID == webFeedID {
|
||||
return feed
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func existingFeed(withURL url: String) -> Feed? {
|
||||
for feed in flattenedFeeds() {
|
||||
func existingWebFeed(withURL url: String) -> WebFeed? {
|
||||
for feed in flattenedWebFeeds() {
|
||||
if feed.url == url {
|
||||
return feed
|
||||
}
|
||||
|
@ -11,14 +11,14 @@ import Articles
|
||||
import RSParser
|
||||
|
||||
public extension Notification.Name {
|
||||
static let FeedSettingDidChange = Notification.Name(rawValue: "FeedSettingDidChangeNotification")
|
||||
static let WebFeedSettingDidChange = Notification.Name(rawValue: "FeedSettingDidChangeNotification")
|
||||
}
|
||||
|
||||
public extension Feed {
|
||||
public extension WebFeed {
|
||||
|
||||
static let FeedSettingUserInfoKey = "feedSetting"
|
||||
static let WebFeedSettingUserInfoKey = "feedSetting"
|
||||
|
||||
struct FeedSettingKey {
|
||||
struct WebFeedSettingKey {
|
||||
public static let homePageURL = "homePageURL"
|
||||
public static let iconURL = "iconURL"
|
||||
public static let faviconURL = "faviconURL"
|
||||
@ -30,7 +30,7 @@ public extension Feed {
|
||||
}
|
||||
}
|
||||
|
||||
extension Feed {
|
||||
extension WebFeed {
|
||||
|
||||
func takeSettings(from parsedFeed: ParsedFeed) {
|
||||
iconURL = parsedFeed.iconURL
|
||||
@ -40,9 +40,9 @@ extension Feed {
|
||||
authors = Author.authorsWithParsedAuthors(parsedFeed.authors)
|
||||
}
|
||||
|
||||
func postFeedSettingDidChangeNotification(_ codingKey: FeedMetadata.CodingKeys) {
|
||||
let userInfo = [Feed.FeedSettingUserInfoKey: codingKey.stringValue]
|
||||
NotificationCenter.default.post(name: .FeedSettingDidChange, object: self, userInfo: userInfo)
|
||||
func postFeedSettingDidChangeNotification(_ codingKey: WebFeedMetadata.CodingKeys) {
|
||||
let userInfo = [WebFeed.WebFeedSettingUserInfoKey: codingKey.stringValue]
|
||||
NotificationCenter.default.post(name: .WebFeedSettingDidChange, object: self, userInfo: userInfo)
|
||||
}
|
||||
}
|
||||
|
||||
@ -56,8 +56,8 @@ public extension Article {
|
||||
return manager.existingAccount(with: accountID)
|
||||
}
|
||||
|
||||
var feed: Feed? {
|
||||
return account?.existingFeed(withFeedID: feedID)
|
||||
var webFeed: WebFeed? {
|
||||
return account?.existingWebFeed(withWebFeedID: webFeedID)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -275,7 +275,7 @@ final class FeedbinAPICaller: NSObject {
|
||||
|
||||
}
|
||||
|
||||
func createTagging(feedID: Int, name: String, completion: @escaping (Result<Int, Error>) -> Void) {
|
||||
func createTagging(webFeedID: Int, name: String, completion: @escaping (Result<Int, Error>) -> Void) {
|
||||
|
||||
let callURL = feedbinBaseURL.appendingPathComponent("taggings.json")
|
||||
var request = URLRequest(url: callURL, credentials: credentials)
|
||||
@ -283,7 +283,7 @@ final class FeedbinAPICaller: NSObject {
|
||||
|
||||
let payload: Data
|
||||
do {
|
||||
payload = try JSONEncoder().encode(FeedbinCreateTagging(feedID: feedID, name: name))
|
||||
payload = try JSONEncoder().encode(FeedbinCreateTagging(feedID: webFeedID, name: name))
|
||||
} catch {
|
||||
completion(.failure(error))
|
||||
return
|
||||
|
@ -268,7 +268,7 @@ final class FeedbinAccountDelegate: AccountDelegate {
|
||||
|
||||
func renameFolder(for account: Account, with folder: Folder, to name: String, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
|
||||
guard folder.hasAtLeastOneFeed() else {
|
||||
guard folder.hasAtLeastOneWebFeed() else {
|
||||
folder.name = name
|
||||
return
|
||||
}
|
||||
@ -296,7 +296,7 @@ final class FeedbinAccountDelegate: AccountDelegate {
|
||||
func removeFolder(for account: Account, with folder: Folder, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
|
||||
// Feedbin uses tags and if at least one feed isn't tagged, then the folder doesn't exist on their system
|
||||
guard folder.hasAtLeastOneFeed() else {
|
||||
guard folder.hasAtLeastOneWebFeed() else {
|
||||
account.removeFolder(folder)
|
||||
completion(.success(()))
|
||||
return
|
||||
@ -304,7 +304,7 @@ final class FeedbinAccountDelegate: AccountDelegate {
|
||||
|
||||
let group = DispatchGroup()
|
||||
|
||||
for feed in folder.topLevelFeeds {
|
||||
for feed in folder.topLevelWebFeeds {
|
||||
|
||||
if feed.folderRelationship?.count ?? 0 > 1 {
|
||||
|
||||
@ -336,7 +336,7 @@ final class FeedbinAccountDelegate: AccountDelegate {
|
||||
switch result {
|
||||
case .success:
|
||||
DispatchQueue.main.async {
|
||||
account.clearFeedMetadata(feed)
|
||||
account.clearWebFeedMetadata(feed)
|
||||
}
|
||||
case .failure(let error):
|
||||
os_log(.error, log: self.log, "Remove feed error: %@.", error.localizedDescription)
|
||||
@ -356,7 +356,7 @@ final class FeedbinAccountDelegate: AccountDelegate {
|
||||
|
||||
}
|
||||
|
||||
func createFeed(for account: Account, url: String, name: String?, container: Container, completion: @escaping (Result<Feed, Error>) -> Void) {
|
||||
func createWebFeed(for account: Account, url: String, name: String?, container: Container, completion: @escaping (Result<WebFeed, Error>) -> Void) {
|
||||
|
||||
refreshProgress.addToNumberOfTasksAndRemaining(1)
|
||||
caller.createSubscription(url: url) { result in
|
||||
@ -388,7 +388,7 @@ final class FeedbinAccountDelegate: AccountDelegate {
|
||||
|
||||
}
|
||||
|
||||
func renameFeed(for account: Account, with feed: Feed, to name: String, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
func renameWebFeed(for account: Account, with feed: WebFeed, to name: String, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
|
||||
// This error should never happen
|
||||
guard let subscriptionID = feed.subscriptionID else {
|
||||
@ -415,7 +415,7 @@ final class FeedbinAccountDelegate: AccountDelegate {
|
||||
|
||||
}
|
||||
|
||||
func removeFeed(for account: Account, with feed: Feed, from container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
func removeWebFeed(for account: Account, with feed: WebFeed, from container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
if feed.folderRelationship?.count ?? 0 > 1 {
|
||||
deleteTagging(for: account, with: feed, from: container, completion: completion)
|
||||
} else {
|
||||
@ -423,14 +423,14 @@ final class FeedbinAccountDelegate: AccountDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
func moveFeed(for account: Account, with feed: Feed, from: Container, to: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
func moveWebFeed(for account: Account, with feed: WebFeed, from: Container, to: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
if from is Account {
|
||||
addFeed(for: account, with: feed, to: to, completion: completion)
|
||||
addWebFeed(for: account, with: feed, to: to, completion: completion)
|
||||
} else {
|
||||
deleteTagging(for: account, with: feed, from: from) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
self.addFeed(for: account, with: feed, to: to, completion: completion)
|
||||
self.addWebFeed(for: account, with: feed, to: to, completion: completion)
|
||||
case .failure(let error):
|
||||
completion(.failure(error))
|
||||
}
|
||||
@ -438,18 +438,18 @@ final class FeedbinAccountDelegate: AccountDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
func addFeed(for account: Account, with feed: Feed, to container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
func addWebFeed(for account: Account, with feed: WebFeed, to container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
|
||||
if let folder = container as? Folder, let feedID = Int(feed.feedID) {
|
||||
if let folder = container as? Folder, let webFeedID = Int(feed.webFeedID) {
|
||||
refreshProgress.addToNumberOfTasksAndRemaining(1)
|
||||
caller.createTagging(feedID: feedID, name: folder.name ?? "") { result in
|
||||
caller.createTagging(webFeedID: webFeedID, name: folder.name ?? "") { result in
|
||||
self.refreshProgress.completeTask()
|
||||
switch result {
|
||||
case .success(let taggingID):
|
||||
DispatchQueue.main.async {
|
||||
self.saveFolderRelationship(for: feed, withFolderName: folder.name ?? "", id: String(taggingID))
|
||||
account.removeFeed(feed)
|
||||
folder.addFeed(feed)
|
||||
account.removeWebFeed(feed)
|
||||
folder.addWebFeed(feed)
|
||||
completion(.success(()))
|
||||
}
|
||||
case .failure(let error):
|
||||
@ -470,10 +470,10 @@ final class FeedbinAccountDelegate: AccountDelegate {
|
||||
|
||||
}
|
||||
|
||||
func restoreFeed(for account: Account, feed: Feed, container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
func restoreWebFeed(for account: Account, feed: WebFeed, container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
|
||||
if let existingFeed = account.existingFeed(withURL: feed.url) {
|
||||
account.addFeed(existingFeed, to: container) { result in
|
||||
if let existingFeed = account.existingWebFeed(withURL: feed.url) {
|
||||
account.addWebFeed(existingFeed, to: container) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
completion(.success(()))
|
||||
@ -482,7 +482,7 @@ final class FeedbinAccountDelegate: AccountDelegate {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
createFeed(for: account, url: feed.url, name: feed.editedName, container: container) { result in
|
||||
createWebFeed(for: account, url: feed.url, name: feed.editedName, container: container) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
completion(.success(()))
|
||||
@ -498,12 +498,12 @@ final class FeedbinAccountDelegate: AccountDelegate {
|
||||
|
||||
let group = DispatchGroup()
|
||||
|
||||
for feed in folder.topLevelFeeds {
|
||||
for feed in folder.topLevelWebFeeds {
|
||||
|
||||
folder.topLevelFeeds.remove(feed)
|
||||
folder.topLevelWebFeeds.remove(feed)
|
||||
|
||||
group.enter()
|
||||
restoreFeed(for: account, feed: feed, container: folder) { result in
|
||||
restoreWebFeed(for: account, feed: feed, container: folder) { result in
|
||||
group.leave()
|
||||
switch result {
|
||||
case .success:
|
||||
@ -721,8 +721,8 @@ private extension FeedbinAccountDelegate {
|
||||
if let folders = account.folders {
|
||||
folders.forEach { folder in
|
||||
if !tagNames.contains(folder.name ?? "") {
|
||||
for feed in folder.topLevelFeeds {
|
||||
account.addFeed(feed)
|
||||
for feed in folder.topLevelWebFeeds {
|
||||
account.addWebFeed(feed)
|
||||
clearFolderRelationship(for: feed, withFolderName: folder.name ?? "")
|
||||
}
|
||||
account.removeFolder(folder)
|
||||
@ -759,17 +759,17 @@ private extension FeedbinAccountDelegate {
|
||||
// Remove any feeds that are no longer in the subscriptions
|
||||
if let folders = account.folders {
|
||||
for folder in folders {
|
||||
for feed in folder.topLevelFeeds {
|
||||
if !subFeedIds.contains(feed.feedID) {
|
||||
folder.removeFeed(feed)
|
||||
for feed in folder.topLevelWebFeeds {
|
||||
if !subFeedIds.contains(feed.webFeedID) {
|
||||
folder.removeWebFeed(feed)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for feed in account.topLevelFeeds {
|
||||
if !subFeedIds.contains(feed.feedID) {
|
||||
account.removeFeed(feed)
|
||||
for feed in account.topLevelWebFeeds {
|
||||
if !subFeedIds.contains(feed.webFeedID) {
|
||||
account.removeWebFeed(feed)
|
||||
}
|
||||
}
|
||||
|
||||
@ -779,7 +779,7 @@ private extension FeedbinAccountDelegate {
|
||||
|
||||
let subFeedId = String(subscription.feedID)
|
||||
|
||||
if let feed = account.existingFeed(withFeedID: subFeedId) {
|
||||
if let feed = account.existingWebFeed(withWebFeedID: subFeedId) {
|
||||
feed.name = subscription.name
|
||||
// If the name has been changed on the server remove the locally edited name
|
||||
feed.editedName = nil
|
||||
@ -795,9 +795,9 @@ private extension FeedbinAccountDelegate {
|
||||
|
||||
// Actually add subscriptions all in one go, so we don’t trigger various rebuilding things that Account does.
|
||||
subscriptionsToAdd.forEach { subscription in
|
||||
let feed = account.createFeed(with: subscription.name, url: subscription.url, feedID: String(subscription.feedID), homePageURL: subscription.homePageURL)
|
||||
let feed = account.createWebFeed(with: subscription.name, url: subscription.url, webFeedID: String(subscription.feedID), homePageURL: subscription.homePageURL)
|
||||
feed.subscriptionID = String(subscription.subscriptionID)
|
||||
account.addFeed(feed)
|
||||
account.addWebFeed(feed)
|
||||
}
|
||||
}
|
||||
|
||||
@ -836,25 +836,25 @@ private extension FeedbinAccountDelegate {
|
||||
let taggingFeedIDs = groupedTaggings.map { String($0.feedID) }
|
||||
|
||||
// Move any feeds not in the folder to the account
|
||||
for feed in folder.topLevelFeeds {
|
||||
if !taggingFeedIDs.contains(feed.feedID) {
|
||||
folder.removeFeed(feed)
|
||||
for feed in folder.topLevelWebFeeds {
|
||||
if !taggingFeedIDs.contains(feed.webFeedID) {
|
||||
folder.removeWebFeed(feed)
|
||||
clearFolderRelationship(for: feed, withFolderName: folder.name ?? "")
|
||||
account.addFeed(feed)
|
||||
account.addWebFeed(feed)
|
||||
}
|
||||
}
|
||||
|
||||
// Add any feeds not in the folder
|
||||
let folderFeedIds = folder.topLevelFeeds.map { $0.feedID }
|
||||
let folderFeedIds = folder.topLevelWebFeeds.map { $0.webFeedID }
|
||||
|
||||
for tagging in groupedTaggings {
|
||||
let taggingFeedID = String(tagging.feedID)
|
||||
if !folderFeedIds.contains(taggingFeedID) {
|
||||
guard let feed = account.existingFeed(withFeedID: taggingFeedID) else {
|
||||
guard let feed = account.existingWebFeed(withWebFeedID: taggingFeedID) else {
|
||||
continue
|
||||
}
|
||||
saveFolderRelationship(for: feed, withFolderName: folderName, id: String(tagging.taggingID))
|
||||
folder.addFeed(feed)
|
||||
folder.addWebFeed(feed)
|
||||
}
|
||||
}
|
||||
|
||||
@ -863,9 +863,9 @@ private extension FeedbinAccountDelegate {
|
||||
let taggedFeedIDs = Set(taggings.map { String($0.feedID) })
|
||||
|
||||
// Remove all feeds from the account container that have a tag
|
||||
for feed in account.topLevelFeeds {
|
||||
if taggedFeedIDs.contains(feed.feedID) {
|
||||
account.removeFeed(feed)
|
||||
for feed in account.topLevelWebFeeds {
|
||||
if taggedFeedIDs.contains(feed.webFeedID) {
|
||||
account.removeWebFeed(feed)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -913,7 +913,7 @@ private extension FeedbinAccountDelegate {
|
||||
}
|
||||
|
||||
func renameFolderRelationship(for account: Account, fromName: String, toName: String) {
|
||||
for feed in account.flattenedFeeds() {
|
||||
for feed in account.flattenedWebFeeds() {
|
||||
if var folderRelationship = feed.folderRelationship {
|
||||
let relationship = folderRelationship[fromName]
|
||||
folderRelationship[fromName] = nil
|
||||
@ -923,14 +923,14 @@ private extension FeedbinAccountDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
func clearFolderRelationship(for feed: Feed, withFolderName folderName: String) {
|
||||
func clearFolderRelationship(for feed: WebFeed, withFolderName folderName: String) {
|
||||
if var folderRelationship = feed.folderRelationship {
|
||||
folderRelationship[folderName] = nil
|
||||
feed.folderRelationship = folderRelationship
|
||||
}
|
||||
}
|
||||
|
||||
func saveFolderRelationship(for feed: Feed, withFolderName folderName: String, id: String) {
|
||||
func saveFolderRelationship(for feed: WebFeed, withFolderName folderName: String, id: String) {
|
||||
if var folderRelationship = feed.folderRelationship {
|
||||
folderRelationship[folderName] = id
|
||||
feed.folderRelationship = folderRelationship
|
||||
@ -939,7 +939,7 @@ private extension FeedbinAccountDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
func decideBestFeedChoice(account: Account, url: String, name: String?, container: Container, choices: [FeedbinSubscriptionChoice], completion: @escaping (Result<Feed, Error>) -> Void) {
|
||||
func decideBestFeedChoice(account: Account, url: String, name: String?, container: Container, choices: [FeedbinSubscriptionChoice], completion: @escaping (Result<WebFeed, Error>) -> Void) {
|
||||
|
||||
let feedSpecifiers: [FeedSpecifier] = choices.map { choice in
|
||||
let source = url == choice.url ? FeedSpecifier.Source.UserEntered : FeedSpecifier.Source.HTMLLink
|
||||
@ -949,7 +949,7 @@ private extension FeedbinAccountDelegate {
|
||||
|
||||
if let bestSpecifier = FeedSpecifier.bestFeed(in: Set(feedSpecifiers)) {
|
||||
if let bestSubscription = choices.filter({ bestSpecifier.urlString == $0.url }).first {
|
||||
createFeed(for: account, url: bestSubscription.url, name: name, container: container, completion: completion)
|
||||
createWebFeed(for: account, url: bestSubscription.url, name: name, container: container, completion: completion)
|
||||
} else {
|
||||
DispatchQueue.main.async {
|
||||
completion(.failure(FeedbinAccountDelegateError.invalidParameter))
|
||||
@ -963,20 +963,20 @@ private extension FeedbinAccountDelegate {
|
||||
|
||||
}
|
||||
|
||||
func createFeed( account: Account, subscription sub: FeedbinSubscription, name: String?, container: Container, completion: @escaping (Result<Feed, Error>) -> Void) {
|
||||
func createFeed( account: Account, subscription sub: FeedbinSubscription, name: String?, container: Container, completion: @escaping (Result<WebFeed, Error>) -> Void) {
|
||||
|
||||
DispatchQueue.main.async {
|
||||
|
||||
let feed = account.createFeed(with: sub.name, url: sub.url, feedID: String(sub.feedID), homePageURL: sub.homePageURL)
|
||||
let feed = account.createWebFeed(with: sub.name, url: sub.url, webFeedID: String(sub.feedID), homePageURL: sub.homePageURL)
|
||||
feed.subscriptionID = String(sub.subscriptionID)
|
||||
feed.iconURL = sub.jsonFeed?.icon
|
||||
feed.faviconURL = sub.jsonFeed?.favicon
|
||||
|
||||
account.addFeed(feed, to: container) { result in
|
||||
account.addWebFeed(feed, to: container) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
if let name = name {
|
||||
account.renameFeed(feed, to: name) { result in
|
||||
account.renameWebFeed(feed, to: name) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
self.initialFeedDownload(account: account, feed: feed, completion: completion)
|
||||
@ -996,13 +996,13 @@ private extension FeedbinAccountDelegate {
|
||||
|
||||
}
|
||||
|
||||
func initialFeedDownload( account: Account, feed: Feed, completion: @escaping (Result<Feed, Error>) -> Void) {
|
||||
func initialFeedDownload( account: Account, feed: WebFeed, completion: @escaping (Result<WebFeed, Error>) -> Void) {
|
||||
|
||||
// refreshArticles is being reused and will clear one of the tasks for us
|
||||
refreshProgress.addToNumberOfTasksAndRemaining(4)
|
||||
|
||||
// Download the initial articles
|
||||
self.caller.retrieveEntries(feedID: feed.feedID) { result in
|
||||
self.caller.retrieveEntries(feedID: feed.webFeedID) { result in
|
||||
self.refreshProgress.completeTask()
|
||||
|
||||
switch result {
|
||||
@ -1155,8 +1155,8 @@ private extension FeedbinAccountDelegate {
|
||||
|
||||
func processEntries(account: Account, entries: [FeedbinEntry]?, completion: @escaping (() -> Void)) {
|
||||
let parsedItems = mapEntriesToParsedItems(entries: entries)
|
||||
let feedIDsAndItems = Dictionary(grouping: parsedItems, by: { item in item.feedURL } ).mapValues { Set($0) }
|
||||
account.update(feedIDsAndItems: feedIDsAndItems, defaultRead: true, completion: completion)
|
||||
let webFeedIDsAndItems = Dictionary(grouping: parsedItems, by: { item in item.feedURL } ).mapValues { Set($0) }
|
||||
account.update(webFeedIDsAndItems: webFeedIDsAndItems, defaultRead: true, completion: completion)
|
||||
}
|
||||
|
||||
func mapEntriesToParsedItems(entries: [FeedbinEntry]?) -> Set<ParsedItem> {
|
||||
@ -1232,7 +1232,7 @@ private extension FeedbinAccountDelegate {
|
||||
account.ensureStatuses(missingUnstarredArticleIDs, true, .starred, false)
|
||||
}
|
||||
|
||||
func deleteTagging(for account: Account, with feed: Feed, from container: Container?, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
func deleteTagging(for account: Account, with feed: WebFeed, from container: Container?, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
|
||||
if let folder = container as? Folder, let feedTaggingID = feed.folderRelationship?[folder.name ?? ""] {
|
||||
refreshProgress.addToNumberOfTasksAndRemaining(1)
|
||||
@ -1242,7 +1242,7 @@ private extension FeedbinAccountDelegate {
|
||||
case .success:
|
||||
DispatchQueue.main.async {
|
||||
self.clearFolderRelationship(for: feed, withFolderName: folder.name ?? "")
|
||||
folder.removeFeed(feed)
|
||||
folder.removeWebFeed(feed)
|
||||
account.addFeedIfNotInAnyFolder(feed)
|
||||
completion(.success(()))
|
||||
}
|
||||
@ -1255,14 +1255,14 @@ private extension FeedbinAccountDelegate {
|
||||
}
|
||||
} else {
|
||||
if let account = container as? Account {
|
||||
account.removeFeed(feed)
|
||||
account.removeWebFeed(feed)
|
||||
}
|
||||
completion(.success(()))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func deleteSubscription(for account: Account, with feed: Feed, from container: Container?, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
func deleteSubscription(for account: Account, with feed: WebFeed, from container: Container?, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
|
||||
// This error should never happen
|
||||
guard let subscriptionID = feed.subscriptionID else {
|
||||
@ -1276,11 +1276,11 @@ private extension FeedbinAccountDelegate {
|
||||
switch result {
|
||||
case .success:
|
||||
DispatchQueue.main.async {
|
||||
account.clearFeedMetadata(feed)
|
||||
account.removeFeed(feed)
|
||||
account.clearWebFeedMetadata(feed)
|
||||
account.removeWebFeed(feed)
|
||||
if let folders = account.folders {
|
||||
for folder in folders {
|
||||
folder.removeFeed(feed)
|
||||
folder.removeWebFeed(feed)
|
||||
}
|
||||
}
|
||||
completion(.success(()))
|
||||
|
@ -294,7 +294,7 @@ final class FeedlyAccountDelegate: AccountDelegate {
|
||||
|
||||
var createFeedRequest: FeedlyAddFeedRequest?
|
||||
|
||||
func createFeed(for account: Account, url: String, name: String?, container: Container, completion: @escaping (Result<Feed, Error>) -> Void) {
|
||||
func createWebFeed(for account: Account, url: String, name: String?, container: Container, completion: @escaping (Result<WebFeed, Error>) -> Void) {
|
||||
|
||||
let progress = refreshProgress
|
||||
progress.addToNumberOfTasksAndRemaining(1)
|
||||
@ -310,14 +310,14 @@ final class FeedlyAccountDelegate: AccountDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
func renameFeed(for account: Account, with feed: Feed, to name: String, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
func renameWebFeed(for account: Account, with feed: WebFeed, to name: String, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
let folderCollectionIds = account.folders?.filter { $0.has(feed) }.compactMap { $0.externalID }
|
||||
guard let collectionIds = folderCollectionIds, let collectionId = collectionIds.first else {
|
||||
completion(.failure(FeedlyAccountDelegateError.unableToRenameFeed(feed.nameForDisplay, name)))
|
||||
return
|
||||
}
|
||||
|
||||
let feedId = FeedlyFeedResourceId(id: feed.feedID)
|
||||
let feedId = FeedlyFeedResourceId(id: feed.webFeedID)
|
||||
let editedNameBefore = feed.editedName
|
||||
|
||||
// Adding an existing feed updates it.
|
||||
@ -339,7 +339,7 @@ final class FeedlyAccountDelegate: AccountDelegate {
|
||||
|
||||
var addFeedRequest: FeedlyAddFeedRequest?
|
||||
|
||||
func addFeed(for account: Account, with feed: Feed, to container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
func addWebFeed(for account: Account, with feed: WebFeed, to container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
|
||||
let progress = refreshProgress
|
||||
progress.addToNumberOfTasksAndRemaining(1)
|
||||
@ -362,62 +362,62 @@ final class FeedlyAccountDelegate: AccountDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
func removeFeed(for account: Account, with feed: Feed, from container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
func removeWebFeed(for account: Account, with feed: WebFeed, from container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
guard let folder = container as? Folder, let collectionId = folder.externalID else {
|
||||
return DispatchQueue.main.async {
|
||||
completion(.failure(FeedlyAccountDelegateError.unableToRemoveFeed(feed)))
|
||||
}
|
||||
}
|
||||
|
||||
caller.removeFeed(feed.feedID, fromCollectionWith: collectionId) { result in
|
||||
caller.removeFeed(feed.webFeedID, fromCollectionWith: collectionId) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
completion(.success(()))
|
||||
case .failure(let error):
|
||||
folder.addFeed(feed)
|
||||
folder.addWebFeed(feed)
|
||||
completion(.failure(error))
|
||||
}
|
||||
}
|
||||
|
||||
folder.removeFeed(feed)
|
||||
folder.removeWebFeed(feed)
|
||||
}
|
||||
|
||||
func moveFeed(for account: Account, with feed: Feed, from: Container, to: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
func moveWebFeed(for account: Account, with feed: WebFeed, from: Container, to: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
guard let from = from as? Folder, let to = to as? Folder else {
|
||||
return DispatchQueue.main.async {
|
||||
completion(.failure(FeedlyAccountDelegateError.addFeedChooseFolder))
|
||||
}
|
||||
}
|
||||
|
||||
addFeed(for: account, with: feed, to: to) { [weak self] addResult in
|
||||
addWebFeed(for: account, with: feed, to: to) { [weak self] addResult in
|
||||
switch addResult {
|
||||
// now that we have added the feed, remove it from the other collection
|
||||
case .success:
|
||||
self?.removeFeed(for: account, with: feed, from: from) { removeResult in
|
||||
self?.removeWebFeed(for: account, with: feed, from: from) { removeResult in
|
||||
switch removeResult {
|
||||
case .success:
|
||||
completion(.success(()))
|
||||
case .failure:
|
||||
from.addFeed(feed)
|
||||
from.addWebFeed(feed)
|
||||
completion(.failure(FeedlyAccountDelegateError.unableToMoveFeedBetweenFolders(feed, from, to)))
|
||||
}
|
||||
}
|
||||
case .failure(let error):
|
||||
from.addFeed(feed)
|
||||
to.removeFeed(feed)
|
||||
from.addWebFeed(feed)
|
||||
to.removeWebFeed(feed)
|
||||
completion(.failure(error))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// optimistically move the feed, undoing as appropriate to the failure
|
||||
from.removeFeed(feed)
|
||||
to.addFeed(feed)
|
||||
from.removeWebFeed(feed)
|
||||
to.addWebFeed(feed)
|
||||
}
|
||||
|
||||
func restoreFeed(for account: Account, feed: Feed, container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
if let existingFeed = account.existingFeed(withURL: feed.url) {
|
||||
account.addFeed(existingFeed, to: container) { result in
|
||||
func restoreWebFeed(for account: Account, feed: WebFeed, container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
if let existingFeed = account.existingWebFeed(withURL: feed.url) {
|
||||
account.addWebFeed(existingFeed, to: container) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
completion(.success(()))
|
||||
@ -426,7 +426,7 @@ final class FeedlyAccountDelegate: AccountDelegate {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
createFeed(for: account, url: feed.url, name: feed.editedName, container: container) { result in
|
||||
createWebFeed(for: account, url: feed.url, name: feed.editedName, container: container) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
completion(.success(()))
|
||||
@ -440,12 +440,12 @@ final class FeedlyAccountDelegate: AccountDelegate {
|
||||
func restoreFolder(for account: Account, folder: Folder, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
let group = DispatchGroup()
|
||||
|
||||
for feed in folder.topLevelFeeds {
|
||||
for feed in folder.topLevelWebFeeds {
|
||||
|
||||
folder.topLevelFeeds.remove(feed)
|
||||
folder.topLevelWebFeeds.remove(feed)
|
||||
|
||||
group.enter()
|
||||
restoreFeed(for: account, feed: feed, container: folder) { result in
|
||||
restoreWebFeed(for: account, feed: feed, container: folder) { result in
|
||||
group.leave()
|
||||
switch result {
|
||||
case .success:
|
||||
|
@ -13,11 +13,11 @@ enum FeedlyAccountDelegateError: LocalizedError {
|
||||
case unableToAddFolder(String)
|
||||
case unableToRenameFolder(String, String)
|
||||
case unableToRemoveFolder(String)
|
||||
case unableToMoveFeedBetweenFolders(Feed, Folder, Folder)
|
||||
case unableToMoveFeedBetweenFolders(WebFeed, Folder, Folder)
|
||||
case addFeedChooseFolder
|
||||
case addFeedInvalidFolder(Folder)
|
||||
case unableToRenameFeed(String, String)
|
||||
case unableToRemoveFeed(Feed)
|
||||
case unableToRemoveFeed(WebFeed)
|
||||
|
||||
var errorDescription: String? {
|
||||
switch self {
|
||||
|
@ -29,7 +29,7 @@ final class FeedlyAddFeedRequest {
|
||||
self.resourceProvider = resourceProvider
|
||||
}
|
||||
|
||||
var completionHandler: ((Result<Feed, Error>) -> ())?
|
||||
var completionHandler: ((Result<WebFeed, Error>) -> ())?
|
||||
var error: Error?
|
||||
|
||||
func feedlyOperation(_ operation: FeedlyOperation, didFailWith error: Error) {
|
||||
@ -37,17 +37,17 @@ final class FeedlyAddFeedRequest {
|
||||
}
|
||||
}
|
||||
|
||||
func addNewFeed(at url: String, name: String? = nil, completion: @escaping (Result<Feed, Error>) -> Void) {
|
||||
func addNewFeed(at url: String, name: String? = nil, completion: @escaping (Result<WebFeed, Error>) -> Void) {
|
||||
let resource = FeedlyFeedResourceId(url: url)
|
||||
self.start(resource: resource, name: name, refreshes: true, completion: completion)
|
||||
}
|
||||
|
||||
func add(existing feed: Feed, name: String? = nil, completion: @escaping (Result<Feed, Error>) -> Void) {
|
||||
let resource = FeedlyFeedResourceId(id: feed.feedID)
|
||||
func add(existing feed: WebFeed, name: String? = nil, completion: @escaping (Result<WebFeed, Error>) -> Void) {
|
||||
let resource = FeedlyFeedResourceId(id: feed.webFeedID)
|
||||
self.start(resource: resource, name: name, refreshes: false, completion: completion)
|
||||
}
|
||||
|
||||
private func start(resource: FeedlyFeedResourceId, name: String?, refreshes: Bool, completion: @escaping (Result<Feed, Error>) -> Void) {
|
||||
private func start(resource: FeedlyFeedResourceId, name: String?, refreshes: Bool, completion: @escaping (Result<WebFeed, Error>) -> Void) {
|
||||
|
||||
let (folder, collectionId): (Folder, String)
|
||||
do {
|
||||
@ -115,7 +115,7 @@ final class FeedlyAddFeedRequest {
|
||||
if let error = delegate.error {
|
||||
handler(.failure(error))
|
||||
|
||||
} else if let feed = folder.existingFeed(withFeedID: resource.id) {
|
||||
} else if let feed = folder.existingWebFeed(withWebFeedID: resource.id) {
|
||||
handler(.success(feed))
|
||||
|
||||
} else {
|
||||
|
@ -31,13 +31,13 @@ final class FeedlyCreateFeedsForCollectionFoldersOperation: FeedlyOperation {
|
||||
|
||||
let feedsBefore = Set(pairs
|
||||
.map { $0.1 }
|
||||
.flatMap { $0.topLevelFeeds })
|
||||
.flatMap { $0.topLevelWebFeeds })
|
||||
|
||||
// Remove feeds in a folder which are not in the corresponding collection.
|
||||
for (collectionFeeds, folder) in pairs {
|
||||
let feedsInFolder = folder.topLevelFeeds
|
||||
let feedsInFolder = folder.topLevelWebFeeds
|
||||
let feedsInCollection = Set(collectionFeeds.map { $0.id })
|
||||
let feedsToRemove = feedsInFolder.filter { !feedsInCollection.contains($0.feedID) }
|
||||
let feedsToRemove = feedsInFolder.filter { !feedsInCollection.contains($0.webFeedID) }
|
||||
if !feedsToRemove.isEmpty {
|
||||
folder.removeFeeds(feedsToRemove)
|
||||
// os_log(.debug, log: log, "\"%@\" - removed: %@", collection.label, feedsToRemove.map { $0.feedID }, feedsInCollection)
|
||||
@ -46,7 +46,7 @@ final class FeedlyCreateFeedsForCollectionFoldersOperation: FeedlyOperation {
|
||||
}
|
||||
|
||||
// Pair each Feed with its Folder.
|
||||
var feedsAdded = Set<Feed>()
|
||||
var feedsAdded = Set<WebFeed>()
|
||||
|
||||
let feedsAndFolders = pairs
|
||||
.map({ (collectionFeeds, folder) -> [(FeedlyFeed, Folder)] in
|
||||
@ -55,14 +55,14 @@ final class FeedlyCreateFeedsForCollectionFoldersOperation: FeedlyOperation {
|
||||
}
|
||||
})
|
||||
.flatMap { $0 }
|
||||
.compactMap { (collectionFeed, folder) -> (Feed, Folder) in
|
||||
.compactMap { (collectionFeed, folder) -> (WebFeed, Folder) in
|
||||
|
||||
// find an existing feed previously added to the account
|
||||
if let feed = account.existingFeed(withFeedID: collectionFeed.id) {
|
||||
if let feed = account.existingWebFeed(withWebFeedID: collectionFeed.id) {
|
||||
return (feed, folder)
|
||||
} else {
|
||||
// find an existing feed we created below in an earlier value
|
||||
for feed in feedsAdded where feed.feedID == collectionFeed.id {
|
||||
for feed in feedsAdded where feed.webFeedID == collectionFeed.id {
|
||||
return (feed, folder)
|
||||
}
|
||||
}
|
||||
@ -70,7 +70,7 @@ final class FeedlyCreateFeedsForCollectionFoldersOperation: FeedlyOperation {
|
||||
// no exsiting feed, create a new one
|
||||
let id = collectionFeed.id
|
||||
let url = FeedlyFeedResourceId(id: id).url
|
||||
let feed = account.createFeed(with: collectionFeed.title, url: url, feedID: id, homePageURL: collectionFeed.website)
|
||||
let feed = account.createWebFeed(with: collectionFeed.title, url: url, webFeedID: id, homePageURL: collectionFeed.website)
|
||||
|
||||
// So the same feed isn't created more than once.
|
||||
feedsAdded.insert(feed)
|
||||
@ -81,7 +81,7 @@ final class FeedlyCreateFeedsForCollectionFoldersOperation: FeedlyOperation {
|
||||
os_log(.debug, log: log, "Processing %i feeds.", feedsAndFolders.count)
|
||||
feedsAndFolders.forEach { (feed, folder) in
|
||||
if !folder.has(feed) {
|
||||
folder.addFeed(feed)
|
||||
folder.addWebFeed(feed)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -29,10 +29,10 @@ final class FeedlyUpdateAccountFeedsWithItemsOperation: FeedlyOperation {
|
||||
return
|
||||
}
|
||||
|
||||
let feedIDsAndItems = organisedItemsProvider.parsedItemsKeyedByFeedId
|
||||
let webFeedIDsAndItems = organisedItemsProvider.parsedItemsKeyedByFeedId
|
||||
|
||||
account.update(feedIDsAndItems: feedIDsAndItems, defaultRead: true) {
|
||||
os_log(.debug, log: self.log, "Updated %i feeds for \"%@\"", feedIDsAndItems.count, self.organisedItemsProvider.providerName)
|
||||
account.update(webFeedIDsAndItems: webFeedIDsAndItems, defaultRead: true) {
|
||||
os_log(.debug, log: self.log, "Updated %i feeds for \"%@\"", webFeedIDsAndItems.count, self.organisedItemsProvider.providerName)
|
||||
self.didFinish()
|
||||
}
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ import RSCore
|
||||
public final class Folder: DisplayNameProvider, Renamable, Container, UnreadCountProvider, Hashable {
|
||||
|
||||
public weak var account: Account?
|
||||
public var topLevelFeeds: Set<Feed> = Set<Feed>()
|
||||
public var topLevelWebFeeds: Set<WebFeed> = Set<WebFeed>()
|
||||
public var folders: Set<Folder>? = nil // subfolders are not supported, so this is always nil
|
||||
|
||||
public var name: String? {
|
||||
@ -80,42 +80,42 @@ public final class Folder: DisplayNameProvider, Renamable, Container, UnreadCoun
|
||||
|
||||
// MARK: Container
|
||||
|
||||
public func flattenedFeeds() -> Set<Feed> {
|
||||
public func flattenedWebFeeds() -> Set<WebFeed> {
|
||||
// Since sub-folders are not supported, it’s always the top-level feeds.
|
||||
return topLevelFeeds
|
||||
return topLevelWebFeeds
|
||||
}
|
||||
|
||||
public func objectIsChild(_ object: AnyObject) -> Bool {
|
||||
// Folders contain Feed objects only, at least for now.
|
||||
guard let feed = object as? Feed else {
|
||||
guard let feed = object as? WebFeed else {
|
||||
return false
|
||||
}
|
||||
return topLevelFeeds.contains(feed)
|
||||
return topLevelWebFeeds.contains(feed)
|
||||
}
|
||||
|
||||
public func addFeed(_ feed: Feed) {
|
||||
topLevelFeeds.insert(feed)
|
||||
public func addWebFeed(_ feed: WebFeed) {
|
||||
topLevelWebFeeds.insert(feed)
|
||||
postChildrenDidChangeNotification()
|
||||
}
|
||||
|
||||
public func addFeeds(_ feeds: Set<Feed>) {
|
||||
public func addFeeds(_ feeds: Set<WebFeed>) {
|
||||
guard !feeds.isEmpty else {
|
||||
return
|
||||
}
|
||||
topLevelFeeds.formUnion(feeds)
|
||||
topLevelWebFeeds.formUnion(feeds)
|
||||
postChildrenDidChangeNotification()
|
||||
}
|
||||
|
||||
public func removeFeed(_ feed: Feed) {
|
||||
topLevelFeeds.remove(feed)
|
||||
public func removeWebFeed(_ feed: WebFeed) {
|
||||
topLevelWebFeeds.remove(feed)
|
||||
postChildrenDidChangeNotification()
|
||||
}
|
||||
|
||||
public func removeFeeds(_ feeds: Set<Feed>) {
|
||||
public func removeFeeds(_ feeds: Set<WebFeed>) {
|
||||
guard !feeds.isEmpty else {
|
||||
return
|
||||
}
|
||||
topLevelFeeds.subtract(feeds)
|
||||
topLevelWebFeeds.subtract(feeds)
|
||||
postChildrenDidChangeNotification()
|
||||
}
|
||||
|
||||
@ -138,14 +138,14 @@ private extension Folder {
|
||||
|
||||
func updateUnreadCount() {
|
||||
var updatedUnreadCount = 0
|
||||
for feed in topLevelFeeds {
|
||||
for feed in topLevelWebFeeds {
|
||||
updatedUnreadCount += feed.unreadCount
|
||||
}
|
||||
unreadCount = updatedUnreadCount
|
||||
}
|
||||
|
||||
func childrenContain(_ feed: Feed) -> Bool {
|
||||
return topLevelFeeds.contains(feed)
|
||||
func childrenContain(_ feed: WebFeed) -> Bool {
|
||||
return topLevelWebFeeds.contains(feed)
|
||||
}
|
||||
}
|
||||
|
||||
@ -169,7 +169,7 @@ extension Folder: OPMLRepresentable {
|
||||
|
||||
var hasAtLeastOneChild = false
|
||||
|
||||
for feed in topLevelFeeds {
|
||||
for feed in topLevelWebFeeds {
|
||||
s += feed.OPMLString(indentLevel: indentLevel + 1, strictConformance: strictConformance)
|
||||
hasAtLeastOneChild = true
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ final class LocalAccountDelegate: AccountDelegate {
|
||||
}
|
||||
|
||||
func refreshAll(for account: Account, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
refresher.refreshFeeds(account.flattenedFeeds()) {
|
||||
refresher.refreshFeeds(account.flattenedWebFeeds()) {
|
||||
completion(.success(()))
|
||||
}
|
||||
}
|
||||
@ -91,7 +91,7 @@ final class LocalAccountDelegate: AccountDelegate {
|
||||
|
||||
}
|
||||
|
||||
func createFeed(for account: Account, url urlString: String, name: String?, container: Container, completion: @escaping (Result<Feed, Error>) -> Void) {
|
||||
func createWebFeed(for account: Account, url urlString: String, name: String?, container: Container, completion: @escaping (Result<WebFeed, Error>) -> Void) {
|
||||
guard let url = URL(string: urlString) else {
|
||||
completion(.failure(LocalAccountDelegateError.invalidParameter))
|
||||
return
|
||||
@ -109,13 +109,13 @@ final class LocalAccountDelegate: AccountDelegate {
|
||||
return
|
||||
}
|
||||
|
||||
if account.hasFeed(withURL: bestFeedSpecifier.urlString) {
|
||||
if account.hasWebFeed(withURL: bestFeedSpecifier.urlString) {
|
||||
self.refreshProgress.completeTask()
|
||||
completion(.failure(AccountError.createErrorAlreadySubscribed))
|
||||
return
|
||||
}
|
||||
|
||||
let feed = account.createFeed(with: nil, url: url.absoluteString, feedID: url.absoluteString, homePageURL: nil)
|
||||
let feed = account.createWebFeed(with: nil, url: url.absoluteString, webFeedID: url.absoluteString, homePageURL: nil)
|
||||
|
||||
InitialFeedDownloader.download(url) { parsedFeed in
|
||||
self.refreshProgress.completeTask()
|
||||
@ -126,7 +126,7 @@ final class LocalAccountDelegate: AccountDelegate {
|
||||
|
||||
feed.editedName = name
|
||||
|
||||
container.addFeed(feed)
|
||||
container.addWebFeed(feed)
|
||||
completion(.success(feed))
|
||||
|
||||
}
|
||||
@ -140,29 +140,29 @@ final class LocalAccountDelegate: AccountDelegate {
|
||||
|
||||
}
|
||||
|
||||
func renameFeed(for account: Account, with feed: Feed, to name: String, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
func renameWebFeed(for account: Account, with feed: WebFeed, to name: String, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
feed.editedName = name
|
||||
completion(.success(()))
|
||||
}
|
||||
|
||||
func removeFeed(for account: Account, with feed: Feed, from container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
container.removeFeed(feed)
|
||||
func removeWebFeed(for account: Account, with feed: WebFeed, from container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
container.removeWebFeed(feed)
|
||||
completion(.success(()))
|
||||
}
|
||||
|
||||
func moveFeed(for account: Account, with feed: Feed, from: Container, to: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
from.removeFeed(feed)
|
||||
to.addFeed(feed)
|
||||
func moveWebFeed(for account: Account, with feed: WebFeed, from: Container, to: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
from.removeWebFeed(feed)
|
||||
to.addWebFeed(feed)
|
||||
completion(.success(()))
|
||||
}
|
||||
|
||||
func addFeed(for account: Account, with feed: Feed, to container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
container.addFeed(feed)
|
||||
func addWebFeed(for account: Account, with feed: WebFeed, to container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
container.addWebFeed(feed)
|
||||
completion(.success(()))
|
||||
}
|
||||
|
||||
func restoreFeed(for account: Account, feed: Feed, container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
container.addFeed(feed)
|
||||
func restoreWebFeed(for account: Account, feed: WebFeed, container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
container.addWebFeed(feed)
|
||||
completion(.success(()))
|
||||
}
|
||||
|
||||
|
@ -24,7 +24,7 @@ final class LocalAccountRefresher {
|
||||
return downloadSession.progress
|
||||
}
|
||||
|
||||
public func refreshFeeds(_ feeds: Set<Feed>, completion: @escaping () -> Void) {
|
||||
public func refreshFeeds(_ feeds: Set<WebFeed>, completion: @escaping () -> Void) {
|
||||
self.completion = completion
|
||||
downloadSession.downloadObjects(feeds as NSSet)
|
||||
}
|
||||
@ -40,7 +40,7 @@ final class LocalAccountRefresher {
|
||||
extension LocalAccountRefresher: DownloadSessionDelegate {
|
||||
|
||||
func downloadSession(_ downloadSession: DownloadSession, requestForRepresentedObject representedObject: AnyObject) -> URLRequest? {
|
||||
guard let feed = representedObject as? Feed else {
|
||||
guard let feed = representedObject as? WebFeed else {
|
||||
return nil
|
||||
}
|
||||
guard let url = URL(string: feed.url) else {
|
||||
@ -56,7 +56,7 @@ extension LocalAccountRefresher: DownloadSessionDelegate {
|
||||
}
|
||||
|
||||
func downloadSession(_ downloadSession: DownloadSession, downloadDidCompleteForRepresentedObject representedObject: AnyObject, response: URLResponse?, data: Data, error: NSError?) {
|
||||
guard let feed = representedObject as? Feed, !data.isEmpty else {
|
||||
guard let feed = representedObject as? WebFeed, !data.isEmpty else {
|
||||
return
|
||||
}
|
||||
|
||||
@ -86,7 +86,7 @@ extension LocalAccountRefresher: DownloadSessionDelegate {
|
||||
}
|
||||
|
||||
func downloadSession(_ downloadSession: DownloadSession, shouldContinueAfterReceivingData data: Data, representedObject: AnyObject) -> Bool {
|
||||
guard let feed = representedObject as? Feed else {
|
||||
guard let feed = representedObject as? WebFeed else {
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -43,7 +43,7 @@ private extension OPMLFile {
|
||||
func loadCallback() {
|
||||
guard let opmlItems = parsedOPMLItems() else { return }
|
||||
BatchUpdate.shared.perform {
|
||||
account.topLevelFeeds.removeAll()
|
||||
account.topLevelWebFeeds.removeAll()
|
||||
account.loadOPMLItems(opmlItems, parentFolder: nil)
|
||||
}
|
||||
}
|
||||
|
@ -227,9 +227,9 @@ final class ReaderAPIAccountDelegate: AccountDelegate {
|
||||
func removeFolder(for account: Account, with folder: Folder, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
let group = DispatchGroup()
|
||||
|
||||
for feed in folder.topLevelFeeds {
|
||||
for feed in folder.topLevelWebFeeds {
|
||||
group.enter()
|
||||
removeFeed(for: account, with: feed, from: folder) { result in
|
||||
removeWebFeed(for: account, with: feed, from: folder) { result in
|
||||
group.leave()
|
||||
switch result {
|
||||
case .success:
|
||||
@ -256,7 +256,7 @@ final class ReaderAPIAccountDelegate: AccountDelegate {
|
||||
|
||||
}
|
||||
|
||||
func createFeed(for account: Account, url: String, name: String?, container: Container, completion: @escaping (Result<Feed, Error>) -> Void) {
|
||||
func createWebFeed(for account: Account, url: String, name: String?, container: Container, completion: @escaping (Result<WebFeed, Error>) -> Void) {
|
||||
|
||||
caller.createSubscription(url: url) { result in
|
||||
switch result {
|
||||
@ -284,7 +284,7 @@ final class ReaderAPIAccountDelegate: AccountDelegate {
|
||||
|
||||
}
|
||||
|
||||
func renameFeed(for account: Account, with feed: Feed, to name: String, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
func renameWebFeed(for account: Account, with feed: WebFeed, to name: String, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
|
||||
// This error should never happen
|
||||
guard let subscriptionID = feed.subscriptionID else {
|
||||
@ -309,23 +309,23 @@ final class ReaderAPIAccountDelegate: AccountDelegate {
|
||||
|
||||
}
|
||||
|
||||
func removeFeed(for account: Account, with feed: Feed, from container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
func removeWebFeed(for account: Account, with feed: WebFeed, from container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
if feed.folderRelationship?.count ?? 0 > 1 {
|
||||
deleteTagging(for: account, with: feed, from: container, completion: completion)
|
||||
} else {
|
||||
account.clearFeedMetadata(feed)
|
||||
account.clearWebFeedMetadata(feed)
|
||||
deleteSubscription(for: account, with: feed, from: container, completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
func moveFeed(for account: Account, with feed: Feed, from: Container, to: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
func moveWebFeed(for account: Account, with feed: WebFeed, from: Container, to: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
if from is Account {
|
||||
addFeed(for: account, with: feed, to: to, completion: completion)
|
||||
addWebFeed(for: account, with: feed, to: to, completion: completion)
|
||||
} else {
|
||||
deleteTagging(for: account, with: feed, from: from) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
self.addFeed(for: account, with: feed, to: to, completion: completion)
|
||||
self.addWebFeed(for: account, with: feed, to: to, completion: completion)
|
||||
case .failure(let error):
|
||||
completion(.failure(error))
|
||||
}
|
||||
@ -333,7 +333,7 @@ final class ReaderAPIAccountDelegate: AccountDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
func addFeed(for account: Account, with feed: Feed, to container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
func addWebFeed(for account: Account, with feed: WebFeed, to container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
|
||||
if let folder = container as? Folder, let feedName = feed.subscriptionID {
|
||||
caller.createTagging(subscriptionID: feedName, tagName: folder.name ?? "") { result in
|
||||
@ -341,8 +341,8 @@ final class ReaderAPIAccountDelegate: AccountDelegate {
|
||||
case .success:
|
||||
DispatchQueue.main.async {
|
||||
self.saveFolderRelationship(for: feed, withFolderName: folder.name ?? "", id: feed.subscriptionID!)
|
||||
account.removeFeed(feed)
|
||||
folder.addFeed(feed)
|
||||
account.removeWebFeed(feed)
|
||||
folder.addWebFeed(feed)
|
||||
completion(.success(()))
|
||||
}
|
||||
case .failure(let error):
|
||||
@ -363,9 +363,9 @@ final class ReaderAPIAccountDelegate: AccountDelegate {
|
||||
|
||||
}
|
||||
|
||||
func restoreFeed(for account: Account, feed: Feed, container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
func restoreWebFeed(for account: Account, feed: WebFeed, container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
|
||||
createFeed(for: account, url: feed.url, name: feed.editedName, container: container) { result in
|
||||
createWebFeed(for: account, url: feed.url, name: feed.editedName, container: container) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
completion(.success(()))
|
||||
@ -381,12 +381,12 @@ final class ReaderAPIAccountDelegate: AccountDelegate {
|
||||
account.addFolder(folder)
|
||||
let group = DispatchGroup()
|
||||
|
||||
for feed in folder.topLevelFeeds {
|
||||
for feed in folder.topLevelWebFeeds {
|
||||
|
||||
group.enter()
|
||||
addFeed(for: account, with: feed, to: folder) { result in
|
||||
if account.topLevelFeeds.contains(feed) {
|
||||
account.removeFeed(feed)
|
||||
addWebFeed(for: account, with: feed, to: folder) { result in
|
||||
if account.topLevelWebFeeds.contains(feed) {
|
||||
account.removeWebFeed(feed)
|
||||
}
|
||||
group.leave()
|
||||
}
|
||||
@ -469,8 +469,8 @@ private extension ReaderAPIAccountDelegate {
|
||||
if let folders = account.folders {
|
||||
folders.forEach { folder in
|
||||
if !tagNames.contains(folder.name ?? "") {
|
||||
for feed in folder.topLevelFeeds {
|
||||
account.addFeed(feed)
|
||||
for feed in folder.topLevelWebFeeds {
|
||||
account.addWebFeed(feed)
|
||||
clearFolderRelationship(for: feed, withFolderName: folder.name ?? "")
|
||||
}
|
||||
account.removeFolder(folder)
|
||||
@ -532,17 +532,17 @@ private extension ReaderAPIAccountDelegate {
|
||||
// Remove any feeds that are no longer in the subscriptions
|
||||
if let folders = account.folders {
|
||||
for folder in folders {
|
||||
for feed in folder.topLevelFeeds {
|
||||
if !subFeedIds.contains(feed.feedID) {
|
||||
folder.removeFeed(feed)
|
||||
for feed in folder.topLevelWebFeeds {
|
||||
if !subFeedIds.contains(feed.webFeedID) {
|
||||
folder.removeWebFeed(feed)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for feed in account.topLevelFeeds {
|
||||
if !subFeedIds.contains(feed.feedID) {
|
||||
account.removeFeed(feed)
|
||||
for feed in account.topLevelWebFeeds {
|
||||
if !subFeedIds.contains(feed.webFeedID) {
|
||||
account.removeWebFeed(feed)
|
||||
}
|
||||
}
|
||||
|
||||
@ -550,14 +550,14 @@ private extension ReaderAPIAccountDelegate {
|
||||
subscriptions.forEach { subscription in
|
||||
|
||||
let subFeedId = String(subscription.feedID)
|
||||
if let feed = account.existingFeed(withFeedID: subFeedId) {
|
||||
if let feed = account.existingWebFeed(withWebFeedID: subFeedId) {
|
||||
feed.name = subscription.name
|
||||
feed.homePageURL = subscription.homePageURL
|
||||
} else {
|
||||
let feed = account.createFeed(with: subscription.name, url: subscription.url, feedID: subFeedId, homePageURL: subscription.homePageURL)
|
||||
let feed = account.createWebFeed(with: subscription.name, url: subscription.url, webFeedID: subFeedId, homePageURL: subscription.homePageURL)
|
||||
feed.iconURL = subscription.iconURL
|
||||
feed.subscriptionID = String(subscription.feedID)
|
||||
account.addFeed(feed)
|
||||
account.addWebFeed(feed)
|
||||
}
|
||||
|
||||
}
|
||||
@ -606,25 +606,25 @@ private extension ReaderAPIAccountDelegate {
|
||||
let taggingFeedIDs = groupedTaggings.map { String($0.feedID) }
|
||||
|
||||
// Move any feeds not in the folder to the account
|
||||
for feed in folder.topLevelFeeds {
|
||||
if !taggingFeedIDs.contains(feed.feedID) {
|
||||
folder.removeFeed(feed)
|
||||
for feed in folder.topLevelWebFeeds {
|
||||
if !taggingFeedIDs.contains(feed.webFeedID) {
|
||||
folder.removeWebFeed(feed)
|
||||
clearFolderRelationship(for: feed, withFolderName: folder.name ?? "")
|
||||
account.addFeed(feed)
|
||||
account.addWebFeed(feed)
|
||||
}
|
||||
}
|
||||
|
||||
// Add any feeds not in the folder
|
||||
let folderFeedIds = folder.topLevelFeeds.map { $0.feedID }
|
||||
let folderFeedIds = folder.topLevelWebFeeds.map { $0.webFeedID }
|
||||
|
||||
for subscription in groupedTaggings {
|
||||
let taggingFeedID = String(subscription.feedID)
|
||||
if !folderFeedIds.contains(taggingFeedID) {
|
||||
guard let feed = account.existingFeed(withFeedID: taggingFeedID) else {
|
||||
guard let feed = account.existingWebFeed(withWebFeedID: taggingFeedID) else {
|
||||
continue
|
||||
}
|
||||
saveFolderRelationship(for: feed, withFolderName: folderName, id: String(subscription.feedID))
|
||||
folder.addFeed(feed)
|
||||
folder.addWebFeed(feed)
|
||||
}
|
||||
}
|
||||
|
||||
@ -633,9 +633,9 @@ private extension ReaderAPIAccountDelegate {
|
||||
let taggedFeedIDs = Set(subscriptions.map { String($0.feedID) })
|
||||
|
||||
// Remove all feeds from the account container that have a tag
|
||||
for feed in account.topLevelFeeds {
|
||||
if taggedFeedIDs.contains(feed.feedID) {
|
||||
account.removeFeed(feed)
|
||||
for feed in account.topLevelWebFeeds {
|
||||
if taggedFeedIDs.contains(feed.webFeedID) {
|
||||
account.removeWebFeed(feed)
|
||||
}
|
||||
}
|
||||
|
||||
@ -679,14 +679,14 @@ private extension ReaderAPIAccountDelegate {
|
||||
|
||||
|
||||
|
||||
func clearFolderRelationship(for feed: Feed, withFolderName folderName: String) {
|
||||
func clearFolderRelationship(for feed: WebFeed, withFolderName folderName: String) {
|
||||
if var folderRelationship = feed.folderRelationship {
|
||||
folderRelationship[folderName] = nil
|
||||
feed.folderRelationship = folderRelationship
|
||||
}
|
||||
}
|
||||
|
||||
func saveFolderRelationship(for feed: Feed, withFolderName folderName: String, id: String) {
|
||||
func saveFolderRelationship(for feed: WebFeed, withFolderName folderName: String, id: String) {
|
||||
if var folderRelationship = feed.folderRelationship {
|
||||
folderRelationship[folderName] = id
|
||||
feed.folderRelationship = folderRelationship
|
||||
@ -695,7 +695,7 @@ private extension ReaderAPIAccountDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
func decideBestFeedChoice(account: Account, url: String, name: String?, container: Container, choices: [ReaderAPISubscriptionChoice], completion: @escaping (Result<Feed, Error>) -> Void) {
|
||||
func decideBestFeedChoice(account: Account, url: String, name: String?, container: Container, choices: [ReaderAPISubscriptionChoice], completion: @escaping (Result<WebFeed, Error>) -> Void) {
|
||||
|
||||
let feedSpecifiers: [FeedSpecifier] = choices.map { choice in
|
||||
let source = url == choice.url ? FeedSpecifier.Source.UserEntered : FeedSpecifier.Source.HTMLLink
|
||||
@ -705,7 +705,7 @@ private extension ReaderAPIAccountDelegate {
|
||||
|
||||
if let bestSpecifier = FeedSpecifier.bestFeed(in: Set(feedSpecifiers)) {
|
||||
if let bestSubscription = choices.filter({ bestSpecifier.urlString == $0.url }).first {
|
||||
createFeed(for: account, url: bestSubscription.url, name: name, container: container, completion: completion)
|
||||
createWebFeed(for: account, url: bestSubscription.url, name: name, container: container, completion: completion)
|
||||
} else {
|
||||
DispatchQueue.main.async {
|
||||
completion(.failure(ReaderAPIAccountDelegateError.invalidParameter))
|
||||
@ -719,18 +719,18 @@ private extension ReaderAPIAccountDelegate {
|
||||
|
||||
}
|
||||
|
||||
func createFeed( account: Account, subscription sub: ReaderAPISubscription, name: String?, container: Container, completion: @escaping (Result<Feed, Error>) -> Void) {
|
||||
func createFeed( account: Account, subscription sub: ReaderAPISubscription, name: String?, container: Container, completion: @escaping (Result<WebFeed, Error>) -> Void) {
|
||||
|
||||
DispatchQueue.main.async {
|
||||
|
||||
let feed = account.createFeed(with: sub.name, url: sub.url, feedID: String(sub.feedID), homePageURL: sub.homePageURL)
|
||||
let feed = account.createWebFeed(with: sub.name, url: sub.url, webFeedID: String(sub.feedID), homePageURL: sub.homePageURL)
|
||||
feed.subscriptionID = String(sub.feedID)
|
||||
|
||||
account.addFeed(feed, to: container) { result in
|
||||
account.addWebFeed(feed, to: container) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
if let name = name {
|
||||
account.renameFeed(feed, to: name) { result in
|
||||
account.renameWebFeed(feed, to: name) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
self.initialFeedDownload(account: account, feed: feed, completion: completion)
|
||||
@ -750,10 +750,10 @@ private extension ReaderAPIAccountDelegate {
|
||||
|
||||
}
|
||||
|
||||
func initialFeedDownload( account: Account, feed: Feed, completion: @escaping (Result<Feed, Error>) -> Void) {
|
||||
func initialFeedDownload( account: Account, feed: WebFeed, completion: @escaping (Result<WebFeed, Error>) -> Void) {
|
||||
|
||||
// Download the initial articles
|
||||
self.caller.retrieveEntries(feedID: feed.feedID) { result in
|
||||
self.caller.retrieveEntries(webFeedID: feed.webFeedID) { result in
|
||||
|
||||
switch result {
|
||||
case .success(let (entries, page)):
|
||||
@ -871,8 +871,8 @@ private extension ReaderAPIAccountDelegate {
|
||||
|
||||
func processEntries(account: Account, entries: [ReaderAPIEntry]?, completion: @escaping (() -> Void)) {
|
||||
let parsedItems = mapEntriesToParsedItems(account: account, entries: entries)
|
||||
let feedIDsAndItems = Dictionary(grouping: parsedItems, by: { item in item.feedURL } ).mapValues { Set($0) }
|
||||
account.update(feedIDsAndItems: feedIDsAndItems, defaultRead: true, completion: completion)
|
||||
let webFeedIDsAndItems = Dictionary(grouping: parsedItems, by: { item in item.feedURL } ).mapValues { Set($0) }
|
||||
account.update(webFeedIDsAndItems: webFeedIDsAndItems, defaultRead: true, completion: completion)
|
||||
}
|
||||
|
||||
func mapEntriesToParsedItems(account: Account, entries: [ReaderAPIEntry]?) -> Set<ParsedItem> {
|
||||
@ -953,7 +953,7 @@ private extension ReaderAPIAccountDelegate {
|
||||
|
||||
|
||||
|
||||
func deleteTagging(for account: Account, with feed: Feed, from container: Container?, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
func deleteTagging(for account: Account, with feed: WebFeed, from container: Container?, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
|
||||
if let folder = container as? Folder, let feedName = feed.subscriptionID {
|
||||
caller.deleteTagging(subscriptionID: feedName, tagName: folder.name ?? "") { result in
|
||||
@ -961,7 +961,7 @@ private extension ReaderAPIAccountDelegate {
|
||||
case .success:
|
||||
DispatchQueue.main.async {
|
||||
self.clearFolderRelationship(for: feed, withFolderName: folder.name ?? "")
|
||||
folder.removeFeed(feed)
|
||||
folder.removeWebFeed(feed)
|
||||
account.addFeedIfNotInAnyFolder(feed)
|
||||
completion(.success(()))
|
||||
}
|
||||
@ -974,14 +974,14 @@ private extension ReaderAPIAccountDelegate {
|
||||
}
|
||||
} else {
|
||||
if let account = container as? Account {
|
||||
account.removeFeed(feed)
|
||||
account.removeWebFeed(feed)
|
||||
}
|
||||
completion(.success(()))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func deleteSubscription(for account: Account, with feed: Feed, from container: Container?, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
func deleteSubscription(for account: Account, with feed: WebFeed, from container: Container?, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
|
||||
// This error should never happen
|
||||
guard let subscriptionID = feed.subscriptionID else {
|
||||
@ -993,10 +993,10 @@ private extension ReaderAPIAccountDelegate {
|
||||
switch result {
|
||||
case .success:
|
||||
DispatchQueue.main.async {
|
||||
account.removeFeed(feed)
|
||||
account.removeWebFeed(feed)
|
||||
if let folders = account.folders {
|
||||
for folder in folders {
|
||||
folder.removeFeed(feed)
|
||||
folder.removeWebFeed(feed)
|
||||
}
|
||||
}
|
||||
completion(.success(()))
|
||||
|
@ -594,7 +594,7 @@ final class ReaderAPICaller: NSObject {
|
||||
|
||||
}
|
||||
|
||||
func retrieveEntries(feedID: String, completion: @escaping (Result<([ReaderAPIEntry]?, String?), Error>) -> Void) {
|
||||
func retrieveEntries(webFeedID: String, completion: @escaping (Result<([ReaderAPIEntry]?, String?), Error>) -> Void) {
|
||||
|
||||
let since = Calendar.current.date(byAdding: .month, value: -3, to: Date()) ?? Date()
|
||||
|
||||
@ -606,7 +606,7 @@ final class ReaderAPICaller: NSObject {
|
||||
let url = baseURL
|
||||
.appendingPathComponent(ReaderAPIEndpoints.itemIds.rawValue)
|
||||
.appendingQueryItems([
|
||||
URLQueryItem(name: "s", value: feedID),
|
||||
URLQueryItem(name: "s", value: webFeedID),
|
||||
URLQueryItem(name: "ot", value: String(since.timeIntervalSince1970)),
|
||||
URLQueryItem(name: "output", value: "json")
|
||||
])
|
||||
|
@ -1,5 +1,5 @@
|
||||
//
|
||||
// Feed.swift
|
||||
// WebFeed.swift
|
||||
// NetNewsWire
|
||||
//
|
||||
// Created by Brent Simmons on 7/1/17.
|
||||
@ -11,17 +11,17 @@ import RSCore
|
||||
import RSWeb
|
||||
import Articles
|
||||
|
||||
public final class Feed: DisplayNameProvider, Renamable, UnreadCountProvider, Hashable {
|
||||
public final class WebFeed: DisplayNameProvider, Renamable, UnreadCountProvider, Hashable {
|
||||
|
||||
public weak var account: Account?
|
||||
public let url: String
|
||||
|
||||
public var feedID: String {
|
||||
public var webFeedID: String {
|
||||
get {
|
||||
return metadata.feedID
|
||||
return metadata.webFeedID
|
||||
}
|
||||
set {
|
||||
metadata.feedID = newValue
|
||||
metadata.webFeedID = newValue
|
||||
}
|
||||
}
|
||||
|
||||
@ -176,7 +176,7 @@ public final class Feed: DisplayNameProvider, Renamable, UnreadCountProvider, Ha
|
||||
|
||||
public func rename(to newName: String, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
guard let account = account else { return }
|
||||
account.renameFeed(self, to: newName, completion: completion)
|
||||
account.renameWebFeed(self, to: newName, completion: completion)
|
||||
}
|
||||
|
||||
// MARK: - UnreadCountProvider
|
||||
@ -194,7 +194,7 @@ public final class Feed: DisplayNameProvider, Renamable, UnreadCountProvider, Ha
|
||||
}
|
||||
}
|
||||
|
||||
var metadata: FeedMetadata
|
||||
var metadata: WebFeedMetadata
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
@ -202,7 +202,7 @@ public final class Feed: DisplayNameProvider, Renamable, UnreadCountProvider, Ha
|
||||
|
||||
// MARK: - Init
|
||||
|
||||
init(account: Account, url: String, metadata: FeedMetadata) {
|
||||
init(account: Account, url: String, metadata: WebFeedMetadata) {
|
||||
self.account = account
|
||||
self.accountID = account.accountID
|
||||
self.url = url
|
||||
@ -219,20 +219,20 @@ public final class Feed: DisplayNameProvider, Renamable, UnreadCountProvider, Ha
|
||||
// MARK: - Hashable
|
||||
|
||||
public func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(feedID)
|
||||
hasher.combine(webFeedID)
|
||||
hasher.combine(accountID)
|
||||
}
|
||||
|
||||
// MARK: - Equatable
|
||||
|
||||
public class func ==(lhs: Feed, rhs: Feed) -> Bool {
|
||||
return lhs.feedID == rhs.feedID && lhs.accountID == rhs.accountID
|
||||
public class func ==(lhs: WebFeed, rhs: WebFeed) -> Bool {
|
||||
return lhs.webFeedID == rhs.webFeedID && lhs.accountID == rhs.accountID
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - OPMLRepresentable
|
||||
|
||||
extension Feed: OPMLRepresentable {
|
||||
extension WebFeed: OPMLRepresentable {
|
||||
|
||||
public func OPMLString(indentLevel: Int, strictConformance: Bool) -> String {
|
||||
// https://github.com/brentsimmons/NetNewsWire/issues/527
|
||||
@ -260,9 +260,9 @@ extension Feed: OPMLRepresentable {
|
||||
}
|
||||
}
|
||||
|
||||
extension Set where Element == Feed {
|
||||
extension Set where Element == WebFeed {
|
||||
|
||||
func feedIDs() -> Set<String> {
|
||||
return Set<String>(map { $0.feedID })
|
||||
func webFeedIDs() -> Set<String> {
|
||||
return Set<String>(map { $0.webFeedID })
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
//
|
||||
// FeedMetadata.swift
|
||||
// WebFeedMetadata.swift
|
||||
// NetNewsWire
|
||||
//
|
||||
// Created by Brent Simmons on 3/12/19.
|
||||
@ -10,14 +10,14 @@ import Foundation
|
||||
import RSWeb
|
||||
import Articles
|
||||
|
||||
protocol FeedMetadataDelegate: class {
|
||||
func valueDidChange(_ feedMetadata: FeedMetadata, key: FeedMetadata.CodingKeys)
|
||||
protocol WebFeedMetadataDelegate: class {
|
||||
func valueDidChange(_ feedMetadata: WebFeedMetadata, key: WebFeedMetadata.CodingKeys)
|
||||
}
|
||||
|
||||
final class FeedMetadata: Codable {
|
||||
final class WebFeedMetadata: Codable {
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case feedID
|
||||
case webFeedID = "feedID"
|
||||
case homePageURL
|
||||
case iconURL
|
||||
case faviconURL
|
||||
@ -31,10 +31,10 @@ final class FeedMetadata: Codable {
|
||||
case folderRelationship
|
||||
}
|
||||
|
||||
var feedID: String {
|
||||
var webFeedID: String {
|
||||
didSet {
|
||||
if feedID != oldValue {
|
||||
valueDidChange(.feedID)
|
||||
if webFeedID != oldValue {
|
||||
valueDidChange(.webFeedID)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -128,10 +128,10 @@ final class FeedMetadata: Codable {
|
||||
}
|
||||
}
|
||||
|
||||
weak var delegate: FeedMetadataDelegate?
|
||||
weak var delegate: WebFeedMetadataDelegate?
|
||||
|
||||
init(feedID: String) {
|
||||
self.feedID = feedID
|
||||
init(webFeedID: String) {
|
||||
self.webFeedID = webFeedID
|
||||
}
|
||||
|
||||
func valueDidChange(_ key: CodingKeys) {
|
@ -1,5 +1,5 @@
|
||||
//
|
||||
// FeedMetadataFile.swift
|
||||
// WebFeedMetadataFile.swift
|
||||
// Account
|
||||
//
|
||||
// Created by Maurice Parker on 9/13/19.
|
||||
@ -10,9 +10,9 @@ import Foundation
|
||||
import os.log
|
||||
import RSCore
|
||||
|
||||
final class FeedMetadataFile {
|
||||
final class WebFeedMetadataFile {
|
||||
|
||||
private var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "feedMetadataFile")
|
||||
private var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "webFeedMetadataFile")
|
||||
|
||||
private let fileURL: URL
|
||||
private let account: Account
|
||||
@ -37,7 +37,7 @@ final class FeedMetadataFile {
|
||||
|
||||
}
|
||||
|
||||
private extension FeedMetadataFile {
|
||||
private extension WebFeedMetadataFile {
|
||||
|
||||
func loadCallback() {
|
||||
|
||||
@ -47,11 +47,11 @@ private extension FeedMetadataFile {
|
||||
fileCoordinator.coordinate(readingItemAt: fileURL, options: [], error: errorPointer, byAccessor: { readURL in
|
||||
if let fileData = try? Data(contentsOf: readURL) {
|
||||
let decoder = PropertyListDecoder()
|
||||
account.feedMetadata = (try? decoder.decode(Account.FeedMetadataDictionary.self, from: fileData)) ?? Account.FeedMetadataDictionary()
|
||||
account.webFeedMetadata = (try? decoder.decode(Account.WebFeedMetadataDictionary.self, from: fileData)) ?? Account.WebFeedMetadataDictionary()
|
||||
}
|
||||
account.feedMetadata.values.forEach { $0.delegate = account }
|
||||
account.webFeedMetadata.values.forEach { $0.delegate = account }
|
||||
if !account.startingUp {
|
||||
account.resetFeedMetadataAndUnreadCounts()
|
||||
account.resetWebFeedMetadataAndUnreadCounts()
|
||||
}
|
||||
})
|
||||
|
||||
@ -87,10 +87,10 @@ private extension FeedMetadataFile {
|
||||
}
|
||||
}
|
||||
|
||||
private func metadataForOnlySubscribedToFeeds() -> Account.FeedMetadataDictionary {
|
||||
let feedIDs = account.idToFeedDictionary.keys
|
||||
return account.feedMetadata.filter { (feedID: String, metadata: FeedMetadata) -> Bool in
|
||||
return feedIDs.contains(metadata.feedID)
|
||||
private func metadataForOnlySubscribedToFeeds() -> Account.WebFeedMetadataDictionary {
|
||||
let webFeedIDs = account.idToWebFeedDictionary.keys
|
||||
return account.webFeedMetadata.filter { (feedID: String, metadata: WebFeedMetadata) -> Bool in
|
||||
return webFeedIDs.contains(metadata.webFeedID)
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ public struct Article: Hashable {
|
||||
|
||||
public let articleID: String // Unique database ID (possibly sync service ID)
|
||||
public let accountID: String
|
||||
public let feedID: String // Likely a URL, but not necessarily
|
||||
public let webFeedID: String // Likely a URL, but not necessarily
|
||||
public let uniqueID: String // Unique per feed (RSS guid, for example)
|
||||
public let title: String?
|
||||
public let contentHTML: String?
|
||||
@ -30,10 +30,10 @@ public struct Article: Hashable {
|
||||
public let attachments: Set<Attachment>?
|
||||
public let status: ArticleStatus
|
||||
|
||||
public init(accountID: String, articleID: String?, feedID: String, uniqueID: String, title: String?, contentHTML: String?, contentText: String?, url: String?, externalURL: String?, summary: String?, imageURL: String?, bannerImageURL: String?, datePublished: Date?, dateModified: Date?, authors: Set<Author>?, attachments: Set<Attachment>?, status: ArticleStatus) {
|
||||
public init(accountID: String, articleID: String?, webFeedID: String, uniqueID: String, title: String?, contentHTML: String?, contentText: String?, url: String?, externalURL: String?, summary: String?, imageURL: String?, bannerImageURL: String?, datePublished: Date?, dateModified: Date?, authors: Set<Author>?, attachments: Set<Attachment>?, status: ArticleStatus) {
|
||||
|
||||
self.accountID = accountID
|
||||
self.feedID = feedID
|
||||
self.webFeedID = webFeedID
|
||||
self.uniqueID = uniqueID
|
||||
self.title = title
|
||||
self.contentHTML = contentHTML
|
||||
@ -53,12 +53,12 @@ public struct Article: Hashable {
|
||||
self.articleID = articleID
|
||||
}
|
||||
else {
|
||||
self.articleID = Article.calculatedArticleID(feedID: feedID, uniqueID: uniqueID)
|
||||
self.articleID = Article.calculatedArticleID(webFeedID: webFeedID, uniqueID: uniqueID)
|
||||
}
|
||||
}
|
||||
|
||||
public static func calculatedArticleID(feedID: String, uniqueID: String) -> String {
|
||||
return databaseIDWithString("\(feedID) \(uniqueID)")
|
||||
public static func calculatedArticleID(webFeedID: String, uniqueID: String) -> String {
|
||||
return databaseIDWithString("\(webFeedID) \(uniqueID)")
|
||||
}
|
||||
|
||||
// MARK: - Hashable
|
||||
|
@ -16,7 +16,7 @@ import Articles
|
||||
|
||||
// Main thread only.
|
||||
|
||||
public typealias UnreadCountDictionary = [String: Int] // feedID: unreadCount
|
||||
public typealias UnreadCountDictionary = [String: Int] // webFeedID: unreadCount
|
||||
public typealias UnreadCountCompletionBlock = (UnreadCountDictionary) -> Void
|
||||
public typealias UpdateArticlesCompletionBlock = (Set<Article>?, Set<Article>?) -> Void //newArticles, updatedArticles
|
||||
|
||||
@ -44,28 +44,28 @@ public final class ArticlesDatabase {
|
||||
|
||||
// MARK: - Fetching Articles
|
||||
|
||||
public func fetchArticles(_ feedID: String) -> Set<Article> {
|
||||
return articlesTable.fetchArticles(feedID)
|
||||
public func fetchArticles(_ webFeedID: String) -> Set<Article> {
|
||||
return articlesTable.fetchArticles(webFeedID)
|
||||
}
|
||||
|
||||
public func fetchArticles(articleIDs: Set<String>) -> Set<Article> {
|
||||
return articlesTable.fetchArticles(articleIDs: articleIDs)
|
||||
}
|
||||
|
||||
public func fetchUnreadArticles(_ feedIDs: Set<String>) -> Set<Article> {
|
||||
return articlesTable.fetchUnreadArticles(feedIDs)
|
||||
public func fetchUnreadArticles(_ webFeedID: Set<String>) -> Set<Article> {
|
||||
return articlesTable.fetchUnreadArticles(webFeedID)
|
||||
}
|
||||
|
||||
public func fetchTodayArticles(_ feedIDs: Set<String>) -> Set<Article> {
|
||||
return articlesTable.fetchArticlesSince(feedIDs, todayCutoffDate())
|
||||
public func fetchTodayArticles(_ webFeedIDs: Set<String>) -> Set<Article> {
|
||||
return articlesTable.fetchArticlesSince(webFeedIDs, todayCutoffDate())
|
||||
}
|
||||
|
||||
public func fetchStarredArticles(_ feedIDs: Set<String>) -> Set<Article> {
|
||||
return articlesTable.fetchStarredArticles(feedIDs)
|
||||
public func fetchStarredArticles(_ webFeedIDs: Set<String>) -> Set<Article> {
|
||||
return articlesTable.fetchStarredArticles(webFeedIDs)
|
||||
}
|
||||
|
||||
public func fetchArticlesMatching(_ searchString: String, _ feedIDs: Set<String>) -> Set<Article> {
|
||||
return articlesTable.fetchArticlesMatching(searchString, feedIDs)
|
||||
public func fetchArticlesMatching(_ searchString: String, _ webFeedIDs: Set<String>) -> Set<Article> {
|
||||
return articlesTable.fetchArticlesMatching(searchString, webFeedIDs)
|
||||
}
|
||||
|
||||
public func fetchArticlesMatchingWithArticleIDs(_ searchString: String, _ articleIDs: Set<String>) -> Set<Article> {
|
||||
@ -74,28 +74,28 @@ public final class ArticlesDatabase {
|
||||
|
||||
// MARK: - Fetching Articles Async
|
||||
|
||||
public func fetchArticlesAsync(_ feedID: String, _ callback: @escaping ArticleSetBlock) {
|
||||
articlesTable.fetchArticlesAsync(feedID, callback)
|
||||
public func fetchArticlesAsync(_ webFeedID: String, _ callback: @escaping ArticleSetBlock) {
|
||||
articlesTable.fetchArticlesAsync(webFeedID, callback)
|
||||
}
|
||||
|
||||
public func fetchArticlesAsync(articleIDs: Set<String>, _ callback: @escaping ArticleSetBlock) {
|
||||
articlesTable.fetchArticlesAsync(articleIDs: articleIDs, callback)
|
||||
}
|
||||
|
||||
public func fetchUnreadArticlesAsync(_ feedIDs: Set<String>, _ callback: @escaping ArticleSetBlock) {
|
||||
articlesTable.fetchUnreadArticlesAsync(feedIDs, callback)
|
||||
public func fetchUnreadArticlesAsync(_ webFeedIDs: Set<String>, _ callback: @escaping ArticleSetBlock) {
|
||||
articlesTable.fetchUnreadArticlesAsync(webFeedIDs, callback)
|
||||
}
|
||||
|
||||
public func fetchTodayArticlesAsync(_ feedIDs: Set<String>, _ callback: @escaping ArticleSetBlock) {
|
||||
articlesTable.fetchArticlesSinceAsync(feedIDs, todayCutoffDate(), callback)
|
||||
public func fetchTodayArticlesAsync(_ webFeedIDs: Set<String>, _ callback: @escaping ArticleSetBlock) {
|
||||
articlesTable.fetchArticlesSinceAsync(webFeedIDs, todayCutoffDate(), callback)
|
||||
}
|
||||
|
||||
public func fetchedStarredArticlesAsync(_ feedIDs: Set<String>, _ callback: @escaping ArticleSetBlock) {
|
||||
articlesTable.fetchStarredArticlesAsync(feedIDs, callback)
|
||||
public func fetchedStarredArticlesAsync(_ webFeedIDs: Set<String>, _ callback: @escaping ArticleSetBlock) {
|
||||
articlesTable.fetchStarredArticlesAsync(webFeedIDs, callback)
|
||||
}
|
||||
|
||||
public func fetchArticlesMatchingAsync(_ searchString: String, _ feedIDs: Set<String>, _ callback: @escaping ArticleSetBlock) {
|
||||
articlesTable.fetchArticlesMatchingAsync(searchString, feedIDs, callback)
|
||||
public func fetchArticlesMatchingAsync(_ searchString: String, _ webFeedIDs: Set<String>, _ callback: @escaping ArticleSetBlock) {
|
||||
articlesTable.fetchArticlesMatchingAsync(searchString, webFeedIDs, callback)
|
||||
}
|
||||
|
||||
public func fetchArticlesMatchingWithArticleIDsAsync(_ searchString: String, _ articleIDs: Set<String>, _ callback: @escaping ArticleSetBlock) {
|
||||
@ -104,20 +104,20 @@ public final class ArticlesDatabase {
|
||||
|
||||
// MARK: - Unread Counts
|
||||
|
||||
public func fetchUnreadCounts(for feedIDs: Set<String>, _ callback: @escaping UnreadCountCompletionBlock) {
|
||||
articlesTable.fetchUnreadCounts(feedIDs, callback)
|
||||
public func fetchUnreadCounts(for webFeedIDs: Set<String>, _ callback: @escaping UnreadCountCompletionBlock) {
|
||||
articlesTable.fetchUnreadCounts(webFeedIDs, callback)
|
||||
}
|
||||
|
||||
public func fetchUnreadCountForToday(for feedIDs: Set<String>, callback: @escaping (Int) -> Void) {
|
||||
fetchUnreadCount(for: feedIDs, since: todayCutoffDate(), callback: callback)
|
||||
public func fetchUnreadCountForToday(for webFeedIDs: Set<String>, callback: @escaping (Int) -> Void) {
|
||||
fetchUnreadCount(for: webFeedIDs, since: todayCutoffDate(), callback: callback)
|
||||
}
|
||||
|
||||
public func fetchUnreadCount(for feedIDs: Set<String>, since: Date, callback: @escaping (Int) -> Void) {
|
||||
articlesTable.fetchUnreadCount(feedIDs, since, callback)
|
||||
public func fetchUnreadCount(for webFeedIDs: Set<String>, since: Date, callback: @escaping (Int) -> Void) {
|
||||
articlesTable.fetchUnreadCount(webFeedIDs, since, callback)
|
||||
}
|
||||
|
||||
public func fetchStarredAndUnreadCount(for feedIDs: Set<String>, callback: @escaping (Int) -> Void) {
|
||||
articlesTable.fetchStarredAndUnreadCount(feedIDs, callback)
|
||||
public func fetchStarredAndUnreadCount(for webFeedIDs: Set<String>, callback: @escaping (Int) -> Void) {
|
||||
articlesTable.fetchStarredAndUnreadCount(webFeedIDs, callback)
|
||||
}
|
||||
|
||||
public func fetchAllNonZeroUnreadCounts(_ callback: @escaping UnreadCountCompletionBlock) {
|
||||
@ -126,9 +126,9 @@ public final class ArticlesDatabase {
|
||||
|
||||
// MARK: - Saving and Updating Articles
|
||||
|
||||
/// Update articles and save new ones. The key for feedIDsAndItems is feedID.
|
||||
public func update(feedIDsAndItems: [String: Set<ParsedItem>], defaultRead: Bool, completion: @escaping UpdateArticlesCompletionBlock) {
|
||||
articlesTable.update(feedIDsAndItems, defaultRead, completion)
|
||||
/// Update articles and save new ones. The key for ewbFeedIDsAndItems is webFeedID.
|
||||
public func update(webFeedIDsAndItems: [String: Set<ParsedItem>], defaultRead: Bool, completion: @escaping UpdateArticlesCompletionBlock) {
|
||||
articlesTable.update(webFeedIDsAndItems, defaultRead, completion)
|
||||
}
|
||||
|
||||
public func ensureStatuses(_ articleIDs: Set<String>, _ defaultRead: Bool, _ statusKey: ArticleStatus.Key, _ flag: Bool, completionHandler: (() -> ())? = nil) {
|
||||
@ -166,8 +166,8 @@ public final class ArticlesDatabase {
|
||||
// These are to be used only at startup. These are to prevent the database from growing forever.
|
||||
|
||||
/// Calls the various clean-up functions.
|
||||
public func cleanupDatabaseAtStartup(subscribedToFeedIDs: Set<String>) {
|
||||
articlesTable.deleteArticlesNotInSubscribedToFeedIDs(subscribedToFeedIDs)
|
||||
public func cleanupDatabaseAtStartup(subscribedToWebFeedIDs: Set<String>) {
|
||||
articlesTable.deleteArticlesNotInSubscribedToFeedIDs(subscribedToWebFeedIDs)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -48,16 +48,16 @@ final class ArticlesTable: DatabaseTable {
|
||||
|
||||
// MARK: - Fetching Articles for Feed
|
||||
|
||||
func fetchArticles(_ feedID: String) -> Set<Article> {
|
||||
return fetchArticles{ self.fetchArticlesForFeedID(feedID, withLimits: true, $0) }
|
||||
func fetchArticles(_ webFeedID: String) -> Set<Article> {
|
||||
return fetchArticles{ self.fetchArticlesForFeedID(webFeedID, withLimits: true, $0) }
|
||||
}
|
||||
|
||||
func fetchArticlesAsync(_ feedID: String, _ callback: @escaping ArticleSetBlock) {
|
||||
fetchArticlesAsync({ self.fetchArticlesForFeedID(feedID, withLimits: true, $0) }, callback)
|
||||
func fetchArticlesAsync(_ webFeedID: String, _ callback: @escaping ArticleSetBlock) {
|
||||
fetchArticlesAsync({ self.fetchArticlesForFeedID(webFeedID, withLimits: true, $0) }, callback)
|
||||
}
|
||||
|
||||
private func fetchArticlesForFeedID(_ feedID: String, withLimits: Bool, _ database: FMDatabase) -> Set<Article> {
|
||||
return fetchArticlesWithWhereClause(database, whereClause: "articles.feedID = ?", parameters: [feedID as AnyObject], withLimits: withLimits)
|
||||
private func fetchArticlesForFeedID(_ webFeedID: String, withLimits: Bool, _ database: FMDatabase) -> Set<Article> {
|
||||
return fetchArticlesWithWhereClause(database, whereClause: "articles.feedID = ?", parameters: [webFeedID as AnyObject], withLimits: withLimits)
|
||||
}
|
||||
|
||||
// MARK: - Fetching Articles by articleID
|
||||
@ -82,65 +82,65 @@ final class ArticlesTable: DatabaseTable {
|
||||
|
||||
// MARK: - Fetching Unread Articles
|
||||
|
||||
func fetchUnreadArticles(_ feedIDs: Set<String>) -> Set<Article> {
|
||||
return fetchArticles{ self.fetchUnreadArticles(feedIDs, $0) }
|
||||
func fetchUnreadArticles(_ webFeedIDs: Set<String>) -> Set<Article> {
|
||||
return fetchArticles{ self.fetchUnreadArticles(webFeedIDs, $0) }
|
||||
}
|
||||
|
||||
func fetchUnreadArticlesAsync(_ feedIDs: Set<String>, _ callback: @escaping ArticleSetBlock) {
|
||||
fetchArticlesAsync({ self.fetchUnreadArticles(feedIDs, $0) }, callback)
|
||||
func fetchUnreadArticlesAsync(_ webFeedIDs: Set<String>, _ callback: @escaping ArticleSetBlock) {
|
||||
fetchArticlesAsync({ self.fetchUnreadArticles(webFeedIDs, $0) }, callback)
|
||||
}
|
||||
|
||||
private func fetchUnreadArticles(_ feedIDs: Set<String>, _ database: FMDatabase) -> Set<Article> {
|
||||
private func fetchUnreadArticles(_ webFeedIDs: Set<String>, _ database: FMDatabase) -> Set<Article> {
|
||||
// select * from articles natural join statuses where feedID in ('http://ranchero.com/xml/rss.xml') and read=0
|
||||
if feedIDs.isEmpty {
|
||||
if webFeedIDs.isEmpty {
|
||||
return Set<Article>()
|
||||
}
|
||||
let parameters = feedIDs.map { $0 as AnyObject }
|
||||
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(feedIDs.count))!
|
||||
let parameters = webFeedIDs.map { $0 as AnyObject }
|
||||
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(webFeedIDs.count))!
|
||||
let whereClause = "feedID in \(placeholders) and read=0"
|
||||
return fetchArticlesWithWhereClause(database, whereClause: whereClause, parameters: parameters, withLimits: true)
|
||||
}
|
||||
|
||||
// MARK: - Fetching Today Articles
|
||||
|
||||
func fetchArticlesSince(_ feedIDs: Set<String>, _ cutoffDate: Date) -> Set<Article> {
|
||||
return fetchArticles{ self.fetchArticlesSince(feedIDs, cutoffDate, $0) }
|
||||
func fetchArticlesSince(_ webFeedIDs: Set<String>, _ cutoffDate: Date) -> Set<Article> {
|
||||
return fetchArticles{ self.fetchArticlesSince(webFeedIDs, cutoffDate, $0) }
|
||||
}
|
||||
|
||||
func fetchArticlesSinceAsync(_ feedIDs: Set<String>, _ cutoffDate: Date, _ callback: @escaping ArticleSetBlock) {
|
||||
fetchArticlesAsync({ self.fetchArticlesSince(feedIDs, cutoffDate, $0) }, callback)
|
||||
func fetchArticlesSinceAsync(_ webFeedIDs: Set<String>, _ cutoffDate: Date, _ callback: @escaping ArticleSetBlock) {
|
||||
fetchArticlesAsync({ self.fetchArticlesSince(webFeedIDs, cutoffDate, $0) }, callback)
|
||||
}
|
||||
|
||||
private func fetchArticlesSince(_ feedIDs: Set<String>, _ cutoffDate: Date, _ database: FMDatabase) -> Set<Article> {
|
||||
private func fetchArticlesSince(_ webFeedIDs: Set<String>, _ cutoffDate: Date, _ database: FMDatabase) -> Set<Article> {
|
||||
// select * from articles natural join statuses where feedID in ('http://ranchero.com/xml/rss.xml') and (datePublished > ? || (datePublished is null and dateArrived > ?)
|
||||
//
|
||||
// datePublished may be nil, so we fall back to dateArrived.
|
||||
if feedIDs.isEmpty {
|
||||
if webFeedIDs.isEmpty {
|
||||
return Set<Article>()
|
||||
}
|
||||
let parameters = feedIDs.map { $0 as AnyObject } + [cutoffDate as AnyObject, cutoffDate as AnyObject]
|
||||
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(feedIDs.count))!
|
||||
let parameters = webFeedIDs.map { $0 as AnyObject } + [cutoffDate as AnyObject, cutoffDate as AnyObject]
|
||||
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(webFeedIDs.count))!
|
||||
let whereClause = "feedID in \(placeholders) and (datePublished > ? or (datePublished is null and dateArrived > ?)) and userDeleted = 0"
|
||||
return fetchArticlesWithWhereClause(database, whereClause: whereClause, parameters: parameters, withLimits: false)
|
||||
}
|
||||
|
||||
// MARK: - Fetching Starred Articles
|
||||
|
||||
func fetchStarredArticles(_ feedIDs: Set<String>) -> Set<Article> {
|
||||
return fetchArticles{ self.fetchStarredArticles(feedIDs, $0) }
|
||||
func fetchStarredArticles(_ webFeedIDs: Set<String>) -> Set<Article> {
|
||||
return fetchArticles{ self.fetchStarredArticles(webFeedIDs, $0) }
|
||||
}
|
||||
|
||||
func fetchStarredArticlesAsync(_ feedIDs: Set<String>, _ callback: @escaping ArticleSetBlock) {
|
||||
fetchArticlesAsync({ self.fetchStarredArticles(feedIDs, $0) }, callback)
|
||||
func fetchStarredArticlesAsync(_ webFeedIDs: Set<String>, _ callback: @escaping ArticleSetBlock) {
|
||||
fetchArticlesAsync({ self.fetchStarredArticles(webFeedIDs, $0) }, callback)
|
||||
}
|
||||
|
||||
private func fetchStarredArticles(_ feedIDs: Set<String>, _ database: FMDatabase) -> Set<Article> {
|
||||
private func fetchStarredArticles(_ webFeedIDs: Set<String>, _ database: FMDatabase) -> Set<Article> {
|
||||
// select * from articles natural join statuses where feedID in ('http://ranchero.com/xml/rss.xml') and starred = 1 and userDeleted = 0;
|
||||
if feedIDs.isEmpty {
|
||||
if webFeedIDs.isEmpty {
|
||||
return Set<Article>()
|
||||
}
|
||||
let parameters = feedIDs.map { $0 as AnyObject }
|
||||
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(feedIDs.count))!
|
||||
let parameters = webFeedIDs.map { $0 as AnyObject }
|
||||
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(webFeedIDs.count))!
|
||||
let whereClause = "feedID in \(placeholders) and starred = 1 and userDeleted = 0"
|
||||
return fetchArticlesWithWhereClause(database, whereClause: whereClause, parameters: parameters, withLimits: false)
|
||||
}
|
||||
@ -155,9 +155,9 @@ final class ArticlesTable: DatabaseTable {
|
||||
return articles
|
||||
}
|
||||
|
||||
func fetchArticlesMatching(_ searchString: String, _ feedIDs: Set<String>) -> Set<Article> {
|
||||
func fetchArticlesMatching(_ searchString: String, _ webFeedIDs: Set<String>) -> Set<Article> {
|
||||
var articles = fetchArticlesMatching(searchString)
|
||||
articles = articles.filter{ feedIDs.contains($0.feedID) }
|
||||
articles = articles.filter{ webFeedIDs.contains($0.webFeedID) }
|
||||
return articles
|
||||
}
|
||||
|
||||
@ -167,18 +167,18 @@ final class ArticlesTable: DatabaseTable {
|
||||
return articles
|
||||
}
|
||||
|
||||
func fetchArticlesMatchingAsync(_ searchString: String, _ feedIDs: Set<String>, _ callback: @escaping ArticleSetBlock) {
|
||||
fetchArticlesAsync({ self.fetchArticlesMatching(searchString, feedIDs, $0) }, callback)
|
||||
func fetchArticlesMatchingAsync(_ searchString: String, _ webFeedIDs: Set<String>, _ callback: @escaping ArticleSetBlock) {
|
||||
fetchArticlesAsync({ self.fetchArticlesMatching(searchString, webFeedIDs, $0) }, callback)
|
||||
}
|
||||
|
||||
func fetchArticlesMatchingWithArticleIDsAsync(_ searchString: String, _ articleIDs: Set<String>, _ callback: @escaping ArticleSetBlock) {
|
||||
fetchArticlesAsync({ self.fetchArticlesMatchingWithArticleIDs(searchString, articleIDs, $0) }, callback)
|
||||
}
|
||||
|
||||
private func fetchArticlesMatching(_ searchString: String, _ feedIDs: Set<String>, _ database: FMDatabase) -> Set<Article> {
|
||||
private func fetchArticlesMatching(_ searchString: String, _ webFeedIDs: Set<String>, _ database: FMDatabase) -> Set<Article> {
|
||||
let articles = fetchArticlesMatching(searchString, database)
|
||||
// TODO: include the feedIDs in the SQL rather than filtering here.
|
||||
return articles.filter{ feedIDs.contains($0.feedID) }
|
||||
return articles.filter{ webFeedIDs.contains($0.webFeedID) }
|
||||
}
|
||||
|
||||
private func fetchArticlesMatchingWithArticleIDs(_ searchString: String, _ articleIDs: Set<String>, _ database: FMDatabase) -> Set<Article> {
|
||||
@ -216,8 +216,8 @@ final class ArticlesTable: DatabaseTable {
|
||||
|
||||
// MARK: - Updating
|
||||
|
||||
func update(_ feedIDsAndItems: [String: Set<ParsedItem>], _ read: Bool, _ completion: @escaping UpdateArticlesCompletionBlock) {
|
||||
if feedIDsAndItems.isEmpty {
|
||||
func update(_ webFeedIDsAndItems: [String: Set<ParsedItem>], _ read: Bool, _ completion: @escaping UpdateArticlesCompletionBlock) {
|
||||
if webFeedIDsAndItems.isEmpty {
|
||||
completion(nil, nil)
|
||||
return
|
||||
}
|
||||
@ -232,7 +232,7 @@ final class ArticlesTable: DatabaseTable {
|
||||
// 8. Update search index.
|
||||
|
||||
var articleIDs = Set<String>()
|
||||
for (_, parsedItems) in feedIDsAndItems {
|
||||
for (_, parsedItems) in webFeedIDsAndItems {
|
||||
articleIDs.formUnion(parsedItems.articleIDs())
|
||||
}
|
||||
|
||||
@ -240,7 +240,7 @@ final class ArticlesTable: DatabaseTable {
|
||||
let statusesDictionary = self.statusesTable.ensureStatusesForArticleIDs(articleIDs, read, database) //1
|
||||
assert(statusesDictionary.count == articleIDs.count)
|
||||
|
||||
let allIncomingArticles = Article.articlesWithFeedIDsAndItems(feedIDsAndItems, self.accountID, statusesDictionary) //2
|
||||
let allIncomingArticles = Article.articlesWithWebFeedIDsAndItems(webFeedIDsAndItems, self.accountID, statusesDictionary) //2
|
||||
if allIncomingArticles.isEmpty {
|
||||
self.callUpdateArticlesCompletionBlock(nil, nil, completion)
|
||||
return
|
||||
@ -292,8 +292,8 @@ final class ArticlesTable: DatabaseTable {
|
||||
|
||||
// MARK: - Unread Counts
|
||||
|
||||
func fetchUnreadCounts(_ feedIDs: Set<String>, _ completion: @escaping UnreadCountCompletionBlock) {
|
||||
if feedIDs.isEmpty {
|
||||
func fetchUnreadCounts(_ webFeedIDs: Set<String>, _ completion: @escaping UnreadCountCompletionBlock) {
|
||||
if webFeedIDs.isEmpty {
|
||||
completion(UnreadCountDictionary())
|
||||
return
|
||||
}
|
||||
@ -301,8 +301,8 @@ final class ArticlesTable: DatabaseTable {
|
||||
var unreadCountDictionary = UnreadCountDictionary()
|
||||
|
||||
queue.fetch { (database) in
|
||||
for feedID in feedIDs {
|
||||
unreadCountDictionary[feedID] = self.fetchUnreadCount(feedID, database)
|
||||
for webFeedID in webFeedIDs {
|
||||
unreadCountDictionary[webFeedID] = self.fetchUnreadCount(webFeedID, database)
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
@ -311,20 +311,20 @@ final class ArticlesTable: DatabaseTable {
|
||||
}
|
||||
}
|
||||
|
||||
func fetchUnreadCount(_ feedIDs: Set<String>, _ since: Date, _ callback: @escaping (Int) -> Void) {
|
||||
func fetchUnreadCount(_ webFeedIDs: Set<String>, _ since: Date, _ callback: @escaping (Int) -> Void) {
|
||||
// Get unread count for today, for instance.
|
||||
|
||||
if feedIDs.isEmpty {
|
||||
if webFeedIDs.isEmpty {
|
||||
callback(0)
|
||||
return
|
||||
}
|
||||
|
||||
queue.fetch { (database) in
|
||||
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(feedIDs.count))!
|
||||
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(webFeedIDs.count))!
|
||||
let sql = "select count(*) from articles natural join statuses where feedID in \(placeholders) and (datePublished > ? or (datePublished is null and dateArrived > ?)) and read=0 and userDeleted=0;"
|
||||
|
||||
var parameters = [Any]()
|
||||
parameters += Array(feedIDs) as [Any]
|
||||
parameters += Array(webFeedIDs) as [Any]
|
||||
parameters += [since] as [Any]
|
||||
parameters += [since] as [Any]
|
||||
|
||||
@ -354,8 +354,8 @@ final class ArticlesTable: DatabaseTable {
|
||||
var d = UnreadCountDictionary()
|
||||
while resultSet.next() {
|
||||
let unreadCount = resultSet.long(forColumnIndex: 1)
|
||||
if let feedID = resultSet.string(forColumnIndex: 0) {
|
||||
d[feedID] = unreadCount
|
||||
if let webFeedID = resultSet.string(forColumnIndex: 0) {
|
||||
d[webFeedID] = unreadCount
|
||||
}
|
||||
}
|
||||
|
||||
@ -365,16 +365,16 @@ final class ArticlesTable: DatabaseTable {
|
||||
}
|
||||
}
|
||||
|
||||
func fetchStarredAndUnreadCount(_ feedIDs: Set<String>, _ callback: @escaping (Int) -> Void) {
|
||||
if feedIDs.isEmpty {
|
||||
func fetchStarredAndUnreadCount(_ webFeedIDs: Set<String>, _ callback: @escaping (Int) -> Void) {
|
||||
if webFeedIDs.isEmpty {
|
||||
callback(0)
|
||||
return
|
||||
}
|
||||
|
||||
queue.fetch { (database) in
|
||||
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(feedIDs.count))!
|
||||
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(webFeedIDs.count))!
|
||||
let sql = "select count(*) from articles natural join statuses where feedID in \(placeholders) and read=0 and starred=1 and userDeleted=0;"
|
||||
let parameters = Array(feedIDs) as [Any]
|
||||
let parameters = Array(webFeedIDs) as [Any]
|
||||
|
||||
let unreadCount = self.numberWithSQLAndParameters(sql, parameters, in: database)
|
||||
|
||||
@ -439,14 +439,14 @@ final class ArticlesTable: DatabaseTable {
|
||||
/// Delete articles from feeds that are no longer in the current set of subscribed-to feeds.
|
||||
/// This deletes from the articles and articleStatuses tables,
|
||||
/// and, via a trigger, it also deletes from the search index.
|
||||
func deleteArticlesNotInSubscribedToFeedIDs(_ feedIDs: Set<String>) {
|
||||
if feedIDs.isEmpty {
|
||||
func deleteArticlesNotInSubscribedToFeedIDs(_ webFeedIDs: Set<String>) {
|
||||
if webFeedIDs.isEmpty {
|
||||
return
|
||||
}
|
||||
queue.run { (database) in
|
||||
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(feedIDs.count))!
|
||||
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(webFeedIDs.count))!
|
||||
let sql = "select articleID from articles where feedID not in \(placeholders);"
|
||||
let parameters = Array(feedIDs) as [Any]
|
||||
let parameters = Array(webFeedIDs) as [Any]
|
||||
guard let resultSet = database.executeQuery(sql, withArgumentsIn: parameters) else {
|
||||
return
|
||||
}
|
||||
@ -540,7 +540,7 @@ private extension ArticlesTable {
|
||||
assertionFailure("Expected status.")
|
||||
return nil
|
||||
}
|
||||
guard let feedID = row.string(forColumn: DatabaseKey.feedID) else {
|
||||
guard let webFeedID = row.string(forColumn: DatabaseKey.feedID) else {
|
||||
assertionFailure("Expected feedID.")
|
||||
return nil
|
||||
}
|
||||
@ -560,7 +560,7 @@ private extension ArticlesTable {
|
||||
let datePublished = row.date(forColumn: DatabaseKey.datePublished)
|
||||
let dateModified = row.date(forColumn: DatabaseKey.dateModified)
|
||||
|
||||
let databaseArticle = DatabaseArticle(articleID: articleID, feedID: feedID, uniqueID: uniqueID, title: title, contentHTML: contentHTML, contentText: contentText, url: url, externalURL: externalURL, summary: summary, imageURL: imageURL, bannerImageURL: bannerImageURL, datePublished: datePublished, dateModified: dateModified, status: status)
|
||||
let databaseArticle = DatabaseArticle(articleID: articleID, webFeedID: webFeedID, uniqueID: uniqueID, title: title, contentHTML: contentHTML, contentText: contentText, url: url, externalURL: externalURL, summary: summary, imageURL: imageURL, bannerImageURL: bannerImageURL, datePublished: datePublished, dateModified: dateModified, status: status)
|
||||
databaseArticlesCache[articleID] = databaseArticle
|
||||
return databaseArticle
|
||||
}
|
||||
@ -583,14 +583,14 @@ private extension ArticlesTable {
|
||||
}
|
||||
}
|
||||
|
||||
func fetchUnreadCount(_ feedID: String, _ database: FMDatabase) -> Int {
|
||||
func fetchUnreadCount(_ webFeedID: String, _ database: FMDatabase) -> Int {
|
||||
// Count only the articles that would appear in the UI.
|
||||
// * Must be unread.
|
||||
// * Must not be deleted.
|
||||
// * Must be either 1) starred or 2) dateArrived must be newer than cutoff date.
|
||||
|
||||
let sql = "select count(*) from articles natural join statuses where feedID=? and read=0 and userDeleted=0 and (starred=1 or dateArrived>?);"
|
||||
return numberWithSQLAndParameters(sql, [feedID, articleCutoffDate], in: database)
|
||||
return numberWithSQLAndParameters(sql, [webFeedID, articleCutoffDate], in: database)
|
||||
}
|
||||
|
||||
func fetchArticlesMatching(_ searchString: String, _ database: FMDatabase) -> Set<Article> {
|
||||
|
@ -15,7 +15,7 @@ import Articles
|
||||
struct DatabaseArticle: Hashable {
|
||||
|
||||
let articleID: String
|
||||
let feedID: String
|
||||
let webFeedID: String
|
||||
let uniqueID: String
|
||||
let title: String?
|
||||
let contentHTML: String?
|
||||
|
@ -14,10 +14,10 @@ import RSParser
|
||||
extension Article {
|
||||
|
||||
init(databaseArticle: DatabaseArticle, accountID: String, authors: Set<Author>?, attachments: Set<Attachment>?) {
|
||||
self.init(accountID: accountID, articleID: databaseArticle.articleID, feedID: databaseArticle.feedID, uniqueID: databaseArticle.uniqueID, title: databaseArticle.title, contentHTML: databaseArticle.contentHTML, contentText: databaseArticle.contentText, url: databaseArticle.url, externalURL: databaseArticle.externalURL, summary: databaseArticle.summary, imageURL: databaseArticle.imageURL, bannerImageURL: databaseArticle.bannerImageURL, datePublished: databaseArticle.datePublished, dateModified: databaseArticle.dateModified, authors: authors, attachments: attachments, status: databaseArticle.status)
|
||||
self.init(accountID: accountID, articleID: databaseArticle.articleID, webFeedID: databaseArticle.webFeedID, uniqueID: databaseArticle.uniqueID, title: databaseArticle.title, contentHTML: databaseArticle.contentHTML, contentText: databaseArticle.contentText, url: databaseArticle.url, externalURL: databaseArticle.externalURL, summary: databaseArticle.summary, imageURL: databaseArticle.imageURL, bannerImageURL: databaseArticle.bannerImageURL, datePublished: databaseArticle.datePublished, dateModified: databaseArticle.dateModified, authors: authors, attachments: attachments, status: databaseArticle.status)
|
||||
}
|
||||
|
||||
init(parsedItem: ParsedItem, maximumDateAllowed: Date, accountID: String, feedID: String, status: ArticleStatus) {
|
||||
init(parsedItem: ParsedItem, maximumDateAllowed: Date, accountID: String, webFeedID: String, status: ArticleStatus) {
|
||||
let authors = Author.authorsWithParsedAuthors(parsedItem.authors)
|
||||
let attachments = Attachment.attachmentsWithParsedAttachments(parsedItem.attachments)
|
||||
|
||||
@ -35,7 +35,7 @@ extension Article {
|
||||
dateModified = nil
|
||||
}
|
||||
|
||||
self.init(accountID: accountID, articleID: parsedItem.syncServiceID, feedID: feedID, uniqueID: parsedItem.uniqueID, title: parsedItem.title, contentHTML: parsedItem.contentHTML, contentText: parsedItem.contentText, url: parsedItem.url, externalURL: parsedItem.externalURL, summary: parsedItem.summary, imageURL: parsedItem.imageURL, bannerImageURL: parsedItem.bannerImageURL, datePublished: datePublished, dateModified: dateModified, authors: authors, attachments: attachments, status: status)
|
||||
self.init(accountID: accountID, articleID: parsedItem.syncServiceID, webFeedID: webFeedID, uniqueID: parsedItem.uniqueID, title: parsedItem.title, contentHTML: parsedItem.contentHTML, contentText: parsedItem.contentText, url: parsedItem.url, externalURL: parsedItem.externalURL, summary: parsedItem.summary, imageURL: parsedItem.imageURL, bannerImageURL: parsedItem.bannerImageURL, datePublished: datePublished, dateModified: dateModified, authors: authors, attachments: attachments, status: status)
|
||||
}
|
||||
|
||||
private func addPossibleStringChangeWithKeyPath(_ comparisonKeyPath: KeyPath<Article,String?>, _ otherArticle: Article, _ key: String, _ dictionary: inout DatabaseDictionary) {
|
||||
@ -84,11 +84,11 @@ extension Article {
|
||||
// return Set(parsedItems.map{ Article(parsedItem: $0, maximumDateAllowed: maximumDateAllowed, accountID: accountID, feedID: feedID, status: statusesDictionary[$0.articleID]!) })
|
||||
// }
|
||||
|
||||
static func articlesWithFeedIDsAndItems(_ feedIDsAndItems: [String: Set<ParsedItem>], _ accountID: String, _ statusesDictionary: [String: ArticleStatus]) -> Set<Article> {
|
||||
static func articlesWithWebFeedIDsAndItems(_ webFeedIDsAndItems: [String: Set<ParsedItem>], _ accountID: String, _ statusesDictionary: [String: ArticleStatus]) -> Set<Article> {
|
||||
let maximumDateAllowed = Date().addingTimeInterval(60 * 60 * 24) // Allow dates up to about 24 hours ahead of now
|
||||
var articles = Set<Article>()
|
||||
for (feedID, parsedItems) in feedIDsAndItems {
|
||||
let feedArticles = Set(parsedItems.map{ Article(parsedItem: $0, maximumDateAllowed: maximumDateAllowed, accountID: accountID, feedID: feedID, status: statusesDictionary[$0.articleID]!) })
|
||||
for (webFeedID, parsedItems) in webFeedIDsAndItems {
|
||||
let feedArticles = Set(parsedItems.map{ Article(parsedItem: $0, maximumDateAllowed: maximumDateAllowed, accountID: accountID, webFeedID: webFeedID, status: statusesDictionary[$0.articleID]!) })
|
||||
articles.formUnion(feedArticles)
|
||||
}
|
||||
return articles
|
||||
@ -101,7 +101,7 @@ extension Article: DatabaseObject {
|
||||
var d = DatabaseDictionary()
|
||||
|
||||
d[DatabaseKey.articleID] = articleID
|
||||
d[DatabaseKey.feedID] = feedID
|
||||
d[DatabaseKey.feedID] = webFeedID
|
||||
d[DatabaseKey.uniqueID] = uniqueID
|
||||
|
||||
if let title = title {
|
||||
|
@ -17,6 +17,6 @@ extension ParsedItem {
|
||||
return s
|
||||
}
|
||||
// Must be same calculation as for Article.
|
||||
return Article.calculatedArticleID(feedID: feedURL, uniqueID: uniqueID)
|
||||
return Article.calculatedArticleID(webFeedID: feedURL, uniqueID: uniqueID)
|
||||
}
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations,
|
||||
var faviconDownloader: FaviconDownloader!
|
||||
var imageDownloader: ImageDownloader!
|
||||
var authorAvatarDownloader: AuthorAvatarDownloader!
|
||||
var feedIconDownloader: FeedIconDownloader!
|
||||
var webFeedIconDownloader: WebFeedIconDownloader!
|
||||
var appName: String!
|
||||
|
||||
var refreshTimer: AccountRefreshTimer?
|
||||
@ -186,7 +186,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations,
|
||||
imageDownloader = ImageDownloader(folder: imagesFolder)
|
||||
|
||||
authorAvatarDownloader = AuthorAvatarDownloader(imageDownloader: imageDownloader)
|
||||
feedIconDownloader = FeedIconDownloader(imageDownloader: imageDownloader, folder: cacheFolder)
|
||||
webFeedIconDownloader = WebFeedIconDownloader(imageDownloader: imageDownloader, folder: cacheFolder)
|
||||
|
||||
updateSortMenuItems()
|
||||
updateGroupByFeedMenuItem()
|
||||
@ -195,7 +195,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations,
|
||||
mainWindowController?.window?.center()
|
||||
}
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(feedSettingDidChange(_:)), name: .FeedSettingDidChange, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(webFeedSettingDidChange(_:)), name: .WebFeedSettingDidChange, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(userDefaultsDidChange(_:)), name: UserDefaults.didChangeNotification, object: nil)
|
||||
|
||||
DispatchQueue.main.async {
|
||||
@ -299,12 +299,12 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations,
|
||||
}
|
||||
}
|
||||
|
||||
@objc func feedSettingDidChange(_ note: Notification) {
|
||||
@objc func webFeedSettingDidChange(_ note: Notification) {
|
||||
|
||||
guard let feed = note.object as? Feed, let key = note.userInfo?[Feed.FeedSettingUserInfoKey] as? String else {
|
||||
guard let feed = note.object as? WebFeed, let key = note.userInfo?[WebFeed.WebFeedSettingUserInfoKey] as? String else {
|
||||
return
|
||||
}
|
||||
if key == Feed.FeedSettingKey.homePageURL || key == Feed.FeedSettingKey.faviconURL {
|
||||
if key == WebFeed.WebFeedSettingKey.homePageURL || key == WebFeed.WebFeedSettingKey.faviconURL {
|
||||
let _ = faviconDownloader.favicon(for: feed)
|
||||
}
|
||||
}
|
||||
|
@ -31,7 +31,7 @@
|
||||
<!--Feed-->
|
||||
<scene sceneID="vUh-Rc-fPi">
|
||||
<objects>
|
||||
<viewController title="Feed" storyboardIdentifier="Feed" showSeguePresentationStyle="single" id="sfH-oR-GXm" customClass="FeedInspectorViewController" customModule="NetNewsWire" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<viewController title="Feed" storyboardIdentifier="Feed" showSeguePresentationStyle="single" id="sfH-oR-GXm" customClass="WebFeedInspectorViewController" customModule="NetNewsWire" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" id="ecA-UY-KEd">
|
||||
<rect key="frame" x="0.0" y="0.0" width="256" height="332"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
|
@ -10,7 +10,7 @@ import AppKit
|
||||
import Articles
|
||||
import Account
|
||||
|
||||
final class FeedInspectorViewController: NSViewController, Inspector {
|
||||
final class WebFeedInspectorViewController: NSViewController, Inspector {
|
||||
|
||||
@IBOutlet weak var iconView: IconView!
|
||||
@IBOutlet weak var nameTextField: NSTextField?
|
||||
@ -19,7 +19,7 @@ final class FeedInspectorViewController: NSViewController, Inspector {
|
||||
@IBOutlet weak var isNotifyAboutNewArticlesCheckBox: NSButton!
|
||||
@IBOutlet weak var isReaderViewAlwaysOnCheckBox: NSButton?
|
||||
|
||||
private var feed: Feed? {
|
||||
private var feed: WebFeed? {
|
||||
didSet {
|
||||
if feed != oldValue {
|
||||
updateUI()
|
||||
@ -37,7 +37,7 @@ final class FeedInspectorViewController: NSViewController, Inspector {
|
||||
}
|
||||
|
||||
func canInspect(_ objects: [Any]) -> Bool {
|
||||
return objects.count == 1 && objects.first is Feed
|
||||
return objects.count == 1 && objects.first is WebFeed
|
||||
}
|
||||
|
||||
// MARK: NSViewController
|
||||
@ -64,7 +64,7 @@ final class FeedInspectorViewController: NSViewController, Inspector {
|
||||
|
||||
}
|
||||
|
||||
extension FeedInspectorViewController: NSTextFieldDelegate {
|
||||
extension WebFeedInspectorViewController: NSTextFieldDelegate {
|
||||
|
||||
func controlTextDidChange(_ note: Notification) {
|
||||
guard let feed = feed, let nameTextField = nameTextField else {
|
||||
@ -75,10 +75,10 @@ extension FeedInspectorViewController: NSTextFieldDelegate {
|
||||
|
||||
}
|
||||
|
||||
private extension FeedInspectorViewController {
|
||||
private extension WebFeedInspectorViewController {
|
||||
|
||||
func updateFeed() {
|
||||
guard let objects = objects, objects.count == 1, let singleFeed = objects.first as? Feed else {
|
||||
guard let objects = objects, objects.count == 1, let singleFeed = objects.first as? WebFeed else {
|
||||
feed = nil
|
||||
return
|
||||
}
|
||||
@ -101,7 +101,7 @@ private extension FeedInspectorViewController {
|
||||
return
|
||||
}
|
||||
|
||||
if let feedIcon = appDelegate.feedIconDownloader.icon(for: feed) {
|
||||
if let feedIcon = appDelegate.webFeedIconDownloader.icon(for: feed) {
|
||||
iconView.iconImage = feedIcon
|
||||
return
|
||||
}
|
@ -53,14 +53,14 @@ class AddFeedController: AddFeedWindowControllerDelegate {
|
||||
}
|
||||
let account = accountAndFolderSpecifier.account
|
||||
|
||||
if account.hasFeed(withURL: url.absoluteString) {
|
||||
if account.hasWebFeed(withURL: url.absoluteString) {
|
||||
showAlreadySubscribedError(url.absoluteString)
|
||||
return
|
||||
}
|
||||
|
||||
BatchUpdate.shared.start()
|
||||
|
||||
account.createFeed(url: url.absoluteString, name: title, container: container) { result in
|
||||
account.createWebFeed(url: url.absoluteString, name: title, container: container) { result in
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.endShowingProgress()
|
||||
@ -70,7 +70,7 @@ class AddFeedController: AddFeedWindowControllerDelegate {
|
||||
|
||||
switch result {
|
||||
case .success(let feed):
|
||||
NotificationCenter.default.post(name: .UserDidAddFeed, object: self, userInfo: [UserInfoKey.feed: feed])
|
||||
NotificationCenter.default.post(name: .UserDidAddFeed, object: self, userInfo: [UserInfoKey.webFeed: feed])
|
||||
case .failure(let error):
|
||||
switch error {
|
||||
case AccountError.createErrorAlreadySubscribed:
|
||||
|
@ -172,7 +172,7 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations {
|
||||
}
|
||||
}
|
||||
|
||||
if let feed = currentFeedOrFolder as? Feed, let noteObject = noteObject as? Feed {
|
||||
if let feed = currentFeedOrFolder as? WebFeed, let noteObject = noteObject as? WebFeed {
|
||||
if feed == noteObject {
|
||||
updateWindowTitle()
|
||||
return
|
||||
@ -482,7 +482,7 @@ extension MainWindowController: TimelineContainerViewControllerDelegate {
|
||||
if let articles = articles {
|
||||
if articles.count == 1 {
|
||||
activityManager.reading(fetcher: nil, article: articles.first)
|
||||
if articles.first?.feed?.isArticleExtractorAlwaysOn ?? false {
|
||||
if articles.first?.webFeed?.isArticleExtractorAlwaysOn ?? false {
|
||||
detailState = .loading
|
||||
startArticleExtractorForCurrentLink()
|
||||
} else {
|
||||
|
@ -52,7 +52,7 @@ struct PasteboardFolder: Hashable {
|
||||
}
|
||||
|
||||
if let foundType = pasteboardType {
|
||||
if let folderDictionary = pasteboardItem.propertyList(forType: foundType) as? PasteboardFeedDictionary {
|
||||
if let folderDictionary = pasteboardItem.propertyList(forType: foundType) as? PasteboardWebFeedDictionary {
|
||||
self.init(dictionary: folderDictionary)
|
||||
return
|
||||
}
|
||||
@ -72,7 +72,7 @@ struct PasteboardFolder: Hashable {
|
||||
// MARK: - Writing
|
||||
|
||||
func internalDictionary() -> PasteboardFolderDictionary {
|
||||
var d = PasteboardFeedDictionary()
|
||||
var d = PasteboardWebFeedDictionary()
|
||||
d[PasteboardFolder.Key.name] = name
|
||||
if let folderID = folderID {
|
||||
d[PasteboardFolder.Key.folderID] = folderID
|
||||
@ -131,7 +131,7 @@ private extension FolderPasteboardWriter {
|
||||
return PasteboardFolder(name: folder.name ?? "", folderID: String(folder.folderID), accountID: folder.account?.accountID)
|
||||
}
|
||||
|
||||
var internalDictionary: PasteboardFeedDictionary {
|
||||
var internalDictionary: PasteboardWebFeedDictionary {
|
||||
return pasteboardFolder.internalDictionary()
|
||||
}
|
||||
}
|
||||
|
@ -11,9 +11,9 @@ import Articles
|
||||
import Account
|
||||
import RSCore
|
||||
|
||||
typealias PasteboardFeedDictionary = [String: String]
|
||||
typealias PasteboardWebFeedDictionary = [String: String]
|
||||
|
||||
struct PasteboardFeed: Hashable {
|
||||
struct PasteboardWebFeed: Hashable {
|
||||
|
||||
private struct Key {
|
||||
static let url = "URL"
|
||||
@ -23,12 +23,12 @@ struct PasteboardFeed: Hashable {
|
||||
// Internal
|
||||
static let accountID = "accountID"
|
||||
static let accountType = "accountType"
|
||||
static let feedID = "feedID"
|
||||
static let webFeedID = "webFeedID"
|
||||
static let editedName = "editedName"
|
||||
}
|
||||
|
||||
let url: String
|
||||
let feedID: String?
|
||||
let webFeedID: String?
|
||||
let homePageURL: String?
|
||||
let name: String?
|
||||
let editedName: String?
|
||||
@ -36,9 +36,9 @@ struct PasteboardFeed: Hashable {
|
||||
let accountType: AccountType?
|
||||
let isLocalFeed: Bool
|
||||
|
||||
init(url: String, feedID: String?, homePageURL: String?, name: String?, editedName: String?, accountID: String?, accountType: AccountType?) {
|
||||
init(url: String, webFeedID: String?, homePageURL: String?, name: String?, editedName: String?, accountID: String?, accountType: AccountType?) {
|
||||
self.url = url.rs_normalizedURL()
|
||||
self.feedID = feedID
|
||||
self.webFeedID = webFeedID
|
||||
self.homePageURL = homePageURL?.rs_normalizedURL()
|
||||
self.name = name
|
||||
self.editedName = editedName
|
||||
@ -49,7 +49,7 @@ struct PasteboardFeed: Hashable {
|
||||
|
||||
// MARK: - Reading
|
||||
|
||||
init?(dictionary: PasteboardFeedDictionary) {
|
||||
init?(dictionary: PasteboardWebFeedDictionary) {
|
||||
guard let url = dictionary[Key.url] else {
|
||||
return nil
|
||||
}
|
||||
@ -57,7 +57,7 @@ struct PasteboardFeed: Hashable {
|
||||
let homePageURL = dictionary[Key.homePageURL]
|
||||
let name = dictionary[Key.name]
|
||||
let accountID = dictionary[Key.accountID]
|
||||
let feedID = dictionary[Key.feedID]
|
||||
let webFeedID = dictionary[Key.webFeedID]
|
||||
let editedName = dictionary[Key.editedName]
|
||||
|
||||
var accountType: AccountType? = nil
|
||||
@ -65,19 +65,19 @@ struct PasteboardFeed: Hashable {
|
||||
accountType = AccountType(rawValue: accountTypeInt)
|
||||
}
|
||||
|
||||
self.init(url: url, feedID: feedID, homePageURL: homePageURL, name: name, editedName: editedName, accountID: accountID, accountType: accountType)
|
||||
self.init(url: url, webFeedID: webFeedID, homePageURL: homePageURL, name: name, editedName: editedName, accountID: accountID, accountType: accountType)
|
||||
}
|
||||
|
||||
init?(pasteboardItem: NSPasteboardItem) {
|
||||
var pasteboardType: NSPasteboard.PasteboardType?
|
||||
if pasteboardItem.types.contains(FeedPasteboardWriter.feedUTIInternalType) {
|
||||
pasteboardType = FeedPasteboardWriter.feedUTIInternalType
|
||||
if pasteboardItem.types.contains(WebFeedPasteboardWriter.webFeedUTIInternalType) {
|
||||
pasteboardType = WebFeedPasteboardWriter.webFeedUTIInternalType
|
||||
}
|
||||
else if pasteboardItem.types.contains(FeedPasteboardWriter.feedUTIType) {
|
||||
pasteboardType = FeedPasteboardWriter.feedUTIType
|
||||
else if pasteboardItem.types.contains(WebFeedPasteboardWriter.webFeedUTIType) {
|
||||
pasteboardType = WebFeedPasteboardWriter.webFeedUTIType
|
||||
}
|
||||
if let foundType = pasteboardType {
|
||||
if let feedDictionary = pasteboardItem.propertyList(forType: foundType) as? PasteboardFeedDictionary {
|
||||
if let feedDictionary = pasteboardItem.propertyList(forType: foundType) as? PasteboardWebFeedDictionary {
|
||||
self.init(dictionary: feedDictionary)
|
||||
return
|
||||
}
|
||||
@ -94,7 +94,7 @@ struct PasteboardFeed: Hashable {
|
||||
if let foundType = pasteboardType {
|
||||
if let possibleURLString = pasteboardItem.string(forType: foundType) {
|
||||
if possibleURLString.rs_stringMayBeURL() {
|
||||
self.init(url: possibleURLString, feedID: nil, homePageURL: nil, name: nil, editedName: nil, accountID: nil, accountType: nil)
|
||||
self.init(url: possibleURLString, webFeedID: nil, homePageURL: nil, name: nil, editedName: nil, accountID: nil, accountType: nil)
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -103,18 +103,18 @@ struct PasteboardFeed: Hashable {
|
||||
return nil
|
||||
}
|
||||
|
||||
static func pasteboardFeeds(with pasteboard: NSPasteboard) -> Set<PasteboardFeed>? {
|
||||
static func pasteboardFeeds(with pasteboard: NSPasteboard) -> Set<PasteboardWebFeed>? {
|
||||
guard let items = pasteboard.pasteboardItems else {
|
||||
return nil
|
||||
}
|
||||
let feeds = items.compactMap { PasteboardFeed(pasteboardItem: $0) }
|
||||
return feeds.isEmpty ? nil : Set(feeds)
|
||||
let webFeeds = items.compactMap { PasteboardWebFeed(pasteboardItem: $0) }
|
||||
return webFeeds.isEmpty ? nil : Set(webFeeds)
|
||||
}
|
||||
|
||||
// MARK: - Writing
|
||||
|
||||
func exportDictionary() -> PasteboardFeedDictionary {
|
||||
var d = PasteboardFeedDictionary()
|
||||
func exportDictionary() -> PasteboardWebFeedDictionary {
|
||||
var d = PasteboardWebFeedDictionary()
|
||||
d[Key.url] = url
|
||||
d[Key.homePageURL] = homePageURL ?? ""
|
||||
if let nameForDisplay = editedName ?? name {
|
||||
@ -123,54 +123,54 @@ struct PasteboardFeed: Hashable {
|
||||
return d
|
||||
}
|
||||
|
||||
func internalDictionary() -> PasteboardFeedDictionary {
|
||||
var d = PasteboardFeedDictionary()
|
||||
d[PasteboardFeed.Key.feedID] = feedID
|
||||
d[PasteboardFeed.Key.url] = url
|
||||
func internalDictionary() -> PasteboardWebFeedDictionary {
|
||||
var d = PasteboardWebFeedDictionary()
|
||||
d[PasteboardWebFeed.Key.webFeedID] = webFeedID
|
||||
d[PasteboardWebFeed.Key.url] = url
|
||||
if let homePageURL = homePageURL {
|
||||
d[PasteboardFeed.Key.homePageURL] = homePageURL
|
||||
d[PasteboardWebFeed.Key.homePageURL] = homePageURL
|
||||
}
|
||||
if let name = name {
|
||||
d[PasteboardFeed.Key.name] = name
|
||||
d[PasteboardWebFeed.Key.name] = name
|
||||
}
|
||||
if let editedName = editedName {
|
||||
d[PasteboardFeed.Key.editedName] = editedName
|
||||
d[PasteboardWebFeed.Key.editedName] = editedName
|
||||
}
|
||||
if let accountID = accountID {
|
||||
d[PasteboardFeed.Key.accountID] = accountID
|
||||
d[PasteboardWebFeed.Key.accountID] = accountID
|
||||
}
|
||||
if let accountType = accountType {
|
||||
d[PasteboardFeed.Key.accountType] = String(accountType.rawValue)
|
||||
d[PasteboardWebFeed.Key.accountType] = String(accountType.rawValue)
|
||||
}
|
||||
return d
|
||||
}
|
||||
}
|
||||
|
||||
extension Feed: PasteboardWriterOwner {
|
||||
extension WebFeed: PasteboardWriterOwner {
|
||||
|
||||
public var pasteboardWriter: NSPasteboardWriting {
|
||||
return FeedPasteboardWriter(feed: self)
|
||||
return WebFeedPasteboardWriter(webFeed: self)
|
||||
}
|
||||
}
|
||||
|
||||
@objc final class FeedPasteboardWriter: NSObject, NSPasteboardWriting {
|
||||
@objc final class WebFeedPasteboardWriter: NSObject, NSPasteboardWriting {
|
||||
|
||||
private let feed: Feed
|
||||
static let feedUTI = "com.ranchero.feed"
|
||||
static let feedUTIType = NSPasteboard.PasteboardType(rawValue: feedUTI)
|
||||
static let feedUTIInternal = "com.ranchero.NetNewsWire-Evergreen.internal.feed"
|
||||
static let feedUTIInternalType = NSPasteboard.PasteboardType(rawValue: feedUTIInternal)
|
||||
private let webFeed: WebFeed
|
||||
static let webFeedUTI = "com.ranchero.webFeed"
|
||||
static let webFeedUTIType = NSPasteboard.PasteboardType(rawValue: webFeedUTI)
|
||||
static let webFeedUTIInternal = "com.ranchero.NetNewsWire-Evergreen.internal.webFeed"
|
||||
static let webFeedUTIInternalType = NSPasteboard.PasteboardType(rawValue: webFeedUTIInternal)
|
||||
|
||||
|
||||
init(feed: Feed) {
|
||||
self.feed = feed
|
||||
init(webFeed: WebFeed) {
|
||||
self.webFeed = webFeed
|
||||
}
|
||||
|
||||
// MARK: - NSPasteboardWriting
|
||||
|
||||
func writableTypes(for pasteboard: NSPasteboard) -> [NSPasteboard.PasteboardType] {
|
||||
|
||||
return [FeedPasteboardWriter.feedUTIType, .URL, .string, FeedPasteboardWriter.feedUTIInternalType]
|
||||
return [WebFeedPasteboardWriter.webFeedUTIType, .URL, .string, WebFeedPasteboardWriter.webFeedUTIInternalType]
|
||||
}
|
||||
|
||||
func pasteboardPropertyList(forType type: NSPasteboard.PasteboardType) -> Any? {
|
||||
@ -179,12 +179,12 @@ extension Feed: PasteboardWriterOwner {
|
||||
|
||||
switch type {
|
||||
case .string:
|
||||
plist = feed.nameForDisplay
|
||||
plist = webFeed.nameForDisplay
|
||||
case .URL:
|
||||
plist = feed.url
|
||||
case FeedPasteboardWriter.feedUTIType:
|
||||
plist = webFeed.url
|
||||
case WebFeedPasteboardWriter.webFeedUTIType:
|
||||
plist = exportDictionary
|
||||
case FeedPasteboardWriter.feedUTIInternalType:
|
||||
case WebFeedPasteboardWriter.webFeedUTIInternalType:
|
||||
plist = internalDictionary
|
||||
default:
|
||||
plist = nil
|
||||
@ -194,17 +194,17 @@ extension Feed: PasteboardWriterOwner {
|
||||
}
|
||||
}
|
||||
|
||||
private extension FeedPasteboardWriter {
|
||||
private extension WebFeedPasteboardWriter {
|
||||
|
||||
var pasteboardFeed: PasteboardFeed {
|
||||
return PasteboardFeed(url: feed.url, feedID: feed.feedID, homePageURL: feed.homePageURL, name: feed.name, editedName: feed.editedName, accountID: feed.account?.accountID, accountType: feed.account?.type)
|
||||
var pasteboardFeed: PasteboardWebFeed {
|
||||
return PasteboardWebFeed(url: webFeed.url, webFeedID: webFeed.webFeedID, homePageURL: webFeed.homePageURL, name: webFeed.name, editedName: webFeed.editedName, accountID: webFeed.account?.accountID, accountType: webFeed.account?.type)
|
||||
}
|
||||
|
||||
var exportDictionary: PasteboardFeedDictionary {
|
||||
var exportDictionary: PasteboardWebFeedDictionary {
|
||||
return pasteboardFeed.exportDictionary()
|
||||
}
|
||||
|
||||
var internalDictionary: PasteboardFeedDictionary {
|
||||
var internalDictionary: PasteboardWebFeedDictionary {
|
||||
return pasteboardFeed.internalDictionary()
|
||||
}
|
||||
}
|
@ -55,7 +55,7 @@ import Account
|
||||
|
||||
func outlineView(_ outlineView: NSOutlineView, validateDrop info: NSDraggingInfo, proposedItem item: Any?, proposedChildIndex index: Int) -> NSDragOperation {
|
||||
let draggedFolders = PasteboardFolder.pasteboardFolders(with: info.draggingPasteboard)
|
||||
let draggedFeeds = PasteboardFeed.pasteboardFeeds(with: info.draggingPasteboard)
|
||||
let draggedFeeds = PasteboardWebFeed.pasteboardFeeds(with: info.draggingPasteboard)
|
||||
if (draggedFolders == nil && draggedFeeds == nil) || (draggedFolders != nil && draggedFeeds != nil) {
|
||||
return SidebarOutlineDataSource.dragOperationNone
|
||||
}
|
||||
@ -91,7 +91,7 @@ import Account
|
||||
|
||||
func outlineView(_ outlineView: NSOutlineView, acceptDrop info: NSDraggingInfo, item: Any?, childIndex index: Int) -> Bool {
|
||||
let draggedFolders = PasteboardFolder.pasteboardFolders(with: info.draggingPasteboard)
|
||||
let draggedFeeds = PasteboardFeed.pasteboardFeeds(with: info.draggingPasteboard)
|
||||
let draggedFeeds = PasteboardWebFeed.pasteboardFeeds(with: info.draggingPasteboard)
|
||||
if (draggedFolders == nil && draggedFeeds == nil) || (draggedFolders != nil && draggedFeeds != nil) {
|
||||
return false
|
||||
}
|
||||
@ -136,7 +136,7 @@ private extension SidebarOutlineDataSource {
|
||||
// Don’t allow PseudoFeed to be dragged.
|
||||
// This will have to be revisited later. For instance,
|
||||
// user-created smart feeds should be draggable, maybe.
|
||||
return node.representedObject is Folder || node.representedObject is Feed
|
||||
return node.representedObject is Folder || node.representedObject is WebFeed
|
||||
}
|
||||
|
||||
// MARK: - Drag and Drop
|
||||
@ -145,7 +145,7 @@ private extension SidebarOutlineDataSource {
|
||||
case empty, singleLocal, singleNonLocal, multipleLocal, multipleNonLocal, mixed
|
||||
}
|
||||
|
||||
func draggedFeedContentsType(_ draggedFeeds: Set<PasteboardFeed>) -> DraggedFeedsContentsType {
|
||||
func draggedFeedContentsType(_ draggedFeeds: Set<PasteboardWebFeed>) -> DraggedFeedsContentsType {
|
||||
if draggedFeeds.isEmpty {
|
||||
return .empty
|
||||
}
|
||||
@ -173,14 +173,14 @@ private extension SidebarOutlineDataSource {
|
||||
return .multipleNonLocal
|
||||
}
|
||||
|
||||
func singleNonLocalFeed(from feeds: Set<PasteboardFeed>) -> PasteboardFeed? {
|
||||
func singleNonLocalFeed(from feeds: Set<PasteboardWebFeed>) -> PasteboardWebFeed? {
|
||||
guard feeds.count == 1, let feed = feeds.first else {
|
||||
return nil
|
||||
}
|
||||
return feed.isLocalFeed ? nil : feed
|
||||
}
|
||||
|
||||
func validateSingleNonLocalFeedDrop(_ outlineView: NSOutlineView, _ draggedFeed: PasteboardFeed, _ parentNode: Node, _ index: Int) -> NSDragOperation {
|
||||
func validateSingleNonLocalFeedDrop(_ outlineView: NSOutlineView, _ draggedFeed: PasteboardWebFeed, _ parentNode: Node, _ index: Int) -> NSDragOperation {
|
||||
// A non-local feed should always drag on to an Account or Folder node, with NSOutlineViewDropOnItemIndex — since we don’t know where it would sort till we read the feed.
|
||||
guard let dropTargetNode = ancestorThatCanAcceptNonLocalFeed(parentNode) else {
|
||||
return SidebarOutlineDataSource.dragOperationNone
|
||||
@ -191,7 +191,7 @@ private extension SidebarOutlineDataSource {
|
||||
return .copy
|
||||
}
|
||||
|
||||
func validateSingleLocalFeedDrop(_ outlineView: NSOutlineView, _ draggedFeed: PasteboardFeed, _ parentNode: Node, _ index: Int) -> NSDragOperation {
|
||||
func validateSingleLocalFeedDrop(_ outlineView: NSOutlineView, _ draggedFeed: PasteboardWebFeed, _ parentNode: Node, _ index: Int) -> NSDragOperation {
|
||||
// A local feed should always drag on to an Account or Folder node, and we can provide an index.
|
||||
guard let dropTargetNode = ancestorThatCanAcceptLocalFeed(parentNode) else {
|
||||
return SidebarOutlineDataSource.dragOperationNone
|
||||
@ -212,7 +212,7 @@ private extension SidebarOutlineDataSource {
|
||||
return localDragOperation()
|
||||
}
|
||||
|
||||
func validateLocalFeedsDrop(_ outlineView: NSOutlineView, _ draggedFeeds: Set<PasteboardFeed>, _ parentNode: Node, _ index: Int) -> NSDragOperation {
|
||||
func validateLocalFeedsDrop(_ outlineView: NSOutlineView, _ draggedFeeds: Set<PasteboardWebFeed>, _ parentNode: Node, _ index: Int) -> NSDragOperation {
|
||||
// Local feeds should always drag on to an Account or Folder node, and index should be NSOutlineViewDropOnItemIndex since we can’t provide multiple indexes.
|
||||
guard let dropTargetNode = ancestorThatCanAcceptLocalFeed(parentNode) else {
|
||||
return SidebarOutlineDataSource.dragOperationNone
|
||||
@ -244,7 +244,7 @@ private extension SidebarOutlineDataSource {
|
||||
if let folder = node.representedObject as? Folder {
|
||||
return folder.account
|
||||
}
|
||||
if let feed = node.representedObject as? Feed {
|
||||
if let feed = node.representedObject as? WebFeed {
|
||||
return feed.account
|
||||
}
|
||||
return nil
|
||||
@ -303,12 +303,12 @@ private extension SidebarOutlineDataSource {
|
||||
return localDragOperation()
|
||||
}
|
||||
|
||||
func copyFeedInAccount(node: Node, to parentNode: Node) {
|
||||
guard let feed = node.representedObject as? Feed, let destination = parentNode.representedObject as? Container else {
|
||||
func copyWebFeedInAccount(node: Node, to parentNode: Node) {
|
||||
guard let feed = node.representedObject as? WebFeed, let destination = parentNode.representedObject as? Container else {
|
||||
return
|
||||
}
|
||||
|
||||
destination.account?.addFeed(feed, to: destination) { result in
|
||||
destination.account?.addWebFeed(feed, to: destination) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
break
|
||||
@ -318,15 +318,15 @@ private extension SidebarOutlineDataSource {
|
||||
}
|
||||
}
|
||||
|
||||
func moveFeedInAccount(node: Node, to parentNode: Node) {
|
||||
guard let feed = node.representedObject as? Feed,
|
||||
func moveWebFeedInAccount(node: Node, to parentNode: Node) {
|
||||
guard let feed = node.representedObject as? WebFeed,
|
||||
let source = node.parent?.representedObject as? Container,
|
||||
let destination = parentNode.representedObject as? Container else {
|
||||
return
|
||||
}
|
||||
|
||||
BatchUpdate.shared.start()
|
||||
source.account?.moveFeed(feed, from: source, to: destination) { result in
|
||||
source.account?.moveWebFeed(feed, from: source, to: destination) { result in
|
||||
BatchUpdate.shared.end()
|
||||
switch result {
|
||||
case .success:
|
||||
@ -337,15 +337,15 @@ private extension SidebarOutlineDataSource {
|
||||
}
|
||||
}
|
||||
|
||||
func copyFeedBetweenAccounts(node: Node, to parentNode: Node) {
|
||||
guard let feed = node.representedObject as? Feed,
|
||||
func copyWebFeedBetweenAccounts(node: Node, to parentNode: Node) {
|
||||
guard let feed = node.representedObject as? WebFeed,
|
||||
let destinationAccount = nodeAccount(parentNode),
|
||||
let destinationContainer = parentNode.representedObject as? Container else {
|
||||
return
|
||||
}
|
||||
|
||||
if let existingFeed = destinationAccount.existingFeed(withURL: feed.url) {
|
||||
destinationAccount.addFeed(existingFeed, to: destinationContainer) { result in
|
||||
if let existingFeed = destinationAccount.existingWebFeed(withURL: feed.url) {
|
||||
destinationAccount.addWebFeed(existingFeed, to: destinationContainer) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
break
|
||||
@ -354,7 +354,7 @@ private extension SidebarOutlineDataSource {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
destinationAccount.createFeed(url: feed.url, name: feed.editedName, container: destinationContainer) { result in
|
||||
destinationAccount.createWebFeed(url: feed.url, name: feed.editedName, container: destinationContainer) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
break
|
||||
@ -365,8 +365,8 @@ private extension SidebarOutlineDataSource {
|
||||
}
|
||||
}
|
||||
|
||||
func moveFeedBetweenAccounts(node: Node, to parentNode: Node) {
|
||||
guard let feed = node.representedObject as? Feed,
|
||||
func moveWebFeedBetweenAccounts(node: Node, to parentNode: Node) {
|
||||
guard let feed = node.representedObject as? WebFeed,
|
||||
let sourceAccount = nodeAccount(node),
|
||||
let sourceContainer = node.parent?.representedObject as? Container,
|
||||
let destinationAccount = nodeAccount(parentNode),
|
||||
@ -374,13 +374,13 @@ private extension SidebarOutlineDataSource {
|
||||
return
|
||||
}
|
||||
|
||||
if let existingFeed = destinationAccount.existingFeed(withURL: feed.url) {
|
||||
if let existingFeed = destinationAccount.existingWebFeed(withURL: feed.url) {
|
||||
|
||||
BatchUpdate.shared.start()
|
||||
destinationAccount.addFeed(existingFeed, to: destinationContainer) { result in
|
||||
destinationAccount.addWebFeed(existingFeed, to: destinationContainer) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
sourceAccount.removeFeed(feed, from: sourceContainer) { result in
|
||||
sourceAccount.removeWebFeed(feed, from: sourceContainer) { result in
|
||||
BatchUpdate.shared.end()
|
||||
switch result {
|
||||
case .success:
|
||||
@ -398,10 +398,10 @@ private extension SidebarOutlineDataSource {
|
||||
} else {
|
||||
|
||||
BatchUpdate.shared.start()
|
||||
destinationAccount.createFeed(url: feed.url, name: feed.editedName, container: destinationContainer) { result in
|
||||
destinationAccount.createWebFeed(url: feed.url, name: feed.editedName, container: destinationContainer) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
sourceAccount.removeFeed(feed, from: sourceContainer) { result in
|
||||
sourceAccount.removeWebFeed(feed, from: sourceContainer) { result in
|
||||
BatchUpdate.shared.end()
|
||||
switch result {
|
||||
case .success:
|
||||
@ -419,7 +419,7 @@ private extension SidebarOutlineDataSource {
|
||||
}
|
||||
}
|
||||
|
||||
func acceptLocalFeedsDrop(_ outlineView: NSOutlineView, _ draggedFeeds: Set<PasteboardFeed>, _ parentNode: Node, _ index: Int) -> Bool {
|
||||
func acceptLocalFeedsDrop(_ outlineView: NSOutlineView, _ draggedFeeds: Set<PasteboardWebFeed>, _ parentNode: Node, _ index: Int) -> Bool {
|
||||
guard let draggedNodes = draggedNodes else {
|
||||
return false
|
||||
}
|
||||
@ -427,15 +427,15 @@ private extension SidebarOutlineDataSource {
|
||||
draggedNodes.forEach { node in
|
||||
if sameAccount(node, parentNode) {
|
||||
if NSApplication.shared.currentEvent?.modifierFlags.contains(.option) ?? false {
|
||||
copyFeedInAccount(node: node, to: parentNode)
|
||||
copyWebFeedInAccount(node: node, to: parentNode)
|
||||
} else {
|
||||
moveFeedInAccount(node: node, to: parentNode)
|
||||
moveWebFeedInAccount(node: node, to: parentNode)
|
||||
}
|
||||
} else {
|
||||
if NSApplication.shared.currentEvent?.modifierFlags.contains(.option) ?? false {
|
||||
copyFeedBetweenAccounts(node: node, to: parentNode)
|
||||
copyWebFeedBetweenAccounts(node: node, to: parentNode)
|
||||
} else {
|
||||
moveFeedBetweenAccounts(node: node, to: parentNode)
|
||||
moveWebFeedBetweenAccounts(node: node, to: parentNode)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -509,10 +509,10 @@ private extension SidebarOutlineDataSource {
|
||||
switch result {
|
||||
case .success(let destinationFolder):
|
||||
let group = DispatchGroup()
|
||||
for feed in folder.topLevelFeeds {
|
||||
if let existingFeed = destinationAccount.existingFeed(withURL: feed.url) {
|
||||
for feed in folder.topLevelWebFeeds {
|
||||
if let existingFeed = destinationAccount.existingWebFeed(withURL: feed.url) {
|
||||
group.enter()
|
||||
destinationAccount.addFeed(existingFeed, to: destinationFolder) { result in
|
||||
destinationAccount.addWebFeed(existingFeed, to: destinationFolder) { result in
|
||||
group.leave()
|
||||
switch result {
|
||||
case .success:
|
||||
@ -523,7 +523,7 @@ private extension SidebarOutlineDataSource {
|
||||
}
|
||||
} else {
|
||||
group.enter()
|
||||
destinationAccount.createFeed(url: feed.url, name: feed.editedName, container: destinationFolder) { result in
|
||||
destinationAccount.createWebFeed(url: feed.url, name: feed.editedName, container: destinationFolder) { result in
|
||||
group.leave()
|
||||
switch result {
|
||||
case .success:
|
||||
@ -563,7 +563,7 @@ private extension SidebarOutlineDataSource {
|
||||
return true
|
||||
}
|
||||
|
||||
func acceptSingleNonLocalFeedDrop(_ outlineView: NSOutlineView, _ draggedFeed: PasteboardFeed, _ parentNode: Node, _ index: Int) -> Bool {
|
||||
func acceptSingleNonLocalFeedDrop(_ outlineView: NSOutlineView, _ draggedFeed: PasteboardWebFeed, _ parentNode: Node, _ index: Int) -> Bool {
|
||||
guard nodeIsDropTarget(parentNode), index == NSOutlineViewDropOnItemIndex else {
|
||||
return false
|
||||
}
|
||||
@ -580,12 +580,12 @@ private extension SidebarOutlineDataSource {
|
||||
return true
|
||||
}
|
||||
|
||||
func nodeHasChildRepresentingDraggedFeed(_ parentNode: Node, _ draggedFeed: PasteboardFeed) -> Bool {
|
||||
func nodeHasChildRepresentingDraggedFeed(_ parentNode: Node, _ draggedFeed: PasteboardWebFeed) -> Bool {
|
||||
return nodeHasChildRepresentingAnyDraggedFeed(parentNode, Set([draggedFeed]))
|
||||
}
|
||||
|
||||
func nodeRepresentsAnyDraggedFeed(_ node: Node, _ draggedFeeds: Set<PasteboardFeed>) -> Bool {
|
||||
guard let feed = node.representedObject as? Feed else {
|
||||
func nodeRepresentsAnyDraggedFeed(_ node: Node, _ draggedFeeds: Set<PasteboardWebFeed>) -> Bool {
|
||||
guard let feed = node.representedObject as? WebFeed else {
|
||||
return false
|
||||
}
|
||||
for draggedFeed in draggedFeeds {
|
||||
@ -610,8 +610,8 @@ private extension SidebarOutlineDataSource {
|
||||
return account
|
||||
} else if let folder = node.representedObject as? Folder {
|
||||
return folder.account
|
||||
} else if let feed = node.representedObject as? Feed {
|
||||
return feed.account
|
||||
} else if let webFeed = node.representedObject as? WebFeed {
|
||||
return webFeed.account
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
@ -622,7 +622,7 @@ private extension SidebarOutlineDataSource {
|
||||
return nodeAccount(node)?.accountID
|
||||
}
|
||||
|
||||
func nodeHasChildRepresentingAnyDraggedFeed(_ parentNode: Node, _ draggedFeeds: Set<PasteboardFeed>) -> Bool {
|
||||
func nodeHasChildRepresentingAnyDraggedFeed(_ parentNode: Node, _ draggedFeeds: Set<PasteboardWebFeed>) -> Bool {
|
||||
for node in parentNode.childNodes {
|
||||
if nodeRepresentsAnyDraggedFeed(node, draggedFeeds) {
|
||||
return true
|
||||
@ -631,11 +631,11 @@ private extension SidebarOutlineDataSource {
|
||||
return false
|
||||
}
|
||||
|
||||
func violatesAccountSpecificBehavior(_ parentNode: Node, _ draggedFeed: PasteboardFeed) -> Bool {
|
||||
func violatesAccountSpecificBehavior(_ parentNode: Node, _ draggedFeed: PasteboardWebFeed) -> Bool {
|
||||
return violatesAccountSpecificBehavior(parentNode, Set([draggedFeed]))
|
||||
}
|
||||
|
||||
func violatesAccountSpecificBehavior(_ parentNode: Node, _ draggedFeeds: Set<PasteboardFeed>) -> Bool {
|
||||
func violatesAccountSpecificBehavior(_ parentNode: Node, _ draggedFeeds: Set<PasteboardWebFeed>) -> Bool {
|
||||
if violatesDisallowFeedInRootFolder(parentNode) {
|
||||
return true
|
||||
}
|
||||
@ -659,7 +659,7 @@ private extension SidebarOutlineDataSource {
|
||||
return false
|
||||
}
|
||||
|
||||
func violatesDisallowFeedCopyInRootFolder(_ parentNode: Node, _ draggedFeeds: Set<PasteboardFeed>) -> Bool {
|
||||
func violatesDisallowFeedCopyInRootFolder(_ parentNode: Node, _ draggedFeeds: Set<PasteboardWebFeed>) -> Bool {
|
||||
guard let parentAccount = nodeAccount(parentNode), parentAccount.behaviors.contains(.disallowFeedCopyInRootFolder) else {
|
||||
return false
|
||||
}
|
||||
@ -677,7 +677,7 @@ private extension SidebarOutlineDataSource {
|
||||
return false
|
||||
}
|
||||
|
||||
func indexWhereDraggedFeedWouldAppear(_ parentNode: Node, _ draggedFeed: PasteboardFeed) -> Int {
|
||||
func indexWhereDraggedFeedWouldAppear(_ parentNode: Node, _ draggedFeed: PasteboardWebFeed) -> Int {
|
||||
let draggedFeedWrapper = PasteboardFeedObjectWrapper(pasteboardFeed: draggedFeed)
|
||||
let draggedFeedNode = Node(representedObject: draggedFeedWrapper, parent: nil)
|
||||
let nodes = parentNode.childNodes + [draggedFeedNode]
|
||||
@ -706,9 +706,9 @@ final class PasteboardFeedObjectWrapper: DisplayNameProvider {
|
||||
var nameForDisplay: String {
|
||||
return pasteboardFeed.editedName ?? pasteboardFeed.name ?? ""
|
||||
}
|
||||
let pasteboardFeed: PasteboardFeed
|
||||
let pasteboardFeed: PasteboardWebFeed
|
||||
|
||||
init(pasteboardFeed: PasteboardFeed) {
|
||||
init(pasteboardFeed: PasteboardWebFeed) {
|
||||
self.pasteboardFeed = pasteboardFeed
|
||||
}
|
||||
}
|
||||
|
@ -26,8 +26,8 @@ extension SidebarViewController {
|
||||
let object = objects.first!
|
||||
|
||||
switch object {
|
||||
case is Feed:
|
||||
return menuForFeed(object as! Feed)
|
||||
case is WebFeed:
|
||||
return menuForWebFeed(object as! WebFeed)
|
||||
case is Folder:
|
||||
return menuForFolder(object as! Folder)
|
||||
case is PseudoFeed:
|
||||
@ -83,7 +83,7 @@ extension SidebarViewController {
|
||||
|
||||
@objc func renameFromContextualMenu(_ sender: Any?) {
|
||||
|
||||
guard let window = view.window, let menuItem = sender as? NSMenuItem, let object = menuItem.representedObject as? DisplayNameProvider, object is Feed || object is Folder else {
|
||||
guard let window = view.window, let menuItem = sender as? NSMenuItem, let object = menuItem.representedObject as? DisplayNameProvider, object is WebFeed || object is Folder else {
|
||||
return
|
||||
}
|
||||
|
||||
@ -99,7 +99,7 @@ extension SidebarViewController: RenameWindowControllerDelegate {
|
||||
|
||||
func renameWindowController(_ windowController: RenameWindowController, didRenameObject object: Any, withNewName name: String) {
|
||||
|
||||
if let feed = object as? Feed {
|
||||
if let feed = object as? WebFeed {
|
||||
feed.rename(to: name) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
@ -135,32 +135,32 @@ private extension SidebarViewController {
|
||||
return menu
|
||||
}
|
||||
|
||||
func menuForFeed(_ feed: Feed) -> NSMenu? {
|
||||
func menuForWebFeed(_ webFeed: WebFeed) -> NSMenu? {
|
||||
|
||||
let menu = NSMenu(title: "")
|
||||
|
||||
if feed.unreadCount > 0 {
|
||||
menu.addItem(markAllReadMenuItem([feed]))
|
||||
if webFeed.unreadCount > 0 {
|
||||
menu.addItem(markAllReadMenuItem([webFeed]))
|
||||
menu.addItem(NSMenuItem.separator())
|
||||
}
|
||||
|
||||
if let homePageURL = feed.homePageURL, let _ = URL(string: homePageURL) {
|
||||
if let homePageURL = webFeed.homePageURL, let _ = URL(string: homePageURL) {
|
||||
let item = menuItem(NSLocalizedString("Open Home Page", comment: "Command"), #selector(openHomePageFromContextualMenu(_:)), homePageURL)
|
||||
menu.addItem(item)
|
||||
menu.addItem(NSMenuItem.separator())
|
||||
}
|
||||
|
||||
let copyFeedURLItem = menuItem(NSLocalizedString("Copy Feed URL", comment: "Command"), #selector(copyURLFromContextualMenu(_:)), feed.url)
|
||||
let copyFeedURLItem = menuItem(NSLocalizedString("Copy Feed URL", comment: "Command"), #selector(copyURLFromContextualMenu(_:)), webFeed.url)
|
||||
menu.addItem(copyFeedURLItem)
|
||||
|
||||
if let homePageURL = feed.homePageURL {
|
||||
if let homePageURL = webFeed.homePageURL {
|
||||
let item = menuItem(NSLocalizedString("Copy Home Page URL", comment: "Command"), #selector(copyURLFromContextualMenu(_:)), homePageURL)
|
||||
menu.addItem(item)
|
||||
}
|
||||
menu.addItem(NSMenuItem.separator())
|
||||
|
||||
menu.addItem(renameMenuItem(feed))
|
||||
menu.addItem(deleteMenuItem([feed]))
|
||||
menu.addItem(renameMenuItem(webFeed))
|
||||
menu.addItem(deleteMenuItem([webFeed]))
|
||||
|
||||
return menu
|
||||
}
|
||||
@ -245,7 +245,7 @@ private extension SidebarViewController {
|
||||
|
||||
func objectIsFeedOrFolder(_ object: Any) -> Bool {
|
||||
|
||||
return object is Feed || object is Folder
|
||||
return object is WebFeed || object is Folder
|
||||
}
|
||||
|
||||
func menuItem(_ title: String, _ action: Selector, _ representedObject: Any) -> NSMenuItem {
|
||||
|
@ -23,7 +23,7 @@ protocol SidebarDelegate: class {
|
||||
|
||||
weak var delegate: SidebarDelegate?
|
||||
|
||||
let treeControllerDelegate = FeedTreeControllerDelegate()
|
||||
let treeControllerDelegate = WebFeedTreeControllerDelegate()
|
||||
lazy var treeController: TreeController = {
|
||||
return TreeController(delegate: treeControllerDelegate)
|
||||
}()
|
||||
@ -49,7 +49,7 @@ protocol SidebarDelegate: class {
|
||||
outlineView.dataSource = dataSource
|
||||
outlineView.doubleAction = #selector(doubleClickedSidebar(_:))
|
||||
outlineView.setDraggingSourceOperationMask([.move, .copy], forLocal: true)
|
||||
outlineView.registerForDraggedTypes([FeedPasteboardWriter.feedUTIInternalType, FeedPasteboardWriter.feedUTIType, .URL, .string])
|
||||
outlineView.registerForDraggedTypes([WebFeedPasteboardWriter.webFeedUTIInternalType, WebFeedPasteboardWriter.webFeedUTIType, .URL, .string])
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(unreadCountDidChange(_:)), name: .UnreadCountDidChange, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(containerChildrenDidChange(_:)), name: .ChildrenDidChange, object: nil)
|
||||
@ -59,7 +59,7 @@ protocol SidebarDelegate: class {
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(userDidAddFeed(_:)), name: .UserDidAddFeed, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(batchUpdateDidPerform(_:)), name: .BatchUpdateDidPerform, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(faviconDidBecomeAvailable(_:)), name: .FaviconDidBecomeAvailable, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(feedSettingDidChange(_:)), name: .FeedSettingDidChange, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(webFeedSettingDidChange(_:)), name: .WebFeedSettingDidChange, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(displayNameDidChange(_:)), name: .DisplayNameDidChange, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(userDidRequestSidebarSelection(_:)), name: .UserDidRequestSidebarSelection, object: nil)
|
||||
|
||||
@ -110,7 +110,7 @@ protocol SidebarDelegate: class {
|
||||
}
|
||||
|
||||
@objc func userDidAddFeed(_ notification: Notification) {
|
||||
guard let feed = notification.userInfo?[UserInfoKey.feed] else {
|
||||
guard let feed = notification.userInfo?[UserInfoKey.webFeed] else {
|
||||
return
|
||||
}
|
||||
revealAndSelectRepresentedObject(feed as AnyObject)
|
||||
@ -120,12 +120,12 @@ protocol SidebarDelegate: class {
|
||||
applyToAvailableCells(configureFavicon)
|
||||
}
|
||||
|
||||
@objc func feedSettingDidChange(_ note: Notification) {
|
||||
guard let feed = note.object as? Feed, let key = note.userInfo?[Feed.FeedSettingUserInfoKey] as? String else {
|
||||
@objc func webFeedSettingDidChange(_ note: Notification) {
|
||||
guard let webFeed = note.object as? WebFeed, let key = note.userInfo?[WebFeed.WebFeedSettingUserInfoKey] as? String else {
|
||||
return
|
||||
}
|
||||
if key == Feed.FeedSettingKey.homePageURL || key == Feed.FeedSettingKey.faviconURL {
|
||||
configureCellsForRepresentedObject(feed)
|
||||
if key == WebFeed.WebFeedSettingKey.homePageURL || key == WebFeed.WebFeedSettingKey.faviconURL {
|
||||
configureCellsForRepresentedObject(webFeed)
|
||||
}
|
||||
}
|
||||
|
||||
@ -140,7 +140,7 @@ protocol SidebarDelegate: class {
|
||||
}
|
||||
|
||||
@objc func userDidRequestSidebarSelection(_ note: Notification) {
|
||||
guard let feed = note.userInfo?[UserInfoKey.feed] else {
|
||||
guard let feed = note.userInfo?[UserInfoKey.webFeed] else {
|
||||
return
|
||||
}
|
||||
revealAndSelectRepresentedObject(feed as AnyObject)
|
||||
@ -370,11 +370,11 @@ private extension SidebarViewController {
|
||||
return selectedNodes.first!
|
||||
}
|
||||
|
||||
var singleSelectedFeed: Feed? {
|
||||
var singleSelectedFeed: WebFeed? {
|
||||
guard let node = singleSelectedNode else {
|
||||
return nil
|
||||
}
|
||||
return node.representedObject as? Feed
|
||||
return node.representedObject as? WebFeed
|
||||
}
|
||||
|
||||
func rebuildTreeAndReloadDataIfNeeded() {
|
||||
@ -413,11 +413,11 @@ private extension SidebarViewController {
|
||||
// For feeds, actually fetch from database.
|
||||
|
||||
for object in objects {
|
||||
if let feed = object as? Feed, let account = feed.account {
|
||||
if let feed = object as? WebFeed, let account = feed.account {
|
||||
account.updateUnreadCounts(for: Set([feed]))
|
||||
}
|
||||
else if let folder = object as? Folder, let account = folder.account {
|
||||
account.updateUnreadCounts(for: folder.flattenedFeeds())
|
||||
account.updateUnreadCounts(for: folder.flattenedWebFeeds())
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -504,10 +504,10 @@ private extension SidebarViewController {
|
||||
}
|
||||
|
||||
func findFeedNode(_ userInfo: [AnyHashable : Any]?, beginningAt startingNode: Node) -> Node? {
|
||||
guard let feedID = userInfo?[ArticlePathKey.feedID] as? String else {
|
||||
guard let webFeedID = userInfo?[ArticlePathKey.webFeedID] as? String else {
|
||||
return nil
|
||||
}
|
||||
if let node = startingNode.descendantNode(where: { ($0.representedObject as? Feed)?.feedID == feedID }) {
|
||||
if let node = startingNode.descendantNode(where: { ($0.representedObject as? WebFeed)?.webFeedID == webFeedID }) {
|
||||
return node
|
||||
}
|
||||
return nil
|
||||
@ -620,7 +620,7 @@ private extension Node {
|
||||
if representedObject === object {
|
||||
return true
|
||||
}
|
||||
if let feed1 = object as? Feed, let feed2 = representedObject as? Feed {
|
||||
if let feed1 = object as? WebFeed, let feed2 = representedObject as? WebFeed {
|
||||
return feed1 == feed2
|
||||
}
|
||||
return false
|
||||
|
@ -91,7 +91,7 @@ private extension ArticlePasteboardWriter {
|
||||
|
||||
s += "Date: \(article.logicalDatePublished)\n\n"
|
||||
|
||||
if let feed = article.feed {
|
||||
if let feed = article.webFeed {
|
||||
s += "Feed: \(feed.nameForDisplay)\n"
|
||||
if let homePageURL = feed.homePageURL {
|
||||
s += "Home page: \(homePageURL)\n"
|
||||
@ -106,7 +106,7 @@ private extension ArticlePasteboardWriter {
|
||||
static let articleID = "articleID" // database ID, unique per account
|
||||
static let uniqueID = "uniqueID" // unique ID, unique per feed (guid, or possibly calculated)
|
||||
static let feedURL = "feedURL"
|
||||
static let feedID = "feedID" // may differ from feedURL if coming from a syncing system
|
||||
static let webFeedID = "webFeedID" // may differ from feedURL if coming from a syncing system
|
||||
static let title = "title"
|
||||
static let contentHTML = "contentHTML"
|
||||
static let contentText = "contentText"
|
||||
@ -147,11 +147,11 @@ private extension ArticlePasteboardWriter {
|
||||
d[Key.articleID] = article.articleID
|
||||
d[Key.uniqueID] = article.uniqueID
|
||||
|
||||
if let feed = article.feed {
|
||||
if let feed = article.webFeed {
|
||||
d[Key.feedURL] = feed.url
|
||||
}
|
||||
|
||||
d[Key.feedID] = article.feedID
|
||||
d[Key.webFeedID] = article.webFeedID
|
||||
d[Key.title] = article.title ?? nil
|
||||
d[Key.contentHTML] = article.contentHTML ?? nil
|
||||
d[Key.contentText] = article.contentText ?? nil
|
||||
|
@ -74,12 +74,12 @@ extension TimelineViewController {
|
||||
|
||||
@objc func selectFeedInSidebarFromContextualMenu(_ sender: Any?) {
|
||||
|
||||
guard let menuItem = sender as? NSMenuItem, let feed = menuItem.representedObject as? Feed else {
|
||||
guard let menuItem = sender as? NSMenuItem, let feed = menuItem.representedObject as? WebFeed else {
|
||||
return
|
||||
}
|
||||
|
||||
var userInfo = UserInfoDictionary()
|
||||
userInfo[UserInfoKey.feed] = feed
|
||||
userInfo[UserInfoKey.webFeed] = feed
|
||||
|
||||
NotificationCenter.default.post(name: .UserDidRequestSidebarSelection, object: self, userInfo: userInfo)
|
||||
|
||||
@ -174,7 +174,7 @@ private extension TimelineViewController {
|
||||
|
||||
menu.addSeparatorIfNeeded()
|
||||
|
||||
if articles.count == 1, let feed = articles.first!.feed {
|
||||
if articles.count == 1, let feed = articles.first!.webFeed {
|
||||
menu.addItem(selectFeedInSidebarMenuItem(feed))
|
||||
if let markAllMenuItem = markAllAsReadMenuItem(feed) {
|
||||
menu.addItem(markAllMenuItem)
|
||||
@ -247,13 +247,13 @@ private extension TimelineViewController {
|
||||
return menuItem(NSLocalizedString("Mark Older as Read", comment: "Command"), #selector(markOlderArticlesReadFromContextualMenu(_:)), articles)
|
||||
}
|
||||
|
||||
func selectFeedInSidebarMenuItem(_ feed: Feed) -> NSMenuItem {
|
||||
func selectFeedInSidebarMenuItem(_ feed: WebFeed) -> NSMenuItem {
|
||||
let localizedMenuText = NSLocalizedString("Select “%@” in Sidebar", comment: "Command")
|
||||
let formattedMenuText = NSString.localizedStringWithFormat(localizedMenuText as NSString, feed.nameForDisplay)
|
||||
return menuItem(formattedMenuText as String, #selector(selectFeedInSidebarFromContextualMenu(_:)), feed)
|
||||
}
|
||||
|
||||
func markAllAsReadMenuItem(_ feed: Feed) -> NSMenuItem? {
|
||||
func markAllAsReadMenuItem(_ feed: WebFeed) -> NSMenuItem? {
|
||||
|
||||
let articles = Array(feed.fetchArticles())
|
||||
guard articles.canMarkAllAsRead() else {
|
||||
|
@ -25,7 +25,7 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr
|
||||
if !representedObjectArraysAreEqual(oldValue, representedObjects) {
|
||||
unreadCount = 0
|
||||
if let representedObjects = representedObjects {
|
||||
if representedObjects.count == 1 && representedObjects.first is Feed {
|
||||
if representedObjects.count == 1 && representedObjects.first is WebFeed {
|
||||
showFeedNames = false
|
||||
}
|
||||
else {
|
||||
@ -168,7 +168,7 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr
|
||||
|
||||
if !didRegisterForNotifications {
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(statusesDidChange(_:)), name: .StatusesDidChange, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(feedIconDidBecomeAvailable(_:)), name: .FeedIconDidBecomeAvailable, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(webFeedIconDidBecomeAvailable(_:)), name: .WebFeedIconDidBecomeAvailable, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(avatarDidBecomeAvailable(_:)), name: .AvatarDidBecomeAvailable, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(faviconDidBecomeAvailable(_:)), name: .FaviconDidBecomeAvailable, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(accountDidDownloadArticles(_:)), name: .AccountDidDownloadArticles, object: nil)
|
||||
@ -437,15 +437,15 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr
|
||||
updateUnreadCount()
|
||||
}
|
||||
|
||||
@objc func feedIconDidBecomeAvailable(_ note: Notification) {
|
||||
guard showIcons, let feed = note.userInfo?[UserInfoKey.feed] as? Feed else {
|
||||
@objc func webFeedIconDidBecomeAvailable(_ note: Notification) {
|
||||
guard showIcons, let feed = note.userInfo?[UserInfoKey.webFeed] as? WebFeed else {
|
||||
return
|
||||
}
|
||||
let indexesToReload = tableView.indexesOfAvailableRowsPassingTest { (row) -> Bool in
|
||||
guard let article = articles.articleAtRow(row) else {
|
||||
return false
|
||||
}
|
||||
return feed == article.feed
|
||||
return feed == article.webFeed
|
||||
}
|
||||
if let indexesToReload = indexesToReload {
|
||||
reloadCells(for: indexesToReload)
|
||||
@ -480,11 +480,11 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr
|
||||
}
|
||||
|
||||
@objc func accountDidDownloadArticles(_ note: Notification) {
|
||||
guard let feeds = note.userInfo?[Account.UserInfoKey.feeds] as? Set<Feed> else {
|
||||
guard let feeds = note.userInfo?[Account.UserInfoKey.webFeeds] as? Set<WebFeed> else {
|
||||
return
|
||||
}
|
||||
|
||||
let shouldFetchAndMergeArticles = representedObjectsContainsAnyFeed(feeds) || representedObjectsContainsAnyPseudoFeed()
|
||||
let shouldFetchAndMergeArticles = representedObjectsContainsAnyWebFeed(feeds) || representedObjectsContainsAnyPseudoFeed()
|
||||
if shouldFetchAndMergeArticles {
|
||||
queueFetchAndMergeArticles()
|
||||
}
|
||||
@ -568,7 +568,7 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr
|
||||
let longTitle = "But I must explain to you how all this mistaken idea of denouncing pleasure and praising pain was born and I will give you a complete account of the system, and expound the actual teachings of the great explorer of the truth, the master-builder of human happiness. No one rejects, dislikes, or avoids pleasure itself, because it is pleasure, but because those who do not know how to pursue pleasure rationally encounter consequences that are extremely painful. Nor again is there anyone who loves or pursues or desires to obtain pain of itself, because it is pain, but because occasionally circumstances occur in which toil and pain can procure him some great pleasure. To take a trivial example, which of us ever undertakes laborious physical exercise, except to obtain some advantage from it? But who has any right to find fault with a man who chooses to enjoy a pleasure that has no annoying consequences, or one who avoids a pain that produces no resultant pleasure?"
|
||||
let prototypeID = "prototype"
|
||||
let status = ArticleStatus(articleID: prototypeID, read: false, starred: false, userDeleted: false, dateArrived: Date())
|
||||
let prototypeArticle = Article(accountID: prototypeID, articleID: prototypeID, feedID: prototypeID, uniqueID: prototypeID, title: longTitle, contentHTML: nil, contentText: nil, url: nil, externalURL: nil, summary: nil, imageURL: nil, bannerImageURL: nil, datePublished: nil, dateModified: nil, authors: nil, attachments: nil, status: status)
|
||||
let prototypeArticle = Article(accountID: prototypeID, articleID: prototypeID, webFeedID: prototypeID, uniqueID: prototypeID, title: longTitle, contentHTML: nil, contentText: nil, url: nil, externalURL: nil, summary: nil, imageURL: nil, bannerImageURL: nil, datePublished: nil, dateModified: nil, authors: nil, attachments: nil, status: status)
|
||||
|
||||
let prototypeCellData = TimelineCellData(article: prototypeArticle, showFeedName: showingFeedNames, feedName: "Prototype Feed Name", iconImage: nil, showIcon: false, featuredImage: nil)
|
||||
let height = TimelineCellLayout.height(for: 100, cellData: prototypeCellData, appearance: cellAppearance)
|
||||
@ -717,7 +717,7 @@ extension TimelineViewController: NSTableViewDelegate {
|
||||
private func configureTimelineCell(_ cell: TimelineTableCellView, article: Article) {
|
||||
cell.objectValue = article
|
||||
let iconImage = article.iconImage()
|
||||
cell.cellData = TimelineCellData(article: article, showFeedName: showFeedNames, feedName: article.feed?.nameForDisplay, iconImage: iconImage, showIcon: showIcons, featuredImage: nil)
|
||||
cell.cellData = TimelineCellData(article: article, showFeedName: showFeedNames, feedName: article.webFeed?.nameForDisplay, iconImage: iconImage, showIcon: showIcons, featuredImage: nil)
|
||||
}
|
||||
|
||||
private func iconFor(_ article: Article) -> IconImage? {
|
||||
@ -733,11 +733,11 @@ extension TimelineViewController: NSTableViewDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
guard let feed = article.feed else {
|
||||
guard let feed = article.webFeed else {
|
||||
return nil
|
||||
}
|
||||
|
||||
if let feedIcon = appDelegate.feedIconDownloader.icon(for: feed) {
|
||||
if let feedIcon = appDelegate.webFeedIconDownloader.icon(for: feed) {
|
||||
return feedIcon
|
||||
}
|
||||
|
||||
@ -1056,23 +1056,23 @@ private extension TimelineViewController {
|
||||
return representedObjects?.contains(where: { $0 is Folder }) ?? false
|
||||
}
|
||||
|
||||
func representedObjectsContainsAnyFeed(_ feeds: Set<Feed>) -> Bool {
|
||||
func representedObjectsContainsAnyWebFeed(_ webFeeds: Set<WebFeed>) -> Bool {
|
||||
// Return true if there’s a match or if a folder contains (recursively) one of feeds
|
||||
|
||||
guard let representedObjects = representedObjects else {
|
||||
return false
|
||||
}
|
||||
for representedObject in representedObjects {
|
||||
if let feed = representedObject as? Feed {
|
||||
for oneFeed in feeds {
|
||||
if feed.feedID == oneFeed.feedID || feed.url == oneFeed.url {
|
||||
if let feed = representedObject as? WebFeed {
|
||||
for oneFeed in webFeeds {
|
||||
if feed.webFeedID == oneFeed.webFeedID || feed.url == oneFeed.url {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
else if let folder = representedObject as? Folder {
|
||||
for oneFeed in feeds {
|
||||
if folder.hasFeed(with: oneFeed.feedID) || folder.hasFeed(withURL: oneFeed.url) {
|
||||
for oneFeed in webFeeds {
|
||||
if folder.hasWebFeed(with: oneFeed.webFeedID) || folder.hasWebFeed(withURL: oneFeed.url) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
@ -81,7 +81,7 @@ class ScriptableAccount: NSObject, UniqueIdScriptingObject, ScriptingObjectConta
|
||||
} else {
|
||||
container = account
|
||||
}
|
||||
account.removeFeed(scriptableFeed.feed, from: container!) { result in
|
||||
account.removeWebFeed(scriptableFeed.webFeed, from: container!) { result in
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -96,19 +96,19 @@ class ScriptableAccount: NSObject, UniqueIdScriptingObject, ScriptingObjectConta
|
||||
|
||||
@objc(feeds)
|
||||
var feeds:NSArray {
|
||||
return account.topLevelFeeds.map { ScriptableFeed($0, container:self) } as NSArray
|
||||
return account.topLevelWebFeeds.map { ScriptableFeed($0, container:self) } as NSArray
|
||||
}
|
||||
|
||||
@objc(valueInFeedsWithUniqueID:)
|
||||
func valueInFeeds(withUniqueID id:String) -> ScriptableFeed? {
|
||||
let feeds = Array(account.topLevelFeeds)
|
||||
guard let feed = feeds.first(where:{$0.feedID == id}) else { return nil }
|
||||
let feeds = Array(account.topLevelWebFeeds)
|
||||
guard let feed = feeds.first(where:{$0.webFeedID == id}) else { return nil }
|
||||
return ScriptableFeed(feed, container:self)
|
||||
}
|
||||
|
||||
@objc(valueInFeedsWithName:)
|
||||
func valueInFeeds(withName name:String) -> ScriptableFeed? {
|
||||
let feeds = Array(account.topLevelFeeds)
|
||||
let feeds = Array(account.topLevelWebFeeds)
|
||||
guard let feed = feeds.first(where:{$0.name == name}) else { return nil }
|
||||
return ScriptableFeed(feed, container:self)
|
||||
}
|
||||
@ -134,13 +134,13 @@ class ScriptableAccount: NSObject, UniqueIdScriptingObject, ScriptingObjectConta
|
||||
@objc(allFeeds)
|
||||
var allFeeds: NSArray {
|
||||
var feeds = [ScriptableFeed]()
|
||||
for feed in account.topLevelFeeds {
|
||||
for feed in account.topLevelWebFeeds {
|
||||
feeds.append(ScriptableFeed(feed, container: self))
|
||||
}
|
||||
if let folders = account.folders {
|
||||
for folder in folders {
|
||||
let scriptableFolder = ScriptableFolder(folder, container: self)
|
||||
for feed in folder.topLevelFeeds {
|
||||
for feed in folder.topLevelWebFeeds {
|
||||
feeds.append(ScriptableFeed(feed, container: scriptableFolder))
|
||||
}
|
||||
}
|
||||
|
@ -14,11 +14,11 @@ import Articles
|
||||
@objc(ScriptableFeed)
|
||||
class ScriptableFeed: NSObject, UniqueIdScriptingObject, ScriptingObjectContainer {
|
||||
|
||||
let feed:Feed
|
||||
let webFeed:WebFeed
|
||||
let container:ScriptingObjectContainer
|
||||
|
||||
init (_ feed:Feed, container:ScriptingObjectContainer) {
|
||||
self.feed = feed
|
||||
init (_ feed:WebFeed, container:ScriptingObjectContainer) {
|
||||
self.webFeed = feed
|
||||
self.container = container
|
||||
}
|
||||
|
||||
@ -45,7 +45,7 @@ class ScriptableFeed: NSObject, UniqueIdScriptingObject, ScriptingObjectContaine
|
||||
// but in either case it seems like the accountID would be used as the keydata, so I chose ID
|
||||
@objc(uniqueId)
|
||||
var scriptingUniqueId:Any {
|
||||
return feed.feedID
|
||||
return webFeed.webFeedID
|
||||
}
|
||||
|
||||
// MARK: --- ScriptingObjectContainer protocol ---
|
||||
@ -71,7 +71,7 @@ class ScriptableFeed: NSObject, UniqueIdScriptingObject, ScriptingObjectContaine
|
||||
return url
|
||||
}
|
||||
|
||||
class func scriptableFeed(_ feed:Feed, account:Account, folder:Folder?) -> ScriptableFeed {
|
||||
class func scriptableFeed(_ feed:WebFeed, account:Account, folder:Folder?) -> ScriptableFeed {
|
||||
let scriptableAccount = ScriptableAccount(account)
|
||||
if let folder = folder {
|
||||
let scriptableFolder = ScriptableFolder(folder, container:scriptableAccount)
|
||||
@ -88,7 +88,7 @@ class ScriptableFeed: NSObject, UniqueIdScriptingObject, ScriptingObjectContaine
|
||||
let (account, folder) = command.accountAndFolderForNewChild()
|
||||
guard let url = self.urlForNewFeed(arguments:arguments) else {return nil}
|
||||
|
||||
if let existingFeed = account.existingFeed(withURL:url) {
|
||||
if let existingFeed = account.existingWebFeed(withURL:url) {
|
||||
return scriptableFeed(existingFeed, account:account, folder:folder).objectSpecifier
|
||||
}
|
||||
|
||||
@ -102,10 +102,10 @@ class ScriptableFeed: NSObject, UniqueIdScriptingObject, ScriptingObjectContaine
|
||||
// suspendExecution(). When we get the callback, we supply the event result and call resumeExecution().
|
||||
command.suspendExecution()
|
||||
|
||||
account.createFeed(url: url, name: titleFromArgs, container: container) { result in
|
||||
account.createWebFeed(url: url, name: titleFromArgs, container: container) { result in
|
||||
switch result {
|
||||
case .success(let feed):
|
||||
NotificationCenter.default.post(name: .UserDidAddFeed, object: self, userInfo: [UserInfoKey.feed: feed])
|
||||
NotificationCenter.default.post(name: .UserDidAddFeed, object: self, userInfo: [UserInfoKey.webFeed: feed])
|
||||
let scriptableFeed = self.scriptableFeed(feed, account:account, folder:folder)
|
||||
command.resumeExecution(withResult:scriptableFeed.objectSpecifier)
|
||||
case .failure:
|
||||
@ -121,51 +121,51 @@ class ScriptableFeed: NSObject, UniqueIdScriptingObject, ScriptingObjectContaine
|
||||
|
||||
@objc(url)
|
||||
var url:String {
|
||||
return self.feed.url
|
||||
return self.webFeed.url
|
||||
}
|
||||
|
||||
@objc(name)
|
||||
var name:String {
|
||||
return self.feed.name ?? ""
|
||||
return self.webFeed.name ?? ""
|
||||
}
|
||||
|
||||
@objc(homePageURL)
|
||||
var homePageURL:String {
|
||||
return self.feed.homePageURL ?? ""
|
||||
return self.webFeed.homePageURL ?? ""
|
||||
}
|
||||
|
||||
@objc(iconURL)
|
||||
var iconURL:String {
|
||||
return self.feed.iconURL ?? ""
|
||||
return self.webFeed.iconURL ?? ""
|
||||
}
|
||||
|
||||
@objc(faviconURL)
|
||||
var faviconURL:String {
|
||||
return self.feed.faviconURL ?? ""
|
||||
return self.webFeed.faviconURL ?? ""
|
||||
}
|
||||
|
||||
@objc(opmlRepresentation)
|
||||
var opmlRepresentation:String {
|
||||
return self.feed.OPMLString(indentLevel:0, strictConformance: true)
|
||||
return self.webFeed.OPMLString(indentLevel:0, strictConformance: true)
|
||||
}
|
||||
|
||||
// MARK: --- scriptable elements ---
|
||||
|
||||
@objc(authors)
|
||||
var authors:NSArray {
|
||||
let feedAuthors = feed.authors ?? []
|
||||
let feedAuthors = webFeed.authors ?? []
|
||||
return feedAuthors.map { ScriptableAuthor($0, container:self) } as NSArray
|
||||
}
|
||||
|
||||
@objc(valueInAuthorsWithUniqueID:)
|
||||
func valueInAuthors(withUniqueID id:String) -> ScriptableAuthor? {
|
||||
guard let author = feed.authors?.first(where:{$0.authorID == id}) else { return nil }
|
||||
guard let author = webFeed.authors?.first(where:{$0.authorID == id}) else { return nil }
|
||||
return ScriptableAuthor(author, container:self)
|
||||
}
|
||||
|
||||
@objc(articles)
|
||||
var articles:NSArray {
|
||||
let feedArticles = feed.fetchArticles()
|
||||
let feedArticles = webFeed.fetchArticles()
|
||||
// the articles are a set, use the sorting algorithm from the viewer
|
||||
let sortedArticles = feedArticles.sorted(by:{
|
||||
return $0.logicalDatePublished > $1.logicalDatePublished
|
||||
@ -175,7 +175,7 @@ class ScriptableFeed: NSObject, UniqueIdScriptingObject, ScriptingObjectContaine
|
||||
|
||||
@objc(valueInArticlesWithUniqueID:)
|
||||
func valueInArticles(withUniqueID id:String) -> ScriptableArticle? {
|
||||
let articles = feed.fetchArticles()
|
||||
let articles = webFeed.fetchArticles()
|
||||
guard let article = articles.first(where:{$0.uniqueID == id}) else { return nil }
|
||||
return ScriptableArticle(article, container:self)
|
||||
}
|
||||
|
@ -53,7 +53,7 @@ class ScriptableFolder: NSObject, UniqueIdScriptingObject, ScriptingObjectContai
|
||||
func deleteElement(_ element:ScriptingObject) {
|
||||
if let scriptableFeed = element as? ScriptableFeed {
|
||||
BatchUpdate.shared.perform {
|
||||
folder.account?.removeFeed(scriptableFeed.feed, from: folder) { result in }
|
||||
folder.account?.removeWebFeed(scriptableFeed.webFeed, from: folder) { result in }
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -97,7 +97,7 @@ class ScriptableFolder: NSObject, UniqueIdScriptingObject, ScriptingObjectContai
|
||||
|
||||
@objc(feeds)
|
||||
var feeds:NSArray {
|
||||
let feeds = Array(folder.topLevelFeeds)
|
||||
let feeds = Array(folder.topLevelWebFeeds)
|
||||
return feeds.map { ScriptableFeed($0, container:self) } as NSArray
|
||||
}
|
||||
|
||||
|
@ -30,7 +30,7 @@ extension NSApplication : ScriptingObjectContainer {
|
||||
func currentArticle() -> ScriptableArticle? {
|
||||
var scriptableArticle: ScriptableArticle?
|
||||
if let currentArticle = appDelegate.scriptingCurrentArticle {
|
||||
if let feed = currentArticle.feed {
|
||||
if let feed = currentArticle.webFeed {
|
||||
let scriptableFeed = ScriptableFeed(feed, container:self)
|
||||
scriptableArticle = ScriptableArticle(currentArticle, container:scriptableFeed)
|
||||
}
|
||||
@ -42,7 +42,7 @@ extension NSApplication : ScriptingObjectContainer {
|
||||
func selectedArticles() -> NSArray {
|
||||
let articles = appDelegate.scriptingSelectedArticles
|
||||
let scriptableArticles:[ScriptableArticle] = articles.compactMap { article in
|
||||
if let feed = article.feed {
|
||||
if let feed = article.webFeed {
|
||||
let scriptableFeed = ScriptableFeed(feed, container:self)
|
||||
return ScriptableArticle(article, container:scriptableFeed)
|
||||
} else {
|
||||
@ -73,11 +73,11 @@ extension NSApplication : ScriptingObjectContainer {
|
||||
for 'articles of feed "The Shape of Everything" of account "On My Mac"'
|
||||
*/
|
||||
|
||||
func allFeeds() -> [Feed] {
|
||||
func allFeeds() -> [WebFeed] {
|
||||
let accounts = AccountManager.shared.activeAccounts
|
||||
let emptyFeeds:[Feed] = []
|
||||
return accounts.reduce(emptyFeeds) { (result, nthAccount) -> [Feed] in
|
||||
let accountFeeds = Array(nthAccount.topLevelFeeds)
|
||||
let emptyFeeds:[WebFeed] = []
|
||||
return accounts.reduce(emptyFeeds) { (result, nthAccount) -> [WebFeed] in
|
||||
let accountFeeds = Array(nthAccount.topLevelWebFeeds)
|
||||
return result + accountFeeds
|
||||
}
|
||||
}
|
||||
@ -91,7 +91,7 @@ extension NSApplication : ScriptingObjectContainer {
|
||||
@objc(valueInFeedsWithUniqueID:)
|
||||
func valueInFeeds(withUniqueID id:String) -> ScriptableFeed? {
|
||||
let feeds = self.allFeeds()
|
||||
guard let feed = feeds.first(where:{$0.feedID == id}) else { return nil }
|
||||
guard let feed = feeds.first(where:{$0.webFeedID == id}) else { return nil }
|
||||
return ScriptableFeed(feed, container:self)
|
||||
}
|
||||
}
|
||||
|
@ -34,14 +34,14 @@
|
||||
512AF9C2236ED52C0066F8BE /* InspectorImageHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 512AF9C1236ED52C0066F8BE /* InspectorImageHeaderView.swift */; };
|
||||
512AF9DD236F05230066F8BE /* InteractiveLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 512AF9DC236F05230066F8BE /* InteractiveLabel.swift */; };
|
||||
512E08E62268800D00BDCFDD /* FolderTreeControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97A11ED9F180007D329B /* FolderTreeControllerDelegate.swift */; };
|
||||
512E08E72268801200BDCFDD /* FeedTreeControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97611ED9EB96007D329B /* FeedTreeControllerDelegate.swift */; };
|
||||
512E08E72268801200BDCFDD /* WebFeedTreeControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97611ED9EB96007D329B /* WebFeedTreeControllerDelegate.swift */; };
|
||||
512E09012268907400BDCFDD /* MasterFeedTableViewSectionHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 512E08F722688F7C00BDCFDD /* MasterFeedTableViewSectionHeader.swift */; };
|
||||
512E09352268B25900BDCFDD /* UISplitViewController-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 512E092B2268B25500BDCFDD /* UISplitViewController-Extensions.swift */; };
|
||||
512E094D2268B8AB00BDCFDD /* DeleteCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B99C9C1FAE83C600ECDEDB /* DeleteCommand.swift */; };
|
||||
5131463E235A7BBE00387FDC /* NetNewsWire iOS Intents Extension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 51314637235A7BBE00387FDC /* NetNewsWire iOS Intents Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
51314668235A7E4600387FDC /* IntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51314666235A7E4600387FDC /* IntentHandler.swift */; };
|
||||
513146B2235A81A400387FDC /* AddFeedIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 513146B1235A81A400387FDC /* AddFeedIntentHandler.swift */; };
|
||||
513146B3235A81A400387FDC /* AddFeedIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 513146B1235A81A400387FDC /* AddFeedIntentHandler.swift */; };
|
||||
513146B2235A81A400387FDC /* AddWebFeedIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 513146B1235A81A400387FDC /* AddWebFeedIntentHandler.swift */; };
|
||||
513146B3235A81A400387FDC /* AddWebFeedIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 513146B1235A81A400387FDC /* AddWebFeedIntentHandler.swift */; };
|
||||
513146B4235A8FD000387FDC /* RSCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84C37F8520DD8CF200CA8CF5 /* RSCore.framework */; };
|
||||
513146B6235A8FD000387FDC /* RSDatabase.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84C37FC420DD8E0C00CA8CF5 /* RSDatabase.framework */; };
|
||||
513146B8235A8FD000387FDC /* RSParser.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84C37F8C20DD8CF800CA8CF5 /* RSParser.framework */; };
|
||||
@ -67,7 +67,7 @@
|
||||
513C5D0A232574D2003D4054 /* RSWeb.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84C37FA320DD8D0500CA8CF5 /* RSWeb.framework */; };
|
||||
513C5D0C232574DA003D4054 /* RSTree.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84C37F9520DD8CFE00CA8CF5 /* RSTree.framework */; };
|
||||
513C5D0E232574E4003D4054 /* SyncDatabase.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 51554C01228B6EB50055115A /* SyncDatabase.framework */; };
|
||||
5141E7392373C18B0013FF27 /* FeedInspectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5141E7382373C18B0013FF27 /* FeedInspectorViewController.swift */; };
|
||||
5141E7392373C18B0013FF27 /* WebFeedInspectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5141E7382373C18B0013FF27 /* WebFeedInspectorViewController.swift */; };
|
||||
5141E7562374A2890013FF27 /* ArticleIconSchemeHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5141E7552374A2890013FF27 /* ArticleIconSchemeHandler.swift */; };
|
||||
5142192A23522B5500E07E2C /* ImageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5142192923522B5500E07E2C /* ImageViewController.swift */; };
|
||||
514219372352510100E07E2C /* ImageScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 514219362352510100E07E2C /* ImageScrollView.swift */; };
|
||||
@ -171,7 +171,7 @@
|
||||
51C4527F2265092C00C03939 /* ArticleViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C4527E2265092C00C03939 /* ArticleViewController.swift */; };
|
||||
51C452852265093600C03939 /* FlattenedAccountFolderPickerData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C452812265093600C03939 /* FlattenedAccountFolderPickerData.swift */; };
|
||||
51C452862265093600C03939 /* Add.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 51C452822265093600C03939 /* Add.storyboard */; };
|
||||
51C452882265093600C03939 /* AddFeedViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C452842265093600C03939 /* AddFeedViewController.swift */; };
|
||||
51C452882265093600C03939 /* AddWebFeedViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C452842265093600C03939 /* AddWebFeedViewController.swift */; };
|
||||
51C4528D2265095F00C03939 /* AddFolderViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C4528B2265095F00C03939 /* AddFolderViewController.swift */; };
|
||||
51C4528E2265099C00C03939 /* SmartFeedsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CC88171FE59CBF00644329 /* SmartFeedsController.swift */; };
|
||||
51C4528F226509BD00C03939 /* UnreadFeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F2D5391FC2308B00998D64 /* UnreadFeed.swift */; };
|
||||
@ -189,7 +189,7 @@
|
||||
51C4529D22650A1000C03939 /* FaviconURLFinder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84FF69B01FC3793300DC198E /* FaviconURLFinder.swift */; };
|
||||
51C4529E22650A1900C03939 /* ImageDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845213221FCA5B10003B6E93 /* ImageDownloader.swift */; };
|
||||
51C4529F22650A1900C03939 /* AuthorAvatarDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E850851FCB60CE0072EA88 /* AuthorAvatarDownloader.swift */; };
|
||||
51C452A022650A1900C03939 /* FeedIconDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842611891FCB67AA0086A189 /* FeedIconDownloader.swift */; };
|
||||
51C452A022650A1900C03939 /* WebFeedIconDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842611891FCB67AA0086A189 /* WebFeedIconDownloader.swift */; };
|
||||
51C452A222650A1900C03939 /* RSHTMLMetadata+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842611A11FCB769D0086A189 /* RSHTMLMetadata+Extension.swift */; };
|
||||
51C452A322650A1E00C03939 /* HTMLMetadataDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8426119D1FCB6ED40086A189 /* HTMLMetadataDownloader.swift */; };
|
||||
51C452A422650A2D00C03939 /* ArticleUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97581ED9EB0D007D329B /* ArticleUtilities.swift */; };
|
||||
@ -290,11 +290,11 @@
|
||||
65ED3FD8235DEF6C0081F399 /* NSView-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8405DD9B22153BD7008CE1BF /* NSView-Extensions.swift */; };
|
||||
65ED3FD9235DEF6C0081F399 /* SidebarCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A979E1ED9F130007D329B /* SidebarCell.swift */; };
|
||||
65ED3FDA235DEF6C0081F399 /* ArticleStatusSyncTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E595A4228CC36500FCC42B /* ArticleStatusSyncTimer.swift */; };
|
||||
65ED3FDB235DEF6C0081F399 /* FeedTreeControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97611ED9EB96007D329B /* FeedTreeControllerDelegate.swift */; };
|
||||
65ED3FDB235DEF6C0081F399 /* WebFeedTreeControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97611ED9EB96007D329B /* WebFeedTreeControllerDelegate.swift */; };
|
||||
65ED3FDC235DEF6C0081F399 /* UnreadCountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97631ED9EB96007D329B /* UnreadCountView.swift */; };
|
||||
65ED3FDD235DEF6C0081F399 /* ActivityType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51D87EE02311D34700E63F03 /* ActivityType.swift */; };
|
||||
65ED3FDE235DEF6C0081F399 /* CrashReportWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840BEE4021D70E64009BBAFA /* CrashReportWindowController.swift */; };
|
||||
65ED3FDF235DEF6C0081F399 /* FeedIconDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842611891FCB67AA0086A189 /* FeedIconDownloader.swift */; };
|
||||
65ED3FDF235DEF6C0081F399 /* WebFeedIconDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842611891FCB67AA0086A189 /* WebFeedIconDownloader.swift */; };
|
||||
65ED3FE0235DEF6C0081F399 /* AccountsControlsBackgroundView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84C9FC7122629E1200D921D6 /* AccountsControlsBackgroundView.swift */; };
|
||||
65ED3FE1235DEF6C0081F399 /* MarkCommandValidationStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84162A142038C12C00035290 /* MarkCommandValidationStatus.swift */; };
|
||||
65ED3FE2235DEF6C0081F399 /* ArticlePasteboardWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E95D231FB1087500552D99 /* ArticlePasteboardWriter.swift */; };
|
||||
@ -320,7 +320,7 @@
|
||||
65ED3FF7235DEF6C0081F399 /* SearchFeedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8477ACBD22238E9500DF7F37 /* SearchFeedDelegate.swift */; };
|
||||
65ED3FF8235DEF6C0081F399 /* ErrorHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E3EB32229AB02C00645299 /* ErrorHandler.swift */; };
|
||||
65ED3FF9235DEF6C0081F399 /* ActivityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51934CCD2310792F006127BE /* ActivityManager.swift */; };
|
||||
65ED3FFA235DEF6C0081F399 /* FeedInspectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8472058020142E8900AD578B /* FeedInspectorViewController.swift */; };
|
||||
65ED3FFA235DEF6C0081F399 /* WebFeedInspectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8472058020142E8900AD578B /* WebFeedInspectorViewController.swift */; };
|
||||
65ED3FFB235DEF6C0081F399 /* AccountsReaderAPIWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55E15BCA229D65A900D6602A /* AccountsReaderAPIWindowController.swift */; };
|
||||
65ED3FFC235DEF6C0081F399 /* AccountsAddLocalWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5144EA372279FC6200D19003 /* AccountsAddLocalWindowController.swift */; };
|
||||
65ED3FFD235DEF6C0081F399 /* PasteboardFolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84AD1EA92031617300BC20B7 /* PasteboardFolder.swift */; };
|
||||
@ -346,7 +346,7 @@
|
||||
65ED4011235DEF6C0081F399 /* AddFolderWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97421ED9EAA9007D329B /* AddFolderWindowController.swift */; };
|
||||
65ED4012235DEF6C0081F399 /* TimelineContainerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8405DDA422168C62008CE1BF /* TimelineContainerViewController.swift */; };
|
||||
65ED4013235DEF6C0081F399 /* MainWIndowKeyboardHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844B5B661FEA18E300C7C76A /* MainWIndowKeyboardHandler.swift */; };
|
||||
65ED4014235DEF6C0081F399 /* PasteboardFeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848D578D21543519005FFAD5 /* PasteboardFeed.swift */; };
|
||||
65ED4014235DEF6C0081F399 /* PasteboardWebFeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848D578D21543519005FFAD5 /* PasteboardWebFeed.swift */; };
|
||||
65ED4015235DEF6C0081F399 /* AccountsDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5144EA2E2279FAB600D19003 /* AccountsDetailViewController.swift */; };
|
||||
65ED4016235DEF6C0081F399 /* DetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A977E1ED9EC42007D329B /* DetailViewController.swift */; };
|
||||
65ED4017235DEF6C0081F399 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84C9FC6622629B3900D921D6 /* AppDelegate.swift */; };
|
||||
@ -469,7 +469,7 @@
|
||||
841ABA5E20145E9200980E11 /* FolderInspectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 841ABA5D20145E9200980E11 /* FolderInspectorViewController.swift */; };
|
||||
841ABA6020145EC100980E11 /* BuiltinSmartFeedInspectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 841ABA5F20145EC100980E11 /* BuiltinSmartFeedInspectorViewController.swift */; };
|
||||
84216D0322128B9D0049B9B9 /* DetailWebViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84216D0222128B9D0049B9B9 /* DetailWebViewController.swift */; };
|
||||
8426118A1FCB67AA0086A189 /* FeedIconDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842611891FCB67AA0086A189 /* FeedIconDownloader.swift */; };
|
||||
8426118A1FCB67AA0086A189 /* WebFeedIconDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842611891FCB67AA0086A189 /* WebFeedIconDownloader.swift */; };
|
||||
8426119E1FCB6ED40086A189 /* HTMLMetadataDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8426119D1FCB6ED40086A189 /* HTMLMetadataDownloader.swift */; };
|
||||
842611A21FCB769D0086A189 /* RSHTMLMetadata+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842611A11FCB769D0086A189 /* RSHTMLMetadata+Extension.swift */; };
|
||||
842E45CE1ED8C308000A8B52 /* AppNotifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842E45CD1ED8C308000A8B52 /* AppNotifications.swift */; };
|
||||
@ -489,7 +489,7 @@
|
||||
845EE7B11FC2366500854A1F /* StarredFeedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845EE7B01FC2366500854A1F /* StarredFeedDelegate.swift */; };
|
||||
845EE7C11FC2488C00854A1F /* SmartFeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845EE7C01FC2488C00854A1F /* SmartFeed.swift */; };
|
||||
84702AA41FA27AC0006B8943 /* MarkStatusCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84702AA31FA27AC0006B8943 /* MarkStatusCommand.swift */; };
|
||||
8472058120142E8900AD578B /* FeedInspectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8472058020142E8900AD578B /* FeedInspectorViewController.swift */; };
|
||||
8472058120142E8900AD578B /* WebFeedInspectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8472058020142E8900AD578B /* WebFeedInspectorViewController.swift */; };
|
||||
8477ACBE22238E9500DF7F37 /* SearchFeedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8477ACBD22238E9500DF7F37 /* SearchFeedDelegate.swift */; };
|
||||
847CD6CA232F4CBF00FAC46D /* IconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847CD6C9232F4CBF00FAC46D /* IconView.swift */; };
|
||||
847E64A02262783000E00365 /* NSAppleEventDescriptor+UserRecordFields.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847E64942262782F00E00365 /* NSAppleEventDescriptor+UserRecordFields.swift */; };
|
||||
@ -501,7 +501,7 @@
|
||||
8483630B2262A3F000DA1D35 /* RenameSheet.xib in Resources */ = {isa = PBXBuildFile; fileRef = 848363092262A3F000DA1D35 /* RenameSheet.xib */; };
|
||||
8483630E2262A3FE00DA1D35 /* MainWindow.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 8483630C2262A3FE00DA1D35 /* MainWindow.storyboard */; };
|
||||
848B937221C8C5540038DC0D /* CrashReporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848B937121C8C5540038DC0D /* CrashReporter.swift */; };
|
||||
848D578E21543519005FFAD5 /* PasteboardFeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848D578D21543519005FFAD5 /* PasteboardFeed.swift */; };
|
||||
848D578E21543519005FFAD5 /* PasteboardWebFeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848D578D21543519005FFAD5 /* PasteboardWebFeed.swift */; };
|
||||
848F6AE51FC29CFB002D422E /* FaviconDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848F6AE41FC29CFA002D422E /* FaviconDownloader.swift */; };
|
||||
849A97431ED9EAA9007D329B /* AddFolderWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97421ED9EAA9007D329B /* AddFolderWindowController.swift */; };
|
||||
849A97531ED9EAC0007D329B /* AddFeedController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97511ED9EAC0007D329B /* AddFeedController.swift */; };
|
||||
@ -510,7 +510,7 @@
|
||||
849A975C1ED9EB0D007D329B /* DefaultFeedsImporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97591ED9EB0D007D329B /* DefaultFeedsImporter.swift */; };
|
||||
849A975E1ED9EB72007D329B /* MainWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A975D1ED9EB72007D329B /* MainWindowController.swift */; };
|
||||
849A97641ED9EB96007D329B /* SidebarOutlineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97601ED9EB96007D329B /* SidebarOutlineView.swift */; };
|
||||
849A97651ED9EB96007D329B /* FeedTreeControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97611ED9EB96007D329B /* FeedTreeControllerDelegate.swift */; };
|
||||
849A97651ED9EB96007D329B /* WebFeedTreeControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97611ED9EB96007D329B /* WebFeedTreeControllerDelegate.swift */; };
|
||||
849A97661ED9EB96007D329B /* SidebarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97621ED9EB96007D329B /* SidebarViewController.swift */; };
|
||||
849A97671ED9EB96007D329B /* UnreadCountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97631ED9EB96007D329B /* UnreadCountView.swift */; };
|
||||
849A976C1ED9EBC8007D329B /* TimelineTableRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97691ED9EBC8007D329B /* TimelineTableRowView.swift */; };
|
||||
@ -1228,7 +1228,7 @@
|
||||
51314665235A7E4600387FDC /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
51314666235A7E4600387FDC /* IntentHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IntentHandler.swift; sourceTree = "<group>"; };
|
||||
51314684235A7EB900387FDC /* NetNewsWire_iOS_IntentsExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = NetNewsWire_iOS_IntentsExtension.entitlements; sourceTree = "<group>"; };
|
||||
513146B1235A81A400387FDC /* AddFeedIntentHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddFeedIntentHandler.swift; sourceTree = "<group>"; };
|
||||
513146B1235A81A400387FDC /* AddWebFeedIntentHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddWebFeedIntentHandler.swift; sourceTree = "<group>"; };
|
||||
51314706235C41FC00387FDC /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.intentdefinition; name = Base; path = Base.lproj/Intents.intentdefinition; sourceTree = "<group>"; };
|
||||
51314714235C420900387FDC /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Intents.strings; sourceTree = "<group>"; };
|
||||
513228F2233037620033D4ED /* Reachability.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Reachability.swift; sourceTree = "<group>"; };
|
||||
@ -1236,7 +1236,7 @@
|
||||
513C5CE8232571C2003D4054 /* ShareViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareViewController.swift; sourceTree = "<group>"; };
|
||||
513C5CEB232571C2003D4054 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = "<group>"; };
|
||||
513C5CED232571C2003D4054 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
5141E7382373C18B0013FF27 /* FeedInspectorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedInspectorViewController.swift; sourceTree = "<group>"; };
|
||||
5141E7382373C18B0013FF27 /* WebFeedInspectorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebFeedInspectorViewController.swift; sourceTree = "<group>"; };
|
||||
5141E7552374A2890013FF27 /* ArticleIconSchemeHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleIconSchemeHandler.swift; sourceTree = "<group>"; };
|
||||
5142192923522B5500E07E2C /* ImageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageViewController.swift; sourceTree = "<group>"; };
|
||||
514219362352510100E07E2C /* ImageScrollView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageScrollView.swift; sourceTree = "<group>"; };
|
||||
@ -1308,7 +1308,7 @@
|
||||
51C4527E2265092C00C03939 /* ArticleViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArticleViewController.swift; sourceTree = "<group>"; };
|
||||
51C452812265093600C03939 /* FlattenedAccountFolderPickerData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FlattenedAccountFolderPickerData.swift; sourceTree = "<group>"; };
|
||||
51C452822265093600C03939 /* Add.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Add.storyboard; sourceTree = "<group>"; };
|
||||
51C452842265093600C03939 /* AddFeedViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddFeedViewController.swift; sourceTree = "<group>"; };
|
||||
51C452842265093600C03939 /* AddWebFeedViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddWebFeedViewController.swift; sourceTree = "<group>"; };
|
||||
51C4528B2265095F00C03939 /* AddFolderViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddFolderViewController.swift; sourceTree = "<group>"; };
|
||||
51C452B32265141B00C03939 /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS12.2.sdk/System/Library/Frameworks/WebKit.framework; sourceTree = DEVELOPER_DIR; };
|
||||
51C452B72265178500C03939 /* styleSheet.css */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.css; path = styleSheet.css; sourceTree = "<group>"; };
|
||||
@ -1383,7 +1383,7 @@
|
||||
841D4D542106B3D500DD04E6 /* Articles.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Articles.xcodeproj; path = ../Frameworks/Articles/Articles.xcodeproj; sourceTree = "<group>"; };
|
||||
841D4D5E2106B3E100DD04E6 /* ArticlesDatabase.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = ArticlesDatabase.xcodeproj; path = ../Frameworks/ArticlesDatabase/ArticlesDatabase.xcodeproj; sourceTree = "<group>"; };
|
||||
84216D0222128B9D0049B9B9 /* DetailWebViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailWebViewController.swift; sourceTree = "<group>"; };
|
||||
842611891FCB67AA0086A189 /* FeedIconDownloader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedIconDownloader.swift; sourceTree = "<group>"; };
|
||||
842611891FCB67AA0086A189 /* WebFeedIconDownloader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebFeedIconDownloader.swift; sourceTree = "<group>"; };
|
||||
8426119D1FCB6ED40086A189 /* HTMLMetadataDownloader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTMLMetadataDownloader.swift; sourceTree = "<group>"; };
|
||||
8426119F1FCB72600086A189 /* FeaturedImageDownloader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeaturedImageDownloader.swift; sourceTree = "<group>"; };
|
||||
842611A11FCB769D0086A189 /* RSHTMLMetadata+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RSHTMLMetadata+Extension.swift"; sourceTree = "<group>"; };
|
||||
@ -1406,7 +1406,7 @@
|
||||
845EE7C01FC2488C00854A1F /* SmartFeed.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SmartFeed.swift; sourceTree = "<group>"; };
|
||||
846E77301F6EF5D600A165E2 /* Account.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Account.xcodeproj; path = ../Frameworks/Account/Account.xcodeproj; 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 /* WebFeedInspectorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebFeedInspectorViewController.swift; sourceTree = "<group>"; };
|
||||
847752FE2008879500D93690 /* CoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreServices.framework; path = System/Library/Frameworks/CoreServices.framework; sourceTree = SDKROOT; };
|
||||
8477ACBD22238E9500DF7F37 /* SearchFeedDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchFeedDelegate.swift; sourceTree = "<group>"; };
|
||||
847CD6C9232F4CBF00FAC46D /* IconView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconView.swift; sourceTree = "<group>"; };
|
||||
@ -1419,7 +1419,7 @@
|
||||
8483630A2262A3F000DA1D35 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Mac/Base.lproj/RenameSheet.xib; sourceTree = SOURCE_ROOT; };
|
||||
8483630D2262A3FE00DA1D35 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Mac/Base.lproj/MainWindow.storyboard; sourceTree = SOURCE_ROOT; };
|
||||
848B937121C8C5540038DC0D /* CrashReporter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CrashReporter.swift; sourceTree = "<group>"; };
|
||||
848D578D21543519005FFAD5 /* PasteboardFeed.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasteboardFeed.swift; sourceTree = "<group>"; };
|
||||
848D578D21543519005FFAD5 /* PasteboardWebFeed.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasteboardWebFeed.swift; sourceTree = "<group>"; };
|
||||
848F6AE41FC29CFA002D422E /* FaviconDownloader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FaviconDownloader.swift; sourceTree = "<group>"; };
|
||||
849A97421ED9EAA9007D329B /* AddFolderWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddFolderWindowController.swift; sourceTree = "<group>"; };
|
||||
849A97511ED9EAC0007D329B /* AddFeedController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AddFeedController.swift; path = AddFeed/AddFeedController.swift; sourceTree = "<group>"; };
|
||||
@ -1428,7 +1428,7 @@
|
||||
849A97591ED9EB0D007D329B /* DefaultFeedsImporter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DefaultFeedsImporter.swift; sourceTree = "<group>"; };
|
||||
849A975D1ED9EB72007D329B /* MainWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainWindowController.swift; sourceTree = "<group>"; };
|
||||
849A97601ED9EB96007D329B /* SidebarOutlineView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SidebarOutlineView.swift; sourceTree = "<group>"; };
|
||||
849A97611ED9EB96007D329B /* FeedTreeControllerDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeedTreeControllerDelegate.swift; sourceTree = "<group>"; };
|
||||
849A97611ED9EB96007D329B /* WebFeedTreeControllerDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebFeedTreeControllerDelegate.swift; sourceTree = "<group>"; };
|
||||
849A97621ED9EB96007D329B /* SidebarViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SidebarViewController.swift; sourceTree = "<group>"; };
|
||||
849A97631ED9EB96007D329B /* UnreadCountView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnreadCountView.swift; sourceTree = "<group>"; };
|
||||
849A97691ED9EBC8007D329B /* TimelineTableRowView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimelineTableRowView.swift; sourceTree = "<group>"; };
|
||||
@ -1688,7 +1688,7 @@
|
||||
children = (
|
||||
516A09412361248000EAE89B /* Inspector.storyboard */,
|
||||
51A16991235E10D600EB091F /* AccountInspectorViewController.swift */,
|
||||
5141E7382373C18B0013FF27 /* FeedInspectorViewController.swift */,
|
||||
5141E7382373C18B0013FF27 /* WebFeedInspectorViewController.swift */,
|
||||
5110C37C2373A8D100A9C04F /* InspectorIconHeaderView.swift */,
|
||||
512AF9C1236ED52C0066F8BE /* InspectorImageHeaderView.swift */,
|
||||
);
|
||||
@ -1707,7 +1707,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
51C452812265093600C03939 /* FlattenedAccountFolderPickerData.swift */,
|
||||
849A97611ED9EB96007D329B /* FeedTreeControllerDelegate.swift */,
|
||||
849A97611ED9EB96007D329B /* WebFeedTreeControllerDelegate.swift */,
|
||||
849A97A11ED9F180007D329B /* FolderTreeControllerDelegate.swift */,
|
||||
);
|
||||
path = Tree;
|
||||
@ -1717,7 +1717,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
51314707235C41FC00387FDC /* Intents.intentdefinition */,
|
||||
513146B1235A81A400387FDC /* AddFeedIntentHandler.swift */,
|
||||
513146B1235A81A400387FDC /* AddWebFeedIntentHandler.swift */,
|
||||
);
|
||||
path = Intents;
|
||||
sourceTree = "<group>";
|
||||
@ -1925,7 +1925,7 @@
|
||||
51C452822265093600C03939 /* Add.storyboard */,
|
||||
51121B5A22661FEF00BC0EC1 /* AddContainerViewController.swift */,
|
||||
514B7D1E23219F3C00BAC947 /* AddControllerType.swift */,
|
||||
51C452842265093600C03939 /* AddFeedViewController.swift */,
|
||||
51C452842265093600C03939 /* AddWebFeedViewController.swift */,
|
||||
51C4528B2265095F00C03939 /* AddFolderViewController.swift */,
|
||||
);
|
||||
path = Add;
|
||||
@ -2119,7 +2119,7 @@
|
||||
children = (
|
||||
845213221FCA5B10003B6E93 /* ImageDownloader.swift */,
|
||||
84E850851FCB60CE0072EA88 /* AuthorAvatarDownloader.swift */,
|
||||
842611891FCB67AA0086A189 /* FeedIconDownloader.swift */,
|
||||
842611891FCB67AA0086A189 /* WebFeedIconDownloader.swift */,
|
||||
8426119F1FCB72600086A189 /* FeaturedImageDownloader.swift */,
|
||||
842611A11FCB769D0086A189 /* RSHTMLMetadata+Extension.swift */,
|
||||
);
|
||||
@ -2210,7 +2210,7 @@
|
||||
84AD1EBB2032AF5C00BC20B7 /* SidebarOutlineDataSource.swift */,
|
||||
849A97601ED9EB96007D329B /* SidebarOutlineView.swift */,
|
||||
849A97631ED9EB96007D329B /* UnreadCountView.swift */,
|
||||
848D578D21543519005FFAD5 /* PasteboardFeed.swift */,
|
||||
848D578D21543519005FFAD5 /* PasteboardWebFeed.swift */,
|
||||
84AD1EA92031617300BC20B7 /* PasteboardFolder.swift */,
|
||||
849A97821ED9EC63007D329B /* SidebarStatusBarView.swift */,
|
||||
844B5B6A1FEA224000C7C76A /* Keyboard */,
|
||||
@ -2340,7 +2340,7 @@
|
||||
children = (
|
||||
84BBB12B20142A4700F054F5 /* Inspector.storyboard */,
|
||||
84BBB12C20142A4700F054F5 /* InspectorWindowController.swift */,
|
||||
8472058020142E8900AD578B /* FeedInspectorViewController.swift */,
|
||||
8472058020142E8900AD578B /* WebFeedInspectorViewController.swift */,
|
||||
841ABA5D20145E9200980E11 /* FolderInspectorViewController.swift */,
|
||||
841ABA5F20145EC100980E11 /* BuiltinSmartFeedInspectorViewController.swift */,
|
||||
841ABA4D20145E7300980E11 /* NothingInspectorViewController.swift */,
|
||||
@ -3679,7 +3679,7 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
513146B3235A81A400387FDC /* AddFeedIntentHandler.swift in Sources */,
|
||||
513146B3235A81A400387FDC /* AddWebFeedIntentHandler.swift in Sources */,
|
||||
51314705235C41FC00387FDC /* Intents.intentdefinition in Sources */,
|
||||
51314668235A7E4600387FDC /* IntentHandler.swift in Sources */,
|
||||
);
|
||||
@ -3753,11 +3753,11 @@
|
||||
65ED3FD8235DEF6C0081F399 /* NSView-Extensions.swift in Sources */,
|
||||
65ED3FD9235DEF6C0081F399 /* SidebarCell.swift in Sources */,
|
||||
65ED3FDA235DEF6C0081F399 /* ArticleStatusSyncTimer.swift in Sources */,
|
||||
65ED3FDB235DEF6C0081F399 /* FeedTreeControllerDelegate.swift in Sources */,
|
||||
65ED3FDB235DEF6C0081F399 /* WebFeedTreeControllerDelegate.swift in Sources */,
|
||||
65ED3FDC235DEF6C0081F399 /* UnreadCountView.swift in Sources */,
|
||||
65ED3FDD235DEF6C0081F399 /* ActivityType.swift in Sources */,
|
||||
65ED3FDE235DEF6C0081F399 /* CrashReportWindowController.swift in Sources */,
|
||||
65ED3FDF235DEF6C0081F399 /* FeedIconDownloader.swift in Sources */,
|
||||
65ED3FDF235DEF6C0081F399 /* WebFeedIconDownloader.swift in Sources */,
|
||||
65ED3FE0235DEF6C0081F399 /* AccountsControlsBackgroundView.swift in Sources */,
|
||||
65ED3FE1235DEF6C0081F399 /* MarkCommandValidationStatus.swift in Sources */,
|
||||
65ED3FE2235DEF6C0081F399 /* ArticlePasteboardWriter.swift in Sources */,
|
||||
@ -3784,7 +3784,7 @@
|
||||
65ED3FF7235DEF6C0081F399 /* SearchFeedDelegate.swift in Sources */,
|
||||
65ED3FF8235DEF6C0081F399 /* ErrorHandler.swift in Sources */,
|
||||
65ED3FF9235DEF6C0081F399 /* ActivityManager.swift in Sources */,
|
||||
65ED3FFA235DEF6C0081F399 /* FeedInspectorViewController.swift in Sources */,
|
||||
65ED3FFA235DEF6C0081F399 /* WebFeedInspectorViewController.swift in Sources */,
|
||||
65ED3FFB235DEF6C0081F399 /* AccountsReaderAPIWindowController.swift in Sources */,
|
||||
65ED3FFC235DEF6C0081F399 /* AccountsAddLocalWindowController.swift in Sources */,
|
||||
65ED3FFD235DEF6C0081F399 /* PasteboardFolder.swift in Sources */,
|
||||
@ -3810,7 +3810,7 @@
|
||||
65ED4011235DEF6C0081F399 /* AddFolderWindowController.swift in Sources */,
|
||||
65ED4012235DEF6C0081F399 /* TimelineContainerViewController.swift in Sources */,
|
||||
65ED4013235DEF6C0081F399 /* MainWIndowKeyboardHandler.swift in Sources */,
|
||||
65ED4014235DEF6C0081F399 /* PasteboardFeed.swift in Sources */,
|
||||
65ED4014235DEF6C0081F399 /* PasteboardWebFeed.swift in Sources */,
|
||||
65ED4015235DEF6C0081F399 /* AccountsDetailViewController.swift in Sources */,
|
||||
65ED4016235DEF6C0081F399 /* DetailViewController.swift in Sources */,
|
||||
65ED4017235DEF6C0081F399 /* AppDelegate.swift in Sources */,
|
||||
@ -3873,7 +3873,7 @@
|
||||
files = (
|
||||
840D617F2029031C009BC708 /* AppDelegate.swift in Sources */,
|
||||
51236339236915B100951F16 /* RoundedProgressView.swift in Sources */,
|
||||
512E08E72268801200BDCFDD /* FeedTreeControllerDelegate.swift in Sources */,
|
||||
512E08E72268801200BDCFDD /* WebFeedTreeControllerDelegate.swift in Sources */,
|
||||
51C452A422650A2D00C03939 /* ArticleUtilities.swift in Sources */,
|
||||
51EF0F79227716380050506E /* ColorHash.swift in Sources */,
|
||||
5183CCDA226E31A50010922C /* NonIntrinsicImageView.swift in Sources */,
|
||||
@ -3891,7 +3891,7 @@
|
||||
517630232336657E00E15FFF /* ArticleViewControllerWebViewProvider.swift in Sources */,
|
||||
51C4528F226509BD00C03939 /* UnreadFeed.swift in Sources */,
|
||||
51FD413B2342BD0500880194 /* MasterTimelineUnreadCountView.swift in Sources */,
|
||||
513146B2235A81A400387FDC /* AddFeedIntentHandler.swift in Sources */,
|
||||
513146B2235A81A400387FDC /* AddWebFeedIntentHandler.swift in Sources */,
|
||||
51D87EE12311D34700E63F03 /* ActivityType.swift in Sources */,
|
||||
51C452772265091600C03939 /* MultilineUILabelSizer.swift in Sources */,
|
||||
51C452A522650A2D00C03939 /* SmallIconProvider.swift in Sources */,
|
||||
@ -3920,7 +3920,7 @@
|
||||
5F323809231DF9F000706F6B /* VibrantTableViewCell.swift in Sources */,
|
||||
512E09352268B25900BDCFDD /* UISplitViewController-Extensions.swift in Sources */,
|
||||
51FE10042345529D0056195D /* UserNotificationManager.swift in Sources */,
|
||||
51C452A022650A1900C03939 /* FeedIconDownloader.swift in Sources */,
|
||||
51C452A022650A1900C03939 /* WebFeedIconDownloader.swift in Sources */,
|
||||
51C4529E22650A1900C03939 /* ImageDownloader.swift in Sources */,
|
||||
51C45292226509C800C03939 /* TodayFeedDelegate.swift in Sources */,
|
||||
51C452A222650A1900C03939 /* RSHTMLMetadata+Extension.swift in Sources */,
|
||||
@ -3945,7 +3945,7 @@
|
||||
51C452AF2265108300C03939 /* ArticleArray.swift in Sources */,
|
||||
51C4528E2265099C00C03939 /* SmartFeedsController.swift in Sources */,
|
||||
51102165233A7D6C0007A5F7 /* ArticleExtractorButton.swift in Sources */,
|
||||
5141E7392373C18B0013FF27 /* FeedInspectorViewController.swift in Sources */,
|
||||
5141E7392373C18B0013FF27 /* WebFeedInspectorViewController.swift in Sources */,
|
||||
5108F6D42375EEEF001ABC45 /* TimelinePreviewTableViewController.swift in Sources */,
|
||||
84CAFCA522BC8C08007694F0 /* FetchRequestQueue.swift in Sources */,
|
||||
51C4529C22650A1000C03939 /* SingleFaviconDownloader.swift in Sources */,
|
||||
@ -3964,7 +3964,7 @@
|
||||
51A1699B235E10D700EB091F /* AccountInspectorViewController.swift in Sources */,
|
||||
51C452762265091600C03939 /* MasterTimelineViewController.swift in Sources */,
|
||||
5108F6D823763094001ABC45 /* TickMarkSlider.swift in Sources */,
|
||||
51C452882265093600C03939 /* AddFeedViewController.swift in Sources */,
|
||||
51C452882265093600C03939 /* AddWebFeedViewController.swift in Sources */,
|
||||
51A169A0235E10D700EB091F /* FeedbinAccountViewController.swift in Sources */,
|
||||
51934CCE2310792F006127BE /* ActivityManager.swift in Sources */,
|
||||
5108F6B72375E612001ABC45 /* CacheCleaner.swift in Sources */,
|
||||
@ -4040,11 +4040,11 @@
|
||||
8405DD9C22153BD7008CE1BF /* NSView-Extensions.swift in Sources */,
|
||||
849A979F1ED9F130007D329B /* SidebarCell.swift in Sources */,
|
||||
51E595A5228CC36500FCC42B /* ArticleStatusSyncTimer.swift in Sources */,
|
||||
849A97651ED9EB96007D329B /* FeedTreeControllerDelegate.swift in Sources */,
|
||||
849A97651ED9EB96007D329B /* WebFeedTreeControllerDelegate.swift in Sources */,
|
||||
849A97671ED9EB96007D329B /* UnreadCountView.swift in Sources */,
|
||||
51FE10092346739D0056195D /* ActivityType.swift in Sources */,
|
||||
840BEE4121D70E64009BBAFA /* CrashReportWindowController.swift in Sources */,
|
||||
8426118A1FCB67AA0086A189 /* FeedIconDownloader.swift in Sources */,
|
||||
8426118A1FCB67AA0086A189 /* WebFeedIconDownloader.swift in Sources */,
|
||||
84C9FC7B22629E1200D921D6 /* AccountsControlsBackgroundView.swift in Sources */,
|
||||
84162A152038C12C00035290 /* MarkCommandValidationStatus.swift in Sources */,
|
||||
84E95D241FB1087500552D99 /* ArticlePasteboardWriter.swift in Sources */,
|
||||
@ -4070,7 +4070,7 @@
|
||||
8477ACBE22238E9500DF7F37 /* SearchFeedDelegate.swift in Sources */,
|
||||
51E3EB33229AB02C00645299 /* ErrorHandler.swift in Sources */,
|
||||
51FE100A234673A00056195D /* ActivityManager.swift in Sources */,
|
||||
8472058120142E8900AD578B /* FeedInspectorViewController.swift in Sources */,
|
||||
8472058120142E8900AD578B /* WebFeedInspectorViewController.swift in Sources */,
|
||||
55E15BCC229D65A900D6602A /* AccountsReaderAPIWindowController.swift in Sources */,
|
||||
5144EA382279FC6200D19003 /* AccountsAddLocalWindowController.swift in Sources */,
|
||||
84AD1EAA2031617300BC20B7 /* PasteboardFolder.swift in Sources */,
|
||||
@ -4098,7 +4098,7 @@
|
||||
849A97431ED9EAA9007D329B /* AddFolderWindowController.swift in Sources */,
|
||||
8405DDA522168C62008CE1BF /* TimelineContainerViewController.swift in Sources */,
|
||||
844B5B671FEA18E300C7C76A /* MainWIndowKeyboardHandler.swift in Sources */,
|
||||
848D578E21543519005FFAD5 /* PasteboardFeed.swift in Sources */,
|
||||
848D578E21543519005FFAD5 /* PasteboardWebFeed.swift in Sources */,
|
||||
5144EA2F2279FAB600D19003 /* AccountsDetailViewController.swift in Sources */,
|
||||
849A97801ED9EC42007D329B /* DetailViewController.swift in Sources */,
|
||||
518C3193237B00D9004D740F /* ArticleIconSchemeHandler.swift in Sources */,
|
||||
|
@ -29,7 +29,7 @@ class ActivityManager {
|
||||
}
|
||||
|
||||
init() {
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(feedIconDidBecomeAvailable(_:)), name: .FeedIconDidBecomeAvailable, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(webFeedIconDidBecomeAvailable(_:)), name: .WebFeedIconDidBecomeAvailable, object: nil)
|
||||
}
|
||||
|
||||
func invalidateCurrentActivities() {
|
||||
@ -43,7 +43,7 @@ class ActivityManager {
|
||||
|
||||
selectingActivity = makeSelectFeedActivity(fetcher: fetcher)
|
||||
|
||||
if let feed = fetcher as? Feed {
|
||||
if let feed = fetcher as? WebFeed {
|
||||
updateSelectingActivityFeedSearchAttributes(with: feed)
|
||||
}
|
||||
|
||||
@ -106,8 +106,8 @@ class ActivityManager {
|
||||
}
|
||||
}
|
||||
|
||||
for feed in account.flattenedFeeds() {
|
||||
ids.append(contentsOf: identifers(for: feed))
|
||||
for webFeed in account.flattenedWebFeeds() {
|
||||
ids.append(contentsOf: identifers(for: webFeed))
|
||||
}
|
||||
|
||||
CSSearchableIndex.default().deleteSearchableItems(withIdentifiers: ids)
|
||||
@ -117,31 +117,31 @@ class ActivityManager {
|
||||
var ids = [String]()
|
||||
ids.append(identifer(for: folder))
|
||||
|
||||
for feed in folder.flattenedFeeds() {
|
||||
ids.append(contentsOf: identifers(for: feed))
|
||||
for webFeed in folder.flattenedWebFeeds() {
|
||||
ids.append(contentsOf: identifers(for: webFeed))
|
||||
}
|
||||
|
||||
CSSearchableIndex.default().deleteSearchableItems(withIdentifiers: ids)
|
||||
}
|
||||
|
||||
static func cleanUp(_ feed: Feed) {
|
||||
CSSearchableIndex.default().deleteSearchableItems(withIdentifiers: identifers(for: feed))
|
||||
static func cleanUp(_ webFeed: WebFeed) {
|
||||
CSSearchableIndex.default().deleteSearchableItems(withIdentifiers: identifers(for: webFeed))
|
||||
}
|
||||
#endif
|
||||
|
||||
@objc func feedIconDidBecomeAvailable(_ note: Notification) {
|
||||
guard let feed = note.userInfo?[UserInfoKey.feed] as? Feed, let activityFeedId = selectingActivity?.userInfo?[ArticlePathKey.feedID] as? String else {
|
||||
@objc func webFeedIconDidBecomeAvailable(_ note: Notification) {
|
||||
guard let webFeed = note.userInfo?[UserInfoKey.webFeed] as? WebFeed, let activityFeedId = selectingActivity?.userInfo?[ArticlePathKey.webFeedID] as? String else {
|
||||
return
|
||||
}
|
||||
|
||||
#if os(iOS)
|
||||
if let article = readingArticle, activityFeedId == article.feedID {
|
||||
if let article = readingArticle, activityFeedId == article.webFeedID {
|
||||
updateReadArticleSearchAttributes(with: article)
|
||||
}
|
||||
#endif
|
||||
|
||||
if activityFeedId == feed.feedID {
|
||||
updateSelectingActivityFeedSearchAttributes(with: feed)
|
||||
if activityFeedId == webFeed.webFeedID {
|
||||
updateSelectingActivityFeedSearchAttributes(with: webFeed)
|
||||
}
|
||||
}
|
||||
|
||||
@ -224,7 +224,7 @@ private extension ActivityManager {
|
||||
#endif
|
||||
|
||||
func makeKeywords(_ article: Article) -> [String] {
|
||||
let feedNameKeywords = makeKeywords(article.feed?.nameForDisplay)
|
||||
let feedNameKeywords = makeKeywords(article.webFeed?.nameForDisplay)
|
||||
let articleTitleKeywords = makeKeywords(ArticleStringFormatter.truncatedTitle(article))
|
||||
return feedNameKeywords + articleTitleKeywords
|
||||
}
|
||||
@ -233,13 +233,13 @@ private extension ActivityManager {
|
||||
return value?.components(separatedBy: " ").filter { $0.count > 2 } ?? []
|
||||
}
|
||||
|
||||
func updateSelectingActivityFeedSearchAttributes(with feed: Feed) {
|
||||
func updateSelectingActivityFeedSearchAttributes(with feed: WebFeed) {
|
||||
|
||||
let attributeSet = CSSearchableItemAttributeSet(itemContentType: kUTTypeItem as String)
|
||||
attributeSet.title = feed.nameForDisplay
|
||||
attributeSet.keywords = makeKeywords(feed.nameForDisplay)
|
||||
attributeSet.relatedUniqueIdentifier = ActivityManager.identifer(for: feed)
|
||||
if let iconImage = appDelegate.feedIconDownloader.icon(for: feed) {
|
||||
if let iconImage = appDelegate.webFeedIconDownloader.icon(for: feed) {
|
||||
attributeSet.thumbnailData = iconImage.image.dataRepresentation()
|
||||
} else if let iconImage = appDelegate.faviconDownloader.faviconAsIcon(for: feed) {
|
||||
attributeSet.thumbnailData = iconImage.image.dataRepresentation()
|
||||
@ -267,15 +267,15 @@ private extension ActivityManager {
|
||||
return "account_\(folder.account!.accountID)_folder_\(folder.nameForDisplay)"
|
||||
}
|
||||
|
||||
static func identifer(for feed: Feed) -> String {
|
||||
return "account_\(feed.account!.accountID)_feed_\(feed.feedID)"
|
||||
static func identifer(for feed: WebFeed) -> String {
|
||||
return "account_\(feed.account!.accountID)_feed_\(feed.webFeedID)"
|
||||
}
|
||||
|
||||
static func identifer(for article: Article) -> String {
|
||||
return "account_\(article.accountID)_feed_\(article.feedID)_article_\(article.articleID)"
|
||||
return "account_\(article.accountID)_feed_\(article.webFeedID)_article_\(article.articleID)"
|
||||
}
|
||||
|
||||
static func identifers(for feed: Feed) -> [String] {
|
||||
static func identifers(for feed: WebFeed) -> [String] {
|
||||
var ids = [String]()
|
||||
ids.append(identifer(for: feed))
|
||||
|
||||
|
@ -144,9 +144,9 @@ private extension ArticleRenderer {
|
||||
d["avatars"] = "<td class=\"header rightAlign avatar\"><img src=\"\(ArticleRenderer.imageIconScheme)://\" height=48 width=48 /></td>";
|
||||
|
||||
var feedLink = ""
|
||||
if let feedTitle = article.feed?.nameForDisplay {
|
||||
if let feedTitle = article.webFeed?.nameForDisplay {
|
||||
feedLink = feedTitle
|
||||
if let feedURL = article.feed?.homePageURL {
|
||||
if let feedURL = article.webFeed?.homePageURL {
|
||||
feedLink = feedLink.htmlByAddingLink(feedURL, className: "feedLink")
|
||||
}
|
||||
}
|
||||
@ -184,7 +184,7 @@ private extension ArticleRenderer {
|
||||
}
|
||||
|
||||
func byline() -> String {
|
||||
guard let authors = article?.authors ?? article?.feed?.authors, !authors.isEmpty else {
|
||||
guard let authors = article?.authors ?? article?.webFeed?.authors, !authors.isEmpty else {
|
||||
return ""
|
||||
}
|
||||
|
||||
@ -192,7 +192,7 @@ private extension ArticleRenderer {
|
||||
// This code assumes that multiple authors would never match the feed name so that
|
||||
// if there feed owner has an article co-author all authors are given the byline.
|
||||
if authors.count == 1, let author = authors.first {
|
||||
if author.name == article?.feed?.nameForDisplay {
|
||||
if author.name == article?.webFeed?.nameForDisplay {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
@ -256,10 +256,10 @@ private extension Article {
|
||||
var baseURL: URL? {
|
||||
var s = url
|
||||
if s == nil {
|
||||
s = feed?.homePageURL
|
||||
s = webFeed?.homePageURL
|
||||
}
|
||||
if s == nil {
|
||||
s = feed?.url
|
||||
s = webFeed?.url
|
||||
}
|
||||
|
||||
guard let urlString = s else {
|
||||
|
@ -63,7 +63,7 @@ final class DeleteCommand: UndoableCommand {
|
||||
}
|
||||
|
||||
for node in nodes {
|
||||
if let _ = node.representedObject as? Feed {
|
||||
if let _ = node.representedObject as? WebFeed {
|
||||
continue
|
||||
}
|
||||
if let _ = node.representedObject as? Folder {
|
||||
@ -84,7 +84,7 @@ private struct SidebarItemSpecifier {
|
||||
private weak var account: Account?
|
||||
private let parentFolder: Folder?
|
||||
private let folder: Folder?
|
||||
private let feed: Feed?
|
||||
private let webFeed: WebFeed?
|
||||
private let path: ContainerPath
|
||||
private let errorHandler: (Error) -> ()
|
||||
|
||||
@ -104,13 +104,13 @@ private struct SidebarItemSpecifier {
|
||||
|
||||
self.parentFolder = node.parentFolder()
|
||||
|
||||
if let feed = node.representedObject as? Feed {
|
||||
self.feed = feed
|
||||
if let webFeed = node.representedObject as? WebFeed {
|
||||
self.webFeed = webFeed
|
||||
self.folder = nil
|
||||
account = feed.account
|
||||
account = webFeed.account
|
||||
}
|
||||
else if let folder = node.representedObject as? Folder {
|
||||
self.feed = nil
|
||||
self.webFeed = nil
|
||||
self.folder = folder
|
||||
account = folder.account
|
||||
}
|
||||
@ -130,7 +130,7 @@ private struct SidebarItemSpecifier {
|
||||
|
||||
func delete(completion: @escaping () -> Void) {
|
||||
|
||||
if let feed = feed {
|
||||
if let webFeed = webFeed {
|
||||
|
||||
guard let container = path.resolveContainer() else {
|
||||
completion()
|
||||
@ -138,7 +138,7 @@ private struct SidebarItemSpecifier {
|
||||
}
|
||||
|
||||
BatchUpdate.shared.start()
|
||||
account?.removeFeed(feed, from: container) { result in
|
||||
account?.removeWebFeed(webFeed, from: container) { result in
|
||||
BatchUpdate.shared.end()
|
||||
completion()
|
||||
self.checkResult(result)
|
||||
@ -158,22 +158,22 @@ private struct SidebarItemSpecifier {
|
||||
|
||||
func restore() {
|
||||
|
||||
if let _ = feed {
|
||||
restoreFeed()
|
||||
if let _ = webFeed {
|
||||
restoreWebFeed()
|
||||
}
|
||||
else if let _ = folder {
|
||||
restoreFolder()
|
||||
}
|
||||
}
|
||||
|
||||
private func restoreFeed() {
|
||||
private func restoreWebFeed() {
|
||||
|
||||
guard let account = account, let feed = feed, let container = path.resolveContainer() else {
|
||||
guard let account = account, let feed = webFeed, let container = path.resolveContainer() else {
|
||||
return
|
||||
}
|
||||
|
||||
BatchUpdate.shared.start()
|
||||
account.restoreFeed(feed, container: container) { result in
|
||||
account.restoreWebFeed(feed, container: container) { result in
|
||||
BatchUpdate.shared.end()
|
||||
self.checkResult(result)
|
||||
}
|
||||
@ -257,7 +257,7 @@ private struct DeleteActionName {
|
||||
var numberOfFolders = 0
|
||||
|
||||
for node in nodes {
|
||||
if let _ = node.representedObject as? Feed {
|
||||
if let _ = node.representedObject as? WebFeed {
|
||||
numberOfFeeds += 1
|
||||
}
|
||||
else if let _ = node.representedObject as? Folder {
|
||||
|
@ -57,7 +57,7 @@ private extension SendToMarsEditCommand {
|
||||
let body = article.contentHTML ?? article.contentText ?? article.summary
|
||||
let authorName = article.authors?.first?.name
|
||||
|
||||
let sender = SendToBlogEditorApp(targetDesciptor: targetDescriptor, title: article.title, body: body, summary: article.summary, link: article.externalURL, permalink: article.url, subject: nil, creator: authorName, commentsURL: nil, guid: article.uniqueID, sourceName: article.feed?.nameForDisplay, sourceHomeURL: article.feed?.homePageURL, sourceFeedURL: article.feed?.url)
|
||||
let sender = SendToBlogEditorApp(targetDesciptor: targetDescriptor, title: article.title, body: body, summary: article.summary, link: article.externalURL, permalink: article.url, subject: nil, creator: authorName, commentsURL: nil, guid: article.uniqueID, sourceName: article.webFeed?.nameForDisplay, sourceHomeURL: article.webFeed?.homePageURL, sourceFeedURL: article.webFeed?.url)
|
||||
let _ = sender.send()
|
||||
}
|
||||
|
||||
|
@ -68,10 +68,10 @@ private extension Article {
|
||||
// Feed name, or feed name + author name (if author is specified per-article).
|
||||
// Includes trailing space.
|
||||
|
||||
if let feedName = feed?.nameForDisplay, let authorName = authors?.first?.name {
|
||||
if let feedName = webFeed?.nameForDisplay, let authorName = authors?.first?.name {
|
||||
return feedName + ", " + authorName + ": "
|
||||
}
|
||||
if let feedName = feed?.nameForDisplay {
|
||||
if let feedName = webFeed?.nameForDisplay {
|
||||
return feedName + ": "
|
||||
}
|
||||
return ""
|
||||
|
@ -42,8 +42,8 @@ private func accountAndArticlesDictionary(_ articles: Set<Article>) -> [String:
|
||||
|
||||
extension Article {
|
||||
|
||||
var feed: Feed? {
|
||||
return account?.existingFeed(withFeedID: feedID)
|
||||
var webFeed: WebFeed? {
|
||||
return account?.existingWebFeed(withWebFeedID: webFeedID)
|
||||
}
|
||||
|
||||
var preferredLink: String? {
|
||||
@ -71,26 +71,26 @@ extension Article {
|
||||
}
|
||||
}
|
||||
|
||||
if let authors = feed?.authors, authors.count == 1, let author = authors.first {
|
||||
if let authors = webFeed?.authors, authors.count == 1, let author = authors.first {
|
||||
if let image = appDelegate.authorAvatarDownloader.image(for: author) {
|
||||
return image
|
||||
}
|
||||
}
|
||||
|
||||
guard let feed = feed else {
|
||||
guard let webFeed = webFeed else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let feedIconImage = appDelegate.feedIconDownloader.icon(for: feed)
|
||||
let feedIconImage = appDelegate.webFeedIconDownloader.icon(for: webFeed)
|
||||
if feedIconImage != nil {
|
||||
return feedIconImage
|
||||
}
|
||||
|
||||
if let faviconImage = appDelegate.faviconDownloader.faviconAsIcon(for: feed) {
|
||||
if let faviconImage = appDelegate.faviconDownloader.faviconAsIcon(for: webFeed) {
|
||||
return faviconImage
|
||||
}
|
||||
|
||||
return FaviconGenerator.favicon(feed)
|
||||
return FaviconGenerator.favicon(webFeed)
|
||||
}
|
||||
}
|
||||
|
||||
@ -99,7 +99,7 @@ extension Article {
|
||||
struct ArticlePathKey {
|
||||
static let accountID = "accountID"
|
||||
static let accountName = "accountName"
|
||||
static let feedID = "feedID"
|
||||
static let webFeedID = "webFeedID"
|
||||
static let articleID = "articleID"
|
||||
}
|
||||
|
||||
@ -109,7 +109,7 @@ extension Article {
|
||||
return [
|
||||
ArticlePathKey.accountID: accountID,
|
||||
ArticlePathKey.accountName: account?.nameForDisplay ?? "",
|
||||
ArticlePathKey.feedID: feedID,
|
||||
ArticlePathKey.webFeedID: webFeedID,
|
||||
ArticlePathKey.articleID: articleID
|
||||
]
|
||||
}
|
||||
@ -121,7 +121,7 @@ extension Article {
|
||||
extension Article: SortableArticle {
|
||||
|
||||
var sortableName: String {
|
||||
return feed?.name ?? ""
|
||||
return webFeed?.name ?? ""
|
||||
}
|
||||
|
||||
var sortableDate: Date {
|
||||
@ -132,8 +132,8 @@ extension Article: SortableArticle {
|
||||
return articleID
|
||||
}
|
||||
|
||||
var sortableFeedID: String {
|
||||
return feedID
|
||||
var sortableWebFeedID: String {
|
||||
return webFeedID
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ protocol SmallIconProvider {
|
||||
var smallIcon: IconImage? { get }
|
||||
}
|
||||
|
||||
extension Feed: SmallIconProvider {
|
||||
extension WebFeed: SmallIconProvider {
|
||||
|
||||
var smallIcon: IconImage? {
|
||||
if let iconImage = appDelegate.faviconDownloader.favicon(for: self) {
|
||||
|
@ -41,7 +41,7 @@ final class FaviconDownloader {
|
||||
}
|
||||
|
||||
private let queue: DispatchQueue
|
||||
private var cache = [Feed: IconImage]() // faviconURL: RSImage
|
||||
private var cache = [WebFeed: IconImage]() // faviconURL: RSImage
|
||||
|
||||
struct UserInfoKey {
|
||||
static let faviconURL = "faviconURL"
|
||||
@ -64,21 +64,21 @@ final class FaviconDownloader {
|
||||
// MARK: - API
|
||||
|
||||
func resetCache() {
|
||||
cache = [Feed: IconImage]()
|
||||
cache = [WebFeed: IconImage]()
|
||||
}
|
||||
|
||||
func favicon(for feed: Feed) -> IconImage? {
|
||||
func favicon(for webFeed: WebFeed) -> IconImage? {
|
||||
|
||||
assert(Thread.isMainThread)
|
||||
|
||||
if let faviconURL = feed.faviconURL {
|
||||
if let faviconURL = webFeed.faviconURL {
|
||||
return favicon(with: faviconURL)
|
||||
}
|
||||
|
||||
var homePageURL = feed.homePageURL
|
||||
var homePageURL = webFeed.homePageURL
|
||||
if homePageURL == nil {
|
||||
// Base homePageURL off feedURL if needed. Won’t always be accurate, but is good enough.
|
||||
if let feedURL = URL(string: feed.url), let scheme = feedURL.scheme, let host = feedURL.host {
|
||||
if let feedURL = URL(string: webFeed.url), let scheme = feedURL.scheme, let host = feedURL.host {
|
||||
homePageURL = scheme + "://" + host + "/"
|
||||
}
|
||||
}
|
||||
@ -89,16 +89,16 @@ final class FaviconDownloader {
|
||||
return nil
|
||||
}
|
||||
|
||||
func faviconAsIcon(for feed: Feed) -> IconImage? {
|
||||
func faviconAsIcon(for webFeed: WebFeed) -> IconImage? {
|
||||
|
||||
if let image = cache[feed] {
|
||||
if let image = cache[webFeed] {
|
||||
return image
|
||||
}
|
||||
|
||||
if let iconImage = favicon(for: feed), let imageData = iconImage.image.dataRepresentation() {
|
||||
if let iconImage = favicon(for: webFeed), let imageData = iconImage.image.dataRepresentation() {
|
||||
if let scaledImage = RSImage.scaledForIcon(imageData) {
|
||||
let scaledIconImage = IconImage(scaledImage)
|
||||
cache[feed] = scaledIconImage
|
||||
cache[webFeed] = scaledIconImage
|
||||
return scaledIconImage
|
||||
}
|
||||
}
|
||||
|
@ -14,16 +14,16 @@ final class FaviconGenerator {
|
||||
|
||||
private static var faviconGeneratorCache = [String: IconImage]() // feedURL: RSImage
|
||||
|
||||
static func favicon(_ feed: Feed) -> IconImage {
|
||||
static func favicon(_ webFeed: WebFeed) -> IconImage {
|
||||
|
||||
if let favicon = FaviconGenerator.faviconGeneratorCache[feed.url] {
|
||||
if let favicon = FaviconGenerator.faviconGeneratorCache[webFeed.url] {
|
||||
return favicon
|
||||
}
|
||||
|
||||
let colorHash = ColorHash(feed.url)
|
||||
let colorHash = ColorHash(webFeed.url)
|
||||
if let favicon = AppAssets.faviconTemplateImage.maskWithColor(color: colorHash.color.cgColor) {
|
||||
let iconImage = IconImage(favicon)
|
||||
FaviconGenerator.faviconGeneratorCache[feed.url] = iconImage
|
||||
FaviconGenerator.faviconGeneratorCache[webFeed.url] = iconImage
|
||||
return iconImage
|
||||
} else {
|
||||
return IconImage(AppAssets.faviconTemplateImage)
|
||||
|
@ -15,10 +15,10 @@ import RSParser
|
||||
|
||||
extension Notification.Name {
|
||||
|
||||
static let FeedIconDidBecomeAvailable = Notification.Name("FeedIconDidBecomeAvailableNotification") // UserInfoKey.feed
|
||||
static let WebFeedIconDidBecomeAvailable = Notification.Name("WebFeedIconDidBecomeAvailableNotification") // UserInfoKey.feed
|
||||
}
|
||||
|
||||
public final class FeedIconDownloader {
|
||||
public final class WebFeedIconDownloader {
|
||||
|
||||
private static let saveQueue = CoalescingQueue(name: "Cache Save Queue", interval: 1.0)
|
||||
|
||||
@ -45,8 +45,8 @@ public final class FeedIconDownloader {
|
||||
}()
|
||||
|
||||
private var urlsInProgress = Set<String>()
|
||||
private var cache = [Feed: IconImage]()
|
||||
private var waitingForFeedURLs = [String: Feed]()
|
||||
private var cache = [WebFeed: IconImage]()
|
||||
private var waitingForFeedURLs = [String: WebFeed]()
|
||||
|
||||
init(imageDownloader: ImageDownloader, folder: String) {
|
||||
self.imageDownloader = imageDownloader
|
||||
@ -58,10 +58,10 @@ public final class FeedIconDownloader {
|
||||
}
|
||||
|
||||
func resetCache() {
|
||||
cache = [Feed: IconImage]()
|
||||
cache = [WebFeed: IconImage]()
|
||||
}
|
||||
|
||||
func icon(for feed: Feed) -> IconImage? {
|
||||
func icon(for feed: WebFeed) -> IconImage? {
|
||||
|
||||
if let cachedImage = cache[feed] {
|
||||
return cachedImage
|
||||
@ -120,9 +120,9 @@ public final class FeedIconDownloader {
|
||||
|
||||
}
|
||||
|
||||
private extension FeedIconDownloader {
|
||||
private extension WebFeedIconDownloader {
|
||||
|
||||
func icon(forHomePageURL homePageURL: String, feed: Feed, _ imageResultBlock: @escaping (RSImage?) -> Void) {
|
||||
func icon(forHomePageURL homePageURL: String, feed: WebFeed, _ imageResultBlock: @escaping (RSImage?) -> Void) {
|
||||
|
||||
if homePagesWithNoIconURLCache.contains(homePageURL) || homePagesWithUglyIcons.contains(homePageURL) {
|
||||
imageResultBlock(nil)
|
||||
@ -137,7 +137,7 @@ private extension FeedIconDownloader {
|
||||
findIconURLForHomePageURL(homePageURL, feed: feed)
|
||||
}
|
||||
|
||||
func icon(forURL url: String, feed: Feed, _ imageResultBlock: @escaping (RSImage?) -> Void) {
|
||||
func icon(forURL url: String, feed: WebFeed, _ imageResultBlock: @escaping (RSImage?) -> Void) {
|
||||
waitingForFeedURLs[url] = feed
|
||||
guard let imageData = imageDownloader.image(for: url) else {
|
||||
imageResultBlock(nil)
|
||||
@ -146,11 +146,11 @@ private extension FeedIconDownloader {
|
||||
RSImage.scaledForIcon(imageData, imageResultBlock: imageResultBlock)
|
||||
}
|
||||
|
||||
func postFeedIconDidBecomeAvailableNotification(_ feed: Feed) {
|
||||
func postFeedIconDidBecomeAvailableNotification(_ feed: WebFeed) {
|
||||
|
||||
DispatchQueue.main.async {
|
||||
let userInfo: [AnyHashable: Any] = [UserInfoKey.feed: feed]
|
||||
NotificationCenter.default.post(name: .FeedIconDidBecomeAvailable, object: self, userInfo: userInfo)
|
||||
let userInfo: [AnyHashable: Any] = [UserInfoKey.webFeed: feed]
|
||||
NotificationCenter.default.post(name: .WebFeedIconDidBecomeAvailable, object: self, userInfo: userInfo)
|
||||
}
|
||||
}
|
||||
|
||||
@ -166,7 +166,7 @@ private extension FeedIconDownloader {
|
||||
homePageToIconURLCacheDirty = true
|
||||
}
|
||||
|
||||
func findIconURLForHomePageURL(_ homePageURL: String, feed: Feed) {
|
||||
func findIconURLForHomePageURL(_ homePageURL: String, feed: WebFeed) {
|
||||
|
||||
guard !urlsInProgress.contains(homePageURL) else {
|
||||
return
|
||||
@ -183,7 +183,7 @@ private extension FeedIconDownloader {
|
||||
}
|
||||
}
|
||||
|
||||
func pullIconURL(from metadata: RSHTMLMetadata, homePageURL: String, feed: Feed) {
|
||||
func pullIconURL(from metadata: RSHTMLMetadata, homePageURL: String, feed: WebFeed) {
|
||||
|
||||
if let url = metadata.bestWebsiteIconURL() {
|
||||
cacheIconURL(for: homePageURL, url)
|
||||
@ -216,11 +216,11 @@ private extension FeedIconDownloader {
|
||||
}
|
||||
|
||||
func queueSaveHomePageToIconURLCacheIfNeeded() {
|
||||
FeedIconDownloader.saveQueue.add(self, #selector(saveHomePageToIconURLCacheIfNeeded))
|
||||
WebFeedIconDownloader.saveQueue.add(self, #selector(saveHomePageToIconURLCacheIfNeeded))
|
||||
}
|
||||
|
||||
func queueHomePagesWithNoIconURLCacheIfNeeded() {
|
||||
FeedIconDownloader.saveQueue.add(self, #selector(saveHomePagesWithNoIconURLCacheIfNeeded))
|
||||
WebFeedIconDownloader.saveQueue.add(self, #selector(saveHomePagesWithNoIconURLCacheIfNeeded))
|
||||
}
|
||||
|
||||
func saveHomePageToIconURLCache() {
|
@ -13,7 +13,7 @@ protocol SortableArticle {
|
||||
var sortableName: String { get }
|
||||
var sortableDate: Date { get }
|
||||
var sortableArticleID: String { get }
|
||||
var sortableFeedID: String { get }
|
||||
var sortableWebFeedID: String { get }
|
||||
}
|
||||
|
||||
struct ArticleSorter {
|
||||
@ -34,7 +34,7 @@ struct ArticleSorter {
|
||||
sortByDateDirection: ComparisonResult) -> [T] {
|
||||
// Group articles by "feed-feedID" - feed ID is used to differentiate between
|
||||
// two feeds that have the same name
|
||||
let groupedArticles = Dictionary(grouping: articles) { "\($0.sortableName.lowercased())-\($0.sortableFeedID)" }
|
||||
let groupedArticles = Dictionary(grouping: articles) { "\($0.sortableName.lowercased())-\($0.sortableWebFeedID)" }
|
||||
return groupedArticles
|
||||
.sorted { $0.key < $1.key }
|
||||
.flatMap { (tuple) -> [T] in
|
||||
|
@ -11,7 +11,7 @@ import RSTree
|
||||
import Articles
|
||||
import Account
|
||||
|
||||
final class FeedTreeControllerDelegate: TreeControllerDelegate {
|
||||
final class WebFeedTreeControllerDelegate: TreeControllerDelegate {
|
||||
|
||||
func treeController(treeController: TreeController, childNodesFor node: Node) -> [Node]? {
|
||||
|
||||
@ -29,7 +29,7 @@ final class FeedTreeControllerDelegate: TreeControllerDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
private extension FeedTreeControllerDelegate {
|
||||
private extension WebFeedTreeControllerDelegate {
|
||||
|
||||
func childNodesForRootNode(_ rootNode: Node) -> [Node]? {
|
||||
|
||||
@ -52,7 +52,7 @@ private extension FeedTreeControllerDelegate {
|
||||
let container = containerNode.representedObject as! Container
|
||||
|
||||
var children = [AnyObject]()
|
||||
children.append(contentsOf: Array(container.topLevelFeeds))
|
||||
children.append(contentsOf: Array(container.topLevelWebFeeds))
|
||||
if let folders = container.folders {
|
||||
children.append(contentsOf: Array(folders))
|
||||
}
|
||||
@ -78,8 +78,8 @@ private extension FeedTreeControllerDelegate {
|
||||
|
||||
func createNode(representedObject: Any, parent: Node) -> Node? {
|
||||
|
||||
if let feed = representedObject as? Feed {
|
||||
return createNode(feed: feed, parent: parent)
|
||||
if let webFeed = representedObject as? WebFeed {
|
||||
return createNode(webFeed: webFeed, parent: parent)
|
||||
}
|
||||
if let folder = representedObject as? Folder {
|
||||
return createNode(folder: folder, parent: parent)
|
||||
@ -91,9 +91,9 @@ private extension FeedTreeControllerDelegate {
|
||||
return nil
|
||||
}
|
||||
|
||||
func createNode(feed: Feed, parent: Node) -> Node {
|
||||
func createNode(webFeed: WebFeed, parent: Node) -> Node {
|
||||
|
||||
return parent.createChildNode(feed)
|
||||
return parent.createChildNode(webFeed)
|
||||
}
|
||||
|
||||
func createNode(folder: Folder, parent: Node) -> Node {
|
@ -17,7 +17,7 @@ struct UserInfoKey {
|
||||
static let articles = "articles"
|
||||
static let navigationKeyPressed = "navigationKeyPressed"
|
||||
static let objects = "objects"
|
||||
static let feed = "feed"
|
||||
static let webFeed = "webFeed"
|
||||
static let url = "url"
|
||||
static let author = "author"
|
||||
static let articlePath = "articlePath"
|
||||
|
@ -25,8 +25,8 @@ final class UserNotificationManager: NSObject {
|
||||
}
|
||||
|
||||
for article in articles {
|
||||
if !article.status.read, let feed = article.feed, feed.isNotifyAboutNewArticles ?? false {
|
||||
sendNotification(feed: feed, article: article)
|
||||
if !article.status.read, let webFeed = article.webFeed, webFeed.isNotifyAboutNewArticles ?? false {
|
||||
sendNotification(webFeed: webFeed, article: article)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -43,10 +43,10 @@ final class UserNotificationManager: NSObject {
|
||||
|
||||
private extension UserNotificationManager {
|
||||
|
||||
private func sendNotification(feed: Feed, article: Article) {
|
||||
private func sendNotification(webFeed: WebFeed, article: Article) {
|
||||
let content = UNMutableNotificationContent()
|
||||
|
||||
content.title = feed.nameForDisplay
|
||||
content.title = webFeed.nameForDisplay
|
||||
content.body = ArticleStringFormatter.truncatedTitle(article)
|
||||
if content.body.isEmpty {
|
||||
content.body = ArticleStringFormatter.truncatedSummary(article)
|
||||
|
@ -10,7 +10,7 @@
|
||||
<!--Add Feed-->
|
||||
<scene sceneID="2Tc-JN-edX">
|
||||
<objects>
|
||||
<tableViewController storyboardIdentifier="AddFeedViewController" id="7aE-6a-iP7" customClass="AddFeedViewController" customModule="NetNewsWire" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<tableViewController storyboardIdentifier="AddWebFeedViewController" id="7aE-6a-iP7" customClass="AddWebFeedViewController" customModule="NetNewsWire" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" scrollEnabled="NO" dataMode="static" style="grouped" separatorStyle="default" allowsSelection="NO" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="1" sectionFooterHeight="5" id="D0S-TM-mtm">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="812"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
@ -78,7 +78,7 @@
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="vaV-kY-CaE">
|
||||
<rect key="frame" x="313" y="11.999999999999998" width="42" height="20.333333333333329"/>
|
||||
<rect key="frame" x="313" y="12" width="42" height="20"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
@ -166,12 +166,12 @@
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<segmentedControl opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="1Ce-E7-rG2">
|
||||
<rect key="frame" x="112.66666666666669" y="108" width="150" height="32"/>
|
||||
<rect key="frame" x="97.666666666666686" y="108" width="180" height="32"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="150" id="IEO-pf-4PB"/>
|
||||
<constraint firstAttribute="width" constant="180" id="IEO-pf-4PB"/>
|
||||
</constraints>
|
||||
<segments>
|
||||
<segment title="Feed"/>
|
||||
<segment title="Web Feed"/>
|
||||
<segment title="Folder"/>
|
||||
</segments>
|
||||
<connections>
|
||||
@ -282,7 +282,7 @@
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="mxj-Bw-Jfx">
|
||||
<rect key="frame" x="313" y="11.999999999999998" width="42" height="20.333333333333329"/>
|
||||
<rect key="frame" x="313" y="12" width="42" height="20"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
|
@ -105,15 +105,15 @@ private extension AddContainerViewController {
|
||||
|
||||
func switchToFeed() {
|
||||
|
||||
guard !(currentViewController is AddFeedViewController) else {
|
||||
guard !(currentViewController is AddWebFeedViewController) else {
|
||||
return
|
||||
}
|
||||
|
||||
navigationItem.title = NSLocalizedString("Add Feed", comment: "Add Feed")
|
||||
navigationItem.title = NSLocalizedString("Add Web Feed", comment: "Add Web Feed")
|
||||
resetUI()
|
||||
hideCurrentController()
|
||||
|
||||
let addFeedController = UIStoryboard.add.instantiateController(ofType: AddFeedViewController.self)
|
||||
let addFeedController = UIStoryboard.add.instantiateController(ofType: AddWebFeedViewController.self)
|
||||
addFeedController.initialFeed = initialFeed
|
||||
addFeedController.initialFeedName = initialFeedName
|
||||
|
||||
|
@ -12,7 +12,7 @@ import RSCore
|
||||
import RSTree
|
||||
import RSParser
|
||||
|
||||
class AddFeedViewController: UITableViewController, AddContainerViewControllerChild {
|
||||
class AddWebFeedViewController: UITableViewController, AddContainerViewControllerChild {
|
||||
|
||||
@IBOutlet private weak var urlTextField: UITextField!
|
||||
@IBOutlet private weak var nameTextField: UITextField!
|
||||
@ -91,7 +91,7 @@ class AddFeedViewController: UITableViewController, AddContainerViewControllerCh
|
||||
account = containerAccount
|
||||
}
|
||||
|
||||
if account!.hasFeed(withURL: url.absoluteString) {
|
||||
if account!.hasWebFeed(withURL: url.absoluteString) {
|
||||
presentError(AccountError.createErrorAlreadySubscribed)
|
||||
return
|
||||
}
|
||||
@ -102,14 +102,14 @@ class AddFeedViewController: UITableViewController, AddContainerViewControllerCh
|
||||
|
||||
BatchUpdate.shared.start()
|
||||
|
||||
account!.createFeed(url: url.absoluteString, name: feedName, container: container) { result in
|
||||
account!.createWebFeed(url: url.absoluteString, name: feedName, container: container) { result in
|
||||
|
||||
BatchUpdate.shared.end()
|
||||
|
||||
switch result {
|
||||
case .success(let feed):
|
||||
self.delegate?.processingDidEnd()
|
||||
NotificationCenter.default.post(name: .UserDidAddFeed, object: self, userInfo: [UserInfoKey.feed: feed])
|
||||
NotificationCenter.default.post(name: .UserDidAddFeed, object: self, userInfo: [UserInfoKey.webFeed: feed])
|
||||
case .failure(let error):
|
||||
self.presentError(error)
|
||||
self.delegate?.processingDidCancel()
|
||||
@ -135,7 +135,7 @@ class AddFeedViewController: UITableViewController, AddContainerViewControllerCh
|
||||
|
||||
}
|
||||
|
||||
extension AddFeedViewController: UIPickerViewDataSource, UIPickerViewDelegate {
|
||||
extension AddWebFeedViewController: UIPickerViewDataSource, UIPickerViewDelegate {
|
||||
|
||||
func numberOfComponents(in pickerView: UIPickerView) ->Int {
|
||||
return 1
|
||||
@ -155,7 +155,7 @@ extension AddFeedViewController: UIPickerViewDataSource, UIPickerViewDelegate {
|
||||
|
||||
}
|
||||
|
||||
extension AddFeedViewController: UITextFieldDelegate {
|
||||
extension AddWebFeedViewController: UITextFieldDelegate {
|
||||
|
||||
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
|
||||
textField.resignFirstResponder()
|
@ -40,7 +40,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
|
||||
var faviconDownloader: FaviconDownloader!
|
||||
var imageDownloader: ImageDownloader!
|
||||
var authorAvatarDownloader: AuthorAvatarDownloader!
|
||||
var feedIconDownloader: FeedIconDownloader!
|
||||
var webFeedIconDownloader: WebFeedIconDownloader!
|
||||
|
||||
var unreadCount = 0 {
|
||||
didSet {
|
||||
@ -192,7 +192,7 @@ private extension AppDelegate {
|
||||
|
||||
let tempFolder = tempDir.absoluteString
|
||||
let tempFolderPath = tempFolder.suffix(from: tempFolder.index(tempFolder.startIndex, offsetBy: 7))
|
||||
feedIconDownloader = FeedIconDownloader(imageDownloader: imageDownloader, folder: String(tempFolderPath))
|
||||
webFeedIconDownloader = WebFeedIconDownloader(imageDownloader: imageDownloader, folder: String(tempFolderPath))
|
||||
}
|
||||
|
||||
private func initializeHomeScreenQuickActions() {
|
||||
|
@ -160,10 +160,10 @@
|
||||
</objects>
|
||||
<point key="canvasLocation" x="863.768115942029" y="-591.29464285714278"/>
|
||||
</scene>
|
||||
<!--Feed Inspector View Controller-->
|
||||
<!--Web Feed Inspector View Controller-->
|
||||
<scene sceneID="jnI-2I-AcU">
|
||||
<objects>
|
||||
<tableViewController storyboardIdentifier="FeedInspectorViewControllelr" id="lEH-bG-pQW" customClass="FeedInspectorViewController" customModule="NetNewsWire" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<tableViewController storyboardIdentifier="FeedInspectorViewControllelr" id="lEH-bG-pQW" customClass="WebFeedInspectorViewController" customModule="NetNewsWire" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="static" style="insetGrouped" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="18" sectionFooterHeight="18" id="26V-ZC-Q2R">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
|
@ -1,5 +1,5 @@
|
||||
//
|
||||
// FeedInspectorViewController.swift
|
||||
// WebFeedInspectorViewController.swift
|
||||
// NetNewsWire-iOS
|
||||
//
|
||||
// Created by Maurice Parker on 11/6/19.
|
||||
@ -9,11 +9,11 @@
|
||||
import UIKit
|
||||
import Account
|
||||
|
||||
class FeedInspectorViewController: UITableViewController {
|
||||
class WebFeedInspectorViewController: UITableViewController {
|
||||
|
||||
static let preferredContentSizeForFormSheetDisplay = CGSize(width: 460.0, height: 500.0)
|
||||
|
||||
var feed: Feed!
|
||||
var webFeed: WebFeed!
|
||||
@IBOutlet weak var nameTextField: UITextField!
|
||||
@IBOutlet weak var notifyAboutNewArticlesSwitch: UISwitch!
|
||||
@IBOutlet weak var alwaysShowReaderViewSwitch: UISwitch!
|
||||
@ -22,49 +22,49 @@ class FeedInspectorViewController: UITableViewController {
|
||||
|
||||
private var headerView: InspectorIconHeaderView?
|
||||
private var iconImage: IconImage {
|
||||
if let feedIcon = appDelegate.feedIconDownloader.icon(for: feed) {
|
||||
if let feedIcon = appDelegate.webFeedIconDownloader.icon(for: webFeed) {
|
||||
return feedIcon
|
||||
}
|
||||
if let favicon = appDelegate.faviconDownloader.favicon(for: feed) {
|
||||
if let favicon = appDelegate.faviconDownloader.favicon(for: webFeed) {
|
||||
return favicon
|
||||
}
|
||||
return FaviconGenerator.favicon(feed)
|
||||
return FaviconGenerator.favicon(webFeed)
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
tableView.register(InspectorIconHeaderView.self, forHeaderFooterViewReuseIdentifier: "SectionHeader")
|
||||
|
||||
navigationItem.title = feed.nameForDisplay
|
||||
nameTextField.text = feed.nameForDisplay
|
||||
navigationItem.title = webFeed.nameForDisplay
|
||||
nameTextField.text = webFeed.nameForDisplay
|
||||
|
||||
notifyAboutNewArticlesSwitch.setOn(feed.isNotifyAboutNewArticles ?? false, animated: false)
|
||||
alwaysShowReaderViewSwitch.setOn(feed.isArticleExtractorAlwaysOn ?? false, animated: false)
|
||||
notifyAboutNewArticlesSwitch.setOn(webFeed.isNotifyAboutNewArticles ?? false, animated: false)
|
||||
alwaysShowReaderViewSwitch.setOn(webFeed.isArticleExtractorAlwaysOn ?? false, animated: false)
|
||||
|
||||
homePageLabel.text = feed.homePageURL
|
||||
feedURLLabel.text = feed.url
|
||||
homePageLabel.text = webFeed.homePageURL
|
||||
feedURLLabel.text = webFeed.url
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(feedIconDidBecomeAvailable(_:)), name: .FeedIconDidBecomeAvailable, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(webFeedIconDidBecomeAvailable(_:)), name: .WebFeedIconDidBecomeAvailable, object: nil)
|
||||
}
|
||||
|
||||
override func viewDidDisappear(_ animated: Bool) {
|
||||
if nameTextField.text != feed.nameForDisplay {
|
||||
if nameTextField.text != webFeed.nameForDisplay {
|
||||
let nameText = nameTextField.text ?? ""
|
||||
let newName = nameText.isEmpty ? (feed.name ?? NSLocalizedString("Untitled", comment: "Feed name")) : nameText
|
||||
feed.rename(to: newName) { _ in }
|
||||
let newName = nameText.isEmpty ? (webFeed.name ?? NSLocalizedString("Untitled", comment: "Feed name")) : nameText
|
||||
webFeed.rename(to: newName) { _ in }
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Notifications
|
||||
@objc func feedIconDidBecomeAvailable(_ notification: Notification) {
|
||||
@objc func webFeedIconDidBecomeAvailable(_ notification: Notification) {
|
||||
headerView?.iconView.iconImage = iconImage
|
||||
}
|
||||
|
||||
@IBAction func notifyAboutNewArticlesChanged(_ sender: Any) {
|
||||
feed.isNotifyAboutNewArticles = notifyAboutNewArticlesSwitch.isOn
|
||||
webFeed.isNotifyAboutNewArticles = notifyAboutNewArticlesSwitch.isOn
|
||||
}
|
||||
|
||||
@IBAction func alwaysShowReaderViewChanged(_ sender: Any) {
|
||||
feed.isArticleExtractorAlwaysOn = alwaysShowReaderViewSwitch.isOn
|
||||
webFeed.isArticleExtractorAlwaysOn = alwaysShowReaderViewSwitch.isOn
|
||||
}
|
||||
|
||||
@IBAction func done(_ sender: Any) {
|
||||
@ -75,7 +75,7 @@ class FeedInspectorViewController: UITableViewController {
|
||||
|
||||
// MARK: Table View
|
||||
|
||||
extension FeedInspectorViewController {
|
||||
extension WebFeedInspectorViewController {
|
||||
|
||||
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
|
||||
return section == 0 ? 64.0 : super.tableView(tableView, heightForHeaderInSection: section)
|
||||
@ -95,7 +95,7 @@ extension FeedInspectorViewController {
|
||||
|
||||
// MARK: UITextFieldDelegate
|
||||
|
||||
extension FeedInspectorViewController: UITextFieldDelegate {
|
||||
extension WebFeedInspectorViewController: UITextFieldDelegate {
|
||||
|
||||
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
|
||||
textField.resignFirstResponder()
|
@ -1,5 +1,5 @@
|
||||
//
|
||||
// AddFeedIntentHandler.swift
|
||||
// AddWebFeedIntentHandler.swift
|
||||
// NetNewsWire
|
||||
//
|
||||
// Created by Maurice Parker on 10/18/19.
|
||||
@ -9,7 +9,7 @@
|
||||
import Intents
|
||||
import Account
|
||||
|
||||
public class AddFeedIntentHandler: NSObject, AddFeedIntentHandling {
|
||||
public class AddWebFeedIntentHandler: NSObject, AddWebFeedIntentHandling {
|
||||
|
||||
override init() {
|
||||
super.init()
|
||||
@ -18,7 +18,7 @@ public class AddFeedIntentHandler: NSObject, AddFeedIntentHandling {
|
||||
}
|
||||
}
|
||||
|
||||
public func resolveUrl(for intent: AddFeedIntent, with completion: @escaping (AddFeedUrlResolutionResult) -> Void) {
|
||||
public func resolveUrl(for intent: AddWebFeedIntent, with completion: @escaping (AddWebFeedUrlResolutionResult) -> Void) {
|
||||
guard let url = intent.url else {
|
||||
completion(.unsupported(forReason: .required))
|
||||
return
|
||||
@ -26,16 +26,16 @@ public class AddFeedIntentHandler: NSObject, AddFeedIntentHandling {
|
||||
completion(.success(with: url))
|
||||
}
|
||||
|
||||
public func provideAccountNameOptions(for intent: AddFeedIntent, with completion: @escaping ([String]?, Error?) -> Void) {
|
||||
public func provideAccountNameOptions(for intent: AddWebFeedIntent, with completion: @escaping ([String]?, Error?) -> Void) {
|
||||
DispatchQueue.main.async {
|
||||
let accountNames = AccountManager.shared.activeAccounts.compactMap { $0.nameForDisplay }
|
||||
completion(accountNames, nil)
|
||||
}
|
||||
}
|
||||
|
||||
public func resolveAccountName(for intent: AddFeedIntent, with completion: @escaping (AddFeedAccountNameResolutionResult) -> Void) {
|
||||
public func resolveAccountName(for intent: AddWebFeedIntent, with completion: @escaping (AddWebFeedAccountNameResolutionResult) -> Void) {
|
||||
guard let accountName = intent.accountName else {
|
||||
completion(AddFeedAccountNameResolutionResult.notRequired())
|
||||
completion(AddWebFeedAccountNameResolutionResult.notRequired())
|
||||
return
|
||||
}
|
||||
DispatchQueue.main.async {
|
||||
@ -47,7 +47,7 @@ public class AddFeedIntentHandler: NSObject, AddFeedIntentHandling {
|
||||
}
|
||||
}
|
||||
|
||||
public func provideFolderNameOptions(for intent: AddFeedIntent, with completion: @escaping ([String]?, Error?) -> Void) {
|
||||
public func provideFolderNameOptions(for intent: AddWebFeedIntent, with completion: @escaping ([String]?, Error?) -> Void) {
|
||||
DispatchQueue.main.async {
|
||||
guard let accountName = intent.accountName, let account = AccountManager.shared.findActiveAccount(forDisplayName: accountName) else {
|
||||
completion([String](), nil)
|
||||
@ -59,9 +59,9 @@ public class AddFeedIntentHandler: NSObject, AddFeedIntentHandling {
|
||||
}
|
||||
}
|
||||
|
||||
public func resolveFolderName(for intent: AddFeedIntent, with completion: @escaping (AddFeedFolderNameResolutionResult) -> Void) {
|
||||
public func resolveFolderName(for intent: AddWebFeedIntent, with completion: @escaping (AddWebFeedFolderNameResolutionResult) -> Void) {
|
||||
guard let accountName = intent.accountName, let folderName = intent.folderName else {
|
||||
completion(AddFeedFolderNameResolutionResult.notRequired())
|
||||
completion(AddWebFeedFolderNameResolutionResult.notRequired())
|
||||
return
|
||||
}
|
||||
|
||||
@ -79,9 +79,9 @@ public class AddFeedIntentHandler: NSObject, AddFeedIntentHandling {
|
||||
}
|
||||
}
|
||||
|
||||
public func handle(intent: AddFeedIntent, completion: @escaping (AddFeedIntentResponse) -> Void) {
|
||||
public func handle(intent: AddWebFeedIntent, completion: @escaping (AddWebFeedIntentResponse) -> Void) {
|
||||
guard let url = intent.url else {
|
||||
completion(AddFeedIntentResponse(code: .failure, userActivity: nil))
|
||||
completion(AddWebFeedIntentResponse(code: .failure, userActivity: nil))
|
||||
return
|
||||
}
|
||||
|
||||
@ -96,7 +96,7 @@ public class AddFeedIntentHandler: NSObject, AddFeedIntentHandling {
|
||||
}()
|
||||
|
||||
guard let validAccount = account else {
|
||||
completion(AddFeedIntentResponse(code: .failure, userActivity: nil))
|
||||
completion(AddWebFeedIntentResponse(code: .failure, userActivity: nil))
|
||||
return
|
||||
}
|
||||
|
||||
@ -109,22 +109,22 @@ public class AddFeedIntentHandler: NSObject, AddFeedIntentHandling {
|
||||
}()
|
||||
|
||||
guard let validContainer = container else {
|
||||
completion(AddFeedIntentResponse(code: .failure, userActivity: nil))
|
||||
completion(AddWebFeedIntentResponse(code: .failure, userActivity: nil))
|
||||
return
|
||||
}
|
||||
|
||||
validAccount.createFeed(url: url.absoluteString, name: nil, container: validContainer) { result in
|
||||
validAccount.createWebFeed(url: url.absoluteString, name: nil, container: validContainer) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
completion(AddFeedIntentResponse(code: .success, userActivity: nil))
|
||||
completion(AddWebFeedIntentResponse(code: .success, userActivity: nil))
|
||||
case .failure(let error):
|
||||
switch error {
|
||||
case AccountError.createErrorNotFound:
|
||||
completion(AddFeedIntentResponse(code: .feedNotFound, userActivity: nil))
|
||||
completion(AddWebFeedIntentResponse(code: .feedNotFound, userActivity: nil))
|
||||
case AccountError.createErrorAlreadySubscribed:
|
||||
completion(AddFeedIntentResponse(code: .alreadySubscribed, userActivity: nil))
|
||||
completion(AddWebFeedIntentResponse(code: .alreadySubscribed, userActivity: nil))
|
||||
default:
|
||||
completion(AddFeedIntentResponse(code: .failure, userActivity: nil))
|
||||
completion(AddWebFeedIntentResponse(code: .failure, userActivity: nil))
|
||||
}
|
||||
}
|
||||
}
|
@ -9,11 +9,11 @@
|
||||
<key>INIntentDefinitionNamespace</key>
|
||||
<string>U6u7RF</string>
|
||||
<key>INIntentDefinitionSystemVersion</key>
|
||||
<string>19A602</string>
|
||||
<string>19B88</string>
|
||||
<key>INIntentDefinitionToolsBuildVersion</key>
|
||||
<string>11B41</string>
|
||||
<string>11B53</string>
|
||||
<key>INIntentDefinitionToolsVersion</key>
|
||||
<string>11.2</string>
|
||||
<string>11.2.1</string>
|
||||
<key>INIntents</key>
|
||||
<array>
|
||||
<dict>
|
||||
@ -22,7 +22,7 @@
|
||||
<key>INIntentConfigurable</key>
|
||||
<true/>
|
||||
<key>INIntentDescription</key>
|
||||
<string>Add a feed</string>
|
||||
<string>Add a web feed</string>
|
||||
<key>INIntentDescriptionID</key>
|
||||
<string>IuAbef</string>
|
||||
<key>INIntentIneligibleForSuggestions</key>
|
||||
@ -59,7 +59,7 @@
|
||||
</dict>
|
||||
</dict>
|
||||
<key>INIntentName</key>
|
||||
<string>AddFeed</string>
|
||||
<string>AddWebFeed</string>
|
||||
<key>INIntentParameters</key>
|
||||
<array>
|
||||
<dict>
|
||||
@ -303,7 +303,7 @@
|
||||
</array>
|
||||
</dict>
|
||||
<key>INIntentTitle</key>
|
||||
<string>Add Feed</string>
|
||||
<string>Add Web Feed</string>
|
||||
<key>INIntentTitleID</key>
|
||||
<string>oV681v</string>
|
||||
<key>INIntentType</key>
|
||||
|
@ -34,7 +34,7 @@
|
||||
<array/>
|
||||
<key>IntentsSupported</key>
|
||||
<array>
|
||||
<string>AddFeedIntent</string>
|
||||
<string>AddWebFeedIntent</string>
|
||||
</array>
|
||||
</dict>
|
||||
<key>NSExtensionPointIdentifier</key>
|
||||
|
@ -12,8 +12,8 @@ class IntentHandler: INExtension {
|
||||
|
||||
override func handler(for intent: INIntent) -> Any {
|
||||
switch intent {
|
||||
case is AddFeedIntent:
|
||||
return AddFeedIntentHandler()
|
||||
case is AddWebFeedIntent:
|
||||
return AddWebFeedIntentHandler()
|
||||
default:
|
||||
fatalError("Unhandled intent type: \(intent)")
|
||||
}
|
||||
|
@ -117,7 +117,7 @@ private extension KeyboardManager {
|
||||
static func globalAuxilaryKeyCommands() -> [UIKeyCommand] {
|
||||
var keys = [UIKeyCommand]()
|
||||
|
||||
let addNewFeedTitle = NSLocalizedString("New Feed", comment: "New Feed")
|
||||
let addNewFeedTitle = NSLocalizedString("New Web Feed", comment: "New Web Feed")
|
||||
keys.append(KeyboardManager.createKeyCommand(title: addNewFeedTitle, action: "addNewFeed:", input: "n", modifiers: [.command]))
|
||||
|
||||
let addNewFolderTitle = NSLocalizedString("New Folder", comment: "New Folder")
|
||||
|
@ -34,12 +34,12 @@ class MasterFeedDataSource: UITableViewDiffableDataSource<Node, Node> {
|
||||
guard let node = itemIdentifier(for: indexPath) else {
|
||||
return false
|
||||
}
|
||||
return node.representedObject is Feed
|
||||
return node.representedObject is WebFeed
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
|
||||
|
||||
guard let sourceNode = itemIdentifier(for: sourceIndexPath), let feed = sourceNode.representedObject as? Feed else {
|
||||
guard let sourceNode = itemIdentifier(for: sourceIndexPath), let webFeed = sourceNode.representedObject as? WebFeed else {
|
||||
return
|
||||
}
|
||||
|
||||
@ -67,15 +67,15 @@ class MasterFeedDataSource: UITableViewDiffableDataSource<Node, Node> {
|
||||
}
|
||||
}()
|
||||
|
||||
// Move the Feed
|
||||
// Move the Web Feed
|
||||
guard let source = sourceNode.parent?.representedObject as? Container, let destination = destParentNode?.representedObject as? Container else {
|
||||
return
|
||||
}
|
||||
|
||||
if sameAccount(sourceNode, destParentNode!) {
|
||||
moveFeedInAccount(feed: feed, sourceContainer: source, destinationContainer: destination)
|
||||
moveWebFeedInAccount(feed: webFeed, sourceContainer: source, destinationContainer: destination)
|
||||
} else {
|
||||
moveFeedBetweenAccounts(feed: feed, sourceContainer: source, destinationContainer: destination)
|
||||
moveWebFeedBetweenAccounts(feed: webFeed, sourceContainer: source, destinationContainer: destination)
|
||||
}
|
||||
|
||||
}
|
||||
@ -94,8 +94,8 @@ class MasterFeedDataSource: UITableViewDiffableDataSource<Node, Node> {
|
||||
return account
|
||||
} else if let folder = node.representedObject as? Folder {
|
||||
return folder.account
|
||||
} else if let feed = node.representedObject as? Feed {
|
||||
return feed.account
|
||||
} else if let webFeed = node.representedObject as? WebFeed {
|
||||
return webFeed.account
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
@ -106,9 +106,9 @@ class MasterFeedDataSource: UITableViewDiffableDataSource<Node, Node> {
|
||||
return nodeAccount(node)?.accountID
|
||||
}
|
||||
|
||||
func moveFeedInAccount(feed: Feed, sourceContainer: Container, destinationContainer: Container) {
|
||||
func moveWebFeedInAccount(feed: WebFeed, sourceContainer: Container, destinationContainer: Container) {
|
||||
BatchUpdate.shared.start()
|
||||
sourceContainer.account?.moveFeed(feed, from: sourceContainer, to: destinationContainer) { result in
|
||||
sourceContainer.account?.moveWebFeed(feed, from: sourceContainer, to: destinationContainer) { result in
|
||||
BatchUpdate.shared.end()
|
||||
switch result {
|
||||
case .success:
|
||||
@ -119,15 +119,15 @@ class MasterFeedDataSource: UITableViewDiffableDataSource<Node, Node> {
|
||||
}
|
||||
}
|
||||
|
||||
func moveFeedBetweenAccounts(feed: Feed, sourceContainer: Container, destinationContainer: Container) {
|
||||
func moveWebFeedBetweenAccounts(feed: WebFeed, sourceContainer: Container, destinationContainer: Container) {
|
||||
|
||||
if let existingFeed = destinationContainer.account?.existingFeed(withURL: feed.url) {
|
||||
if let existingFeed = destinationContainer.account?.existingWebFeed(withURL: feed.url) {
|
||||
|
||||
BatchUpdate.shared.start()
|
||||
destinationContainer.account?.addFeed(existingFeed, to: destinationContainer) { result in
|
||||
destinationContainer.account?.addWebFeed(existingFeed, to: destinationContainer) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
sourceContainer.account?.removeFeed(feed, from: sourceContainer) { result in
|
||||
sourceContainer.account?.removeWebFeed(feed, from: sourceContainer) { result in
|
||||
BatchUpdate.shared.end()
|
||||
switch result {
|
||||
case .success:
|
||||
@ -145,10 +145,10 @@ class MasterFeedDataSource: UITableViewDiffableDataSource<Node, Node> {
|
||||
} else {
|
||||
|
||||
BatchUpdate.shared.start()
|
||||
destinationContainer.account?.createFeed(url: feed.url, name: feed.editedName, container: destinationContainer) { result in
|
||||
destinationContainer.account?.createWebFeed(url: feed.url, name: feed.editedName, container: destinationContainer) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
sourceContainer.account?.removeFeed(feed, from: sourceContainer) { result in
|
||||
sourceContainer.account?.removeWebFeed(feed, from: sourceContainer) { result in
|
||||
BatchUpdate.shared.end()
|
||||
switch result {
|
||||
case .success:
|
||||
|
@ -53,9 +53,9 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner {
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(unreadCountDidChange(_:)), name: .UnreadCountDidChange, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(faviconDidBecomeAvailable(_:)), name: .FaviconDidBecomeAvailable, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(feedIconDidBecomeAvailable(_:)), name: .FeedIconDidBecomeAvailable, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(feedSettingDidChange(_:)), name: .FeedSettingDidChange, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(feedMetadataDidChange(_:)), name: .FeedMetadataDidChange, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(webFeedIconDidBecomeAvailable(_:)), name: .WebFeedIconDidBecomeAvailable, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(webFeedSettingDidChange(_:)), name: .WebFeedSettingDidChange, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(webFeedMetadataDidChange(_:)), name: .WebFeedMetadataDidChange, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(userDidAddFeed(_:)), name: .UserDidAddFeed, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(contentSizeCategoryDidChange), name: UIContentSizeCategory.didChangeNotification, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(willEnterForeground(_:)), name: UIApplication.willEnterForegroundNotification, object: nil)
|
||||
@ -117,31 +117,31 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner {
|
||||
applyToAvailableCells(configureIcon)
|
||||
}
|
||||
|
||||
@objc func feedIconDidBecomeAvailable(_ note: Notification) {
|
||||
guard let feed = note.userInfo?[UserInfoKey.feed] as? Feed else {
|
||||
@objc func webFeedIconDidBecomeAvailable(_ note: Notification) {
|
||||
guard let webFeed = note.userInfo?[UserInfoKey.webFeed] as? WebFeed else {
|
||||
return
|
||||
}
|
||||
applyToCellsForRepresentedObject(feed, configureIcon(_:_:))
|
||||
applyToCellsForRepresentedObject(webFeed, configureIcon(_:_:))
|
||||
}
|
||||
|
||||
@objc func feedSettingDidChange(_ note: Notification) {
|
||||
guard let feed = note.object as? Feed, let key = note.userInfo?[Feed.FeedSettingUserInfoKey] as? String else {
|
||||
@objc func webFeedSettingDidChange(_ note: Notification) {
|
||||
guard let webFeed = note.object as? WebFeed, let key = note.userInfo?[WebFeed.WebFeedSettingUserInfoKey] as? String else {
|
||||
return
|
||||
}
|
||||
if key == Feed.FeedSettingKey.homePageURL || key == Feed.FeedSettingKey.faviconURL {
|
||||
configureCellsForRepresentedObject(feed)
|
||||
if key == WebFeed.WebFeedSettingKey.homePageURL || key == WebFeed.WebFeedSettingKey.faviconURL {
|
||||
configureCellsForRepresentedObject(webFeed)
|
||||
}
|
||||
}
|
||||
|
||||
@objc func feedMetadataDidChange(_ note: Notification) {
|
||||
@objc func webFeedMetadataDidChange(_ note: Notification) {
|
||||
reloadAllVisibleCells()
|
||||
}
|
||||
|
||||
@objc func userDidAddFeed(_ notification: Notification) {
|
||||
guard let feed = notification.userInfo?[UserInfoKey.feed] as? Feed else {
|
||||
guard let webFeed = notification.userInfo?[UserInfoKey.webFeed] as? WebFeed else {
|
||||
return
|
||||
}
|
||||
discloseFeed(feed, animated: true)
|
||||
discloseFeed(webFeed, animated: true)
|
||||
}
|
||||
|
||||
@objc func contentSizeCategoryDidChange(_ note: Notification) {
|
||||
@ -237,13 +237,13 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner {
|
||||
renameAction.backgroundColor = UIColor.systemOrange
|
||||
actions.append(renameAction)
|
||||
|
||||
if let feed = dataSource.itemIdentifier(for: indexPath)?.representedObject as? Feed {
|
||||
if let webFeed = dataSource.itemIdentifier(for: indexPath)?.representedObject as? WebFeed {
|
||||
let moreTitle = NSLocalizedString("More", comment: "More")
|
||||
let moreAction = UIContextualAction(style: .normal, title: moreTitle) { [weak self] (action, view, completionHandler) in
|
||||
|
||||
if let self = self {
|
||||
|
||||
let alert = UIAlertController(title: feed.nameForDisplay, message: nil, preferredStyle: .actionSheet)
|
||||
let alert = UIAlertController(title: webFeed.nameForDisplay, message: nil, preferredStyle: .actionSheet)
|
||||
if let popoverController = alert.popoverPresentationController {
|
||||
popoverController.sourceView = view
|
||||
popoverController.sourceRect = CGRect(x: view.frame.size.width/2, y: view.frame.size.height/2, width: 1, height: 1)
|
||||
@ -288,7 +288,7 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner {
|
||||
guard let node = dataSource.itemIdentifier(for: indexPath), !(node.representedObject is PseudoFeed) else {
|
||||
return nil
|
||||
}
|
||||
if node.representedObject is Feed {
|
||||
if node.representedObject is WebFeed {
|
||||
return makeFeedContextMenu(indexPath: indexPath, includeDeleteRename: true)
|
||||
} else {
|
||||
return makeFolderContextMenu(indexPath: indexPath)
|
||||
@ -508,7 +508,7 @@ class MasterFeedViewController: UITableViewController, UndoableCommandRunner {
|
||||
}
|
||||
}
|
||||
|
||||
func discloseFeed(_ feed: Feed, animated: Bool, completion: (() -> Void)? = nil) {
|
||||
func discloseFeed(_ feed: WebFeed, animated: Bool, completion: (() -> Void)? = nil) {
|
||||
|
||||
guard let node = coordinator.rootNode.descendantNodeRepresentingObject(feed as AnyObject) else {
|
||||
completion?()
|
||||
@ -674,14 +674,14 @@ private extension MasterFeedViewController {
|
||||
}
|
||||
|
||||
func imageFor(_ node: Node) -> IconImage? {
|
||||
if let feed = node.representedObject as? Feed {
|
||||
if let webFeed = node.representedObject as? WebFeed {
|
||||
|
||||
let feedIconImage = appDelegate.feedIconDownloader.icon(for: feed)
|
||||
let feedIconImage = appDelegate.webFeedIconDownloader.icon(for: webFeed)
|
||||
if feedIconImage != nil {
|
||||
return feedIconImage
|
||||
}
|
||||
|
||||
if let faviconImage = appDelegate.faviconDownloader.favicon(for: feed) {
|
||||
if let faviconImage = appDelegate.faviconDownloader.favicon(for: webFeed) {
|
||||
return faviconImage
|
||||
}
|
||||
|
||||
@ -742,7 +742,7 @@ private extension MasterFeedViewController {
|
||||
if let folder = node.representedObject as? Folder {
|
||||
return folder.account
|
||||
}
|
||||
if let feed = node.representedObject as? Feed {
|
||||
if let feed = node.representedObject as? WebFeed {
|
||||
return feed.account
|
||||
}
|
||||
return nil
|
||||
@ -843,7 +843,7 @@ private extension MasterFeedViewController {
|
||||
|
||||
func copyFeedPageAction(indexPath: IndexPath) -> UIAction? {
|
||||
guard let node = dataSource.itemIdentifier(for: indexPath),
|
||||
let feed = node.representedObject as? Feed,
|
||||
let feed = node.representedObject as? WebFeed,
|
||||
let url = URL(string: feed.url) else {
|
||||
return nil
|
||||
}
|
||||
@ -857,7 +857,7 @@ private extension MasterFeedViewController {
|
||||
|
||||
func copyFeedPageAlertAction(indexPath: IndexPath, completionHandler: @escaping (Bool) -> Void) -> UIAlertAction? {
|
||||
guard let node = dataSource.itemIdentifier(for: indexPath),
|
||||
let feed = node.representedObject as? Feed,
|
||||
let feed = node.representedObject as? WebFeed,
|
||||
let url = URL(string: feed.url) else {
|
||||
return nil
|
||||
}
|
||||
@ -872,7 +872,7 @@ private extension MasterFeedViewController {
|
||||
|
||||
func copyHomePageAction(indexPath: IndexPath) -> UIAction? {
|
||||
guard let node = dataSource.itemIdentifier(for: indexPath),
|
||||
let feed = node.representedObject as? Feed,
|
||||
let feed = node.representedObject as? WebFeed,
|
||||
let homePageURL = feed.homePageURL,
|
||||
let url = URL(string: homePageURL) else {
|
||||
return nil
|
||||
@ -887,7 +887,7 @@ private extension MasterFeedViewController {
|
||||
|
||||
func copyHomePageAlertAction(indexPath: IndexPath, completionHandler: @escaping (Bool) -> Void) -> UIAlertAction? {
|
||||
guard let node = dataSource.itemIdentifier(for: indexPath),
|
||||
let feed = node.representedObject as? Feed,
|
||||
let feed = node.representedObject as? WebFeed,
|
||||
let homePageURL = feed.homePageURL,
|
||||
let url = URL(string: homePageURL) else {
|
||||
return nil
|
||||
@ -919,7 +919,7 @@ private extension MasterFeedViewController {
|
||||
}
|
||||
|
||||
func getInfoAction(indexPath: IndexPath) -> UIAction? {
|
||||
guard let node = dataSource.itemIdentifier(for: indexPath), let feed = node.representedObject as? Feed else {
|
||||
guard let node = dataSource.itemIdentifier(for: indexPath), let feed = node.representedObject as? WebFeed else {
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -947,7 +947,7 @@ private extension MasterFeedViewController {
|
||||
}
|
||||
|
||||
func getInfoAlertAction(indexPath: IndexPath, completionHandler: @escaping (Bool) -> Void) -> UIAlertAction? {
|
||||
guard let node = dataSource.itemIdentifier(for: indexPath), let feed = node.representedObject as? Feed else {
|
||||
guard let node = dataSource.itemIdentifier(for: indexPath), let feed = node.representedObject as? WebFeed else {
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -979,7 +979,7 @@ private extension MasterFeedViewController {
|
||||
return
|
||||
}
|
||||
|
||||
if let feed = node.representedObject as? Feed {
|
||||
if let feed = node.representedObject as? WebFeed {
|
||||
feed.rename(to: name) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
@ -1023,7 +1023,7 @@ private extension MasterFeedViewController {
|
||||
|
||||
if let folder = deleteNode.representedObject as? Folder {
|
||||
ActivityManager.cleanUp(folder)
|
||||
} else if let feed = deleteNode.representedObject as? Feed {
|
||||
} else if let feed = deleteNode.representedObject as? WebFeed {
|
||||
ActivityManager.cleanUp(feed)
|
||||
}
|
||||
|
||||
|
@ -42,7 +42,7 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(unreadCountDidChange(_:)), name: .UnreadCountDidChange, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(statusesDidChange(_:)), name: .StatusesDidChange, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(feedIconDidBecomeAvailable(_:)), name: .FeedIconDidBecomeAvailable, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(webFeedIconDidBecomeAvailable(_:)), name: .WebFeedIconDidBecomeAvailable, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(avatarDidBecomeAvailable(_:)), name: .AvatarDidBecomeAvailable, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(faviconDidBecomeAvailable(_:)), name: .FaviconDidBecomeAvailable, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(userDefaultsDidChange(_:)), name: UserDefaults.didChangeNotification, object: nil)
|
||||
@ -316,16 +316,16 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner
|
||||
}
|
||||
}
|
||||
|
||||
@objc func feedIconDidBecomeAvailable(_ note: Notification) {
|
||||
@objc func webFeedIconDidBecomeAvailable(_ note: Notification) {
|
||||
titleView?.iconView.iconImage = coordinator.timelineIconImage
|
||||
guard let feed = note.userInfo?[UserInfoKey.feed] as? Feed else {
|
||||
guard let feed = note.userInfo?[UserInfoKey.webFeed] as? WebFeed else {
|
||||
return
|
||||
}
|
||||
tableView.indexPathsForVisibleRows?.forEach { indexPath in
|
||||
guard let article = dataSource.itemIdentifier(for: indexPath) else {
|
||||
return
|
||||
}
|
||||
if article.feed == feed, let cell = tableView.cellForRow(at: indexPath) as? MasterTimelineTableViewCell, let image = iconImageFor(article) {
|
||||
if article.webFeed == feed, let cell = tableView.cellForRow(at: indexPath) as? MasterTimelineTableViewCell, let image = iconImageFor(article) {
|
||||
cell.setIconImage(image)
|
||||
}
|
||||
}
|
||||
@ -402,7 +402,7 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner
|
||||
|
||||
let prototypeID = "prototype"
|
||||
let status = ArticleStatus(articleID: prototypeID, read: false, starred: false, userDeleted: false, dateArrived: Date())
|
||||
let prototypeArticle = Article(accountID: prototypeID, articleID: prototypeID, feedID: prototypeID, uniqueID: prototypeID, title: longTitle, contentHTML: nil, contentText: nil, url: nil, externalURL: nil, summary: nil, imageURL: nil, bannerImageURL: nil, datePublished: nil, dateModified: nil, authors: nil, attachments: nil, status: status)
|
||||
let prototypeArticle = Article(accountID: prototypeID, articleID: prototypeID, webFeedID: prototypeID, uniqueID: prototypeID, title: longTitle, contentHTML: nil, contentText: nil, url: nil, externalURL: nil, summary: nil, imageURL: nil, bannerImageURL: nil, datePublished: nil, dateModified: nil, authors: nil, attachments: nil, status: status)
|
||||
|
||||
let prototypeCellData = MasterTimelineCellData(article: prototypeArticle, showFeedName: true, feedName: "Prototype Feed Name", iconImage: nil, showIcon: false, featuredImage: nil, numberOfLines: numberOfTextLines, iconSize: iconSize)
|
||||
|
||||
@ -464,7 +464,7 @@ private extension MasterTimelineViewController {
|
||||
titleView.label.text = coordinator.timelineName
|
||||
updateTitleUnreadCount()
|
||||
|
||||
if coordinator.timelineFetcher is Feed {
|
||||
if coordinator.timelineFetcher is WebFeed {
|
||||
titleView.heightAnchor.constraint(equalToConstant: 44.0).isActive = true
|
||||
let tap = UITapGestureRecognizer(target: self, action:#selector(showFeedInspector(_:)))
|
||||
titleView.addGestureRecognizer(tap)
|
||||
@ -525,7 +525,7 @@ private extension MasterTimelineViewController {
|
||||
|
||||
let showFeedNames = coordinator.showFeedNames
|
||||
let showIcon = coordinator.showIcons && iconImage != nil
|
||||
cell.cellData = MasterTimelineCellData(article: article, showFeedName: showFeedNames, feedName: article.feed?.nameForDisplay, iconImage: iconImage, showIcon: showIcon, featuredImage: featuredImage, numberOfLines: numberOfTextLines, iconSize: iconSize)
|
||||
cell.cellData = MasterTimelineCellData(article: article, showFeedName: showFeedNames, feedName: article.webFeed?.nameForDisplay, iconImage: iconImage, showIcon: showIcon, featuredImage: featuredImage, numberOfLines: numberOfTextLines, iconSize: iconSize)
|
||||
|
||||
}
|
||||
|
||||
@ -590,36 +590,36 @@ private extension MasterTimelineViewController {
|
||||
}
|
||||
|
||||
func discloseFeedAction(_ article: Article) -> UIAction? {
|
||||
guard let feed = article.feed else { return nil }
|
||||
guard let webFeed = article.webFeed else { return nil }
|
||||
|
||||
let title = NSLocalizedString("Go to Feed", comment: "Go to Feed")
|
||||
let action = UIAction(title: title, image: AppAssets.openInSidebarImage) { [weak self] action in
|
||||
self?.coordinator.discloseFeed(feed, animated: true)
|
||||
self?.coordinator.discloseFeed(webFeed, animated: true)
|
||||
}
|
||||
return action
|
||||
}
|
||||
|
||||
func discloseFeedAlertAction(_ article: Article, completionHandler: @escaping (Bool) -> Void) -> UIAlertAction? {
|
||||
guard let feed = article.feed else { return nil }
|
||||
guard let webFeed = article.webFeed else { return nil }
|
||||
|
||||
let title = NSLocalizedString("Go to Feed", comment: "Go to Feed")
|
||||
let action = UIAlertAction(title: title, style: .default) { [weak self] action in
|
||||
self?.coordinator.discloseFeed(feed, animated: true)
|
||||
self?.coordinator.discloseFeed(webFeed, animated: true)
|
||||
completionHandler(true)
|
||||
}
|
||||
return action
|
||||
}
|
||||
|
||||
func markAllInFeedAsReadAction(_ article: Article) -> UIAction? {
|
||||
guard let feed = article.feed else { return nil }
|
||||
guard let webFeed = article.webFeed else { return nil }
|
||||
|
||||
let articles = Array(feed.fetchArticles())
|
||||
let articles = Array(webFeed.fetchArticles())
|
||||
guard articles.canMarkAllAsRead() else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let localizedMenuText = NSLocalizedString("Mark All as Read in “%@”", comment: "Command")
|
||||
let title = NSString.localizedStringWithFormat(localizedMenuText as NSString, feed.nameForDisplay) as String
|
||||
let title = NSString.localizedStringWithFormat(localizedMenuText as NSString, webFeed.nameForDisplay) as String
|
||||
|
||||
let action = UIAction(title: title, image: AppAssets.markAllInFeedAsReadImage) { [weak self] action in
|
||||
self?.coordinator.markAllAsRead(articles)
|
||||
@ -628,15 +628,15 @@ private extension MasterTimelineViewController {
|
||||
}
|
||||
|
||||
func markAllInFeedAsReadAlertAction(_ article: Article, completionHandler: @escaping (Bool) -> Void) -> UIAlertAction? {
|
||||
guard let feed = article.feed else { return nil }
|
||||
guard let webFeed = article.webFeed else { return nil }
|
||||
|
||||
let articles = Array(feed.fetchArticles())
|
||||
let articles = Array(webFeed.fetchArticles())
|
||||
guard articles.canMarkAllAsRead() else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let localizedMenuText = NSLocalizedString("Mark All as Read in “%@”", comment: "Mark All as Read in Feed")
|
||||
let title = NSString.localizedStringWithFormat(localizedMenuText as NSString, feed.nameForDisplay) as String
|
||||
let title = NSString.localizedStringWithFormat(localizedMenuText as NSString, webFeed.nameForDisplay) as String
|
||||
|
||||
let action = UIAlertAction(title: title, style: .default) { [weak self] action in
|
||||
self?.coordinator.markAllAsRead(articles)
|
||||
|
@ -60,7 +60,7 @@
|
||||
<string>Grant permission to save images from the article.</string>
|
||||
<key>NSUserActivityTypes</key>
|
||||
<array>
|
||||
<string>AddFeedIntent</string>
|
||||
<string>AddWebFeedIntent</string>
|
||||
<string>NextUnread</string>
|
||||
<string>ReadArticle</string>
|
||||
<string>SelectFeed</string>
|
||||
|
@ -88,7 +88,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
|
||||
set { AppDefaults.displayUndoAvailableTip = newValue }
|
||||
}
|
||||
|
||||
private let treeControllerDelegate = FeedTreeControllerDelegate()
|
||||
private let treeControllerDelegate = WebFeedTreeControllerDelegate()
|
||||
private let treeController: TreeController
|
||||
|
||||
var stateRestorationActivity: NSUserActivity? {
|
||||
@ -110,9 +110,9 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
|
||||
private(set) var currentFeedIndexPath: IndexPath?
|
||||
|
||||
var timelineIconImage: IconImage? {
|
||||
if let feed = timelineFetcher as? Feed {
|
||||
if let feed = timelineFetcher as? WebFeed {
|
||||
|
||||
let feedIconImage = appDelegate.feedIconDownloader.icon(for: feed)
|
||||
let feedIconImage = appDelegate.webFeedIconDownloader.icon(for: feed)
|
||||
if feedIconImage != nil {
|
||||
return feedIconImage
|
||||
}
|
||||
@ -135,7 +135,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
|
||||
|
||||
timelineMiddleIndexPath = nil
|
||||
|
||||
if timelineFetcher is Feed {
|
||||
if timelineFetcher is WebFeed {
|
||||
showFeedNames = false
|
||||
} else {
|
||||
showFeedNames = true
|
||||
@ -442,7 +442,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
|
||||
}
|
||||
|
||||
@objc func accountDidDownloadArticles(_ note: Notification) {
|
||||
guard let feeds = note.userInfo?[Account.UserInfoKey.feeds] as? Set<Feed> else {
|
||||
guard let feeds = note.userInfo?[Account.UserInfoKey.webFeeds] as? Set<WebFeed> else {
|
||||
return
|
||||
}
|
||||
|
||||
@ -607,7 +607,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
|
||||
|
||||
masterTimelineViewController?.updateArticleSelection(animated: animated)
|
||||
|
||||
if article!.feed?.isArticleExtractorAlwaysOn ?? false {
|
||||
if article!.webFeed?.isArticleExtractorAlwaysOn ?? false {
|
||||
startArticleExtractorForCurrentLink()
|
||||
currentArticleViewController.state = .loading
|
||||
} else {
|
||||
@ -780,7 +780,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
|
||||
markArticlesWithUndo([article], statusKey: .starred, flag: !article.status.starred)
|
||||
}
|
||||
|
||||
func discloseFeed(_ feed: Feed, animated: Bool, completion: (() -> Void)? = nil) {
|
||||
func discloseFeed(_ feed: WebFeed, animated: Bool, completion: (() -> Void)? = nil) {
|
||||
masterFeedViewController.discloseFeed(feed, animated: animated) {
|
||||
completion?()
|
||||
}
|
||||
@ -806,19 +806,19 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
|
||||
}
|
||||
|
||||
func showFeedInspector() {
|
||||
guard let feed = timelineFetcher as? Feed else {
|
||||
guard let feed = timelineFetcher as? WebFeed else {
|
||||
return
|
||||
}
|
||||
showFeedInspector(for: feed)
|
||||
}
|
||||
|
||||
func showFeedInspector(for feed: Feed) {
|
||||
func showFeedInspector(for feed: WebFeed) {
|
||||
let feedInspectorNavController =
|
||||
UIStoryboard.inspector.instantiateViewController(identifier: "FeedInspectorNavigationViewController") as! UINavigationController
|
||||
let feedInspectorController = feedInspectorNavController.topViewController as! FeedInspectorViewController
|
||||
let feedInspectorController = feedInspectorNavController.topViewController as! WebFeedInspectorViewController
|
||||
feedInspectorNavController.modalPresentationStyle = .formSheet
|
||||
feedInspectorNavController.preferredContentSize = FeedInspectorViewController.preferredContentSizeForFormSheetDisplay
|
||||
feedInspectorController.feed = feed
|
||||
feedInspectorNavController.preferredContentSize = WebFeedInspectorViewController.preferredContentSizeForFormSheetDisplay
|
||||
feedInspectorController.webFeed = feed
|
||||
rootSplitViewController.present(feedInspectorNavController, animated: true)
|
||||
}
|
||||
|
||||
@ -878,7 +878,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
|
||||
|
||||
func homePageURLForFeed(_ indexPath: IndexPath) -> URL? {
|
||||
guard let node = nodeFor(indexPath),
|
||||
let feed = node.representedObject as? Feed,
|
||||
let feed = node.representedObject as? WebFeed,
|
||||
let homePageURL = feed.homePageURL,
|
||||
let url = URL(string: homePageURL) else {
|
||||
return nil
|
||||
@ -1460,19 +1460,19 @@ private extension SceneCoordinator {
|
||||
return false
|
||||
}
|
||||
|
||||
func timelineFetcherContainsAnyFeed(_ feeds: Set<Feed>) -> Bool {
|
||||
func timelineFetcherContainsAnyFeed(_ feeds: Set<WebFeed>) -> Bool {
|
||||
|
||||
// Return true if there’s a match or if a folder contains (recursively) one of feeds
|
||||
|
||||
if let feed = timelineFetcher as? Feed {
|
||||
if let feed = timelineFetcher as? WebFeed {
|
||||
for oneFeed in feeds {
|
||||
if feed.feedID == oneFeed.feedID || feed.url == oneFeed.url {
|
||||
if feed.webFeedID == oneFeed.webFeedID || feed.url == oneFeed.url {
|
||||
return true
|
||||
}
|
||||
}
|
||||
} else if let folder = timelineFetcher as? Folder {
|
||||
for oneFeed in feeds {
|
||||
if folder.hasFeed(with: oneFeed.feedID) || folder.hasFeed(withURL: oneFeed.url) {
|
||||
if folder.hasWebFeed(with: oneFeed.webFeedID) || folder.hasWebFeed(withURL: oneFeed.url) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@ -1648,11 +1648,11 @@ private extension SceneCoordinator {
|
||||
selectFeed(indexPath, animated: false)
|
||||
}
|
||||
|
||||
case .feed(let accountID, let feedID):
|
||||
guard let accountNode = findAccountNode(accountID: accountID), let feedNode = findFeedNode(feedID: feedID, beginningAt: accountNode) else {
|
||||
case .webFeed(let accountID, let webFeedID):
|
||||
guard let accountNode = findAccountNode(accountID: accountID), let feedNode = findWebFeedNode(webFeedID: webFeedID, beginningAt: accountNode) else {
|
||||
return
|
||||
}
|
||||
if let feed = feedNode.representedObject as? Feed {
|
||||
if let feed = feedNode.representedObject as? WebFeed {
|
||||
discloseFeed(feed, animated: false)
|
||||
}
|
||||
|
||||
@ -1664,16 +1664,16 @@ private extension SceneCoordinator {
|
||||
let articlePathUserInfo = userInfo[UserInfoKey.articlePath] as? [AnyHashable : Any],
|
||||
let accountID = articlePathUserInfo[ArticlePathKey.accountID] as? String,
|
||||
let accountName = articlePathUserInfo[ArticlePathKey.accountName] as? String,
|
||||
let feedID = articlePathUserInfo[ArticlePathKey.feedID] as? String,
|
||||
let webFeedID = articlePathUserInfo[ArticlePathKey.webFeedID] as? String,
|
||||
let articleID = articlePathUserInfo[ArticlePathKey.articleID] as? String else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let accountNode = findAccountNode(accountID: accountID, accountName: accountName), let feedNode = findFeedNode(feedID: feedID, beginningAt: accountNode) else {
|
||||
guard let accountNode = findAccountNode(accountID: accountID, accountName: accountName), let feedNode = findWebFeedNode(webFeedID: webFeedID, beginningAt: accountNode) else {
|
||||
return
|
||||
}
|
||||
|
||||
discloseFeed(feedNode.representedObject as! Feed, animated: false) {
|
||||
discloseFeed(feedNode.representedObject as! WebFeed, animated: false) {
|
||||
if let article = self.articles.first(where: { $0.articleID == articleID }) {
|
||||
self.selectArticle(article)
|
||||
}
|
||||
@ -1699,8 +1699,8 @@ private extension SceneCoordinator {
|
||||
return nil
|
||||
}
|
||||
|
||||
func findFeedNode(feedID: String, beginningAt startingNode: Node) -> Node? {
|
||||
if let node = startingNode.descendantNode(where: { ($0.representedObject as? Feed)?.feedID == feedID }) {
|
||||
func findWebFeedNode(webFeedID: String, beginningAt startingNode: Node) -> Node? {
|
||||
if let node = startingNode.descendantNode(where: { ($0.representedObject as? WebFeed)?.webFeedID == webFeedID }) {
|
||||
return node
|
||||
}
|
||||
return nil
|
||||
|
@ -67,7 +67,7 @@ private extension TimelinePreviewTableViewController {
|
||||
|
||||
let prototypeID = "prototype"
|
||||
let status = ArticleStatus(articleID: prototypeID, read: false, starred: false, userDeleted: false, dateArrived: Date())
|
||||
let prototypeArticle = Article(accountID: prototypeID, articleID: prototypeID, feedID: prototypeID, uniqueID: prototypeID, title: longTitle, contentHTML: nil, contentText: nil, url: nil, externalURL: nil, summary: nil, imageURL: nil, bannerImageURL: nil, datePublished: nil, dateModified: nil, authors: nil, attachments: nil, status: status)
|
||||
let prototypeArticle = Article(accountID: prototypeID, articleID: prototypeID, webFeedID: prototypeID, uniqueID: prototypeID, title: longTitle, contentHTML: nil, contentText: nil, url: nil, externalURL: nil, summary: nil, imageURL: nil, bannerImageURL: nil, datePublished: nil, dateModified: nil, authors: nil, attachments: nil, status: status)
|
||||
|
||||
let iconImage = IconImage(AppAssets.faviconTemplateImage.withTintColor(AppAssets.secondaryAccentColor))
|
||||
|
||||
|
@ -108,14 +108,14 @@ class ShareViewController: SLComposeServiceViewController, ShareFolderPickerCont
|
||||
account = containerAccount
|
||||
}
|
||||
|
||||
if let urlString = url?.absoluteString, account!.hasFeed(withURL: urlString) {
|
||||
if let urlString = url?.absoluteString, account!.hasWebFeed(withURL: urlString) {
|
||||
presentError(AccountError.createErrorAlreadySubscribed)
|
||||
return
|
||||
}
|
||||
|
||||
let feedName = contentText.isEmpty ? nil : contentText
|
||||
|
||||
account!.createFeed(url: url!.absoluteString, name: feedName, container: container!) { result in
|
||||
account!.createWebFeed(url: url!.absoluteString, name: feedName, container: container!) { result in
|
||||
|
||||
switch result {
|
||||
case .success:
|
||||
|
Loading…
x
Reference in New Issue
Block a user