mirror of
https://github.com/Ranchero-Software/NetNewsWire.git
synced 2025-02-01 11:36:56 +01:00
Merge branch 'main' of https://github.com/Ranchero-Software/NetNewsWire
This commit is contained in:
commit
0e7ef5f82e
@ -16,7 +16,7 @@ public enum AccountError: LocalizedError {
|
||||
case opmlImportInProgress
|
||||
case wrappedError(error: Error, account: Account)
|
||||
|
||||
public var acount: Account? {
|
||||
public var account: Account? {
|
||||
if case .wrappedError(_, let account) = self {
|
||||
return account
|
||||
} else {
|
||||
|
@ -49,7 +49,7 @@ class CloudKitArticlesZoneDelegate: CloudKitZoneDelegate {
|
||||
}
|
||||
|
||||
case .failure(let error):
|
||||
os_log(.error, log: self.log, "Error occurred geting pending starred records: %@", error.localizedDescription)
|
||||
os_log(.error, log: self.log, "Error occurred getting pending starred records: %@", error.localizedDescription)
|
||||
completion(.failure(CloudKitZoneError.unknown))
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,7 @@
|
||||
import Foundation
|
||||
import RSWeb
|
||||
|
||||
// Combines the refresh progress of mutliple accounts into one struct,
|
||||
// Combines the refresh progress of multiple accounts into one struct,
|
||||
// for use by refresh status view and so on.
|
||||
|
||||
public struct CombinedRefreshProgress {
|
||||
|
@ -20,7 +20,7 @@ struct FeedlyFeedResourceId: FeedlyResourceId {
|
||||
let id: String
|
||||
|
||||
/// The location of the kind of resource a concrete type represents.
|
||||
/// If the conrete type cannot strip the resource type from the Id, it should just return the Id
|
||||
/// If the concrete type cannot strip the resource type from the Id, it should just return the Id
|
||||
/// since the Id is a legitimate URL.
|
||||
/// This is basically assuming Feedly prefixes source feed URLs with `feed/`.
|
||||
/// It is not documented as such and could potentially change.
|
||||
|
@ -35,7 +35,7 @@ public protocol OAuthAcessTokenRefreshRequesting {
|
||||
|
||||
/// Access tokens expire. Perform a request for a fresh access token given the long life refresh token received when authorization was granted.
|
||||
/// - Parameter refreshRequest: The refresh token and other information the authorization server requires to grant the client fresh access tokens on the user's behalf.
|
||||
/// - Parameter completion: On success, the access token response appropriate for concrete type's service. Both the access and refresh token should be stored, preferrably on the Keychain. On failure, possibly a `URLError` or `OAuthAuthorizationErrorResponse` value.
|
||||
/// - Parameter completion: On success, the access token response appropriate for concrete type's service. Both the access and refresh token should be stored, preferably on the Keychain. On failure, possibly a `URLError` or `OAuthAuthorizationErrorResponse` value.
|
||||
func refreshAccessToken(_ refreshRequest: OAuthRefreshAccessTokenRequest, completion: @escaping (Result<AccessTokenResponse, Error>) -> ())
|
||||
}
|
||||
|
||||
|
@ -13,7 +13,7 @@ extension OAuthAuthorizationClient {
|
||||
|
||||
static var feedlyCloudClient: OAuthAuthorizationClient {
|
||||
/// Models private NetNewsWire client secrets.
|
||||
/// These placeholders are substitued at build time using a Run Script phase with build settings.
|
||||
/// These placeholders are substituted at build time using a Run Script phase with build settings.
|
||||
/// https://developer.feedly.com/v3/auth/#authenticating-a-user-and-obtaining-an-auth-code
|
||||
return OAuthAuthorizationClient(id: SecretsManager.provider.feedlyClientId,
|
||||
redirectUri: "netnewswire://auth/feedly",
|
||||
|
@ -131,7 +131,7 @@ public struct OAuthAccessTokenRequest: Encodable {
|
||||
}
|
||||
|
||||
/// Models the minimum subset of properties of a response in section 4.1.4 of the OAuth 2.0 Authorization Framework
|
||||
/// Concrete types model other paramters beyond the scope of the OAuth spec.
|
||||
/// Concrete types model other parameters beyond the scope of the OAuth spec.
|
||||
/// For example, Feedly provides the ID of the user who has consented to the grant.
|
||||
/// https://tools.ietf.org/html/rfc6749#section-4.1.4
|
||||
public protocol OAuthAccessTokenResponse {
|
||||
|
@ -81,7 +81,7 @@ final class FeedlyCreateFeedsForCollectionFoldersOperation: FeedlyOperation {
|
||||
}
|
||||
}
|
||||
|
||||
// no exsiting feed, create a new one
|
||||
// no existing feed, create a new one
|
||||
let parser = FeedlyFeedParser(feed: collectionFeed)
|
||||
let feed = account.createWebFeed(with: parser.title,
|
||||
url: parser.url,
|
||||
|
@ -61,7 +61,7 @@ class FeedlyGetStreamContentsOperationTests: XCTestCase {
|
||||
service.mockResult = .success(mockStream)
|
||||
service.getStreamContentsExpectation = expectation(description: "Did Call Service")
|
||||
service.parameterTester = { serviceResource, serviceContinuation, serviceNewerThan, serviceUnreadOnly in
|
||||
// Verify these values given to the opeartion are passed to the service.
|
||||
// Verify these values given to the operation are passed to the service.
|
||||
XCTAssertEqual(serviceResource.id, resource.id)
|
||||
XCTAssertEqual(serviceContinuation, continuation)
|
||||
XCTAssertEqual(serviceNewerThan, newerThan)
|
||||
|
@ -61,7 +61,7 @@ class FeedlyGetStreamIdsOperationTests: XCTestCase {
|
||||
service.mockResult = .success(mockStreamIds)
|
||||
service.getStreamIdsExpectation = expectation(description: "Did Call Service")
|
||||
service.parameterTester = { serviceResource, serviceContinuation, serviceNewerThan, serviceUnreadOnly in
|
||||
// Verify these values given to the opeartion are passed to the service.
|
||||
// Verify these values given to the operation are passed to the service.
|
||||
XCTAssertEqual(serviceResource.id, resource.id)
|
||||
XCTAssertEqual(serviceContinuation, continuation)
|
||||
XCTAssertEqual(serviceNewerThan, newerThan)
|
||||
|
@ -1,6 +1,6 @@
|
||||
-- This script creates an Excel spreadsheet with statistics about all the feeds in your NetNewsWire
|
||||
|
||||
-- the exportToExcel() function creats a single line of data in a spreadsheet
|
||||
-- the exportToExcel() function creates a single line of data in a spreadsheet
|
||||
|
||||
to exportToExcel(docname, rowIndex, feedname, numArticles, numStars, numRead)
|
||||
tell application "Microsoft Excel"
|
||||
|
@ -1408,7 +1408,7 @@ private extension MainWindowController {
|
||||
let sidebarWidth: Int = widths[0]
|
||||
let timelineWidth: Int = widths[1]
|
||||
|
||||
// Make sure the detail view has its mimimum thickness, at least.
|
||||
// Make sure the detail view has its minimum thickness, at least.
|
||||
if windowWidth < sidebarWidth + dividerThickness + timelineWidth + dividerThickness + MainWindowController.detailViewMinimumThickness {
|
||||
return
|
||||
}
|
||||
|
@ -90,7 +90,7 @@ function messageHandler(event) {
|
||||
|
||||
// There is a bug in Safari where the messageHandler is apparently held on to by Safari
|
||||
// even after an extension is disabled. So an effort to "ping" an extension's scripts will
|
||||
// succeed even if its been disabled and the page reloaded. Checking for the existance of
|
||||
// succeed even if its been disabled and the page reloaded. Checking for the existence of
|
||||
// document.location seems to ensure we have enough of a handle still on the document that
|
||||
// we can do something useful with it.
|
||||
var shouldValidate = (document.location != null) && (thisPageLinkObjects.length > 0);
|
||||
|
@ -156,7 +156,7 @@ typedef void (^OnePasswordExtensionItemCompletionBlock)(NSExtensionItem * __null
|
||||
|
||||
@param loginDetailsDictionary about the Login to be saved, including old password and the username, are stored in an dictionary and given to the 1Password Extension.
|
||||
|
||||
@param passwordGenerationOptions The Password generator options epresented in a dictionary form.
|
||||
@param passwordGenerationOptions The Password generator options represented in a dictionary form.
|
||||
|
||||
@param viewController The view controller from which the 1Password Extension is invoked. Usually `self`
|
||||
|
||||
@ -189,7 +189,7 @@ typedef void (^OnePasswordExtensionItemCompletionBlock)(NSExtensionItem * __null
|
||||
/*!
|
||||
Called in the UIActivityViewController completion block to find out whether or not the user selected the 1Password Extension activity.
|
||||
|
||||
@param activityType or the bundle identidier of the selected activity in the share sheet.
|
||||
@param activityType or the bundle identifier of the selected activity in the share sheet.
|
||||
|
||||
@return isOnePasswordExtensionActivityType Returns YES if the selected activity is the 1Password extension, NO otherwise.
|
||||
*/
|
||||
|
@ -112,12 +112,12 @@ class ActivityManager {
|
||||
|
||||
if let folders = account.folders {
|
||||
for folder in folders {
|
||||
ids.append(identifer(for: folder))
|
||||
ids.append(identifier(for: folder))
|
||||
}
|
||||
}
|
||||
|
||||
for webFeed in account.flattenedWebFeeds() {
|
||||
ids.append(contentsOf: identifers(for: webFeed))
|
||||
ids.append(contentsOf: identifiers(for: webFeed))
|
||||
}
|
||||
|
||||
CSSearchableIndex.default().deleteSearchableItems(withIdentifiers: ids)
|
||||
@ -125,17 +125,17 @@ class ActivityManager {
|
||||
|
||||
static func cleanUp(_ folder: Folder) {
|
||||
var ids = [String]()
|
||||
ids.append(identifer(for: folder))
|
||||
ids.append(identifier(for: folder))
|
||||
|
||||
for webFeed in folder.flattenedWebFeeds() {
|
||||
ids.append(contentsOf: identifers(for: webFeed))
|
||||
ids.append(contentsOf: identifiers(for: webFeed))
|
||||
}
|
||||
|
||||
CSSearchableIndex.default().deleteSearchableItems(withIdentifiers: ids)
|
||||
}
|
||||
|
||||
static func cleanUp(_ webFeed: WebFeed) {
|
||||
CSSearchableIndex.default().deleteSearchableItems(withIdentifiers: identifers(for: webFeed))
|
||||
CSSearchableIndex.default().deleteSearchableItems(withIdentifiers: identifiers(for: webFeed))
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -201,7 +201,7 @@ private extension ActivityManager {
|
||||
|
||||
activity.isEligibleForHandoff = true
|
||||
|
||||
activity.persistentIdentifier = ActivityManager.identifer(for: article)
|
||||
activity.persistentIdentifier = ActivityManager.identifier(for: article)
|
||||
|
||||
#if os(iOS)
|
||||
activity.keywords = Set(makeKeywords(article))
|
||||
@ -222,7 +222,7 @@ private extension ActivityManager {
|
||||
attributeSet.title = ArticleStringFormatter.truncatedTitle(article)
|
||||
attributeSet.contentDescription = article.summary
|
||||
attributeSet.keywords = makeKeywords(article)
|
||||
attributeSet.relatedUniqueIdentifier = ActivityManager.identifer(for: article)
|
||||
attributeSet.relatedUniqueIdentifier = ActivityManager.identifier(for: article)
|
||||
|
||||
if let iconImage = article.iconImage() {
|
||||
attributeSet.thumbnailData = iconImage.image.pngData()
|
||||
@ -249,7 +249,7 @@ private extension ActivityManager {
|
||||
let attributeSet = CSSearchableItemAttributeSet(itemContentType: kUTTypeItem as String)
|
||||
attributeSet.title = feed.nameForDisplay
|
||||
attributeSet.keywords = makeKeywords(feed.nameForDisplay)
|
||||
attributeSet.relatedUniqueIdentifier = ActivityManager.identifer(for: feed)
|
||||
attributeSet.relatedUniqueIdentifier = ActivityManager.identifier(for: feed)
|
||||
|
||||
if let iconImage = IconImageCache.shared.imageForFeed(feed) {
|
||||
attributeSet.thumbnailData = iconImage.image.dataRepresentation()
|
||||
@ -273,24 +273,24 @@ private extension ActivityManager {
|
||||
activity.becomeCurrent()
|
||||
}
|
||||
|
||||
static func identifer(for folder: Folder) -> String {
|
||||
static func identifier(for folder: Folder) -> String {
|
||||
return "account_\(folder.account!.accountID)_folder_\(folder.nameForDisplay)"
|
||||
}
|
||||
|
||||
static func identifer(for feed: WebFeed) -> String {
|
||||
static func identifier(for feed: WebFeed) -> String {
|
||||
return "account_\(feed.account!.accountID)_feed_\(feed.webFeedID)"
|
||||
}
|
||||
|
||||
static func identifer(for article: Article) -> String {
|
||||
static func identifier(for article: Article) -> String {
|
||||
return "account_\(article.accountID)_feed_\(article.webFeedID)_article_\(article.articleID)"
|
||||
}
|
||||
|
||||
static func identifers(for feed: WebFeed) -> [String] {
|
||||
static func identifiers(for feed: WebFeed) -> [String] {
|
||||
var ids = [String]()
|
||||
ids.append(identifer(for: feed))
|
||||
ids.append(identifier(for: feed))
|
||||
if let articles = try? feed.fetchArticles() {
|
||||
for article in articles {
|
||||
ids.append(identifer(for: article))
|
||||
ids.append(identifier(for: article))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -69,7 +69,7 @@ public class ArticleThemeDownloader {
|
||||
}
|
||||
|
||||
|
||||
/// Performs a deep search of the unzipped direcotry to find the theme file.
|
||||
/// Performs a deep search of the unzipped directory to find the theme file.
|
||||
/// - Parameter searchPath: directory to search
|
||||
/// - Returns: optional `String`
|
||||
private func findThemeFile(in searchPath: String) -> String? {
|
||||
@ -84,13 +84,13 @@ public class ArticleThemeDownloader {
|
||||
return nil
|
||||
}
|
||||
|
||||
/// The download directory used by the theme downloader: `Application Suppport/NetNewsWire/Downloads`
|
||||
/// The download directory used by the theme downloader: `Application Support/NetNewsWire/Downloads`
|
||||
/// - Returns: `URL`
|
||||
private func downloadDirectory() -> URL {
|
||||
FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first!.appendingPathComponent("NetNewsWire/Downloads", isDirectory: true)
|
||||
}
|
||||
|
||||
/// Removes downloaded themes, where themes == folders, from `Application Suppport/NetNewsWire/Downloads`.
|
||||
/// Removes downloaded themes, where themes == folders, from `Application Support/NetNewsWire/Downloads`.
|
||||
public func cleanUp() {
|
||||
guard let filenames = try? FileManager.default.contentsOfDirectory(atPath: downloadDirectory().path) else {
|
||||
return
|
||||
|
@ -7,11 +7,11 @@ uses `xcodebuild` to build the project after syncing the repository and the vari
|
||||
The build itself focuses on the scheme NetNewsWire for macOS and NetNewsWire-iOS for iOS. Also it leverages the
|
||||
`NetNewsWire.xcworkspace` configuration.
|
||||
|
||||
Private keys, certificates and provisioning profiles are stored in Github under `buildscripts` folder. Decrypting neccessary certificates, copy to build machine keychain and delete the certificates are handled by the [`buildscripts/ci-build.sh`](https://github.com/Ranchero-Software/NetNewsWire/blob/main/buildscripts/ci-build.sh) script.
|
||||
Private keys, certificates and provisioning profiles are stored in Github under `buildscripts` folder. Decrypting necessary certificates, copy to build machine keychain and delete the certificates are handled by the [`buildscripts/ci-build.sh`](https://github.com/Ranchero-Software/NetNewsWire/blob/main/buildscripts/ci-build.sh) script.
|
||||
|
||||
Each submodule also has it's own CI configuration, which are set up and built from
|
||||
their own repositories. The submodule CI systems are entirely independent so that
|
||||
those libraries can grow and change, getting CI verification, indepdent of NetNewsWire.
|
||||
those libraries can grow and change, getting CI verification, independent of NetNewsWire.
|
||||
|
||||
Build failures are notified to our slack group via [Notify Slack](https://github.com/8398a7/action-slack) GitHub action.
|
||||
|
||||
|
@ -20,7 +20,7 @@ class AppleScriptXCTestCase: XCTestCase {
|
||||
|
||||
/*
|
||||
@function doIndividualScript
|
||||
@param filename -- name of a .applescript (sans extention) in the test bundle's
|
||||
@param filename -- name of a .applescript (sans extension) in the test bundle's
|
||||
Resources/TestScripts directory
|
||||
@brief given a file, loads the script and runs it. Expects a result from running
|
||||
the script of the form
|
||||
|
@ -76,7 +76,7 @@ class ScriptingTests: AppleScriptXCTestCase {
|
||||
|
||||
/*
|
||||
@function testCurrentArticleScripts
|
||||
@brief the pices of the test are broken up into smaller pieces because of the
|
||||
@brief the pieces of the test are broken up into smaller pieces because of the
|
||||
way events are delivered to the app -- I tried one single script with all the
|
||||
actions and the keystrokes aren't delivered to the app right away, so the ui
|
||||
isn't updated in time for 'current article' to be set. But, breaking them up
|
||||
@ -85,7 +85,7 @@ class ScriptingTests: AppleScriptXCTestCase {
|
||||
July 30, 2019: There's an issue where in order for a script to send keystrokes,
|
||||
The app has to be allowed to interact with the SystemEvents.app in
|
||||
System Preferences -> Security & Privacy -> Privacy -> Accessibility
|
||||
and this premission needs to be renewed every time the app is recompiled unless
|
||||
and this permission needs to be renewed every time the app is recompiled unless
|
||||
the app is codesigned. Until we figure out how to get around this limitation,
|
||||
this test is disabled.
|
||||
*/
|
||||
|
@ -288,7 +288,7 @@ private extension AppDelegate {
|
||||
|
||||
func waitToComplete(completion: @escaping (Bool) -> Void) {
|
||||
guard UIApplication.shared.applicationState == .background else {
|
||||
os_log("App came back to forground, no longer waiting.", log: self.log, type: .info)
|
||||
os_log("App came back to foreground, no longer waiting.", log: self.log, type: .info)
|
||||
completion(false)
|
||||
return
|
||||
}
|
||||
|
@ -23,14 +23,14 @@ open class ImageScrollView: UIScrollView {
|
||||
}
|
||||
|
||||
@objc public enum Offset: Int {
|
||||
case begining
|
||||
case beginning
|
||||
case center
|
||||
}
|
||||
|
||||
static let kZoomInFactorFromMinWhenDoubleTap: CGFloat = 2
|
||||
|
||||
@objc open var imageContentMode: ScaleMode = .widthFill
|
||||
@objc open var initialOffset: Offset = .begining
|
||||
@objc open var initialOffset: Offset = .beginning
|
||||
|
||||
@objc public private(set) var zoomView: UIImageView? = nil
|
||||
|
||||
@ -201,7 +201,7 @@ open class ImageScrollView: UIScrollView {
|
||||
zoomScale = minimumZoomScale
|
||||
|
||||
switch initialOffset {
|
||||
case .begining:
|
||||
case .beginning:
|
||||
contentOffset = CGPoint.zero
|
||||
case .center:
|
||||
let xOffset = contentSize.width < bounds.width ? 0 : (contentSize.width - bounds.width)/2
|
||||
|
@ -424,7 +424,7 @@ extension WebViewController: WKUIDelegate {
|
||||
|
||||
func webView(_ webView: WKWebView, contextMenuForElement elementInfo: WKContextMenuElementInfo, willCommitWithAnimator animator: UIContextMenuInteractionCommitAnimating) {
|
||||
// We need to have at least an unimplemented WKUIDelegate assigned to the WKWebView. This makes the
|
||||
// link preview launch Safari when the link preview is tapped. In theory, you shoud be able to get
|
||||
// link preview launch Safari when the link preview is tapped. In theory, you should be able to get
|
||||
// the link from the elementInfo above and transition to SFSafariViewController instead of launching
|
||||
// Safari. As the time of this writing, the link in elementInfo is always nil. ¯\_(ツ)_/¯
|
||||
}
|
||||
|
@ -1319,7 +1319,7 @@ class SceneCoordinator: NSObject, UndoableCommandRunner {
|
||||
|
||||
/// This will dismiss the foremost view controller if the user
|
||||
/// has launched from an external action (i.e., a widget tap, or
|
||||
/// selecting an artice via a notification).
|
||||
/// selecting an article via a notification).
|
||||
///
|
||||
/// The dismiss is only applicable if the view controller is a
|
||||
/// `SFSafariViewController` or `SettingsViewController`,
|
||||
@ -1402,7 +1402,7 @@ extension SceneCoordinator: UINavigationControllerDelegate {
|
||||
// If we are using a phone and navigate away from the detail, clear up the article resources (including activity).
|
||||
// Don't clear it if we have pushed an ArticleViewController, but don't yet see it on the navigation stack.
|
||||
// This happens when we are going to the next unread and we need to grab another timeline to continue. The
|
||||
// ArticleViewController will be pushed, but we will breifly show the Timeline. Don't clear things out when that happens.
|
||||
// ArticleViewController will be pushed, but we will briefly show the Timeline. Don't clear things out when that happens.
|
||||
if viewController === masterTimelineViewController && !isThreePanelMode && rootSplitViewController.isCollapsed && !isArticleViewControllerPending {
|
||||
currentArticle = nil
|
||||
masterTimelineViewController?.updateArticleSelection(animations: [.scroll, .select, .navigation])
|
||||
|
@ -60,7 +60,7 @@ private extension UIViewController {
|
||||
let title = NSLocalizedString("Account Error", comment: "Account Error")
|
||||
let alertController = UIAlertController(title: title, message: error.localizedDescription, preferredStyle: .alert)
|
||||
|
||||
if error.acount?.type == .feedbin {
|
||||
if error.account?.type == .feedbin {
|
||||
|
||||
let credentialsTitle = NSLocalizedString("Update Credentials", comment: "Update Credentials")
|
||||
let credentialsAction = UIAlertAction(title: credentialsTitle, style: .default) { [weak self] _ in
|
||||
@ -69,7 +69,7 @@ private extension UIViewController {
|
||||
let navController = UIStoryboard.account.instantiateViewController(withIdentifier: "FeedbinAccountNavigationViewController") as! UINavigationController
|
||||
navController.modalPresentationStyle = .formSheet
|
||||
let addViewController = navController.topViewController as! FeedbinAccountViewController
|
||||
addViewController.account = error.acount
|
||||
addViewController.account = error.account
|
||||
self?.present(navController, animated: true)
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user