mirror of
https://github.com/Ranchero-Software/NetNewsWire
synced 2025-08-12 06:26:36 +00:00
Merge branch 'master' of https://github.com/brentsimmons/NetNewsWire
This commit is contained in:
@@ -15,6 +15,7 @@ import RSWeb
|
||||
enum DetailState: Equatable {
|
||||
case noSelection
|
||||
case multipleSelection
|
||||
case loading
|
||||
case article(Article)
|
||||
case extracted(Article, ExtractedArticle)
|
||||
}
|
||||
|
||||
@@ -104,13 +104,8 @@ final class DetailWebViewController: NSViewController, WKUIDelegate {
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(webInspectorEnabledDidChange(_:)), name: .WebInspectorEnabledDidChange, object: nil)
|
||||
#endif
|
||||
|
||||
webView.loadHTMLString(template(), baseURL: nil)
|
||||
}
|
||||
|
||||
func template() -> String {
|
||||
let path = Bundle.main.path(forResource: "page", ofType: "html")!
|
||||
let s = try! NSString(contentsOfFile: path, encoding: String.Encoding.utf8.rawValue)
|
||||
return s as String
|
||||
webView.loadHTMLString(ArticleRenderer.page.html, baseURL: ArticleRenderer.page.baseURL)
|
||||
|
||||
}
|
||||
|
||||
// MARK: Scrolling
|
||||
@@ -182,13 +177,15 @@ private extension DetailWebViewController {
|
||||
|
||||
func reloadHTML() {
|
||||
let style = ArticleStylesManager.shared.currentStyle
|
||||
let rendering: ArticleRendering
|
||||
let rendering: ArticleRenderer.Rendering
|
||||
|
||||
switch state {
|
||||
case .noSelection:
|
||||
rendering = ArticleRenderer.noSelectionHTML(style: style)
|
||||
case .multipleSelection:
|
||||
rendering = ArticleRenderer.multipleSelectionHTML(style: style)
|
||||
case .loading:
|
||||
rendering = ArticleRenderer.loadingHTML(style: style)
|
||||
case .article(let article):
|
||||
rendering = ArticleRenderer.articleHTML(article: article, style: style)
|
||||
case .extracted(let article, let extractedArticle):
|
||||
|
||||
@@ -2,33 +2,7 @@
|
||||
<head>
|
||||
<style>
|
||||
</style>
|
||||
<script>
|
||||
function mouseDidEnterLink(anchor) {
|
||||
window.webkit.messageHandlers.mouseDidEnter.postMessage(anchor.href);
|
||||
}
|
||||
|
||||
function mouseDidExitLink(anchor) {
|
||||
window.webkit.messageHandlers.mouseDidExit.postMessage(anchor.href);
|
||||
}
|
||||
|
||||
function render(data) {
|
||||
document.getElementsByTagName("style")[0].innerHTML = data.style;
|
||||
document.body.innerHTML = data.body;
|
||||
window.scrollTo(0, 0);
|
||||
var anchors = document.getElementsByTagName("a");
|
||||
for (var i = 0; i < anchors.length; i++) {
|
||||
anchors[i].addEventListener("mouseenter", function() { mouseDidEnterLink(this) });
|
||||
anchors[i].addEventListener("mouseleave", function() { mouseDidExitLink(this) });
|
||||
}
|
||||
|
||||
document.getElementsByTagName("body")[0].querySelectorAll("style, link[rel=stylesheet]").forEach(element => element.remove());
|
||||
document.getElementsByTagName("body")[0].querySelectorAll("[style]").forEach(element => element.removeAttribute("style"));
|
||||
}
|
||||
|
||||
function error() {
|
||||
document.body.innerHTML = "error";
|
||||
}
|
||||
</script>
|
||||
<script src="main.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
|
||||
@@ -133,6 +133,20 @@ figcaption {
|
||||
line-height: 1.3em;
|
||||
}
|
||||
|
||||
.iframeWrap {
|
||||
position: relative;
|
||||
display: block;
|
||||
padding-top: 56.25%;
|
||||
}
|
||||
|
||||
.iframeWrap iframe {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100% !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
/*Block ads and junk*/
|
||||
|
||||
iframe[src*="feedads"],
|
||||
|
||||
@@ -329,11 +329,7 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations {
|
||||
detailViewController?.setState(detailState, mode: timelineSourceMode)
|
||||
}
|
||||
} else {
|
||||
if let extractor = ArticleExtractor(currentLink) {
|
||||
extractor.delegate = self
|
||||
extractor.process()
|
||||
articleExtractor = extractor
|
||||
}
|
||||
startArticleExtractorForCurrentLink()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -453,18 +449,24 @@ extension MainWindowController: SidebarDelegate {
|
||||
extension MainWindowController: TimelineContainerViewControllerDelegate {
|
||||
|
||||
func timelineSelectionDidChange(_: TimelineContainerViewController, articles: [Article]?, mode: TimelineSourceMode) {
|
||||
|
||||
articleExtractor?.cancel()
|
||||
articleExtractor = nil
|
||||
isShowingExtractedArticle = false
|
||||
|
||||
makeToolbarValidate()
|
||||
|
||||
let detailState: DetailState
|
||||
if let articles = articles {
|
||||
detailState = articles.count == 1 ? .article(articles.first!) : .multipleSelection
|
||||
}
|
||||
else {
|
||||
if articles.count == 1 {
|
||||
if articles.first?.feed?.isArticleExtractorAlwaysOn ?? false {
|
||||
detailState = .loading
|
||||
startArticleExtractorForCurrentLink()
|
||||
} else {
|
||||
detailState = .article(articles.first!)
|
||||
}
|
||||
} else {
|
||||
detailState = .multipleSelection
|
||||
}
|
||||
} else {
|
||||
detailState = .noSelection
|
||||
}
|
||||
|
||||
@@ -833,6 +835,14 @@ private extension MainWindowController {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func startArticleExtractorForCurrentLink() {
|
||||
if let link = currentLink, let extractor = ArticleExtractor(link) {
|
||||
extractor.delegate = self
|
||||
extractor.process()
|
||||
articleExtractor = extractor
|
||||
}
|
||||
}
|
||||
|
||||
func saveSplitViewState() {
|
||||
// TODO: Update this for multiple windows.
|
||||
|
||||
Binary file not shown.
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
@@ -78,6 +78,9 @@
|
||||
515D4FCC2325815A00EE1167 /* SafariExt.js in Resources */ = {isa = PBXBuildFile; fileRef = 515D4FCB2325815A00EE1167 /* SafariExt.js */; };
|
||||
51707439232AA97100A461A3 /* ShareFolderPickerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51707438232AA97100A461A3 /* ShareFolderPickerController.swift */; };
|
||||
5170743A232AABFC00A461A3 /* FlattenedAccountFolderPickerData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C452812265093600C03939 /* FlattenedAccountFolderPickerData.swift */; };
|
||||
517630042336215100E15FFF /* main.js in Resources */ = {isa = PBXBuildFile; fileRef = 517630032336215100E15FFF /* main.js */; };
|
||||
517630052336215100E15FFF /* main.js in Resources */ = {isa = PBXBuildFile; fileRef = 517630032336215100E15FFF /* main.js */; };
|
||||
517630232336657E00E15FFF /* DetailViewControllerWebViewProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 517630222336657E00E15FFF /* DetailViewControllerWebViewProvider.swift */; };
|
||||
5183CCD0226E1E880010922C /* NonIntrinsicLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCCF226E1E880010922C /* NonIntrinsicLabel.swift */; };
|
||||
5183CCDA226E31A50010922C /* NonIntrinsicImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCD9226E31A50010922C /* NonIntrinsicImageView.swift */; };
|
||||
5183CCDD226F1F5C0010922C /* NavigationProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCDC226F1F5C0010922C /* NavigationProgressView.swift */; };
|
||||
@@ -813,6 +816,8 @@
|
||||
515D4FCD2325909200EE1167 /* NetNewsWire_iOS_ShareExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = NetNewsWire_iOS_ShareExtension.entitlements; sourceTree = "<group>"; };
|
||||
515D4FCE2325B3D000EE1167 /* NetNewsWire_iOSshareextension_target.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = NetNewsWire_iOSshareextension_target.xcconfig; sourceTree = "<group>"; };
|
||||
51707438232AA97100A461A3 /* ShareFolderPickerController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareFolderPickerController.swift; sourceTree = "<group>"; };
|
||||
517630032336215100E15FFF /* main.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = main.js; sourceTree = "<group>"; };
|
||||
517630222336657E00E15FFF /* DetailViewControllerWebViewProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailViewControllerWebViewProvider.swift; sourceTree = "<group>"; };
|
||||
5183CCCF226E1E880010922C /* NonIntrinsicLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonIntrinsicLabel.swift; sourceTree = "<group>"; };
|
||||
5183CCD9226E31A50010922C /* NonIntrinsicImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonIntrinsicImageView.swift; sourceTree = "<group>"; };
|
||||
5183CCDC226F1F5C0010922C /* NavigationProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationProgressView.swift; sourceTree = "<group>"; };
|
||||
@@ -1370,6 +1375,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
51C4527E2265092C00C03939 /* DetailViewController.swift */,
|
||||
517630222336657E00E15FFF /* DetailViewControllerWebViewProvider.swift */,
|
||||
515ADE3F22E11FAE006B2460 /* SystemMessageViewController.swift */,
|
||||
);
|
||||
path = Detail;
|
||||
@@ -1393,6 +1399,7 @@
|
||||
49F40DEF2335B71000552BF4 /* newsfoot.js */,
|
||||
849A977D1ED9EC42007D329B /* ArticleRenderer.swift */,
|
||||
848362FE2262A30E00DA1D35 /* template.html */,
|
||||
517630032336215100E15FFF /* main.js */,
|
||||
);
|
||||
path = "Article Rendering";
|
||||
sourceTree = "<group>";
|
||||
@@ -2502,6 +2509,7 @@
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
517630052336215100E15FFF /* main.js in Resources */,
|
||||
511D43D0231FA62500FB1562 /* TimelineKeyboardShortcuts.plist in Resources */,
|
||||
51C452862265093600C03939 /* Add.storyboard in Resources */,
|
||||
511D43EF231FBDE900FB1562 /* LaunchScreenPad.storyboard in Resources */,
|
||||
@@ -2537,6 +2545,7 @@
|
||||
51EF0F8E2279C9260050506E /* AccountsAdd.xib in Resources */,
|
||||
84C9FC8F22629E8F00D921D6 /* NetNewsWire.sdef in Resources */,
|
||||
84C9FC7D22629E1200D921D6 /* AccountsDetail.xib in Resources */,
|
||||
517630042336215100E15FFF /* main.js in Resources */,
|
||||
5144EA362279FC3D00D19003 /* AccountsAddLocal.xib in Resources */,
|
||||
84C9FC8C22629E8F00D921D6 /* KeyboardShortcuts.html in Resources */,
|
||||
5144EA3B227A379E00D19003 /* ImportOPMLSheet.xib in Resources */,
|
||||
@@ -2702,6 +2711,7 @@
|
||||
51C452A722650A3D00C03939 /* RSImage-Extensions.swift in Sources */,
|
||||
51C45269226508F600C03939 /* MasterFeedTableViewCell.swift in Sources */,
|
||||
51F85BFD2275DCA800C787DC /* SingleLineUILabelSizer.swift in Sources */,
|
||||
517630232336657E00E15FFF /* DetailViewControllerWebViewProvider.swift in Sources */,
|
||||
51C4528F226509BD00C03939 /* UnreadFeed.swift in Sources */,
|
||||
51AF460E232488C6001742EF /* Account-Extensions.swift in Sources */,
|
||||
5183CCDD226F1F5C0010922C /* NavigationProgressView.swift in Sources */,
|
||||
|
||||
@@ -11,10 +11,18 @@ import RSCore
|
||||
import Articles
|
||||
import Account
|
||||
|
||||
typealias ArticleRendering = (style: String, html: String)
|
||||
|
||||
struct ArticleRenderer {
|
||||
|
||||
typealias Rendering = (style: String, html: String)
|
||||
typealias Page = (html: String, baseURL: URL)
|
||||
|
||||
static var page: Page = {
|
||||
let pageURL = Bundle.main.url(forResource: "page", withExtension: "html")!
|
||||
let html = try! String(contentsOf: pageURL)
|
||||
let baseURL = pageURL.deletingLastPathComponent()
|
||||
return Page(html: html, baseURL: baseURL)
|
||||
}()
|
||||
|
||||
private let article: Article?
|
||||
private let extractedArticle: ExtractedArticle?
|
||||
private let articleStyle: ArticleStyle
|
||||
@@ -38,22 +46,27 @@ struct ArticleRenderer {
|
||||
|
||||
// MARK: - API
|
||||
|
||||
static func articleHTML(article: Article, extractedArticle: ExtractedArticle? = nil, style: ArticleStyle) -> ArticleRendering {
|
||||
static func articleHTML(article: Article, extractedArticle: ExtractedArticle? = nil, style: ArticleStyle) -> Rendering {
|
||||
let renderer = ArticleRenderer(article: article, extractedArticle: extractedArticle, style: style)
|
||||
return (renderer.styleString(), renderer.articleHTML)
|
||||
}
|
||||
|
||||
static func multipleSelectionHTML(style: ArticleStyle) -> ArticleRendering {
|
||||
static func multipleSelectionHTML(style: ArticleStyle) -> Rendering {
|
||||
let renderer = ArticleRenderer(article: nil, extractedArticle: nil, style: style)
|
||||
return (renderer.styleString(), renderer.multipleSelectionHTML)
|
||||
}
|
||||
|
||||
static func noSelectionHTML(style: ArticleStyle) -> ArticleRendering {
|
||||
static func loadingHTML(style: ArticleStyle) -> Rendering {
|
||||
let renderer = ArticleRenderer(article: nil, extractedArticle: nil, style: style)
|
||||
return (renderer.styleString(), renderer.loadingHTML)
|
||||
}
|
||||
|
||||
static func noSelectionHTML(style: ArticleStyle) -> Rendering {
|
||||
let renderer = ArticleRenderer(article: nil, extractedArticle: nil, style: style)
|
||||
return (renderer.styleString(), renderer.noSelectionHTML)
|
||||
}
|
||||
|
||||
static func noContentHTML(style: ArticleStyle) -> ArticleRendering {
|
||||
static func noContentHTML(style: ArticleStyle) -> Rendering {
|
||||
let renderer = ArticleRenderer(article: nil, extractedArticle: nil, style: style)
|
||||
return (renderer.styleString(), renderer.noContentHTML)
|
||||
}
|
||||
@@ -73,6 +86,11 @@ private extension ArticleRenderer {
|
||||
return renderHTML(withBody: body)
|
||||
}
|
||||
|
||||
private var loadingHTML: String {
|
||||
let body = "<h3 class='systemMessage'>Loading...</h3>"
|
||||
return renderHTML(withBody: body)
|
||||
}
|
||||
|
||||
private var noSelectionHTML: String {
|
||||
let body = "<h3 class='systemMessage'>No selection</h3>"
|
||||
return renderHTML(withBody: body)
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
function mouseDidEnterLink(anchor) {
|
||||
window.webkit.messageHandlers.mouseDidEnter.postMessage(anchor.href);
|
||||
}
|
||||
|
||||
function mouseDidExitLink(anchor) {
|
||||
window.webkit.messageHandlers.mouseDidExit.postMessage(anchor.href);
|
||||
}
|
||||
|
||||
function wrapFrames() {
|
||||
document.querySelectorAll("iframe").forEach(element => {
|
||||
var wrapper = document.createElement("div");
|
||||
wrapper.classList.add("iframeWrap");
|
||||
element.parentNode.insertBefore(wrapper, element);
|
||||
wrapper.appendChild(element);
|
||||
});
|
||||
}
|
||||
|
||||
function stripStyles() {
|
||||
document.getElementsByTagName("body")[0].querySelectorAll("style, link[rel=stylesheet]").forEach(element => element.remove());
|
||||
document.getElementsByTagName("body")[0].querySelectorAll("[style]").forEach(element => element.removeAttribute("style"));
|
||||
}
|
||||
|
||||
function linkHover() {
|
||||
var anchors = document.getElementsByTagName("a");
|
||||
for (var i = 0; i < anchors.length; i++) {
|
||||
anchors[i].addEventListener("mouseenter", function() { mouseDidEnterLink(this) });
|
||||
anchors[i].addEventListener("mouseleave", function() { mouseDidExitLink(this) });
|
||||
}
|
||||
}
|
||||
|
||||
function error() {
|
||||
document.body.innerHTML = "error";
|
||||
}
|
||||
|
||||
function render(data) {
|
||||
document.getElementsByTagName("style")[0].innerHTML = data.style;
|
||||
document.body.innerHTML = data.body;
|
||||
window.scrollTo(0, 0);
|
||||
|
||||
wrapFrames()
|
||||
stripStyles()
|
||||
linkHover()
|
||||
}
|
||||
@@ -40,27 +40,23 @@ class DetailViewController: UIViewController {
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
webView = DetailViewControllerWebViewProvider.shared.dequeueWebView()
|
||||
webView.translatesAutoresizingMaskIntoConstraints = false
|
||||
webView.navigationDelegate = self
|
||||
|
||||
webViewContainer.addSubview(webView)
|
||||
|
||||
let constraints: [NSLayoutConstraint] = [
|
||||
webView.leadingAnchor.constraint(equalTo: webViewContainer.safeAreaLayoutGuide.leadingAnchor),
|
||||
webView.trailingAnchor.constraint(equalTo: webViewContainer.safeAreaLayoutGuide.trailingAnchor),
|
||||
webView.topAnchor.constraint(equalTo: webViewContainer.safeAreaLayoutGuide.topAnchor),
|
||||
webView.bottomAnchor.constraint(equalTo: webViewContainer.safeAreaLayoutGuide.bottomAnchor),
|
||||
]
|
||||
|
||||
NSLayoutConstraint.activate(constraints)
|
||||
|
||||
updateArticleSelection()
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(unreadCountDidChange(_:)), name: .UnreadCountDidChange, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(statusesDidChange(_:)), name: .StatusesDidChange, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(progressDidChange(_:)), name: .AccountRefreshProgressDidChange, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(contentSizeCategoryDidChange(_:)), name: UIContentSizeCategory.didChangeNotification, object: nil)
|
||||
|
||||
DetailViewControllerWebViewProvider.shared.dequeueWebView() { webView in
|
||||
|
||||
self.webView = webView
|
||||
self.webViewContainer.addChildAndPin(webView)
|
||||
webView.navigationDelegate = self
|
||||
|
||||
// Even though page.html should be loaded into this webview, we have to do it again
|
||||
// to work around this bug: http://www.openradar.me/22855188
|
||||
webView.loadHTMLString(ArticleRenderer.page.html, baseURL: ArticleRenderer.page.baseURL)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
@@ -243,6 +239,11 @@ extension DetailViewController: WKNavigationDelegate {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
|
||||
self.updateArticleSelection()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: Private
|
||||
@@ -261,60 +262,3 @@ private struct TemplateData: Codable {
|
||||
let style: String
|
||||
let body: String
|
||||
}
|
||||
|
||||
|
||||
// MARK: -
|
||||
|
||||
/// WKWebView has an awful behavior of a flash to white on first load when in dark mode.
|
||||
/// Keep a queue of WebViews where we've already done a trivial load so that by the time we need them in the UI, they're past the flash-to-shite part of their lifecycle.
|
||||
class DetailViewControllerWebViewProvider {
|
||||
|
||||
static var shared = DetailViewControllerWebViewProvider()
|
||||
|
||||
static let template: String = {
|
||||
let path = Bundle.main.path(forResource: "page", ofType: "html")!
|
||||
let s = try! NSString(contentsOfFile: path, encoding: String.Encoding.utf8.rawValue)
|
||||
return s as String
|
||||
}()
|
||||
|
||||
func dequeueWebView() -> WKWebView {
|
||||
if let webView = queue.popLast() {
|
||||
replenishQueueIfNeeded()
|
||||
return webView
|
||||
}
|
||||
|
||||
assertionFailure("Creating WKWebView in \(#function); queue has run dry.")
|
||||
let webView = WKWebView(frame: .zero)
|
||||
return webView
|
||||
}
|
||||
|
||||
func enqueueWebView(_ webView: WKWebView) {
|
||||
guard queue.count < maximumQueueDepth else {
|
||||
return
|
||||
}
|
||||
|
||||
webView.uiDelegate = nil
|
||||
webView.navigationDelegate = nil
|
||||
|
||||
webView.loadHTMLString(DetailViewControllerWebViewProvider.template, baseURL: nil)
|
||||
|
||||
queue.insert(webView, at: 0)
|
||||
}
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let minimumQueueDepth = 3
|
||||
private let maximumQueueDepth = 6
|
||||
private var queue: [WKWebView] = []
|
||||
|
||||
private init() {
|
||||
replenishQueueIfNeeded()
|
||||
}
|
||||
|
||||
private func replenishQueueIfNeeded() {
|
||||
while queue.count < minimumQueueDepth {
|
||||
let webView = WKWebView(frame: .zero)
|
||||
enqueueWebView(webView)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
//
|
||||
// DetailViewControllerWebViewProvider.swift
|
||||
// NetNewsWire-iOS
|
||||
//
|
||||
// Created by Maurice Parker on 9/21/19.
|
||||
// Copyright © 2019 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import WebKit
|
||||
|
||||
/// WKWebView has an awful behavior of a flash to white on first load when in dark mode.
|
||||
/// Keep a queue of WebViews where we've already done a trivial load so that by the time we need them in the UI, they're past the flash-to-shite part of their lifecycle.
|
||||
class DetailViewControllerWebViewProvider: NSObject, WKNavigationDelegate {
|
||||
|
||||
static let shared = DetailViewControllerWebViewProvider()
|
||||
|
||||
private let minimumQueueDepth = 3
|
||||
private let maximumQueueDepth = 6
|
||||
private var queue: [WKWebView] = []
|
||||
|
||||
private var waitingForFirstLoad = true
|
||||
private var waitingCompletionHandler: ((WKWebView) -> ())?
|
||||
|
||||
func dequeueWebView(completion: @escaping (WKWebView) -> ()) {
|
||||
if waitingForFirstLoad {
|
||||
waitingCompletionHandler = completion
|
||||
} else {
|
||||
completeRequest(completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
func enqueueWebView(_ webView: WKWebView) {
|
||||
guard queue.count < maximumQueueDepth else {
|
||||
return
|
||||
}
|
||||
|
||||
webView.navigationDelegate = self
|
||||
queue.insert(webView, at: 0)
|
||||
|
||||
webView.loadHTMLString(ArticleRenderer.page.html, baseURL: ArticleRenderer.page.baseURL)
|
||||
|
||||
}
|
||||
|
||||
// MARK: WKNavigationDelegate
|
||||
|
||||
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
|
||||
if waitingForFirstLoad {
|
||||
waitingForFirstLoad = false
|
||||
if let completion = waitingCompletionHandler {
|
||||
completeRequest(completion: completion)
|
||||
waitingCompletionHandler = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private override init() {
|
||||
super.init()
|
||||
replenishQueueIfNeeded()
|
||||
}
|
||||
|
||||
private func replenishQueueIfNeeded() {
|
||||
while queue.count < minimumQueueDepth {
|
||||
let webView = WKWebView(frame: .zero)
|
||||
enqueueWebView(webView)
|
||||
}
|
||||
}
|
||||
|
||||
private func completeRequest(completion: @escaping (WKWebView) -> ()) {
|
||||
if let webView = queue.popLast() {
|
||||
webView.navigationDelegate = nil
|
||||
replenishQueueIfNeeded()
|
||||
completion(webView)
|
||||
return
|
||||
}
|
||||
|
||||
assertionFailure("Creating WKWebView in \(#function); queue has run dry.")
|
||||
let webView = WKWebView(frame: .zero)
|
||||
completion(webView)
|
||||
}
|
||||
|
||||
}
|
||||
+1
-22
@@ -6,29 +6,8 @@
|
||||
color-scheme: light dark;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
function mouseDidEnterLink(anchor) {
|
||||
window.webkit.messageHandlers.mouseDidEnter.postMessage(anchor.href);
|
||||
}
|
||||
|
||||
function mouseDidExitLink(anchor) {
|
||||
window.webkit.messageHandlers.mouseDidExit.postMessage(anchor.href);
|
||||
}
|
||||
|
||||
function render(data) {
|
||||
document.getElementsByTagName("style")[0].innerHTML = data.style;
|
||||
document.body.innerHTML = data.body;
|
||||
window.scrollTo(0, 0);
|
||||
document.getElementsByTagName("body")[0].querySelectorAll("style, link[rel=stylesheet]").forEach(element => element.remove());
|
||||
document.getElementsByTagName("body")[0].querySelectorAll("[style]").forEach(element => element.removeAttribute("style"));
|
||||
}
|
||||
|
||||
function error() {
|
||||
document.body.innerHTML = "error";
|
||||
}
|
||||
</script>
|
||||
<script src="main.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
@@ -133,6 +133,20 @@ figcaption {
|
||||
line-height: 1.3em;
|
||||
}
|
||||
|
||||
.iframeWrap {
|
||||
position: relative;
|
||||
display: block;
|
||||
padding-top: 56.25%;
|
||||
}
|
||||
|
||||
.iframeWrap iframe {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100% !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
/*Block ads and junk*/
|
||||
|
||||
iframe[src*="feedads"],
|
||||
|
||||
+1
-1
Submodule submodules/RSCore updated: 4dbd31b090...960eaf6033
Reference in New Issue
Block a user