Clean up ArticleRenderer code a bit. Dropped over 100 lines. Fix #287.
This commit is contained in:
parent
6416cb6bf2
commit
0f48ac3e03
|
@ -11,78 +11,9 @@ import RSCore
|
||||||
import Articles
|
import Articles
|
||||||
import Account
|
import Account
|
||||||
|
|
||||||
var cachedStyleString = ""
|
|
||||||
var cachedTemplate = ""
|
|
||||||
|
|
||||||
// NOTE: THIS CODE IS A TOTAL MESS RIGHT NOW WHILE WE’RE EXPERIMENTING WITH DIFFERENT LAYOUTS. DON’T JUDGE, YOU!
|
|
||||||
|
|
||||||
class ArticleRenderer {
|
class ArticleRenderer {
|
||||||
|
|
||||||
let article: Article?
|
let baseURL: URL?
|
||||||
let articleStyle: ArticleStyle
|
|
||||||
let appearance: NSAppearance?
|
|
||||||
|
|
||||||
static var faviconImgTagCache = [Feed: String]()
|
|
||||||
static var feedIconImgTagCache = [Feed: String]()
|
|
||||||
|
|
||||||
lazy var longDateFormatter: DateFormatter = {
|
|
||||||
let dateFormatter = DateFormatter()
|
|
||||||
dateFormatter.dateStyle = .long
|
|
||||||
dateFormatter.timeStyle = .medium
|
|
||||||
return dateFormatter
|
|
||||||
}()
|
|
||||||
|
|
||||||
lazy var mediumDateFormatter: DateFormatter = {
|
|
||||||
let dateFormatter = DateFormatter()
|
|
||||||
dateFormatter.dateStyle = .medium
|
|
||||||
dateFormatter.timeStyle = .short
|
|
||||||
return dateFormatter
|
|
||||||
}()
|
|
||||||
|
|
||||||
lazy var shortDateFormatter: DateFormatter = {
|
|
||||||
let dateFormatter = DateFormatter()
|
|
||||||
dateFormatter.dateStyle = .short
|
|
||||||
dateFormatter.timeStyle = .short
|
|
||||||
return dateFormatter
|
|
||||||
}()
|
|
||||||
|
|
||||||
lazy var title: String = {
|
|
||||||
if let articleTitle = self.article?.title {
|
|
||||||
return articleTitle
|
|
||||||
}
|
|
||||||
|
|
||||||
return ""
|
|
||||||
}()
|
|
||||||
|
|
||||||
lazy var baseURL: URL? = {
|
|
||||||
|
|
||||||
var s = self.article?.url
|
|
||||||
if s == nil {
|
|
||||||
s = self.article?.feed?.homePageURL
|
|
||||||
}
|
|
||||||
if s == nil {
|
|
||||||
s = self.article?.feed?.url
|
|
||||||
}
|
|
||||||
if s == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var urlComponents = URLComponents(string: s!)
|
|
||||||
if urlComponents == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Can’t use url-with-fragment as base URL. The webview won’t load. See scripting.com/rss.xml for example.
|
|
||||||
urlComponents!.fragment = nil
|
|
||||||
|
|
||||||
if let url = urlComponents!.url {
|
|
||||||
if url.scheme == "http" || url.scheme == "https" {
|
|
||||||
return url
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}()
|
|
||||||
|
|
||||||
var articleHTML: String {
|
var articleHTML: String {
|
||||||
let body = RSMacroProcessor.renderedText(withTemplate: template(), substitutions: substitutions(), macroStart: "[[", macroEnd: "]]")
|
let body = RSMacroProcessor.renderedText(withTemplate: template(), substitutions: substitutions(), macroStart: "[[", macroEnd: "]]")
|
||||||
|
@ -93,86 +24,70 @@ class ArticleRenderer {
|
||||||
let body = "<h3 class='systemMessage'>Multiple selection</h3>"
|
let body = "<h3 class='systemMessage'>Multiple selection</h3>"
|
||||||
return renderHTML(withBody: body)
|
return renderHTML(withBody: body)
|
||||||
}
|
}
|
||||||
|
|
||||||
var noSelectionHTML: String {
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private let article: Article?
|
||||||
|
private let articleStyle: ArticleStyle
|
||||||
|
private let appearance: NSAppearance?
|
||||||
|
private let title: String
|
||||||
|
|
||||||
init(article: Article?, style: ArticleStyle, appearance: NSAppearance? = nil) {
|
init(article: Article?, style: ArticleStyle, appearance: NSAppearance? = nil) {
|
||||||
self.article = article
|
self.article = article
|
||||||
self.articleStyle = style
|
self.articleStyle = style
|
||||||
self.appearance = appearance
|
self.appearance = appearance
|
||||||
}
|
self.title = article?.title ?? ""
|
||||||
|
if let article = article {
|
||||||
// MARK: Private
|
self.baseURL = ArticleRenderer.baseURL(for: article)
|
||||||
|
|
||||||
private func textInsideTag(_ text: String, _ tag: String) -> String {
|
|
||||||
|
|
||||||
return "<\(tag)>\(text)</\(tag)>"
|
|
||||||
}
|
|
||||||
|
|
||||||
private func styleString() -> String {
|
|
||||||
|
|
||||||
if let s = articleStyle.css {
|
|
||||||
return s
|
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
if cachedStyleString.isEmpty {
|
self.baseURL = nil
|
||||||
|
|
||||||
let path = Bundle.main.path(forResource: "styleSheet", ofType: "css")!
|
|
||||||
let s = try! NSString(contentsOfFile: path, encoding: String.Encoding.utf8.rawValue)
|
|
||||||
cachedStyleString = "\n\(s)\n"
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return cachedStyleString
|
// MARK: Private
|
||||||
|
|
||||||
|
private extension ArticleRenderer {
|
||||||
|
|
||||||
|
static var faviconImgTagCache = [Feed: String]()
|
||||||
|
static var feedIconImgTagCache = [Feed: String]()
|
||||||
|
|
||||||
|
static var defaultStyleSheet: String = {
|
||||||
|
let path = Bundle.main.path(forResource: "styleSheet", ofType: "css")!
|
||||||
|
let s = try! NSString(contentsOfFile: path, encoding: String.Encoding.utf8.rawValue)
|
||||||
|
return "\n\(s)\n"
|
||||||
|
}()
|
||||||
|
|
||||||
|
static let defaultTemplate: String = {
|
||||||
|
let path = Bundle.main.path(forResource: "template", ofType: "html")!
|
||||||
|
let s = try! NSString(contentsOfFile: path, encoding: String.Encoding.utf8.rawValue)
|
||||||
|
return s as String
|
||||||
|
}()
|
||||||
|
|
||||||
|
// func textInsideTag(_ text: String, _ tag: String) -> String {
|
||||||
|
// return "<\(tag)>\(text)</\(tag)>"
|
||||||
|
// }
|
||||||
|
|
||||||
|
func styleString() -> String {
|
||||||
|
return articleStyle.css ?? ArticleRenderer.defaultStyleSheet
|
||||||
}
|
}
|
||||||
|
|
||||||
private func template() -> String {
|
func template() -> String {
|
||||||
|
return articleStyle.template ?? ArticleRenderer.defaultTemplate
|
||||||
if let s = articleStyle.template {
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
if cachedTemplate.isEmpty {
|
|
||||||
let path = Bundle.main.path(forResource: "template", ofType: "html")!
|
|
||||||
let s = try! NSString(contentsOfFile: path, encoding: String.Encoding.utf8.rawValue)
|
|
||||||
cachedTemplate = s as String
|
|
||||||
}
|
|
||||||
|
|
||||||
return cachedTemplate
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func linkWithTextAndClass(_ text: String, _ href: String, _ className: String) -> String {
|
func titleOrTitleLink() -> String {
|
||||||
|
|
||||||
return "<a class=\"\(className)\" href=\"\(href)\">\(text)</a>"
|
|
||||||
}
|
|
||||||
|
|
||||||
private func linkWithText(_ text: String, _ href: String) -> String {
|
|
||||||
|
|
||||||
return ArticleRenderer.linkWithText(text, href)
|
|
||||||
}
|
|
||||||
|
|
||||||
private static func linkWithText(_ text: String, _ href: String) -> String {
|
|
||||||
|
|
||||||
return "<a href=\"\(href)\">\(text)</a>"
|
|
||||||
}
|
|
||||||
|
|
||||||
private func linkWithLink(_ href: String) -> String {
|
|
||||||
|
|
||||||
return linkWithText(href, href)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func titleOrTitleLink() -> String {
|
|
||||||
|
|
||||||
if let link = article?.preferredLink {
|
if let link = article?.preferredLink {
|
||||||
return linkWithText(title, link)
|
return title.htmlByAddingLink(link)
|
||||||
}
|
}
|
||||||
return title
|
return title
|
||||||
}
|
}
|
||||||
|
|
||||||
private func substitutions() -> [String: String] {
|
func substitutions() -> [String: String] {
|
||||||
|
|
||||||
var d = [String: String]()
|
var d = [String: String]()
|
||||||
|
|
||||||
guard let article = article else {
|
guard let article = article else {
|
||||||
|
@ -183,13 +98,12 @@ class ArticleRenderer {
|
||||||
let title = titleOrTitleLink()
|
let title = titleOrTitleLink()
|
||||||
d["title"] = title
|
d["title"] = title
|
||||||
|
|
||||||
let body = article.body == nil ? "" : article.body
|
let body = article.body ?? ""
|
||||||
d["body"] = body
|
d["body"] = body
|
||||||
|
|
||||||
d["avatars"] = ""
|
d["avatars"] = ""
|
||||||
var didAddAvatar = false
|
var didAddAvatar = false
|
||||||
if let avatarHTML = avatarImgTag() {
|
if let avatarHTML = avatarImgTag() {
|
||||||
// d["avatars"] = avatarHTML
|
|
||||||
d["avatars"] = "<td class=\"header rightAlign avatar\">\(avatarHTML)</td>";
|
d["avatars"] = "<td class=\"header rightAlign avatar\">\(avatarHTML)</td>";
|
||||||
didAddAvatar = true
|
didAddAvatar = true
|
||||||
}
|
}
|
||||||
|
@ -198,27 +112,26 @@ class ArticleRenderer {
|
||||||
if let feedTitle = article.feed?.nameForDisplay {
|
if let feedTitle = article.feed?.nameForDisplay {
|
||||||
feedLink = feedTitle
|
feedLink = feedTitle
|
||||||
if let feedURL = article.feed?.homePageURL {
|
if let feedURL = article.feed?.homePageURL {
|
||||||
feedLink = linkWithTextAndClass(feedTitle, feedURL, "feedLink")
|
feedLink = feedLink.htmlByAddingLink(feedURL, className: "feedLink")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
d["feedlink"] = feedLink
|
d["feedlink"] = feedLink
|
||||||
d["feedlink_withfavicon"] = feedLink
|
|
||||||
|
|
||||||
// d["favicon"] = ""
|
|
||||||
if !didAddAvatar, let feed = article.feed {
|
if !didAddAvatar, let feed = article.feed {
|
||||||
if let favicon = faviconImgTag(forFeed: feed) {
|
if let favicon = faviconImgTag(forFeed: feed) {
|
||||||
d["avatars"] = "<td class=\"header rightAlign\">\(favicon)</td>";
|
d["avatars"] = "<td class=\"header rightAlign\">\(favicon)</td>";
|
||||||
// d["favicon"] = favicon
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let longDate = longDateFormatter.string(from: article.logicalDatePublished)
|
let datePublished = article.logicalDatePublished
|
||||||
let mediumDate = mediumDateFormatter.string(from: article.logicalDatePublished)
|
let longDate = dateString(datePublished, .long, .medium)
|
||||||
let shortDate = shortDateFormatter.string(from: article.logicalDatePublished)
|
let mediumDate = dateString(datePublished, .medium, .short)
|
||||||
|
let shortDate = dateString(datePublished, .short, .short)
|
||||||
|
|
||||||
if dateShouldBeLink() || self.title == "", let permalink = article.url {
|
if dateShouldBeLink() || self.title == "", let permalink = article.url {
|
||||||
d["date_long"] = linkWithText(longDate, permalink)
|
d["date_long"] = longDate.htmlByAddingLink(permalink)
|
||||||
d["date_medium"] = linkWithText(mediumDate, permalink)
|
d["date_medium"] = mediumDate.htmlByAddingLink(permalink)
|
||||||
d["date_short"] = linkWithText(shortDate, permalink)
|
d["date_short"] = shortDate.htmlByAddingLink(permalink)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
d["date_long"] = longDate
|
d["date_long"] = longDate
|
||||||
|
@ -227,12 +140,11 @@ class ArticleRenderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
d["byline"] = byline()
|
d["byline"] = byline()
|
||||||
// d["author_avatar"] = authorAvatar()
|
|
||||||
|
|
||||||
return d
|
return d
|
||||||
}
|
}
|
||||||
|
|
||||||
private func dateShouldBeLink() -> Bool {
|
func dateShouldBeLink() -> Bool {
|
||||||
guard let permalink = article?.url else {
|
guard let permalink = article?.url else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -242,21 +154,7 @@ class ArticleRenderer {
|
||||||
return permalink != preferredLink // Make date a link if it’s a different link from the title’s link
|
return permalink != preferredLink // Make date a link if it’s a different link from the title’s link
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Avatar {
|
func faviconImgTag(forFeed feed: Feed) -> String? {
|
||||||
let imageURL: String
|
|
||||||
let url: String?
|
|
||||||
|
|
||||||
func html(dimension: Int) -> String {
|
|
||||||
|
|
||||||
let imageTag = "<img src=\"\(imageURL)\" width=\(dimension) height=\(dimension) />"
|
|
||||||
if let url = url {
|
|
||||||
return linkWithText(imageTag, url)
|
|
||||||
}
|
|
||||||
return imageTag
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func faviconImgTag(forFeed feed: Feed) -> String? {
|
|
||||||
|
|
||||||
if let cachedImgTag = ArticleRenderer.faviconImgTagCache[feed] {
|
if let cachedImgTag = ArticleRenderer.faviconImgTagCache[feed] {
|
||||||
return cachedImgTag
|
return cachedImgTag
|
||||||
|
@ -264,14 +162,14 @@ class ArticleRenderer {
|
||||||
|
|
||||||
if let favicon = appDelegate.faviconDownloader.favicon(for: feed) {
|
if let favicon = appDelegate.faviconDownloader.favicon(for: feed) {
|
||||||
if let s = base64String(forImage: favicon) {
|
if let s = base64String(forImage: favicon) {
|
||||||
var dimension = min(favicon.size.height, CGFloat(avatarDimension)) // Assuming square images.
|
var dimension = min(favicon.size.height, CGFloat(ArticleRenderer.avatarDimension)) // Assuming square images.
|
||||||
dimension = max(dimension, 16) // Some favicons say they’re < 16. Force them larger.
|
dimension = max(dimension, 16) // Some favicons say they’re < 16. Force them larger.
|
||||||
if dimension >= CGFloat(avatarDimension) * 0.8 { //Close enough to scale up.
|
if dimension >= CGFloat(ArticleRenderer.avatarDimension) * 0.8 { //Close enough to scale up.
|
||||||
dimension = CGFloat(avatarDimension)
|
dimension = CGFloat(ArticleRenderer.avatarDimension)
|
||||||
}
|
}
|
||||||
|
|
||||||
let imgTag: String
|
let imgTag: String
|
||||||
if dimension >= CGFloat(avatarDimension) {
|
if dimension >= CGFloat(ArticleRenderer.avatarDimension) {
|
||||||
// Use rounded corners.
|
// Use rounded corners.
|
||||||
imgTag = "<img src=\"data:image/tiff;base64, " + s + "\" height=\(Int(dimension)) width=\(Int(dimension)) style=\"border-radius:4px\" />"
|
imgTag = "<img src=\"data:image/tiff;base64, " + s + "\" height=\(Int(dimension)) width=\(Int(dimension)) style=\"border-radius:4px\" />"
|
||||||
}
|
}
|
||||||
|
@ -286,8 +184,7 @@ class ArticleRenderer {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
private func feedIconImgTag(forFeed feed: Feed) -> String? {
|
func feedIconImgTag(forFeed feed: Feed) -> String? {
|
||||||
|
|
||||||
if let cachedImgTag = ArticleRenderer.feedIconImgTagCache[feed] {
|
if let cachedImgTag = ArticleRenderer.feedIconImgTagCache[feed] {
|
||||||
return cachedImgTag
|
return cachedImgTag
|
||||||
}
|
}
|
||||||
|
@ -303,116 +200,57 @@ class ArticleRenderer {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
private func base64String(forImage image: NSImage) -> String? {
|
func base64String(forImage image: NSImage) -> String? {
|
||||||
|
return image.tiffRepresentation?.base64EncodedString()
|
||||||
|
|
||||||
let d = image.tiffRepresentation
|
|
||||||
return d?.base64EncodedString()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func singleArticleSpecifiedAuthor() -> Author? {
|
func singleArticleSpecifiedAuthor() -> Author? {
|
||||||
|
|
||||||
// The author of this article, if just one.
|
// The author of this article, if just one.
|
||||||
|
|
||||||
if let authors = article?.authors, authors.count == 1 {
|
if let authors = article?.authors, authors.count == 1 {
|
||||||
return authors.first!
|
return authors.first!
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
private func singleFeedSpecifiedAuthor() -> Author? {
|
func singleFeedSpecifiedAuthor() -> Author? {
|
||||||
|
|
||||||
if let authors = article?.feed?.authors, authors.count == 1 {
|
if let authors = article?.feed?.authors, authors.count == 1 {
|
||||||
return authors.first!
|
return authors.first!
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
private func feedAvatar() -> Avatar? {
|
static let avatarDimension = 48
|
||||||
|
|
||||||
guard let feedIconURL = article?.feed?.iconURL else {
|
struct Avatar {
|
||||||
return nil
|
let imageURL: String
|
||||||
|
let url: String?
|
||||||
|
|
||||||
|
func html(dimension: Int) -> String {
|
||||||
|
let imageTag = "<img src=\"\(imageURL)\" width=\(dimension) height=\(dimension) />"
|
||||||
|
if let url = url {
|
||||||
|
return imageTag.htmlByAddingLink(url)
|
||||||
|
}
|
||||||
|
return imageTag
|
||||||
}
|
}
|
||||||
return Avatar(imageURL: feedIconURL, url: article?.feed?.homePageURL ?? article?.feed?.url)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func authorAvatar() -> Avatar? {
|
func avatarImgTag() -> String? {
|
||||||
|
|
||||||
if let author = singleArticleSpecifiedAuthor(), let imageURL = author.avatarURL {
|
if let author = singleArticleSpecifiedAuthor(), let imageURL = author.avatarURL {
|
||||||
return Avatar(imageURL: imageURL, url: author.url)
|
return Avatar(imageURL: imageURL, url: author.url).html(dimension: ArticleRenderer.avatarDimension)
|
||||||
}
|
|
||||||
if let author = singleFeedSpecifiedAuthor(), let imageURL = author.avatarURL {
|
|
||||||
return Avatar(imageURL: imageURL, url: author.url)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
private func avatarsToShow() -> [Avatar]? {
|
|
||||||
|
|
||||||
var avatars = [Avatar]()
|
|
||||||
if let avatar = feedAvatar() {
|
|
||||||
avatars.append(avatar)
|
|
||||||
}
|
|
||||||
if let avatar = authorAvatar() {
|
|
||||||
avatars.append(avatar)
|
|
||||||
}
|
|
||||||
return avatars.isEmpty ? nil : avatars
|
|
||||||
}
|
|
||||||
|
|
||||||
private func avatarToUse() -> Avatar? {
|
|
||||||
|
|
||||||
// Use author if article specifies an author, otherwise use feed icon.
|
|
||||||
// If no feed icon, use feed-specified author.
|
|
||||||
|
|
||||||
if let author = singleArticleSpecifiedAuthor(), let imageURL = author.avatarURL {
|
|
||||||
return Avatar(imageURL: imageURL, url: author.url)
|
|
||||||
}
|
|
||||||
if let feedIconURL = article?.feed?.iconURL {
|
|
||||||
return Avatar(imageURL: feedIconURL, url: article?.feed?.homePageURL ?? article?.feed?.url)
|
|
||||||
}
|
|
||||||
if let author = singleFeedSpecifiedAuthor(), let imageURL = author.avatarURL {
|
|
||||||
return Avatar(imageURL: imageURL, url: author.url)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
private let avatarDimension = 48
|
|
||||||
|
|
||||||
private func avatarImgTag() -> String? {
|
|
||||||
|
|
||||||
if let author = singleArticleSpecifiedAuthor(), let imageURL = author.avatarURL {
|
|
||||||
return Avatar(imageURL: imageURL, url: author.url).html(dimension: avatarDimension)
|
|
||||||
}
|
}
|
||||||
if let feed = article?.feed, let imgTag = feedIconImgTag(forFeed: feed) {
|
if let feed = article?.feed, let imgTag = feedIconImgTag(forFeed: feed) {
|
||||||
return imgTag
|
return imgTag
|
||||||
}
|
}
|
||||||
if let feedIconURL = article?.feed?.iconURL {
|
if let feedIconURL = article?.feed?.iconURL {
|
||||||
return Avatar(imageURL: feedIconURL, url: article?.feed?.homePageURL ?? article?.feed?.url).html(dimension: avatarDimension)
|
return Avatar(imageURL: feedIconURL, url: article?.feed?.homePageURL ?? article?.feed?.url).html(dimension: ArticleRenderer.avatarDimension)
|
||||||
}
|
}
|
||||||
if let author = singleFeedSpecifiedAuthor(), let imageURL = author.avatarURL {
|
if let author = singleFeedSpecifiedAuthor(), let imageURL = author.avatarURL {
|
||||||
return Avatar(imageURL: imageURL, url: author.url).html(dimension: avatarDimension)
|
return Avatar(imageURL: imageURL, url: author.url).html(dimension: ArticleRenderer.avatarDimension)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// private func authorAvatar() -> String {
|
func byline() -> String {
|
||||||
//
|
|
||||||
// guard let authors = article.authors, authors.count == 1, let author = authors.first else {
|
|
||||||
// return ""
|
|
||||||
// }
|
|
||||||
// guard let avatarURL = author.avatarURL else {
|
|
||||||
// return ""
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// var imageTag = "<img src=\"\(avatarURL)\" height=64 width=64 />"
|
|
||||||
// if let authorURL = author.url {
|
|
||||||
// imageTag = linkWithText(imageTag, authorURL)
|
|
||||||
// }
|
|
||||||
// return "<div id=authorAvatar>\(imageTag)</div>"
|
|
||||||
// }
|
|
||||||
|
|
||||||
private func byline() -> String {
|
|
||||||
|
|
||||||
guard let authors = article?.authors ?? article?.feed?.authors, !authors.isEmpty else {
|
guard let authors = article?.authors ?? article?.feed?.authors, !authors.isEmpty else {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
@ -439,11 +277,10 @@ class ArticleRenderer {
|
||||||
byline += emailAddress // probably name plus email address
|
byline += emailAddress // probably name plus email address
|
||||||
}
|
}
|
||||||
else if let name = author.name, let url = author.url {
|
else if let name = author.name, let url = author.url {
|
||||||
byline += linkWithText(name, url)
|
byline += name.htmlByAddingLink(url)
|
||||||
}
|
}
|
||||||
else if let name = author.name, let emailAddress = author.emailAddress {
|
else if let name = author.name, let emailAddress = author.emailAddress {
|
||||||
byline += "\(name) <\(emailAddress)≶"
|
byline += "\(name) <\(emailAddress)≶"
|
||||||
// byline += linkWithText(name, "mailto:\(emailAddress)") //TODO
|
|
||||||
}
|
}
|
||||||
else if let name = author.name {
|
else if let name = author.name {
|
||||||
byline += name
|
byline += name
|
||||||
|
@ -452,19 +289,50 @@ class ArticleRenderer {
|
||||||
byline += "<\(emailAddress)>" // TODO: mailto link
|
byline += "<\(emailAddress)>" // TODO: mailto link
|
||||||
}
|
}
|
||||||
else if let url = author.url {
|
else if let url = author.url {
|
||||||
byline += linkWithLink(url)
|
byline += String.htmlWithLink(url)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return byline
|
return byline
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func renderHTML(withBody body: String) -> String {
|
func dateString(_ date: Date, _ dateStyle: DateFormatter.Style, _ timeStyle: DateFormatter.Style) -> String {
|
||||||
|
let dateFormatter = DateFormatter()
|
||||||
|
dateFormatter.dateStyle = dateStyle
|
||||||
|
dateFormatter.timeStyle = timeStyle
|
||||||
|
return dateFormatter.string(from: date)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func baseURL(for article: Article) -> URL? {
|
||||||
|
var s = article.url
|
||||||
|
if s == nil {
|
||||||
|
s = article.feed?.homePageURL
|
||||||
|
}
|
||||||
|
if s == nil {
|
||||||
|
s = article.feed?.url
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let urlString = s else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var urlComponents = URLComponents(string: urlString)
|
||||||
|
if urlComponents == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Can’t use url-with-fragment as base URL. The webview won’t load. See scripting.com/rss.xml for example.
|
||||||
|
urlComponents!.fragment = nil
|
||||||
|
guard let url = urlComponents!.url, url.scheme == "http" || url.scheme == "https" else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return url
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderHTML(withBody body: String) -> String {
|
||||||
|
|
||||||
var s = "<!DOCTYPE html><html><head>\n\n"
|
var s = "<!DOCTYPE html><html><head>\n\n"
|
||||||
s += textInsideTag(title, "title")
|
s += title.htmlBySurroundingWithTag("title")
|
||||||
s += textInsideTag(styleString(), "style")
|
s += styleString().htmlBySurroundingWithTag("style")
|
||||||
|
|
||||||
s += """
|
s += """
|
||||||
|
|
||||||
|
@ -492,15 +360,12 @@ class ArticleRenderer {
|
||||||
|
|
||||||
let appearanceClass = appearance?.isDarkMode ?? false ? "dark" : "light"
|
let appearanceClass = appearance?.isDarkMode ?? false ? "dark" : "light"
|
||||||
s += "\n\n</head><body id='bodyId' onload='startup()' class=\(appearanceClass)>\n\n"
|
s += "\n\n</head><body id='bodyId' onload='startup()' class=\(appearanceClass)>\n\n"
|
||||||
|
|
||||||
s += body
|
s += body
|
||||||
|
|
||||||
s += "\n\n</body></html>"
|
s += "\n\n</body></html>"
|
||||||
|
|
||||||
//print(s)
|
//print(s)
|
||||||
|
|
||||||
return s
|
return s
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue