From 40ada2ba5a4f1560d943fa94d2cfe9ef7777fdb8 Mon Sep 17 00:00:00 2001 From: Brent Simmons Date: Wed, 22 Jan 2025 22:17:02 -0800 Subject: [PATCH] Fix lint issues. --- Mac/AppAssets.swift | 50 ++--- Mac/AppDefaults.swift | 60 +++--- Mac/AppDelegate.swift | 183 +++++++++-------- Mac/Browser.swift | 6 +- .../CrashReportWindowController.swift | 1 - Mac/CrashReporter/CrashReporter.swift | 4 +- Mac/ErrorHandler.swift | 6 +- ...ltinSmartFeedInspectorViewController.swift | 2 +- .../FeedInspectorViewController.swift | 29 ++- .../FolderInspectorViewController.swift | 16 +- Mac/Inspector/InspectorWindowController.swift | 15 +- .../NothingInspectorViewController.swift | 3 +- .../AddFeed/AddFeedController.swift | 19 +- .../AddFeed/AddFeedWindowController.swift | 4 +- .../AddFeed/AddWebFeedWindowController.swift | 26 +-- Mac/MainWindow/AddFeed/FolderTreeMenu.swift | 26 +-- .../AddFolder/AddFolderWindowController.swift | 28 +-- Mac/MainWindow/ArticleExtractorButton.swift | 26 +-- .../Detail/DetailContainerView.swift | 2 +- .../Detail/DetailStatusBarView.swift | 10 +- .../Detail/DetailViewController.swift | 16 +- Mac/MainWindow/Detail/DetailWebView.swift | 21 +- .../Detail/DetailWebViewController.swift | 48 ++--- Mac/MainWindow/IconView.swift | 23 ++- .../Keyboard/MainWIndowKeyboardHandler.swift | 1 - .../LegacyArticleExtractorButton.swift | 28 +-- Mac/MainWindow/MainWindowController.swift | 186 +++++++++--------- Mac/MainWindow/NNW3/NNW3Document.swift | 1 - .../NNW3/NNW3ImportController.swift | 2 +- ...NNW3OpenPanelAccessoryViewController.swift | 2 +- .../OPML/ExportOPMLWindowController.swift | 41 ++-- .../OPML/ImportOPMLWindowController.swift | 43 ++-- Mac/MainWindow/SharingServiceDelegate.swift | 6 +- .../SharingServicePickerDelegate.swift | 8 +- Mac/MainWindow/Sidebar/Cell/SidebarCell.swift | 19 +- .../Sidebar/Cell/SidebarCellAppearance.swift | 3 +- .../Sidebar/Cell/SidebarCellLayout.swift | 16 +- .../Keyboard/SidebarKeyboardDelegate.swift | 3 +- Mac/MainWindow/Sidebar/PasteboardFeed.swift | 11 +- Mac/MainWindow/Sidebar/PasteboardFolder.swift | 25 ++- .../Renaming/RenameWindowController.swift | 2 +- .../Sidebar/SidebarDeleteItemsAlert.swift | 10 +- .../Sidebar/SidebarOutlineDataSource.swift | 73 ++++--- .../Sidebar/SidebarOutlineView.swift | 8 +- .../Sidebar/SidebarStatusBarView.swift | 4 +- ...idebarViewController+ContextualMenus.swift | 21 +- .../Sidebar/SidebarViewController.swift | 136 +++++++------ Mac/MainWindow/Sidebar/UnreadCountView.swift | 19 +- .../Timeline/ArticlePasteboardWriter.swift | 9 +- .../Cell/MultilineTextFieldSizer.swift | 7 +- .../Cell/SingleLineTextFieldSizer.swift | 2 +- .../Cell/TimelineCellAppearance.swift | 6 +- .../Timeline/Cell/TimelineCellData.swift | 12 +- .../Timeline/Cell/TimelineCellLayout.swift | 37 ++-- .../Timeline/Cell/TimelineTableCellView.swift | 33 ++-- .../Timeline/Cell/UnreadIndicatorView.swift | 6 +- .../Timeline/TimelineContainerView.swift | 1 - .../TimelineContainerViewController.swift | 53 +++-- .../Timeline/TimelineTableRowView.swift | 12 +- .../Timeline/TimelineTableView.swift | 12 +- ...melineViewController+ContextualMenus.swift | 32 ++- .../Timeline/TimelineViewController.swift | 150 +++++++------- Mac/Preferences/Accounts/AccountCell.swift | 20 +- .../AccountsAddCloudKitWindowController.swift | 20 +- .../AccountsAddLocalWindowController.swift | 16 +- .../AccountsDetailViewController.swift | 29 ++- .../AccountsFeedbinWindowController.swift | 62 +++--- .../AccountsNewsBlurWindowController.swift | 16 +- .../AccountsPreferencesViewController.swift | 73 ++++--- .../AccountsReaderAPIWindowController.swift | 62 +++--- .../Accounts/AddAccountHelpView.swift | 10 +- .../Accounts/AddAccountsView.swift | 65 +++--- .../AdvancedPreferencesViewController.swift | 5 +- .../GeneralPrefencesViewController.swift | 10 +- .../PreferencesControlsBackgroundView.swift | 2 +- .../PreferencesTableViewBackgroundView.swift | 2 +- .../PreferencesWindowController.swift | 29 ++- .../SafariExtensionHandler.swift | 23 ++- .../SafariExtensionViewController.swift | 2 +- Mac/Scriptability/Account+Scriptability.swift | 76 +++---- .../AppDelegate+Scriptability.swift | 60 +++--- Mac/Scriptability/Article+Scriptability.swift | 62 +++--- Mac/Scriptability/Author+Scriptability.swift | 26 +-- Mac/Scriptability/Folder+Scriptability.swift | 56 +++--- .../MainWindowController+Scriptability.swift | 1 - .../NSApplication+Scriptability.swift | 36 ++-- .../NSScriptCommand+NetNewsWire.swift | 28 +-- Mac/Scriptability/ScriptingObject.swift | 6 +- .../ScriptingObjectContainer.swift | 19 +- Mac/Scriptability/WebFeed+Scriptability.swift | 108 +++++----- Mac/ShareExtension/ShareViewController.swift | 40 ++-- 91 files changed, 1278 insertions(+), 1350 deletions(-) diff --git a/Mac/AppAssets.swift b/Mac/AppAssets.swift index d6387e64a..9a2b2aa74 100644 --- a/Mac/AppAssets.swift +++ b/Mac/AppAssets.swift @@ -23,11 +23,11 @@ struct AppAssets { static var accountFeedbin: RSImage! = { return RSImage(named: "accountFeedbin") }() - + static var accountFeedly: RSImage! = { return RSImage(named: "accountFeedly") }() - + static var accountFreshRSS: RSImage! = { return RSImage(named: "accountFreshRSS") }() @@ -43,7 +43,7 @@ struct AppAssets { static var accountNewsBlur: RSImage! = { return RSImage(named: "accountNewsBlur") }() - + static var accountTheOldReader: RSImage! = { return RSImage(named: "accountTheOldReader") }() @@ -78,11 +78,11 @@ struct AppAssets { static var marsEditIcon: RSImage = { return RSImage(named: "MarsEditIcon")! }() - + static var microblogIcon: RSImage = { return RSImage(named: "MicroblogIcon")! }() - + static var faviconTemplateImage: RSImage = { return RSImage(named: "faviconTemplateImage")! }() @@ -102,39 +102,39 @@ struct AppAssets { static var iconDarkBackgroundColor: NSColor = { return NSColor(named: NSColor.Name("iconDarkBackgroundColor"))! }() - + static var legacyArticleExtractor: RSImage! = { return RSImage(named: "legacyArticleExtractor") }() - + static var legacyArticleExtractorError: RSImage! = { return RSImage(named: "legacyArticleExtractorError") }() - + static var legacyArticleExtractorInactiveDark: RSImage! = { return RSImage(named: "legacyArticleExtractorInactiveDark") }() - + static var legacyArticleExtractorInactiveLight: RSImage! = { return RSImage(named: "legacyArticleExtractorInactiveLight") }() - + static var legacyArticleExtractorProgress1: RSImage! = { return RSImage(named: "legacyArticleExtractorProgress1") }() - + static var legacyArticleExtractorProgress2: RSImage! = { return RSImage(named: "legacyArticleExtractorProgress2") }() - + static var legacyArticleExtractorProgress3: RSImage! = { return RSImage(named: "legacyArticleExtractorProgress3") }() - + static var legacyArticleExtractorProgress4: RSImage! = { return RSImage(named: "legacyArticleExtractorProgress4") }() - + static var folderImage: IconImage { let image = NSImage(systemSymbolName: "folder", accessibilityDescription: nil)! let preferredColor = NSColor(named: "AccentColor")! @@ -157,15 +157,15 @@ struct AppAssets { static var preferencesToolbarAccountsImage: RSImage = { return NSImage(systemSymbolName: "at", accessibilityDescription: nil)! }() - + static var preferencesToolbarExtensionsImage: RSImage = { return RSImage(named: "preferencesToolbarExtensions")! }() - + static var preferencesToolbarGeneralImage: RSImage = { return NSImage(systemSymbolName: "gearshape", accessibilityDescription: nil)! }() - + static var preferencesToolbarAdvancedImage: RSImage = { return NSImage(systemSymbolName: "gearshape.2", accessibilityDescription: nil)! }() @@ -177,15 +177,15 @@ struct AppAssets { static var readOpenImage: RSImage = { return NSImage(systemSymbolName: "circle", accessibilityDescription: nil)! }() - + static var refreshImage: RSImage = { return NSImage(systemSymbolName: "arrow.clockwise", accessibilityDescription: nil)! }() - + static var searchFeedImage: IconImage = { return IconImage(RSImage(named: NSImage.smartBadgeTemplateName)!, isSymbol: true, isBackgroundSuppressed: true) }() - + static var shareImage: RSImage = { return NSImage(systemSymbolName: "square.and.arrow.up", accessibilityDescription: nil)! }() @@ -193,7 +193,7 @@ struct AppAssets { static var sidebarToggleImage: RSImage = { return NSImage(systemSymbolName: "sidebar.left", accessibilityDescription: nil)! }() - + static var starClosedImage: RSImage = { return NSImage(systemSymbolName: "star.fill", accessibilityDescription: nil)! }() @@ -212,7 +212,7 @@ struct AppAssets { static var timelineSeparatorColor: NSColor = { return NSColor(named: "timelineSeparatorColor")! }() - + static var timelineStarSelected: RSImage! = { return RSImage(named: "timelineStar")?.tinted(with: .white) }() @@ -254,11 +254,11 @@ struct AppAssets { return RSImage(systemSymbolName: "star", accessibilityDescription: "Unstar")! .withSymbolConfiguration(.init(scale: .large))! }() - + static var starColor: NSColor = { return NSColor(named: NSColor.Name("StarColor"))! }() - + static func image(for accountType: AccountType) -> NSImage? { switch accountType { case .onMyMac: @@ -281,5 +281,5 @@ struct AppAssets { return AppAssets.accountTheOldReader } } - + } diff --git a/Mac/AppDefaults.swift b/Mac/AppDefaults.swift index 274caf8e1..065304cdb 100644 --- a/Mac/AppDefaults.swift +++ b/Mac/AppDefaults.swift @@ -16,7 +16,7 @@ enum FontSize: Int { } final class AppDefaults { - + static let defaultThemeName = "Default" static let shared = AppDefaults() @@ -49,11 +49,11 @@ final class AppDefaults { static let showTitleOnMainWindow = "KafasisTitleMode" static let feedDoubleClickMarkAsRead = "GruberFeedDoubleClickMarkAsRead" static let suppressSyncOnLaunch = "DevroeSuppressSyncOnLaunch" - + static let webInspectorEnabled = "WebInspectorEnabled" static let webInspectorStartsAttached = "__WebInspectorPageGroupLevel1__.WebKit2InspectorStartsAttached" } - + private static let smallestFontSizeRawValue = FontSize.small.rawValue private static let largestFontSizeRawValue = FontSize.veryLarge.rawValue @@ -63,7 +63,7 @@ final class AppDefaults { } return false }() - + var isFirstRun: Bool = { if let _ = UserDefaults.standard.object(forKey: Key.firstRunDate) as? Date { return false @@ -71,16 +71,16 @@ final class AppDefaults { firstRunDate = Date() return true }() - - var windowState: [AnyHashable : Any]? { + + var windowState: [AnyHashable: Any]? { get { - return UserDefaults.standard.object(forKey: Key.windowState) as? [AnyHashable : Any] + return UserDefaults.standard.object(forKey: Key.windowState) as? [AnyHashable: Any] } set { UserDefaults.standard.set(newValue, forKey: Key.windowState) } } - + var lastImageCacheFlushDate: Date? { get { return AppDefaults.date(for: Key.lastImageCacheFlushDate) @@ -89,7 +89,7 @@ final class AppDefaults { AppDefaults.setDate(for: Key.lastImageCacheFlushDate, newValue) } } - + var openInBrowserInBackground: Bool { get { return AppDefaults.bool(for: Key.openInBrowserInBackground) @@ -105,8 +105,7 @@ final class AppDefaults { if let appGroupID = Bundle.main.object(forInfoDictionaryKey: "AppGroup") as? String, let appGroupDefaults = UserDefaults(suiteName: appGroupID) { return appGroupDefaults - } - else { + } else { return UserDefaults.standard } } @@ -155,7 +154,7 @@ final class AppDefaults { AppDefaults.setString(for: Key.addFeedAccountID, newValue) } } - + var addFeedFolderName: String? { get { return AppDefaults.string(for: Key.addFeedFolderName) @@ -173,7 +172,7 @@ final class AppDefaults { AppDefaults.setString(for: Key.addFolderAccountID, newValue) } } - + var importOPMLAccountID: String? { get { return AppDefaults.string(for: Key.importOPMLAccountID) @@ -182,7 +181,7 @@ final class AppDefaults { AppDefaults.setString(for: Key.importOPMLAccountID, newValue) } } - + var exportOPMLAccountID: String? { get { return AppDefaults.string(for: Key.exportOPMLAccountID) @@ -200,7 +199,7 @@ final class AppDefaults { AppDefaults.setString(for: Key.defaultBrowserID, newValue) } } - + var currentThemeName: String? { get { return AppDefaults.string(for: Key.currentThemeName) @@ -209,7 +208,7 @@ final class AppDefaults { AppDefaults.setString(for: Key.currentThemeName, newValue) } } - + var showTitleOnMainWindow: Bool { return AppDefaults.bool(for: Key.showTitleOnMainWindow) } @@ -235,7 +234,7 @@ final class AppDefaults { AppDefaults.setBool(for: Key.suppressSyncOnLaunch, newValue) } } - + var webInspectorEnabled: Bool { get { return AppDefaults.bool(for: Key.webInspectorEnabled) @@ -244,7 +243,7 @@ final class AppDefaults { AppDefaults.setBool(for: Key.webInspectorEnabled, newValue) } } - + var webInspectorStartsAttached: Bool { get { return AppDefaults.bool(for: Key.webInspectorStartsAttached) @@ -253,7 +252,7 @@ final class AppDefaults { AppDefaults.setBool(for: Key.webInspectorStartsAttached, newValue) } } - + var timelineSortDirection: ComparisonResult { get { return AppDefaults.sortDirection(for: Key.timelineSortDirection) @@ -262,7 +261,7 @@ final class AppDefaults { AppDefaults.setSortDirection(for: Key.timelineSortDirection, newValue) } } - + var timelineGroupByFeed: Bool { get { return AppDefaults.bool(for: Key.timelineGroupByFeed) @@ -271,7 +270,7 @@ final class AppDefaults { AppDefaults.setBool(for: Key.timelineGroupByFeed, newValue) } } - + var timelineShowsSeparators: Bool { return AppDefaults.bool(for: Key.timelineShowsSeparators) } @@ -312,7 +311,7 @@ final class AppDefaults { let showDebugMenu = false #endif - let defaults: [String : Any] = [ + let defaults: [String: Any] = [ Key.sidebarFontSize: FontSize.medium.rawValue, Key.timelineFontSize: FontSize.medium.rawValue, Key.detailFontSize: FontSize.medium.rawValue, @@ -379,19 +378,19 @@ private extension AppDefaults { // } // return FontSize(rawValue: rawFontSize)! } - + static func setFontSize(for key: String, _ fontSize: FontSize) { setInt(for: key, fontSize.rawValue) } - + static func string(for key: String) -> String? { return UserDefaults.standard.string(forKey: key) } - + static func setString(for key: String, _ value: String?) { UserDefaults.standard.set(value, forKey: key) } - + static func bool(for key: String) -> Bool { return UserDefaults.standard.bool(forKey: key) } @@ -403,11 +402,11 @@ private extension AppDefaults { static func int(for key: String) -> Int { return UserDefaults.standard.integer(forKey: key) } - + static func setInt(for key: String, _ x: Int) { UserDefaults.standard.set(x, forKey: key) } - + static func date(for key: String) -> Date? { return UserDefaults.standard.object(forKey: key) as? Date } @@ -416,7 +415,7 @@ private extension AppDefaults { UserDefaults.standard.set(date, forKey: key) } - static func sortDirection(for key:String) -> ComparisonResult { + static func sortDirection(for key: String) -> ComparisonResult { let rawInt = int(for: key) if rawInt == ComparisonResult.orderedAscending.rawValue { return .orderedAscending @@ -427,8 +426,7 @@ private extension AppDefaults { static func setSortDirection(for key: String, _ value: ComparisonResult) { if value == .orderedAscending { setInt(for: key, ComparisonResult.orderedAscending.rawValue) - } - else { + } else { setInt(for: key, ComparisonResult.orderedDescending.rawValue) } } diff --git a/Mac/AppDelegate.swift b/Mac/AppDelegate.swift index b25613f65..a86c6c4a7 100644 --- a/Mac/AppDelegate.swift +++ b/Mac/AppDelegate.swift @@ -22,24 +22,23 @@ import Sparkle var appDelegate: AppDelegate! @NSApplicationMain -class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, UNUserNotificationCenterDelegate, UnreadCountProvider, SPUStandardUserDriverDelegate, SPUUpdaterDelegate -{ +class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, UNUserNotificationCenterDelegate, UnreadCountProvider, SPUStandardUserDriverDelegate, SPUUpdaterDelegate { private struct WindowRestorationIdentifiers { static let mainWindow = "mainWindow" } - + var userNotificationManager: UserNotificationManager! var faviconDownloader: FaviconDownloader! var extensionContainersFile: ExtensionContainersFile! var extensionFeedAddRequestFile: ExtensionFeedAddRequestFile! var appName: String! - + var refreshTimer: AccountRefreshTimer? var syncTimer: ArticleStatusSyncTimer? var lastRefreshInterval = AppDefaults.shared.refreshInterval - + var shuttingDown = false { didSet { if shuttingDown { @@ -52,7 +51,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, } var isShutDownSyncDone = false - + @IBOutlet var debugMenuItem: NSMenuItem! @IBOutlet var sortByOldestArticleOnTopMenuItem: NSMenuItem! @IBOutlet var sortByNewestArticleOnTopMenuItem: NSMenuItem! @@ -81,7 +80,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, } return bestController } - + private var mainWindowControllers = [MainWindowController]() private var preferencesWindowController: NSWindowController? private var addFeedController: AddFeedController? @@ -127,20 +126,19 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, addFeedController = AddFeedController(hostWindow: window) addFeedController?.showAddFeedSheet(urlString, name, account, folder) } - + // MARK: - NSApplicationDelegate - + func applicationWillFinishLaunching(_ notification: Notification) { installAppleEventHandlers() - + CacheCleaner.purgeIfNecessary() // Try to establish a cache in the Caches folder, but if it fails for some reason fall back to a temporary dir let cacheFolder: String if let userCacheFolder = try? FileManager.default.url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: false).path { cacheFolder = userCacheFolder - } - else { + } else { let bundleIdentifier = (Bundle.main.infoDictionary!["CFBundleIdentifier"]! as! String) cacheFolder = (NSTemporaryDirectory() as NSString).appendingPathComponent(bundleIdentifier) } @@ -156,7 +154,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, appName = (Bundle.main.infoDictionary!["CFBundleExecutable"]! as! String) } - + func applicationDidFinishLaunching(_ note: Notification) { // Initialize Sparkle... @@ -166,8 +164,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, do { try self.softwareUpdater.start() - } - catch { + } catch { NSLog("Failed to start software updater with error: \(error)") } @@ -187,12 +184,12 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, updateSortMenuItems() updateGroupByFeedMenuItem() - + if mainWindowController == nil { let mainWindowController = createAndShowMainWindow() mainWindowController.restoreStateFromUserDefaults() } - + if isFirstRun { mainWindowController?.window?.center() } @@ -213,8 +210,8 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, refreshTimer = AccountRefreshTimer() syncTimer = ArticleStatusSyncTimer() - - UNUserNotificationCenter.current().requestAuthorization(options:[.badge]) { (granted, error) in } + + UNUserNotificationCenter.current().requestAuthorization(options: [.badge]) { (_, _) in } UNUserNotificationCenter.current().getNotificationSettings { (settings) in if settings.authorizationStatus == .authorized { @@ -241,7 +238,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, } } #endif - + if !AppDefaults.shared.showDebugMenu { debugMenuItem.menu?.removeItem(debugMenuItem) } @@ -250,7 +247,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, CrashReporter.check(crashReporter: self.crashReporter) } } - + func application(_ application: NSApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([NSUserActivityRestoring]) -> Void) -> Bool { guard let mainWindowController = mainWindowController else { return false @@ -276,32 +273,32 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, func applicationDidBecomeActive(_ notification: Notification) { fireOldTimers() } - + func applicationDidResignActive(_ notification: Notification) { ArticleStringFormatter.emptyCaches() saveState() } - func application(_ application: NSApplication, didReceiveRemoteNotification userInfo: [String : Any]) { + func application(_ application: NSApplication, didReceiveRemoteNotification userInfo: [String: Any]) { AccountManager.shared.receiveRemoteNotification(userInfo: userInfo) } - + func application(_ sender: NSApplication, openFile filename: String) -> Bool { guard filename.hasSuffix(ArticleTheme.nnwThemeSuffix) else { return false } importTheme(filename: filename) return true } - + func applicationWillTerminate(_ notification: Notification) { shuttingDown = true saveState() - + ArticleThemeDownloader.shared.cleanUp() - - AccountManager.shared.sendArticleStatusAll() { + + AccountManager.shared.sendArticleStatusAll { self.isShutDownSyncDone = true } - + let timeout = Date().addingTimeInterval(2) while !isShutDownSyncDone && RunLoop.current.run(mode: .default, before: timeout) && timeout > Date() { } } @@ -318,7 +315,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, return } if key == Feed.FeedSettingKey.homePageURL || key == Feed.FeedSettingKey.faviconURL { - let _ = faviconDownloader.favicon(for: feed) + _ = faviconDownloader.favicon(for: feed) } } @@ -332,19 +329,19 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, @objc func userDefaultsDidChange(_ note: Notification) { updateSortMenuItems() updateGroupByFeedMenuItem() - + if lastRefreshInterval != AppDefaults.shared.refreshInterval { refreshTimer?.update() lastRefreshInterval = AppDefaults.shared.refreshInterval } - + updateDockBadge() } - + @objc func didWakeNotification(_ note: Notification) { fireOldTimers() } - + @objc func importDownloadedTheme(_ note: Notification) { guard let userInfo = note.userInfo, let url = userInfo["url"] as? URL else { @@ -356,7 +353,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, } // MARK: Main Window - + func createMainWindowController() -> MainWindowController { let controller: MainWindowController = windowControllerWithName("UnifiedWindow") as! MainWindowController @@ -376,12 +373,12 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, func createAndShowMainWindow() -> MainWindowController { let controller = createMainWindowController() controller.showWindow(self) - + if let window = controller.window { window.restorationClass = Self.self window.identifier = NSUserInterfaceItemIdentifier(rawValue: WindowRestorationIdentifiers.mainWindow) } - + return controller } @@ -399,7 +396,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, mainWindowControllers.remove(at: index) } } - + // MARK: NSUserInterfaceValidations func validateUserInterfaceItem(_ item: NSValidatedUserInterfaceItem) -> Bool { if shuttingDown { @@ -411,23 +408,23 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, if item.action == #selector(refreshAll(_:)) { return !AccountManager.shared.refreshInProgress && !AccountManager.shared.activeAccounts.isEmpty } - + if item.action == #selector(importOPMLFromFile(_:)) { return AccountManager.shared.activeAccounts.contains(where: { !$0.behaviors.contains(where: { $0 == .disallowOPMLImports }) }) } - + if item.action == #selector(addAppNews(_:)) { return !isDisplayingSheet && !AccountManager.shared.anyAccountHasNetNewsWireNewsSubscription() && !AccountManager.shared.activeAccounts.isEmpty } - + if item.action == #selector(sortByNewestArticleOnTop(_:)) || item.action == #selector(sortByOldestArticleOnTop(_:)) { return mainWindowController?.isOpen ?? false } - + if item.action == #selector(showAddFeedWindow(_:)) || item.action == #selector(showAddFolderWindow(_:)) { return !isDisplayingSheet && !AccountManager.shared.activeAccounts.isEmpty } - + if item.action == #selector(toggleWebInspectorEnabled(_:)) { (item as! NSMenuItem).state = AppDefaults.shared.webInspectorEnabled ? .on : .off } @@ -436,15 +433,15 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, } // MARK: UNUserNotificationCenterDelegate - + func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { completionHandler([.banner, .badge, .sound]) } - + func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { - + let userInfo = response.notification.request.content.userInfo - + switch response.actionIdentifier { case "MARK_AS_READ": handleMarkAsRead(userInfo: userInfo) @@ -455,11 +452,11 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, } completionHandler() } - + // MARK: Add Feed func addFeed(_ urlString: String?, name: String? = nil, account: Account? = nil, folder: Folder? = nil) { createAndShowMainWindowIfNecessary() - + if mainWindowController!.isDisplayingSheet { return } @@ -506,7 +503,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, @IBAction func showKeyboardShortcutsWindow(_ sender: Any?) { if keyboardShortcutsWindowController == nil { - + keyboardShortcutsWindowController = WebViewWindowController(title: NSLocalizedString("Keyboard Shortcuts", comment: "window title")) let htmlFile = Bundle(for: type(of: self)).path(forResource: "KeyboardShortcuts", ofType: "html")! keyboardShortcutsWindowController?.displayContents(of: htmlFile) @@ -517,7 +514,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, let minSize = NSSize(width: 400, height: 400) window.setPointAndSizeAdjustingForScreen(point: point, size: size, minimumSize: minSize) } - + } keyboardShortcutsWindowController!.showWindow(self) @@ -530,8 +527,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, if inspectorWindowController!.isOpen { inspectorWindowController!.window!.performClose(self) - } - else { + } else { inspectorWindowController!.objects = objectsForInspector() inspectorWindowController!.showWindow(self) } @@ -542,11 +538,11 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, if mainWindowController!.isDisplayingSheet { return } - + importOPMLController = ImportOPMLWindowController() importOPMLController?.runSheetOnWindow(mainWindowController!.window!) } - + @IBAction func importNNW3FromFile(_ sender: Any?) { createAndShowMainWindowIfNecessary() if mainWindowController!.isDisplayingSheet { @@ -554,17 +550,17 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, } NNW3ImportController.askUserToImportNNW3Subscriptions(window: mainWindowController!.window!) } - + @IBAction func exportOPML(_ sender: Any?) { createAndShowMainWindowIfNecessary() if mainWindowController!.isDisplayingSheet { return } - + exportOPMLController = ExportOPMLWindowController() exportOPMLController?.runSheetOnWindow(mainWindowController!.window!) } - + @IBAction func addAppNews(_ sender: Any?) { if AccountManager.shared.anyAccountHasNetNewsWireNewsSubscription() { return @@ -576,17 +572,16 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, Browser.open("https://netnewswire.com/", inBackground: false) } - + @IBAction func openReleaseNotes(_ sender: Any?) { Browser.open(URL.releaseNotes.absoluteString, inBackground: false) } - @IBAction func openHowToSupport(_ sender: Any?) { - + Browser.open("https://github.com/brentsimmons/NetNewsWire/blob/main/Technotes/HowToSupportNetNewsWire.markdown", inBackground: false) } - + @IBAction func openRepository(_ sender: Any?) { Browser.open("https://github.com/brentsimmons/NetNewsWire", inBackground: false) @@ -646,8 +641,8 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations, AppDefaults.shared.timelineSortDirection = .orderedDescending } - - @IBAction func groupByFeedToggled(_ sender: NSMenuItem) { + + @IBAction func groupByFeedToggled(_ sender: NSMenuItem) { AppDefaults.shared.timelineGroupByFeed.toggle() } @@ -715,7 +710,7 @@ internal extension AppDelegate { refreshTimer?.fireOldTimer() syncTimer?.fireOldTimer() } - + func objectsForInspector() -> [Any]? { guard let window = NSApplication.shared.mainWindow, let windowController = window.windowController as? MainWindowController else { return nil @@ -733,15 +728,15 @@ internal extension AppDelegate { sortByNewestArticleOnTopMenuItem.state = sortByNewestOnTop ? .on : .off sortByOldestArticleOnTopMenuItem.state = sortByNewestOnTop ? .off : .on } - + func updateGroupByFeedMenuItem() { let groupByFeedEnabled = AppDefaults.shared.timelineGroupByFeed groupArticlesByFeedMenuItem.state = groupByFeedEnabled ? .on : .off } - + func importTheme(filename: String) { guard let window = mainWindowController?.window else { return } - + do { let themeURL = URL(filePath: filename) let theme = try ArticleTheme(url: themeURL, isAppTheme: false) @@ -750,11 +745,11 @@ internal extension AppDelegate { let localizedMessageText = NSLocalizedString("Install theme “%@” by %@?", comment: "Theme message text") alert.messageText = NSString.localizedStringWithFormat(localizedMessageText as NSString, theme.name, theme.creatorName) as String - - var attrs = [NSAttributedString.Key : Any]() + + var attrs = [NSAttributedString.Key: Any]() attrs[.font] = NSFont.systemFont(ofSize: NSFont.smallSystemFontSize) attrs[.foregroundColor] = NSColor.textColor - + let titleParagraphStyle = NSMutableParagraphStyle() titleParagraphStyle.alignment = .center attrs[.paragraphStyle] = titleParagraphStyle @@ -774,10 +769,10 @@ internal extension AppDelegate { textView.drawsBackground = false textView.textStorage?.setAttributedString(websiteText) alert.accessoryView = textView - + alert.addButton(withTitle: NSLocalizedString("Install Theme", comment: "Install Theme")) alert.addButton(withTitle: NSLocalizedString("Cancel", comment: "Cancel Install Theme")) - + func importTheme() { do { try ArticleThemesManager.shared.importTheme(filename: filename) @@ -786,7 +781,7 @@ internal extension AppDelegate { NSApplication.shared.presentError(error) } } - + alert.beginSheetModal(for: window) { result in if result == NSApplication.ModalResponse.alertFirstButtonReturn { @@ -799,7 +794,7 @@ internal extension AppDelegate { alert.addButton(withTitle: NSLocalizedString("Overwrite", comment: "Overwrite")) alert.addButton(withTitle: NSLocalizedString("Cancel", comment: "Cancel Install Theme")) - + alert.beginSheetModal(for: window) { result in if result == NSApplication.ModalResponse.alertFirstButtonReturn { importTheme() @@ -811,25 +806,25 @@ internal extension AppDelegate { } } } catch { - NotificationCenter.default.post(name: .didFailToImportThemeWithError, object: nil, userInfo: ["error" : error, "path": filename]) + NotificationCenter.default.post(name: .didFailToImportThemeWithError, object: nil, userInfo: ["error": error, "path": filename]) } } - + func confirmImportSuccess(themeName: String) { guard let window = mainWindowController?.window else { return } - + let alert = NSAlert() alert.alertStyle = .informational alert.messageText = NSLocalizedString("Theme installed", comment: "Theme installed") - + let localizedInformativeText = NSLocalizedString("The theme “%@” has been installed.", comment: "Theme installed") alert.informativeText = NSString.localizedStringWithFormat(localizedInformativeText as NSString, themeName) as String - + alert.addButton(withTitle: NSLocalizedString("OK", comment: "OK")) alert.beginSheetModal(for: window) } - + @objc func themeImportError(_ note: Notification) { guard let userInfo = note.userInfo, let error = userInfo["error"] as? Error else { @@ -856,14 +851,14 @@ internal extension AppDelegate { } let localizedError = NSLocalizedString("This theme cannot be used because of data corruption in the Info.plist: %@.", comment: "Decoding key missing") informativeText = NSString.localizedStringWithFormat(localizedError as NSString, debugDescription) as String - + default: informativeText = error.localizedDescription } } else { informativeText = error.localizedDescription } - + DispatchQueue.main.async { let alert = NSAlert() alert.alertStyle = .warning @@ -871,7 +866,7 @@ internal extension AppDelegate { alert.informativeText = informativeText alert.addButton(withTitle: NSLocalizedString("Open Theme Folder", comment: "Open Theme Folder")) alert.addButton(withTitle: NSLocalizedString("OK", comment: "OK")) - + let button = alert.buttons.first button?.target = self button?.action = #selector(self.openThemesFolder(_:)) @@ -890,7 +885,7 @@ internal extension AppDelegate { NSWorkspace.shared.open(url.deletingLastPathComponent()) } } - + } /* @@ -900,7 +895,7 @@ internal extension AppDelegate { These would be unnecessary if the similar accessors were marked internal rather than private, but for now, we'll keep the stratification of visibility */ -extension AppDelegate : ScriptingAppDelegate { +extension AppDelegate: ScriptingAppDelegate { internal var scriptingMainWindowController: ScriptingMainWindowController? { return mainWindowController @@ -916,28 +911,28 @@ extension AppDelegate : ScriptingAppDelegate { } extension AppDelegate: NSWindowRestoration { - + @objc static func restoreWindow(withIdentifier identifier: NSUserInterfaceItemIdentifier, state: NSCoder, completionHandler: @escaping (NSWindow?, Error?) -> Void) { - var mainWindow: NSWindow? = nil + var mainWindow: NSWindow? if identifier.rawValue == WindowRestorationIdentifiers.mainWindow { mainWindow = appDelegate.createAndShowMainWindow().window } completionHandler(mainWindow, nil) } - + } // Handle Notification Actions private extension AppDelegate { - + func handleMarkAsRead(userInfo: [AnyHashable: Any]) { - guard let articlePathUserInfo = userInfo[UserInfoKey.articlePath] as? [AnyHashable : Any], + guard let articlePathUserInfo = userInfo[UserInfoKey.articlePath] as? [AnyHashable: Any], let accountID = articlePathUserInfo[ArticlePathKey.accountID] as? String, let articleID = articlePathUserInfo[ArticlePathKey.articleID] as? String else { return } - + let account = AccountManager.shared.existingAccount(with: accountID) guard account != nil else { os_log(.debug, "No account found from notification.") @@ -950,9 +945,9 @@ private extension AppDelegate { } account!.markArticles(article!, statusKey: .read, flag: true) { _ in } } - + func handleMarkAsStarred(userInfo: [AnyHashable: Any]) { - guard let articlePathUserInfo = userInfo[UserInfoKey.articlePath] as? [AnyHashable : Any], + guard let articlePathUserInfo = userInfo[UserInfoKey.articlePath] as? [AnyHashable: Any], let accountID = articlePathUserInfo[ArticlePathKey.accountID] as? String, let articleID = articlePathUserInfo[ArticlePathKey.articleID] as? String else { return diff --git a/Mac/Browser.swift b/Mac/Browser.swift index e2558667a..45ce5a23c 100644 --- a/Mac/Browser.swift +++ b/Mac/Browser.swift @@ -23,7 +23,6 @@ struct Browser { return nil } - /// Opens a URL in the default browser. /// /// - Parameters: @@ -34,7 +33,6 @@ struct Browser { open(urlString, inBackground: invert ? !AppDefaults.shared.openInBrowserInBackground : AppDefaults.shared.openInBrowserInBackground) } - /// Opens a URL in the default browser. /// /// - Parameters: @@ -44,7 +42,7 @@ struct Browser { /// to open in the background. static func open(_ urlString: String, inBackground: Bool) { guard let url = URL(string: urlString), let preparedURL = url.preparedForOpeningInBrowser() else { return } - + let configuration = NSWorkspace.OpenConfiguration() configuration.requiresUniversalLinks = true configuration.promptsUserIfNeeded = false @@ -52,7 +50,7 @@ struct Browser { configuration.activates = false } - NSWorkspace.shared.open(preparedURL, configuration: configuration) { (runningApplication, error) in + NSWorkspace.shared.open(preparedURL, configuration: configuration) { (_, error) in guard error != nil else { return } if let defaultBrowser = defaultBrowser { defaultBrowser.openURL(url, inBackground: inBackground) diff --git a/Mac/CrashReporter/CrashReportWindowController.swift b/Mac/CrashReporter/CrashReportWindowController.swift index b0d155d43..0e73c106f 100644 --- a/Mac/CrashReporter/CrashReportWindowController.swift +++ b/Mac/CrashReporter/CrashReportWindowController.swift @@ -59,4 +59,3 @@ final class CrashReportWindowController: NSWindowController { close() } } - diff --git a/Mac/CrashReporter/CrashReporter.swift b/Mac/CrashReporter/CrashReporter.swift index 95423994d..f5c170bbf 100644 --- a/Mac/CrashReporter/CrashReporter.swift +++ b/Mac/CrashReporter/CrashReporter.swift @@ -37,7 +37,7 @@ struct CrashReporter { } else { runCrashReporterWindow(crashLogText) } - + crashReporter.purgePendingCrashReport() } @@ -48,7 +48,7 @@ struct CrashReporter { let boundary = "0xKhTmLbOuNdArY" let contentType = "multipart/form-data; boundary=\(boundary)" - request.setValue(contentType, forHTTPHeaderField:HTTPRequestHeader.contentType) + request.setValue(contentType, forHTTPHeaderField: HTTPRequestHeader.contentType) let formString = "--\(boundary)\r\nContent-Disposition: form-data; name=\"crashlog\"\r\n\r\n\(crashLogText)\r\n--\(boundary)--\r\n" let formData = formString.data(using: .utf8, allowLossyConversion: true) diff --git a/Mac/ErrorHandler.swift b/Mac/ErrorHandler.swift index 018da1a63..085b20edc 100644 --- a/Mac/ErrorHandler.swift +++ b/Mac/ErrorHandler.swift @@ -13,13 +13,13 @@ import os.log struct ErrorHandler { private static var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "Account") - + public static func present(_ error: Error) { NSApplication.shared.presentError(error) } - + public static func log(_ error: Error) { os_log(.error, log: self.log, "%@", error.localizedDescription) } - + } diff --git a/Mac/Inspector/BuiltinSmartFeedInspectorViewController.swift b/Mac/Inspector/BuiltinSmartFeedInspectorViewController.swift index 3c6c7b908..309fc646b 100644 --- a/Mac/Inspector/BuiltinSmartFeedInspectorViewController.swift +++ b/Mac/Inspector/BuiltinSmartFeedInspectorViewController.swift @@ -12,7 +12,7 @@ final class BuiltinSmartFeedInspectorViewController: NSViewController, Inspector @IBOutlet var nameTextField: NSTextField? @IBOutlet weak var smartFeedImageView: NSImageView! - + private var smartFeed: PseudoFeed? { didSet { updateUI() diff --git a/Mac/Inspector/FeedInspectorViewController.swift b/Mac/Inspector/FeedInspectorViewController.swift index 1d5dafc2a..02face97f 100644 --- a/Mac/Inspector/FeedInspectorViewController.swift +++ b/Mac/Inspector/FeedInspectorViewController.swift @@ -19,7 +19,7 @@ final class FeedInspectorViewController: NSViewController, Inspector { @IBOutlet weak var urlTextField: NSTextField? @IBOutlet weak var isNotifyAboutNewArticlesCheckBox: NSButton! @IBOutlet weak var isReaderViewAlwaysOnCheckBox: NSButton? - + private var feed: Feed? { didSet { if feed != oldValue { @@ -52,27 +52,27 @@ final class FeedInspectorViewController: NSViewController, Inspector { NotificationCenter.default.addObserver(self, selector: #selector(imageDidBecomeAvailable(_:)), name: .ImageDidBecomeAvailable, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(updateUI), name: .DidUpdateFeedPreferencesFromContextMenu, object: nil) } - + override func viewDidAppear() { updateNotificationSettings() } - + override func viewDidDisappear() { renameFeedIfNecessary() } - + // MARK: Actions @IBAction func isNotifyAboutNewArticlesChanged(_ sender: Any) { - guard userNotificationSettings != nil else { + guard userNotificationSettings != nil else { DispatchQueue.main.async { self.isNotifyAboutNewArticlesCheckBox.setNextState() } return } - + UNUserNotificationCenter.current().getNotificationSettings { (settings) in self.updateNotificationSettings() - + if settings.authorizationStatus == .denied { DispatchQueue.main.async { self.isNotifyAboutNewArticlesCheckBox.setNextState() @@ -83,7 +83,7 @@ final class FeedInspectorViewController: NSViewController, Inspector { self.feed?.isNotifyAboutNewArticles = (self.isNotifyAboutNewArticlesCheckBox?.state ?? .off) == .on ? true : false } } else { - UNUserNotificationCenter.current().requestAuthorization(options: [.badge, .sound, .alert]) { (granted, error) in + UNUserNotificationCenter.current().requestAuthorization(options: [.badge, .sound, .alert]) { (granted, _) in self.updateNotificationSettings() if granted { DispatchQueue.main.async { @@ -99,17 +99,17 @@ final class FeedInspectorViewController: NSViewController, Inspector { } } } - + @IBAction func isReaderViewAlwaysOnChanged(_ sender: Any) { feed?.isArticleExtractorAlwaysOn = (isReaderViewAlwaysOnCheckBox?.state ?? .off) == .on ? true : false } - + // MARK: Notifications @objc func imageDidBecomeAvailable(_ note: Notification) { updateImage() } - + } extension FeedInspectorViewController: NSTextFieldDelegate { @@ -117,7 +117,7 @@ extension FeedInspectorViewController: NSTextFieldDelegate { func controlTextDidEndEditing(_ note: Notification) { renameFeedIfNecessary() } - + } private extension FeedInspectorViewController { @@ -130,7 +130,6 @@ private extension FeedInspectorViewController { feed = singleFeed } - @objc func updateUI() { updateImage() updateName() @@ -209,7 +208,7 @@ private extension FeedInspectorViewController { feed.nameForDisplay != nameTextField.stringValue else { return } - + account.renameFeed(feed, to: nameTextField.stringValue) { [weak self] result in if case .failure(let error) = result { self?.presentError(error) @@ -218,5 +217,5 @@ private extension FeedInspectorViewController { } } } - + } diff --git a/Mac/Inspector/FolderInspectorViewController.swift b/Mac/Inspector/FolderInspectorViewController.swift index 63f76b0d7..b88bf7732 100644 --- a/Mac/Inspector/FolderInspectorViewController.swift +++ b/Mac/Inspector/FolderInspectorViewController.swift @@ -14,7 +14,7 @@ final class FolderInspectorViewController: NSViewController, Inspector { @IBOutlet var nameTextField: NSTextField? @IBOutlet weak var folderImageView: NSImageView! - + private var folder: Folder? { didSet { if folder != oldValue { @@ -46,18 +46,18 @@ final class FolderInspectorViewController: NSViewController, Inspector { override func viewDidLoad() { updateUI() - + let image = NSImage(systemSymbolName: "folder", accessibilityDescription: nil)! folderImageView.image = image folderImageView.contentTintColor = NSColor.controlAccentColor - + NotificationCenter.default.addObserver(self, selector: #selector(displayNameDidChange(_:)), name: .DisplayNameDidChange, object: nil) } override func viewDidDisappear() { renameFolderIfNecessary() } - + // MARK: Notifications @objc func displayNameDidChange(_ note: Notification) { @@ -73,7 +73,7 @@ extension FolderInspectorViewController: NSTextFieldDelegate { func controlTextDidEndEditing(_ obj: Notification) { renameFolderIfNecessary() } - + } private extension FolderInspectorViewController { @@ -104,7 +104,7 @@ private extension FolderInspectorViewController { } windowTitle = folder?.nameForDisplay ?? NSLocalizedString("Folder Inspector", comment: "Folder Inspector window title") } - + func renameFolderIfNecessary() { guard let folder = folder, let account = folder.account, @@ -112,7 +112,7 @@ private extension FolderInspectorViewController { folder.nameForDisplay != nameTextField.stringValue else { return } - + account.renameFolder(folder, to: nameTextField.stringValue) { [weak self] result in if case .failure(let error) = result { self?.presentError(error) @@ -121,5 +121,5 @@ private extension FolderInspectorViewController { } } } - + } diff --git a/Mac/Inspector/InspectorWindowController.swift b/Mac/Inspector/InspectorWindowController.swift index 2686f86c4..8349f3eae 100644 --- a/Mac/Inspector/InspectorWindowController.swift +++ b/Mac/Inspector/InspectorWindowController.swift @@ -19,7 +19,6 @@ protocol Inspector: AnyObject { typealias InspectorViewController = Inspector & NSViewController - final class InspectorWindowController: NSWindowController { class var shouldOpenAtStartup: Bool { @@ -28,7 +27,7 @@ final class InspectorWindowController: NSWindowController { var objects: [Any]? { didSet { - let _ = window + _ = window currentInspector = inspector(for: objects) } } @@ -67,21 +66,19 @@ final class InspectorWindowController: NSWindowController { if let savedOrigin = originFromDefaults() { window?.setFlippedOriginAdjustingForScreen(savedOrigin) - } - else { + } else { window?.flippedOrigin = NSPoint(x: 256, y: 256) } } func inspector(for objects: [Any]?) -> InspectorViewController { - var fallbackInspector: InspectorViewController? = nil + var fallbackInspector: InspectorViewController? for inspector in inspectors { if inspector.isFallbackInspector { fallbackInspector = inspector - } - else if let objects = objects, inspector.canInspect(objects) { + } else if let objects = objects, inspector.canInspect(objects) { return inspector } } @@ -113,7 +110,7 @@ private extension InspectorWindowController { DispatchQueue.main.async { window.title = inspector.windowTitle - } + } let flippedOrigin = window.flippedOrigin @@ -121,7 +118,7 @@ private extension InspectorWindowController { window.contentViewController = inspector window.makeFirstResponder(nil) } - + window.layoutIfNeeded() if let flippedOrigin = flippedOrigin { window.setFlippedOriginAdjustingForScreen(flippedOrigin) diff --git a/Mac/Inspector/NothingInspectorViewController.swift b/Mac/Inspector/NothingInspectorViewController.swift index 376e6a594..f23ad9b64 100644 --- a/Mac/Inspector/NothingInspectorViewController.swift +++ b/Mac/Inspector/NothingInspectorViewController.swift @@ -39,8 +39,7 @@ private extension NothingInspectorViewController { if let objects = objects, objects.count > 1 { nothingTextField?.isHidden = true multipleTextField?.isHidden = false - } - else { + } else { nothingTextField?.isHidden = false multipleTextField?.isHidden = true } diff --git a/Mac/MainWindow/AddFeed/AddFeedController.swift b/Mac/MainWindow/AddFeed/AddFeedController.swift index 09000604e..87f420f2d 100644 --- a/Mac/MainWindow/AddFeed/AddFeedController.swift +++ b/Mac/MainWindow/AddFeed/AddFeedController.swift @@ -28,7 +28,7 @@ class AddFeedController: AddFeedWindowControllerDelegate { private var addFeedWindowController: AddFeedWindowController? private var foundFeedURLString: String? private var titleFromFeed: String? - + init(hostWindow: NSWindow) { self.hostWindow = hostWindow } @@ -62,11 +62,11 @@ class AddFeedController: AddFeedWindowControllerDelegate { } account.createFeed(url: url.absoluteString, name: title, container: container, validateFeed: true) { result in - + DispatchQueue.main.async { self.endShowingProgress() } - + switch result { case .success(let feed): NotificationCenter.default.post(name: .UserDidAddFeed, object: self, userInfo: [UserInfoKey.feed: feed]) @@ -82,9 +82,9 @@ class AddFeedController: AddFeedWindowControllerDelegate { } } } - + } - + beginShowingProgress() } @@ -102,7 +102,7 @@ private extension AddFeedController { } return nil } - + struct AccountAndFolderSpecifier { let account: Account let folder: Folder? @@ -156,13 +156,12 @@ private extension AddFeedController { // MARK: Progress func beginShowingProgress() { - runIndeterminateProgressWithMessage(NSLocalizedString("Finding feed…", comment:"Feed finder")) + runIndeterminateProgressWithMessage(NSLocalizedString("Finding feed…", comment: "Feed finder")) } - + func endShowingProgress() { stopIndeterminateProgress() hostWindow.makeKeyAndOrderFront(self) } - -} +} diff --git a/Mac/MainWindow/AddFeed/AddFeedWindowController.swift b/Mac/MainWindow/AddFeed/AddFeedWindowController.swift index 40e14166b..fd70ffc8f 100644 --- a/Mac/MainWindow/AddFeed/AddFeedWindowController.swift +++ b/Mac/MainWindow/AddFeed/AddFeedWindowController.swift @@ -16,8 +16,8 @@ protocol AddFeedWindowControllerDelegate: AnyObject { func addFeedWindowControllerUserDidCancel(_: AddFeedWindowController) } -//protocol AddFeedWindowController { +// protocol AddFeedWindowController { // // var window: NSWindow? { get } // func runSheetOnWindow(_ hostWindow: NSWindow) -//} +// } diff --git a/Mac/MainWindow/AddFeed/AddWebFeedWindowController.swift b/Mac/MainWindow/AddFeed/AddWebFeedWindowController.swift index dc63ba4cb..e013102c1 100644 --- a/Mac/MainWindow/AddFeed/AddWebFeedWindowController.swift +++ b/Mac/MainWindow/AddFeed/AddWebFeedWindowController.swift @@ -12,7 +12,7 @@ import RSTree import Articles import Account -final class AddFeedWindowController : NSWindowController { +final class AddFeedWindowController: NSWindowController { @IBOutlet var urlTextField: NSTextField! @IBOutlet var nameTextField: NSTextField! @@ -34,7 +34,7 @@ final class AddFeedWindowController : NSWindowController { } return s } - + var hostWindow: NSWindow! convenience init(urlString: String?, name: String?, account: Account?, folder: Folder?, folderTreeController: TreeController, delegate: AddFeedWindowControllerDelegate?) { @@ -46,9 +46,9 @@ final class AddFeedWindowController : NSWindowController { self.delegate = delegate self.folderTreeController = folderTreeController } - + func runSheetOnWindow(_ hostWindow: NSWindow) { - hostWindow.beginSheet(window!) { (returnCode: NSApplication.ModalResponse) -> Void in + hostWindow.beginSheet(window!) { (_: NSApplication.ModalResponse) in } } @@ -61,7 +61,7 @@ final class AddFeedWindowController : NSWindowController { } folderPopupButton.menu = FolderTreeMenu.createFolderPopupMenu(with: folderTreeController.rootNode) - + if let account = initialAccount { FolderTreeMenu.select(account: account, folder: initialFolder, in: folderPopupButton) } else if let container = AddFeedDefaultContainer.defaultContainer { @@ -73,41 +73,41 @@ final class AddFeedWindowController : NSWindowController { } } } - + updateUI() } // MARK: Actions - + @IBAction func cancel(_ sender: Any?) { cancelSheet() } - + @IBAction func addFeed(_ sender: Any?) { let urlString = urlTextField.stringValue let normalizedURLString = urlString.normalizedURL if normalizedURLString.isEmpty { cancelSheet() - return; + return } guard let url = URL(string: normalizedURLString) else { cancelSheet() return } - + guard let container = selectedContainer() else { return } AddFeedDefaultContainer.saveDefaultContainer(container) delegate?.addFeedWindowController(self, userEnteredURL: url, userEnteredTitle: userEnteredTitle, container: container) - + } @IBAction func localShowFeedList(_ sender: Any?) { NSApplication.shared.sendAction(NSSelectorFromString("showFeedList:"), to: nil, from: sender) hostWindow.endSheet(window!, returnCode: NSApplication.ModalResponse.continue) } - + // MARK: NSTextFieldDelegate @objc func controlTextDidEndEditing(_ obj: Notification) { @@ -117,7 +117,7 @@ final class AddFeedWindowController : NSWindowController { @objc func controlTextDidChange(_ obj: Notification) { updateUI() } - + private func updateUI() { addButton.isEnabled = urlTextField.stringValue.mayBeURL && selectedContainer() != nil } diff --git a/Mac/MainWindow/AddFeed/FolderTreeMenu.swift b/Mac/MainWindow/AddFeed/FolderTreeMenu.swift index c9e122769..7ab0153f8 100644 --- a/Mac/MainWindow/AddFeed/FolderTreeMenu.swift +++ b/Mac/MainWindow/AddFeed/FolderTreeMenu.swift @@ -14,34 +14,34 @@ import Account class FolderTreeMenu { static func createFolderPopupMenu(with rootNode: Node, restrictToSpecialAccounts: Bool = false) -> NSMenu { - + let menu = NSMenu(title: "Folders") menu.autoenablesItems = false - + for childNode in rootNode.childNodes { - + guard let account = childNode.representedObject as? Account else { continue } - + if restrictToSpecialAccounts && !(account.type == .onMyMac || account.type == .cloudKit) { continue } - + let menuItem = NSMenuItem(title: account.nameForDisplay, action: nil, keyEquivalent: "") menuItem.representedObject = childNode.representedObject - + if account.behaviors.contains(.disallowFeedInRootFolder) { menuItem.isEnabled = false } - + menu.addItem(menuItem) let childNodes = childNode.childNodes addFolderItemsToMenuWithNodes(menu: menu, nodes: childNodes, indentationLevel: 1) - + } - + return menu } @@ -61,21 +61,21 @@ class FolderTreeMenu { } private static func addFolderItemsToMenuWithNodes(menu: NSMenu, nodes: [Node], indentationLevel: Int) { - + for oneNode in nodes { if let nameProvider = oneNode.representedObject as? DisplayNameProvider { - + let menuItem = NSMenuItem(title: nameProvider.nameForDisplay, action: nil, keyEquivalent: "") menuItem.indentationLevel = indentationLevel menuItem.representedObject = oneNode.representedObject menu.addItem(menuItem) - + if oneNode.numberOfChildNodes > 0 { addFolderItemsToMenuWithNodes(menu: menu, nodes: oneNode.childNodes, indentationLevel: indentationLevel + 1) } } } } - + } diff --git a/Mac/MainWindow/AddFolder/AddFolderWindowController.swift b/Mac/MainWindow/AddFolder/AddFolderWindowController.swift index 84053ee20..6d98a6a1e 100644 --- a/Mac/MainWindow/AddFolder/AddFolderWindowController.swift +++ b/Mac/MainWindow/AddFolder/AddFolderWindowController.swift @@ -10,8 +10,8 @@ import AppKit import Articles import Account -class AddFolderWindowController : NSWindowController { - +class AddFolderWindowController: NSWindowController { + @IBOutlet var folderNameTextField: NSTextField! @IBOutlet var accountPopupButton: NSPopUpButton! @IBOutlet var addFolderButton: NSButton! @@ -22,11 +22,11 @@ class AddFolderWindowController : NSWindowController { } // MARK: - API - + func runSheetOnWindow(_ w: NSWindow) { hostWindow = w - hostWindow!.beginSheet(window!) { (returnCode: NSApplication.ModalResponse) -> Void in - + hostWindow!.beginSheet(window!) { (returnCode: NSApplication.ModalResponse) in + if returnCode == NSApplication.ModalResponse.OK { self.addFolderIfNeeded() } @@ -34,37 +34,37 @@ class AddFolderWindowController : NSWindowController { } // MARK: - NSViewController - + override func windowDidLoad() { let preferredAccountID = AppDefaults.shared.addFolderAccountID accountPopupButton.removeAllItems() - + let menu = NSMenu() accountPopupButton.menu = menu - + let accounts = AccountManager.shared .sortedActiveAccounts .filter { !$0.behaviors.contains(.disallowFolderManagement) } - + for oneAccount in accounts { - + let oneMenuItem = NSMenuItem() oneMenuItem.title = oneAccount.nameForDisplay oneMenuItem.representedObject = oneAccount menu.addItem(oneMenuItem) - + if oneAccount.accountID == preferredAccountID { accountPopupButton.select(oneMenuItem) } } } - + // MARK: - Actions - + @IBAction func cancel(_ sender: Any?) { hostWindow!.endSheet(window!, returnCode: .cancel) } - + @IBAction func addFolder(_ sender: Any?) { hostWindow!.endSheet(window!, returnCode: .OK) } diff --git a/Mac/MainWindow/ArticleExtractorButton.swift b/Mac/MainWindow/ArticleExtractorButton.swift index bbde82a6f..b4da51581 100644 --- a/Mac/MainWindow/ArticleExtractorButton.swift +++ b/Mac/MainWindow/ArticleExtractorButton.swift @@ -16,9 +16,9 @@ enum ArticleExtractorButtonState { } class ArticleExtractorButton: NSButton { - + private var animatedLayer: CALayer? - + var buttonState: ArticleExtractorButtonState = .off { didSet { if buttonState != oldValue { @@ -39,7 +39,7 @@ class ArticleExtractorButton: NSButton { } } } - + override func accessibilityLabel() -> String? { switch buttonState { case .error: @@ -57,12 +57,12 @@ class ArticleExtractorButton: NSButton { super.init(frame: frameRect) commonInit() } - + required init?(coder: NSCoder) { super.init(coder: coder) commonInit() } - + private func commonInit() { wantsLayer = true bezelStyle = .texturedRounded @@ -70,7 +70,7 @@ class ArticleExtractorButton: NSButton { imageScaling = .scaleProportionallyDown widthAnchor.constraint(equalTo: heightAnchor).isActive = true } - + override func layout() { super.layout() guard case .animated = buttonState else { @@ -79,31 +79,31 @@ class ArticleExtractorButton: NSButton { stripAnimatedSublayer() addAnimatedSublayer(to: layer!) } - + private func stripAnimatedSublayer() { animatedLayer?.removeFromSuperlayer() } - + private func addAnimatedSublayer(to hostedLayer: CALayer) { let image1 = AppAssets.articleExtractorOff.tinted(with: NSColor.controlTextColor) let image2 = AppAssets.articleExtractorOn.tinted(with: NSColor.controlTextColor) let images = [image1, image2, image1] - + animatedLayer = CALayer() let imageSize = AppAssets.articleExtractorOff.size animatedLayer!.bounds = CGRect(x: 0, y: 0, width: imageSize.width, height: imageSize.height) animatedLayer!.position = CGPoint(x: bounds.midX, y: bounds.midY) - + hostedLayer.addSublayer(animatedLayer!) - + let animation = CAKeyframeAnimation(keyPath: "contents") animation.calculationMode = CAAnimationCalculationMode.linear animation.keyTimes = [0, 0.5, 1] animation.duration = 2 animation.values = images animation.repeatCount = HUGE - + animatedLayer!.add(animation, forKey: "contents") } - + } diff --git a/Mac/MainWindow/Detail/DetailContainerView.swift b/Mac/MainWindow/Detail/DetailContainerView.swift index f51ec96ef..f52a747b2 100644 --- a/Mac/MainWindow/Detail/DetailContainerView.swift +++ b/Mac/MainWindow/Detail/DetailContainerView.swift @@ -38,7 +38,7 @@ final class DetailContainerView: NSView { override func draw(_ dirtyRect: NSRect) { NSColor.controlBackgroundColor.set() - let r = NSIntersectionRect(dirtyRect, bounds) + let r = dirtyRect.intersection(bounds) r.fill() } } diff --git a/Mac/MainWindow/Detail/DetailStatusBarView.swift b/Mac/MainWindow/Detail/DetailStatusBarView.swift index eea5f72d2..0d110e798 100644 --- a/Mac/MainWindow/Detail/DetailStatusBarView.swift +++ b/Mac/MainWindow/Detail/DetailStatusBarView.swift @@ -25,8 +25,7 @@ final class DetailStatusBarView: NSView { if let link = linkForDisplay { urlLabel.stringValue = link self.isHidden = false - } - else { + } else { urlLabel.stringValue = "" self.isHidden = true } @@ -38,7 +37,7 @@ final class DetailStatusBarView: NSView { override var isOpaque: Bool { return false } - + override var isFlipped: Bool { return true } @@ -68,11 +67,8 @@ private extension DetailStatusBarView { func updateLinkForDisplay() { if let mouseoverLink = mouseoverLink, !mouseoverLink.isEmpty { linkForDisplay = mouseoverLink.strippingHTTPOrHTTPSScheme - } - else { + } else { linkForDisplay = nil } } } - - diff --git a/Mac/MainWindow/Detail/DetailViewController.swift b/Mac/MainWindow/Detail/DetailViewController.swift index 4a84a3f48..6f746ec6c 100644 --- a/Mac/MainWindow/Detail/DetailViewController.swift +++ b/Mac/MainWindow/Detail/DetailViewController.swift @@ -82,7 +82,7 @@ final class DetailViewController: NSViewController, WKUIDelegate { func stopMediaPlayback() { currentWebViewController.stopMediaPlayback() } - + func canScrollDown(_ callback: @escaping (Bool) -> Void) { currentWebViewController.canScrollDown(callback) } @@ -98,22 +98,22 @@ final class DetailViewController: NSViewController, WKUIDelegate { override func scrollPageUp(_ sender: Any?) { currentWebViewController.scrollPageUp(sender) } - + // MARK: - Navigation - + func focus() { guard let window = currentWebViewController.webView.window else { return } window.makeFirstResponderUnlessDescendantIsFirstResponder(currentWebViewController.webView) } - + // MARK: State Restoration - - func saveState(to state: inout [AnyHashable : Any]) { + + func saveState(to state: inout [AnyHashable: Any]) { currentWebViewController.saveState(to: &state) } - + } // MARK: - DetailWebViewControllerDelegate @@ -158,7 +158,7 @@ private extension DetailViewController { } } - @objc func userDefaultsDidChange(_ : Notification) { + @objc func userDefaultsDidChange(_: Notification) { if AppDefaults.shared.isArticleContentJavascriptEnabled != isArticleContentJavascriptEnabled { isArticleContentJavascriptEnabled = AppDefaults.shared.isArticleContentJavascriptEnabled createNewWebViewsAndRestoreState() diff --git a/Mac/MainWindow/Detail/DetailWebView.swift b/Mac/MainWindow/Detail/DetailWebView.swift index b217b54f2..2fb45e835 100644 --- a/Mac/MainWindow/Detail/DetailWebView.swift +++ b/Mac/MainWindow/Detail/DetailWebView.swift @@ -13,13 +13,13 @@ import RSCore final class DetailWebView: WKWebView { weak var keyboardDelegate: KeyboardDelegate? - + override func accessibilityLabel() -> String? { return NSLocalizedString("Article", comment: "Article") } // MARK: - NSResponder - + override func keyDown(with event: NSEvent) { if keyboardDelegate?.keydown(event, in: self) ?? false { return @@ -55,16 +55,16 @@ final class DetailWebView: WKWebView { evaluateJavaScript("document.body.style.overflow = 'visible';", completionHandler: nil) bigSurOffsetFix() } - + override func setFrameSize(_ newSize: NSSize) { super.setFrameSize(newSize) - if (!inLiveResize) { + if !inLiveResize { bigSurOffsetFix() } } - + private var inBigSurOffsetFix = false - + private func bigSurOffsetFix() { /* On macOS 11, when a user exits full screen @@ -77,17 +77,17 @@ final class DetailWebView: WKWebView { guard var frame = window?.frame else { return } - + guard !inBigSurOffsetFix else { return } - + inBigSurOffsetFix = true - + defer { inBigSurOffsetFix = false } - + frame.size = NSSize(width: window!.frame.width, height: window!.frame.height - 1) window!.setFrame(frame, display: false) frame.size = NSSize(width: window!.frame.width, height: window!.frame.height + 1) @@ -128,4 +128,3 @@ private extension DetailWebView { return false } } - diff --git a/Mac/MainWindow/Detail/DetailWebViewController.swift b/Mac/MainWindow/Detail/DetailWebViewController.swift index 1c99691af..0e6873929 100644 --- a/Mac/MainWindow/Detail/DetailWebViewController.swift +++ b/Mac/MainWindow/Detail/DetailWebViewController.swift @@ -34,7 +34,7 @@ final class DetailWebViewController: NSViewController { } } } - + var article: Article? { switch state { case .article(let article, _): @@ -45,9 +45,9 @@ final class DetailWebViewController: NSViewController { return nil } } - + private var articleTextSize = AppDefaults.shared.articleTextSize - + private var webInspectorEnabled: Bool { get { return webView.configuration.preferences._developerExtrasEnabled @@ -64,7 +64,7 @@ final class DetailWebViewController: NSViewController { private var isShowingExtractedArticle: Bool { switch state { - case .extracted(_, _, _): + case .extracted: return true default: return false @@ -96,7 +96,7 @@ final class DetailWebViewController: NSViewController { // See bug #901. webView.isHidden = true waitingForFirstReload = true - + webInspectorEnabled = AppDefaults.shared.webInspectorEnabled NotificationCenter.default.addObserver(self, selector: #selector(webInspectorEnabledDidChange(_:)), name: .WebInspectorEnabledDidChange, object: nil) @@ -110,7 +110,7 @@ final class DetailWebViewController: NSViewController { } // MARK: Notifications - + @objc func feedIconDidBecomeAvailable(_ note: Notification) { reloadArticleImage() } @@ -122,24 +122,24 @@ final class DetailWebViewController: NSViewController { @objc func faviconDidBecomeAvailable(_ note: Notification) { reloadArticleImage() } - + @objc func userDefaultsDidChange(_ note: Notification) { if articleTextSize != AppDefaults.shared.articleTextSize { articleTextSize = AppDefaults.shared.articleTextSize reloadHTMLMaintainingScrollPosition() } } - + @objc func currentArticleThemeDidChangeNotification(_ note: Notification) { reloadHTMLMaintainingScrollPosition() } - + // MARK: Media Functions - + func stopMediaPlayback() { webView.evaluateJavaScript("stopMediaPlayback();") } - + // MARK: Scrolling func canScrollDown(_ completion: @escaping (Bool) -> Void) { @@ -163,12 +163,12 @@ final class DetailWebViewController: NSViewController { } // MARK: State Restoration - - func saveState(to state: inout [AnyHashable : Any]) { + + func saveState(to state: inout [AnyHashable: Any]) { state[UserInfoKey.isShowingExtractedArticle] = isShowingExtractedArticle state[UserInfoKey.articleWindowScrollY] = windowScrollY } - + } // MARK: - ArticleIconSchemeHandlerDelegate @@ -228,7 +228,7 @@ extension DetailWebViewController: WKNavigationDelegate, WKUIDelegate { decisionHandler(.allow) } - + public func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { // See note in viewDidLoad() if waitingForFirstReload { @@ -250,7 +250,7 @@ extension DetailWebViewController: WKNavigationDelegate, WKUIDelegate { } // WKUIDelegate - + func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? { // This method is reached when WebKit handles a JavaScript based window.open() invocation, for example. One // example where this is used is in YouTube's embedded video player when a user clicks on the video's title @@ -270,18 +270,18 @@ private extension DetailWebViewController { func reloadArticleImage() { guard let article = article else { return } - + var components = URLComponents() components.scheme = ArticleRenderer.imageIconScheme components.path = article.articleID - + if let imageSrc = components.string { webView?.evaluateJavaScript("reloadArticleImage(\"\(imageSrc)\")") } } - + func reloadHTMLMaintainingScrollPosition() { - fetchScrollInfo() { scrollInfo in + fetchScrollInfo { scrollInfo in self.windowScrollY = scrollInfo?.offsetY self.reloadHTML() } @@ -289,7 +289,7 @@ private extension DetailWebViewController { func reloadHTML() { delegate?.mouseDidExit(self) - + let theme = ArticleThemesManager.shared.currentTheme let rendering: ArticleRenderer.Rendering @@ -305,14 +305,14 @@ private extension DetailWebViewController { case .extracted(let article, let extractedArticle, _): rendering = ArticleRenderer.articleHTML(article: article, extractedArticle: extractedArticle, theme: theme) } - + let substitutions = [ "title": rendering.title, "baseURL": rendering.baseURL, "style": rendering.style, "body": rendering.html ] - + let html = try! MacroProcessor.renderedText(withTemplate: ArticleRenderer.page.html, substitutions: substitutions) webView.loadHTMLString(html, baseURL: URL(string: rendering.baseURL)) } @@ -320,7 +320,7 @@ private extension DetailWebViewController { func fetchScrollInfo(_ completion: @escaping (ScrollInfo?) -> Void) { let javascriptString = "var x = {contentHeight: document.body.scrollHeight, offsetY: window.pageYOffset}; x" - webView.evaluateJavaScript(javascriptString) { (info, error) in + webView.evaluateJavaScript(javascriptString) { (info, _) in guard let info = info as? [String: Any] else { completion(nil) return diff --git a/Mac/MainWindow/IconView.swift b/Mac/MainWindow/IconView.swift index 46b96a75d..e0993b5ac 100644 --- a/Mac/MainWindow/IconView.swift +++ b/Mac/MainWindow/IconView.swift @@ -10,7 +10,7 @@ import AppKit final class IconView: NSView { - var iconImage: IconImage? = nil { + var iconImage: IconImage? { didSet { if iconImage !== oldValue { imageView.image = iconImage?.image @@ -36,7 +36,7 @@ final class IconView: NSView { } private var isDiscernable = true - + override var isFlipped: Bool { return true } @@ -76,7 +76,7 @@ final class IconView: NSView { } override func layout() { - resizeSubviews(withOldSize: NSZeroSize) + resizeSubviews(withOldSize: NSSize.zero) } override func resizeSubviews(withOldSize oldSize: NSSize) { @@ -89,7 +89,7 @@ final class IconView: NSView { let color = NSApplication.shared.effectiveAppearance.isDarkMode ? IconView.darkBackgroundColor : IconView.lightBackgroundColor color.set() - let r = NSIntersectionRect(dirtyRect, bounds) + let r = dirtyRect.intersection(bounds) r.fill() } } @@ -104,9 +104,9 @@ private extension IconView { func rectForImageView() -> NSRect { guard !(iconImage?.isSymbol ?? false) else { - return NSMakeRect(0.0, 0.0, bounds.size.width, bounds.size.height) + return NSRect(x: 0.0, y: 0.0, width: bounds.size.width, height: bounds.size.height) } - + guard let image = iconImage?.image else { return NSRect.zero } @@ -116,22 +116,21 @@ private extension IconView { if imageSize.height == imageSize.width { if imageSize.height >= viewSize.height * 0.75 { // Close enough to viewSize to scale up the image. - return NSMakeRect(0.0, 0.0, viewSize.width, viewSize.height) + return NSRect(x: 0.0, y: 0.0, width: viewSize.width, height: viewSize.height) } let offset = floor((viewSize.height - imageSize.height) / 2.0) - return NSMakeRect(offset, offset, imageSize.width, imageSize.height) - } - else if imageSize.height > imageSize.width { + return NSRect(x: offset, y: offset, width: imageSize.width, height: imageSize.height) + } else if imageSize.height > imageSize.width { let factor = viewSize.height / imageSize.height let width = imageSize.width * factor let originX = floor((viewSize.width - width) / 2.0) - return NSMakeRect(originX, 0.0, width, viewSize.height) + return NSRect(x: originX, y: 0.0, width: width, height: viewSize.height) } // Wider than tall: imageSize.width > imageSize.height let factor = viewSize.width / imageSize.width let height = imageSize.height * factor let originY = floor((viewSize.height - height) / 2.0) - return NSMakeRect(0.0, originY, viewSize.width, height) + return NSRect(x: 0.0, y: originY, width: viewSize.width, height: height) } } diff --git a/Mac/MainWindow/Keyboard/MainWIndowKeyboardHandler.swift b/Mac/MainWindow/Keyboard/MainWIndowKeyboardHandler.swift index 04483edca..740f5ba65 100644 --- a/Mac/MainWindow/Keyboard/MainWIndowKeyboardHandler.swift +++ b/Mac/MainWindow/Keyboard/MainWIndowKeyboardHandler.swift @@ -33,4 +33,3 @@ final class MainWindowKeyboardHandler: KeyboardDelegate { return true } } - diff --git a/Mac/MainWindow/LegacyArticleExtractorButton.swift b/Mac/MainWindow/LegacyArticleExtractorButton.swift index 14a805134..e0ff64711 100644 --- a/Mac/MainWindow/LegacyArticleExtractorButton.swift +++ b/Mac/MainWindow/LegacyArticleExtractorButton.swift @@ -9,7 +9,7 @@ import Foundation class LegacyArticleExtractorButton: NSButton { - + var isError = false { didSet { if isError != oldValue { @@ -17,7 +17,7 @@ class LegacyArticleExtractorButton: NSButton { } } } - + var isInProgress = false { didSet { if isInProgress != oldValue { @@ -25,12 +25,12 @@ class LegacyArticleExtractorButton: NSButton { } } } - + override init(frame frameRect: NSRect) { super.init(frame: frameRect) wantsLayer = true } - + required init?(coder: NSCoder) { super.init(coder: coder) wantsLayer = true @@ -38,7 +38,7 @@ class LegacyArticleExtractorButton: NSButton { override func draw(_ dirtyRect: NSRect) { super.draw(dirtyRect) - + guard let hostedLayer = self.layer else { return } @@ -52,7 +52,7 @@ class LegacyArticleExtractorButton: NSButton { } let opacity: Float = isEnabled ? 1.0 : 0.5 - + switch true { case isError: addImageSublayer(to: hostedLayer, image: AppAssets.legacyArticleExtractorError, opacity: opacity) @@ -70,42 +70,42 @@ class LegacyArticleExtractorButton: NSButton { } } } - + private func makeLayerForImage(_ image: NSImage) -> CALayer { let imageLayer = CALayer() imageLayer.bounds = CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height) imageLayer.position = CGPoint(x: bounds.midX, y: floor(bounds.midY)) return imageLayer } - + private func addImageSublayer(to hostedLayer: CALayer, image: NSImage, opacity: Float = 1.0) { let imageLayer = makeLayerForImage(image) imageLayer.contents = image imageLayer.opacity = opacity hostedLayer.addSublayer(imageLayer) } - + private func addAnimatedSublayer(to hostedLayer: CALayer) { let imageProgress1 = AppAssets.legacyArticleExtractorProgress1 let imageProgress2 = AppAssets.legacyArticleExtractorProgress2 let imageProgress3 = AppAssets.legacyArticleExtractorProgress3 let imageProgress4 = AppAssets.legacyArticleExtractorProgress4 let images = [imageProgress1, imageProgress2, imageProgress3, imageProgress4, imageProgress3, imageProgress2, imageProgress1] - + let imageLayer = CALayer() imageLayer.bounds = CGRect(x: 0, y: 0, width: imageProgress1?.size.width ?? 0, height: imageProgress1?.size.height ?? 0) imageLayer.position = CGPoint(x: bounds.midX, y: floor(bounds.midY)) - + hostedLayer.addSublayer(imageLayer) - + let animation = CAKeyframeAnimation(keyPath: "contents") animation.calculationMode = CAAnimationCalculationMode.linear animation.keyTimes = [0, 0.16, 0.32, 0.50, 0.66, 0.82, 1] animation.duration = 2 animation.values = images as [Any] animation.repeatCount = HUGE - + imageLayer.add(animation, forKey: "contents") } - + } diff --git a/Mac/MainWindow/MainWindowController.swift b/Mac/MainWindow/MainWindowController.swift index 2e3ee6e60..75be07446 100644 --- a/Mac/MainWindow/MainWindowController.swift +++ b/Mac/MainWindow/MainWindowController.swift @@ -16,14 +16,14 @@ enum TimelineSourceMode { case regular, search } -class MainWindowController : NSWindowController, NSUserInterfaceValidations { +class MainWindowController: NSWindowController, NSUserInterfaceValidations { @IBOutlet weak var articleThemePopUpButton: NSPopUpButton? - + private var activityManager = ActivityManager() private var isShowingExtractedArticle = false - private var articleExtractor: ArticleExtractor? = nil + private var articleExtractor: ArticleExtractor? private var sharingServicePickerDelegate: NSSharingServicePickerDelegate? private let windowAutosaveName = NSWindow.FrameAutosaveName("MainWindow") @@ -36,7 +36,7 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations { } return selectedObjects.first } - + private var shareToolbarItem: NSToolbarItem? { return window?.toolbar?.existingItem(withIdentifier: .share) } @@ -45,17 +45,17 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations { private var sidebarViewController: SidebarViewController? private var timelineContainerViewController: TimelineContainerViewController? private var detailViewController: DetailViewController? - private var currentSearchField: NSSearchField? = nil + private var currentSearchField: NSSearchField? private let articleThemeMenuToolbarItem = NSMenuToolbarItem(itemIdentifier: .articleThemeMenu) - private var searchString: String? = nil - private var lastSentSearchString: String? = nil + private var searchString: String? + private var lastSentSearchString: String? private var timelineSourceMode: TimelineSourceMode = .regular { didSet { timelineContainerViewController?.showTimeline(for: timelineSourceMode) detailViewController?.showDetail(for: timelineSourceMode) } } - private var searchSmartFeed: SmartFeed? = nil + private var searchSmartFeed: SmartFeed? private var restoreArticleWindowScrollY: CGFloat? // MARK: - NSWindowController @@ -97,10 +97,10 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations { NotificationCenter.default.addObserver(self, selector: #selector(unreadCountDidChange(_:)), name: .UnreadCountDidChange, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(displayNameDidChange(_:)), name: .DisplayNameDidChange, object: nil) - + NotificationCenter.default.addObserver(self, selector: #selector(articleThemeNamesDidChangeNotification(_:)), name: .ArticleThemeNamesDidChangeNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(currentArticleThemeDidChangeNotification(_:)), name: .CurrentArticleThemeDidChangeNotification, object: nil) - + DispatchQueue.main.async { self.updateWindowTitle() } @@ -115,14 +115,14 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations { func handle(_ response: UNNotificationResponse) { let userInfo = response.notification.request.content.userInfo - guard let articlePathUserInfo = userInfo[UserInfoKey.articlePath] as? [AnyHashable : Any] else { return } + guard let articlePathUserInfo = userInfo[UserInfoKey.articlePath] as? [AnyHashable: Any] else { return } sidebarViewController?.deepLinkRevealAndSelect(for: articlePathUserInfo) currentTimelineViewController?.goToDeepLink(for: articlePathUserInfo) } func handle(_ activity: NSUserActivity) { guard let userInfo = activity.userInfo else { return } - guard let articlePathUserInfo = userInfo[UserInfoKey.articlePath] as? [AnyHashable : Any] else { return } + guard let articlePathUserInfo = userInfo[UserInfoKey.articlePath] as? [AnyHashable: Any] else { return } sidebarViewController?.deepLinkRevealAndSelect(for: articlePathUserInfo) currentTimelineViewController?.goToDeepLink(for: articlePathUserInfo) } @@ -131,14 +131,14 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations { AppDefaults.shared.windowState = savableState() window?.saveFrame(usingName: windowAutosaveName) } - + func restoreStateFromUserDefaults() { if let state = AppDefaults.shared.windowState { restoreState(from: state) window?.setFrameUsingName(windowAutosaveName, force: true) } } - + // MARK: - Notifications @objc func refreshProgressDidChange(_ note: Notification) { @@ -148,7 +148,7 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations { @objc func unreadCountDidChange(_ note: Notification) { updateWindowTitleIfNecessary(note.object) } - + @objc func displayNameDidChange(_ note: Notification) { updateWindowTitleIfNecessary(note.object) } @@ -162,21 +162,21 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations { } private func updateWindowTitleIfNecessary(_ noteObject: Any?) { - + if let folder = currentFeedOrFolder as? Folder, let noteObject = noteObject as? Folder { if folder == noteObject { updateWindowTitle() return } } - + if let feed = currentFeedOrFolder as? Feed, let noteObject = noteObject as? Feed { if feed == noteObject { updateWindowTitle() return } } - + // If we don't recognize the changed object, we will test it for identity instead // of equality. This works well for us if the window title is displaying a // PsuedoFeed object. @@ -185,28 +185,28 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations { updateWindowTitle() } } - + } // MARK: - Toolbar - + @objc func makeToolbarValidate() { - + window?.toolbar?.validateVisibleItems() } // MARK: - NSUserInterfaceValidations - + public func validateUserInterfaceItem(_ item: NSValidatedUserInterfaceItem) -> Bool { - + if item.action == #selector(copyArticleURL(_:)) { return canCopyArticleURL() } - + if item.action == #selector(copyExternalURL(_:)) { return canCopyExternalURL() } - + if item.action == #selector(openArticleInBrowser(_:)) { if let item = item as? NSMenuItem, item.keyEquivalentModifierMask.contains(.shift) { item.title = Browser.titleForOpenInBrowserInverted @@ -214,11 +214,11 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations { return currentLink != nil } - + if item.action == #selector(nextUnread(_:)) { return canGoToNextUnread(wrappingToTop: true) } - + if item.action == #selector(markAllAsRead(_:)) { return canMarkAllAsRead() } @@ -242,7 +242,7 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations { if item.action == #selector(toggleArticleExtractor(_:)) { return validateToggleArticleExtractor(item) } - + if item.action == #selector(toolbarShowShareMenu(_:)) { return canShowShareMenu() } @@ -283,7 +283,7 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations { return } detailViewController.canScrollUp { (canScroll) in - if (canScroll) { + if canScroll { NSCursor.setHiddenUntilMouseMoves(true) detailViewController.scrollPageUp(sender) } @@ -306,7 +306,7 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations { @IBAction func openArticleInBrowser(_ sender: Any?) { if let link = currentLink { Browser.open(link, invertPreference: NSApp.currentEvent?.modifierFlags.contains(.shift) ?? false) - } + } } @IBAction func openInBrowser(_ sender: Any?) { @@ -340,8 +340,7 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations { // TODO: handle search mode if timelineViewController.canGoToNextUnread(wrappingToTop: false) { goToNextUnreadInTimeline(wrappingToTop: false) - } - else if sidebarViewController.canGoToNextUnread(wrappingToTop: true) { + } else if sidebarViewController.canGoToNextUnread(wrappingToTop: true) { sidebarViewController.goToNextUnread(wrappingToTop: true) // If we ended up on the same timelineViewController, we may need to wrap @@ -373,7 +372,7 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations { } @IBAction func toggleArticleExtractor(_ sender: Any?) { - + guard let currentLink = currentLink, let article = oneSelectedArticle else { return } @@ -381,12 +380,12 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations { defer { makeToolbarValidate() } - + if articleExtractor?.state == .failedToParse { startArticleExtractorForCurrentLink() return } - + guard articleExtractor?.state != .processing else { articleExtractor?.cancel() articleExtractor = nil @@ -394,13 +393,13 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations { detailViewController?.setState(DetailState.article(article, nil), mode: timelineSourceMode) return } - + guard !isShowingExtractedArticle else { isShowingExtractedArticle = false detailViewController?.setState(DetailState.article(article, nil), mode: timelineSourceMode) return } - + if let articleExtractor = articleExtractor, let extractedArticle = articleExtractor.article { if currentLink == articleExtractor.articleLink { isShowingExtractedArticle = true @@ -410,11 +409,11 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations { } else { startArticleExtractorForCurrentLink() } - + } @IBAction func markAllAsReadAndGoToNextUnread(_ sender: Any?) { - currentTimelineViewController?.markAllAsRead() { + currentTimelineViewController?.markAllAsRead { self.nextUnread(sender) } } @@ -432,7 +431,7 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations { @IBAction func markOlderArticlesAsRead(_ sender: Any?) { currentTimelineViewController?.markOlderArticlesRead() } - + @IBAction func markAboveArticlesAsRead(_ sender: Any?) { currentTimelineViewController?.markAboveArticlesRead() } @@ -452,7 +451,7 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations { @IBAction func navigateToDetail(_ sender: Any?) { detailViewController?.focus() } - + @IBAction func goToPreviousSubscription(_ sender: Any?) { sidebarViewController?.outlineView.selectPreviousRow(sender) } @@ -504,31 +503,31 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations { @IBAction func cleanUp(_ sender: Any?) { timelineContainerViewController?.cleanUp() } - + @IBAction func toggleReadFeedsFilter(_ sender: Any?) { sidebarViewController?.toggleReadFilter() } - + @IBAction func toggleReadArticlesFilter(_ sender: Any?) { timelineContainerViewController?.toggleReadFilter() } - + @objc func selectArticleTheme(_ menuItem: NSMenuItem) { ArticleThemesManager.shared.currentThemeName = menuItem.title } - + } // MARK: NSWindowDelegate extension MainWindowController: NSWindowDelegate { - + func window(_ window: NSWindow, willEncodeRestorableState coder: NSCoder) { coder.encode(savableState(), forKey: UserInfoKey.windowState) } func window(_ window: NSWindow, didDecodeRestorableState coder: NSCoder) { - guard let state = try? coder.decodeTopLevelObject(forKey: UserInfoKey.windowState) as? [AnyHashable : Any] else { return } + guard let state = try? coder.decodeTopLevelObject(forKey: UserInfoKey.windowState) as? [AnyHashable: Any] else { return } restoreState(from: state) } @@ -536,7 +535,7 @@ extension MainWindowController: NSWindowDelegate { detailViewController?.stopMediaPlayback() appDelegate.removeMainWindow(self) } - + } // MARK: - SidebarDelegate @@ -563,11 +562,11 @@ extension MainWindowController: SidebarDelegate { } return timelineViewController.unreadCount } - + func sidebarInvalidatedRestorationState(_: SidebarViewController) { invalidateRestorableState() } - + } // MARK: - TimelineContainerViewControllerDelegate @@ -576,12 +575,12 @@ extension MainWindowController: TimelineContainerViewControllerDelegate { func timelineSelectionDidChange(_: TimelineContainerViewController, articles: [Article]?, mode: TimelineSourceMode) { activityManager.invalidateReading() - + articleExtractor?.cancel() articleExtractor = nil isShowingExtractedArticle = false makeToolbarValidate() - + let detailState: DetailState if let articles = articles { if articles.count == 1 { @@ -606,11 +605,11 @@ extension MainWindowController: TimelineContainerViewControllerDelegate { func timelineRequestedFeedSelection(_: TimelineContainerViewController, feed: Feed) { sidebarViewController?.selectFeed(feed) } - + func timelineInvalidatedRestorationState(_: TimelineContainerViewController) { invalidateRestorableState() } - + } // MARK: - NSSearchFieldDelegate @@ -683,11 +682,11 @@ extension MainWindowController: NSSearchFieldDelegate { // MARK: - ArticleExtractorDelegate extension MainWindowController: ArticleExtractorDelegate { - + func articleExtractionDidFail(with: Error) { makeToolbarValidate() } - + func articleExtractionDidComplete(extractedArticle: ExtractedArticle) { if let article = oneSelectedArticle, articleExtractor?.state != .cancelled { isShowingExtractedArticle = true @@ -697,7 +696,7 @@ extension MainWindowController: ArticleExtractorDelegate { makeToolbarValidate() } } - + } // MARK: - Scripting Access @@ -710,7 +709,7 @@ extension MainWindowController: ArticleExtractorDelegate { but for now, we'll keep the stratification of visibility */ -extension MainWindowController : ScriptingMainWindowController { +extension MainWindowController: ScriptingMainWindowController { internal var scriptingCurrentArticle: Article? { return self.oneSelectedArticle @@ -827,7 +826,7 @@ extension MainWindowController: NSToolbarDelegate { return nil } - + func toolbarAllowedItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] { [ NSToolbarItem.Identifier.toggleSidebar, @@ -929,7 +928,7 @@ private extension MainWindowController { var detailSplitViewItem: NSSplitViewItem? { return splitViewController?.splitViewItems[2] } - + var selectedArticles: [Article]? { return currentTimelineViewController?.selectedArticles } @@ -946,9 +945,9 @@ private extension MainWindowController { } // MARK: - State Restoration - - func savableState() -> [AnyHashable : Any] { - var state = [AnyHashable : Any]() + + func savableState() -> [AnyHashable: Any] { + var state = [AnyHashable: Any]() state[UserInfoKey.windowFullScreenState] = window?.styleMask.contains(.fullScreen) ?? false saveSplitViewState(to: &state) sidebarViewController?.saveState(to: &state) @@ -957,56 +956,56 @@ private extension MainWindowController { return state } - func restoreState(from state: [AnyHashable : Any]) { + func restoreState(from state: [AnyHashable: Any]) { if let fullScreen = state[UserInfoKey.windowFullScreenState] as? Bool, fullScreen { window?.toggleFullScreen(self) } restoreSplitViewState(from: state) - + sidebarViewController?.restoreState(from: state) - + let articleWindowScrollY = state[UserInfoKey.articleWindowScrollY] as? CGFloat restoreArticleWindowScrollY = articleWindowScrollY timelineContainerViewController?.restoreState(from: state) - + let isShowingExtractedArticle = state[UserInfoKey.isShowingExtractedArticle] as? Bool ?? false if isShowingExtractedArticle { restoreArticleWindowScrollY = articleWindowScrollY startArticleExtractorForCurrentLink() } - + } // MARK: - Command Validation - + func canCopyArticleURL() -> Bool { return currentLink != nil } - + func canCopyExternalURL() -> Bool { return oneSelectedArticle?.externalLink != nil && oneSelectedArticle?.externalLink != currentLink } func canGoToNextUnread(wrappingToTop wrapping: Bool = false) -> Bool { - + guard let timelineViewController = currentTimelineViewController, let sidebarViewController = sidebarViewController else { return false } // TODO: handle search mode return timelineViewController.canGoToNextUnread(wrappingToTop: wrapping) || sidebarViewController.canGoToNextUnread(wrappingToTop: wrapping) } - + func canMarkAllAsRead() -> Bool { - + return currentTimelineViewController?.canMarkAllAsRead() ?? false } - + func validateToggleRead(_ item: NSValidatedUserInterfaceItem) -> Bool { let validationStatus = currentTimelineViewController?.markReadCommandStatus() ?? .canDoNothing let markingRead: Bool let result: Bool - + switch validationStatus { case .canMark: markingRead = true @@ -1018,21 +1017,21 @@ private extension MainWindowController { markingRead = true result = false } - + let commandName = markingRead ? NSLocalizedString("Mark as Read", comment: "Command") : NSLocalizedString("Mark as Unread", comment: "Command") - + if let toolbarItem = item as? NSToolbarItem { toolbarItem.toolTip = commandName } - + if let menuItem = item as? NSMenuItem { menuItem.title = commandName } - + if let toolbarItem = item as? NSToolbarItem, let button = toolbarItem.view as? NSButton { button.image = markingRead ? AppAssets.readClosedImage : AppAssets.readOpenImage } - + return result } @@ -1076,7 +1075,7 @@ private extension MainWindowController { func canMarkBelowArticlesAsRead() -> Bool { return currentTimelineViewController?.canMarkBelowArticlesAsRead() ?? false } - + func canShowShareMenu() -> Bool { guard let selectedArticles = selectedArticles else { @@ -1119,7 +1118,7 @@ private extension MainWindowController { return result } - + func validateCleanUp(_ item: NSValidatedUserInterfaceItem) -> Bool { return timelineContainerViewController?.isCleanUpAvailable ?? false } @@ -1159,7 +1158,7 @@ private extension MainWindowController { button.image = AppAssets.filterInactive } } - + return true } @@ -1192,7 +1191,7 @@ private extension MainWindowController { window?.subtitle = "" return } - + func setSubtitle(_ count: Int) { let localizedLabel = NSLocalizedString("%d unread", comment: "Unread") let formattedLabel = NSString.localizedStringWithFormat(localizedLabel as NSString, count) @@ -1235,16 +1234,16 @@ private extension MainWindowController { } } - func saveSplitViewState(to state: inout [AnyHashable : Any]) { + func saveSplitViewState(to state: inout [AnyHashable: Any]) { guard let splitView = splitViewController?.splitView else { return } - let widths = splitView.arrangedSubviews.map{ Int(floor($0.frame.width)) } + let widths = splitView.arrangedSubviews.map { Int(floor($0.frame.width)) } state[MainWindowController.mainWindowWidthsStateKey] = widths } - func restoreSplitViewState(from state: [AnyHashable : Any]) { + func restoreSplitViewState(from state: [AnyHashable: Any]) { guard let splitView = splitViewController?.splitView, let widths = state[MainWindowController.mainWindowWidthsStateKey] as? [Int], widths.count == 3, @@ -1269,13 +1268,13 @@ private extension MainWindowController { func buildToolbarButton(_ itemIdentifier: NSToolbarItem.Identifier, _ title: String, _ image: NSImage, _ selector: String) -> NSToolbarItem { let toolbarItem = RSToolbarItem(itemIdentifier: itemIdentifier) toolbarItem.autovalidates = true - + let button = NSButton() button.bezelStyle = .texturedRounded button.image = image button.imageScaling = .scaleProportionallyDown button.action = Selector((selector)) - + toolbarItem.view = button toolbarItem.toolTip = title toolbarItem.label = title @@ -1284,23 +1283,23 @@ private extension MainWindowController { func buildNewSidebarItemMenu() -> NSMenu { let menu = NSMenu() - + 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") newFolderFeedItem.action = Selector(("showAddFolderWindow:")) menu.addItem(newFolderFeedItem) - + return menu } func updateArticleThemeMenu() { let articleThemeMenu = NSMenu() - + let defaultThemeItem = NSMenuItem() defaultThemeItem.title = ArticleTheme.defaultTheme.name defaultThemeItem.action = #selector(selectArticleTheme(_:)) @@ -1322,4 +1321,3 @@ private extension MainWindowController { } } - diff --git a/Mac/MainWindow/NNW3/NNW3Document.swift b/Mac/MainWindow/NNW3/NNW3Document.swift index 9d7fbed7f..2fb3b4e32 100644 --- a/Mac/MainWindow/NNW3/NNW3Document.swift +++ b/Mac/MainWindow/NNW3/NNW3Document.swift @@ -141,4 +141,3 @@ extension NNW3Feed: OPMLRepresentable { return s } } - diff --git a/Mac/MainWindow/NNW3/NNW3ImportController.swift b/Mac/MainWindow/NNW3/NNW3ImportController.swift index c54a77c76..bdeb7e466 100644 --- a/Mac/MainWindow/NNW3/NNW3ImportController.swift +++ b/Mac/MainWindow/NNW3/NNW3ImportController.swift @@ -74,7 +74,7 @@ private extension NNW3ImportController { panel.accessoryView = accessoryViewController.view panel.isAccessoryViewDisclosed = true panel.title = NSLocalizedString("Choose a Subscriptions.plist file:", comment: "NNW3 Import") - + panel.beginSheetModal(for: window) { modalResult in guard modalResult == .OK, let subscriptionsPlistURL = panel.url else { return diff --git a/Mac/MainWindow/NNW3/NNW3OpenPanelAccessoryViewController.swift b/Mac/MainWindow/NNW3/NNW3OpenPanelAccessoryViewController.swift index 110377b11..c6a53edb1 100644 --- a/Mac/MainWindow/NNW3/NNW3OpenPanelAccessoryViewController.swift +++ b/Mac/MainWindow/NNW3/NNW3OpenPanelAccessoryViewController.swift @@ -22,7 +22,7 @@ final class NNW3OpenPanelAccessoryViewController: NSViewController { } // MARK: - NSViewController - + required init?(coder: NSCoder) { preconditionFailure("NNW3OpenPanelAccessoryViewController.init(coder) not implemented by design.") } diff --git a/Mac/MainWindow/OPML/ExportOPMLWindowController.swift b/Mac/MainWindow/OPML/ExportOPMLWindowController.swift index 2d2ac30cf..3fe900e5a 100644 --- a/Mac/MainWindow/OPML/ExportOPMLWindowController.swift +++ b/Mac/MainWindow/OPML/ExportOPMLWindowController.swift @@ -14,24 +14,24 @@ class ExportOPMLWindowController: NSWindowController { @IBOutlet weak var accountPopUpButton: NSPopUpButton! private weak var hostWindow: NSWindow? - + convenience init() { self.init(windowNibName: NSNib.Name("ExportOPMLSheet")) } - + override func windowDidLoad() { accountPopUpButton.removeAllItems() let menu = NSMenu() accountPopUpButton.menu = menu - + for oneAccount in AccountManager.shared.sortedAccounts { - + let oneMenuItem = NSMenuItem() oneMenuItem.title = oneAccount.nameForDisplay oneMenuItem.representedObject = oneAccount menu.addItem(oneMenuItem) - + if oneAccount.accountID == AppDefaults.shared.exportOPMLAccountID { accountPopUpButton.select(oneMenuItem) } @@ -40,26 +40,26 @@ class ExportOPMLWindowController: NSWindowController { } // MARK: API - + func runSheetOnWindow(_ hostWindow: NSWindow) { - + self.hostWindow = hostWindow - + if AccountManager.shared.accounts.count == 1 { let account = AccountManager.shared.accounts.first! exportOPML(account: account) } else { hostWindow.beginSheet(window!) } - + } - + // MARK: Actions - + @IBAction func cancel(_ sender: Any) { hostWindow!.endSheet(window!, returnCode: NSApplication.ModalResponse.cancel) } - + @IBAction func exportOPML(_ sender: Any) { guard let menuItem = accountPopUpButton.selectedItem else { @@ -70,11 +70,11 @@ class ExportOPMLWindowController: NSWindowController { AppDefaults.shared.exportOPMLAccountID = account.accountID hostWindow!.endSheet(window!, returnCode: NSApplication.ModalResponse.OK) exportOPML(account: account) - + } - + func exportOPML(account: Account) { - + let panel = NSSavePanel() panel.allowedContentTypes = [UTType.opml] panel.allowsOtherFileTypes = false @@ -83,10 +83,10 @@ class ExportOPMLWindowController: NSWindowController { panel.nameFieldLabel = NSLocalizedString("Export to:", comment: "Export OPML") panel.message = NSLocalizedString("Choose a location for the exported OPML file.", comment: "Export OPML") panel.isExtensionHidden = false - + let accountName = account.nameForDisplay.replacingOccurrences(of: " ", with: "").trimmingCharacters(in: .whitespaces) panel.nameFieldStringValue = "Subscriptions-\(accountName).opml" - + panel.beginSheetModal(for: hostWindow!) { result in if result == NSApplication.ModalResponse.OK, let url = panel.url { DispatchQueue.main.async { @@ -94,14 +94,13 @@ class ExportOPMLWindowController: NSWindowController { let opmlString = OPMLExporter.OPMLString(with: account, title: filename) do { try opmlString.write(to: url, atomically: true, encoding: String.Encoding.utf8) - } - catch let error as NSError { + } catch let error as NSError { NSApplication.shared.presentError(error) } } } } - + } - + } diff --git a/Mac/MainWindow/OPML/ImportOPMLWindowController.swift b/Mac/MainWindow/OPML/ImportOPMLWindowController.swift index 4606d2a22..7cfd938cf 100644 --- a/Mac/MainWindow/OPML/ImportOPMLWindowController.swift +++ b/Mac/MainWindow/OPML/ImportOPMLWindowController.swift @@ -14,71 +14,71 @@ class ImportOPMLWindowController: NSWindowController { @IBOutlet weak var accountPopUpButton: NSPopUpButton! private weak var hostWindow: NSWindow? - + convenience init() { self.init(windowNibName: NSNib.Name("ImportOPMLSheet")) } - + override func windowDidLoad() { accountPopUpButton.removeAllItems() - + let menu = NSMenu() accountPopUpButton.menu = menu for oneAccount in AccountManager.shared.sortedActiveAccounts { - + if oneAccount.behaviors.contains(.disallowOPMLImports) { continue } - + let oneMenuItem = NSMenuItem() oneMenuItem.title = oneAccount.nameForDisplay oneMenuItem.representedObject = oneAccount menu.addItem(oneMenuItem) - + if oneAccount.accountID == AppDefaults.shared.importOPMLAccountID { accountPopUpButton.select(oneMenuItem) } - + } } - + // MARK: API - + func runSheetOnWindow(_ hostWindow: NSWindow) { - + self.hostWindow = hostWindow - + if AccountManager.shared.activeAccounts.count == 1 { let account = AccountManager.shared.activeAccounts.first! importOPML(account: account) } else { hostWindow.beginSheet(window!) } - + } - + // MARK: Actions - + @IBAction func cancel(_ sender: Any) { hostWindow!.endSheet(window!, returnCode: NSApplication.ModalResponse.cancel) } - + @IBAction func importOPML(_ sender: Any) { guard let menuItem = accountPopUpButton.selectedItem else { return } - + let account = menuItem.representedObject as! Account AppDefaults.shared.importOPMLAccountID = account.accountID hostWindow!.endSheet(window!, returnCode: NSApplication.ModalResponse.OK) importOPML(account: account) - + } - + func importOPML(account: Account) { - + let panel = NSOpenPanel() panel.canDownloadUbiquitousContents = true panel.canResolveUbiquitousConflicts = true @@ -88,7 +88,7 @@ class ImportOPMLWindowController: NSWindowController { panel.resolvesAliases = true panel.allowedContentTypes = [UTType.opml, UTType.xml] panel.allowsOtherFileTypes = false - + panel.beginSheetModal(for: hostWindow!) { modalResult in if modalResult == NSApplication.ModalResponse.OK, let url = panel.url { account.importOPML(url) { result in @@ -101,8 +101,7 @@ class ImportOPMLWindowController: NSWindowController { } } } - + } } - diff --git a/Mac/MainWindow/SharingServiceDelegate.swift b/Mac/MainWindow/SharingServiceDelegate.swift index 01e78fc3f..298616d33 100644 --- a/Mac/MainWindow/SharingServiceDelegate.swift +++ b/Mac/MainWindow/SharingServiceDelegate.swift @@ -11,7 +11,7 @@ import AppKit @objc final class SharingServiceDelegate: NSObject, NSSharingServiceDelegate { weak var window: NSWindow? - + init(_ window: NSWindow?) { self.window = window } @@ -24,9 +24,9 @@ import AppKit } .joined(separator: ", ") } - + func sharingService(_ sharingService: NSSharingService, sourceWindowForShareItems items: [Any], sharingContentScope: UnsafeMutablePointer) -> NSWindow? { return window } - + } diff --git a/Mac/MainWindow/SharingServicePickerDelegate.swift b/Mac/MainWindow/SharingServicePickerDelegate.swift index b95793e2e..9a1d42e98 100644 --- a/Mac/MainWindow/SharingServicePickerDelegate.swift +++ b/Mac/MainWindow/SharingServicePickerDelegate.swift @@ -10,13 +10,13 @@ import AppKit import RSCore @objc final class SharingServicePickerDelegate: NSObject, NSSharingServicePickerDelegate { - + private let sharingServiceDelegate: SharingServiceDelegate - + init(_ window: NSWindow?) { sharingServiceDelegate = SharingServiceDelegate(window) } - + func sharingServicePicker(_ sharingServicePicker: NSSharingServicePicker, sharingServicesForItems items: [Any], proposedSharingServices proposedServices: [NSSharingService]) -> [NSSharingService] { let filteredServices = proposedServices.filter { $0.menuItemTitle != "NetNewsWire" } return filteredServices + SharingServicePickerDelegate.customSharingServices(for: items) @@ -34,7 +34,7 @@ import RSCore guard let object = items.first else { return nil } - + guard sendToCommand.canSendObject(object, selectedText: nil) else { return nil } diff --git a/Mac/MainWindow/Sidebar/Cell/SidebarCell.swift b/Mac/MainWindow/Sidebar/Cell/SidebarCell.swift index 5be6545a6..b8f972ca1 100644 --- a/Mac/MainWindow/Sidebar/Cell/SidebarCell.swift +++ b/Mac/MainWindow/Sidebar/Cell/SidebarCell.swift @@ -11,7 +11,7 @@ import RSCore import Account import RSTree -class SidebarCell : NSTableCellView { +class SidebarCell: NSTableCellView { var iconImage: IconImage? { didSet { @@ -73,14 +73,14 @@ class SidebarCell : NSTableCellView { }() private let faviconImageView = IconView() - private let unreadCountView = UnreadCountView(frame: NSZeroRect) + private let unreadCountView = UnreadCountView(frame: NSRect.zero) override var backgroundStyle: NSView.BackgroundStyle { didSet { updateFaviconImage() } } - + override var isFlipped: Bool { return true } @@ -89,8 +89,8 @@ class SidebarCell : NSTableCellView { super.init(frame: frameRect) commonInit() } - - required init?(coder: NSCoder) { + + required init?(coder: NSCoder) { super.init(coder: coder) commonInit() } @@ -99,7 +99,7 @@ class SidebarCell : NSTableCellView { if let cellAppearance = cellAppearance { titleView.font = cellAppearance.textFieldFont } - resizeSubviews(withOldSize: NSZeroSize) + resizeSubviews(withOldSize: NSSize.zero) } override func resizeSubviews(withOldSize oldSize: NSSize) { @@ -138,10 +138,10 @@ private extension SidebarCell { titleView.setFrame(ifNotEqualTo: layout.titleRect) unreadCountView.setFrame(ifNotEqualTo: layout.unreadCountRect) } - + func updateFaviconImage() { var updatedIconImage = iconImage - + if let iconImage = iconImage, iconImage.isSymbol { if backgroundStyle != .normal { let image = iconImage.image.tinted(with: .white) @@ -163,6 +163,5 @@ private extension SidebarCell { faviconImageView.iconImage = nil } } - -} +} diff --git a/Mac/MainWindow/Sidebar/Cell/SidebarCellAppearance.swift b/Mac/MainWindow/Sidebar/Cell/SidebarCellAppearance.swift index c13996732..fdf0dd96e 100644 --- a/Mac/MainWindow/Sidebar/Cell/SidebarCellAppearance.swift +++ b/Mac/MainWindow/Sidebar/Cell/SidebarCellAppearance.swift @@ -28,8 +28,7 @@ struct SidebarCellAppearance: Equatable { imageSize = CGSize(width: 19, height: 19) textFieldFontSize = 13 } - + self.textFieldFont = NSFont.systemFont(ofSize: textFieldFontSize) } } - diff --git a/Mac/MainWindow/Sidebar/Cell/SidebarCellLayout.swift b/Mac/MainWindow/Sidebar/Cell/SidebarCellLayout.swift index e054b924a..a55deed9a 100644 --- a/Mac/MainWindow/Sidebar/Cell/SidebarCellLayout.swift +++ b/Mac/MainWindow/Sidebar/Cell/SidebarCellLayout.swift @@ -16,7 +16,7 @@ struct SidebarCellLayout { let faviconRect: CGRect let titleRect: CGRect let unreadCountRect: CGRect - + init(appearance: SidebarCellAppearance, cellSize: NSSize, shouldShowImage: Bool, textField: NSTextField, unreadCountView: UnreadCountView) { let bounds = NSRect(x: 0.0, y: 0.0, width: floor(cellSize.width), height: floor(cellSize.height)) @@ -32,7 +32,7 @@ struct SidebarCellLayout { var rTextField = NSRect(x: 0.0, y: 0.0, width: textFieldSize.width, height: textFieldSize.height) if shouldShowImage { - rTextField.origin.x = NSMaxX(rFavicon) + appearance.imageMarginRight + rTextField.origin.x = rFavicon.maxX + appearance.imageMarginRight } rTextField = rTextField.centeredVertically(in: bounds) @@ -42,17 +42,17 @@ struct SidebarCellLayout { var rUnread = NSRect.zero if !unreadCountIsHidden { rUnread.size = unreadCountSize - rUnread.origin.x = NSMaxX(bounds) - unreadCountSize.width + rUnread.origin.x = bounds.maxX - unreadCountSize.width rUnread = rUnread.centeredVertically(in: bounds) - let textFieldMaxX = NSMinX(rUnread) - appearance.unreadCountMarginLeft - if NSMaxX(rTextField) > textFieldMaxX { - rTextField.size.width = textFieldMaxX - NSMinX(rTextField) + let textFieldMaxX = rUnread.minX - appearance.unreadCountMarginLeft + if rTextField.maxX > textFieldMaxX { + rTextField.size.width = textFieldMaxX - rTextField.minX } } self.unreadCountRect = rUnread - if NSMaxX(rTextField) > NSMaxX(bounds) { - rTextField.size.width = NSMaxX(bounds) - NSMinX(rTextField) + if rTextField.maxX > bounds.maxX { + rTextField.size.width = bounds.maxX - rTextField.minX } self.titleRect = rTextField } diff --git a/Mac/MainWindow/Sidebar/Keyboard/SidebarKeyboardDelegate.swift b/Mac/MainWindow/Sidebar/Keyboard/SidebarKeyboardDelegate.swift index 26d5a299d..7db6fd9e1 100644 --- a/Mac/MainWindow/Sidebar/Keyboard/SidebarKeyboardDelegate.swift +++ b/Mac/MainWindow/Sidebar/Keyboard/SidebarKeyboardDelegate.swift @@ -23,7 +23,7 @@ import RSCore super.init() } - + func keydown(_ event: NSEvent, in view: NSView) -> Bool { if MainWindowKeyboardHandler.shared.keydown(event, in: view) { @@ -39,4 +39,3 @@ import RSCore return true } } - diff --git a/Mac/MainWindow/Sidebar/PasteboardFeed.swift b/Mac/MainWindow/Sidebar/PasteboardFeed.swift index 074dc32b2..49e96c105 100644 --- a/Mac/MainWindow/Sidebar/PasteboardFeed.swift +++ b/Mac/MainWindow/Sidebar/PasteboardFeed.swift @@ -60,11 +60,11 @@ struct PasteboardFeed: Hashable { let feedID = dictionary[Key.feedID] let editedName = dictionary[Key.editedName] - var accountType: AccountType? = nil + var accountType: AccountType? if let accountTypeString = dictionary[Key.accountType], let accountTypeInt = Int(accountTypeString) { accountType = AccountType(rawValue: accountTypeInt) } - + self.init(url: url, feedID: feedID, homePageURL: homePageURL, name: name, editedName: editedName, accountID: accountID, accountType: accountType) } @@ -72,8 +72,7 @@ struct PasteboardFeed: Hashable { var pasteboardType: NSPasteboard.PasteboardType? if pasteboardItem.types.contains(FeedPasteboardWriter.feedUTIInternalType) { pasteboardType = FeedPasteboardWriter.feedUTIInternalType - } - else if pasteboardItem.types.contains(FeedPasteboardWriter.feedUTIType) { + } else if pasteboardItem.types.contains(FeedPasteboardWriter.feedUTIType) { pasteboardType = FeedPasteboardWriter.feedUTIType } if let foundType = pasteboardType { @@ -87,8 +86,7 @@ struct PasteboardFeed: Hashable { // Check for URL or a string that may be a URL. if pasteboardItem.types.contains(.URL) { pasteboardType = .URL - } - else if pasteboardItem.types.contains(.string) { + } else if pasteboardItem.types.contains(.string) { pasteboardType = .string } if let foundType = pasteboardType { @@ -161,7 +159,6 @@ extension Feed: @retroactive PasteboardWriterOwner { static let feedUTIInternal = "com.ranchero.NetNewsWire-Evergreen.internal.feed" static let feedUTIInternalType = NSPasteboard.PasteboardType(rawValue: feedUTIInternal) - init(feed: Feed) { self.feed = feed } diff --git a/Mac/MainWindow/Sidebar/PasteboardFolder.swift b/Mac/MainWindow/Sidebar/PasteboardFolder.swift index 0d8f1cc33..5b3c51034 100644 --- a/Mac/MainWindow/Sidebar/PasteboardFolder.swift +++ b/Mac/MainWindow/Sidebar/PasteboardFolder.swift @@ -13,7 +13,7 @@ import RSCore typealias PasteboardFolderDictionary = [String: String] struct PasteboardFolder: Hashable { - + private struct Key { static let name = "name" // Internal @@ -21,30 +21,29 @@ struct PasteboardFolder: Hashable { static let accountID = "accountID" } - let name: String let folderID: String? let accountID: String? - + init(name: String, folderID: String?, accountID: String?) { self.name = name self.folderID = folderID self.accountID = accountID } - + // MARK: - Reading - + init?(dictionary: PasteboardFolderDictionary) { guard let name = dictionary[Key.name] else { return nil } - + let folderID = dictionary[Key.folderID] let accountID = dictionary[Key.accountID] - + self.init(name: name, folderID: folderID, accountID: accountID) } - + init?(pasteboardItem: NSPasteboardItem) { var pasteboardType: NSPasteboard.PasteboardType? if pasteboardItem.types.contains(FolderPasteboardWriter.folderUTIInternalType) { @@ -57,10 +56,10 @@ struct PasteboardFolder: Hashable { return } } - + return nil } - + static func pasteboardFolders(with pasteboard: NSPasteboard) -> Set? { guard let items = pasteboard.pasteboardItems else { return nil @@ -68,9 +67,9 @@ struct PasteboardFolder: Hashable { let folders = items.compactMap { PasteboardFolder(pasteboardItem: $0) } return folders.isEmpty ? nil : Set(folders) } - + // MARK: - Writing - + func internalDictionary() -> PasteboardFolderDictionary { var d = PasteboardFeedDictionary() d[PasteboardFolder.Key.name] = name @@ -130,7 +129,7 @@ private extension FolderPasteboardWriter { var pasteboardFolder: PasteboardFolder { return PasteboardFolder(name: folder.name ?? "", folderID: String(folder.folderID), accountID: folder.account?.accountID) } - + var internalDictionary: PasteboardFeedDictionary { return pasteboardFolder.internalDictionary() } diff --git a/Mac/MainWindow/Sidebar/Renaming/RenameWindowController.swift b/Mac/MainWindow/Sidebar/Renaming/RenameWindowController.swift index 5cb8b6309..229a791ad 100644 --- a/Mac/MainWindow/Sidebar/Renaming/RenameWindowController.swift +++ b/Mac/MainWindow/Sidebar/Renaming/RenameWindowController.swift @@ -18,7 +18,7 @@ final class RenameWindowController: NSWindowController { @IBOutlet var renamePrompt: NSTextField! @IBOutlet var newTitleTextField: NSTextField! @IBOutlet var renameButton: NSButton! - + private var originalTitle: String? private var representedObject: Any? private var delegate: RenameWindowControllerDelegate? diff --git a/Mac/MainWindow/Sidebar/SidebarDeleteItemsAlert.swift b/Mac/MainWindow/Sidebar/SidebarDeleteItemsAlert.swift index 96d325044..3e1630bfa 100644 --- a/Mac/MainWindow/Sidebar/SidebarDeleteItemsAlert.swift +++ b/Mac/MainWindow/Sidebar/SidebarDeleteItemsAlert.swift @@ -11,12 +11,12 @@ import RSTree import Account enum SidebarDeleteItemsAlert { - + /// Builds a delete confirmation dialog for the supplied nodes static func build(_ nodes: [Node]) -> NSAlert { let alert = NSAlert() alert.alertStyle = .warning - + if nodes.count == 1 { if let folder = nodes.first?.representedObject as? Folder { alert.messageText = NSLocalizedString("Delete Folder", comment: "Delete Folder") @@ -32,11 +32,11 @@ enum SidebarDeleteItemsAlert { let localizedInformativeText = NSLocalizedString("Are you sure you want to delete the %d selected items?", comment: "Items delete text") alert.informativeText = NSString.localizedStringWithFormat(localizedInformativeText as NSString, nodes.count) as String } - + alert.addButton(withTitle: NSLocalizedString("Delete", comment: "Delete Account")) alert.addButton(withTitle: NSLocalizedString("Cancel", comment: "Cancel Delete Account")) - + return alert } - + } diff --git a/Mac/MainWindow/Sidebar/SidebarOutlineDataSource.swift b/Mac/MainWindow/Sidebar/SidebarOutlineDataSource.swift index 81c9ed6a3..608fd39fd 100644 --- a/Mac/MainWindow/Sidebar/SidebarOutlineDataSource.swift +++ b/Mac/MainWindow/Sidebar/SidebarOutlineDataSource.swift @@ -16,7 +16,7 @@ import Account let treeController: TreeController static let dragOperationNone = NSDragOperation(rawValue: 0) - private var draggedNodes: Set? = nil + private var draggedNodes: Set? init(treeController: TreeController) { self.treeController = treeController @@ -56,7 +56,7 @@ import Account func outlineView(_ outlineView: NSOutlineView, validateDrop info: NSDraggingInfo, proposedItem item: Any?, proposedChildIndex index: Int) -> NSDragOperation { let draggedFolders = PasteboardFolder.pasteboardFolders(with: info.draggingPasteboard) let draggedFeeds = PasteboardFeed.pasteboardFeeds(with: info.draggingPasteboard) - if (draggedFolders == nil && draggedFeeds == nil) || (draggedFolders != nil && draggedFeeds != nil) { + if (draggedFolders == nil && draggedFeeds == nil) || (draggedFolders != nil && draggedFeeds != nil) { return SidebarOutlineDataSource.dragOperationNone } let parentNode = nodeForItem(item) @@ -68,7 +68,7 @@ import Account return validateLocalFoldersDrop(outlineView, draggedFolders, parentNode, index) } } - + if let draggedFeeds = draggedFeeds { let contentsType = draggedFeedContentsType(draggedFeeds) @@ -88,11 +88,11 @@ import Account return SidebarOutlineDataSource.dragOperationNone } - + func outlineView(_ outlineView: NSOutlineView, acceptDrop info: NSDraggingInfo, item: Any?, childIndex index: Int) -> Bool { let draggedFolders = PasteboardFolder.pasteboardFolders(with: info.draggingPasteboard) let draggedFeeds = PasteboardFeed.pasteboardFeeds(with: info.draggingPasteboard) - if (draggedFolders == nil && draggedFeeds == nil) || (draggedFolders != nil && draggedFeeds != nil) { + if (draggedFolders == nil && draggedFeeds == nil) || (draggedFolders != nil && draggedFeeds != nil) { return false } let parentNode = nodeForItem(item) @@ -100,7 +100,7 @@ import Account if let draggedFolders = draggedFolders { return acceptLocalFoldersDrop(outlineView, draggedFolders, parentNode, index) } - + if let draggedFeeds = draggedFeeds { let contentsType = draggedFeedContentsType(draggedFeeds) @@ -116,7 +116,7 @@ import Account return false } } - + return false } } @@ -159,8 +159,7 @@ private extension SidebarOutlineDataSource { for feed in draggedFeeds { if feed.isLocalFeed { hasLocalFeed = true - } - else { + } else { hasNonLocalFeed = true } if hasLocalFeed && hasNonLocalFeed { @@ -228,7 +227,7 @@ private extension SidebarOutlineDataSource { } return localDragOperation(parentNode: parentNode) } - + func localDragOperation(parentNode: Node) -> NSDragOperation { guard let firstDraggedNode = draggedNodes?.first else { return .move } if sameAccount(firstDraggedNode, parentNode) { @@ -275,7 +274,7 @@ private extension SidebarOutlineDataSource { } return false } - + func validateLocalFolderDrop(_ outlineView: NSOutlineView, _ draggedFolder: PasteboardFolder, _ parentNode: Node, _ index: Int) -> NSDragOperation { guard let dropAccount = parentNode.representedObject as? Account, dropAccount.accountID != draggedFolder.accountID else { return SidebarOutlineDataSource.dragOperationNone @@ -289,7 +288,7 @@ private extension SidebarOutlineDataSource { } return localDragOperation(parentNode: parentNode) } - + func validateLocalFoldersDrop(_ outlineView: NSOutlineView, _ draggedFolders: Set, _ parentNode: Node, _ index: Int) -> NSDragOperation { guard let dropAccount = parentNode.representedObject as? Account else { return SidebarOutlineDataSource.dragOperationNone @@ -307,12 +306,12 @@ private extension SidebarOutlineDataSource { } return localDragOperation(parentNode: parentNode) } - + func copyFeedInAccount(node: Node, to parentNode: Node) { guard let feed = node.representedObject as? Feed, let destination = parentNode.representedObject as? Container else { return } - + destination.account?.addFeed(feed, to: destination) { result in switch result { case .success: @@ -348,7 +347,7 @@ private extension SidebarOutlineDataSource { let destinationContainer = parentNode.representedObject as? Container else { return } - + if let existingFeed = destinationAccount.existingFeed(withURL: feed.url) { destinationAccount.addFeed(existingFeed, to: destinationContainer) { result in switch result { @@ -386,7 +385,7 @@ private extension SidebarOutlineDataSource { copyFeedBetweenAccounts(node: node, to: parentNode) } } - + return true } @@ -463,13 +462,13 @@ private extension SidebarOutlineDataSource { guard let draggedNodes = draggedNodes else { return false } - + for node in draggedNodes { if !sameAccount(node, parentNode) { copyFolderBetweenAccounts(node: node, to: parentNode) } } - + return true } @@ -486,7 +485,7 @@ private extension SidebarOutlineDataSource { let folder = parentNode.representedObject as? Folder appDelegate.addFeed(draggedFeed.url, name: draggedFeed.editedName ?? draggedFeed.name, account: account, folder: folder) } - + return true } @@ -505,7 +504,7 @@ private extension SidebarOutlineDataSource { } return false } - + func sameAccount(_ node: Node, _ parentNode: Node) -> Bool { if let accountID = nodeAccountID(node), let parentAccountID = nodeAccountID(parentNode) { if accountID == parentAccountID { @@ -514,7 +513,7 @@ private extension SidebarOutlineDataSource { } return false } - + func nodeAccount(_ node: Node) -> Account? { if let account = node.representedObject as? Account { return account @@ -527,11 +526,11 @@ private extension SidebarOutlineDataSource { } } - + func nodeAccountID(_ node: Node) -> String? { return nodeAccount(node)?.accountID } - + func nodeHasChildRepresentingAnyDraggedFeed(_ parentNode: Node, _ draggedFeeds: Set) -> Bool { for node in parentNode.childNodes { if nodeRepresentsAnyDraggedFeed(node, draggedFeeds) { @@ -544,7 +543,7 @@ private extension SidebarOutlineDataSource { func violatesAccountSpecificBehavior(_ dropTargetNode: Node, _ draggedFeed: PasteboardFeed) -> Bool { return violatesAccountSpecificBehavior(dropTargetNode, Set([draggedFeed])) } - + func violatesAccountSpecificBehavior(_ dropTargetNode: Node, _ draggedFeeds: Set) -> Bool { if violatesDisallowFeedInRootFolder(dropTargetNode) { return true @@ -553,23 +552,23 @@ private extension SidebarOutlineDataSource { if violatesDisallowFeedCopyInRootFolder(dropTargetNode, draggedFeeds) { return true } - + if violatesDisallowFeedInMultipleFolders(dropTargetNode, draggedFeeds) { return true } - + return false } - + func violatesDisallowFeedInRootFolder(_ dropTargetNode: Node) -> Bool { guard let parentAccount = nodeAccount(dropTargetNode), parentAccount.behaviors.contains(.disallowFeedInRootFolder) else { return false } - + if dropTargetNode.representedObject is Account { return true } - + return false } @@ -577,17 +576,17 @@ private extension SidebarOutlineDataSource { guard let dropTargetAccount = nodeAccount(dropTargetNode), dropTargetAccount.behaviors.contains(.disallowFeedCopyInRootFolder) else { return false } - + for draggedFeed in draggedFeeds { if dropTargetAccount.accountID != draggedFeed.accountID { return false } } - + if dropTargetNode.representedObject is Account && (NSApplication.shared.currentEvent?.modifierFlags.contains(.option) ?? false) { return true } - + return false } @@ -595,7 +594,7 @@ private extension SidebarOutlineDataSource { guard let dropTargetAccount = nodeAccount(dropTargetNode), dropTargetAccount.behaviors.contains(.disallowFeedInMultipleFolders) else { return false } - + for draggedFeed in draggedFeeds { if dropTargetAccount.accountID == draggedFeed.accountID { if NSApplication.shared.currentEvent?.modifierFlags.contains(.option) ?? false { @@ -607,7 +606,7 @@ private extension SidebarOutlineDataSource { } } } - + return false } @@ -627,7 +626,7 @@ private extension SidebarOutlineDataSource { let draggedFolderNode = Node(representedObject: draggedFolderWrapper, parent: nil) draggedFolderNode.canHaveChildNodes = true let nodes = parentNode.childNodes + [draggedFolderNode] - + // Revisit if the tree controller can ever be sorted in some other way. let sortedNodes = nodes.sortedAlphabeticallyWithFoldersAtEnd() let index = sortedNodes.firstIndex(of: draggedFolderNode)! @@ -648,12 +647,12 @@ final class PasteboardFeedObjectWrapper: DisplayNameProvider { } final class PasteboardFolderObjectWrapper: DisplayNameProvider { - + var nameForDisplay: String { return pasteboardFolder.name } let pasteboardFolder: PasteboardFolder - + init(pasteboardFolder: PasteboardFolder) { self.pasteboardFolder = pasteboardFolder } diff --git a/Mac/MainWindow/Sidebar/SidebarOutlineView.swift b/Mac/MainWindow/Sidebar/SidebarOutlineView.swift index 2be34e87c..55902492d 100644 --- a/Mac/MainWindow/Sidebar/SidebarOutlineView.swift +++ b/Mac/MainWindow/Sidebar/SidebarOutlineView.swift @@ -10,7 +10,7 @@ import AppKit import RSCore import RSTree -class SidebarOutlineView : NSOutlineView { +class SidebarOutlineView: NSOutlineView { @IBOutlet var keyboardDelegate: KeyboardDelegate! @@ -40,15 +40,15 @@ class SidebarOutlineView : NSOutlineView { // MARK: NSView override func viewWillStartLiveResize() { - + if let scrollView = self.enclosingScrollView { scrollView.hasVerticalScroller = false } super.viewWillStartLiveResize() } - + override func viewDidEndLiveResize() { - + if let scrollView = self.enclosingScrollView { scrollView.hasVerticalScroller = true } diff --git a/Mac/MainWindow/Sidebar/SidebarStatusBarView.swift b/Mac/MainWindow/Sidebar/SidebarStatusBarView.swift index d1e9de7dc..a3e9b226d 100644 --- a/Mac/MainWindow/Sidebar/SidebarStatusBarView.swift +++ b/Mac/MainWindow/Sidebar/SidebarStatusBarView.swift @@ -32,8 +32,8 @@ final class SidebarStatusBarView: NSView { let progressLabelFontSize = progressLabel.font?.pointSize ?? 13.0 progressLabel.font = NSFont.monospacedDigitSystemFont(ofSize: progressLabelFontSize, weight: NSFont.Weight.regular) - progressLabel.stringValue = "" - + progressLabel.stringValue = "" + NotificationCenter.default.addObserver(self, selector: #selector(progressDidChange(_:)), name: .combinedRefreshProgressDidChange, object: nil) } diff --git a/Mac/MainWindow/Sidebar/SidebarViewController+ContextualMenus.swift b/Mac/MainWindow/Sidebar/SidebarViewController+ContextualMenus.swift index d76d0091b..435577c0e 100644 --- a/Mac/MainWindow/Sidebar/SidebarViewController+ContextualMenus.swift +++ b/Mac/MainWindow/Sidebar/SidebarViewController+ContextualMenus.swift @@ -68,7 +68,7 @@ extension SidebarViewController { guard let menuItem = sender as? NSMenuItem, let objects = menuItem.representedObject as? [Any] else { return } - + let articles = unreadArticles(for: objects) guard let undoManager = undoManager, let markReadCommand = MarkStatusCommand(initialArticles: Array(articles), markingRead: true, undoManager: undoManager) else { return @@ -80,7 +80,7 @@ extension SidebarViewController { guard let menuItem = sender as? NSMenuItem, let objects = menuItem.representedObject as? [AnyObject] else { return } - + let nodes = objects.compactMap { treeController.nodeInTreeRepresentingObject($0) } let alert = SidebarDeleteItemsAlert.build(nodes) @@ -103,7 +103,7 @@ extension SidebarViewController { } window.beginSheet(renameSheet) } - + @objc func toggleNotificationsFromContextMenu(_ sender: Any?) { guard let item = sender as? NSMenuItem, let feed = item.representedObject as? Feed else { @@ -119,7 +119,7 @@ extension SidebarViewController { NotificationCenter.default.post(Notification(name: .DidUpdateFeedPreferencesFromContextMenu)) } } else { - UNUserNotificationCenter.current().requestAuthorization(options: [.badge, .sound, .alert]) { (granted, error) in + UNUserNotificationCenter.current().requestAuthorization(options: [.badge, .sound, .alert]) { (granted, _) in if granted { DispatchQueue.main.async { if feed.isNotifyAboutNewArticles == nil { feed.isNotifyAboutNewArticles = false } @@ -134,7 +134,7 @@ extension SidebarViewController { } } } - + @objc func toggleArticleExtractorFromContextMenu(_ sender: Any?) { guard let item = sender as? NSMenuItem, let feed = item.representedObject as? Feed else { @@ -144,7 +144,7 @@ extension SidebarViewController { feed.isArticleExtractorAlwaysOn?.toggle() NotificationCenter.default.post(Notification(name: .DidUpdateFeedPreferencesFromContextMenu)) } - + func showNotificationsNotEnabledAlert() { DispatchQueue.main.async { let alert = NSAlert() @@ -163,7 +163,7 @@ extension SidebarViewController { } } } - + } extension SidebarViewController: RenameWindowControllerDelegate { @@ -229,9 +229,9 @@ private extension SidebarViewController { menu.addItem(item) } menu.addItem(NSMenuItem.separator()) - + let notificationText = feed.notificationDisplayName.capitalized - + let notificationMenuItem = menuItem(notificationText, #selector(toggleNotificationsFromContextMenu(_:)), feed) if feed.isNotifyAboutNewArticles == nil || feed.isNotifyAboutNewArticles! == false { notificationMenuItem.state = .off @@ -251,7 +251,7 @@ private extension SidebarViewController { menu.addItem(articleExtractorMenuItem) menu.addItem(NSMenuItem.separator()) - + menu.addItem(renameMenuItem(feed)) menu.addItem(deleteMenuItem([feed])) @@ -362,4 +362,3 @@ private extension SidebarViewController { return articles } } - diff --git a/Mac/MainWindow/Sidebar/SidebarViewController.swift b/Mac/MainWindow/Sidebar/SidebarViewController.swift index 15f50d213..f1541bd8a 100644 --- a/Mac/MainWindow/Sidebar/SidebarViewController.swift +++ b/Mac/MainWindow/Sidebar/SidebarViewController.swift @@ -23,9 +23,9 @@ protocol SidebarDelegate: AnyObject { } @objc class SidebarViewController: NSViewController, NSOutlineViewDelegate, NSMenuDelegate, UndoableCommandRunner { - + @IBOutlet weak var outlineView: NSOutlineView! - + weak var delegate: SidebarDelegate? private let rebuildTreeAndRestoreSelectionQueue = CoalescingQueue(name: "Rebuild Tree Queue", interval: 1.0) @@ -36,7 +36,7 @@ protocol SidebarDelegate: AnyObject { lazy var dataSource: SidebarOutlineDataSource = { return SidebarOutlineDataSource(treeController: treeController) }() - + var isReadFiltered: Bool { get { return treeControllerDelegate.isReadFiltered @@ -89,19 +89,19 @@ protocol SidebarDelegate: AnyObject { } } expandNodes() - + } // MARK: State Restoration - - func saveState(to state: inout [AnyHashable : Any]) { + + func saveState(to state: inout [AnyHashable: Any]) { state[UserInfoKey.readFeedsFilterState] = isReadFiltered state[UserInfoKey.containerExpandedWindowState] = expandedTable.map { $0.userInfo } state[UserInfoKey.selectedFeedsState] = selectedFeeds.compactMap { $0.sidebarItemID?.userInfo } } - - func restoreState(from state: [AnyHashable : Any]) { - + + func restoreState(from state: [AnyHashable: Any]) { + if let containerExpandedWindowState = state[UserInfoKey.containerExpandedWindowState] as? [[AnyHashable: AnyHashable]] { let containerIdentifiers = containerExpandedWindowState.compactMap( { ContainerIdentifier(userInfo: $0) }) expandedTable = Set(containerIdentifiers) @@ -117,7 +117,7 @@ protocol SidebarDelegate: AnyObject { } rebuildTreeAndReloadDataIfNeeded() - + var selectIndexes = IndexSet() func selectFeedsVisitor(node: Node) { @@ -131,12 +131,12 @@ protocol SidebarDelegate: AnyObject { treeController.visitNodes(selectFeedsVisitor(node:)) outlineView.selectRowIndexes(selectIndexes, byExtendingSelection: false) focus() - + if let readFeedsFilterState = state[UserInfoKey.readFeedsFilterState] as? Bool { isReadFiltered = readFeedsFilterState } } - + // MARK: - Notifications @objc func unreadCountDidInitialize(_ notification: Notification) { @@ -152,7 +152,7 @@ protocol SidebarDelegate: AnyObject { guard let representedObject = note.object else { return } - + if let timelineViewController = representedObject as? TimelineViewController { configureUnreadCountForCellsForRepresentedObjects(timelineViewController.representedObjects) } else { @@ -175,7 +175,7 @@ protocol SidebarDelegate: AnyObject { @objc func accountsDidChange(_ notification: Notification) { rebuildTreeAndRestoreSelection() } - + @objc func accountStateDidChange(_ notification: Notification) { rebuildTreeAndRestoreSelection() } @@ -183,7 +183,7 @@ protocol SidebarDelegate: AnyObject { @objc func batchUpdateDidPerform(_ notification: Notification) { rebuildTreeAndRestoreSelection() } - + @objc func userDidAddFeed(_ notification: Notification) { guard let feed = notification.userInfo?[UserInfoKey.feed] else { return @@ -199,7 +199,7 @@ protocol SidebarDelegate: AnyObject { guard let feed = note.userInfo?[UserInfoKey.feed] as? Feed else { return } configureCellsForRepresentedObject(feed) } - + @objc func feedSettingDidChange(_ note: Notification) { guard let feed = note.object as? Feed, let key = note.userInfo?[Feed.FeedSettingUserInfoKey] as? String else { return @@ -228,18 +228,18 @@ protocol SidebarDelegate: AnyObject { self.restoreSelection(to: savedSelection, sendNotificationIfChanged: true) } } - + // MARK: - Actions @IBAction func delete(_ sender: AnyObject?) { let availableSelectedNodes = selectedNodes.filter { !($0.representedObject is PseudoFeed) } - + if availableSelectedNodes.isEmpty { return } - + let alert = SidebarDeleteItemsAlert.build(availableSelectedNodes) - + alert.beginSheetModal(for: view.window!) { [weak self] result in if result == NSApplication.ModalResponse.alertFirstButtonReturn { guard let self = self else { return } @@ -252,7 +252,7 @@ protocol SidebarDelegate: AnyObject { } } } - + @IBAction func doubleClickedSidebar(_ sender: Any?) { guard outlineView.clickedRow == outlineView.selectedRow else { return @@ -300,20 +300,20 @@ protocol SidebarDelegate: AnyObject { } // MARK: - Navigation - + func canGoToNextUnread(wrappingToTop wrapping: Bool = false) -> Bool { if let _ = nextSelectableRowWithUnreadArticle(wrappingToTop: wrapping) { return true } return false } - + func goToNextUnread(wrappingToTop wrapping: Bool = false) { guard let row = nextSelectableRowWithUnreadArticle(wrappingToTop: wrapping) else { assertionFailure("goToNextUnread called before checking if there is a next unread.") return } - + NSCursor.setHiddenUntilMouseMoves(true) outlineView.selectRowIndexes(IndexSet([row]), byExtendingSelection: false) outlineView.scrollTo(row: row) @@ -339,13 +339,13 @@ protocol SidebarDelegate: AnyObject { // If the clickedRow is part of the selected rows, then do a contextual menu for all the selected rows. return contextualMenuForSelectedObjects() } - + let object = node.representedObject return menu(for: [object]) } // MARK: - NSMenuDelegate - + public func menuNeedsUpdate(_ menu: NSMenu) { menu.removeAllItems() guard let contextualMenu = contextualMenuForClickedRows() else { @@ -354,9 +354,8 @@ protocol SidebarDelegate: AnyObject { menu.takeItems(from: contextualMenu) } - // MARK: - NSOutlineViewDelegate - + func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? { let node = item as! Node @@ -398,7 +397,7 @@ protocol SidebarDelegate: AnyObject { func outlineViewSelectionDidChange(_ notification: Notification) { selectionDidChange(selectedObjects.isEmpty ? nil : selectedObjects) } - + func outlineViewItemDidExpand(_ notification: Notification) { guard let node = notification.userInfo?["NSObject"] as? Node, let containerID = (node.representedObject as? ContainerIdentifiable)?.containerID else { @@ -409,7 +408,7 @@ protocol SidebarDelegate: AnyObject { delegate?.sidebarInvalidatedRestorationState(self) } } - + func outlineViewItemDidCollapse(_ notification: Notification) { guard let node = notification.userInfo?["NSObject"] as? Node, let containerID = (node.representedObject as? ContainerIdentifiable)?.containerID else { @@ -420,43 +419,43 @@ protocol SidebarDelegate: AnyObject { delegate?.sidebarInvalidatedRestorationState(self) } } - - //MARK: - Node Manipulation - + + // MARK: - Node Manipulation + func deleteNodes(_ nodes: [Node]) { let nodesToDelete = treeController.normalizedSelectedNodes(nodes) - + guard let undoManager = undoManager, let deleteCommand = DeleteCommand(nodesToDelete: nodesToDelete, treeController: treeController, undoManager: undoManager, errorHandler: ErrorHandler.present) else { return } - + animatingChanges = true outlineView.beginUpdates() - + let indexSetsGroupedByParent = Node.indexSetsGroupedByParent(nodesToDelete) for (parent, indexSet) in indexSetsGroupedByParent { outlineView.removeItems(at: indexSet, inParent: parent.isRoot ? nil : parent, withAnimation: [.slideDown]) } - + outlineView.endUpdates() - + runCommand(deleteCommand) animatingChanges = false } // MARK: - API - + func selectFeed(_ feed: SidebarItem) { if isReadFiltered, let feedID = feed.sidebarItemID { self.treeControllerDelegate.addFilterException(feedID) - + 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) } } - + addTreeControllerToFilterExceptions() rebuildTreeAndRestoreSelection() } @@ -464,7 +463,7 @@ protocol SidebarDelegate: AnyObject { revealAndSelectRepresentedObject(feed as AnyObject) } - func deepLinkRevealAndSelect(for userInfo: [AnyHashable : Any]) { + func deepLinkRevealAndSelect(for userInfo: [AnyHashable: Any]) { guard let accountNode = findAccountNode(userInfo), let feedNode = findFeedNode(userInfo, beginningAt: accountNode), let feed = feedNode.representedObject as? SidebarItem else { @@ -482,7 +481,7 @@ protocol SidebarDelegate: AnyObject { delegate?.sidebarInvalidatedRestorationState(self) rebuildTreeAndRestoreSelection() } - + } // MARK: - NSUserInterfaceValidations @@ -497,21 +496,21 @@ extension SidebarViewController: NSUserInterfaceValidations { } } -//MARK: - Private +// MARK: - Private private extension SidebarViewController { - + var accountNodes: [Account] { return treeController.rootNode.childNodes.compactMap { $0.representedObject as? Account } } - + var selectedNodes: [Node] { if let nodes = outlineView.selectedItems as? [Node] { return nodes } return [Node]() } - + var selectedFeeds: [SidebarItem] { selectedNodes.compactMap { $0.representedObject as? SidebarItem } } @@ -529,13 +528,13 @@ private extension SidebarViewController { } return node.representedObject as? Feed } - + func addAllSelectedToFilterExceptions() { for feed in selectedFeeds { addToFilterExceptionsIfNecessary(feed) } } - + func addToFilterExceptionsIfNecessary(_ feed: SidebarItem?) { if isReadFiltered, let feedID = feed?.sidebarItemID { if feed is PseudoFeed { @@ -552,29 +551,28 @@ private extension SidebarViewController { } } } - + func addParentFolderToFilterExceptions(_ feed: SidebarItem) { guard let node = treeController.rootNode.descendantNodeRepresentingObject(feed as AnyObject), let folder = node.parent?.representedObject as? Folder, let folderFeedID = folder.sidebarItemID else { return } - + treeControllerDelegate.addFilterException(folderFeedID) } - func queueRebuildTreeAndRestoreSelection() { rebuildTreeAndRestoreSelectionQueue.add(self, #selector(rebuildTreeAndRestoreSelection)) } - + @objc func rebuildTreeAndRestoreSelection() { let savedAccounts = accountNodes let savedSelection = selectedNodes - + rebuildTreeAndReloadDataIfNeeded() restoreSelection(to: savedSelection, sendNotificationIfChanged: true) - + // Automatically expand any new or newly active accounts for account in AccountManager.shared.activeAccounts { if !savedAccounts.contains(account) { @@ -583,7 +581,7 @@ private extension SidebarViewController { } } } - + func rebuildTreeAndReloadDataIfNeeded() { if !animatingChanges && !BatchUpdate.shared.isPerforming { addAllSelectedToFilterExceptions() @@ -593,11 +591,11 @@ private extension SidebarViewController { expandNodes() } } - + func expandNodes() { treeController.visitNodes(expandNodesVisitor(node:)) } - + func expandNodesVisitor(node: Node) { if let containerID = (node.representedObject as? ContainerIdentifiable)?.containerID { if expandedTable.contains(containerID) { @@ -607,7 +605,7 @@ private extension SidebarViewController { } } } - + func addTreeControllerToFilterExceptions() { treeController.visitNodes(addTreeControllerToFilterExceptionsVisitor(node:)) } @@ -654,13 +652,13 @@ private extension SidebarViewController { if row < 0 || row >= outlineView.numberOfRows { return nil } - + if let node = outlineView.item(atRow: row) as? Node { return node } return nil } - + func rowHasAtLeastOneUnreadArticle(_ row: Int) -> Bool { if let oneNode = nodeForRow(row) { if let unreadCountProvider = oneNode.representedObject as? UnreadCountProvider { @@ -716,15 +714,15 @@ private extension SidebarViewController { return row } } - + return nil } - func findAccountNode(_ userInfo: [AnyHashable : Any]?) -> Node? { + func findAccountNode(_ userInfo: [AnyHashable: Any]?) -> Node? { guard let accountID = userInfo?[ArticlePathKey.accountID] as? String else { return nil } - + if let node = treeController.rootNode.descendantNode(where: { ($0.representedObject as? Account)?.accountID == accountID }) { return node } @@ -739,8 +737,8 @@ private extension SidebarViewController { return nil } - - func findFeedNode(_ userInfo: [AnyHashable : Any]?, beginningAt startingNode: Node) -> Node? { + + func findFeedNode(_ userInfo: [AnyHashable: Any]?, beginningAt startingNode: Node) -> Node? { guard let feedID = userInfo?[ArticlePathKey.feedID] as? String else { return nil } @@ -749,7 +747,7 @@ private extension SidebarViewController { } return nil } - + func configure(_ cell: SidebarCell, _ node: Node) { cell.cellAppearance = SidebarCellAppearance(rowSizeStyle: outlineView.effectiveRowSizeStyle) cell.name = nameFor(node) @@ -819,7 +817,7 @@ private extension SidebarViewController { } func applyToAvailableCells(_ completion: (SidebarCell, Node) -> Void) { - outlineView.enumerateAvailableRowViews { (rowView: NSTableRowView, row: Int) -> Void in + outlineView.enumerateAvailableRowViews { (rowView: NSTableRowView, row: Int) in guard let cell = cellForRowView(rowView), let node = nodeForRow(row) else { return } @@ -852,7 +850,7 @@ private extension SidebarViewController { func revealAndSelectRepresentedObject(_ representedObject: AnyObject) -> Bool { return outlineView.revealAndSelectRepresentedObject(representedObject, treeController) } - + } private extension Node { diff --git a/Mac/MainWindow/Sidebar/UnreadCountView.swift b/Mac/MainWindow/Sidebar/UnreadCountView.swift index 25ee1c2a4..0918484d7 100644 --- a/Mac/MainWindow/Sidebar/UnreadCountView.swift +++ b/Mac/MainWindow/Sidebar/UnreadCountView.swift @@ -8,7 +8,7 @@ import AppKit -class UnreadCountView : NSView { +class UnreadCountView: NSView { struct Appearance { static let padding = NSEdgeInsets(top: 1.0, left: 7.0, bottom: 1.0, right: 7.0) @@ -31,11 +31,11 @@ class UnreadCountView : NSView { } private var intrinsicContentSizeIsValid = false - private var _intrinsicContentSize = NSZeroSize - + private var _intrinsicContentSize = NSSize.zero + override var intrinsicContentSize: NSSize { if !intrinsicContentSizeIsValid { - var size = NSZeroSize + var size = NSSize.zero if unreadCount > 0 { size = textSize() size.width += (Appearance.padding.left + Appearance.padding.right) @@ -46,11 +46,11 @@ class UnreadCountView : NSView { } return _intrinsicContentSize } - + override var isFlipped: Bool { return true } - + override func invalidateIntrinsicContentSize() { intrinsicContentSizeIsValid = false } @@ -59,7 +59,7 @@ class UnreadCountView : NSView { private func textSize() -> NSSize { if unreadCount < 1 { - return NSZeroSize + return NSSize.zero } if let cachedSize = UnreadCountView.textSizeCache[unreadCount] { @@ -76,9 +76,9 @@ class UnreadCountView : NSView { private func textRect() -> NSRect { let size = textSize() - var r = NSZeroRect + var r = NSRect.zero r.size = size - r.origin.x = (NSMaxX(bounds) - Appearance.padding.right) - r.size.width + r.origin.x = (bounds.maxX - Appearance.padding.right) - r.size.width r.origin.y = Appearance.padding.top return r } @@ -93,4 +93,3 @@ class UnreadCountView : NSView { } } } - diff --git a/Mac/MainWindow/Timeline/ArticlePasteboardWriter.swift b/Mac/MainWindow/Timeline/ArticlePasteboardWriter.swift index 52aa89b2c..28ffb6eaa 100644 --- a/Mac/MainWindow/Timeline/ArticlePasteboardWriter.swift +++ b/Mac/MainWindow/Timeline/ArticlePasteboardWriter.swift @@ -78,11 +78,9 @@ private extension ArticlePasteboardWriter { } if let text = article.contentText { s += "\(text)\n\n" - } - else if let summary = article.summary { + } else if let summary = article.summary { s += "\(summary)\n\n" - } - else if let html = article.contentHTML { + } else if let html = article.contentHTML { let convertedHTML = html.convertingToPlainText() s += "\(convertedHTML)\n\n" } @@ -184,7 +182,6 @@ private extension ArticlePasteboardWriter { guard let authors = article.authors, !authors.isEmpty else { return nil } - return authors.map{ authorDictionary($0) } + return authors.map { authorDictionary($0) } } } - diff --git a/Mac/MainWindow/Timeline/Cell/MultilineTextFieldSizer.swift b/Mac/MainWindow/Timeline/Cell/MultilineTextFieldSizer.swift index 82f49370a..41b0d3754 100644 --- a/Mac/MainWindow/Timeline/Cell/MultilineTextFieldSizer.swift +++ b/Mac/MainWindow/Timeline/Cell/MultilineTextFieldSizer.swift @@ -30,7 +30,7 @@ final class MultilineTextFieldSizer { private let numberOfLines: Int private let font: NSFont - private let textField:NSTextField + private let textField: NSTextField private let singleLineHeightEstimate: Int private let doubleLineHeightEstimate: Int private var cache = [String: WidthHeightCache]() // Each string has a cache. @@ -56,7 +56,7 @@ final class MultilineTextFieldSizer { guard attributedString.length > 0 else { return TextFieldSizeInfo(size: NSSize.zero, numberOfLinesUsed: 0) } - + // Assumes the same font family/size for the whole string let font = attributedString.attribute(.font, at: 0, effectiveRange: nil) as! NSFont @@ -227,8 +227,7 @@ private extension MultilineTextFieldSizer { if oneWidth < width && (oneWidth > smallNeighbor.width || smallNeighbor.width == 0) { smallNeighbor = (oneWidth, oneHeight) - } - else if oneWidth > width && (oneWidth < largeNeighbor.width || largeNeighbor.width == 0) { + } else if oneWidth > width && (oneWidth < largeNeighbor.width || largeNeighbor.width == 0) { largeNeighbor = (oneWidth, oneHeight) } diff --git a/Mac/MainWindow/Timeline/Cell/SingleLineTextFieldSizer.swift b/Mac/MainWindow/Timeline/Cell/SingleLineTextFieldSizer.swift index e40db7235..db598d3fa 100644 --- a/Mac/MainWindow/Timeline/Cell/SingleLineTextFieldSizer.swift +++ b/Mac/MainWindow/Timeline/Cell/SingleLineTextFieldSizer.swift @@ -38,7 +38,7 @@ final class SingleLineTextFieldSizer { var calculatedSize = textField.fittingSize calculatedSize.height = ceil(calculatedSize.height) calculatedSize.width = ceil(calculatedSize.width) - + cache[text] = calculatedSize return calculatedSize } diff --git a/Mac/MainWindow/Timeline/Cell/TimelineCellAppearance.swift b/Mac/MainWindow/Timeline/Cell/TimelineCellAppearance.swift index 83e5a81c5..457919e6c 100644 --- a/Mac/MainWindow/Timeline/Cell/TimelineCellAppearance.swift +++ b/Mac/MainWindow/Timeline/Cell/TimelineCellAppearance.swift @@ -13,7 +13,7 @@ struct TimelineCellAppearance: Equatable { let showIcon: Bool let cellPadding: NSEdgeInsets - + let feedNameFont: NSFont let dateFont: NSFont @@ -22,7 +22,7 @@ struct TimelineCellAppearance: Equatable { let titleFont: NSFont let titleBottomMargin: CGFloat = 1.0 let titleNumberOfLines = 3 - + let textFont: NSFont let textOnlyFont: NSFont @@ -55,7 +55,7 @@ struct TimelineCellAppearance: Equatable { self.textOnlyFont = NSFont.systemFont(ofSize: largeItemFontSize) self.showIcon = showIcon - + cellPadding = NSEdgeInsets(top: 8.0, left: 4.0, bottom: 10.0, right: 4.0) let margin = self.cellPadding.left + self.unreadCircleDimension + self.unreadCircleMarginRight diff --git a/Mac/MainWindow/Timeline/Cell/TimelineCellData.swift b/Mac/MainWindow/Timeline/Cell/TimelineCellData.swift index 53bb79adb..1bca50050 100644 --- a/Mac/MainWindow/Timeline/Cell/TimelineCellData.swift +++ b/Mac/MainWindow/Timeline/Cell/TimelineCellData.swift @@ -10,9 +10,9 @@ import AppKit import Articles struct TimelineCellData { - + private static let noText = NSLocalizedString("(No Text)", comment: "No Text") - + let title: String let attributedTitle: NSAttributedString let text: String @@ -37,7 +37,7 @@ struct TimelineCellData { } else { self.text = truncatedSummary } - + self.dateString = ArticleStringFormatter.dateString(article.logicalDatePublished) if let feedName = feedName { @@ -45,7 +45,7 @@ struct TimelineCellData { } else { self.feedName = "" } - + if let byline = byline { self.byline = byline } else { @@ -57,12 +57,12 @@ struct TimelineCellData { self.showIcon = showIcon self.iconImage = iconImage self.featuredImage = featuredImage - + self.read = article.status.read self.starred = article.status.starred } - init() { //Empty + init() { // Empty self.title = "" self.text = "" self.dateString = "" diff --git a/Mac/MainWindow/Timeline/Cell/TimelineCellLayout.swift b/Mac/MainWindow/Timeline/Cell/TimelineCellLayout.swift index 3c06c1423..de99415e2 100644 --- a/Mac/MainWindow/Timeline/Cell/TimelineCellLayout.swift +++ b/Mac/MainWindow/Timeline/Cell/TimelineCellLayout.swift @@ -10,7 +10,7 @@ import AppKit import RSCore struct TimelineCellLayout { - + let width: CGFloat let height: CGFloat let feedNameRect: NSRect @@ -24,9 +24,9 @@ struct TimelineCellLayout { let iconImageRect: NSRect let separatorRect: NSRect let paddingBottom: CGFloat - + init(width: CGFloat, height: CGFloat, feedNameRect: NSRect, dateRect: NSRect, titleRect: NSRect, numberOfLinesForTitle: Int, summaryRect: NSRect, textRect: NSRect, unreadIndicatorRect: NSRect, starRect: NSRect, iconImageRect: NSRect, separatorRect: NSRect, paddingBottom: CGFloat) { - + self.width = width self.feedNameRect = feedNameRect self.dateRect = dateRect @@ -42,8 +42,7 @@ struct TimelineCellLayout { if height > 0.1 { self.height = height - } - else { + } else { self.height = [feedNameRect, dateRect, titleRect, summaryRect, textRect, unreadIndicatorRect, iconImageRect].maxY() + paddingBottom } } @@ -62,8 +61,7 @@ struct TimelineCellLayout { var lastTextRect = titleRect if numberOfLinesForTitle == 0 { lastTextRect = textRect - } - else if numberOfLinesForTitle < appearance.titleNumberOfLines { + } else if numberOfLinesForTitle < appearance.titleNumberOfLines { if summaryRect.height > 0.1 { lastTextRect = summaryRect } @@ -114,7 +112,7 @@ private extension TimelineCellLayout { r.size.height = 0 return (r, 0) } - + let attributedTitle = cellData.attributedTitle.adding(font: appearance.titleFont) let sizeInfo = MultilineTextFieldSizer.size(for: attributedTitle, numberOfLines: appearance.titleNumberOfLines, width: Int(textBoxRect.width)) r.size.height = sizeInfo.size.height @@ -124,15 +122,15 @@ private extension TimelineCellLayout { return (r, sizeInfo.numberOfLinesUsed) } - static func rectForSummary(_ textBoxRect: NSRect, _ titleRect: NSRect, _ titleNumberOfLines: Int, _ appearance: TimelineCellAppearance, _ cellData: TimelineCellData) -> NSRect { + static func rectForSummary(_ textBoxRect: NSRect, _ titleRect: NSRect, _ titleNumberOfLines: Int, _ appearance: TimelineCellAppearance, _ cellData: TimelineCellData) -> NSRect { if titleNumberOfLines >= appearance.titleNumberOfLines || cellData.text.isEmpty { return NSRect.zero } var r = textBoxRect - r.origin.y = NSMaxY(titleRect) + r.origin.y = titleRect.maxY let summaryNumberOfLines = appearance.titleNumberOfLines - titleNumberOfLines - + let sizeInfo = MultilineTextFieldSizer.size(for: cellData.text, font: appearance.textOnlyFont, numberOfLines: summaryNumberOfLines, width: Int(textBoxRect.width)) r.size.height = sizeInfo.size.height if sizeInfo.numberOfLinesUsed < 1 { @@ -160,10 +158,10 @@ private extension TimelineCellLayout { static func rectForDate(_ textBoxRect: NSRect, _ rectAbove: NSRect, _ appearance: TimelineCellAppearance, _ cellData: TimelineCellData) -> NSRect { let textFieldSize = SingleLineTextFieldSizer.size(for: cellData.dateString, font: appearance.dateFont) - - var r = NSZeroRect + + var r = NSRect.zero r.size = textFieldSize - r.origin.y = NSMaxY(rectAbove) + appearance.titleBottomMargin + r.origin.y = rectAbove.maxY + appearance.titleBottomMargin r.size.width = textFieldSize.width r.origin.x = textBoxRect.maxX - textFieldSize.width @@ -173,22 +171,22 @@ private extension TimelineCellLayout { static func rectForFeedName(_ textBoxRect: NSRect, _ dateRect: NSRect, _ appearance: TimelineCellAppearance, _ cellData: TimelineCellData) -> NSRect { if cellData.showFeedName == .none { - return NSZeroRect + return NSRect.zero } let textFieldSize = SingleLineTextFieldSizer.size(for: cellData.feedName, font: appearance.feedNameFont) - var r = NSZeroRect + var r = NSRect.zero r.size = textFieldSize r.origin.y = dateRect.minY r.origin.x = textBoxRect.origin.x r.size.width = (textBoxRect.maxX - (dateRect.size.width + appearance.dateMarginLeft)) - textBoxRect.origin.x - + return r } static func rectForUnreadIndicator(_ appearance: TimelineCellAppearance, _ titleRect: NSRect) -> NSRect { - var r = NSZeroRect + var r = NSRect.zero r.size = NSSize(width: appearance.unreadCircleDimension, height: appearance.unreadCircleDimension) r.origin.x = appearance.cellPadding.left r.origin.y = titleRect.minY + 6 @@ -220,7 +218,7 @@ private extension TimelineCellLayout { return r } - + static func rectForSeparator(_ cellData: TimelineCellData, _ appearance: TimelineCellAppearance, _ alignmentRect: NSRect, _ width: CGFloat, _ height: CGFloat) -> NSRect { return NSRect(x: alignmentRect.minX, y: height - 1, width: width - alignmentRect.minX, height: 1) } @@ -237,4 +235,3 @@ private extension Array where Element == NSRect { return y } } - diff --git a/Mac/MainWindow/Timeline/Cell/TimelineTableCellView.swift b/Mac/MainWindow/Timeline/Cell/TimelineTableCellView.swift index 24a88abc0..8c46687b8 100644 --- a/Mac/MainWindow/Timeline/Cell/TimelineTableCellView.swift +++ b/Mac/MainWindow/Timeline/Cell/TimelineTableCellView.swift @@ -14,7 +14,7 @@ class TimelineTableCellView: NSTableCellView { private let titleView = TimelineTableCellView.multiLineTextField() private let summaryView = TimelineTableCellView.multiLineTextField() private let textView = TimelineTableCellView.multiLineTextField() - private let unreadIndicatorView = UnreadIndicatorView(frame: NSZeroRect) + private let unreadIndicatorView = UnreadIndicatorView(frame: NSRect.zero) private let dateView = TimelineTableCellView.singleLineTextField() private let feedNameView = TimelineTableCellView.singleLineTextField() @@ -35,13 +35,13 @@ class TimelineTableCellView: NSTableCellView { } } } - + var cellData: TimelineCellData! { didSet { updateSubviews() } } - + var isEmphasized: Bool = false { didSet { unreadIndicatorView.isEmphasized = isEmphasized @@ -55,7 +55,7 @@ class TimelineTableCellView: NSTableCellView { updateStarView() } } - + override var isFlipped: Bool { return true } @@ -64,8 +64,8 @@ class TimelineTableCellView: NSTableCellView { super.init(frame: frameRect) commonInit() } - - required init?(coder: NSCoder) { + + required init?(coder: NSCoder) { super.init(coder: coder) commonInit() } @@ -73,29 +73,29 @@ class TimelineTableCellView: NSTableCellView { convenience init() { self.init(frame: NSRect.zero) } - + override func setFrameSize(_ newSize: NSSize) { - + if newSize == self.frame.size { return } - + super.setFrameSize(newSize) needsLayout = true } override func viewDidMoveToSuperview() { - + updateSubviews() } - + override func layout() { - resizeSubviews(withOldSize: NSZeroSize) + resizeSubviews(withOldSize: NSSize.zero) } - + override func resizeSubviews(withOldSize oldSize: NSSize) { - + let layoutRects = updatedLayoutRects() setFrame(for: titleView, rect: layoutRects.titleRect) @@ -144,13 +144,12 @@ private extension TimelineTableCellView { imageView.imageScaling = scaling return imageView } - + func setFrame(for textField: NSTextField, rect: NSRect) { if Int(floor(rect.height)) == 0 || Int(floor(rect.width)) == 0 { hideView(textField) - } - else { + } else { showView(textField) textField.setFrame(ifNotEqualTo: rect) } diff --git a/Mac/MainWindow/Timeline/Cell/UnreadIndicatorView.swift b/Mac/MainWindow/Timeline/Cell/UnreadIndicatorView.swift index d3b4d7c00..40d9a62f6 100644 --- a/Mac/MainWindow/Timeline/Cell/UnreadIndicatorView.swift +++ b/Mac/MainWindow/Timeline/Cell/UnreadIndicatorView.swift @@ -11,7 +11,7 @@ import AppKit class UnreadIndicatorView: NSView { static let unreadCircleDimension: CGFloat = 8.0 - + var isEmphasized = false { didSet { if isEmphasized != oldValue { @@ -19,7 +19,7 @@ class UnreadIndicatorView: NSView { } } } - + var isSelected = false { didSet { if isSelected != oldValue { @@ -41,5 +41,5 @@ class UnreadIndicatorView: NSView { } UnreadIndicatorView.bezierPath.fill() } - + } diff --git a/Mac/MainWindow/Timeline/TimelineContainerView.swift b/Mac/MainWindow/Timeline/TimelineContainerView.swift index e75475405..7944af3d7 100644 --- a/Mac/MainWindow/Timeline/TimelineContainerView.swift +++ b/Mac/MainWindow/Timeline/TimelineContainerView.swift @@ -34,4 +34,3 @@ final class TimelineContainerView: NSView { } } } - diff --git a/Mac/MainWindow/Timeline/TimelineContainerViewController.swift b/Mac/MainWindow/Timeline/TimelineContainerViewController.swift index b589aa939..6e5021114 100644 --- a/Mac/MainWindow/Timeline/TimelineContainerViewController.swift +++ b/Mac/MainWindow/Timeline/TimelineContainerViewController.swift @@ -23,7 +23,7 @@ final class TimelineContainerViewController: NSViewController { @IBOutlet weak var newestToOldestMenuItem: NSMenuItem! @IBOutlet weak var oldestToNewestMenuItem: NSMenuItem! @IBOutlet weak var groupByFeedMenuItem: NSMenuItem! - + @IBOutlet weak var readFilteredButton: NSButton! @IBOutlet var containerView: TimelineContainerView! @@ -49,7 +49,7 @@ final class TimelineContainerViewController: NSViewController { guard let currentTimelineViewController = currentTimelineViewController, mode(for: currentTimelineViewController) == .regular else { return false } return regularTimelineViewController.isCleanUpAvailable } - + lazy var regularTimelineViewController = { return TimelineViewController(delegate: self) }() @@ -63,21 +63,21 @@ final class TimelineContainerViewController: NSViewController { super.viewDidLoad() setRepresentedObjects(nil, mode: .regular) showTimeline(for: .regular) - + makeMenuItemTitleLarger(newestToOldestMenuItem) makeMenuItemTitleLarger(oldestToNewestMenuItem) makeMenuItemTitleLarger(groupByFeedMenuItem) updateViewOptionsPopUpButton() - + NotificationCenter.default.addObserver(self, selector: #selector(userDefaultsDidChange(_:)), name: UserDefaults.didChangeNotification, object: nil) } - + // MARK: - Notifications - + @objc func userDefaultsDidChange(_ note: Notification) { updateViewOptionsPopUpButton() } - + // MARK: - API func setRepresentedObjects(_ objects: [AnyObject]?, mode: TimelineSourceMode) { @@ -107,29 +107,29 @@ final class TimelineContainerViewController: NSViewController { return false } for object in representedObjects { - guard let _ = currentObjects.firstIndex(where: { $0 === object } ) else { + guard let _ = currentObjects.firstIndex(where: { $0 === object }) else { return false } } return true } - + func cleanUp() { regularTimelineViewController.cleanUp() } - + func toggleReadFilter() { regularTimelineViewController.toggleReadFilter() updateReadFilterButton() } - + // MARK: State Restoration - - func saveState(to state: inout [AnyHashable : Any]) { + + func saveState(to state: inout [AnyHashable: Any]) { regularTimelineViewController.saveState(to: &state) } - - func restoreState(from state: [AnyHashable : Any]) { + + func restoreState(from state: [AnyHashable: Any]) { regularTimelineViewController.restoreState(from: state) updateReadFilterButton() } @@ -144,11 +144,11 @@ extension TimelineContainerViewController: TimelineDelegate { func timelineRequestedFeedSelection(_: TimelineViewController, feed: Feed) { delegate?.timelineRequestedFeedSelection(self, feed: feed) } - + func timelineInvalidatedRestorationState(_: TimelineViewController) { delegate?.timelineInvalidatedRestorationState(self) } - + } private extension TimelineContainerViewController { @@ -157,7 +157,7 @@ private extension TimelineContainerViewController { menuItem.attributedTitle = NSAttributedString(string: menuItem.title, attributes: [NSAttributedString.Key.font: NSFont.controlContentFont(ofSize: NSFont.systemFontSize)]) } - + func timelineViewController(for mode: TimelineSourceMode) -> TimelineViewController { switch mode { case .regular: @@ -170,14 +170,13 @@ private extension TimelineContainerViewController { func mode(for timelineViewController: TimelineViewController) -> TimelineSourceMode { if timelineViewController === regularTimelineViewController { return .regular - } - else if timelineViewController === searchTimelineViewController { + } else if timelineViewController === searchTimelineViewController { return .search } assertionFailure("Expected timelineViewController to match either regular or search timelineViewController, but it doesn’t.") return .regular // Should never get here. } - + func updateViewOptionsPopUpButton() { if AppDefaults.shared.timelineSortDirection == .orderedAscending { newestToOldestMenuItem.state = .off @@ -188,32 +187,32 @@ private extension TimelineContainerViewController { oldestToNewestMenuItem.state = .off viewOptionsPopUpButton.setTitle(newestToOldestMenuItem.title) } - + if AppDefaults.shared.timelineGroupByFeed == true { groupByFeedMenuItem.state = .on } else { groupByFeedMenuItem.state = .off } } - + func updateReadFilterButton() { guard currentTimelineViewController == regularTimelineViewController else { readFilteredButton.isHidden = true return } - + guard let isReadFiltered = regularTimelineViewController.isReadFiltered else { readFilteredButton.isHidden = true return } - + readFilteredButton.isHidden = false - + if isReadFiltered { readFilteredButton.image = AppAssets.filterActive } else { readFilteredButton.image = AppAssets.filterInactive } } - + } diff --git a/Mac/MainWindow/Timeline/TimelineTableRowView.swift b/Mac/MainWindow/Timeline/TimelineTableRowView.swift index d06f39930..5714c2b5c 100644 --- a/Mac/MainWindow/Timeline/TimelineTableRowView.swift +++ b/Mac/MainWindow/Timeline/TimelineTableRowView.swift @@ -8,10 +8,10 @@ import AppKit -class TimelineTableRowView : NSTableRowView { +class TimelineTableRowView: NSTableRowView { private var separator: NSView? - + override var isOpaque: Bool { return true } @@ -21,14 +21,14 @@ class TimelineTableRowView : NSTableRowView { cellView?.isEmphasized = isEmphasized } } - + override var isSelected: Bool { didSet { cellView?.isSelected = isSelected separator?.isHidden = isSelected } } - + init() { super.init(frame: NSRect.zero) } @@ -36,7 +36,7 @@ class TimelineTableRowView : NSTableRowView { required init?(coder: NSCoder) { super.init(coder: coder) } - + private var cellView: TimelineTableCellView? { for oneSubview in subviews { if let foundView = oneSubview as? TimelineTableCellView { @@ -51,7 +51,7 @@ class TimelineTableRowView : NSTableRowView { addSeparatorView() } } - + private func addSeparatorView() { guard let cellView = cellView, separator == nil else { return } separator = NSView() diff --git a/Mac/MainWindow/Timeline/TimelineTableView.swift b/Mac/MainWindow/Timeline/TimelineTableView.swift index e70bbed17..45c193b68 100644 --- a/Mac/MainWindow/Timeline/TimelineTableView.swift +++ b/Mac/MainWindow/Timeline/TimelineTableView.swift @@ -10,15 +10,15 @@ import AppKit import RSCore class TimelineTableView: NSTableView { - + weak var keyboardDelegate: KeyboardDelegate? - + override func accessibilityLabel() -> String? { return NSLocalizedString("Timeline", comment: "Timeline") } - + // MARK: - NSResponder - + override func keyDown(with event: NSEvent) { if keyboardDelegate?.keydown(event, in: self) ?? false { return @@ -31,14 +31,14 @@ class TimelineTableView: NSTableView { override var isOpaque: Bool { return true } - + override func viewWillStartLiveResize() { if let scrollView = self.enclosingScrollView { scrollView.hasVerticalScroller = false } super.viewWillStartLiveResize() } - + override func viewDidEndLiveResize() { if let scrollView = self.enclosingScrollView { scrollView.hasVerticalScroller = true diff --git a/Mac/MainWindow/Timeline/TimelineViewController+ContextualMenus.swift b/Mac/MainWindow/Timeline/TimelineViewController+ContextualMenus.swift index 0a05787e3..85761a27b 100644 --- a/Mac/MainWindow/Timeline/TimelineViewController+ContextualMenus.swift +++ b/Mac/MainWindow/Timeline/TimelineViewController+ContextualMenus.swift @@ -70,19 +70,19 @@ extension TimelineViewController { } delegate?.timelineRequestedFeedSelection(self, feed: feed) } - + @objc func markAllInFeedAsRead(_ sender: Any?) { guard let menuItem = sender as? NSMenuItem, let feedArticles = menuItem.representedObject as? ArticleArray else { return } - + guard let undoManager = undoManager, let markReadCommand = MarkStatusCommand(initialArticles: feedArticles, markingRead: true, undoManager: undoManager) else { return } - + runCommand(markReadCommand) } - + @objc func openInBrowserFromContextualMenu(_ sender: Any?) { guard let menuItem = sender as? NSMenuItem, let urlString = menuItem.representedObject as? String else { @@ -90,7 +90,7 @@ extension TimelineViewController { } Browser.open(urlString, inBackground: false) } - + @objc func copyURLFromContextualMenu(_ sender: Any?) { guard let menuItem = sender as? NSMenuItem, let urlString = menuItem.representedObject as? String else { return @@ -106,7 +106,6 @@ extension TimelineViewController { } } - private extension TimelineViewController { func markArticles(_ articles: [Article], read: Bool) { @@ -162,7 +161,7 @@ private extension TimelineViewController { } menu.addSeparatorIfNeeded() - + if articles.count == 1, let feed = articles.first!.feed { if !(representedObjects?.contains(where: { $0 as? Feed == feed }) ?? false) { menu.addItem(selectFeedInSidebarMenuItem(feed)) @@ -171,13 +170,13 @@ private extension TimelineViewController { menu.addItem(markAllMenuItem) } } - + if articles.count == 1, let link = articles.first!.preferredLink { menu.addSeparatorIfNeeded() menu.addItem(openInBrowserMenuItem(link)) menu.addSeparatorIfNeeded() menu.addItem(copyArticleURLMenuItem(link)) - + if let externalLink = articles.first?.externalLink, externalLink != link { menu.addItem(copyExternalURLMenuItem(externalLink)) } @@ -241,11 +240,11 @@ private extension TimelineViewController { } func markAboveReadMenuItem(_ articles: [Article]) -> NSMenuItem { - return menuItem(NSLocalizedString("Mark Above as Read", comment: "Command"), #selector(markAboveArticlesReadFromContextualMenu(_:)), articles) + return menuItem(NSLocalizedString("Mark Above as Read", comment: "Command"), #selector(markAboveArticlesReadFromContextualMenu(_:)), articles) } - + func markBelowReadMenuItem(_ articles: [Article]) -> NSMenuItem { - return menuItem(NSLocalizedString("Mark Below as Read", comment: "Command"), #selector(markBelowArticlesReadFromContextualMenu(_:)), articles) + return menuItem(NSLocalizedString("Mark Below as Read", comment: "Command"), #selector(markBelowArticlesReadFromContextualMenu(_:)), articles) } func selectFeedInSidebarMenuItem(_ feed: Feed) -> NSMenuItem { @@ -265,24 +264,23 @@ private extension TimelineViewController { let localizedMenuText = NSLocalizedString("Mark All as Read in “%@”", comment: "Command") let menuText = NSString.localizedStringWithFormat(localizedMenuText as NSString, feed.nameForDisplay) as String - + return menuItem(menuText, #selector(markAllInFeedAsRead(_:)), articles) } - + func openInBrowserMenuItem(_ urlString: String) -> NSMenuItem { return menuItem(NSLocalizedString("Open in Browser", comment: "Command"), #selector(openInBrowserFromContextualMenu(_:)), urlString) } - + func copyArticleURLMenuItem(_ urlString: String) -> NSMenuItem { return menuItem(NSLocalizedString("Copy Article URL", comment: "Command"), #selector(copyURLFromContextualMenu(_:)), urlString) } - + func copyExternalURLMenuItem(_ urlString: String) -> NSMenuItem { return menuItem(NSLocalizedString("Copy External URL", comment: "Command"), #selector(copyURLFromContextualMenu(_:)), urlString) } - func menuItem(_ title: String, _ action: Selector, _ representedObject: Any) -> NSMenuItem { let item = NSMenuItem(title: title, action: action, keyEquivalent: "") diff --git a/Mac/MainWindow/Timeline/TimelineViewController.swift b/Mac/MainWindow/Timeline/TimelineViewController.swift index f9648c13e..6a20ed797 100644 --- a/Mac/MainWindow/Timeline/TimelineViewController.swift +++ b/Mac/MainWindow/Timeline/TimelineViewController.swift @@ -12,7 +12,7 @@ import Articles import Account import os.log -protocol TimelineDelegate: AnyObject { +protocol TimelineDelegate: AnyObject { func timelineSelectionDidChange(_: TimelineViewController, selectedArticles: [Article]?) func timelineRequestedFeedSelection(_: TimelineViewController, feed: Feed) func timelineInvalidatedRestorationState(_: TimelineViewController) @@ -42,10 +42,10 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr return timelineFeed.defaultReadFilterType == .read } } - + var isCleanUpAvailable: Bool { let isEligibleForCleanUp: Bool? - + if representedObjects?.count == 1, let timelineFeed = representedObjects?.first as? SidebarItem, timelineFeed.defaultReadFilterType == .alwaysRead { isEligibleForCleanUp = true } else { @@ -53,14 +53,14 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr } guard isEligibleForCleanUp ?? false else { return false } - + let readSelectedCount = selectedArticles.filter({ $0.status.read }).count let readArticleCount = articles.count - unreadCount let availableToCleanCount = readArticleCount - readSelectedCount - + return availableToCleanCount > 0 } - + var representedObjects: [AnyObject]? { didSet { if !representedObjectArraysAreEqual(oldValue, representedObjects) { @@ -196,7 +196,7 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr self.init(nibName: "TimelineTableView", bundle: nil) self.delegate = delegate } - + override func viewDidLoad() { cellAppearance = TimelineCellAppearance(showIcon: false, fontSize: fontSize) cellAppearanceWithIcon = TimelineCellAppearance(showIcon: true, fontSize: fontSize) @@ -208,7 +208,7 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr tableView.setDraggingSourceOperationMask(.copy, forLocal: false) tableView.keyboardDelegate = keyboardDelegate tableView.style = .inset - + if !didRegisterForNotifications { NotificationCenter.default.addObserver(self, selector: #selector(statusesDidChange(_:)), name: .StatusesDidChange, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(feedIconDidBecomeAvailable(_:)), name: .feedIconDidBecomeAvailable, object: nil) @@ -223,13 +223,13 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr didRegisterForNotifications = true } } - + override func viewDidAppear() { sharingServiceDelegate = SharingServiceDelegate(self.view.window) } // MARK: - API - + func markAllAsRead(completion: (() -> Void)? = nil) { guard let undoManager = undoManager, let markReadCommand = MarkStatusCommand(initialArticles: articles, markingRead: true, undoManager: undoManager, completion: completion) else { return @@ -254,7 +254,7 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr } return representedObjects.first! === object } - + func cleanUp() { fetchAndReplacePreservingSelection() } @@ -265,19 +265,19 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr delegate?.timelineInvalidatedRestorationState(self) fetchAndReplacePreservingSelection() } - + // MARK: State Restoration - - func saveState(to state: inout [AnyHashable : Any]) { + + func saveState(to state: inout [AnyHashable: Any]) { state[UserInfoKey.readArticlesFilterStateKeys] = readFilterEnabledTable.keys.compactMap { $0.userInfo } state[UserInfoKey.readArticlesFilterStateValues] = readFilterEnabledTable.values.compactMap( { $0 }) - + if selectedArticles.count == 1 { state[UserInfoKey.articlePath] = selectedArticles.first!.pathUserInfo } } - - func restoreState(from state: [AnyHashable : Any]) { + + func restoreState(from state: [AnyHashable: Any]) { guard let readArticlesFilterStateKeys = state[UserInfoKey.readArticlesFilterStateKeys] as? [[AnyHashable: AnyHashable]], let readArticlesFilterStateValues = state[UserInfoKey.readArticlesFilterStateValues] as? [Bool] else { return @@ -288,15 +288,15 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr readFilterEnabledTable[feedIdentifier] = readArticlesFilterStateValues[i] } } - - if let articlePathUserInfo = state[UserInfoKey.articlePath] as? [AnyHashable : Any], + + if let articlePathUserInfo = state[UserInfoKey.articlePath] as? [AnyHashable: Any], let accountID = articlePathUserInfo[ArticlePathKey.accountID] as? String, let account = AccountManager.shared.existingAccount(with: accountID), let articleID = articlePathUserInfo[ArticlePathKey.articleID] as? String { - + exceptionArticleFetcher = SingleArticleFetcher(account: account, articleID: articleID) fetchAndReplaceArticlesSync() - + if let selectedIndex = articles.firstIndex(where: { $0.articleID == articleID }) { tableView.selectRow(selectedIndex) DispatchQueue.main.async { @@ -306,13 +306,13 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr } } else { - + fetchAndReplaceArticlesSync() } - + } - + // MARK: - Actions @objc func openArticleInBrowser(_ sender: Any?) { @@ -320,7 +320,7 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr Browser.open(link, invertPreference: NSApp.currentEvent?.modifierFlags.contains(.shift) ?? false) } } - + @IBAction func toggleStatusOfSelectedArticles(_ sender: Any?) { guard !selectedArticles.isEmpty else { return @@ -331,8 +331,7 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr if markAsRead { markSelectedArticlesAsRead(sender) - } - else { + } else { markSelectedArticlesAsUnread(sender) } } @@ -343,7 +342,7 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr } runCommand(markReadCommand) } - + @IBAction func markSelectedArticlesAsUnread(_ sender: Any?) { guard let undoManager = undoManager, let markUnreadCommand = MarkStatusCommand(initialArticles: selectedArticles, markingRead: false, undoManager: undoManager) else { return @@ -359,36 +358,36 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr guard let lastSelectedRow = tableView.selectedRowIndexes.last else { return } - + let nextRowIndex = lastSelectedRow - 1 if nextRowIndex <= 0 { tableView.scrollTo(row: 0, extraHeight: 0) } - + tableView.selectRow(nextRowIndex) - + let followingRowIndex = nextRowIndex - 1 if followingRowIndex < 0 { return } - + tableView.scrollToRowIfNotVisible(followingRowIndex) - + } - + @IBAction func selectNextDown(_ sender: Any?) { guard let firstSelectedRow = tableView.selectedRowIndexes.first else { return } - + let tableMaxIndex = tableView.numberOfRows - 1 let nextRowIndex = firstSelectedRow + 1 if nextRowIndex >= tableMaxIndex { tableView.scrollTo(row: tableMaxIndex, extraHeight: 0) } - + tableView.selectRow(nextRowIndex) - + let followingRowIndex = nextRowIndex + 1 if followingRowIndex > tableMaxIndex { return @@ -397,11 +396,11 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr tableView.scrollToRowIfNotVisible(followingRowIndex) } - + func toggleReadStatusForSelectedArticles() { // If any one of the selected articles is unread, then mark them as read. // If all articles are read, then mark them as unread them. - + let commandStatus = markReadCommandStatus() let markingRead: Bool switch commandStatus { @@ -412,14 +411,14 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr case .canDoNothing: return } - + guard let undoManager = undoManager, let markStarredCommand = MarkStatusCommand(initialArticles: selectedArticles, markingRead: markingRead, undoManager: undoManager) else { return } - + runCommand(markStarredCommand) } - + func toggleStarredStatusForSelectedArticles() { // If any one of the selected articles is not starred, then star them. @@ -448,11 +447,11 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr func markReadCommandStatus() -> MarkCommandValidationStatus { let articles = selectedArticles - + if articles.anyArticleIsUnread() { return .canMark } - + if articles.anyArticleIsReadAndCanMarkUnread() { return .canUnmark } @@ -485,12 +484,11 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr func markOlderArticlesRead(_ selectedArticles: [Article]) { // Mark articles older than the selectedArticles(s) as read. - var cutoffDate: Date? = nil + var cutoffDate: Date? for article in selectedArticles { if cutoffDate == nil { cutoffDate = article.logicalDatePublished - } - else if cutoffDate! > article.logicalDatePublished { + } else if cutoffDate! > article.logicalDatePublished { cutoffDate = article.logicalDatePublished } } @@ -530,8 +528,8 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr } // MARK: - Navigation - - func goToDeepLink(for userInfo: [AnyHashable : Any]) { + + func goToDeepLink(for userInfo: [AnyHashable: Any]) { guard let articleID = userInfo[ArticlePathKey.articleID] as? String else { return } if isReadFiltered ?? false { @@ -543,12 +541,12 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr } guard let ix = articles.firstIndex(where: { $0.articleID == articleID }) else { return } - + NSCursor.setHiddenUntilMouseMoves(true) tableView.selectRow(ix) tableView.scrollTo(row: ix) } - + func goToNextUnread(wrappingToTop wrapping: Bool = false) { guard let ix = indexOfNextUnreadArticle() else { return @@ -557,14 +555,14 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr tableView.selectRow(ix) tableView.scrollTo(row: ix) } - + func canGoToNextUnread(wrappingToTop wrapping: Bool = false) -> Bool { guard let _ = indexOfNextUnreadArticle(wrappingToTop: wrapping) else { return false } return true } - + func indexOfNextUnreadArticle(wrappingToTop wrapping: Bool = false) -> Int? { return articles.rowOfNextUnreadArticle(tableView.selectedRow, wrappingToTop: wrapping) } @@ -573,7 +571,7 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr guard let window = tableView.window else { return } - + window.makeFirstResponderUnlessDescendantIsFirstResponder(tableView) if !hasAtLeastOneSelectedArticle && articles.count > 0 { tableView.selectRowAndScrollToVisible(0) @@ -648,7 +646,7 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr fetchAndReplaceArticlesAsync() } } - + @objc func accountsDidChange(_ note: Notification) { if representedObjectsContainsAnyPseudoFeed() { fetchAndReplaceArticlesAsync() @@ -666,7 +664,7 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr self.sortDirection = AppDefaults.shared.timelineSortDirection self.groupByFeed = AppDefaults.shared.timelineGroupByFeed } - + // MARK: - Reloading Data private func cellForRowView(_ rowView: NSView) -> NSView? { @@ -682,7 +680,7 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr } reloadVisibleCells(for: indexes) } - + private func reloadVisibleCells(for articles: [Article]) { reloadVisibleCells(for: Set(articles.articleIDs())) } @@ -690,7 +688,7 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr private func reloadVisibleCells(for articles: Set
) { reloadVisibleCells(for: articles.articleIDs()) } - + private func reloadVisibleCells(for articleIDs: Set) { if articleIDs.isEmpty { return @@ -714,7 +712,7 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr } tableView.reloadData(forRowIndexes: indexes, columnIndexes: NSIndexSet(index: 0) as IndexSet) } - + // MARK: - Cell Configuring private func calculateRowHeight() -> CGFloat { @@ -833,8 +831,7 @@ extension TimelineViewController: NSTableViewDelegate { cell.cellAppearance = showIcons ? cellAppearanceWithIcon : cellAppearance if let article = articles.articleAtRow(row) { configureTimelineCell(cell, article: article) - } - else { + } else { makeTimelineCellEmpty(cell) } } @@ -917,16 +914,16 @@ extension TimelineViewController: NSTableViewDelegate { switch edge { case .leading: - let action = NSTableViewRowAction(style: .regular, title: article.status.read ? "Unread" : "Read") { (action, row) in - self.toggleArticleRead(article); + let action = NSTableViewRowAction(style: .regular, title: article.status.read ? "Unread" : "Read") { (_, _) in + self.toggleArticleRead(article) tableView.rowActionsVisible = false } action.image = article.status.read ? AppAssets.swipeMarkUnreadImage : AppAssets.swipeMarkReadImage return [action] case .trailing: - let action = NSTableViewRowAction(style: .regular, title: article.status.starred ? "Unstar" : "Star") { (action, row) in - self.toggleArticleStarred(article); + let action = NSTableViewRowAction(style: .regular, title: article.status.starred ? "Unstar" : "Star") { (_, _) in + self.toggleArticleStarred(article) tableView.rowActionsVisible = false } action.backgroundColor = AppAssets.starColor @@ -944,7 +941,7 @@ extension TimelineViewController: NSTableViewDelegate { // MARK: - Private private extension TimelineViewController { - + func fetchAndReplacePreservingSelection() { if let article = oneSelectedArticle, let account = article.account { exceptionArticleFetcher = SingleArticleFetcher(account: account, articleID: article.articleID) @@ -953,7 +950,7 @@ private extension TimelineViewController { fetchAndReplaceArticlesSync() } } - + @objc func reloadAvailableCells() { if let indexesToReload = tableView.indexesOfAvailableRows() { reloadCells(for: indexesToReload) @@ -988,7 +985,7 @@ private extension TimelineViewController { self.showIcons = false return } - + for article in articles { if let authors = article.authors { for author in authors { @@ -1015,7 +1012,7 @@ private extension TimelineViewController { replaceArticles(with: unsortedArticles) } } - + func selectedArticleIDs() -> [String] { return selectedArticles.articleIDs() } @@ -1090,7 +1087,7 @@ private extension TimelineViewController { } // MARK: - Fetching Articles - + func fetchAndReplaceArticlesSync() { // To be called when the user has made a change of selection in the sidebar. // It blocks the main thread, so that there’s no async delay, @@ -1101,12 +1098,12 @@ private extension TimelineViewController { emptyTheTimeline() return } - + if exceptionArticleFetcher != nil { representedObjects.append(exceptionArticleFetcher as AnyObject) exceptionArticleFetcher = nil } - + let fetchedArticles = fetchUnsortedArticlesSync(for: representedObjects) replaceArticles(with: fetchedArticles) } @@ -1119,12 +1116,12 @@ private extension TimelineViewController { emptyTheTimeline() return } - + if exceptionArticleFetcher != nil { representedObjects.append(exceptionArticleFetcher as AnyObject) exceptionArticleFetcher = nil } - + fetchUnsortedArticlesAsync(for: representedObjects) { [weak self] (articles) in self?.replaceArticles(with: articles) } @@ -1141,7 +1138,7 @@ private extension TimelineViewController { func fetchUnsortedArticlesSync(for representedObjects: [Any]) -> Set
{ cancelPendingAsyncFetches() - let fetchers = representedObjects.compactMap{ $0 as? ArticleFetcher } + let fetchers = representedObjects.compactMap { $0 as? ArticleFetcher } if fetchers.isEmpty { return Set
() } @@ -1236,8 +1233,7 @@ private extension TimelineViewController { return true } } - } - else if let folder = representedObject as? Folder { + } else if let folder = representedObject as? Folder { for oneFeed in feeds { if folder.hasFeed(with: oneFeed.feedID) || folder.hasFeed(withURL: oneFeed.url) { return true diff --git a/Mac/Preferences/Accounts/AccountCell.swift b/Mac/Preferences/Accounts/AccountCell.swift index 7a1dbecef..60bb8fcc4 100644 --- a/Mac/Preferences/Accounts/AccountCell.swift +++ b/Mac/Preferences/Accounts/AccountCell.swift @@ -9,33 +9,33 @@ import AppKit class AccountCell: NSTableCellView { - + private var originalImage: NSImage? - + var isImageTemplateCapable = true - + override func prepareForReuse() { originalImage = nil } - + override var backgroundStyle: NSView.BackgroundStyle { didSet { updateImage() } } - + } private extension AccountCell { - + func updateImage() { guard isImageTemplateCapable else { return } - + if backgroundStyle != .normal { guard !(imageView?.image?.isTemplate ?? false) else { return } - + originalImage = imageView?.image - + let templateImage = imageView?.image?.copy() as? NSImage templateImage?.isTemplate = true imageView?.image = templateImage @@ -44,5 +44,5 @@ private extension AccountCell { imageView?.image = originalImage } } - + } diff --git a/Mac/Preferences/Accounts/AccountsAddCloudKitWindowController.swift b/Mac/Preferences/Accounts/AccountsAddCloudKitWindowController.swift index 7bf883a15..b88f19ad1 100644 --- a/Mac/Preferences/Accounts/AccountsAddCloudKitWindowController.swift +++ b/Mac/Preferences/Accounts/AccountsAddCloudKitWindowController.swift @@ -11,7 +11,7 @@ import Account enum AccountsAddCloudKitWindowControllerError: LocalizedError { case iCloudDriveMissing - + var errorDescription: String? { return NSLocalizedString("Unable to add iCloud Account. Please make sure you have iCloud and iCloud Drive enabled in System Settings.", comment: "Unable to add iCloud Account.") } @@ -26,35 +26,35 @@ class AccountsAddCloudKitWindowController: NSWindowController { convenience init() { self.init(windowNibName: NSNib.Name("AccountsAddCloudKit")) } - + override func windowDidLoad() { super.windowDidLoad() - + let attrString = NSAttributedString(linkText: CloudKitWebDocumentation.limitationsAndSolutionsText, linkURL: CloudKitWebDocumentation.limitationsAndSolutionsURL) limitationsAndSolutionsTextField.attributedStringValue = attrString } - + // 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) { guard FileManager.default.ubiquityIdentityToken != nil else { NSApplication.shared.presentError(AccountsAddCloudKitWindowControllerError.iCloudDriveMissing) return } - - let _ = AccountManager.shared.createAccount(type: .cloudKit) + + _ = AccountManager.shared.createAccount(type: .cloudKit) hostWindow!.endSheet(window!, returnCode: NSApplication.ModalResponse.OK) } - + } diff --git a/Mac/Preferences/Accounts/AccountsAddLocalWindowController.swift b/Mac/Preferences/Accounts/AccountsAddLocalWindowController.swift index 8aedbccd7..abe9d4288 100644 --- a/Mac/Preferences/Accounts/AccountsAddLocalWindowController.swift +++ b/Mac/Preferences/Accounts/AccountsAddLocalWindowController.swift @@ -13,32 +13,32 @@ class AccountsAddLocalWindowController: NSWindowController { @IBOutlet private weak var nameTextField: NSTextField! @IBOutlet private weak var localAccountNameTextField: NSTextField! - + private weak var hostWindow: NSWindow? convenience init() { self.init(windowNibName: NSNib.Name("AccountsAddLocal")) } - + override func windowDidLoad() { super.windowDidLoad() - + localAccountNameTextField.stringValue = NSLocalizedString("Create a local account on your Mac.", comment: "Account Local") } - + // MARK: API - + func runSheetOnWindow(_ hostWindow: NSWindow) { self.hostWindow = hostWindow hostWindow.beginSheet(window!) } // MARK: Actions - + @IBAction func cancel(_ sender: Any) { hostWindow!.endSheet(window!, returnCode: NSApplication.ModalResponse.cancel) } - + @IBAction func create(_ sender: Any) { let account = AccountManager.shared.createAccount(type: .onMyMac) if !nameTextField.stringValue.isEmpty { @@ -46,5 +46,5 @@ class AccountsAddLocalWindowController: NSWindowController { } hostWindow!.endSheet(window!, returnCode: NSApplication.ModalResponse.OK) } - + } diff --git a/Mac/Preferences/Accounts/AccountsDetailViewController.swift b/Mac/Preferences/Accounts/AccountsDetailViewController.swift index 3fe5487ee..5ad088041 100644 --- a/Mac/Preferences/Accounts/AccountsDetailViewController.swift +++ b/Mac/Preferences/Accounts/AccountsDetailViewController.swift @@ -17,7 +17,7 @@ final class AccountsDetailViewController: NSViewController, NSTextFieldDelegate @IBOutlet weak var limitationsAndSolutionsRow: NSGridRow! @IBOutlet weak var limitationsAndSolutionsTextField: NSTextField! @IBOutlet weak var credentialsButton: NSButton! - + private var accountsWindowController: NSWindowController? private var account: Account? @@ -29,7 +29,7 @@ final class AccountsDetailViewController: NSViewController, NSTextFieldDelegate public required init?(coder: NSCoder) { super.init(coder: coder) } - + private var hidesCredentialsButton: Bool { guard let account = account else { return true @@ -41,25 +41,25 @@ final class AccountsDetailViewController: NSViewController, NSTextFieldDelegate return false } } - + override func viewDidLoad() { super.viewDidLoad() - + nameTextField.delegate = self typeLabel.stringValue = account?.defaultName ?? "" nameTextField.stringValue = account?.name ?? "" activeButton.state = account?.isActive ?? false ? .on : .off - + if account?.type == .cloudKit { let attrString = NSAttributedString(linkText: CloudKitWebDocumentation.limitationsAndSolutionsText, linkURL: CloudKitWebDocumentation.limitationsAndSolutionsURL) limitationsAndSolutionsTextField.attributedStringValue = attrString } else { limitationsAndSolutionsRow.isHidden = true } - + credentialsButton.isHidden = hidesCredentialsButton } - + func controlTextDidEndEditing(_ obj: Notification) { if !nameTextField.stringValue.isEmpty { account?.name = nameTextField.stringValue @@ -67,28 +67,27 @@ final class AccountsDetailViewController: NSViewController, NSTextFieldDelegate account?.name = nil } } - + @IBAction func active(_ sender: NSButtonCell) { account?.isActive = sender.state == .on ? true : false } - + @IBAction func credentials(_ sender: Any) { - + guard let account = account else { return } - + switch account.type { case .feedbin: let accountsFeedbinWindowController = AccountsFeedbinWindowController() accountsFeedbinWindowController.account = account accountsFeedbinWindowController.runSheetOnWindow(self.view.window!) accountsWindowController = accountsFeedbinWindowController - case .inoreader, .bazQux, .theOldReader, .freshRSS: + case .inoreader, .bazQux, .theOldReader, .freshRSS: let accountsReaderAPIWindowController = AccountsReaderAPIWindowController() accountsReaderAPIWindowController.accountType = account.type accountsReaderAPIWindowController.account = account accountsReaderAPIWindowController.runSheetOnWindow(self.view.window!) accountsWindowController = accountsReaderAPIWindowController - break case .newsBlur: let accountsNewsBlurWindowController = AccountsNewsBlurWindowController() accountsNewsBlurWindowController.account = account @@ -97,7 +96,7 @@ final class AccountsDetailViewController: NSViewController, NSTextFieldDelegate default: break } - + } - + } diff --git a/Mac/Preferences/Accounts/AccountsFeedbinWindowController.swift b/Mac/Preferences/Accounts/AccountsFeedbinWindowController.swift index 10eb7287f..a108b8eff 100644 --- a/Mac/Preferences/Accounts/AccountsFeedbinWindowController.swift +++ b/Mac/Preferences/Accounts/AccountsFeedbinWindowController.swift @@ -21,15 +21,15 @@ class AccountsFeedbinWindowController: NSWindowController { @IBOutlet weak var passwordTextField: NSSecureTextField! @IBOutlet weak var errorMessageLabel: NSTextField! @IBOutlet weak var actionButton: NSButton! - + var account: Account? - + private weak var hostWindow: NSWindow? - + convenience init() { self.init(windowNibName: NSNib.Name("AccountsFeedbin")) } - + override func windowDidLoad() { if let account = account, let credentials = try? account.retrieveCredentials(type: .basic) { usernameTextField.stringValue = credentials.username @@ -41,69 +41,69 @@ class AccountsFeedbinWindowController: NSWindowController { actionButton.title = NSLocalizedString("Create", comment: "Add Account") signInTextField.stringValue = NSLocalizedString("Sign in to your Feedbin account.", comment: "SignIn") } - + enableAutofill() - + usernameTextField.becomeFirstResponder() } - + // 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 action(_ sender: Any) { - + self.errorMessageLabel.stringValue = "" - + guard !usernameTextField.stringValue.isEmpty && !passwordTextField.stringValue.isEmpty else { self.errorMessageLabel.stringValue = NSLocalizedString("Username & password required.", comment: "Credentials Error") return } - + guard account != nil || !AccountManager.shared.duplicateServiceAccount(type: .feedbin, username: usernameTextField.stringValue) else { self.errorMessageLabel.stringValue = NSLocalizedString("There is already a Feedbin account with that username created.", comment: "Duplicate Error") return } - + actionButton.isEnabled = false progressIndicator.isHidden = false progressIndicator.startAnimation(self) - + let credentials = Credentials(type: .basic, username: usernameTextField.stringValue, secret: passwordTextField.stringValue) Account.validateCredentials(type: .feedbin, credentials: credentials) { [weak self] result in - + guard let self = self else { return } - + self.actionButton.isEnabled = true self.progressIndicator.isHidden = true self.progressIndicator.stopAnimation(self) - + switch result { case .success(let validatedCredentials): - + guard let validatedCredentials = validatedCredentials else { self.errorMessageLabel.stringValue = NSLocalizedString("Invalid email/password combination.", comment: "Credentials Error") return } - + if self.account == nil { self.account = AccountManager.shared.createAccount(type: .feedbin) } - + do { try self.account?.removeCredentials(type: .basic) try self.account?.storeCredentials(validatedCredentials) - self.account?.refreshAll() { result in + self.account?.refreshAll { result in switch result { case .success: break @@ -111,30 +111,30 @@ class AccountsFeedbinWindowController: NSWindowController { NSApplication.shared.presentError(error) } } - + self.hostWindow?.endSheet(self.window!, returnCode: NSApplication.ModalResponse.OK) } catch { self.errorMessageLabel.stringValue = NSLocalizedString("Keychain error while storing credentials.", comment: "Credentials Error") } - + case .failure: - + self.errorMessageLabel.stringValue = NSLocalizedString("Network error. Try again later.", comment: "Credentials Error") - + } - + } - + } - + @IBAction func createAccountWithProvider(_ sender: Any) { NSWorkspace.shared.open(URL(string: "https://feedbin.com/signup")!) } - + // MARK: Autofill func enableAutofill() { usernameTextField.contentType = .username passwordTextField.contentType = .password } - + } diff --git a/Mac/Preferences/Accounts/AccountsNewsBlurWindowController.swift b/Mac/Preferences/Accounts/AccountsNewsBlurWindowController.swift index 66c23e02d..904a93b3e 100644 --- a/Mac/Preferences/Accounts/AccountsNewsBlurWindowController.swift +++ b/Mac/Preferences/Accounts/AccountsNewsBlurWindowController.swift @@ -12,7 +12,7 @@ import RSWeb import Secrets class AccountsNewsBlurWindowController: NSWindowController { - + @IBOutlet weak var signInTextField: NSTextField! @IBOutlet weak var noAccountTextField: NSTextField! @IBOutlet weak var createNewAccountButton: NSButton! @@ -65,12 +65,12 @@ class AccountsNewsBlurWindowController: NSWindowController { self.errorMessageLabel.stringValue = NSLocalizedString("Username required.", comment: "Credentials Error") return } - + guard account != nil || !AccountManager.shared.duplicateServiceAccount(type: .newsBlur, username: usernameTextField.stringValue) else { self.errorMessageLabel.stringValue = NSLocalizedString("There is already a NewsBlur account with that username created.", comment: "Duplicate Error") return } - + actionButton.isEnabled = false progressIndicator.isHidden = false progressIndicator.startAnimation(self) @@ -101,7 +101,7 @@ class AccountsNewsBlurWindowController: NSWindowController { try self.account?.storeCredentials(credentials) try self.account?.storeCredentials(validatedCredentials) - self.account?.refreshAll() { result in + self.account?.refreshAll { result in switch result { case .success: break @@ -109,7 +109,7 @@ class AccountsNewsBlurWindowController: NSWindowController { NSApplication.shared.presentError(error) } } - + self.hostWindow?.endSheet(self.window!, returnCode: NSApplication.ModalResponse.OK) } catch { self.errorMessageLabel.stringValue = NSLocalizedString("Keychain error while storing credentials.", comment: "Credentials Error") @@ -122,14 +122,14 @@ class AccountsNewsBlurWindowController: NSWindowController { } } } - + @IBAction func createAccountWithProvider(_ sender: Any) { NSWorkspace.shared.open(URL(string: "https://newsblur.com")!) } - + // MARK: Autofill func enableAutofill() { usernameTextField.contentType = .username passwordTextField.contentType = .password - } + } } diff --git a/Mac/Preferences/Accounts/AccountsPreferencesViewController.swift b/Mac/Preferences/Accounts/AccountsPreferencesViewController.swift index 10d89ae8d..7c19e551e 100644 --- a/Mac/Preferences/Accounts/AccountsPreferencesViewController.swift +++ b/Mac/Preferences/Accounts/AccountsPreferencesViewController.swift @@ -35,44 +35,43 @@ final class AccountsPreferencesViewController: NSViewController { tableView.delegate = self tableView.dataSource = self addAccountDelegate = self - + NotificationCenter.default.addObserver(self, selector: #selector(displayNameDidChange(_:)), name: .DisplayNameDidChange, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(accountsDidChange(_:)), name: .UserDidAddAccount, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(accountsDidChange(_:)), name: .UserDidDeleteAccount, object: nil) - // Fix tableView frame — for some reason IB wants it 1pt wider than the clip view. This leads to unwanted horizontal scrolling. var rTable = tableView.frame rTable.size.width = tableView.superview!.frame.size.width tableView.frame = rTable - + hideController() } - + @IBAction func addAccount(_ sender: Any) { let controller = NSHostingController(rootView: AddAccountsView(delegate: self)) controller.rootView.parent = controller addAccountsViewController = controller presentAsSheet(controller) } - + @IBAction func removeAccount(_ sender: Any) { - + guard tableView.selectedRow != -1 else { return } - + let acctName = sortedAccounts[tableView.selectedRow].nameForDisplay - + let alert = NSAlert() alert.alertStyle = .warning let deletePrompt = NSLocalizedString("Delete", comment: "Delete") alert.messageText = "\(deletePrompt) “\(acctName)”?" alert.informativeText = NSLocalizedString("Are you sure you want to delete the account “\(acctName)”? This cannot be undone.", comment: "Delete text") - + alert.addButton(withTitle: NSLocalizedString("Delete", comment: "Delete Account")) alert.addButton(withTitle: NSLocalizedString("Cancel", comment: "Cancel Delete Account")) - + alert.beginSheetModal(for: view.window!) { [weak self] result in if result == NSApplication.ModalResponse.alertFirstButtonReturn { guard let self = self else { return } @@ -80,19 +79,19 @@ final class AccountsPreferencesViewController: NSViewController { self.hideController() } } - + } - + @objc func displayNameDidChange(_ note: Notification) { updateSortedAccounts() tableView.reloadData() } - + @objc func accountsDidChange(_ note: Notification) { updateSortedAccounts() tableView.reloadData() } - + } // MARK: - NSTableViewDataSource @@ -118,18 +117,18 @@ extension AccountsPreferencesViewController: NSTableViewDelegate { let account = sortedAccounts[row] cell.textField?.stringValue = account.nameForDisplay cell.imageView?.image = account.smallIcon?.image - + if account.type == .feedbin { cell.isImageTemplateCapable = false } - + return cell } return nil } func tableViewSelectionDidChange(_ notification: Notification) { - + let selectedRow = tableView.selectedRow if tableView.selectedRow == -1 { deleteButton.isEnabled = false @@ -143,12 +142,12 @@ extension AccountsPreferencesViewController: NSTableViewDelegate { if AccountManager.shared.defaultAccount == account { deleteButton.isEnabled = false } - + let controller = AccountsDetailViewController(account: account) showController(controller) - + } - + } extension AccountsPreferencesViewController: AccountsPreferencesAddAccountDelegate { @@ -187,26 +186,26 @@ extension AccountsPreferencesViewController: AccountsPreferencesAddAccountDelega addAccountWindowController = accountsNewsBlurWindowController } } - + private func runAwaitingFeedlyLoginAlertModal(forLifetimeOf operation: OAuthAccountAuthorizationOperation) { let alert = NSAlert() alert.alertStyle = .informational alert.messageText = NSLocalizedString("Waiting for access to Feedly", comment: "Alert title when adding a Feedly account and waiting for authorization from the user.") - + alert.informativeText = NSLocalizedString("A web browser will open the Feedly login for you to authorize access.", comment: "Alert informative text when adding a Feedly account and waiting for authorization from the user.") - + alert.addButton(withTitle: NSLocalizedString("Cancel", comment: "Cancel")) - + let attachedWindow = self.view.window! - + alert.beginSheetModal(for: attachedWindow) { response in if response == .alertFirstButtonReturn { operation.cancel() } } - + operation.completionBlock = { _ in guard alert.window.isVisible else { return @@ -223,22 +222,22 @@ private extension AccountsPreferencesViewController { func updateSortedAccounts() { sortedAccounts = AccountManager.shared.sortedAccounts } - + func showController(_ controller: NSViewController) { hideController() - + addChild(controller) controller.view.translatesAutoresizingMaskIntoConstraints = false detailView.addSubview(controller.view) detailView.addFullSizeConstraints(forSubview: controller.view) } - + func hideController() { if let controller = children.first { children.removeAll() controller.view.removeFromSuperview() } - + if tableView.selectedRow == -1 { var helpText = "" if sortedAccounts.count == 0 { @@ -246,7 +245,7 @@ private extension AccountsPreferencesViewController { } else { helpText = NSLocalizedString("Select an account or add a new account by clicking the + button.", comment: "Add Account Explainer") } - + let textHostingController = NSHostingController(rootView: AddAccountHelpView(delegate: addAccountDelegate, helpText: helpText)) addChild(textHostingController) @@ -257,21 +256,21 @@ private extension AccountsPreferencesViewController { NSLayoutConstraint(item: textHostingController.view, attribute: .bottom, relatedBy: .equal, toItem: detailView, attribute: .bottom, multiplier: 1, constant: -deleteButton.frame.height), NSLayoutConstraint(item: textHostingController.view, attribute: .width, relatedBy: .equal, toItem: detailView, attribute: .width, multiplier: 1, constant: 1) ]) - + } } - + } extension AccountsPreferencesViewController: OAuthAccountAuthorizationOperationDelegate { - + func oauthAccountAuthorizationOperation(_ operation: OAuthAccountAuthorizationOperation, didCreate account: Account) { // `OAuthAccountAuthorizationOperation` is using `ASWebAuthenticationSession` which bounces the user // to their browser on macOS for authorizing NetNewsWire to access the user's Feedly account. // When this authorization is granted, the browser remains the foreground app which is unfortunate // because the user probably wants to see the result of authorizing NetNewsWire to act on their behalf. NSApp.activate(ignoringOtherApps: true) - + account.refreshAll { [weak self] result in switch result { case .success: @@ -281,12 +280,12 @@ extension AccountsPreferencesViewController: OAuthAccountAuthorizationOperationD } } } - + func oauthAccountAuthorizationOperation(_ operation: OAuthAccountAuthorizationOperation, didFailWith error: Error) { // `OAuthAccountAuthorizationOperation` is using `ASWebAuthenticationSession` which bounces the user // to their browser on macOS for authorizing NetNewsWire to access the user's Feedly account. NSApp.activate(ignoringOtherApps: true) - + view.window?.presentError(error) } } diff --git a/Mac/Preferences/Accounts/AccountsReaderAPIWindowController.swift b/Mac/Preferences/Accounts/AccountsReaderAPIWindowController.swift index bf52673b2..d4fcb88a8 100644 --- a/Mac/Preferences/Accounts/AccountsReaderAPIWindowController.swift +++ b/Mac/Preferences/Accounts/AccountsReaderAPIWindowController.swift @@ -15,7 +15,7 @@ class AccountsReaderAPIWindowController: NSWindowController { @IBOutlet weak var titleImageView: NSImageView! @IBOutlet weak var titleLabel: NSTextField! - + @IBOutlet weak var gridView: NSGridView! @IBOutlet weak var progressIndicator: NSProgressIndicator! @IBOutlet weak var usernameTextField: NSTextField! @@ -25,16 +25,16 @@ class AccountsReaderAPIWindowController: NSWindowController { @IBOutlet weak var errorMessageLabel: NSTextField! @IBOutlet weak var actionButton: NSButton! @IBOutlet weak var noAccountTextField: NSTextField! - + var account: Account? var accountType: AccountType? - + private weak var hostWindow: NSWindow? - + convenience init() { self.init(windowNibName: NSNib.Name("AccountsReaderAPI")) } - + override func windowDidLoad() { if let accountType = accountType { switch accountType { @@ -63,7 +63,7 @@ class AccountsReaderAPIWindowController: NSWindowController { break } } - + if let account = account, let credentials = try? account.retrieveCredentials(type: .readerBasic) { usernameTextField.stringValue = credentials.username apiURLTextField.stringValue = account.endpointURL?.absoluteString ?? "" @@ -71,42 +71,42 @@ class AccountsReaderAPIWindowController: NSWindowController { } else { actionButton.title = NSLocalizedString("Create", comment: "Create") } - + enableAutofill() usernameTextField.becomeFirstResponder() } - + // 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 action(_ sender: Any) { self.errorMessageLabel.stringValue = "" - + guard !usernameTextField.stringValue.isEmpty && !passwordTextField.stringValue.isEmpty else { self.errorMessageLabel.stringValue = NSLocalizedString("Username, password & API URL are required.", comment: "Credentials Error") return } - + guard let accountType = accountType, !(accountType == .freshRSS && apiURLTextField.stringValue.isEmpty) else { self.errorMessageLabel.stringValue = NSLocalizedString("Username, password & API URL are required.", comment: "Credentials Error") return } - + guard account != nil || !AccountManager.shared.duplicateServiceAccount(type: accountType, username: usernameTextField.stringValue) else { self.errorMessageLabel.stringValue = NSLocalizedString("There is already an account of this type with that username created.", comment: "Duplicate Error") return } - + let apiURL: URL switch accountType { case .freshRSS: @@ -125,31 +125,31 @@ class AccountsReaderAPIWindowController: NSWindowController { self.errorMessageLabel.stringValue = NSLocalizedString("Unrecognized account type.", comment: "Bad account type") return } - + actionButton.isEnabled = false progressIndicator.isHidden = false progressIndicator.startAnimation(self) - + let credentials = Credentials(type: .readerBasic, username: usernameTextField.stringValue, secret: passwordTextField.stringValue) Account.validateCredentials(type: accountType, credentials: credentials, endpoint: apiURL) { [weak self] result in - + guard let self = self else { return } - + self.actionButton.isEnabled = true self.progressIndicator.isHidden = true self.progressIndicator.stopAnimation(self) - + switch result { case .success(let validatedCredentials): guard let validatedCredentials = validatedCredentials else { self.errorMessageLabel.stringValue = NSLocalizedString("Invalid email/password combination.", comment: "Credentials Error") return } - + if self.account == nil { self.account = AccountManager.shared.createAccount(type: self.accountType!) } - + do { self.account?.endpointURL = apiURL @@ -157,8 +157,8 @@ class AccountsReaderAPIWindowController: NSWindowController { try self.account?.removeCredentials(type: .readerAPIKey) try self.account?.storeCredentials(credentials) try self.account?.storeCredentials(validatedCredentials) - - self.account?.refreshAll() { result in + + self.account?.refreshAll { result in switch result { case .success: break @@ -166,20 +166,20 @@ class AccountsReaderAPIWindowController: NSWindowController { NSApplication.shared.presentError(error) } } - + self.hostWindow?.endSheet(self.window!, returnCode: NSApplication.ModalResponse.OK) } catch { self.errorMessageLabel.stringValue = NSLocalizedString("Keychain error while storing credentials.", comment: "Credentials Error") } - + case .failure: self.errorMessageLabel.stringValue = NSLocalizedString("Network error. Try again later.", comment: "Credentials Error") } - + } - + } - + @IBAction func createAccountWithProvider(_ sender: Any) { switch accountType { case .freshRSS: @@ -194,10 +194,10 @@ class AccountsReaderAPIWindowController: NSWindowController { return } } - + // MARK: Autofill func enableAutofill() { usernameTextField.contentType = .username passwordTextField.contentType = .password - } + } } diff --git a/Mac/Preferences/Accounts/AddAccountHelpView.swift b/Mac/Preferences/Accounts/AddAccountHelpView.swift index 4fa1fd54a..3f520fd1f 100644 --- a/Mac/Preferences/Accounts/AddAccountHelpView.swift +++ b/Mac/Preferences/Accounts/AddAccountHelpView.swift @@ -10,12 +10,12 @@ import SwiftUI import Account struct AddAccountHelpView: View { - + let accountTypes: [AccountType] = AddAccountSections.allOrdered.sectionContent var delegate: AccountsPreferencesAddAccountDelegate? var helpText: String @State private var iCloudUnavailableError: Bool = false - + var body: some View { VStack { HStack { @@ -36,11 +36,11 @@ struct AddAccountHelpView: View { } } } - + Text(helpText) .multilineTextAlignment(.center) .padding(.top, 8) - + } .alert(isPresented: $iCloudUnavailableError, content: { Alert(title: Text(NSLocalizedString("Error", comment: "Error")), @@ -50,5 +50,5 @@ struct AddAccountHelpView: View { })) }) } - + } diff --git a/Mac/Preferences/Accounts/AddAccountsView.swift b/Mac/Preferences/Accounts/AddAccountsView.swift index 745ba04ee..33365bd7b 100644 --- a/Mac/Preferences/Accounts/AddAccountsView.swift +++ b/Mac/Preferences/Accounts/AddAccountsView.swift @@ -16,7 +16,7 @@ enum AddAccountSections: Int, CaseIterable { case web case selfhosted case allOrdered - + var sectionHeader: String { switch self { case .local: @@ -31,7 +31,7 @@ enum AddAccountSections: Int, CaseIterable { return "" } } - + var sectionFooter: String { switch self { case .local: @@ -46,7 +46,7 @@ enum AddAccountSections: Int, CaseIterable { return "" } } - + var sectionContent: [AccountType] { switch self { case .local: @@ -68,38 +68,35 @@ enum AddAccountSections: Int, CaseIterable { AddAccountSections.selfhosted.sectionContent } } - - - - + } struct AddAccountsView: View { - + weak var parent: NSHostingController? // required because presentationMode.dismiss() doesn't work var addAccountDelegate: AccountsPreferencesAddAccountDelegate? private let chunkLimit = 4 // use this to control number of accounts in each web account column @State private var selectedAccount: AccountType = .onMyMac - + init(delegate: AccountsPreferencesAddAccountDelegate?) { self.addAccountDelegate = delegate } - + var body: some View { VStack(alignment: .leading, spacing: 8) { Text("Choose an account type to add...") .font(.headline) .padding() - + localAccount - + if !AppDefaults.shared.isDeveloperBuild { icloudAccount } - + webAccounts selfhostedAccounts - + HStack(spacing: 12) { Spacer() Button(action: { @@ -135,7 +132,7 @@ struct AddAccountsView: View { Text("Local") .font(.headline) .padding(.horizontal) - + Picker(selection: $selectedAccount, label: Text(""), content: { ForEach(AddAccountSections.local.sectionContent, id: \.self, content: { account in HStack(alignment: .center) { @@ -151,23 +148,23 @@ struct AddAccountsView: View { }) .pickerStyle(RadioGroupPickerStyle()) .offset(x: 7.5, y: 0) - + Text(AddAccountSections.local.sectionFooter).foregroundColor(.gray) .padding(.horizontal) .lineLimit(3) .fixedSize(horizontal: false, vertical: true) - + } - + } - + var icloudAccount: some View { VStack(alignment: .leading) { Text("iCloud") .font(.headline) .padding(.horizontal) .padding(.top, 8) - + Picker(selection: $selectedAccount, label: Text(""), content: { ForEach(AddAccountSections.icloud.sectionContent, id: \.self, content: { account in HStack(alignment: .center) { @@ -176,7 +173,7 @@ struct AddAccountsView: View { .aspectRatio(contentMode: .fit) .frame(width: 20, height: 20, alignment: .center) .padding(.leading, 4) - + Text(account.localizedAccountName()) } .tag(account) @@ -184,14 +181,14 @@ struct AddAccountsView: View { }) .offset(x: 7.5, y: 0) .disabled(isCloudInUse()) - + Text(AddAccountSections.icloud.sectionFooter).foregroundColor(.gray) .padding(.horizontal) .lineLimit(3) .fixedSize(horizontal: false, vertical: true) } } - + @ViewBuilder var webAccounts: some View { VStack(alignment: .leading) { @@ -199,13 +196,13 @@ struct AddAccountsView: View { .font(.headline) .padding(.horizontal) .padding(.top, 8) - + HStack { ForEach(0.. Bool { AccountManager.shared.accounts.contains(where: { $0.type == .cloudKit }) } - + private func chunkedWebAccounts() -> [[AccountType]] { AddAccountSections.web.sectionContent.chunked(into: chunkLimit) } } - struct AddAccountsView_Previews: PreviewProvider { static var previews: some View { AddAccountsView(delegate: nil) } } - diff --git a/Mac/Preferences/Advanced/AdvancedPreferencesViewController.swift b/Mac/Preferences/Advanced/AdvancedPreferencesViewController.swift index 296f2c876..9743bbbe1 100644 --- a/Mac/Preferences/Advanced/AdvancedPreferencesViewController.swift +++ b/Mac/Preferences/Advanced/AdvancedPreferencesViewController.swift @@ -53,12 +53,11 @@ private extension AdvancedPreferencesViewController { func updateUI() { if wantsTestBuilds { testBuildsButton.state = .on - } - else { + } else { releaseBuildsButton.state = .on } } - + func currentAppcastURL() -> String { return UserDefaults.standard.string(forKey: appcastDefaultsKey) ?? "" } diff --git a/Mac/Preferences/General/GeneralPrefencesViewController.swift b/Mac/Preferences/General/GeneralPrefencesViewController.swift index 7fb93a71a..8ad06f129 100644 --- a/Mac/Preferences/General/GeneralPrefencesViewController.swift +++ b/Mac/Preferences/General/GeneralPrefencesViewController.swift @@ -51,7 +51,7 @@ final class GeneralPreferencesViewController: NSViewController { let url = URL(fileURLWithPath: ArticleThemesManager.shared.folderPath) NSWorkspace.shared.open(url) } - + @IBAction func articleThemePopUpDidChange(_ sender: Any) { guard let menuItem = articleThemePopup.selectedItem else { return @@ -59,7 +59,7 @@ final class GeneralPreferencesViewController: NSViewController { ArticleThemesManager.shared.currentThemeName = menuItem.title updateArticleThemePopup() } - + @IBAction func browserPopUpDidChangeValue(_ sender: Any?) { guard let menuItem = defaultBrowserPopup.selectedItem else { return @@ -84,18 +84,18 @@ private extension GeneralPreferencesViewController { updateArticleThemePopup() updateBrowserPopup() } - + func updateArticleThemePopup() { let menu = articleThemePopup.menu! menu.removeAllItems() - + menu.addItem(NSMenuItem(title: ArticleTheme.defaultTheme.name, action: nil, keyEquivalent: "")) menu.addItem(NSMenuItem.separator()) for themeName in ArticleThemesManager.shared.themeNames { menu.addItem(NSMenuItem(title: themeName, action: nil, keyEquivalent: "")) } - + articleThemePopup.selectItem(withTitle: ArticleThemesManager.shared.currentThemeName) if articleThemePopup.indexOfSelectedItem == -1 { articleThemePopup.selectItem(withTitle: ArticleTheme.defaultTheme.name) diff --git a/Mac/Preferences/PreferencesControlsBackgroundView.swift b/Mac/Preferences/PreferencesControlsBackgroundView.swift index 5625f3d3d..f5b59ad0e 100644 --- a/Mac/Preferences/PreferencesControlsBackgroundView.swift +++ b/Mac/Preferences/PreferencesControlsBackgroundView.swift @@ -29,7 +29,7 @@ final class PreferencesControlsBackgroundView: NSView { let fillColor = self.effectiveAppearance.isDarkMode ? darkModeFillColor : lightModeFillColor fillColor.setFill() - let r = NSIntersectionRect(dirtyRect, bounds) + let r = dirtyRect.intersection(bounds) r.fill() let borderColor = self.effectiveAppearance.isDarkMode ? darkModeBorderColor : lightModeBorderColor diff --git a/Mac/Preferences/PreferencesTableViewBackgroundView.swift b/Mac/Preferences/PreferencesTableViewBackgroundView.swift index 21839a895..6450f242c 100644 --- a/Mac/Preferences/PreferencesTableViewBackgroundView.swift +++ b/Mac/Preferences/PreferencesTableViewBackgroundView.swift @@ -17,7 +17,7 @@ final class PreferencesTableViewBackgroundView: NSView { let color = self.effectiveAppearance.isDarkMode ? darkBorderColor : lightBorderColor color.setFill() - let r = NSIntersectionRect(dirtyRect, bounds) + let r = dirtyRect.intersection(bounds) r.fill() } } diff --git a/Mac/Preferences/PreferencesWindowController.swift b/Mac/Preferences/PreferencesWindowController.swift index 8525241f4..20cfabcaf 100644 --- a/Mac/Preferences/PreferencesWindowController.swift +++ b/Mac/Preferences/PreferencesWindowController.swift @@ -13,7 +13,7 @@ private struct PreferencesToolbarItemSpec { let identifier: NSToolbarItem.Identifier let name: String let image: NSImage? - + init(identifierRawValue: String, name: String, image: NSImage?) { self.identifier = NSToolbarItem.Identifier(identifierRawValue) self.name = name @@ -27,8 +27,8 @@ private struct ToolbarItemIdentifier { static let Advanced = "Advanced" } -class PreferencesWindowController : NSWindowController, NSToolbarDelegate { - +class PreferencesWindowController: NSWindowController, NSToolbarDelegate { + private let windowWidth = CGFloat(512.0) // Width is constant for all views; only the height changes private var viewControllers = [String: NSViewController]() private let toolbarItemSpecs: [PreferencesToolbarItemSpec] = { @@ -126,7 +126,7 @@ private extension PreferencesWindowController { assertionFailure("Preferences window: no view controller matching \(identifier).") return } - + if newViewController.view == currentView { return } @@ -140,8 +140,7 @@ private extension PreferencesWindowController { if let currentView = currentView { window!.contentView?.replaceSubview(currentView, with: newViewController.view) - } - else { + } else { window!.contentView?.addSubview(newViewController.view) } @@ -167,23 +166,23 @@ private extension PreferencesWindowController { let viewFrame = view.frame let windowFrame = window!.frame let contentViewFrame = window!.contentView!.frame - - let deltaHeight = NSHeight(contentViewFrame) - NSHeight(viewFrame) - let heightForWindow = NSHeight(windowFrame) - deltaHeight - let windowOriginY = NSMinY(windowFrame) + deltaHeight - + + let deltaHeight = contentViewFrame.height - viewFrame.height + let heightForWindow = windowFrame.height - deltaHeight + let windowOriginY = windowFrame.minY + deltaHeight + var updatedWindowFrame = windowFrame updatedWindowFrame.size.height = heightForWindow updatedWindowFrame.origin.y = windowOriginY - updatedWindowFrame.size.width = windowWidth //NSWidth(viewFrame) - + updatedWindowFrame.size.width = windowWidth // NSWidth(viewFrame) + var updatedViewFrame = viewFrame - updatedViewFrame.origin = NSZeroPoint + updatedViewFrame.origin = NSPoint.zero updatedViewFrame.size.width = windowWidth if viewFrame != updatedViewFrame { view.frame = updatedViewFrame } - + if windowFrame != updatedWindowFrame { window!.contentView?.alphaValue = 0.0 window!.setFrame(updatedWindowFrame, display: true, animate: true) diff --git a/Mac/SafariExtension/SafariExtensionHandler.swift b/Mac/SafariExtension/SafariExtensionHandler.swift index 2589d4034..ffbd6f564 100644 --- a/Mac/SafariExtension/SafariExtensionHandler.swift +++ b/Mac/SafariExtension/SafariExtensionHandler.swift @@ -29,7 +29,7 @@ class SafariExtensionHandler: SFSafariExtensionHandler { } // Maps from UUID to a validation wrapper - static var gPingPongMap = Dictionary() + static var gPingPongMap = [String: ValidationWrapper]() static var validationQueue = DispatchQueue(label: "Toolbar Validation") // Bottleneck for calling through to a validation handler we have saved, and removing it from the list. @@ -40,8 +40,8 @@ class SafariExtensionHandler: SFSafariExtensionHandler { } } - override func messageReceived(withName messageName: String, from page: SFSafariPage, userInfo: [String : Any]?) { - if (messageName == "subscribeToFeed") { + override func messageReceived(withName messageName: String, from page: SFSafariPage, userInfo: [String: Any]?) { + if messageName == "subscribeToFeed" { if var feedURLString = userInfo?["url"] as? String { var openInDefaultBrowser = false @@ -61,16 +61,15 @@ class SafariExtensionHandler: SFSafariExtensionHandler { NSWorkspace.shared.open(feedURL) } } - } - else if (messageName == "pong") { + } else if messageName == "pong" { if let validationIDString = userInfo?["validationID"] as? String { // Should we validate the button? let shouldValidate = userInfo?["shouldValidate"] as? Bool ?? false - SafariExtensionHandler.callValidationHandler(forHandlerID: validationIDString, withShouldValidate:shouldValidate) + SafariExtensionHandler.callValidationHandler(forHandlerID: validationIDString, withShouldValidate: shouldValidate) } } } - + override func toolbarItemClicked(in window: SFSafariWindow) { window.getActiveTab { (activeTab) in activeTab?.getActivePage(completionHandler: { (activePage) in @@ -97,7 +96,7 @@ class SafariExtensionHandler: SFSafariExtensionHandler { if thisValidationID != uniqueValidationID { // Default to valid ... we'll know soon enough whether the latest state // is actually still valid or not... - SafariExtensionHandler.callValidationHandler(forHandlerID: thisValidationID, withShouldValidate: true); + SafariExtensionHandler.callValidationHandler(forHandlerID: thisValidationID, withShouldValidate: true) } } @@ -108,14 +107,14 @@ class SafariExtensionHandler: SFSafariExtensionHandler { // a timeout period has elapsed window.getActiveTab { (activeTab) in guard let activeTab = activeTab else { - SafariExtensionHandler.callValidationHandler(forHandlerID: uniqueValidationID, withShouldValidate:false); + SafariExtensionHandler.callValidationHandler(forHandlerID: uniqueValidationID, withShouldValidate: false) return } activeTab.getActivePage { (activePage) in guard let activePage = activePage else { - SafariExtensionHandler.callValidationHandler(forHandlerID: uniqueValidationID, withShouldValidate:false); - return + SafariExtensionHandler.callValidationHandler(forHandlerID: uniqueValidationID, withShouldValidate: false) + return } activePage.getPropertiesWithCompletionHandler { (pageProperties) in @@ -127,7 +126,7 @@ class SafariExtensionHandler: SFSafariExtensionHandler { let pongTimeoutInNanoseconds = Int(Double(NSEC_PER_SEC) * 0.5) let timeoutDeadline = DispatchTime.now() + DispatchTimeInterval.nanoseconds(pongTimeoutInNanoseconds) DispatchQueue.main.asyncAfter(deadline: timeoutDeadline, execute: { [timedOutValidationID = uniqueValidationID] in - SafariExtensionHandler.callValidationHandler(forHandlerID: timedOutValidationID, withShouldValidate:false) + SafariExtensionHandler.callValidationHandler(forHandlerID: timedOutValidationID, withShouldValidate: false) }) } } diff --git a/Mac/SafariExtension/SafariExtensionViewController.swift b/Mac/SafariExtension/SafariExtensionViewController.swift index aac562ef0..e3a0d715f 100644 --- a/Mac/SafariExtension/SafariExtensionViewController.swift +++ b/Mac/SafariExtension/SafariExtensionViewController.swift @@ -13,7 +13,7 @@ class SafariExtensionViewController: SFSafariExtensionViewController { // This would be the place to handle a popover that could, for example, list the possibly multiple feeds offered by a site. static let shared: SafariExtensionViewController = { let shared = SafariExtensionViewController() - shared.preferredContentSize = NSSize(width:320, height:240) + shared.preferredContentSize = NSSize(width: 320, height: 240) return shared }() diff --git a/Mac/Scriptability/Account+Scriptability.swift b/Mac/Scriptability/Account+Scriptability.swift index 0c7c84380..5b7c5fc06 100644 --- a/Mac/Scriptability/Account+Scriptability.swift +++ b/Mac/Scriptability/Account+Scriptability.swift @@ -13,16 +13,16 @@ import RSCore @objc(ScriptableAccount) class ScriptableAccount: NSObject, UniqueIdScriptingObject, ScriptingObjectContainer { - - let account:Account - init (_ account:Account) { + + let account: Account + init (_ account: Account) { self.account = account } - + @objc(objectSpecifier) override var objectSpecifier: NSScriptObjectSpecifier? { let myContainer = NSApplication.shared - let scriptObjectSpecifier = myContainer.makeFormUniqueIDScriptObjectSpecifier(forObject:self) + let scriptObjectSpecifier = myContainer.makeFormUniqueIDScriptObjectSpecifier(forObject: self) return (scriptObjectSpecifier) } @@ -47,91 +47,91 @@ class ScriptableAccount: NSObject, UniqueIdScriptingObject, ScriptingObjectConta } // MARK: --- ScriptingObject protocol --- - + var scriptingKey: String { return "accounts" } // MARK: --- UniqueIdScriptingObject protocol --- - + // I am not sure if account should prefer to be specified by name or by ID // but in either case it seems like the accountID would be used as the keydata, so I chose ID @objc(uniqueId) - var scriptingUniqueId:Any { + var scriptingUniqueId: Any { return account.accountID } // MARK: --- ScriptingObjectContainer protocol --- - + var scriptingClassDescription: NSScriptClassDescription { return self.classDescription as! NSScriptClassDescription } - - func deleteElement(_ element:ScriptingObject) { + + func deleteElement(_ element: ScriptingObject) { if let scriptableFolder = element as? ScriptableFolder { BatchUpdate.shared.perform { - account.removeFolder(scriptableFolder.folder) { result in + account.removeFolder(scriptableFolder.folder) { _ in } } } else if let scriptableFeed = element as? ScriptableFeed { BatchUpdate.shared.perform { - var container: Container? = nil + var container: Container? if let scriptableFolder = scriptableFeed.container as? ScriptableFolder { container = scriptableFolder.folder } else { container = account } - account.removeFeed(scriptableFeed.feed, from: container!) { result in + account.removeFeed(scriptableFeed.feed, from: container!) { _ in } } } } @objc(isLocationRequiredToCreateForKey:) - func isLocationRequiredToCreate(forKey key:String) -> Bool { - return false; + func isLocationRequiredToCreate(forKey key: String) -> Bool { + return false } // MARK: --- Scriptable elements --- - + @objc(feeds) - var feeds:NSArray { - return account.topLevelFeeds.map { ScriptableFeed($0, container:self) } as NSArray + var feeds: NSArray { + return account.topLevelFeeds.map { ScriptableFeed($0, container: self) } as NSArray } - + @objc(valueInFeedsWithUniqueID:) - func valueInFeeds(withUniqueID id:String) -> ScriptableFeed? { + func valueInFeeds(withUniqueID id: String) -> ScriptableFeed? { guard let feed = account.existingFeed(withFeedID: id) else { return nil } - return ScriptableFeed(feed, container:self) + return ScriptableFeed(feed, container: self) } - + @objc(valueInFeedsWithName:) - func valueInFeeds(withName name:String) -> ScriptableFeed? { + func valueInFeeds(withName name: String) -> ScriptableFeed? { let feeds = Array(account.flattenedFeeds()) - guard let feed = feeds.first(where:{$0.name == name}) else { return nil } - return ScriptableFeed(feed, container:self) + guard let feed = feeds.first(where: {$0.name == name}) else { return nil } + return ScriptableFeed(feed, container: self) } @objc(folders) - var folders:NSArray { + var folders: NSArray { let foldersSet = account.folders ?? Set() let folders = Array(foldersSet) - return folders.map { ScriptableFolder($0, container:self) } as NSArray + return folders.map { ScriptableFolder($0, container: self) } as NSArray } - + @objc(valueInFoldersWithUniqueID:) - func valueInFolders(withUniqueID id:NSNumber) -> ScriptableFolder? { + func valueInFolders(withUniqueID id: NSNumber) -> ScriptableFolder? { let folderId = id.intValue let foldersSet = account.folders ?? Set() let folders = Array(foldersSet) - guard let folder = folders.first(where:{$0.folderID == folderId}) else { return nil } - return ScriptableFolder(folder, container:self) - } + guard let folder = folders.first(where: {$0.folderID == folderId}) else { return nil } + return ScriptableFolder(folder, container: self) + } // MARK: --- Scriptable properties --- @objc(allFeeds) - var allFeeds: NSArray { + var allFeeds: NSArray { var feeds = [ScriptableFeed]() for feed in account.topLevelFeeds { feeds.append(ScriptableFeed(feed, container: self)) @@ -148,13 +148,13 @@ class ScriptableAccount: NSObject, UniqueIdScriptingObject, ScriptingObjectConta } @objc(opmlRepresentation) - var opmlRepresentation:String { - return self.account.OPMLString(indentLevel:0) + var opmlRepresentation: String { + return self.account.OPMLString(indentLevel: 0) } @objc(accountType) - var accountType:OSType { - var osType:String = "" + var accountType: OSType { + var osType: String = "" switch self.account.type { case .onMyMac: osType = "Locl" diff --git a/Mac/Scriptability/AppDelegate+Scriptability.swift b/Mac/Scriptability/AppDelegate+Scriptability.swift index b7c76de0f..cea394199 100644 --- a/Mac/Scriptability/AppDelegate+Scriptability.swift +++ b/Mac/Scriptability/AppDelegate+Scriptability.swift @@ -25,19 +25,19 @@ protocol AppDelegateAppleEvents { } protocol ScriptingAppDelegate { - var scriptingCurrentArticle: Article? {get} - var scriptingSelectedArticles: [Article] {get} - var scriptingMainWindowController:ScriptingMainWindowController? {get} + var scriptingCurrentArticle: Article? {get} + var scriptingSelectedArticles: [Article] {get} + var scriptingMainWindowController: ScriptingMainWindowController? {get} } -extension AppDelegate : AppDelegateAppleEvents { - +extension AppDelegate: AppDelegateAppleEvents { + // MARK: GetURL Apple Event func installAppleEventHandlers() { NSAppleEventManager.shared().setEventHandler(self, andSelector: #selector(AppDelegate.getURL(_:_:)), forEventClass: AEEventClass(kInternetEventClass), andEventID: AEEventID(kAEGetURL)) } - + @objc func getURL(_ event: NSAppleEventDescriptor, _ withReplyEvent: NSAppleEventDescriptor) { guard var urlString = event.paramDescriptor(forKeyword: keyDirectObject)?.stringValue else { @@ -51,14 +51,14 @@ extension AppDelegate : AppDelegateAppleEvents { let themeURLString = queryItems.first(where: { $0.name == "url" })?.value else { return } - + if let themeURL = URL(string: themeURLString) { let request = URLRequest(url: themeURL) - let task = URLSession.shared.downloadTask(with: request) { location, response, error in + let task = URLSession.shared.downloadTask(with: request) { location, _, error in guard let location = location else { return } - + do { try ArticleThemeDownloader.shared.handleFile(at: location) } catch { @@ -68,10 +68,9 @@ extension AppDelegate : AppDelegateAppleEvents { task.resume() } return - + } - - + // Special case URL with specific scheme handler x-netnewswire-feed: intended to ensure we open // it regardless of which news reader may be set as the default let nnwScheme = "x-netnewswire-feed:" @@ -91,13 +90,13 @@ extension AppDelegate : AppDelegateAppleEvents { } } -class NetNewsWireCreateElementCommand : NSCreateCommand { +class NetNewsWireCreateElementCommand: NSCreateCommand { override func performDefaultImplementation() -> Any? { let classDescription = self.createClassDescription - if (classDescription.className == "feed") { - return ScriptableFeed.handleCreateElement(command:self) - } else if (classDescription.className == "folder") { - return ScriptableFolder.handleCreateElement(command:self) + if classDescription.className == "feed" { + return ScriptableFeed.handleCreateElement(command: self) + } else if classDescription.className == "folder" { + return ScriptableFolder.handleCreateElement(command: self) } return nil } @@ -111,7 +110,7 @@ class NetNewsWireCreateElementCommand : NSCreateCommand { is ambiguity about whether specifiers are lists or single objects, the code switches based on which it is. */ -class NetNewsWireDeleteCommand : NSDeleteCommand { +class NetNewsWireDeleteCommand: NSDeleteCommand { /* delete(objectToDelete:, from container:) @@ -119,16 +118,16 @@ class NetNewsWireDeleteCommand : NSDeleteCommand { Here the code unravels the case of objectToDelete being a list or a single object, ultimately calling container.deleteElement(element) for each element to delete */ - func delete(objectToDelete:Any, from container:ScriptingObjectContainer) { + func delete(objectToDelete: Any, from container: ScriptingObjectContainer) { if let objectList = objectToDelete as? [Any] { for nthObject in objectList { - self.delete(objectToDelete:nthObject, from:container) + self.delete(objectToDelete: nthObject, from: container) } } else if let element = objectToDelete as? ScriptingObject { container.deleteElement(element) } } - + /* delete(specifier:, from container:) At this point in handling the command, the container could be a list or a single object, @@ -138,14 +137,14 @@ class NetNewsWireDeleteCommand : NSDeleteCommand { After resolving, we call delete(objectToDelete:, from container:) with the container and the resolved objects */ - func delete(specifier:NSScriptObjectSpecifier, from container:Any) { + func delete(specifier: NSScriptObjectSpecifier, from container: Any) { if let containerList = container as? [Any] { for nthObject in containerList { - self.delete(specifier:specifier, from:nthObject) + self.delete(specifier: specifier, from: nthObject) } } else if let container = container as? ScriptingObjectContainer { - if let resolvedObjects = specifier.objectsByEvaluating(withContainers:container) { - self.delete(objectToDelete:resolvedObjects, from:container) + if let resolvedObjects = specifier.objectsByEvaluating(withContainers: container) { + self.delete(objectToDelete: resolvedObjects, from: container) } } } @@ -159,14 +158,14 @@ class NetNewsWireDeleteCommand : NSDeleteCommand { override func performDefaultImplementation() -> Any? { if let receiversSpecifier = self.receiversSpecifier { if let receiverObjects = receiversSpecifier.objectsByEvaluatingSpecifier { - self.delete(specifier:self.keySpecifier, from:receiverObjects) - } + self.delete(specifier: self.keySpecifier, from: receiverObjects) + } } return nil } } -class NetNewsWireExistsCommand : NSExistsCommand { +class NetNewsWireExistsCommand: NSExistsCommand { // cocoa default behavior doesn't work here, because of cases where we define an object's property // to be another object type. e.g., 'permalink of the current article' parses as @@ -177,10 +176,9 @@ class NetNewsWireExistsCommand : NSExistsCommand { // must not exist. Otherwise, we return the result of the defaultImplementation // The wrinkle is that it is possible that the direct object is a list, so we need to // handle that case as well - + override func performDefaultImplementation() -> Any? { - guard let result = super.performDefaultImplementation() else { return NSNumber(booleanLiteral:false) } + guard let result = super.performDefaultImplementation() else { return NSNumber(booleanLiteral: false) } return result } } - diff --git a/Mac/Scriptability/Article+Scriptability.swift b/Mac/Scriptability/Article+Scriptability.swift index 094a023a3..2b7b9ddbd 100644 --- a/Mac/Scriptability/Article+Scriptability.swift +++ b/Mac/Scriptability/Article+Scriptability.swift @@ -13,17 +13,17 @@ import Articles @objc(ScriptableArticle) class ScriptableArticle: NSObject, UniqueIdScriptingObject, ScriptingObjectContainer { - let article:Article - let container:ScriptingObjectContainer - - init (_ article:Article, container:ScriptingObjectContainer) { + let article: Article + let container: ScriptingObjectContainer + + init (_ article: Article, container: ScriptingObjectContainer) { self.article = article self.container = container } @objc(objectSpecifier) override var objectSpecifier: NSScriptObjectSpecifier? { - let scriptObjectSpecifier = self.container.makeFormUniqueIDScriptObjectSpecifier(forObject:self) + let scriptObjectSpecifier = self.container.makeFormUniqueIDScriptObjectSpecifier(forObject: self) return (scriptObjectSpecifier) } @@ -39,76 +39,76 @@ class ScriptableArticle: NSObject, UniqueIdScriptingObject, ScriptingObjectConta // article.uniqueID here is the feed unique id @objc(uniqueId) - var scriptingUniqueId:Any { + var scriptingUniqueId: Any { return article.uniqueID } // MARK: --- ScriptingObjectContainer protocol --- - + var scriptingClassDescription: NSScriptClassDescription { return self.classDescription as! NSScriptClassDescription } - - func deleteElement(_ element:ScriptingObject) { - print ("delete event not handled") + + func deleteElement(_ element: ScriptingObject) { + print("delete event not handled") } // MARK: --- Scriptable properties --- - + @objc(url) - var url:String? { + var url: String? { return article.preferredLink } @objc(permalink) - var permalink:String? { + var permalink: String? { return article.link } @objc(externalUrl) - var externalUrl:String? { + var externalUrl: String? { return article.externalLink } - + @objc(title) - var title:String { + var title: String { return article.title ?? "" } @objc(contents) - var contents:String { + var contents: String { return article.contentText ?? "" } @objc(html) - var html:String { + var html: String { return article.contentHTML ?? "" } @objc(summary) - var summary:String { + var summary: String { return article.summary ?? "" } @objc(datePublished) - var datePublished:Date? { + var datePublished: Date? { return article.datePublished } @objc(dateModified) - var dateModified:Date? { + var dateModified: Date? { return article.dateModified } @objc(dateArrived) - var dateArrived:Date { + var dateArrived: Date { return article.status.dateArrived } @objc(read) - var read:Bool { + var read: Bool { get { - return article.status.boolStatus(forKey:.read) + return article.status.boolStatus(forKey: .read) } set { markArticles([self.article], statusKey: .read, flag: newValue) @@ -116,9 +116,9 @@ class ScriptableArticle: NSObject, UniqueIdScriptingObject, ScriptingObjectConta } @objc(starred) - var starred:Bool { + var starred: Bool { get { - return article.status.boolStatus(forKey:.starred) + return article.status.boolStatus(forKey: .starred) } set { markArticles([self.article], statusKey: .starred, flag: newValue) @@ -126,19 +126,19 @@ class ScriptableArticle: NSObject, UniqueIdScriptingObject, ScriptingObjectConta } @objc(deleted) - var deleted:Bool { + var deleted: Bool { return false } @objc(imageURL) - var imageURL:String { + var imageURL: String { return article.imageLink ?? "" } - + @objc(authors) - var authors:NSArray { + var authors: NSArray { let articleAuthors = article.authors ?? [] - return articleAuthors.map { ScriptableAuthor($0, container:self) } as NSArray + return articleAuthors.map { ScriptableAuthor($0, container: self) } as NSArray } @objc(feed) diff --git a/Mac/Scriptability/Author+Scriptability.swift b/Mac/Scriptability/Author+Scriptability.swift index 1b39cb0e9..524cb6f80 100644 --- a/Mac/Scriptability/Author+Scriptability.swift +++ b/Mac/Scriptability/Author+Scriptability.swift @@ -13,17 +13,17 @@ import Articles @objc(ScriptableAuthor) class ScriptableAuthor: NSObject, UniqueIdScriptingObject { - let author:Author - let container:ScriptingObjectContainer - - init (_ author:Author, container:ScriptingObjectContainer) { + let author: Author + let container: ScriptingObjectContainer + + init (_ author: Author, container: ScriptingObjectContainer) { self.author = author self.container = container } @objc(objectSpecifier) override var objectSpecifier: NSScriptObjectSpecifier? { - let scriptObjectSpecifier = self.container.makeFormUniqueIDScriptObjectSpecifier(forObject:self) + let scriptObjectSpecifier = self.container.makeFormUniqueIDScriptObjectSpecifier(forObject: self) return (scriptObjectSpecifier) } @@ -41,29 +41,29 @@ class ScriptableAuthor: NSObject, UniqueIdScriptingObject { // MARK: --- UniqueIdScriptingObject protocol --- @objc(uniqueId) - var scriptingUniqueId:Any { + var scriptingUniqueId: Any { return author.authorID } - + // MARK: --- Scriptable properties --- - + @objc(url) - var url:String { + var url: String { return self.author.url ?? "" } - + @objc(name) - var name:String { + var name: String { return self.author.name ?? "" } @objc(avatarURL) - var avatarURL:String { + var avatarURL: String { return self.author.avatarURL ?? "" } @objc(emailAddress) - var emailAddress:String { + var emailAddress: String { return self.author.emailAddress ?? "" } } diff --git a/Mac/Scriptability/Folder+Scriptability.swift b/Mac/Scriptability/Folder+Scriptability.swift index 61925db59..e60b45182 100644 --- a/Mac/Scriptability/Folder+Scriptability.swift +++ b/Mac/Scriptability/Folder+Scriptability.swift @@ -14,17 +14,17 @@ import RSCore @objc(ScriptableFolder) class ScriptableFolder: NSObject, UniqueIdScriptingObject, ScriptingObjectContainer { - let folder:Folder - let container:ScriptingObjectContainer + let folder: Folder + let container: ScriptingObjectContainer - init (_ folder:Folder, container:ScriptingObjectContainer) { + init (_ folder: Folder, container: ScriptingObjectContainer) { self.folder = folder self.container = container } @objc(objectSpecifier) override var objectSpecifier: NSScriptObjectSpecifier? { - let scriptObjectSpecifier = self.container.makeFormUniqueIDScriptObjectSpecifier(forObject:self) + let scriptObjectSpecifier = self.container.makeFormUniqueIDScriptObjectSpecifier(forObject: self) return (scriptObjectSpecifier) } @@ -40,20 +40,20 @@ class ScriptableFolder: NSObject, UniqueIdScriptingObject, ScriptingObjectContai // but in either case it seems like the accountID would be used as the keydata, so I chose ID @objc(uniqueId) - var scriptingUniqueId:Any { + var scriptingUniqueId: Any { return folder.folderID } - + // MARK: --- ScriptingObjectContainer protocol --- - + var scriptingClassDescription: NSScriptClassDescription { return self.classDescription as! NSScriptClassDescription } - - func deleteElement(_ element:ScriptingObject) { + + func deleteElement(_ element: ScriptingObject) { if let scriptableFeed = element as? ScriptableFeed { BatchUpdate.shared.perform { - folder.account?.removeFeed(scriptableFeed.feed, from: folder) { result in } + folder.account?.removeFeed(scriptableFeed.feed, from: folder) { _ in } } } } @@ -65,52 +65,52 @@ class ScriptableFolder: NSObject, UniqueIdScriptingObject, ScriptingObjectContai or tell account X to make new folder at end with properties {name:"new folder name"} */ - class func handleCreateElement(command:NSCreateCommand) -> Any? { - guard command.isCreateCommand(forClass:"fold") else { return nil } - let name = command.property(forKey:"name") as? String ?? "" + class func handleCreateElement(command: NSCreateCommand) -> Any? { + guard command.isCreateCommand(forClass: "fold") else { return nil } + let name = command.property(forKey: "name") as? String ?? "" // some combination of the tell target and the location specifier ("in" or "at") // identifies where the new folder should be created let (account, folder) = command.accountAndFolderForNewChild() guard folder == nil else { - print("support for folders within folders is NYI"); + print("support for folders within folders is NYI") return nil } - + command.suspendExecution() - + account.addFolder(name) { result in switch result { case .success(let folder): let scriptableAccount = ScriptableAccount(account) - let scriptableFolder = ScriptableFolder(folder, container:scriptableAccount) - command.resumeExecution(withResult:scriptableFolder.objectSpecifier) + let scriptableFolder = ScriptableFolder(folder, container: scriptableAccount) + command.resumeExecution(withResult: scriptableFolder.objectSpecifier) case .failure: - command.resumeExecution(withResult:nil) + command.resumeExecution(withResult: nil) } } - + return nil } - + // MARK: --- Scriptable elements --- - + @objc(feeds) - var feeds:NSArray { + var feeds: NSArray { let feeds = Array(folder.topLevelFeeds) - return feeds.map { ScriptableFeed($0, container:self) } as NSArray + return feeds.map { ScriptableFeed($0, container: self) } as NSArray } // MARK: --- Scriptable properties --- - + @objc(name) - var name:String { + var name: String { return self.folder.name ?? "" } @objc(opmlRepresentation) - var opmlRepresentation:String { - return self.folder.OPMLString(indentLevel:0) + var opmlRepresentation: String { + return self.folder.OPMLString(indentLevel: 0) } } diff --git a/Mac/Scriptability/MainWindowController+Scriptability.swift b/Mac/Scriptability/MainWindowController+Scriptability.swift index e83a29711..8b5b07b81 100644 --- a/Mac/Scriptability/MainWindowController+Scriptability.swift +++ b/Mac/Scriptability/MainWindowController+Scriptability.swift @@ -13,4 +13,3 @@ protocol ScriptingMainWindowController { var scriptingCurrentArticle: Article? { get } var scriptingSelectedArticles: [Article] { get } } - diff --git a/Mac/Scriptability/NSApplication+Scriptability.swift b/Mac/Scriptability/NSApplication+Scriptability.swift index c49f92de3..dd5e5a657 100644 --- a/Mac/Scriptability/NSApplication+Scriptability.swift +++ b/Mac/Scriptability/NSApplication+Scriptability.swift @@ -10,7 +10,7 @@ import AppKit import Account import Articles -extension NSApplication : ScriptingObjectContainer { +extension NSApplication: ScriptingObjectContainer { // MARK: --- ScriptingObjectContainer protocol --- @@ -18,21 +18,21 @@ extension NSApplication : ScriptingObjectContainer { return NSApplication.shared.classDescription as! NSScriptClassDescription } - func deleteElement(_ element:ScriptingObject) { - print ("delete event not handled") + func deleteElement(_ element: ScriptingObject) { + print("delete event not handled") } var scriptingKey: String { return "application" } - + @objc(currentArticle) func currentArticle() -> ScriptableArticle? { var scriptableArticle: ScriptableArticle? if let currentArticle = appDelegate.scriptingCurrentArticle { if let feed = currentArticle.feed { - let scriptableFeed = ScriptableFeed(feed, container:self) - scriptableArticle = ScriptableArticle(currentArticle, container:scriptableFeed) + let scriptableFeed = ScriptableFeed(feed, container: self) + scriptableArticle = ScriptableArticle(currentArticle, container: scriptableFeed) } } return scriptableArticle @@ -41,8 +41,8 @@ extension NSApplication : ScriptingObjectContainer { @objc(selectedArticles) func selectedArticles() -> NSArray { let articles = appDelegate.scriptingSelectedArticles - let scriptableArticles:[ScriptableArticle] = articles.compactMap { article in - if let feed = article.feed, let account = feed.account { + let scriptableArticles: [ScriptableArticle] = articles.compactMap { article in + if let feed = article.feed, let account = feed.account { let scriptableFeed = ScriptableFeed(feed, container: ScriptableAccount(account)) return ScriptableArticle(article, container: scriptableFeed) } else { @@ -59,11 +59,11 @@ extension NSApplication : ScriptingObjectContainer { let accounts = AccountManager.shared.accounts return accounts.map { ScriptableAccount($0) } as NSArray } - + @objc(valueInAccountsWithUniqueID:) - func valueInAccounts(withUniqueID id:String) -> ScriptableAccount? { + func valueInAccounts(withUniqueID id: String) -> ScriptableAccount? { let accounts = AccountManager.shared.accounts - guard let account = accounts.first(where:{$0.accountID == id}) else { return nil } + guard let account = accounts.first(where: {$0.accountID == id}) else { return nil } return ScriptableAccount(account) } @@ -72,10 +72,10 @@ extension NSApplication : ScriptingObjectContainer { this allows a script like 'articles of feed "The Shape of Everything"' as a shorthand for 'articles of feed "The Shape of Everything" of account "On My Mac"' */ - + func allFeeds() -> [Feed] { let accounts = AccountManager.shared.activeAccounts - let emptyFeeds:[Feed] = [] + let emptyFeeds: [Feed] = [] return accounts.reduce(emptyFeeds) { (result, nthAccount) -> [Feed] in let accountFeeds = Array(nthAccount.topLevelFeeds) return result + accountFeeds @@ -85,15 +85,13 @@ extension NSApplication : ScriptingObjectContainer { @objc(feeds) func feeds() -> NSArray { let feeds = self.allFeeds() - return feeds.map { ScriptableFeed($0, container:self) } as NSArray + return feeds.map { ScriptableFeed($0, container: self) } as NSArray } @objc(valueInFeedsWithUniqueID:) - func valueInFeeds(withUniqueID id:String) -> ScriptableFeed? { + 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) + guard let feed = feeds.first(where: {$0.feedID == id}) else { return nil } + return ScriptableFeed(feed, container: self) } } - - diff --git a/Mac/Scriptability/NSScriptCommand+NetNewsWire.swift b/Mac/Scriptability/NSScriptCommand+NetNewsWire.swift index 1cc5e36e5..279d6e04e 100644 --- a/Mac/Scriptability/NSScriptCommand+NetNewsWire.swift +++ b/Mac/Scriptability/NSScriptCommand+NetNewsWire.swift @@ -10,43 +10,43 @@ import Foundation import Account extension NSScriptCommand { - func property(forKey key:String) -> Any? { - if let evaluatedArguments = self.evaluatedArguments { + func property(forKey key: String) -> Any? { + if let evaluatedArguments = self.evaluatedArguments { if let props = evaluatedArguments["KeyDictionary"] as? [String: Any] { - return props[key] + return props[key] } } return nil } - - func isCreateCommand(forClass whatClass:String) -> Bool { + + func isCreateCommand(forClass whatClass: String) -> Bool { guard let arguments = self.arguments else {return false} guard let newObjectClass = arguments["ObjectClass"] as? Int else {return false} - guard (newObjectClass.fourCharCode == whatClass.fourCharCode) else {return false} + guard newObjectClass.fourCharCode == whatClass.fourCharCode else {return false} return true } func accountAndFolderForNewChild() -> (Account, Folder?) { let appleEvent = self.appleEvent var account = AccountManager.shared.defaultAccount - var folder:Folder? = nil + var folder: Folder? if let appleEvent = appleEvent { - var descriptorToConsider:NSAppleEventDescriptor? - if let insertionLocationDescriptor = appleEvent.paramDescriptor(forKeyword:keyAEInsertHere) { + var descriptorToConsider: NSAppleEventDescriptor? + if let insertionLocationDescriptor = appleEvent.paramDescriptor(forKeyword: keyAEInsertHere) { print("insertionLocation : \(insertionLocationDescriptor)") // insertion location can be a typeObjectSpecifier, e.g. 'in account "Acct"' // or a typeInsertionLocation, e.g. 'at end of folder " - if (insertionLocationDescriptor.descriptorType == "insl".fourCharCode) { + if insertionLocationDescriptor.descriptorType == "insl".fourCharCode { descriptorToConsider = insertionLocationDescriptor.forKeyword("kobj".fourCharCode) - } else if ( insertionLocationDescriptor.descriptorType == "obj ".fourCharCode) { + } else if insertionLocationDescriptor.descriptorType == "obj ".fourCharCode { descriptorToConsider = insertionLocationDescriptor } - } else if let subjectDescriptor = appleEvent.attributeDescriptor(forKeyword:"subj".fourCharCode) { + } else if let subjectDescriptor = appleEvent.attributeDescriptor(forKeyword: "subj".fourCharCode) { descriptorToConsider = subjectDescriptor } - + if let descriptorToConsider = descriptorToConsider { - guard let newContainerSpecifier = NSScriptObjectSpecifier(descriptor:descriptorToConsider) else {return (account, folder)} + guard let newContainerSpecifier = NSScriptObjectSpecifier(descriptor: descriptorToConsider) else {return (account, folder)} let newContainer = newContainerSpecifier.objectsByEvaluatingSpecifier if let scriptableAccount = newContainer as? ScriptableAccount { account = scriptableAccount.account diff --git a/Mac/Scriptability/ScriptingObject.swift b/Mac/Scriptability/ScriptingObject.swift index 64187b6dc..d90c66bc6 100644 --- a/Mac/Scriptability/ScriptingObject.swift +++ b/Mac/Scriptability/ScriptingObject.swift @@ -9,14 +9,14 @@ import Foundation protocol ScriptingObject { - var objectSpecifier: NSScriptObjectSpecifier? { get } + var objectSpecifier: NSScriptObjectSpecifier? { get } var scriptingKey: String { get } } protocol NamedScriptingObject: ScriptingObject { - var name:String { get } + var name: String { get } } protocol UniqueIdScriptingObject: ScriptingObject { - var scriptingUniqueId:Any { get } + var scriptingUniqueId: Any { get } } diff --git a/Mac/Scriptability/ScriptingObjectContainer.swift b/Mac/Scriptability/ScriptingObjectContainer.swift index 8bfe695d7..4bae74251 100644 --- a/Mac/Scriptability/ScriptingObjectContainer.swift +++ b/Mac/Scriptability/ScriptingObjectContainer.swift @@ -10,30 +10,29 @@ import AppKit import Account protocol ScriptingObjectContainer: ScriptingObject { - var scriptingClassDescription:NSScriptClassDescription { get } - func deleteElement(_ element:ScriptingObject) + var scriptingClassDescription: NSScriptClassDescription { get } + func deleteElement(_ element: ScriptingObject) } extension ScriptingObjectContainer { - func makeFormNameScriptObjectSpecifier(forObject object:NamedScriptingObject) -> NSScriptObjectSpecifier? { + func makeFormNameScriptObjectSpecifier(forObject object: NamedScriptingObject) -> NSScriptObjectSpecifier? { let containerClassDescription = self.scriptingClassDescription let containerScriptObjectSpecifier = self.objectSpecifier let scriptingKey = object.scriptingKey let name = object.name - let specifier = NSNameSpecifier(containerClassDescription:containerClassDescription, - containerSpecifier:containerScriptObjectSpecifier, key:scriptingKey, name:name) + let specifier = NSNameSpecifier(containerClassDescription: containerClassDescription, + containerSpecifier: containerScriptObjectSpecifier, key: scriptingKey, name: name) return specifier } - - func makeFormUniqueIDScriptObjectSpecifier(forObject object:UniqueIdScriptingObject) -> NSScriptObjectSpecifier? { + + func makeFormUniqueIDScriptObjectSpecifier(forObject object: UniqueIdScriptingObject) -> NSScriptObjectSpecifier? { let containerClassDescription = self.scriptingClassDescription let containerScriptObjectSpecifier = self.objectSpecifier let scriptingKey = object.scriptingKey let uniqueId = object.scriptingUniqueId - let specifier = NSUniqueIDSpecifier(containerClassDescription:containerClassDescription, - containerSpecifier:containerScriptObjectSpecifier, key:scriptingKey, uniqueID: uniqueId) + let specifier = NSUniqueIDSpecifier(containerClassDescription: containerClassDescription, + containerSpecifier: containerScriptObjectSpecifier, key: scriptingKey, uniqueID: uniqueId) return specifier } } - diff --git a/Mac/Scriptability/WebFeed+Scriptability.swift b/Mac/Scriptability/WebFeed+Scriptability.swift index 59387c7ec..30e3e88c9 100644 --- a/Mac/Scriptability/WebFeed+Scriptability.swift +++ b/Mac/Scriptability/WebFeed+Scriptability.swift @@ -16,15 +16,15 @@ class ScriptableFeed: NSObject, UniqueIdScriptingObject, ScriptingObjectContaine let feed: Feed let container: ScriptingObjectContainer - - init (_ feed:Feed, container:ScriptingObjectContainer) { + + init (_ feed: Feed, container: ScriptingObjectContainer) { self.feed = feed self.container = container } @objc(objectSpecifier) override var objectSpecifier: NSScriptObjectSpecifier? { - let scriptObjectSpecifier = self.container.makeFormUniqueIDScriptObjectSpecifier(forObject:self) + let scriptObjectSpecifier = self.container.makeFormUniqueIDScriptObjectSpecifier(forObject: self) return (scriptObjectSpecifier) } @@ -44,56 +44,56 @@ class ScriptableFeed: NSObject, UniqueIdScriptingObject, ScriptingObjectContaine // I am not sure if account should prefer to be specified by name or by ID // but in either case it seems like the accountID would be used as the keydata, so I chose ID @objc(uniqueId) - var scriptingUniqueId:Any { + var scriptingUniqueId: Any { return feed.feedID } // MARK: --- ScriptingObjectContainer protocol --- - + var scriptingClassDescription: NSScriptClassDescription { return self.classDescription as! NSScriptClassDescription } - - func deleteElement(_ element:ScriptingObject) { + + func deleteElement(_ element: ScriptingObject) { } // MARK: --- handle NSCreateCommand --- - class func urlForNewFeed(arguments:[String:Any]) -> String? { - var url:String? + class func urlForNewFeed(arguments: [String: Any]) -> String? { + var url: String? if let withDataParam = arguments["ObjectData"] { if let objectDataDescriptor = withDataParam as? NSAppleEventDescriptor { url = objectDataDescriptor.stringValue } - } else if let withPropsParam = arguments["ObjectProperties"] as? [String:Any] { + } else if let withPropsParam = arguments["ObjectProperties"] as? [String: Any] { url = withPropsParam["url"] as? String } return url } - - class func scriptableFeed(_ feed:Feed, account:Account, folder:Folder?) -> ScriptableFeed { + + 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 ScriptableFeed(feed, container:scriptableFolder) - } else { - return ScriptableFeed(feed, container:scriptableAccount) + let scriptableFolder = ScriptableFolder(folder, container: scriptableAccount) + return ScriptableFeed(feed, container: scriptableFolder) + } else { + return ScriptableFeed(feed, container: scriptableAccount) } } - - class func handleCreateElement(command:NSCreateCommand) -> Any? { - guard command.isCreateCommand(forClass:"Feed") else { return nil } + + class func handleCreateElement(command: NSCreateCommand) -> Any? { + guard command.isCreateCommand(forClass: "Feed") else { return nil } guard let arguments = command.arguments else {return nil} - let titleFromArgs = command.property(forKey:"name") as? String + let titleFromArgs = command.property(forKey: "name") as? String let (account, folder) = command.accountAndFolderForNewChild() - guard let url = self.urlForNewFeed(arguments:arguments) else {return nil} - - if let existingFeed = account.existingFeed(withURL:url) { - return scriptableFeed(existingFeed, account:account, folder:folder).objectSpecifier + guard let url = self.urlForNewFeed(arguments: arguments) else {return nil} + + if let existingFeed = account.existingFeed(withURL: url) { + return scriptableFeed(existingFeed, account: account, folder: folder).objectSpecifier } - + let container: Container = folder != nil ? folder! : account - + // We need to download the feed and parse it. // Parser does the callback for the download on the main thread. // Because we can't wait here (on the main thread) for the callback, we have to return from this function. @@ -101,83 +101,83 @@ class ScriptableFeed: NSObject, UniqueIdScriptingObject, ScriptingObjectContaine // but we don’t yet have the result of the event yet, so we prevent the Apple event from returning by calling // suspendExecution(). When we get the callback, we supply the event result and call resumeExecution(). command.suspendExecution() - + 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.feed: feed]) - let scriptableFeed = self.scriptableFeed(feed, account:account, folder:folder) - command.resumeExecution(withResult:scriptableFeed.objectSpecifier) + let scriptableFeed = self.scriptableFeed(feed, account: account, folder: folder) + command.resumeExecution(withResult: scriptableFeed.objectSpecifier) case .failure: - command.resumeExecution(withResult:nil) + command.resumeExecution(withResult: nil) } } - + return nil } // MARK: --- Scriptable properties --- @objc(url) - var url:String { + var url: String { return self.feed.url } - + @objc(name) - var name:String { + var name: String { return self.feed.name ?? "" } @objc(homePageURL) - var homePageURL:String { + var homePageURL: String { return self.feed.homePageURL ?? "" } @objc(iconURL) - var iconURL:String { + var iconURL: String { return self.feed.iconURL ?? "" } @objc(faviconURL) - var faviconURL:String { + var faviconURL: String { return self.feed.faviconURL ?? "" } @objc(opmlRepresentation) - var opmlRepresentation:String { - return self.feed.OPMLString(indentLevel:0) + var opmlRepresentation: String { + return self.feed.OPMLString(indentLevel: 0) } - + // MARK: --- scriptable elements --- @objc(authors) - var authors:NSArray { + var authors: NSArray { let feedAuthors = feed.authors ?? [] - return feedAuthors.map { ScriptableAuthor($0, container:self) } as NSArray + return feedAuthors.map { ScriptableAuthor($0, container: self) } as NSArray } - + @objc(valueInAuthorsWithUniqueID:) - func valueInAuthors(withUniqueID id:String) -> ScriptableAuthor? { - guard let author = feed.authors?.first(where:{$0.authorID == id}) else { return nil } - return ScriptableAuthor(author, container:self) + func valueInAuthors(withUniqueID id: String) -> ScriptableAuthor? { + guard let author = feed.authors?.first(where: {$0.authorID == id}) else { return nil } + return ScriptableAuthor(author, container: self) } - + @objc(articles) - var articles:NSArray { + var articles: NSArray { let feedArticles = (try? feed.fetchArticles()) ?? Set
() // the articles are a set, use the sorting algorithm from the viewer - let sortedArticles = feedArticles.sorted(by:{ + let sortedArticles = feedArticles.sorted(by: { return $0.logicalDatePublished > $1.logicalDatePublished }) - return sortedArticles.map { ScriptableArticle($0, container:self) } as NSArray + return sortedArticles.map { ScriptableArticle($0, container: self) } as NSArray } - + @objc(valueInArticlesWithUniqueID:) - func valueInArticles(withUniqueID id:String) -> ScriptableArticle? { + func valueInArticles(withUniqueID id: String) -> ScriptableArticle? { let articles = (try? feed.fetchArticles()) ?? Set
() - guard let article = articles.first(where:{$0.uniqueID == id}) else { return nil } - return ScriptableArticle(article, container:self) + guard let article = articles.first(where: {$0.uniqueID == id}) else { return nil } + return ScriptableArticle(article, container: self) } } diff --git a/Mac/ShareExtension/ShareViewController.swift b/Mac/ShareExtension/ShareViewController.swift index 85a58488f..9301f78a3 100644 --- a/Mac/ShareExtension/ShareViewController.swift +++ b/Mac/ShareExtension/ShareViewController.swift @@ -14,7 +14,7 @@ class ShareViewController: NSViewController { @IBOutlet weak var nameTextField: NSTextField! @IBOutlet weak var folderPopUpButton: NSPopUpButton! - + private var url: URL? private var extensionContainers: ExtensionContainers? private var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "ShareViewController") @@ -25,11 +25,11 @@ class ShareViewController: NSViewController { override func loadView() { super.loadView() - + extensionContainers = ExtensionContainersFile.read() buildFolderPopupMenu() - - var provider: NSItemProvider? = nil + + var provider: NSItemProvider? // Try to get any HTML that is maybe passed in for item in self.extensionContext!.inputItems as! [NSExtensionItem] { @@ -40,7 +40,7 @@ class ShareViewController: NSViewController { } } - if provider != nil { + if provider != nil { provider!.loadItem(forTypeIdentifier: UTType.propertyList.identifier, options: nil, completionHandler: { [weak self] (pList, error) in if error != nil { return @@ -67,7 +67,7 @@ class ShareViewController: NSViewController { } } - if provider != nil { + if provider != nil { provider!.loadItem(forTypeIdentifier: UTType.url.identifier, options: nil, completionHandler: { [weak self] (urlCoded, error) in if error != nil { return @@ -93,7 +93,7 @@ class ShareViewController: NSViewController { let name = nameTextField.stringValue.isEmpty ? nil : nameTextField.stringValue let request = ExtensionFeedAddRequest(name: name, feedURL: url, destinationContainerID: containerID) ExtensionFeedAddRequestFile.save(request) - + self.extensionContext!.completeRequest(returningItems: [], completionHandler: nil) } @@ -105,31 +105,31 @@ class ShareViewController: NSViewController { } private extension ShareViewController { - + func buildFolderPopupMenu() { - + let menu = NSMenu(title: "Folders") menu.autoenablesItems = false - + guard let extensionContainers = extensionContainers else { folderPopUpButton.menu = nil return } let defaultContainer = ShareDefaultContainer.defaultContainer(containers: extensionContainers) - var defaultMenuItem: NSMenuItem? = nil - + var defaultMenuItem: NSMenuItem? + for account in extensionContainers.accounts { - + let menuItem = NSMenuItem(title: account.name, action: nil, keyEquivalent: "") menuItem.representedObject = account - + if account.disallowFeedInRootFolder { menuItem.isEnabled = false } - + menu.addItem(menuItem) - + if defaultContainer?.containerID == account.containerID { defaultMenuItem = menuItem } @@ -143,15 +143,15 @@ private extension ShareViewController { defaultMenuItem = menuItem } } - + } - + folderPopUpButton.menu = menu folderPopUpButton.select(defaultMenuItem) } - + func selectedContainer() -> ExtensionContainer? { return folderPopUpButton.selectedItem?.representedObject as? ExtensionContainer } - + }