Merge branch 'master' of https://github.com/brentsimmons/NetNewsWire
This commit is contained in:
commit
20f8fe91df
@ -15,6 +15,7 @@ import RSWeb
|
|||||||
enum DetailState: Equatable {
|
enum DetailState: Equatable {
|
||||||
case noSelection
|
case noSelection
|
||||||
case multipleSelection
|
case multipleSelection
|
||||||
|
case loading
|
||||||
case article(Article)
|
case article(Article)
|
||||||
case extracted(Article, ExtractedArticle)
|
case extracted(Article, ExtractedArticle)
|
||||||
}
|
}
|
||||||
|
@ -104,13 +104,8 @@ final class DetailWebViewController: NSViewController, WKUIDelegate {
|
|||||||
NotificationCenter.default.addObserver(self, selector: #selector(webInspectorEnabledDidChange(_:)), name: .WebInspectorEnabledDidChange, object: nil)
|
NotificationCenter.default.addObserver(self, selector: #selector(webInspectorEnabledDidChange(_:)), name: .WebInspectorEnabledDidChange, object: nil)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
webView.loadHTMLString(template(), baseURL: nil)
|
webView.loadHTMLString(ArticleRenderer.page.html, baseURL: ArticleRenderer.page.baseURL)
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Scrolling
|
// MARK: Scrolling
|
||||||
@ -182,13 +177,15 @@ private extension DetailWebViewController {
|
|||||||
|
|
||||||
func reloadHTML() {
|
func reloadHTML() {
|
||||||
let style = ArticleStylesManager.shared.currentStyle
|
let style = ArticleStylesManager.shared.currentStyle
|
||||||
let rendering: ArticleRendering
|
let rendering: ArticleRenderer.Rendering
|
||||||
|
|
||||||
switch state {
|
switch state {
|
||||||
case .noSelection:
|
case .noSelection:
|
||||||
rendering = ArticleRenderer.noSelectionHTML(style: style)
|
rendering = ArticleRenderer.noSelectionHTML(style: style)
|
||||||
case .multipleSelection:
|
case .multipleSelection:
|
||||||
rendering = ArticleRenderer.multipleSelectionHTML(style: style)
|
rendering = ArticleRenderer.multipleSelectionHTML(style: style)
|
||||||
|
case .loading:
|
||||||
|
rendering = ArticleRenderer.loadingHTML(style: style)
|
||||||
case .article(let article):
|
case .article(let article):
|
||||||
rendering = ArticleRenderer.articleHTML(article: article, style: style)
|
rendering = ArticleRenderer.articleHTML(article: article, style: style)
|
||||||
case .extracted(let article, let extractedArticle):
|
case .extracted(let article, let extractedArticle):
|
||||||
|
@ -2,33 +2,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<style>
|
<style>
|
||||||
</style>
|
</style>
|
||||||
<script>
|
<script src="main.js"></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>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
</body>
|
</body>
|
||||||
|
@ -133,6 +133,20 @@ figcaption {
|
|||||||
line-height: 1.3em;
|
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*/
|
/*Block ads and junk*/
|
||||||
|
|
||||||
iframe[src*="feedads"],
|
iframe[src*="feedads"],
|
||||||
|
@ -329,11 +329,7 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations {
|
|||||||
detailViewController?.setState(detailState, mode: timelineSourceMode)
|
detailViewController?.setState(detailState, mode: timelineSourceMode)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if let extractor = ArticleExtractor(currentLink) {
|
startArticleExtractorForCurrentLink()
|
||||||
extractor.delegate = self
|
|
||||||
extractor.process()
|
|
||||||
articleExtractor = extractor
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -453,18 +449,24 @@ extension MainWindowController: SidebarDelegate {
|
|||||||
extension MainWindowController: TimelineContainerViewControllerDelegate {
|
extension MainWindowController: TimelineContainerViewControllerDelegate {
|
||||||
|
|
||||||
func timelineSelectionDidChange(_: TimelineContainerViewController, articles: [Article]?, mode: TimelineSourceMode) {
|
func timelineSelectionDidChange(_: TimelineContainerViewController, articles: [Article]?, mode: TimelineSourceMode) {
|
||||||
|
|
||||||
articleExtractor?.cancel()
|
articleExtractor?.cancel()
|
||||||
articleExtractor = nil
|
articleExtractor = nil
|
||||||
isShowingExtractedArticle = false
|
isShowingExtractedArticle = false
|
||||||
|
|
||||||
makeToolbarValidate()
|
makeToolbarValidate()
|
||||||
|
|
||||||
let detailState: DetailState
|
let detailState: DetailState
|
||||||
if let articles = articles {
|
if let articles = articles {
|
||||||
detailState = articles.count == 1 ? .article(articles.first!) : .multipleSelection
|
if articles.count == 1 {
|
||||||
|
if articles.first?.feed?.isArticleExtractorAlwaysOn ?? false {
|
||||||
|
detailState = .loading
|
||||||
|
startArticleExtractorForCurrentLink()
|
||||||
|
} else {
|
||||||
|
detailState = .article(articles.first!)
|
||||||
}
|
}
|
||||||
else {
|
} else {
|
||||||
|
detailState = .multipleSelection
|
||||||
|
}
|
||||||
|
} else {
|
||||||
detailState = .noSelection
|
detailState = .noSelection
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -834,6 +836,14 @@ private extension MainWindowController {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func startArticleExtractorForCurrentLink() {
|
||||||
|
if let link = currentLink, let extractor = ArticleExtractor(link) {
|
||||||
|
extractor.delegate = self
|
||||||
|
extractor.process()
|
||||||
|
articleExtractor = extractor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func saveSplitViewState() {
|
func saveSplitViewState() {
|
||||||
// TODO: Update this for multiple windows.
|
// TODO: Update this for multiple windows.
|
||||||
// Also: use standard state restoration mechanism.
|
// Also: use standard state restoration mechanism.
|
||||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -78,6 +78,9 @@
|
|||||||
515D4FCC2325815A00EE1167 /* SafariExt.js in Resources */ = {isa = PBXBuildFile; fileRef = 515D4FCB2325815A00EE1167 /* SafariExt.js */; };
|
515D4FCC2325815A00EE1167 /* SafariExt.js in Resources */ = {isa = PBXBuildFile; fileRef = 515D4FCB2325815A00EE1167 /* SafariExt.js */; };
|
||||||
51707439232AA97100A461A3 /* ShareFolderPickerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51707438232AA97100A461A3 /* ShareFolderPickerController.swift */; };
|
51707439232AA97100A461A3 /* ShareFolderPickerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51707438232AA97100A461A3 /* ShareFolderPickerController.swift */; };
|
||||||
5170743A232AABFC00A461A3 /* FlattenedAccountFolderPickerData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C452812265093600C03939 /* FlattenedAccountFolderPickerData.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 */; };
|
5183CCD0226E1E880010922C /* NonIntrinsicLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCCF226E1E880010922C /* NonIntrinsicLabel.swift */; };
|
||||||
5183CCDA226E31A50010922C /* NonIntrinsicImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCD9226E31A50010922C /* NonIntrinsicImageView.swift */; };
|
5183CCDA226E31A50010922C /* NonIntrinsicImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCD9226E31A50010922C /* NonIntrinsicImageView.swift */; };
|
||||||
5183CCDD226F1F5C0010922C /* NavigationProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCDC226F1F5C0010922C /* NavigationProgressView.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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
5183CCDC226F1F5C0010922C /* NavigationProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationProgressView.swift; sourceTree = "<group>"; };
|
||||||
@ -1370,6 +1375,7 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
51C4527E2265092C00C03939 /* DetailViewController.swift */,
|
51C4527E2265092C00C03939 /* DetailViewController.swift */,
|
||||||
|
517630222336657E00E15FFF /* DetailViewControllerWebViewProvider.swift */,
|
||||||
515ADE3F22E11FAE006B2460 /* SystemMessageViewController.swift */,
|
515ADE3F22E11FAE006B2460 /* SystemMessageViewController.swift */,
|
||||||
);
|
);
|
||||||
path = Detail;
|
path = Detail;
|
||||||
@ -1393,6 +1399,7 @@
|
|||||||
49F40DEF2335B71000552BF4 /* newsfoot.js */,
|
49F40DEF2335B71000552BF4 /* newsfoot.js */,
|
||||||
849A977D1ED9EC42007D329B /* ArticleRenderer.swift */,
|
849A977D1ED9EC42007D329B /* ArticleRenderer.swift */,
|
||||||
848362FE2262A30E00DA1D35 /* template.html */,
|
848362FE2262A30E00DA1D35 /* template.html */,
|
||||||
|
517630032336215100E15FFF /* main.js */,
|
||||||
);
|
);
|
||||||
path = "Article Rendering";
|
path = "Article Rendering";
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -2502,6 +2509,7 @@
|
|||||||
isa = PBXResourcesBuildPhase;
|
isa = PBXResourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
517630052336215100E15FFF /* main.js in Resources */,
|
||||||
511D43D0231FA62500FB1562 /* TimelineKeyboardShortcuts.plist in Resources */,
|
511D43D0231FA62500FB1562 /* TimelineKeyboardShortcuts.plist in Resources */,
|
||||||
51C452862265093600C03939 /* Add.storyboard in Resources */,
|
51C452862265093600C03939 /* Add.storyboard in Resources */,
|
||||||
511D43EF231FBDE900FB1562 /* LaunchScreenPad.storyboard in Resources */,
|
511D43EF231FBDE900FB1562 /* LaunchScreenPad.storyboard in Resources */,
|
||||||
@ -2537,6 +2545,7 @@
|
|||||||
51EF0F8E2279C9260050506E /* AccountsAdd.xib in Resources */,
|
51EF0F8E2279C9260050506E /* AccountsAdd.xib in Resources */,
|
||||||
84C9FC8F22629E8F00D921D6 /* NetNewsWire.sdef in Resources */,
|
84C9FC8F22629E8F00D921D6 /* NetNewsWire.sdef in Resources */,
|
||||||
84C9FC7D22629E1200D921D6 /* AccountsDetail.xib in Resources */,
|
84C9FC7D22629E1200D921D6 /* AccountsDetail.xib in Resources */,
|
||||||
|
517630042336215100E15FFF /* main.js in Resources */,
|
||||||
5144EA362279FC3D00D19003 /* AccountsAddLocal.xib in Resources */,
|
5144EA362279FC3D00D19003 /* AccountsAddLocal.xib in Resources */,
|
||||||
84C9FC8C22629E8F00D921D6 /* KeyboardShortcuts.html in Resources */,
|
84C9FC8C22629E8F00D921D6 /* KeyboardShortcuts.html in Resources */,
|
||||||
5144EA3B227A379E00D19003 /* ImportOPMLSheet.xib in Resources */,
|
5144EA3B227A379E00D19003 /* ImportOPMLSheet.xib in Resources */,
|
||||||
@ -2702,6 +2711,7 @@
|
|||||||
51C452A722650A3D00C03939 /* RSImage-Extensions.swift in Sources */,
|
51C452A722650A3D00C03939 /* RSImage-Extensions.swift in Sources */,
|
||||||
51C45269226508F600C03939 /* MasterFeedTableViewCell.swift in Sources */,
|
51C45269226508F600C03939 /* MasterFeedTableViewCell.swift in Sources */,
|
||||||
51F85BFD2275DCA800C787DC /* SingleLineUILabelSizer.swift in Sources */,
|
51F85BFD2275DCA800C787DC /* SingleLineUILabelSizer.swift in Sources */,
|
||||||
|
517630232336657E00E15FFF /* DetailViewControllerWebViewProvider.swift in Sources */,
|
||||||
51C4528F226509BD00C03939 /* UnreadFeed.swift in Sources */,
|
51C4528F226509BD00C03939 /* UnreadFeed.swift in Sources */,
|
||||||
51AF460E232488C6001742EF /* Account-Extensions.swift in Sources */,
|
51AF460E232488C6001742EF /* Account-Extensions.swift in Sources */,
|
||||||
5183CCDD226F1F5C0010922C /* NavigationProgressView.swift in Sources */,
|
5183CCDD226F1F5C0010922C /* NavigationProgressView.swift in Sources */,
|
||||||
|
@ -11,10 +11,18 @@ import RSCore
|
|||||||
import Articles
|
import Articles
|
||||||
import Account
|
import Account
|
||||||
|
|
||||||
typealias ArticleRendering = (style: String, html: String)
|
|
||||||
|
|
||||||
struct ArticleRenderer {
|
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 article: Article?
|
||||||
private let extractedArticle: ExtractedArticle?
|
private let extractedArticle: ExtractedArticle?
|
||||||
private let articleStyle: ArticleStyle
|
private let articleStyle: ArticleStyle
|
||||||
@ -38,22 +46,27 @@ struct ArticleRenderer {
|
|||||||
|
|
||||||
// MARK: - API
|
// 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)
|
let renderer = ArticleRenderer(article: article, extractedArticle: extractedArticle, style: style)
|
||||||
return (renderer.styleString(), renderer.articleHTML)
|
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)
|
let renderer = ArticleRenderer(article: nil, extractedArticle: nil, style: style)
|
||||||
return (renderer.styleString(), renderer.multipleSelectionHTML)
|
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)
|
let renderer = ArticleRenderer(article: nil, extractedArticle: nil, style: style)
|
||||||
return (renderer.styleString(), renderer.noSelectionHTML)
|
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)
|
let renderer = ArticleRenderer(article: nil, extractedArticle: nil, style: style)
|
||||||
return (renderer.styleString(), renderer.noContentHTML)
|
return (renderer.styleString(), renderer.noContentHTML)
|
||||||
}
|
}
|
||||||
@ -73,6 +86,11 @@ private extension ArticleRenderer {
|
|||||||
return renderHTML(withBody: body)
|
return renderHTML(withBody: body)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var loadingHTML: String {
|
||||||
|
let body = "<h3 class='systemMessage'>Loading...</h3>"
|
||||||
|
return renderHTML(withBody: body)
|
||||||
|
}
|
||||||
|
|
||||||
private var noSelectionHTML: String {
|
private var noSelectionHTML: String {
|
||||||
let body = "<h3 class='systemMessage'>No selection</h3>"
|
let body = "<h3 class='systemMessage'>No selection</h3>"
|
||||||
return renderHTML(withBody: body)
|
return renderHTML(withBody: body)
|
||||||
|
43
Shared/Article Rendering/main.js
Normal file
43
Shared/Article Rendering/main.js
Normal file
@ -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() {
|
override func viewDidLoad() {
|
||||||
super.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(unreadCountDidChange(_:)), name: .UnreadCountDidChange, object: nil)
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(statusesDidChange(_:)), name: .StatusesDidChange, 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(progressDidChange(_:)), name: .AccountRefreshProgressDidChange, object: nil)
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(contentSizeCategoryDidChange(_:)), name: UIContentSizeCategory.didChangeNotification, 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) {
|
override func viewDidAppear(_ animated: Bool) {
|
||||||
@ -243,6 +239,11 @@ extension DetailViewController: WKNavigationDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
|
||||||
|
self.updateArticleSelection()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Private
|
// MARK: Private
|
||||||
@ -261,60 +262,3 @@ private struct TemplateData: Codable {
|
|||||||
let style: String
|
let style: String
|
||||||
let body: 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
84
iOS/Detail/DetailViewControllerWebViewProvider.swift
Normal file
84
iOS/Detail/DetailViewControllerWebViewProvider.swift
Normal file
@ -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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -6,29 +6,8 @@
|
|||||||
color-scheme: light dark;
|
color-scheme: light dark;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<script>
|
<script src="main.js"></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>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
@ -133,6 +133,20 @@ figcaption {
|
|||||||
line-height: 1.3em;
|
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*/
|
/*Block ads and junk*/
|
||||||
|
|
||||||
iframe[src*="feedads"],
|
iframe[src*="feedads"],
|
||||||
|
@ -1 +1 @@
|
|||||||
Subproject commit 4dbd31b090ab15c3966e9810a65edbf4abdbdd33
|
Subproject commit 960eaf60336f592306fb1bf6f5a62800a9c5050f
|
Loading…
x
Reference in New Issue
Block a user