mirror of
https://github.com/Ranchero-Software/NetNewsWire
synced 2025-08-12 06:26:36 +00:00
Merge branch 'mac-release' into main
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(()))
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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(()))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(()))
|
||||
|
||||
@@ -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(()))
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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?) {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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/")!)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}}}
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -4,7 +4,7 @@ It’s 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 @@ Here’s [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 you’d 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 you’d 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.
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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")!
|
||||
|
||||
@@ -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]
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 wouldn’t 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 don’t 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 won’t 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 Apple’s 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 can’t 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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}}}
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user