2022-01-10 04:38:44 +00:00
|
|
|
//
|
|
|
|
// ReadViewController.swift
|
|
|
|
// Reader
|
|
|
|
//
|
|
|
|
// Created by Shadowfacts on 1/9/22.
|
|
|
|
//
|
|
|
|
|
|
|
|
import UIKit
|
|
|
|
import WebKit
|
|
|
|
import HTMLEntities
|
2022-01-11 03:34:29 +00:00
|
|
|
import SafariServices
|
2022-01-10 04:38:44 +00:00
|
|
|
|
|
|
|
class ReadViewController: UIViewController {
|
|
|
|
|
|
|
|
private static let publishedFormatter: DateFormatter = {
|
|
|
|
let f = DateFormatter()
|
|
|
|
f.dateStyle = .medium
|
|
|
|
f.timeStyle = .medium
|
|
|
|
return f
|
|
|
|
}()
|
|
|
|
|
|
|
|
let fervorController: FervorController
|
|
|
|
let item: Item
|
|
|
|
|
|
|
|
override var prefersStatusBarHidden: Bool {
|
|
|
|
navigationController?.isNavigationBarHidden ?? false
|
|
|
|
}
|
|
|
|
|
|
|
|
override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation {
|
|
|
|
.slide
|
|
|
|
}
|
|
|
|
|
|
|
|
init(item: Item, fervorController: FervorController) {
|
|
|
|
self.fervorController = fervorController
|
|
|
|
self.item = item
|
|
|
|
|
|
|
|
super.init(nibName: nil, bundle: nil)
|
|
|
|
}
|
|
|
|
|
|
|
|
required init?(coder: NSCoder) {
|
|
|
|
fatalError("init(coder:) has not been implemented")
|
|
|
|
}
|
|
|
|
|
|
|
|
override func viewDidLoad() {
|
|
|
|
super.viewDidLoad()
|
|
|
|
|
|
|
|
navigationItem.largeTitleDisplayMode = .never
|
|
|
|
|
|
|
|
view.backgroundColor = .appBackground
|
|
|
|
|
|
|
|
let webView = WKWebView()
|
|
|
|
webView.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
webView.navigationDelegate = self
|
2022-01-11 03:34:29 +00:00
|
|
|
webView.uiDelegate = self
|
2022-01-10 04:38:44 +00:00
|
|
|
if let content = itemContentHTML() {
|
|
|
|
// todo: using the bundle url is the only way to get the stylesheet to load, but feels wrong
|
|
|
|
// will break, e.g., images with relative urls
|
|
|
|
webView.loadHTMLString(content, baseURL: Bundle.main.bundleURL)
|
|
|
|
}
|
|
|
|
view.addSubview(webView)
|
|
|
|
|
|
|
|
NSLayoutConstraint.activate([
|
|
|
|
webView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
|
|
|
webView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
|
|
|
webView.topAnchor.constraint(equalTo: view.topAnchor),
|
|
|
|
webView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
|
|
|
|
])
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
private func itemContentHTML() -> String? {
|
|
|
|
guard let content = item.content else {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
var info = ""
|
|
|
|
if let title = item.title, !title.isEmpty {
|
|
|
|
info += "<h1 id=\"item-title\">"
|
|
|
|
if let url = item.url {
|
|
|
|
info += "<a href=\"\(url.absoluteString)\">"
|
|
|
|
}
|
|
|
|
info += title.htmlEscape()
|
|
|
|
if item.url != nil {
|
|
|
|
info += "</a>"
|
|
|
|
}
|
|
|
|
info += "</h1>"
|
|
|
|
}
|
|
|
|
if let feedTitle = item.feed!.title, !feedTitle.isEmpty {
|
|
|
|
info += "<h2 id=\"item-feed-title\">\(feedTitle.htmlEscape())</h2>"
|
|
|
|
}
|
|
|
|
if let author = item.author, !author.isEmpty {
|
|
|
|
info += "<h3 id=\"item-author\">\(author)</h3>"
|
|
|
|
}
|
|
|
|
if let published = item.published {
|
|
|
|
let formatted = ReadViewController.publishedFormatter.string(from: published)
|
|
|
|
info += "<h3 id=\"item-published\">\(formatted)</h3>"
|
|
|
|
}
|
|
|
|
return """
|
|
|
|
<!DOCTYPE html>
|
|
|
|
<html>
|
|
|
|
<head>
|
|
|
|
<meta charset="UTF-8" />
|
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no" />
|
|
|
|
<link rel="stylesheet" href="\(Bundle.main.url(forResource: "read", withExtension: "css")!.absoluteString)" />
|
|
|
|
</head>
|
|
|
|
<body>
|
|
|
|
<div id="item-info">
|
|
|
|
\(info)
|
|
|
|
</div>
|
|
|
|
<div id="item-content">
|
|
|
|
\(content)
|
|
|
|
</div>
|
|
|
|
</body>
|
|
|
|
</html>
|
|
|
|
"""
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
extension ReadViewController: WKNavigationDelegate {
|
|
|
|
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction) async -> WKNavigationActionPolicy {
|
|
|
|
let url = navigationAction.request.url!
|
|
|
|
if url == Bundle.main.bundleURL {
|
|
|
|
return .allow
|
2022-01-11 03:34:29 +00:00
|
|
|
} else {
|
|
|
|
present(SFSafariViewController(url: url), animated: true)
|
|
|
|
return .cancel
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
extension ReadViewController: WKUIDelegate {
|
|
|
|
func webView(_ webView: WKWebView, contextMenuConfigurationFor elementInfo: WKContextMenuElementInfo) async -> UIContextMenuConfiguration? {
|
|
|
|
guard let url = elementInfo.linkURL,
|
|
|
|
["http", "https"].contains(url.scheme?.lowercased()) else {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return UIContextMenuConfiguration(identifier: nil) {
|
|
|
|
SFSafariViewController(url: url)
|
|
|
|
} actionProvider: { _ in
|
|
|
|
return UIMenu(children: [
|
|
|
|
UIAction(title: "Open in Safari", image: UIImage(systemName: "safari"), handler: { [weak self] _ in
|
|
|
|
self?.present(SFSafariViewController(url: url), animated: true)
|
|
|
|
}),
|
|
|
|
UIAction(title: "Share", image: UIImage(systemName: "square.and.arrow.up"), handler: { [weak self] _ in
|
|
|
|
self?.present(UIActivityViewController(activityItems: [url], applicationActivities: nil), animated: true)
|
|
|
|
}),
|
|
|
|
])
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
func webView(_ webView: WKWebView, contextMenuForElement elementInfo: WKContextMenuElementInfo, willCommitWithAnimator animator: UIContextMenuInteractionCommitAnimating) {
|
|
|
|
if let vc = animator.previewViewController as? SFSafariViewController {
|
|
|
|
animator.preferredCommitStyle = .pop
|
|
|
|
animator.addCompletion {
|
|
|
|
self.present(vc, animated: true)
|
|
|
|
}
|
2022-01-10 04:38:44 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|