mirror of
https://github.com/Ranchero-Software/NetNewsWire
synced 2025-08-12 06:26:36 +00:00
Rename WebFeed and webFeed to Feed and feed.
This commit is contained in:
@@ -56,7 +56,7 @@ public enum FetchType {
|
||||
case unread(_: Int? = nil)
|
||||
case today(_: Int? = nil)
|
||||
case folder(Folder, Bool)
|
||||
case webFeed(Feed)
|
||||
case feed(Feed)
|
||||
case articleIDs(Set<String>)
|
||||
case search(String)
|
||||
case searchWithArticleIDs(String, Set<String>)
|
||||
@@ -73,7 +73,7 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
public static let articleIDs = "articleIDs" // StatusesDidChange
|
||||
public static let statusKey = "statusKey" // StatusesDidChange
|
||||
public static let statusFlag = "statusFlag" // StatusesDidChange
|
||||
public static let webFeeds = "webFeeds" // AccountDidDownloadArticles, StatusesDidChange
|
||||
public static let feeds = "feeds" // AccountDidDownloadArticles, StatusesDidChange
|
||||
public static let syncErrors = "syncErrors" // AccountsDidFailToSyncWithErrors
|
||||
}
|
||||
|
||||
@@ -142,7 +142,7 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
}
|
||||
}
|
||||
|
||||
public var topLevelWebFeeds = Set<Feed>()
|
||||
public var topLevelFeeds = Set<Feed>()
|
||||
public var folders: Set<Folder>? = Set<Folder>()
|
||||
|
||||
public var externalID: String? {
|
||||
@@ -161,24 +161,24 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
return nil
|
||||
}
|
||||
|
||||
private var webFeedDictionariesNeedUpdate = true
|
||||
private var _idToWebFeedDictionary = [String: Feed]()
|
||||
var idToWebFeedDictionary: [String: Feed] {
|
||||
if webFeedDictionariesNeedUpdate {
|
||||
rebuildWebFeedDictionaries()
|
||||
private var feedDictionariesNeedUpdate = true
|
||||
private var _idToFeedDictionary = [String: Feed]()
|
||||
var idToFeedDictionary: [String: Feed] {
|
||||
if feedDictionariesNeedUpdate {
|
||||
rebuildFeedDictionaries()
|
||||
}
|
||||
return _idToWebFeedDictionary
|
||||
return _idToFeedDictionary
|
||||
}
|
||||
private var _externalIDToWebFeedDictionary = [String: Feed]()
|
||||
var externalIDToWebFeedDictionary: [String: Feed] {
|
||||
if webFeedDictionariesNeedUpdate {
|
||||
rebuildWebFeedDictionaries()
|
||||
private var _externalIDToFeedDictionary = [String: Feed]()
|
||||
var externalIDToFeedDictionary: [String: Feed] {
|
||||
if feedDictionariesNeedUpdate {
|
||||
rebuildFeedDictionaries()
|
||||
}
|
||||
return _externalIDToWebFeedDictionary
|
||||
return _externalIDToFeedDictionary
|
||||
}
|
||||
|
||||
var flattenedWebFeedURLs: Set<String> {
|
||||
return Set(flattenedWebFeeds().map({ $0.url }))
|
||||
var flattenedFeedURLs: Set<String> {
|
||||
return Set(flattenedFeeds().map({ $0.url }))
|
||||
}
|
||||
|
||||
var username: String? {
|
||||
@@ -213,8 +213,8 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
|
||||
private var unreadCounts = [String: Int]() // [feedID: Int]
|
||||
|
||||
private var _flattenedWebFeeds = Set<Feed>()
|
||||
private var flattenedWebFeedsNeedUpdate = true
|
||||
private var _flattenedFeeds = Set<Feed>()
|
||||
private var flattenedFeedsNeedUpdate = 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)
|
||||
@@ -224,9 +224,9 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
}
|
||||
}
|
||||
|
||||
private lazy var webFeedMetadataFile = WebFeedMetadataFile(filename: (dataFolder as NSString).appendingPathComponent("FeedMetadata.plist"), account: self)
|
||||
typealias WebFeedMetadataDictionary = [String: WebFeedMetadata]
|
||||
var webFeedMetadata = WebFeedMetadataDictionary()
|
||||
private lazy var feedMetadataFile = FeedMetadataFile(filename: (dataFolder as NSString).appendingPathComponent("FeedMetadata.plist"), account: self)
|
||||
typealias FeedMetadataDictionary = [String: FeedMetadata]
|
||||
var feedMetadata = FeedMetadataDictionary()
|
||||
|
||||
public var unreadCount = 0 {
|
||||
didSet {
|
||||
@@ -318,7 +318,7 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(childrenDidChange(_:)), name: .ChildrenDidChange, object: nil)
|
||||
|
||||
metadataFile.load()
|
||||
webFeedMetadataFile.load()
|
||||
feedMetadataFile.load()
|
||||
opmlFile.load()
|
||||
|
||||
var shouldHandleRetentionPolicyChange = false
|
||||
@@ -334,7 +334,7 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
self.metadata.performedApril2020RetentionPolicyChange = true
|
||||
}
|
||||
|
||||
self.database.cleanupDatabaseAtStartup(subscribedToWebFeedIDs: self.flattenedWebFeeds().webFeedIDs())
|
||||
self.database.cleanupDatabaseAtStartup(subscribedToFeedIDs: self.flattenedFeeds().feedIDs())
|
||||
self.fetchAllUnreadCounts()
|
||||
}
|
||||
|
||||
@@ -488,7 +488,7 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
|
||||
public func save() {
|
||||
metadataFile.save()
|
||||
webFeedMetadataFile.save()
|
||||
feedMetadataFile.save()
|
||||
opmlFile.save()
|
||||
}
|
||||
|
||||
@@ -499,13 +499,13 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
func addOPMLItems(_ items: [RSOPMLItem]) {
|
||||
for item in items {
|
||||
if let feedSpecifier = item.feedSpecifier {
|
||||
addWebFeed(newWebFeed(with: feedSpecifier))
|
||||
addFeed(newFeed(with: feedSpecifier))
|
||||
} else {
|
||||
if let title = item.titleFromAttributes, let folder = ensureFolder(with: title) {
|
||||
folder.externalID = item.attributes?["nnw_externalID"] as? String
|
||||
item.children?.forEach { itemChild in
|
||||
if let feedSpecifier = itemChild.feedSpecifier {
|
||||
folder.addWebFeed(newWebFeed(with: feedSpecifier))
|
||||
folder.addFeed(newFeed(with: feedSpecifier))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -528,13 +528,13 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
return existingFolder(withExternalID: externalID)
|
||||
}
|
||||
|
||||
func existingContainers(withWebFeed webFeed: Feed) -> [Container] {
|
||||
func existingContainers(withFeed feed: Feed) -> [Container] {
|
||||
var containers = [Container]()
|
||||
if topLevelWebFeeds.contains(webFeed) {
|
||||
if topLevelFeeds.contains(feed) {
|
||||
containers.append(self)
|
||||
}
|
||||
folders?.forEach { folder in
|
||||
if folder.topLevelWebFeeds.contains(webFeed) {
|
||||
if folder.topLevelFeeds.contains(feed) {
|
||||
containers.append(folder)
|
||||
}
|
||||
}
|
||||
@@ -579,9 +579,9 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
return folders?.first(where: { $0.externalID == externalID })
|
||||
}
|
||||
|
||||
func newWebFeed(with opmlFeedSpecifier: RSOPMLFeedSpecifier) -> Feed {
|
||||
func newFeed(with opmlFeedSpecifier: RSOPMLFeedSpecifier) -> Feed {
|
||||
let feedURL = opmlFeedSpecifier.feedURL
|
||||
let metadata = webFeedMetadata(feedURL: feedURL, webFeedID: feedURL)
|
||||
let metadata = feedMetadata(feedURL: feedURL, feedID: feedURL)
|
||||
let feed = Feed(account: self, url: opmlFeedSpecifier.feedURL, metadata: metadata)
|
||||
if let feedTitle = opmlFeedSpecifier.title {
|
||||
if feed.name == nil {
|
||||
@@ -591,36 +591,36 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
return feed
|
||||
}
|
||||
|
||||
public func addWebFeed(_ feed: Feed, to container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
delegate.addWebFeed(for: self, with: feed, to: container, completion: completion)
|
||||
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 createWebFeed(url: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result<Feed, Error>) -> Void) {
|
||||
delegate.createWebFeed(for: self, url: url, name: name, container: container, validateFeed: validateFeed, completion: completion)
|
||||
public func createFeed(url: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result<Feed, Error>) -> Void) {
|
||||
delegate.createFeed(for: self, url: url, name: name, container: container, validateFeed: validateFeed, completion: completion)
|
||||
}
|
||||
|
||||
func createWebFeed(with name: String?, url: String, webFeedID: String, homePageURL: String?) -> Feed {
|
||||
let metadata = webFeedMetadata(feedURL: url, webFeedID: webFeedID)
|
||||
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)
|
||||
feed.name = name
|
||||
feed.homePageURL = homePageURL
|
||||
return feed
|
||||
}
|
||||
|
||||
public func removeWebFeed(_ feed: Feed, from container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
delegate.removeWebFeed(for: self, with: feed, from: container, completion: completion)
|
||||
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 moveWebFeed(_ feed: Feed, from: Container, to: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
delegate.moveWebFeed(for: self, with: feed, from: from, to: to, 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 renameWebFeed(_ feed: Feed, to name: String, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
delegate.renameWebFeed(for: self, with: feed, to: name, 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 restoreWebFeed(_ feed: Feed, container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
delegate.restoreWebFeed(for: self, feed: feed, container: container, 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 addFolder(_ name: String, completion: @escaping (Result<Folder, Error>) -> Void) {
|
||||
@@ -639,8 +639,8 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
delegate.restoreFolder(for: self, folder: folder, completion: completion)
|
||||
}
|
||||
|
||||
func clearWebFeedMetadata(_ feed: Feed) {
|
||||
webFeedMetadata[feed.url] = nil
|
||||
func clearFeedMetadata(_ feed: Feed) {
|
||||
feedMetadata[feed.url] = nil
|
||||
}
|
||||
|
||||
func addFolder(_ folder: Folder) {
|
||||
@@ -649,8 +649,8 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
structureDidChange()
|
||||
}
|
||||
|
||||
public func updateUnreadCounts(for webFeeds: Set<Feed>, completion: VoidCompletionBlock? = nil) {
|
||||
fetchUnreadCounts(for: webFeeds, completion: completion)
|
||||
public func updateUnreadCounts(for feeds: Set<Feed>, completion: VoidCompletionBlock? = nil) {
|
||||
fetchUnreadCounts(for: feeds, completion: completion)
|
||||
}
|
||||
|
||||
public func fetchArticles(_ fetchType: FetchType) throws -> Set<Article> {
|
||||
@@ -667,8 +667,8 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
} else {
|
||||
return try fetchArticles(folder: folder)
|
||||
}
|
||||
case .webFeed(let webFeed):
|
||||
return try fetchArticles(webFeed: webFeed)
|
||||
case .feed(let feed):
|
||||
return try fetchArticles(feed: feed)
|
||||
case .articleIDs(let articleIDs):
|
||||
return try fetchArticles(articleIDs: articleIDs)
|
||||
case .search(let searchString):
|
||||
@@ -692,8 +692,8 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
} else {
|
||||
return fetchArticlesAsync(folder: folder, completion)
|
||||
}
|
||||
case .webFeed(let webFeed):
|
||||
fetchArticlesAsync(webFeed: webFeed, completion)
|
||||
case .feed(let feed):
|
||||
fetchArticlesAsync(feed: feed, completion)
|
||||
case .articleIDs(let articleIDs):
|
||||
fetchArticlesAsync(articleIDs: articleIDs, completion)
|
||||
case .search(let searchString):
|
||||
@@ -704,11 +704,11 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
}
|
||||
|
||||
public func fetchUnreadCountForToday(_ completion: @escaping SingleUnreadCountCompletionBlock) {
|
||||
database.fetchUnreadCountForToday(for: flattenedWebFeeds().webFeedIDs(), completion: completion)
|
||||
database.fetchUnreadCountForToday(for: flattenedFeeds().feedIDs(), completion: completion)
|
||||
}
|
||||
|
||||
public func fetchUnreadCountForStarredArticles(_ completion: @escaping SingleUnreadCountCompletionBlock) {
|
||||
database.fetchStarredAndUnreadCount(for: flattenedWebFeeds().webFeedIDs(), completion: completion)
|
||||
database.fetchStarredAndUnreadCount(for: flattenedFeeds().feedIDs(), completion: completion)
|
||||
}
|
||||
|
||||
public func fetchUnreadArticleIDs(_ completion: @escaping ArticleIDsCompletionBlock) {
|
||||
@@ -724,43 +724,43 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
database.fetchArticleIDsForStatusesWithoutArticlesNewerThanCutoffDate(completion)
|
||||
}
|
||||
|
||||
public func unreadCount(for webFeed: Feed) -> Int {
|
||||
return unreadCounts[webFeed.webFeedID] ?? 0
|
||||
public func unreadCount(for feed: Feed) -> Int {
|
||||
return unreadCounts[feed.feedID] ?? 0
|
||||
}
|
||||
|
||||
public func setUnreadCount(_ unreadCount: Int, for webFeed: Feed) {
|
||||
unreadCounts[webFeed.webFeedID] = unreadCount
|
||||
public func setUnreadCount(_ unreadCount: Int, for feed: Feed) {
|
||||
unreadCounts[feed.feedID] = unreadCount
|
||||
}
|
||||
|
||||
public func structureDidChange() {
|
||||
// Feeds were added or deleted. Or folders added or deleted.
|
||||
// Or feeds inside folders were added or deleted.
|
||||
opmlFile.markAsDirty()
|
||||
flattenedWebFeedsNeedUpdate = true
|
||||
webFeedDictionariesNeedUpdate = true
|
||||
flattenedFeedsNeedUpdate = true
|
||||
feedDictionariesNeedUpdate = true
|
||||
}
|
||||
|
||||
func update(_ webFeed: Feed, with parsedFeed: ParsedFeed, _ completion: @escaping UpdateArticlesCompletionBlock) {
|
||||
func update(_ feed: Feed, with parsedFeed: ParsedFeed, _ completion: @escaping UpdateArticlesCompletionBlock) {
|
||||
// Used only by an On My Mac or iCloud account.
|
||||
precondition(Thread.isMainThread)
|
||||
precondition(type == .onMyMac || type == .cloudKit)
|
||||
|
||||
webFeed.takeSettings(from: parsedFeed)
|
||||
feed.takeSettings(from: parsedFeed)
|
||||
let parsedItems = parsedFeed.items
|
||||
guard !parsedItems.isEmpty else {
|
||||
completion(.success(ArticleChanges()))
|
||||
return
|
||||
}
|
||||
|
||||
update(webFeed.webFeedID, with: parsedItems, completion: completion)
|
||||
update(feed.feedID, with: parsedItems, completion: completion)
|
||||
}
|
||||
|
||||
func update(_ webFeedID: String, with parsedItems: Set<ParsedItem>, deleteOlder: Bool = true, completion: @escaping UpdateArticlesCompletionBlock) {
|
||||
func update(_ feedID: String, with parsedItems: Set<ParsedItem>, deleteOlder: Bool = true, completion: @escaping UpdateArticlesCompletionBlock) {
|
||||
// Used only by an On My Mac or iCloud account.
|
||||
precondition(Thread.isMainThread)
|
||||
precondition(type == .onMyMac || type == .cloudKit)
|
||||
|
||||
database.update(with: parsedItems, webFeedID: webFeedID, deleteOlder: deleteOlder) { updateArticlesResult in
|
||||
database.update(with: parsedItems, feedID: feedID, deleteOlder: deleteOlder) { updateArticlesResult in
|
||||
switch updateArticlesResult {
|
||||
case .success(let articleChanges):
|
||||
self.sendNotificationAbout(articleChanges)
|
||||
@@ -771,16 +771,16 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
}
|
||||
}
|
||||
|
||||
func update(webFeedIDsAndItems: [String: Set<ParsedItem>], defaultRead: Bool, completion: @escaping DatabaseCompletionBlock) {
|
||||
func update(feedIDsAndItems: [String: Set<ParsedItem>], defaultRead: Bool, completion: @escaping DatabaseCompletionBlock) {
|
||||
// Used only by syncing systems.
|
||||
precondition(Thread.isMainThread)
|
||||
precondition(type != .onMyMac && type != .cloudKit)
|
||||
guard !webFeedIDsAndItems.isEmpty else {
|
||||
guard !feedIDsAndItems.isEmpty else {
|
||||
completion(nil)
|
||||
return
|
||||
}
|
||||
|
||||
database.update(webFeedIDsAndItems: webFeedIDsAndItems, defaultRead: defaultRead) { updateArticlesResult in
|
||||
database.update(feedIDsAndItems: feedIDsAndItems, defaultRead: defaultRead) { updateArticlesResult in
|
||||
switch updateArticlesResult {
|
||||
case .success(let newAndUpdatedArticles):
|
||||
self.sendNotificationAbout(newAndUpdatedArticles)
|
||||
@@ -888,38 +888,38 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
|
||||
// MARK: - Container
|
||||
|
||||
public func flattenedWebFeeds() -> Set<Feed> {
|
||||
public func flattenedFeeds() -> Set<Feed> {
|
||||
assert(Thread.isMainThread)
|
||||
if flattenedWebFeedsNeedUpdate {
|
||||
updateFlattenedWebFeeds()
|
||||
if flattenedFeedsNeedUpdate {
|
||||
updateFlattenedFeeds()
|
||||
}
|
||||
return _flattenedWebFeeds
|
||||
return _flattenedFeeds
|
||||
}
|
||||
|
||||
public func removeWebFeed(_ webFeed: Feed) {
|
||||
topLevelWebFeeds.remove(webFeed)
|
||||
public func removeFeed(_ feed: Feed) {
|
||||
topLevelFeeds.remove(feed)
|
||||
structureDidChange()
|
||||
postChildrenDidChangeNotification()
|
||||
}
|
||||
|
||||
public func removeFeeds(_ webFeeds: Set<Feed>) {
|
||||
guard !webFeeds.isEmpty else {
|
||||
public func removeFeeds(_ feeds: Set<Feed>) {
|
||||
guard !feeds.isEmpty else {
|
||||
return
|
||||
}
|
||||
topLevelWebFeeds.subtract(webFeeds)
|
||||
topLevelFeeds.subtract(feeds)
|
||||
structureDidChange()
|
||||
postChildrenDidChangeNotification()
|
||||
}
|
||||
|
||||
public func addWebFeed(_ webFeed: Feed) {
|
||||
topLevelWebFeeds.insert(webFeed)
|
||||
public func addFeed(_ feed: Feed) {
|
||||
topLevelFeeds.insert(feed)
|
||||
structureDidChange()
|
||||
postChildrenDidChangeNotification()
|
||||
}
|
||||
|
||||
func addFeedIfNotInAnyFolder(_ webFeed: Feed) {
|
||||
if !flattenedWebFeeds().contains(webFeed) {
|
||||
addWebFeed(webFeed)
|
||||
func addFeedIfNotInAnyFolder(_ feed: Feed) {
|
||||
if !flattenedFeeds().contains(feed) {
|
||||
addFeed(feed)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -933,7 +933,7 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
|
||||
public func debugDropConditionalGetInfo() {
|
||||
#if DEBUG
|
||||
flattenedWebFeeds().forEach{ $0.dropConditionalGetInfo() }
|
||||
flattenedFeeds().forEach{ $0.dropConditionalGetInfo() }
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -965,8 +965,8 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
}
|
||||
|
||||
@objc func batchUpdateDidPerform(_ note: Notification) {
|
||||
flattenedWebFeedsNeedUpdate = true
|
||||
rebuildWebFeedDictionaries()
|
||||
flattenedFeedsNeedUpdate = true
|
||||
rebuildFeedDictionaries()
|
||||
updateUnreadCount()
|
||||
}
|
||||
|
||||
@@ -1012,11 +1012,11 @@ extension Account: AccountMetadataDelegate {
|
||||
|
||||
// MARK: - FeedMetadataDelegate
|
||||
|
||||
extension Account: WebFeedMetadataDelegate {
|
||||
extension Account: FeedMetadataDelegate {
|
||||
|
||||
func valueDidChange(_ feedMetadata: WebFeedMetadata, key: WebFeedMetadata.CodingKeys) {
|
||||
webFeedMetadataFile.markAsDirty()
|
||||
guard let feed = existingWebFeed(withWebFeedID: feedMetadata.webFeedID) else {
|
||||
func valueDidChange(_ feedMetadata: FeedMetadata, key: FeedMetadata.CodingKeys) {
|
||||
feedMetadataFile.markAsDirty()
|
||||
guard let feed = existingFeed(withFeedID: feedMetadata.feedID) else {
|
||||
return
|
||||
}
|
||||
feed.postFeedSettingDidChangeNotification(key)
|
||||
@@ -1028,11 +1028,11 @@ extension Account: WebFeedMetadataDelegate {
|
||||
private extension Account {
|
||||
|
||||
func fetchStarredArticles(limit: Int?) throws -> Set<Article> {
|
||||
return try database.fetchStarredArticles(flattenedWebFeeds().webFeedIDs(), limit)
|
||||
return try database.fetchStarredArticles(flattenedFeeds().feedIDs(), limit)
|
||||
}
|
||||
|
||||
func fetchStarredArticlesAsync(limit: Int?, _ completion: @escaping ArticleSetResultBlock) {
|
||||
database.fetchedStarredArticlesAsync(flattenedWebFeeds().webFeedIDs(), limit, completion)
|
||||
database.fetchedStarredArticlesAsync(flattenedFeeds().feedIDs(), limit, completion)
|
||||
}
|
||||
|
||||
func fetchUnreadArticles(limit: Int?) throws -> Set<Article> {
|
||||
@@ -1044,11 +1044,11 @@ private extension Account {
|
||||
}
|
||||
|
||||
func fetchTodayArticles(limit: Int?) throws -> Set<Article> {
|
||||
return try database.fetchTodayArticles(flattenedWebFeeds().webFeedIDs(), limit)
|
||||
return try database.fetchTodayArticles(flattenedFeeds().feedIDs(), limit)
|
||||
}
|
||||
|
||||
func fetchTodayArticlesAsync(limit: Int?, _ completion: @escaping ArticleSetResultBlock) {
|
||||
database.fetchTodayArticlesAsync(flattenedWebFeeds().webFeedIDs(), limit, completion)
|
||||
database.fetchTodayArticlesAsync(flattenedFeeds().feedIDs(), limit, completion)
|
||||
}
|
||||
|
||||
func fetchArticles(folder: Folder) throws -> Set<Article> {
|
||||
@@ -1067,17 +1067,17 @@ private extension Account {
|
||||
fetchUnreadArticlesAsync(forContainer: folder, limit: nil, completion)
|
||||
}
|
||||
|
||||
func fetchArticles(webFeed: Feed) throws -> Set<Article> {
|
||||
let articles = try database.fetchArticles(webFeed.webFeedID)
|
||||
validateUnreadCount(webFeed, articles)
|
||||
func fetchArticles(feed: Feed) throws -> Set<Article> {
|
||||
let articles = try database.fetchArticles(feed.feedID)
|
||||
validateUnreadCount(feed, articles)
|
||||
return articles
|
||||
}
|
||||
|
||||
func fetchArticlesAsync(webFeed: Feed, _ completion: @escaping ArticleSetResultBlock) {
|
||||
database.fetchArticlesAsync(webFeed.webFeedID) { [weak self] articleSetResult in
|
||||
func fetchArticlesAsync(feed: Feed, _ completion: @escaping ArticleSetResultBlock) {
|
||||
database.fetchArticlesAsync(feed.feedID) { [weak self] articleSetResult in
|
||||
switch articleSetResult {
|
||||
case .success(let articles):
|
||||
self?.validateUnreadCount(webFeed, articles)
|
||||
self?.validateUnreadCount(feed, articles)
|
||||
completion(.success(articles))
|
||||
case .failure(let databaseError):
|
||||
completion(.failure(databaseError))
|
||||
@@ -1086,7 +1086,7 @@ private extension Account {
|
||||
}
|
||||
|
||||
func fetchArticlesMatching(_ searchString: String) throws -> Set<Article> {
|
||||
return try database.fetchArticlesMatching(searchString, flattenedWebFeeds().webFeedIDs())
|
||||
return try database.fetchArticlesMatching(searchString, flattenedFeeds().feedIDs())
|
||||
}
|
||||
|
||||
func fetchArticlesMatchingWithArticleIDs(_ searchString: String, _ articleIDs: Set<String>) throws -> Set<Article> {
|
||||
@@ -1094,7 +1094,7 @@ private extension Account {
|
||||
}
|
||||
|
||||
func fetchArticlesMatchingAsync(_ searchString: String, _ completion: @escaping ArticleSetResultBlock) {
|
||||
database.fetchArticlesMatchingAsync(searchString, flattenedWebFeeds().webFeedIDs(), completion)
|
||||
database.fetchArticlesMatchingAsync(searchString, flattenedFeeds().feedIDs(), completion)
|
||||
}
|
||||
|
||||
func fetchArticlesMatchingWithArticleIDsAsync(_ searchString: String, _ articleIDs: Set<String>, _ completion: @escaping ArticleSetResultBlock) {
|
||||
@@ -1109,25 +1109,25 @@ private extension Account {
|
||||
return database.fetchArticlesAsync(articleIDs: articleIDs, completion)
|
||||
}
|
||||
|
||||
func fetchUnreadArticles(webFeed: Feed) throws -> Set<Article> {
|
||||
let articles = try database.fetchUnreadArticles(Set([webFeed.webFeedID]), nil)
|
||||
validateUnreadCount(webFeed, articles)
|
||||
func fetchUnreadArticles(feed: Feed) throws -> Set<Article> {
|
||||
let articles = try database.fetchUnreadArticles(Set([feed.feedID]), nil)
|
||||
validateUnreadCount(feed, articles)
|
||||
return articles
|
||||
}
|
||||
|
||||
func fetchArticles(forContainer container: Container) throws -> Set<Article> {
|
||||
let feeds = container.flattenedWebFeeds()
|
||||
let articles = try database.fetchArticles(feeds.webFeedIDs())
|
||||
let feeds = container.flattenedFeeds()
|
||||
let articles = try database.fetchArticles(feeds.feedIDs())
|
||||
validateUnreadCountsAfterFetchingUnreadArticles(feeds, articles)
|
||||
return articles
|
||||
}
|
||||
|
||||
func fetchArticlesAsync(forContainer container: Container, _ completion: @escaping ArticleSetResultBlock) {
|
||||
let webFeeds = container.flattenedWebFeeds()
|
||||
database.fetchArticlesAsync(webFeeds.webFeedIDs()) { [weak self] (articleSetResult) in
|
||||
let feeds = container.flattenedFeeds()
|
||||
database.fetchArticlesAsync(feeds.feedIDs()) { [weak self] (articleSetResult) in
|
||||
switch articleSetResult {
|
||||
case .success(let articles):
|
||||
self?.validateUnreadCountsAfterFetchingUnreadArticles(webFeeds, articles)
|
||||
self?.validateUnreadCountsAfterFetchingUnreadArticles(feeds, articles)
|
||||
completion(.success(articles))
|
||||
case .failure(let databaseError):
|
||||
completion(.failure(databaseError))
|
||||
@@ -1136,8 +1136,8 @@ private extension Account {
|
||||
}
|
||||
|
||||
func fetchUnreadArticles(forContainer container: Container, limit: Int?) throws -> Set<Article> {
|
||||
let feeds = container.flattenedWebFeeds()
|
||||
let articles = try database.fetchUnreadArticles(feeds.webFeedIDs(), limit)
|
||||
let feeds = container.flattenedFeeds()
|
||||
let articles = try database.fetchUnreadArticles(feeds.feedIDs(), limit)
|
||||
|
||||
// We don't validate limit queries because they, by definition, won't correctly match the
|
||||
// complete unread state for the given container.
|
||||
@@ -1149,15 +1149,15 @@ private extension Account {
|
||||
}
|
||||
|
||||
func fetchUnreadArticlesAsync(forContainer container: Container, limit: Int?, _ completion: @escaping ArticleSetResultBlock) {
|
||||
let webFeeds = container.flattenedWebFeeds()
|
||||
database.fetchUnreadArticlesAsync(webFeeds.webFeedIDs(), limit) { [weak self] (articleSetResult) in
|
||||
let feeds = container.flattenedFeeds()
|
||||
database.fetchUnreadArticlesAsync(feeds.feedIDs(), limit) { [weak self] (articleSetResult) in
|
||||
switch articleSetResult {
|
||||
case .success(let articles):
|
||||
|
||||
// We don't validate limit queries because they, by definition, won't correctly match the
|
||||
// complete unread state for the given container.
|
||||
if limit == nil {
|
||||
self?.validateUnreadCountsAfterFetchingUnreadArticles(webFeeds, articles)
|
||||
self?.validateUnreadCountsAfterFetchingUnreadArticles(feeds, articles)
|
||||
}
|
||||
|
||||
completion(.success(articles))
|
||||
@@ -1167,34 +1167,34 @@ private extension Account {
|
||||
}
|
||||
}
|
||||
|
||||
func validateUnreadCountsAfterFetchingUnreadArticles(_ webFeeds: Set<Feed>, _ articles: Set<Article>) {
|
||||
func validateUnreadCountsAfterFetchingUnreadArticles(_ feeds: Set<Feed>, _ 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]() // [WebFeedID: Int]
|
||||
var unreadCountStorage = [String: Int]() // [FeedID: Int]
|
||||
for article in articles where !article.status.read {
|
||||
unreadCountStorage[article.webFeedID, default: 0] += 1
|
||||
unreadCountStorage[article.feedID, default: 0] += 1
|
||||
}
|
||||
webFeeds.forEach { (webFeed) in
|
||||
let unreadCount = unreadCountStorage[webFeed.webFeedID, default: 0]
|
||||
webFeed.unreadCount = unreadCount
|
||||
feeds.forEach { (feed) in
|
||||
let unreadCount = unreadCountStorage[feed.feedID, default: 0]
|
||||
feed.unreadCount = unreadCount
|
||||
}
|
||||
}
|
||||
|
||||
func validateUnreadCount(_ webFeed: Feed, _ articles: Set<Article>) {
|
||||
func validateUnreadCount(_ feed: Feed, _ 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.webFeed == webFeed && !article.status.read {
|
||||
if article.feed == feed && !article.status.read {
|
||||
return result + 1
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
webFeed.unreadCount = feedUnreadCount
|
||||
feed.unreadCount = feedUnreadCount
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1202,42 +1202,42 @@ private extension Account {
|
||||
|
||||
private extension Account {
|
||||
|
||||
func webFeedMetadata(feedURL: String, webFeedID: String) -> WebFeedMetadata {
|
||||
if let d = webFeedMetadata[feedURL] {
|
||||
func feedMetadata(feedURL: String, feedID: String) -> FeedMetadata {
|
||||
if let d = feedMetadata[feedURL] {
|
||||
assert(d.delegate === self)
|
||||
return d
|
||||
}
|
||||
let d = WebFeedMetadata(webFeedID: webFeedID)
|
||||
let d = FeedMetadata(feedID: feedID)
|
||||
d.delegate = self
|
||||
webFeedMetadata[feedURL] = d
|
||||
feedMetadata[feedURL] = d
|
||||
return d
|
||||
}
|
||||
|
||||
func updateFlattenedWebFeeds() {
|
||||
func updateFlattenedFeeds() {
|
||||
var feeds = Set<Feed>()
|
||||
feeds.formUnion(topLevelWebFeeds)
|
||||
feeds.formUnion(topLevelFeeds)
|
||||
for folder in folders! {
|
||||
feeds.formUnion(folder.flattenedWebFeeds())
|
||||
feeds.formUnion(folder.flattenedFeeds())
|
||||
}
|
||||
|
||||
_flattenedWebFeeds = feeds
|
||||
flattenedWebFeedsNeedUpdate = false
|
||||
_flattenedFeeds = feeds
|
||||
flattenedFeedsNeedUpdate = false
|
||||
}
|
||||
|
||||
func rebuildWebFeedDictionaries() {
|
||||
func rebuildFeedDictionaries() {
|
||||
var idDictionary = [String: Feed]()
|
||||
var externalIDDictionary = [String: Feed]()
|
||||
|
||||
flattenedWebFeeds().forEach { (feed) in
|
||||
idDictionary[feed.webFeedID] = feed
|
||||
flattenedFeeds().forEach { (feed) in
|
||||
idDictionary[feed.feedID] = feed
|
||||
if let externalID = feed.externalID {
|
||||
externalIDDictionary[externalID] = feed
|
||||
}
|
||||
}
|
||||
|
||||
_idToWebFeedDictionary = idDictionary
|
||||
_externalIDToWebFeedDictionary = externalIDDictionary
|
||||
webFeedDictionariesNeedUpdate = false
|
||||
_idToFeedDictionary = idDictionary
|
||||
_externalIDToFeedDictionary = externalIDDictionary
|
||||
feedDictionariesNeedUpdate = false
|
||||
}
|
||||
|
||||
func updateUnreadCount() {
|
||||
@@ -1245,14 +1245,14 @@ private extension Account {
|
||||
return
|
||||
}
|
||||
var updatedUnreadCount = 0
|
||||
for feed in flattenedWebFeeds() {
|
||||
for feed in flattenedFeeds() {
|
||||
updatedUnreadCount += feed.unreadCount
|
||||
}
|
||||
unreadCount = updatedUnreadCount
|
||||
}
|
||||
|
||||
func noteStatusesForArticlesDidChange(_ articles: Set<Article>) {
|
||||
let feeds = Set(articles.compactMap { $0.webFeed })
|
||||
let feeds = Set(articles.compactMap { $0.feed })
|
||||
let statuses = Set(articles.map { $0.status })
|
||||
let articleIDs = Set(articles.map { $0.articleID })
|
||||
|
||||
@@ -1260,7 +1260,7 @@ private extension Account {
|
||||
// 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.articleIDs: articleIDs, UserInfoKey.webFeeds: feeds])
|
||||
NotificationCenter.default.post(name: .StatusesDidChange, object: self, userInfo: [UserInfoKey.statuses: statuses, UserInfoKey.articles: articles, UserInfoKey.articleIDs: articleIDs, UserInfoKey.feeds: feeds])
|
||||
}
|
||||
|
||||
func noteStatusesForArticleIDsDidChange(articleIDs: Set<String>, statusKey: ArticleStatus.Key, flag: Bool) {
|
||||
@@ -1293,7 +1293,7 @@ private extension Account {
|
||||
}
|
||||
|
||||
func fetchUnreadCount(_ feed: Feed, _ completion: VoidCompletionBlock?) {
|
||||
database.fetchUnreadCount(feed.webFeedID) { result in
|
||||
database.fetchUnreadCount(feed.feedID) { result in
|
||||
if let unreadCount = try? result.get() {
|
||||
feed.unreadCount = unreadCount
|
||||
}
|
||||
@@ -1302,8 +1302,8 @@ private extension Account {
|
||||
}
|
||||
|
||||
func fetchUnreadCounts(_ feeds: Set<Feed>, _ completion: VoidCompletionBlock?) {
|
||||
let webFeedIDs = Set(feeds.map { $0.webFeedID })
|
||||
database.fetchUnreadCounts(for: webFeedIDs) { result in
|
||||
let feedIDs = Set(feeds.map { $0.feedID })
|
||||
database.fetchUnreadCounts(for: feedIDs) { result in
|
||||
if let unreadCountDictionary = try? result.get() {
|
||||
self.processUnreadCounts(unreadCountDictionary: unreadCountDictionary, feeds: feeds)
|
||||
}
|
||||
@@ -1318,7 +1318,7 @@ private extension Account {
|
||||
completion?()
|
||||
return
|
||||
}
|
||||
self.processUnreadCounts(unreadCountDictionary: unreadCountDictionary, feeds: self.flattenedWebFeeds())
|
||||
self.processUnreadCounts(unreadCountDictionary: unreadCountDictionary, feeds: self.flattenedFeeds())
|
||||
|
||||
self.fetchingAllUnreadCounts = false
|
||||
self.updateUnreadCount()
|
||||
@@ -1334,19 +1334,19 @@ private extension Account {
|
||||
func processUnreadCounts(unreadCountDictionary: UnreadCountDictionary, feeds: Set<Feed>) {
|
||||
for feed in feeds {
|
||||
// When the unread count is zero, it won’t appear in unreadCountDictionary.
|
||||
let unreadCount = unreadCountDictionary[feed.webFeedID] ?? 0
|
||||
let unreadCount = unreadCountDictionary[feed.feedID] ?? 0
|
||||
feed.unreadCount = unreadCount
|
||||
}
|
||||
}
|
||||
|
||||
func sendNotificationAbout(_ articleChanges: ArticleChanges) {
|
||||
var webFeeds = Set<Feed>()
|
||||
var feeds = Set<Feed>()
|
||||
|
||||
if let newArticles = articleChanges.newArticles {
|
||||
webFeeds.formUnion(Set(newArticles.compactMap { $0.webFeed }))
|
||||
feeds.formUnion(Set(newArticles.compactMap { $0.feed }))
|
||||
}
|
||||
if let updatedArticles = articleChanges.updatedArticles {
|
||||
webFeeds.formUnion(Set(updatedArticles.compactMap { $0.webFeed }))
|
||||
feeds.formUnion(Set(updatedArticles.compactMap { $0.feed }))
|
||||
}
|
||||
|
||||
var shouldSendNotification = false
|
||||
@@ -1369,11 +1369,11 @@ private extension Account {
|
||||
}
|
||||
|
||||
if shouldUpdateUnreadCounts {
|
||||
self.updateUnreadCounts(for: webFeeds)
|
||||
self.updateUnreadCounts(for: feeds)
|
||||
}
|
||||
|
||||
if shouldSendNotification {
|
||||
userInfo[UserInfoKey.webFeeds] = webFeeds
|
||||
userInfo[UserInfoKey.feeds] = feeds
|
||||
NotificationCenter.default.post(name: .AccountDidDownloadArticles, object: self, userInfo: userInfo)
|
||||
}
|
||||
}
|
||||
@@ -1383,12 +1383,12 @@ private extension Account {
|
||||
|
||||
extension Account {
|
||||
|
||||
public func existingWebFeed(withWebFeedID webFeedID: String) -> Feed? {
|
||||
return idToWebFeedDictionary[webFeedID]
|
||||
public func existingFeed(withFeedID feedID: String) -> Feed? {
|
||||
return idToFeedDictionary[feedID]
|
||||
}
|
||||
|
||||
public func existingWebFeed(withExternalID externalID: String) -> Feed? {
|
||||
return externalIDToWebFeedDictionary[externalID]
|
||||
public func existingFeed(withExternalID externalID: String) -> Feed? {
|
||||
return externalIDToFeedDictionary[externalID]
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1399,7 +1399,7 @@ extension Account: OPMLRepresentable {
|
||||
|
||||
public func OPMLString(indentLevel: Int, allowCustomAttributes: Bool) -> String {
|
||||
var s = ""
|
||||
for feed in topLevelWebFeeds.sorted() {
|
||||
for feed in topLevelFeeds.sorted() {
|
||||
s += feed.OPMLString(indentLevel: indentLevel + 1, allowCustomAttributes: allowCustomAttributes)
|
||||
}
|
||||
for folder in folders!.sorted() {
|
||||
|
||||
@@ -36,13 +36,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 createWebFeed(for account: Account, url: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result<Feed, Error>) -> Void)
|
||||
func renameWebFeed(for account: Account, with feed: Feed, to name: String, completion: @escaping (Result<Void, Error>) -> Void)
|
||||
func addWebFeed(for account: Account, with: Feed, to container: Container, completion: @escaping (Result<Void, Error>) -> Void)
|
||||
func removeWebFeed(for account: Account, with feed: Feed, from container: Container, completion: @escaping (Result<Void, Error>) -> Void)
|
||||
func moveWebFeed(for account: Account, with feed: Feed, from: Container, to: Container, completion: @escaping (Result<Void, Error>) -> Void)
|
||||
func createFeed(for account: Account, url: String, name: String?, container: Container, validateFeed: Bool, 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 restoreWebFeed(for account: Account, feed: Feed, container: Container, completion: @escaping (Result<Void, Error>) -> Void)
|
||||
func restoreFeed(for account: Account, feed: Feed, 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, completion: @escaping (Result<Void, Error>) -> Void)
|
||||
|
||||
@@ -203,9 +203,9 @@ public final class AccountManager: UnreadCountProvider {
|
||||
if let account = existingAccount(with: accountID) {
|
||||
return account.existingFolder(with: folderName)
|
||||
}
|
||||
case .webFeed(let accountID, let webFeedID):
|
||||
case .feed(let accountID, let feedID):
|
||||
if let account = existingAccount(with: accountID) {
|
||||
return account.existingWebFeed(withWebFeedID: webFeedID)
|
||||
return account.existingFeed(withFeedID: feedID)
|
||||
}
|
||||
default:
|
||||
break
|
||||
@@ -330,7 +330,7 @@ public final class AccountManager: UnreadCountProvider {
|
||||
|
||||
public func anyAccountHasAtLeastOneFeed() -> Bool {
|
||||
for account in activeAccounts {
|
||||
if account.hasAtLeastOneWebFeed() {
|
||||
if account.hasAtLeastOneFeed() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -344,7 +344,7 @@ public final class AccountManager: UnreadCountProvider {
|
||||
|
||||
public func anyAccountHasFeedWithURL(_ urlString: String) -> Bool {
|
||||
for account in activeAccounts {
|
||||
if let _ = account.existingWebFeed(withURL: urlString) {
|
||||
if let _ = account.existingFeed(withURL: urlString) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ public protocol ArticleFetcher {
|
||||
extension Feed: ArticleFetcher {
|
||||
|
||||
public func fetchArticles() throws -> Set<Article> {
|
||||
return try account?.fetchArticles(.webFeed(self)) ?? Set<Article>()
|
||||
return try account?.fetchArticles(.feed(self)) ?? Set<Article>()
|
||||
}
|
||||
|
||||
public func fetchArticlesAsync(_ completion: @escaping ArticleSetResultBlock) {
|
||||
@@ -30,7 +30,7 @@ extension Feed: ArticleFetcher {
|
||||
completion(.success(Set<Article>()))
|
||||
return
|
||||
}
|
||||
account.fetchArticlesAsync(.webFeed(self), completion)
|
||||
account.fetchArticlesAsync(.feed(self), completion)
|
||||
}
|
||||
|
||||
public func fetchUnreadArticles() throws -> Set<Article> {
|
||||
@@ -43,7 +43,7 @@ extension Feed: ArticleFetcher {
|
||||
completion(.success(Set<Article>()))
|
||||
return
|
||||
}
|
||||
account.fetchArticlesAsync(.webFeed(self)) { articleSetResult in
|
||||
account.fetchArticlesAsync(.feed(self)) { articleSetResult in
|
||||
switch articleSetResult {
|
||||
case .success(let articles):
|
||||
completion(.success(articles.unreadArticles()))
|
||||
|
||||
@@ -175,7 +175,7 @@ final class CloudKitAccountDelegate: AccountDelegate {
|
||||
|
||||
}
|
||||
|
||||
func createWebFeed(for account: Account, url urlString: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result<Feed, Error>) -> Void) {
|
||||
func createFeed(for account: Account, url urlString: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result<Feed, Error>) -> Void) {
|
||||
guard let url = URL(string: urlString) else {
|
||||
completion(.failure(LocalAccountDelegateError.invalidParameter))
|
||||
return
|
||||
@@ -183,13 +183,13 @@ final class CloudKitAccountDelegate: AccountDelegate {
|
||||
|
||||
let editedName = name == nil || name!.isEmpty ? nil : name
|
||||
|
||||
createRSSWebFeed(for: account, url: url, editedName: editedName, container: container, validateFeed: validateFeed, completion: completion)
|
||||
createRSSFeed(for: account, url: url, editedName: editedName, container: container, validateFeed: validateFeed, completion: completion)
|
||||
}
|
||||
|
||||
func renameWebFeed(for account: Account, with feed: Feed, to name: String, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
func renameFeed(for account: Account, with feed: Feed, to name: String, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
let editedName = name.isEmpty ? nil : name
|
||||
refreshProgress.addToNumberOfTasksAndRemaining(1)
|
||||
accountZone.renameWebFeed(feed, editedName: editedName) { result in
|
||||
accountZone.renameFeed(feed, editedName: editedName) { result in
|
||||
self.refreshProgress.completeTask()
|
||||
switch result {
|
||||
case .success:
|
||||
@@ -202,19 +202,19 @@ final class CloudKitAccountDelegate: AccountDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
func removeWebFeed(for account: Account, with feed: Feed, from container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
removeWebFeedFromCloud(for: account, with: feed, from: container) { result in
|
||||
func removeFeed(for account: Account, with feed: Feed, from container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
removeFeedFromCloud(for: account, with: feed, from: container) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
account.clearWebFeedMetadata(feed)
|
||||
container.removeWebFeed(feed)
|
||||
account.clearFeedMetadata(feed)
|
||||
container.removeFeed(feed)
|
||||
completion(.success(()))
|
||||
case .failure(let error):
|
||||
switch error {
|
||||
case CloudKitZoneError.corruptAccount:
|
||||
// We got into a bad state and should remove the feed to clear up the bad data
|
||||
account.clearWebFeedMetadata(feed)
|
||||
container.removeWebFeed(feed)
|
||||
account.clearFeedMetadata(feed)
|
||||
container.removeFeed(feed)
|
||||
default:
|
||||
completion(.failure(error))
|
||||
}
|
||||
@@ -222,14 +222,14 @@ final class CloudKitAccountDelegate: AccountDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
func moveWebFeed(for account: Account, with feed: Feed, from fromContainer: Container, to toContainer: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
func moveFeed(for account: Account, with feed: Feed, from fromContainer: Container, to toContainer: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
refreshProgress.addToNumberOfTasksAndRemaining(1)
|
||||
accountZone.moveWebFeed(feed, from: fromContainer, to: toContainer) { result in
|
||||
accountZone.moveFeed(feed, from: fromContainer, to: toContainer) { result in
|
||||
self.refreshProgress.completeTask()
|
||||
switch result {
|
||||
case .success:
|
||||
fromContainer.removeWebFeed(feed)
|
||||
toContainer.addWebFeed(feed)
|
||||
fromContainer.removeFeed(feed)
|
||||
toContainer.addFeed(feed)
|
||||
completion(.success(()))
|
||||
case .failure(let error):
|
||||
self.processAccountError(account, error)
|
||||
@@ -238,13 +238,13 @@ final class CloudKitAccountDelegate: AccountDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
func addWebFeed(for account: Account, with feed: Feed, to container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
func addFeed(for account: Account, with feed: Feed, to container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
refreshProgress.addToNumberOfTasksAndRemaining(1)
|
||||
accountZone.addWebFeed(feed, to: container) { result in
|
||||
accountZone.addFeed(feed, to: container) { result in
|
||||
self.refreshProgress.completeTask()
|
||||
switch result {
|
||||
case .success:
|
||||
container.addWebFeed(feed)
|
||||
container.addFeed(feed)
|
||||
completion(.success(()))
|
||||
case .failure(let error):
|
||||
self.processAccountError(account, error)
|
||||
@@ -253,8 +253,8 @@ final class CloudKitAccountDelegate: AccountDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
func restoreWebFeed(for account: Account, feed: Feed, container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
createWebFeed(for: account, url: feed.url, name: feed.editedName, container: container, validateFeed: true) { result in
|
||||
func restoreFeed(for account: Account, feed: Feed, container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
createFeed(for: account, url: feed.url, name: feed.editedName, container: container, validateFeed: true) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
completion(.success(()))
|
||||
@@ -301,18 +301,18 @@ final class CloudKitAccountDelegate: AccountDelegate {
|
||||
func removeFolder(for account: Account, with folder: Folder, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
|
||||
refreshProgress.addToNumberOfTasksAndRemaining(2)
|
||||
accountZone.findWebFeedExternalIDs(for: folder) { result in
|
||||
accountZone.findFeedExternalIDs(for: folder) { result in
|
||||
self.refreshProgress.completeTask()
|
||||
switch result {
|
||||
case .success(let webFeedExternalIDs):
|
||||
case .success(let feedExternalIDs):
|
||||
|
||||
let webFeeds = webFeedExternalIDs.compactMap { account.existingWebFeed(withExternalID: $0) }
|
||||
let feeds = feedExternalIDs.compactMap { account.existingFeed(withExternalID: $0) }
|
||||
let group = DispatchGroup()
|
||||
var errorOccurred = false
|
||||
|
||||
for webFeed in webFeeds {
|
||||
for feed in feeds {
|
||||
group.enter()
|
||||
self.removeWebFeedFromCloud(for: account, with: webFeed, from: folder) { result in
|
||||
self.removeFeedFromCloud(for: account, with: feed, from: folder) { result in
|
||||
group.leave()
|
||||
if case .failure(let error) = result {
|
||||
os_log(.error, log: self.log, "Remove folder, remove webfeed error: %@.", error.localizedDescription)
|
||||
@@ -358,7 +358,7 @@ final class CloudKitAccountDelegate: AccountDelegate {
|
||||
return
|
||||
}
|
||||
|
||||
let feedsToRestore = folder.topLevelWebFeeds
|
||||
let feedsToRestore = folder.topLevelFeeds
|
||||
refreshProgress.addToNumberOfTasksAndRemaining(1 + feedsToRestore.count)
|
||||
|
||||
accountZone.createFolder(name: name) { result in
|
||||
@@ -371,10 +371,10 @@ final class CloudKitAccountDelegate: AccountDelegate {
|
||||
let group = DispatchGroup()
|
||||
for feed in feedsToRestore {
|
||||
|
||||
folder.topLevelWebFeeds.remove(feed)
|
||||
folder.topLevelFeeds.remove(feed)
|
||||
|
||||
group.enter()
|
||||
self.restoreWebFeed(for: account, feed: feed, container: folder) { result in
|
||||
self.restoreFeed(for: account, feed: feed, container: folder) { result in
|
||||
self.refreshProgress.completeTask()
|
||||
group.leave()
|
||||
switch result {
|
||||
@@ -485,8 +485,8 @@ private extension CloudKitAccountDelegate {
|
||||
accountZone.fetchChangesInZone() { result in
|
||||
self.refreshProgress.completeTask()
|
||||
|
||||
let webFeeds = account.flattenedWebFeeds()
|
||||
self.refreshProgress.addToNumberOfTasksAndRemaining(webFeeds.count)
|
||||
let feeds = account.flattenedFeeds()
|
||||
self.refreshProgress.addToNumberOfTasksAndRemaining(feeds.count)
|
||||
|
||||
switch result {
|
||||
case .success:
|
||||
@@ -495,7 +495,7 @@ private extension CloudKitAccountDelegate {
|
||||
switch result {
|
||||
case .success:
|
||||
|
||||
self.combinedRefresh(account, webFeeds) { result in
|
||||
self.combinedRefresh(account, feeds) { result in
|
||||
self.refreshProgress.clear()
|
||||
switch result {
|
||||
case .success:
|
||||
@@ -518,8 +518,8 @@ private extension CloudKitAccountDelegate {
|
||||
|
||||
func standardRefreshAll(for account: Account, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
|
||||
let intialWebFeedsCount = account.flattenedWebFeeds().count
|
||||
refreshProgress.addToNumberOfTasksAndRemaining(3 + intialWebFeedsCount)
|
||||
let intialFeedsCount = account.flattenedFeeds().count
|
||||
refreshProgress.addToNumberOfTasksAndRemaining(3 + intialFeedsCount)
|
||||
|
||||
func fail(_ error: Error) {
|
||||
self.processAccountError(account, error)
|
||||
@@ -532,14 +532,14 @@ private extension CloudKitAccountDelegate {
|
||||
case .success:
|
||||
|
||||
self.refreshProgress.completeTask()
|
||||
let webFeeds = account.flattenedWebFeeds()
|
||||
self.refreshProgress.addToNumberOfTasksAndRemaining(webFeeds.count - intialWebFeedsCount)
|
||||
let feeds = account.flattenedFeeds()
|
||||
self.refreshProgress.addToNumberOfTasksAndRemaining(feeds.count - intialFeedsCount)
|
||||
|
||||
self.refreshArticleStatus(for: account) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
self.refreshProgress.completeTask()
|
||||
self.combinedRefresh(account, webFeeds) { result in
|
||||
self.combinedRefresh(account, feeds) { result in
|
||||
self.sendArticleStatus(for: account, showProgress: true) { _ in
|
||||
self.refreshProgress.clear()
|
||||
if case .failure(let error) = result {
|
||||
@@ -562,12 +562,12 @@ private extension CloudKitAccountDelegate {
|
||||
|
||||
}
|
||||
|
||||
func combinedRefresh(_ account: Account, _ webFeeds: Set<Feed>, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
func combinedRefresh(_ account: Account, _ feeds: Set<Feed>, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
|
||||
let group = DispatchGroup()
|
||||
|
||||
group.enter()
|
||||
refresher.refreshFeeds(webFeeds) {
|
||||
refresher.refreshFeeds(feeds) {
|
||||
group.leave()
|
||||
}
|
||||
|
||||
@@ -576,13 +576,13 @@ private extension CloudKitAccountDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
func createRSSWebFeed(for account: Account, url: URL, editedName: String?, container: Container, validateFeed: Bool, completion: @escaping (Result<Feed, Error>) -> Void) {
|
||||
func createRSSFeed(for account: Account, url: URL, editedName: String?, container: Container, validateFeed: Bool, completion: @escaping (Result<Feed, Error>) -> Void) {
|
||||
|
||||
func addDeadFeed() {
|
||||
let feed = account.createWebFeed(with: editedName, url: url.absoluteString, webFeedID: url.absoluteString, homePageURL: nil)
|
||||
container.addWebFeed(feed)
|
||||
let feed = account.createFeed(with: editedName, url: url.absoluteString, feedID: url.absoluteString, homePageURL: nil)
|
||||
container.addFeed(feed)
|
||||
|
||||
self.accountZone.createWebFeed(url: url.absoluteString,
|
||||
self.accountZone.createFeed(url: url.absoluteString,
|
||||
name: editedName,
|
||||
editedName: nil,
|
||||
homePageURL: nil,
|
||||
@@ -594,7 +594,7 @@ private extension CloudKitAccountDelegate {
|
||||
feed.externalID = externalID
|
||||
completion(.success(feed))
|
||||
case .failure(let error):
|
||||
container.removeWebFeed(feed)
|
||||
container.removeFeed(feed)
|
||||
completion(.failure(error))
|
||||
}
|
||||
}
|
||||
@@ -617,15 +617,15 @@ private extension CloudKitAccountDelegate {
|
||||
return
|
||||
}
|
||||
|
||||
if account.hasWebFeed(withURL: bestFeedSpecifier.urlString) {
|
||||
if account.hasFeed(withURL: bestFeedSpecifier.urlString) {
|
||||
self.refreshProgress.completeTasks(4)
|
||||
completion(.failure(AccountError.createErrorAlreadySubscribed))
|
||||
return
|
||||
}
|
||||
|
||||
let feed = account.createWebFeed(with: nil, url: url.absoluteString, webFeedID: url.absoluteString, homePageURL: nil)
|
||||
let feed = account.createFeed(with: nil, url: url.absoluteString, feedID: url.absoluteString, homePageURL: nil)
|
||||
feed.editedName = editedName
|
||||
container.addWebFeed(feed)
|
||||
container.addFeed(feed)
|
||||
|
||||
InitialFeedDownloader.download(url) { parsedFeed in
|
||||
self.refreshProgress.completeTask()
|
||||
@@ -635,7 +635,7 @@ private extension CloudKitAccountDelegate {
|
||||
switch result {
|
||||
case .success:
|
||||
|
||||
self.accountZone.createWebFeed(url: bestFeedSpecifier.urlString,
|
||||
self.accountZone.createFeed(url: bestFeedSpecifier.urlString,
|
||||
name: parsedFeed.title,
|
||||
editedName: editedName,
|
||||
homePageURL: parsedFeed.homePageURL,
|
||||
@@ -648,7 +648,7 @@ private extension CloudKitAccountDelegate {
|
||||
self.sendNewArticlesToTheCloud(account, feed)
|
||||
completion(.success(feed))
|
||||
case .failure(let error):
|
||||
container.removeWebFeed(feed)
|
||||
container.removeFeed(feed)
|
||||
self.refreshProgress.completeTasks(2)
|
||||
completion(.failure(error))
|
||||
}
|
||||
@@ -656,7 +656,7 @@ private extension CloudKitAccountDelegate {
|
||||
}
|
||||
|
||||
case .failure(let error):
|
||||
container.removeWebFeed(feed)
|
||||
container.removeFeed(feed)
|
||||
self.refreshProgress.completeTasks(3)
|
||||
completion(.failure(error))
|
||||
}
|
||||
@@ -664,7 +664,7 @@ private extension CloudKitAccountDelegate {
|
||||
}
|
||||
} else {
|
||||
self.refreshProgress.completeTasks(3)
|
||||
container.removeWebFeed(feed)
|
||||
container.removeFeed(feed)
|
||||
completion(.failure(AccountError.createErrorNotFound))
|
||||
}
|
||||
|
||||
@@ -684,7 +684,7 @@ private extension CloudKitAccountDelegate {
|
||||
}
|
||||
|
||||
func sendNewArticlesToTheCloud(_ account: Account, _ feed: Feed) {
|
||||
account.fetchArticlesAsync(.webFeed(feed)) { result in
|
||||
account.fetchArticlesAsync(.feed(feed)) { result in
|
||||
switch result {
|
||||
case .success(let articles):
|
||||
self.storeArticleChanges(new: articles, updated: Set<Article>(), deleted: Set<Article>()) {
|
||||
@@ -706,7 +706,7 @@ private extension CloudKitAccountDelegate {
|
||||
|
||||
func processAccountError(_ account: Account, _ error: Error) {
|
||||
if case CloudKitZoneError.userDeletedZone = error {
|
||||
account.removeFeeds(account.topLevelWebFeeds)
|
||||
account.removeFeeds(account.topLevelFeeds)
|
||||
for folder in account.folders ?? Set<Folder>() {
|
||||
account.removeFolder(folder)
|
||||
}
|
||||
@@ -771,17 +771,17 @@ private extension CloudKitAccountDelegate {
|
||||
}
|
||||
|
||||
|
||||
func removeWebFeedFromCloud(for account: Account, with feed: Feed, from container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
func removeFeedFromCloud(for account: Account, with feed: Feed, from container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
refreshProgress.addToNumberOfTasksAndRemaining(2)
|
||||
accountZone.removeWebFeed(feed, from: container) { result in
|
||||
accountZone.removeFeed(feed, from: container) { result in
|
||||
self.refreshProgress.completeTask()
|
||||
switch result {
|
||||
case .success:
|
||||
guard let webFeedExternalID = feed.externalID else {
|
||||
guard let feedExternalID = feed.externalID else {
|
||||
completion(.success(()))
|
||||
return
|
||||
}
|
||||
self.articlesZone.deleteArticles(webFeedExternalID) { result in
|
||||
self.articlesZone.deleteArticles(feedExternalID) { result in
|
||||
feed.dropConditionalGetInfo()
|
||||
self.refreshProgress.completeTask()
|
||||
completion(result)
|
||||
|
||||
@@ -29,7 +29,7 @@ final class CloudKitAccountZone: CloudKitZone {
|
||||
weak var database: CKDatabase?
|
||||
var delegate: CloudKitZoneDelegate?
|
||||
|
||||
struct CloudKitWebFeed {
|
||||
struct CloudKitFeed {
|
||||
static let recordType = "AccountWebFeed"
|
||||
struct Fields {
|
||||
static let url = "url"
|
||||
@@ -60,13 +60,13 @@ final class CloudKitAccountZone: CloudKitZone {
|
||||
var feedRecords = [String: CKRecord]()
|
||||
|
||||
func processFeed(feedSpecifier: RSOPMLFeedSpecifier, containerExternalID: String) {
|
||||
if let webFeedRecord = feedRecords[feedSpecifier.feedURL], var containerExternalIDs = webFeedRecord[CloudKitWebFeed.Fields.containerExternalIDs] as? [String] {
|
||||
if let feedRecord = feedRecords[feedSpecifier.feedURL], var containerExternalIDs = feedRecord[CloudKitFeed.Fields.containerExternalIDs] as? [String] {
|
||||
containerExternalIDs.append(containerExternalID)
|
||||
webFeedRecord[CloudKitWebFeed.Fields.containerExternalIDs] = containerExternalIDs
|
||||
feedRecord[CloudKitFeed.Fields.containerExternalIDs] = containerExternalIDs
|
||||
} else {
|
||||
let webFeedRecord = newWebFeedCKRecord(feedSpecifier: feedSpecifier, containerExternalID: containerExternalID)
|
||||
records.append(webFeedRecord)
|
||||
feedRecords[feedSpecifier.feedURL] = webFeedRecord
|
||||
let feedRecord = newFeedCKRecord(feedSpecifier: feedSpecifier, containerExternalID: containerExternalID)
|
||||
records.append(feedRecord)
|
||||
feedRecords[feedSpecifier.feedURL] = feedRecord
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,23 +90,23 @@ final class CloudKitAccountZone: CloudKitZone {
|
||||
}
|
||||
|
||||
/// Persist a web feed record to iCloud and return the external key
|
||||
func createWebFeed(url: String, name: String?, editedName: String?, homePageURL: String?, container: Container, completion: @escaping (Result<String, Error>) -> Void) {
|
||||
func createFeed(url: String, name: String?, editedName: String?, homePageURL: String?, container: Container, completion: @escaping (Result<String, Error>) -> Void) {
|
||||
let recordID = CKRecord.ID(recordName: url.md5String, zoneID: zoneID)
|
||||
let record = CKRecord(recordType: CloudKitWebFeed.recordType, recordID: recordID)
|
||||
record[CloudKitWebFeed.Fields.url] = url
|
||||
record[CloudKitWebFeed.Fields.name] = name
|
||||
let record = CKRecord(recordType: CloudKitFeed.recordType, recordID: recordID)
|
||||
record[CloudKitFeed.Fields.url] = url
|
||||
record[CloudKitFeed.Fields.name] = name
|
||||
if let editedName = editedName {
|
||||
record[CloudKitWebFeed.Fields.editedName] = editedName
|
||||
record[CloudKitFeed.Fields.editedName] = editedName
|
||||
}
|
||||
if let homePageURL = homePageURL {
|
||||
record[CloudKitWebFeed.Fields.homePageURL] = homePageURL
|
||||
record[CloudKitFeed.Fields.homePageURL] = homePageURL
|
||||
}
|
||||
|
||||
guard let containerExternalID = container.externalID else {
|
||||
completion(.failure(CloudKitZoneError.corruptAccount))
|
||||
return
|
||||
}
|
||||
record[CloudKitWebFeed.Fields.containerExternalIDs] = [containerExternalID]
|
||||
record[CloudKitFeed.Fields.containerExternalIDs] = [containerExternalID]
|
||||
|
||||
save(record) { result in
|
||||
switch result {
|
||||
@@ -119,15 +119,15 @@ final class CloudKitAccountZone: CloudKitZone {
|
||||
}
|
||||
|
||||
/// Rename the given web feed
|
||||
func renameWebFeed(_ webFeed: Feed, editedName: String?, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
guard let externalID = webFeed.externalID else {
|
||||
func renameFeed(_ feed: Feed, editedName: String?, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
guard let externalID = feed.externalID else {
|
||||
completion(.failure(CloudKitZoneError.corruptAccount))
|
||||
return
|
||||
}
|
||||
|
||||
let recordID = CKRecord.ID(recordName: externalID, zoneID: zoneID)
|
||||
let record = CKRecord(recordType: CloudKitWebFeed.recordType, recordID: recordID)
|
||||
record[CloudKitWebFeed.Fields.editedName] = editedName
|
||||
let record = CKRecord(recordType: CloudKitFeed.recordType, recordID: recordID)
|
||||
record[CloudKitFeed.Fields.editedName] = editedName
|
||||
|
||||
save(record) { result in
|
||||
switch result {
|
||||
@@ -140,22 +140,22 @@ final class CloudKitAccountZone: CloudKitZone {
|
||||
}
|
||||
|
||||
/// Removes a web feed from a container and optionally deletes it, calling the completion with true if deleted
|
||||
func removeWebFeed(_ webFeed: Feed, from: Container, completion: @escaping (Result<Bool, Error>) -> Void) {
|
||||
func removeFeed(_ feed: Feed, from: Container, completion: @escaping (Result<Bool, Error>) -> Void) {
|
||||
guard let fromContainerExternalID = from.externalID else {
|
||||
completion(.failure(CloudKitZoneError.corruptAccount))
|
||||
return
|
||||
}
|
||||
|
||||
fetch(externalID: webFeed.externalID) { result in
|
||||
fetch(externalID: feed.externalID) { result in
|
||||
switch result {
|
||||
case .success(let record):
|
||||
|
||||
if let containerExternalIDs = record[CloudKitWebFeed.Fields.containerExternalIDs] as? [String] {
|
||||
if let containerExternalIDs = record[CloudKitFeed.Fields.containerExternalIDs] as? [String] {
|
||||
var containerExternalIDSet = Set(containerExternalIDs)
|
||||
containerExternalIDSet.remove(fromContainerExternalID)
|
||||
|
||||
if containerExternalIDSet.isEmpty {
|
||||
self.delete(externalID: webFeed.externalID) { result in
|
||||
self.delete(externalID: feed.externalID) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
completion(.success(true))
|
||||
@@ -166,7 +166,7 @@ final class CloudKitAccountZone: CloudKitZone {
|
||||
|
||||
} else {
|
||||
|
||||
record[CloudKitWebFeed.Fields.containerExternalIDs] = Array(containerExternalIDSet)
|
||||
record[CloudKitFeed.Fields.containerExternalIDs] = Array(containerExternalIDSet)
|
||||
self.save(record) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
@@ -189,20 +189,20 @@ final class CloudKitAccountZone: CloudKitZone {
|
||||
}
|
||||
}
|
||||
|
||||
func moveWebFeed(_ webFeed: Feed, from: Container, to: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
func moveFeed(_ feed: Feed, from: Container, to: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
guard let fromContainerExternalID = from.externalID, let toContainerExternalID = to.externalID else {
|
||||
completion(.failure(CloudKitZoneError.corruptAccount))
|
||||
return
|
||||
}
|
||||
|
||||
fetch(externalID: webFeed.externalID) { result in
|
||||
fetch(externalID: feed.externalID) { result in
|
||||
switch result {
|
||||
case .success(let record):
|
||||
if let containerExternalIDs = record[CloudKitWebFeed.Fields.containerExternalIDs] as? [String] {
|
||||
if let containerExternalIDs = record[CloudKitFeed.Fields.containerExternalIDs] as? [String] {
|
||||
var containerExternalIDSet = Set(containerExternalIDs)
|
||||
containerExternalIDSet.remove(fromContainerExternalID)
|
||||
containerExternalIDSet.insert(toContainerExternalID)
|
||||
record[CloudKitWebFeed.Fields.containerExternalIDs] = Array(containerExternalIDSet)
|
||||
record[CloudKitFeed.Fields.containerExternalIDs] = Array(containerExternalIDSet)
|
||||
self.save(record, completion: completion)
|
||||
}
|
||||
case .failure(let error):
|
||||
@@ -211,19 +211,19 @@ final class CloudKitAccountZone: CloudKitZone {
|
||||
}
|
||||
}
|
||||
|
||||
func addWebFeed(_ webFeed: Feed, to: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
func addFeed(_ feed: Feed, to: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
guard let toContainerExternalID = to.externalID else {
|
||||
completion(.failure(CloudKitZoneError.corruptAccount))
|
||||
return
|
||||
}
|
||||
|
||||
fetch(externalID: webFeed.externalID) { result in
|
||||
fetch(externalID: feed.externalID) { result in
|
||||
switch result {
|
||||
case .success(let record):
|
||||
if let containerExternalIDs = record[CloudKitWebFeed.Fields.containerExternalIDs] as? [String] {
|
||||
if let containerExternalIDs = record[CloudKitFeed.Fields.containerExternalIDs] as? [String] {
|
||||
var containerExternalIDSet = Set(containerExternalIDs)
|
||||
containerExternalIDSet.insert(toContainerExternalID)
|
||||
record[CloudKitWebFeed.Fields.containerExternalIDs] = Array(containerExternalIDSet)
|
||||
record[CloudKitFeed.Fields.containerExternalIDs] = Array(containerExternalIDSet)
|
||||
self.save(record, completion: completion)
|
||||
}
|
||||
case .failure(let error):
|
||||
@@ -232,20 +232,20 @@ final class CloudKitAccountZone: CloudKitZone {
|
||||
}
|
||||
}
|
||||
|
||||
func findWebFeedExternalIDs(for folder: Folder, completion: @escaping (Result<[String], Error>) -> Void) {
|
||||
func findFeedExternalIDs(for folder: Folder, completion: @escaping (Result<[String], Error>) -> Void) {
|
||||
guard let folderExternalID = folder.externalID else {
|
||||
completion(.failure(CloudKitAccountZoneError.unknown))
|
||||
return
|
||||
}
|
||||
|
||||
let predicate = NSPredicate(format: "containerExternalIDs CONTAINS %@", folderExternalID)
|
||||
let ckQuery = CKQuery(recordType: CloudKitWebFeed.recordType, predicate: predicate)
|
||||
let ckQuery = CKQuery(recordType: CloudKitFeed.recordType, predicate: predicate)
|
||||
|
||||
query(ckQuery) { result in
|
||||
switch result {
|
||||
case .success(let records):
|
||||
let webFeedExternalIds = records.map { $0.externalID }
|
||||
completion(.success(webFeedExternalIds))
|
||||
let feedExternalIds = records.map { $0.externalID }
|
||||
completion(.success(feedExternalIds))
|
||||
case .failure(let error):
|
||||
completion(.failure(error))
|
||||
}
|
||||
@@ -322,16 +322,16 @@ final class CloudKitAccountZone: CloudKitZone {
|
||||
|
||||
private extension CloudKitAccountZone {
|
||||
|
||||
func newWebFeedCKRecord(feedSpecifier: RSOPMLFeedSpecifier, containerExternalID: String) -> CKRecord {
|
||||
let record = CKRecord(recordType: CloudKitWebFeed.recordType, recordID: generateRecordID())
|
||||
record[CloudKitWebFeed.Fields.url] = feedSpecifier.feedURL
|
||||
func newFeedCKRecord(feedSpecifier: RSOPMLFeedSpecifier, containerExternalID: String) -> CKRecord {
|
||||
let record = CKRecord(recordType: CloudKitFeed.recordType, recordID: generateRecordID())
|
||||
record[CloudKitFeed.Fields.url] = feedSpecifier.feedURL
|
||||
if let editedName = feedSpecifier.title {
|
||||
record[CloudKitWebFeed.Fields.editedName] = editedName
|
||||
record[CloudKitFeed.Fields.editedName] = editedName
|
||||
}
|
||||
if let homePageURL = feedSpecifier.homePageURL {
|
||||
record[CloudKitWebFeed.Fields.homePageURL] = homePageURL
|
||||
record[CloudKitFeed.Fields.homePageURL] = homePageURL
|
||||
}
|
||||
record[CloudKitWebFeed.Fields.containerExternalIDs] = [containerExternalID]
|
||||
record[CloudKitFeed.Fields.containerExternalIDs] = [containerExternalID]
|
||||
return record
|
||||
}
|
||||
|
||||
|
||||
@@ -15,9 +15,9 @@ import Articles
|
||||
|
||||
class CloudKitAcountZoneDelegate: CloudKitZoneDelegate {
|
||||
|
||||
private typealias UnclaimedWebFeed = (url: URL, name: String?, editedName: String?, homePageURL: String?, webFeedExternalID: String)
|
||||
private var newUnclaimedWebFeeds = [String: [UnclaimedWebFeed]]()
|
||||
private var existingUnclaimedWebFeeds = [String: [Feed]]()
|
||||
private typealias UnclaimedFeed = (url: URL, name: String?, editedName: String?, homePageURL: String?, feedExternalID: String)
|
||||
private var newUnclaimedFeeds = [String: [UnclaimedFeed]]()
|
||||
private var existingUnclaimedFeeds = [String: [Feed]]()
|
||||
|
||||
private var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "CloudKit")
|
||||
|
||||
@@ -34,8 +34,8 @@ class CloudKitAcountZoneDelegate: CloudKitZoneDelegate {
|
||||
func cloudKitDidModify(changed: [CKRecord], deleted: [CloudKitRecordKey], completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
for deletedRecordKey in deleted {
|
||||
switch deletedRecordKey.recordType {
|
||||
case CloudKitAccountZone.CloudKitWebFeed.recordType:
|
||||
removeWebFeed(deletedRecordKey.recordID.externalID)
|
||||
case CloudKitAccountZone.CloudKitFeed.recordType:
|
||||
removeFeed(deletedRecordKey.recordID.externalID)
|
||||
case CloudKitAccountZone.CloudKitContainer.recordType:
|
||||
removeContainer(deletedRecordKey.recordID.externalID)
|
||||
default:
|
||||
@@ -45,8 +45,8 @@ class CloudKitAcountZoneDelegate: CloudKitZoneDelegate {
|
||||
|
||||
for changedRecord in changed {
|
||||
switch changedRecord.recordType {
|
||||
case CloudKitAccountZone.CloudKitWebFeed.recordType:
|
||||
addOrUpdateWebFeed(changedRecord)
|
||||
case CloudKitAccountZone.CloudKitFeed.recordType:
|
||||
addOrUpdateFeed(changedRecord)
|
||||
case CloudKitAccountZone.CloudKitContainer.recordType:
|
||||
addOrUpdateContainer(changedRecord)
|
||||
default:
|
||||
@@ -57,36 +57,36 @@ class CloudKitAcountZoneDelegate: CloudKitZoneDelegate {
|
||||
completion(.success(()))
|
||||
}
|
||||
|
||||
func addOrUpdateWebFeed(_ record: CKRecord) {
|
||||
func addOrUpdateFeed(_ record: CKRecord) {
|
||||
guard let account = account,
|
||||
let urlString = record[CloudKitAccountZone.CloudKitWebFeed.Fields.url] as? String,
|
||||
let containerExternalIDs = record[CloudKitAccountZone.CloudKitWebFeed.Fields.containerExternalIDs] as? [String],
|
||||
let urlString = record[CloudKitAccountZone.CloudKitFeed.Fields.url] as? String,
|
||||
let containerExternalIDs = record[CloudKitAccountZone.CloudKitFeed.Fields.containerExternalIDs] as? [String],
|
||||
let url = URL(string: urlString) else {
|
||||
return
|
||||
}
|
||||
|
||||
let name = record[CloudKitAccountZone.CloudKitWebFeed.Fields.name] as? String
|
||||
let editedName = record[CloudKitAccountZone.CloudKitWebFeed.Fields.editedName] as? String
|
||||
let homePageURL = record[CloudKitAccountZone.CloudKitWebFeed.Fields.homePageURL] as? String
|
||||
let name = record[CloudKitAccountZone.CloudKitFeed.Fields.name] as? String
|
||||
let editedName = record[CloudKitAccountZone.CloudKitFeed.Fields.editedName] as? String
|
||||
let homePageURL = record[CloudKitAccountZone.CloudKitFeed.Fields.homePageURL] as? String
|
||||
|
||||
if let webFeed = account.existingWebFeed(withExternalID: record.externalID) {
|
||||
updateWebFeed(webFeed, name: name, editedName: editedName, homePageURL: homePageURL, containerExternalIDs: containerExternalIDs)
|
||||
if let feed = account.existingFeed(withExternalID: record.externalID) {
|
||||
updateFeed(feed, name: name, editedName: editedName, homePageURL: homePageURL, containerExternalIDs: containerExternalIDs)
|
||||
} else {
|
||||
for containerExternalID in containerExternalIDs {
|
||||
if let container = account.existingContainer(withExternalID: containerExternalID) {
|
||||
createWebFeedIfNecessary(url: url, name: name, editedName: editedName, homePageURL: homePageURL, webFeedExternalID: record.externalID, container: container)
|
||||
createFeedIfNecessary(url: url, name: name, editedName: editedName, homePageURL: homePageURL, feedExternalID: record.externalID, container: container)
|
||||
} else {
|
||||
addNewUnclaimedWebFeed(url: url, name: name, editedName: editedName, homePageURL: homePageURL, webFeedExternalID: record.externalID, containerExternalID: containerExternalID)
|
||||
addNewUnclaimedFeed(url: url, name: name, editedName: editedName, homePageURL: homePageURL, feedExternalID: record.externalID, containerExternalID: containerExternalID)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func removeWebFeed(_ externalID: String) {
|
||||
if let webFeed = account?.existingWebFeed(withExternalID: externalID), let containers = account?.existingContainers(withWebFeed: webFeed) {
|
||||
func removeFeed(_ externalID: String) {
|
||||
if let feed = account?.existingFeed(withExternalID: externalID), let containers = account?.existingContainers(withFeed: feed) {
|
||||
containers.forEach {
|
||||
webFeed.dropConditionalGetInfo()
|
||||
$0.removeWebFeed(webFeed)
|
||||
feed.dropConditionalGetInfo()
|
||||
$0.removeFeed(feed)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -109,24 +109,24 @@ class CloudKitAcountZoneDelegate: CloudKitZoneDelegate {
|
||||
|
||||
guard let container = folder, let containerExternalID = container.externalID else { return }
|
||||
|
||||
if let newUnclaimedWebFeeds = newUnclaimedWebFeeds[containerExternalID] {
|
||||
for newUnclaimedWebFeed in newUnclaimedWebFeeds {
|
||||
createWebFeedIfNecessary(url: newUnclaimedWebFeed.url,
|
||||
name: newUnclaimedWebFeed.name,
|
||||
editedName: newUnclaimedWebFeed.editedName,
|
||||
homePageURL: newUnclaimedWebFeed.homePageURL,
|
||||
webFeedExternalID: newUnclaimedWebFeed.webFeedExternalID,
|
||||
if let newUnclaimedFeeds = newUnclaimedFeeds[containerExternalID] {
|
||||
for newUnclaimedFeed in newUnclaimedFeeds {
|
||||
createFeedIfNecessary(url: newUnclaimedFeed.url,
|
||||
name: newUnclaimedFeed.name,
|
||||
editedName: newUnclaimedFeed.editedName,
|
||||
homePageURL: newUnclaimedFeed.homePageURL,
|
||||
feedExternalID: newUnclaimedFeed.feedExternalID,
|
||||
container: container)
|
||||
}
|
||||
|
||||
self.newUnclaimedWebFeeds.removeValue(forKey: containerExternalID)
|
||||
self.newUnclaimedFeeds.removeValue(forKey: containerExternalID)
|
||||
}
|
||||
|
||||
if let existingUnclaimedWebFeeds = existingUnclaimedWebFeeds[containerExternalID] {
|
||||
for existingUnclaimedWebFeed in existingUnclaimedWebFeeds {
|
||||
container.addWebFeed(existingUnclaimedWebFeed)
|
||||
if let existingUnclaimedFeeds = existingUnclaimedFeeds[containerExternalID] {
|
||||
for existingUnclaimedFeed in existingUnclaimedFeeds {
|
||||
container.addFeed(existingUnclaimedFeed)
|
||||
}
|
||||
self.existingUnclaimedWebFeeds.removeValue(forKey: containerExternalID)
|
||||
self.existingUnclaimedFeeds.removeValue(forKey: containerExternalID)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,14 +140,14 @@ class CloudKitAcountZoneDelegate: CloudKitZoneDelegate {
|
||||
|
||||
private extension CloudKitAcountZoneDelegate {
|
||||
|
||||
func updateWebFeed(_ webFeed: Feed, name: String?, editedName: String?, homePageURL: String?, containerExternalIDs: [String]) {
|
||||
func updateFeed(_ feed: Feed, name: String?, editedName: String?, homePageURL: String?, containerExternalIDs: [String]) {
|
||||
guard let account = account else { return }
|
||||
|
||||
webFeed.name = name
|
||||
webFeed.editedName = editedName
|
||||
webFeed.homePageURL = homePageURL
|
||||
feed.name = name
|
||||
feed.editedName = editedName
|
||||
feed.homePageURL = homePageURL
|
||||
|
||||
let existingContainers = account.existingContainers(withWebFeed: webFeed)
|
||||
let existingContainers = account.existingContainers(withFeed: feed)
|
||||
let existingContainerExternalIds = existingContainers.compactMap { $0.externalID }
|
||||
|
||||
let diff = containerExternalIDs.difference(from: existingContainerExternalIds)
|
||||
@@ -156,50 +156,50 @@ private extension CloudKitAcountZoneDelegate {
|
||||
switch change {
|
||||
case .remove(_, let externalID, _):
|
||||
if let container = existingContainers.first(where: { $0.externalID == externalID }) {
|
||||
container.removeWebFeed(webFeed)
|
||||
container.removeFeed(feed)
|
||||
}
|
||||
case .insert(_, let externalID, _):
|
||||
if let container = account.existingContainer(withExternalID: externalID) {
|
||||
container.addWebFeed(webFeed)
|
||||
container.addFeed(feed)
|
||||
} else {
|
||||
addExistingUnclaimedWebFeed(webFeed, containerExternalID: externalID)
|
||||
addExistingUnclaimedFeed(feed, containerExternalID: externalID)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func createWebFeedIfNecessary(url: URL, name: String?, editedName: String?, homePageURL: String?, webFeedExternalID: String, container: Container) {
|
||||
func createFeedIfNecessary(url: URL, name: String?, editedName: String?, homePageURL: String?, feedExternalID: String, container: Container) {
|
||||
guard let account = account else { return }
|
||||
|
||||
if account.existingWebFeed(withExternalID: webFeedExternalID) != nil {
|
||||
if account.existingFeed(withExternalID: feedExternalID) != nil {
|
||||
return
|
||||
}
|
||||
|
||||
let webFeed = account.createWebFeed(with: name, url: url.absoluteString, webFeedID: url.absoluteString, homePageURL: homePageURL)
|
||||
webFeed.editedName = editedName
|
||||
webFeed.externalID = webFeedExternalID
|
||||
container.addWebFeed(webFeed)
|
||||
let feed = account.createFeed(with: name, url: url.absoluteString, feedID: url.absoluteString, homePageURL: homePageURL)
|
||||
feed.editedName = editedName
|
||||
feed.externalID = feedExternalID
|
||||
container.addFeed(feed)
|
||||
}
|
||||
|
||||
func addNewUnclaimedWebFeed(url: URL, name: String?, editedName: String?, homePageURL: String?, webFeedExternalID: String, containerExternalID: String) {
|
||||
if var unclaimedWebFeeds = self.newUnclaimedWebFeeds[containerExternalID] {
|
||||
unclaimedWebFeeds.append(UnclaimedWebFeed(url: url, name: name, editedName: editedName, homePageURL: homePageURL, webFeedExternalID: webFeedExternalID))
|
||||
self.newUnclaimedWebFeeds[containerExternalID] = unclaimedWebFeeds
|
||||
func addNewUnclaimedFeed(url: URL, name: String?, editedName: String?, homePageURL: String?, feedExternalID: String, containerExternalID: String) {
|
||||
if var unclaimedFeeds = self.newUnclaimedFeeds[containerExternalID] {
|
||||
unclaimedFeeds.append(UnclaimedFeed(url: url, name: name, editedName: editedName, homePageURL: homePageURL, feedExternalID: feedExternalID))
|
||||
self.newUnclaimedFeeds[containerExternalID] = unclaimedFeeds
|
||||
} else {
|
||||
var unclaimedWebFeeds = [UnclaimedWebFeed]()
|
||||
unclaimedWebFeeds.append(UnclaimedWebFeed(url: url, name: name, editedName: editedName, homePageURL: homePageURL, webFeedExternalID: webFeedExternalID))
|
||||
self.newUnclaimedWebFeeds[containerExternalID] = unclaimedWebFeeds
|
||||
var unclaimedFeeds = [UnclaimedFeed]()
|
||||
unclaimedFeeds.append(UnclaimedFeed(url: url, name: name, editedName: editedName, homePageURL: homePageURL, feedExternalID: feedExternalID))
|
||||
self.newUnclaimedFeeds[containerExternalID] = unclaimedFeeds
|
||||
}
|
||||
}
|
||||
|
||||
func addExistingUnclaimedWebFeed(_ webFeed: Feed, containerExternalID: String) {
|
||||
if var unclaimedWebFeeds = self.existingUnclaimedWebFeeds[containerExternalID] {
|
||||
unclaimedWebFeeds.append(webFeed)
|
||||
self.existingUnclaimedWebFeeds[containerExternalID] = unclaimedWebFeeds
|
||||
func addExistingUnclaimedFeed(_ feed: Feed, containerExternalID: String) {
|
||||
if var unclaimedFeeds = self.existingUnclaimedFeeds[containerExternalID] {
|
||||
unclaimedFeeds.append(feed)
|
||||
self.existingUnclaimedFeeds[containerExternalID] = unclaimedFeeds
|
||||
} else {
|
||||
var unclaimedWebFeeds = [Feed]()
|
||||
unclaimedWebFeeds.append(webFeed)
|
||||
self.existingUnclaimedWebFeeds[containerExternalID] = unclaimedWebFeeds
|
||||
var unclaimedFeeds = [Feed]()
|
||||
unclaimedFeeds.append(feed)
|
||||
self.existingUnclaimedFeeds[containerExternalID] = unclaimedFeeds
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ final class CloudKitArticlesZone: CloudKitZone {
|
||||
static let recordType = "Article"
|
||||
struct Fields {
|
||||
static let articleStatus = "articleStatus"
|
||||
static let webFeedURL = "webFeedURL"
|
||||
static let feedURL = "webFeedURL"
|
||||
static let uniqueID = "uniqueID"
|
||||
static let title = "title"
|
||||
static let contentHTML = "contentHTML"
|
||||
@@ -51,7 +51,7 @@ final class CloudKitArticlesZone: CloudKitZone {
|
||||
struct CloudKitArticleStatus {
|
||||
static let recordType = "ArticleStatus"
|
||||
struct Fields {
|
||||
static let webFeedExternalID = "webFeedExternalID"
|
||||
static let feedExternalID = "webFeedExternalID"
|
||||
static let read = "read"
|
||||
static let starred = "starred"
|
||||
}
|
||||
@@ -106,8 +106,8 @@ final class CloudKitArticlesZone: CloudKitZone {
|
||||
}
|
||||
}
|
||||
|
||||
func deleteArticles(_ webFeedExternalID: String, completion: @escaping ((Result<Void, Error>) -> Void)) {
|
||||
let predicate = NSPredicate(format: "webFeedExternalID = %@", webFeedExternalID)
|
||||
func deleteArticles(_ feedExternalID: String, completion: @escaping ((Result<Void, Error>) -> Void)) {
|
||||
let predicate = NSPredicate(format: "webFeedExternalID = %@", feedExternalID)
|
||||
let ckQuery = CKQuery(recordType: CloudKitArticleStatus.recordType, predicate: predicate)
|
||||
delete(ckQuery: ckQuery, completion: completion)
|
||||
}
|
||||
@@ -190,8 +190,8 @@ private extension CloudKitArticlesZone {
|
||||
func makeStatusRecord(_ article: Article) -> CKRecord {
|
||||
let recordID = CKRecord.ID(recordName: statusID(article.articleID), zoneID: zoneID)
|
||||
let record = CKRecord(recordType: CloudKitArticleStatus.recordType, recordID: recordID)
|
||||
if let webFeedExternalID = article.webFeed?.externalID {
|
||||
record[CloudKitArticleStatus.Fields.webFeedExternalID] = webFeedExternalID
|
||||
if let feedExternalID = article.feed?.externalID {
|
||||
record[CloudKitArticleStatus.Fields.feedExternalID] = feedExternalID
|
||||
}
|
||||
record[CloudKitArticleStatus.Fields.read] = article.status.read ? "1" : "0"
|
||||
record[CloudKitArticleStatus.Fields.starred] = article.status.starred ? "1" : "0"
|
||||
@@ -202,8 +202,8 @@ private extension CloudKitArticlesZone {
|
||||
let recordID = CKRecord.ID(recordName: statusID(statusUpdate.articleID), zoneID: zoneID)
|
||||
let record = CKRecord(recordType: CloudKitArticleStatus.recordType, recordID: recordID)
|
||||
|
||||
if let webFeedExternalID = statusUpdate.article?.webFeed?.externalID {
|
||||
record[CloudKitArticleStatus.Fields.webFeedExternalID] = webFeedExternalID
|
||||
if let feedExternalID = statusUpdate.article?.feed?.externalID {
|
||||
record[CloudKitArticleStatus.Fields.feedExternalID] = feedExternalID
|
||||
}
|
||||
|
||||
record[CloudKitArticleStatus.Fields.read] = statusUpdate.isRead ? "1" : "0"
|
||||
@@ -218,7 +218,7 @@ private extension CloudKitArticlesZone {
|
||||
|
||||
let articleStatusRecordID = CKRecord.ID(recordName: statusID(article.articleID), zoneID: zoneID)
|
||||
record[CloudKitArticle.Fields.articleStatus] = CKRecord.Reference(recordID: articleStatusRecordID, action: .deleteSelf)
|
||||
record[CloudKitArticle.Fields.webFeedURL] = article.webFeed?.url
|
||||
record[CloudKitArticle.Fields.feedURL] = article.feed?.url
|
||||
record[CloudKitArticle.Fields.uniqueID] = article.uniqueID
|
||||
record[CloudKitArticle.Fields.title] = article.title
|
||||
record[CloudKitArticle.Fields.contentHTML] = article.contentHTML
|
||||
|
||||
@@ -137,12 +137,12 @@ private extension CloudKitArticlesZoneDelegate {
|
||||
group.enter()
|
||||
compressionQueue.async {
|
||||
let parsedItems = records.compactMap { self.makeParsedItem($0) }
|
||||
let webFeedIDsAndItems = Dictionary(grouping: parsedItems, by: { item in item.feedURL } ).mapValues { Set($0) }
|
||||
let feedIDsAndItems = Dictionary(grouping: parsedItems, by: { item in item.feedURL } ).mapValues { Set($0) }
|
||||
|
||||
DispatchQueue.main.async {
|
||||
for (webFeedID, parsedItems) in webFeedIDsAndItems {
|
||||
for (feedID, parsedItems) in feedIDsAndItems {
|
||||
group.enter()
|
||||
self.account?.update(webFeedID, with: parsedItems, deleteOlder: false) { result in
|
||||
self.account?.update(feedID, with: parsedItems, deleteOlder: false) { result in
|
||||
switch result {
|
||||
case .success(let articleChanges):
|
||||
guard let deletes = articleChanges.deletedArticles, !deletes.isEmpty else {
|
||||
@@ -196,7 +196,7 @@ private extension CloudKitArticlesZoneDelegate {
|
||||
}
|
||||
|
||||
guard let uniqueID = articleRecord[CloudKitArticlesZone.CloudKitArticle.Fields.uniqueID] as? String,
|
||||
let webFeedURL = articleRecord[CloudKitArticlesZone.CloudKitArticle.Fields.webFeedURL] as? String else {
|
||||
let feedURL = articleRecord[CloudKitArticlesZone.CloudKitArticle.Fields.feedURL] as? String else {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -216,7 +216,7 @@ private extension CloudKitArticlesZoneDelegate {
|
||||
|
||||
let parsedItem = ParsedItem(syncServiceID: nil,
|
||||
uniqueID: uniqueID,
|
||||
feedURL: webFeedURL,
|
||||
feedURL: feedURL,
|
||||
url: articleRecord[CloudKitArticlesZone.CloudKitArticle.Fields.url] as? String,
|
||||
externalURL: articleRecord[CloudKitArticlesZone.CloudKitArticle.Fields.externalURL] as? String,
|
||||
title: articleRecord[CloudKitArticlesZone.CloudKitArticle.Fields.title] as? String,
|
||||
|
||||
@@ -171,7 +171,7 @@ private extension CloudKitSendStatusOperation {
|
||||
|
||||
func processAccountError(_ account: Account, _ error: Error) {
|
||||
if case CloudKitZoneError.userDeletedZone = error {
|
||||
account.removeFeeds(account.topLevelWebFeeds)
|
||||
account.removeFeeds(account.topLevelFeeds)
|
||||
for folder in account.folders ?? Set<Folder>() {
|
||||
account.removeFolder(folder)
|
||||
}
|
||||
|
||||
@@ -19,27 +19,27 @@ extension Notification.Name {
|
||||
public protocol Container: AnyObject, ContainerIdentifiable {
|
||||
|
||||
var account: Account? { get }
|
||||
var topLevelWebFeeds: Set<Feed> { get set }
|
||||
var topLevelFeeds: Set<Feed> { get set }
|
||||
var folders: Set<Folder>? { get set }
|
||||
var externalID: String? { get set }
|
||||
|
||||
func hasAtLeastOneWebFeed() -> Bool
|
||||
func hasAtLeastOneFeed() -> Bool
|
||||
func objectIsChild(_ object: AnyObject) -> Bool
|
||||
|
||||
func hasChildFolder(with: String) -> Bool
|
||||
func childFolder(with: String) -> Folder?
|
||||
|
||||
func removeWebFeed(_ webFeed: Feed)
|
||||
func addWebFeed(_ webFeed: Feed)
|
||||
func removeFeed(_ feed: Feed)
|
||||
func addFeed(_ feed: Feed)
|
||||
|
||||
//Recursive — checks subfolders
|
||||
func flattenedWebFeeds() -> Set<Feed>
|
||||
func has(_ webFeed: Feed) -> Bool
|
||||
func hasWebFeed(with webFeedID: String) -> Bool
|
||||
func hasWebFeed(withURL url: String) -> Bool
|
||||
func existingWebFeed(withWebFeedID: String) -> Feed?
|
||||
func existingWebFeed(withURL url: String) -> Feed?
|
||||
func existingWebFeed(withExternalID externalID: String) -> Feed?
|
||||
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 existingFeed(withExternalID externalID: String) -> Feed?
|
||||
func existingFolder(with name: String) -> Folder?
|
||||
func existingFolder(withID: Int) -> Folder?
|
||||
|
||||
@@ -48,8 +48,8 @@ public protocol Container: AnyObject, ContainerIdentifiable {
|
||||
|
||||
public extension Container {
|
||||
|
||||
func hasAtLeastOneWebFeed() -> Bool {
|
||||
return topLevelWebFeeds.count > 0
|
||||
func hasAtLeastOneFeed() -> Bool {
|
||||
return topLevelFeeds.count > 0
|
||||
}
|
||||
|
||||
func hasChildFolder(with name: String) -> Bool {
|
||||
@@ -70,7 +70,7 @@ public extension Container {
|
||||
|
||||
func objectIsChild(_ object: AnyObject) -> Bool {
|
||||
if let feed = object as? Feed {
|
||||
return topLevelWebFeeds.contains(feed)
|
||||
return topLevelFeeds.contains(feed)
|
||||
}
|
||||
if let folder = object as? Folder {
|
||||
return folders?.contains(folder) ?? false
|
||||
@@ -78,40 +78,40 @@ public extension Container {
|
||||
return false
|
||||
}
|
||||
|
||||
func flattenedWebFeeds() -> Set<Feed> {
|
||||
func flattenedFeeds() -> Set<Feed> {
|
||||
var feeds = Set<Feed>()
|
||||
feeds.formUnion(topLevelWebFeeds)
|
||||
feeds.formUnion(topLevelFeeds)
|
||||
if let folders = folders {
|
||||
for folder in folders {
|
||||
feeds.formUnion(folder.flattenedWebFeeds())
|
||||
feeds.formUnion(folder.flattenedFeeds())
|
||||
}
|
||||
}
|
||||
return feeds
|
||||
}
|
||||
|
||||
func hasWebFeed(with webFeedID: String) -> Bool {
|
||||
return existingWebFeed(withWebFeedID: webFeedID) != nil
|
||||
func hasFeed(with feedID: String) -> Bool {
|
||||
return existingFeed(withFeedID: feedID) != nil
|
||||
}
|
||||
|
||||
func hasWebFeed(withURL url: String) -> Bool {
|
||||
return existingWebFeed(withURL: url) != nil
|
||||
func hasFeed(withURL url: String) -> Bool {
|
||||
return existingFeed(withURL: url) != nil
|
||||
}
|
||||
|
||||
func has(_ webFeed: Feed) -> Bool {
|
||||
return flattenedWebFeeds().contains(webFeed)
|
||||
func has(_ feed: Feed) -> Bool {
|
||||
return flattenedFeeds().contains(feed)
|
||||
}
|
||||
|
||||
func existingWebFeed(withWebFeedID webFeedID: String) -> Feed? {
|
||||
for feed in flattenedWebFeeds() {
|
||||
if feed.webFeedID == webFeedID {
|
||||
func existingFeed(withFeedID feedID: String) -> Feed? {
|
||||
for feed in flattenedFeeds() {
|
||||
if feed.feedID == feedID {
|
||||
return feed
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func existingWebFeed(withURL url: String) -> Feed? {
|
||||
for feed in flattenedWebFeeds() {
|
||||
func existingFeed(withURL url: String) -> Feed? {
|
||||
for feed in flattenedFeeds() {
|
||||
if feed.url == url {
|
||||
return feed
|
||||
}
|
||||
@@ -119,8 +119,8 @@ public extension Container {
|
||||
return nil
|
||||
}
|
||||
|
||||
func existingWebFeed(withExternalID externalID: String) -> Feed? {
|
||||
for feed in flattenedWebFeeds() {
|
||||
func existingFeed(withExternalID externalID: String) -> Feed? {
|
||||
for feed in flattenedFeeds() {
|
||||
if feed.externalID == externalID {
|
||||
return feed
|
||||
}
|
||||
|
||||
@@ -11,14 +11,14 @@ import Articles
|
||||
import RSParser
|
||||
|
||||
public extension Notification.Name {
|
||||
static let WebFeedSettingDidChange = Notification.Name(rawValue: "FeedSettingDidChangeNotification")
|
||||
static let FeedSettingDidChange = Notification.Name(rawValue: "FeedSettingDidChangeNotification")
|
||||
}
|
||||
|
||||
public extension Feed {
|
||||
|
||||
static let WebFeedSettingUserInfoKey = "feedSetting"
|
||||
static let FeedSettingUserInfoKey = "feedSetting"
|
||||
|
||||
struct WebFeedSettingKey {
|
||||
struct FeedSettingKey {
|
||||
public static let homePageURL = "homePageURL"
|
||||
public static let iconURL = "iconURL"
|
||||
public static let faviconURL = "faviconURL"
|
||||
@@ -40,9 +40,9 @@ extension Feed {
|
||||
authors = Author.authorsWithParsedAuthors(parsedFeed.authors)
|
||||
}
|
||||
|
||||
func postFeedSettingDidChangeNotification(_ codingKey: WebFeedMetadata.CodingKeys) {
|
||||
let userInfo = [Feed.WebFeedSettingUserInfoKey: codingKey.stringValue]
|
||||
NotificationCenter.default.post(name: .WebFeedSettingDidChange, object: self, userInfo: userInfo)
|
||||
func postFeedSettingDidChangeNotification(_ codingKey: FeedMetadata.CodingKeys) {
|
||||
let userInfo = [Feed.FeedSettingUserInfoKey: codingKey.stringValue]
|
||||
NotificationCenter.default.post(name: .FeedSettingDidChange, object: self, userInfo: userInfo)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,8 +56,8 @@ public extension Article {
|
||||
return manager.existingAccount(with: accountID)
|
||||
}
|
||||
|
||||
var webFeed: Feed? {
|
||||
return account?.existingWebFeed(withWebFeedID: webFeedID)
|
||||
var feed: Feed? {
|
||||
return account?.existingFeed(withFeedID: feedID)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// WebFeed.swift
|
||||
// Feed.swift
|
||||
// NetNewsWire
|
||||
//
|
||||
// Created by Brent Simmons on 7/1/17.
|
||||
@@ -22,18 +22,18 @@ public final class Feed: SidebarItem, Renamable, Hashable {
|
||||
assertionFailure("Expected feed.account, but got nil.")
|
||||
return nil
|
||||
}
|
||||
return SidebarItemIdentifier.webFeed(accountID, webFeedID)
|
||||
return SidebarItemIdentifier.feed(accountID, feedID)
|
||||
}
|
||||
|
||||
public weak var account: Account?
|
||||
public let url: String
|
||||
|
||||
public var webFeedID: String {
|
||||
public var feedID: String {
|
||||
get {
|
||||
return metadata.webFeedID
|
||||
return metadata.feedID
|
||||
}
|
||||
set {
|
||||
metadata.webFeedID = newValue
|
||||
metadata.feedID = newValue
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,7 +203,7 @@ public final class Feed: SidebarItem, Renamable, Hashable {
|
||||
|
||||
public func rename(to newName: String, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
guard let account = account else { return }
|
||||
account.renameWebFeed(self, to: newName, completion: completion)
|
||||
account.renameFeed(self, to: newName, completion: completion)
|
||||
}
|
||||
|
||||
// MARK: - UnreadCountProvider
|
||||
@@ -238,7 +238,7 @@ public final class Feed: SidebarItem, Renamable, Hashable {
|
||||
#endif
|
||||
}
|
||||
|
||||
var metadata: WebFeedMetadata
|
||||
var metadata: FeedMetadata
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
@@ -246,7 +246,7 @@ public final class Feed: SidebarItem, Renamable, Hashable {
|
||||
|
||||
// MARK: - Init
|
||||
|
||||
init(account: Account, url: String, metadata: WebFeedMetadata) {
|
||||
init(account: Account, url: String, metadata: FeedMetadata) {
|
||||
self.account = account
|
||||
self.accountID = account.accountID
|
||||
self.url = url
|
||||
@@ -264,13 +264,13 @@ public final class Feed: SidebarItem, Renamable, Hashable {
|
||||
// MARK: - Hashable
|
||||
|
||||
public func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(webFeedID)
|
||||
hasher.combine(feedID)
|
||||
}
|
||||
|
||||
// MARK: - Equatable
|
||||
|
||||
public class func ==(lhs: Feed, rhs: Feed) -> Bool {
|
||||
return lhs.webFeedID == rhs.webFeedID && lhs.accountID == rhs.accountID
|
||||
return lhs.feedID == rhs.feedID && lhs.accountID == rhs.accountID
|
||||
}
|
||||
}
|
||||
|
||||
@@ -306,16 +306,16 @@ extension Feed: OPMLRepresentable {
|
||||
|
||||
extension Set where Element == Feed {
|
||||
|
||||
func webFeedIDs() -> Set<String> {
|
||||
return Set<String>(map { $0.webFeedID })
|
||||
func feedIDs() -> Set<String> {
|
||||
return Set<String>(map { $0.feedID })
|
||||
}
|
||||
|
||||
func sorted() -> Array<Feed> {
|
||||
return sorted(by: { (webFeed1, webFeed2) -> Bool in
|
||||
if webFeed1.nameForDisplay.localizedStandardCompare(webFeed2.nameForDisplay) == .orderedSame {
|
||||
return webFeed1.url < webFeed2.url
|
||||
return sorted(by: { (feed1, feed2) -> Bool in
|
||||
if feed1.nameForDisplay.localizedStandardCompare(feed2.nameForDisplay) == .orderedSame {
|
||||
return feed1.url < feed2.url
|
||||
}
|
||||
return webFeed1.nameForDisplay.localizedStandardCompare(webFeed2.nameForDisplay) == .orderedAscending
|
||||
return feed1.nameForDisplay.localizedStandardCompare(feed2.nameForDisplay) == .orderedAscending
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
+11
-11
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// WebFeedMetadata.swift
|
||||
// FeedMetadata.swift
|
||||
// NetNewsWire
|
||||
//
|
||||
// Created by Brent Simmons on 3/12/19.
|
||||
@@ -10,14 +10,14 @@ import Foundation
|
||||
import RSWeb
|
||||
import Articles
|
||||
|
||||
protocol WebFeedMetadataDelegate: AnyObject {
|
||||
func valueDidChange(_ feedMetadata: WebFeedMetadata, key: WebFeedMetadata.CodingKeys)
|
||||
protocol FeedMetadataDelegate: AnyObject {
|
||||
func valueDidChange(_ feedMetadata: FeedMetadata, key: FeedMetadata.CodingKeys)
|
||||
}
|
||||
|
||||
final class WebFeedMetadata: Codable {
|
||||
final class FeedMetadata: Codable {
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case webFeedID = "feedID"
|
||||
case feedID = "feedID"
|
||||
case homePageURL
|
||||
case iconURL
|
||||
case faviconURL
|
||||
@@ -32,10 +32,10 @@ final class WebFeedMetadata: Codable {
|
||||
case folderRelationship
|
||||
}
|
||||
|
||||
var webFeedID: String {
|
||||
var feedID: String {
|
||||
didSet {
|
||||
if webFeedID != oldValue {
|
||||
valueDidChange(.webFeedID)
|
||||
if feedID != oldValue {
|
||||
valueDidChange(.feedID)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -137,10 +137,10 @@ final class WebFeedMetadata: Codable {
|
||||
}
|
||||
}
|
||||
|
||||
weak var delegate: WebFeedMetadataDelegate?
|
||||
weak var delegate: FeedMetadataDelegate?
|
||||
|
||||
init(webFeedID: String) {
|
||||
self.webFeedID = webFeedID
|
||||
init(feedID: String) {
|
||||
self.feedID = feedID
|
||||
}
|
||||
|
||||
func valueDidChange(_ key: CodingKeys) {
|
||||
+10
-10
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// WebFeedMetadataFile.swift
|
||||
// FeedMetadataFile.swift
|
||||
// Account
|
||||
//
|
||||
// Created by Maurice Parker on 9/13/19.
|
||||
@@ -10,9 +10,9 @@ import Foundation
|
||||
import os.log
|
||||
import RSCore
|
||||
|
||||
final class WebFeedMetadataFile {
|
||||
final class FeedMetadataFile {
|
||||
|
||||
private var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "webFeedMetadataFile")
|
||||
private var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "feedMetadataFile")
|
||||
|
||||
private let fileURL: URL
|
||||
private let account: Account
|
||||
@@ -36,9 +36,9 @@ final class WebFeedMetadataFile {
|
||||
func load() {
|
||||
if let fileData = try? Data(contentsOf: fileURL) {
|
||||
let decoder = PropertyListDecoder()
|
||||
account.webFeedMetadata = (try? decoder.decode(Account.WebFeedMetadataDictionary.self, from: fileData)) ?? Account.WebFeedMetadataDictionary()
|
||||
account.feedMetadata = (try? decoder.decode(Account.FeedMetadataDictionary.self, from: fileData)) ?? Account.FeedMetadataDictionary()
|
||||
}
|
||||
account.webFeedMetadata.values.forEach { $0.delegate = account }
|
||||
account.feedMetadata.values.forEach { $0.delegate = account }
|
||||
}
|
||||
|
||||
func save() {
|
||||
@@ -59,7 +59,7 @@ final class WebFeedMetadataFile {
|
||||
|
||||
}
|
||||
|
||||
private extension WebFeedMetadataFile {
|
||||
private extension FeedMetadataFile {
|
||||
|
||||
func queueSaveToDiskIfNeeded() {
|
||||
saveQueue.add(self, #selector(saveToDiskIfNeeded))
|
||||
@@ -72,10 +72,10 @@ private extension WebFeedMetadataFile {
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -357,7 +357,7 @@ final class FeedbinAPICaller: NSObject {
|
||||
|
||||
}
|
||||
|
||||
func createTagging(webFeedID: Int, name: String, completion: @escaping (Result<Int, Error>) -> Void) {
|
||||
func createTagging(feedID: Int, name: String, completion: @escaping (Result<Int, Error>) -> Void) {
|
||||
|
||||
let callURL = feedbinBaseURL.appendingPathComponent("taggings.json")
|
||||
var request = URLRequest(url: callURL, credentials: credentials)
|
||||
@@ -365,7 +365,7 @@ final class FeedbinAPICaller: NSObject {
|
||||
|
||||
let payload: Data
|
||||
do {
|
||||
payload = try JSONEncoder().encode(FeedbinCreateTagging(feedID: webFeedID, name: name))
|
||||
payload = try JSONEncoder().encode(FeedbinCreateTagging(feedID: feedID, name: name))
|
||||
} catch {
|
||||
completion(.failure(error))
|
||||
return
|
||||
|
||||
@@ -300,7 +300,7 @@ final class FeedbinAccountDelegate: AccountDelegate {
|
||||
|
||||
func renameFolder(for account: Account, with folder: Folder, to name: String, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
|
||||
guard folder.hasAtLeastOneWebFeed() else {
|
||||
guard folder.hasAtLeastOneFeed() else {
|
||||
folder.name = name
|
||||
return
|
||||
}
|
||||
@@ -328,7 +328,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.hasAtLeastOneWebFeed() else {
|
||||
guard folder.hasAtLeastOneFeed() else {
|
||||
account.removeFolder(folder)
|
||||
completion(.success(()))
|
||||
return
|
||||
@@ -336,7 +336,7 @@ final class FeedbinAccountDelegate: AccountDelegate {
|
||||
|
||||
let group = DispatchGroup()
|
||||
|
||||
for feed in folder.topLevelWebFeeds {
|
||||
for feed in folder.topLevelFeeds {
|
||||
|
||||
if feed.folderRelationship?.count ?? 0 > 1 {
|
||||
|
||||
@@ -368,7 +368,7 @@ final class FeedbinAccountDelegate: AccountDelegate {
|
||||
switch result {
|
||||
case .success:
|
||||
DispatchQueue.main.async {
|
||||
account.clearWebFeedMetadata(feed)
|
||||
account.clearFeedMetadata(feed)
|
||||
}
|
||||
case .failure(let error):
|
||||
os_log(.error, log: self.log, "Remove feed error: %@.", error.localizedDescription)
|
||||
@@ -388,7 +388,7 @@ final class FeedbinAccountDelegate: AccountDelegate {
|
||||
|
||||
}
|
||||
|
||||
func createWebFeed(for account: Account, url: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result<Feed, Error>) -> Void) {
|
||||
func createFeed(for account: Account, url: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result<Feed, Error>) -> Void) {
|
||||
|
||||
refreshProgress.addToNumberOfTasksAndRemaining(1)
|
||||
caller.createSubscription(url: url) { result in
|
||||
@@ -420,7 +420,7 @@ final class FeedbinAccountDelegate: AccountDelegate {
|
||||
|
||||
}
|
||||
|
||||
func renameWebFeed(for account: Account, with feed: Feed, to name: String, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
func renameFeed(for account: Account, with feed: Feed, to name: String, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
|
||||
// This error should never happen
|
||||
guard let subscriptionID = feed.externalID else {
|
||||
@@ -447,7 +447,7 @@ final class FeedbinAccountDelegate: AccountDelegate {
|
||||
|
||||
}
|
||||
|
||||
func removeWebFeed(for account: Account, with feed: Feed, from 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) {
|
||||
if feed.folderRelationship?.count ?? 0 > 1 {
|
||||
deleteTagging(for: account, with: feed, from: container, completion: completion)
|
||||
} else {
|
||||
@@ -455,14 +455,14 @@ final class FeedbinAccountDelegate: AccountDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
func moveWebFeed(for account: Account, with feed: Feed, from: Container, to: 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) {
|
||||
if from is Account {
|
||||
addWebFeed(for: account, with: feed, to: to, completion: completion)
|
||||
addFeed(for: account, with: feed, to: to, completion: completion)
|
||||
} else {
|
||||
deleteTagging(for: account, with: feed, from: from) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
self.addWebFeed(for: account, with: feed, to: to, completion: completion)
|
||||
self.addFeed(for: account, with: feed, to: to, completion: completion)
|
||||
case .failure(let error):
|
||||
completion(.failure(error))
|
||||
}
|
||||
@@ -470,18 +470,18 @@ final class FeedbinAccountDelegate: AccountDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
func addWebFeed(for account: Account, with feed: Feed, to container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
func addFeed(for account: Account, with feed: Feed, to container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
|
||||
if let folder = container as? Folder, let webFeedID = Int(feed.webFeedID) {
|
||||
if let folder = container as? Folder, let feedID = Int(feed.feedID) {
|
||||
refreshProgress.addToNumberOfTasksAndRemaining(1)
|
||||
caller.createTagging(webFeedID: webFeedID, name: folder.name ?? "") { result in
|
||||
caller.createTagging(feedID: feedID, 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.removeWebFeed(feed)
|
||||
folder.addWebFeed(feed)
|
||||
account.removeFeed(feed)
|
||||
folder.addFeed(feed)
|
||||
completion(.success(()))
|
||||
}
|
||||
case .failure(let error):
|
||||
@@ -502,10 +502,10 @@ final class FeedbinAccountDelegate: AccountDelegate {
|
||||
|
||||
}
|
||||
|
||||
func restoreWebFeed(for account: Account, feed: Feed, container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
func restoreFeed(for account: Account, feed: Feed, container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
|
||||
if let existingFeed = account.existingWebFeed(withURL: feed.url) {
|
||||
account.addWebFeed(existingFeed, to: container) { result in
|
||||
if let existingFeed = account.existingFeed(withURL: feed.url) {
|
||||
account.addFeed(existingFeed, to: container) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
completion(.success(()))
|
||||
@@ -514,7 +514,7 @@ final class FeedbinAccountDelegate: AccountDelegate {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
createWebFeed(for: account, url: feed.url, name: feed.editedName, container: container, validateFeed: true) { result in
|
||||
createFeed(for: account, url: feed.url, name: feed.editedName, container: container, validateFeed: true) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
completion(.success(()))
|
||||
@@ -530,12 +530,12 @@ final class FeedbinAccountDelegate: AccountDelegate {
|
||||
|
||||
let group = DispatchGroup()
|
||||
|
||||
for feed in folder.topLevelWebFeeds {
|
||||
for feed in folder.topLevelFeeds {
|
||||
|
||||
folder.topLevelWebFeeds.remove(feed)
|
||||
folder.topLevelFeeds.remove(feed)
|
||||
|
||||
group.enter()
|
||||
restoreWebFeed(for: account, feed: feed, container: folder) { result in
|
||||
restoreFeed(for: account, feed: feed, container: folder) { result in
|
||||
group.leave()
|
||||
switch result {
|
||||
case .success:
|
||||
@@ -780,8 +780,8 @@ private extension FeedbinAccountDelegate {
|
||||
if let folders = account.folders {
|
||||
folders.forEach { folder in
|
||||
if !tagNames.contains(folder.name ?? "") {
|
||||
for feed in folder.topLevelWebFeeds {
|
||||
account.addWebFeed(feed)
|
||||
for feed in folder.topLevelFeeds {
|
||||
account.addFeed(feed)
|
||||
clearFolderRelationship(for: feed, withFolderName: folder.name ?? "")
|
||||
}
|
||||
account.removeFolder(folder)
|
||||
@@ -818,17 +818,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.topLevelWebFeeds {
|
||||
if !subFeedIds.contains(feed.webFeedID) {
|
||||
folder.removeWebFeed(feed)
|
||||
for feed in folder.topLevelFeeds {
|
||||
if !subFeedIds.contains(feed.feedID) {
|
||||
folder.removeFeed(feed)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for feed in account.topLevelWebFeeds {
|
||||
if !subFeedIds.contains(feed.webFeedID) {
|
||||
account.removeWebFeed(feed)
|
||||
for feed in account.topLevelFeeds {
|
||||
if !subFeedIds.contains(feed.feedID) {
|
||||
account.removeFeed(feed)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -838,7 +838,7 @@ private extension FeedbinAccountDelegate {
|
||||
|
||||
let subFeedId = String(subscription.feedID)
|
||||
|
||||
if let feed = account.existingWebFeed(withWebFeedID: subFeedId) {
|
||||
if let feed = account.existingFeed(withFeedID: subFeedId) {
|
||||
feed.name = subscription.name
|
||||
// If the name has been changed on the server remove the locally edited name
|
||||
feed.editedName = nil
|
||||
@@ -854,9 +854,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.createWebFeed(with: subscription.name, url: subscription.url, webFeedID: String(subscription.feedID), homePageURL: subscription.homePageURL)
|
||||
let feed = account.createFeed(with: subscription.name, url: subscription.url, feedID: String(subscription.feedID), homePageURL: subscription.homePageURL)
|
||||
feed.externalID = String(subscription.subscriptionID)
|
||||
account.addWebFeed(feed)
|
||||
account.addFeed(feed)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -888,25 +888,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.topLevelWebFeeds {
|
||||
if !taggingFeedIDs.contains(feed.webFeedID) {
|
||||
folder.removeWebFeed(feed)
|
||||
for feed in folder.topLevelFeeds {
|
||||
if !taggingFeedIDs.contains(feed.feedID) {
|
||||
folder.removeFeed(feed)
|
||||
clearFolderRelationship(for: feed, withFolderName: folder.name ?? "")
|
||||
account.addWebFeed(feed)
|
||||
account.addFeed(feed)
|
||||
}
|
||||
}
|
||||
|
||||
// Add any feeds not in the folder
|
||||
let folderFeedIds = folder.topLevelWebFeeds.map { $0.webFeedID }
|
||||
let folderFeedIds = folder.topLevelFeeds.map { $0.feedID }
|
||||
|
||||
for tagging in groupedTaggings {
|
||||
let taggingFeedID = String(tagging.feedID)
|
||||
if !folderFeedIds.contains(taggingFeedID) {
|
||||
guard let feed = account.existingWebFeed(withWebFeedID: taggingFeedID) else {
|
||||
guard let feed = account.existingFeed(withFeedID: taggingFeedID) else {
|
||||
continue
|
||||
}
|
||||
saveFolderRelationship(for: feed, withFolderName: folderName, id: String(tagging.taggingID))
|
||||
folder.addWebFeed(feed)
|
||||
folder.addFeed(feed)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -915,9 +915,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.topLevelWebFeeds {
|
||||
if taggedFeedIDs.contains(feed.webFeedID) {
|
||||
account.removeWebFeed(feed)
|
||||
for feed in account.topLevelFeeds {
|
||||
if taggedFeedIDs.contains(feed.feedID) {
|
||||
account.removeFeed(feed)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -980,7 +980,7 @@ private extension FeedbinAccountDelegate {
|
||||
}
|
||||
|
||||
func renameFolderRelationship(for account: Account, fromName: String, toName: String) {
|
||||
for feed in account.flattenedWebFeeds() {
|
||||
for feed in account.flattenedFeeds() {
|
||||
if var folderRelationship = feed.folderRelationship {
|
||||
let relationship = folderRelationship[fromName]
|
||||
folderRelationship[fromName] = nil
|
||||
@@ -1017,7 +1017,7 @@ private extension FeedbinAccountDelegate {
|
||||
}
|
||||
|
||||
if let bestSpecifier = FeedSpecifier.bestFeed(in: Set(feedSpecifiers)) {
|
||||
createWebFeed(for: account, url: bestSpecifier.urlString, name: name, container: container, validateFeed: true, completion: completion)
|
||||
createFeed(for: account, url: bestSpecifier.urlString, name: name, container: container, validateFeed: true, completion: completion)
|
||||
} else {
|
||||
DispatchQueue.main.async {
|
||||
completion(.failure(FeedbinAccountDelegateError.invalidParameter))
|
||||
@@ -1029,16 +1029,16 @@ private extension FeedbinAccountDelegate {
|
||||
|
||||
DispatchQueue.main.async {
|
||||
|
||||
let feed = account.createWebFeed(with: sub.name, url: sub.url, webFeedID: String(sub.feedID), homePageURL: sub.homePageURL)
|
||||
let feed = account.createFeed(with: sub.name, url: sub.url, feedID: String(sub.feedID), homePageURL: sub.homePageURL)
|
||||
feed.externalID = String(sub.subscriptionID)
|
||||
feed.iconURL = sub.jsonFeed?.icon
|
||||
feed.faviconURL = sub.jsonFeed?.favicon
|
||||
|
||||
account.addWebFeed(feed, to: container) { result in
|
||||
account.addFeed(feed, to: container) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
if let name = name {
|
||||
account.renameWebFeed(feed, to: name) { result in
|
||||
account.renameFeed(feed, to: name) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
self.initialFeedDownload(account: account, feed: feed, completion: completion)
|
||||
@@ -1064,7 +1064,7 @@ private extension FeedbinAccountDelegate {
|
||||
refreshProgress.addToNumberOfTasksAndRemaining(4)
|
||||
|
||||
// Download the initial articles
|
||||
self.caller.retrieveEntries(feedID: feed.webFeedID) { result in
|
||||
self.caller.retrieveEntries(feedID: feed.feedID) { result in
|
||||
self.refreshProgress.completeTask()
|
||||
|
||||
switch result {
|
||||
@@ -1248,8 +1248,8 @@ private extension FeedbinAccountDelegate {
|
||||
|
||||
func processEntries(account: Account, entries: [FeedbinEntry]?, completion: @escaping DatabaseCompletionBlock) {
|
||||
let parsedItems = mapEntriesToParsedItems(entries: entries)
|
||||
let webFeedIDsAndItems = Dictionary(grouping: parsedItems, by: { item in item.feedURL } ).mapValues { Set($0) }
|
||||
account.update(webFeedIDsAndItems: webFeedIDsAndItems, defaultRead: true, completion: completion)
|
||||
let feedIDsAndItems = Dictionary(grouping: parsedItems, by: { item in item.feedURL } ).mapValues { Set($0) }
|
||||
account.update(feedIDsAndItems: feedIDsAndItems, defaultRead: true, completion: completion)
|
||||
}
|
||||
|
||||
func mapEntriesToParsedItems(entries: [FeedbinEntry]?) -> Set<ParsedItem> {
|
||||
@@ -1381,7 +1381,7 @@ private extension FeedbinAccountDelegate {
|
||||
case .success:
|
||||
DispatchQueue.main.async {
|
||||
self.clearFolderRelationship(for: feed, withFolderName: folder.name ?? "")
|
||||
folder.removeWebFeed(feed)
|
||||
folder.removeFeed(feed)
|
||||
account.addFeedIfNotInAnyFolder(feed)
|
||||
completion(.success(()))
|
||||
}
|
||||
@@ -1394,7 +1394,7 @@ private extension FeedbinAccountDelegate {
|
||||
}
|
||||
} else {
|
||||
if let account = container as? Account {
|
||||
account.removeWebFeed(feed)
|
||||
account.removeFeed(feed)
|
||||
}
|
||||
completion(.success(()))
|
||||
}
|
||||
@@ -1415,11 +1415,11 @@ private extension FeedbinAccountDelegate {
|
||||
switch result {
|
||||
case .success:
|
||||
DispatchQueue.main.async {
|
||||
account.clearWebFeedMetadata(feed)
|
||||
account.removeWebFeed(feed)
|
||||
account.clearFeedMetadata(feed)
|
||||
account.removeFeed(feed)
|
||||
if let folders = account.folders {
|
||||
for folder in folders {
|
||||
folder.removeWebFeed(feed)
|
||||
folder.removeFeed(feed)
|
||||
}
|
||||
}
|
||||
completion(.success(()))
|
||||
|
||||
@@ -314,7 +314,7 @@ final class FeedlyAccountDelegate: AccountDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
func createWebFeed(for account: Account, url: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result<Feed, Error>) -> Void) {
|
||||
func createFeed(for account: Account, url: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result<Feed, Error>) -> Void) {
|
||||
|
||||
do {
|
||||
guard let credentials = credentials else {
|
||||
@@ -347,14 +347,14 @@ final class FeedlyAccountDelegate: AccountDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
func renameWebFeed(for account: Account, with feed: Feed, to name: String, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
func renameFeed(for account: Account, with feed: Feed, 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.webFeedID)
|
||||
let feedId = FeedlyFeedResourceId(id: feed.feedID)
|
||||
let editedNameBefore = feed.editedName
|
||||
|
||||
// Adding an existing feed updates it.
|
||||
@@ -374,14 +374,14 @@ final class FeedlyAccountDelegate: AccountDelegate {
|
||||
feed.editedName = name
|
||||
}
|
||||
|
||||
func addWebFeed(for account: Account, with feed: Feed, to container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
func addFeed(for account: Account, with feed: Feed, to container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
|
||||
do {
|
||||
guard let credentials = credentials else {
|
||||
throw FeedlyAccountDelegateError.notLoggedIn
|
||||
}
|
||||
|
||||
let resource = FeedlyFeedResourceId(id: feed.webFeedID)
|
||||
let resource = FeedlyFeedResourceId(id: feed.feedID)
|
||||
let addExistingFeed = try FeedlyAddExistingFeedOperation(account: account,
|
||||
credentials: credentials,
|
||||
resource: resource,
|
||||
@@ -405,62 +405,62 @@ final class FeedlyAccountDelegate: AccountDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
func removeWebFeed(for account: Account, with feed: Feed, from 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) {
|
||||
guard let folder = container as? Folder, let collectionId = folder.externalID else {
|
||||
return DispatchQueue.main.async {
|
||||
completion(.failure(FeedlyAccountDelegateError.unableToRemoveFeed(feed)))
|
||||
}
|
||||
}
|
||||
|
||||
caller.removeFeed(feed.webFeedID, fromCollectionWith: collectionId) { result in
|
||||
caller.removeFeed(feed.feedID, fromCollectionWith: collectionId) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
completion(.success(()))
|
||||
case .failure(let error):
|
||||
folder.addWebFeed(feed)
|
||||
folder.addFeed(feed)
|
||||
completion(.failure(error))
|
||||
}
|
||||
}
|
||||
|
||||
folder.removeWebFeed(feed)
|
||||
folder.removeFeed(feed)
|
||||
}
|
||||
|
||||
func moveWebFeed(for account: Account, with feed: Feed, from: Container, to: 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) {
|
||||
guard let from = from as? Folder, let to = to as? Folder else {
|
||||
return DispatchQueue.main.async {
|
||||
completion(.failure(FeedlyAccountDelegateError.addFeedChooseFolder))
|
||||
}
|
||||
}
|
||||
|
||||
addWebFeed(for: account, with: feed, to: to) { [weak self] addResult in
|
||||
addFeed(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?.removeWebFeed(for: account, with: feed, from: from) { removeResult in
|
||||
self?.removeFeed(for: account, with: feed, from: from) { removeResult in
|
||||
switch removeResult {
|
||||
case .success:
|
||||
completion(.success(()))
|
||||
case .failure:
|
||||
from.addWebFeed(feed)
|
||||
from.addFeed(feed)
|
||||
completion(.failure(FeedlyAccountDelegateError.unableToMoveFeedBetweenFolders(feed, from, to)))
|
||||
}
|
||||
}
|
||||
case .failure(let error):
|
||||
from.addWebFeed(feed)
|
||||
to.removeWebFeed(feed)
|
||||
from.addFeed(feed)
|
||||
to.removeFeed(feed)
|
||||
completion(.failure(error))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// optimistically move the feed, undoing as appropriate to the failure
|
||||
from.removeWebFeed(feed)
|
||||
to.addWebFeed(feed)
|
||||
from.removeFeed(feed)
|
||||
to.addFeed(feed)
|
||||
}
|
||||
|
||||
func restoreWebFeed(for account: Account, feed: Feed, container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
if let existingFeed = account.existingWebFeed(withURL: feed.url) {
|
||||
account.addWebFeed(existingFeed, to: container) { result in
|
||||
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
|
||||
switch result {
|
||||
case .success:
|
||||
completion(.success(()))
|
||||
@@ -469,7 +469,7 @@ final class FeedlyAccountDelegate: AccountDelegate {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
createWebFeed(for: account, url: feed.url, name: feed.editedName, container: container, validateFeed: true) { result in
|
||||
createFeed(for: account, url: feed.url, name: feed.editedName, container: container, validateFeed: true) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
completion(.success(()))
|
||||
@@ -483,12 +483,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.topLevelWebFeeds {
|
||||
for feed in folder.topLevelFeeds {
|
||||
|
||||
folder.topLevelWebFeeds.remove(feed)
|
||||
folder.topLevelFeeds.remove(feed)
|
||||
|
||||
group.enter()
|
||||
restoreWebFeed(for: account, feed: feed, container: folder) { result in
|
||||
restoreFeed(for: account, feed: feed, container: folder) { result in
|
||||
group.leave()
|
||||
switch result {
|
||||
case .success:
|
||||
|
||||
@@ -19,7 +19,7 @@ struct FeedlyEntryParser {
|
||||
return entry.id
|
||||
}
|
||||
|
||||
/// When ingesting articles, the feedURL must match a feed's `webFeedID` for the article to be reachable between it and its matching feed. It reminds me of a foreign key.
|
||||
/// When ingesting articles, the feedURL must match a feed's `feedID` for the article to be reachable between it and its matching feed. It reminds me of a foreign key.
|
||||
var feedUrl: String? {
|
||||
guard let id = entry.origin?.streamId else {
|
||||
// At this point, check Feedly's API isn't glitching or the response has not changed structure.
|
||||
|
||||
@@ -17,7 +17,7 @@ struct FeedlyFeedParser {
|
||||
return rightToLeftTextSantizer.sanitize(feed.title) ?? ""
|
||||
}
|
||||
|
||||
var webFeedID: String {
|
||||
var feedID: String {
|
||||
return feed.id
|
||||
}
|
||||
|
||||
|
||||
@@ -137,7 +137,7 @@ class FeedlyAddNewFeedOperation: FeedlyOperation, FeedlyOperationDelegate, Feedl
|
||||
guard let handler = addCompletionHandler else {
|
||||
return
|
||||
}
|
||||
if let feedResource = feedResourceId, let feed = folder.existingWebFeed(withWebFeedID: feedResource.id) {
|
||||
if let feedResource = feedResourceId, let feed = folder.existingFeed(withFeedID: feedResource.id) {
|
||||
handler(.success(feed))
|
||||
}
|
||||
else {
|
||||
|
||||
+8
-8
@@ -31,13 +31,13 @@ final class FeedlyCreateFeedsForCollectionFoldersOperation: FeedlyOperation {
|
||||
|
||||
let feedsBefore = Set(pairs
|
||||
.map { $0.1 }
|
||||
.flatMap { $0.topLevelWebFeeds })
|
||||
.flatMap { $0.topLevelFeeds })
|
||||
|
||||
// Remove feeds in a folder which are not in the corresponding collection.
|
||||
for (collectionFeeds, folder) in pairs {
|
||||
let feedsInFolder = folder.topLevelWebFeeds
|
||||
let feedsInFolder = folder.topLevelFeeds
|
||||
let feedsInCollection = Set(collectionFeeds.map { $0.id })
|
||||
let feedsToRemove = feedsInFolder.filter { !feedsInCollection.contains($0.webFeedID) }
|
||||
let feedsToRemove = feedsInFolder.filter { !feedsInCollection.contains($0.feedID) }
|
||||
if !feedsToRemove.isEmpty {
|
||||
folder.removeFeeds(feedsToRemove)
|
||||
// os_log(.debug, log: log, "\"%@\" - removed: %@", collection.label, feedsToRemove.map { $0.feedID }, feedsInCollection)
|
||||
@@ -58,7 +58,7 @@ final class FeedlyCreateFeedsForCollectionFoldersOperation: FeedlyOperation {
|
||||
.compactMap { (collectionFeed, folder) -> (Feed, Folder) in
|
||||
|
||||
// find an existing feed previously added to the account
|
||||
if let feed = account.existingWebFeed(withWebFeedID: collectionFeed.id) {
|
||||
if let feed = account.existingFeed(withFeedID: collectionFeed.id) {
|
||||
|
||||
// If the feed was renamed on Feedly, ensure we ingest the new name.
|
||||
if feed.nameForDisplay != collectionFeed.title {
|
||||
@@ -76,16 +76,16 @@ final class FeedlyCreateFeedsForCollectionFoldersOperation: FeedlyOperation {
|
||||
return (feed, folder)
|
||||
} else {
|
||||
// find an existing feed we created below in an earlier value
|
||||
for feed in feedsAdded where feed.webFeedID == collectionFeed.id {
|
||||
for feed in feedsAdded where feed.feedID == collectionFeed.id {
|
||||
return (feed, folder)
|
||||
}
|
||||
}
|
||||
|
||||
// no existing feed, create a new one
|
||||
let parser = FeedlyFeedParser(feed: collectionFeed)
|
||||
let feed = account.createWebFeed(with: parser.title,
|
||||
let feed = account.createFeed(with: parser.title,
|
||||
url: parser.url,
|
||||
webFeedID: parser.webFeedID,
|
||||
feedID: parser.feedID,
|
||||
homePageURL: parser.homePageURL)
|
||||
|
||||
// So the same feed isn't created more than once.
|
||||
@@ -97,7 +97,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.addWebFeed(feed)
|
||||
folder.addFeed(feed)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -24,15 +24,15 @@ final class FeedlyUpdateAccountFeedsWithItemsOperation: FeedlyOperation {
|
||||
}
|
||||
|
||||
override func run() {
|
||||
let webFeedIDsAndItems = organisedItemsProvider.parsedItemsKeyedByFeedId
|
||||
let feedIDsAndItems = organisedItemsProvider.parsedItemsKeyedByFeedId
|
||||
|
||||
account.update(webFeedIDsAndItems: webFeedIDsAndItems, defaultRead: true) { databaseError in
|
||||
account.update(feedIDsAndItems: feedIDsAndItems, defaultRead: true) { databaseError in
|
||||
if let error = databaseError {
|
||||
self.didFinish(with: error)
|
||||
return
|
||||
}
|
||||
|
||||
os_log(.debug, log: self.log, "Updated %i feeds for \"%@\"", webFeedIDsAndItems.count, self.organisedItemsProvider.parsedItemsByFeedProviderName)
|
||||
os_log(.debug, log: self.log, "Updated %i feeds for \"%@\"", feedIDsAndItems.count, self.organisedItemsProvider.parsedItemsByFeedProviderName)
|
||||
self.didFinish()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ public final class Folder: SidebarItem, Renamable, Container, Hashable {
|
||||
}
|
||||
|
||||
public weak var account: Account?
|
||||
public var topLevelWebFeeds: Set<Feed> = Set<Feed>()
|
||||
public var topLevelFeeds: Set<Feed> = Set<Feed>()
|
||||
public var folders: Set<Folder>? = nil // subfolders are not supported, so this is always nil
|
||||
|
||||
public var name: String? {
|
||||
@@ -100,9 +100,9 @@ public final class Folder: SidebarItem, Renamable, Container, Hashable {
|
||||
|
||||
// MARK: Container
|
||||
|
||||
public func flattenedWebFeeds() -> Set<Feed> {
|
||||
public func flattenedFeeds() -> Set<Feed> {
|
||||
// Since sub-folders are not supported, it’s always the top-level feeds.
|
||||
return topLevelWebFeeds
|
||||
return topLevelFeeds
|
||||
}
|
||||
|
||||
public func objectIsChild(_ object: AnyObject) -> Bool {
|
||||
@@ -110,11 +110,11 @@ public final class Folder: SidebarItem, Renamable, Container, Hashable {
|
||||
guard let feed = object as? Feed else {
|
||||
return false
|
||||
}
|
||||
return topLevelWebFeeds.contains(feed)
|
||||
return topLevelFeeds.contains(feed)
|
||||
}
|
||||
|
||||
public func addWebFeed(_ feed: Feed) {
|
||||
topLevelWebFeeds.insert(feed)
|
||||
public func addFeed(_ feed: Feed) {
|
||||
topLevelFeeds.insert(feed)
|
||||
postChildrenDidChangeNotification()
|
||||
}
|
||||
|
||||
@@ -122,12 +122,12 @@ public final class Folder: SidebarItem, Renamable, Container, Hashable {
|
||||
guard !feeds.isEmpty else {
|
||||
return
|
||||
}
|
||||
topLevelWebFeeds.formUnion(feeds)
|
||||
topLevelFeeds.formUnion(feeds)
|
||||
postChildrenDidChangeNotification()
|
||||
}
|
||||
|
||||
public func removeWebFeed(_ feed: Feed) {
|
||||
topLevelWebFeeds.remove(feed)
|
||||
public func removeFeed(_ feed: Feed) {
|
||||
topLevelFeeds.remove(feed)
|
||||
postChildrenDidChangeNotification()
|
||||
}
|
||||
|
||||
@@ -135,7 +135,7 @@ public final class Folder: SidebarItem, Renamable, Container, Hashable {
|
||||
guard !feeds.isEmpty else {
|
||||
return
|
||||
}
|
||||
topLevelWebFeeds.subtract(feeds)
|
||||
topLevelFeeds.subtract(feeds)
|
||||
postChildrenDidChangeNotification()
|
||||
}
|
||||
|
||||
@@ -158,14 +158,14 @@ private extension Folder {
|
||||
|
||||
func updateUnreadCount() {
|
||||
var updatedUnreadCount = 0
|
||||
for feed in topLevelWebFeeds {
|
||||
for feed in topLevelFeeds {
|
||||
updatedUnreadCount += feed.unreadCount
|
||||
}
|
||||
unreadCount = updatedUnreadCount
|
||||
}
|
||||
|
||||
func childrenContain(_ feed: Feed) -> Bool {
|
||||
return topLevelWebFeeds.contains(feed)
|
||||
return topLevelFeeds.contains(feed)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,7 +189,7 @@ extension Folder: OPMLRepresentable {
|
||||
|
||||
var hasAtLeastOneChild = false
|
||||
|
||||
for feed in topLevelWebFeeds.sorted() {
|
||||
for feed in topLevelFeeds.sorted() {
|
||||
s += feed.OPMLString(indentLevel: indentLevel + 1, allowCustomAttributes: allowCustomAttributes)
|
||||
hasAtLeastOneChild = true
|
||||
}
|
||||
|
||||
@@ -50,13 +50,13 @@ final class LocalAccountDelegate: AccountDelegate {
|
||||
return
|
||||
}
|
||||
|
||||
let webFeeds = account.flattenedWebFeeds()
|
||||
refreshProgress.addToNumberOfTasksAndRemaining(webFeeds.count)
|
||||
let feeds = account.flattenedFeeds()
|
||||
refreshProgress.addToNumberOfTasksAndRemaining(feeds.count)
|
||||
|
||||
let group = DispatchGroup()
|
||||
|
||||
group.enter()
|
||||
refresher?.refreshFeeds(webFeeds) {
|
||||
refresher?.refreshFeeds(feeds) {
|
||||
group.leave()
|
||||
}
|
||||
|
||||
@@ -122,38 +122,38 @@ final class LocalAccountDelegate: AccountDelegate {
|
||||
|
||||
}
|
||||
|
||||
func createWebFeed(for account: Account, url urlString: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result<Feed, Error>) -> Void) {
|
||||
func createFeed(for account: Account, url urlString: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result<Feed, Error>) -> Void) {
|
||||
guard let url = URL(string: urlString) else {
|
||||
completion(.failure(LocalAccountDelegateError.invalidParameter))
|
||||
return
|
||||
}
|
||||
|
||||
createRSSWebFeed(for: account, url: url, editedName: name, container: container, completion: completion)
|
||||
createRSSFeed(for: account, url: url, editedName: name, container: container, completion: completion)
|
||||
}
|
||||
|
||||
func renameWebFeed(for account: Account, with feed: Feed, to name: String, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
func renameFeed(for account: Account, with feed: Feed, to name: String, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
feed.editedName = name
|
||||
completion(.success(()))
|
||||
}
|
||||
|
||||
func removeWebFeed(for account: Account, with feed: Feed, from container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
container.removeWebFeed(feed)
|
||||
func removeFeed(for account: Account, with feed: Feed, from container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
container.removeFeed(feed)
|
||||
completion(.success(()))
|
||||
}
|
||||
|
||||
func moveWebFeed(for account: Account, with feed: Feed, from: Container, to: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
from.removeWebFeed(feed)
|
||||
to.addWebFeed(feed)
|
||||
func moveFeed(for account: Account, with feed: Feed, from: Container, to: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
from.removeFeed(feed)
|
||||
to.addFeed(feed)
|
||||
completion(.success(()))
|
||||
}
|
||||
|
||||
func addWebFeed(for account: Account, with feed: Feed, to container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
container.addWebFeed(feed)
|
||||
func addFeed(for account: Account, with feed: Feed, to container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
container.addFeed(feed)
|
||||
completion(.success(()))
|
||||
}
|
||||
|
||||
func restoreWebFeed(for account: Account, feed: Feed, container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
container.addWebFeed(feed)
|
||||
func restoreFeed(for account: Account, feed: Feed, container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
container.addFeed(feed)
|
||||
completion(.success(()))
|
||||
}
|
||||
|
||||
@@ -231,7 +231,7 @@ extension LocalAccountDelegate: LocalAccountRefresherDelegate {
|
||||
|
||||
private extension LocalAccountDelegate {
|
||||
|
||||
func createRSSWebFeed(for account: Account, url: URL, editedName: String?, container: Container, completion: @escaping (Result<Feed, Error>) -> Void) {
|
||||
func createRSSFeed(for account: Account, url: URL, editedName: String?, container: Container, completion: @escaping (Result<Feed, Error>) -> Void) {
|
||||
|
||||
// We need to use a batch update here because we need to assign add the feed to the
|
||||
// container before the name has been downloaded. This will put it in the sidebar
|
||||
@@ -250,7 +250,7 @@ private extension LocalAccountDelegate {
|
||||
return
|
||||
}
|
||||
|
||||
if account.hasWebFeed(withURL: bestFeedSpecifier.urlString) {
|
||||
if account.hasFeed(withURL: bestFeedSpecifier.urlString) {
|
||||
self.refreshProgress.completeTask()
|
||||
BatchUpdate.shared.end()
|
||||
completion(.failure(AccountError.createErrorAlreadySubscribed))
|
||||
@@ -261,9 +261,9 @@ private extension LocalAccountDelegate {
|
||||
self.refreshProgress.completeTask()
|
||||
|
||||
if let parsedFeed = parsedFeed {
|
||||
let feed = account.createWebFeed(with: nil, url: url.absoluteString, webFeedID: url.absoluteString, homePageURL: nil)
|
||||
let feed = account.createFeed(with: nil, url: url.absoluteString, feedID: url.absoluteString, homePageURL: nil)
|
||||
feed.editedName = editedName
|
||||
container.addWebFeed(feed)
|
||||
container.addFeed(feed)
|
||||
|
||||
account.update(feed, with: parsedFeed, {_ in
|
||||
BatchUpdate.shared.end()
|
||||
|
||||
@@ -47,8 +47,8 @@ extension NewsBlurAccountDelegate {
|
||||
if let folders = account.folders {
|
||||
folders.forEach { folder in
|
||||
if !folderNames.contains(folder.name ?? "") {
|
||||
for feed in folder.topLevelWebFeeds {
|
||||
account.addWebFeed(feed)
|
||||
for feed in folder.topLevelFeeds {
|
||||
account.addFeed(feed)
|
||||
clearFolderRelationship(for: feed, withFolderName: folder.name ?? "")
|
||||
}
|
||||
account.removeFolder(folder)
|
||||
@@ -84,17 +84,17 @@ extension NewsBlurAccountDelegate {
|
||||
// Remove any feeds that are no longer in the subscriptions
|
||||
if let folders = account.folders {
|
||||
for folder in folders {
|
||||
for feed in folder.topLevelWebFeeds {
|
||||
if !newsBlurFeedIds.contains(feed.webFeedID) {
|
||||
folder.removeWebFeed(feed)
|
||||
for feed in folder.topLevelFeeds {
|
||||
if !newsBlurFeedIds.contains(feed.feedID) {
|
||||
folder.removeFeed(feed)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for feed in account.topLevelWebFeeds {
|
||||
if !newsBlurFeedIds.contains(feed.webFeedID) {
|
||||
account.removeWebFeed(feed)
|
||||
for feed in account.topLevelFeeds {
|
||||
if !newsBlurFeedIds.contains(feed.feedID) {
|
||||
account.removeFeed(feed)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,13 +103,13 @@ extension NewsBlurAccountDelegate {
|
||||
feeds.forEach { feed in
|
||||
let subFeedId = String(feed.feedID)
|
||||
|
||||
if let webFeed = account.existingWebFeed(withWebFeedID: subFeedId) {
|
||||
webFeed.name = feed.name
|
||||
if let feed = account.existingFeed(withFeedID: subFeedId) {
|
||||
feed.name = feed.name
|
||||
// If the name has been changed on the server remove the locally edited name
|
||||
webFeed.editedName = nil
|
||||
webFeed.homePageURL = feed.homePageURL
|
||||
webFeed.externalID = String(feed.feedID)
|
||||
webFeed.faviconURL = feed.faviconURL
|
||||
feed.editedName = nil
|
||||
feed.homePageURL = feed.homePageURL
|
||||
feed.externalID = String(feed.feedID)
|
||||
feed.faviconURL = feed.faviconURL
|
||||
}
|
||||
else {
|
||||
feedsToAdd.insert(feed)
|
||||
@@ -118,9 +118,9 @@ extension NewsBlurAccountDelegate {
|
||||
|
||||
// Actually add feeds all in one go, so we don’t trigger various rebuilding things that Account does.
|
||||
feedsToAdd.forEach { feed in
|
||||
let webFeed = account.createWebFeed(with: feed.name, url: feed.feedURL, webFeedID: String(feed.feedID), homePageURL: feed.homePageURL)
|
||||
webFeed.externalID = String(feed.feedID)
|
||||
account.addWebFeed(webFeed)
|
||||
let feed = account.createFeed(with: feed.name, url: feed.feedURL, feedID: String(feed.feedID), homePageURL: feed.homePageURL)
|
||||
feed.externalID = String(feed.feedID)
|
||||
account.addFeed(feed)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,25 +155,25 @@ extension NewsBlurAccountDelegate {
|
||||
guard let folder = folderDict[folderName] else { return }
|
||||
|
||||
// Move any feeds not in the folder to the account
|
||||
for feed in folder.topLevelWebFeeds {
|
||||
if !newsBlurFolderFeedIDs.contains(feed.webFeedID) {
|
||||
folder.removeWebFeed(feed)
|
||||
for feed in folder.topLevelFeeds {
|
||||
if !newsBlurFolderFeedIDs.contains(feed.feedID) {
|
||||
folder.removeFeed(feed)
|
||||
clearFolderRelationship(for: feed, withFolderName: folder.name ?? "")
|
||||
account.addWebFeed(feed)
|
||||
account.addFeed(feed)
|
||||
}
|
||||
}
|
||||
|
||||
// Add any feeds not in the folder
|
||||
let folderFeedIds = folder.topLevelWebFeeds.map { $0.webFeedID }
|
||||
let folderFeedIds = folder.topLevelFeeds.map { $0.feedID }
|
||||
|
||||
for relationship in folderRelationships {
|
||||
let folderFeedID = String(relationship.feedID)
|
||||
if !folderFeedIds.contains(folderFeedID) {
|
||||
guard let feed = account.existingWebFeed(withWebFeedID: folderFeedID) else {
|
||||
guard let feed = account.existingFeed(withFeedID: folderFeedID) else {
|
||||
continue
|
||||
}
|
||||
saveFolderRelationship(for: feed, withFolderName: folderName, id: relationship.folderName)
|
||||
folder.addWebFeed(feed)
|
||||
folder.addFeed(feed)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -182,14 +182,14 @@ extension NewsBlurAccountDelegate {
|
||||
// in folders and we need to remove them all from the account level.
|
||||
if let folderRelationships = newsBlurFolderDict[" "] {
|
||||
let newsBlurFolderFeedIDs = folderRelationships.map { String($0.feedID) }
|
||||
for feed in account.topLevelWebFeeds {
|
||||
if !newsBlurFolderFeedIDs.contains(feed.webFeedID) {
|
||||
account.removeWebFeed(feed)
|
||||
for feed in account.topLevelFeeds {
|
||||
if !newsBlurFolderFeedIDs.contains(feed.feedID) {
|
||||
account.removeFeed(feed)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for feed in account.topLevelWebFeeds {
|
||||
account.removeWebFeed(feed)
|
||||
for feed in account.topLevelFeeds {
|
||||
account.removeFeed(feed)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -419,24 +419,24 @@ extension NewsBlurAccountDelegate {
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
let webFeed = account.createWebFeed(with: feed.name, url: feed.feedURL, webFeedID: String(feed.feedID), homePageURL: feed.homePageURL)
|
||||
webFeed.externalID = String(feed.feedID)
|
||||
webFeed.faviconURL = feed.faviconURL
|
||||
let feed = account.createFeed(with: feed.name, url: feed.feedURL, feedID: String(feed.feedID), homePageURL: feed.homePageURL)
|
||||
feed.externalID = String(feed.feedID)
|
||||
feed.faviconURL = feed.faviconURL
|
||||
|
||||
account.addWebFeed(webFeed, to: container) { result in
|
||||
account.addFeed(feed, to: container) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
if let name = name {
|
||||
account.renameWebFeed(webFeed, to: name) { result in
|
||||
account.renameFeed(feed, to: name) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
self.initialFeedDownload(account: account, feed: webFeed, completion: completion)
|
||||
self.initialFeedDownload(account: account, feed: feed, completion: completion)
|
||||
case .failure(let error):
|
||||
completion(.failure(error))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.initialFeedDownload(account: account, feed: webFeed, completion: completion)
|
||||
self.initialFeedDownload(account: account, feed: feed, completion: completion)
|
||||
}
|
||||
case .failure(let error):
|
||||
completion(.failure(error))
|
||||
@@ -448,7 +448,7 @@ extension NewsBlurAccountDelegate {
|
||||
func downloadFeed(account: Account, feed: Feed, page: Int, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
refreshProgress.addToNumberOfTasksAndRemaining(1)
|
||||
|
||||
caller.retrieveStories(feedID: feed.webFeedID, page: page) { result in
|
||||
caller.retrieveStories(feedID: feed.feedID, page: page) { result in
|
||||
switch result {
|
||||
case .success((let stories, _)):
|
||||
// No more stories
|
||||
@@ -529,20 +529,20 @@ extension NewsBlurAccountDelegate {
|
||||
switch result {
|
||||
case .success:
|
||||
DispatchQueue.main.async {
|
||||
let feedID = feed.webFeedID
|
||||
let feedID = feed.feedID
|
||||
|
||||
if folderName == nil {
|
||||
account.removeWebFeed(feed)
|
||||
account.removeFeed(feed)
|
||||
}
|
||||
|
||||
if let folders = account.folders {
|
||||
for folder in folders where folderName != nil && folder.name == folderName {
|
||||
folder.removeWebFeed(feed)
|
||||
folder.removeFeed(feed)
|
||||
}
|
||||
}
|
||||
|
||||
if account.existingWebFeed(withWebFeedID: feedID) != nil {
|
||||
account.clearWebFeedMetadata(feed)
|
||||
if account.existingFeed(withFeedID: feedID) != nil {
|
||||
account.clearFeedMetadata(feed)
|
||||
}
|
||||
|
||||
completion(.success(()))
|
||||
|
||||
@@ -331,17 +331,17 @@ final class NewsBlurAccountDelegate: AccountDelegate {
|
||||
|
||||
return datePublished >= since
|
||||
}
|
||||
let webFeedIDsAndItems = Dictionary(grouping: parsedItems, by: { item in item.feedURL }).mapValues {
|
||||
let feedIDsAndItems = Dictionary(grouping: parsedItems, by: { item in item.feedURL }).mapValues {
|
||||
Set($0)
|
||||
}
|
||||
|
||||
account.update(webFeedIDsAndItems: webFeedIDsAndItems, defaultRead: true) { error in
|
||||
account.update(feedIDsAndItems: feedIDsAndItems, defaultRead: true) { error in
|
||||
if let error = error {
|
||||
completion(.failure(error))
|
||||
return
|
||||
}
|
||||
|
||||
completion(.success(!webFeedIDsAndItems.isEmpty))
|
||||
completion(.success(!feedIDsAndItems.isEmpty))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -400,7 +400,7 @@ final class NewsBlurAccountDelegate: AccountDelegate {
|
||||
}
|
||||
|
||||
var feedIDs: [String] = []
|
||||
for feed in folder.topLevelWebFeeds {
|
||||
for feed in folder.topLevelFeeds {
|
||||
if (feed.folderRelationship?.count ?? 0) > 1 {
|
||||
clearFolderRelationship(for: feed, withFolderName: folderToRemove)
|
||||
} else if let feedID = feed.externalID {
|
||||
@@ -423,7 +423,7 @@ final class NewsBlurAccountDelegate: AccountDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
func createWebFeed(for account: Account, url: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result<Feed, Error>) -> ()) {
|
||||
func createFeed(for account: Account, url: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result<Feed, Error>) -> ()) {
|
||||
refreshProgress.addToNumberOfTasksAndRemaining(1)
|
||||
|
||||
let folderName = (container as? Folder)?.name
|
||||
@@ -442,7 +442,7 @@ final class NewsBlurAccountDelegate: AccountDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
func renameWebFeed(for account: Account, with feed: Feed, to name: String, completion: @escaping (Result<Void, Error>) -> ()) {
|
||||
func renameFeed(for account: Account, with feed: Feed, to name: String, completion: @escaping (Result<Void, Error>) -> ()) {
|
||||
guard let feedID = feed.externalID else {
|
||||
completion(.failure(NewsBlurError.invalidParameter))
|
||||
return
|
||||
@@ -469,11 +469,11 @@ final class NewsBlurAccountDelegate: AccountDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
func addWebFeed(for account: Account, with feed: Feed, to container: Container, completion: @escaping (Result<Void, Error>) -> ()) {
|
||||
func addFeed(for account: Account, with feed: Feed, to container: Container, completion: @escaping (Result<Void, Error>) -> ()) {
|
||||
guard let folder = container as? Folder else {
|
||||
DispatchQueue.main.async {
|
||||
if let account = container as? Account {
|
||||
account.addWebFeed(feed)
|
||||
account.addFeed(feed)
|
||||
}
|
||||
completion(.success(()))
|
||||
}
|
||||
@@ -483,16 +483,16 @@ final class NewsBlurAccountDelegate: AccountDelegate {
|
||||
|
||||
let folderName = folder.name ?? ""
|
||||
saveFolderRelationship(for: feed, withFolderName: folderName, id: folderName)
|
||||
folder.addWebFeed(feed)
|
||||
folder.addFeed(feed)
|
||||
|
||||
completion(.success(()))
|
||||
}
|
||||
|
||||
func removeWebFeed(for account: Account, with feed: Feed, from container: Container, completion: @escaping (Result<Void, Error>) -> ()) {
|
||||
func removeFeed(for account: Account, with feed: Feed, from container: Container, completion: @escaping (Result<Void, Error>) -> ()) {
|
||||
deleteFeed(for: account, with: feed, from: container, completion: completion)
|
||||
}
|
||||
|
||||
func moveWebFeed(for account: Account, with feed: Feed, from: Container, to: Container, completion: @escaping (Result<Void, Error>) -> ()) {
|
||||
func moveFeed(for account: Account, with feed: Feed, from: Container, to: Container, completion: @escaping (Result<Void, Error>) -> ()) {
|
||||
guard let feedID = feed.externalID else {
|
||||
completion(.failure(NewsBlurError.invalidParameter))
|
||||
return
|
||||
@@ -509,8 +509,8 @@ final class NewsBlurAccountDelegate: AccountDelegate {
|
||||
|
||||
switch result {
|
||||
case .success:
|
||||
from.removeWebFeed(feed)
|
||||
to.addWebFeed(feed)
|
||||
from.removeFeed(feed)
|
||||
to.addFeed(feed)
|
||||
|
||||
completion(.success(()))
|
||||
case .failure(let error):
|
||||
@@ -519,9 +519,9 @@ final class NewsBlurAccountDelegate: AccountDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
func restoreWebFeed(for account: Account, feed: Feed, container: Container, completion: @escaping (Result<Void, Error>) -> ()) {
|
||||
if let existingFeed = account.existingWebFeed(withURL: feed.url) {
|
||||
account.addWebFeed(existingFeed, to: container) { result in
|
||||
func restoreFeed(for account: Account, feed: Feed, container: Container, completion: @escaping (Result<Void, Error>) -> ()) {
|
||||
if let existingFeed = account.existingFeed(withURL: feed.url) {
|
||||
account.addFeed(existingFeed, to: container) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
completion(.success(()))
|
||||
@@ -530,7 +530,7 @@ final class NewsBlurAccountDelegate: AccountDelegate {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
createWebFeed(for: account, url: feed.url, name: feed.editedName, container: container, validateFeed: true) { result in
|
||||
createFeed(for: account, url: feed.url, name: feed.editedName, container: container, validateFeed: true) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
completion(.success(()))
|
||||
@@ -548,9 +548,9 @@ final class NewsBlurAccountDelegate: AccountDelegate {
|
||||
}
|
||||
|
||||
var feedsToRestore: [Feed] = []
|
||||
for feed in folder.topLevelWebFeeds {
|
||||
for feed in folder.topLevelFeeds {
|
||||
feedsToRestore.append(feed)
|
||||
folder.topLevelWebFeeds.remove(feed)
|
||||
folder.topLevelFeeds.remove(feed)
|
||||
}
|
||||
|
||||
let group = DispatchGroup()
|
||||
@@ -562,7 +562,7 @@ final class NewsBlurAccountDelegate: AccountDelegate {
|
||||
case .success(let folder):
|
||||
for feed in feedsToRestore {
|
||||
group.enter()
|
||||
self.restoreWebFeed(for: account, feed: feed, container: folder) { result in
|
||||
self.restoreFeed(for: account, feed: feed, container: folder) { result in
|
||||
group.leave()
|
||||
switch result {
|
||||
case .success:
|
||||
|
||||
@@ -326,7 +326,7 @@ final class ReaderAPIAccountDelegate: AccountDelegate {
|
||||
|
||||
let group = DispatchGroup()
|
||||
|
||||
for feed in folder.topLevelWebFeeds {
|
||||
for feed in folder.topLevelFeeds {
|
||||
|
||||
if feed.folderRelationship?.count ?? 0 > 1 {
|
||||
|
||||
@@ -358,7 +358,7 @@ final class ReaderAPIAccountDelegate: AccountDelegate {
|
||||
switch result {
|
||||
case .success:
|
||||
DispatchQueue.main.async {
|
||||
account.clearWebFeedMetadata(feed)
|
||||
account.clearFeedMetadata(feed)
|
||||
}
|
||||
case .failure(let error):
|
||||
os_log(.error, log: self.log, "Remove feed error: %@.", error.localizedDescription)
|
||||
@@ -390,7 +390,7 @@ final class ReaderAPIAccountDelegate: AccountDelegate {
|
||||
|
||||
}
|
||||
|
||||
func createWebFeed(for account: Account, url: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result<Feed, Error>) -> Void) {
|
||||
func createFeed(for account: Account, url: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result<Feed, Error>) -> Void) {
|
||||
guard let url = URL(string: url) else {
|
||||
completion(.failure(ReaderAPIAccountDelegateError.invalidParameter))
|
||||
return
|
||||
@@ -439,7 +439,7 @@ final class ReaderAPIAccountDelegate: AccountDelegate {
|
||||
|
||||
}
|
||||
|
||||
func renameWebFeed(for account: Account, with feed: Feed, to name: String, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
func renameFeed(for account: Account, with feed: Feed, to name: String, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
|
||||
// This error should never happen
|
||||
guard let subscriptionID = feed.externalID else {
|
||||
@@ -466,7 +466,7 @@ final class ReaderAPIAccountDelegate: AccountDelegate {
|
||||
|
||||
}
|
||||
|
||||
func removeWebFeed(for account: Account, with feed: Feed, from 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) {
|
||||
guard let subscriptionID = feed.externalID else {
|
||||
completion(.failure(ReaderAPIAccountDelegateError.invalidParameter))
|
||||
return
|
||||
@@ -478,11 +478,11 @@ final class ReaderAPIAccountDelegate: AccountDelegate {
|
||||
switch result {
|
||||
case .success:
|
||||
DispatchQueue.main.async {
|
||||
account.clearWebFeedMetadata(feed)
|
||||
account.removeWebFeed(feed)
|
||||
account.clearFeedMetadata(feed)
|
||||
account.removeFeed(feed)
|
||||
if let folders = account.folders {
|
||||
for folder in folders {
|
||||
folder.removeWebFeed(feed)
|
||||
folder.removeFeed(feed)
|
||||
}
|
||||
}
|
||||
completion(.success(()))
|
||||
@@ -496,9 +496,9 @@ final class ReaderAPIAccountDelegate: AccountDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
func moveWebFeed(for account: Account, with feed: Feed, from: Container, to: 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) {
|
||||
if from is Account {
|
||||
addWebFeed(for: account, with: feed, to: to, completion: completion)
|
||||
addFeed(for: account, with: feed, to: to, completion: completion)
|
||||
} else {
|
||||
guard
|
||||
let subscriptionId = feed.externalID,
|
||||
@@ -514,8 +514,8 @@ final class ReaderAPIAccountDelegate: AccountDelegate {
|
||||
self.refreshProgress.completeTask()
|
||||
switch result {
|
||||
case .success:
|
||||
from.removeWebFeed(feed)
|
||||
to.addWebFeed(feed)
|
||||
from.removeFeed(feed)
|
||||
to.addFeed(feed)
|
||||
completion(.success(()))
|
||||
case .failure(let error):
|
||||
completion(.failure(error))
|
||||
@@ -524,7 +524,7 @@ final class ReaderAPIAccountDelegate: AccountDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
func addWebFeed(for account: Account, with feed: Feed, to container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
func addFeed(for account: Account, with feed: Feed, to container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
if let folder = container as? Folder, let feedExternalID = feed.externalID {
|
||||
refreshProgress.addToNumberOfTasksAndRemaining(1)
|
||||
caller.createTagging(subscriptionID: feedExternalID, tagName: folder.name ?? "") { result in
|
||||
@@ -533,8 +533,8 @@ final class ReaderAPIAccountDelegate: AccountDelegate {
|
||||
case .success:
|
||||
DispatchQueue.main.async {
|
||||
self.saveFolderRelationship(for: feed, folderExternalID: folder.externalID, feedExternalID: feedExternalID)
|
||||
account.removeWebFeed(feed)
|
||||
folder.addWebFeed(feed)
|
||||
account.removeFeed(feed)
|
||||
folder.addFeed(feed)
|
||||
completion(.success(()))
|
||||
}
|
||||
case .failure(let error):
|
||||
@@ -554,10 +554,10 @@ final class ReaderAPIAccountDelegate: AccountDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
func restoreWebFeed(for account: Account, feed: Feed, container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
func restoreFeed(for account: Account, feed: Feed, container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
|
||||
|
||||
if let existingFeed = account.existingWebFeed(withURL: feed.url) {
|
||||
account.addWebFeed(existingFeed, to: container) { result in
|
||||
if let existingFeed = account.existingFeed(withURL: feed.url) {
|
||||
account.addFeed(existingFeed, to: container) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
completion(.success(()))
|
||||
@@ -566,7 +566,7 @@ final class ReaderAPIAccountDelegate: AccountDelegate {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
createWebFeed(for: account, url: feed.url, name: feed.editedName, container: container, validateFeed: true) { result in
|
||||
createFeed(for: account, url: feed.url, name: feed.editedName, container: container, validateFeed: true) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
completion(.success(()))
|
||||
@@ -582,12 +582,12 @@ final class ReaderAPIAccountDelegate: AccountDelegate {
|
||||
|
||||
let group = DispatchGroup()
|
||||
|
||||
for feed in folder.topLevelWebFeeds {
|
||||
for feed in folder.topLevelFeeds {
|
||||
|
||||
folder.topLevelWebFeeds.remove(feed)
|
||||
folder.topLevelFeeds.remove(feed)
|
||||
|
||||
group.enter()
|
||||
restoreWebFeed(for: account, feed: feed, container: folder) { result in
|
||||
restoreFeed(for: account, feed: feed, container: folder) { result in
|
||||
group.leave()
|
||||
switch result {
|
||||
case .success:
|
||||
@@ -719,8 +719,8 @@ private extension ReaderAPIAccountDelegate {
|
||||
if let folders = account.folders {
|
||||
folders.forEach { folder in
|
||||
if !readerFolderExternalIDs.contains(folder.externalID ?? "") {
|
||||
for feed in folder.topLevelWebFeeds {
|
||||
account.addWebFeed(feed)
|
||||
for feed in folder.topLevelFeeds {
|
||||
account.addFeed(feed)
|
||||
clearFolderRelationship(for: feed, folderExternalID: folder.externalID)
|
||||
}
|
||||
account.removeFolder(folder)
|
||||
@@ -758,32 +758,32 @@ 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.topLevelWebFeeds {
|
||||
if !subFeedIds.contains(feed.webFeedID) {
|
||||
folder.removeWebFeed(feed)
|
||||
for feed in folder.topLevelFeeds {
|
||||
if !subFeedIds.contains(feed.feedID) {
|
||||
folder.removeFeed(feed)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for feed in account.topLevelWebFeeds {
|
||||
if !subFeedIds.contains(feed.webFeedID) {
|
||||
account.clearWebFeedMetadata(feed)
|
||||
account.removeWebFeed(feed)
|
||||
for feed in account.topLevelFeeds {
|
||||
if !subFeedIds.contains(feed.feedID) {
|
||||
account.clearFeedMetadata(feed)
|
||||
account.removeFeed(feed)
|
||||
}
|
||||
}
|
||||
|
||||
// Add any feeds we don't have and update any we do
|
||||
subscriptions.forEach { subscription in
|
||||
|
||||
if let feed = account.existingWebFeed(withWebFeedID: subscription.feedID) {
|
||||
if let feed = account.existingFeed(withFeedID: subscription.feedID) {
|
||||
feed.name = subscription.name
|
||||
feed.editedName = nil
|
||||
feed.homePageURL = subscription.homePageURL
|
||||
} else {
|
||||
let feed = account.createWebFeed(with: subscription.name, url: subscription.url, webFeedID: subscription.feedID, homePageURL: subscription.homePageURL)
|
||||
let feed = account.createFeed(with: subscription.name, url: subscription.url, feedID: subscription.feedID, homePageURL: subscription.homePageURL)
|
||||
feed.externalID = subscription.feedID
|
||||
account.addWebFeed(feed)
|
||||
account.addFeed(feed)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -818,25 +818,25 @@ private extension ReaderAPIAccountDelegate {
|
||||
let taggingFeedIDs = groupedTaggings.map { $0.feedID }
|
||||
|
||||
// Move any feeds not in the folder to the account
|
||||
for feed in folder.topLevelWebFeeds {
|
||||
if !taggingFeedIDs.contains(feed.webFeedID) {
|
||||
folder.removeWebFeed(feed)
|
||||
for feed in folder.topLevelFeeds {
|
||||
if !taggingFeedIDs.contains(feed.feedID) {
|
||||
folder.removeFeed(feed)
|
||||
clearFolderRelationship(for: feed, folderExternalID: folder.externalID)
|
||||
account.addWebFeed(feed)
|
||||
account.addFeed(feed)
|
||||
}
|
||||
}
|
||||
|
||||
// Add any feeds not in the folder
|
||||
let folderFeedIds = folder.topLevelWebFeeds.map { $0.webFeedID }
|
||||
let folderFeedIds = folder.topLevelFeeds.map { $0.feedID }
|
||||
|
||||
for subscription in groupedTaggings {
|
||||
let taggingFeedID = subscription.feedID
|
||||
if !folderFeedIds.contains(taggingFeedID) {
|
||||
guard let feed = account.existingWebFeed(withWebFeedID: taggingFeedID) else {
|
||||
guard let feed = account.existingFeed(withFeedID: taggingFeedID) else {
|
||||
continue
|
||||
}
|
||||
saveFolderRelationship(for: feed, folderExternalID: folderExternalID, feedExternalID: subscription.feedID)
|
||||
folder.addWebFeed(feed)
|
||||
folder.addFeed(feed)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -845,9 +845,9 @@ private extension ReaderAPIAccountDelegate {
|
||||
let taggedFeedIDs = Set(subscriptions.filter({ !$0.categories.isEmpty }).map { String($0.feedID) })
|
||||
|
||||
// Remove all feeds from the account container that have a tag
|
||||
for feed in account.topLevelWebFeeds {
|
||||
if taggedFeedIDs.contains(feed.webFeedID) {
|
||||
account.removeWebFeed(feed)
|
||||
for feed in account.topLevelFeeds {
|
||||
if taggedFeedIDs.contains(feed.feedID) {
|
||||
account.removeFeed(feed)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -921,14 +921,14 @@ private extension ReaderAPIAccountDelegate {
|
||||
|
||||
DispatchQueue.main.async {
|
||||
|
||||
let feed = account.createWebFeed(with: sub.name, url: sub.url, webFeedID: String(sub.feedID), homePageURL: sub.homePageURL)
|
||||
let feed = account.createFeed(with: sub.name, url: sub.url, feedID: String(sub.feedID), homePageURL: sub.homePageURL)
|
||||
feed.externalID = String(sub.feedID)
|
||||
|
||||
account.addWebFeed(feed, to: container) { result in
|
||||
account.addFeed(feed, to: container) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
if let name = name {
|
||||
self.renameWebFeed(for: account, with: feed, to: name) { result in
|
||||
self.renameFeed(for: account, with: feed, to: name) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
self.initialFeedDownload(account: account, feed: feed, completion: completion)
|
||||
@@ -952,7 +952,7 @@ private extension ReaderAPIAccountDelegate {
|
||||
refreshProgress.addToNumberOfTasksAndRemaining(5)
|
||||
|
||||
// Download the initial articles
|
||||
self.caller.retrieveItemIDs(type: .allForFeed, webFeedID: feed.webFeedID) { result in
|
||||
self.caller.retrieveItemIDs(type: .allForFeed, feedID: feed.feedID) { result in
|
||||
self.refreshProgress.completeTask()
|
||||
switch result {
|
||||
case .success(let articleIDs):
|
||||
@@ -1032,8 +1032,8 @@ private extension ReaderAPIAccountDelegate {
|
||||
|
||||
func processEntries(account: Account, entries: [ReaderAPIEntry]?, completion: @escaping VoidCompletionBlock) {
|
||||
let parsedItems = mapEntriesToParsedItems(account: account, entries: entries)
|
||||
let webFeedIDsAndItems = Dictionary(grouping: parsedItems, by: { item in item.feedURL } ).mapValues { Set($0) }
|
||||
account.update(webFeedIDsAndItems: webFeedIDsAndItems, defaultRead: true) { _ in
|
||||
let feedIDsAndItems = Dictionary(grouping: parsedItems, by: { item in item.feedURL } ).mapValues { Set($0) }
|
||||
account.update(feedIDsAndItems: feedIDsAndItems, defaultRead: true) { _ in
|
||||
completion()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -554,7 +554,7 @@ final class ReaderAPICaller: NSObject {
|
||||
|
||||
}
|
||||
|
||||
func retrieveItemIDs(type: ItemIDType, webFeedID: String? = nil, completion: @escaping ((Result<[String], Error>) -> Void)) {
|
||||
func retrieveItemIDs(type: ItemIDType, feedID: String? = nil, completion: @escaping ((Result<[String], Error>) -> Void)) {
|
||||
guard let baseURL = apiBaseURL else {
|
||||
completion(.failure(CredentialsError.incompleteCredentials))
|
||||
return
|
||||
@@ -579,13 +579,13 @@ final class ReaderAPICaller: NSObject {
|
||||
queryItems.append(URLQueryItem(name: "ot", value: String(Int(sinceTimeInterval))))
|
||||
queryItems.append(URLQueryItem(name: "s", value: ReaderStreams.readingList.rawValue))
|
||||
case .allForFeed:
|
||||
guard let webFeedID = webFeedID else {
|
||||
guard let feedID = feedID else {
|
||||
completion(.failure(ReaderAPIAccountDelegateError.invalidParameter))
|
||||
return
|
||||
}
|
||||
let sinceTimeInterval = (Calendar.current.date(byAdding: .month, value: -3, to: Date()) ?? Date()).timeIntervalSince1970
|
||||
queryItems.append(URLQueryItem(name: "ot", value: String(Int(sinceTimeInterval))))
|
||||
queryItems.append(URLQueryItem(name: "s", value: webFeedID))
|
||||
queryItems.append(URLQueryItem(name: "s", value: feedID))
|
||||
case .unread:
|
||||
queryItems.append(URLQueryItem(name: "s", value: ReaderStreams.readingList.rawValue))
|
||||
queryItems.append(URLQueryItem(name: "xt", value: ReaderState.read.rawValue))
|
||||
|
||||
@@ -16,7 +16,7 @@ public enum SidebarItemIdentifier: CustomStringConvertible, Hashable, Equatable
|
||||
|
||||
case smartFeed(String) // String is a unique identifier
|
||||
case script(String) // String is a unique identifier
|
||||
case webFeed(String, String) // accountID, webFeedID
|
||||
case feed(String, String) // accountID, feedID
|
||||
case folder(String, String) // accountID, folderName
|
||||
|
||||
public var description: String {
|
||||
@@ -25,8 +25,8 @@ public enum SidebarItemIdentifier: CustomStringConvertible, Hashable, Equatable
|
||||
return "smartFeed: \(id)"
|
||||
case .script(let id):
|
||||
return "script: \(id)"
|
||||
case .webFeed(let accountID, let webFeedID):
|
||||
return "feed: \(accountID)_\(webFeedID)"
|
||||
case .feed(let accountID, let feedID):
|
||||
return "feed: \(accountID)_\(feedID)"
|
||||
case .folder(let accountID, let folderName):
|
||||
return "folder: \(accountID)_\(folderName)"
|
||||
}
|
||||
@@ -44,11 +44,11 @@ public enum SidebarItemIdentifier: CustomStringConvertible, Hashable, Equatable
|
||||
"type": "script",
|
||||
"id": id
|
||||
]
|
||||
case .webFeed(let accountID, let webFeedID):
|
||||
case .feed(let accountID, let feedID):
|
||||
return [
|
||||
"type": "feed",
|
||||
"accountID": accountID,
|
||||
"webFeedID": webFeedID
|
||||
"feedID": feedID
|
||||
]
|
||||
case .folder(let accountID, let folderName):
|
||||
return [
|
||||
@@ -70,8 +70,8 @@ public enum SidebarItemIdentifier: CustomStringConvertible, Hashable, Equatable
|
||||
guard let id = userInfo["id"] as? String else { return nil }
|
||||
self = SidebarItemIdentifier.script(id)
|
||||
case "feed":
|
||||
guard let accountID = userInfo["accountID"] as? String, let webFeedID = userInfo["webFeedID"] as? String else { return nil }
|
||||
self = SidebarItemIdentifier.webFeed(accountID, webFeedID)
|
||||
guard let accountID = userInfo["accountID"] as? String, let feedID = userInfo["feedID"] as? String else { return nil }
|
||||
self = SidebarItemIdentifier.feed(accountID, feedID)
|
||||
case "folder":
|
||||
guard let accountID = userInfo["accountID"] as? String, let folderName = userInfo["folderName"] as? String else { return nil }
|
||||
self = SidebarItemIdentifier.folder(accountID, folderName)
|
||||
|
||||
@@ -33,8 +33,8 @@ class AccountFeedbinFolderContentsSyncTest: XCTestCase {
|
||||
waitForExpectations(timeout: 5, handler: nil)
|
||||
|
||||
let folder = account.folders?.filter { $0.name == "Developers" } .first!
|
||||
XCTAssertEqual(156, folder?.topLevelWebFeeds.count ?? 0)
|
||||
XCTAssertEqual(2, account.topLevelWebFeeds.count)
|
||||
XCTAssertEqual(156, folder?.topLevelFeeds.count ?? 0)
|
||||
XCTAssertEqual(2, account.topLevelFeeds.count)
|
||||
|
||||
// Test Adding a Feed to the folder
|
||||
testTransport.testFiles["https://api.feedbin.com/v2/taggings.json"] = "JSON/taggings_add.json"
|
||||
@@ -45,8 +45,8 @@ class AccountFeedbinFolderContentsSyncTest: XCTestCase {
|
||||
}
|
||||
waitForExpectations(timeout: 5, handler: nil)
|
||||
|
||||
XCTAssertEqual(157, folder?.topLevelWebFeeds.count ?? 0)
|
||||
XCTAssertEqual(1, account.topLevelWebFeeds.count)
|
||||
XCTAssertEqual(157, folder?.topLevelFeeds.count ?? 0)
|
||||
XCTAssertEqual(1, account.topLevelFeeds.count)
|
||||
|
||||
// Test Deleting some Feeds from the folder
|
||||
testTransport.testFiles["https://api.feedbin.com/v2/taggings.json"] = "JSON/taggings_delete.json"
|
||||
@@ -57,8 +57,8 @@ class AccountFeedbinFolderContentsSyncTest: XCTestCase {
|
||||
}
|
||||
waitForExpectations(timeout: 5, handler: nil)
|
||||
|
||||
XCTAssertEqual(153, folder?.topLevelWebFeeds.count ?? 0)
|
||||
XCTAssertEqual(5, account.topLevelWebFeeds.count)
|
||||
XCTAssertEqual(153, folder?.topLevelFeeds.count ?? 0)
|
||||
XCTAssertEqual(5, account.topLevelFeeds.count)
|
||||
|
||||
TestAccountManager.shared.deleteAccount(account)
|
||||
|
||||
|
||||
@@ -36,9 +36,9 @@ class AccountFeedbinSyncTest: XCTestCase {
|
||||
}
|
||||
waitForExpectations(timeout: 5, handler: nil)
|
||||
|
||||
XCTAssertEqual(224, account.flattenedWebFeeds().count)
|
||||
XCTAssertEqual(224, account.flattenedFeeds().count)
|
||||
|
||||
let daringFireball = account.idToWebFeedDictionary["1296379"]
|
||||
let daringFireball = account.idToFeedDictionary["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.flattenedWebFeeds().count)
|
||||
XCTAssertEqual(225, account.flattenedFeeds().count)
|
||||
|
||||
let bPixels = account.idToWebFeedDictionary["1096623"]
|
||||
let bPixels = account.idToFeedDictionary["1096623"]
|
||||
XCTAssertEqual("Beautiful Pixels", bPixels?.name)
|
||||
XCTAssertEqual("https://feedpress.me/beautifulpixels", bPixels?.url)
|
||||
XCTAssertEqual("https://beautifulpixels.com/", bPixels?.homePageURL)
|
||||
|
||||
+8
-8
@@ -59,7 +59,7 @@ class FeedlyCreateFeedsForCollectionFoldersOperationTests: XCTestCase {
|
||||
completionExpectation.fulfill()
|
||||
}
|
||||
|
||||
XCTAssertTrue(account.flattenedWebFeeds().isEmpty, "Expected empty account.")
|
||||
XCTAssertTrue(account.flattenedFeeds().isEmpty, "Expected empty account.")
|
||||
|
||||
MainThreadOperationQueue.shared.add(createFeeds)
|
||||
|
||||
@@ -73,8 +73,8 @@ class FeedlyCreateFeedsForCollectionFoldersOperationTests: XCTestCase {
|
||||
.flatMap { $0 }
|
||||
.map { $0.title })
|
||||
|
||||
let accountFeeds = account.flattenedWebFeeds()
|
||||
let ingestedIds = Set(accountFeeds.map { $0.webFeedID })
|
||||
let accountFeeds = account.flattenedFeeds()
|
||||
let ingestedIds = Set(accountFeeds.map { $0.feedID })
|
||||
let ingestedTitles = Set(accountFeeds.map { $0.nameForDisplay })
|
||||
|
||||
let missingIds = feedIds.subtracting(ingestedIds)
|
||||
@@ -92,7 +92,7 @@ class FeedlyCreateFeedsForCollectionFoldersOperationTests: XCTestCase {
|
||||
let ingestedFolderAndFeedIds = (account.folders ?? Set())
|
||||
.sorted { $0.externalID! < $1.externalID! }
|
||||
.compactMap { folder -> [String: [String]]? in
|
||||
return [folder.externalID!: folder.topLevelWebFeeds.map { $0.webFeedID }.sorted(by: <)]
|
||||
return [folder.externalID!: folder.topLevelFeeds.map { $0.feedID }.sorted(by: <)]
|
||||
}
|
||||
|
||||
XCTAssertEqual(expectedFolderAndFeedIds, ingestedFolderAndFeedIds, "Did not ingest feeds in their corresponding folders.")
|
||||
@@ -130,7 +130,7 @@ class FeedlyCreateFeedsForCollectionFoldersOperationTests: XCTestCase {
|
||||
completionExpectation.fulfill()
|
||||
}
|
||||
|
||||
XCTAssertTrue(account.flattenedWebFeeds().isEmpty, "Expected empty account.")
|
||||
XCTAssertTrue(account.flattenedFeeds().isEmpty, "Expected empty account.")
|
||||
|
||||
MainThreadOperationQueue.shared.add(createFeeds)
|
||||
|
||||
@@ -166,8 +166,8 @@ class FeedlyCreateFeedsForCollectionFoldersOperationTests: XCTestCase {
|
||||
.flatMap { $0 }
|
||||
.map { $0.title })
|
||||
|
||||
let accountFeeds = account.flattenedWebFeeds()
|
||||
let ingestedIds = Set(accountFeeds.map { $0.webFeedID })
|
||||
let accountFeeds = account.flattenedFeeds()
|
||||
let ingestedIds = Set(accountFeeds.map { $0.feedID })
|
||||
let ingestedTitles = Set(accountFeeds.map { $0.nameForDisplay })
|
||||
|
||||
XCTAssertEqual(ingestedIds.count, feedIds.count)
|
||||
@@ -188,7 +188,7 @@ class FeedlyCreateFeedsForCollectionFoldersOperationTests: XCTestCase {
|
||||
let ingestedFolderAndFeedIds = (account.folders ?? Set())
|
||||
.sorted { $0.externalID! < $1.externalID! }
|
||||
.compactMap { folder -> [String: [String]]? in
|
||||
return [folder.externalID!: folder.topLevelWebFeeds.map { $0.webFeedID }.sorted(by: <)]
|
||||
return [folder.externalID!: folder.topLevelFeeds.map { $0.feedID }.sorted(by: <)]
|
||||
}
|
||||
|
||||
XCTAssertEqual(expectedFolderAndFeedIds, ingestedFolderAndFeedIds, "Did not ingest feeds to their corresponding folders.")
|
||||
|
||||
@@ -55,7 +55,7 @@ class FeedlyEntryParserTests: XCTestCase {
|
||||
XCTAssertEqual(item.uniqueID, entry.id)
|
||||
|
||||
// The following is not an error.
|
||||
// The feedURL must match the webFeedID for the article to be connected to its matching feed.
|
||||
// The feedURL must match the feedID for the article to be connected to its matching feed.
|
||||
XCTAssertEqual(item.feedURL, origin.streamId)
|
||||
XCTAssertEqual(item.title, entry.title)
|
||||
XCTAssertEqual(item.contentHTML, content.content)
|
||||
|
||||
@@ -22,7 +22,7 @@ class FeedlyFeedParserTests: XCTestCase {
|
||||
XCTAssertEqual(parser.title, name)
|
||||
XCTAssertEqual(parser.homePageURL, website)
|
||||
XCTAssertEqual(parser.url, url)
|
||||
XCTAssertEqual(parser.webFeedID, id)
|
||||
XCTAssertEqual(parser.feedID, id)
|
||||
}
|
||||
|
||||
func testSanitization() {
|
||||
@@ -36,6 +36,6 @@ class FeedlyFeedParserTests: XCTestCase {
|
||||
XCTAssertEqual(parser.title, name)
|
||||
XCTAssertEqual(parser.homePageURL, website)
|
||||
XCTAssertEqual(parser.url, url)
|
||||
XCTAssertEqual(parser.webFeedID, id)
|
||||
XCTAssertEqual(parser.feedID, id)
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -180,7 +180,7 @@ class FeedlyMirrorCollectionsAsFoldersOperationTests: XCTestCase {
|
||||
|
||||
waitForExpectations(timeout: 2)
|
||||
|
||||
XCTAssertFalse(account.flattenedWebFeeds().isEmpty, "Expected account to have feeds.")
|
||||
XCTAssertFalse(account.flattenedFeeds().isEmpty, "Expected account to have feeds.")
|
||||
}
|
||||
|
||||
// Now that the folders are added, remove them all.
|
||||
@@ -197,7 +197,7 @@ class FeedlyMirrorCollectionsAsFoldersOperationTests: XCTestCase {
|
||||
|
||||
waitForExpectations(timeout: 2)
|
||||
|
||||
let feeds = account.flattenedWebFeeds()
|
||||
let feeds = account.flattenedFeeds()
|
||||
|
||||
XCTAssertTrue(feeds.isEmpty)
|
||||
}
|
||||
|
||||
@@ -134,12 +134,12 @@ class FeedlyTestSupport {
|
||||
return
|
||||
}
|
||||
let collectionFeeds = collection["feeds"] as! [[String: Any]]
|
||||
let folderFeeds = folder.topLevelWebFeeds
|
||||
let folderFeeds = folder.topLevelFeeds
|
||||
|
||||
XCTAssertEqual(collectionFeeds.count, folderFeeds.count)
|
||||
|
||||
let collectionFeedIds = Set(collectionFeeds.map { $0["id"] as! String })
|
||||
let folderFeedIds = Set(folderFeeds.map { $0.webFeedID })
|
||||
let folderFeedIds = Set(folderFeeds.map { $0.feedID })
|
||||
let missingFeedIds = collectionFeedIds.subtracting(folderFeedIds)
|
||||
|
||||
XCTAssertTrue(missingFeedIds.isEmpty, "Feeds with these ids were not found in the \"\(label)\" folder.")
|
||||
@@ -210,7 +210,7 @@ class FeedlyTestSupport {
|
||||
for item in articleItems where item.id == article.articleID {
|
||||
XCTAssertEqual(article.uniqueID, item.id)
|
||||
XCTAssertEqual(article.contentHTML, item.content)
|
||||
XCTAssertEqual(article.webFeedID, item.feedId)
|
||||
XCTAssertEqual(article.feedID, item.feedId)
|
||||
XCTAssertEqual(article.externalURL, item.externalUrl)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 webFeedID: String // Likely a URL, but not necessarily
|
||||
public let feedID: 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?
|
||||
@@ -28,9 +28,9 @@ public struct Article: Hashable {
|
||||
public let authors: Set<Author>?
|
||||
public let 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?, datePublished: Date?, dateModified: Date?, authors: Set<Author>?, 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?, datePublished: Date?, dateModified: Date?, authors: Set<Author>?, status: ArticleStatus) {
|
||||
self.accountID = accountID
|
||||
self.webFeedID = webFeedID
|
||||
self.feedID = feedID
|
||||
self.uniqueID = uniqueID
|
||||
self.title = title
|
||||
self.contentHTML = contentHTML
|
||||
@@ -48,12 +48,12 @@ public struct Article: Hashable {
|
||||
self.articleID = articleID
|
||||
}
|
||||
else {
|
||||
self.articleID = Article.calculatedArticleID(webFeedID: webFeedID, uniqueID: uniqueID)
|
||||
self.articleID = Article.calculatedArticleID(feedID: feedID, uniqueID: uniqueID)
|
||||
}
|
||||
}
|
||||
|
||||
public static func calculatedArticleID(webFeedID: String, uniqueID: String) -> String {
|
||||
return databaseIDWithString("\(webFeedID) \(uniqueID)")
|
||||
public static func calculatedArticleID(feedID: String, uniqueID: String) -> String {
|
||||
return databaseIDWithString("\(feedID) \(uniqueID)")
|
||||
}
|
||||
|
||||
// MARK: - Hashable
|
||||
@@ -65,7 +65,7 @@ public struct Article: Hashable {
|
||||
// MARK: - Equatable
|
||||
|
||||
static public func ==(lhs: Article, rhs: Article) -> Bool {
|
||||
return lhs.articleID == rhs.articleID && lhs.accountID == rhs.accountID && lhs.webFeedID == rhs.webFeedID && lhs.uniqueID == rhs.uniqueID && lhs.title == rhs.title && lhs.contentHTML == rhs.contentHTML && lhs.contentText == rhs.contentText && lhs.rawLink == rhs.rawLink && lhs.rawExternalLink == rhs.rawExternalLink && lhs.summary == rhs.summary && lhs.rawImageLink == rhs.rawImageLink && lhs.datePublished == rhs.datePublished && lhs.dateModified == rhs.dateModified && lhs.authors == rhs.authors
|
||||
return lhs.articleID == rhs.articleID && lhs.accountID == rhs.accountID && lhs.feedID == rhs.feedID && lhs.uniqueID == rhs.uniqueID && lhs.title == rhs.title && lhs.contentHTML == rhs.contentHTML && lhs.contentText == rhs.contentText && lhs.rawLink == rhs.rawLink && lhs.rawExternalLink == rhs.rawExternalLink && lhs.summary == rhs.summary && lhs.rawImageLink == rhs.rawImageLink && lhs.datePublished == rhs.datePublished && lhs.dateModified == rhs.dateModified && lhs.authors == rhs.authors
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ import Articles
|
||||
|
||||
// Main thread only.
|
||||
|
||||
public typealias UnreadCountDictionary = [String: Int] // webFeedID: unreadCount
|
||||
public typealias UnreadCountDictionary = [String: Int] // feedID: unreadCount
|
||||
public typealias UnreadCountDictionaryCompletionResult = Result<UnreadCountDictionary,DatabaseError>
|
||||
public typealias UnreadCountDictionaryCompletionBlock = (UnreadCountDictionaryCompletionResult) -> Void
|
||||
|
||||
@@ -90,32 +90,32 @@ public final class ArticlesDatabase {
|
||||
|
||||
// MARK: - Fetching Articles
|
||||
|
||||
public func fetchArticles(_ webFeedID: String) throws -> Set<Article> {
|
||||
return try articlesTable.fetchArticles(webFeedID)
|
||||
public func fetchArticles(_ feedID: String) throws -> Set<Article> {
|
||||
return try articlesTable.fetchArticles(feedID)
|
||||
}
|
||||
|
||||
public func fetchArticles(_ webFeedIDs: Set<String>) throws -> Set<Article> {
|
||||
return try articlesTable.fetchArticles(webFeedIDs)
|
||||
public func fetchArticles(_ feedIDs: Set<String>) throws -> Set<Article> {
|
||||
return try articlesTable.fetchArticles(feedIDs)
|
||||
}
|
||||
|
||||
public func fetchArticles(articleIDs: Set<String>) throws -> Set<Article> {
|
||||
return try articlesTable.fetchArticles(articleIDs: articleIDs)
|
||||
}
|
||||
|
||||
public func fetchUnreadArticles(_ webFeedIDs: Set<String>, _ limit: Int?) throws -> Set<Article> {
|
||||
return try articlesTable.fetchUnreadArticles(webFeedIDs, limit)
|
||||
public func fetchUnreadArticles(_ feedIDs: Set<String>, _ limit: Int?) throws -> Set<Article> {
|
||||
return try articlesTable.fetchUnreadArticles(feedIDs, limit)
|
||||
}
|
||||
|
||||
public func fetchTodayArticles(_ webFeedIDs: Set<String>, _ limit: Int?) throws -> Set<Article> {
|
||||
return try articlesTable.fetchArticlesSince(webFeedIDs, todayCutoffDate(), limit)
|
||||
public func fetchTodayArticles(_ feedIDs: Set<String>, _ limit: Int?) throws -> Set<Article> {
|
||||
return try articlesTable.fetchArticlesSince(feedIDs, todayCutoffDate(), limit)
|
||||
}
|
||||
|
||||
public func fetchStarredArticles(_ webFeedIDs: Set<String>, _ limit: Int?) throws -> Set<Article> {
|
||||
return try articlesTable.fetchStarredArticles(webFeedIDs, limit)
|
||||
public func fetchStarredArticles(_ feedIDs: Set<String>, _ limit: Int?) throws -> Set<Article> {
|
||||
return try articlesTable.fetchStarredArticles(feedIDs, limit)
|
||||
}
|
||||
|
||||
public func fetchArticlesMatching(_ searchString: String, _ webFeedIDs: Set<String>) throws -> Set<Article> {
|
||||
return try articlesTable.fetchArticlesMatching(searchString, webFeedIDs)
|
||||
public func fetchArticlesMatching(_ searchString: String, _ feedIDs: Set<String>) throws -> Set<Article> {
|
||||
return try articlesTable.fetchArticlesMatching(searchString, feedIDs)
|
||||
}
|
||||
|
||||
public func fetchArticlesMatchingWithArticleIDs(_ searchString: String, _ articleIDs: Set<String>) throws -> Set<Article> {
|
||||
@@ -124,32 +124,32 @@ public final class ArticlesDatabase {
|
||||
|
||||
// MARK: - Fetching Articles Async
|
||||
|
||||
public func fetchArticlesAsync(_ webFeedID: String, _ completion: @escaping ArticleSetResultBlock) {
|
||||
articlesTable.fetchArticlesAsync(webFeedID, completion)
|
||||
public func fetchArticlesAsync(_ feedID: String, _ completion: @escaping ArticleSetResultBlock) {
|
||||
articlesTable.fetchArticlesAsync(feedID, completion)
|
||||
}
|
||||
|
||||
public func fetchArticlesAsync(_ webFeedIDs: Set<String>, _ completion: @escaping ArticleSetResultBlock) {
|
||||
articlesTable.fetchArticlesAsync(webFeedIDs, completion)
|
||||
public func fetchArticlesAsync(_ feedIDs: Set<String>, _ completion: @escaping ArticleSetResultBlock) {
|
||||
articlesTable.fetchArticlesAsync(feedIDs, completion)
|
||||
}
|
||||
|
||||
public func fetchArticlesAsync(articleIDs: Set<String>, _ completion: @escaping ArticleSetResultBlock) {
|
||||
articlesTable.fetchArticlesAsync(articleIDs: articleIDs, completion)
|
||||
}
|
||||
|
||||
public func fetchUnreadArticlesAsync(_ webFeedIDs: Set<String>, _ limit: Int?, _ completion: @escaping ArticleSetResultBlock) {
|
||||
articlesTable.fetchUnreadArticlesAsync(webFeedIDs, limit, completion)
|
||||
public func fetchUnreadArticlesAsync(_ feedIDs: Set<String>, _ limit: Int?, _ completion: @escaping ArticleSetResultBlock) {
|
||||
articlesTable.fetchUnreadArticlesAsync(feedIDs, limit, completion)
|
||||
}
|
||||
|
||||
public func fetchTodayArticlesAsync(_ webFeedIDs: Set<String>, _ limit: Int?, _ completion: @escaping ArticleSetResultBlock) {
|
||||
articlesTable.fetchArticlesSinceAsync(webFeedIDs, todayCutoffDate(), limit, completion)
|
||||
public func fetchTodayArticlesAsync(_ feedIDs: Set<String>, _ limit: Int?, _ completion: @escaping ArticleSetResultBlock) {
|
||||
articlesTable.fetchArticlesSinceAsync(feedIDs, todayCutoffDate(), limit, completion)
|
||||
}
|
||||
|
||||
public func fetchedStarredArticlesAsync(_ webFeedIDs: Set<String>, _ limit: Int?, _ completion: @escaping ArticleSetResultBlock) {
|
||||
articlesTable.fetchStarredArticlesAsync(webFeedIDs, limit, completion)
|
||||
public func fetchedStarredArticlesAsync(_ feedIDs: Set<String>, _ limit: Int?, _ completion: @escaping ArticleSetResultBlock) {
|
||||
articlesTable.fetchStarredArticlesAsync(feedIDs, limit, completion)
|
||||
}
|
||||
|
||||
public func fetchArticlesMatchingAsync(_ searchString: String, _ webFeedIDs: Set<String>, _ completion: @escaping ArticleSetResultBlock) {
|
||||
articlesTable.fetchArticlesMatchingAsync(searchString, webFeedIDs, completion)
|
||||
public func fetchArticlesMatchingAsync(_ searchString: String, _ feedIDs: Set<String>, _ completion: @escaping ArticleSetResultBlock) {
|
||||
articlesTable.fetchArticlesMatchingAsync(searchString, feedIDs, completion)
|
||||
}
|
||||
|
||||
public func fetchArticlesMatchingWithArticleIDsAsync(_ searchString: String, _ articleIDs: Set<String>, _ completion: @escaping ArticleSetResultBlock) {
|
||||
@@ -170,8 +170,8 @@ public final class ArticlesDatabase {
|
||||
}
|
||||
|
||||
/// Fetch unread count for a single feed.
|
||||
public func fetchUnreadCount(_ webFeedID: String, _ completion: @escaping SingleUnreadCountCompletionBlock) {
|
||||
let operation = FetchFeedUnreadCountOperation(webFeedID: webFeedID, databaseQueue: queue, cutoffDate: articlesTable.articleCutoffDate)
|
||||
public func fetchUnreadCount(_ feedID: String, _ completion: @escaping SingleUnreadCountCompletionBlock) {
|
||||
let operation = FetchFeedUnreadCountOperation(feedID: feedID, databaseQueue: queue, cutoffDate: articlesTable.articleCutoffDate)
|
||||
operation.completionBlock = { operation in
|
||||
let fetchOperation = operation as! FetchFeedUnreadCountOperation
|
||||
completion(fetchOperation.result)
|
||||
@@ -179,9 +179,9 @@ public final class ArticlesDatabase {
|
||||
operationQueue.add(operation)
|
||||
}
|
||||
|
||||
/// Fetch non-zero unread counts for given webFeedIDs.
|
||||
public func fetchUnreadCounts(for webFeedIDs: Set<String>, _ completion: @escaping UnreadCountDictionaryCompletionBlock) {
|
||||
let operation = FetchUnreadCountsForFeedsOperation(webFeedIDs: webFeedIDs, databaseQueue: queue)
|
||||
/// Fetch non-zero unread counts for given feedIDs.
|
||||
public func fetchUnreadCounts(for feedIDs: Set<String>, _ completion: @escaping UnreadCountDictionaryCompletionBlock) {
|
||||
let operation = FetchUnreadCountsForFeedsOperation(feedIDs: feedIDs, databaseQueue: queue)
|
||||
operation.completionBlock = { operation in
|
||||
let fetchOperation = operation as! FetchUnreadCountsForFeedsOperation
|
||||
completion(fetchOperation.result)
|
||||
@@ -189,30 +189,30 @@ public final class ArticlesDatabase {
|
||||
operationQueue.add(operation)
|
||||
}
|
||||
|
||||
public func fetchUnreadCountForToday(for webFeedIDs: Set<String>, completion: @escaping SingleUnreadCountCompletionBlock) {
|
||||
fetchUnreadCount(for: webFeedIDs, since: todayCutoffDate(), completion: completion)
|
||||
public func fetchUnreadCountForToday(for feedIDs: Set<String>, completion: @escaping SingleUnreadCountCompletionBlock) {
|
||||
fetchUnreadCount(for: feedIDs, since: todayCutoffDate(), completion: completion)
|
||||
}
|
||||
|
||||
public func fetchUnreadCount(for webFeedIDs: Set<String>, since: Date, completion: @escaping SingleUnreadCountCompletionBlock) {
|
||||
articlesTable.fetchUnreadCount(webFeedIDs, since, completion)
|
||||
public func fetchUnreadCount(for feedIDs: Set<String>, since: Date, completion: @escaping SingleUnreadCountCompletionBlock) {
|
||||
articlesTable.fetchUnreadCount(feedIDs, since, completion)
|
||||
}
|
||||
|
||||
public func fetchStarredAndUnreadCount(for webFeedIDs: Set<String>, completion: @escaping SingleUnreadCountCompletionBlock) {
|
||||
articlesTable.fetchStarredAndUnreadCount(webFeedIDs, completion)
|
||||
public func fetchStarredAndUnreadCount(for feedIDs: Set<String>, completion: @escaping SingleUnreadCountCompletionBlock) {
|
||||
articlesTable.fetchStarredAndUnreadCount(feedIDs, completion)
|
||||
}
|
||||
|
||||
// MARK: - Saving, Updating, and Deleting Articles
|
||||
|
||||
/// Update articles and save new ones — for feed-based systems (local and iCloud).
|
||||
public func update(with parsedItems: Set<ParsedItem>, webFeedID: String, deleteOlder: Bool, completion: @escaping UpdateArticlesCompletionBlock) {
|
||||
public func update(with parsedItems: Set<ParsedItem>, feedID: String, deleteOlder: Bool, completion: @escaping UpdateArticlesCompletionBlock) {
|
||||
precondition(retentionStyle == .feedBased)
|
||||
articlesTable.update(parsedItems, webFeedID, deleteOlder, completion)
|
||||
articlesTable.update(parsedItems, feedID, deleteOlder, completion)
|
||||
}
|
||||
|
||||
/// Update articles and save new ones — for sync systems (Feedbin, Feedly, etc.).
|
||||
public func update(webFeedIDsAndItems: [String: Set<ParsedItem>], defaultRead: Bool, completion: @escaping UpdateArticlesCompletionBlock) {
|
||||
public func update(feedIDsAndItems: [String: Set<ParsedItem>], defaultRead: Bool, completion: @escaping UpdateArticlesCompletionBlock) {
|
||||
precondition(retentionStyle == .syncSystem)
|
||||
articlesTable.update(webFeedIDsAndItems, defaultRead, completion)
|
||||
articlesTable.update(feedIDsAndItems, defaultRead, completion)
|
||||
}
|
||||
|
||||
/// Delete articles
|
||||
@@ -289,11 +289,11 @@ public final class ArticlesDatabase {
|
||||
/// This prevents the database from growing forever. If we didn’t do this:
|
||||
/// 1) The database would grow to an inordinate size, and
|
||||
/// 2) the app would become very slow.
|
||||
public func cleanupDatabaseAtStartup(subscribedToWebFeedIDs: Set<String>) {
|
||||
public func cleanupDatabaseAtStartup(subscribedToFeedIDs: Set<String>) {
|
||||
if retentionStyle == .syncSystem {
|
||||
articlesTable.deleteOldArticles()
|
||||
}
|
||||
articlesTable.deleteArticlesNotInSubscribedToFeedIDs(subscribedToWebFeedIDs)
|
||||
articlesTable.deleteArticlesNotInSubscribedToFeedIDs(subscribedToFeedIDs)
|
||||
articlesTable.deleteOldStatuses()
|
||||
}
|
||||
|
||||
|
||||
@@ -47,20 +47,20 @@ final class ArticlesTable: DatabaseTable {
|
||||
|
||||
// MARK: - Fetching Articles for Feed
|
||||
|
||||
func fetchArticles(_ webFeedID: String) throws -> Set<Article> {
|
||||
return try fetchArticles{ self.fetchArticlesForFeedID(webFeedID, $0) }
|
||||
func fetchArticles(_ feedID: String) throws -> Set<Article> {
|
||||
return try fetchArticles{ self.fetchArticlesForFeedID(feedID, $0) }
|
||||
}
|
||||
|
||||
func fetchArticlesAsync(_ webFeedID: String, _ completion: @escaping ArticleSetResultBlock) {
|
||||
fetchArticlesAsync({ self.fetchArticlesForFeedID(webFeedID, $0) }, completion)
|
||||
func fetchArticlesAsync(_ feedID: String, _ completion: @escaping ArticleSetResultBlock) {
|
||||
fetchArticlesAsync({ self.fetchArticlesForFeedID(feedID, $0) }, completion)
|
||||
}
|
||||
|
||||
func fetchArticles(_ webFeedIDs: Set<String>) throws -> Set<Article> {
|
||||
return try fetchArticles{ self.fetchArticles(webFeedIDs, $0) }
|
||||
func fetchArticles(_ feedIDs: Set<String>) throws -> Set<Article> {
|
||||
return try fetchArticles{ self.fetchArticles(feedIDs, $0) }
|
||||
}
|
||||
|
||||
func fetchArticlesAsync(_ webFeedIDs: Set<String>, _ completion: @escaping ArticleSetResultBlock) {
|
||||
fetchArticlesAsync({ self.fetchArticles(webFeedIDs, $0) }, completion)
|
||||
func fetchArticlesAsync(_ feedIDs: Set<String>, _ completion: @escaping ArticleSetResultBlock) {
|
||||
fetchArticlesAsync({ self.fetchArticles(feedIDs, $0) }, completion)
|
||||
}
|
||||
|
||||
// MARK: - Fetching Articles by articleID
|
||||
@@ -75,32 +75,32 @@ final class ArticlesTable: DatabaseTable {
|
||||
|
||||
// MARK: - Fetching Unread Articles
|
||||
|
||||
func fetchUnreadArticles(_ webFeedIDs: Set<String>, _ limit: Int?) throws -> Set<Article> {
|
||||
return try fetchArticles{ self.fetchUnreadArticles(webFeedIDs, limit, $0) }
|
||||
func fetchUnreadArticles(_ feedIDs: Set<String>, _ limit: Int?) throws -> Set<Article> {
|
||||
return try fetchArticles{ self.fetchUnreadArticles(feedIDs, limit, $0) }
|
||||
}
|
||||
|
||||
func fetchUnreadArticlesAsync(_ webFeedIDs: Set<String>, _ limit: Int?, _ completion: @escaping ArticleSetResultBlock) {
|
||||
fetchArticlesAsync({ self.fetchUnreadArticles(webFeedIDs, limit, $0) }, completion)
|
||||
func fetchUnreadArticlesAsync(_ feedIDs: Set<String>, _ limit: Int?, _ completion: @escaping ArticleSetResultBlock) {
|
||||
fetchArticlesAsync({ self.fetchUnreadArticles(feedIDs, limit, $0) }, completion)
|
||||
}
|
||||
|
||||
// MARK: - Fetching Today Articles
|
||||
|
||||
func fetchArticlesSince(_ webFeedIDs: Set<String>, _ cutoffDate: Date, _ limit: Int?) throws -> Set<Article> {
|
||||
return try fetchArticles{ self.fetchArticlesSince(webFeedIDs, cutoffDate, limit, $0) }
|
||||
func fetchArticlesSince(_ feedIDs: Set<String>, _ cutoffDate: Date, _ limit: Int?) throws -> Set<Article> {
|
||||
return try fetchArticles{ self.fetchArticlesSince(feedIDs, cutoffDate, limit, $0) }
|
||||
}
|
||||
|
||||
func fetchArticlesSinceAsync(_ webFeedIDs: Set<String>, _ cutoffDate: Date, _ limit: Int?, _ completion: @escaping ArticleSetResultBlock) {
|
||||
fetchArticlesAsync({ self.fetchArticlesSince(webFeedIDs, cutoffDate, limit, $0) }, completion)
|
||||
func fetchArticlesSinceAsync(_ feedIDs: Set<String>, _ cutoffDate: Date, _ limit: Int?, _ completion: @escaping ArticleSetResultBlock) {
|
||||
fetchArticlesAsync({ self.fetchArticlesSince(feedIDs, cutoffDate, limit, $0) }, completion)
|
||||
}
|
||||
|
||||
// MARK: - Fetching Starred Articles
|
||||
|
||||
func fetchStarredArticles(_ webFeedIDs: Set<String>, _ limit: Int?) throws -> Set<Article> {
|
||||
return try fetchArticles{ self.fetchStarredArticles(webFeedIDs, limit, $0) }
|
||||
func fetchStarredArticles(_ feedIDs: Set<String>, _ limit: Int?) throws -> Set<Article> {
|
||||
return try fetchArticles{ self.fetchStarredArticles(feedIDs, limit, $0) }
|
||||
}
|
||||
|
||||
func fetchStarredArticlesAsync(_ webFeedIDs: Set<String>, _ limit: Int?, _ completion: @escaping ArticleSetResultBlock) {
|
||||
fetchArticlesAsync({ self.fetchStarredArticles(webFeedIDs, limit, $0) }, completion)
|
||||
func fetchStarredArticlesAsync(_ feedIDs: Set<String>, _ limit: Int?, _ completion: @escaping ArticleSetResultBlock) {
|
||||
fetchArticlesAsync({ self.fetchStarredArticles(feedIDs, limit, $0) }, completion)
|
||||
}
|
||||
|
||||
// MARK: - Fetching Search Articles
|
||||
@@ -124,9 +124,9 @@ final class ArticlesTable: DatabaseTable {
|
||||
return articles
|
||||
}
|
||||
|
||||
func fetchArticlesMatching(_ searchString: String, _ webFeedIDs: Set<String>) throws -> Set<Article> {
|
||||
func fetchArticlesMatching(_ searchString: String, _ feedIDs: Set<String>) throws -> Set<Article> {
|
||||
var articles = try fetchArticlesMatching(searchString)
|
||||
articles = articles.filter{ webFeedIDs.contains($0.webFeedID) }
|
||||
articles = articles.filter{ feedIDs.contains($0.feedID) }
|
||||
return articles
|
||||
}
|
||||
|
||||
@@ -136,8 +136,8 @@ final class ArticlesTable: DatabaseTable {
|
||||
return articles
|
||||
}
|
||||
|
||||
func fetchArticlesMatchingAsync(_ searchString: String, _ webFeedIDs: Set<String>, _ completion: @escaping ArticleSetResultBlock) {
|
||||
fetchArticlesAsync({ self.fetchArticlesMatching(searchString, webFeedIDs, $0) }, completion)
|
||||
func fetchArticlesMatchingAsync(_ searchString: String, _ feedIDs: Set<String>, _ completion: @escaping ArticleSetResultBlock) {
|
||||
fetchArticlesAsync({ self.fetchArticlesMatching(searchString, feedIDs, $0) }, completion)
|
||||
}
|
||||
|
||||
func fetchArticlesMatchingWithArticleIDsAsync(_ searchString: String, _ articleIDs: Set<String>, _ completion: @escaping ArticleSetResultBlock) {
|
||||
@@ -190,7 +190,7 @@ final class ArticlesTable: DatabaseTable {
|
||||
|
||||
// MARK: - Updating and Deleting
|
||||
|
||||
func update(_ parsedItems: Set<ParsedItem>, _ webFeedID: String, _ deleteOlder: Bool, _ completion: @escaping UpdateArticlesCompletionBlock) {
|
||||
func update(_ parsedItems: Set<ParsedItem>, _ feedID: String, _ deleteOlder: Bool, _ completion: @escaping UpdateArticlesCompletionBlock) {
|
||||
precondition(retentionStyle == .feedBased)
|
||||
if parsedItems.isEmpty {
|
||||
callUpdateArticlesCompletionBlock(nil, nil, nil, completion)
|
||||
@@ -215,13 +215,13 @@ final class ArticlesTable: DatabaseTable {
|
||||
let (statusesDictionary, _) = self.statusesTable.ensureStatusesForArticleIDs(articleIDs, false, database) //1
|
||||
assert(statusesDictionary.count == articleIDs.count)
|
||||
|
||||
let incomingArticles = Article.articlesWithParsedItems(parsedItems, webFeedID, self.accountID, statusesDictionary) //2
|
||||
let incomingArticles = Article.articlesWithParsedItems(parsedItems, feedID, self.accountID, statusesDictionary) //2
|
||||
if incomingArticles.isEmpty {
|
||||
self.callUpdateArticlesCompletionBlock(nil, nil, nil, completion)
|
||||
return
|
||||
}
|
||||
|
||||
let fetchedArticles = self.fetchArticlesForFeedID(webFeedID, database) //4
|
||||
let fetchedArticles = self.fetchArticlesForFeedID(feedID, database) //4
|
||||
let fetchedArticlesDictionary = fetchedArticles.dictionary()
|
||||
|
||||
let newArticles = self.findAndSaveNewArticles(incomingArticles, fetchedArticlesDictionary, database) //5
|
||||
@@ -270,9 +270,9 @@ final class ArticlesTable: DatabaseTable {
|
||||
}
|
||||
}
|
||||
|
||||
func update(_ webFeedIDsAndItems: [String: Set<ParsedItem>], _ read: Bool, _ completion: @escaping UpdateArticlesCompletionBlock) {
|
||||
func update(_ feedIDsAndItems: [String: Set<ParsedItem>], _ read: Bool, _ completion: @escaping UpdateArticlesCompletionBlock) {
|
||||
precondition(retentionStyle == .syncSystem)
|
||||
if webFeedIDsAndItems.isEmpty {
|
||||
if feedIDsAndItems.isEmpty {
|
||||
callUpdateArticlesCompletionBlock(nil, nil, nil, completion)
|
||||
return
|
||||
}
|
||||
@@ -290,14 +290,14 @@ final class ArticlesTable: DatabaseTable {
|
||||
|
||||
func makeDatabaseCalls(_ database: FMDatabase) {
|
||||
var articleIDs = Set<String>()
|
||||
for (_, parsedItems) in webFeedIDsAndItems {
|
||||
for (_, parsedItems) in feedIDsAndItems {
|
||||
articleIDs.formUnion(parsedItems.articleIDs())
|
||||
}
|
||||
|
||||
let (statusesDictionary, _) = self.statusesTable.ensureStatusesForArticleIDs(articleIDs, read, database) //1
|
||||
assert(statusesDictionary.count == articleIDs.count)
|
||||
|
||||
let allIncomingArticles = Article.articlesWithWebFeedIDsAndItems(webFeedIDsAndItems, self.accountID, statusesDictionary) //2
|
||||
let allIncomingArticles = Article.articlesWithFeedIDsAndItems(feedIDsAndItems, self.accountID, statusesDictionary) //2
|
||||
if allIncomingArticles.isEmpty {
|
||||
self.callUpdateArticlesCompletionBlock(nil, nil, nil, completion)
|
||||
return
|
||||
@@ -366,9 +366,9 @@ final class ArticlesTable: DatabaseTable {
|
||||
|
||||
// MARK: - Unread Counts
|
||||
|
||||
func fetchUnreadCount(_ webFeedIDs: Set<String>, _ since: Date, _ completion: @escaping SingleUnreadCountCompletionBlock) {
|
||||
func fetchUnreadCount(_ feedIDs: Set<String>, _ since: Date, _ completion: @escaping SingleUnreadCountCompletionBlock) {
|
||||
// Get unread count for today, for instance.
|
||||
if webFeedIDs.isEmpty {
|
||||
if feedIDs.isEmpty {
|
||||
completion(.success(0))
|
||||
return
|
||||
}
|
||||
@@ -376,11 +376,11 @@ final class ArticlesTable: DatabaseTable {
|
||||
queue.runInDatabase { databaseResult in
|
||||
|
||||
func makeDatabaseCalls(_ database: FMDatabase) {
|
||||
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(webFeedIDs.count))!
|
||||
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(feedIDs.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;"
|
||||
|
||||
var parameters = [Any]()
|
||||
parameters += Array(webFeedIDs) as [Any]
|
||||
parameters += Array(feedIDs) as [Any]
|
||||
parameters += [since] as [Any]
|
||||
parameters += [since] as [Any]
|
||||
|
||||
@@ -402,8 +402,8 @@ final class ArticlesTable: DatabaseTable {
|
||||
}
|
||||
}
|
||||
|
||||
func fetchStarredAndUnreadCount(_ webFeedIDs: Set<String>, _ completion: @escaping SingleUnreadCountCompletionBlock) {
|
||||
if webFeedIDs.isEmpty {
|
||||
func fetchStarredAndUnreadCount(_ feedIDs: Set<String>, _ completion: @escaping SingleUnreadCountCompletionBlock) {
|
||||
if feedIDs.isEmpty {
|
||||
completion(.success(0))
|
||||
return
|
||||
}
|
||||
@@ -411,9 +411,9 @@ final class ArticlesTable: DatabaseTable {
|
||||
queue.runInDatabase { databaseResult in
|
||||
|
||||
func makeDatabaseCalls(_ database: FMDatabase) {
|
||||
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(webFeedIDs.count))!
|
||||
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(feedIDs.count))!
|
||||
let sql = "select count(*) from articles natural join statuses where feedID in \(placeholders) and read=0 and starred=1;"
|
||||
let parameters = Array(webFeedIDs) as [Any]
|
||||
let parameters = Array(feedIDs) as [Any]
|
||||
|
||||
let unreadCount = self.numberWithSQLAndParameters(sql, parameters, in: database)
|
||||
|
||||
@@ -604,16 +604,16 @@ 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(_ webFeedIDs: Set<String>) {
|
||||
if webFeedIDs.isEmpty {
|
||||
func deleteArticlesNotInSubscribedToFeedIDs(_ feedIDs: Set<String>) {
|
||||
if feedIDs.isEmpty {
|
||||
return
|
||||
}
|
||||
queue.runInDatabase { databaseResult in
|
||||
|
||||
func makeDatabaseCalls(_ database: FMDatabase) {
|
||||
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(webFeedIDs.count))!
|
||||
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(feedIDs.count))!
|
||||
let sql = "select articleID from articles where feedID not in \(placeholders);"
|
||||
let parameters = Array(webFeedIDs) as [Any]
|
||||
let parameters = Array(feedIDs) as [Any]
|
||||
guard let resultSet = database.executeQuery(sql, withArgumentsIn: parameters) else {
|
||||
return
|
||||
}
|
||||
@@ -785,24 +785,24 @@ private extension ArticlesTable {
|
||||
return articlesWithResultSet(resultSet, database)
|
||||
}
|
||||
|
||||
func fetchArticles(_ webFeedIDs: Set<String>, _ database: FMDatabase) -> Set<Article> {
|
||||
func fetchArticles(_ feedIDs: 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 webFeedIDs.isEmpty {
|
||||
if feedIDs.isEmpty {
|
||||
return Set<Article>()
|
||||
}
|
||||
let parameters = webFeedIDs.map { $0 as AnyObject }
|
||||
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(webFeedIDs.count))!
|
||||
let parameters = feedIDs.map { $0 as AnyObject }
|
||||
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(feedIDs.count))!
|
||||
let whereClause = "feedID in \(placeholders)"
|
||||
return fetchArticlesWithWhereClause(database, whereClause: whereClause, parameters: parameters)
|
||||
}
|
||||
|
||||
func fetchUnreadArticles(_ webFeedIDs: Set<String>, _ limit: Int?, _ database: FMDatabase) -> Set<Article> {
|
||||
func fetchUnreadArticles(_ feedIDs: Set<String>, _ limit: Int?, _ database: FMDatabase) -> Set<Article> {
|
||||
// select * from articles natural join statuses where feedID in ('http://ranchero.com/xml/rss.xml') and read=0
|
||||
if webFeedIDs.isEmpty {
|
||||
if feedIDs.isEmpty {
|
||||
return Set<Article>()
|
||||
}
|
||||
let parameters = webFeedIDs.map { $0 as AnyObject }
|
||||
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(webFeedIDs.count))!
|
||||
let parameters = feedIDs.map { $0 as AnyObject }
|
||||
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(feedIDs.count))!
|
||||
var whereClause = "feedID in \(placeholders) and read=0"
|
||||
if let limit = limit {
|
||||
whereClause.append(" order by coalesce(datePublished, dateModified, dateArrived) desc limit \(limit)")
|
||||
@@ -810,8 +810,8 @@ private extension ArticlesTable {
|
||||
return fetchArticlesWithWhereClause(database, whereClause: whereClause, parameters: parameters)
|
||||
}
|
||||
|
||||
func fetchArticlesForFeedID(_ webFeedID: String, _ database: FMDatabase) -> Set<Article> {
|
||||
return fetchArticlesWithWhereClause(database, whereClause: "articles.feedID = ?", parameters: [webFeedID as AnyObject])
|
||||
func fetchArticlesForFeedID(_ feedID: String, _ database: FMDatabase) -> Set<Article> {
|
||||
return fetchArticlesWithWhereClause(database, whereClause: "articles.feedID = ?", parameters: [feedID as AnyObject])
|
||||
}
|
||||
|
||||
func fetchArticles(articleIDs: Set<String>, _ database: FMDatabase) -> Set<Article> {
|
||||
@@ -824,15 +824,15 @@ private extension ArticlesTable {
|
||||
return fetchArticlesWithWhereClause(database, whereClause: whereClause, parameters: parameters)
|
||||
}
|
||||
|
||||
func fetchArticlesSince(_ webFeedIDs: Set<String>, _ cutoffDate: Date, _ limit: Int?, _ database: FMDatabase) -> Set<Article> {
|
||||
func fetchArticlesSince(_ feedIDs: Set<String>, _ cutoffDate: Date, _ limit: Int?, _ 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 webFeedIDs.isEmpty {
|
||||
if feedIDs.isEmpty {
|
||||
return Set<Article>()
|
||||
}
|
||||
let parameters = webFeedIDs.map { $0 as AnyObject } + [cutoffDate as AnyObject, cutoffDate as AnyObject]
|
||||
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(webFeedIDs.count))!
|
||||
let parameters = feedIDs.map { $0 as AnyObject } + [cutoffDate as AnyObject, cutoffDate as AnyObject]
|
||||
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(feedIDs.count))!
|
||||
var whereClause = "feedID in \(placeholders) and (datePublished > ? or (datePublished is null and dateArrived > ?))"
|
||||
if let limit = limit {
|
||||
whereClause.append(" order by coalesce(datePublished, dateModified, dateArrived) desc limit \(limit)")
|
||||
@@ -840,13 +840,13 @@ private extension ArticlesTable {
|
||||
return fetchArticlesWithWhereClause(database, whereClause: whereClause, parameters: parameters)
|
||||
}
|
||||
|
||||
func fetchStarredArticles(_ webFeedIDs: Set<String>, _ limit: Int?, _ database: FMDatabase) -> Set<Article> {
|
||||
func fetchStarredArticles(_ feedIDs: Set<String>, _ limit: Int?, _ database: FMDatabase) -> Set<Article> {
|
||||
// select * from articles natural join statuses where feedID in ('http://ranchero.com/xml/rss.xml') and starred=1;
|
||||
if webFeedIDs.isEmpty {
|
||||
if feedIDs.isEmpty {
|
||||
return Set<Article>()
|
||||
}
|
||||
let parameters = webFeedIDs.map { $0 as AnyObject }
|
||||
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(webFeedIDs.count))!
|
||||
let parameters = feedIDs.map { $0 as AnyObject }
|
||||
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(feedIDs.count))!
|
||||
var whereClause = "feedID in \(placeholders) and starred=1"
|
||||
if let limit = limit {
|
||||
whereClause.append(" order by coalesce(datePublished, dateModified, dateArrived) desc limit \(limit)")
|
||||
@@ -854,10 +854,10 @@ private extension ArticlesTable {
|
||||
return fetchArticlesWithWhereClause(database, whereClause: whereClause, parameters: parameters)
|
||||
}
|
||||
|
||||
func fetchArticlesMatching(_ searchString: String, _ webFeedIDs: Set<String>, _ database: FMDatabase) -> Set<Article> {
|
||||
func fetchArticlesMatching(_ searchString: String, _ feedIDs: 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{ webFeedIDs.contains($0.webFeedID) }
|
||||
return articles.filter{ feedIDs.contains($0.feedID) }
|
||||
}
|
||||
|
||||
func fetchArticlesMatchingWithArticleIDs(_ searchString: String, _ articleIDs: Set<String>, _ database: FMDatabase) -> Set<Article> {
|
||||
|
||||
@@ -19,7 +19,7 @@ extension Article {
|
||||
assertionFailure("Expected articleID.")
|
||||
return nil
|
||||
}
|
||||
guard let webFeedID = row.string(forColumn: DatabaseKey.feedID) else {
|
||||
guard let feedID = row.string(forColumn: DatabaseKey.feedID) else {
|
||||
assertionFailure("Expected feedID.")
|
||||
return nil
|
||||
}
|
||||
@@ -38,10 +38,10 @@ extension Article {
|
||||
let datePublished = row.date(forColumn: DatabaseKey.datePublished)
|
||||
let dateModified = row.date(forColumn: DatabaseKey.dateModified)
|
||||
|
||||
self.init(accountID: accountID, articleID: articleID, webFeedID: webFeedID, uniqueID: uniqueID, title: title, contentHTML: contentHTML, contentText: contentText, url: url, externalURL: externalURL, summary: summary, imageURL: imageURL, datePublished: datePublished, dateModified: dateModified, authors: nil, status: status)
|
||||
self.init(accountID: accountID, articleID: articleID, feedID: feedID, uniqueID: uniqueID, title: title, contentHTML: contentHTML, contentText: contentText, url: url, externalURL: externalURL, summary: summary, imageURL: imageURL, datePublished: datePublished, dateModified: dateModified, authors: nil, status: status)
|
||||
}
|
||||
|
||||
init(parsedItem: ParsedItem, maximumDateAllowed: Date, accountID: String, webFeedID: String, status: ArticleStatus) {
|
||||
init(parsedItem: ParsedItem, maximumDateAllowed: Date, accountID: String, feedID: String, status: ArticleStatus) {
|
||||
let authors = Author.authorsWithParsedAuthors(parsedItem.authors)
|
||||
|
||||
// Deal with future datePublished and dateModified dates.
|
||||
@@ -58,7 +58,7 @@ extension Article {
|
||||
dateModified = nil
|
||||
}
|
||||
|
||||
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, datePublished: datePublished, dateModified: dateModified, authors: authors, status: status)
|
||||
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, datePublished: datePublished, dateModified: dateModified, authors: authors, status: status)
|
||||
}
|
||||
|
||||
private func addPossibleStringChangeWithKeyPath(_ comparisonKeyPath: KeyPath<Article,String?>, _ otherArticle: Article, _ key: String, _ dictionary: inout DatabaseDictionary) {
|
||||
@@ -71,7 +71,7 @@ extension Article {
|
||||
if authors.isEmpty {
|
||||
return self
|
||||
}
|
||||
return Article(accountID: self.accountID, articleID: self.articleID, webFeedID: self.webFeedID, uniqueID: self.uniqueID, title: self.title, contentHTML: self.contentHTML, contentText: self.contentText, url: self.rawLink, externalURL: self.rawExternalLink, summary: self.summary, imageURL: self.rawImageLink, datePublished: self.datePublished, dateModified: self.dateModified, authors: authors, status: self.status)
|
||||
return Article(accountID: self.accountID, articleID: self.articleID, feedID: self.feedID, uniqueID: self.uniqueID, title: self.title, contentHTML: self.contentHTML, contentText: self.contentText, url: self.rawLink, externalURL: self.rawExternalLink, summary: self.summary, imageURL: self.rawImageLink, datePublished: self.datePublished, dateModified: self.dateModified, authors: authors, status: self.status)
|
||||
}
|
||||
|
||||
func changesFrom(_ existingArticle: Article) -> DatabaseDictionary? {
|
||||
@@ -117,22 +117,22 @@ extension Article {
|
||||
return Date().addingTimeInterval(60 * 60 * 24) // Allow dates up to about 24 hours ahead of now
|
||||
}
|
||||
|
||||
static func articlesWithWebFeedIDsAndItems(_ webFeedIDsAndItems: [String: Set<ParsedItem>], _ accountID: String, _ statusesDictionary: [String: ArticleStatus]) -> Set<Article> {
|
||||
static func articlesWithFeedIDsAndItems(_ feedIDsAndItems: [String: Set<ParsedItem>], _ accountID: String, _ statusesDictionary: [String: ArticleStatus]) -> Set<Article> {
|
||||
let maximumDateAllowed = _maximumDateAllowed()
|
||||
var feedArticles = Set<Article>()
|
||||
for (webFeedID, parsedItems) in webFeedIDsAndItems {
|
||||
for (feedID, parsedItems) in feedIDsAndItems {
|
||||
for parsedItem in parsedItems {
|
||||
let status = statusesDictionary[parsedItem.articleID]!
|
||||
let article = Article(parsedItem: parsedItem, maximumDateAllowed: maximumDateAllowed, accountID: accountID, webFeedID: webFeedID, status: status)
|
||||
let article = Article(parsedItem: parsedItem, maximumDateAllowed: maximumDateAllowed, accountID: accountID, feedID: feedID, status: status)
|
||||
feedArticles.insert(article)
|
||||
}
|
||||
}
|
||||
return feedArticles
|
||||
}
|
||||
|
||||
static func articlesWithParsedItems(_ parsedItems: Set<ParsedItem>, _ webFeedID: String, _ accountID: String, _ statusesDictionary: [String: ArticleStatus]) -> Set<Article> {
|
||||
static func articlesWithParsedItems(_ parsedItems: Set<ParsedItem>, _ feedID: String, _ accountID: String, _ statusesDictionary: [String: ArticleStatus]) -> Set<Article> {
|
||||
let maximumDateAllowed = _maximumDateAllowed()
|
||||
return Set(parsedItems.map{ Article(parsedItem: $0, maximumDateAllowed: maximumDateAllowed, accountID: accountID, webFeedID: webFeedID, status: statusesDictionary[$0.articleID]!) })
|
||||
return Set(parsedItems.map{ Article(parsedItem: $0, maximumDateAllowed: maximumDateAllowed, accountID: accountID, feedID: feedID, status: statusesDictionary[$0.articleID]!) })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,7 +142,7 @@ extension Article: DatabaseObject {
|
||||
var d = DatabaseDictionary()
|
||||
|
||||
d[DatabaseKey.articleID] = articleID
|
||||
d[DatabaseKey.feedID] = webFeedID
|
||||
d[DatabaseKey.feedID] = feedID
|
||||
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(webFeedID: feedURL, uniqueID: uniqueID)
|
||||
return Article.calculatedArticleID(feedID: feedURL, uniqueID: uniqueID)
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -63,8 +63,8 @@ private extension FetchAllUnreadCountsOperation {
|
||||
return
|
||||
}
|
||||
let unreadCount = resultSet.long(forColumnIndex: 1)
|
||||
if let webFeedID = resultSet.string(forColumnIndex: 0) {
|
||||
unreadCountDictionary[webFeedID] = unreadCount
|
||||
if let feedID = resultSet.string(forColumnIndex: 0) {
|
||||
unreadCountDictionary[feedID] = unreadCount
|
||||
}
|
||||
}
|
||||
resultSet.close()
|
||||
|
||||
+4
-4
@@ -25,10 +25,10 @@ public final class FetchFeedUnreadCountOperation: MainThreadOperation {
|
||||
|
||||
private let queue: DatabaseQueue
|
||||
private let cutoffDate: Date
|
||||
private let webFeedID: String
|
||||
private let feedID: String
|
||||
|
||||
init(webFeedID: String, databaseQueue: DatabaseQueue, cutoffDate: Date) {
|
||||
self.webFeedID = webFeedID
|
||||
init(feedID: String, databaseQueue: DatabaseQueue, cutoffDate: Date) {
|
||||
self.feedID = feedID
|
||||
self.queue = databaseQueue
|
||||
self.cutoffDate = cutoffDate
|
||||
}
|
||||
@@ -55,7 +55,7 @@ private extension FetchFeedUnreadCountOperation {
|
||||
func fetchUnreadCount(_ database: FMDatabase) {
|
||||
let sql = "select count(*) from articles natural join statuses where feedID=? and read=0;"
|
||||
|
||||
guard let resultSet = database.executeQuery(sql, withArgumentsIn: [webFeedID]) else {
|
||||
guard let resultSet = database.executeQuery(sql, withArgumentsIn: [feedID]) else {
|
||||
informOperationDelegateOfCompletion()
|
||||
return
|
||||
}
|
||||
|
||||
+7
-7
@@ -24,10 +24,10 @@ public final class FetchUnreadCountsForFeedsOperation: MainThreadOperation {
|
||||
public var completionBlock: MainThreadOperation.MainThreadOperationCompletionBlock?
|
||||
|
||||
private let queue: DatabaseQueue
|
||||
private let webFeedIDs: Set<String>
|
||||
private let feedIDs: Set<String>
|
||||
|
||||
init(webFeedIDs: Set<String>, databaseQueue: DatabaseQueue) {
|
||||
self.webFeedIDs = webFeedIDs
|
||||
init(feedIDs: Set<String>, databaseQueue: DatabaseQueue) {
|
||||
self.feedIDs = feedIDs
|
||||
self.queue = databaseQueue
|
||||
}
|
||||
|
||||
@@ -51,10 +51,10 @@ public final class FetchUnreadCountsForFeedsOperation: MainThreadOperation {
|
||||
private extension FetchUnreadCountsForFeedsOperation {
|
||||
|
||||
func fetchUnreadCounts(_ database: FMDatabase) {
|
||||
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(webFeedIDs.count))!
|
||||
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(feedIDs.count))!
|
||||
let sql = "select distinct feedID, count(*) from articles natural join statuses where feedID in \(placeholders) and read=0 group by feedID;"
|
||||
|
||||
let parameters = Array(webFeedIDs) as [Any]
|
||||
let parameters = Array(feedIDs) as [Any]
|
||||
|
||||
guard let resultSet = database.executeQuery(sql, withArgumentsIn: parameters) else {
|
||||
informOperationDelegateOfCompletion()
|
||||
@@ -74,8 +74,8 @@ private extension FetchUnreadCountsForFeedsOperation {
|
||||
return
|
||||
}
|
||||
let unreadCount = resultSet.long(forColumnIndex: 1)
|
||||
if let webFeedID = resultSet.string(forColumnIndex: 0) {
|
||||
unreadCountDictionary[webFeedID] = unreadCount
|
||||
if let feedID = resultSet.string(forColumnIndex: 0) {
|
||||
unreadCountDictionary[feedID] = unreadCount
|
||||
}
|
||||
}
|
||||
resultSet.close()
|
||||
|
||||
@@ -34,8 +34,8 @@ final class AppDefaults {
|
||||
static let subscribeToFeedsInDefaultBrowser = "subscribeToFeedsInDefaultBrowser"
|
||||
static let articleTextSize = "articleTextSize"
|
||||
static let refreshInterval = "refreshInterval"
|
||||
static let addWebFeedAccountID = "addWebFeedAccountID"
|
||||
static let addWebFeedFolderName = "addWebFeedFolderName"
|
||||
static let addFeedAccountID = "addWebFeedAccountID"
|
||||
static let addFeedFolderName = "addWebFeedFolderName"
|
||||
static let addFolderAccountID = "addFolderAccountID"
|
||||
static let importOPMLAccountID = "importOPMLAccountID"
|
||||
static let exportOPMLAccountID = "exportOPMLAccountID"
|
||||
@@ -149,21 +149,21 @@ final class AppDefaults {
|
||||
}
|
||||
}
|
||||
|
||||
var addWebFeedAccountID: String? {
|
||||
var addFeedAccountID: String? {
|
||||
get {
|
||||
return AppDefaults.string(for: Key.addWebFeedAccountID)
|
||||
return AppDefaults.string(for: Key.addFeedAccountID)
|
||||
}
|
||||
set {
|
||||
AppDefaults.setString(for: Key.addWebFeedAccountID, newValue)
|
||||
AppDefaults.setString(for: Key.addFeedAccountID, newValue)
|
||||
}
|
||||
}
|
||||
|
||||
var addWebFeedFolderName: String? {
|
||||
var addFeedFolderName: String? {
|
||||
get {
|
||||
return AppDefaults.string(for: Key.addWebFeedFolderName)
|
||||
return AppDefaults.string(for: Key.addFeedFolderName)
|
||||
}
|
||||
set {
|
||||
AppDefaults.setString(for: Key.addWebFeedFolderName, newValue)
|
||||
AppDefaults.setString(for: Key.addFeedFolderName, newValue)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+14
-14
@@ -40,7 +40,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidat
|
||||
var faviconDownloader: FaviconDownloader!
|
||||
var imageDownloader: ImageDownloader!
|
||||
var authorAvatarDownloader: AuthorAvatarDownloader!
|
||||
var webFeedIconDownloader: WebFeedIconDownloader!
|
||||
var feedIconDownloader: FeedIconDownloader!
|
||||
var extensionContainersFile: ExtensionContainersFile!
|
||||
var extensionFeedAddRequestFile: ExtensionFeedAddRequestFile!
|
||||
|
||||
@@ -138,9 +138,9 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidat
|
||||
addFolderWindowController!.runSheetOnWindow(window)
|
||||
}
|
||||
|
||||
func showAddWebFeedSheetOnWindow(_ window: NSWindow, urlString: String?, name: String?, account: Account?, folder: Folder?) {
|
||||
func showAddFeedSheetOnWindow(_ window: NSWindow, urlString: String?, name: String?, account: Account?, folder: Folder?) {
|
||||
addFeedController = AddFeedController(hostWindow: window)
|
||||
addFeedController?.showAddFeedSheet(.webFeed, urlString, name, account, folder)
|
||||
addFeedController?.showAddFeedSheet(urlString, name, account, folder)
|
||||
}
|
||||
|
||||
// MARK: - NSApplicationDelegate
|
||||
@@ -171,7 +171,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidat
|
||||
imageDownloader = ImageDownloader(folder: imagesFolder)
|
||||
|
||||
authorAvatarDownloader = AuthorAvatarDownloader(imageDownloader: imageDownloader)
|
||||
webFeedIconDownloader = WebFeedIconDownloader(imageDownloader: imageDownloader, folder: cacheFolder)
|
||||
feedIconDownloader = FeedIconDownloader(imageDownloader: imageDownloader, folder: cacheFolder)
|
||||
|
||||
appName = (Bundle.main.infoDictionary!["CFBundleExecutable"]! as! String)
|
||||
}
|
||||
@@ -220,7 +220,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidat
|
||||
mainWindowController?.window?.center()
|
||||
}
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(webFeedSettingDidChange(_:)), name: .WebFeedSettingDidChange, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(feedSettingDidChange(_:)), name: .FeedSettingDidChange, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(userDefaultsDidChange(_:)), name: UserDefaults.didChangeNotification, object: nil)
|
||||
|
||||
DispatchQueue.main.async {
|
||||
@@ -348,11 +348,11 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidat
|
||||
}
|
||||
}
|
||||
|
||||
@objc func webFeedSettingDidChange(_ note: Notification) {
|
||||
guard let feed = note.object as? Feed, let key = note.userInfo?[Feed.WebFeedSettingUserInfoKey] as? String else {
|
||||
@objc func feedSettingDidChange(_ note: Notification) {
|
||||
guard let feed = note.object as? Feed, let key = note.userInfo?[Feed.FeedSettingUserInfoKey] as? String else {
|
||||
return
|
||||
}
|
||||
if key == Feed.WebFeedSettingKey.homePageURL || key == Feed.WebFeedSettingKey.faviconURL {
|
||||
if key == Feed.FeedSettingKey.homePageURL || key == Feed.FeedSettingKey.faviconURL {
|
||||
let _ = faviconDownloader.favicon(for: feed)
|
||||
}
|
||||
}
|
||||
@@ -464,7 +464,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidat
|
||||
return mainWindowController?.isOpen ?? false
|
||||
}
|
||||
|
||||
if item.action == #selector(showAddWebFeedWindow(_:)) || item.action == #selector(showAddFolderWindow(_:)) {
|
||||
if item.action == #selector(showAddFeedWindow(_:)) || item.action == #selector(showAddFolderWindow(_:)) {
|
||||
return !isDisplayingSheet && !AccountManager.shared.activeAccounts.isEmpty
|
||||
}
|
||||
|
||||
@@ -499,14 +499,14 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidat
|
||||
}
|
||||
|
||||
// MARK: Add Feed
|
||||
func addWebFeed(_ urlString: String?, name: String? = nil, account: Account? = nil, folder: Folder? = nil) {
|
||||
func addFeed(_ urlString: String?, name: String? = nil, account: Account? = nil, folder: Folder? = nil) {
|
||||
createAndShowMainWindowIfNecessary()
|
||||
|
||||
if mainWindowController!.isDisplayingSheet {
|
||||
return
|
||||
}
|
||||
|
||||
showAddWebFeedSheetOnWindow(mainWindowController!.window!, urlString: urlString, name: name, account: account, folder: folder)
|
||||
showAddFeedSheetOnWindow(mainWindowController!.window!, urlString: urlString, name: name, account: account, folder: folder)
|
||||
}
|
||||
|
||||
// MARK: - Dock Badge
|
||||
@@ -537,8 +537,8 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidat
|
||||
AccountManager.shared.refreshAll(errorHandler: ErrorHandler.present)
|
||||
}
|
||||
|
||||
@IBAction func showAddWebFeedWindow(_ sender: Any?) {
|
||||
addWebFeed(nil)
|
||||
@IBAction func showAddFeedWindow(_ sender: Any?) {
|
||||
addFeed(nil)
|
||||
}
|
||||
|
||||
@IBAction func showAddFolderWindow(_ sender: Any?) {
|
||||
@@ -611,7 +611,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidat
|
||||
if AccountManager.shared.anyAccountHasNetNewsWireNewsSubscription() {
|
||||
return
|
||||
}
|
||||
addWebFeed(AccountManager.netNewsWireNewsURL, name: "NetNewsWire News")
|
||||
addFeed(AccountManager.netNewsWireNewsURL, name: "NetNewsWire News")
|
||||
}
|
||||
|
||||
@IBAction func openWebsite(_ sender: Any?) {
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="16096" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="22505" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="16096"/>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="22505"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<customObject id="-2" userLabel="File's Owner" customClass="AddWebFeedWindowController" customModule="NetNewsWire" customModuleProvider="target">
|
||||
<customObject id="-2" userLabel="File's Owner" customClass="AddFeedWindowController" customModule="NetNewsWire" customModuleProvider="target">
|
||||
<connections>
|
||||
<outlet property="addButton" destination="dtI-Hu-rFb" id="D11-zR-dWH"/>
|
||||
<outlet property="folderPopupButton" destination="6vt-DL-mVR" id="98M-xt-ZYU"/>
|
||||
@@ -20,21 +21,21 @@
|
||||
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES"/>
|
||||
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
|
||||
<rect key="contentRect" x="196" y="240" width="480" height="217"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1417"/>
|
||||
<view key="contentView" id="EiT-Mj-1SZ">
|
||||
<rect key="frame" x="0.0" y="0.0" width="480" height="216"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1415"/>
|
||||
<view key="contentView" misplaced="YES" id="EiT-Mj-1SZ">
|
||||
<rect key="frame" x="0.0" y="0.0" width="480" height="217"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="hVI-F6-nNT">
|
||||
<rect key="frame" x="33" y="180" width="35" height="16"/>
|
||||
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="hVI-F6-nNT">
|
||||
<rect key="frame" x="33" y="178" width="35" height="16"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="URL:" id="8jE-9v-BT2">
|
||||
<font key="font" metaFont="systemBold"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="gbr-mI-Uzj" userLabel="URL Text Field">
|
||||
<rect key="frame" x="74" y="123" width="386" height="73"/>
|
||||
<textField focusRingType="none" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="gbr-mI-Uzj" userLabel="URL Text Field">
|
||||
<rect key="frame" x="74" y="121" width="386" height="73"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="386" id="Wfx-Jk-wQ0"/>
|
||||
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="73" id="x84-xj-BzJ"/>
|
||||
@@ -48,24 +49,24 @@
|
||||
<outlet property="delegate" destination="-2" id="gcI-CI-e5I"/>
|
||||
</connections>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="sM9-DX-M0c">
|
||||
<rect key="frame" x="22" y="95" width="46" height="16"/>
|
||||
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="sM9-DX-M0c">
|
||||
<rect key="frame" x="22" y="93" width="46" height="16"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Name:" id="8ca-Qp-BkT">
|
||||
<font key="font" metaFont="systemBold"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="TzV-3k-fXd" userLabel="Name Text Field">
|
||||
<rect key="frame" x="74" y="92" width="386" height="21"/>
|
||||
<textField focusRingType="none" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="TzV-3k-fXd" userLabel="Name Text Field">
|
||||
<rect key="frame" x="74" y="90" width="386" height="21"/>
|
||||
<textFieldCell key="cell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" state="on" borderStyle="bezel" placeholderString="Optional" drawsBackground="YES" usesSingleLineMode="YES" id="pLP-pL-5R5">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="dNV-oD-vzR">
|
||||
<rect key="frame" x="18" y="64" width="50" height="16"/>
|
||||
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="dNV-oD-vzR">
|
||||
<rect key="frame" x="18" y="63" width="50" height="16"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Folder:" id="Kwx-7B-CIu">
|
||||
<font key="font" metaFont="systemBold"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
@@ -73,10 +74,10 @@
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="6vt-DL-mVR" userLabel="Folder Popup">
|
||||
<rect key="frame" x="72" y="58" width="391" height="25"/>
|
||||
<rect key="frame" x="71" y="56" width="393" height="25"/>
|
||||
<popUpButtonCell key="cell" type="push" title="Item 1" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="tLJ-zY-CcZ" id="0cM-5q-Snl">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
<menu key="menu" id="OpL-Uf-woJ">
|
||||
<items>
|
||||
<menuItem title="Item 1" state="on" id="tLJ-zY-CcZ"/>
|
||||
@@ -87,7 +88,7 @@
|
||||
</popUpButtonCell>
|
||||
</popUpButton>
|
||||
<button hidden="YES" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="QcY-PB-8Y0">
|
||||
<rect key="frame" x="68" y="13" width="166" height="32"/>
|
||||
<rect key="frame" x="67" y="13" width="160" height="32"/>
|
||||
<buttonCell key="cell" type="push" title="Open Feed Directory" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="wKl-a9-7FY">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
@@ -97,7 +98,7 @@
|
||||
</connections>
|
||||
</button>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="hXq-IS-19x">
|
||||
<rect key="frame" x="302" y="13" width="82" height="32"/>
|
||||
<rect key="frame" x="317" y="13" width="76" height="32"/>
|
||||
<buttonCell key="cell" type="push" title="Cancel" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="Dop-HC-6Q9">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
@@ -110,7 +111,7 @@ Gw
|
||||
</connections>
|
||||
</button>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="dtI-Hu-rFb">
|
||||
<rect key="frame" x="384" y="13" width="82" height="32"/>
|
||||
<rect key="frame" x="391" y="13" width="76" height="32"/>
|
||||
<buttonCell key="cell" type="push" title="Add" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="6NK-Ql-drk">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
@@ -1,8 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="21701" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="22505" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="21701"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="22505"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Application-->
|
||||
@@ -71,7 +70,7 @@
|
||||
<items>
|
||||
<menuItem title="New Feed…" keyEquivalent="n" id="Was-JA-tGl">
|
||||
<connections>
|
||||
<action selector="showAddWebFeedWindow:" target="Ady-hI-5gd" id="LkT-kx-aCR"/>
|
||||
<action selector="showAddFeedWindow:" target="Ady-hI-5gd" id="LkT-kx-aCR"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="New Folder…" keyEquivalent="N" id="wkh-LX-Xp1">
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="17701" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" initialViewController="B8D-0N-5wS">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="22505" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" initialViewController="B8D-0N-5wS">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="17701"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="22505"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
@@ -23,7 +22,7 @@
|
||||
<toolbarItem implicitItemIdentifier="DD0FA79F-72C1-488B-B113-0D2DE89AA468" explicitItemIdentifier="search" label="Search" paletteLabel="Search" toolTip="Search Articles" id="1Ql-WJ-KYi">
|
||||
<size key="minSize" width="96" height="22"/>
|
||||
<size key="maxSize" width="320" height="28"/>
|
||||
<searchField key="view" wantsLayer="YES" verticalHuggingPriority="750" id="Fcs-4u-xuP">
|
||||
<searchField key="view" wantsLayer="YES" focusRingType="none" verticalHuggingPriority="750" id="Fcs-4u-xuP">
|
||||
<rect key="frame" x="0.0" y="14" width="320" height="22"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<searchFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" borderStyle="bezel" usesSingleLineMode="YES" bezelStyle="round" id="syc-TO-rPc">
|
||||
@@ -60,7 +59,7 @@
|
||||
</buttonCell>
|
||||
</button>
|
||||
<connections>
|
||||
<action selector="showAddWebFeedWindow:" target="Oky-zY-oP4" id="pEy-MV-Lnd"/>
|
||||
<action selector="showAddFeedWindow:" target="Oky-zY-oP4" id="pEy-MV-Lnd"/>
|
||||
</connections>
|
||||
</toolbarItem>
|
||||
<toolbarItem implicitItemIdentifier="25C9E98A-867B-4EE2-BC1A-7B453D6B40BF" explicitItemIdentifier="newFolder" label="New Folder" paletteLabel="New Folder" toolTip="New Folder" image="newFolder" id="st0-Wp-nPK" customClass="RSToolbarItem" customModule="RSCore">
|
||||
@@ -141,7 +140,7 @@
|
||||
<toolbarItem implicitItemIdentifier="4DED27B7-8961-48F3-A995-9C961E2C0257" explicitItemIdentifier="openInBrowser" label="Open in Browser" paletteLabel="Open in Browser" toolTip="Open in Browser" image="openInBrowser" id="tid-SB-me3" customClass="RSToolbarItem" customModule="RSCore">
|
||||
<size key="minSize" width="38" height="25"/>
|
||||
<size key="maxSize" width="38" height="27"/>
|
||||
<button key="view" verticalHuggingPriority="750" allowsExpansionToolTips="YES" id="pgp-lZ-j6S">
|
||||
<button key="view" allowsExpansionToolTips="YES" verticalHuggingPriority="750" id="pgp-lZ-j6S">
|
||||
<rect key="frame" x="28" y="14" width="38" height="25"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<buttonCell key="cell" type="roundTextured" bezelStyle="texturedRounded" image="openInBrowser" imagePosition="overlaps" alignment="center" lineBreakMode="truncatingTail" state="on" borderStyle="border" inset="2" id="PNN-ND-sO0">
|
||||
@@ -196,11 +195,11 @@
|
||||
<action selector="cleanUp:" target="Oky-zY-oP4" id="UCH-DG-yk4"/>
|
||||
</connections>
|
||||
</toolbarItem>
|
||||
<toolbarItem implicitItemIdentifier="596363B5-CE41-417C-B8AB-11CC2C99BCA5" label="Article Theme" paletteLabel="Article Theme" sizingBehavior="auto" id="3Hc-al-vK2">
|
||||
<toolbarItem implicitItemIdentifier="596363B5-CE41-417C-B8AB-11CC2C99BCA5" label="Article Theme" paletteLabel="Article Theme" title="Item 1" sizingBehavior="auto" id="3Hc-al-vK2">
|
||||
<nil key="toolTip"/>
|
||||
<popUpButton key="view" verticalHuggingPriority="750" id="MFT-nb-eLG">
|
||||
<rect key="frame" x="0.0" y="14" width="100" height="24"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<popUpButtonCell key="cell" type="roundTextured" title="Item 1" bezelStyle="texturedRounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" selectedItem="xAs-IL-tMv" id="ior-Gb-LTq">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
@@ -309,16 +308,16 @@
|
||||
<rect key="frame" x="0.0" y="0.0" width="240" height="307"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<scrollView borderType="none" autohidesScrollers="YES" horizontalLineScroll="26" horizontalPageScroll="10" verticalLineScroll="26" verticalPageScroll="10" hasHorizontalScroller="NO" horizontalScrollElasticity="none" translatesAutoresizingMaskIntoConstraints="NO" id="cJj-Wv-9ep">
|
||||
<scrollView borderType="none" autohidesScrollers="YES" horizontalLineScroll="32" horizontalPageScroll="10" verticalLineScroll="32" verticalPageScroll="10" hasHorizontalScroller="NO" horizontalScrollElasticity="none" translatesAutoresizingMaskIntoConstraints="NO" id="cJj-Wv-9ep">
|
||||
<rect key="frame" x="0.0" y="0.0" width="240" height="307"/>
|
||||
<clipView key="contentView" drawsBackground="NO" copiesOnScroll="NO" id="2eU-Wz-F9g">
|
||||
<rect key="frame" x="0.0" y="0.0" width="240" height="307"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<outlineView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="firstColumnOnly" selectionHighlightStyle="sourceList" columnReordering="NO" columnResizing="NO" autosaveColumns="NO" typeSelect="NO" rowHeight="24" rowSizeStyle="systemDefault" viewBased="YES" floatsGroupRows="NO" indentationPerLevel="16" outlineTableColumn="ih9-mJ-EA7" id="cnV-kg-Dn2" customClass="SidebarOutlineView" customModule="NetNewsWire" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="241" height="307"/>
|
||||
<outlineView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="firstColumnOnly" selectionHighlightStyle="sourceList" columnReordering="NO" columnResizing="NO" autosaveColumns="NO" typeSelect="NO" rowHeight="32" rowSizeStyle="systemDefault" viewBased="YES" floatsGroupRows="NO" indentationPerLevel="16" outlineTableColumn="ih9-mJ-EA7" id="cnV-kg-Dn2" customClass="SidebarOutlineView" customModule="NetNewsWire" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="270" height="307"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<size key="intercellSpacing" width="3" height="2"/>
|
||||
<size key="intercellSpacing" width="3" height="0.0"/>
|
||||
<color key="backgroundColor" name="_sourceListBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="gridColor" name="gridColor" catalog="System" colorSpace="catalog"/>
|
||||
<tableColumns>
|
||||
@@ -335,10 +334,10 @@
|
||||
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES"/>
|
||||
<prototypeCellViews>
|
||||
<tableCellView identifier="HeaderCell" id="qkt-WA-5tB">
|
||||
<rect key="frame" x="1" y="1" width="238" height="17"/>
|
||||
<rect key="frame" x="11" y="0.0" width="247" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="fNJ-z1-0Up">
|
||||
<textField focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="fNJ-z1-0Up">
|
||||
<rect key="frame" x="0.0" y="1" width="145" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="HEADER CELL" id="dRB-0K-qxz">
|
||||
@@ -353,7 +352,7 @@
|
||||
</connections>
|
||||
</tableCellView>
|
||||
<tableCellView identifier="DataCell" id="HJn-Tm-YNO" customClass="SidebarCell" customModule="NetNewsWire" customModuleProvider="target">
|
||||
<rect key="frame" x="1" y="20" width="238" height="17"/>
|
||||
<rect key="frame" x="11" y="17" width="247" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
</tableCellView>
|
||||
</prototypeCellViews>
|
||||
@@ -393,7 +392,7 @@
|
||||
<constraint firstAttribute="width" constant="40" id="1Yw-ER-8pT"/>
|
||||
</constraints>
|
||||
</progressIndicator>
|
||||
<textField hidden="YES" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="iyL-pW-cT6">
|
||||
<textField hidden="YES" focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="iyL-pW-cT6">
|
||||
<rect key="frame" x="62" y="6" width="160" height="16"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Label" id="dVE-XG-mlU">
|
||||
<font key="font" metaFont="system"/>
|
||||
@@ -469,10 +468,7 @@
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="lSU-OC-sEC">
|
||||
<rect key="frame" x="8" y="176" width="51" height="19"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="18" id="DoO-KI-ena"/>
|
||||
</constraints>
|
||||
<rect key="frame" x="8" y="176" width="46" height="19"/>
|
||||
<popUpButtonCell key="cell" type="recessed" title="Sort" bezelStyle="recessed" alignment="center" lineBreakMode="truncatingTail" borderStyle="border" tag="1" imageScaling="proportionallyDown" inset="2" pullsDown="YES" id="bl0-6I-cH2">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES" changeBackground="YES" changeGray="YES"/>
|
||||
<font key="font" metaFont="smallSystemBold"/>
|
||||
@@ -501,9 +497,12 @@
|
||||
</items>
|
||||
</menu>
|
||||
</popUpButtonCell>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="18" id="DoO-KI-ena"/>
|
||||
</constraints>
|
||||
</popUpButton>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="iA5-go-AO0">
|
||||
<rect key="frame" x="350" y="179" width="13" height="14"/>
|
||||
<rect key="frame" x="350" y="180" width="13" height="13"/>
|
||||
<buttonCell key="cell" type="bevel" bezelStyle="rounded" image="filterInactive" imagePosition="overlaps" alignment="center" imageScaling="proportionallyDown" inset="2" id="j7d-36-DO5">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
@@ -564,7 +563,7 @@
|
||||
<customView hidden="YES" alphaValue="0.90000000000000002" translatesAutoresizingMaskIntoConstraints="NO" id="xI5-lx-RD8" customClass="DetailStatusBarView" customModule="NetNewsWire" customModuleProvider="target">
|
||||
<rect key="frame" x="6" y="2" width="12" height="20"/>
|
||||
<subviews>
|
||||
<textField horizontalHuggingPriority="850" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="Dim-ed-Dcz" userLabel="URL Label">
|
||||
<textField focusRingType="none" horizontalHuggingPriority="850" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="Dim-ed-Dcz" userLabel="URL Label">
|
||||
<rect key="frame" x="4" y="2" width="4" height="16"/>
|
||||
<textFieldCell key="cell" lineBreakMode="truncatingMiddle" selectable="YES" allowsUndo="NO" sendsActionOnEndEditing="YES" alignment="left" usesSingleLineMode="YES" id="znU-Fh-L7H">
|
||||
<font key="font" metaFont="system"/>
|
||||
@@ -604,9 +603,9 @@
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<image name="NSAddTemplate" width="11" height="11"/>
|
||||
<image name="NSRefreshTemplate" width="11" height="15"/>
|
||||
<image name="NSShareTemplate" width="11" height="16"/>
|
||||
<image name="NSAddTemplate" width="18" height="17"/>
|
||||
<image name="NSRefreshTemplate" width="18" height="21"/>
|
||||
<image name="NSShareTemplate" width="19" height="23"/>
|
||||
<image name="cleanUp" width="149" height="113"/>
|
||||
<image name="filterInactive" width="13" height="13"/>
|
||||
<image name="markAllRead" width="22" height="19"/>
|
||||
|
||||
+8
-8
@@ -11,7 +11,7 @@ import Articles
|
||||
import Account
|
||||
import UserNotifications
|
||||
|
||||
final class WebFeedInspectorViewController: NSViewController, Inspector {
|
||||
final class FeedInspectorViewController: NSViewController, Inspector {
|
||||
|
||||
@IBOutlet weak var iconView: IconView!
|
||||
@IBOutlet weak var nameTextField: NSTextField?
|
||||
@@ -35,7 +35,7 @@ final class WebFeedInspectorViewController: NSViewController, Inspector {
|
||||
let isFallbackInspector = false
|
||||
var objects: [Any]? {
|
||||
didSet {
|
||||
renameWebFeedIfNecessary()
|
||||
renameFeedIfNecessary()
|
||||
updateFeed()
|
||||
}
|
||||
}
|
||||
@@ -58,7 +58,7 @@ final class WebFeedInspectorViewController: NSViewController, Inspector {
|
||||
}
|
||||
|
||||
override func viewDidDisappear() {
|
||||
renameWebFeedIfNecessary()
|
||||
renameFeedIfNecessary()
|
||||
}
|
||||
|
||||
// MARK: Actions
|
||||
@@ -112,15 +112,15 @@ final class WebFeedInspectorViewController: NSViewController, Inspector {
|
||||
|
||||
}
|
||||
|
||||
extension WebFeedInspectorViewController: NSTextFieldDelegate {
|
||||
extension FeedInspectorViewController: NSTextFieldDelegate {
|
||||
|
||||
func controlTextDidEndEditing(_ note: Notification) {
|
||||
renameWebFeedIfNecessary()
|
||||
renameFeedIfNecessary()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private extension WebFeedInspectorViewController {
|
||||
private extension FeedInspectorViewController {
|
||||
|
||||
func updateFeed() {
|
||||
guard let objects = objects, objects.count == 1, let singleFeed = objects.first as? Feed else {
|
||||
@@ -202,7 +202,7 @@ private extension WebFeedInspectorViewController {
|
||||
}
|
||||
}
|
||||
|
||||
func renameWebFeedIfNecessary() {
|
||||
func renameFeedIfNecessary() {
|
||||
guard let feed = feed,
|
||||
let account = feed.account,
|
||||
let nameTextField = nameTextField,
|
||||
@@ -210,7 +210,7 @@ private extension WebFeedInspectorViewController {
|
||||
return
|
||||
}
|
||||
|
||||
account.renameWebFeed(feed, to: nameTextField.stringValue) { [weak self] result in
|
||||
account.renameFeed(feed, to: nameTextField.stringValue) { [weak self] result in
|
||||
if case .failure(let error) = result {
|
||||
self?.presentError(error)
|
||||
} else {
|
||||
@@ -1,8 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="17506" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" initialViewController="cfG-Pn-VJS">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="22505" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" initialViewController="cfG-Pn-VJS">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="17506"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="22505"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
@@ -32,12 +31,12 @@
|
||||
<!--Feed-->
|
||||
<scene sceneID="vUh-Rc-fPi">
|
||||
<objects>
|
||||
<viewController title="Feed" storyboardIdentifier="Feed" showSeguePresentationStyle="single" id="sfH-oR-GXm" customClass="WebFeedInspectorViewController" customModule="NetNewsWire" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<viewController title="Feed" storyboardIdentifier="Feed" showSeguePresentationStyle="single" id="sfH-oR-GXm" customClass="FeedInspectorViewController" customModule="NetNewsWire" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" id="ecA-UY-KEd">
|
||||
<rect key="frame" x="0.0" y="0.0" width="273" height="322"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="IWu-80-XC5">
|
||||
<textField focusRingType="none" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="IWu-80-XC5">
|
||||
<rect key="frame" x="20" y="190" width="233" height="56"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="56" id="zV3-AX-gyC"/>
|
||||
@@ -54,7 +53,7 @@ Field</string>
|
||||
<outlet property="delegate" destination="sfH-oR-GXm" id="Dd0-5H-8HH"/>
|
||||
</connections>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="2WO-Iu-p5e">
|
||||
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="2WO-Iu-p5e">
|
||||
<rect key="frame" x="18" y="96" width="237" height="16"/>
|
||||
<textFieldCell key="cell" lineBreakMode="truncatingTail" allowsUndo="NO" sendsActionOnEndEditing="YES" title="Home Page" usesSingleLineMode="YES" id="Fg8-rA-G5J">
|
||||
<font key="font" metaFont="system"/>
|
||||
@@ -62,7 +61,7 @@ Field</string>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="1000" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" textCompletion="NO" translatesAutoresizingMaskIntoConstraints="NO" id="zm0-15-BFy">
|
||||
<textField focusRingType="none" verticalHuggingPriority="1000" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" textCompletion="NO" translatesAutoresizingMaskIntoConstraints="NO" id="zm0-15-BFy">
|
||||
<rect key="frame" x="18" y="76" width="237" height="16"/>
|
||||
<textFieldCell key="cell" selectable="YES" allowsUndo="NO" sendsActionOnEndEditing="YES" title="http://example.com/" id="L2p-ur-j7a">
|
||||
<font key="font" metaFont="system"/>
|
||||
@@ -70,7 +69,7 @@ Field</string>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ju6-Zo-8X4">
|
||||
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ju6-Zo-8X4">
|
||||
<rect key="frame" x="18" y="40" width="237" height="16"/>
|
||||
<textFieldCell key="cell" lineBreakMode="truncatingTail" allowsUndo="NO" sendsActionOnEndEditing="YES" title="Feed" usesSingleLineMode="YES" id="zzB-rX-1dK">
|
||||
<font key="font" metaFont="system"/>
|
||||
@@ -78,7 +77,7 @@ Field</string>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="1000" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="Vvk-KG-JlG">
|
||||
<textField focusRingType="none" verticalHuggingPriority="1000" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="Vvk-KG-JlG">
|
||||
<rect key="frame" x="18" y="20" width="237" height="16"/>
|
||||
<textFieldCell key="cell" selectable="YES" allowsUndo="NO" sendsActionOnEndEditing="YES" title="http://example.com/feed" id="HpC-rK-YGK">
|
||||
<font key="font" metaFont="system"/>
|
||||
@@ -169,7 +168,7 @@ Field</string>
|
||||
</constraints>
|
||||
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyUpOrDown" image="NSFolder" id="C4n-vS-297"/>
|
||||
</imageView>
|
||||
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="jHf-rc-GNr" userLabel="Folder Name Field">
|
||||
<textField focusRingType="none" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="jHf-rc-GNr" userLabel="Folder Name Field">
|
||||
<rect key="frame" x="20" y="20" width="227" height="56"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="56" id="Ele-JD-mB2"/>
|
||||
@@ -221,7 +220,7 @@ Field</string>
|
||||
</constraints>
|
||||
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyUpOrDown" image="NSSmartBadgeTemplate" id="Z52-bd-Lgz"/>
|
||||
</imageView>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="4Xp-FX-kn3">
|
||||
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="4Xp-FX-kn3">
|
||||
<rect key="frame" x="18" y="20" width="231" height="16"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="center" title="Label" id="3v9-Z7-d7l">
|
||||
<font key="font" metaFont="system"/>
|
||||
@@ -256,7 +255,7 @@ Field</string>
|
||||
<rect key="frame" x="0.0" y="0.0" width="267" height="56"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="icb-M6-R2N">
|
||||
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="icb-M6-R2N">
|
||||
<rect key="frame" x="18" y="20" width="231" height="16"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="center" title="Nothing to inspect" id="iLD-8q-EAJ">
|
||||
<font key="font" metaFont="system"/>
|
||||
@@ -264,7 +263,7 @@ Field</string>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="zQp-oc-Qtc">
|
||||
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="zQp-oc-Qtc">
|
||||
<rect key="frame" x="18" y="20" width="231" height="16"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="center" title="Multiple selection" id="5oG-0x-T8O">
|
||||
<font key="font" metaFont="system"/>
|
||||
|
||||
@@ -33,20 +33,18 @@ class AddFeedController: AddFeedWindowControllerDelegate {
|
||||
self.hostWindow = hostWindow
|
||||
}
|
||||
|
||||
func showAddFeedSheet(_ type: AddFeedWindowControllerType, _ urlString: String? = nil, _ name: String? = nil, _ account: Account? = nil, _ folder: Folder? = nil) {
|
||||
func showAddFeedSheet(_ urlString: String? = nil, _ name: String? = nil, _ account: Account? = nil, _ folder: Folder? = nil) {
|
||||
|
||||
let folderTreeControllerDelegate = FolderTreeControllerDelegate()
|
||||
let folderTreeController = TreeController(delegate: folderTreeControllerDelegate)
|
||||
|
||||
switch type {
|
||||
case .webFeed:
|
||||
addFeedWindowController = AddWebFeedWindowController(urlString: urlString ?? urlStringFromPasteboard,
|
||||
name: name,
|
||||
account: account,
|
||||
folder: folder,
|
||||
folderTreeController: folderTreeController,
|
||||
delegate: self)
|
||||
}
|
||||
|
||||
addFeedWindowController = AddFeedWindowController(urlString: urlString ?? urlStringFromPasteboard,
|
||||
name: name,
|
||||
account: account,
|
||||
folder: folder,
|
||||
folderTreeController: folderTreeController,
|
||||
delegate: self)
|
||||
|
||||
addFeedWindowController!.runSheetOnWindow(hostWindow)
|
||||
}
|
||||
|
||||
@@ -60,12 +58,12 @@ class AddFeedController: AddFeedWindowControllerDelegate {
|
||||
}
|
||||
let account = accountAndFolderSpecifier.account
|
||||
|
||||
if account.hasWebFeed(withURL: url.absoluteString) {
|
||||
if account.hasFeed(withURL: url.absoluteString) {
|
||||
showAlreadySubscribedError(url.absoluteString)
|
||||
return
|
||||
}
|
||||
|
||||
account.createWebFeed(url: url.absoluteString, name: title, container: container, validateFeed: true) { result in
|
||||
account.createFeed(url: url.absoluteString, name: title, container: container, validateFeed: true) { result in
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.endShowingProgress()
|
||||
@@ -73,7 +71,7 @@ class AddFeedController: AddFeedWindowControllerDelegate {
|
||||
|
||||
switch result {
|
||||
case .success(let feed):
|
||||
NotificationCenter.default.post(name: .UserDidAddFeed, object: self, userInfo: [UserInfoKey.webFeed: feed])
|
||||
NotificationCenter.default.post(name: .UserDidAddFeed, object: self, userInfo: [UserInfoKey.feed: feed])
|
||||
case .failure(let error):
|
||||
switch error {
|
||||
case AccountError.createErrorAlreadySubscribed:
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
//
|
||||
// AddFeedWIndowController.swift
|
||||
// NetNewsWire
|
||||
//
|
||||
// Created by Maurice Parker on 4/21/20.
|
||||
// Copyright © 2020 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Account
|
||||
|
||||
enum AddFeedWindowControllerType {
|
||||
case webFeed
|
||||
}
|
||||
|
||||
protocol AddFeedWindowControllerDelegate: AnyObject {
|
||||
|
||||
// userEnteredURL will have already been validated and normalized.
|
||||
func addFeedWindowController(_: AddFeedWindowController, userEnteredURL: URL, userEnteredTitle: String?, container: Container)
|
||||
func addFeedWindowControllerUserDidCancel(_: AddFeedWindowController)
|
||||
|
||||
}
|
||||
|
||||
protocol AddFeedWindowController {
|
||||
|
||||
var window: NSWindow? { get }
|
||||
func runSheetOnWindow(_ hostWindow: NSWindow)
|
||||
|
||||
}
|
||||
+12
-5
@@ -12,7 +12,14 @@ import RSTree
|
||||
import Articles
|
||||
import Account
|
||||
|
||||
class AddWebFeedWindowController : NSWindowController, AddFeedWindowController {
|
||||
protocol AddFeedWindowControllerDelegate: AnyObject {
|
||||
|
||||
// userEnteredURL will have already been validated and normalized.
|
||||
func addFeedWindowController(_: AddFeedWindowController, userEnteredURL: URL, userEnteredTitle: String?, container: Container)
|
||||
func addFeedWindowControllerUserDidCancel(_: AddFeedWindowController)
|
||||
}
|
||||
|
||||
class AddFeedWindowController : NSWindowController {
|
||||
|
||||
@IBOutlet var urlTextField: NSTextField!
|
||||
@IBOutlet var nameTextField: NSTextField!
|
||||
@@ -38,7 +45,7 @@ class AddWebFeedWindowController : NSWindowController, AddFeedWindowController {
|
||||
var hostWindow: NSWindow!
|
||||
|
||||
convenience init(urlString: String?, name: String?, account: Account?, folder: Folder?, folderTreeController: TreeController, delegate: AddFeedWindowControllerDelegate?) {
|
||||
self.init(windowNibName: NSNib.Name("AddWebFeedSheet"))
|
||||
self.init(windowNibName: NSNib.Name("AddFeedSheet"))
|
||||
self.urlString = urlString
|
||||
self.initialName = name
|
||||
self.initialAccount = account
|
||||
@@ -64,7 +71,7 @@ class AddWebFeedWindowController : NSWindowController, AddFeedWindowController {
|
||||
|
||||
if let account = initialAccount {
|
||||
FolderTreeMenu.select(account: account, folder: initialFolder, in: folderPopupButton)
|
||||
} else if let container = AddWebFeedDefaultContainer.defaultContainer {
|
||||
} else if let container = AddFeedDefaultContainer.defaultContainer {
|
||||
if let folder = container as? Folder, let account = folder.account {
|
||||
FolderTreeMenu.select(account: account, folder: folder, in: folderPopupButton)
|
||||
} else {
|
||||
@@ -97,7 +104,7 @@ class AddWebFeedWindowController : NSWindowController, AddFeedWindowController {
|
||||
}
|
||||
|
||||
guard let container = selectedContainer() else { return }
|
||||
AddWebFeedDefaultContainer.saveDefaultContainer(container)
|
||||
AddFeedDefaultContainer.saveDefaultContainer(container)
|
||||
|
||||
delegate?.addFeedWindowController(self, userEnteredURL: url, userEnteredTitle: userEnteredTitle, container: container)
|
||||
|
||||
@@ -119,7 +126,7 @@ class AddWebFeedWindowController : NSWindowController, AddFeedWindowController {
|
||||
}
|
||||
}
|
||||
|
||||
private extension AddWebFeedWindowController {
|
||||
private extension AddFeedWindowController {
|
||||
|
||||
private func updateUI() {
|
||||
addButton.isEnabled = urlTextField.stringValue.mayBeURL && selectedContainer() != nil
|
||||
@@ -142,7 +142,7 @@ final class DetailWebViewController: NSViewController {
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(webInspectorEnabledDidChange(_:)), name: .WebInspectorEnabledDidChange, object: nil)
|
||||
#endif
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(webFeedIconDidBecomeAvailable(_:)), name: .WebFeedIconDidBecomeAvailable, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(feedIconDidBecomeAvailable(_:)), name: .FeedIconDidBecomeAvailable, 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)
|
||||
@@ -153,7 +153,7 @@ final class DetailWebViewController: NSViewController {
|
||||
|
||||
// MARK: Notifications
|
||||
|
||||
@objc func webFeedIconDidBecomeAvailable(_ note: Notification) {
|
||||
@objc func feedIconDidBecomeAvailable(_ note: Notification) {
|
||||
reloadArticleImage()
|
||||
}
|
||||
|
||||
|
||||
@@ -610,7 +610,7 @@ extension MainWindowController: TimelineContainerViewControllerDelegate {
|
||||
if let articles = articles {
|
||||
if articles.count == 1 {
|
||||
activityManager.reading(feed: nil, article: articles.first)
|
||||
if articles.first?.webFeed?.isArticleExtractorAlwaysOn ?? false {
|
||||
if articles.first?.feed?.isArticleExtractorAlwaysOn ?? false {
|
||||
detailState = .loading
|
||||
startArticleExtractorForCurrentLink()
|
||||
} else {
|
||||
@@ -627,8 +627,8 @@ extension MainWindowController: TimelineContainerViewControllerDelegate {
|
||||
detailViewController?.setState(detailState, mode: mode)
|
||||
}
|
||||
|
||||
func timelineRequestedWebFeedSelection(_: TimelineContainerViewController, webFeed: Feed) {
|
||||
sidebarViewController?.selectFeed(webFeed)
|
||||
func timelineRequestedFeedSelection(_: TimelineContainerViewController, feed: Feed) {
|
||||
sidebarViewController?.selectFeed(feed)
|
||||
}
|
||||
|
||||
func timelineInvalidatedRestorationState(_: TimelineContainerViewController) {
|
||||
@@ -1076,7 +1076,7 @@ private extension MainWindowController {
|
||||
return currentLink != nil
|
||||
}
|
||||
|
||||
if currentTimelineViewController?.selectedArticles.first?.webFeed != nil {
|
||||
if currentTimelineViewController?.selectedArticles.first?.feed != nil {
|
||||
toolbarButton.isEnabled = true
|
||||
}
|
||||
|
||||
@@ -1312,10 +1312,10 @@ private extension MainWindowController {
|
||||
func buildNewSidebarItemMenu() -> NSMenu {
|
||||
let menu = NSMenu()
|
||||
|
||||
let newWebFeedItem = NSMenuItem()
|
||||
newWebFeedItem.title = NSLocalizedString("New Feed…", comment: "New Feed")
|
||||
newWebFeedItem.action = Selector(("showAddWebFeedWindow:"))
|
||||
menu.addItem(newWebFeedItem)
|
||||
let newFeedItem = NSMenuItem()
|
||||
newFeedItem.title = NSLocalizedString("New Feed…", comment: "New Feed")
|
||||
newFeedItem.action = Selector(("showAddFeedWindow:"))
|
||||
menu.addItem(newFeedItem)
|
||||
|
||||
let newFolderFeedItem = NSMenuItem()
|
||||
newFolderFeedItem.title = NSLocalizedString("New Folder…", comment: "New Folder")
|
||||
|
||||
+48
-48
@@ -11,9 +11,9 @@ import Articles
|
||||
import Account
|
||||
import RSCore
|
||||
|
||||
typealias PasteboardWebFeedDictionary = [String: String]
|
||||
typealias PasteboardFeedDictionary = [String: String]
|
||||
|
||||
struct PasteboardWebFeed: Hashable {
|
||||
struct PasteboardFeed: Hashable {
|
||||
|
||||
private struct Key {
|
||||
static let url = "URL"
|
||||
@@ -23,12 +23,12 @@ struct PasteboardWebFeed: Hashable {
|
||||
// Internal
|
||||
static let accountID = "accountID"
|
||||
static let accountType = "accountType"
|
||||
static let webFeedID = "webFeedID"
|
||||
static let feedID = "feedID"
|
||||
static let editedName = "editedName"
|
||||
}
|
||||
|
||||
let url: String
|
||||
let webFeedID: String?
|
||||
let feedID: String?
|
||||
let homePageURL: String?
|
||||
let name: String?
|
||||
let editedName: String?
|
||||
@@ -36,9 +36,9 @@ struct PasteboardWebFeed: Hashable {
|
||||
let accountType: AccountType?
|
||||
let isLocalFeed: Bool
|
||||
|
||||
init(url: String, webFeedID: String?, homePageURL: String?, name: String?, editedName: String?, accountID: String?, accountType: AccountType?) {
|
||||
init(url: String, feedID: String?, homePageURL: String?, name: String?, editedName: String?, accountID: String?, accountType: AccountType?) {
|
||||
self.url = url.normalizedURL
|
||||
self.webFeedID = webFeedID
|
||||
self.feedID = feedID
|
||||
self.homePageURL = homePageURL?.normalizedURL
|
||||
self.name = name
|
||||
self.editedName = editedName
|
||||
@@ -49,7 +49,7 @@ struct PasteboardWebFeed: Hashable {
|
||||
|
||||
// MARK: - Reading
|
||||
|
||||
init?(dictionary: PasteboardWebFeedDictionary) {
|
||||
init?(dictionary: PasteboardFeedDictionary) {
|
||||
guard let url = dictionary[Key.url] else {
|
||||
return nil
|
||||
}
|
||||
@@ -57,7 +57,7 @@ struct PasteboardWebFeed: Hashable {
|
||||
let homePageURL = dictionary[Key.homePageURL]
|
||||
let name = dictionary[Key.name]
|
||||
let accountID = dictionary[Key.accountID]
|
||||
let webFeedID = dictionary[Key.webFeedID]
|
||||
let feedID = dictionary[Key.feedID]
|
||||
let editedName = dictionary[Key.editedName]
|
||||
|
||||
var accountType: AccountType? = nil
|
||||
@@ -65,19 +65,19 @@ struct PasteboardWebFeed: Hashable {
|
||||
accountType = AccountType(rawValue: accountTypeInt)
|
||||
}
|
||||
|
||||
self.init(url: url, webFeedID: webFeedID, homePageURL: homePageURL, name: name, editedName: editedName, accountID: accountID, accountType: accountType)
|
||||
self.init(url: url, feedID: feedID, homePageURL: homePageURL, name: name, editedName: editedName, accountID: accountID, accountType: accountType)
|
||||
}
|
||||
|
||||
init?(pasteboardItem: NSPasteboardItem) {
|
||||
var pasteboardType: NSPasteboard.PasteboardType?
|
||||
if pasteboardItem.types.contains(WebFeedPasteboardWriter.webFeedUTIInternalType) {
|
||||
pasteboardType = WebFeedPasteboardWriter.webFeedUTIInternalType
|
||||
if pasteboardItem.types.contains(FeedPasteboardWriter.feedUTIInternalType) {
|
||||
pasteboardType = FeedPasteboardWriter.feedUTIInternalType
|
||||
}
|
||||
else if pasteboardItem.types.contains(WebFeedPasteboardWriter.webFeedUTIType) {
|
||||
pasteboardType = WebFeedPasteboardWriter.webFeedUTIType
|
||||
else if pasteboardItem.types.contains(FeedPasteboardWriter.feedUTIType) {
|
||||
pasteboardType = FeedPasteboardWriter.feedUTIType
|
||||
}
|
||||
if let foundType = pasteboardType {
|
||||
if let feedDictionary = pasteboardItem.propertyList(forType: foundType) as? PasteboardWebFeedDictionary {
|
||||
if let feedDictionary = pasteboardItem.propertyList(forType: foundType) as? PasteboardFeedDictionary {
|
||||
self.init(dictionary: feedDictionary)
|
||||
return
|
||||
}
|
||||
@@ -94,7 +94,7 @@ struct PasteboardWebFeed: Hashable {
|
||||
if let foundType = pasteboardType {
|
||||
if let possibleURLString = pasteboardItem.string(forType: foundType) {
|
||||
if possibleURLString.mayBeURL {
|
||||
self.init(url: possibleURLString, webFeedID: nil, homePageURL: nil, name: nil, editedName: nil, accountID: nil, accountType: nil)
|
||||
self.init(url: possibleURLString, feedID: nil, homePageURL: nil, name: nil, editedName: nil, accountID: nil, accountType: nil)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -103,18 +103,18 @@ struct PasteboardWebFeed: Hashable {
|
||||
return nil
|
||||
}
|
||||
|
||||
static func pasteboardFeeds(with pasteboard: NSPasteboard) -> Set<PasteboardWebFeed>? {
|
||||
static func pasteboardFeeds(with pasteboard: NSPasteboard) -> Set<PasteboardFeed>? {
|
||||
guard let items = pasteboard.pasteboardItems else {
|
||||
return nil
|
||||
}
|
||||
let webFeeds = items.compactMap { PasteboardWebFeed(pasteboardItem: $0) }
|
||||
return webFeeds.isEmpty ? nil : Set(webFeeds)
|
||||
let feeds = items.compactMap { PasteboardFeed(pasteboardItem: $0) }
|
||||
return feeds.isEmpty ? nil : Set(feeds)
|
||||
}
|
||||
|
||||
// MARK: - Writing
|
||||
|
||||
func exportDictionary() -> PasteboardWebFeedDictionary {
|
||||
var d = PasteboardWebFeedDictionary()
|
||||
func exportDictionary() -> PasteboardFeedDictionary {
|
||||
var d = PasteboardFeedDictionary()
|
||||
d[Key.url] = url
|
||||
d[Key.homePageURL] = homePageURL ?? ""
|
||||
if let nameForDisplay = editedName ?? name {
|
||||
@@ -123,24 +123,24 @@ struct PasteboardWebFeed: Hashable {
|
||||
return d
|
||||
}
|
||||
|
||||
func internalDictionary() -> PasteboardWebFeedDictionary {
|
||||
var d = PasteboardWebFeedDictionary()
|
||||
d[PasteboardWebFeed.Key.webFeedID] = webFeedID
|
||||
d[PasteboardWebFeed.Key.url] = url
|
||||
func internalDictionary() -> PasteboardFeedDictionary {
|
||||
var d = PasteboardFeedDictionary()
|
||||
d[PasteboardFeed.Key.feedID] = feedID
|
||||
d[PasteboardFeed.Key.url] = url
|
||||
if let homePageURL = homePageURL {
|
||||
d[PasteboardWebFeed.Key.homePageURL] = homePageURL
|
||||
d[PasteboardFeed.Key.homePageURL] = homePageURL
|
||||
}
|
||||
if let name = name {
|
||||
d[PasteboardWebFeed.Key.name] = name
|
||||
d[PasteboardFeed.Key.name] = name
|
||||
}
|
||||
if let editedName = editedName {
|
||||
d[PasteboardWebFeed.Key.editedName] = editedName
|
||||
d[PasteboardFeed.Key.editedName] = editedName
|
||||
}
|
||||
if let accountID = accountID {
|
||||
d[PasteboardWebFeed.Key.accountID] = accountID
|
||||
d[PasteboardFeed.Key.accountID] = accountID
|
||||
}
|
||||
if let accountType = accountType {
|
||||
d[PasteboardWebFeed.Key.accountType] = String(accountType.rawValue)
|
||||
d[PasteboardFeed.Key.accountType] = String(accountType.rawValue)
|
||||
}
|
||||
return d
|
||||
}
|
||||
@@ -149,28 +149,28 @@ struct PasteboardWebFeed: Hashable {
|
||||
extension Feed: PasteboardWriterOwner {
|
||||
|
||||
public var pasteboardWriter: NSPasteboardWriting {
|
||||
return WebFeedPasteboardWriter(webFeed: self)
|
||||
return FeedPasteboardWriter(feed: self)
|
||||
}
|
||||
}
|
||||
|
||||
@objc final class WebFeedPasteboardWriter: NSObject, NSPasteboardWriting {
|
||||
@objc final class FeedPasteboardWriter: NSObject, NSPasteboardWriting {
|
||||
|
||||
private let webFeed: Feed
|
||||
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)
|
||||
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)
|
||||
|
||||
|
||||
init(webFeed: Feed) {
|
||||
self.webFeed = webFeed
|
||||
init(feed: Feed) {
|
||||
self.feed = feed
|
||||
}
|
||||
|
||||
// MARK: - NSPasteboardWriting
|
||||
|
||||
func writableTypes(for pasteboard: NSPasteboard) -> [NSPasteboard.PasteboardType] {
|
||||
|
||||
return [WebFeedPasteboardWriter.webFeedUTIType, .URL, .string, WebFeedPasteboardWriter.webFeedUTIInternalType]
|
||||
return [FeedPasteboardWriter.feedUTIType, .URL, .string, FeedPasteboardWriter.feedUTIInternalType]
|
||||
}
|
||||
|
||||
func pasteboardPropertyList(forType type: NSPasteboard.PasteboardType) -> Any? {
|
||||
@@ -179,12 +179,12 @@ extension Feed: PasteboardWriterOwner {
|
||||
|
||||
switch type {
|
||||
case .string:
|
||||
plist = webFeed.nameForDisplay
|
||||
plist = feed.nameForDisplay
|
||||
case .URL:
|
||||
plist = webFeed.url
|
||||
case WebFeedPasteboardWriter.webFeedUTIType:
|
||||
plist = feed.url
|
||||
case FeedPasteboardWriter.feedUTIType:
|
||||
plist = exportDictionary
|
||||
case WebFeedPasteboardWriter.webFeedUTIInternalType:
|
||||
case FeedPasteboardWriter.feedUTIInternalType:
|
||||
plist = internalDictionary
|
||||
default:
|
||||
plist = nil
|
||||
@@ -194,17 +194,17 @@ extension Feed: PasteboardWriterOwner {
|
||||
}
|
||||
}
|
||||
|
||||
private extension WebFeedPasteboardWriter {
|
||||
private extension FeedPasteboardWriter {
|
||||
|
||||
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 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 exportDictionary: PasteboardWebFeedDictionary {
|
||||
var exportDictionary: PasteboardFeedDictionary {
|
||||
return pasteboardFeed.exportDictionary()
|
||||
}
|
||||
|
||||
var internalDictionary: PasteboardWebFeedDictionary {
|
||||
var internalDictionary: PasteboardFeedDictionary {
|
||||
return pasteboardFeed.internalDictionary()
|
||||
}
|
||||
}
|
||||
@@ -52,7 +52,7 @@ struct PasteboardFolder: Hashable {
|
||||
}
|
||||
|
||||
if let foundType = pasteboardType {
|
||||
if let folderDictionary = pasteboardItem.propertyList(forType: foundType) as? PasteboardWebFeedDictionary {
|
||||
if let folderDictionary = pasteboardItem.propertyList(forType: foundType) as? PasteboardFeedDictionary {
|
||||
self.init(dictionary: folderDictionary)
|
||||
return
|
||||
}
|
||||
@@ -72,7 +72,7 @@ struct PasteboardFolder: Hashable {
|
||||
// MARK: - Writing
|
||||
|
||||
func internalDictionary() -> PasteboardFolderDictionary {
|
||||
var d = PasteboardWebFeedDictionary()
|
||||
var d = PasteboardFeedDictionary()
|
||||
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: PasteboardWebFeedDictionary {
|
||||
var internalDictionary: PasteboardFeedDictionary {
|
||||
return pasteboardFolder.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 = PasteboardWebFeed.pasteboardFeeds(with: info.draggingPasteboard)
|
||||
let draggedFeeds = PasteboardFeed.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 = PasteboardWebFeed.pasteboardFeeds(with: info.draggingPasteboard)
|
||||
let draggedFeeds = PasteboardFeed.pasteboardFeeds(with: info.draggingPasteboard)
|
||||
if (draggedFolders == nil && draggedFeeds == nil) || (draggedFolders != nil && draggedFeeds != nil) {
|
||||
return false
|
||||
}
|
||||
@@ -145,7 +145,7 @@ private extension SidebarOutlineDataSource {
|
||||
case empty, singleLocal, singleNonLocal, multipleLocal, multipleNonLocal, mixed
|
||||
}
|
||||
|
||||
func draggedFeedContentsType(_ draggedFeeds: Set<PasteboardWebFeed>) -> DraggedFeedsContentsType {
|
||||
func draggedFeedContentsType(_ draggedFeeds: Set<PasteboardFeed>) -> DraggedFeedsContentsType {
|
||||
if draggedFeeds.isEmpty {
|
||||
return .empty
|
||||
}
|
||||
@@ -173,14 +173,14 @@ private extension SidebarOutlineDataSource {
|
||||
return .multipleNonLocal
|
||||
}
|
||||
|
||||
func singleNonLocalFeed(from feeds: Set<PasteboardWebFeed>) -> PasteboardWebFeed? {
|
||||
func singleNonLocalFeed(from feeds: Set<PasteboardFeed>) -> PasteboardFeed? {
|
||||
guard feeds.count == 1, let feed = feeds.first else {
|
||||
return nil
|
||||
}
|
||||
return feed.isLocalFeed ? nil : feed
|
||||
}
|
||||
|
||||
func validateSingleNonLocalFeedDrop(_ outlineView: NSOutlineView, _ draggedFeed: PasteboardWebFeed, _ parentNode: Node, _ index: Int) -> NSDragOperation {
|
||||
func validateSingleNonLocalFeedDrop(_ outlineView: NSOutlineView, _ draggedFeed: PasteboardFeed, _ 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: PasteboardWebFeed, _ parentNode: Node, _ index: Int) -> NSDragOperation {
|
||||
func validateSingleLocalFeedDrop(_ outlineView: NSOutlineView, _ draggedFeed: PasteboardFeed, _ 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(parentNode: parentNode)
|
||||
}
|
||||
|
||||
func validateLocalFeedsDrop(_ outlineView: NSOutlineView, _ draggedFeeds: Set<PasteboardWebFeed>, _ parentNode: Node, _ index: Int) -> NSDragOperation {
|
||||
func validateLocalFeedsDrop(_ outlineView: NSOutlineView, _ draggedFeeds: Set<PasteboardFeed>, _ 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
|
||||
@@ -308,12 +308,12 @@ private extension SidebarOutlineDataSource {
|
||||
return localDragOperation(parentNode: parentNode)
|
||||
}
|
||||
|
||||
func copyWebFeedInAccount(node: Node, to parentNode: Node) {
|
||||
func copyFeedInAccount(node: Node, to parentNode: Node) {
|
||||
guard let feed = node.representedObject as? Feed, let destination = parentNode.representedObject as? Container else {
|
||||
return
|
||||
}
|
||||
|
||||
destination.account?.addWebFeed(feed, to: destination) { result in
|
||||
destination.account?.addFeed(feed, to: destination) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
break
|
||||
@@ -323,7 +323,7 @@ private extension SidebarOutlineDataSource {
|
||||
}
|
||||
}
|
||||
|
||||
func moveWebFeedInAccount(node: Node, to parentNode: Node) {
|
||||
func moveFeedInAccount(node: Node, to parentNode: Node) {
|
||||
guard let feed = node.representedObject as? Feed,
|
||||
let source = node.parent?.representedObject as? Container,
|
||||
let destination = parentNode.representedObject as? Container else {
|
||||
@@ -331,7 +331,7 @@ private extension SidebarOutlineDataSource {
|
||||
}
|
||||
|
||||
BatchUpdate.shared.start()
|
||||
source.account?.moveWebFeed(feed, from: source, to: destination) { result in
|
||||
source.account?.moveFeed(feed, from: source, to: destination) { result in
|
||||
BatchUpdate.shared.end()
|
||||
switch result {
|
||||
case .success:
|
||||
@@ -342,15 +342,15 @@ private extension SidebarOutlineDataSource {
|
||||
}
|
||||
}
|
||||
|
||||
func copyWebFeedBetweenAccounts(node: Node, to parentNode: Node) {
|
||||
func copyFeedBetweenAccounts(node: Node, to parentNode: Node) {
|
||||
guard let feed = node.representedObject as? Feed,
|
||||
let destinationAccount = nodeAccount(parentNode),
|
||||
let destinationContainer = parentNode.representedObject as? Container else {
|
||||
return
|
||||
}
|
||||
|
||||
if let existingFeed = destinationAccount.existingWebFeed(withURL: feed.url) {
|
||||
destinationAccount.addWebFeed(existingFeed, to: destinationContainer) { result in
|
||||
if let existingFeed = destinationAccount.existingFeed(withURL: feed.url) {
|
||||
destinationAccount.addFeed(existingFeed, to: destinationContainer) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
break
|
||||
@@ -359,7 +359,7 @@ private extension SidebarOutlineDataSource {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
destinationAccount.createWebFeed(url: feed.url, name: feed.nameForDisplay, container: destinationContainer, validateFeed: false) { result in
|
||||
destinationAccount.createFeed(url: feed.url, name: feed.nameForDisplay, container: destinationContainer, validateFeed: false) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
break
|
||||
@@ -370,7 +370,7 @@ private extension SidebarOutlineDataSource {
|
||||
}
|
||||
}
|
||||
|
||||
func acceptLocalFeedsDrop(_ outlineView: NSOutlineView, _ draggedFeeds: Set<PasteboardWebFeed>, _ parentNode: Node, _ index: Int) -> Bool {
|
||||
func acceptLocalFeedsDrop(_ outlineView: NSOutlineView, _ draggedFeeds: Set<PasteboardFeed>, _ parentNode: Node, _ index: Int) -> Bool {
|
||||
guard let draggedNodes = draggedNodes else {
|
||||
return false
|
||||
}
|
||||
@@ -378,12 +378,12 @@ private extension SidebarOutlineDataSource {
|
||||
draggedNodes.forEach { node in
|
||||
if sameAccount(node, parentNode) {
|
||||
if NSApplication.shared.currentEvent?.modifierFlags.contains(.option) ?? false {
|
||||
copyWebFeedInAccount(node: node, to: parentNode)
|
||||
copyFeedInAccount(node: node, to: parentNode)
|
||||
} else {
|
||||
moveWebFeedInAccount(node: node, to: parentNode)
|
||||
moveFeedInAccount(node: node, to: parentNode)
|
||||
}
|
||||
} else {
|
||||
copyWebFeedBetweenAccounts(node: node, to: parentNode)
|
||||
copyFeedBetweenAccounts(node: node, to: parentNode)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -431,9 +431,9 @@ private extension SidebarOutlineDataSource {
|
||||
destinationAccount.addFolder(folder.name ?? "") { result in
|
||||
switch result {
|
||||
case .success(let destinationFolder):
|
||||
for feed in folder.topLevelWebFeeds {
|
||||
if let existingFeed = destinationAccount.existingWebFeed(withURL: feed.url) {
|
||||
destinationAccount.addWebFeed(existingFeed, to: destinationFolder) { result in
|
||||
for feed in folder.topLevelFeeds {
|
||||
if let existingFeed = destinationAccount.existingFeed(withURL: feed.url) {
|
||||
destinationAccount.addFeed(existingFeed, to: destinationFolder) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
break
|
||||
@@ -442,7 +442,7 @@ private extension SidebarOutlineDataSource {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
destinationAccount.createWebFeed(url: feed.url, name: feed.nameForDisplay, container: destinationFolder, validateFeed: false) { result in
|
||||
destinationAccount.createFeed(url: feed.url, name: feed.nameForDisplay, container: destinationFolder, validateFeed: false) { result in
|
||||
switch result {
|
||||
case .success:
|
||||
break
|
||||
@@ -473,28 +473,28 @@ private extension SidebarOutlineDataSource {
|
||||
return true
|
||||
}
|
||||
|
||||
func acceptSingleNonLocalFeedDrop(_ outlineView: NSOutlineView, _ draggedFeed: PasteboardWebFeed, _ parentNode: Node, _ index: Int) -> Bool {
|
||||
func acceptSingleNonLocalFeedDrop(_ outlineView: NSOutlineView, _ draggedFeed: PasteboardFeed, _ parentNode: Node, _ index: Int) -> Bool {
|
||||
guard nodeIsDropTarget(parentNode), index == NSOutlineViewDropOnItemIndex else {
|
||||
return false
|
||||
}
|
||||
|
||||
// Show the add-feed sheet.
|
||||
if let account = parentNode.representedObject as? Account {
|
||||
appDelegate.addWebFeed(draggedFeed.url, name: draggedFeed.editedName ?? draggedFeed.name, account: account, folder: nil)
|
||||
appDelegate.addFeed(draggedFeed.url, name: draggedFeed.editedName ?? draggedFeed.name, account: account, folder: nil)
|
||||
} else {
|
||||
let account = parentNode.parent?.representedObject as? Account
|
||||
let folder = parentNode.representedObject as? Folder
|
||||
appDelegate.addWebFeed(draggedFeed.url, name: draggedFeed.editedName ?? draggedFeed.name, account: account, folder: folder)
|
||||
appDelegate.addFeed(draggedFeed.url, name: draggedFeed.editedName ?? draggedFeed.name, account: account, folder: folder)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func nodeHasChildRepresentingDraggedFeed(_ parentNode: Node, _ draggedFeed: PasteboardWebFeed) -> Bool {
|
||||
func nodeHasChildRepresentingDraggedFeed(_ parentNode: Node, _ draggedFeed: PasteboardFeed) -> Bool {
|
||||
return nodeHasChildRepresentingAnyDraggedFeed(parentNode, Set([draggedFeed]))
|
||||
}
|
||||
|
||||
func nodeRepresentsAnyDraggedFeed(_ node: Node, _ draggedFeeds: Set<PasteboardWebFeed>) -> Bool {
|
||||
func nodeRepresentsAnyDraggedFeed(_ node: Node, _ draggedFeeds: Set<PasteboardFeed>) -> Bool {
|
||||
guard let feed = node.representedObject as? Feed else {
|
||||
return false
|
||||
}
|
||||
@@ -520,8 +520,8 @@ private extension SidebarOutlineDataSource {
|
||||
return account
|
||||
} else if let folder = node.representedObject as? Folder {
|
||||
return folder.account
|
||||
} else if let webFeed = node.representedObject as? Feed {
|
||||
return webFeed.account
|
||||
} else if let feed = node.representedObject as? Feed {
|
||||
return feed.account
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
@@ -532,7 +532,7 @@ private extension SidebarOutlineDataSource {
|
||||
return nodeAccount(node)?.accountID
|
||||
}
|
||||
|
||||
func nodeHasChildRepresentingAnyDraggedFeed(_ parentNode: Node, _ draggedFeeds: Set<PasteboardWebFeed>) -> Bool {
|
||||
func nodeHasChildRepresentingAnyDraggedFeed(_ parentNode: Node, _ draggedFeeds: Set<PasteboardFeed>) -> Bool {
|
||||
for node in parentNode.childNodes {
|
||||
if nodeRepresentsAnyDraggedFeed(node, draggedFeeds) {
|
||||
return true
|
||||
@@ -541,11 +541,11 @@ private extension SidebarOutlineDataSource {
|
||||
return false
|
||||
}
|
||||
|
||||
func violatesAccountSpecificBehavior(_ dropTargetNode: Node, _ draggedFeed: PasteboardWebFeed) -> Bool {
|
||||
func violatesAccountSpecificBehavior(_ dropTargetNode: Node, _ draggedFeed: PasteboardFeed) -> Bool {
|
||||
return violatesAccountSpecificBehavior(dropTargetNode, Set([draggedFeed]))
|
||||
}
|
||||
|
||||
func violatesAccountSpecificBehavior(_ dropTargetNode: Node, _ draggedFeeds: Set<PasteboardWebFeed>) -> Bool {
|
||||
func violatesAccountSpecificBehavior(_ dropTargetNode: Node, _ draggedFeeds: Set<PasteboardFeed>) -> Bool {
|
||||
if violatesDisallowFeedInRootFolder(dropTargetNode) {
|
||||
return true
|
||||
}
|
||||
@@ -573,7 +573,7 @@ private extension SidebarOutlineDataSource {
|
||||
return false
|
||||
}
|
||||
|
||||
func violatesDisallowFeedCopyInRootFolder(_ dropTargetNode: Node, _ draggedFeeds: Set<PasteboardWebFeed>) -> Bool {
|
||||
func violatesDisallowFeedCopyInRootFolder(_ dropTargetNode: Node, _ draggedFeeds: Set<PasteboardFeed>) -> Bool {
|
||||
guard let dropTargetAccount = nodeAccount(dropTargetNode), dropTargetAccount.behaviors.contains(.disallowFeedCopyInRootFolder) else {
|
||||
return false
|
||||
}
|
||||
@@ -591,7 +591,7 @@ private extension SidebarOutlineDataSource {
|
||||
return false
|
||||
}
|
||||
|
||||
func violatesDisallowFeedInMultipleFolders(_ dropTargetNode: Node, _ draggedFeeds: Set<PasteboardWebFeed>) -> Bool {
|
||||
func violatesDisallowFeedInMultipleFolders(_ dropTargetNode: Node, _ draggedFeeds: Set<PasteboardFeed>) -> Bool {
|
||||
guard let dropTargetAccount = nodeAccount(dropTargetNode), dropTargetAccount.behaviors.contains(.disallowFeedInMultipleFolders) else {
|
||||
return false
|
||||
}
|
||||
@@ -602,7 +602,7 @@ private extension SidebarOutlineDataSource {
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
if dropTargetAccount.hasWebFeed(withURL: draggedFeed.url) {
|
||||
if dropTargetAccount.hasFeed(withURL: draggedFeed.url) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -611,7 +611,7 @@ private extension SidebarOutlineDataSource {
|
||||
return false
|
||||
}
|
||||
|
||||
func indexWhereDraggedFeedWouldAppear(_ parentNode: Node, _ draggedFeed: PasteboardWebFeed) -> Int {
|
||||
func indexWhereDraggedFeedWouldAppear(_ parentNode: Node, _ draggedFeed: PasteboardFeed) -> Int {
|
||||
let draggedFeedWrapper = PasteboardFeedObjectWrapper(pasteboardFeed: draggedFeed)
|
||||
let draggedFeedNode = Node(representedObject: draggedFeedWrapper, parent: nil)
|
||||
let nodes = parentNode.childNodes + [draggedFeedNode]
|
||||
@@ -640,9 +640,9 @@ final class PasteboardFeedObjectWrapper: DisplayNameProvider {
|
||||
var nameForDisplay: String {
|
||||
return pasteboardFeed.editedName ?? pasteboardFeed.name ?? ""
|
||||
}
|
||||
let pasteboardFeed: PasteboardWebFeed
|
||||
let pasteboardFeed: PasteboardFeed
|
||||
|
||||
init(pasteboardFeed: PasteboardWebFeed) {
|
||||
init(pasteboardFeed: PasteboardFeed) {
|
||||
self.pasteboardFeed = pasteboardFeed
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ extension SidebarViewController {
|
||||
|
||||
switch object {
|
||||
case is Feed:
|
||||
return menuForWebFeed(object as! Feed)
|
||||
return menuForFeed(object as! Feed)
|
||||
case is Folder:
|
||||
return menuForFolder(object as! Folder)
|
||||
case is PseudoFeed:
|
||||
@@ -200,40 +200,40 @@ private extension SidebarViewController {
|
||||
|
||||
let menu = NSMenu(title: "")
|
||||
|
||||
menu.addItem(withTitle: NSLocalizedString("New Feed", comment: "Command"), action: #selector(AppDelegate.showAddWebFeedWindow(_:)), keyEquivalent: "")
|
||||
menu.addItem(withTitle: NSLocalizedString("New Feed", comment: "Command"), action: #selector(AppDelegate.showAddFeedWindow(_:)), keyEquivalent: "")
|
||||
menu.addItem(withTitle: NSLocalizedString("New Folder", comment: "Command"), action: #selector(AppDelegate.showAddFolderWindow(_:)), keyEquivalent: "")
|
||||
|
||||
return menu
|
||||
}
|
||||
|
||||
func menuForWebFeed(_ webFeed: Feed) -> NSMenu? {
|
||||
func menuForFeed(_ feed: Feed) -> NSMenu? {
|
||||
|
||||
let menu = NSMenu(title: "")
|
||||
|
||||
if webFeed.unreadCount > 0 {
|
||||
menu.addItem(markAllReadMenuItem([webFeed]))
|
||||
if feed.unreadCount > 0 {
|
||||
menu.addItem(markAllReadMenuItem([feed]))
|
||||
menu.addItem(NSMenuItem.separator())
|
||||
}
|
||||
|
||||
if let homePageURL = webFeed.homePageURL, let _ = URL(string: homePageURL) {
|
||||
if let homePageURL = feed.homePageURL, let _ = URL(string: homePageURL) {
|
||||
let item = menuItem(NSLocalizedString("Open Home Page", comment: "Command"), #selector(openHomePageFromContextualMenu(_:)), homePageURL.decodedURLString ?? homePageURL)
|
||||
menu.addItem(item)
|
||||
menu.addItem(NSMenuItem.separator())
|
||||
}
|
||||
|
||||
let copyFeedURLItem = menuItem(NSLocalizedString("Copy Feed URL", comment: "Command"), #selector(copyURLFromContextualMenu(_:)), webFeed.url.decodedURLString ?? webFeed.url)
|
||||
let copyFeedURLItem = menuItem(NSLocalizedString("Copy Feed URL", comment: "Command"), #selector(copyURLFromContextualMenu(_:)), feed.url.decodedURLString ?? feed.url)
|
||||
menu.addItem(copyFeedURLItem)
|
||||
|
||||
if let homePageURL = webFeed.homePageURL {
|
||||
if let homePageURL = feed.homePageURL {
|
||||
let item = menuItem(NSLocalizedString("Copy Home Page URL", comment: "Command"), #selector(copyURLFromContextualMenu(_:)), homePageURL.decodedURLString ?? homePageURL)
|
||||
menu.addItem(item)
|
||||
}
|
||||
menu.addItem(NSMenuItem.separator())
|
||||
|
||||
let notificationText = webFeed.notificationDisplayName.capitalized
|
||||
let notificationText = feed.notificationDisplayName.capitalized
|
||||
|
||||
let notificationMenuItem = menuItem(notificationText, #selector(toggleNotificationsFromContextMenu(_:)), webFeed)
|
||||
if webFeed.isNotifyAboutNewArticles == nil || webFeed.isNotifyAboutNewArticles! == false {
|
||||
let notificationMenuItem = menuItem(notificationText, #selector(toggleNotificationsFromContextMenu(_:)), feed)
|
||||
if feed.isNotifyAboutNewArticles == nil || feed.isNotifyAboutNewArticles! == false {
|
||||
notificationMenuItem.state = .off
|
||||
} else {
|
||||
notificationMenuItem.state = .on
|
||||
@@ -241,9 +241,9 @@ private extension SidebarViewController {
|
||||
menu.addItem(notificationMenuItem)
|
||||
|
||||
let articleExtractorText = NSLocalizedString("Always Use Reader View", comment: "Always Use Reader View")
|
||||
let articleExtractorMenuItem = menuItem(articleExtractorText, #selector(toggleArticleExtractorFromContextMenu(_:)), webFeed)
|
||||
let articleExtractorMenuItem = menuItem(articleExtractorText, #selector(toggleArticleExtractorFromContextMenu(_:)), feed)
|
||||
|
||||
if webFeed.isArticleExtractorAlwaysOn == nil || webFeed.isArticleExtractorAlwaysOn! == false {
|
||||
if feed.isArticleExtractorAlwaysOn == nil || feed.isArticleExtractorAlwaysOn! == false {
|
||||
articleExtractorMenuItem.state = .off
|
||||
} else {
|
||||
articleExtractorMenuItem.state = .on
|
||||
@@ -252,8 +252,8 @@ private extension SidebarViewController {
|
||||
|
||||
menu.addItem(NSMenuItem.separator())
|
||||
|
||||
menu.addItem(renameMenuItem(webFeed))
|
||||
menu.addItem(deleteMenuItem([webFeed]))
|
||||
menu.addItem(renameMenuItem(feed))
|
||||
menu.addItem(deleteMenuItem([feed]))
|
||||
|
||||
return menu
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ protocol SidebarDelegate: AnyObject {
|
||||
weak var delegate: SidebarDelegate?
|
||||
|
||||
private let rebuildTreeAndRestoreSelectionQueue = CoalescingQueue(name: "Rebuild Tree Queue", interval: 1.0)
|
||||
let treeControllerDelegate = WebFeedTreeControllerDelegate()
|
||||
let treeControllerDelegate = FeedTreeControllerDelegate()
|
||||
lazy var treeController: TreeController = {
|
||||
return TreeController(delegate: treeControllerDelegate)
|
||||
}()
|
||||
@@ -64,7 +64,7 @@ protocol SidebarDelegate: AnyObject {
|
||||
outlineView.dataSource = dataSource
|
||||
outlineView.doubleAction = #selector(doubleClickedSidebar(_:))
|
||||
outlineView.setDraggingSourceOperationMask([.move, .copy], forLocal: true)
|
||||
outlineView.registerForDraggedTypes([WebFeedPasteboardWriter.webFeedUTIInternalType, WebFeedPasteboardWriter.webFeedUTIType, .URL, .string])
|
||||
outlineView.registerForDraggedTypes([FeedPasteboardWriter.feedUTIInternalType, FeedPasteboardWriter.feedUTIType, .URL, .string])
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(unreadCountDidInitialize(_:)), name: .UnreadCountDidInitialize, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(unreadCountDidChange(_:)), name: .UnreadCountDidChange, object: nil)
|
||||
@@ -75,8 +75,8 @@ protocol SidebarDelegate: AnyObject {
|
||||
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(webFeedIconDidBecomeAvailable(_:)), name: .WebFeedIconDidBecomeAvailable, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(webFeedSettingDidChange(_:)), name: .WebFeedSettingDidChange, 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(displayNameDidChange(_:)), name: .DisplayNameDidChange, object: nil)
|
||||
DistributedNotificationCenter.default().addObserver(self, selector: #selector(appleSideBarDefaultIconSizeChanged(_:)), name: .appleSideBarDefaultIconSizeChanged, object: nil)
|
||||
|
||||
@@ -183,7 +183,7 @@ protocol SidebarDelegate: AnyObject {
|
||||
}
|
||||
|
||||
@objc func userDidAddFeed(_ notification: Notification) {
|
||||
guard let feed = notification.userInfo?[UserInfoKey.webFeed] else {
|
||||
guard let feed = notification.userInfo?[UserInfoKey.feed] else {
|
||||
return
|
||||
}
|
||||
revealAndSelectRepresentedObject(feed as AnyObject)
|
||||
@@ -193,17 +193,17 @@ protocol SidebarDelegate: AnyObject {
|
||||
applyToAvailableCells(configureFavicon)
|
||||
}
|
||||
|
||||
@objc func webFeedIconDidBecomeAvailable(_ note: Notification) {
|
||||
guard let webFeed = note.userInfo?[UserInfoKey.webFeed] as? Feed else { return }
|
||||
configureCellsForRepresentedObject(webFeed)
|
||||
@objc func feedIconDidBecomeAvailable(_ note: Notification) {
|
||||
guard let feed = note.userInfo?[UserInfoKey.feed] as? Feed else { return }
|
||||
configureCellsForRepresentedObject(feed)
|
||||
}
|
||||
|
||||
@objc func webFeedSettingDidChange(_ note: Notification) {
|
||||
guard let webFeed = note.object as? Feed, let key = note.userInfo?[Feed.WebFeedSettingUserInfoKey] as? String else {
|
||||
@objc func feedSettingDidChange(_ note: Notification) {
|
||||
guard let feed = note.object as? Feed, let key = note.userInfo?[Feed.FeedSettingUserInfoKey] as? String else {
|
||||
return
|
||||
}
|
||||
if key == Feed.WebFeedSettingKey.homePageURL || key == Feed.WebFeedSettingKey.faviconURL {
|
||||
configureCellsForRepresentedObject(webFeed)
|
||||
if key == Feed.FeedSettingKey.homePageURL || key == Feed.FeedSettingKey.faviconURL {
|
||||
configureCellsForRepresentedObject(feed)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -255,7 +255,7 @@ protocol SidebarDelegate: AnyObject {
|
||||
guard outlineView.clickedRow == outlineView.selectedRow else {
|
||||
return
|
||||
}
|
||||
if AppDefaults.shared.feedDoubleClickMarkAsRead, let articles = try? singleSelectedWebFeed?.fetchUnreadArticles() {
|
||||
if AppDefaults.shared.feedDoubleClickMarkAsRead, let articles = try? singleSelectedFeed?.fetchUnreadArticles() {
|
||||
if let undoManager = undoManager, let markReadCommand = MarkStatusCommand(initialArticles: Array(articles), markingRead: true, undoManager: undoManager) {
|
||||
runCommand(markReadCommand)
|
||||
}
|
||||
@@ -264,7 +264,7 @@ protocol SidebarDelegate: AnyObject {
|
||||
}
|
||||
|
||||
@IBAction func openInBrowser(_ sender: Any?) {
|
||||
guard let feed = singleSelectedWebFeed, let homePageURL = feed.homePageURL else {
|
||||
guard let feed = singleSelectedFeed, let homePageURL = feed.homePageURL else {
|
||||
return
|
||||
}
|
||||
Browser.open(homePageURL, invertPreference: NSApp.currentEvent?.modifierFlags.contains(.shift) ?? false)
|
||||
@@ -272,7 +272,7 @@ protocol SidebarDelegate: AnyObject {
|
||||
|
||||
@objc func openInAppBrowser(_ sender: Any?) {
|
||||
// There is no In-App Browser for mac - so we use safari
|
||||
guard let feed = singleSelectedWebFeed, let homePageURL = feed.homePageURL else {
|
||||
guard let feed = singleSelectedFeed, let homePageURL = feed.homePageURL else {
|
||||
return
|
||||
}
|
||||
Browser.open(homePageURL, invertPreference: NSApp.currentEvent?.modifierFlags.contains(.shift) ?? false)
|
||||
@@ -451,8 +451,8 @@ protocol SidebarDelegate: AnyObject {
|
||||
if isReadFiltered, let sidebarItemID = feed.sidebarItemID {
|
||||
self.treeControllerDelegate.addFilterException(sidebarItemID)
|
||||
|
||||
if let webFeed = feed as? Feed, let account = webFeed.account {
|
||||
let parentFolder = account.sortedFolders?.first(where: { $0.objectIsChild(webFeed) })
|
||||
if let feed = feed as? Feed, let account = feed.account {
|
||||
let parentFolder = account.sortedFolders?.first(where: { $0.objectIsChild(feed) })
|
||||
if let parentFolderFeedID = parentFolder?.sidebarItemID {
|
||||
self.treeControllerDelegate.addFilterException(parentFolderFeedID)
|
||||
}
|
||||
@@ -524,7 +524,7 @@ private extension SidebarViewController {
|
||||
return selectedNodes.first!
|
||||
}
|
||||
|
||||
var singleSelectedWebFeed: Feed? {
|
||||
var singleSelectedFeed: Feed? {
|
||||
guard let node = singleSelectedNode else {
|
||||
return nil
|
||||
}
|
||||
@@ -543,10 +543,10 @@ private extension SidebarViewController {
|
||||
if folderFeed.account?.existingFolder(withID: folderFeed.folderID) != nil {
|
||||
treeControllerDelegate.addFilterException(sidebarItemID)
|
||||
}
|
||||
} else if let webFeed = feed as? Feed {
|
||||
if webFeed.account?.existingWebFeed(withWebFeedID: webFeed.webFeedID) != nil {
|
||||
} else if let feed = feed as? Feed {
|
||||
if feed.account?.existingFeed(withFeedID: feed.feedID) != nil {
|
||||
treeControllerDelegate.addFilterException(sidebarItemID)
|
||||
addParentFolderToFilterExceptions(webFeed)
|
||||
addParentFolderToFilterExceptions(feed)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -741,10 +741,10 @@ private extension SidebarViewController {
|
||||
}
|
||||
|
||||
func findFeedNode(_ userInfo: [AnyHashable : Any]?, beginningAt startingNode: Node) -> Node? {
|
||||
guard let webFeedID = userInfo?[ArticlePathKey.webFeedID] as? String else {
|
||||
guard let feedID = userInfo?[ArticlePathKey.feedID] as? String else {
|
||||
return nil
|
||||
}
|
||||
if let node = startingNode.descendantNode(where: { ($0.representedObject as? Feed)?.webFeedID == webFeedID }) {
|
||||
if let node = startingNode.descendantNode(where: { ($0.representedObject as? Feed)?.feedID == feedID }) {
|
||||
return node
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -96,7 +96,7 @@ private extension ArticlePasteboardWriter {
|
||||
|
||||
s += "Date: \(article.logicalDatePublished)\n\n"
|
||||
|
||||
if let feed = article.webFeed {
|
||||
if let feed = article.feed {
|
||||
s += "Feed: \(feed.nameForDisplay)\n"
|
||||
if let homePageURL = feed.homePageURL {
|
||||
s += "Home page: \(homePageURL)\n"
|
||||
@@ -111,7 +111,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 webFeedID = "webFeedID" // may differ from feedURL if coming from a syncing system
|
||||
static let feedID = "feedID" // may differ from feedURL if coming from a syncing system
|
||||
static let title = "title"
|
||||
static let contentHTML = "contentHTML"
|
||||
static let contentText = "contentText"
|
||||
@@ -143,11 +143,11 @@ private extension ArticlePasteboardWriter {
|
||||
d[Key.articleID] = article.articleID
|
||||
d[Key.uniqueID] = article.uniqueID
|
||||
|
||||
if let feed = article.webFeed {
|
||||
if let feed = article.feed {
|
||||
d[Key.feedURL] = feed.url
|
||||
}
|
||||
|
||||
d[Key.webFeedID] = article.webFeedID
|
||||
d[Key.feedID] = article.feedID
|
||||
d[Key.title] = article.title ?? nil
|
||||
d[Key.contentHTML] = article.contentHTML ?? nil
|
||||
d[Key.contentText] = article.contentText ?? nil
|
||||
|
||||
@@ -12,7 +12,7 @@ import Articles
|
||||
|
||||
protocol TimelineContainerViewControllerDelegate: AnyObject {
|
||||
func timelineSelectionDidChange(_: TimelineContainerViewController, articles: [Article]?, mode: TimelineSourceMode)
|
||||
func timelineRequestedWebFeedSelection(_: TimelineContainerViewController, webFeed: Feed)
|
||||
func timelineRequestedFeedSelection(_: TimelineContainerViewController, feed: Feed)
|
||||
func timelineInvalidatedRestorationState(_: TimelineContainerViewController)
|
||||
|
||||
}
|
||||
@@ -141,8 +141,8 @@ extension TimelineContainerViewController: TimelineDelegate {
|
||||
delegate?.timelineSelectionDidChange(self, articles: selectedArticles, mode: mode(for: timelineViewController))
|
||||
}
|
||||
|
||||
func timelineRequestedWebFeedSelection(_: TimelineViewController, webFeed: Feed) {
|
||||
delegate?.timelineRequestedWebFeedSelection(self, webFeed: webFeed)
|
||||
func timelineRequestedFeedSelection(_: TimelineViewController, feed: Feed) {
|
||||
delegate?.timelineRequestedFeedSelection(self, feed: feed)
|
||||
}
|
||||
|
||||
func timelineInvalidatedRestorationState(_: TimelineViewController) {
|
||||
|
||||
@@ -65,10 +65,10 @@ extension TimelineViewController {
|
||||
}
|
||||
|
||||
@objc func selectFeedInSidebarFromContextualMenu(_ sender: Any?) {
|
||||
guard let menuItem = sender as? NSMenuItem, let webFeed = menuItem.representedObject as? Feed else {
|
||||
guard let menuItem = sender as? NSMenuItem, let feed = menuItem.representedObject as? Feed else {
|
||||
return
|
||||
}
|
||||
delegate?.timelineRequestedWebFeedSelection(self, webFeed: webFeed)
|
||||
delegate?.timelineRequestedFeedSelection(self, feed: feed)
|
||||
}
|
||||
|
||||
@objc func markAllInFeedAsRead(_ sender: Any?) {
|
||||
@@ -163,7 +163,7 @@ private extension TimelineViewController {
|
||||
|
||||
menu.addSeparatorIfNeeded()
|
||||
|
||||
if articles.count == 1, let feed = articles.first!.webFeed {
|
||||
if articles.count == 1, let feed = articles.first!.feed {
|
||||
if !(representedObjects?.contains(where: { $0 as? Feed == feed }) ?? false) {
|
||||
menu.addItem(selectFeedInSidebarMenuItem(feed))
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ import os.log
|
||||
|
||||
protocol TimelineDelegate: AnyObject {
|
||||
func timelineSelectionDidChange(_: TimelineViewController, selectedArticles: [Article]?)
|
||||
func timelineRequestedWebFeedSelection(_: TimelineViewController, webFeed: Feed)
|
||||
func timelineRequestedFeedSelection(_: TimelineViewController, feed: Feed)
|
||||
func timelineInvalidatedRestorationState(_: TimelineViewController)
|
||||
}
|
||||
|
||||
@@ -214,7 +214,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(webFeedIconDidBecomeAvailable(_:)), name: .WebFeedIconDidBecomeAvailable, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(feedIconDidBecomeAvailable(_:)), name: .FeedIconDidBecomeAvailable, 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)
|
||||
@@ -593,15 +593,15 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr
|
||||
updateUnreadCount()
|
||||
}
|
||||
|
||||
@objc func webFeedIconDidBecomeAvailable(_ note: Notification) {
|
||||
guard showIcons, let feed = note.userInfo?[UserInfoKey.webFeed] as? Feed else {
|
||||
@objc func feedIconDidBecomeAvailable(_ note: Notification) {
|
||||
guard showIcons, let feed = note.userInfo?[UserInfoKey.feed] as? Feed else {
|
||||
return
|
||||
}
|
||||
let indexesToReload = tableView.indexesOfAvailableRowsPassingTest { (row) -> Bool in
|
||||
guard let article = articles.articleAtRow(row) else {
|
||||
return false
|
||||
}
|
||||
return feed == article.webFeed
|
||||
return feed == article.feed
|
||||
}
|
||||
if let indexesToReload = indexesToReload {
|
||||
reloadCells(for: indexesToReload)
|
||||
@@ -636,11 +636,11 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr
|
||||
}
|
||||
|
||||
@objc func accountDidDownloadArticles(_ note: Notification) {
|
||||
guard let feeds = note.userInfo?[Account.UserInfoKey.webFeeds] as? Set<Feed> else {
|
||||
guard let feeds = note.userInfo?[Account.UserInfoKey.feeds] as? Set<Feed> else {
|
||||
return
|
||||
}
|
||||
|
||||
let shouldFetchAndMergeArticles = representedObjectsContainsAnyWebFeed(feeds) || representedObjectsContainsAnyPseudoFeed()
|
||||
let shouldFetchAndMergeArticles = representedObjectsContainsAnyFeed(feeds) || representedObjectsContainsAnyPseudoFeed()
|
||||
if shouldFetchAndMergeArticles {
|
||||
queueFetchAndMergeArticles()
|
||||
}
|
||||
@@ -724,8 +724,8 @@ 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, dateArrived: Date())
|
||||
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, datePublished: nil, dateModified: nil, authors: nil, status: status)
|
||||
|
||||
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, datePublished: nil, dateModified: nil, authors: nil, status: status)
|
||||
|
||||
let prototypeCellData = TimelineCellData(article: prototypeArticle, showFeedName: .feed, feedName: "Prototype Feed Name", byline: nil, iconImage: nil, showIcon: false, featuredImage: nil)
|
||||
let height = TimelineCellLayout.height(for: 100, cellData: prototypeCellData, appearance: cellAppearance)
|
||||
return height
|
||||
@@ -879,7 +879,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.webFeed?.nameForDisplay, byline: article.byline(), iconImage: iconImage, showIcon: showIcons, featuredImage: nil)
|
||||
cell.cellData = TimelineCellData(article: article, showFeedName: showFeedNames, feedName: article.feed?.nameForDisplay, byline: article.byline(), iconImage: iconImage, showIcon: showIcons, featuredImage: nil)
|
||||
}
|
||||
|
||||
private func iconFor(_ article: Article) -> IconImage? {
|
||||
@@ -1226,7 +1226,7 @@ private extension TimelineViewController {
|
||||
return representedObjects?.contains(where: { $0 is Folder }) ?? false
|
||||
}
|
||||
|
||||
func representedObjectsContainsAnyWebFeed(_ webFeeds: Set<Feed>) -> Bool {
|
||||
func representedObjectsContainsAnyFeed(_ feeds: Set<Feed>) -> Bool {
|
||||
// Return true if there’s a match or if a folder contains (recursively) one of feeds
|
||||
|
||||
guard let representedObjects = representedObjects else {
|
||||
@@ -1234,15 +1234,15 @@ private extension TimelineViewController {
|
||||
}
|
||||
for representedObject in representedObjects {
|
||||
if let feed = representedObject as? Feed {
|
||||
for oneFeed in webFeeds {
|
||||
if feed.webFeedID == oneFeed.webFeedID || feed.url == oneFeed.url {
|
||||
for oneFeed in feeds {
|
||||
if feed.feedID == oneFeed.feedID || feed.url == oneFeed.url {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
else if let folder = representedObject as? Folder {
|
||||
for oneFeed in webFeeds {
|
||||
if folder.hasWebFeed(with: oneFeed.webFeedID) || folder.hasWebFeed(withURL: oneFeed.url) {
|
||||
for oneFeed in feeds {
|
||||
if folder.hasFeed(with: oneFeed.feedID) || folder.hasFeed(withURL: oneFeed.url) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,8 +60,8 @@
|
||||
<element type="account">
|
||||
<cocoa key="accounts"/>
|
||||
</element>
|
||||
<element type="webFeed">
|
||||
<cocoa key="webFeeds"/>
|
||||
<element type="feed">
|
||||
<cocoa key="feeds"/>
|
||||
</element>
|
||||
</class>
|
||||
|
||||
@@ -88,23 +88,23 @@
|
||||
<property name="active" code="Actv" type="boolean" access="rw" description="Whether or not the account is active">
|
||||
<cocoa key="scriptingIsActive"/>
|
||||
</property>
|
||||
<property name="allWebFeeds" code="Feds" access="r" description="All feeds, including feeds inside folders">
|
||||
<cocoa key="allWebFeeds"/>
|
||||
<type type="webFeed" list="yes"/>
|
||||
<property name="allFeeds" code="Feds" access="r" description="All feeds, including feeds inside folders">
|
||||
<cocoa key="allFeeds"/>
|
||||
<type type="feed" list="yes"/>
|
||||
</property>
|
||||
<property name="opml representation" code="OPML" type="text" access="r" description="OPML representation for the account">
|
||||
<cocoa key="opmlRepresentation"/>
|
||||
</property>
|
||||
<element type="webFeed">
|
||||
<cocoa key="webFeeds"/>
|
||||
<element type="feed">
|
||||
<cocoa key="feeds"/>
|
||||
</element>
|
||||
<element type="folder">
|
||||
<cocoa key="folders"/>
|
||||
</element>
|
||||
</class>
|
||||
|
||||
<class name="webFeed" code="Feed" plural="webFeeds" description="An RSS feed">
|
||||
<cocoa class="ScriptableWebFeed"/>
|
||||
<class name="feed" code="Feed" plural="feeds" description="An RSS feed">
|
||||
<cocoa class="ScriptableFeed"/>
|
||||
<property name="name" code="pnam" type="text" access="r" description="The name of the feed">
|
||||
<cocoa key="name"/>
|
||||
</property>
|
||||
@@ -165,8 +165,8 @@
|
||||
<property name="opml representation" code="OPML" type="text" access="r" description="OPML representation for the folder">
|
||||
<cocoa key="opmlRepresentation"/>
|
||||
</property>
|
||||
<element type="webFeed">
|
||||
<cocoa key="webFeeds"/>
|
||||
<element type="feed">
|
||||
<cocoa key="feeds"/>
|
||||
</element>
|
||||
</class>
|
||||
|
||||
@@ -217,7 +217,7 @@
|
||||
<property name="image url" code="IURL" type="text" access="r" description="an image url for the article">
|
||||
<cocoa key="imageURL"/>
|
||||
</property>
|
||||
<property name="feed" code="Feed" type="webFeed" access="r" description="the containing feed">
|
||||
<property name="feed" code="Feed" type="feed" access="r" description="the containing feed">
|
||||
<cocoa key="feed"/>
|
||||
</property>
|
||||
<element type="author">
|
||||
|
||||
@@ -73,7 +73,7 @@ class ScriptableAccount: NSObject, UniqueIdScriptingObject, ScriptingObjectConta
|
||||
account.removeFolder(scriptableFolder.folder) { result in
|
||||
}
|
||||
}
|
||||
} else if let scriptableFeed = element as? ScriptableWebFeed {
|
||||
} else if let scriptableFeed = element as? ScriptableFeed {
|
||||
BatchUpdate.shared.perform {
|
||||
var container: Container? = nil
|
||||
if let scriptableFolder = scriptableFeed.container as? ScriptableFolder {
|
||||
@@ -81,7 +81,7 @@ class ScriptableAccount: NSObject, UniqueIdScriptingObject, ScriptingObjectConta
|
||||
} else {
|
||||
container = account
|
||||
}
|
||||
account.removeWebFeed(scriptableFeed.webFeed, from: container!) { result in
|
||||
account.removeFeed(scriptableFeed.feed, from: container!) { result in
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -94,22 +94,22 @@ class ScriptableAccount: NSObject, UniqueIdScriptingObject, ScriptingObjectConta
|
||||
|
||||
// MARK: --- Scriptable elements ---
|
||||
|
||||
@objc(webFeeds)
|
||||
var webFeeds:NSArray {
|
||||
return account.topLevelWebFeeds.map { ScriptableWebFeed($0, container:self) } as NSArray
|
||||
@objc(feeds)
|
||||
var feeds:NSArray {
|
||||
return account.topLevelFeeds.map { ScriptableFeed($0, container:self) } as NSArray
|
||||
}
|
||||
|
||||
@objc(valueInWebFeedsWithUniqueID:)
|
||||
func valueInWebFeeds(withUniqueID id:String) -> ScriptableWebFeed? {
|
||||
guard let feed = account.existingWebFeed(withWebFeedID: id) else { return nil }
|
||||
return ScriptableWebFeed(feed, container:self)
|
||||
@objc(valueInFeedsWithUniqueID:)
|
||||
func valueInFeeds(withUniqueID id:String) -> ScriptableFeed? {
|
||||
guard let feed = account.existingFeed(withFeedID: id) else { return nil }
|
||||
return ScriptableFeed(feed, container:self)
|
||||
}
|
||||
|
||||
@objc(valueInWebFeedsWithName:)
|
||||
func valueInWebFeeds(withName name:String) -> ScriptableWebFeed? {
|
||||
let feeds = Array(account.flattenedWebFeeds())
|
||||
@objc(valueInFeedsWithName:)
|
||||
func valueInFeeds(withName name:String) -> ScriptableFeed? {
|
||||
let feeds = Array(account.flattenedFeeds())
|
||||
guard let feed = feeds.first(where:{$0.name == name}) else { return nil }
|
||||
return ScriptableWebFeed(feed, container:self)
|
||||
return ScriptableFeed(feed, container:self)
|
||||
}
|
||||
|
||||
@objc(folders)
|
||||
@@ -130,21 +130,21 @@ class ScriptableAccount: NSObject, UniqueIdScriptingObject, ScriptingObjectConta
|
||||
|
||||
// MARK: --- Scriptable properties ---
|
||||
|
||||
@objc(allWebFeeds)
|
||||
var allWebFeeds: NSArray {
|
||||
var webFeeds = [ScriptableWebFeed]()
|
||||
for webFeed in account.topLevelWebFeeds {
|
||||
webFeeds.append(ScriptableWebFeed(webFeed, container: self))
|
||||
@objc(allFeeds)
|
||||
var allFeeds: NSArray {
|
||||
var feeds = [ScriptableFeed]()
|
||||
for feed in account.topLevelFeeds {
|
||||
feeds.append(ScriptableFeed(feed, container: self))
|
||||
}
|
||||
if let folders = account.folders {
|
||||
for folder in folders {
|
||||
let scriptableFolder = ScriptableFolder(folder, container: self)
|
||||
for webFeed in folder.topLevelWebFeeds {
|
||||
webFeeds.append(ScriptableWebFeed(webFeed, container: scriptableFolder))
|
||||
for feed in folder.topLevelFeeds {
|
||||
feeds.append(ScriptableFeed(feed, container: scriptableFolder))
|
||||
}
|
||||
}
|
||||
}
|
||||
return webFeeds as NSArray
|
||||
return feeds as NSArray
|
||||
}
|
||||
|
||||
@objc(opmlRepresentation)
|
||||
|
||||
@@ -86,7 +86,7 @@ extension AppDelegate : AppDelegateAppleEvents {
|
||||
|
||||
DispatchQueue.main.async {
|
||||
|
||||
self.addWebFeed(normalizedURLString)
|
||||
self.addFeed(normalizedURLString)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -94,8 +94,8 @@ extension AppDelegate : AppDelegateAppleEvents {
|
||||
class NetNewsWireCreateElementCommand : NSCreateCommand {
|
||||
override func performDefaultImplementation() -> Any? {
|
||||
let classDescription = self.createClassDescription
|
||||
if (classDescription.className == "webFeed") {
|
||||
return ScriptableWebFeed.handleCreateElement(command:self)
|
||||
if (classDescription.className == "feed") {
|
||||
return ScriptableFeed.handleCreateElement(command:self)
|
||||
} else if (classDescription.className == "folder") {
|
||||
return ScriptableFolder.handleCreateElement(command:self)
|
||||
}
|
||||
|
||||
@@ -142,12 +142,12 @@ class ScriptableArticle: NSObject, UniqueIdScriptingObject, ScriptingObjectConta
|
||||
}
|
||||
|
||||
@objc(feed)
|
||||
var feed: ScriptableWebFeed? {
|
||||
guard let parentFeed = self.article.webFeed,
|
||||
var feed: ScriptableFeed? {
|
||||
guard let parentFeed = self.article.feed,
|
||||
let account = parentFeed.account
|
||||
else { return nil }
|
||||
|
||||
return ScriptableWebFeed(parentFeed, container: ScriptableAccount(account))
|
||||
return ScriptableFeed(parentFeed, container: ScriptableAccount(account))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+23
-23
@@ -11,14 +11,14 @@ import RSParser
|
||||
import Account
|
||||
import Articles
|
||||
|
||||
@objc(ScriptableWebFeed)
|
||||
class ScriptableWebFeed: NSObject, UniqueIdScriptingObject, ScriptingObjectContainer {
|
||||
@objc(ScriptableFeed)
|
||||
class ScriptableFeed: NSObject, UniqueIdScriptingObject, ScriptingObjectContainer {
|
||||
|
||||
let webFeed:Feed
|
||||
let feed:Feed
|
||||
let container:ScriptingObjectContainer
|
||||
|
||||
init (_ webFeed:Feed, container:ScriptingObjectContainer) {
|
||||
self.webFeed = webFeed
|
||||
init (_ feed:Feed, container:ScriptingObjectContainer) {
|
||||
self.feed = feed
|
||||
self.container = container
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ class ScriptableWebFeed: NSObject, UniqueIdScriptingObject, ScriptingObjectConta
|
||||
// MARK: --- ScriptingObject protocol ---
|
||||
|
||||
var scriptingKey: String {
|
||||
return "webFeeds"
|
||||
return "feeds"
|
||||
}
|
||||
|
||||
// MARK: --- UniqueIdScriptingObject protocol ---
|
||||
@@ -45,7 +45,7 @@ class ScriptableWebFeed: NSObject, UniqueIdScriptingObject, ScriptingObjectConta
|
||||
// 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 webFeed.webFeedID
|
||||
return feed.feedID
|
||||
}
|
||||
|
||||
// MARK: --- ScriptingObjectContainer protocol ---
|
||||
@@ -71,13 +71,13 @@ class ScriptableWebFeed: NSObject, UniqueIdScriptingObject, ScriptingObjectConta
|
||||
return url
|
||||
}
|
||||
|
||||
class func scriptableFeed(_ feed:Feed, account:Account, folder:Folder?) -> ScriptableWebFeed {
|
||||
class func scriptableFeed(_ feed:Feed, account:Account, folder:Folder?) -> ScriptableFeed {
|
||||
let scriptableAccount = ScriptableAccount(account)
|
||||
if let folder = folder {
|
||||
let scriptableFolder = ScriptableFolder(folder, container:scriptableAccount)
|
||||
return ScriptableWebFeed(feed, container:scriptableFolder)
|
||||
return ScriptableFeed(feed, container:scriptableFolder)
|
||||
} else {
|
||||
return ScriptableWebFeed(feed, container:scriptableAccount)
|
||||
return ScriptableFeed(feed, container:scriptableAccount)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ class ScriptableWebFeed: NSObject, UniqueIdScriptingObject, ScriptingObjectConta
|
||||
let (account, folder) = command.accountAndFolderForNewChild()
|
||||
guard let url = self.urlForNewFeed(arguments:arguments) else {return nil}
|
||||
|
||||
if let existingFeed = account.existingWebFeed(withURL:url) {
|
||||
if let existingFeed = account.existingFeed(withURL:url) {
|
||||
return scriptableFeed(existingFeed, account:account, folder:folder).objectSpecifier
|
||||
}
|
||||
|
||||
@@ -102,10 +102,10 @@ class ScriptableWebFeed: NSObject, UniqueIdScriptingObject, ScriptingObjectConta
|
||||
// suspendExecution(). When we get the callback, we supply the event result and call resumeExecution().
|
||||
command.suspendExecution()
|
||||
|
||||
account.createWebFeed(url: url, name: titleFromArgs, container: container, validateFeed: true) { result in
|
||||
account.createFeed(url: url, name: titleFromArgs, container: container, validateFeed: true) { result in
|
||||
switch result {
|
||||
case .success(let feed):
|
||||
NotificationCenter.default.post(name: .UserDidAddFeed, object: self, userInfo: [UserInfoKey.webFeed: feed])
|
||||
NotificationCenter.default.post(name: .UserDidAddFeed, object: self, userInfo: [UserInfoKey.feed: feed])
|
||||
let scriptableFeed = self.scriptableFeed(feed, account:account, folder:folder)
|
||||
command.resumeExecution(withResult:scriptableFeed.objectSpecifier)
|
||||
case .failure:
|
||||
@@ -121,51 +121,51 @@ class ScriptableWebFeed: NSObject, UniqueIdScriptingObject, ScriptingObjectConta
|
||||
|
||||
@objc(url)
|
||||
var url:String {
|
||||
return self.webFeed.url
|
||||
return self.feed.url
|
||||
}
|
||||
|
||||
@objc(name)
|
||||
var name:String {
|
||||
return self.webFeed.name ?? ""
|
||||
return self.feed.name ?? ""
|
||||
}
|
||||
|
||||
@objc(homePageURL)
|
||||
var homePageURL:String {
|
||||
return self.webFeed.homePageURL ?? ""
|
||||
return self.feed.homePageURL ?? ""
|
||||
}
|
||||
|
||||
@objc(iconURL)
|
||||
var iconURL:String {
|
||||
return self.webFeed.iconURL ?? ""
|
||||
return self.feed.iconURL ?? ""
|
||||
}
|
||||
|
||||
@objc(faviconURL)
|
||||
var faviconURL:String {
|
||||
return self.webFeed.faviconURL ?? ""
|
||||
return self.feed.faviconURL ?? ""
|
||||
}
|
||||
|
||||
@objc(opmlRepresentation)
|
||||
var opmlRepresentation:String {
|
||||
return self.webFeed.OPMLString(indentLevel:0)
|
||||
return self.feed.OPMLString(indentLevel:0)
|
||||
}
|
||||
|
||||
// MARK: --- scriptable elements ---
|
||||
|
||||
@objc(authors)
|
||||
var authors:NSArray {
|
||||
let feedAuthors = webFeed.authors ?? []
|
||||
let feedAuthors = feed.authors ?? []
|
||||
return feedAuthors.map { ScriptableAuthor($0, container:self) } as NSArray
|
||||
}
|
||||
|
||||
@objc(valueInAuthorsWithUniqueID:)
|
||||
func valueInAuthors(withUniqueID id:String) -> ScriptableAuthor? {
|
||||
guard let author = webFeed.authors?.first(where:{$0.authorID == id}) else { return nil }
|
||||
guard let author = feed.authors?.first(where:{$0.authorID == id}) else { return nil }
|
||||
return ScriptableAuthor(author, container:self)
|
||||
}
|
||||
|
||||
@objc(articles)
|
||||
var articles:NSArray {
|
||||
let feedArticles = (try? webFeed.fetchArticles()) ?? Set<Article>()
|
||||
let feedArticles = (try? feed.fetchArticles()) ?? Set<Article>()
|
||||
// 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 ScriptableWebFeed: NSObject, UniqueIdScriptingObject, ScriptingObjectConta
|
||||
|
||||
@objc(valueInArticlesWithUniqueID:)
|
||||
func valueInArticles(withUniqueID id:String) -> ScriptableArticle? {
|
||||
let articles = (try? webFeed.fetchArticles()) ?? Set<Article>()
|
||||
let articles = (try? feed.fetchArticles()) ?? Set<Article>()
|
||||
guard let article = articles.first(where:{$0.uniqueID == id}) else { return nil }
|
||||
return ScriptableArticle(article, container:self)
|
||||
}
|
||||
@@ -51,9 +51,9 @@ class ScriptableFolder: NSObject, UniqueIdScriptingObject, ScriptingObjectContai
|
||||
}
|
||||
|
||||
func deleteElement(_ element:ScriptingObject) {
|
||||
if let scriptableFeed = element as? ScriptableWebFeed {
|
||||
if let scriptableFeed = element as? ScriptableFeed {
|
||||
BatchUpdate.shared.perform {
|
||||
folder.account?.removeWebFeed(scriptableFeed.webFeed, from: folder) { result in }
|
||||
folder.account?.removeFeed(scriptableFeed.feed, from: folder) { result in }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -95,10 +95,10 @@ class ScriptableFolder: NSObject, UniqueIdScriptingObject, ScriptingObjectContai
|
||||
|
||||
// MARK: --- Scriptable elements ---
|
||||
|
||||
@objc(webFeeds)
|
||||
var webFeeds:NSArray {
|
||||
let feeds = Array(folder.topLevelWebFeeds)
|
||||
return feeds.map { ScriptableWebFeed($0, container:self) } as NSArray
|
||||
@objc(feeds)
|
||||
var feeds:NSArray {
|
||||
let feeds = Array(folder.topLevelFeeds)
|
||||
return feeds.map { ScriptableFeed($0, container:self) } as NSArray
|
||||
}
|
||||
|
||||
// MARK: --- Scriptable properties ---
|
||||
|
||||
@@ -30,8 +30,8 @@ extension NSApplication : ScriptingObjectContainer {
|
||||
func currentArticle() -> ScriptableArticle? {
|
||||
var scriptableArticle: ScriptableArticle?
|
||||
if let currentArticle = appDelegate.scriptingCurrentArticle {
|
||||
if let feed = currentArticle.webFeed {
|
||||
let scriptableFeed = ScriptableWebFeed(feed, container:self)
|
||||
if let feed = currentArticle.feed {
|
||||
let scriptableFeed = ScriptableFeed(feed, container:self)
|
||||
scriptableArticle = ScriptableArticle(currentArticle, container:scriptableFeed)
|
||||
}
|
||||
}
|
||||
@@ -42,8 +42,8 @@ extension NSApplication : ScriptingObjectContainer {
|
||||
func selectedArticles() -> NSArray {
|
||||
let articles = appDelegate.scriptingSelectedArticles
|
||||
let scriptableArticles:[ScriptableArticle] = articles.compactMap { article in
|
||||
if let feed = article.webFeed {
|
||||
let scriptableFeed = ScriptableWebFeed(feed, container:self)
|
||||
if let feed = article.feed {
|
||||
let scriptableFeed = ScriptableFeed(feed, container:self)
|
||||
return ScriptableArticle(article, container:scriptableFeed)
|
||||
} else {
|
||||
return nil
|
||||
@@ -73,26 +73,26 @@ extension NSApplication : ScriptingObjectContainer {
|
||||
for 'articles of feed "The Shape of Everything" of account "On My Mac"'
|
||||
*/
|
||||
|
||||
func allWebFeeds() -> [Feed] {
|
||||
func allFeeds() -> [Feed] {
|
||||
let accounts = AccountManager.shared.activeAccounts
|
||||
let emptyFeeds:[Feed] = []
|
||||
return accounts.reduce(emptyFeeds) { (result, nthAccount) -> [Feed] in
|
||||
let accountFeeds = Array(nthAccount.topLevelWebFeeds)
|
||||
let accountFeeds = Array(nthAccount.topLevelFeeds)
|
||||
return result + accountFeeds
|
||||
}
|
||||
}
|
||||
|
||||
@objc(webFeeds)
|
||||
func webFeeds() -> NSArray {
|
||||
let webFeeds = self.allWebFeeds()
|
||||
return webFeeds.map { ScriptableWebFeed($0, container:self) } as NSArray
|
||||
@objc(feeds)
|
||||
func feeds() -> NSArray {
|
||||
let feeds = self.allFeeds()
|
||||
return feeds.map { ScriptableFeed($0, container:self) } as NSArray
|
||||
}
|
||||
|
||||
@objc(valueInWebFeedsWithUniqueID:)
|
||||
func valueInWebFeeds(withUniqueID id:String) -> ScriptableWebFeed? {
|
||||
let webFeeds = self.allWebFeeds()
|
||||
guard let webFeed = webFeeds.first(where:{$0.webFeedID == id}) else { return nil }
|
||||
return ScriptableWebFeed(webFeed, container:self)
|
||||
@objc(valueInFeedsWithUniqueID:)
|
||||
func valueInFeeds(withUniqueID id:String) -> ScriptableFeed? {
|
||||
let feeds = self.allFeeds()
|
||||
guard let feed = feeds.first(where:{$0.feedID == id}) else { return nil }
|
||||
return ScriptableFeed(feed, container:self)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -124,13 +124,13 @@
|
||||
512D554423C804DE0023FFFA /* OpenInSafariActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 512D554323C804DE0023FFFA /* OpenInSafariActivity.swift */; };
|
||||
512DD4C92430086400C17B1F /* CloudKitAccountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 512DD4C82430086400C17B1F /* CloudKitAccountViewController.swift */; };
|
||||
512E08E62268800D00BDCFDD /* FolderTreeControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97A11ED9F180007D329B /* FolderTreeControllerDelegate.swift */; };
|
||||
512E08E72268801200BDCFDD /* WebFeedTreeControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97611ED9EB96007D329B /* WebFeedTreeControllerDelegate.swift */; };
|
||||
512E08E72268801200BDCFDD /* FeedTreeControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97611ED9EB96007D329B /* FeedTreeControllerDelegate.swift */; };
|
||||
512E09012268907400BDCFDD /* MasterFeedTableViewSectionHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 512E08F722688F7C00BDCFDD /* MasterFeedTableViewSectionHeader.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 /* AddWebFeedIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 513146B1235A81A400387FDC /* AddWebFeedIntentHandler.swift */; };
|
||||
513146B3235A81A400387FDC /* AddWebFeedIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 513146B1235A81A400387FDC /* AddWebFeedIntentHandler.swift */; };
|
||||
513146B2235A81A400387FDC /* AddFeedIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 513146B1235A81A400387FDC /* AddFeedIntentHandler.swift */; };
|
||||
513146B3235A81A400387FDC /* AddFeedIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 513146B1235A81A400387FDC /* AddFeedIntentHandler.swift */; };
|
||||
51314704235C41FC00387FDC /* Intents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = 51314707235C41FC00387FDC /* Intents.intentdefinition */; };
|
||||
51314705235C41FC00387FDC /* Intents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = 51314707235C41FC00387FDC /* Intents.intentdefinition */; };
|
||||
513277442590FBB60064F1E7 /* Account in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 514C16CD24D2E63F009A3AFA /* Account */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
|
||||
@@ -175,7 +175,7 @@
|
||||
513F32812593EF180003048F /* Account in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 516B695E24D2F33B00B5702F /* Account */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
|
||||
513F32882593EF8F0003048F /* RSCore in Frameworks */ = {isa = PBXBuildFile; productRef = 513F32872593EF8F0003048F /* RSCore */; };
|
||||
513F32892593EF8F0003048F /* RSCore in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 513F32872593EF8F0003048F /* RSCore */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
|
||||
5141E7392373C18B0013FF27 /* WebFeedInspectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5141E7382373C18B0013FF27 /* WebFeedInspectorViewController.swift */; };
|
||||
5141E7392373C18B0013FF27 /* FeedInspectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5141E7382373C18B0013FF27 /* FeedInspectorViewController.swift */; };
|
||||
5142192A23522B5500E07E2C /* ImageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5142192923522B5500E07E2C /* ImageViewController.swift */; };
|
||||
514219372352510100E07E2C /* ImageScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 514219362352510100E07E2C /* ImageScrollView.swift */; };
|
||||
5142194B2353C1CF00E07E2C /* main_mac.js in Resources */ = {isa = PBXBuildFile; fileRef = 5142194A2353C1CF00E07E2C /* main_mac.js */; };
|
||||
@@ -238,8 +238,6 @@
|
||||
519B8D332143397200FA689C /* SharingServiceDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519B8D322143397200FA689C /* SharingServiceDelegate.swift */; };
|
||||
519CA8E525841DB700EB079A /* CrashReporter in Frameworks */ = {isa = PBXBuildFile; productRef = 519CA8E425841DB700EB079A /* CrashReporter */; };
|
||||
519E743D22C663F900A78E47 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519E743422C663F900A78E47 /* SceneDelegate.swift */; };
|
||||
51A052CE244FB9D7006C2024 /* AddFeedWIndowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A052CD244FB9D6006C2024 /* AddFeedWIndowController.swift */; };
|
||||
51A052CF244FB9D7006C2024 /* AddFeedWIndowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A052CD244FB9D6006C2024 /* AddFeedWIndowController.swift */; };
|
||||
51A16999235E10D700EB091F /* LocalAccountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A1698F235E10D600EB091F /* LocalAccountViewController.swift */; };
|
||||
51A1699A235E10D700EB091F /* Settings.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 51A16990235E10D600EB091F /* Settings.storyboard */; };
|
||||
51A1699B235E10D700EB091F /* AccountInspectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A16991235E10D600EB091F /* AccountInspectorViewController.swift */; };
|
||||
@@ -247,7 +245,7 @@
|
||||
51A1699D235E10D700EB091F /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A16993235E10D600EB091F /* SettingsViewController.swift */; };
|
||||
51A1699F235E10D700EB091F /* AboutViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A16995235E10D600EB091F /* AboutViewController.swift */; };
|
||||
51A169A0235E10D700EB091F /* FeedbinAccountViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A16996235E10D700EB091F /* FeedbinAccountViewController.swift */; };
|
||||
51A66685238075AE00CB272D /* AddWebFeedDefaultContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A66684238075AE00CB272D /* AddWebFeedDefaultContainer.swift */; };
|
||||
51A66685238075AE00CB272D /* AddFeedDefaultContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A66684238075AE00CB272D /* AddFeedDefaultContainer.swift */; };
|
||||
51A737AE24DB19730015FA66 /* RSCore in Frameworks */ = {isa = PBXBuildFile; productRef = 51A737AD24DB19730015FA66 /* RSCore */; };
|
||||
51A737AF24DB19730015FA66 /* RSCore in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 51A737AD24DB19730015FA66 /* RSCore */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
|
||||
51A737BF24DB197F0015FA66 /* RSDatabase in Frameworks */ = {isa = PBXBuildFile; productRef = 51A737BE24DB197F0015FA66 /* RSDatabase */; };
|
||||
@@ -263,8 +261,8 @@
|
||||
51A9A5ED2380D6000033AADF /* AppAssets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C45254226507D200C03939 /* AppAssets.swift */; };
|
||||
51A9A5EE2380D6080033AADF /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 84C9FC9B2262A1A900D921D6 /* Assets.xcassets */; };
|
||||
51A9A5EF2380D63B0033AADF /* IconImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 516AE9DE2372269A007DEEAA /* IconImage.swift */; };
|
||||
51A9A5F22380DE520033AADF /* AddWebFeedDefaultContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A66684238075AE00CB272D /* AddWebFeedDefaultContainer.swift */; };
|
||||
51A9A5F32380DE530033AADF /* AddWebFeedDefaultContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A66684238075AE00CB272D /* AddWebFeedDefaultContainer.swift */; };
|
||||
51A9A5F22380DE520033AADF /* AddFeedDefaultContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A66684238075AE00CB272D /* AddFeedDefaultContainer.swift */; };
|
||||
51A9A5F32380DE530033AADF /* AddFeedDefaultContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A66684238075AE00CB272D /* AddFeedDefaultContainer.swift */; };
|
||||
51A9A5F52380F6A60033AADF /* ModalNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A9A5F42380F6A60033AADF /* ModalNavigationController.swift */; };
|
||||
51A9A60A2382FD240033AADF /* PoppableGestureRecognizerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A9A6092382FD240033AADF /* PoppableGestureRecognizerDelegate.swift */; };
|
||||
51AB8AB323B7F4C6008F147D /* WebViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51AB8AB223B7F4C6008F147D /* WebViewController.swift */; };
|
||||
@@ -329,7 +327,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 /* WebFeedIconDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842611891FCB67AA0086A189 /* WebFeedIconDownloader.swift */; };
|
||||
51C452A022650A1900C03939 /* FeedIconDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842611891FCB67AA0086A189 /* FeedIconDownloader.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 */; };
|
||||
@@ -488,11 +486,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 /* WebFeedTreeControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97611ED9EB96007D329B /* WebFeedTreeControllerDelegate.swift */; };
|
||||
65ED3FDB235DEF6C0081F399 /* FeedTreeControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97611ED9EB96007D329B /* FeedTreeControllerDelegate.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 /* WebFeedIconDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842611891FCB67AA0086A189 /* WebFeedIconDownloader.swift */; };
|
||||
65ED3FDF235DEF6C0081F399 /* FeedIconDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842611891FCB67AA0086A189 /* FeedIconDownloader.swift */; };
|
||||
65ED3FE0235DEF6C0081F399 /* PreferencesControlsBackgroundView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84C9FC7122629E1200D921D6 /* PreferencesControlsBackgroundView.swift */; };
|
||||
65ED3FE1235DEF6C0081F399 /* MarkCommandValidationStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84162A142038C12C00035290 /* MarkCommandValidationStatus.swift */; };
|
||||
65ED3FE2235DEF6C0081F399 /* ArticlePasteboardWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E95D231FB1087500552D99 /* ArticlePasteboardWriter.swift */; };
|
||||
@@ -518,7 +516,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 /* WebFeedInspectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8472058020142E8900AD578B /* WebFeedInspectorViewController.swift */; };
|
||||
65ED3FFA235DEF6C0081F399 /* FeedInspectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8472058020142E8900AD578B /* FeedInspectorViewController.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 */; };
|
||||
@@ -543,7 +541,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 /* PasteboardWebFeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848D578D21543519005FFAD5 /* PasteboardWebFeed.swift */; };
|
||||
65ED4014235DEF6C0081F399 /* PasteboardFeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848D578D21543519005FFAD5 /* PasteboardFeed.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 */; };
|
||||
@@ -565,7 +563,7 @@
|
||||
65ED4027235DEF6C0081F399 /* UnreadIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97751ED9EC04007D329B /* UnreadIndicatorView.swift */; };
|
||||
65ED4028235DEF6C0081F399 /* ExtractedArticle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FA73A62332BE880090D516 /* ExtractedArticle.swift */; };
|
||||
65ED4029235DEF6C0081F399 /* DeleteCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B99C9C1FAE83C600ECDEDB /* DeleteCommand.swift */; };
|
||||
65ED402A235DEF6C0081F399 /* AddWebFeedWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97521ED9EAC0007D329B /* AddWebFeedWindowController.swift */; };
|
||||
65ED402A235DEF6C0081F399 /* AddFeedWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97521ED9EAC0007D329B /* AddFeedWindowController.swift */; };
|
||||
65ED402B235DEF6C0081F399 /* ImportOPMLWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5144EA3E227A37EC00D19003 /* ImportOPMLWindowController.swift */; };
|
||||
65ED402C235DEF6C0081F399 /* TimelineTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A976A1ED9EBC8007D329B /* TimelineTableView.swift */; };
|
||||
65ED402D235DEF6C0081F399 /* DetailStatusBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84D52E941FE588BB00D14F5B /* DetailStatusBarView.swift */; };
|
||||
@@ -581,7 +579,7 @@
|
||||
65ED4037235DEF6C0081F399 /* FolderTreeControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97A11ED9F180007D329B /* FolderTreeControllerDelegate.swift */; };
|
||||
65ED4038235DEF6C0081F399 /* RSImage-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51126DA3225FDE2F00722696 /* RSImage-Extensions.swift */; };
|
||||
65ED4039235DEF6C0081F399 /* SingleFaviconDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845A29081FC74B8E007B49E3 /* SingleFaviconDownloader.swift */; };
|
||||
65ED403A235DEF6C0081F399 /* WebFeed+Scriptability.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F4EDB620074D6500B9E363 /* WebFeed+Scriptability.swift */; };
|
||||
65ED403A235DEF6C0081F399 /* Feed+Scriptability.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F4EDB620074D6500B9E363 /* Feed+Scriptability.swift */; };
|
||||
65ED403B235DEF6C0081F399 /* AuthorAvatarDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E850851FCB60CE0072EA88 /* AuthorAvatarDownloader.swift */; };
|
||||
65ED403C235DEF6C0081F399 /* SingleLineTextFieldSizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E185B2203B74E500F69BFA /* SingleLineTextFieldSizer.swift */; };
|
||||
65ED403D235DEF6C0081F399 /* TimelineTableCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97741ED9EC04007D329B /* TimelineTableCellView.swift */; };
|
||||
@@ -616,7 +614,7 @@
|
||||
65ED406B235DEF6C0081F399 /* CrashReporterWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 84BAE64821CEDAF20046DB56 /* CrashReporterWindow.xib */; };
|
||||
65ED406C235DEF6C0081F399 /* Credits.rtf in Resources */ = {isa = PBXBuildFile; fileRef = 84C9FC8922629E8F00D921D6 /* Credits.rtf */; };
|
||||
65ED406D235DEF6C0081F399 /* Inspector.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 84BBB12B20142A4700F054F5 /* Inspector.storyboard */; };
|
||||
65ED406E235DEF6C0081F399 /* AddWebFeedSheet.xib in Resources */ = {isa = PBXBuildFile; fileRef = 848363002262A3BC00DA1D35 /* AddWebFeedSheet.xib */; };
|
||||
65ED406E235DEF6C0081F399 /* AddFeedSheet.xib in Resources */ = {isa = PBXBuildFile; fileRef = 848363002262A3BC00DA1D35 /* AddFeedSheet.xib */; };
|
||||
65ED4092235DEF770081F399 /* SafariExtensionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6581C73920CED60100F4AD34 /* SafariExtensionViewController.swift */; };
|
||||
65ED4093235DEF770081F399 /* SafariExtensionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6581C73720CED60100F4AD34 /* SafariExtensionHandler.swift */; };
|
||||
65ED4096235DEF770081F399 /* ToolbarItemIcon.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 6581C74120CED60100F4AD34 /* ToolbarItemIcon.pdf */; };
|
||||
@@ -638,7 +636,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 /* WebFeedIconDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842611891FCB67AA0086A189 /* WebFeedIconDownloader.swift */; };
|
||||
8426118A1FCB67AA0086A189 /* FeedIconDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842611891FCB67AA0086A189 /* FeedIconDownloader.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 */; };
|
||||
@@ -664,27 +662,27 @@
|
||||
847120D72B8AE6AF00BBFC34 /* UTType+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847120D62B8AE6AF00BBFC34 /* UTType+Extensions.swift */; };
|
||||
847120D82B8AE6AF00BBFC34 /* UTType+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847120D62B8AE6AF00BBFC34 /* UTType+Extensions.swift */; };
|
||||
847120D92B8AE6AF00BBFC34 /* UTType+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847120D62B8AE6AF00BBFC34 /* UTType+Extensions.swift */; };
|
||||
8472058120142E8900AD578B /* WebFeedInspectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8472058020142E8900AD578B /* WebFeedInspectorViewController.swift */; };
|
||||
8472058120142E8900AD578B /* FeedInspectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8472058020142E8900AD578B /* FeedInspectorViewController.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 */; };
|
||||
848362FF2262A30E00DA1D35 /* template.html in Resources */ = {isa = PBXBuildFile; fileRef = 848362FE2262A30E00DA1D35 /* template.html */; };
|
||||
848363022262A3BD00DA1D35 /* AddWebFeedSheet.xib in Resources */ = {isa = PBXBuildFile; fileRef = 848363002262A3BC00DA1D35 /* AddWebFeedSheet.xib */; };
|
||||
848363022262A3BD00DA1D35 /* AddFeedSheet.xib in Resources */ = {isa = PBXBuildFile; fileRef = 848363002262A3BC00DA1D35 /* AddFeedSheet.xib */; };
|
||||
848363052262A3CC00DA1D35 /* AddFolderSheet.xib in Resources */ = {isa = PBXBuildFile; fileRef = 848363032262A3CC00DA1D35 /* AddFolderSheet.xib */; };
|
||||
848363082262A3DD00DA1D35 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 848363062262A3DD00DA1D35 /* Main.storyboard */; };
|
||||
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 /* PasteboardWebFeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848D578D21543519005FFAD5 /* PasteboardWebFeed.swift */; };
|
||||
848D578E21543519005FFAD5 /* PasteboardFeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848D578D21543519005FFAD5 /* PasteboardFeed.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 */; };
|
||||
849A97541ED9EAC0007D329B /* AddWebFeedWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97521ED9EAC0007D329B /* AddWebFeedWindowController.swift */; };
|
||||
849A97541ED9EAC0007D329B /* AddFeedWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97521ED9EAC0007D329B /* AddFeedWindowController.swift */; };
|
||||
849A975B1ED9EB0D007D329B /* ArticleUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97581ED9EB0D007D329B /* ArticleUtilities.swift */; };
|
||||
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 /* WebFeedTreeControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97611ED9EB96007D329B /* WebFeedTreeControllerDelegate.swift */; };
|
||||
849A97651ED9EB96007D329B /* FeedTreeControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97611ED9EB96007D329B /* FeedTreeControllerDelegate.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 */; };
|
||||
@@ -801,7 +799,7 @@
|
||||
D5E4CC54202C1361009B4FFC /* AppDelegate+Scriptability.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5E4CC53202C1361009B4FFC /* AppDelegate+Scriptability.swift */; };
|
||||
D5E4CC64202C1AC1009B4FFC /* MainWindowController+Scriptability.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5E4CC63202C1AC1009B4FFC /* MainWindowController+Scriptability.swift */; };
|
||||
D5F4EDB5200744A700B9E363 /* ScriptingObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F4EDB4200744A700B9E363 /* ScriptingObject.swift */; };
|
||||
D5F4EDB720074D6500B9E363 /* WebFeed+Scriptability.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F4EDB620074D6500B9E363 /* WebFeed+Scriptability.swift */; };
|
||||
D5F4EDB720074D6500B9E363 /* Feed+Scriptability.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F4EDB620074D6500B9E363 /* Feed+Scriptability.swift */; };
|
||||
D5F4EDB920074D7C00B9E363 /* Folder+Scriptability.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F4EDB820074D7C00B9E363 /* Folder+Scriptability.swift */; };
|
||||
DD82AB0A231003F6002269DF /* SharingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD82AB09231003F6002269DF /* SharingTests.swift */; };
|
||||
DFD6AACE27ADE86E00463FAD /* NewsFax.nnwtheme in Resources */ = {isa = PBXBuildFile; fileRef = DFD6AACD27ADE86E00463FAD /* NewsFax.nnwtheme */; };
|
||||
@@ -1147,7 +1145,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 /* AddWebFeedIntentHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddWebFeedIntentHandler.swift; sourceTree = "<group>"; };
|
||||
513146B1235A81A400387FDC /* AddFeedIntentHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddFeedIntentHandler.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>"; };
|
||||
5132779E2591034D0064F1E7 /* icon.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = icon.icns; sourceTree = "<group>"; };
|
||||
@@ -1158,7 +1156,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 /* WebFeedInspectorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebFeedInspectorViewController.swift; sourceTree = "<group>"; };
|
||||
5141E7382373C18B0013FF27 /* FeedInspectorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedInspectorViewController.swift; sourceTree = "<group>"; };
|
||||
5141E7552374A2890013FF27 /* DetailIconSchemeHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailIconSchemeHandler.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>"; };
|
||||
@@ -1210,7 +1208,6 @@
|
||||
5195C1DB2720BD3000888867 /* MasterFeedRowIdentifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MasterFeedRowIdentifier.swift; sourceTree = "<group>"; };
|
||||
519B8D322143397200FA689C /* SharingServiceDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharingServiceDelegate.swift; sourceTree = "<group>"; };
|
||||
519E743422C663F900A78E47 /* SceneDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
|
||||
51A052CD244FB9D6006C2024 /* AddFeedWIndowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AddFeedWIndowController.swift; path = AddFeed/AddFeedWIndowController.swift; sourceTree = "<group>"; };
|
||||
51A1698F235E10D600EB091F /* LocalAccountViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocalAccountViewController.swift; sourceTree = "<group>"; };
|
||||
51A16990235E10D600EB091F /* Settings.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Settings.storyboard; sourceTree = "<group>"; };
|
||||
51A16991235E10D600EB091F /* AccountInspectorViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountInspectorViewController.swift; sourceTree = "<group>"; };
|
||||
@@ -1218,7 +1215,7 @@
|
||||
51A16993235E10D600EB091F /* SettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = "<group>"; };
|
||||
51A16995235E10D600EB091F /* AboutViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AboutViewController.swift; sourceTree = "<group>"; };
|
||||
51A16996235E10D700EB091F /* FeedbinAccountViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeedbinAccountViewController.swift; sourceTree = "<group>"; };
|
||||
51A66684238075AE00CB272D /* AddWebFeedDefaultContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddWebFeedDefaultContainer.swift; sourceTree = "<group>"; };
|
||||
51A66684238075AE00CB272D /* AddFeedDefaultContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddFeedDefaultContainer.swift; sourceTree = "<group>"; };
|
||||
51A9A5E32380C8870033AADF /* ShareFolderPickerAccountCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ShareFolderPickerAccountCell.xib; sourceTree = "<group>"; };
|
||||
51A9A5E52380C8B20033AADF /* ShareFolderPickerFolderCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ShareFolderPickerFolderCell.xib; sourceTree = "<group>"; };
|
||||
51A9A5E72380CA130033AADF /* ShareFolderPickerCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareFolderPickerCell.swift; sourceTree = "<group>"; };
|
||||
@@ -1345,7 +1342,7 @@
|
||||
841ABA5D20145E9200980E11 /* FolderInspectorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FolderInspectorViewController.swift; sourceTree = "<group>"; };
|
||||
841ABA5F20145EC100980E11 /* BuiltinSmartFeedInspectorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuiltinSmartFeedInspectorViewController.swift; sourceTree = "<group>"; };
|
||||
84216D0222128B9D0049B9B9 /* DetailWebViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailWebViewController.swift; sourceTree = "<group>"; };
|
||||
842611891FCB67AA0086A189 /* WebFeedIconDownloader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebFeedIconDownloader.swift; sourceTree = "<group>"; };
|
||||
842611891FCB67AA0086A189 /* FeedIconDownloader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedIconDownloader.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>"; };
|
||||
@@ -1369,28 +1366,28 @@
|
||||
845EE7C01FC2488C00854A1F /* SmartFeed.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SmartFeed.swift; sourceTree = "<group>"; };
|
||||
84702AA31FA27AC0006B8943 /* MarkStatusCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarkStatusCommand.swift; sourceTree = "<group>"; };
|
||||
847120D62B8AE6AF00BBFC34 /* UTType+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UTType+Extensions.swift"; sourceTree = "<group>"; };
|
||||
8472058020142E8900AD578B /* WebFeedInspectorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebFeedInspectorViewController.swift; sourceTree = "<group>"; };
|
||||
8472058020142E8900AD578B /* FeedInspectorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedInspectorViewController.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>"; };
|
||||
847E64942262782F00E00365 /* NSAppleEventDescriptor+UserRecordFields.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSAppleEventDescriptor+UserRecordFields.swift"; sourceTree = "<group>"; };
|
||||
848362FE2262A30E00DA1D35 /* template.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = template.html; sourceTree = "<group>"; };
|
||||
848363012262A3BC00DA1D35 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Mac/Base.lproj/AddWebFeedSheet.xib; sourceTree = SOURCE_ROOT; };
|
||||
848363012262A3BC00DA1D35 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Mac/Base.lproj/AddFeedSheet.xib; sourceTree = SOURCE_ROOT; };
|
||||
848363042262A3CC00DA1D35 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Mac/Base.lproj/AddFolderSheet.xib; sourceTree = SOURCE_ROOT; };
|
||||
848363072262A3DD00DA1D35 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
|
||||
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 /* PasteboardWebFeed.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasteboardWebFeed.swift; sourceTree = "<group>"; };
|
||||
848D578D21543519005FFAD5 /* PasteboardFeed.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasteboardFeed.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>"; };
|
||||
849A97521ED9EAC0007D329B /* AddWebFeedWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AddWebFeedWindowController.swift; path = AddFeed/AddWebFeedWindowController.swift; sourceTree = "<group>"; };
|
||||
849A97521ED9EAC0007D329B /* AddFeedWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AddFeedWindowController.swift; path = AddFeed/AddFeedWindowController.swift; sourceTree = "<group>"; };
|
||||
849A97581ED9EB0D007D329B /* ArticleUtilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArticleUtilities.swift; sourceTree = "<group>"; };
|
||||
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 /* WebFeedTreeControllerDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebFeedTreeControllerDelegate.swift; sourceTree = "<group>"; };
|
||||
849A97611ED9EB96007D329B /* FeedTreeControllerDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeedTreeControllerDelegate.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>"; };
|
||||
@@ -1514,7 +1511,7 @@
|
||||
D5E4CC53202C1361009B4FFC /* AppDelegate+Scriptability.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppDelegate+Scriptability.swift"; sourceTree = "<group>"; };
|
||||
D5E4CC63202C1AC1009B4FFC /* MainWindowController+Scriptability.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MainWindowController+Scriptability.swift"; sourceTree = "<group>"; };
|
||||
D5F4EDB4200744A700B9E363 /* ScriptingObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScriptingObject.swift; sourceTree = "<group>"; };
|
||||
D5F4EDB620074D6500B9E363 /* WebFeed+Scriptability.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WebFeed+Scriptability.swift"; sourceTree = "<group>"; };
|
||||
D5F4EDB620074D6500B9E363 /* Feed+Scriptability.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Feed+Scriptability.swift"; sourceTree = "<group>"; };
|
||||
D5F4EDB820074D7C00B9E363 /* Folder+Scriptability.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Folder+Scriptability.swift"; sourceTree = "<group>"; };
|
||||
DD82AB09231003F6002269DF /* SharingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SharingTests.swift; sourceTree = "<group>"; };
|
||||
DFD6AACD27ADE86E00463FAD /* NewsFax.nnwtheme */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = NewsFax.nnwtheme; sourceTree = "<group>"; };
|
||||
@@ -1775,7 +1772,7 @@
|
||||
516A09412361248000EAE89B /* Inspector.storyboard */,
|
||||
51A16991235E10D600EB091F /* AccountInspectorViewController.swift */,
|
||||
5110C37C2373A8D100A9C04F /* InspectorIconHeaderView.swift */,
|
||||
5141E7382373C18B0013FF27 /* WebFeedInspectorViewController.swift */,
|
||||
5141E7382373C18B0013FF27 /* FeedInspectorViewController.swift */,
|
||||
);
|
||||
path = Inspector;
|
||||
sourceTree = "<group>";
|
||||
@@ -1791,7 +1788,7 @@
|
||||
512E08DD22687FA000BDCFDD /* Tree */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
849A97611ED9EB96007D329B /* WebFeedTreeControllerDelegate.swift */,
|
||||
849A97611ED9EB96007D329B /* FeedTreeControllerDelegate.swift */,
|
||||
849A97A11ED9F180007D329B /* FolderTreeControllerDelegate.swift */,
|
||||
);
|
||||
path = Tree;
|
||||
@@ -1801,7 +1798,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
51314707235C41FC00387FDC /* Intents.intentdefinition */,
|
||||
513146B1235A81A400387FDC /* AddWebFeedIntentHandler.swift */,
|
||||
513146B1235A81A400387FDC /* AddFeedIntentHandler.swift */,
|
||||
);
|
||||
path = Intents;
|
||||
sourceTree = "<group>";
|
||||
@@ -2189,7 +2186,7 @@
|
||||
children = (
|
||||
845213221FCA5B10003B6E93 /* ImageDownloader.swift */,
|
||||
84E850851FCB60CE0072EA88 /* AuthorAvatarDownloader.swift */,
|
||||
842611891FCB67AA0086A189 /* WebFeedIconDownloader.swift */,
|
||||
842611891FCB67AA0086A189 /* FeedIconDownloader.swift */,
|
||||
8426119F1FCB72600086A189 /* FeaturedImageDownloader.swift */,
|
||||
842611A11FCB769D0086A189 /* RSHTMLMetadata+Extension.swift */,
|
||||
);
|
||||
@@ -2252,9 +2249,8 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
849A97511ED9EAC0007D329B /* AddFeedController.swift */,
|
||||
51A052CD244FB9D6006C2024 /* AddFeedWIndowController.swift */,
|
||||
848363002262A3BC00DA1D35 /* AddWebFeedSheet.xib */,
|
||||
849A97521ED9EAC0007D329B /* AddWebFeedWindowController.swift */,
|
||||
848363002262A3BC00DA1D35 /* AddFeedSheet.xib */,
|
||||
849A97521ED9EAC0007D329B /* AddFeedWindowController.swift */,
|
||||
51EC114B2149FE3300B296E3 /* FolderTreeMenu.swift */,
|
||||
);
|
||||
name = "Add Feed";
|
||||
@@ -2263,7 +2259,7 @@
|
||||
849A97561ED9EB0D007D329B /* Extensions */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
51A66684238075AE00CB272D /* AddWebFeedDefaultContainer.swift */,
|
||||
51A66684238075AE00CB272D /* AddFeedDefaultContainer.swift */,
|
||||
849A97731ED9EC04007D329B /* ArticleStringFormatter.swift */,
|
||||
849A97581ED9EB0D007D329B /* ArticleUtilities.swift */,
|
||||
5108F6B52375E612001ABC45 /* CacheCleaner.swift */,
|
||||
@@ -2284,7 +2280,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
84AD1EA92031617300BC20B7 /* PasteboardFolder.swift */,
|
||||
848D578D21543519005FFAD5 /* PasteboardWebFeed.swift */,
|
||||
848D578D21543519005FFAD5 /* PasteboardFeed.swift */,
|
||||
51868BF0254386630011A17B /* SidebarDeleteItemsAlert.swift */,
|
||||
84AD1EBB2032AF5C00BC20B7 /* SidebarOutlineDataSource.swift */,
|
||||
849A97601ED9EB96007D329B /* SidebarOutlineView.swift */,
|
||||
@@ -2416,7 +2412,7 @@
|
||||
children = (
|
||||
84BBB12B20142A4700F054F5 /* Inspector.storyboard */,
|
||||
84BBB12C20142A4700F054F5 /* InspectorWindowController.swift */,
|
||||
8472058020142E8900AD578B /* WebFeedInspectorViewController.swift */,
|
||||
8472058020142E8900AD578B /* FeedInspectorViewController.swift */,
|
||||
841ABA5D20145E9200980E11 /* FolderInspectorViewController.swift */,
|
||||
841ABA5F20145EC100980E11 /* BuiltinSmartFeedInspectorViewController.swift */,
|
||||
841ABA4D20145E7300980E11 /* NothingInspectorViewController.swift */,
|
||||
@@ -2731,7 +2727,7 @@
|
||||
D5E4CC53202C1361009B4FFC /* AppDelegate+Scriptability.swift */,
|
||||
D553737C20186C1F006D8857 /* Article+Scriptability.swift */,
|
||||
D5A2678B20130ECF00A8D3C0 /* Author+Scriptability.swift */,
|
||||
D5F4EDB620074D6500B9E363 /* WebFeed+Scriptability.swift */,
|
||||
D5F4EDB620074D6500B9E363 /* Feed+Scriptability.swift */,
|
||||
D5F4EDB820074D7C00B9E363 /* Folder+Scriptability.swift */,
|
||||
D5E4CC63202C1AC1009B4FFC /* MainWindowController+Scriptability.swift */,
|
||||
D5907D7E2004AC00005947E5 /* NSApplication+Scriptability.swift */,
|
||||
@@ -3275,7 +3271,7 @@
|
||||
65ED406B235DEF6C0081F399 /* CrashReporterWindow.xib in Resources */,
|
||||
65ED406C235DEF6C0081F399 /* Credits.rtf in Resources */,
|
||||
65ED406D235DEF6C0081F399 /* Inspector.storyboard in Resources */,
|
||||
65ED406E235DEF6C0081F399 /* AddWebFeedSheet.xib in Resources */,
|
||||
65ED406E235DEF6C0081F399 /* AddFeedSheet.xib in Resources */,
|
||||
51077C5527A86C9E000C71DB /* Hyperlegible.nnwtheme in Resources */,
|
||||
51DEE81326FB9233006DAA56 /* Appanoose.nnwtheme in Resources */,
|
||||
B27EEBFA244D15F3000932E6 /* stylesheet.css in Resources */,
|
||||
@@ -3379,7 +3375,7 @@
|
||||
51DEE81226FB9233006DAA56 /* Appanoose.nnwtheme in Resources */,
|
||||
84C9FC8E22629E8F00D921D6 /* Credits.rtf in Resources */,
|
||||
84BBB12D20142A4700F054F5 /* Inspector.storyboard in Resources */,
|
||||
848363022262A3BD00DA1D35 /* AddWebFeedSheet.xib in Resources */,
|
||||
848363022262A3BD00DA1D35 /* AddFeedSheet.xib in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -3669,7 +3665,7 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
513146B3235A81A400387FDC /* AddWebFeedIntentHandler.swift in Sources */,
|
||||
513146B3235A81A400387FDC /* AddFeedIntentHandler.swift in Sources */,
|
||||
51B5C8E623F4BBFA00032075 /* ExtensionFeedAddRequest.swift in Sources */,
|
||||
51314705235C41FC00387FDC /* Intents.intentdefinition in Sources */,
|
||||
51B5C8E523F4BBFA00032075 /* ExtensionContainersFile.swift in Sources */,
|
||||
@@ -3767,15 +3763,14 @@
|
||||
65ED3FD6235DEF6C0081F399 /* MarkStatusCommand.swift in Sources */,
|
||||
65ED3FD7235DEF6C0081F399 /* NSApplication+Scriptability.swift in Sources */,
|
||||
65ED3FD8235DEF6C0081F399 /* NSView-Extensions.swift in Sources */,
|
||||
51A052CF244FB9D7006C2024 /* AddFeedWIndowController.swift in Sources */,
|
||||
5103A9F824225E4C00410853 /* AccountsAddCloudKitWindowController.swift in Sources */,
|
||||
65ED3FD9235DEF6C0081F399 /* SidebarCell.swift in Sources */,
|
||||
65ED3FDA235DEF6C0081F399 /* ArticleStatusSyncTimer.swift in Sources */,
|
||||
65ED3FDB235DEF6C0081F399 /* WebFeedTreeControllerDelegate.swift in Sources */,
|
||||
65ED3FDB235DEF6C0081F399 /* FeedTreeControllerDelegate.swift in Sources */,
|
||||
65ED3FDC235DEF6C0081F399 /* UnreadCountView.swift in Sources */,
|
||||
65ED3FDD235DEF6C0081F399 /* ActivityType.swift in Sources */,
|
||||
65ED3FDE235DEF6C0081F399 /* CrashReportWindowController.swift in Sources */,
|
||||
65ED3FDF235DEF6C0081F399 /* WebFeedIconDownloader.swift in Sources */,
|
||||
65ED3FDF235DEF6C0081F399 /* FeedIconDownloader.swift in Sources */,
|
||||
B24E9ADD245AB88400DA5718 /* NSAttributedString+NetNewsWire.swift in Sources */,
|
||||
510C417C24E5D1AE008226FD /* ExtensionFeedAddRequestFile.swift in Sources */,
|
||||
510C417D24E5D1AE008226FD /* ExtensionContainers.swift in Sources */,
|
||||
@@ -3808,7 +3803,7 @@
|
||||
65ED3FF8235DEF6C0081F399 /* ErrorHandler.swift in Sources */,
|
||||
65ED3FF9235DEF6C0081F399 /* ActivityManager.swift in Sources */,
|
||||
1710B9142552354E00679C0D /* AddAccountHelpView.swift in Sources */,
|
||||
65ED3FFA235DEF6C0081F399 /* WebFeedInspectorViewController.swift in Sources */,
|
||||
65ED3FFA235DEF6C0081F399 /* FeedInspectorViewController.swift in Sources */,
|
||||
65ED3FFB235DEF6C0081F399 /* AccountsReaderAPIWindowController.swift in Sources */,
|
||||
65ED3FFC235DEF6C0081F399 /* AccountsAddLocalWindowController.swift in Sources */,
|
||||
65ED3FFD235DEF6C0081F399 /* PasteboardFolder.swift in Sources */,
|
||||
@@ -3835,7 +3830,7 @@
|
||||
65ED4011235DEF6C0081F399 /* AddFolderWindowController.swift in Sources */,
|
||||
65ED4012235DEF6C0081F399 /* TimelineContainerViewController.swift in Sources */,
|
||||
65ED4013235DEF6C0081F399 /* MainWIndowKeyboardHandler.swift in Sources */,
|
||||
65ED4014235DEF6C0081F399 /* PasteboardWebFeed.swift in Sources */,
|
||||
65ED4014235DEF6C0081F399 /* PasteboardFeed.swift in Sources */,
|
||||
510C417B24E5D1AE008226FD /* ExtensionContainersFile.swift in Sources */,
|
||||
65ED4015235DEF6C0081F399 /* AccountsDetailViewController.swift in Sources */,
|
||||
65ED4016235DEF6C0081F399 /* DetailViewController.swift in Sources */,
|
||||
@@ -3859,10 +3854,10 @@
|
||||
B2B80779239C4C7300F191E0 /* RSImage-AppIcons.swift in Sources */,
|
||||
65ED4026235DEF6C0081F399 /* TimelineTableRowView.swift in Sources */,
|
||||
65ED4027235DEF6C0081F399 /* UnreadIndicatorView.swift in Sources */,
|
||||
51A9A5F22380DE520033AADF /* AddWebFeedDefaultContainer.swift in Sources */,
|
||||
51A9A5F22380DE520033AADF /* AddFeedDefaultContainer.swift in Sources */,
|
||||
65ED4028235DEF6C0081F399 /* ExtractedArticle.swift in Sources */,
|
||||
65ED4029235DEF6C0081F399 /* DeleteCommand.swift in Sources */,
|
||||
65ED402A235DEF6C0081F399 /* AddWebFeedWindowController.swift in Sources */,
|
||||
65ED402A235DEF6C0081F399 /* AddFeedWindowController.swift in Sources */,
|
||||
65ED402B235DEF6C0081F399 /* ImportOPMLWindowController.swift in Sources */,
|
||||
65ED402C235DEF6C0081F399 /* TimelineTableView.swift in Sources */,
|
||||
178A9F9E2549449F00AB7E9D /* AddAccountsView.swift in Sources */,
|
||||
@@ -3884,7 +3879,7 @@
|
||||
65ED4037235DEF6C0081F399 /* FolderTreeControllerDelegate.swift in Sources */,
|
||||
65ED4038235DEF6C0081F399 /* RSImage-Extensions.swift in Sources */,
|
||||
65ED4039235DEF6C0081F399 /* SingleFaviconDownloader.swift in Sources */,
|
||||
65ED403A235DEF6C0081F399 /* WebFeed+Scriptability.swift in Sources */,
|
||||
65ED403A235DEF6C0081F399 /* Feed+Scriptability.swift in Sources */,
|
||||
65ED403B235DEF6C0081F399 /* AuthorAvatarDownloader.swift in Sources */,
|
||||
65ED403C235DEF6C0081F399 /* SingleLineTextFieldSizer.swift in Sources */,
|
||||
65ED403D235DEF6C0081F399 /* TimelineTableCellView.swift in Sources */,
|
||||
@@ -3914,7 +3909,7 @@
|
||||
512DD4C92430086400C17B1F /* CloudKitAccountViewController.swift in Sources */,
|
||||
840D617F2029031C009BC708 /* AppDelegate.swift in Sources */,
|
||||
51236339236915B100951F16 /* RoundedProgressView.swift in Sources */,
|
||||
512E08E72268801200BDCFDD /* WebFeedTreeControllerDelegate.swift in Sources */,
|
||||
512E08E72268801200BDCFDD /* FeedTreeControllerDelegate.swift in Sources */,
|
||||
51C452A422650A2D00C03939 /* ArticleUtilities.swift in Sources */,
|
||||
51EF0F79227716380050506E /* ColorHash.swift in Sources */,
|
||||
51F9F3FB23DFB25700A314FD /* Animations.swift in Sources */,
|
||||
@@ -3939,7 +3934,7 @@
|
||||
51E43962238037C400015C31 /* AddFeedFolderViewController.swift in Sources */,
|
||||
51C4528F226509BD00C03939 /* UnreadFeed.swift in Sources */,
|
||||
51FD413B2342BD0500880194 /* MasterTimelineUnreadCountView.swift in Sources */,
|
||||
513146B2235A81A400387FDC /* AddWebFeedIntentHandler.swift in Sources */,
|
||||
513146B2235A81A400387FDC /* AddFeedIntentHandler.swift in Sources */,
|
||||
51D87EE12311D34700E63F03 /* ActivityType.swift in Sources */,
|
||||
51C452772265091600C03939 /* MultilineUILabelSizer.swift in Sources */,
|
||||
51C452A522650A2D00C03939 /* SmallIconProvider.swift in Sources */,
|
||||
@@ -3974,9 +3969,9 @@
|
||||
5F323809231DF9F000706F6B /* VibrantTableViewCell.swift in Sources */,
|
||||
51FE10042345529D0056195D /* UserNotificationManager.swift in Sources */,
|
||||
51C4CFF224D37D1F00AF9874 /* Secrets.swift in Sources */,
|
||||
51C452A022650A1900C03939 /* WebFeedIconDownloader.swift in Sources */,
|
||||
51C452A022650A1900C03939 /* FeedIconDownloader.swift in Sources */,
|
||||
51C4529E22650A1900C03939 /* ImageDownloader.swift in Sources */,
|
||||
51A66685238075AE00CB272D /* AddWebFeedDefaultContainer.swift in Sources */,
|
||||
51A66685238075AE00CB272D /* AddFeedDefaultContainer.swift in Sources */,
|
||||
176813E92564BAE200D98635 /* WidgetDeepLinks.swift in Sources */,
|
||||
51B5C87723F22B8200032075 /* ExtensionContainers.swift in Sources */,
|
||||
51C45292226509C800C03939 /* TodayFeedDelegate.swift in Sources */,
|
||||
@@ -4008,7 +4003,7 @@
|
||||
51C9DE5823EA2EF4003D5A6D /* WrapperScriptMessageHandler.swift in Sources */,
|
||||
51B5C87D23F2346200032075 /* ExtensionContainersFile.swift in Sources */,
|
||||
51102165233A7D6C0007A5F7 /* ArticleExtractorButton.swift in Sources */,
|
||||
5141E7392373C18B0013FF27 /* WebFeedInspectorViewController.swift in Sources */,
|
||||
5141E7392373C18B0013FF27 /* FeedInspectorViewController.swift in Sources */,
|
||||
C5A6ED6D23C9B0C800AB6BE2 /* UIActivityViewController-Extensions.swift in Sources */,
|
||||
5108F6D42375EEEF001ABC45 /* TimelinePreviewTableViewController.swift in Sources */,
|
||||
84CAFCA522BC8C08007694F0 /* FetchRequestQueue.swift in Sources */,
|
||||
@@ -4116,18 +4111,18 @@
|
||||
D57BE6E0204CD35F00D11AAC /* NSScriptCommand+NetNewsWire.swift in Sources */,
|
||||
D553738B20186C20006D8857 /* Article+Scriptability.swift in Sources */,
|
||||
845EE7C11FC2488C00854A1F /* SmartFeed.swift in Sources */,
|
||||
51A9A5F32380DE530033AADF /* AddWebFeedDefaultContainer.swift in Sources */,
|
||||
51A9A5F32380DE530033AADF /* AddFeedDefaultContainer.swift in Sources */,
|
||||
84702AA41FA27AC0006B8943 /* MarkStatusCommand.swift in Sources */,
|
||||
D5907D7F2004AC00005947E5 /* NSApplication+Scriptability.swift in Sources */,
|
||||
8405DD9C22153BD7008CE1BF /* NSView-Extensions.swift in Sources */,
|
||||
849A979F1ED9F130007D329B /* SidebarCell.swift in Sources */,
|
||||
51E595A5228CC36500FCC42B /* ArticleStatusSyncTimer.swift in Sources */,
|
||||
849A97651ED9EB96007D329B /* WebFeedTreeControllerDelegate.swift in Sources */,
|
||||
849A97651ED9EB96007D329B /* FeedTreeControllerDelegate.swift in Sources */,
|
||||
849A97671ED9EB96007D329B /* UnreadCountView.swift in Sources */,
|
||||
510C418024E5D1AE008226FD /* ExtensionFeedAddRequestFile.swift in Sources */,
|
||||
51FE10092346739D0056195D /* ActivityType.swift in Sources */,
|
||||
840BEE4121D70E64009BBAFA /* CrashReportWindowController.swift in Sources */,
|
||||
8426118A1FCB67AA0086A189 /* WebFeedIconDownloader.swift in Sources */,
|
||||
8426118A1FCB67AA0086A189 /* FeedIconDownloader.swift in Sources */,
|
||||
84C9FC7B22629E1200D921D6 /* PreferencesControlsBackgroundView.swift in Sources */,
|
||||
84162A152038C12C00035290 /* MarkCommandValidationStatus.swift in Sources */,
|
||||
17D643B126F8A436008D4C05 /* ArticleThemeDownloader.swift in Sources */,
|
||||
@@ -4157,7 +4152,7 @@
|
||||
8477ACBE22238E9500DF7F37 /* SearchFeedDelegate.swift in Sources */,
|
||||
51E3EB33229AB02C00645299 /* ErrorHandler.swift in Sources */,
|
||||
51FE100A234673A00056195D /* ActivityManager.swift in Sources */,
|
||||
8472058120142E8900AD578B /* WebFeedInspectorViewController.swift in Sources */,
|
||||
8472058120142E8900AD578B /* FeedInspectorViewController.swift in Sources */,
|
||||
55E15BCC229D65A900D6602A /* AccountsReaderAPIWindowController.swift in Sources */,
|
||||
5144EA382279FC6200D19003 /* AccountsAddLocalWindowController.swift in Sources */,
|
||||
84AD1EAA2031617300BC20B7 /* PasteboardFolder.swift in Sources */,
|
||||
@@ -4188,7 +4183,7 @@
|
||||
849A97431ED9EAA9007D329B /* AddFolderWindowController.swift in Sources */,
|
||||
8405DDA522168C62008CE1BF /* TimelineContainerViewController.swift in Sources */,
|
||||
844B5B671FEA18E300C7C76A /* MainWIndowKeyboardHandler.swift in Sources */,
|
||||
848D578E21543519005FFAD5 /* PasteboardWebFeed.swift in Sources */,
|
||||
848D578E21543519005FFAD5 /* PasteboardFeed.swift in Sources */,
|
||||
5144EA2F2279FAB600D19003 /* AccountsDetailViewController.swift in Sources */,
|
||||
849A97801ED9EC42007D329B /* DetailViewController.swift in Sources */,
|
||||
173A64172547BE0900267F6E /* AccountType+Helpers.swift in Sources */,
|
||||
@@ -4214,14 +4209,13 @@
|
||||
849A977B1ED9EC04007D329B /* UnreadIndicatorView.swift in Sources */,
|
||||
51FA73A72332BE880090D516 /* ExtractedArticle.swift in Sources */,
|
||||
84B99C9D1FAE83C600ECDEDB /* DeleteCommand.swift in Sources */,
|
||||
849A97541ED9EAC0007D329B /* AddWebFeedWindowController.swift in Sources */,
|
||||
849A97541ED9EAC0007D329B /* AddFeedWindowController.swift in Sources */,
|
||||
5144EA40227A37EC00D19003 /* ImportOPMLWindowController.swift in Sources */,
|
||||
178A9F9D2549449F00AB7E9D /* AddAccountsView.swift in Sources */,
|
||||
51C4CFF024D37D1F00AF9874 /* Secrets.swift in Sources */,
|
||||
849A976D1ED9EBC8007D329B /* TimelineTableView.swift in Sources */,
|
||||
84D52E951FE588BB00D14F5B /* DetailStatusBarView.swift in Sources */,
|
||||
D5E4CC64202C1AC1009B4FFC /* MainWindowController+Scriptability.swift in Sources */,
|
||||
51A052CE244FB9D7006C2024 /* AddFeedWIndowController.swift in Sources */,
|
||||
84C9FC7922629E1200D921D6 /* PreferencesWindowController.swift in Sources */,
|
||||
84411E711FE5FBFA004B527F /* SmallIconProvider.swift in Sources */,
|
||||
847120D72B8AE6AF00BBFC34 /* UTType+Extensions.swift in Sources */,
|
||||
@@ -4237,7 +4231,7 @@
|
||||
849A97A31ED9F180007D329B /* FolderTreeControllerDelegate.swift in Sources */,
|
||||
51126DA4225FDE2F00722696 /* RSImage-Extensions.swift in Sources */,
|
||||
845A29091FC74B8E007B49E3 /* SingleFaviconDownloader.swift in Sources */,
|
||||
D5F4EDB720074D6500B9E363 /* WebFeed+Scriptability.swift in Sources */,
|
||||
D5F4EDB720074D6500B9E363 /* Feed+Scriptability.swift in Sources */,
|
||||
84E850861FCB60CE0072EA88 /* AuthorAvatarDownloader.swift in Sources */,
|
||||
84E185B3203B74E500F69BFA /* SingleLineTextFieldSizer.swift in Sources */,
|
||||
849A977A1ED9EC04007D329B /* TimelineTableCellView.swift in Sources */,
|
||||
@@ -4383,12 +4377,12 @@
|
||||
name = SafariExtensionViewController.xib;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
848363002262A3BC00DA1D35 /* AddWebFeedSheet.xib */ = {
|
||||
848363002262A3BC00DA1D35 /* AddFeedSheet.xib */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
848363012262A3BC00DA1D35 /* Base */,
|
||||
);
|
||||
name = AddWebFeedSheet.xib;
|
||||
name = AddFeedSheet.xib;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
848363032262A3CC00DA1D35 /* AddFolderSheet.xib */ = {
|
||||
|
||||
@@ -40,7 +40,7 @@ class ActivityManager {
|
||||
}
|
||||
|
||||
init() {
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(webFeedIconDidBecomeAvailable(_:)), name: .WebFeedIconDidBecomeAvailable, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(feedIconDidBecomeAvailable(_:)), name: .FeedIconDidBecomeAvailable, object: nil)
|
||||
}
|
||||
|
||||
func invalidateCurrentActivities() {
|
||||
@@ -54,8 +54,8 @@ class ActivityManager {
|
||||
|
||||
selectingActivity = makeSelectFeedActivity(feed: feed)
|
||||
|
||||
if let webFeed = feed as? Feed {
|
||||
updateSelectingActivityFeedSearchAttributes(with: webFeed)
|
||||
if let feed = feed as? Feed {
|
||||
updateSelectingActivityFeedSearchAttributes(with: feed)
|
||||
}
|
||||
|
||||
donate(selectingActivity!)
|
||||
@@ -117,8 +117,8 @@ class ActivityManager {
|
||||
}
|
||||
}
|
||||
|
||||
for webFeed in account.flattenedWebFeeds() {
|
||||
ids.append(contentsOf: identifiers(for: webFeed))
|
||||
for feed in account.flattenedFeeds() {
|
||||
ids.append(contentsOf: identifiers(for: feed))
|
||||
}
|
||||
|
||||
CSSearchableIndex.default().deleteSearchableItems(withIdentifiers: ids)
|
||||
@@ -128,31 +128,31 @@ class ActivityManager {
|
||||
var ids = [String]()
|
||||
ids.append(identifier(for: folder))
|
||||
|
||||
for webFeed in folder.flattenedWebFeeds() {
|
||||
ids.append(contentsOf: identifiers(for: webFeed))
|
||||
for feed in folder.flattenedFeeds() {
|
||||
ids.append(contentsOf: identifiers(for: feed))
|
||||
}
|
||||
|
||||
CSSearchableIndex.default().deleteSearchableItems(withIdentifiers: ids)
|
||||
}
|
||||
|
||||
static func cleanUp(_ webFeed: Feed) {
|
||||
CSSearchableIndex.default().deleteSearchableItems(withIdentifiers: identifiers(for: webFeed))
|
||||
static func cleanUp(_ feed: Feed) {
|
||||
CSSearchableIndex.default().deleteSearchableItems(withIdentifiers: identifiers(for: feed))
|
||||
}
|
||||
#endif
|
||||
|
||||
@objc func webFeedIconDidBecomeAvailable(_ note: Notification) {
|
||||
guard let webFeed = note.userInfo?[UserInfoKey.webFeed] as? Feed, let activityFeedId = selectingActivity?.userInfo?[ArticlePathKey.webFeedID] as? String else {
|
||||
@objc func feedIconDidBecomeAvailable(_ note: Notification) {
|
||||
guard let feed = note.userInfo?[UserInfoKey.feed] as? Feed, let activityFeedId = selectingActivity?.userInfo?[ArticlePathKey.feedID] as? String else {
|
||||
return
|
||||
}
|
||||
|
||||
#if os(iOS)
|
||||
if let article = readingArticle, activityFeedId == article.webFeedID {
|
||||
if let article = readingArticle, activityFeedId == article.feedID {
|
||||
updateReadArticleSearchAttributes(with: article)
|
||||
}
|
||||
#endif
|
||||
|
||||
if activityFeedId == webFeed.webFeedID {
|
||||
updateSelectingActivityFeedSearchAttributes(with: webFeed)
|
||||
if activityFeedId == feed.feedID {
|
||||
updateSelectingActivityFeedSearchAttributes(with: feed)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,7 +236,7 @@ private extension ActivityManager {
|
||||
#endif
|
||||
|
||||
func makeKeywords(_ article: Article) -> [String] {
|
||||
let feedNameKeywords = makeKeywords(article.webFeed?.nameForDisplay)
|
||||
let feedNameKeywords = makeKeywords(article.feed?.nameForDisplay)
|
||||
let articleTitleKeywords = makeKeywords(ArticleStringFormatter.truncatedTitle(article))
|
||||
return feedNameKeywords + articleTitleKeywords
|
||||
}
|
||||
@@ -279,11 +279,11 @@ private extension ActivityManager {
|
||||
}
|
||||
|
||||
static func identifier(for feed: Feed) -> String {
|
||||
return "account_\(feed.account!.accountID)_feed_\(feed.webFeedID)"
|
||||
return "account_\(feed.account!.accountID)_feed_\(feed.feedID)"
|
||||
}
|
||||
|
||||
static func identifier(for article: Article) -> String {
|
||||
return "account_\(article.accountID)_feed_\(article.webFeedID)_article_\(article.articleID)"
|
||||
return "account_\(article.accountID)_feed_\(article.feedID)_article_\(article.articleID)"
|
||||
}
|
||||
|
||||
static func identifiers(for feed: Feed) -> [String] {
|
||||
|
||||
@@ -241,8 +241,8 @@ private extension ArticleRenderer {
|
||||
d["dateline_style"] = "articleDateline"
|
||||
}
|
||||
|
||||
d["feed_link_title"] = article.webFeed?.nameForDisplay ?? ""
|
||||
d["feed_link"] = article.webFeed?.homePageURL ?? ""
|
||||
d["feed_link_title"] = article.feed?.nameForDisplay ?? ""
|
||||
d["feed_link"] = article.feed?.homePageURL ?? ""
|
||||
|
||||
d["byline"] = byline()
|
||||
|
||||
@@ -261,7 +261,7 @@ private extension ArticleRenderer {
|
||||
}
|
||||
|
||||
func byline() -> String {
|
||||
guard let authors = article?.authors ?? article?.webFeed?.authors, !authors.isEmpty else {
|
||||
guard let authors = article?.authors ?? article?.feed?.authors, !authors.isEmpty else {
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -269,7 +269,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?.webFeed?.nameForDisplay {
|
||||
if author.name == article?.feed?.nameForDisplay {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
@@ -333,10 +333,10 @@ private extension Article {
|
||||
var baseURL: URL? {
|
||||
var s = link
|
||||
if s == nil {
|
||||
s = webFeed?.homePageURL
|
||||
s = feed?.homePageURL
|
||||
}
|
||||
if s == nil {
|
||||
s = webFeed?.url
|
||||
s = feed?.url
|
||||
}
|
||||
|
||||
guard let urlString = s else {
|
||||
|
||||
@@ -98,7 +98,7 @@ private struct SidebarItemSpecifier {
|
||||
private weak var account: Account?
|
||||
private let parentFolder: Folder?
|
||||
private let folder: Folder?
|
||||
private let webFeed: Feed?
|
||||
private let feed: Feed?
|
||||
private let path: ContainerPath
|
||||
private let errorHandler: (Error) -> ()
|
||||
|
||||
@@ -118,13 +118,13 @@ private struct SidebarItemSpecifier {
|
||||
|
||||
self.parentFolder = node.parentFolder()
|
||||
|
||||
if let webFeed = node.representedObject as? Feed {
|
||||
self.webFeed = webFeed
|
||||
if let feed = node.representedObject as? Feed {
|
||||
self.feed = feed
|
||||
self.folder = nil
|
||||
account = webFeed.account
|
||||
account = feed.account
|
||||
}
|
||||
else if let folder = node.representedObject as? Folder {
|
||||
self.webFeed = nil
|
||||
self.feed = nil
|
||||
self.folder = folder
|
||||
account = folder.account
|
||||
}
|
||||
@@ -144,7 +144,7 @@ private struct SidebarItemSpecifier {
|
||||
|
||||
func delete(completion: @escaping () -> Void) {
|
||||
|
||||
if let webFeed = webFeed {
|
||||
if let feed = feed {
|
||||
|
||||
guard let container = path.resolveContainer() else {
|
||||
completion()
|
||||
@@ -152,7 +152,7 @@ private struct SidebarItemSpecifier {
|
||||
}
|
||||
|
||||
BatchUpdate.shared.start()
|
||||
account?.removeWebFeed(webFeed, from: container) { result in
|
||||
account?.removeFeed(feed, from: container) { result in
|
||||
BatchUpdate.shared.end()
|
||||
completion()
|
||||
self.checkResult(result)
|
||||
@@ -172,22 +172,22 @@ private struct SidebarItemSpecifier {
|
||||
|
||||
func restore() {
|
||||
|
||||
if let _ = webFeed {
|
||||
restoreWebFeed()
|
||||
if let _ = feed {
|
||||
restoreFeed()
|
||||
}
|
||||
else if let _ = folder {
|
||||
restoreFolder()
|
||||
}
|
||||
}
|
||||
|
||||
private func restoreWebFeed() {
|
||||
private func restoreFeed() {
|
||||
|
||||
guard let account = account, let feed = webFeed, let container = path.resolveContainer() else {
|
||||
guard let account = account, let feed = feed, let container = path.resolveContainer() else {
|
||||
return
|
||||
}
|
||||
|
||||
BatchUpdate.shared.start()
|
||||
account.restoreWebFeed(feed, container: container) { result in
|
||||
account.restoreFeed(feed, container: container) { result in
|
||||
BatchUpdate.shared.end()
|
||||
self.checkResult(result)
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ private extension SendToMarsEditCommand {
|
||||
let body = article.contentHTML ?? article.contentText ?? article.summary
|
||||
let authorName = article.authors?.first?.name
|
||||
|
||||
let sender = SendToBlogEditorApp(targetDescriptor: targetDescriptor, title: article.title, body: body, summary: article.summary, link: article.externalLink, permalink: article.link, subject: nil, creator: authorName, commentsURL: nil, guid: article.uniqueID, sourceName: article.webFeed?.nameForDisplay, sourceHomeURL: article.webFeed?.homePageURL, sourceFeedURL: article.webFeed?.url)
|
||||
let sender = SendToBlogEditorApp(targetDescriptor: targetDescriptor, title: article.title, body: body, summary: article.summary, link: article.externalLink, permalink: article.link, subject: nil, creator: authorName, commentsURL: nil, guid: article.uniqueID, sourceName: article.feed?.nameForDisplay, sourceHomeURL: article.feed?.homePageURL, sourceFeedURL: article.feed?.url)
|
||||
let _ = sender.send()
|
||||
}
|
||||
|
||||
|
||||
@@ -65,10 +65,10 @@ private extension Article {
|
||||
// Feed name, or feed name + author name (if author is specified per-article).
|
||||
// Includes trailing space.
|
||||
|
||||
if let feedName = webFeed?.nameForDisplay, let authorName = authors?.first?.name {
|
||||
if let feedName = feed?.nameForDisplay, let authorName = authors?.first?.name {
|
||||
return feedName + ", " + authorName + ": "
|
||||
}
|
||||
if let feedName = webFeed?.nameForDisplay {
|
||||
if let feedName = feed?.nameForDisplay {
|
||||
return feedName + ": "
|
||||
}
|
||||
return ""
|
||||
|
||||
+7
-7
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// AddWebFeedDefaultContainer.swift
|
||||
// AddFeedDefaultContainer.swift
|
||||
// NetNewsWire-iOS
|
||||
//
|
||||
// Created by Maurice Parker on 11/16/19.
|
||||
@@ -9,12 +9,12 @@
|
||||
import Foundation
|
||||
import Account
|
||||
|
||||
struct AddWebFeedDefaultContainer {
|
||||
struct AddFeedDefaultContainer {
|
||||
|
||||
static var defaultContainer: Container? {
|
||||
|
||||
if let accountID = AppDefaults.shared.addWebFeedAccountID, let account = AccountManager.shared.activeAccounts.first(where: { $0.accountID == accountID }) {
|
||||
if let folderName = AppDefaults.shared.addWebFeedFolderName, let folder = account.existingFolder(withDisplayName: folderName) {
|
||||
if let accountID = AppDefaults.shared.addFeedAccountID, let account = AccountManager.shared.activeAccounts.first(where: { $0.accountID == accountID }) {
|
||||
if let folderName = AppDefaults.shared.addFeedFolderName, let folder = account.existingFolder(withDisplayName: folderName) {
|
||||
return folder
|
||||
} else {
|
||||
return substituteContainerIfNeeded(account: account)
|
||||
@@ -28,11 +28,11 @@ struct AddWebFeedDefaultContainer {
|
||||
}
|
||||
|
||||
static func saveDefaultContainer(_ container: Container) {
|
||||
AppDefaults.shared.addWebFeedAccountID = container.account?.accountID
|
||||
AppDefaults.shared.addFeedAccountID = container.account?.accountID
|
||||
if let folder = container as? Folder {
|
||||
AppDefaults.shared.addWebFeedFolderName = folder.nameForDisplay
|
||||
AppDefaults.shared.addFeedFolderName = folder.nameForDisplay
|
||||
} else {
|
||||
AppDefaults.shared.addWebFeedFolderName = nil
|
||||
AppDefaults.shared.addFeedFolderName = nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,8 +42,8 @@ private func accountAndArticlesDictionary(_ articles: Set<Article>) -> [String:
|
||||
|
||||
extension Article {
|
||||
|
||||
var webFeed: Feed? {
|
||||
return account?.existingWebFeed(withWebFeedID: webFeedID)
|
||||
var feed: Feed? {
|
||||
return account?.existingFeed(withFeedID: feedID)
|
||||
}
|
||||
|
||||
var url: URL? {
|
||||
@@ -121,11 +121,11 @@ extension Article {
|
||||
return IconImageCache.shared.imageForArticle(self)
|
||||
}
|
||||
|
||||
func iconImageUrl(webFeed: Feed) -> URL? {
|
||||
func iconImageUrl(feed: Feed) -> URL? {
|
||||
if let image = iconImage() {
|
||||
let fm = FileManager.default
|
||||
var path = fm.urls(for: .cachesDirectory, in: .userDomainMask)[0]
|
||||
let feedID = webFeed.webFeedID.replacingOccurrences(of: "/", with: "_")
|
||||
let feedID = feed.feedID.replacingOccurrences(of: "/", with: "_")
|
||||
#if os(macOS)
|
||||
path.appendPathComponent(feedID + "_smallIcon.tiff")
|
||||
#else
|
||||
@@ -139,7 +139,7 @@ extension Article {
|
||||
}
|
||||
|
||||
func byline() -> String {
|
||||
guard let authors = authors ?? webFeed?.authors, !authors.isEmpty else {
|
||||
guard let authors = authors ?? feed?.authors, !authors.isEmpty else {
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -147,7 +147,7 @@ extension Article {
|
||||
// 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 == webFeed?.nameForDisplay {
|
||||
if author.name == feed?.nameForDisplay {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
@@ -193,7 +193,7 @@ extension Article {
|
||||
struct ArticlePathKey {
|
||||
static let accountID = "accountID"
|
||||
static let accountName = "accountName"
|
||||
static let webFeedID = "webFeedID"
|
||||
static let feedID = "feedID"
|
||||
static let articleID = "articleID"
|
||||
}
|
||||
|
||||
@@ -203,7 +203,7 @@ extension Article {
|
||||
return [
|
||||
ArticlePathKey.accountID: accountID,
|
||||
ArticlePathKey.accountName: account?.nameForDisplay ?? "",
|
||||
ArticlePathKey.webFeedID: webFeedID,
|
||||
ArticlePathKey.feedID: feedID,
|
||||
ArticlePathKey.articleID: articleID
|
||||
]
|
||||
}
|
||||
@@ -215,7 +215,7 @@ extension Article {
|
||||
extension Article: SortableArticle {
|
||||
|
||||
var sortableName: String {
|
||||
return webFeed?.name ?? ""
|
||||
return feed?.name ?? ""
|
||||
}
|
||||
|
||||
var sortableDate: Date {
|
||||
@@ -226,8 +226,8 @@ extension Article: SortableArticle {
|
||||
return articleID
|
||||
}
|
||||
|
||||
var sortableWebFeedID: String {
|
||||
return webFeedID
|
||||
var sortableFeedID: String {
|
||||
return feedID
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -71,18 +71,18 @@ final class FaviconDownloader {
|
||||
cache = [Feed: IconImage]()
|
||||
}
|
||||
|
||||
func favicon(for webFeed: Feed) -> IconImage? {
|
||||
func favicon(for feed: Feed) -> IconImage? {
|
||||
|
||||
assert(Thread.isMainThread)
|
||||
|
||||
var homePageURL = webFeed.homePageURL
|
||||
if let faviconURL = webFeed.faviconURL {
|
||||
var homePageURL = feed.homePageURL
|
||||
if let faviconURL = feed.faviconURL {
|
||||
return favicon(with: faviconURL, homePageURL: homePageURL)
|
||||
}
|
||||
|
||||
if homePageURL == nil {
|
||||
// Base homePageURL off feedURL if needed. Won’t always be accurate, but is good enough.
|
||||
if let feedURL = URL(string: webFeed.url), let scheme = feedURL.scheme, let host = feedURL.host {
|
||||
if let feedURL = URL(string: feed.url), let scheme = feedURL.scheme, let host = feedURL.host {
|
||||
homePageURL = scheme + "://" + host + "/"
|
||||
}
|
||||
}
|
||||
@@ -93,16 +93,16 @@ final class FaviconDownloader {
|
||||
return nil
|
||||
}
|
||||
|
||||
func faviconAsIcon(for webFeed: Feed) -> IconImage? {
|
||||
func faviconAsIcon(for feed: Feed) -> IconImage? {
|
||||
|
||||
if let image = cache[webFeed] {
|
||||
if let image = cache[feed] {
|
||||
return image
|
||||
}
|
||||
|
||||
if let iconImage = favicon(for: webFeed), let imageData = iconImage.image.dataRepresentation() {
|
||||
if let iconImage = favicon(for: feed), let imageData = iconImage.image.dataRepresentation() {
|
||||
if let scaledImage = RSImage.scaledForIcon(imageData) {
|
||||
let scaledIconImage = IconImage(scaledImage)
|
||||
cache[webFeed] = scaledIconImage
|
||||
cache[feed] = scaledIconImage
|
||||
return scaledIconImage
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,16 +14,16 @@ final class FaviconGenerator {
|
||||
|
||||
private static var faviconGeneratorCache = [String: IconImage]() // feedURL: RSImage
|
||||
|
||||
static func favicon(_ webFeed: Feed) -> IconImage {
|
||||
static func favicon(_ feed: Feed) -> IconImage {
|
||||
|
||||
if let favicon = FaviconGenerator.faviconGeneratorCache[webFeed.url] {
|
||||
if let favicon = FaviconGenerator.faviconGeneratorCache[feed.url] {
|
||||
return favicon
|
||||
}
|
||||
|
||||
let colorHash = ColorHash(webFeed.url)
|
||||
let colorHash = ColorHash(feed.url)
|
||||
if let favicon = AppAssets.faviconTemplateImage.maskWithColor(color: colorHash.color.cgColor) {
|
||||
let iconImage = IconImage(favicon, isBackgroundSupressed: true)
|
||||
FaviconGenerator.faviconGeneratorCache[webFeed.url] = iconImage
|
||||
FaviconGenerator.faviconGeneratorCache[feed.url] = iconImage
|
||||
return iconImage
|
||||
} else {
|
||||
return IconImage(AppAssets.faviconTemplateImage, isBackgroundSupressed: true)
|
||||
|
||||
@@ -15,7 +15,7 @@ class IconImageCache {
|
||||
static var shared = IconImageCache()
|
||||
|
||||
private var smartFeedIconImageCache = [SidebarItemIdentifier: IconImage]()
|
||||
private var webFeedIconImageCache = [SidebarItemIdentifier: IconImage]()
|
||||
private var feedIconImageCache = [SidebarItemIdentifier: IconImage]()
|
||||
private var faviconImageCache = [SidebarItemIdentifier: IconImage]()
|
||||
private var smallIconImageCache = [SidebarItemIdentifier: IconImage]()
|
||||
private var authorIconImageCache = [Author: IconImage]()
|
||||
@@ -38,7 +38,7 @@ class IconImageCache {
|
||||
if let smartFeed = sidebarItem as? PseudoFeed {
|
||||
return imageForSmartFeed(smartFeed, sidebarItemID)
|
||||
}
|
||||
if let webFeed = sidebarItem as? Feed, let iconImage = imageForWebFeed(webFeed, sidebarItemID) {
|
||||
if let feed = sidebarItem as? Feed, let iconImage = imageForFeed(feed, sidebarItemID) {
|
||||
return iconImage
|
||||
}
|
||||
if let smallIconProvider = sidebarItem as? SmallIconProvider {
|
||||
@@ -52,7 +52,7 @@ class IconImageCache {
|
||||
if let iconImage = imageForAuthors(article.authors) {
|
||||
return iconImage
|
||||
}
|
||||
guard let feed = article.webFeed else {
|
||||
guard let feed = article.feed else {
|
||||
return nil
|
||||
}
|
||||
return imageForFeed(feed)
|
||||
@@ -60,7 +60,7 @@ class IconImageCache {
|
||||
|
||||
func emptyCache() {
|
||||
smartFeedIconImageCache = [SidebarItemIdentifier: IconImage]()
|
||||
webFeedIconImageCache = [SidebarItemIdentifier: IconImage]()
|
||||
feedIconImageCache = [SidebarItemIdentifier: IconImage]()
|
||||
faviconImageCache = [SidebarItemIdentifier: IconImage]()
|
||||
smallIconImageCache = [SidebarItemIdentifier: IconImage]()
|
||||
authorIconImageCache = [Author: IconImage]()
|
||||
@@ -80,18 +80,18 @@ private extension IconImageCache {
|
||||
return nil
|
||||
}
|
||||
|
||||
func imageForWebFeed(_ webFeed: Feed, _ feedID: SidebarItemIdentifier) -> IconImage? {
|
||||
if let iconImage = webFeedIconImageCache[feedID] {
|
||||
func imageForFeed(_ feed: Feed, _ feedID: SidebarItemIdentifier) -> IconImage? {
|
||||
if let iconImage = feedIconImageCache[feedID] {
|
||||
return iconImage
|
||||
}
|
||||
if let iconImage = appDelegate.webFeedIconDownloader.icon(for: webFeed) {
|
||||
webFeedIconImageCache[feedID] = iconImage
|
||||
if let iconImage = appDelegate.feedIconDownloader.icon(for: feed) {
|
||||
feedIconImageCache[feedID] = iconImage
|
||||
return iconImage
|
||||
}
|
||||
if let faviconImage = faviconImageCache[feedID] {
|
||||
return faviconImage
|
||||
}
|
||||
if let faviconImage = appDelegate.faviconDownloader.faviconAsIcon(for: webFeed) {
|
||||
if let faviconImage = appDelegate.faviconDownloader.faviconAsIcon(for: feed) {
|
||||
faviconImageCache[feedID] = faviconImage
|
||||
return faviconImage
|
||||
}
|
||||
|
||||
@@ -15,10 +15,10 @@ import RSParser
|
||||
|
||||
extension Notification.Name {
|
||||
|
||||
static let WebFeedIconDidBecomeAvailable = Notification.Name("WebFeedIconDidBecomeAvailableNotification") // UserInfoKey.feed
|
||||
static let FeedIconDidBecomeAvailable = Notification.Name("FeedIconDidBecomeAvailableNotification") // UserInfoKey.feed
|
||||
}
|
||||
|
||||
public final class WebFeedIconDownloader {
|
||||
public final class FeedIconDownloader {
|
||||
|
||||
private static let saveQueue = CoalescingQueue(name: "Cache Save Queue", interval: 1.0)
|
||||
|
||||
@@ -151,7 +151,7 @@ public final class WebFeedIconDownloader {
|
||||
|
||||
}
|
||||
|
||||
private extension WebFeedIconDownloader {
|
||||
private extension FeedIconDownloader {
|
||||
|
||||
func icon(forHomePageURL homePageURL: String, feed: Feed, _ imageResultBlock: @escaping (RSImage?) -> Void) {
|
||||
|
||||
@@ -180,8 +180,8 @@ private extension WebFeedIconDownloader {
|
||||
func postFeedIconDidBecomeAvailableNotification(_ feed: Feed) {
|
||||
|
||||
DispatchQueue.main.async {
|
||||
let userInfo: [AnyHashable: Any] = [UserInfoKey.webFeed: feed]
|
||||
NotificationCenter.default.post(name: .WebFeedIconDidBecomeAvailable, object: self, userInfo: userInfo)
|
||||
let userInfo: [AnyHashable: Any] = [UserInfoKey.feed: feed]
|
||||
NotificationCenter.default.post(name: .FeedIconDidBecomeAvailable, object: self, userInfo: userInfo)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -256,15 +256,15 @@ private extension WebFeedIconDownloader {
|
||||
}
|
||||
|
||||
func queueSaveFeedURLToIconURLCacheIfNeeded() {
|
||||
WebFeedIconDownloader.saveQueue.add(self, #selector(saveFeedURLToIconURLCacheIfNeeded))
|
||||
FeedIconDownloader.saveQueue.add(self, #selector(saveFeedURLToIconURLCacheIfNeeded))
|
||||
}
|
||||
|
||||
func queueSaveHomePageToIconURLCacheIfNeeded() {
|
||||
WebFeedIconDownloader.saveQueue.add(self, #selector(saveHomePageToIconURLCacheIfNeeded))
|
||||
FeedIconDownloader.saveQueue.add(self, #selector(saveHomePageToIconURLCacheIfNeeded))
|
||||
}
|
||||
|
||||
func queueHomePagesWithNoIconURLCacheIfNeeded() {
|
||||
WebFeedIconDownloader.saveQueue.add(self, #selector(saveHomePagesWithNoIconURLCacheIfNeeded))
|
||||
FeedIconDownloader.saveQueue.add(self, #selector(saveHomePagesWithNoIconURLCacheIfNeeded))
|
||||
}
|
||||
|
||||
func saveFeedURLToIconURLCache() {
|
||||
@@ -154,7 +154,7 @@ private extension ExtensionFeedAddRequestFile {
|
||||
|
||||
guard let container = destinationContainer else { return }
|
||||
|
||||
account.createWebFeed(url: request.feedURL.absoluteString, name: request.name, container: container, validateFeed: true) { _ in }
|
||||
account.createFeed(url: request.feedURL.absoluteString, name: request.name, container: container, validateFeed: true) { _ in }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -12,8 +12,8 @@ struct ShareDefaultContainer {
|
||||
|
||||
static func defaultContainer(containers: ExtensionContainers) -> ExtensionContainer? {
|
||||
|
||||
if let accountID = AppDefaults.shared.addWebFeedAccountID, let account = containers.accounts.first(where: { $0.accountID == accountID }) {
|
||||
if let folderName = AppDefaults.shared.addWebFeedFolderName, let folder = account.folders.first(where: { $0.name == folderName }) {
|
||||
if let accountID = AppDefaults.shared.addFeedAccountID, let account = containers.accounts.first(where: { $0.accountID == accountID }) {
|
||||
if let folderName = AppDefaults.shared.addFeedFolderName, let folder = account.folders.first(where: { $0.name == folderName }) {
|
||||
return folder
|
||||
} else {
|
||||
return substituteContainerIfNeeded(account: account)
|
||||
@@ -27,11 +27,11 @@ struct ShareDefaultContainer {
|
||||
}
|
||||
|
||||
static func saveDefaultContainer(_ container: ExtensionContainer) {
|
||||
AppDefaults.shared.addWebFeedAccountID = container.accountID
|
||||
AppDefaults.shared.addFeedAccountID = container.accountID
|
||||
if let folder = container as? ExtensionFolder {
|
||||
AppDefaults.shared.addWebFeedFolderName = folder.name
|
||||
AppDefaults.shared.addFeedFolderName = folder.name
|
||||
} else {
|
||||
AppDefaults.shared.addWebFeedFolderName = nil
|
||||
AppDefaults.shared.addFeedFolderName = nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ protocol SortableArticle {
|
||||
var sortableName: String { get }
|
||||
var sortableDate: Date { get }
|
||||
var sortableArticleID: String { get }
|
||||
var sortableWebFeedID: String { get }
|
||||
var sortableFeedID: 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.sortableWebFeedID)" }
|
||||
let groupedArticles = Dictionary(grouping: articles) { "\($0.sortableName.lowercased())-\($0.sortableFeedID)" }
|
||||
return groupedArticles
|
||||
.sorted { $0.key < $1.key }
|
||||
.flatMap { (tuple) -> [T] in
|
||||
|
||||
+9
-9
@@ -11,7 +11,7 @@ import RSTree
|
||||
import Articles
|
||||
import Account
|
||||
|
||||
final class WebFeedTreeControllerDelegate: TreeControllerDelegate {
|
||||
final class FeedTreeControllerDelegate: TreeControllerDelegate {
|
||||
|
||||
private var filterExceptions = Set<SidebarItemIdentifier>()
|
||||
var isReadFiltered = false
|
||||
@@ -39,7 +39,7 @@ final class WebFeedTreeControllerDelegate: TreeControllerDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
private extension WebFeedTreeControllerDelegate {
|
||||
private extension FeedTreeControllerDelegate {
|
||||
|
||||
func childNodesForRootNode(_ rootNode: Node) -> [Node]? {
|
||||
var topLevelNodes = [Node]()
|
||||
@@ -66,9 +66,9 @@ private extension WebFeedTreeControllerDelegate {
|
||||
|
||||
var children = [AnyObject]()
|
||||
|
||||
for webFeed in container.topLevelWebFeeds {
|
||||
if let sidebarItemID = webFeed.sidebarItemID, !(!filterExceptions.contains(sidebarItemID) && isReadFiltered && webFeed.unreadCount == 0) {
|
||||
children.append(webFeed)
|
||||
for feed in container.topLevelFeeds {
|
||||
if let sidebarItemID = feed.sidebarItemID, !(!filterExceptions.contains(sidebarItemID) && isReadFiltered && feed.unreadCount == 0) {
|
||||
children.append(feed)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,8 +100,8 @@ private extension WebFeedTreeControllerDelegate {
|
||||
}
|
||||
|
||||
func createNode(representedObject: Any, parent: Node) -> Node? {
|
||||
if let webFeed = representedObject as? Feed {
|
||||
return createNode(webFeed: webFeed, parent: parent)
|
||||
if let feed = representedObject as? Feed {
|
||||
return createNode(feed: feed, parent: parent)
|
||||
}
|
||||
|
||||
if let folder = representedObject as? Folder {
|
||||
@@ -115,8 +115,8 @@ private extension WebFeedTreeControllerDelegate {
|
||||
return nil
|
||||
}
|
||||
|
||||
func createNode(webFeed: Feed, parent: Node) -> Node {
|
||||
return parent.createChildNode(webFeed)
|
||||
func createNode(feed: Feed, parent: Node) -> Node {
|
||||
return parent.createChildNode(feed)
|
||||
}
|
||||
|
||||
func createNode(folder: Folder, parent: Node) -> Node {
|
||||
@@ -10,7 +10,7 @@ import Foundation
|
||||
|
||||
struct UserInfoKey {
|
||||
|
||||
static let webFeed = "webFeed"
|
||||
static let feed = "feed"
|
||||
static let url = "url"
|
||||
static let articlePath = "articlePath"
|
||||
static let feedIdentifier = "feedIdentifier"
|
||||
|
||||
@@ -26,8 +26,8 @@ final class UserNotificationManager: NSObject {
|
||||
}
|
||||
|
||||
for article in articles {
|
||||
if !article.status.read, let webFeed = article.webFeed, webFeed.isNotifyAboutNewArticles ?? false {
|
||||
sendNotification(webFeed: webFeed, article: article)
|
||||
if !article.status.read, let feed = article.feed, feed.isNotifyAboutNewArticles ?? false {
|
||||
sendNotification(feed: feed, article: article)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -53,19 +53,19 @@ final class UserNotificationManager: NSObject {
|
||||
|
||||
private extension UserNotificationManager {
|
||||
|
||||
func sendNotification(webFeed: Feed, article: Article) {
|
||||
func sendNotification(feed: Feed, article: Article) {
|
||||
let content = UNMutableNotificationContent()
|
||||
|
||||
content.title = webFeed.nameForDisplay
|
||||
content.title = feed.nameForDisplay
|
||||
if !ArticleStringFormatter.truncatedTitle(article).isEmpty {
|
||||
content.subtitle = ArticleStringFormatter.truncatedTitle(article)
|
||||
}
|
||||
content.body = ArticleStringFormatter.truncatedSummary(article)
|
||||
content.threadIdentifier = webFeed.webFeedID
|
||||
content.threadIdentifier = feed.feedID
|
||||
content.sound = UNNotificationSound.default
|
||||
content.userInfo = [UserInfoKey.articlePath: article.pathUserInfo]
|
||||
content.categoryIdentifier = "NEW_ARTICLE_NOTIFICATION_CATEGORY"
|
||||
if let attachment = thumbnailAttachment(for: article, webFeed: webFeed) {
|
||||
if let attachment = thumbnailAttachment(for: article, feed: feed) {
|
||||
content.attachments.append(attachment)
|
||||
}
|
||||
|
||||
@@ -76,12 +76,12 @@ private extension UserNotificationManager {
|
||||
/// Determine if there is an available icon for the article. This will then move it to the caches directory and make it avialble for the notification.
|
||||
/// - Parameters:
|
||||
/// - article: `Article`
|
||||
/// - webFeed: `WebFeed`
|
||||
/// - feed: `Feed`
|
||||
/// - Returns: A `UNNotifcationAttachment` if an icon is available. Otherwise nil.
|
||||
/// - Warning: In certain scenarios, this will return the `faviconTemplateImage`.
|
||||
func thumbnailAttachment(for article: Article, webFeed: Feed) -> UNNotificationAttachment? {
|
||||
if let imageURL = article.iconImageUrl(webFeed: webFeed) {
|
||||
let thumbnail = try? UNNotificationAttachment(identifier: webFeed.webFeedID, url: imageURL, options: nil)
|
||||
func thumbnailAttachment(for article: Article, feed: Feed) -> UNNotificationAttachment? {
|
||||
if let imageURL = article.iconImageUrl(feed: feed) {
|
||||
let thumbnail = try? UNNotificationAttachment(identifier: feed.feedID, url: imageURL, options: nil)
|
||||
return thumbnail
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -19,10 +19,10 @@ class ArticleSorterTests: XCTestCase {
|
||||
func testSortByDateAscending() {
|
||||
let now = Date()
|
||||
|
||||
let article1 = TestArticle(sortableName: "Susie's Feed", sortableDate: now.addingTimeInterval(-60.0), sortableArticleID: "1", sortableWebFeedID: "4")
|
||||
let article2 = TestArticle(sortableName: "Phil's Feed", sortableDate: now.addingTimeInterval(60.0), sortableArticleID: "2", sortableWebFeedID: "6")
|
||||
let article3 = TestArticle(sortableName: "Phil's Feed", sortableDate: now.addingTimeInterval(120.0), sortableArticleID: "3", sortableWebFeedID: "6")
|
||||
let article4 = TestArticle(sortableName: "Susie's Feed", sortableDate: now.addingTimeInterval(-120.0), sortableArticleID: "4", sortableWebFeedID: "5")
|
||||
let article1 = TestArticle(sortableName: "Susie's Feed", sortableDate: now.addingTimeInterval(-60.0), sortableArticleID: "1", sortableFeedID: "4")
|
||||
let article2 = TestArticle(sortableName: "Phil's Feed", sortableDate: now.addingTimeInterval(60.0), sortableArticleID: "2", sortableFeedID: "6")
|
||||
let article3 = TestArticle(sortableName: "Phil's Feed", sortableDate: now.addingTimeInterval(120.0), sortableArticleID: "3", sortableFeedID: "6")
|
||||
let article4 = TestArticle(sortableName: "Susie's Feed", sortableDate: now.addingTimeInterval(-120.0), sortableArticleID: "4", sortableFeedID: "5")
|
||||
|
||||
let articles = [article1, article2, article3, article4]
|
||||
let sortedArticles = ArticleSorter.sortedByDate(articles: articles,
|
||||
@@ -40,11 +40,11 @@ class ArticleSorterTests: XCTestCase {
|
||||
let now = Date()
|
||||
|
||||
// Articles with the same date should end up being sorted by their article ID
|
||||
let article1 = TestArticle(sortableName: "Phil's Feed", sortableDate: now, sortableArticleID: "1", sortableWebFeedID: "1")
|
||||
let article2 = TestArticle(sortableName: "Matt's Feed", sortableDate: now, sortableArticleID: "2", sortableWebFeedID: "2")
|
||||
let article3 = TestArticle(sortableName: "Sally's Feed", sortableDate: now, sortableArticleID: "3", sortableWebFeedID: "3")
|
||||
let article4 = TestArticle(sortableName: "Susie's Feed", sortableDate: Date(timeInterval: -60.0, since: now), sortableArticleID: "4", sortableWebFeedID: "4")
|
||||
let article5 = TestArticle(sortableName: "Paul's Feed", sortableDate: Date(timeInterval: -120.0, since: now), sortableArticleID: "5", sortableWebFeedID: "5")
|
||||
let article1 = TestArticle(sortableName: "Phil's Feed", sortableDate: now, sortableArticleID: "1", sortableFeedID: "1")
|
||||
let article2 = TestArticle(sortableName: "Matt's Feed", sortableDate: now, sortableArticleID: "2", sortableFeedID: "2")
|
||||
let article3 = TestArticle(sortableName: "Sally's Feed", sortableDate: now, sortableArticleID: "3", sortableFeedID: "3")
|
||||
let article4 = TestArticle(sortableName: "Susie's Feed", sortableDate: Date(timeInterval: -60.0, since: now), sortableArticleID: "4", sortableFeedID: "4")
|
||||
let article5 = TestArticle(sortableName: "Paul's Feed", sortableDate: Date(timeInterval: -120.0, since: now), sortableArticleID: "5", sortableFeedID: "5")
|
||||
|
||||
let articles = [article1, article2, article3, article4, article5]
|
||||
let sortedArticles = ArticleSorter.sortedByDate(articles: articles,
|
||||
@@ -62,15 +62,15 @@ class ArticleSorterTests: XCTestCase {
|
||||
func testSortByDateAscendingWithGroupByFeed() {
|
||||
let now = Date()
|
||||
|
||||
let article1 = TestArticle(sortableName: "Phil's Feed", sortableDate: Date(timeInterval: -100.0, since: now), sortableArticleID: "1", sortableWebFeedID: "1")
|
||||
let article2 = TestArticle(sortableName: "Jenny's Feed", sortableDate: now, sortableArticleID: "1", sortableWebFeedID: "2")
|
||||
let article3 = TestArticle(sortableName: "Jenny's Feed", sortableDate: Date(timeInterval: -10.0, since: now), sortableArticleID: "2", sortableWebFeedID: "2")
|
||||
let article4 = TestArticle(sortableName: "Gordy's Blog", sortableDate: Date(timeInterval: -1000.0, since: now), sortableArticleID: "1", sortableWebFeedID: "3")
|
||||
let article5 = TestArticle(sortableName: "Gordy's Blog", sortableDate: Date(timeInterval: -10.0, since: now), sortableArticleID: "2", sortableWebFeedID: "3")
|
||||
let article6 = TestArticle(sortableName: "Jenny's Feed", sortableDate: Date(timeInterval: 10.0, since: now), sortableArticleID: "3", sortableWebFeedID: "2")
|
||||
let article7 = TestArticle(sortableName: "Phil's Feed", sortableDate: now, sortableArticleID: "2", sortableWebFeedID: "1")
|
||||
let article8 = TestArticle(sortableName: "Zippy's Feed", sortableDate: now, sortableArticleID: "1", sortableWebFeedID: "0")
|
||||
let article9 = TestArticle(sortableName: "Zippy's Feed", sortableDate: now, sortableArticleID: "2", sortableWebFeedID: "0")
|
||||
let article1 = TestArticle(sortableName: "Phil's Feed", sortableDate: Date(timeInterval: -100.0, since: now), sortableArticleID: "1", sortableFeedID: "1")
|
||||
let article2 = TestArticle(sortableName: "Jenny's Feed", sortableDate: now, sortableArticleID: "1", sortableFeedID: "2")
|
||||
let article3 = TestArticle(sortableName: "Jenny's Feed", sortableDate: Date(timeInterval: -10.0, since: now), sortableArticleID: "2", sortableFeedID: "2")
|
||||
let article4 = TestArticle(sortableName: "Gordy's Blog", sortableDate: Date(timeInterval: -1000.0, since: now), sortableArticleID: "1", sortableFeedID: "3")
|
||||
let article5 = TestArticle(sortableName: "Gordy's Blog", sortableDate: Date(timeInterval: -10.0, since: now), sortableArticleID: "2", sortableFeedID: "3")
|
||||
let article6 = TestArticle(sortableName: "Jenny's Feed", sortableDate: Date(timeInterval: 10.0, since: now), sortableArticleID: "3", sortableFeedID: "2")
|
||||
let article7 = TestArticle(sortableName: "Phil's Feed", sortableDate: now, sortableArticleID: "2", sortableFeedID: "1")
|
||||
let article8 = TestArticle(sortableName: "Zippy's Feed", sortableDate: now, sortableArticleID: "1", sortableFeedID: "0")
|
||||
let article9 = TestArticle(sortableName: "Zippy's Feed", sortableDate: now, sortableArticleID: "2", sortableFeedID: "0")
|
||||
|
||||
let articles = [article1, article2, article3, article4, article5, article6, article7, article8, article9]
|
||||
let sortedArticles = ArticleSorter.sortedByDate(articles: articles, sortDirection: .orderedAscending, groupByFeed: true)
|
||||
@@ -97,10 +97,10 @@ class ArticleSorterTests: XCTestCase {
|
||||
func testSortByDateDescending() {
|
||||
let now = Date()
|
||||
|
||||
let article1 = TestArticle(sortableName: "Susie's Feed", sortableDate: now.addingTimeInterval(-60.0), sortableArticleID: "1", sortableWebFeedID: "4")
|
||||
let article2 = TestArticle(sortableName: "Phil's Feed", sortableDate: now.addingTimeInterval(60.0), sortableArticleID: "2", sortableWebFeedID: "6")
|
||||
let article3 = TestArticle(sortableName: "Phil's Feed", sortableDate: now.addingTimeInterval(120.0), sortableArticleID: "3", sortableWebFeedID: "6")
|
||||
let article4 = TestArticle(sortableName: "Susie's Feed", sortableDate: now.addingTimeInterval(-120.0), sortableArticleID: "4", sortableWebFeedID: "5")
|
||||
let article1 = TestArticle(sortableName: "Susie's Feed", sortableDate: now.addingTimeInterval(-60.0), sortableArticleID: "1", sortableFeedID: "4")
|
||||
let article2 = TestArticle(sortableName: "Phil's Feed", sortableDate: now.addingTimeInterval(60.0), sortableArticleID: "2", sortableFeedID: "6")
|
||||
let article3 = TestArticle(sortableName: "Phil's Feed", sortableDate: now.addingTimeInterval(120.0), sortableArticleID: "3", sortableFeedID: "6")
|
||||
let article4 = TestArticle(sortableName: "Susie's Feed", sortableDate: now.addingTimeInterval(-120.0), sortableArticleID: "4", sortableFeedID: "5")
|
||||
|
||||
let articles = [article1, article2, article3, article4]
|
||||
let sortedArticles = ArticleSorter.sortedByDate(articles: articles,
|
||||
@@ -118,11 +118,11 @@ class ArticleSorterTests: XCTestCase {
|
||||
let now = Date()
|
||||
|
||||
// Articles with the same date should end up being sorted by their article ID
|
||||
let article1 = TestArticle(sortableName: "Phil's Feed", sortableDate: now, sortableArticleID: "1", sortableWebFeedID: "1")
|
||||
let article2 = TestArticle(sortableName: "Matt's Feed", sortableDate: now, sortableArticleID: "2", sortableWebFeedID: "2")
|
||||
let article3 = TestArticle(sortableName: "Sally's Feed", sortableDate: now, sortableArticleID: "3", sortableWebFeedID: "3")
|
||||
let article4 = TestArticle(sortableName: "Susie's Feed", sortableDate: Date(timeInterval: -60.0, since: now), sortableArticleID: "4", sortableWebFeedID: "4")
|
||||
let article5 = TestArticle(sortableName: "Paul's Feed", sortableDate: Date(timeInterval: -120.0, since: now), sortableArticleID: "5", sortableWebFeedID: "5")
|
||||
let article1 = TestArticle(sortableName: "Phil's Feed", sortableDate: now, sortableArticleID: "1", sortableFeedID: "1")
|
||||
let article2 = TestArticle(sortableName: "Matt's Feed", sortableDate: now, sortableArticleID: "2", sortableFeedID: "2")
|
||||
let article3 = TestArticle(sortableName: "Sally's Feed", sortableDate: now, sortableArticleID: "3", sortableFeedID: "3")
|
||||
let article4 = TestArticle(sortableName: "Susie's Feed", sortableDate: Date(timeInterval: -60.0, since: now), sortableArticleID: "4", sortableFeedID: "4")
|
||||
let article5 = TestArticle(sortableName: "Paul's Feed", sortableDate: Date(timeInterval: -120.0, since: now), sortableArticleID: "5", sortableFeedID: "5")
|
||||
|
||||
let articles = [article1, article2, article3, article4, article5]
|
||||
let sortedArticles = ArticleSorter.sortedByDate(articles: articles,
|
||||
@@ -140,15 +140,15 @@ class ArticleSorterTests: XCTestCase {
|
||||
func testSortByDateDescendingWithGroupByFeed() {
|
||||
let now = Date()
|
||||
|
||||
let article1 = TestArticle(sortableName: "Phil's Feed", sortableDate: Date(timeInterval: -100.0, since: now), sortableArticleID: "1", sortableWebFeedID: "1")
|
||||
let article2 = TestArticle(sortableName: "Jenny's Feed", sortableDate: now, sortableArticleID: "1", sortableWebFeedID: "2")
|
||||
let article3 = TestArticle(sortableName: "Jenny's Feed", sortableDate: Date(timeInterval: -10.0, since: now), sortableArticleID: "2", sortableWebFeedID: "2")
|
||||
let article4 = TestArticle(sortableName: "Gordy's Blog", sortableDate: Date(timeInterval: -1000.0, since: now), sortableArticleID: "1", sortableWebFeedID: "3")
|
||||
let article5 = TestArticle(sortableName: "Gordy's Blog", sortableDate: Date(timeInterval: -10.0, since: now), sortableArticleID: "2", sortableWebFeedID: "3")
|
||||
let article6 = TestArticle(sortableName: "Jenny's Feed", sortableDate: Date(timeInterval: 10.0, since: now), sortableArticleID: "3", sortableWebFeedID: "2")
|
||||
let article7 = TestArticle(sortableName: "Phil's Feed", sortableDate: now, sortableArticleID: "2", sortableWebFeedID: "1")
|
||||
let article8 = TestArticle(sortableName: "Zippy's Feed", sortableDate: now, sortableArticleID: "1", sortableWebFeedID: "0")
|
||||
let article9 = TestArticle(sortableName: "Zippy's Feed", sortableDate: now, sortableArticleID: "2", sortableWebFeedID: "0")
|
||||
let article1 = TestArticle(sortableName: "Phil's Feed", sortableDate: Date(timeInterval: -100.0, since: now), sortableArticleID: "1", sortableFeedID: "1")
|
||||
let article2 = TestArticle(sortableName: "Jenny's Feed", sortableDate: now, sortableArticleID: "1", sortableFeedID: "2")
|
||||
let article3 = TestArticle(sortableName: "Jenny's Feed", sortableDate: Date(timeInterval: -10.0, since: now), sortableArticleID: "2", sortableFeedID: "2")
|
||||
let article4 = TestArticle(sortableName: "Gordy's Blog", sortableDate: Date(timeInterval: -1000.0, since: now), sortableArticleID: "1", sortableFeedID: "3")
|
||||
let article5 = TestArticle(sortableName: "Gordy's Blog", sortableDate: Date(timeInterval: -10.0, since: now), sortableArticleID: "2", sortableFeedID: "3")
|
||||
let article6 = TestArticle(sortableName: "Jenny's Feed", sortableDate: Date(timeInterval: 10.0, since: now), sortableArticleID: "3", sortableFeedID: "2")
|
||||
let article7 = TestArticle(sortableName: "Phil's Feed", sortableDate: now, sortableArticleID: "2", sortableFeedID: "1")
|
||||
let article8 = TestArticle(sortableName: "Zippy's Feed", sortableDate: now, sortableArticleID: "1", sortableFeedID: "0")
|
||||
let article9 = TestArticle(sortableName: "Zippy's Feed", sortableDate: now, sortableArticleID: "2", sortableFeedID: "0")
|
||||
|
||||
let articles = [article1, article2, article3, article4, article5, article6, article7, article8, article9]
|
||||
let sortedArticles = ArticleSorter.sortedByDate(articles: articles, sortDirection: .orderedDescending, groupByFeed: true)
|
||||
@@ -175,11 +175,11 @@ class ArticleSorterTests: XCTestCase {
|
||||
func testGroupByFeedWithCaseInsensitiveFeedNames() {
|
||||
let now = Date()
|
||||
|
||||
let article1 = TestArticle(sortableName: "phil's feed", sortableDate: now, sortableArticleID: "1", sortableWebFeedID: "1")
|
||||
let article2 = TestArticle(sortableName: "PhIl's FEed", sortableDate: now, sortableArticleID: "2", sortableWebFeedID: "1")
|
||||
let article3 = TestArticle(sortableName: "APPLE's feed", sortableDate: now, sortableArticleID: "3", sortableWebFeedID: "2")
|
||||
let article4 = TestArticle(sortableName: "PHIL'S FEED", sortableDate: now, sortableArticleID: "4", sortableWebFeedID: "1")
|
||||
let article5 = TestArticle(sortableName: "apple's feed", sortableDate: now, sortableArticleID: "5", sortableWebFeedID: "2")
|
||||
let article1 = TestArticle(sortableName: "phil's feed", sortableDate: now, sortableArticleID: "1", sortableFeedID: "1")
|
||||
let article2 = TestArticle(sortableName: "PhIl's FEed", sortableDate: now, sortableArticleID: "2", sortableFeedID: "1")
|
||||
let article3 = TestArticle(sortableName: "APPLE's feed", sortableDate: now, sortableArticleID: "3", sortableFeedID: "2")
|
||||
let article4 = TestArticle(sortableName: "PHIL'S FEED", sortableDate: now, sortableArticleID: "4", sortableFeedID: "1")
|
||||
let article5 = TestArticle(sortableName: "apple's feed", sortableDate: now, sortableArticleID: "5", sortableFeedID: "2")
|
||||
|
||||
let articles = [article1, article2, article3, article4, article5]
|
||||
let sortedArticles = ArticleSorter.sortedByDate(articles: articles,
|
||||
@@ -201,11 +201,11 @@ class ArticleSorterTests: XCTestCase {
|
||||
let now = Date()
|
||||
|
||||
// Articles with the same feed name should be sorted by feed ID
|
||||
let article1 = TestArticle(sortableName: "Phil's Feed", sortableDate: now, sortableArticleID: "1", sortableWebFeedID: "2")
|
||||
let article2 = TestArticle(sortableName: "Phil's Feed", sortableDate: now, sortableArticleID: "2", sortableWebFeedID: "2")
|
||||
let article3 = TestArticle(sortableName: "Phil's Feed", sortableDate: now, sortableArticleID: "3", sortableWebFeedID: "1")
|
||||
let article4 = TestArticle(sortableName: "Phil's Feed", sortableDate: now, sortableArticleID: "4", sortableWebFeedID: "2")
|
||||
let article5 = TestArticle(sortableName: "Phil's Feed", sortableDate: now, sortableArticleID: "5", sortableWebFeedID: "1")
|
||||
let article1 = TestArticle(sortableName: "Phil's Feed", sortableDate: now, sortableArticleID: "1", sortableFeedID: "2")
|
||||
let article2 = TestArticle(sortableName: "Phil's Feed", sortableDate: now, sortableArticleID: "2", sortableFeedID: "2")
|
||||
let article3 = TestArticle(sortableName: "Phil's Feed", sortableDate: now, sortableArticleID: "3", sortableFeedID: "1")
|
||||
let article4 = TestArticle(sortableName: "Phil's Feed", sortableDate: now, sortableArticleID: "4", sortableFeedID: "2")
|
||||
let article5 = TestArticle(sortableName: "Phil's Feed", sortableDate: now, sortableArticleID: "5", sortableFeedID: "1")
|
||||
|
||||
let articles = [article1, article2, article3, article4, article5]
|
||||
let sortedArticles = ArticleSorter.sortedByDate(articles: articles,
|
||||
@@ -226,7 +226,7 @@ private struct TestArticle: SortableArticle, Equatable {
|
||||
let sortableName: String
|
||||
let sortableDate: Date
|
||||
let sortableArticleID: String
|
||||
let sortableWebFeedID: String
|
||||
let sortableFeedID: String
|
||||
}
|
||||
|
||||
private extension Array where Element == TestArticle {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
-- this script just tests that no error was generated from the script
|
||||
try
|
||||
tell application "NetNewsWire"
|
||||
exists webFeed 1 of account 1
|
||||
exists feed 1 of account 1
|
||||
end tell
|
||||
on error message
|
||||
return {test_result:false, script_result:message}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
-- this script just tests that no error was generated from the script
|
||||
try
|
||||
tell application "NetNewsWire"
|
||||
opml representation of webFeed 1 of account 1
|
||||
opml representation of feed 1 of account 1
|
||||
end tell
|
||||
on error message
|
||||
return {test_result:false, script_result:message}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
-- this script just tests that no error was generated from the script
|
||||
try
|
||||
tell application "NetNewsWire"
|
||||
{name, url} of every webFeed of every account
|
||||
{name, url} of every feed of every account
|
||||
end tell
|
||||
on error message
|
||||
return {test_result:false, script_result:message}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
-- and that the returned list is greater than 0
|
||||
try
|
||||
tell application "NetNewsWire"
|
||||
set namesResult to name of every author of every webFeed of every account
|
||||
set namesResult to name of every author of every feed of every account
|
||||
end tell
|
||||
set test_result to ((count items of namesResult) > 0)
|
||||
on error message
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
-- this script just tests that no error was generated from the script
|
||||
try
|
||||
tell application "NetNewsWire"
|
||||
title of every article of webFeed "Six Colors" where read is true
|
||||
title of every article of feed "Six Colors" where read is true
|
||||
end tell
|
||||
on error message
|
||||
return {test_result:false, script_result:message}
|
||||
|
||||
@@ -42,7 +42,7 @@ class SharingTests: XCTestCase {
|
||||
let articleId = randomId()
|
||||
return Article(accountID: randomId(),
|
||||
articleID: articleId,
|
||||
webFeedID: randomId(),
|
||||
feedID: randomId(),
|
||||
uniqueID: randomId(),
|
||||
title: title,
|
||||
contentHTML: nil,
|
||||
|
||||
+32
-33
@@ -1,17 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="17701" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="22505" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<device id="retina5_9" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17703"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22504"/>
|
||||
<capability name="Named colors" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Add Web Feed-->
|
||||
<!--Add Feed-->
|
||||
<scene sceneID="2Tc-JN-edX">
|
||||
<objects>
|
||||
<tableViewController storyboardIdentifier="AddWebFeedViewController" id="7aE-6a-iP7" customClass="AddFeedViewController" customModule="NetNewsWire" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<tableViewController storyboardIdentifier="AddFeedViewController" id="7aE-6a-iP7" userLabel="Add Feed" customClass="AddFeedViewController" customModule="NetNewsWire" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" scrollEnabled="NO" dataMode="static" style="insetGrouped" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="18" sectionFooterHeight="18" id="D0S-TM-mtm">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="812"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
@@ -19,14 +18,14 @@
|
||||
<tableViewSection id="3tl-Mb-Eno">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" reuseIdentifier="Cell" rowHeight="44" id="lyJ-rf-8GA">
|
||||
<rect key="frame" x="16" y="18" width="343" height="44"/>
|
||||
<rect key="frame" x="20" y="18" width="335" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="lyJ-rf-8GA" id="eNS-Rp-w0A">
|
||||
<rect key="frame" x="0.0" y="0.0" width="343" height="44"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="335" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" placeholder="URL" textAlignment="natural" adjustsFontForContentSizeCategory="YES" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="eRp-AP-WFq">
|
||||
<rect key="frame" x="20" y="4" width="323" height="36"/>
|
||||
<rect key="frame" x="20" y="4" width="315" height="36"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<textInputTraits key="textInputTraits" keyboardType="URL"/>
|
||||
</textField>
|
||||
@@ -40,14 +39,14 @@
|
||||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" rowHeight="44" id="Pxz-fv-QhQ">
|
||||
<rect key="frame" x="16" y="62" width="343" height="44"/>
|
||||
<rect key="frame" x="20" y="62" width="335" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="Pxz-fv-QhQ" id="8aP-2A-8jc">
|
||||
<rect key="frame" x="0.0" y="0.0" width="343" height="44"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="335" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" placeholder="Title (Optional)" textAlignment="natural" adjustsFontForContentSizeCategory="YES" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="u7n-VL-Ho9">
|
||||
<rect key="frame" x="20" y="4" width="303" height="36"/>
|
||||
<rect key="frame" x="20" y="4" width="295" height="36"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<textInputTraits key="textInputTraits" autocapitalizationType="words"/>
|
||||
</textField>
|
||||
@@ -61,10 +60,10 @@
|
||||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="Cell" rowHeight="44" id="rlc-34-flT">
|
||||
<rect key="frame" x="16" y="106" width="343" height="44"/>
|
||||
<rect key="frame" x="20" y="106" width="335" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="rlc-34-flT" id="ZbC-Z6-dtq">
|
||||
<rect key="frame" x="0.0" y="0.0" width="343" height="44"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="335" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Folder" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="RtT-rR-5LA">
|
||||
@@ -74,7 +73,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="htg-Nn-3xi">
|
||||
<rect key="frame" x="281" y="11.666666666666664" width="42" height="21"/>
|
||||
<rect key="frame" x="273" y="11.666666666666664" width="42" height="21"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
@@ -112,7 +111,7 @@
|
||||
</barButtonItem>
|
||||
<barButtonItem id="qP4-8x-XbO">
|
||||
<view key="customView" contentMode="scaleToFill" id="9ct-kH-nAp">
|
||||
<rect key="frame" x="300.33333333333331" y="12" width="20" height="20"/>
|
||||
<rect key="frame" x="299" y="12" width="20" height="20"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<subviews>
|
||||
<activityIndicatorView hidden="YES" opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" fixedFrame="YES" style="gray" translatesAutoresizingMaskIntoConstraints="NO" id="3ph-Td-s1Z">
|
||||
@@ -142,9 +141,9 @@
|
||||
<!--Modal Navigation Controller-->
|
||||
<scene sceneID="hbe-Yu-anW">
|
||||
<objects>
|
||||
<navigationController storyboardIdentifier="AddWebFeedFolderNavViewController" id="WDg-F7-gfk" customClass="ModalNavigationController" customModule="NetNewsWire" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<navigationController storyboardIdentifier="AddFeedFolderNavViewController" id="WDg-F7-gfk" customClass="ModalNavigationController" customModule="NetNewsWire" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="akM-T1-shZ">
|
||||
<rect key="frame" x="0.0" y="44" width="375" height="44"/>
|
||||
<rect key="frame" x="0.0" y="50" width="375" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</navigationBar>
|
||||
<connections>
|
||||
@@ -155,16 +154,16 @@
|
||||
</objects>
|
||||
<point key="canvasLocation" x="-1711" y="756"/>
|
||||
</scene>
|
||||
<!--Web Feed Folder-->
|
||||
<!--Feed Folder-->
|
||||
<scene sceneID="wnD-aY-W32">
|
||||
<objects>
|
||||
<tableViewController storyboardIdentifier="AddWebFeedFolderViewController" title="Web Feed Folder" id="acA-n7-ohN" customClass="AddFeedFolderViewController" customModule="NetNewsWire" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<tableViewController storyboardIdentifier="AddFeedFolderViewController" title="Web Feed Folder" id="acA-n7-ohN" userLabel="Feed Folder" customClass="AddFeedFolderViewController" customModule="NetNewsWire" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" id="new-nW-ba2">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="812"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<prototypes>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="AccountCell" id="bp5-2u-4S4" customClass="AddComboTableViewCell" customModule="NetNewsWire" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="28" width="375" height="43.666667938232422"/>
|
||||
<rect key="frame" x="0.0" y="50" width="375" height="43.666667938232422"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="bp5-2u-4S4" id="9HE-eR-YIp">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="43.666667938232422"/>
|
||||
@@ -199,7 +198,7 @@
|
||||
</connections>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="FolderCell" id="S24-c1-0Ir" customClass="AddComboTableViewCell" customModule="NetNewsWire" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="71.666667938232422" width="375" height="43.666667938232422"/>
|
||||
<rect key="frame" x="0.0" y="93.666667938232422" width="375" height="43.666667938232422"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="S24-c1-0Ir" id="bA3-AB-H1n">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="43.666667938232422"/>
|
||||
@@ -254,9 +253,9 @@
|
||||
<!--Navigation Controller-->
|
||||
<scene sceneID="1In-mk-bYI">
|
||||
<objects>
|
||||
<navigationController storyboardIdentifier="AddWebFeedViewControllerNav" id="4Ej-aI-tUP" sceneMemberID="viewController">
|
||||
<navigationController storyboardIdentifier="AddFeedViewControllerNav" id="4Ej-aI-tUP" sceneMemberID="viewController">
|
||||
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="Fot-QF-7Rn">
|
||||
<rect key="frame" x="0.0" y="44" width="375" height="44"/>
|
||||
<rect key="frame" x="0.0" y="50" width="375" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</navigationBar>
|
||||
<connections>
|
||||
@@ -278,14 +277,14 @@
|
||||
<tableViewSection id="12M-tp-EeV">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="44" id="XJI-0M-cAh">
|
||||
<rect key="frame" x="16" y="18" width="343" height="44"/>
|
||||
<rect key="frame" x="20" y="18" width="335" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="XJI-0M-cAh" id="tIS-Tx-i17">
|
||||
<rect key="frame" x="0.0" y="0.0" width="343" height="44"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="335" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" placeholder="Name" textAlignment="natural" adjustsFontForContentSizeCategory="YES" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="lZK-wx-jbo">
|
||||
<rect key="frame" x="20" y="4" width="303" height="36"/>
|
||||
<rect key="frame" x="20" y="4" width="295" height="36"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<textInputTraits key="textInputTraits" autocapitalizationType="words"/>
|
||||
</textField>
|
||||
@@ -303,10 +302,10 @@
|
||||
<tableViewSection id="bX9-Y2-S2D">
|
||||
<cells>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="44" id="uU0-Jh-goT">
|
||||
<rect key="frame" x="16" y="98" width="343" height="44"/>
|
||||
<rect key="frame" x="20" y="98" width="335" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="uU0-Jh-goT" id="y2g-dW-fPZ">
|
||||
<rect key="frame" x="0.0" y="0.0" width="343" height="44"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="335" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Account" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="YRf-I7-nkL">
|
||||
@@ -316,7 +315,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="281" y="12" width="42" height="20"/>
|
||||
<rect key="frame" x="273" y="12" width="42" height="20"/>
|
||||
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
|
||||
<nil key="textColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
@@ -333,14 +332,14 @@
|
||||
</tableViewCellContentView>
|
||||
</tableViewCell>
|
||||
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" rowHeight="140" id="zRi-p6-4KU">
|
||||
<rect key="frame" x="16" y="142" width="343" height="140"/>
|
||||
<rect key="frame" x="20" y="142" width="335" height="140"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="zRi-p6-4KU" id="wek-Qh-OXr">
|
||||
<rect key="frame" x="0.0" y="0.0" width="343" height="140"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="335" height="140"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<pickerView contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="eGY-V8-gzJ">
|
||||
<rect key="frame" x="0.0" y="0.0" width="343" height="140"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="335" height="140"/>
|
||||
</pickerView>
|
||||
</subviews>
|
||||
<constraints>
|
||||
@@ -388,7 +387,7 @@
|
||||
<objects>
|
||||
<navigationController storyboardIdentifier="AddFolderViewControllerNav" id="edk-IE-nce" sceneMemberID="viewController">
|
||||
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="Pi1-Bc-YXQ">
|
||||
<rect key="frame" x="0.0" y="44" width="375" height="44"/>
|
||||
<rect key="frame" x="0.0" y="50" width="375" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
</navigationBar>
|
||||
<connections>
|
||||
|
||||
@@ -64,7 +64,7 @@ class AddFeedViewController: UITableViewController {
|
||||
nameTextField.text = initialFeedName
|
||||
nameTextField.delegate = self
|
||||
|
||||
if let defaultContainer = AddWebFeedDefaultContainer.defaultContainer {
|
||||
if let defaultContainer = AddFeedDefaultContainer.defaultContainer {
|
||||
container = defaultContainer
|
||||
} else {
|
||||
addButton.isEnabled = false
|
||||
@@ -104,7 +104,7 @@ class AddFeedViewController: UITableViewController {
|
||||
account = containerAccount
|
||||
}
|
||||
|
||||
if account!.hasWebFeed(withURL: url.absoluteString) {
|
||||
if account!.hasFeed(withURL: url.absoluteString) {
|
||||
presentError(AccountError.createErrorAlreadySubscribed)
|
||||
return
|
||||
}
|
||||
@@ -117,14 +117,14 @@ class AddFeedViewController: UITableViewController {
|
||||
|
||||
BatchUpdate.shared.start()
|
||||
|
||||
account!.createWebFeed(url: url.absoluteString, name: feedName, container: container, validateFeed: true) { result in
|
||||
account!.createFeed(url: url.absoluteString, name: feedName, container: container, validateFeed: true) { result in
|
||||
|
||||
BatchUpdate.shared.end()
|
||||
|
||||
switch result {
|
||||
case .success(let feed):
|
||||
self.dismiss(animated: true)
|
||||
NotificationCenter.default.post(name: .UserDidAddFeed, object: self, userInfo: [UserInfoKey.webFeed: feed])
|
||||
NotificationCenter.default.post(name: .UserDidAddFeed, object: self, userInfo: [UserInfoKey.feed: feed])
|
||||
case .failure(let error):
|
||||
self.addButton.isEnabled = true
|
||||
self.activityIndicator.isHidden = true
|
||||
@@ -152,7 +152,7 @@ class AddFeedViewController: UITableViewController {
|
||||
|
||||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
if indexPath.row == 2 {
|
||||
let navController = UIStoryboard.add.instantiateViewController(withIdentifier: "AddWebFeedFolderNavViewController") as! UINavigationController
|
||||
let navController = UIStoryboard.add.instantiateViewController(withIdentifier: "AddFeedFolderNavViewController") as! UINavigationController
|
||||
navController.modalPresentationStyle = .currentContext
|
||||
let folderViewController = navController.topViewController as! AddFeedFolderViewController
|
||||
folderViewController.delegate = self
|
||||
@@ -164,13 +164,13 @@ class AddFeedViewController: UITableViewController {
|
||||
|
||||
}
|
||||
|
||||
// MARK: AddWebFeedFolderViewControllerDelegate
|
||||
// MARK: AddFeedFolderViewControllerDelegate
|
||||
|
||||
extension AddFeedViewController: AddFeedFolderViewControllerDelegate {
|
||||
func didSelect(container: Container) {
|
||||
self.container = container
|
||||
updateFolderLabel()
|
||||
AddWebFeedDefaultContainer.saveDefaultContainer(container)
|
||||
AddFeedDefaultContainer.saveDefaultContainer(container)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user