Merge branch 'master' of https://github.com/brentsimmons/NetNewsWire
This commit is contained in:
commit
4e8409a891
|
@ -63,6 +63,8 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||||
private let feedsPath: ODBPath
|
private let feedsPath: ODBPath
|
||||||
private let feedsTable: ODBTable
|
private let feedsTable: ODBTable
|
||||||
|
|
||||||
|
private let opmlFilePath: String
|
||||||
|
|
||||||
private struct SettingsKey {
|
private struct SettingsKey {
|
||||||
static let unreadCount = "unreadCount"
|
static let unreadCount = "unreadCount"
|
||||||
}
|
}
|
||||||
|
@ -115,6 +117,8 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||||
self.settingsFile = settingsFile
|
self.settingsFile = settingsFile
|
||||||
self.dataFolder = dataFolder
|
self.dataFolder = dataFolder
|
||||||
|
|
||||||
|
self.opmlFilePath = (dataFolder as NSString).appendingPathComponent("Subscriptions.opml")
|
||||||
|
|
||||||
let databaseFilePath = (dataFolder as NSString).appendingPathComponent("DB.sqlite3")
|
let databaseFilePath = (dataFolder as NSString).appendingPathComponent("DB.sqlite3")
|
||||||
self.database = ArticlesDatabase(databaseFilePath: databaseFilePath, accountID: accountID)
|
self.database = ArticlesDatabase(databaseFilePath: databaseFilePath, accountID: accountID)
|
||||||
|
|
||||||
|
@ -417,6 +421,32 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||||
flattenedFeeds().forEach { $0.unreadCount = 0 }
|
flattenedFeeds().forEach { $0.unreadCount = 0 }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func opmlDocument() -> String {
|
||||||
|
let escapedTitle = nameForDisplay.rs_stringByEscapingSpecialXMLCharacters()
|
||||||
|
let openingText =
|
||||||
|
"""
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!-- OPML generated by NetNewsWire -->
|
||||||
|
<opml version="1.1">
|
||||||
|
<head>
|
||||||
|
<title>\(escapedTitle)</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
let middleText = OPMLString(indentLevel: 0)
|
||||||
|
|
||||||
|
let closingText =
|
||||||
|
"""
|
||||||
|
</body>
|
||||||
|
</opml>
|
||||||
|
"""
|
||||||
|
|
||||||
|
let opml = openingText + middleText + closingText
|
||||||
|
return opml
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Debug
|
// MARK: - Debug
|
||||||
|
|
||||||
public func debugDropConditionalGetInfo() {
|
public func debugDropConditionalGetInfo() {
|
||||||
|
@ -585,6 +615,8 @@ private extension Account {
|
||||||
|
|
||||||
func saveToDisk() {
|
func saveToDisk() {
|
||||||
|
|
||||||
|
dirty = false
|
||||||
|
|
||||||
let d = diskDictionary()
|
let d = diskDictionary()
|
||||||
do {
|
do {
|
||||||
try RSPlist.write(d, filePath: settingsFile)
|
try RSPlist.write(d, filePath: settingsFile)
|
||||||
|
@ -592,7 +624,15 @@ private extension Account {
|
||||||
catch let error as NSError {
|
catch let error as NSError {
|
||||||
NSApplication.shared.presentError(error)
|
NSApplication.shared.presentError(error)
|
||||||
}
|
}
|
||||||
dirty = false
|
|
||||||
|
let opmlDocumentString = opmlDocument()
|
||||||
|
do {
|
||||||
|
let url = URL(fileURLWithPath: opmlFilePath)
|
||||||
|
try opmlDocumentString.write(to: url, atomically: true, encoding: .utf8)
|
||||||
|
}
|
||||||
|
catch let error as NSError {
|
||||||
|
NSApplication.shared.presentError(error)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -116,7 +116,6 @@ public final class Feed: DisplayNameProvider, UnreadCountProvider, Hashable {
|
||||||
self.faviconURL = dictionary[Key.faviconURL] as? String
|
self.faviconURL = dictionary[Key.faviconURL] as? String
|
||||||
self.name = dictionary[Key.name] as? String
|
self.name = dictionary[Key.name] as? String
|
||||||
self.editedName = dictionary[Key.editedName] as? String
|
self.editedName = dictionary[Key.editedName] as? String
|
||||||
// self.contentHash = dictionary[Key.contentHash] as? String
|
|
||||||
|
|
||||||
if let conditionalGetInfoDictionary = dictionary[Key.conditionalGetInfo] as? [String: String] {
|
if let conditionalGetInfoDictionary = dictionary[Key.conditionalGetInfo] as? [String: String] {
|
||||||
self.conditionalGetInfo = HTTPConditionalGetInfo(dictionary: conditionalGetInfoDictionary)
|
self.conditionalGetInfo = HTTPConditionalGetInfo(dictionary: conditionalGetInfoDictionary)
|
||||||
|
@ -160,9 +159,6 @@ public final class Feed: DisplayNameProvider, UnreadCountProvider, Hashable {
|
||||||
if let authorsArray = authors?.diskArray() {
|
if let authorsArray = authors?.diskArray() {
|
||||||
d[Key.authors] = authorsArray
|
d[Key.authors] = authorsArray
|
||||||
}
|
}
|
||||||
// if let contentHash = contentHash {
|
|
||||||
// d[Key.contentHash] = contentHash
|
|
||||||
// }
|
|
||||||
if let conditionalGetInfo = conditionalGetInfo {
|
if let conditionalGetInfo = conditionalGetInfo {
|
||||||
d[Key.conditionalGetInfo] = conditionalGetInfo.dictionary
|
d[Key.conditionalGetInfo] = conditionalGetInfo.dictionary
|
||||||
}
|
}
|
||||||
|
|
|
@ -80,7 +80,6 @@ extension LocalAccountRefresher: DownloadSessionDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
feed.contentHash = dataHash
|
feed.contentHash = dataHash
|
||||||
feed.account?.dirty = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,9 @@
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
519B8D332143397200FA689C /* SharingServiceDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519B8D322143397200FA689C /* SharingServiceDelegate.swift */; };
|
519B8D332143397200FA689C /* SharingServiceDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519B8D322143397200FA689C /* SharingServiceDelegate.swift */; };
|
||||||
|
51EC114C2149FE3300B296E3 /* FolderTreeMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51EC114B2149FE3300B296E3 /* FolderTreeMenu.swift */; };
|
||||||
|
51EC11A1214A94AD00B296E3 /* AddFeedFromListSheet.xib in Resources */ = {isa = PBXBuildFile; fileRef = 51EC1193214A94AC00B296E3 /* AddFeedFromListSheet.xib */; };
|
||||||
|
51EC11A3214A990000B296E3 /* AddFeedFromListWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51EC11A2214A990000B296E3 /* AddFeedFromListWindowController.swift */; };
|
||||||
6581C73820CED60100F4AD34 /* SafariExtensionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6581C73720CED60100F4AD34 /* SafariExtensionHandler.swift */; };
|
6581C73820CED60100F4AD34 /* SafariExtensionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6581C73720CED60100F4AD34 /* SafariExtensionHandler.swift */; };
|
||||||
6581C73A20CED60100F4AD34 /* SafariExtensionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6581C73920CED60100F4AD34 /* SafariExtensionViewController.swift */; };
|
6581C73A20CED60100F4AD34 /* SafariExtensionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6581C73920CED60100F4AD34 /* SafariExtensionViewController.swift */; };
|
||||||
6581C73D20CED60100F4AD34 /* SafariExtensionViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6581C73B20CED60100F4AD34 /* SafariExtensionViewController.xib */; };
|
6581C73D20CED60100F4AD34 /* SafariExtensionViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6581C73B20CED60100F4AD34 /* SafariExtensionViewController.xib */; };
|
||||||
|
@ -480,6 +483,9 @@
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
519B8D322143397200FA689C /* SharingServiceDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharingServiceDelegate.swift; sourceTree = "<group>"; };
|
519B8D322143397200FA689C /* SharingServiceDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharingServiceDelegate.swift; sourceTree = "<group>"; };
|
||||||
|
51EC114B2149FE3300B296E3 /* FolderTreeMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = FolderTreeMenu.swift; path = AddFeed/FolderTreeMenu.swift; sourceTree = "<group>"; };
|
||||||
|
51EC1194214A94AC00B296E3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = NetNewsWire/Base.lproj/AddFeedFromListSheet.xib; sourceTree = SOURCE_ROOT; };
|
||||||
|
51EC11A2214A990000B296E3 /* AddFeedFromListWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AddFeedFromListWindowController.swift; path = AddFeed/AddFeedFromListWindowController.swift; sourceTree = "<group>"; };
|
||||||
6581C73320CED60000F4AD34 /* Subscribe to Feed.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "Subscribe to Feed.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
|
6581C73320CED60000F4AD34 /* Subscribe to Feed.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "Subscribe to Feed.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
6581C73420CED60100F4AD34 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; };
|
6581C73420CED60100F4AD34 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; };
|
||||||
6581C73720CED60100F4AD34 /* SafariExtensionHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafariExtensionHandler.swift; sourceTree = "<group>"; };
|
6581C73720CED60100F4AD34 /* SafariExtensionHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafariExtensionHandler.swift; sourceTree = "<group>"; };
|
||||||
|
@ -948,10 +954,13 @@
|
||||||
849A97551ED9EAC3007D329B /* Add Feed */ = {
|
849A97551ED9EAC3007D329B /* Add Feed */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
51EC1193214A94AC00B296E3 /* AddFeedFromListSheet.xib */,
|
||||||
|
51EC11A2214A990000B296E3 /* AddFeedFromListWindowController.swift */,
|
||||||
849A97A71ED9F9AA007D329B /* AddFeedSheet.xib */,
|
849A97A71ED9F9AA007D329B /* AddFeedSheet.xib */,
|
||||||
849A97511ED9EAC0007D329B /* AddFeedController.swift */,
|
849A97511ED9EAC0007D329B /* AddFeedController.swift */,
|
||||||
849A97521ED9EAC0007D329B /* AddFeedWindowController.swift */,
|
849A97521ED9EAC0007D329B /* AddFeedWindowController.swift */,
|
||||||
849A97A01ED9F180007D329B /* InitialFeedDownloader.swift */,
|
849A97A01ED9F180007D329B /* InitialFeedDownloader.swift */,
|
||||||
|
51EC114B2149FE3300B296E3 /* FolderTreeMenu.swift */,
|
||||||
849A97A11ED9F180007D329B /* FolderTreeControllerDelegate.swift */,
|
849A97A11ED9F180007D329B /* FolderTreeControllerDelegate.swift */,
|
||||||
);
|
);
|
||||||
name = "Add Feed";
|
name = "Add Feed";
|
||||||
|
@ -1528,8 +1537,8 @@
|
||||||
ORGANIZATIONNAME = "Ranchero Software";
|
ORGANIZATIONNAME = "Ranchero Software";
|
||||||
TargetAttributes = {
|
TargetAttributes = {
|
||||||
6581C73220CED60000F4AD34 = {
|
6581C73220CED60000F4AD34 = {
|
||||||
DevelopmentTeam = M8L2WTLA8W;
|
DevelopmentTeam = SHJK2V3AJG;
|
||||||
ProvisioningStyle = Manual;
|
ProvisioningStyle = Automatic;
|
||||||
};
|
};
|
||||||
840D617B2029031C009BC708 = {
|
840D617B2029031C009BC708 = {
|
||||||
CreatedOnToolsVersion = 9.3;
|
CreatedOnToolsVersion = 9.3;
|
||||||
|
@ -1550,12 +1559,12 @@
|
||||||
};
|
};
|
||||||
849C645F1ED37A5D003D8FC0 = {
|
849C645F1ED37A5D003D8FC0 = {
|
||||||
CreatedOnToolsVersion = 8.2.1;
|
CreatedOnToolsVersion = 8.2.1;
|
||||||
DevelopmentTeam = M8L2WTLA8W;
|
DevelopmentTeam = SHJK2V3AJG;
|
||||||
ProvisioningStyle = Manual;
|
ProvisioningStyle = Automatic;
|
||||||
};
|
};
|
||||||
849C64701ED37A5D003D8FC0 = {
|
849C64701ED37A5D003D8FC0 = {
|
||||||
CreatedOnToolsVersion = 8.2.1;
|
CreatedOnToolsVersion = 8.2.1;
|
||||||
DevelopmentTeam = 9C84TZ7Q6Z;
|
DevelopmentTeam = SHJK2V3AJG;
|
||||||
ProvisioningStyle = Automatic;
|
ProvisioningStyle = Automatic;
|
||||||
TestTargetID = 849C645F1ED37A5D003D8FC0;
|
TestTargetID = 849C645F1ED37A5D003D8FC0;
|
||||||
};
|
};
|
||||||
|
@ -1811,6 +1820,7 @@
|
||||||
isa = PBXResourcesBuildPhase;
|
isa = PBXResourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
51EC11A1214A94AD00B296E3 /* AddFeedFromListSheet.xib in Resources */,
|
||||||
84EB381F1FBA8B9F000D2111 /* KeyboardShortcuts.html in Resources */,
|
84EB381F1FBA8B9F000D2111 /* KeyboardShortcuts.html in Resources */,
|
||||||
849A97951ED9EF7A007D329B /* IndeterminateProgressWindow.xib in Resources */,
|
849A97951ED9EF7A007D329B /* IndeterminateProgressWindow.xib in Resources */,
|
||||||
844B5B651FEA11F200C7C76A /* GlobalKeyboardShortcuts.plist in Resources */,
|
844B5B651FEA11F200C7C76A /* GlobalKeyboardShortcuts.plist in Resources */,
|
||||||
|
@ -1940,6 +1950,7 @@
|
||||||
8426118A1FCB67AA0086A189 /* FeedIconDownloader.swift in Sources */,
|
8426118A1FCB67AA0086A189 /* FeedIconDownloader.swift in Sources */,
|
||||||
84162A152038C12C00035290 /* MarkCommandValidationStatus.swift in Sources */,
|
84162A152038C12C00035290 /* MarkCommandValidationStatus.swift in Sources */,
|
||||||
84E95D241FB1087500552D99 /* ArticlePasteboardWriter.swift in Sources */,
|
84E95D241FB1087500552D99 /* ArticlePasteboardWriter.swift in Sources */,
|
||||||
|
51EC11A3214A990000B296E3 /* AddFeedFromListWindowController.swift in Sources */,
|
||||||
849A975B1ED9EB0D007D329B /* ArticleUtilities.swift in Sources */,
|
849A975B1ED9EB0D007D329B /* ArticleUtilities.swift in Sources */,
|
||||||
84DAEE301F86CAFE0058304B /* OPMLImporter.swift in Sources */,
|
84DAEE301F86CAFE0058304B /* OPMLImporter.swift in Sources */,
|
||||||
849A975C1ED9EB0D007D329B /* DefaultFeedsImporter.swift in Sources */,
|
849A975C1ED9EB0D007D329B /* DefaultFeedsImporter.swift in Sources */,
|
||||||
|
@ -2005,6 +2016,7 @@
|
||||||
84B99C691FAE36B800ECDEDB /* FeedListFolder.swift in Sources */,
|
84B99C691FAE36B800ECDEDB /* FeedListFolder.swift in Sources */,
|
||||||
84411E711FE5FBFA004B527F /* SmallIconProvider.swift in Sources */,
|
84411E711FE5FBFA004B527F /* SmallIconProvider.swift in Sources */,
|
||||||
844B5B591FE9FE4F00C7C76A /* SidebarKeyboardDelegate.swift in Sources */,
|
844B5B591FE9FE4F00C7C76A /* SidebarKeyboardDelegate.swift in Sources */,
|
||||||
|
51EC114C2149FE3300B296E3 /* FolderTreeMenu.swift in Sources */,
|
||||||
849A97A31ED9F180007D329B /* FolderTreeControllerDelegate.swift in Sources */,
|
849A97A31ED9F180007D329B /* FolderTreeControllerDelegate.swift in Sources */,
|
||||||
84A1500320048D660046AD9A /* SendToCommand.swift in Sources */,
|
84A1500320048D660046AD9A /* SendToCommand.swift in Sources */,
|
||||||
845A29091FC74B8E007B49E3 /* SingleFaviconDownloader.swift in Sources */,
|
845A29091FC74B8E007B49E3 /* SingleFaviconDownloader.swift in Sources */,
|
||||||
|
@ -2113,6 +2125,14 @@
|
||||||
/* End PBXTargetDependency section */
|
/* End PBXTargetDependency section */
|
||||||
|
|
||||||
/* Begin PBXVariantGroup section */
|
/* Begin PBXVariantGroup section */
|
||||||
|
51EC1193214A94AC00B296E3 /* AddFeedFromListSheet.xib */ = {
|
||||||
|
isa = PBXVariantGroup;
|
||||||
|
children = (
|
||||||
|
51EC1194214A94AC00B296E3 /* Base */,
|
||||||
|
);
|
||||||
|
name = AddFeedFromListSheet.xib;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
6581C73B20CED60100F4AD34 /* SafariExtensionViewController.xib */ = {
|
6581C73B20CED60100F4AD34 /* SafariExtensionViewController.xib */ = {
|
||||||
isa = PBXVariantGroup;
|
isa = PBXVariantGroup;
|
||||||
children = (
|
children = (
|
||||||
|
|
|
@ -49,6 +49,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations,
|
||||||
private var readerWindows = [NSWindowController]()
|
private var readerWindows = [NSWindowController]()
|
||||||
private var feedListWindowController: NSWindowController?
|
private var feedListWindowController: NSWindowController?
|
||||||
private var addFeedController: AddFeedController?
|
private var addFeedController: AddFeedController?
|
||||||
|
private var addFeedFromListController: AddFeedFromListWindowController?
|
||||||
private var addFolderWindowController: AddFolderWindowController?
|
private var addFolderWindowController: AddFolderWindowController?
|
||||||
private var keyboardShortcutsWindowController: WebViewWindowController?
|
private var keyboardShortcutsWindowController: WebViewWindowController?
|
||||||
private var inspectorWindowController: InspectorWindowController?
|
private var inspectorWindowController: InspectorWindowController?
|
||||||
|
@ -101,6 +102,18 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations,
|
||||||
addFeedController = AddFeedController(hostWindow: window)
|
addFeedController = AddFeedController(hostWindow: window)
|
||||||
addFeedController?.showAddFeedSheet(urlString, name)
|
addFeedController?.showAddFeedSheet(urlString, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func showAddFeedFromListOnMainWindow(_ feedListFeeds: [FeedListFeed]) {
|
||||||
|
|
||||||
|
addFeedFromListController = AddFeedFromListWindowController(feedListFeeds)
|
||||||
|
|
||||||
|
createAndShowMainWindow()
|
||||||
|
|
||||||
|
let isDisplayingSheet = mainWindowController?.isDisplayingSheet ?? false
|
||||||
|
if !isDisplayingSheet, let mainWindow = mainWindowController?.window {
|
||||||
|
addFeedFromListController!.runSheetOnWindow(mainWindow)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - NSApplicationDelegate
|
// MARK: - NSApplicationDelegate
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,107 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14313.18" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||||
|
<dependencies>
|
||||||
|
<deployment identifier="macosx"/>
|
||||||
|
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14313.18"/>
|
||||||
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
|
</dependencies>
|
||||||
|
<objects>
|
||||||
|
<customObject id="-2" userLabel="File's Owner" customClass="AddFeedFromListWindowController" customModule="NetNewsWire" customModuleProvider="target">
|
||||||
|
<connections>
|
||||||
|
<outlet property="addButton" destination="dtI-Hu-rFb" id="D11-zR-dWH"/>
|
||||||
|
<outlet property="addFeedTextField" destination="7If-aT-UTD" id="HMp-AC-iuV"/>
|
||||||
|
<outlet property="folderPopupButton" destination="6vt-DL-mVR" id="rm7-y9-tBF"/>
|
||||||
|
<outlet property="window" destination="QvC-M9-y7g" id="7rH-S2-LF4"/>
|
||||||
|
</connections>
|
||||||
|
</customObject>
|
||||||
|
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
|
||||||
|
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
|
||||||
|
<window title="Add Feed" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="QvC-M9-y7g">
|
||||||
|
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES"/>
|
||||||
|
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
|
||||||
|
<rect key="contentRect" x="196" y="240" width="480" height="217"/>
|
||||||
|
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1417"/>
|
||||||
|
<view key="contentView" misplaced="YES" id="EiT-Mj-1SZ">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="480" height="217"/>
|
||||||
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
|
<subviews>
|
||||||
|
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="dNV-oD-vzR">
|
||||||
|
<rect key="frame" x="18" y="63" width="50" height="17"/>
|
||||||
|
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Folder:" id="Kwx-7B-CIu">
|
||||||
|
<font key="font" metaFont="systemBold"/>
|
||||||
|
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||||
|
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||||
|
</textFieldCell>
|
||||||
|
</textField>
|
||||||
|
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="6vt-DL-mVR" userLabel="Folder Popup">
|
||||||
|
<rect key="frame" x="72" y="58" width="391" height="25"/>
|
||||||
|
<popUpButtonCell key="cell" type="push" title="Item 1" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="tLJ-zY-CcZ" id="0cM-5q-Snl">
|
||||||
|
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||||
|
<font key="font" metaFont="menu"/>
|
||||||
|
<menu key="menu" id="OpL-Uf-woJ">
|
||||||
|
<items>
|
||||||
|
<menuItem title="Item 1" state="on" id="tLJ-zY-CcZ"/>
|
||||||
|
<menuItem title="Item 2" id="APc-af-7Um"/>
|
||||||
|
<menuItem title="Item 3" id="j09-9b-bGs"/>
|
||||||
|
</items>
|
||||||
|
</menu>
|
||||||
|
</popUpButtonCell>
|
||||||
|
</popUpButton>
|
||||||
|
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="hXq-IS-19x">
|
||||||
|
<rect key="frame" x="302" y="13" width="82" height="32"/>
|
||||||
|
<buttonCell key="cell" type="push" title="Cancel" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="Dop-HC-6Q9">
|
||||||
|
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||||
|
<font key="font" metaFont="system"/>
|
||||||
|
<string key="keyEquivalent" base64-UTF8="YES">
|
||||||
|
Gw
|
||||||
|
</string>
|
||||||
|
</buttonCell>
|
||||||
|
<connections>
|
||||||
|
<action selector="cancel:" target="-2" id="tcT-tt-t99"/>
|
||||||
|
</connections>
|
||||||
|
</button>
|
||||||
|
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="dtI-Hu-rFb">
|
||||||
|
<rect key="frame" x="384" y="13" width="82" height="32"/>
|
||||||
|
<buttonCell key="cell" type="push" title="Add" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="6NK-Ql-drk">
|
||||||
|
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||||
|
<font key="font" metaFont="system"/>
|
||||||
|
<string key="keyEquivalent" base64-UTF8="YES">
|
||||||
|
DQ
|
||||||
|
</string>
|
||||||
|
</buttonCell>
|
||||||
|
<connections>
|
||||||
|
<action selector="addFeed:" target="-2" id="Ilv-Un-eDp"/>
|
||||||
|
</connections>
|
||||||
|
</button>
|
||||||
|
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="7If-aT-UTD">
|
||||||
|
<rect key="frame" x="18" y="98" width="82" height="17"/>
|
||||||
|
<textFieldCell key="cell" lineBreakMode="clipping" title="Add feed(s)?" id="CRI-fB-GoB">
|
||||||
|
<font key="font" metaFont="system"/>
|
||||||
|
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||||
|
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||||
|
</textFieldCell>
|
||||||
|
</textField>
|
||||||
|
</subviews>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstItem="dNV-oD-vzR" firstAttribute="baseline" secondItem="6vt-DL-mVR" secondAttribute="baseline" id="14b-jN-4Y6"/>
|
||||||
|
<constraint firstItem="hXq-IS-19x" firstAttribute="centerY" secondItem="dtI-Hu-rFb" secondAttribute="centerY" id="6FR-Hu-qkL"/>
|
||||||
|
<constraint firstAttribute="bottom" secondItem="dtI-Hu-rFb" secondAttribute="bottom" constant="20" symbolic="YES" id="6ac-2K-RnD"/>
|
||||||
|
<constraint firstItem="7If-aT-UTD" firstAttribute="top" secondItem="EiT-Mj-1SZ" secondAttribute="top" constant="20" symbolic="YES" id="89w-ob-4GI"/>
|
||||||
|
<constraint firstItem="dtI-Hu-rFb" firstAttribute="leading" secondItem="hXq-IS-19x" secondAttribute="trailing" constant="12" symbolic="YES" id="Djw-dV-x3S"/>
|
||||||
|
<constraint firstItem="6vt-DL-mVR" firstAttribute="leading" secondItem="dNV-oD-vzR" secondAttribute="trailing" constant="8" symbolic="YES" id="E9B-wf-c92"/>
|
||||||
|
<constraint firstItem="dtI-Hu-rFb" firstAttribute="width" secondItem="hXq-IS-19x" secondAttribute="width" id="J80-aG-OjE"/>
|
||||||
|
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="7If-aT-UTD" secondAttribute="trailing" constant="20" symbolic="YES" id="Lhg-Q2-wh5"/>
|
||||||
|
<constraint firstItem="dtI-Hu-rFb" firstAttribute="top" secondItem="6vt-DL-mVR" secondAttribute="bottom" constant="20" symbolic="YES" id="YTA-Ca-UMg"/>
|
||||||
|
<constraint firstItem="dtI-Hu-rFb" firstAttribute="leading" secondItem="hXq-IS-19x" secondAttribute="trailing" constant="12" symbolic="YES" id="ahD-oU-iFu"/>
|
||||||
|
<constraint firstItem="hXq-IS-19x" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="EiT-Mj-1SZ" secondAttribute="leading" constant="20" symbolic="YES" id="dIj-8p-5kK"/>
|
||||||
|
<constraint firstAttribute="trailing" secondItem="dtI-Hu-rFb" secondAttribute="trailing" constant="20" symbolic="YES" id="kEo-af-SUe"/>
|
||||||
|
<constraint firstAttribute="trailing" secondItem="6vt-DL-mVR" secondAttribute="trailing" constant="20" symbolic="YES" id="suO-dd-E0b"/>
|
||||||
|
<constraint firstItem="7If-aT-UTD" firstAttribute="leading" secondItem="EiT-Mj-1SZ" secondAttribute="leading" constant="20" symbolic="YES" id="uWY-iC-9nq"/>
|
||||||
|
<constraint firstItem="dNV-oD-vzR" firstAttribute="leading" secondItem="EiT-Mj-1SZ" secondAttribute="leading" constant="20" symbolic="YES" id="vee-7b-pH8"/>
|
||||||
|
<constraint firstItem="6vt-DL-mVR" firstAttribute="top" secondItem="7If-aT-UTD" secondAttribute="bottom" constant="16" id="y91-8q-6dz"/>
|
||||||
|
</constraints>
|
||||||
|
</view>
|
||||||
|
<point key="canvasLocation" x="189" y="-767.5"/>
|
||||||
|
</window>
|
||||||
|
</objects>
|
||||||
|
</document>
|
|
@ -1,8 +1,8 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="14313.3.2" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" initialViewController="B8D-0N-5wS">
|
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="14313.18" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" initialViewController="B8D-0N-5wS">
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<deployment identifier="macosx"/>
|
<deployment identifier="macosx"/>
|
||||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14313.3.2"/>
|
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14313.18"/>
|
||||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
<scenes>
|
<scenes>
|
||||||
|
@ -120,7 +120,7 @@
|
||||||
</buttonCell>
|
</buttonCell>
|
||||||
</button>
|
</button>
|
||||||
<connections>
|
<connections>
|
||||||
<action selector="markRead:" target="Oky-zY-oP4" id="yFY-gd-FOd"/>
|
<action selector="toggleRead:" target="B8D-0N-5wS" id="r02-sN-noB"/>
|
||||||
</connections>
|
</connections>
|
||||||
</toolbarItem>
|
</toolbarItem>
|
||||||
<toolbarItem implicitItemIdentifier="09CE2FC7-B9B6-4A74-85B3-2DED57082923" label="Star" paletteLabel="Star" toolTip="Star or Unstar" image="star" id="Gxg-WQ-ufC" customClass="RSToolbarItem" customModule="RSCore">
|
<toolbarItem implicitItemIdentifier="09CE2FC7-B9B6-4A74-85B3-2DED57082923" label="Star" paletteLabel="Star" toolTip="Star or Unstar" image="star" id="Gxg-WQ-ufC" customClass="RSToolbarItem" customModule="RSCore">
|
||||||
|
@ -349,11 +349,11 @@
|
||||||
<constraints>
|
<constraints>
|
||||||
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="150" id="pzy-wh-tgi"/>
|
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="150" id="pzy-wh-tgi"/>
|
||||||
</constraints>
|
</constraints>
|
||||||
<scroller key="horizontalScroller" hidden="YES" verticalHuggingPriority="750" horizontal="YES" id="vs5-5h-CXe">
|
<scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="YES" id="vs5-5h-CXe">
|
||||||
<rect key="frame" x="-100" y="-100" width="238" height="15"/>
|
<rect key="frame" x="-100" y="-100" width="238" height="15"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
</scroller>
|
</scroller>
|
||||||
<scroller key="verticalScroller" hidden="YES" verticalHuggingPriority="750" horizontal="NO" id="FWV-kB-qct">
|
<scroller key="verticalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="NO" id="FWV-kB-qct">
|
||||||
<rect key="frame" x="224" y="17" width="15" height="102"/>
|
<rect key="frame" x="224" y="17" width="15" height="102"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
</scroller>
|
</scroller>
|
||||||
|
@ -517,11 +517,11 @@
|
||||||
</subviews>
|
</subviews>
|
||||||
<nil key="backgroundColor"/>
|
<nil key="backgroundColor"/>
|
||||||
</clipView>
|
</clipView>
|
||||||
<scroller key="horizontalScroller" hidden="YES" verticalHuggingPriority="750" horizontal="YES" id="9r2-h4-K46">
|
<scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="YES" id="9r2-h4-K46">
|
||||||
<rect key="frame" x="-100" y="-100" width="223" height="15"/>
|
<rect key="frame" x="-100" y="-100" width="223" height="15"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
</scroller>
|
</scroller>
|
||||||
<scroller key="verticalScroller" hidden="YES" verticalHuggingPriority="750" horizontal="NO" id="hSI-DO-hVu">
|
<scroller key="verticalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="NO" id="hSI-DO-hVu">
|
||||||
<rect key="frame" x="224" y="17" width="15" height="102"/>
|
<rect key="frame" x="224" y="17" width="15" height="102"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
</scroller>
|
</scroller>
|
||||||
|
|
|
@ -76,8 +76,10 @@ extension FeedListViewController {
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction func addToFeeds(_ sender: Any?) {
|
@IBAction func addToFeeds(_ sender: Any?) {
|
||||||
|
let selectedFeeds = selectedObjects.map { $0 as! FeedListFeed }
|
||||||
|
appDelegate.showAddFeedFromListOnMainWindow(selectedFeeds)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - NSOutlineViewDataSource
|
// MARK: - NSOutlineViewDataSource
|
||||||
|
|
|
@ -0,0 +1,122 @@
|
||||||
|
//
|
||||||
|
// AddFeedFromListWindowController.swift
|
||||||
|
// NetNewsWire
|
||||||
|
//
|
||||||
|
// Created by Maurice Parker on 9/13/18.
|
||||||
|
// Copyright © 2018 Ranchero Software, LLC. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import AppKit
|
||||||
|
import RSCore
|
||||||
|
import RSTree
|
||||||
|
import Articles
|
||||||
|
import Account
|
||||||
|
|
||||||
|
|
||||||
|
class AddFeedFromListWindowController : NSWindowController {
|
||||||
|
|
||||||
|
@IBOutlet weak var addFeedTextField: NSTextField!
|
||||||
|
@IBOutlet weak var folderPopupButton: NSPopUpButton!
|
||||||
|
|
||||||
|
private var feedListFeeds: [FeedListFeed]?
|
||||||
|
private var hostWindow: NSWindow!
|
||||||
|
private var folderTreeController: TreeController?
|
||||||
|
|
||||||
|
convenience init(_ feedListFeeds: [FeedListFeed]) {
|
||||||
|
self.init(windowNibName: NSNib.Name(rawValue: "AddFeedFromListSheet"))
|
||||||
|
self.feedListFeeds = feedListFeeds
|
||||||
|
}
|
||||||
|
|
||||||
|
func runSheetOnWindow(_ w: NSWindow) {
|
||||||
|
hostWindow = w
|
||||||
|
if let sheetWindow = self.window {
|
||||||
|
hostWindow.beginSheet(sheetWindow) { (returnCode: NSApplication.ModalResponse) -> Void in
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func windowDidLoad() {
|
||||||
|
|
||||||
|
guard let feedListFeeds = feedListFeeds else {
|
||||||
|
assertionFailure("Feeds should have been passed in the initializer")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if feedListFeeds.count == 1 {
|
||||||
|
addFeedTextField.stringValue = "Add \"\(feedListFeeds.first!.nameForDisplay)\"?"
|
||||||
|
} else {
|
||||||
|
addFeedTextField.stringValue = "Add \(feedListFeeds.count) feeds?"
|
||||||
|
}
|
||||||
|
|
||||||
|
let rootNode = Node(representedObject: AccountManager.shared.localAccount, parent: nil)
|
||||||
|
rootNode.canHaveChildNodes = true
|
||||||
|
folderTreeController = TreeController(delegate: FolderTreeControllerDelegate(), rootNode: rootNode)
|
||||||
|
|
||||||
|
folderPopupButton.menu = FolderTreeMenu.createFolderPopupMenu(with: folderTreeController!.rootNode)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: Actions
|
||||||
|
|
||||||
|
@IBAction func cancel(_ sender: Any?) {
|
||||||
|
if let sheetWindow = window {
|
||||||
|
hostWindow.endSheet(sheetWindow, returnCode: NSApplication.ModalResponse.cancel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@IBAction func addFeed(_ sender: Any?) {
|
||||||
|
|
||||||
|
guard let container = folderPopupButton.selectedItem?.representedObject as? Container else {
|
||||||
|
assertionFailure("Expected the folderPopupButton to have a container.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let feedListFeeds = feedListFeeds else {
|
||||||
|
assertionFailure("Feeds should have been passed in the initializer")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var account: Account?
|
||||||
|
var folder: Folder?
|
||||||
|
if container is Folder {
|
||||||
|
folder = (container as! Folder)
|
||||||
|
account = folder!.account
|
||||||
|
} else {
|
||||||
|
account = (container as! Account)
|
||||||
|
}
|
||||||
|
|
||||||
|
for feedListFeed in feedListFeeds {
|
||||||
|
|
||||||
|
if account!.hasFeed(withURL: feedListFeed.url) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let feed = account!.createFeed(with: feedListFeed.nameForDisplay, editedName: nil, url: feedListFeed.url) else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let url = URL(string: feedListFeed.url) else {
|
||||||
|
assertionFailure("Malformed URL string: \(feedListFeed.url).")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if account!.addFeed(feed, to: folder) {
|
||||||
|
NotificationCenter.default.post(name: .UserDidAddFeed, object: self, userInfo: [UserInfoKey.feed: feed])
|
||||||
|
}
|
||||||
|
|
||||||
|
InitialFeedDownloader.download(url) { (parsedFeed) in
|
||||||
|
if let parsedFeed = parsedFeed {
|
||||||
|
account!.update(feed, with: parsedFeed, {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if let sheetWindow = window {
|
||||||
|
hostWindow.endSheet(sheetWindow, returnCode: NSApplication.ModalResponse.OK)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -68,7 +68,7 @@ class AddFeedWindowController : NSWindowController {
|
||||||
nameTextField.stringValue = initialName
|
nameTextField.stringValue = initialName
|
||||||
}
|
}
|
||||||
|
|
||||||
folderPopupButton.menu = createFolderPopupMenu()
|
folderPopupButton.menu = FolderTreeMenu.createFolderPopupMenu(with: folderTreeController.rootNode)
|
||||||
updateUI()
|
updateUI()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -139,35 +139,4 @@ private extension AddFeedWindowController {
|
||||||
return folderPopupButton.selectedItem?.representedObject as? Container
|
return folderPopupButton.selectedItem?.representedObject as? Container
|
||||||
}
|
}
|
||||||
|
|
||||||
func createFolderPopupMenu() -> NSMenu {
|
|
||||||
|
|
||||||
let menu = NSMenu(title: "Folders")
|
|
||||||
|
|
||||||
let menuItem = NSMenuItem(title: NSLocalizedString("Top Level", comment: "Add Feed Sheet"), action: nil, keyEquivalent: "")
|
|
||||||
menuItem.representedObject = folderTreeController.rootNode.representedObject
|
|
||||||
menu.addItem(menuItem)
|
|
||||||
|
|
||||||
let childNodes = folderTreeController.rootNode.childNodes
|
|
||||||
addFolderItemsToMenuWithNodes(menu: menu, nodes: childNodes, indentationLevel: 1)
|
|
||||||
|
|
||||||
return menu
|
|
||||||
}
|
|
||||||
|
|
||||||
func addFolderItemsToMenuWithNodes(menu: NSMenu, nodes: [Node], indentationLevel: Int) {
|
|
||||||
|
|
||||||
nodes.forEach { (oneNode) in
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
//
|
||||||
|
// FolderTreeMenu.swift
|
||||||
|
// NetNewsWire
|
||||||
|
//
|
||||||
|
// Created by Maurice Parker on 9/12/18.
|
||||||
|
// Copyright © 2018 Ranchero Software, LLC. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import AppKit
|
||||||
|
import RSCore
|
||||||
|
import RSTree
|
||||||
|
|
||||||
|
class FolderTreeMenu {
|
||||||
|
|
||||||
|
static func createFolderPopupMenu(with rootNode: Node) -> NSMenu {
|
||||||
|
|
||||||
|
let menu = NSMenu(title: "Folders")
|
||||||
|
|
||||||
|
let menuItem = NSMenuItem(title: NSLocalizedString("Top Level", comment: "Add Feed Sheet"), action: nil, keyEquivalent: "")
|
||||||
|
menuItem.representedObject = rootNode.representedObject
|
||||||
|
menu.addItem(menuItem)
|
||||||
|
|
||||||
|
let childNodes = rootNode.childNodes
|
||||||
|
addFolderItemsToMenuWithNodes(menu: menu, nodes: childNodes, indentationLevel: 1)
|
||||||
|
|
||||||
|
return menu
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func addFolderItemsToMenuWithNodes(menu: NSMenu, nodes: [Node], indentationLevel: Int) {
|
||||||
|
|
||||||
|
nodes.forEach { (oneNode) in
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -129,8 +129,8 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations {
|
||||||
return canMarkAllAsRead()
|
return canMarkAllAsRead()
|
||||||
}
|
}
|
||||||
|
|
||||||
if item.action == #selector(markRead(_:)) {
|
if item.action == #selector(toggleRead(_:)) {
|
||||||
return canMarkRead()
|
return validateToggleRead(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
if item.action == #selector(toggleStarred(_:)) {
|
if item.action == #selector(toggleStarred(_:)) {
|
||||||
|
@ -223,9 +223,9 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations {
|
||||||
timelineViewController?.markAllAsRead()
|
timelineViewController?.markAllAsRead()
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction func markRead(_ sender: Any?) {
|
@IBAction func toggleRead(_ sender: Any?) {
|
||||||
|
|
||||||
timelineViewController?.markSelectedArticlesAsRead(sender)
|
timelineViewController?.toggleReadStatusForSelectedArticles()
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction func markUnread(_ sender: Any?) {
|
@IBAction func markUnread(_ sender: Any?) {
|
||||||
|
@ -407,9 +407,35 @@ private extension MainWindowController {
|
||||||
return timelineViewController?.canMarkAllAsRead() ?? false
|
return timelineViewController?.canMarkAllAsRead() ?? false
|
||||||
}
|
}
|
||||||
|
|
||||||
func canMarkRead() -> Bool {
|
func validateToggleRead(_ item: NSValidatedUserInterfaceItem) -> Bool {
|
||||||
|
|
||||||
return timelineViewController?.canMarkSelectedArticlesAsRead() ?? false
|
let validationStatus = timelineViewController?.markReadCommandStatus() ?? .canDoNothing
|
||||||
|
let markingRead: Bool
|
||||||
|
let result: Bool
|
||||||
|
|
||||||
|
switch validationStatus {
|
||||||
|
case .canMark:
|
||||||
|
markingRead = true
|
||||||
|
result = true
|
||||||
|
case .canUnmark:
|
||||||
|
markingRead = false
|
||||||
|
result = true
|
||||||
|
case .canDoNothing:
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func canMarkOlderArticlesAsRead() -> Bool {
|
func canMarkOlderArticlesAsRead() -> Bool {
|
||||||
|
|
|
@ -194,6 +194,8 @@ import RSCore
|
||||||
|
|
||||||
NSCursor.setHiddenUntilMouseMoves(true)
|
NSCursor.setHiddenUntilMouseMoves(true)
|
||||||
outlineView.selectRowIndexes(IndexSet([row]), byExtendingSelection: false)
|
outlineView.selectRowIndexes(IndexSet([row]), byExtendingSelection: false)
|
||||||
|
outlineView.scrollTo(row: row)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func focus() {
|
func focus() {
|
||||||
|
|
|
@ -208,6 +208,29 @@ class TimelineViewController: NSViewController, UndoableCommandRunner {
|
||||||
NSPasteboard.general.copyObjects(selectedArticles)
|
NSPasteboard.general.copyObjects(selectedArticles)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
case .canMark:
|
||||||
|
markingRead = true
|
||||||
|
case .canUnmark:
|
||||||
|
markingRead = false
|
||||||
|
case .canDoNothing:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let undoManager = undoManager, let markStarredCommand = MarkStatusCommand(initialArticles: selectedArticles, markingRead: markingRead, undoManager: undoManager) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
runCommand(markStarredCommand)
|
||||||
|
}
|
||||||
|
|
||||||
func toggleStarredStatusForSelectedArticles() {
|
func toggleStarredStatusForSelectedArticles() {
|
||||||
|
|
||||||
// If any one of the selected articles is not starred, then star them.
|
// If any one of the selected articles is not starred, then star them.
|
||||||
|
|
Loading…
Reference in New Issue