Revert more changes since 6193 in hopes of fixing the scrolling bug.

This commit is contained in:
Brent Simmons
2025-05-09 09:22:34 -07:00
parent ef7aa6bb27
commit 9aa93166b7
12 changed files with 84 additions and 215 deletions

View File

@@ -85,7 +85,7 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr
var showsSearchResults = false
var selectedArticles: [Article] {
return articles.articlesForIndexes(tableView.selectedRowIndexes)
return Array(articles.articlesForIndexes(tableView.selectedRowIndexes))
}
var hasAtLeastOneSelectedArticle: Bool {

View File

@@ -189,28 +189,13 @@ public extension String {
/// Removes an HTML tag and everything between its start and end tags.
///
/// The regex pattern `<tag>[\\s\\S]*?</tag>` explanation:
/// - `<` matches the literal `<` character.
/// - `tag` matches the literal parameter provided to the function, e.g., `style`.
/// - `>` matches the literal `>` character.
/// - `[\\s\\S]*?`
/// - `[\\s\\S]` matches _any_ character, including new lines.
/// - `*` will match zero or more of the preceeding character, in this case _any_
/// character.
/// - `?` switches the matching mode to [lazy](https://javascript.info/regexp-greedy-and-lazy)
/// so it will match as few as characters as possible before satisfying the rest of the pattern.
/// - `<` matches the literal `<` character.
/// - `/` matches the literal `/` character.
/// - `tag` matches the literal parameter provided to the function, e.g., `style`.
/// - `>` matches the literal `>` character.
///
/// - Parameter tag: The tag to remove.
///
/// - Returns: A new copy of `self` with the tag removed.
///
/// - Note: Doesn't work correctly with nested tags of the same name.
private func removingTagAndContents(_ tag: String) -> String {
return self.replacingOccurrences(of: "<\(tag)>[\\s\\S]*?</\(tag)>", with: "", options: [.regularExpression, .caseInsensitive])
return self.replacingOccurrences(of: "<\(tag).+?</\(tag)>", with: "", options: [.regularExpression, .caseInsensitive])
}
/// Strips HTML from a string.

View File

@@ -7,27 +7,18 @@
//
import Foundation
import os
import RSCore
public typealias DownloadCallback = @MainActor (Data?, URLResponse?, Error?) -> Swift.Void
public typealias DownloadCallback = (Data?, URLResponse?, Error?) -> Swift.Void
/// Simple downloader, for a one-shot download like an image
/// or a web page. For a download-feeds session, see DownloadSession.
/// Caches response for a short time for GET requests. May return cached response.
@MainActor public final class Downloader {
public final class Downloader {
public static let shared = Downloader()
private let urlSession: URLSession
private var callbacks = [URL: [DownloadCallback]]()
// Cache  short-lived
private let cache = Cache<DownloaderRecord>(timeToLive: 60 * 3, timeBetweenCleanups: 60 * 2)
nonisolated private static let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "Downloader")
nonisolated private static let debugLoggingEnabled = false
private init() {
let sessionConfiguration = URLSessionConfiguration.ephemeral
sessionConfiguration.requestCachePolicy = .reloadIgnoringLocalCacheData
sessionConfiguration.httpShouldSetCookies = false
@@ -46,103 +37,20 @@ public typealias DownloadCallback = @MainActor (Data?, URLResponse?, Error?) ->
urlSession.invalidateAndCancel()
}
public func download(_ url: URL, _ callback: @escaping DownloadCallback) {
assert(Thread.isMainThread)
download(URLRequest(url: url), callback)
public func download(_ url: URL, _ completion: DownloadCallback? = nil) {
download(URLRequest(url: url), completion)
}
public func download(_ urlRequest: URLRequest, _ callback: @escaping DownloadCallback) {
assert(Thread.isMainThread)
guard let url = urlRequest.url else {
Self.logger.fault("Downloader: skipping download for URLRequest without a URL")
return
}
let isCacheableRequest = urlRequest.httpMethod == HTTPMethod.get
// Return cached record if available.
if isCacheableRequest {
if let cachedRecord = cache[url.absoluteString] {
if Self.debugLoggingEnabled {
Self.logger.debug("Downloader: returning cached record for \(url)")
}
callback(cachedRecord.data, cachedRecord.response, cachedRecord.error)
return
}
}
// Add callback. If there is already a download in progress for this URL, return early.
if callbacks[url] == nil {
if Self.debugLoggingEnabled {
Self.logger.debug("Downloader: downloading \(url)")
}
callbacks[url] = [callback]
} else {
// A download is already be in progress for this URL. Dont start a separate download.
// Add the callback to the callbacks array for this URL.
if Self.debugLoggingEnabled {
Self.logger.debug("Downloader: download in progress for \(url) — adding callback")
}
callbacks[url]?.append(callback)
return
}
public func download(_ urlRequest: URLRequest, _ completion: DownloadCallback? = nil) {
var urlRequestToUse = urlRequest
urlRequestToUse.addSpecialCaseUserAgentIfNeeded()
let task = urlSession.dataTask(with: urlRequestToUse) { (data, response, error) in
if isCacheableRequest {
if Self.debugLoggingEnabled {
Self.logger.debug("Downloader: caching record for \(url)")
}
let cachedRecord = DownloaderRecord(data: data, response: response, error: error)
self.cache[url.absoluteString] = cachedRecord
}
Task { @MainActor in
self.callAndReleaseCallbacks(url, data, response, error)
DispatchQueue.main.async() {
completion?(data, response, error)
}
}
task.resume()
}
}
private extension Downloader {
func callAndReleaseCallbacks(_ url: URL, _ data: Data? = nil, _ response: URLResponse? = nil, _ error: Error? = nil) {
assert(Thread.isMainThread)
defer {
callbacks[url] = nil
}
guard let callbacksForURL = callbacks[url] else {
assertionFailure("Downloader: downloaded URL \(url) but no callbacks found")
Self.logger.fault("Downloader: downloaded URL \(url) but no callbacks found")
return
}
if Self.debugLoggingEnabled {
let count = callbacksForURL.count
if count == 1 {
Self.logger.debug("Downloader: calling 1 callback for URL \(url)")
} else {
Self.logger.debug("Downloader: calling \(count) callbacks for URL \(url)")
}
}
for callback in callbacksForURL {
callback(data, response, error)
}
}
}
struct DownloaderRecord: CacheRecord, Sendable {
let dateCreated = Date()
let data: Data?
let response: URLResponse?
let error: Error?
}

View File

@@ -87,7 +87,6 @@ private extension HTMLMetadataDownloader {
Self.logger.debug("HTMLMetadataDownloader downloading for \(url)")
}
Task { @MainActor in
Downloader.shared.download(actualURL) { data, response, error in
if let data, !data.isEmpty, let response, response.statusIsOK {
let urlToUse = response.url ?? actualURL
@@ -109,7 +108,6 @@ private extension HTMLMetadataDownloader {
}
}
}
}
func urlShouldBeSkippedDueToPrevious4xxResponse(_ url: String) -> Bool {

View File

@@ -75,10 +75,6 @@ public class ArticleThemeDownloader {
private func findThemeFile(in searchPath: String) -> String? {
if let directoryContents = FileManager.default.enumerator(atPath: searchPath) {
while let file = directoryContents.nextObject() as? String {
if file.hasPrefix("__MACOSX/") {
//logger.debug("Ignoring theme file in __MACOSX folder.")
continue
}
if file.hasSuffix(".nnwtheme") {
return file
}

View File

@@ -139,7 +139,6 @@ private extension SingleFaviconDownloader {
return
}
Task { @MainActor in
Downloader.shared.download(url) { (data, response, error) in
if let data = data, !data.isEmpty, let response = response, response.statusIsOK, error == nil {
@@ -155,7 +154,6 @@ private extension SingleFaviconDownloader {
completion(nil)
}
}
}
func postDidLoadFaviconNotification() {

View File

@@ -105,7 +105,6 @@ private extension ImageDownloader {
return
}
Task { @MainActor in
Downloader.shared.download(imageURL) { (data, response, error) in
if let data = data, !data.isEmpty, let response = response, response.statusIsOK, error == nil {
@@ -124,7 +123,6 @@ private extension ImageDownloader {
completion(nil)
}
}
}
func saveToDisk(_ url: String, _ data: Data) {

View File

@@ -44,10 +44,10 @@ extension Array where Element == Article {
return nil
}
func articlesForIndexes(_ indexes: IndexSet) -> [Article] {
return indexes.compactMap{ (oneIndex) -> Article? in
func articlesForIndexes(_ indexes: IndexSet) -> Set<Article> {
return Set(indexes.compactMap{ (oneIndex) -> Article? in
return articleAtRow(oneIndex)
}
})
}
func sortedByDate(_ sortDirection: ComparisonResult, groupByFeed: Bool = false) -> ArticleArray {

View File

@@ -25,16 +25,6 @@ class ImageViewController: UIViewController {
return imageScrollView.zoomedFrame
}
override var keyCommands: [UIKeyCommand]? {
return [
UIKeyCommand(
title: NSLocalizedString("Close Image", comment: "Close Image"),
action: #selector(done(_:)),
input: " "
)
]
}
override func viewDidLoad() {
super.viewDidLoad()

View File

@@ -39,7 +39,6 @@ class WebViewController: UIViewController {
private var isFullScreenAvailable: Bool {
return AppDefaults.shared.articleFullscreenAvailable && traitCollection.userInterfaceIdiom == .phone && coordinator.isRootSplitCollapsed
}
private lazy var articleIconSchemeHandler = ArticleIconSchemeHandler(coordinator: coordinator);
private lazy var transition = ImageTransition(controller: self)
private var clickedImageCompletion: (() -> Void)?
@@ -592,8 +591,7 @@ private extension WebViewController {
"windowScrollY": String(windowScrollY)
]
var html = try! MacroProcessor.renderedText(withTemplate: ArticleRenderer.page.html, substitutions: substitutions)
html = ArticleRenderingSpecialCases.filterHTMLIfNeeded(baseURL: rendering.baseURL, html: html)
let html = try! MacroProcessor.renderedText(withTemplate: ArticleRenderer.page.html, substitutions: substitutions)
webView.loadHTMLString(html, baseURL: ArticleRenderer.page.baseURL)
}

View File

@@ -7,7 +7,6 @@
//
import UIKit
import WebKit
import Account
import Articles
import RSCore

View File

@@ -7,7 +7,6 @@
//
import UIKit
import WebKit
import RSCore
import RSWeb
import Account