This commit is contained in:
Brent Simmons 2020-10-31 11:45:11 -07:00
commit 46085d0a3d
48 changed files with 616 additions and 1030 deletions

View File

@ -194,6 +194,7 @@ final class CloudKitAccountDelegate: AccountDelegate {
removeWebFeedFromCloud(for: account, with: feed, from: container) { result in
switch result {
case .success:
account.clearWebFeedMetadata(feed)
container.removeWebFeed(feed)
completion(.success(()))
case .failure(let error):

View File

@ -280,7 +280,7 @@ extension RedditFeedProvider: OAuth2SwiftProvider {
consumerSecret: "",
authorizeUrl: "https://www.reddit.com/api/v1/authorize.compact?",
accessTokenUrl: "https://www.reddit.com/api/v1/access_token",
responseType: "token")
responseType: "code")
oauth2.accessTokenBasicAuthentification = true
return oauth2
}
@ -293,12 +293,7 @@ extension RedditFeedProvider: OAuth2SwiftProvider {
let state = generateState(withLength: 20)
let scope = "identity mysubreddits read"
let params = [
"client_id" : SecretsManager.provider.redditConsumerKey,
"response_type" : "code",
"state" : state,
"redirect_uri" : "netnewswire://success",
"duration" : "permanent",
"scope" : scope
]
return (state: state, scope: scope, params: params)
}

View File

@ -98,7 +98,7 @@ final class RedditLinkData: Codable {
guard let url = url else { return "" }
if url.hasSuffix(".gif") {
return "<img src=\"\(url)\">"
return "<img class=\"nnw-nozoom\" src=\"\(url)\">"
}
if isVideo ?? false, let videoURL = media?.video?.hlsURL {
@ -109,12 +109,25 @@ final class RedditLinkData: Codable {
if let width = media?.video?.width, let height = media?.video?.height {
html += "width=\"\(width)\" height=\"\(height)\" "
}
html += "src=\"\(videoURL)\" autoplay muted loop></video>"
html += "src=\"\(videoURL)\"></video>"
return html
}
if let imageVariantURL = preview?.images?.first?.variants?.mp4?.source?.url {
var html = "<video class=\"nnwAnimatedGIF\" "
if let previewImageURL = preview?.images?.first?.source?.url {
html += "poster=\"\(previewImageURL)\" "
}
if let width = preview?.images?.first?.variants?.mp4?.source?.width, let height = preview?.images?.first?.variants?.mp4?.source?.height {
html += "width=\"\(width)\" height=\"\(height)\" "
}
html += "src=\"\(imageVariantURL)\" autoplay muted loop></video>"
html += linkURL(url)
return html
}
if let videoPreviewURL = preview?.videoPreview?.url {
var html = "<video "
var html = "<video class=\"nnwAnimatedGIF\" "
if let previewImageURL = preview?.images?.first?.source?.url {
html += "poster=\"\(previewImageURL)\" "
}

View File

@ -23,6 +23,42 @@ struct RedditPreview: Codable {
struct RedditPreviewImage: Codable {
let source: RedditPreviewImageSource?
let variants: RedditPreviewImageVariants?
enum CodingKeys: String, CodingKey {
case source = "source"
case variants = "variants"
}
}
struct RedditPreviewImageSource: Codable {
let url: String?
let width: Int?
let height: Int?
enum CodingKeys: String, CodingKey {
case url = "url"
case width = "width"
case height = "height"
}
}
struct RedditPreviewImageVariants: Codable {
let mp4: RedditPreviewImageVariantsMP4?
enum CodingKeys: String, CodingKey {
case mp4 = "mp4"
}
}
struct RedditPreviewImageVariantsMP4: Codable {
let source: RedditPreviewImageVariantsMP4Source?
enum CodingKeys: String, CodingKey {
case source = "source"
@ -30,7 +66,7 @@ struct RedditPreviewImage: Codable {
}
struct RedditPreviewImageSource: Codable {
struct RedditPreviewImageVariantsMP4Source: Codable {
let url: String?
let width: Int?

View File

@ -36,8 +36,10 @@ struct TwitterExtendedMedia: Codable {
switch type {
case "photo":
html += renderPhotoAsHTML()
case "video", "animated_gif":
case "video":
html += renderVideoAsHTML()
case "animated_gif":
html += renderAnimatedGIFAsHTML()
default:
break
}
@ -74,6 +76,21 @@ private extension TwitterExtendedMedia {
return html
}
func renderAnimatedGIFAsHTML() -> String {
guard let bestVariantURL = findBestVariant()?.url else { return "" }
var html = "<video class=\"nnwAnimatedGIF\" "
if let httpsMediaURL = httpsMediaURL {
html += "poster=\"\(httpsMediaURL)\" "
} else if let mediaURL = mediaURL {
html += "poster=\"\(mediaURL)\" "
}
html += "src=\"\(bestVariantURL)\" autoplay muted loop></video>"
return html
}
func findBestVariant() -> TwitterVideo.Variant? {
var best: TwitterVideo.Variant? = nil
if let variants = video?.variants {

View File

@ -59,6 +59,20 @@ final class FeedlyCreateFeedsForCollectionFoldersOperation: FeedlyOperation {
// find an existing feed previously added to the account
if let feed = account.existingWebFeed(withWebFeedID: collectionFeed.id) {
// If the feed was renamed on Feedly, ensure we ingest the new name.
if feed.nameForDisplay != collectionFeed.title {
feed.name = collectionFeed.title
// Let the rest of the app (e.g.: the sidebar) know the feed name changed
// `editedName` would post this if its value is changing.
// Setting the `name` property has no side effects like this.
if feed.editedName != nil {
feed.editedName = nil
} else {
feed.postDisplayNameDidChangeNotification()
}
}
return (feed, folder)
} else {
// find an existing feed we created below in an earlier value

View File

@ -312,14 +312,14 @@ private extension LocalAccountDelegate {
return
}
let feed = account.createWebFeed(with: nil, url: url.absoluteString, webFeedID: url.absoluteString, homePageURL: nil)
feed.editedName = editedName
container.addWebFeed(feed)
InitialFeedDownloader.download(url) { parsedFeed in
self.refreshProgress.completeTask()
if let parsedFeed = parsedFeed {
let feed = account.createWebFeed(with: nil, url: url.absoluteString, webFeedID: url.absoluteString, homePageURL: nil)
feed.editedName = editedName
container.addWebFeed(feed)
account.update(feed, with: parsedFeed, {_ in
BatchUpdate.shared.end()
completion(.success(feed))

View File

@ -263,7 +263,7 @@ final class ReaderAPIAccountDelegate: AccountDelegate {
switch result {
case .success:
DispatchQueue.main.async {
self.clearFolderRelationship(for: feed, withFolderName: folder.name ?? "")
self.clearFolderRelationship(for: feed, folderExternalID: folder.externalID)
}
case .failure(let error):
os_log(.error, log: self.log, "Remove feed error: %@.", error.localizedDescription)
@ -390,8 +390,33 @@ final class ReaderAPIAccountDelegate: AccountDelegate {
}
func removeWebFeed(for account: Account, with feed: WebFeed, from container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
account.clearWebFeedMetadata(feed)
deleteSubscription(for: account, with: feed, from: container, completion: completion)
guard let subscriptionID = feed.externalID else {
completion(.failure(ReaderAPIAccountDelegateError.invalidParameter))
return
}
refreshProgress.addToNumberOfTasksAndRemaining(1)
caller.deleteSubscription(subscriptionID: subscriptionID) { result in
self.refreshProgress.completeTask()
switch result {
case .success:
DispatchQueue.main.async {
account.clearWebFeedMetadata(feed)
account.removeWebFeed(feed)
if let folders = account.folders {
for folder in folders {
folder.removeWebFeed(feed)
}
}
completion(.success(()))
}
case .failure(let error):
DispatchQueue.main.async {
let wrappedError = AccountError.wrappedError(error: error, account: account)
completion(.failure(wrappedError))
}
}
}
}
func moveWebFeed(for account: Account, with feed: WebFeed, from: Container, to: Container, completion: @escaping (Result<Void, Error>) -> Void) {
@ -410,14 +435,14 @@ final class ReaderAPIAccountDelegate: AccountDelegate {
}
func addWebFeed(for account: Account, with feed: WebFeed, to container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
if let folder = container as? Folder, let feedName = feed.externalID {
if let folder = container as? Folder, let feedExternalID = feed.externalID {
refreshProgress.addToNumberOfTasksAndRemaining(1)
caller.createTagging(subscriptionID: feedName, tagName: folder.name ?? "") { result in
caller.createTagging(subscriptionID: feedExternalID, tagName: folder.name ?? "") { result in
self.refreshProgress.completeTask()
switch result {
case .success:
DispatchQueue.main.async {
self.saveFolderRelationship(for: feed, withFolderName: folder.name ?? "", id: feed.externalID!)
self.saveFolderRelationship(for: feed, folderExternalID: folder.externalID, feedExternalID: feedExternalID)
account.removeWebFeed(feed)
folder.addWebFeed(feed)
completion(.success(()))
@ -561,11 +586,21 @@ private extension ReaderAPIAccountDelegate {
caller.retrieveTags { result in
switch result {
case .success(let tags):
BatchUpdate.shared.perform {
self.syncFolders(account, tags)
}
self.refreshProgress.completeTask()
self.refreshFeeds(account, completion: completion)
self.caller.retrieveSubscriptions { result in
self.refreshProgress.completeTask()
switch result {
case .success(let subscriptions):
BatchUpdate.shared.perform {
self.syncFolders(account, tags)
self.syncFeeds(account, subscriptions)
self.syncFeedFolderRelationship(account, subscriptions)
}
completion(.success(()))
case .failure(let error):
completion(.failure(error))
}
}
case .failure(let error):
completion(.failure(error))
}
@ -575,58 +610,45 @@ private extension ReaderAPIAccountDelegate {
func syncFolders(_ account: Account, _ tags: [ReaderAPITag]?) {
guard let tags = tags else { return }
assert(Thread.isMainThread)
let folderTags = tags.filter{ $0.tagID.contains("/label/") }
guard !folderTags.isEmpty else { return }
os_log(.debug, log: log, "Syncing folders with %ld tags.", folderTags.count)
os_log(.debug, log: log, "Syncing folders with %ld tags.", tags.count)
let readerFolderNames = tags.compactMap { $0.folderName }
let readerFolderExternalIDs = folderTags.compactMap { $0.tagID }
// Delete any folders not at Reader
if let folders = account.folders {
folders.forEach { folder in
if !readerFolderNames.contains(folder.name ?? "") {
if !readerFolderExternalIDs.contains(folder.externalID ?? "") {
for feed in folder.topLevelWebFeeds {
account.addWebFeed(feed)
clearFolderRelationship(for: feed, withFolderName: folder.name ?? "")
clearFolderRelationship(for: feed, folderExternalID: folder.externalID)
}
account.removeFolder(folder)
}
}
}
let folderNames: [String] = {
let folderExternalIDs: [String] = {
if let folders = account.folders {
return folders.map { $0.name ?? "" }
return folders.compactMap { $0.externalID }
} else {
return [String]()
}
}()
// Make any folders Reader has, but we don't
tags.forEach { tag in
if let tagFolderName = tag.folderName, !folderNames.contains(tagFolderName) {
let folder = account.ensureFolder(with: tagFolderName)
folderTags.forEach { tag in
if !folderExternalIDs.contains(tag.tagID) {
let folder = account.ensureFolder(with: tag.folderName ?? "None")
folder?.externalID = tag.tagID
}
}
}
func refreshFeeds(_ account: Account, completion: @escaping (Result<Void, Error>) -> Void) {
caller.retrieveSubscriptions { result in
switch result {
case .success(let subscriptions):
self.refreshProgress.completeTask()
BatchUpdate.shared.perform {
self.syncFeeds(account, subscriptions)
self.syncTaggings(account, subscriptions)
}
completion(.success(()))
case .failure(let error):
completion(.failure(error))
}
}
}
func syncFeeds(_ account: Account, _ subscriptions: [ReaderAPISubscription]?) {
guard let subscriptions = subscriptions else { return }
@ -649,6 +671,7 @@ private extension ReaderAPIAccountDelegate {
for feed in account.topLevelWebFeeds {
if !subFeedIds.contains(feed.webFeedID) {
account.clearWebFeedMetadata(feed)
account.removeWebFeed(feed)
}
}
@ -670,43 +693,38 @@ private extension ReaderAPIAccountDelegate {
}
func syncTaggings(_ account: Account, _ subscriptions: [ReaderAPISubscription]?) {
func syncFeedFolderRelationship(_ account: Account, _ subscriptions: [ReaderAPISubscription]?) {
guard let subscriptions = subscriptions else { return }
assert(Thread.isMainThread)
os_log(.debug, log: log, "Syncing taggings with %ld subscriptions.", subscriptions.count)
// Set up some structures to make syncing easier
let folderDict = nameToFolderDictionary(with: account.folders)
let folderDict = externalIDToFolderDictionary(with: account.folders)
let taggingsDict = subscriptions.reduce([String: [ReaderAPISubscription]]()) { (dict, subscription) in
var taggedFeeds = dict
// For each category that this feed belongs to, add the feed to that name in the dict
subscription.categories.forEach({ (category) in
let categoryName = category.categoryLabel.replacingOccurrences(of: "user/-/label/", with: "")
if var taggedFeed = taggedFeeds[categoryName] {
if var taggedFeed = taggedFeeds[category.categoryId] {
taggedFeed.append(subscription)
taggedFeeds[categoryName] = taggedFeed
taggedFeeds[category.categoryId] = taggedFeed
} else {
taggedFeeds[categoryName] = [subscription]
taggedFeeds[category.categoryId] = [subscription]
}
})
return taggedFeeds
}
var taggedFeedIDs = Set<String>()
// Sync the folders
for (folderName, groupedTaggings) in taggingsDict {
guard let folder = folderDict[folderName] else { return }
for (folderExternalID, groupedTaggings) in taggingsDict {
guard let folder = folderDict[folderExternalID] else { return }
let taggingFeedIDs = groupedTaggings.map { $0.feedID }
// Move any feeds not in the folder to the account
for feed in folder.topLevelWebFeeds {
if !taggingFeedIDs.contains(feed.webFeedID) {
folder.removeWebFeed(feed)
clearFolderRelationship(for: feed, withFolderName: folder.name ?? "")
clearFolderRelationship(for: feed, folderExternalID: folder.externalID)
account.addWebFeed(feed)
}
}
@ -720,14 +738,15 @@ private extension ReaderAPIAccountDelegate {
guard let feed = account.existingWebFeed(withWebFeedID: taggingFeedID) else {
continue
}
saveFolderRelationship(for: feed, withFolderName: folderName, id: String(subscription.feedID))
saveFolderRelationship(for: feed, folderExternalID: folderExternalID, feedExternalID: subscription.feedID)
folder.addWebFeed(feed)
taggedFeedIDs.insert(taggingFeedID)
}
}
}
let taggedFeedIDs = Set(subscriptions.filter({ !$0.categories.isEmpty }).map { String($0.feedID) })
// Remove all feeds from the account container that have a tag
for feed in account.topLevelWebFeeds {
if taggedFeedIDs.contains(feed.webFeedID) {
@ -736,18 +755,19 @@ private extension ReaderAPIAccountDelegate {
}
}
func nameToFolderDictionary(with folders: Set<Folder>?) -> [String: Folder] {
func externalIDToFolderDictionary(with folders: Set<Folder>?) -> [String: Folder] {
guard let folders = folders else {
return [String: Folder]()
}
var d = [String: Folder]()
for folder in folders {
let name = folder.name ?? ""
if d[name] == nil {
d[name] = folder
if let externalID = folder.externalID, d[externalID] == nil {
d[externalID] = folder
}
}
return d
}
@ -784,19 +804,19 @@ private extension ReaderAPIAccountDelegate {
}
func clearFolderRelationship(for feed: WebFeed, withFolderName folderName: String) {
if var folderRelationship = feed.folderRelationship {
folderRelationship[folderName] = nil
feed.folderRelationship = folderRelationship
}
func clearFolderRelationship(for feed: WebFeed, folderExternalID: String?) {
guard var folderRelationship = feed.folderRelationship, let folderExternalID = folderExternalID else { return }
folderRelationship[folderExternalID] = nil
feed.folderRelationship = folderRelationship
}
func saveFolderRelationship(for feed: WebFeed, withFolderName folderName: String, id: String) {
func saveFolderRelationship(for feed: WebFeed, folderExternalID: String?, feedExternalID: String) {
guard let folderExternalID = folderExternalID else { return }
if var folderRelationship = feed.folderRelationship {
folderRelationship[folderName] = id
folderRelationship[folderExternalID] = feedExternalID
feed.folderRelationship = folderRelationship
} else {
feed.folderRelationship = [folderName: id]
feed.folderRelationship = [folderExternalID: feedExternalID]
}
}
@ -889,15 +909,23 @@ private extension ReaderAPIAccountDelegate {
account.fetchArticleIDsForStatusesWithoutArticlesNewerThanCutoffDate { articleIDsResult in
func process(_ fetchedArticleIDs: Set<String>) {
guard !fetchedArticleIDs.isEmpty else {
completion()
return
}
os_log(.debug, log: self.log, "Refreshing missing articles...")
let group = DispatchGroup()
let articleIDs = Array(fetchedArticleIDs)
let chunkedArticleIDs = articleIDs.chunked(into: 100)
self.refreshProgress.addToNumberOfTasksAndRemaining(chunkedArticleIDs.count - 1)
for chunk in chunkedArticleIDs {
group.enter()
self.caller.retrieveEntries(articleIDs: chunk) { result in
self.refreshProgress.completeTask()
switch result {
case .success(let entries):
@ -1052,7 +1080,7 @@ private extension ReaderAPIAccountDelegate {
switch result {
case .success:
DispatchQueue.main.async {
self.clearFolderRelationship(for: feed, withFolderName: folder.name ?? "")
self.clearFolderRelationship(for: feed, folderExternalID: folder.externalID)
folder.removeWebFeed(feed)
account.addFeedIfNotInAnyFolder(feed)
completion(.success(()))
@ -1072,35 +1100,5 @@ private extension ReaderAPIAccountDelegate {
}
}
func deleteSubscription(for account: Account, with feed: WebFeed, from container: Container?, completion: @escaping (Result<Void, Error>) -> Void) {
// This error should never happen
guard let subscriptionID = feed.externalID else {
completion(.failure(ReaderAPIAccountDelegateError.invalidParameter))
return
}
caller.deleteSubscription(subscriptionID: subscriptionID) { result in
switch result {
case .success:
DispatchQueue.main.async {
account.removeWebFeed(feed)
if let folders = account.folders {
for folder in folders {
folder.removeWebFeed(feed)
}
}
completion(.success(()))
}
case .failure(let error):
DispatchQueue.main.async {
let wrappedError = AccountError.wrappedError(error: error, account: account)
completion(.failure(wrappedError))
}
}
}
}
}

View File

@ -12,11 +12,6 @@ import RSCore
import RSWeb
import Articles
extension Notification.Name {
static let appleColorPreferencesChangedNotification = Notification.Name("AppleColorPreferencesChangedNotification")
static let appleInterfaceThemeChangedNotification = Notification.Name("AppleInterfaceThemeChangedNotification")
}
protocol DetailWebViewControllerDelegate: class {
func mouseDidEnter(_: DetailWebViewController, link: String)
func mouseDidExit(_: DetailWebViewController, link: String)
@ -125,9 +120,6 @@ final class DetailWebViewController: NSViewController, WKUIDelegate {
NotificationCenter.default.addObserver(self, selector: #selector(faviconDidBecomeAvailable(_:)), name: .FaviconDidBecomeAvailable, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(faviconDidBecomeAvailable(_:)), name: .FaviconDidBecomeAvailable, object: nil)
DistributedNotificationCenter.default().addObserver(self, selector: #selector(appleColorPreferencesChanged(_:)), name: .appleColorPreferencesChangedNotification, object: nil)
DistributedNotificationCenter.default().addObserver(self, selector: #selector(appleInterfaceThemeChanged(_:)), name: .appleInterfaceThemeChangedNotification, object: nil)
webView.loadFileURL(ArticleRenderer.blank.url, allowingReadAccessTo: ArticleRenderer.blank.baseURL)
}
@ -145,14 +137,6 @@ final class DetailWebViewController: NSViewController, WKUIDelegate {
reloadArticleImage()
}
@objc func appleColorPreferencesChanged(_ note: Notification) {
reloadHTML()
}
@objc func appleInterfaceThemeChanged(_ note: Notification) {
reloadHTML()
}
// MARK: Media Functions
func stopMediaPlayback() {

View File

@ -1,123 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="17506" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="17506"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="ExtensionPointAddViewController" customModule="NetNewsWire" customModuleProvider="target">
<connections>
<outlet property="tableView" destination="lyM-Zu-Let" id="JDz-05-OOg"/>
<outlet property="view" destination="c22-O7-iKe" id="Vfr-rK-EHC"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<customView id="c22-O7-iKe">
<rect key="frame" x="0.0" y="0.0" width="480" height="272"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews>
<scrollView autohidesScrollers="YES" horizontalLineScroll="42" horizontalPageScroll="10" verticalLineScroll="42" verticalPageScroll="10" usesPredominantAxisScrolling="NO" id="y2z-6c-TH0">
<rect key="frame" x="0.0" y="0.0" width="480" height="272"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<clipView key="contentView" id="qCn-Bf-ICO">
<rect key="frame" x="1" y="1" width="478" height="270"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<tableView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" columnReordering="NO" columnSelection="YES" columnResizing="NO" multipleSelection="NO" autosaveColumns="NO" rowHeight="40" rowSizeStyle="automatic" viewBased="YES" id="lyM-Zu-Let">
<rect key="frame" x="0.0" y="0.0" width="478" height="270"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<size key="intercellSpacing" width="3" height="2"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
<tableViewGridLines key="gridStyleMask" horizontal="YES"/>
<color key="gridColor" name="gridColor" catalog="System" colorSpace="catalog"/>
<tableColumns>
<tableColumn width="466" minWidth="40" maxWidth="1000" id="SlU-lH-CzT">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border">
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
</tableHeaderCell>
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" title="Text Cell" id="Nhn-I6-76l">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
<prototypeCellViews>
<tableCellView identifier="Cell" id="EGi-CQ-lPc" customClass="AccountsAddTableCellView" customModule="NetNewsWire" customModuleProvider="target">
<rect key="frame" x="11" y="1" width="475" height="40"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<stackView distribution="fill" orientation="horizontal" alignment="centerY" spacing="17" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="iCD-Yx-4V5">
<rect key="frame" x="20" y="8" width="133" height="24"/>
<subviews>
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="KmN-Zk-TBU">
<rect key="frame" x="0.0" y="0.0" width="24" height="24"/>
<constraints>
<constraint firstAttribute="height" constant="24" id="dbz-aC-h0q"/>
<constraint firstAttribute="width" constant="24" id="jN0-Et-ysS"/>
</constraints>
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" id="oGL-yl-27S"/>
</imageView>
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="uyu-5W-IaW">
<rect key="frame" x="39" y="4" width="96" height="16"/>
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Table View Cell" id="iOW-VJ-bkx">
<font key="font" textStyle="body" name=".SFNS-Regular"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<visibilityPriorities>
<integer value="1000"/>
<integer value="1000"/>
</visibilityPriorities>
<customSpacing>
<real value="3.4028234663852886e+38"/>
<real value="3.4028234663852886e+38"/>
</customSpacing>
</stackView>
<button id="y48-E2-CL3">
<rect key="frame" x="0.0" y="0.0" width="475" height="40"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="bevel" bezelStyle="rounded" alignment="center" imageScaling="proportionallyDown" inset="2" id="yf7-Ye-Pcd">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" textStyle="body" name=".SFNS-Regular"/>
</buttonCell>
<connections>
<action selector="pressed:" target="EGi-CQ-lPc" id="2a9-Bp-K3K"/>
</connections>
</button>
</subviews>
<constraints>
<constraint firstItem="iCD-Yx-4V5" firstAttribute="centerY" secondItem="EGi-CQ-lPc" secondAttribute="centerY" id="IS1-7W-BWY"/>
<constraint firstItem="iCD-Yx-4V5" firstAttribute="leading" secondItem="EGi-CQ-lPc" secondAttribute="leading" constant="20" id="IsY-WH-f93"/>
</constraints>
<connections>
<outlet property="accountImageView" destination="KmN-Zk-TBU" id="ksF-ga-V5P"/>
<outlet property="accountNameLabel" destination="uyu-5W-IaW" id="reb-QA-Xpx"/>
<outlet property="imageView" destination="KmN-Zk-TBU" id="Tfy-Eb-Isb"/>
<outlet property="titleLabel" destination="uyu-5W-IaW" id="QAe-Gk-Eeo"/>
</connections>
</tableCellView>
</prototypeCellViews>
</tableColumn>
</tableColumns>
</tableView>
</subviews>
</clipView>
<scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="YES" id="qOf-Dj-ubR">
<rect key="frame" x="1" y="255" width="478" height="16"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
<scroller key="verticalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="NO" id="XFQ-Xy-wny">
<rect key="frame" x="224" y="17" width="15" height="102"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
</scrollView>
</subviews>
<point key="canvasLocation" x="139" y="154"/>
</customView>
</objects>
</document>

View File

@ -1,29 +0,0 @@
//
// AccountsAddTableCellView.swift
// NetNewsWire
//
// Created by Maurice Parker on 5/1/19.
// Copyright © 2019 Ranchero Software. All rights reserved.
//
import AppKit
import Account
protocol AccountsAddTableCellViewDelegate: class {
func addAccount(_ accountType: AccountType)
}
class AccountsAddTableCellView: NSTableCellView {
weak var delegate: AccountsAddTableCellViewDelegate?
var accountType: AccountType?
@IBOutlet weak var accountImageView: NSImageView?
@IBOutlet weak var accountNameLabel: NSTextField?
@IBAction func pressed(_ sender: Any) {
guard let accountType = accountType else { return }
delegate?.addAccount(accountType)
}
}

View File

@ -1,260 +0,0 @@
//
// AccountsAddViewController.swift
// NetNewsWire
//
// Created by Maurice Parker on 5/1/19.
// Copyright © 2019 Ranchero Software. All rights reserved.
//
import AppKit
import Account
import RSCore
class AccountsAddViewController: NSViewController {
@IBOutlet weak var tableView: NSTableView!
private var accountsAddWindowController: NSWindowController?
#if DEBUG
private var addableAccountTypes: [AccountType] = [.onMyMac, .cloudKit, .bazQux, .feedbin, .feedly, .feedWrangler, .inoreader, .newsBlur, .theOldReader, .freshRSS]
#else
private var addableAccountTypes: [AccountType] = [.onMyMac, .cloudKit, .bazQux, .feedbin, .feedly, .feedWrangler, .inoreader, .newsBlur, .theOldReader, .freshRSS]
#endif
init() {
super.init(nibName: "AccountsAdd", bundle: nil)
}
public required init?(coder: NSCoder) {
super.init(coder: coder)
}
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
tableView.delegate = self
restrictAccounts()
}
}
// MARK: - NSTableViewDataSource
extension AccountsAddViewController: NSTableViewDataSource {
func numberOfRows(in tableView: NSTableView) -> Int {
return addableAccountTypes.count
}
func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any? {
return nil
}
}
// MARK: - NSTableViewDelegate
extension AccountsAddViewController: NSTableViewDelegate {
private static let cellIdentifier = NSUserInterfaceItemIdentifier(rawValue: "AccountCell")
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
if let cell = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "Cell"), owner: nil) as? AccountsAddTableCellView {
cell.accountType = addableAccountTypes[row]
cell.delegate = self
switch addableAccountTypes[row] {
case .onMyMac:
cell.accountNameLabel?.stringValue = Account.defaultLocalAccountName
cell.accountImageView?.image = AppAssets.accountLocal
case .cloudKit:
cell.accountNameLabel?.stringValue = NSLocalizedString("iCloud", comment: "iCloud")
cell.accountImageView?.image = AppAssets.accountCloudKit
case .feedbin:
cell.accountNameLabel?.stringValue = NSLocalizedString("Feedbin", comment: "Feedbin")
cell.accountImageView?.image = AppAssets.accountFeedbin
case .feedWrangler:
cell.accountNameLabel?.stringValue = NSLocalizedString("Feed Wrangler", comment: "Feed Wrangler")
cell.accountImageView?.image = AppAssets.accountFeedWrangler
case .freshRSS:
cell.accountNameLabel?.stringValue = NSLocalizedString("FreshRSS", comment: "FreshRSS")
cell.accountImageView?.image = AppAssets.accountFreshRSS
case .feedly:
cell.accountNameLabel?.stringValue = NSLocalizedString("Feedly", comment: "Feedly")
cell.accountImageView?.image = AppAssets.accountFeedly
case .newsBlur:
cell.accountNameLabel?.stringValue = NSLocalizedString("NewsBlur", comment: "NewsBlur")
cell.accountImageView?.image = AppAssets.accountNewsBlur
case .inoreader:
cell.accountNameLabel?.stringValue = NSLocalizedString("Inoreader", comment: "Inoreader")
cell.accountImageView?.image = AppAssets.accountInoreader
case .bazQux:
cell.accountNameLabel?.stringValue = NSLocalizedString("Bazqux", comment: "Bazqux")
cell.accountImageView?.image = AppAssets.accountBazQux
case .theOldReader:
cell.accountNameLabel?.stringValue = NSLocalizedString("The Old Reader", comment: "The Old Reader")
cell.accountImageView?.image = AppAssets.accountTheOldReader
}
return cell
}
return nil
}
}
// MARK: AccountsAddTableCellViewDelegate
extension AccountsAddViewController: AccountsAddTableCellViewDelegate {
func addAccount(_ accountType: AccountType) {
switch accountType {
case .onMyMac:
let accountsAddLocalWindowController = AccountsAddLocalWindowController()
accountsAddLocalWindowController.runSheetOnWindow(self.view.window!)
accountsAddWindowController = accountsAddLocalWindowController
case .cloudKit:
let accountsAddCloudKitWindowController = AccountsAddCloudKitWindowController()
accountsAddCloudKitWindowController.runSheetOnWindow(self.view.window!) { response in
if response == NSApplication.ModalResponse.OK {
self.restrictAccounts()
self.tableView.reloadData()
}
}
accountsAddWindowController = accountsAddCloudKitWindowController
case .feedbin:
let accountsFeedbinWindowController = AccountsFeedbinWindowController()
accountsFeedbinWindowController.runSheetOnWindow(self.view.window!)
accountsAddWindowController = accountsFeedbinWindowController
case .feedWrangler:
let accountsFeedWranglerWindowController = AccountsFeedWranglerWindowController()
accountsFeedWranglerWindowController.runSheetOnWindow(self.view.window!)
accountsAddWindowController = accountsFeedWranglerWindowController
case .freshRSS:
let accountsReaderAPIWindowController = AccountsReaderAPIWindowController()
accountsReaderAPIWindowController.accountType = .freshRSS
accountsReaderAPIWindowController.runSheetOnWindow(self.view.window!)
accountsAddWindowController = accountsReaderAPIWindowController
case .feedly:
let addAccount = OAuthAccountAuthorizationOperation(accountType: .feedly)
addAccount.delegate = self
addAccount.presentationAnchor = self.view.window!
runAwaitingFeedlyLoginAlertModal(forLifetimeOf: addAccount)
MainThreadOperationQueue.shared.add(addAccount)
case .newsBlur:
let accountsNewsBlurWindowController = AccountsNewsBlurWindowController()
accountsNewsBlurWindowController.runSheetOnWindow(self.view.window!)
accountsAddWindowController = accountsNewsBlurWindowController
case .inoreader:
let accountsReaderAPIWindowController = AccountsReaderAPIWindowController()
accountsReaderAPIWindowController.accountType = .inoreader
accountsReaderAPIWindowController.runSheetOnWindow(self.view.window!)
accountsAddWindowController = accountsReaderAPIWindowController
case .bazQux:
let accountsReaderAPIWindowController = AccountsReaderAPIWindowController()
accountsReaderAPIWindowController.accountType = .bazQux
accountsReaderAPIWindowController.runSheetOnWindow(self.view.window!)
accountsAddWindowController = accountsReaderAPIWindowController
case .theOldReader:
let accountsReaderAPIWindowController = AccountsReaderAPIWindowController()
accountsReaderAPIWindowController.accountType = .theOldReader
accountsReaderAPIWindowController.runSheetOnWindow(self.view.window!)
accountsAddWindowController = accountsReaderAPIWindowController
}
}
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("Your default 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
}
attachedWindow.endSheet(alert.window)
}
}
}
// MARK: OAuthAccountAuthorizationOperationDelegate
extension AccountsAddViewController: 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:
break
case .failure(let error):
self?.presentError(error)
}
}
}
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)
}
}
// MARK: Private
private extension AccountsAddViewController {
func restrictAccounts() {
func removeAccountType(_ accountType: AccountType) {
if let index = addableAccountTypes.firstIndex(of: accountType) {
addableAccountTypes.remove(at: index)
}
}
if AppDefaults.shared.isDeveloperBuild {
removeAccountType(.cloudKit)
removeAccountType(.feedly)
removeAccountType(.feedWrangler)
removeAccountType(.inoreader)
return
}
if AccountManager.shared.accounts.firstIndex(where: { $0.type == .cloudKit }) != nil {
removeAccountType(.cloudKit)
}
}
}

View File

@ -58,7 +58,7 @@ class AccountsFeedbinWindowController: NSWindowController {
return
}
guard !AccountManager.shared.duplicateServiceAccount(type: .feedbin, username: usernameTextField.stringValue) else {
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
}

View File

@ -11,14 +11,19 @@ import Account
import SwiftUI
import RSCore
// MARK: - AccountsPreferencesAddAccountDelegate
protocol AccountsPreferencesAddAccountDelegate {
func presentSheetForAccount(_ accountType: AccountType)
}
// MARK: - AccountsPreferencesViewController
final class AccountsPreferencesViewController: NSViewController {
@IBOutlet weak var tableView: NSTableView!
@IBOutlet weak var detailView: NSView!
@IBOutlet weak var deleteButton: NSButton!
var addAccountDelegate: AccountsPreferencesAddAccountDelegate?
var addAccountWindowController: NSWindowController?
private var sortedAccounts = [Account]()
@ -39,6 +44,11 @@ final class AccountsPreferencesViewController: NSViewController {
var rTable = tableView.frame
rTable.size.width = tableView.superview!.frame.size.width
tableView.frame = rTable
// Set initial row selection
if sortedAccounts.count > 0 {
tableView.selectRow(0)
}
}
@IBAction func addAccount(_ sender: Any) {
@ -119,7 +129,7 @@ extension AccountsPreferencesViewController: NSTableViewDelegate {
let selectedRow = tableView.selectedRow
if tableView.selectedRow == -1 {
deleteButton.isEnabled = false
showController(AccountsAddViewController())
hideController()
return
} else {
deleteButton.isEnabled = true
@ -137,18 +147,13 @@ extension AccountsPreferencesViewController: NSTableViewDelegate {
}
// MARK: - AccountsPreferencesAddAccountDelegate
protocol AccountsPreferencesAddAccountDelegate {
func presentSheetForAccount(_ accountType: AccountType)
}
extension AccountsPreferencesViewController: AccountsPreferencesAddAccountDelegate {
func presentSheetForAccount(_ accountType: AccountType) {
switch accountType {
case .onMyMac:
let accountsAddLocalWindowController = AccountsAddLocalWindowController()
accountsAddLocalWindowController.runSheetOnWindow(self.view.window!)
addAccountWindowController = accountsAddLocalWindowController
case .cloudKit:
let accountsAddCloudKitWindowController = AccountsAddCloudKitWindowController()
accountsAddCloudKitWindowController.runSheetOnWindow(self.view.window!) { response in
@ -156,17 +161,20 @@ extension AccountsPreferencesViewController: AccountsPreferencesAddAccountDelega
self.tableView.reloadData()
}
}
addAccountWindowController = accountsAddCloudKitWindowController
case .feedbin:
let accountsFeedbinWindowController = AccountsFeedbinWindowController()
accountsFeedbinWindowController.runSheetOnWindow(self.view.window!)
addAccountWindowController = accountsFeedbinWindowController
case .feedWrangler:
let accountsFeedWranglerWindowController = AccountsFeedWranglerWindowController()
accountsFeedWranglerWindowController.runSheetOnWindow(self.view.window!)
case .freshRSS:
addAccountWindowController = accountsFeedWranglerWindowController
case .freshRSS, .inoreader, .bazQux, .theOldReader:
let accountsReaderAPIWindowController = AccountsReaderAPIWindowController()
accountsReaderAPIWindowController.accountType = .freshRSS
accountsReaderAPIWindowController.accountType = accountType
accountsReaderAPIWindowController.runSheetOnWindow(self.view.window!)
addAccountWindowController = accountsReaderAPIWindowController
case .feedly:
let addAccount = OAuthAccountAuthorizationOperation(accountType: .feedly)
addAccount.delegate = self
@ -176,18 +184,7 @@ extension AccountsPreferencesViewController: AccountsPreferencesAddAccountDelega
case .newsBlur:
let accountsNewsBlurWindowController = AccountsNewsBlurWindowController()
accountsNewsBlurWindowController.runSheetOnWindow(self.view.window!)
case .inoreader:
let accountsReaderAPIWindowController = AccountsReaderAPIWindowController()
accountsReaderAPIWindowController.accountType = .inoreader
accountsReaderAPIWindowController.runSheetOnWindow(self.view.window!)
case .bazQux:
let accountsReaderAPIWindowController = AccountsReaderAPIWindowController()
accountsReaderAPIWindowController.accountType = .bazQux
accountsReaderAPIWindowController.runSheetOnWindow(self.view.window!)
case .theOldReader:
let accountsReaderAPIWindowController = AccountsReaderAPIWindowController()
accountsReaderAPIWindowController.accountType = .theOldReader
accountsReaderAPIWindowController.runSheetOnWindow(self.view.window!)
addAccountWindowController = accountsNewsBlurWindowController
}
}
@ -228,11 +225,7 @@ private extension AccountsPreferencesViewController {
}
func showController(_ controller: NSViewController) {
if let controller = children.first {
children.removeAll()
controller.view.removeFromSuperview()
}
hideController()
addChild(controller)
controller.view.translatesAutoresizingMaskIntoConstraints = false
@ -241,6 +234,13 @@ private extension AccountsPreferencesViewController {
}
func hideController() {
if let controller = children.first {
children.removeAll()
controller.view.removeFromSuperview()
}
}
}
extension AccountsPreferencesViewController: OAuthAccountAuthorizationOperationDelegate {

View File

@ -31,7 +31,7 @@ private enum AddAccountSections: Int, CaseIterable {
var sectionFooter: String {
switch self {
case .local:
return NSLocalizedString("This account does not sync subscriptions across devices.", comment: "Local Account")
return NSLocalizedString("Local accounts do not sync subscriptions across devices.", comment: "Local Account")
case .icloud:
return NSLocalizedString("Use your iCloud account to sync your subscriptions across your iOS and macOS devices.", comment: "iCloud Account")
case .web:
@ -117,7 +117,9 @@ struct AddAccountsView: View {
.frame(width: 80)
})
}
}.padding(.vertical, 8)
}
.padding(.top, 12)
.padding(.bottom, 4)
}
.pickerStyle(RadioGroupPickerStyle())
.fixedSize(horizontal: false, vertical: true)
@ -130,28 +132,29 @@ 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: .top) {
HStack(alignment: .center) {
account.image()
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 25, height: 25, alignment: .center)
.offset(CGSize(width: 0, height: -2.5))
.padding(.leading, 4)
VStack(alignment: .leading, spacing: 4) {
Text(account.localizedAccountName())
Text(AddAccountSections.local.sectionFooter).foregroundColor(.gray)
.font(.caption)
}
Text(account.localizedAccountName())
}
.tag(account)
})
})
.pickerStyle(RadioGroupPickerStyle())
.offset(x: 7.5, y: 0)
Text(AddAccountSections.local.sectionFooter).foregroundColor(.gray)
.font(.caption)
.padding(.horizontal)
}
}
@ -161,27 +164,28 @@ struct AddAccountsView: View {
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: .top) {
HStack(alignment: .center) {
account.image()
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 25, height: 25, alignment: .center)
.offset(CGSize(width: 0, height: -5))
.padding(.leading, 4)
VStack(alignment: .leading, spacing: 4) {
Text(account.localizedAccountName())
Text(AddAccountSections.icloud.sectionFooter).foregroundColor(.gray)
.font(.caption)
}
Text(account.localizedAccountName())
}
.tag(account)
})
})
.offset(x: 7.5, y: 0)
.disabled(isCloudInUse())
Text(AddAccountSections.icloud.sectionFooter).foregroundColor(.gray)
.font(.caption)
.padding(.horizontal)
}
}
@ -190,6 +194,8 @@ struct AddAccountsView: View {
Text("Web")
.font(.headline)
.padding(.horizontal)
.padding(.top, 8)
Picker(selection: $selectedAccount, label: Text(""), content: {
ForEach(AddAccountSections.web.sectionContent.filter({ isRestricted($0) != true }), id: \.self, content: { account in
@ -200,15 +206,17 @@ struct AddAccountsView: View {
.frame(width: 25, height: 25, alignment: .center)
.padding(.leading, 4)
VStack(alignment: .leading) {
Text(account.localizedAccountName())
}
Text(account.localizedAccountName())
}
.tag(account)
})
})
.offset(x: 7.5, y: 0)
Text(AddAccountSections.web.sectionFooter).foregroundColor(.gray)
.font(.caption)
.padding(.horizontal)
}
}
@ -218,29 +226,25 @@ struct AddAccountsView: View {
.font(.headline)
.padding(.horizontal)
.padding(.top, 8)
Picker(selection: $selectedAccount, label: Text(""), content: {
ForEach(AddAccountSections.selfhosted.sectionContent, id: \.self, content: { account in
HStack(alignment: .top) {
HStack(alignment: .center) {
account.image()
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 25, height: 25, alignment: .center)
.offset(CGSize(width: 0, height: -4))
.padding(.leading, 4)
VStack(alignment: .leading, spacing: 4) {
Text(account.localizedAccountName())
Text("Web and self-hosted accounts sync across all signed-in devices.")
.font(.caption)
.foregroundColor(.gray)
}
Text(account.localizedAccountName())
}.tag(account)
})
})
.offset(x: 7.5, y: 0)
Text(AddAccountSections.selfhosted.sectionFooter).foregroundColor(.gray)
.font(.caption)
.padding(.horizontal)
}
}

View File

@ -0,0 +1,172 @@
//
// EnableExtensionPointView.swift
// NetNewsWire
//
// Created by Maurice Parker on 10/30/20.
// Copyright © 2020 Ranchero Software. All rights reserved.
//
import AppKit
import SwiftUI
import RSCore
struct EnableExtensionPointView: View {
weak var parent: NSHostingController<EnableExtensionPointView>? // required because presentationMode.dismiss() doesn't work
weak var enabler: ExtensionPointPreferencesEnabler?
@State private var extensionPointTypeName = String(describing: Self.feedProviderExtensionPointTypes.first!)
init(enabler: ExtensionPointPreferencesEnabler?) {
self.enabler = enabler
}
var body: some View {
VStack(alignment: .leading, spacing: 8) {
Text("Choose an extension point to add...")
.font(.headline)
.padding()
feedProviderExtensionPoints
sendToCommandExtensionPoints
HStack(spacing: 12) {
Spacer()
if #available(OSX 11.0, *) {
Button(action: {
parent?.dismiss(nil)
}, label: {
Text("Cancel")
.frame(width: 80)
})
.help("Cancel")
.keyboardShortcut(.cancelAction)
} else {
Button(action: {
parent?.dismiss(nil)
}, label: {
Text("Cancel")
.frame(width: 80)
})
.accessibility(label: Text("Add Account"))
}
if #available(OSX 11.0, *) {
Button(action: {
enabler?.enable(typeFromName(extensionPointTypeName))
parent?.dismiss(nil)
}, label: {
Text("Continue")
.frame(width: 80)
})
.help("Add Account")
.keyboardShortcut(.defaultAction)
} else {
Button(action: {
enabler?.enable(typeFromName(extensionPointTypeName))
parent?.dismiss(nil)
}, label: {
Text("Continue")
.frame(width: 80)
})
}
}
.padding(.top, 12)
.padding(.bottom, 4)
}
.pickerStyle(RadioGroupPickerStyle())
.fixedSize(horizontal: false, vertical: true)
.frame(width: 420)
.padding()
}
var feedProviderExtensionPoints: some View {
VStack(alignment: .leading) {
let extensionPointTypeNames = Self.feedProviderExtensionPointTypes.map { String(describing: $0) }
if extensionPointTypeNames.count > 0 {
Text("Feed Provider")
.font(.headline)
.padding(.horizontal)
Picker(selection: $extensionPointTypeName, label: Text(""), content: {
ForEach(extensionPointTypeNames, id: \.self, content: { extensionPointTypeName in
let extensionPointType = typeFromName(extensionPointTypeName)
HStack(alignment: .center) {
Image(nsImage: extensionPointType.image)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 25, height: 25, alignment: .center)
.padding(.leading, 4)
Text(extensionPointType.title)
}
.tag(extensionPointTypeNames)
})
})
.pickerStyle(RadioGroupPickerStyle())
.offset(x: 7.5, y: 0)
Text("An extension point that makes websites appear to provide RSS feeds for their content.")
.foregroundColor(.gray)
.font(.caption)
.padding(.horizontal)
}
}
}
var sendToCommandExtensionPoints: some View {
VStack(alignment: .leading) {
let extensionPointTypeNames = Self.sendToCommandExtensionPointTypes.map { String(describing: $0) }
if extensionPointTypeNames.count > 0 {
Text("Third-Party Integration")
.font(.headline)
.padding(.horizontal)
.padding(.top, 8)
Picker(selection: $extensionPointTypeName, label: Text(""), content: {
ForEach(extensionPointTypeNames, id: \.self, content: { extensionPointTypeName in
let extensionPointType = typeFromName(extensionPointTypeName)
HStack(alignment: .center) {
Image(nsImage: extensionPointType.image)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 25, height: 25, alignment: .center)
.padding(.leading, 4)
Text(extensionPointType.title)
}
.tag(extensionPointTypeNames)
})
})
.pickerStyle(RadioGroupPickerStyle())
.offset(x: 7.5, y: 0)
Text("An extension point that enables a share menu item that passes article data to a third-party application.")
.foregroundColor(.gray)
.font(.caption)
.padding(.horizontal)
}
}
}
static var sendToCommandExtensionPointTypes: [ExtensionPoint.Type] {
return ExtensionPointManager.shared.availableExtensionPointTypes.filter({ $0 is SendToCommand.Type })
}
static var feedProviderExtensionPointTypes: [ExtensionPoint.Type] {
return ExtensionPointManager.shared.availableExtensionPointTypes.filter({ !($0 is SendToCommand.Type) })
}
func typeFromName(_ name: String) -> ExtensionPoint.Type {
for type in ExtensionPointManager.shared.possibleExtensionPointTypes {
if name == String(describing: type) {
return type
}
}
fatalError()
}
}

View File

@ -1,121 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="17505" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="17505"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="ExtensionPointAddViewController" customModule="NetNewsWire" customModuleProvider="target">
<connections>
<outlet property="tableView" destination="lyM-Zu-Let" id="JDz-05-OOg"/>
<outlet property="view" destination="c22-O7-iKe" id="Vfr-rK-EHC"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<customView id="c22-O7-iKe">
<rect key="frame" x="0.0" y="0.0" width="480" height="272"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews>
<scrollView autohidesScrollers="YES" horizontalLineScroll="42" horizontalPageScroll="10" verticalLineScroll="42" verticalPageScroll="10" usesPredominantAxisScrolling="NO" id="y2z-6c-TH0">
<rect key="frame" x="0.0" y="0.0" width="480" height="272"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<clipView key="contentView" id="qCn-Bf-ICO">
<rect key="frame" x="1" y="1" width="478" height="270"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<tableView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" columnReordering="NO" columnSelection="YES" columnResizing="NO" multipleSelection="NO" autosaveColumns="NO" rowHeight="40" rowSizeStyle="automatic" viewBased="YES" id="lyM-Zu-Let">
<rect key="frame" x="0.0" y="0.0" width="478" height="270"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<size key="intercellSpacing" width="3" height="2"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
<tableViewGridLines key="gridStyleMask" horizontal="YES"/>
<color key="gridColor" name="gridColor" catalog="System" colorSpace="catalog"/>
<tableColumns>
<tableColumn width="466" minWidth="40" maxWidth="1000" id="SlU-lH-CzT">
<tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border">
<color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="headerColor" catalog="System" colorSpace="catalog"/>
</tableHeaderCell>
<textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" title="Text Cell" id="Nhn-I6-76l">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
<prototypeCellViews>
<tableCellView identifier="Cell" id="EGi-CQ-lPc" customClass="ExtensionPointAddTableCellView" customModule="NetNewsWire" customModuleProvider="target">
<rect key="frame" x="1" y="1" width="475" height="40"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<stackView distribution="fill" orientation="horizontal" alignment="centerY" spacing="17" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="iCD-Yx-4V5">
<rect key="frame" x="20" y="8" width="174" height="24"/>
<subviews>
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="KmN-Zk-TBU">
<rect key="frame" x="0.0" y="0.0" width="24" height="24"/>
<constraints>
<constraint firstAttribute="height" constant="24" id="dbz-aC-h0q"/>
<constraint firstAttribute="width" constant="24" id="jN0-Et-ysS"/>
</constraints>
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" id="oGL-yl-27S"/>
</imageView>
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="uyu-5W-IaW">
<rect key="frame" x="39" y="0.0" width="137" height="24"/>
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Table View Cell" id="iOW-VJ-bkx">
<font key="font" metaFont="system" size="20"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<visibilityPriorities>
<integer value="1000"/>
<integer value="1000"/>
</visibilityPriorities>
<customSpacing>
<real value="3.4028234663852886e+38"/>
<real value="3.4028234663852886e+38"/>
</customSpacing>
</stackView>
<button id="y48-E2-CL3">
<rect key="frame" x="0.0" y="0.0" width="475" height="40"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="bevel" bezelStyle="rounded" alignment="center" imageScaling="proportionallyDown" inset="2" id="yf7-Ye-Pcd">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="pressed:" target="EGi-CQ-lPc" id="hgF-B0-Dyh"/>
</connections>
</button>
</subviews>
<constraints>
<constraint firstItem="iCD-Yx-4V5" firstAttribute="centerY" secondItem="EGi-CQ-lPc" secondAttribute="centerY" id="IS1-7W-BWY"/>
<constraint firstItem="iCD-Yx-4V5" firstAttribute="leading" secondItem="EGi-CQ-lPc" secondAttribute="leading" constant="20" id="IsY-WH-f93"/>
</constraints>
<connections>
<outlet property="imageView" destination="KmN-Zk-TBU" id="Tfy-Eb-Isb"/>
<outlet property="titleLabel" destination="uyu-5W-IaW" id="QAe-Gk-Eeo"/>
</connections>
</tableCellView>
</prototypeCellViews>
</tableColumn>
</tableColumns>
</tableView>
</subviews>
</clipView>
<scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="YES" id="qOf-Dj-ubR">
<rect key="frame" x="1" y="255" width="478" height="16"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
<scroller key="verticalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="NO" id="XFQ-Xy-wny">
<rect key="frame" x="224" y="17" width="15" height="102"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
</scrollView>
</subviews>
<point key="canvasLocation" x="139" y="154"/>
</customView>
</objects>
</document>

View File

@ -1,28 +0,0 @@
//
// ExtensionPointAddTableCellView.swift
// NetNewsWire
//
// Created by Maurice Parker on 4/6/20.
// Copyright © 2020 Ranchero Software. All rights reserved.
//
import AppKit
protocol ExtensionPointTableCellViewDelegate: class {
func addExtensionPoint(_ extensionPointType: ExtensionPoint.Type)
}
class ExtensionPointAddTableCellView: NSTableCellView {
weak var delegate: ExtensionPointTableCellViewDelegate?
var extensionPointType: ExtensionPoint.Type?
@IBOutlet weak var templateImageView: NSImageView?
@IBOutlet weak var titleLabel: NSTextField?
@IBAction func pressed(_ sender: Any) {
guard let extensionPointType = extensionPointType else { return }
delegate?.addExtensionPoint(extensionPointType)
}
}

View File

@ -1,76 +0,0 @@
//
// ExtensionPointAddViewController.swift
// NetNewsWire
//
// Created by Maurice Parker on 4/6/20.
// Copyright © 2020 Ranchero Software. All rights reserved.
//
import AppKit
class ExtensionPointAddViewController: NSViewController {
@IBOutlet weak var tableView: NSTableView!
private var availableExtensionPointTypes = [ExtensionPoint.Type]()
private var extensionPointAddWindowController: NSWindowController?
init() {
super.init(nibName: "ExtensionPointAdd", bundle: nil)
}
public required init?(coder: NSCoder) {
super.init(coder: coder)
}
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
tableView.delegate = self
availableExtensionPointTypes = ExtensionPointManager.shared.availableExtensionPointTypes.sorted(by: { $0.title < $1.title })
}
}
// MARK: - NSTableViewDataSource
extension ExtensionPointAddViewController: NSTableViewDataSource {
func numberOfRows(in tableView: NSTableView) -> Int {
return availableExtensionPointTypes.count
}
func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any? {
return nil
}
}
// MARK: - NSTableViewDelegate
extension ExtensionPointAddViewController: NSTableViewDelegate {
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
if let cell = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "Cell"), owner: nil) as? ExtensionPointAddTableCellView {
let extensionPointType = availableExtensionPointTypes[row]
cell.extensionPointType = extensionPointType
cell.delegate = self
cell.titleLabel?.stringValue = extensionPointType.title
cell.imageView?.image = extensionPointType.templateImage
return cell
}
return nil
}
}
extension ExtensionPointAddViewController: ExtensionPointTableCellViewDelegate {
func addExtensionPoint(_ extensionPointType: ExtensionPoint.Type) {
let windowController = ExtensionPointEnableWindowController()
windowController.extensionPointType = extensionPointType
windowController.runSheetOnWindow(self.view.window!)
extensionPointAddWindowController = windowController
}
}

View File

@ -29,7 +29,7 @@ class ExtensionPointDetailViewController: NSViewController {
override func viewDidLoad() {
super.viewDidLoad()
guard let extensionPoint = extensionPoint else { return }
imageView.image = extensionPoint.templateImage
imageView.image = extensionPoint.image
titleLabel.stringValue = extensionPoint.title
descriptionLabel.attributedStringValue = extensionPoint.description
}

View File

@ -1,116 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="16096" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="16096"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="ExtensionPointEnableWindowController" customModule="NetNewsWire" customModuleProvider="target">
<connections>
<outlet property="descriptionLabel" destination="thC-ep-vXS" id="o9I-vp-z54"/>
<outlet property="enableButton" destination="sGb-z5-IdF" id="yNw-Nn-4Kq"/>
<outlet property="imageView" destination="LSA-B8-aGZ" id="AN5-t1-d52"/>
<outlet property="titleLabel" destination="iAC-tU-rvZ" id="vMx-2H-b44"/>
<outlet property="window" destination="HNe-Jr-kev" id="C8n-l1-WhI"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<window title="Window" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="HNe-Jr-kev">
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="196" y="240" width="407" height="156"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1417"/>
<view key="contentView" wantsLayer="YES" id="qAd-AQ-5ue">
<rect key="frame" x="0.0" y="0.0" width="407" height="156"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<stackView distribution="fill" orientation="horizontal" alignment="bottom" spacing="19" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="nLd-4a-dQg">
<rect key="frame" x="109" y="89" width="189" height="51"/>
<subviews>
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="LSA-B8-aGZ">
<rect key="frame" x="0.0" y="0.0" width="36" height="36"/>
<constraints>
<constraint firstAttribute="width" constant="36" id="SuU-du-YHk"/>
<constraint firstAttribute="height" constant="36" id="qxc-dc-d8U"/>
</constraints>
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyUpOrDown" image="NSAdvanced" id="5qe-pZ-t40"/>
</imageView>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="iAC-tU-rvZ">
<rect key="frame" x="53" y="13" width="138" height="38"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="Extension" id="kuv-Xu-aIk">
<font key="font" metaFont="system" size="32"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<visibilityPriorities>
<integer value="1000"/>
<integer value="1000"/>
</visibilityPriorities>
<customSpacing>
<real value="3.4028234663852886e+38"/>
<real value="3.4028234663852886e+38"/>
</customSpacing>
</stackView>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="sGb-z5-IdF">
<rect key="frame" x="312" y="13" width="81" height="32"/>
<buttonCell key="cell" type="push" title="Enable" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="Oh8-q3-Aup">
<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="enable:" target="-2" id="BN5-u0-DNe"/>
</connections>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="aKy-4s-WDM">
<rect key="frame" x="231" y="13" width="82" height="32"/>
<buttonCell key="cell" type="push" title="Cancel" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="2nM-LA-6fh">
<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="WK9-uJ-mIw"/>
</connections>
</button>
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" textCompletion="NO" translatesAutoresizingMaskIntoConstraints="NO" id="thC-ep-vXS">
<rect key="frame" x="52" y="57" width="304" height="16"/>
<constraints>
<constraint firstAttribute="width" constant="300" id="igx-s6-xe9"/>
</constraints>
<textFieldCell key="cell" selectable="YES" allowsUndo="NO" alignment="left" allowsEditingTextAttributes="YES" id="aUU-dO-RNt">
<font key="font" metaFont="system"/>
<color key="textColor" name="secondaryLabelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<constraints>
<constraint firstItem="iAC-tU-rvZ" firstAttribute="top" secondItem="qAd-AQ-5ue" secondAttribute="top" constant="16" id="Cxn-GQ-jzh"/>
<constraint firstAttribute="bottom" secondItem="sGb-z5-IdF" secondAttribute="bottom" constant="20" id="Moe-ce-JeY"/>
<constraint firstAttribute="trailing" secondItem="sGb-z5-IdF" secondAttribute="trailing" constant="20" id="OdS-3p-qyB"/>
<constraint firstItem="sGb-z5-IdF" firstAttribute="leading" secondItem="aKy-4s-WDM" secondAttribute="trailing" constant="11" id="QPh-zm-9uL"/>
<constraint firstItem="thC-ep-vXS" firstAttribute="centerX" secondItem="qAd-AQ-5ue" secondAttribute="centerX" id="fC4-fE-SyO"/>
<constraint firstItem="aKy-4s-WDM" firstAttribute="centerY" secondItem="sGb-z5-IdF" secondAttribute="centerY" id="naD-Tq-iwx"/>
<constraint firstItem="thC-ep-vXS" firstAttribute="top" secondItem="nLd-4a-dQg" secondAttribute="bottom" constant="16" id="qRM-G0-del"/>
<constraint firstItem="aKy-4s-WDM" firstAttribute="top" secondItem="thC-ep-vXS" secondAttribute="bottom" constant="16" id="vrt-3v-j4f"/>
<constraint firstItem="nLd-4a-dQg" firstAttribute="centerX" secondItem="qAd-AQ-5ue" secondAttribute="centerX" id="xXl-e5-lnN"/>
</constraints>
</view>
<connections>
<outlet property="delegate" destination="-2" id="fo9-G5-zJh"/>
</connections>
<point key="canvasLocation" x="103.5" y="89.5"/>
</window>
</objects>
<resources>
<image name="NSAdvanced" width="32" height="32"/>
</resources>
</document>

View File

@ -33,7 +33,7 @@ class ExtensionPointEnableWindowController: NSWindowController {
super.windowDidLoad()
guard let extensionPointType = extensionPointType else { return }
imageView.image = extensionPointType.templateImage
imageView.image = extensionPointType.image
titleLabel.stringValue = extensionPointType.title
descriptionLabel.attributedStringValue = extensionPointType.description
}

View File

@ -7,6 +7,14 @@
//
import AppKit
import SwiftUI
import AuthenticationServices
import OAuthSwift
import Secrets
protocol ExtensionPointPreferencesEnabler: class {
func enable(_ extensionPointType: ExtensionPoint.Type)
}
final class ExtensionPointPreferencesViewController: NSViewController {
@ -15,6 +23,8 @@ final class ExtensionPointPreferencesViewController: NSViewController {
@IBOutlet weak var deleteButton: NSButton!
private var activeExtensionPoints = [ExtensionPoint]()
private var callbackURL: URL? = nil
private var oauth: OAuthSwift?
override func viewDidLoad() {
super.viewDidLoad()
@ -30,11 +40,17 @@ final class ExtensionPointPreferencesViewController: NSViewController {
tableView.frame = rTable
showDefaultView()
// Set initial row selection
if activeExtensionPoints.count > 0 {
tableView.selectRow(0)
}
}
@IBAction func enableExtensionPoints(_ sender: Any) {
tableView.selectRowIndexes([], byExtendingSelection: false)
showController(ExtensionPointAddViewController())
let controller = NSHostingController(rootView: EnableExtensionPointView(enabler: self))
controller.rootView.parent = controller
presentAsSheet(controller)
}
@IBAction func disableExtensionPoint(_ sender: Any) {
@ -44,8 +60,7 @@ final class ExtensionPointPreferencesViewController: NSViewController {
let extensionPoint = activeExtensionPoints[tableView.selectedRow]
ExtensionPointManager.shared.deactivateExtensionPoint(extensionPoint.extensionPointID)
showController(ExtensionPointAddViewController())
hideController()
}
}
@ -72,7 +87,7 @@ extension ExtensionPointPreferencesViewController: NSTableViewDelegate {
if let cell = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "Cell"), owner: nil) as? NSTableCellView {
let extensionPoint = activeExtensionPoints[row]
cell.textField?.stringValue = extensionPoint.title
cell.imageView?.image = extensionPoint.templateImage
cell.imageView?.image = extensionPoint.image
return cell
}
return nil
@ -83,6 +98,7 @@ extension ExtensionPointPreferencesViewController: NSTableViewDelegate {
let selectedRow = tableView.selectedRow
if tableView.selectedRow == -1 {
deleteButton.isEnabled = false
hideController()
return
} else {
deleteButton.isEnabled = true
@ -96,6 +112,62 @@ extension ExtensionPointPreferencesViewController: NSTableViewDelegate {
}
// MARK: ExtensionPointPreferencesViewController
extension ExtensionPointPreferencesViewController: ExtensionPointPreferencesEnabler {
func enable(_ extensionPointType: ExtensionPoint.Type) {
if let oauth1 = extensionPointType as? OAuth1SwiftProvider.Type {
enableOauth1(oauth1, extensionPointType: extensionPointType)
} else if let oauth2 = extensionPointType as? OAuth2SwiftProvider.Type {
enableOauth2(oauth2, extensionPointType: extensionPointType)
} else {
ExtensionPointManager.shared.activateExtensionPoint(extensionPointType) { result in
if case .failure(let error) = result {
self.presentError(error)
}
}
}
}
}
extension ExtensionPointPreferencesViewController: OAuthSwiftURLHandlerType {
public func handle(_ url: URL) {
let session = ASWebAuthenticationSession(url: url, callbackURLScheme: callbackURL!.scheme, completionHandler: { (url, error) in
if let callbackedURL = url {
OAuth1Swift.handle(url: callbackedURL)
}
guard let error = error else { return }
self.oauth?.cancel()
self.oauth = nil
if case ASWebAuthenticationSessionError.canceledLogin = error {
print("Login cancelled.")
} else {
NSApplication.shared.presentError(error)
}
})
session.presentationContextProvider = self
if !session.start() {
print("Session failed to start!!!")
}
}
}
extension ExtensionPointPreferencesViewController: ASWebAuthenticationPresentationContextProviding {
public func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor {
return view.window!
}
}
// MARK: - Private
private extension ExtensionPointPreferencesViewController {
@ -107,20 +179,77 @@ private extension ExtensionPointPreferencesViewController {
func showDefaultView() {
activeExtensionPoints = Array(ExtensionPointManager.shared.activeExtensionPoints.values).sorted(by: { $0.title < $1.title })
tableView.reloadData()
showController(ExtensionPointAddViewController())
}
func showController(_ controller: NSViewController) {
if let controller = children.first {
children.removeAll()
controller.view.removeFromSuperview()
}
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()
}
}
func enableOauth1(_ provider: OAuth1SwiftProvider.Type, extensionPointType: ExtensionPoint.Type) {
callbackURL = provider.callbackURL
let oauth1 = provider.oauth1Swift
self.oauth = oauth1
oauth1.authorizeURLHandler = self
oauth1.authorize(withCallbackURL: callbackURL!) { [weak self] result in
guard let self = self else { return }
switch result {
case .success(let tokenSuccess):
ExtensionPointManager.shared.activateExtensionPoint(extensionPointType, tokenSuccess: tokenSuccess) { result in
if case .failure(let error) = result {
self.presentError(error)
}
}
case .failure(let oauthSwiftError):
self.presentError(oauthSwiftError)
}
self.oauth?.cancel()
self.oauth = nil
}
}
func enableOauth2(_ provider: OAuth2SwiftProvider.Type, extensionPointType: ExtensionPoint.Type) {
callbackURL = provider.callbackURL
let oauth2 = provider.oauth2Swift
self.oauth = oauth2
oauth2.authorizeURLHandler = self
let oauth2Vars = provider.oauth2Vars
oauth2.authorize(withCallbackURL: callbackURL!, scope: oauth2Vars.scope, state: oauth2Vars.state, parameters: oauth2Vars.params) { [weak self] result in
guard let self = self else { return }
switch result {
case .success(let tokenSuccess):
ExtensionPointManager.shared.activateExtensionPoint(extensionPointType, tokenSuccess: tokenSuccess) { result in
if case .failure(let error) = result {
self.presentError(error)
}
}
case .failure(let oauthSwiftError):
self.presentError(oauthSwiftError)
}
self.oauth?.cancel()
self.oauth = nil
}
}

View File

@ -1,7 +1,7 @@
{
"images" : [
{
"filename" : "freshrss-any.pdf",
"filename" : "FreshRSS.pdf",
"idiom" : "universal"
}
],

View File

@ -1,8 +1,18 @@
{
"images" : [
{
"filename" : "reddit_logo.pdf",
"idiom" : "universal"
"filename" : "reddit24x24.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "reddit48x48.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

@ -1,8 +1,18 @@
{
"images" : [
{
"filename" : "twitter.pdf",
"idiom" : "universal"
"filename" : "twitter24x24.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "twitter48x48.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@ -11,11 +11,6 @@ import Combine
import RSCore
import Articles
extension Notification.Name {
static let appleColorPreferencesChangedNotification = Notification.Name("AppleColorPreferencesChangedNotification")
static let appleInterfaceThemeChangedNotification = Notification.Name("AppleInterfaceThemeChangedNotification")
}
protocol WebViewControllerDelegate: class {
func webViewController(_: WebViewController, articleExtractorButtonStateDidUpdate: ArticleExtractorButtonState)
}
@ -67,8 +62,6 @@ class WebViewController: NSViewController {
NotificationCenter.default.addObserver(self, selector: #selector(webFeedIconDidBecomeAvailable(_:)), name: .WebFeedIconDidBecomeAvailable, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(avatarDidBecomeAvailable(_:)), name: .AvatarDidBecomeAvailable, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(faviconDidBecomeAvailable(_:)), name: .FaviconDidBecomeAvailable, object: nil)
DistributedNotificationCenter.default().addObserver(self, selector: #selector(appleColorPreferencesChanged(_:)), name: .appleColorPreferencesChangedNotification, object: nil)
DistributedNotificationCenter.default().addObserver(self, selector: #selector(appleInterfaceThemeChanged(_:)), name: .appleInterfaceThemeChangedNotification, object: nil)
statusBarView = WebStatusBarView()
statusBarView.translatesAutoresizingMaskIntoConstraints = false
@ -100,14 +93,6 @@ class WebViewController: NSViewController {
reloadArticleImage()
}
@objc func appleColorPreferencesChanged(_ note: Notification) {
loadWebView()
}
@objc func appleInterfaceThemeChanged(_ note: Notification) {
loadWebView()
}
// MARK: API
func focus() {

View File

@ -105,12 +105,6 @@
510C418424E5D1B4008226FD /* ExtensionFeedAddRequestFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B5C8BF23F3866C00032075 /* ExtensionFeedAddRequestFile.swift */; };
510C418524E5D1B4008226FD /* ExtensionContainers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B5C87623F22B8200032075 /* ExtensionContainers.swift */; };
510C418624E5D1B4008226FD /* ExtensionFeedAddRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B5C87A23F2317700032075 /* ExtensionFeedAddRequest.swift */; };
510C43ED243C0973009F70C3 /* ExtensionPointAdd.xib in Resources */ = {isa = PBXBuildFile; fileRef = 510C43EC243C0973009F70C3 /* ExtensionPointAdd.xib */; };
510C43EE243C0973009F70C3 /* ExtensionPointAdd.xib in Resources */ = {isa = PBXBuildFile; fileRef = 510C43EC243C0973009F70C3 /* ExtensionPointAdd.xib */; };
510C43F0243C0A80009F70C3 /* ExtensionPointAddViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510C43EF243C0A80009F70C3 /* ExtensionPointAddViewController.swift */; };
510C43F1243C0A80009F70C3 /* ExtensionPointAddViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510C43EF243C0A80009F70C3 /* ExtensionPointAddViewController.swift */; };
510C43F3243C11FE009F70C3 /* ExtensionPointAddTableCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510C43F2243C11FE009F70C3 /* ExtensionPointAddTableCellView.swift */; };
510C43F4243C11FE009F70C3 /* ExtensionPointAddTableCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510C43F2243C11FE009F70C3 /* ExtensionPointAddTableCellView.swift */; };
510C43F7243D035C009F70C3 /* ExtensionPoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510C43F6243D035C009F70C3 /* ExtensionPoint.swift */; };
510C43F8243D035C009F70C3 /* ExtensionPoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510C43F6243D035C009F70C3 /* ExtensionPoint.swift */; };
51102165233A7D6C0007A5F7 /* ArticleExtractorButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51102164233A7D6C0007A5F7 /* ArticleExtractorButton.swift */; };
@ -222,8 +216,6 @@
515A5108243D0CCD0089E588 /* TwitterFeedProvider-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515A5106243D0CCD0089E588 /* TwitterFeedProvider-Extensions.swift */; };
515A5148243E64BA0089E588 /* ExtensionPointEnableWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515A5147243E64BA0089E588 /* ExtensionPointEnableWindowController.swift */; };
515A5149243E64BA0089E588 /* ExtensionPointEnableWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515A5147243E64BA0089E588 /* ExtensionPointEnableWindowController.swift */; };
515A5168243E66910089E588 /* ExtensionPointEnable.xib in Resources */ = {isa = PBXBuildFile; fileRef = 515A5167243E66910089E588 /* ExtensionPointEnable.xib */; };
515A5169243E66910089E588 /* ExtensionPointEnable.xib in Resources */ = {isa = PBXBuildFile; fileRef = 515A5167243E66910089E588 /* ExtensionPointEnable.xib */; };
515A516E243E7F950089E588 /* ExtensionPointDetail.xib in Resources */ = {isa = PBXBuildFile; fileRef = 515A516D243E7F950089E588 /* ExtensionPointDetail.xib */; };
515A516F243E7F950089E588 /* ExtensionPointDetail.xib in Resources */ = {isa = PBXBuildFile; fileRef = 515A516D243E7F950089E588 /* ExtensionPointDetail.xib */; };
515A5171243E802B0089E588 /* ExtensionPointDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515A5170243E802B0089E588 /* ExtensionPointDetailViewController.swift */; };
@ -300,6 +292,8 @@
5183CCE5226F4DFA0010922C /* RefreshInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCE4226F4DFA0010922C /* RefreshInterval.swift */; };
5183CCE6226F4E110010922C /* RefreshInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCE4226F4DFA0010922C /* RefreshInterval.swift */; };
5183CCE8226F68D90010922C /* AccountRefreshTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCE7226F68D90010922C /* AccountRefreshTimer.swift */; };
5183CFAF254C78C8006B83A5 /* EnableExtensionPointView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CFAE254C78C8006B83A5 /* EnableExtensionPointView.swift */; };
5183CFB0254C78C8006B83A5 /* EnableExtensionPointView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CFAE254C78C8006B83A5 /* EnableExtensionPointView.swift */; };
518651B223555EB20078E021 /* NNW3Document.swift in Sources */ = {isa = PBXBuildFile; fileRef = 518651AB23555EB20078E021 /* NNW3Document.swift */; };
518651DA235621840078E021 /* ImageTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 518651D9235621840078E021 /* ImageTransition.swift */; };
51868BF1254386630011A17B /* SidebarDeleteItemsAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51868BF0254386630011A17B /* SidebarDeleteItemsAlert.swift */; };
@ -662,9 +656,6 @@
51EF0F7A22771B890050506E /* ColorHash.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51EF0F78227716380050506E /* ColorHash.swift */; };
51EF0F7E2277A57D0050506E /* MasterTimelineAccessibilityCellLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51EF0F7D2277A57D0050506E /* MasterTimelineAccessibilityCellLayout.swift */; };
51EF0F802277A8330050506E /* MasterTimelineCellLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51EF0F7F2277A8330050506E /* MasterTimelineCellLayout.swift */; };
51EF0F8E2279C9260050506E /* AccountsAdd.xib in Resources */ = {isa = PBXBuildFile; fileRef = 51EF0F8D2279C9260050506E /* AccountsAdd.xib */; };
51EF0F902279C9500050506E /* AccountsAddViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51EF0F8F2279C9500050506E /* AccountsAddViewController.swift */; };
51EF0F922279CA620050506E /* AccountsAddTableCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51EF0F912279CA620050506E /* AccountsAddTableCellView.swift */; };
51EFDA1A24E6159C0085C3D6 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 849C64671ED37A5D003D8FC0 /* Assets.xcassets */; };
51EFDA1B24E6D16A0085C3D6 /* SafariExt.js in Resources */ = {isa = PBXBuildFile; fileRef = 515D4FCB2325815A00EE1167 /* SafariExt.js */; };
51EFDA1D24E6E27E0085C3D6 /* icon.icns in Resources */ = {isa = PBXBuildFile; fileRef = 51EFDA1C24E6E27E0085C3D6 /* icon.icns */; };
@ -737,7 +728,6 @@
65ED3FCF235DEF6C0081F399 /* TimelineContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8405DD9822153B6B008CE1BF /* TimelineContainerView.swift */; };
65ED3FD0235DEF6C0081F399 /* Author+Scriptability.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5A2678B20130ECF00A8D3C0 /* Author+Scriptability.swift */; };
65ED3FD1235DEF6C0081F399 /* PseudoFeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F2D5351FC22FCB00998D64 /* PseudoFeed.swift */; };
65ED3FD2235DEF6C0081F399 /* AccountsAddViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51EF0F8F2279C9500050506E /* AccountsAddViewController.swift */; };
65ED3FD3235DEF6C0081F399 /* NSScriptCommand+NetNewsWire.swift in Sources */ = {isa = PBXBuildFile; fileRef = D57BE6DF204CD35F00D11AAC /* NSScriptCommand+NetNewsWire.swift */; };
65ED3FD4235DEF6C0081F399 /* Article+Scriptability.swift in Sources */ = {isa = PBXBuildFile; fileRef = D553737C20186C1F006D8857 /* Article+Scriptability.swift */; };
65ED3FD5235DEF6C0081F399 /* SmartFeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845EE7C01FC2488C00854A1F /* SmartFeed.swift */; };
@ -798,7 +788,6 @@
65ED400D235DEF6C0081F399 /* SmartFeedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84DEE56422C32CA4005FC42C /* SmartFeedDelegate.swift */; };
65ED400E235DEF6C0081F399 /* ImageDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845213221FCA5B10003B6E93 /* ImageDownloader.swift */; };
65ED400F235DEF6C0081F399 /* LegacyArticleExtractorButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FA73B62332D5F70090D516 /* LegacyArticleExtractorButton.swift */; };
65ED4010235DEF6C0081F399 /* AccountsAddTableCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51EF0F912279CA620050506E /* AccountsAddTableCellView.swift */; };
65ED4011235DEF6C0081F399 /* AddFolderWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97421ED9EAA9007D329B /* AddFolderWindowController.swift */; };
65ED4012235DEF6C0081F399 /* TimelineContainerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8405DDA422168C62008CE1BF /* TimelineContainerViewController.swift */; };
65ED4013235DEF6C0081F399 /* MainWIndowKeyboardHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844B5B661FEA18E300C7C76A /* MainWIndowKeyboardHandler.swift */; };
@ -853,7 +842,6 @@
65ED4051235DEF6C0081F399 /* TimelineKeyboardShortcuts.plist in Resources */ = {isa = PBXBuildFile; fileRef = 845479871FEB77C000AD8B59 /* TimelineKeyboardShortcuts.plist */; };
65ED4052235DEF6C0081F399 /* template.html in Resources */ = {isa = PBXBuildFile; fileRef = 848362FE2262A30E00DA1D35 /* template.html */; };
65ED4054235DEF6C0081F399 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 848363062262A3DD00DA1D35 /* Main.storyboard */; };
65ED4055235DEF6C0081F399 /* AccountsAdd.xib in Resources */ = {isa = PBXBuildFile; fileRef = 51EF0F8D2279C9260050506E /* AccountsAdd.xib */; };
65ED4056235DEF6C0081F399 /* NetNewsWire.sdef in Resources */ = {isa = PBXBuildFile; fileRef = 84C9FC8A22629E8F00D921D6 /* NetNewsWire.sdef */; };
65ED4057235DEF6C0081F399 /* AccountsDetail.xib in Resources */ = {isa = PBXBuildFile; fileRef = 84C9FC7422629E1200D921D6 /* AccountsDetail.xib */; };
65ED4058235DEF6C0081F399 /* main.js in Resources */ = {isa = PBXBuildFile; fileRef = 517630032336215100E15FFF /* main.js */; };
@ -1501,9 +1489,6 @@
510C416524E5CDE3008226FD /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
510C416624E5CDE3008226FD /* ShareExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ShareExtension.entitlements; sourceTree = "<group>"; };
510C418724E5D2E3008226FD /* NetNewsWire_shareextension_target.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = NetNewsWire_shareextension_target.xcconfig; sourceTree = "<group>"; };
510C43EC243C0973009F70C3 /* ExtensionPointAdd.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ExtensionPointAdd.xib; sourceTree = "<group>"; };
510C43EF243C0A80009F70C3 /* ExtensionPointAddViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionPointAddViewController.swift; sourceTree = "<group>"; };
510C43F2243C11FE009F70C3 /* ExtensionPointAddTableCellView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionPointAddTableCellView.swift; sourceTree = "<group>"; };
510C43F6243D035C009F70C3 /* ExtensionPoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionPoint.swift; sourceTree = "<group>"; };
51102164233A7D6C0007A5F7 /* ArticleExtractorButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleExtractorButton.swift; sourceTree = "<group>"; };
51107745243BEE2500D97C8C /* ExtensionPointPreferencesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionPointPreferencesViewController.swift; sourceTree = "<group>"; };
@ -1566,7 +1551,6 @@
515A50E5243D07A90089E588 /* ExtensionPointManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionPointManager.swift; sourceTree = "<group>"; };
515A5106243D0CCD0089E588 /* TwitterFeedProvider-Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TwitterFeedProvider-Extensions.swift"; sourceTree = "<group>"; };
515A5147243E64BA0089E588 /* ExtensionPointEnableWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionPointEnableWindowController.swift; sourceTree = "<group>"; };
515A5167243E66910089E588 /* ExtensionPointEnable.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ExtensionPointEnable.xib; sourceTree = "<group>"; };
515A516D243E7F950089E588 /* ExtensionPointDetail.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ExtensionPointDetail.xib; sourceTree = "<group>"; };
515A5170243E802B0089E588 /* ExtensionPointDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionPointDetailViewController.swift; sourceTree = "<group>"; };
515A5176243E90200089E588 /* ExtensionPointIdentifer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionPointIdentifer.swift; sourceTree = "<group>"; };
@ -1626,6 +1610,7 @@
5183CCD9226E31A50010922C /* NonIntrinsicImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonIntrinsicImageView.swift; sourceTree = "<group>"; };
5183CCE4226F4DFA0010922C /* RefreshInterval.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RefreshInterval.swift; sourceTree = "<group>"; };
5183CCE7226F68D90010922C /* AccountRefreshTimer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountRefreshTimer.swift; sourceTree = "<group>"; };
5183CFAE254C78C8006B83A5 /* EnableExtensionPointView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnableExtensionPointView.swift; sourceTree = "<group>"; };
518651AB23555EB20078E021 /* NNW3Document.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NNW3Document.swift; sourceTree = "<group>"; };
518651D9235621840078E021 /* ImageTransition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageTransition.swift; sourceTree = "<group>"; };
51868BF0254386630011A17B /* SidebarDeleteItemsAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarDeleteItemsAlert.swift; sourceTree = "<group>"; };
@ -1774,9 +1759,6 @@
51EF0F78227716380050506E /* ColorHash.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorHash.swift; sourceTree = "<group>"; };
51EF0F7D2277A57D0050506E /* MasterTimelineAccessibilityCellLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MasterTimelineAccessibilityCellLayout.swift; sourceTree = "<group>"; };
51EF0F7F2277A8330050506E /* MasterTimelineCellLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MasterTimelineCellLayout.swift; sourceTree = "<group>"; };
51EF0F8D2279C9260050506E /* AccountsAdd.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AccountsAdd.xib; sourceTree = "<group>"; };
51EF0F8F2279C9500050506E /* AccountsAddViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsAddViewController.swift; sourceTree = "<group>"; };
51EF0F912279CA620050506E /* AccountsAddTableCellView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsAddTableCellView.swift; sourceTree = "<group>"; };
51EFDA1C24E6E27E0085C3D6 /* icon.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = icon.icns; sourceTree = "<group>"; };
51F805D32428499E0022C792 /* NetNewsWire-dev.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "NetNewsWire-dev.entitlements"; sourceTree = "<group>"; };
51F805ED24284C1C0022C792 /* NetNewsWire-dev.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "NetNewsWire-dev.entitlements"; sourceTree = "<group>"; };
@ -2321,14 +2303,11 @@
51107744243BEDD300D97C8C /* ExtensionPoints */ = {
isa = PBXGroup;
children = (
510C43EC243C0973009F70C3 /* ExtensionPointAdd.xib */,
510C43F2243C11FE009F70C3 /* ExtensionPointAddTableCellView.swift */,
510C43EF243C0A80009F70C3 /* ExtensionPointAddViewController.swift */,
515A5167243E66910089E588 /* ExtensionPointEnable.xib */,
515A5147243E64BA0089E588 /* ExtensionPointEnableWindowController.swift */,
51107745243BEE2500D97C8C /* ExtensionPointPreferencesViewController.swift */,
5183CFAE254C78C8006B83A5 /* EnableExtensionPointView.swift */,
515A516D243E7F950089E588 /* ExtensionPointDetail.xib */,
515A5170243E802B0089E588 /* ExtensionPointDetailViewController.swift */,
515A5147243E64BA0089E588 /* ExtensionPointEnableWindowController.swift */,
51107745243BEE2500D97C8C /* ExtensionPointPreferencesViewController.swift */,
);
path = ExtensionPoints;
sourceTree = "<group>";
@ -3350,9 +3329,6 @@
children = (
178A9F9C2549449F00AB7E9D /* AddAccountsView.swift */,
84C9FC7222629E1200D921D6 /* AccountsPreferencesViewController.swift */,
51EF0F8D2279C9260050506E /* AccountsAdd.xib */,
51EF0F8F2279C9500050506E /* AccountsAddViewController.swift */,
51EF0F912279CA620050506E /* AccountsAddTableCellView.swift */,
84C9FC7422629E1200D921D6 /* AccountsDetail.xib */,
5144EA2E2279FAB600D19003 /* AccountsDetailViewController.swift */,
5144EA50227B8E4500D19003 /* AccountsFeedbin.xib */,
@ -4192,13 +4168,10 @@
65ED4051235DEF6C0081F399 /* TimelineKeyboardShortcuts.plist in Resources */,
65ED4052235DEF6C0081F399 /* template.html in Resources */,
65ED4054235DEF6C0081F399 /* Main.storyboard in Resources */,
510C43EE243C0973009F70C3 /* ExtensionPointAdd.xib in Resources */,
65ED4055235DEF6C0081F399 /* AccountsAdd.xib in Resources */,
65ED4056235DEF6C0081F399 /* NetNewsWire.sdef in Resources */,
65ED4057235DEF6C0081F399 /* AccountsDetail.xib in Resources */,
65ED4058235DEF6C0081F399 /* main.js in Resources */,
65ED40A1235DEFF00081F399 /* container-migration.plist in Resources */,
515A5169243E66910089E588 /* ExtensionPointEnable.xib in Resources */,
65ED4059235DEF6C0081F399 /* AccountsAddLocal.xib in Resources */,
65ED405A235DEF6C0081F399 /* main_mac.js in Resources */,
65ED405B235DEF6C0081F399 /* KeyboardShortcuts.html in Resources */,
@ -4290,7 +4263,6 @@
845479881FEB77C000AD8B59 /* TimelineKeyboardShortcuts.plist in Resources */,
848362FF2262A30E00DA1D35 /* template.html in Resources */,
848363082262A3DD00DA1D35 /* Main.storyboard in Resources */,
51EF0F8E2279C9260050506E /* AccountsAdd.xib in Resources */,
84C9FC8F22629E8F00D921D6 /* NetNewsWire.sdef in Resources */,
84C9FC7D22629E1200D921D6 /* AccountsDetail.xib in Resources */,
517630042336215100E15FFF /* main.js in Resources */,
@ -4298,7 +4270,6 @@
5144EA362279FC3D00D19003 /* AccountsAddLocal.xib in Resources */,
5142194B2353C1CF00E07E2C /* main_mac.js in Resources */,
84C9FC8C22629E8F00D921D6 /* KeyboardShortcuts.html in Resources */,
515A5168243E66910089E588 /* ExtensionPointEnable.xib in Resources */,
B27EEBF9244D15F3000932E6 /* shared.css in Resources */,
5144EA3B227A379E00D19003 /* ImportOPMLSheet.xib in Resources */,
844B5B691FEA20DF00C7C76A /* SidebarKeyboardShortcuts.plist in Resources */,
@ -4317,7 +4288,6 @@
3B826DCB2385C84800FC1ADB /* AccountsFeedWrangler.xib in Resources */,
55E15BCB229D65A900D6602A /* AccountsReaderAPI.xib in Resources */,
49F40DF82335B71000552BF4 /* newsfoot.js in Resources */,
510C43ED243C0973009F70C3 /* ExtensionPointAdd.xib in Resources */,
51333D3B2468615D00EB5C91 /* AddRedditFeedSheet.xib in Resources */,
BDCB516724282C8A00102A80 /* AccountsNewsBlur.xib in Resources */,
514A89A2244FD63F0085E65D /* AddTwitterFeedSheet.xib in Resources */,
@ -4843,13 +4813,13 @@
65ED3FCF235DEF6C0081F399 /* TimelineContainerView.swift in Sources */,
65ED3FD0235DEF6C0081F399 /* Author+Scriptability.swift in Sources */,
65ED3FD1235DEF6C0081F399 /* PseudoFeed.swift in Sources */,
65ED3FD2235DEF6C0081F399 /* AccountsAddViewController.swift in Sources */,
65ED3FD3235DEF6C0081F399 /* NSScriptCommand+NetNewsWire.swift in Sources */,
65ED3FD4235DEF6C0081F399 /* Article+Scriptability.swift in Sources */,
515A5172243E802B0089E588 /* ExtensionPointDetailViewController.swift in Sources */,
65ED3FD5235DEF6C0081F399 /* SmartFeed.swift in Sources */,
51333D1724685D2E00EB5C91 /* AddRedditFeedWindowController.swift in Sources */,
65ED3FD6235DEF6C0081F399 /* MarkStatusCommand.swift in Sources */,
5183CFB0254C78C8006B83A5 /* EnableExtensionPointView.swift in Sources */,
65ED3FD7235DEF6C0081F399 /* NSApplication+Scriptability.swift in Sources */,
65ED3FD8235DEF6C0081F399 /* NSView-Extensions.swift in Sources */,
51A052CF244FB9D7006C2024 /* AddFeedWIndowController.swift in Sources */,
@ -4916,7 +4886,6 @@
65ED400D235DEF6C0081F399 /* SmartFeedDelegate.swift in Sources */,
65ED400E235DEF6C0081F399 /* ImageDownloader.swift in Sources */,
65ED400F235DEF6C0081F399 /* LegacyArticleExtractorButton.swift in Sources */,
65ED4010235DEF6C0081F399 /* AccountsAddTableCellView.swift in Sources */,
65ED4011235DEF6C0081F399 /* AddFolderWindowController.swift in Sources */,
65ED4012235DEF6C0081F399 /* TimelineContainerViewController.swift in Sources */,
65ED4013235DEF6C0081F399 /* MainWIndowKeyboardHandler.swift in Sources */,
@ -4934,7 +4903,6 @@
65ED401C235DEF6C0081F399 /* FaviconGenerator.swift in Sources */,
65ED401D235DEF6C0081F399 /* RefreshInterval.swift in Sources */,
65ED401E235DEF6C0081F399 /* TimelineCellData.swift in Sources */,
510C43F1243C0A80009F70C3 /* ExtensionPointAddViewController.swift in Sources */,
65ED401F235DEF6C0081F399 /* BuiltinSmartFeedInspectorViewController.swift in Sources */,
65ED4020235DEF6C0081F399 /* AppDelegate+Scriptability.swift in Sources */,
65ED4021235DEF6C0081F399 /* NNW3Document.swift in Sources */,
@ -4974,7 +4942,6 @@
65ED403D235DEF6C0081F399 /* TimelineTableCellView.swift in Sources */,
65ED403E235DEF6C0081F399 /* TimelineCellAppearance.swift in Sources */,
51C4CFF124D37D1F00AF9874 /* Secrets.swift in Sources */,
510C43F4243C11FE009F70C3 /* ExtensionPointAddTableCellView.swift in Sources */,
65ED403F235DEF6C0081F399 /* ArticleRenderer.swift in Sources */,
65ED4040235DEF6C0081F399 /* GeneralPrefencesViewController.swift in Sources */,
179DB1DFBCF9177104B12E0F /* AccountsNewsBlurWindowController.swift in Sources */,
@ -5204,7 +5171,6 @@
8405DD9922153B6B008CE1BF /* TimelineContainerView.swift in Sources */,
D5A2678C20130ECF00A8D3C0 /* Author+Scriptability.swift in Sources */,
84F2D5371FC22FCC00998D64 /* PseudoFeed.swift in Sources */,
51EF0F902279C9500050506E /* AccountsAddViewController.swift in Sources */,
D57BE6E0204CD35F00D11AAC /* NSScriptCommand+NetNewsWire.swift in Sources */,
D553738B20186C20006D8857 /* Article+Scriptability.swift in Sources */,
845EE7C11FC2488C00854A1F /* SmartFeed.swift in Sources */,
@ -5220,7 +5186,6 @@
849A97671ED9EB96007D329B /* UnreadCountView.swift in Sources */,
510C418024E5D1AE008226FD /* ExtensionFeedAddRequestFile.swift in Sources */,
51FE10092346739D0056195D /* ActivityType.swift in Sources */,
510C43F3243C11FE009F70C3 /* ExtensionPointAddTableCellView.swift in Sources */,
840BEE4121D70E64009BBAFA /* CrashReportWindowController.swift in Sources */,
8426118A1FCB67AA0086A189 /* WebFeedIconDownloader.swift in Sources */,
84C9FC7B22629E1200D921D6 /* PreferencesControlsBackgroundView.swift in Sources */,
@ -5245,6 +5210,7 @@
51107746243BEE2500D97C8C /* ExtensionPointPreferencesViewController.swift in Sources */,
519B8D332143397200FA689C /* SharingServiceDelegate.swift in Sources */,
FF3ABF1523259DDB0074C542 /* ArticleSorter.swift in Sources */,
5183CFAF254C78C8006B83A5 /* EnableExtensionPointView.swift in Sources */,
84E8E0DB202EC49300562D8F /* TimelineViewController+ContextualMenus.swift in Sources */,
849A97791ED9EC04007D329B /* ArticleStringFormatter.swift in Sources */,
B24E9ADC245AB88400DA5718 /* NSAttributedString+NetNewsWire.swift in Sources */,
@ -5281,7 +5247,6 @@
84DEE56522C32CA4005FC42C /* SmartFeedDelegate.swift in Sources */,
845213231FCA5B11003B6E93 /* ImageDownloader.swift in Sources */,
51FA73B72332D5F70090D516 /* LegacyArticleExtractorButton.swift in Sources */,
51EF0F922279CA620050506E /* AccountsAddTableCellView.swift in Sources */,
849A97431ED9EAA9007D329B /* AddFolderWindowController.swift in Sources */,
8405DDA522168C62008CE1BF /* TimelineContainerViewController.swift in Sources */,
844B5B671FEA18E300C7C76A /* MainWIndowKeyboardHandler.swift in Sources */,
@ -5305,7 +5270,6 @@
518651B223555EB20078E021 /* NNW3Document.swift in Sources */,
D5F4EDB5200744A700B9E363 /* ScriptingObject.swift in Sources */,
D5F4EDB920074D7C00B9E363 /* Folder+Scriptability.swift in Sources */,
510C43F0243C0A80009F70C3 /* ExtensionPointAddViewController.swift in Sources */,
849A97781ED9EC04007D329B /* TimelineCellLayout.swift in Sources */,
84E8E0EB202F693600562D8F /* DetailWebView.swift in Sources */,
849A976C1ED9EBC8007D329B /* TimelineTableRowView.swift in Sources */,

View File

@ -52,8 +52,10 @@ function wrapTables() {
// on an iphone when viewing an article.
function inlineVideos() {
document.querySelectorAll("video").forEach(element => {
element.setAttribute("playsinline", true)
element.setAttribute("controls", true)
element.setAttribute("playsinline", true);
if (!element.classList.contains("nnwAnimatedGIF")) {
element.setAttribute("controls", true);
}
});
}
@ -102,8 +104,13 @@ function stopMediaPlayback() {
element.src = iframeSrc;
});
// We pause all videos that have controls. Video without controls shouldn't
// have sound and are actually converted gifs. Basically if the user can't
// start the video again, don't stop it.
document.querySelectorAll("video, audio").forEach(element => {
element.pause();
if (element.hasAttribute("controls")) {
element.pause();
}
});
}

View File

@ -18,7 +18,7 @@ protocol ExtensionPoint {
static var isSinglton: Bool { get }
static var isDeveloperBuildRestricted: Bool { get }
static var title: String { get }
static var templateImage: RSImage { get }
static var image: RSImage { get }
static var description: NSAttributedString { get }
var title: String { get }
@ -28,8 +28,8 @@ protocol ExtensionPoint {
extension ExtensionPoint {
var templateImage: RSImage {
return extensionPointID.extensionPointType.templateImage
var image: RSImage {
return extensionPointID.extensionPointType.image
}
var description: NSAttributedString {

View File

@ -14,7 +14,7 @@ extension RedditFeedProvider: ExtensionPoint {
static var isSinglton = false
static var isDeveloperBuildRestricted = true
static var title = NSLocalizedString("Reddit", comment: "Reddit")
static var templateImage = AppAssets.extensionPointReddit
static var image = AppAssets.extensionPointReddit
static var description: NSAttributedString = {
return RedditFeedProvider.makeAttrString("This extension enables you to subscribe to Reddit URL's as if they were RSS feeds. It only works with \(Account.defaultLocalAccountName) or iCloud accounts.")
}()

View File

@ -15,7 +15,7 @@ final class SendToMarsEditCommand: ExtensionPoint, SendToCommand {
static var isSinglton = true
static var isDeveloperBuildRestricted = false
static var title = NSLocalizedString("MarsEdit", comment: "MarsEdit")
static var templateImage = AppAssets.extensionPointMarsEdit
static var image = AppAssets.extensionPointMarsEdit
static var description: NSAttributedString = {
let attrString = SendToMarsEditCommand.makeAttrString("This extension enables share menu functionality to send selected article text to MarsEdit. You need the MarsEdit application for this to work.")
let range = NSRange(location: 81, length: 8)

View File

@ -17,7 +17,7 @@ final class SendToMicroBlogCommand: ExtensionPoint, SendToCommand {
static var isSinglton = true
static var isDeveloperBuildRestricted = false
static var title: String = NSLocalizedString("Micro.blog", comment: "Micro.blog")
static var templateImage = AppAssets.extensionPointMicroblog
static var image = AppAssets.extensionPointMicroblog
static var description: NSAttributedString = {
let attrString = SendToMicroBlogCommand.makeAttrString("This extension enables share menu functionality to send selected article text to Micro.blog. You need the Micro.blog application for this to work.")
let range = NSRange(location: 81, length: 10)

View File

@ -14,7 +14,7 @@ extension TwitterFeedProvider: ExtensionPoint {
static var isSinglton = false
static var isDeveloperBuildRestricted = true
static var title = NSLocalizedString("Twitter", comment: "Twitter")
static var templateImage = AppAssets.extensionPointTwitter
static var image = AppAssets.extensionPointTwitter
static var description: NSAttributedString = {
return TwitterFeedProvider.makeAttrString("This extension enables you to subscribe to Twitter URL's as if they were RSS feeds. It only works with \(Account.defaultLocalAccountName) or iCloud accounts.")
}()

View File

@ -41,7 +41,7 @@ extension ExtensionPointInspectorViewController {
if section == 0 {
let headerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "SectionHeader") as! ImageHeaderView
headerView.imageView.image = extensionPoint.templateImage
headerView.imageView.image = extensionPoint.image
return headerView
} else {
return super.tableView(tableView, viewForHeaderInSection: section)

View File

@ -34,7 +34,7 @@ class AddExtensionPointViewController: UITableViewController, AddExtensionPointD
let extensionPointType = availableExtensionPointTypes[indexPath.row]
cell.comboNameLabel?.text = extensionPointType.title
cell.comboImage?.image = extensionPointType.templateImage
cell.comboImage?.image = extensionPointType.image
return cell
}

View File

@ -59,7 +59,7 @@ class EnableExtensionPointViewController: UITableViewController {
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
if section == 0 {
let headerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "SectionHeader") as! ImageHeaderView
headerView.imageView.image = extensionPointType?.templateImage
headerView.imageView.image = extensionPointType?.image
return headerView
} else {
return super.tableView(tableView, viewForHeaderInSection: section)

View File

@ -153,7 +153,7 @@ class SettingsViewController: UITableViewController {
let acctCell = tableView.dequeueReusableCell(withIdentifier: "SettingsComboTableViewCell", for: indexPath) as! SettingsComboTableViewCell
acctCell.applyThemeProperties()
let extensionPoint = extensionPoints[indexPath.row]
acctCell.comboImage?.image = extensionPoint.templateImage
acctCell.comboImage?.image = extensionPoint.image
acctCell.comboNameLabel?.text = extensionPoint.title
cell = acctCell
}

View File

@ -1,6 +1,6 @@
// High Level Settings common to both the Mac application and any extensions we bundle with it
MARKETING_VERSION = 5.1.1
CURRENT_PROJECT_VERSION = 3012
MARKETING_VERSION = 5.1.2
CURRENT_PROJECT_VERSION = 3016
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon