2017-05-22 22:00:45 +02:00
|
|
|
//
|
|
|
|
// AppDelegate.swift
|
|
|
|
// Evergreen
|
|
|
|
//
|
2017-05-27 19:43:27 +02:00
|
|
|
// Created by Brent Simmons on 7/11/15.
|
|
|
|
// Copyright © 2015 Ranchero Software, LLC. All rights reserved.
|
2017-05-22 22:00:45 +02:00
|
|
|
//
|
|
|
|
|
|
|
|
import Cocoa
|
2017-05-27 19:43:27 +02:00
|
|
|
import DB5
|
2017-09-17 21:22:15 +02:00
|
|
|
import Data
|
2017-05-27 19:43:27 +02:00
|
|
|
import RSTextDrawing
|
|
|
|
import RSTree
|
2017-07-02 02:22:19 +02:00
|
|
|
import RSParser
|
2017-05-27 22:37:50 +02:00
|
|
|
import RSWeb
|
2017-09-18 02:12:42 +02:00
|
|
|
import Account
|
2017-05-27 19:43:27 +02:00
|
|
|
|
|
|
|
var currentTheme: VSTheme!
|
2017-05-22 22:00:45 +02:00
|
|
|
|
|
|
|
@NSApplicationMain
|
2017-05-29 21:04:22 +02:00
|
|
|
class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations {
|
2017-05-22 22:00:45 +02:00
|
|
|
|
2017-05-27 19:43:27 +02:00
|
|
|
let windowControllers = NSMutableArray()
|
|
|
|
var preferencesWindowController: NSWindowController?
|
|
|
|
var mainWindowController: NSWindowController?
|
|
|
|
var feedListWindowController: NSWindowController?
|
|
|
|
var addFeedController: AddFeedController?
|
|
|
|
var addFolderWindowController: AddFolderWindowController?
|
|
|
|
let themeLoader = VSThemeLoader()
|
2017-06-01 23:10:04 +02:00
|
|
|
private let appNewsURLString = "https://ranchero.com/evergreen/feed.json"
|
2017-05-27 19:43:27 +02:00
|
|
|
|
|
|
|
var unreadCount = 0 {
|
|
|
|
didSet {
|
|
|
|
updateBadgeCoalesced()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
override init() {
|
|
|
|
|
|
|
|
NSWindow.allowsAutomaticWindowTabbing = false
|
|
|
|
super.init()
|
|
|
|
}
|
|
|
|
|
|
|
|
// MARK: NSApplicationDelegate
|
|
|
|
|
|
|
|
func applicationDidFinishLaunching(_ note: Notification) {
|
|
|
|
|
|
|
|
registerDefaults()
|
|
|
|
|
|
|
|
currentTheme = themeLoader.defaultTheme
|
|
|
|
|
|
|
|
let _ = AccountManager.sharedInstance
|
|
|
|
|
|
|
|
let kFirstRunDateKey = "firstRun"
|
|
|
|
var isFirstRun = false
|
|
|
|
if UserDefaults.standard.object(forKey: kFirstRunDateKey) == nil {
|
|
|
|
isFirstRun = true
|
|
|
|
UserDefaults.standard.set(Date(), forKey: kFirstRunDateKey)
|
|
|
|
}
|
|
|
|
|
|
|
|
importDefaultFeedsIfNeeded(isFirstRun, account: AccountManager.sharedInstance.localAccount)
|
|
|
|
createAndShowMainWindow()
|
|
|
|
|
|
|
|
#if RELEASE
|
|
|
|
DispatchQueue.main.async {
|
|
|
|
self.refreshAll(self)
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
NSAppleEventManager.shared().setEventHandler(self, andSelector: #selector(AppDelegate.getURL(_:_:)), forEventClass: AEEventClass(kInternetEventClass), andEventID: AEEventID(kAEGetURL))
|
|
|
|
}
|
|
|
|
|
|
|
|
func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool {
|
|
|
|
|
|
|
|
if (!flag) {
|
|
|
|
createAndShowMainWindow()
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
func applicationDidResignActive(_ notification: Notification) {
|
|
|
|
|
|
|
|
RSSingleLineRenderer.emptyCache()
|
|
|
|
RSMultiLineRenderer.emptyCache()
|
|
|
|
TimelineCellData.emptyCache()
|
|
|
|
timelineEmptyCaches()
|
|
|
|
}
|
|
|
|
|
|
|
|
// MARK: GetURL Apple Event
|
|
|
|
|
2017-09-18 02:12:42 +02:00
|
|
|
@objc func getURL(_ event: NSAppleEventDescriptor, _ withReplyEvent: NSAppleEventDescriptor) {
|
2017-05-22 22:00:45 +02:00
|
|
|
|
2017-05-27 19:43:27 +02:00
|
|
|
guard let urlString = event.paramDescriptor(forKeyword: keyDirectObject)?.stringValue else {
|
|
|
|
return
|
|
|
|
}
|
2017-05-22 22:00:45 +02:00
|
|
|
|
2017-05-27 19:43:27 +02:00
|
|
|
let normalizedURLString = urlString.rs_normalizedURL()
|
|
|
|
if !normalizedURLString.rs_stringMayBeURL() {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
DispatchQueue.main.async {
|
|
|
|
|
|
|
|
self.addFeed(normalizedURLString)
|
|
|
|
}
|
2017-05-22 22:00:45 +02:00
|
|
|
}
|
|
|
|
|
2017-05-27 19:43:27 +02:00
|
|
|
|
|
|
|
// MARK: Badge
|
|
|
|
|
|
|
|
private func updateBadgeCoalesced() {
|
|
|
|
|
|
|
|
rs_performSelectorCoalesced(#selector(updateBadge), with: nil, afterDelay: 0.01)
|
2017-05-22 22:00:45 +02:00
|
|
|
}
|
|
|
|
|
2017-09-17 21:34:10 +02:00
|
|
|
@objc dynamic func updateBadge() {
|
2017-05-27 19:43:27 +02:00
|
|
|
|
|
|
|
let label = unreadCount > 0 ? "\(unreadCount)" : ""
|
2017-09-18 02:12:42 +02:00
|
|
|
NSApplication.shared.dockTile.badgeLabel = label
|
2017-05-27 19:43:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// MARK: Notifications
|
|
|
|
|
|
|
|
func unreadCountDidChange(_ note: Notification) {
|
|
|
|
|
|
|
|
let updatedUnreadCount = AccountManager.sharedInstance.unreadCount
|
|
|
|
if updatedUnreadCount != unreadCount {
|
|
|
|
unreadCount = updatedUnreadCount
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// MARK: Main Window
|
|
|
|
|
|
|
|
func windowControllerWithName(_ storyboardName: String) -> NSWindowController {
|
|
|
|
|
2017-09-18 02:12:42 +02:00
|
|
|
let storyboard = NSStoryboard(name: NSStoryboard.Name(rawValue: storyboardName), bundle: nil)
|
2017-05-27 19:43:27 +02:00
|
|
|
return storyboard.instantiateInitialController()! as! NSWindowController
|
|
|
|
}
|
|
|
|
|
|
|
|
func createAndShowMainWindow() {
|
|
|
|
|
|
|
|
if mainWindowController == nil {
|
|
|
|
mainWindowController = windowControllerWithName("MainWindow")
|
|
|
|
}
|
|
|
|
|
|
|
|
mainWindowController!.showWindow(self)
|
|
|
|
}
|
|
|
|
|
|
|
|
// MARK: NSUserInterfaceValidations
|
|
|
|
|
|
|
|
func validateUserInterfaceItem(_ item: NSValidatedUserInterfaceItem) -> Bool {
|
|
|
|
|
|
|
|
if item.action == #selector(refreshAll(_:)) {
|
|
|
|
return !AccountManager.sharedInstance.refreshInProgress
|
|
|
|
}
|
|
|
|
if item.action == #selector(addAppNews(_:)) {
|
|
|
|
return !AccountManager.sharedInstance.anyAccountHasFeedWithURL(appNewsURLString)
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
// MARK: Add Feed
|
|
|
|
|
|
|
|
func addFeed(_ urlString: String?, _ name: String? = nil) {
|
2017-05-22 22:00:45 +02:00
|
|
|
|
2017-05-27 19:43:27 +02:00
|
|
|
createAndShowMainWindow()
|
|
|
|
|
|
|
|
addFeedController = AddFeedController(hostWindow: mainWindowController!.window!)
|
|
|
|
addFeedController?.showAddFeedSheet(urlString, name)
|
|
|
|
}
|
|
|
|
|
|
|
|
// MARK: Actions
|
|
|
|
|
|
|
|
@IBAction func showPreferences(_ sender: AnyObject) {
|
|
|
|
|
|
|
|
if preferencesWindowController == nil {
|
|
|
|
preferencesWindowController = windowControllerWithName("Preferences")
|
|
|
|
}
|
|
|
|
|
|
|
|
preferencesWindowController!.showWindow(self)
|
|
|
|
}
|
|
|
|
|
|
|
|
@IBAction func showMainWindow(_ sender: AnyObject) {
|
|
|
|
|
|
|
|
createAndShowMainWindow()
|
|
|
|
}
|
|
|
|
|
|
|
|
@IBAction func refreshAll(_ sender: AnyObject) {
|
|
|
|
|
|
|
|
AccountManager.sharedInstance.refreshAll()
|
|
|
|
}
|
|
|
|
|
|
|
|
@IBAction func showAddFeedWindow(_ sender: AnyObject) {
|
|
|
|
|
|
|
|
addFeed(nil)
|
|
|
|
}
|
|
|
|
|
|
|
|
@IBAction func showAddFolderWindow(_ sender: AnyObject) {
|
|
|
|
|
|
|
|
createAndShowMainWindow()
|
|
|
|
|
|
|
|
addFolderWindowController = AddFolderWindowController()
|
|
|
|
addFolderWindowController!.runSheetOnWindow(mainWindowController!.window!)
|
|
|
|
}
|
|
|
|
|
|
|
|
@IBAction func showFeedList(_ sender: AnyObject) {
|
|
|
|
|
|
|
|
if feedListWindowController == nil {
|
|
|
|
feedListWindowController = windowControllerWithName("FeedList")
|
|
|
|
}
|
|
|
|
feedListWindowController!.showWindow(self)
|
|
|
|
}
|
|
|
|
|
|
|
|
@IBAction func importOPMLFromFile(_ sender: AnyObject) {
|
|
|
|
|
|
|
|
let panel = NSOpenPanel()
|
|
|
|
panel.canDownloadUbiquitousContents = true
|
|
|
|
panel.canResolveUbiquitousConflicts = true
|
|
|
|
panel.canChooseFiles = true
|
|
|
|
panel.allowsMultipleSelection = false
|
|
|
|
panel.canChooseDirectories = false
|
|
|
|
panel.resolvesAliases = true
|
|
|
|
panel.allowedFileTypes = ["opml"]
|
|
|
|
panel.allowsOtherFileTypes = false
|
|
|
|
|
|
|
|
let result = panel.runModal()
|
|
|
|
if result == NSFileHandlingPanelOKButton {
|
|
|
|
if let url = panel.url {
|
|
|
|
DispatchQueue.main.async {
|
|
|
|
self.parseAndImportOPML(url, AccountManager.sharedInstance.localAccount)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-05-27 20:33:31 +02:00
|
|
|
@IBAction func importOPMLFromURL(_ sender: AnyObject) {
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2017-05-27 19:43:27 +02:00
|
|
|
@IBAction func exportOPML(_ sender: AnyObject) {
|
|
|
|
|
|
|
|
let panel = NSSavePanel()
|
|
|
|
panel.allowedFileTypes = ["opml"]
|
|
|
|
panel.allowsOtherFileTypes = false
|
|
|
|
panel.prompt = NSLocalizedString("Export OPML", comment: "Export OPML")
|
|
|
|
panel.title = NSLocalizedString("Export OPML", comment: "Export OPML")
|
|
|
|
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
|
|
|
|
panel.nameFieldStringValue = "MySubscriptions.opml"
|
|
|
|
|
|
|
|
let result = panel.runModal()
|
|
|
|
if result == NSFileHandlingPanelOKButton {
|
|
|
|
if let url = panel.url {
|
|
|
|
DispatchQueue.main.async {
|
|
|
|
let opmlString = AccountManager.sharedInstance.localAccount.opmlString(indentLevel: 0)
|
|
|
|
do {
|
|
|
|
try opmlString.write(to: url, atomically: true, encoding: String.Encoding.utf8)
|
|
|
|
}
|
|
|
|
catch let error as NSError {
|
|
|
|
NSApplication.shared().presentError(error)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@IBAction func emailSupport(_ sender: AnyObject) {
|
|
|
|
|
|
|
|
let escapedAppName = appName.replacingOccurrences(of: " ", with: "%20")
|
|
|
|
let version = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString")!
|
|
|
|
let urlString = "mailto:support@ranchero.com?subject=I%20need%20help%20with%20\(escapedAppName)%20\(version)&body=I%20ran%20into%20a%20problem:%20"
|
|
|
|
if let url = URL(string: urlString) {
|
|
|
|
NSWorkspace.shared().open(url)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@IBAction func addAppNews(_ sender: AnyObject) {
|
|
|
|
|
|
|
|
if AccountManager.sharedInstance.anyAccountHasFeedWithURL(appNewsURLString) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
addFeed(appNewsURLString, "Evergreen News")
|
|
|
|
}
|
2017-05-27 22:37:50 +02:00
|
|
|
|
|
|
|
@IBAction func openWebsite(_ sender: AnyObject) {
|
|
|
|
|
2017-06-02 01:28:27 +02:00
|
|
|
openInBrowser("https://ranchero.com/evergreen/", inBackground: false)
|
2017-05-27 22:37:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@IBAction func openRepository(_ sender: AnyObject) {
|
|
|
|
|
|
|
|
openInBrowser("https://github.com/brentsimmons/Evergreen", inBackground: false)
|
|
|
|
}
|
|
|
|
|
|
|
|
@IBAction func openBugTracker(_ sender: AnyObject) {
|
|
|
|
|
|
|
|
openInBrowser("https://github.com/brentsimmons/Evergreen/issues", inBackground: false)
|
|
|
|
}
|
|
|
|
|
|
|
|
@IBAction func showHelp(_ sender: AnyObject) {
|
|
|
|
|
2017-06-02 01:28:27 +02:00
|
|
|
openInBrowser("https://ranchero.com/evergreen/help/1.0/", inBackground: false)
|
2017-05-27 22:37:50 +02:00
|
|
|
}
|
2017-05-27 19:43:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
private extension AppDelegate {
|
|
|
|
|
|
|
|
func parseAndImportOPML(_ url: URL, _ account: Account) {
|
|
|
|
|
|
|
|
var fileData: Data?
|
|
|
|
|
|
|
|
do {
|
|
|
|
fileData = try Data(contentsOf: url)
|
|
|
|
} catch {
|
|
|
|
print("Error reading OPML file. \(error)")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
guard let opmlData = fileData else {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2017-07-02 02:22:19 +02:00
|
|
|
let parserData = ParserData(data: opmlData, urlString: url.absoluteString)
|
2017-05-27 19:43:27 +02:00
|
|
|
RSParseOPML(xmlData) { (opmlDocument, error) in
|
|
|
|
|
|
|
|
if let error = error {
|
|
|
|
NSApplication.shared().presentError(error)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if let opmlDocument = opmlDocument {
|
|
|
|
account.importOPML(opmlDocument)
|
|
|
|
// account.refreshAll()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-05-22 22:00:45 +02:00
|
|
|
}
|
|
|
|
|