diff --git a/Frameworks/Account/Account.swift b/Frameworks/Account/Account.swift index 5bbc1f6b2..f8bbaf8a8 100644 --- a/Frameworks/Account/Account.swift +++ b/Frameworks/Account/Account.swift @@ -36,6 +36,7 @@ public extension Notification.Name { public enum AccountType: Int, Codable { // Raw values should not change since they’re stored on disk. case onMyMac = 1 + case cloudKit = 2 case feedly = 16 case feedbin = 17 case feedWrangler = 18 @@ -232,6 +233,8 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container, switch type { case .onMyMac: self.delegate = LocalAccountDelegate() + case .cloudKit: + self.delegate = CloudKitAccountDelegate() case .feedbin: self.delegate = FeedbinAccountDelegate(dataFolder: dataFolder, transport: transport) case .freshRSS: @@ -256,6 +259,8 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container, switch type { case .onMyMac: defaultName = Account.defaultLocalAccountName + case .cloudKit: + defaultName = "iCloud" case .feedly: defaultName = "Feedly" case .feedbin: diff --git a/Frameworks/Account/Account.xcodeproj/project.pbxproj b/Frameworks/Account/Account.xcodeproj/project.pbxproj index 62d8daf90..e96803641 100644 --- a/Frameworks/Account/Account.xcodeproj/project.pbxproj +++ b/Frameworks/Account/Account.xcodeproj/project.pbxproj @@ -18,6 +18,7 @@ 3B826DAE2385C81C00FC1ADB /* FeedWranglerSubscriptionsRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B826DA52385C81C00FC1ADB /* FeedWranglerSubscriptionsRequest.swift */; }; 3B826DAF2385C81C00FC1ADB /* FeedWranglerGenericResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B826DA62385C81C00FC1ADB /* FeedWranglerGenericResult.swift */; }; 3BC23AB92385ECB100371CBA /* FeedWranglerSubscriptionResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BC23AB82385ECB100371CBA /* FeedWranglerSubscriptionResult.swift */; }; + 5103A9D92422546800410853 /* CloudKitAppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5103A9D82422546800410853 /* CloudKitAppDelegate.swift */; }; 5107A09B227DE49500C7C3C5 /* TestAccountManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5107A09A227DE49500C7C3C5 /* TestAccountManager.swift */; }; 5107A09D227DE77700C7C3C5 /* TestTransport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5107A09C227DE77700C7C3C5 /* TestTransport.swift */; }; 510BD111232C3801002692E4 /* AccountMetadataFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510BD110232C3801002692E4 /* AccountMetadataFile.swift */; }; @@ -230,6 +231,7 @@ 3B826DA52385C81C00FC1ADB /* FeedWranglerSubscriptionsRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeedWranglerSubscriptionsRequest.swift; sourceTree = ""; }; 3B826DA62385C81C00FC1ADB /* FeedWranglerGenericResult.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeedWranglerGenericResult.swift; sourceTree = ""; }; 3BC23AB82385ECB100371CBA /* FeedWranglerSubscriptionResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedWranglerSubscriptionResult.swift; sourceTree = ""; }; + 5103A9D82422546800410853 /* CloudKitAppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloudKitAppDelegate.swift; sourceTree = ""; }; 5107A098227DE42E00C7C3C5 /* AccountCredentialsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountCredentialsTest.swift; sourceTree = ""; }; 5107A09A227DE49500C7C3C5 /* TestAccountManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestAccountManager.swift; sourceTree = ""; }; 5107A09C227DE77700C7C3C5 /* TestTransport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestTransport.swift; sourceTree = ""; }; @@ -450,6 +452,14 @@ path = FeedWrangler; sourceTree = ""; }; + 5103A9D7242253DC00410853 /* CloudKit */ = { + isa = PBXGroup; + children = ( + 5103A9D82422546800410853 /* CloudKitAppDelegate.swift */, + ); + path = CloudKit; + sourceTree = ""; + }; 5111D71C2357534700737D45 /* Feedbin */ = { isa = PBXGroup; children = ( @@ -606,6 +616,7 @@ 3B826D9D2385C81C00FC1ADB /* FeedWrangler */, 552032EA229D5D5A009559E0 /* ReaderAPI */, 9EA31339231E368100268BA0 /* Feedly */, + 5103A9D7242253DC00410853 /* CloudKit */, 848935031F62484F00CEBD24 /* AccountTests */, 848934F71F62484F00CEBD24 /* Products */, 8469F80F1F6DC3C10084783E /* Frameworks */, @@ -844,11 +855,11 @@ 848934F51F62484F00CEBD24 = { CreatedOnToolsVersion = 9.0; LastSwiftMigration = 0900; - ProvisioningStyle = Manual; + ProvisioningStyle = Automatic; }; 848934FE1F62484F00CEBD24 = { CreatedOnToolsVersion = 9.0; - ProvisioningStyle = Manual; + ProvisioningStyle = Automatic; }; }; }; @@ -1008,6 +1019,7 @@ 84F73CF1202788D90000BCEF /* ArticleFetcher.swift in Sources */, 841974251F6DDCE4006346C4 /* AccountDelegate.swift in Sources */, 510BD113232C3E9D002692E4 /* WebFeedMetadataFile.swift in Sources */, + 5103A9D92422546800410853 /* CloudKitAppDelegate.swift in Sources */, 5165D73122837F3400D9D53D /* InitialFeedDownloader.swift in Sources */, 9E784EBE237E890600099B1B /* FeedlyLogoutOperation.swift in Sources */, 9EEEF71F23545CB4009E9D80 /* FeedlySendArticleStatusesOperation.swift in Sources */, diff --git a/Frameworks/Account/AccountManager.swift b/Frameworks/Account/AccountManager.swift index fd357b8cc..321e8fb5f 100644 --- a/Frameworks/Account/AccountManager.swift +++ b/Frameworks/Account/AccountManager.swift @@ -120,7 +120,7 @@ public final class AccountManager: UnreadCountProvider { // MARK: - API public func createAccount(type: AccountType) -> Account { - let accountID = UUID().uuidString + let accountID = type == .cloudKit ? "iCloud" : UUID().uuidString let accountFolder = (accountsFolder as NSString).appendingPathComponent("\(type.rawValue)_\(accountID)") do { diff --git a/Frameworks/Account/CloudKit/CloudKitAppDelegate.swift b/Frameworks/Account/CloudKit/CloudKitAppDelegate.swift new file mode 100644 index 000000000..44e921850 --- /dev/null +++ b/Frameworks/Account/CloudKit/CloudKitAppDelegate.swift @@ -0,0 +1,216 @@ +// +// CloudKitAppDelegate.swift +// Account +// +// Created by Maurice Parker on 3/18/20. +// Copyright © 2020 Ranchero Software, LLC. All rights reserved. +// + +import Foundation +import RSCore +import RSParser +import Articles +import RSWeb + +public enum CloudKitAccountDelegateError: String, Error { + case invalidParameter = "An invalid parameter was used." +} + +final class CloudKitAccountDelegate: AccountDelegate { + + let behaviors: AccountBehaviors = [] + let isOPMLImportInProgress = false + + let server: String? = nil + var credentials: Credentials? + var accountMetadata: AccountMetadata? + + private let refresher = LocalAccountRefresher() + + var refreshProgress: DownloadProgress { + return refresher.progress + } + + func refreshAll(for account: Account, completion: @escaping (Result) -> Void) { + refresher.refreshFeeds(account.flattenedWebFeeds()) { + account.metadata.lastArticleFetchEndTime = Date() + completion(.success(())) + } + } + + func sendArticleStatus(for account: Account, completion: @escaping ((Result) -> Void)) { + completion(.success(())) + } + + func refreshArticleStatus(for account: Account, completion: @escaping ((Result) -> Void)) { + completion(.success(())) + } + + func importOPML(for account:Account, opmlFile: URL, completion: @escaping (Result) -> Void) { + var fileData: Data? + + do { + fileData = try Data(contentsOf: opmlFile) + } catch { + completion(.failure(error)) + return + } + + guard let opmlData = fileData else { + completion(.success(())) + return + } + + let parserData = ParserData(url: opmlFile.absoluteString, data: opmlData) + var opmlDocument: RSOPMLDocument? + + do { + opmlDocument = try RSOPMLParser.parseOPML(with: parserData) + } catch { + completion(.failure(error)) + return + } + + guard let loadDocument = opmlDocument else { + completion(.success(())) + return + } + + guard let children = loadDocument.children else { + return + } + + BatchUpdate.shared.perform { + account.loadOPMLItems(children, parentFolder: nil) + } + + completion(.success(())) + + } + + func createWebFeed(for account: Account, url urlString: String, name: String?, container: Container, completion: @escaping (Result) -> Void) { + guard let url = URL(string: urlString) else { + completion(.failure(LocalAccountDelegateError.invalidParameter)) + return + } + + refreshProgress.addToNumberOfTasksAndRemaining(1) + FeedFinder.find(url: url) { result in + + switch result { + case .success(let feedSpecifiers): + guard let bestFeedSpecifier = FeedSpecifier.bestFeed(in: feedSpecifiers), + let url = URL(string: bestFeedSpecifier.urlString) else { + self.refreshProgress.completeTask() + completion(.failure(AccountError.createErrorNotFound)) + return + } + + if account.hasWebFeed(withURL: bestFeedSpecifier.urlString) { + self.refreshProgress.completeTask() + completion(.failure(AccountError.createErrorAlreadySubscribed)) + return + } + + let feed = account.createWebFeed(with: nil, url: url.absoluteString, webFeedID: url.absoluteString, homePageURL: nil) + + InitialFeedDownloader.download(url) { parsedFeed in + self.refreshProgress.completeTask() + + if let parsedFeed = parsedFeed { + account.update(feed, with: parsedFeed, {_ in}) + } + + feed.editedName = name + + container.addWebFeed(feed) + completion(.success(feed)) + + } + + case .failure: + self.refreshProgress.completeTask() + completion(.failure(AccountError.createErrorNotFound)) + } + + } + + } + + func renameWebFeed(for account: Account, with feed: WebFeed, to name: String, completion: @escaping (Result) -> Void) { + feed.editedName = name + completion(.success(())) + } + + func removeWebFeed(for account: Account, with feed: WebFeed, from container: Container, completion: @escaping (Result) -> Void) { + container.removeWebFeed(feed) + completion(.success(())) + } + + func moveWebFeed(for account: Account, with feed: WebFeed, from: Container, to: Container, completion: @escaping (Result) -> Void) { + from.removeWebFeed(feed) + to.addWebFeed(feed) + completion(.success(())) + } + + func addWebFeed(for account: Account, with feed: WebFeed, to container: Container, completion: @escaping (Result) -> Void) { + container.addWebFeed(feed) + completion(.success(())) + } + + func restoreWebFeed(for account: Account, feed: WebFeed, container: Container, completion: @escaping (Result) -> Void) { + container.addWebFeed(feed) + completion(.success(())) + } + + func addFolder(for account: Account, name: String, completion: @escaping (Result) -> Void) { + if let folder = account.ensureFolder(with: name) { + completion(.success(folder)) + } else { + completion(.failure(FeedbinAccountDelegateError.invalidParameter)) + } + } + + func renameFolder(for account: Account, with folder: Folder, to name: String, completion: @escaping (Result) -> Void) { + folder.name = name + completion(.success(())) + } + + func removeFolder(for account: Account, with folder: Folder, completion: @escaping (Result) -> Void) { + account.removeFolder(folder) + completion(.success(())) + } + + func restoreFolder(for account: Account, folder: Folder, completion: @escaping (Result) -> Void) { + account.addFolder(folder) + completion(.success(())) + } + + func markArticles(for account: Account, articles: Set
, statusKey: ArticleStatus.Key, flag: Bool) -> Set
? { + return try? account.update(articles, statusKey: statusKey, flag: flag) + } + + func accountDidInitialize(_ account: Account) { + } + + func accountWillBeDeleted(_ account: Account) { + } + + static func validateCredentials(transport: Transport, credentials: Credentials, endpoint: URL? = nil, completion: (Result) -> Void) { + return completion(.success(nil)) + } + + // MARK: Suspend and Resume (for iOS) + + func suspendNetwork() { + refresher.suspend() + } + + func suspendDatabase() { + // Nothing to do + } + + func resume() { + refresher.resume() + } +} diff --git a/Mac/AppAssets.swift b/Mac/AppAssets.swift index 57f53b07c..038dcfa39 100644 --- a/Mac/AppAssets.swift +++ b/Mac/AppAssets.swift @@ -21,6 +21,10 @@ struct AppAssets { return RSImage(named: .timelineStar) }() + static var accountCloudKit: RSImage! = { + return RSImage(named: "accountCloudKit") + }() + static var accountLocal: RSImage! = { return RSImage(named: "accountLocal") }() @@ -129,6 +133,8 @@ struct AppAssets { switch accountType { case .onMyMac: return AppAssets.accountLocal + case .cloudKit: + return AppAssets.accountCloudKit case .feedbin: return AppAssets.accountFeedbin case .feedly: diff --git a/Mac/Preferences/Accounts/AccountsAddCloudKitWindowController.swift b/Mac/Preferences/Accounts/AccountsAddCloudKitWindowController.swift new file mode 100644 index 000000000..5bd0e8916 --- /dev/null +++ b/Mac/Preferences/Accounts/AccountsAddCloudKitWindowController.swift @@ -0,0 +1,42 @@ +// +// AccountsAddCloudKitWindowController.swift +// NetNewsWire +// +// Created by Maurice Parker on 3/18/20. +// Copyright © 2020 Ranchero Software. All rights reserved. +// + +import Foundation +import Account + +class AccountsAddCloudKitWindowController: NSWindowController { + + private weak var hostWindow: NSWindow? + + convenience init() { + self.init(windowNibName: NSNib.Name("AccountsAddCloudKit")) + } + + override func windowDidLoad() { + super.windowDidLoad() + } + + // MARK: API + + func runSheetOnWindow(_ hostWindow: NSWindow, completion: ((NSApplication.ModalResponse) -> Void)? = nil) { + self.hostWindow = hostWindow + hostWindow.beginSheet(window!, completionHandler: completion) + } + + // MARK: Actions + + @IBAction func cancel(_ sender: Any) { + hostWindow!.endSheet(window!, returnCode: NSApplication.ModalResponse.cancel) + } + + @IBAction func create(_ sender: Any) { + _ = AccountManager.shared.createAccount(type: .cloudKit) + hostWindow!.endSheet(window!, returnCode: NSApplication.ModalResponse.OK) + } + +} diff --git a/Mac/Preferences/Accounts/AccountsAddViewController.swift b/Mac/Preferences/Accounts/AccountsAddViewController.swift index d1a8a76d4..5ef19c9fe 100644 --- a/Mac/Preferences/Accounts/AccountsAddViewController.swift +++ b/Mac/Preferences/Accounts/AccountsAddViewController.swift @@ -16,7 +16,11 @@ class AccountsAddViewController: NSViewController { private var accountsAddWindowController: NSWindowController? - private let addableAccountTypes: [AccountType] = [.onMyMac, .feedbin, .feedly, .feedWrangler, .freshRSS] + #if DEBUG + private var addableAccountTypes: [AccountType] = [.onMyMac, .cloudKit, .feedbin, .feedly, .feedWrangler, .freshRSS] + #else + private var addableAccountTypes: [AccountType] = [.onMyMac, .feedbin, .feedly] + #endif init() { super.init(nibName: "AccountsAdd", bundle: nil) @@ -27,12 +31,10 @@ class AccountsAddViewController: NSViewController { } override func viewDidLoad() { - super.viewDidLoad() - tableView.dataSource = self tableView.delegate = self - + removeCloudKitIfNecessary() } } @@ -63,6 +65,9 @@ extension AccountsAddViewController: NSTableViewDelegate { case .onMyMac: cell.accountNameLabel?.stringValue = Account.defaultLocalAccountName cell.accountImageView?.image = AppAssets.accountLocal + case .cloudKit: + cell.accountNameLabel?.stringValue = NSLocalizedString("iCloud", comment: "iCloud") + cell.accountImageView?.image = AppAssets.accountCloudKit case .feedbin: cell.accountNameLabel?.stringValue = NSLocalizedString("Feedbin", comment: "Feedbin") cell.accountImageView?.image = AppAssets.accountFeedbin @@ -95,6 +100,15 @@ extension AccountsAddViewController: NSTableViewDelegate { let accountsAddLocalWindowController = AccountsAddLocalWindowController() accountsAddLocalWindowController.runSheetOnWindow(self.view.window!) accountsAddWindowController = accountsAddLocalWindowController + case .cloudKit: + let accountsAddCloudKitWindowController = AccountsAddCloudKitWindowController() + accountsAddCloudKitWindowController.runSheetOnWindow(self.view.window!) { response in + if response == NSApplication.ModalResponse.OK { + self.removeCloudKitIfNecessary() + self.tableView.reloadData() + } + } + accountsAddWindowController = accountsAddCloudKitWindowController case .feedbin: let accountsFeedbinWindowController = AccountsFeedbinWindowController() accountsFeedbinWindowController.runSheetOnWindow(self.view.window!) @@ -142,3 +156,13 @@ extension AccountsAddViewController: OAuthAccountAuthorizationOperationDelegate view.window?.presentError(error) } } + +// MARK: Private + +private extension AccountsAddViewController { + func removeCloudKitIfNecessary() { + if let index = AccountManager.shared.activeAccounts.firstIndex(where: { $0.type == .cloudKit }) { + addableAccountTypes.remove(at: index) + } + } +} diff --git a/Mac/Resources/Assets.xcassets/accountCloudKit.imageset/Contents.json b/Mac/Resources/Assets.xcassets/accountCloudKit.imageset/Contents.json new file mode 100644 index 000000000..f0e09dead --- /dev/null +++ b/Mac/Resources/Assets.xcassets/accountCloudKit.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "icloud.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "template-rendering-intent" : "template" + } +} \ No newline at end of file diff --git a/Mac/Resources/Assets.xcassets/accountCloudKit.imageset/icloud.pdf b/Mac/Resources/Assets.xcassets/accountCloudKit.imageset/icloud.pdf new file mode 100644 index 000000000..74406f4cb Binary files /dev/null and b/Mac/Resources/Assets.xcassets/accountCloudKit.imageset/icloud.pdf differ diff --git a/Mac/Scriptability/Account+Scriptability.swift b/Mac/Scriptability/Account+Scriptability.swift index e4ad348ef..84788ad0f 100644 --- a/Mac/Scriptability/Account+Scriptability.swift +++ b/Mac/Scriptability/Account+Scriptability.swift @@ -159,6 +159,8 @@ class ScriptableAccount: NSObject, UniqueIdScriptingObject, ScriptingObjectConta switch self.account.type { case .onMyMac: osType = "Locl" + case .cloudKit: + osType = "Clkt" case .feedly: osType = "Fdly" case .feedbin: diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index a3254266e..e6f57b6e2 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -17,6 +17,10 @@ 5103A9982421643300410853 /* blank.html in Resources */ = {isa = PBXBuildFile; fileRef = 5103A9972421643300410853 /* blank.html */; }; 5103A9992421643300410853 /* blank.html in Resources */ = {isa = PBXBuildFile; fileRef = 5103A9972421643300410853 /* blank.html */; }; 5103A9B424216A4200410853 /* blank.html in Resources */ = {isa = PBXBuildFile; fileRef = 5103A9B324216A4200410853 /* blank.html */; }; + 5103A9F4242258C600410853 /* AccountsAddCloudKit.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5103A9DA242258C600410853 /* AccountsAddCloudKit.xib */; }; + 5103A9F5242258C600410853 /* AccountsAddCloudKit.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5103A9DA242258C600410853 /* AccountsAddCloudKit.xib */; }; + 5103A9F724225E4C00410853 /* AccountsAddCloudKitWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5103A9F624225E4C00410853 /* AccountsAddCloudKitWindowController.swift */; }; + 5103A9F824225E4C00410853 /* AccountsAddCloudKitWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5103A9F624225E4C00410853 /* AccountsAddCloudKitWindowController.swift */; }; 5108F6B62375E612001ABC45 /* CacheCleaner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5108F6B52375E612001ABC45 /* CacheCleaner.swift */; }; 5108F6B72375E612001ABC45 /* CacheCleaner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5108F6B52375E612001ABC45 /* CacheCleaner.swift */; }; 5108F6D22375EED2001ABC45 /* TimelineCustomizerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5108F6D12375EED2001ABC45 /* TimelineCustomizerViewController.swift */; }; @@ -1255,6 +1259,8 @@ 49F40DEF2335B71000552BF4 /* newsfoot.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = newsfoot.js; sourceTree = ""; }; 5103A9972421643300410853 /* blank.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = blank.html; sourceTree = ""; }; 5103A9B324216A4200410853 /* blank.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = blank.html; sourceTree = ""; }; + 5103A9DA242258C600410853 /* AccountsAddCloudKit.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = AccountsAddCloudKit.xib; sourceTree = ""; }; + 5103A9F624225E4C00410853 /* AccountsAddCloudKitWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsAddCloudKitWindowController.swift; sourceTree = ""; }; 5108F6B52375E612001ABC45 /* CacheCleaner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CacheCleaner.swift; sourceTree = ""; }; 5108F6D12375EED2001ABC45 /* TimelineCustomizerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineCustomizerViewController.swift; sourceTree = ""; }; 5108F6D32375EEEF001ABC45 /* TimelinePreviewTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelinePreviewTableViewController.swift; sourceTree = ""; }; @@ -2616,6 +2622,8 @@ 55E15BCA229D65A900D6602A /* AccountsReaderAPIWindowController.swift */, 5144EA352279FC3D00D19003 /* AccountsAddLocal.xib */, 5144EA372279FC6200D19003 /* AccountsAddLocalWindowController.swift */, + 5103A9DA242258C600410853 /* AccountsAddCloudKit.xib */, + 5103A9F624225E4C00410853 /* AccountsAddCloudKitWindowController.swift */, ); path = Accounts; sourceTree = ""; @@ -3468,6 +3476,7 @@ 65ED405B235DEF6C0081F399 /* KeyboardShortcuts.html in Resources */, 65ED405C235DEF6C0081F399 /* ImportOPMLSheet.xib in Resources */, 65ED405D235DEF6C0081F399 /* SidebarKeyboardShortcuts.plist in Resources */, + 5103A9F5242258C600410853 /* AccountsAddCloudKit.xib in Resources */, 65ED405E235DEF6C0081F399 /* DefaultFeeds.opml in Resources */, 65ED405F235DEF6C0081F399 /* Preferences.storyboard in Resources */, 65ED4060235DEF6C0081F399 /* (null) in Resources */, @@ -3556,6 +3565,7 @@ 84C9FC8C22629E8F00D921D6 /* KeyboardShortcuts.html in Resources */, 5144EA3B227A379E00D19003 /* ImportOPMLSheet.xib in Resources */, 844B5B691FEA20DF00C7C76A /* SidebarKeyboardShortcuts.plist in Resources */, + 5103A9F4242258C600410853 /* AccountsAddCloudKit.xib in Resources */, 84A3EE5F223B667F00557320 /* DefaultFeeds.opml in Resources */, 849C78902362AAFC009A71E4 /* ExportOPMLSheet.xib in Resources */, 84C9FC8222629E4800D921D6 /* Preferences.storyboard in Resources */, @@ -3778,6 +3788,7 @@ 65ED3FD6235DEF6C0081F399 /* MarkStatusCommand.swift in Sources */, 65ED3FD7235DEF6C0081F399 /* NSApplication+Scriptability.swift in Sources */, 65ED3FD8235DEF6C0081F399 /* NSView-Extensions.swift in Sources */, + 5103A9F824225E4C00410853 /* AccountsAddCloudKitWindowController.swift in Sources */, 65ED3FD9235DEF6C0081F399 /* SidebarCell.swift in Sources */, 65ED3FDA235DEF6C0081F399 /* ArticleStatusSyncTimer.swift in Sources */, 65ED3FDB235DEF6C0081F399 /* WebFeedTreeControllerDelegate.swift in Sources */, @@ -4129,6 +4140,7 @@ 55E15BCC229D65A900D6602A /* AccountsReaderAPIWindowController.swift in Sources */, 5144EA382279FC6200D19003 /* AccountsAddLocalWindowController.swift in Sources */, 84AD1EAA2031617300BC20B7 /* PasteboardFolder.swift in Sources */, + 5103A9F724225E4C00410853 /* AccountsAddCloudKitWindowController.swift in Sources */, 5144EA51227B8E4500D19003 /* AccountsFeedbinWindowController.swift in Sources */, 84AD1EBC2032AF5C00BC20B7 /* SidebarOutlineDataSource.swift in Sources */, 845A29241FC9255E007B49E3 /* SidebarCellAppearance.swift in Sources */,