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 removeWebFeedFromCloud(for: account, with: feed, from: container) { result in
switch result { switch result {
case .success: case .success:
account.clearWebFeedMetadata(feed)
container.removeWebFeed(feed) container.removeWebFeed(feed)
completion(.success(())) completion(.success(()))
case .failure(let error): case .failure(let error):

View File

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

View File

@ -98,7 +98,7 @@ final class RedditLinkData: Codable {
guard let url = url else { return "" } guard let url = url else { return "" }
if url.hasSuffix(".gif") { if url.hasSuffix(".gif") {
return "<img src=\"\(url)\">" return "<img class=\"nnw-nozoom\" src=\"\(url)\">"
} }
if isVideo ?? false, let videoURL = media?.video?.hlsURL { 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 { if let width = media?.video?.width, let height = media?.video?.height {
html += "width=\"\(width)\" height=\"\(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 return html
} }
if let videoPreviewURL = preview?.videoPreview?.url { if let videoPreviewURL = preview?.videoPreview?.url {
var html = "<video " var html = "<video class=\"nnwAnimatedGIF\" "
if let previewImageURL = preview?.images?.first?.source?.url { if let previewImageURL = preview?.images?.first?.source?.url {
html += "poster=\"\(previewImageURL)\" " html += "poster=\"\(previewImageURL)\" "
} }

View File

@ -23,6 +23,42 @@ struct RedditPreview: Codable {
struct RedditPreviewImage: Codable { struct RedditPreviewImage: Codable {
let source: RedditPreviewImageSource? 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 { enum CodingKeys: String, CodingKey {
case source = "source" case source = "source"
@ -30,7 +66,7 @@ struct RedditPreviewImage: Codable {
} }
struct RedditPreviewImageSource: Codable { struct RedditPreviewImageVariantsMP4Source: Codable {
let url: String? let url: String?
let width: Int? let width: Int?

View File

@ -36,8 +36,10 @@ struct TwitterExtendedMedia: Codable {
switch type { switch type {
case "photo": case "photo":
html += renderPhotoAsHTML() html += renderPhotoAsHTML()
case "video", "animated_gif": case "video":
html += renderVideoAsHTML() html += renderVideoAsHTML()
case "animated_gif":
html += renderAnimatedGIFAsHTML()
default: default:
break break
} }
@ -74,6 +76,21 @@ private extension TwitterExtendedMedia {
return html 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? { func findBestVariant() -> TwitterVideo.Variant? {
var best: TwitterVideo.Variant? = nil var best: TwitterVideo.Variant? = nil
if let variants = video?.variants { if let variants = video?.variants {

View File

@ -59,6 +59,20 @@ final class FeedlyCreateFeedsForCollectionFoldersOperation: FeedlyOperation {
// find an existing feed previously added to the account // find an existing feed previously added to the account
if let feed = account.existingWebFeed(withWebFeedID: collectionFeed.id) { 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) return (feed, folder)
} else { } else {
// find an existing feed we created below in an earlier value // find an existing feed we created below in an earlier value

View File

@ -312,14 +312,14 @@ private extension LocalAccountDelegate {
return 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 InitialFeedDownloader.download(url) { parsedFeed in
self.refreshProgress.completeTask() self.refreshProgress.completeTask()
if let parsedFeed = parsedFeed { 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 account.update(feed, with: parsedFeed, {_ in
BatchUpdate.shared.end() BatchUpdate.shared.end()
completion(.success(feed)) completion(.success(feed))

View File

@ -263,7 +263,7 @@ final class ReaderAPIAccountDelegate: AccountDelegate {
switch result { switch result {
case .success: case .success:
DispatchQueue.main.async { DispatchQueue.main.async {
self.clearFolderRelationship(for: feed, withFolderName: folder.name ?? "") self.clearFolderRelationship(for: feed, folderExternalID: folder.externalID)
} }
case .failure(let error): case .failure(let error):
os_log(.error, log: self.log, "Remove feed error: %@.", error.localizedDescription) 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) { func removeWebFeed(for account: Account, with feed: WebFeed, from container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
account.clearWebFeedMetadata(feed) guard let subscriptionID = feed.externalID else {
deleteSubscription(for: account, with: feed, from: container, completion: completion) 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) { 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) { 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) refreshProgress.addToNumberOfTasksAndRemaining(1)
caller.createTagging(subscriptionID: feedName, tagName: folder.name ?? "") { result in caller.createTagging(subscriptionID: feedExternalID, tagName: folder.name ?? "") { result in
self.refreshProgress.completeTask() self.refreshProgress.completeTask()
switch result { switch result {
case .success: case .success:
DispatchQueue.main.async { 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) account.removeWebFeed(feed)
folder.addWebFeed(feed) folder.addWebFeed(feed)
completion(.success(())) completion(.success(()))
@ -561,11 +586,21 @@ private extension ReaderAPIAccountDelegate {
caller.retrieveTags { result in caller.retrieveTags { result in
switch result { switch result {
case .success(let tags): case .success(let tags):
BatchUpdate.shared.perform {
self.syncFolders(account, tags)
}
self.refreshProgress.completeTask() 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): case .failure(let error):
completion(.failure(error)) completion(.failure(error))
} }
@ -575,58 +610,45 @@ private extension ReaderAPIAccountDelegate {
func syncFolders(_ account: Account, _ tags: [ReaderAPITag]?) { func syncFolders(_ account: Account, _ tags: [ReaderAPITag]?) {
guard let tags = tags else { return } guard let tags = tags else { return }
assert(Thread.isMainThread) 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 readerFolderExternalIDs = folderTags.compactMap { $0.tagID }
let readerFolderNames = tags.compactMap { $0.folderName }
// Delete any folders not at Reader // Delete any folders not at Reader
if let folders = account.folders { if let folders = account.folders {
folders.forEach { folder in folders.forEach { folder in
if !readerFolderNames.contains(folder.name ?? "") { if !readerFolderExternalIDs.contains(folder.externalID ?? "") {
for feed in folder.topLevelWebFeeds { for feed in folder.topLevelWebFeeds {
account.addWebFeed(feed) account.addWebFeed(feed)
clearFolderRelationship(for: feed, withFolderName: folder.name ?? "") clearFolderRelationship(for: feed, folderExternalID: folder.externalID)
} }
account.removeFolder(folder) account.removeFolder(folder)
} }
} }
} }
let folderNames: [String] = { let folderExternalIDs: [String] = {
if let folders = account.folders { if let folders = account.folders {
return folders.map { $0.name ?? "" } return folders.compactMap { $0.externalID }
} else { } else {
return [String]() return [String]()
} }
}() }()
// Make any folders Reader has, but we don't // Make any folders Reader has, but we don't
tags.forEach { tag in folderTags.forEach { tag in
if let tagFolderName = tag.folderName, !folderNames.contains(tagFolderName) { if !folderExternalIDs.contains(tag.tagID) {
let folder = account.ensureFolder(with: tagFolderName) let folder = account.ensureFolder(with: tag.folderName ?? "None")
folder?.externalID = tag.tagID 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]?) { func syncFeeds(_ account: Account, _ subscriptions: [ReaderAPISubscription]?) {
guard let subscriptions = subscriptions else { return } guard let subscriptions = subscriptions else { return }
@ -649,6 +671,7 @@ private extension ReaderAPIAccountDelegate {
for feed in account.topLevelWebFeeds { for feed in account.topLevelWebFeeds {
if !subFeedIds.contains(feed.webFeedID) { if !subFeedIds.contains(feed.webFeedID) {
account.clearWebFeedMetadata(feed)
account.removeWebFeed(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 } guard let subscriptions = subscriptions else { return }
assert(Thread.isMainThread) assert(Thread.isMainThread)
os_log(.debug, log: log, "Syncing taggings with %ld subscriptions.", subscriptions.count) os_log(.debug, log: log, "Syncing taggings with %ld subscriptions.", subscriptions.count)
// Set up some structures to make syncing easier // 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 let taggingsDict = subscriptions.reduce([String: [ReaderAPISubscription]]()) { (dict, subscription) in
var taggedFeeds = dict 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 subscription.categories.forEach({ (category) in
let categoryName = category.categoryLabel.replacingOccurrences(of: "user/-/label/", with: "") if var taggedFeed = taggedFeeds[category.categoryId] {
if var taggedFeed = taggedFeeds[categoryName] {
taggedFeed.append(subscription) taggedFeed.append(subscription)
taggedFeeds[categoryName] = taggedFeed taggedFeeds[category.categoryId] = taggedFeed
} else { } else {
taggedFeeds[categoryName] = [subscription] taggedFeeds[category.categoryId] = [subscription]
} }
}) })
return taggedFeeds return taggedFeeds
} }
var taggedFeedIDs = Set<String>()
// Sync the folders // Sync the folders
for (folderName, groupedTaggings) in taggingsDict { for (folderExternalID, groupedTaggings) in taggingsDict {
guard let folder = folderDict[folderName] else { return } guard let folder = folderDict[folderExternalID] else { return }
let taggingFeedIDs = groupedTaggings.map { $0.feedID } let taggingFeedIDs = groupedTaggings.map { $0.feedID }
// Move any feeds not in the folder to the account // Move any feeds not in the folder to the account
for feed in folder.topLevelWebFeeds { for feed in folder.topLevelWebFeeds {
if !taggingFeedIDs.contains(feed.webFeedID) { if !taggingFeedIDs.contains(feed.webFeedID) {
folder.removeWebFeed(feed) folder.removeWebFeed(feed)
clearFolderRelationship(for: feed, withFolderName: folder.name ?? "") clearFolderRelationship(for: feed, folderExternalID: folder.externalID)
account.addWebFeed(feed) account.addWebFeed(feed)
} }
} }
@ -720,14 +738,15 @@ private extension ReaderAPIAccountDelegate {
guard let feed = account.existingWebFeed(withWebFeedID: taggingFeedID) else { guard let feed = account.existingWebFeed(withWebFeedID: taggingFeedID) else {
continue continue
} }
saveFolderRelationship(for: feed, withFolderName: folderName, id: String(subscription.feedID)) saveFolderRelationship(for: feed, folderExternalID: folderExternalID, feedExternalID: subscription.feedID)
folder.addWebFeed(feed) 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 // Remove all feeds from the account container that have a tag
for feed in account.topLevelWebFeeds { for feed in account.topLevelWebFeeds {
if taggedFeedIDs.contains(feed.webFeedID) { 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 { guard let folders = folders else {
return [String: Folder]() return [String: Folder]()
} }
var d = [String: Folder]() var d = [String: Folder]()
for folder in folders { for folder in folders {
let name = folder.name ?? "" if let externalID = folder.externalID, d[externalID] == nil {
if d[name] == nil { d[externalID] = folder
d[name] = folder
} }
} }
return d return d
} }
@ -784,19 +804,19 @@ private extension ReaderAPIAccountDelegate {
} }
func clearFolderRelationship(for feed: WebFeed, withFolderName folderName: String) { func clearFolderRelationship(for feed: WebFeed, folderExternalID: String?) {
if var folderRelationship = feed.folderRelationship { guard var folderRelationship = feed.folderRelationship, let folderExternalID = folderExternalID else { return }
folderRelationship[folderName] = nil folderRelationship[folderExternalID] = nil
feed.folderRelationship = folderRelationship 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 { if var folderRelationship = feed.folderRelationship {
folderRelationship[folderName] = id folderRelationship[folderExternalID] = feedExternalID
feed.folderRelationship = folderRelationship feed.folderRelationship = folderRelationship
} else { } else {
feed.folderRelationship = [folderName: id] feed.folderRelationship = [folderExternalID: feedExternalID]
} }
} }
@ -889,15 +909,23 @@ private extension ReaderAPIAccountDelegate {
account.fetchArticleIDsForStatusesWithoutArticlesNewerThanCutoffDate { articleIDsResult in account.fetchArticleIDsForStatusesWithoutArticlesNewerThanCutoffDate { articleIDsResult in
func process(_ fetchedArticleIDs: Set<String>) { func process(_ fetchedArticleIDs: Set<String>) {
guard !fetchedArticleIDs.isEmpty else {
completion()
return
}
os_log(.debug, log: self.log, "Refreshing missing articles...") os_log(.debug, log: self.log, "Refreshing missing articles...")
let group = DispatchGroup() let group = DispatchGroup()
let articleIDs = Array(fetchedArticleIDs) let articleIDs = Array(fetchedArticleIDs)
let chunkedArticleIDs = articleIDs.chunked(into: 100) let chunkedArticleIDs = articleIDs.chunked(into: 100)
self.refreshProgress.addToNumberOfTasksAndRemaining(chunkedArticleIDs.count - 1)
for chunk in chunkedArticleIDs { for chunk in chunkedArticleIDs {
group.enter() group.enter()
self.caller.retrieveEntries(articleIDs: chunk) { result in self.caller.retrieveEntries(articleIDs: chunk) { result in
self.refreshProgress.completeTask()
switch result { switch result {
case .success(let entries): case .success(let entries):
@ -1052,7 +1080,7 @@ private extension ReaderAPIAccountDelegate {
switch result { switch result {
case .success: case .success:
DispatchQueue.main.async { DispatchQueue.main.async {
self.clearFolderRelationship(for: feed, withFolderName: folder.name ?? "") self.clearFolderRelationship(for: feed, folderExternalID: folder.externalID)
folder.removeWebFeed(feed) folder.removeWebFeed(feed)
account.addFeedIfNotInAnyFolder(feed) account.addFeedIfNotInAnyFolder(feed)
completion(.success(())) 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 RSWeb
import Articles import Articles
extension Notification.Name {
static let appleColorPreferencesChangedNotification = Notification.Name("AppleColorPreferencesChangedNotification")
static let appleInterfaceThemeChangedNotification = Notification.Name("AppleInterfaceThemeChangedNotification")
}
protocol DetailWebViewControllerDelegate: class { protocol DetailWebViewControllerDelegate: class {
func mouseDidEnter(_: DetailWebViewController, link: String) func mouseDidEnter(_: DetailWebViewController, link: String)
func mouseDidExit(_: 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)
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) webView.loadFileURL(ArticleRenderer.blank.url, allowingReadAccessTo: ArticleRenderer.blank.baseURL)
} }
@ -145,14 +137,6 @@ final class DetailWebViewController: NSViewController, WKUIDelegate {
reloadArticleImage() reloadArticleImage()
} }
@objc func appleColorPreferencesChanged(_ note: Notification) {
reloadHTML()
}
@objc func appleInterfaceThemeChanged(_ note: Notification) {
reloadHTML()
}
// MARK: Media Functions // MARK: Media Functions
func stopMediaPlayback() { 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 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") self.errorMessageLabel.stringValue = NSLocalizedString("There is already a Feedbin account with that username created.", comment: "Duplicate Error")
return return
} }

View File

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

View File

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

View File

@ -7,6 +7,14 @@
// //
import AppKit import AppKit
import SwiftUI
import AuthenticationServices
import OAuthSwift
import Secrets
protocol ExtensionPointPreferencesEnabler: class {
func enable(_ extensionPointType: ExtensionPoint.Type)
}
final class ExtensionPointPreferencesViewController: NSViewController { final class ExtensionPointPreferencesViewController: NSViewController {
@ -15,6 +23,8 @@ final class ExtensionPointPreferencesViewController: NSViewController {
@IBOutlet weak var deleteButton: NSButton! @IBOutlet weak var deleteButton: NSButton!
private var activeExtensionPoints = [ExtensionPoint]() private var activeExtensionPoints = [ExtensionPoint]()
private var callbackURL: URL? = nil
private var oauth: OAuthSwift?
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
@ -30,11 +40,17 @@ final class ExtensionPointPreferencesViewController: NSViewController {
tableView.frame = rTable tableView.frame = rTable
showDefaultView() showDefaultView()
// Set initial row selection
if activeExtensionPoints.count > 0 {
tableView.selectRow(0)
}
} }
@IBAction func enableExtensionPoints(_ sender: Any) { @IBAction func enableExtensionPoints(_ sender: Any) {
tableView.selectRowIndexes([], byExtendingSelection: false) let controller = NSHostingController(rootView: EnableExtensionPointView(enabler: self))
showController(ExtensionPointAddViewController()) controller.rootView.parent = controller
presentAsSheet(controller)
} }
@IBAction func disableExtensionPoint(_ sender: Any) { @IBAction func disableExtensionPoint(_ sender: Any) {
@ -44,8 +60,7 @@ final class ExtensionPointPreferencesViewController: NSViewController {
let extensionPoint = activeExtensionPoints[tableView.selectedRow] let extensionPoint = activeExtensionPoints[tableView.selectedRow]
ExtensionPointManager.shared.deactivateExtensionPoint(extensionPoint.extensionPointID) ExtensionPointManager.shared.deactivateExtensionPoint(extensionPoint.extensionPointID)
hideController()
showController(ExtensionPointAddViewController())
} }
} }
@ -72,7 +87,7 @@ extension ExtensionPointPreferencesViewController: NSTableViewDelegate {
if let cell = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "Cell"), owner: nil) as? NSTableCellView { if let cell = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "Cell"), owner: nil) as? NSTableCellView {
let extensionPoint = activeExtensionPoints[row] let extensionPoint = activeExtensionPoints[row]
cell.textField?.stringValue = extensionPoint.title cell.textField?.stringValue = extensionPoint.title
cell.imageView?.image = extensionPoint.templateImage cell.imageView?.image = extensionPoint.image
return cell return cell
} }
return nil return nil
@ -83,6 +98,7 @@ extension ExtensionPointPreferencesViewController: NSTableViewDelegate {
let selectedRow = tableView.selectedRow let selectedRow = tableView.selectedRow
if tableView.selectedRow == -1 { if tableView.selectedRow == -1 {
deleteButton.isEnabled = false deleteButton.isEnabled = false
hideController()
return return
} else { } else {
deleteButton.isEnabled = true 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 // MARK: - Private
private extension ExtensionPointPreferencesViewController { private extension ExtensionPointPreferencesViewController {
@ -107,20 +179,77 @@ private extension ExtensionPointPreferencesViewController {
func showDefaultView() { func showDefaultView() {
activeExtensionPoints = Array(ExtensionPointManager.shared.activeExtensionPoints.values).sorted(by: { $0.title < $1.title }) activeExtensionPoints = Array(ExtensionPointManager.shared.activeExtensionPoints.values).sorted(by: { $0.title < $1.title })
tableView.reloadData() tableView.reloadData()
showController(ExtensionPointAddViewController())
} }
func showController(_ controller: NSViewController) { func showController(_ controller: NSViewController) {
hideController()
if let controller = children.first {
children.removeAll()
controller.view.removeFromSuperview()
}
addChild(controller) addChild(controller)
controller.view.translatesAutoresizingMaskIntoConstraints = false controller.view.translatesAutoresizingMaskIntoConstraints = false
detailView.addSubview(controller.view) detailView.addSubview(controller.view)
detailView.addFullSizeConstraints(forSubview: 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" : [ "images" : [
{ {
"filename" : "freshrss-any.pdf", "filename" : "FreshRSS.pdf",
"idiom" : "universal" "idiom" : "universal"
} }
], ],

View File

@ -1,8 +1,18 @@
{ {
"images" : [ "images" : [
{ {
"filename" : "reddit_logo.pdf", "filename" : "reddit24x24.png",
"idiom" : "universal" "idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "reddit48x48.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
} }
], ],
"info" : { "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" : [ "images" : [
{ {
"filename" : "twitter.pdf", "filename" : "twitter24x24.png",
"idiom" : "universal" "idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "twitter48x48.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
} }
], ],
"info" : { "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 RSCore
import Articles import Articles
extension Notification.Name {
static let appleColorPreferencesChangedNotification = Notification.Name("AppleColorPreferencesChangedNotification")
static let appleInterfaceThemeChangedNotification = Notification.Name("AppleInterfaceThemeChangedNotification")
}
protocol WebViewControllerDelegate: class { protocol WebViewControllerDelegate: class {
func webViewController(_: WebViewController, articleExtractorButtonStateDidUpdate: ArticleExtractorButtonState) 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(webFeedIconDidBecomeAvailable(_:)), name: .WebFeedIconDidBecomeAvailable, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(avatarDidBecomeAvailable(_:)), name: .AvatarDidBecomeAvailable, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(avatarDidBecomeAvailable(_:)), name: .AvatarDidBecomeAvailable, object: nil)
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)
statusBarView = WebStatusBarView() statusBarView = WebStatusBarView()
statusBarView.translatesAutoresizingMaskIntoConstraints = false statusBarView.translatesAutoresizingMaskIntoConstraints = false
@ -100,14 +93,6 @@ class WebViewController: NSViewController {
reloadArticleImage() reloadArticleImage()
} }
@objc func appleColorPreferencesChanged(_ note: Notification) {
loadWebView()
}
@objc func appleInterfaceThemeChanged(_ note: Notification) {
loadWebView()
}
// MARK: API // MARK: API
func focus() { func focus() {

View File

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

View File

@ -52,8 +52,10 @@ function wrapTables() {
// on an iphone when viewing an article. // on an iphone when viewing an article.
function inlineVideos() { function inlineVideos() {
document.querySelectorAll("video").forEach(element => { document.querySelectorAll("video").forEach(element => {
element.setAttribute("playsinline", true) element.setAttribute("playsinline", true);
element.setAttribute("controls", true) if (!element.classList.contains("nnwAnimatedGIF")) {
element.setAttribute("controls", true);
}
}); });
} }
@ -102,8 +104,13 @@ function stopMediaPlayback() {
element.src = iframeSrc; 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 => { 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 isSinglton: Bool { get }
static var isDeveloperBuildRestricted: Bool { get } static var isDeveloperBuildRestricted: Bool { get }
static var title: String { get } static var title: String { get }
static var templateImage: RSImage { get } static var image: RSImage { get }
static var description: NSAttributedString { get } static var description: NSAttributedString { get }
var title: String { get } var title: String { get }
@ -28,8 +28,8 @@ protocol ExtensionPoint {
extension ExtensionPoint { extension ExtensionPoint {
var templateImage: RSImage { var image: RSImage {
return extensionPointID.extensionPointType.templateImage return extensionPointID.extensionPointType.image
} }
var description: NSAttributedString { var description: NSAttributedString {

View File

@ -14,7 +14,7 @@ extension RedditFeedProvider: ExtensionPoint {
static var isSinglton = false static var isSinglton = false
static var isDeveloperBuildRestricted = true static var isDeveloperBuildRestricted = true
static var title = NSLocalizedString("Reddit", comment: "Reddit") static var title = NSLocalizedString("Reddit", comment: "Reddit")
static var templateImage = AppAssets.extensionPointReddit static var image = AppAssets.extensionPointReddit
static var description: NSAttributedString = { 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.") 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 isSinglton = true
static var isDeveloperBuildRestricted = false static var isDeveloperBuildRestricted = false
static var title = NSLocalizedString("MarsEdit", comment: "MarsEdit") static var title = NSLocalizedString("MarsEdit", comment: "MarsEdit")
static var templateImage = AppAssets.extensionPointMarsEdit static var image = AppAssets.extensionPointMarsEdit
static var description: NSAttributedString = { 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 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) let range = NSRange(location: 81, length: 8)

View File

@ -17,7 +17,7 @@ final class SendToMicroBlogCommand: ExtensionPoint, SendToCommand {
static var isSinglton = true static var isSinglton = true
static var isDeveloperBuildRestricted = false static var isDeveloperBuildRestricted = false
static var title: String = NSLocalizedString("Micro.blog", comment: "Micro.blog") static var title: String = NSLocalizedString("Micro.blog", comment: "Micro.blog")
static var templateImage = AppAssets.extensionPointMicroblog static var image = AppAssets.extensionPointMicroblog
static var description: NSAttributedString = { 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 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) let range = NSRange(location: 81, length: 10)

View File

@ -14,7 +14,7 @@ extension TwitterFeedProvider: ExtensionPoint {
static var isSinglton = false static var isSinglton = false
static var isDeveloperBuildRestricted = true static var isDeveloperBuildRestricted = true
static var title = NSLocalizedString("Twitter", comment: "Twitter") static var title = NSLocalizedString("Twitter", comment: "Twitter")
static var templateImage = AppAssets.extensionPointTwitter static var image = AppAssets.extensionPointTwitter
static var description: NSAttributedString = { 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.") 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 { if section == 0 {
let headerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "SectionHeader") as! ImageHeaderView let headerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "SectionHeader") as! ImageHeaderView
headerView.imageView.image = extensionPoint.templateImage headerView.imageView.image = extensionPoint.image
return headerView return headerView
} else { } else {
return super.tableView(tableView, viewForHeaderInSection: section) return super.tableView(tableView, viewForHeaderInSection: section)

View File

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

View File

@ -59,7 +59,7 @@ class EnableExtensionPointViewController: UITableViewController {
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
if section == 0 { if section == 0 {
let headerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "SectionHeader") as! ImageHeaderView let headerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "SectionHeader") as! ImageHeaderView
headerView.imageView.image = extensionPointType?.templateImage headerView.imageView.image = extensionPointType?.image
return headerView return headerView
} else { } else {
return super.tableView(tableView, viewForHeaderInSection: section) 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 let acctCell = tableView.dequeueReusableCell(withIdentifier: "SettingsComboTableViewCell", for: indexPath) as! SettingsComboTableViewCell
acctCell.applyThemeProperties() acctCell.applyThemeProperties()
let extensionPoint = extensionPoints[indexPath.row] let extensionPoint = extensionPoints[indexPath.row]
acctCell.comboImage?.image = extensionPoint.templateImage acctCell.comboImage?.image = extensionPoint.image
acctCell.comboNameLabel?.text = extensionPoint.title acctCell.comboNameLabel?.text = extensionPoint.title
cell = acctCell cell = acctCell
} }

View File

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