Merge branch 'mac-release' into main

This commit is contained in:
Maurice Parker
2021-03-27 17:21:49 -05:00
51 changed files with 449 additions and 449 deletions

View File

@@ -614,8 +614,8 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
delegate.addWebFeed(for: self, with: feed, to: container, completion: completion)
}
public func createWebFeed(url: String, name: String?, container: Container, completion: @escaping (Result<WebFeed, Error>) -> Void) {
delegate.createWebFeed(for: self, url: url, name: name, container: container, completion: completion)
public func createWebFeed(url: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result<WebFeed, Error>) -> Void) {
delegate.createWebFeed(for: self, url: url, name: name, container: container, validateFeed: validateFeed, completion: completion)
}
func createWebFeed(with name: String?, url: String, webFeedID: String, homePageURL: String?) -> WebFeed {

View File

@@ -35,7 +35,7 @@ protocol AccountDelegate {
func renameFolder(for account: Account, with folder: Folder, to name: String, completion: @escaping (Result<Void, Error>) -> Void)
func removeFolder(for account: Account, with folder: Folder, completion: @escaping (Result<Void, Error>) -> Void)
func createWebFeed(for account: Account, url: String, name: String?, container: Container, completion: @escaping (Result<WebFeed, Error>) -> Void)
func createWebFeed(for account: Account, url: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result<WebFeed, Error>) -> Void)
func renameWebFeed(for account: Account, with feed: WebFeed, to name: String, completion: @escaping (Result<Void, Error>) -> Void)
func addWebFeed(for account: Account, with: WebFeed, to 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)

View File

@@ -157,7 +157,7 @@ final class CloudKitAccountDelegate: AccountDelegate {
}
func createWebFeed(for account: Account, url urlString: String, name: String?, container: Container, completion: @escaping (Result<WebFeed, Error>) -> Void) {
func createWebFeed(for account: Account, url urlString: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result<WebFeed, Error>) -> Void) {
guard let url = URL(string: urlString), let urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false) else {
completion(.failure(LocalAccountDelegateError.invalidParameter))
return
@@ -169,7 +169,7 @@ final class CloudKitAccountDelegate: AccountDelegate {
if let feedProvider = FeedProviderManager.shared.best(for: urlComponents) {
createProviderWebFeed(for: account, urlComponents: urlComponents, editedName: editedName, container: container, feedProvider: feedProvider, completion: completion)
} else {
createRSSWebFeed(for: account, url: url, editedName: editedName, container: container, completion: completion)
createRSSWebFeed(for: account, url: url, editedName: editedName, container: container, validateFeed: validateFeed, completion: completion)
}
}
@@ -199,7 +199,7 @@ final class CloudKitAccountDelegate: AccountDelegate {
completion(.success(()))
case .failure(let error):
switch error {
case CloudKitZoneError.invalidParameter:
case CloudKitZoneError.corruptAccount:
// We got into a bad state and should remove the feed to clear up the bad data
account.clearWebFeedMetadata(feed)
container.removeWebFeed(feed)
@@ -242,7 +242,7 @@ final class CloudKitAccountDelegate: AccountDelegate {
}
func restoreWebFeed(for account: Account, feed: WebFeed, container: Container, completion: @escaping (Result<Void, Error>) -> Void) {
createWebFeed(for: account, url: feed.url, name: feed.editedName, container: container) { result in
createWebFeed(for: account, url: feed.url, name: feed.editedName, container: container, validateFeed: true) { result in
switch result {
case .success:
completion(.success(()))
@@ -310,20 +310,22 @@ final class CloudKitAccountDelegate: AccountDelegate {
}
group.notify(queue: DispatchQueue.global(qos: .background)) {
guard !errorOccurred else {
self.refreshProgress.completeTask()
completion(.failure(CloudKitAccountDelegateError.unknown))
return
}
self.accountZone.removeFolder(folder) { result in
self.refreshProgress.completeTask()
switch result {
case .success:
account.removeFolder(folder)
completion(.success(()))
case .failure(let error):
completion(.failure(error))
DispatchQueue.main.async {
guard !errorOccurred else {
self.refreshProgress.completeTask()
completion(.failure(CloudKitAccountDelegateError.unknown))
return
}
self.accountZone.removeFolder(folder) { result in
self.refreshProgress.completeTask()
switch result {
case .success:
account.removeFolder(folder)
completion(.success(()))
case .failure(let error):
completion(.failure(error))
}
}
}
}
@@ -636,7 +638,9 @@ private extension CloudKitAccountDelegate {
account.update(urlString, with: parsedItems) { result in
switch result {
case .success:
self.sendNewArticlesToTheCloud(account, feed, completion: completion)
self.sendNewArticlesToTheCloud(account, feed)
self.refreshProgress.clear()
completion(.success(feed))
case .failure(let error):
self.refreshProgress.completeTasks(2)
completion(.failure(error))
@@ -657,14 +661,36 @@ private extension CloudKitAccountDelegate {
}
case .failure:
self.refreshProgress.completeTasks(5)
self.refreshProgress.completeTasks(4)
completion(.failure(AccountError.createErrorNotFound))
}
}
}
func createRSSWebFeed(for account: Account, url: URL, editedName: String?, container: Container, completion: @escaping (Result<WebFeed, Error>) -> Void) {
BatchUpdate.shared.start()
func createRSSWebFeed(for account: Account, url: URL, editedName: String?, container: Container, validateFeed: Bool, completion: @escaping (Result<WebFeed, Error>) -> Void) {
func addDeadFeed() {
let feed = account.createWebFeed(with: editedName, url: url.absoluteString, webFeedID: url.absoluteString, homePageURL: nil)
container.addWebFeed(feed)
self.accountZone.createWebFeed(url: url.absoluteString,
name: editedName,
editedName: nil,
homePageURL: nil,
container: container) { result in
self.refreshProgress.completeTask()
switch result {
case .success(let externalID):
feed.externalID = externalID
completion(.success(feed))
case .failure(let error):
container.removeWebFeed(feed)
completion(.failure(error))
}
}
}
refreshProgress.addToNumberOfTasksAndRemaining(5)
FeedFinder.find(url: url) { result in
@@ -672,15 +698,18 @@ private extension CloudKitAccountDelegate {
switch result {
case .success(let feedSpecifiers):
guard let bestFeedSpecifier = FeedSpecifier.bestFeed(in: feedSpecifiers), let url = URL(string: bestFeedSpecifier.urlString) else {
BatchUpdate.shared.end()
self.refreshProgress.completeTasks(5)
completion(.failure(AccountError.createErrorNotFound))
self.refreshProgress.completeTasks(3)
if validateFeed {
self.refreshProgress.completeTask()
completion(.failure(AccountError.createErrorNotFound))
} else {
addDeadFeed()
}
return
}
if account.hasWebFeed(withURL: bestFeedSpecifier.urlString) {
BatchUpdate.shared.end()
self.refreshProgress.completeTasks(5)
self.refreshProgress.completeTasks(4)
completion(.failure(AccountError.createErrorAlreadySubscribed))
return
}
@@ -696,7 +725,6 @@ private extension CloudKitAccountDelegate {
account.update(feed, with: parsedFeed) { result in
switch result {
case .success:
BatchUpdate.shared.end()
self.accountZone.createWebFeed(url: bestFeedSpecifier.urlString,
name: parsedFeed.title,
@@ -708,7 +736,8 @@ private extension CloudKitAccountDelegate {
switch result {
case .success(let externalID):
feed.externalID = externalID
self.sendNewArticlesToTheCloud(account, feed, completion: completion)
self.sendNewArticlesToTheCloud(account, feed)
completion(.success(feed))
case .failure(let error):
container.removeWebFeed(feed)
self.refreshProgress.completeTasks(2)
@@ -718,7 +747,6 @@ private extension CloudKitAccountDelegate {
}
case .failure(let error):
BatchUpdate.shared.end()
self.refreshProgress.completeTasks(3)
completion(.failure(error))
}
@@ -732,15 +760,19 @@ private extension CloudKitAccountDelegate {
}
case .failure:
BatchUpdate.shared.end()
self.refreshProgress.completeTasks(4)
completion(.failure(AccountError.createErrorNotFound))
self.refreshProgress.completeTasks(3)
if validateFeed {
self.refreshProgress.completeTask()
completion(.failure(AccountError.createErrorNotFound))
return
} else {
addDeadFeed()
}
}
}
}
func sendNewArticlesToTheCloud(_ account: Account, _ feed: WebFeed, completion: @escaping (Result<WebFeed, Error>) -> Void) {
func sendNewArticlesToTheCloud(_ account: Account, _ feed: WebFeed) {
account.fetchArticlesAsync(.webFeed(feed)) { result in
switch result {
case .success(let articles):
@@ -749,19 +781,14 @@ private extension CloudKitAccountDelegate {
self.sendArticleStatus(for: account, showProgress: true) { result in
switch result {
case .success:
self.articlesZone.fetchChangesInZone() { _ in
self.refreshProgress.completeTask()
completion(.success(feed))
}
self.articlesZone.fetchChangesInZone() { _ in }
case .failure(let error):
self.refreshProgress.clear()
completion(.failure(error))
os_log(.error, log: self.log, "CloudKit Feed send articles error: %@.", error.localizedDescription)
}
}
}
case .failure(let error):
self.refreshProgress.clear()
completion(.failure(error))
os_log(.error, log: self.log, "CloudKit Feed send articles error: %@.", error.localizedDescription)
}
}
}

View File

@@ -103,7 +103,7 @@ final class CloudKitAccountZone: CloudKitZone {
}
guard let containerExternalID = container.externalID else {
completion(.failure(CloudKitZoneError.invalidParameter))
completion(.failure(CloudKitZoneError.corruptAccount))
return
}
record[CloudKitWebFeed.Fields.containerExternalIDs] = [containerExternalID]
@@ -121,7 +121,7 @@ final class CloudKitAccountZone: CloudKitZone {
/// Rename the given web feed
func renameWebFeed(_ webFeed: WebFeed, editedName: String?, completion: @escaping (Result<Void, Error>) -> Void) {
guard let externalID = webFeed.externalID else {
completion(.failure(CloudKitZoneError.invalidParameter))
completion(.failure(CloudKitZoneError.corruptAccount))
return
}
@@ -142,7 +142,7 @@ final class CloudKitAccountZone: CloudKitZone {
/// Removes a web feed from a container and optionally deletes it, calling the completion with true if deleted
func removeWebFeed(_ webFeed: WebFeed, from: Container, completion: @escaping (Result<Bool, Error>) -> Void) {
guard let fromContainerExternalID = from.externalID else {
completion(.failure(CloudKitZoneError.invalidParameter))
completion(.failure(CloudKitZoneError.corruptAccount))
return
}
@@ -187,7 +187,7 @@ final class CloudKitAccountZone: CloudKitZone {
func moveWebFeed(_ webFeed: WebFeed, from: Container, to: Container, completion: @escaping (Result<Void, Error>) -> Void) {
guard let fromContainerExternalID = from.externalID, let toContainerExternalID = to.externalID else {
completion(.failure(CloudKitZoneError.invalidParameter))
completion(.failure(CloudKitZoneError.corruptAccount))
return
}
@@ -209,7 +209,7 @@ final class CloudKitAccountZone: CloudKitZone {
func addWebFeed(_ webFeed: WebFeed, to: Container, completion: @escaping (Result<Void, Error>) -> Void) {
guard let toContainerExternalID = to.externalID else {
completion(.failure(CloudKitZoneError.invalidParameter))
completion(.failure(CloudKitZoneError.corruptAccount))
return
}
@@ -292,7 +292,7 @@ final class CloudKitAccountZone: CloudKitZone {
func renameFolder(_ folder: Folder, to name: String, completion: @escaping (Result<Void, Error>) -> Void) {
guard let externalID = folder.externalID else {
completion(.failure(CloudKitZoneError.invalidParameter))
completion(.failure(CloudKitZoneError.corruptAccount))
return
}

View File

@@ -25,6 +25,8 @@ final class CloudKitArticlesZone: CloudKitZone {
weak var database: CKDatabase?
var delegate: CloudKitZoneDelegate? = nil
var compressionQueue = DispatchQueue(label: "Articles Zone Compression Queue")
struct CloudKitArticle {
static let recordType = "Article"
struct Fields {
@@ -33,7 +35,9 @@ final class CloudKitArticlesZone: CloudKitZone {
static let uniqueID = "uniqueID"
static let title = "title"
static let contentHTML = "contentHTML"
static let contentHTMLData = "contentHTMLData"
static let contentText = "contentText"
static let contentTextData = "contentTextData"
static let url = "url"
static let externalURL = "externalURL"
static let summary = "summary"
@@ -96,7 +100,10 @@ final class CloudKitArticlesZone: CloudKitZone {
records.append(makeArticleRecord(saveArticle))
}
save(records, completion: completion)
compressionQueue.async {
let compressedRecords = self.compressArticleRecords(records)
self.save(compressedRecords, completion: completion)
}
}
func deleteArticles(_ webFeedExternalID: String, completion: @escaping ((Result<Void, Error>) -> Void)) {
@@ -130,22 +137,27 @@ final class CloudKitArticlesZone: CloudKitZone {
deleteRecordIDs.append(CKRecord.ID(recordName: self.articleID(statusUpdate.articleID), zoneID: zoneID))
}
}
self.modify(recordsToSave: modifyRecords, recordIDsToDelete: deleteRecordIDs) { result in
switch result {
case .success:
self.saveIfNew(newRecords) { result in
switch result {
case .success:
completion(.success(()))
case .failure(let error):
completion(.failure(error))
compressionQueue.async {
let compressedModifyRecords = self.compressArticleRecords(modifyRecords)
self.modify(recordsToSave: compressedModifyRecords, recordIDsToDelete: deleteRecordIDs) { result in
switch result {
case .success:
let compressedNewRecords = self.compressArticleRecords(newRecords)
self.saveIfNew(compressedNewRecords) { result in
switch result {
case .success:
completion(.success(()))
case .failure(let error):
completion(.failure(error))
}
}
case .failure(let error):
self.handleModifyArticlesError(error, statusUpdates: statusUpdates, completion: completion)
}
case .failure(let error):
self.handleModifyArticlesError(error, statusUpdates: statusUpdates, completion: completion)
}
}
}
}
@@ -237,5 +249,35 @@ private extension CloudKitArticlesZone {
return record
}
func compressArticleRecords(_ records: [CKRecord]) -> [CKRecord] {
var result = [CKRecord]()
for record in records {
if record.recordType == CloudKitArticle.recordType {
if let contentHTML = record[CloudKitArticle.Fields.contentHTML] as? String {
let data = Data(contentHTML.utf8) as NSData
if let compressedData = try? data.compressed(using: .lzfse) {
record[CloudKitArticle.Fields.contentHTMLData] = compressedData as Data
record[CloudKitArticle.Fields.contentHTML] = nil
}
}
if let contentText = record[CloudKitArticle.Fields.contentText] as? String {
let data = Data(contentText.utf8) as NSData
if let compressedData = try? data.compressed(using: .lzfse) {
record[CloudKitArticle.Fields.contentTextData] = compressedData as Data
record[CloudKitArticle.Fields.contentText] = nil
}
}
}
result.append(record)
}
return result
}
}

View File

@@ -23,6 +23,7 @@ class CloudKitArticlesZoneDelegate: CloudKitZoneDelegate {
weak var account: Account?
var database: SyncDatabase
weak var articlesZone: CloudKitArticlesZone?
var compressionQueue = DispatchQueue(label: "Articles Zone Delegate Compression Queue")
init(account: Account, database: SyncDatabase, articlesZone: CloudKitArticlesZone) {
self.account = account
@@ -134,7 +135,7 @@ private extension CloudKitArticlesZoneDelegate {
}
group.enter()
DispatchQueue.global(qos: .utility).async {
compressionQueue.async {
let parsedItems = records.compactMap { self.makeParsedItem($0) }
let webFeedIDsAndItems = Dictionary(grouping: parsedItems, by: { item in item.feedURL } ).mapValues { Set($0) }
@@ -199,6 +200,20 @@ private extension CloudKitArticlesZoneDelegate {
return nil
}
var contentHTML = articleRecord[CloudKitArticlesZone.CloudKitArticle.Fields.contentHTML] as? String
if let contentHTMLData = articleRecord[CloudKitArticlesZone.CloudKitArticle.Fields.contentHTMLData] as? NSData {
if let decompressedContentHTMLData = try? contentHTMLData.decompressed(using: .lzfse) {
contentHTML = String(data: decompressedContentHTMLData as Data, encoding: .utf8)
}
}
var contentText = articleRecord[CloudKitArticlesZone.CloudKitArticle.Fields.contentText] as? String
if let contentTextData = articleRecord[CloudKitArticlesZone.CloudKitArticle.Fields.contentTextData] as? NSData {
if let decompressedContentTextData = try? contentTextData.decompressed(using: .lzfse) {
contentText = String(data: decompressedContentTextData as Data, encoding: .utf8)
}
}
let parsedItem = ParsedItem(syncServiceID: nil,
uniqueID: uniqueID,
feedURL: webFeedURL,
@@ -206,8 +221,8 @@ private extension CloudKitArticlesZoneDelegate {
externalURL: articleRecord[CloudKitArticlesZone.CloudKitArticle.Fields.externalURL] as? String,
title: articleRecord[CloudKitArticlesZone.CloudKitArticle.Fields.title] as? String,
language: nil,
contentHTML: articleRecord[CloudKitArticlesZone.CloudKitArticle.Fields.contentHTML] as? String,
contentText: articleRecord[CloudKitArticlesZone.CloudKitArticle.Fields.contentText] as? String,
contentHTML: contentHTML,
contentText: contentText,
summary: articleRecord[CloudKitArticlesZone.CloudKitArticle.Fields.summary] as? String,
imageURL: articleRecord[CloudKitArticlesZone.CloudKitArticle.Fields.imageURL] as? String,
bannerImageURL: articleRecord[CloudKitArticlesZone.CloudKitArticle.Fields.imageURL] as? String,

View File

@@ -16,7 +16,7 @@ import SyncDatabase
class CloudKitSendStatusOperation: MainThreadOperation {
private var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "CloudKit")
private let blockSize = 300
private let blockSize = 150
// MainThreadOperation
public var isCanceled = false

View File

@@ -297,7 +297,7 @@ final class FeedWranglerAccountDelegate: AccountDelegate {
fatalError()
}
func createWebFeed(for account: Account, url: String, name: String?, container: Container, completion: @escaping (Result<WebFeed, Error>) -> Void) {
func createWebFeed(for account: Account, url: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result<WebFeed, Error>) -> Void) {
refreshProgress.addToNumberOfTasksAndRemaining(2)
self.refreshCredentials(for: account) {
@@ -439,7 +439,7 @@ final class FeedWranglerAccountDelegate: AccountDelegate {
}
}
} else {
createWebFeed(for: account, url: feed.url, name: feed.editedName, container: container) { result in
createWebFeed(for: account, url: feed.url, name: feed.editedName, container: container, validateFeed: true) { result in
switch result {
case .success:
completion(.success(()))

View File

@@ -370,7 +370,7 @@ final class FeedbinAccountDelegate: AccountDelegate {
}
func createWebFeed(for account: Account, url: String, name: String?, container: Container, completion: @escaping (Result<WebFeed, Error>) -> Void) {
func createWebFeed(for account: Account, url: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result<WebFeed, Error>) -> Void) {
refreshProgress.addToNumberOfTasksAndRemaining(1)
caller.createSubscription(url: url) { result in
@@ -496,7 +496,7 @@ final class FeedbinAccountDelegate: AccountDelegate {
}
}
} else {
createWebFeed(for: account, url: feed.url, name: feed.editedName, container: container) { result in
createWebFeed(for: account, url: feed.url, name: feed.editedName, container: container, validateFeed: true) { result in
switch result {
case .success:
completion(.success(()))
@@ -998,7 +998,7 @@ private extension FeedbinAccountDelegate {
}
if let bestSpecifier = FeedSpecifier.bestFeed(in: Set(feedSpecifiers)) {
createWebFeed(for: account, url: bestSpecifier.urlString, name: name, container: container, completion: completion)
createWebFeed(for: account, url: bestSpecifier.urlString, name: name, container: container, validateFeed: true, completion: completion)
} else {
DispatchQueue.main.async {
completion(.failure(FeedbinAccountDelegateError.invalidParameter))

View File

@@ -369,7 +369,9 @@ final class FeedlyAPICaller {
}
}
send(request: request, resultType: [FeedlyFeed].self, dateDecoding: .millisecondsSince1970, keyDecoding: .convertFromSnakeCase) { result in
// `resultType` is optional because the Feedly API has gone from returning an array of removed feeds to returning `null`.
// https://developer.feedly.com/v3/collections/#remove-multiple-feeds-from-a-personal-collection
send(request: request, resultType: Optional<[FeedlyFeed]>.self, dateDecoding: .millisecondsSince1970, keyDecoding: .convertFromSnakeCase) { result in
switch result {
case .success((let httpResponse, _)):
if httpResponse.statusCode == 200 {

View File

@@ -296,7 +296,7 @@ final class FeedlyAccountDelegate: AccountDelegate {
}
}
func createWebFeed(for account: Account, url: String, name: String?, container: Container, completion: @escaping (Result<WebFeed, Error>) -> Void) {
func createWebFeed(for account: Account, url: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result<WebFeed, Error>) -> Void) {
do {
guard let credentials = credentials else {
@@ -450,7 +450,7 @@ final class FeedlyAccountDelegate: AccountDelegate {
}
}
} else {
createWebFeed(for: account, url: feed.url, name: feed.editedName, container: container) { result in
createWebFeed(for: account, url: feed.url, name: feed.editedName, container: container, validateFeed: true) { result in
switch result {
case .success:
completion(.success(()))

View File

@@ -146,7 +146,7 @@ final class LocalAccountDelegate: AccountDelegate {
}
func createWebFeed(for account: Account, url urlString: String, name: String?, container: Container, completion: @escaping (Result<WebFeed, Error>) -> Void) {
func createWebFeed(for account: Account, url urlString: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result<WebFeed, Error>) -> Void) {
guard let url = URL(string: urlString), let urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false) else {
completion(.failure(LocalAccountDelegateError.invalidParameter))
return

View File

@@ -405,7 +405,7 @@ final class NewsBlurAccountDelegate: AccountDelegate {
}
}
func createWebFeed(for account: Account, url: String, name: String?, container: Container, completion: @escaping (Result<WebFeed, Error>) -> ()) {
func createWebFeed(for account: Account, url: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result<WebFeed, Error>) -> ()) {
refreshProgress.addToNumberOfTasksAndRemaining(1)
let folderName = (container as? Folder)?.name
@@ -512,7 +512,7 @@ final class NewsBlurAccountDelegate: AccountDelegate {
}
}
} else {
createWebFeed(for: account, url: feed.url, name: feed.editedName, container: container) { result in
createWebFeed(for: account, url: feed.url, name: feed.editedName, container: container, validateFeed: true) { result in
switch result {
case .success:
completion(.success(()))

View File

@@ -326,7 +326,7 @@ final class ReaderAPIAccountDelegate: AccountDelegate {
}
func createWebFeed(for account: Account, url: String, name: String?, container: Container, completion: @escaping (Result<WebFeed, Error>) -> Void) {
func createWebFeed(for account: Account, url: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result<WebFeed, Error>) -> Void) {
guard let url = URL(string: url) else {
completion(.failure(ReaderAPIAccountDelegateError.invalidParameter))
return
@@ -489,7 +489,7 @@ final class ReaderAPIAccountDelegate: AccountDelegate {
}
}
} else {
createWebFeed(for: account, url: feed.url, name: feed.editedName, container: container) { result in
createWebFeed(for: account, url: feed.url, name: feed.editedName, container: container, validateFeed: true) { result in
switch result {
case .success:
completion(.success(()))

View File

@@ -117,7 +117,9 @@ final class ReaderAPICaller: NSObject {
var authData: [String: String] = [:]
rawData.split(separator: "\n").forEach({ (line: Substring) in
let items = line.split(separator: "=").map{String($0)}
authData[items[0]] = items[1]
if items.count == 2 {
authData[items[0]] = items[1]
}
})
guard let authString = authData["Auth"] else {

View File

@@ -43,7 +43,7 @@ final class AppDefaults {
static let showDebugMenu = "ShowDebugMenu"
static let timelineShowsSeparators = "CorreiaSeparators"
static let showTitleOnMainWindow = "KafasisTitleMode"
static let hideDockUnreadCount = "JustinMillerHideDockUnreadCount"
static let feedDoubleClickMarkAsRead = "GruberFeedDoubleClickMarkAsRead"
#if !MAC_APP_STORE
static let webInspectorEnabled = "WebInspectorEnabled"
@@ -194,12 +194,12 @@ final class AppDefaults {
return AppDefaults.bool(for: Key.showDebugMenu)
}
var hideDockUnreadCount: Bool {
var feedDoubleClickMarkAsRead: Bool {
get {
return AppDefaults.bool(for: Key.hideDockUnreadCount)
return AppDefaults.bool(for: Key.feedDoubleClickMarkAsRead)
}
set {
AppDefaults.setBool(for: Key.hideDockUnreadCount, newValue)
AppDefaults.setBool(for: Key.feedDoubleClickMarkAsRead, newValue)
}
}

View File

@@ -49,6 +49,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations,
var refreshTimer: AccountRefreshTimer?
var syncTimer: ArticleStatusSyncTimer?
var lastRefreshInterval = AppDefaults.shared.refreshInterval
var shuttingDown = false {
didSet {
@@ -246,10 +247,17 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations,
UNUserNotificationCenter.current().delegate = self
userNotificationManager = UserNotificationManager()
#if DEBUG
refreshTimer!.update()
syncTimer!.update()
#else
DispatchQueue.main.async {
self.refreshTimer!.timedRefresh(nil)
self.syncTimer!.timedRefresh(nil)
}
#endif
if AppDefaults.shared.showDebugMenu {
refreshTimer!.update()
syncTimer!.update()
// The Web Inspector uses SPI and can never appear in a MAC_APP_STORE build.
#if MAC_APP_STORE
let debugMenu = debugMenuItem.submenu!
@@ -260,10 +268,6 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations,
#endif
} else {
debugMenuItem.menu?.removeItem(debugMenuItem)
DispatchQueue.main.async {
self.refreshTimer!.timedRefresh(nil)
self.syncTimer!.timedRefresh(nil)
}
}
#if !MAC_APP_STORE
@@ -347,7 +351,12 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations,
@objc func userDefaultsDidChange(_ note: Notification) {
updateSortMenuItems()
updateGroupByFeedMenuItem()
refreshTimer?.update()
if lastRefreshInterval != AppDefaults.shared.refreshInterval {
refreshTimer?.update()
lastRefreshInterval = AppDefaults.shared.refreshInterval
}
updateDockBadge()
}
@@ -491,7 +500,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations,
// MARK: - Dock Badge
@objc func updateDockBadge() {
let label = unreadCount > 0 && !AppDefaults.shared.hideDockUnreadCount ? "\(unreadCount)" : ""
let label = unreadCount > 0 ? "\(unreadCount)" : ""
NSApplication.shared.dockTile.badgeLabel = label
}
@@ -608,7 +617,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations,
@IBAction func openWebsite(_ sender: Any?) {
Browser.open("https://ranchero.com/netnewswire/", inBackground: false)
Browser.open("https://netnewswire.com/", inBackground: false)
}
@IBAction func openReleaseNotes(_ sender: Any?) {
@@ -632,7 +641,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations,
}
@IBAction func openSlackGroup(_ sender: Any?) {
Browser.open("https://ranchero.com/netnewswire/slack", inBackground: false)
Browser.open("https://netnewswire.com/slack", inBackground: false)
}
@IBAction func openTechnotes(_ sender: Any?) {
@@ -642,7 +651,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations,
@IBAction func showHelp(_ sender: Any?) {
Browser.open("https://ranchero.com/netnewswire/help/mac/5.1/en/", inBackground: false)
Browser.open("https://netnewswire.com/help/mac/6.0/en/", inBackground: false)
}
@IBAction func donateToAppCampForGirls(_ sender: Any?) {
@@ -650,7 +659,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations,
}
@IBAction func showPrivacyPolicy(_ sender: Any?) {
Browser.open("https://ranchero.com/netnewswire/privacypolicy", inBackground: false)
Browser.open("https://netnewswire.com/privacypolicy", inBackground: false)
}
@IBAction func gotoToday(_ sender: Any?) {
@@ -715,9 +724,11 @@ extension AppDelegate {
}
@IBAction func debugTestCrashReportSending(_ sender: Any?) {
#if DEBUG
CrashReporter.sendCrashLogText("This is a test. Hi, Brent.")
#endif
CrashReporter.sendCrashLogText("This is a test. Hi, Brent.")
}
@IBAction func forceCrash(_ sender: Any?) {
fatalError("This is a deliberate crash.")
}
@IBAction func openApplicationSupportFolder(_ sender: Any?) {

View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="17147" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="17701" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="17147"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="17701"/>
</dependencies>
<scenes>
<!--Application-->
@@ -516,6 +516,7 @@
<action selector="debugDropConditionalGetInfo:" target="Voe-Tx-rLC" id="X24-9X-rwG"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="Dij-Nu-eot"/>
<menuItem title="Test Crash Reporter Window" id="gVd-kQ-efj">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
@@ -528,6 +529,13 @@
<action selector="debugTestCrashReportSending:" target="Ady-hI-5gd" id="olQ-kb-kkm"/>
</connections>
</menuItem>
<menuItem title="Force Crash" id="gVt-cz-eoJ">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="forceCrash:" target="Ady-hI-5gd" id="a2x-LM-60G"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="Nzi-Mb-uxc"/>
<menuItem title="Debug Search" id="9Ot-wC-s5U">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
@@ -682,7 +690,7 @@
</items>
</menu>
</objects>
<point key="canvasLocation" x="-54" y="-144"/>
<point key="canvasLocation" x="-277" y="-69"/>
</scene>
</scenes>
</document>

View File

@@ -31,15 +31,15 @@
<scene sceneID="R4l-Wg-k7x">
<objects>
<viewController title="General" storyboardIdentifier="General" id="iuH-lz-18x" customClass="GeneralPreferencesViewController" customModule="NetNewsWire" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" id="WnV-px-wCT">
<rect key="frame" x="0.0" y="0.0" width="509" height="246"/>
<view key="view" misplaced="YES" id="WnV-px-wCT">
<rect key="frame" x="0.0" y="0.0" width="509" height="220"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<customView horizontalHuggingPriority="1000" verticalHuggingPriority="1000" horizontalCompressionResistancePriority="1000" verticalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="Ut3-yd-q6G">
<rect key="frame" x="57" y="16" width="394" height="214"/>
<rect key="frame" x="54" y="16" width="400" height="188"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="pR2-Bf-7Fd">
<rect key="frame" x="6" y="192" width="106" height="16"/>
<rect key="frame" x="6" y="167" width="106" height="16"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="Article Text Size:" id="xQu-QV-91i">
<font key="font" usesAppearanceFont="YES"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@@ -47,7 +47,7 @@
</textFieldCell>
</textField>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Z6O-Zt-V1g">
<rect key="frame" x="116" y="186" width="281" height="25"/>
<rect key="frame" x="115" y="160" width="289" height="25"/>
<popUpButtonCell key="cell" type="push" title="Medium" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="2" imageScaling="proportionallyDown" inset="2" selectedItem="jMV-2o-5Oh" id="6pw-Vq-tjM">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
@@ -76,10 +76,10 @@
</popUpButtonCell>
</popUpButton>
<box verticalHuggingPriority="750" boxType="separator" translatesAutoresizingMaskIntoConstraints="NO" id="Tdg-6Y-gvW">
<rect key="frame" x="0.0" y="170" width="394" height="5"/>
<rect key="frame" x="0.0" y="145" width="400" height="5"/>
</box>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Wsb-Lr-8Q7">
<rect key="frame" x="54" y="138" width="58" height="16"/>
<rect key="frame" x="54" y="114" width="58" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Browser:" id="CgU-dE-Qtb">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@@ -87,7 +87,7 @@
</textFieldCell>
</textField>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Ci4-fW-KjU">
<rect key="frame" x="116" y="132" width="281" height="25"/>
<rect key="frame" x="115" y="107" width="289" height="25"/>
<popUpButtonCell key="cell" type="push" title="Safari" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="ObP-qK-qDJ" id="hrm-aT-Rc2" userLabel="Popup">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
@@ -102,7 +102,7 @@
</connections>
</popUpButton>
<button horizontalHuggingPriority="1000" verticalHuggingPriority="1000" horizontalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="Ubm-Pk-l7x">
<rect key="frame" x="116" y="105" width="280" height="18"/>
<rect key="frame" x="116" y="80" width="284" height="18"/>
<buttonCell key="cell" type="check" title="Open web pages in background in browser" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="t0a-LN-rCv">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
@@ -122,7 +122,7 @@
</connections>
</button>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="j0t-Wa-UTL">
<rect key="frame" x="135" y="83" width="236" height="16"/>
<rect key="frame" x="135" y="57" width="235" height="16"/>
<textFieldCell key="cell" controlSize="small" title="Press the Shift key to do the opposite." id="EMq-9M-zTJ">
<font key="font" usesAppearanceFont="YES"/>
<color key="textColor" name="secondaryLabelColor" catalog="System" colorSpace="catalog"/>
@@ -130,10 +130,10 @@
</textFieldCell>
</textField>
<box verticalHuggingPriority="750" boxType="separator" translatesAutoresizingMaskIntoConstraints="NO" id="hQy-ng-ijd">
<rect key="frame" x="0.0" y="64" width="394" height="5"/>
<rect key="frame" x="0.0" y="38" width="400" height="5"/>
</box>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ucw-vG-yLt">
<rect key="frame" x="20" y="32" width="92" height="16"/>
<rect key="frame" x="20" y="7" width="92" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Refresh feeds:" id="F7c-lm-g97">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@@ -141,7 +141,7 @@
</textFieldCell>
</textField>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="SFF-mL-yc8">
<rect key="frame" x="116" y="26" width="281" height="25"/>
<rect key="frame" x="115" y="0.0" width="289" height="25"/>
<constraints>
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="200" id="N1a-qV-4Os"/>
</constraints>
@@ -175,37 +175,16 @@
</connections>
</popUpButtonCell>
</popUpButton>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="jVd-Ie-CGX">
<rect key="frame" x="44" y="3" width="68" height="16"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="Dock icon:" id="vFc-Nz-RFp">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="mwT-nY-TrX">
<rect key="frame" x="116" y="2" width="139" height="18"/>
<buttonCell key="cell" type="check" title="Show unread count" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="lh0-G6-9v4">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="toggleShowingUnreadCount:" target="iuH-lz-18x" id="CfQ-Pv-9d2"/>
</connections>
</button>
</subviews>
<constraints>
<constraint firstItem="Wsb-Lr-8Q7" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="Ut3-yd-q6G" secondAttribute="leading" id="17A-5m-ZG0"/>
<constraint firstItem="Tdg-6Y-gvW" firstAttribute="top" secondItem="Z6O-Zt-V1g" secondAttribute="bottom" constant="16" id="2iQ-Xt-RLM"/>
<constraint firstItem="Z6O-Zt-V1g" firstAttribute="leading" secondItem="pR2-Bf-7Fd" secondAttribute="trailing" constant="8" symbolic="YES" id="2wM-K6-eAF"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="Ubm-Pk-l7x" secondAttribute="trailing" id="3h4-m7-pMW"/>
<constraint firstItem="mwT-nY-TrX" firstAttribute="firstBaseline" secondItem="jVd-Ie-CGX" secondAttribute="firstBaseline" id="5nL-J8-5as"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="j0t-Wa-UTL" secondAttribute="trailing" id="7Oh-pf-X12"/>
<constraint firstItem="Ubm-Pk-l7x" firstAttribute="leading" secondItem="SFF-mL-yc8" secondAttribute="leading" id="7cy-O4-Zz2"/>
<constraint firstItem="Ci4-fW-KjU" firstAttribute="width" secondItem="SFF-mL-yc8" secondAttribute="width" id="AE4-am-IWK"/>
<constraint firstItem="mwT-nY-TrX" firstAttribute="top" secondItem="ucw-vG-yLt" secondAttribute="bottom" constant="14" id="CZq-CZ-fC5"/>
<constraint firstItem="Ubm-Pk-l7x" firstAttribute="top" secondItem="Ci4-fW-KjU" secondAttribute="bottom" constant="14" id="GNx-7d-yAo"/>
<constraint firstItem="Wsb-Lr-8Q7" firstAttribute="trailing" secondItem="jVd-Ie-CGX" secondAttribute="trailing" id="ITg-ay-Y2x"/>
<constraint firstItem="Z6O-Zt-V1g" firstAttribute="top" secondItem="Ut3-yd-q6G" secondAttribute="top" constant="4" id="IaE-jL-vMM"/>
<constraint firstItem="hQy-ng-ijd" firstAttribute="leading" secondItem="Ut3-yd-q6G" secondAttribute="leading" id="KEI-R5-rzD"/>
<constraint firstItem="pR2-Bf-7Fd" firstAttribute="leading" secondItem="Ut3-yd-q6G" secondAttribute="leading" constant="8" id="LRG-HZ-yxh"/>
@@ -219,18 +198,15 @@
<constraint firstAttribute="trailing" secondItem="Z6O-Zt-V1g" secondAttribute="trailing" id="aS9-KA-vSH"/>
<constraint firstItem="SFF-mL-yc8" firstAttribute="firstBaseline" secondItem="ucw-vG-yLt" secondAttribute="firstBaseline" id="aqn-St-DJy"/>
<constraint firstItem="Tdg-6Y-gvW" firstAttribute="leading" secondItem="Ut3-yd-q6G" secondAttribute="leading" id="b3I-JF-If3"/>
<constraint firstItem="jVd-Ie-CGX" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="Ut3-yd-q6G" secondAttribute="leading" id="dDb-jw-vca"/>
<constraint firstItem="SFF-mL-yc8" firstAttribute="top" secondItem="hQy-ng-ijd" secondAttribute="bottom" constant="16" id="eM7-OM-Qsz"/>
<constraint firstItem="mwT-nY-TrX" firstAttribute="leading" secondItem="Ubm-Pk-l7x" secondAttribute="leading" id="fb7-Og-rSp"/>
<constraint firstItem="Ci4-fW-KjU" firstAttribute="top" secondItem="Tdg-6Y-gvW" secondAttribute="bottom" constant="16" id="hXl-1D-lTD"/>
<constraint firstAttribute="bottom" secondItem="mwT-nY-TrX" secondAttribute="bottom" constant="4" id="jFE-ye-pSr"/>
<constraint firstItem="ucw-vG-yLt" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="Ut3-yd-q6G" secondAttribute="leading" id="lDL-JN-ANP"/>
<constraint firstItem="Z6O-Zt-V1g" firstAttribute="width" secondItem="SFF-mL-yc8" secondAttribute="width" id="noW-Jf-Xbs"/>
<constraint firstAttribute="trailing" secondItem="Tdg-6Y-gvW" secondAttribute="trailing" id="qzz-gu-8kO"/>
<constraint firstItem="Wsb-Lr-8Q7" firstAttribute="firstBaseline" secondItem="Ci4-fW-KjU" secondAttribute="firstBaseline" id="rPX-je-OG5"/>
<constraint firstItem="Ci4-fW-KjU" firstAttribute="leading" secondItem="Wsb-Lr-8Q7" secondAttribute="trailing" constant="8" symbolic="YES" id="rcx-B6-zLP"/>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="mwT-nY-TrX" secondAttribute="trailing" id="skS-m8-bVR"/>
<constraint firstItem="SFF-mL-yc8" firstAttribute="leading" secondItem="ucw-vG-yLt" secondAttribute="trailing" constant="8" symbolic="YES" id="yBm-Dc-lGA"/>
<constraint firstAttribute="bottom" secondItem="SFF-mL-yc8" secondAttribute="bottom" constant="4" id="zIa-Ca-y3J"/>
<constraint firstAttribute="trailing" secondItem="Ci4-fW-KjU" secondAttribute="trailing" id="zbx-Ch-NEt"/>
</constraints>
</customView>
@@ -243,7 +219,6 @@
</view>
<connections>
<outlet property="defaultBrowserPopup" destination="Ci4-fW-KjU" id="7Nh-nU-Sbc"/>
<outlet property="showUnreadCountCheckbox" destination="mwT-nY-TrX" id="ZH9-P5-JkT"/>
</connections>
</viewController>
<customObject id="bSQ-tq-wd3" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
@@ -256,14 +231,14 @@
<objects>
<viewController storyboardIdentifier="Advanced" id="GNh-Wp-giO" customClass="AdvancedPreferencesViewController" customModule="NetNewsWire" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" id="Hij-7D-6Pw">
<rect key="frame" x="0.0" y="0.0" width="450" height="266"/>
<rect key="frame" x="0.0" y="0.0" width="450" height="267"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<customView translatesAutoresizingMaskIntoConstraints="NO" id="uJD-OF-YVY">
<rect key="frame" x="60" y="20" width="330" height="226"/>
<rect key="frame" x="60" y="20" width="330" height="227"/>
<subviews>
<textField horizontalHuggingPriority="1000" verticalHuggingPriority="1000" horizontalCompressionResistancePriority="1000" verticalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="EH5-aS-E55">
<rect key="frame" x="-2" y="210" width="87" height="16"/>
<rect key="frame" x="-2" y="211" width="87" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="App Updates:" id="zqG-X2-E9b">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@@ -271,7 +246,7 @@
</textFieldCell>
</textField>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="T4A-0o-p2w">
<rect key="frame" x="89" y="209" width="145" height="18"/>
<rect key="frame" x="89" y="210" width="149" height="18"/>
<buttonCell key="cell" type="check" title="Check automatically" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="dm8-Xy-0Ba">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
@@ -289,7 +264,7 @@
</textFieldCell>
</textField>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="QCu-J4-0yV">
<rect key="frame" x="90" y="182" width="110" height="18"/>
<rect key="frame" x="89" y="182" width="114" height="18"/>
<buttonCell key="cell" type="radio" title="Release builds" bezelStyle="regularSquare" imagePosition="left" alignment="left" inset="2" id="F8M-rS-und">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
@@ -299,7 +274,7 @@
</connections>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="CeE-AE-hRG">
<rect key="frame" x="90" y="160" width="88" height="18"/>
<rect key="frame" x="89" y="160" width="92" height="18"/>
<buttonCell key="cell" type="radio" title="Test builds" bezelStyle="regularSquare" imagePosition="left" alignment="left" inset="2" id="Fuf-rU-D6M">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
@@ -320,7 +295,7 @@
</textFieldCell>
</textField>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="TKI-a9-bRX">
<rect key="frame" x="85" y="69" width="154" height="32"/>
<rect key="frame" x="84" y="70" width="148" height="32"/>
<buttonCell key="cell" type="push" title="Check for Updates" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="AaA-Rr-UYD">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
@@ -338,7 +313,7 @@
</textFieldCell>
</textField>
<button horizontalHuggingPriority="1000" verticalHuggingPriority="1000" horizontalCompressionResistancePriority="1000" verticalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="UHg-1l-FlD">
<rect key="frame" x="89" y="40" width="138" height="18"/>
<rect key="frame" x="89" y="40" width="142" height="18"/>
<buttonCell key="cell" type="check" title="Send automatically" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="jnc-C5-4oI">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
@@ -358,7 +333,7 @@
</connections>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="uuc-f2-OFX">
<rect key="frame" x="85" y="-6" width="154" height="32"/>
<rect key="frame" x="84" y="-6" width="148" height="32"/>
<buttonCell key="cell" type="push" title="Privacy Policy" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="kSv-Wu-NYx">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
@@ -429,16 +404,16 @@
<autoresizingMask key="autoresizingMask"/>
<subviews>
<customView translatesAutoresizingMaskIntoConstraints="NO" id="7UM-iq-OLB" customClass="PreferencesTableViewBackgroundView" customModule="NetNewsWire" customModuleProvider="target">
<rect key="frame" x="20" y="44" width="180" height="243"/>
<rect key="frame" x="20" y="44" width="180" height="247"/>
<subviews>
<scrollView borderType="none" autohidesScrollers="YES" horizontalLineScroll="26" horizontalPageScroll="10" verticalLineScroll="26" verticalPageScroll="10" hasHorizontalScroller="NO" horizontalScrollElasticity="none" translatesAutoresizingMaskIntoConstraints="NO" id="PaF-du-r3c">
<rect key="frame" x="1" y="1" width="178" height="241"/>
<rect key="frame" x="1" y="1" width="178" height="245"/>
<clipView key="contentView" id="cil-Gq-akO">
<rect key="frame" x="0.0" y="0.0" width="178" height="241"/>
<rect key="frame" x="0.0" y="0.0" width="178" height="245"/>
<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="24" viewBased="YES" id="aTp-KR-y6b">
<rect key="frame" x="0.0" y="0.0" width="178" height="241"/>
<rect key="frame" x="0.0" y="0.0" width="178" height="245"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<size key="intercellSpacing" width="3" height="2"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
@@ -457,11 +432,11 @@
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
<prototypeCellViews>
<tableCellView identifier="Cell" id="h2e-5a-qNO" customClass="AccountCell" customModule="NetNewsWire" customModuleProvider="target">
<rect key="frame" x="1" y="1" width="146" height="17"/>
<rect key="frame" x="11" y="1" width="155" height="17"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="27f-p8-Wnt">
<rect key="frame" x="6" y="1" width="16" height="16"/>
<rect key="frame" x="6" y="-2" width="16" height="22"/>
<constraints>
<constraint firstAttribute="height" constant="16" id="ewi-ds-jXB"/>
<constraint firstAttribute="width" constant="16" id="k2v-Dn-07l"/>
@@ -469,7 +444,7 @@
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" image="NSActionTemplate" id="lKA-xK-bHU"/>
</imageView>
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" allowsExpansionToolTips="YES" translatesAutoresizingMaskIntoConstraints="NO" id="hR2-bm-0wE">
<rect key="frame" x="26" y="1" width="116" height="16"/>
<rect key="frame" x="26" y="1" width="125" height="16"/>
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Table View Cell" id="CcS-BO-sdv">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
@@ -545,7 +520,7 @@
<rect key="frame" x="83" y="20" width="117" height="24"/>
</customView>
<customView translatesAutoresizingMaskIntoConstraints="NO" id="Y7D-xQ-wep">
<rect key="frame" x="208" y="20" width="222" height="267"/>
<rect key="frame" x="208" y="20" width="222" height="271"/>
</customView>
</subviews>
<constraints>
@@ -600,16 +575,16 @@
<autoresizingMask key="autoresizingMask"/>
<subviews>
<customView translatesAutoresizingMaskIntoConstraints="NO" id="pjs-G4-byk" customClass="PreferencesTableViewBackgroundView" customModule="NetNewsWire" customModuleProvider="target">
<rect key="frame" x="20" y="44" width="180" height="243"/>
<rect key="frame" x="20" y="44" width="180" height="247"/>
<subviews>
<scrollView borderType="none" autohidesScrollers="YES" horizontalLineScroll="26" horizontalPageScroll="10" verticalLineScroll="26" verticalPageScroll="10" hasHorizontalScroller="NO" horizontalScrollElasticity="none" translatesAutoresizingMaskIntoConstraints="NO" id="29T-r2-ckC">
<rect key="frame" x="1" y="1" width="178" height="241"/>
<rect key="frame" x="1" y="1" width="178" height="245"/>
<clipView key="contentView" id="dXw-GY-TP8">
<rect key="frame" x="0.0" y="0.0" width="178" height="241"/>
<rect key="frame" x="0.0" y="0.0" width="178" height="245"/>
<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="24" viewBased="YES" id="dfn-Vn-oDp">
<rect key="frame" x="0.0" y="0.0" width="178" height="241"/>
<rect key="frame" x="0.0" y="0.0" width="178" height="245"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<size key="intercellSpacing" width="3" height="2"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
@@ -628,11 +603,11 @@
<tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/>
<prototypeCellViews>
<tableCellView identifier="Cell" id="xQs-6E-Kpy">
<rect key="frame" x="1" y="1" width="146" height="17"/>
<rect key="frame" x="11" y="1" width="155" height="17"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="kmG-vw-CbN">
<rect key="frame" x="6" y="1" width="16" height="16"/>
<rect key="frame" x="6" y="-2" width="16" height="22"/>
<constraints>
<constraint firstAttribute="height" constant="16" id="qC6-Mb-6EQ"/>
<constraint firstAttribute="width" constant="16" id="yi0-bd-XJq"/>
@@ -640,7 +615,7 @@
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" image="NSActionTemplate" id="OVD-Jo-TXU"/>
</imageView>
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" allowsExpansionToolTips="YES" translatesAutoresizingMaskIntoConstraints="NO" id="6cr-cB-qAN">
<rect key="frame" x="26" y="1" width="116" height="16"/>
<rect key="frame" x="26" y="1" width="125" height="16"/>
<textFieldCell key="cell" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Table View Cell" id="goO-QG-kk7">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
@@ -712,7 +687,7 @@
<rect key="frame" x="83" y="20" width="117" height="24"/>
</customView>
<customView translatesAutoresizingMaskIntoConstraints="NO" id="N1N-pE-gBL">
<rect key="frame" x="208" y="20" width="222" height="267"/>
<rect key="frame" x="208" y="20" width="222" height="271"/>
</customView>
</subviews>
<constraints>
@@ -747,8 +722,8 @@
</scene>
</scenes>
<resources>
<image name="NSActionTemplate" width="14" height="14"/>
<image name="NSAddTemplate" width="11" height="11"/>
<image name="NSRemoveTemplate" width="11" height="11"/>
<image name="NSActionTemplate" width="15" height="15"/>
<image name="NSAddTemplate" width="14" height="13"/>
<image name="NSRemoveTemplate" width="14" height="4"/>
</resources>
</document>

View File

@@ -42,7 +42,7 @@ struct CrashReporter {
}
static func sendCrashLogText(_ crashLogText: String) {
var request = URLRequest(url: URL(string: "https://ranchero.com/netnewswire/crashreportcatcher.php")!)
var request = URLRequest(url: URL(string: "https://services.netnewswire.com/reportCrash.php")!)
request.httpMethod = HTTPMethod.post
let boundary = "0xKhTmLbOuNdArY"

View File

@@ -71,7 +71,7 @@ class AddFeedController: AddFeedWindowControllerDelegate {
return
}
account.createWebFeed(url: url.absoluteString, name: title, container: container) { result in
account.createWebFeed(url: url.absoluteString, name: title, container: container, validateFeed: true) { result in
DispatchQueue.main.async {
self.endShowingProgress()

View File

@@ -101,7 +101,7 @@ extension DetailViewController: DetailWebViewControllerDelegate {
statusBarView.mouseoverLink = link
}
func mouseDidExit(_ detailWebViewController: DetailWebViewController, link: String) {
func mouseDidExit(_ detailWebViewController: DetailWebViewController) {
guard detailWebViewController === currentWebViewController else {
return
}

View File

@@ -53,6 +53,24 @@ final class DetailWebView: WKWebView {
override func viewDidEndLiveResize() {
super.viewDidEndLiveResize()
evaluateJavaScript("document.body.style.overflow = 'visible';", completionHandler: nil)
/*
On macOS 11, when a user exits full screen
or exits zoomed mode by disconnecting an external display
the webview's `origin.y` is offset by a sizeable amount.
This code adjusts the height of the window by -1pt/+1pt,
which puts the webview back in the correct place.
*/
if #available(macOS 11, *) {
guard var frame = window?.frame else {
return
}
frame.size = NSSize(width: window!.frame.width, height: window!.frame.height - 1)
window!.setFrame(frame, display: false)
frame.size = NSSize(width: window!.frame.width, height: window!.frame.height + 1)
window!.setFrame(frame, display: false)
}
}
}

View File

@@ -14,7 +14,7 @@ import Articles
protocol DetailWebViewControllerDelegate: AnyObject {
func mouseDidEnter(_: DetailWebViewController, link: String)
func mouseDidExit(_: DetailWebViewController, link: String)
func mouseDidExit(_: DetailWebViewController)
}
final class DetailWebViewController: NSViewController, WKUIDelegate {
@@ -63,16 +63,6 @@ final class DetailWebViewController: NSViewController, WKUIDelegate {
}
override func loadView() {
// Wrap the webview in a box configured with the same background color that the web view uses
let box = NSBox(frame: .zero)
box.boxType = .custom
box.isTransparent = true
box.titlePosition = .noTitle
box.contentViewMargins = .zero
box.fillColor = NSColor(named: "webviewBackgroundColor")!
view = box
let preferences = WKPreferences()
preferences.minimumFontSize = 12.0
preferences.javaScriptCanOpenWindowsAutomatically = false
@@ -96,17 +86,11 @@ final class DetailWebViewController: NSViewController, WKUIDelegate {
webView.customUserAgent = userAgent
}
box.addSubview(webView)
view = webView
// Use the safe area layout guides if they are available.
if #available(OSX 11.0, *) {
let constraints = [
webView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
webView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
webView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
webView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
]
NSLayoutConstraint.activate(constraints)
// These constraints have been removed as they were unsatisfiable after removing NSBox.
} else {
let constraints = [
webView.topAnchor.constraint(equalTo: view.topAnchor),
@@ -194,8 +178,8 @@ extension DetailWebViewController: WKScriptMessageHandler {
if message.name == MessageName.mouseDidEnter, let link = message.body as? String {
delegate?.mouseDidEnter(self, link: link)
}
else if message.name == MessageName.mouseDidExit, let link = message.body as? String{
delegate?.mouseDidExit(self, link: link)
else if message.name == MessageName.mouseDidExit {
delegate?.mouseDidExit(self)
}
}
}
@@ -251,6 +235,8 @@ private extension DetailWebViewController {
}
func reloadHTML() {
delegate?.mouseDidExit(self)
let style = ArticleStylesManager.shared.currentStyle
let rendering: ArticleRenderer.Rendering

View File

@@ -17,15 +17,15 @@ final class IconView: NSView {
if NSApplication.shared.effectiveAppearance.isDarkMode {
if self.iconImage?.isDark ?? false {
self.isDisconcernable = false
self.isDiscernable = false
} else {
self.isDisconcernable = true
self.isDiscernable = true
}
} else {
if self.iconImage?.isBright ?? false {
self.isDisconcernable = false
self.isDiscernable = false
} else {
self.isDisconcernable = true
self.isDiscernable = true
}
}
@@ -35,7 +35,7 @@ final class IconView: NSView {
}
}
private var isDisconcernable = true
private var isDiscernable = true
override var isFlipped: Bool {
return true
@@ -85,7 +85,7 @@ final class IconView: NSView {
override func draw(_ dirtyRect: NSRect) {
guard !(iconImage?.isBackgroundSupressed ?? false) else { return }
guard hasExposedVerticalBackground || !isDisconcernable else { return }
guard hasExposedVerticalBackground || !isDiscernable else { return }
let color = NSApplication.shared.effectiveAppearance.isDarkMode ? IconView.darkBackgroundColor : IconView.lightBackgroundColor
color.set()

View File

@@ -203,13 +203,13 @@ private extension SidebarOutlineDataSource {
return SidebarOutlineDataSource.dragOperationNone
}
if parentNode == dropTargetNode && index == NSOutlineViewDropOnItemIndex {
return localDragOperation()
return localDragOperation(parentNode: parentNode)
}
let updatedIndex = indexWhereDraggedFeedWouldAppear(dropTargetNode, draggedFeed)
if parentNode !== dropTargetNode || index != updatedIndex {
outlineView.setDropItem(dropTargetNode, dropChildIndex: updatedIndex)
}
return localDragOperation()
return localDragOperation(parentNode: parentNode)
}
func validateLocalFeedsDrop(_ outlineView: NSOutlineView, _ draggedFeeds: Set<PasteboardWebFeed>, _ parentNode: Node, _ index: Int) -> NSDragOperation {
@@ -226,14 +226,19 @@ private extension SidebarOutlineDataSource {
if parentNode !== dropTargetNode || index != NSOutlineViewDropOnItemIndex {
outlineView.setDropItem(dropTargetNode, dropChildIndex: NSOutlineViewDropOnItemIndex)
}
return localDragOperation()
return localDragOperation(parentNode: parentNode)
}
func localDragOperation() -> NSDragOperation {
if NSApplication.shared.currentEvent?.modifierFlags.contains(.option) ?? false {
return .copy
func localDragOperation(parentNode: Node) -> NSDragOperation {
guard let firstDraggedNode = draggedNodes?.first else { return .move }
if sameAccount(firstDraggedNode, parentNode) {
if NSApplication.shared.currentEvent?.modifierFlags.contains(.option) ?? false {
return .copy
} else {
return .move
}
} else {
return .move
return .copy
}
}
@@ -282,7 +287,7 @@ private extension SidebarOutlineDataSource {
if index != updatedIndex {
outlineView.setDropItem(parentNode, dropChildIndex: updatedIndex)
}
return localDragOperation()
return localDragOperation(parentNode: parentNode)
}
func validateLocalFoldersDrop(_ outlineView: NSOutlineView, _ draggedFolders: Set<PasteboardFolder>, _ parentNode: Node, _ index: Int) -> NSDragOperation {
@@ -300,7 +305,7 @@ private extension SidebarOutlineDataSource {
if index != NSOutlineViewDropOnItemIndex {
outlineView.setDropItem(parentNode, dropChildIndex: NSOutlineViewDropOnItemIndex)
}
return localDragOperation()
return localDragOperation(parentNode: parentNode)
}
func copyWebFeedInAccount(node: Node, to parentNode: Node) {
@@ -354,7 +359,7 @@ private extension SidebarOutlineDataSource {
}
}
} else {
destinationAccount.createWebFeed(url: feed.url, name: feed.editedName, container: destinationContainer) { result in
destinationAccount.createWebFeed(url: feed.url, name: feed.nameForDisplay, container: destinationContainer, validateFeed: false) { result in
switch result {
case .success:
break
@@ -365,60 +370,6 @@ private extension SidebarOutlineDataSource {
}
}
func moveWebFeedBetweenAccounts(node: Node, to parentNode: Node) {
guard let feed = node.representedObject as? WebFeed,
let sourceAccount = nodeAccount(node),
let sourceContainer = node.parent?.representedObject as? Container,
let destinationAccount = nodeAccount(parentNode),
let destinationContainer = parentNode.representedObject as? Container else {
return
}
if let existingFeed = destinationAccount.existingWebFeed(withURL: feed.url) {
BatchUpdate.shared.start()
destinationAccount.addWebFeed(existingFeed, to: destinationContainer) { result in
switch result {
case .success:
sourceAccount.removeWebFeed(feed, from: sourceContainer) { result in
BatchUpdate.shared.end()
switch result {
case .success:
break
case .failure(let error):
NSApplication.shared.presentError(error)
}
}
case .failure(let error):
BatchUpdate.shared.end()
NSApplication.shared.presentError(error)
}
}
} else {
BatchUpdate.shared.start()
destinationAccount.createWebFeed(url: feed.url, name: feed.editedName, container: destinationContainer) { result in
switch result {
case .success:
sourceAccount.removeWebFeed(feed, from: sourceContainer) { result in
BatchUpdate.shared.end()
switch result {
case .success:
break
case .failure(let error):
NSApplication.shared.presentError(error)
}
}
case .failure(let error):
BatchUpdate.shared.end()
NSApplication.shared.presentError(error)
}
}
}
}
func acceptLocalFeedsDrop(_ outlineView: NSOutlineView, _ draggedFeeds: Set<PasteboardWebFeed>, _ parentNode: Node, _ index: Int) -> Bool {
guard let draggedNodes = draggedNodes else {
return false
@@ -432,11 +383,7 @@ private extension SidebarOutlineDataSource {
moveWebFeedInAccount(node: node, to: parentNode)
}
} else {
if NSApplication.shared.currentEvent?.modifierFlags.contains(.option) ?? false {
copyWebFeedBetweenAccounts(node: node, to: parentNode)
} else {
moveWebFeedBetweenAccounts(node: node, to: parentNode)
}
copyWebFeedBetweenAccounts(node: node, to: parentNode)
}
}
@@ -476,42 +423,17 @@ private extension SidebarOutlineDataSource {
}
func copyFolderBetweenAccounts(node: Node, to parentNode: Node) {
guard let sourceFolder = node.representedObject as? Folder,
guard let folder = node.representedObject as? Folder,
let destinationAccount = nodeAccount(parentNode) else {
return
}
replicateFolder(sourceFolder, destinationAccount: destinationAccount, completion: {})
}
func moveFolderBetweenAccounts(node: Node, to parentNode: Node) {
guard let sourceFolder = node.representedObject as? Folder,
let sourceAccount = nodeAccount(node),
let destinationAccount = nodeAccount(parentNode) else {
return
}
replicateFolder(sourceFolder, destinationAccount: destinationAccount) {
sourceAccount.removeFolder(sourceFolder) { result in
switch result {
case .success:
break
case .failure(let error):
NSApplication.shared.presentError(error)
}
}
}
}
func replicateFolder(_ folder: Folder, destinationAccount: Account, completion: @escaping () -> Void) {
destinationAccount.addFolder(folder.name ?? "") { result in
switch result {
case .success(let destinationFolder):
let group = DispatchGroup()
for feed in folder.topLevelWebFeeds {
if let existingFeed = destinationAccount.existingWebFeed(withURL: feed.url) {
group.enter()
destinationAccount.addWebFeed(existingFeed, to: destinationFolder) { result in
group.leave()
switch result {
case .success:
break
@@ -520,9 +442,7 @@ private extension SidebarOutlineDataSource {
}
}
} else {
group.enter()
destinationAccount.createWebFeed(url: feed.url, name: feed.editedName, container: destinationFolder) { result in
group.leave()
destinationAccount.createWebFeed(url: feed.url, name: feed.nameForDisplay, container: destinationFolder, validateFeed: false) { result in
switch result {
case .success:
break
@@ -532,12 +452,8 @@ private extension SidebarOutlineDataSource {
}
}
}
group.notify(queue: DispatchQueue.main) {
completion()
}
case .failure(let error):
NSApplication.shared.presentError(error)
completion()
}
}
@@ -550,11 +466,7 @@ private extension SidebarOutlineDataSource {
draggedNodes.forEach { node in
if !sameAccount(node, parentNode) {
if NSApplication.shared.currentEvent?.modifierFlags.contains(.option) ?? false {
copyFolderBetweenAccounts(node: node, to: parentNode)
} else {
moveFolderBetweenAccounts(node: node, to: parentNode)
}
copyFolderBetweenAccounts(node: node, to: parentNode)
}
}

View File

@@ -255,6 +255,11 @@ protocol SidebarDelegate: AnyObject {
guard outlineView.clickedRow == outlineView.selectedRow else {
return
}
if AppDefaults.shared.feedDoubleClickMarkAsRead, let articles = try? singleSelectedWebFeed?.fetchUnreadArticles() {
if let undoManager = undoManager, let markReadCommand = MarkStatusCommand(initialArticles: Array(articles), markingRead: true, undoManager: undoManager) {
runCommand(markReadCommand)
}
}
openInBrowser(sender)
}

View File

@@ -16,7 +16,6 @@ final class GeneralPreferencesViewController: NSViewController {
private var userNotificationSettings: UNNotificationSettings?
@IBOutlet var defaultBrowserPopup: NSPopUpButton!
@IBOutlet weak var showUnreadCountCheckbox: NSButton!
public override init(nibName nibNameOrNil: NSNib.Name?, bundle nibBundleOrNil: Bundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
@@ -51,46 +50,6 @@ final class GeneralPreferencesViewController: NSViewController {
updateUI()
}
@IBAction func toggleShowingUnreadCount(_ sender: Any) {
guard let checkbox = sender as? NSButton else { return }
guard userNotificationSettings != nil else {
DispatchQueue.main.async {
self.showUnreadCountCheckbox.setNextState()
}
return
}
UNUserNotificationCenter.current().getNotificationSettings { (settings) in
self.updateNotificationSettings()
if settings.authorizationStatus == .denied {
DispatchQueue.main.async {
self.showUnreadCountCheckbox.setNextState()
self.showNotificationsDeniedError()
}
} else if settings.authorizationStatus == .authorized {
DispatchQueue.main.async {
AppDefaults.shared.hideDockUnreadCount = (checkbox.state.rawValue == 0)
}
} else {
UNUserNotificationCenter.current().requestAuthorization(options: [.badge]) { (granted, error) in
self.updateNotificationSettings()
if granted {
DispatchQueue.main.async {
AppDefaults.shared.hideDockUnreadCount = checkbox.state.rawValue == 0
NSApplication.shared.registerForRemoteNotifications()
}
} else {
DispatchQueue.main.async {
self.showUnreadCountCheckbox.setNextState()
}
}
}
}
}
}
}
// MARK: - Private
@@ -103,7 +62,6 @@ private extension GeneralPreferencesViewController {
func updateUI() {
updateBrowserPopup()
updateHideUnreadCountCheckbox()
}
func registerAppWithBundleID(_ bundleID: String) {
@@ -144,10 +102,6 @@ private extension GeneralPreferencesViewController {
defaultBrowserPopup.selectItem(at: defaultBrowserPopup.indexOfItem(withRepresentedObject: AppDefaults.shared.defaultBrowserID))
}
func updateHideUnreadCountCheckbox() {
showUnreadCountCheckbox.state = AppDefaults.shared.hideDockUnreadCount ? .off : .on
}
func updateNotificationSettings() {
UNUserNotificationCenter.current().getNotificationSettings { (settings) in
self.userNotificationSettings = settings

View File

@@ -1,38 +0,0 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
},
"colors" : [
{
"idiom" : "universal",
"color" : {
"color-space" : "srgb",
"components" : {
"red" : "1.000",
"alpha" : "1.000",
"blue" : "1.000",
"green" : "1.000"
}
}
},
{
"idiom" : "universal",
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "display-p3",
"components" : {
"red" : "0.176",
"alpha" : "1.000",
"blue" : "0.176",
"green" : "0.176"
}
}
}
]
}

View File

@@ -41,7 +41,7 @@
<key>DeveloperEntitlements</key>
<string>$(DEVELOPER_ENTITLEMENTS)</string>
<key>FeedURLForTestBuilds</key>
<string>https://ranchero.com/downloads/netnewswire6-beta.xml</string>
<string>https://ranchero.com/downloads/netnewswire-beta.xml</string>
<key>LSApplicationCategoryType</key>
<string>public.app-category.news</string>
<key>LSMinimumSystemVersion</key>
@@ -72,6 +72,6 @@
<key>SUFeedURL</key>
<string>https://ranchero.com/downloads/netnewswire-release.xml</string>
<key>UserAgent</key>
<string>NetNewsWire (RSS Reader; https://ranchero.com/netnewswire/)</string>
<string>NetNewsWire (RSS Reader; https://netnewswire.com/)</string>
</dict>
</plist>

View File

@@ -102,7 +102,7 @@ class ScriptableWebFeed: NSObject, UniqueIdScriptingObject, ScriptingObjectConta
// suspendExecution(). When we get the callback, we supply the event result and call resumeExecution().
command.suspendExecution()
account.createWebFeed(url: url, name: titleFromArgs, container: container) { result in
account.createWebFeed(url: url, name: titleFromArgs, container: container, validateFeed: true) { result in
switch result {
case .success(let feed):
NotificationCenter.default.post(name: .UserDidAddFeed, object: self, userInfo: [UserInfoKey.webFeed: feed])

View File

@@ -17,18 +17,18 @@ final class IconView: UIView {
if self.traitCollection.userInterfaceStyle == .dark {
if self.iconImage?.isDark ?? false {
self.isDisconcernable = false
self.isDiscernable = false
self.setNeedsLayout()
} else {
self.isDisconcernable = true
self.isDiscernable = true
self.setNeedsLayout()
}
} else {
if self.iconImage?.isBright ?? false {
self.isDisconcernable = false
self.isDiscernable = false
self.setNeedsLayout()
} else {
self.isDisconcernable = true
self.isDiscernable = true
self.setNeedsLayout()
}
}
@@ -37,7 +37,7 @@ final class IconView: UIView {
}
}
private var isDisconcernable = true
private var isDiscernable = true
private let imageView: UIImageView = {
let imageView = UIImageView(image: AppAssets.faviconTemplateImage)
@@ -79,7 +79,7 @@ final class IconView: UIView {
override func layoutSubviews() {
imageView.setFrameIfNotEqual(rectForImageView())
if (iconImage != nil && isVerticalBackgroundExposed) || !isDisconcernable {
if (iconImage != nil && isVerticalBackgroundExposed) || !isDiscernable {
backgroundColor = AppAssets.uiIconBackgroundColor
} else {
backgroundColor = nil

View File

@@ -72,7 +72,7 @@
<string>remote-notification</string>
</array>
<key>UserAgent</key>
<string>NetNewsWire (RSS Reader; https://ranchero.com/netnewswire/)</string>
<string>NetNewsWire (RSS Reader; https://netnewswire.com/)</string>
<key>OrganizationIdentifier</key>
<string>$(ORGANIZATION_IDENTIFIER)</string>
<key>DeveloperEntitlements</key>

View File

@@ -58,6 +58,6 @@ struct SafariView: View {
struct SafariView_Previews: PreviewProvider {
static var previews: some View {
SafariView(url: URL(string: "https://ranchero.com/netnewswire/")!)
SafariView(url: URL(string: "https://netnewswire.com/")!)
}
}

View File

@@ -1,4 +1,4 @@
{\rtf1\ansi\ansicpg1252\cocoartf2511
{\rtf1\ansi\ansicpg1252\cocoartf2513
\cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fnil\fcharset0 LucidaGrande-Bold;}
{\colortbl;\red255\green255\blue255;\red0\green0\blue0;\red10\green96\blue255;}
{\*\expandedcolortbl;;\cssrgb\c0\c0\c0;\cssrgb\c0\c47843\c100000\cname systemBlueColor;}
@@ -9,4 +9,4 @@
\fs22 \
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0
{\field{\*\fldinst{HYPERLINK "https://ranchero.com/netnewswire/"}}{\fldrslt
\fs28 \cf3 ranchero.com/netnewswire/}}}
\fs28 \cf3 netnewswire.com}}}

View File

@@ -17,9 +17,9 @@ class SettingsModel: ObservableObject {
var url: URL? {
switch self {
case .netNewsWireHelp:
return URL(string: "https://ranchero.com/netnewswire/help/ios/5.0/en/")!
return URL(string: "https://netnewswire.com/help/ios/5.0/en/")!
case .netNewsWire:
return URL(string: "https://ranchero.com/netnewswire/")!
return URL(string: "https://netnewswire.com/")!
case .supportNetNewsWire:
return URL(string: "https://github.com/brentsimmons/NetNewsWire/blob/main/Technotes/HowToSupportNetNewsWire.markdown")!
case .github:
@@ -29,7 +29,7 @@ class SettingsModel: ObservableObject {
case .technotes:
return URL(string: "https://github.com/brentsimmons/NetNewsWire/tree/main/Technotes")!
case .netNewsWireSlack:
return URL(string: "https://ranchero.com/netnewswire/slack")!
return URL(string: "https://netnewswire.com/slack")!
case .releaseNotes:
return URL.releaseNotes
case .none:

View File

@@ -17,15 +17,15 @@ final class IconView: NSView {
if NSApplication.shared.effectiveAppearance.isDarkMode {
if self.iconImage?.isDark ?? false {
self.isDisconcernable = false
self.isDiscernable = false
} else {
self.isDisconcernable = true
self.isDiscernable = true
}
} else {
if self.iconImage?.isBright ?? false {
self.isDisconcernable = false
self.isDiscernable = false
} else {
self.isDisconcernable = true
self.isDiscernable = true
}
}
@@ -35,7 +35,7 @@ final class IconView: NSView {
}
}
private var isDisconcernable = true
private var isDiscernable = true
override var isFlipped: Bool {
return true
@@ -81,7 +81,7 @@ final class IconView: NSView {
}
override func draw(_ dirtyRect: NSRect) {
guard hasExposedVerticalBackground || !isDisconcernable else {
guard hasExposedVerticalBackground || !isDiscernable else {
return
}

View File

@@ -30,7 +30,7 @@
<true/>
</dict>
<key>UserAgent</key>
<string>NetNewsWire (RSS Reader; https://ranchero.com/netnewswire/)</string>
<string>NetNewsWire (RSS Reader; https://netnewswire.com/)</string>
<key>OrganizationIdentifier</key>
<string>$(ORGANIZATION_IDENTIFIER)</string>
<key>DeveloperEntitlements</key>

View File

@@ -30,7 +30,7 @@ struct AdvancedPreferencesView: View {
HStack {
Spacer()
Button("Privacy Policy", action: {
NSWorkspace.shared.open(URL(string: "https://ranchero.com/netnewswire/privacypolicy")!)
NSWorkspace.shared.open(URL(string: "https://netnewswire.com/privacypolicy")!)
})
Spacer()
}

View File

@@ -60,8 +60,8 @@
"repositoryURL": "https://github.com/Ranchero-Software/RSCore.git",
"state": {
"branch": null,
"revision": "6b2ef2580968905af825c40442dc0ba3126032c0",
"version": "1.0.2"
"revision": "665319af9428455e45c1d043156db85548b73f31",
"version": "1.0.5"
}
},
{
@@ -96,8 +96,8 @@
"repositoryURL": "https://github.com/Ranchero-Software/RSWeb.git",
"state": {
"branch": null,
"revision": "2f7bc7671a751e994e2567c8221ba64e884d5583",
"version": "1.0.0"
"revision": "2f9ad98736c5c17dfb2be0b3cc06e71a49b061fa",
"version": "1.0.1"
}
},
{

View File

@@ -4,7 +4,7 @@ Its a free and open source feed reader for macOS and iOS.
It supports [RSS](http://cyber.harvard.edu/rss/rss.html), [Atom](https://tools.ietf.org/html/rfc4287), [JSON Feed](https://jsonfeed.org/), and [RSS-in-JSON](https://github.com/scripting/Scripting-News/blob/master/rss-in-json/README.md) formats.
More info: [https://ranchero.com/netnewswire/](https://ranchero.com/netnewswire/)
More info: [https://netnewswire.com/](https://netnewswire.com/)
Also see the [Technotes](Technotes/) and the [Roadmap](Technotes/Roadmap.md).
@@ -14,7 +14,7 @@ Heres [How to Support NetNewsWire](Technotes/HowToSupportNetNewsWire.markdown
#### Community
[Join the Slack group](https://ranchero.com/netnewswire/slack) to talk with other NetNewsWire users — and to help out, if youd like to, by testing, coding, writing, providing feedback, or just helping us think things through. Everybody is welcome and encouraged to join.
[Join the Slack group](https://netnewswire.com/slack) to talk with other NetNewsWire users — and to help out, if youd like to, by testing, coding, writing, providing feedback, or just helping us think things through. Everybody is welcome and encouraged to join.
Every community member is expected to abide by the code of conduct which is included in the [Contributing](CONTRIBUTING.md) page.

View File

@@ -139,7 +139,6 @@ pre {
margin: 0;
overflow: auto;
overflow-y: hidden;
word-wrap: normal;
word-break: normal;
}
@@ -147,12 +146,18 @@ pre {
pre {
line-height: 1.4286em;
}
code, pre {
font-family: "SF Mono", Menlo, "Courier New", Courier, monospace;
font-size: .8235em;
font-size: 1em;
-webkit-hyphens: none;
}
pre code {
letter-spacing: -.027em;
font-size: 0.9375em;
}
.nnw-overflow {
overflow-x: auto;
}

View File

@@ -24,7 +24,7 @@ struct ArticleStyle: Equatable {
self.path = nil;
self.emptyCSS = nil
self.info = ["CreatorHomePage": "https://ranchero.com/", "CreatorName": "Ranchero Software, LLC", "Version": "1.0"]
self.info = ["CreatorHomePage": "https://netnewswire.com/", "CreatorName": "Ranchero Software", "Version": "1.0"]
let sharedCSSPath = Bundle.main.path(forResource: "shared", ofType: "css")!
let platformCSSPath = Bundle.main.path(forResource: "styleSheet", ofType: "css")!

View File

@@ -11,10 +11,20 @@ import AppKit
extension NSView {
func constraintsToMakeSubViewFullSize(_ subview: NSView) -> [NSLayoutConstraint] {
let leadingConstraint = NSLayoutConstraint(item: subview, attribute: .leading, relatedBy: .equal, toItem: self, attribute: .leading, multiplier: 1.0, constant: 0.0)
let trailingConstraint = NSLayoutConstraint(item: subview, attribute: .trailing, relatedBy: .equal, toItem: self, attribute: .trailing, multiplier: 1.0, constant: 0.0)
let topConstraint = NSLayoutConstraint(item: subview, attribute: .top, relatedBy: .equal, toItem: self, attribute: .top, multiplier: 1.0, constant: 0.0)
let bottomConstraint = NSLayoutConstraint(item: subview, attribute: .bottom, relatedBy: .equal, toItem: self, attribute: .bottom, multiplier: 1.0, constant: 0.0)
return [leadingConstraint, trailingConstraint, topConstraint, bottomConstraint]
if #available(macOS 11, *) {
let leadingConstraint = NSLayoutConstraint(item: subview, attribute: .leading, relatedBy: .equal, toItem: self.safeAreaLayoutGuide, attribute: .leading, multiplier: 1.0, constant: 0.0)
let trailingConstraint = NSLayoutConstraint(item: subview, attribute: .trailing, relatedBy: .equal, toItem: self.safeAreaLayoutGuide, attribute: .trailing, multiplier: 1.0, constant: 0.0)
let topConstraint = NSLayoutConstraint(item: subview, attribute: .top, relatedBy: .equal, toItem: self.safeAreaLayoutGuide, attribute: .top, multiplier: 1.0, constant: 0.0)
let bottomConstraint = NSLayoutConstraint(item: subview, attribute: .bottom, relatedBy: .equal, toItem: self.safeAreaLayoutGuide, attribute: .bottom, multiplier: 1.0, constant: 0.0)
return [leadingConstraint, trailingConstraint, topConstraint, bottomConstraint]
} else {
let leadingConstraint = NSLayoutConstraint(item: subview, attribute: .leading, relatedBy: .equal, toItem: self, attribute: .leading, multiplier: 1.0, constant: 0.0)
let trailingConstraint = NSLayoutConstraint(item: subview, attribute: .trailing, relatedBy: .equal, toItem: self, attribute: .trailing, multiplier: 1.0, constant: 0.0)
let topConstraint = NSLayoutConstraint(item: subview, attribute: .top, relatedBy: .equal, toItem: self, attribute: .top, multiplier: 1.0, constant: 0.0)
let bottomConstraint = NSLayoutConstraint(item: subview, attribute: .bottom, relatedBy: .equal, toItem: self, attribute: .bottom, multiplier: 1.0, constant: 0.0)
return [leadingConstraint, trailingConstraint, topConstraint, bottomConstraint]
}
}
}

View File

@@ -154,7 +154,7 @@ private extension ExtensionFeedAddRequestFile {
guard let container = destinationContainer else { return }
account.createWebFeed(url: request.feedURL.absoluteString, name: request.name, container: container) { _ in }
account.createWebFeed(url: request.feedURL.absoluteString, name: request.name, container: container, validateFeed: true) { _ in }
}
}

View File

@@ -1,5 +1,71 @@
# Mac Release Notes
### 6.0 build 6027 - 26 Mar 2021
No code changes since 6.0b5
Changed the feed URL for test builds back to the normal test build feed URL
### 6.0b5 build 6026 - 25 Mar 2021
Fixed a bug where sometimes the app wouldnt automatically refresh after the Mac wakes from sleep
Updated the Help book link to the 6.0 Mac help book website
App now displays a helpful error message if you dont have iCloud Drive enabled and were able to successfully add an iCloud Account
### 6.0b4 build 6024 - 23 Mar 2021
Feedly: Deleting a feed successfully no longer shows an alert and leaves the feed in the sidebar
iCloud sync: fixed a bug where, in some circumstances, dragging a feed from elsewhere in the sidebar to the iCloud account could trigger the feed-finder
NetNewsWire will now refresh on launch if you have the Debug menu enabled
Article view: footnotes should now work with articles from a Feedly account
### 6.0b3 build 6023 - 16 Mar 2021
Article view: fixed bug where URL status field might not disappear when switching articles
iCloud sync: dragging feeds from elsewhere in the sidebar to the iCloud account wont trigger the feed-finding process since this is a better experience for migrating
Syncing: fixed a bug authenticating with some sync services when the user has some special characters in their password
Preferences: removed checkbox for showing unread count in the Dock — control this instead via System Preferences > Notifications > NetNewsWire > Badge app icon
### 6.0b2 build 6022 - 13 Mar 2021
Feeds list: when dragging feeds/folders from one account to another, the operation is now *always* copy, to avoid data loss due to misunderstanding that moving a feed between accounts does not move its read/starred statuses
iCloud sync: refined logic to improve performance of large uploads
Fixed a crashing bug that could happen when deleting an iCloud-synced folder
Fixed a crashing bug, triggered by bad server data, that could happen when validating credentials with syncing systems that use the Reader API
### 6.0b1 build 6012 - 7 Mar 2021
Article view: fixed several layout edge cases, including with fullscreen
Timeline: fixed a bug scrolling up via arrow key where a row might not be fully visible when it should be
### 6.0a6 build 6011 - 6 Mar 2021
Article view: make code and preformatted fonts and sizes follow Apples precedents more closely
Article view: removed a stray line next to the timeline/article separator
Debug menu: add Force Crash command (beware: works in production)
Debug menu: allow Test Crash Log Sender to work in production
### 6.0a5 build 6010 - 3 Mar 2021
Performance boost: use compression with content synced in CloudKit
Fixed bug where detail view title bar could be overlapped by toolbar when in full screen
Fixed bug where add-feed window could block when syncing CloudKit statuses
Added hidden pref to mark all as read in a feed when double-clicking on it in the sidebar and opening its home page (defaults write com.ranchero.NetNewsWire-Evergreen GruberFeedDoubleClickMarkAsRead -bool true)
Switched the crash log catcher URL to our brand-new crash log catcher server
### 6.0a4 build 6009 - 22 Feb 2021
Fix a bug with keyboard shortcuts on Big Sur (for real this time)
Change drag-and-drop behavior to default to copy when dragging between accounts
Show a single error message when dragging feeds into an account and some of the feeds cant be found
### 6.0a3 build 6008 - 21 Feb 2021
Use the new URL for the crash report catcher (so that we actually get crash logs again)
Update other URLs to point to netnewswire.com when correct
Fix a bug with keyboard shortcuts on Big Sur
Show folders more quickly in the iCloud account when dragging a folder into that account
### 6.0a2 build 6007 - 6 Feb 2021
Fix regression in Preferences toolbar (placement of icons was wrong on Big Sur)

View File

@@ -17,18 +17,18 @@ final class IconView: UIView {
if self.traitCollection.userInterfaceStyle == .dark {
if self.iconImage?.isDark ?? false {
self.isDisconcernable = false
self.isDiscernable = false
self.setNeedsLayout()
} else {
self.isDisconcernable = true
self.isDiscernable = true
self.setNeedsLayout()
}
} else {
if self.iconImage?.isBright ?? false {
self.isDisconcernable = false
self.isDiscernable = false
self.setNeedsLayout()
} else {
self.isDisconcernable = true
self.isDiscernable = true
self.setNeedsLayout()
}
}
@@ -37,7 +37,7 @@ final class IconView: UIView {
}
}
private var isDisconcernable = true
private var isDiscernable = true
private let imageView: UIImageView = {
let imageView = NonIntrinsicImageView(image: AppAssets.faviconTemplateImage)
@@ -79,7 +79,7 @@ final class IconView: UIView {
override func layoutSubviews() {
imageView.setFrameIfNotEqual(rectForImageView())
if !isBackgroundSuppressed && ((iconImage != nil && isVerticalBackgroundExposed) || !isDisconcernable) {
if !isBackgroundSuppressed && ((iconImage != nil && isVerticalBackgroundExposed) || !isDiscernable) {
backgroundColor = AppAssets.iconBackgroundColor
} else {
backgroundColor = nil

View File

@@ -1,4 +1,4 @@
{\rtf1\ansi\ansicpg1252\cocoartf2511
{\rtf1\ansi\ansicpg1252\cocoartf2513
\cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fnil\fcharset0 LucidaGrande-Bold;}
{\colortbl;\red255\green255\blue255;\red0\green0\blue0;\red10\green96\blue255;}
{\*\expandedcolortbl;;\cssrgb\c0\c0\c0;\cssrgb\c0\c47843\c100000\cname systemBlueColor;}
@@ -8,5 +8,5 @@
\f0\b\fs28 \cf2 By Brent Simmons and the Ranchero Software team
\fs22 \
\pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\partightenfactor0
{\field{\*\fldinst{HYPERLINK "https://ranchero.com/netnewswire/"}}{\fldrslt
\fs28 \cf3 ranchero.com/netnewswire/}}}
{\field{\*\fldinst{HYPERLINK "https://netnewswire.com/"}}{\fldrslt
\fs28 \cf3 netnewswire.com}}}

View File

@@ -176,6 +176,6 @@
</dict>
</array>
<key>UserAgent</key>
<string>NetNewsWire (RSS Reader; https://ranchero.com/netnewswire/)</string>
<string>NetNewsWire (RSS Reader; https://netnewswire.com/)</string>
</dict>
</plist>

View File

@@ -226,10 +226,10 @@ class SettingsViewController: UITableViewController {
case 7:
switch indexPath.row {
case 0:
openURL("https://ranchero.com/netnewswire/help/ios/5.0/en/")
openURL("https://netnewswire.com/help/ios/5.0/en/")
tableView.selectRow(at: nil, animated: true, scrollPosition: .none)
case 1:
openURL("https://ranchero.com/netnewswire/")
openURL("https://netnewswire.com/")
tableView.selectRow(at: nil, animated: true, scrollPosition: .none)
case 2:
openURL(URL.releaseNotes.absoluteString)
@@ -247,7 +247,7 @@ class SettingsViewController: UITableViewController {
openURL("https://github.com/brentsimmons/NetNewsWire/tree/main/Technotes")
tableView.selectRow(at: nil, animated: true, scrollPosition: .none)
case 7:
openURL("https://ranchero.com/netnewswire/slack")
openURL("https://netnewswire.com/slack")
tableView.selectRow(at: nil, animated: true, scrollPosition: .none)
case 8:
let timeline = UIStoryboard.settings.instantiateController(ofType: AboutViewController.self)

View File

@@ -1,6 +1,6 @@
// High Level Settings common to both the Mac application and any extensions we bundle with it
MARKETING_VERSION = 6.0a2
CURRENT_PROJECT_VERSION = 6007
MARKETING_VERSION = 6.0
CURRENT_PROJECT_VERSION = 6026
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;