Make Article icons/avatars match Timeline icons/avatars. Issue #1273
This commit is contained in:
parent
d8b1b6c236
commit
701070f2dd
|
@ -39,6 +39,7 @@ final class DetailWebViewController: NSViewController, WKUIDelegate {
|
|||
}
|
||||
#endif
|
||||
|
||||
private let articleIconSchemeHandler = ArticleIconSchemeHandler()
|
||||
private var waitingForFirstReload = false
|
||||
private let keyboardDelegate = DetailKeyboardDelegate()
|
||||
|
||||
|
@ -65,6 +66,7 @@ final class DetailWebViewController: NSViewController, WKUIDelegate {
|
|||
|
||||
let configuration = WKWebViewConfiguration()
|
||||
configuration.preferences = preferences
|
||||
configuration.setURLSchemeHandler(articleIconSchemeHandler, forURLScheme: ArticleRenderer.imageIconScheme)
|
||||
|
||||
let userContentController = WKUserContentController()
|
||||
userContentController.add(self, name: MessageName.mouseDidEnter)
|
||||
|
@ -185,8 +187,10 @@ private extension DetailWebViewController {
|
|||
case .loading:
|
||||
rendering = ArticleRenderer.loadingHTML(style: style)
|
||||
case .article(let article):
|
||||
articleIconSchemeHandler.currentArticle = article
|
||||
rendering = ArticleRenderer.articleHTML(article: article, style: style)
|
||||
case .extracted(let article, let extractedArticle):
|
||||
articleIconSchemeHandler.currentArticle = article
|
||||
rendering = ArticleRenderer.articleHTML(article: article, extractedArticle: extractedArticle, style: style)
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
import AppKit
|
||||
|
||||
final class TimelineIconView: NSView {
|
||||
final class IconView: NSView {
|
||||
|
||||
var iconImage: IconImage? = nil {
|
||||
didSet {
|
||||
|
@ -71,13 +71,13 @@ final class TimelineIconView: NSView {
|
|||
return
|
||||
}
|
||||
|
||||
let color = NSApplication.shared.effectiveAppearance.isDarkMode ? TimelineIconView.darkBackgroundColor : TimelineIconView.lightBackgroundColor
|
||||
let color = NSApplication.shared.effectiveAppearance.isDarkMode ? IconView.darkBackgroundColor : IconView.lightBackgroundColor
|
||||
color.set()
|
||||
dirtyRect.fill()
|
||||
}
|
||||
}
|
||||
|
||||
private extension TimelineIconView {
|
||||
private extension IconView {
|
||||
|
||||
func commonInit() {
|
||||
addSubview(imageView)
|
|
@ -18,7 +18,7 @@ class TimelineTableCellView: NSTableCellView {
|
|||
private let dateView = TimelineTableCellView.singleLineTextField()
|
||||
private let feedNameView = TimelineTableCellView.singleLineTextField()
|
||||
|
||||
private lazy var iconView = TimelineIconView()
|
||||
private lazy var iconView = IconView()
|
||||
|
||||
private let starView = TimelineTableCellView.imageView(with: AppAssets.timelineStar, scaling: .scaleNone)
|
||||
private let separatorView = TimelineTableCellView.separatorView()
|
||||
|
|
|
@ -113,6 +113,8 @@
|
|||
518651DA235621840078E021 /* ImageTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 518651D9235621840078E021 /* ImageTransition.swift */; };
|
||||
5186A635235EF3A800C97195 /* VibrantLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5186A634235EF3A800C97195 /* VibrantLabel.swift */; };
|
||||
518B2EE82351B45600400001 /* NetNewsWire_iOSTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840D61952029031D009BC708 /* NetNewsWire_iOSTests.swift */; };
|
||||
518C3193237B00D9004D740F /* ArticleIconSchemeHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5141E7552374A2890013FF27 /* ArticleIconSchemeHandler.swift */; };
|
||||
518C3194237B00DA004D740F /* ArticleIconSchemeHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5141E7552374A2890013FF27 /* ArticleIconSchemeHandler.swift */; };
|
||||
51934CCB230F599B006127BE /* ThemedNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51934CC1230F5963006127BE /* ThemedNavigationController.swift */; };
|
||||
51934CCE2310792F006127BE /* ActivityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51934CCD2310792F006127BE /* ActivityManager.swift */; };
|
||||
51938DF2231AFC660055A1A0 /* SearchTimelineFeedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51938DF1231AFC660055A1A0 /* SearchTimelineFeedDelegate.swift */; };
|
||||
|
@ -255,7 +257,7 @@
|
|||
6581C74220CED60100F4AD34 /* ToolbarItemIcon.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 6581C74120CED60100F4AD34 /* ToolbarItemIcon.pdf */; };
|
||||
65ED3FB7235DEF6C0081F399 /* ArticleArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F204DF1FAACBB30076E152 /* ArticleArray.swift */; };
|
||||
65ED3FB8235DEF6C0081F399 /* CrashReporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848B937121C8C5540038DC0D /* CrashReporter.swift */; };
|
||||
65ED3FB9235DEF6C0081F399 /* TimelineIconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847CD6C9232F4CBF00FAC46D /* TimelineIconView.swift */; };
|
||||
65ED3FB9235DEF6C0081F399 /* IconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847CD6C9232F4CBF00FAC46D /* IconView.swift */; };
|
||||
65ED3FBA235DEF6C0081F399 /* ArticleExtractorConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FA73A92332C2FD0090D516 /* ArticleExtractorConfig.swift */; };
|
||||
65ED3FBB235DEF6C0081F399 /* InspectorWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84BBB12C20142A4700F054F5 /* InspectorWindowController.swift */; };
|
||||
65ED3FBC235DEF6C0081F399 /* ColorHash.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51EF0F78227716380050506E /* ColorHash.swift */; };
|
||||
|
@ -489,7 +491,7 @@
|
|||
84702AA41FA27AC0006B8943 /* MarkStatusCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84702AA31FA27AC0006B8943 /* MarkStatusCommand.swift */; };
|
||||
8472058120142E8900AD578B /* FeedInspectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8472058020142E8900AD578B /* FeedInspectorViewController.swift */; };
|
||||
8477ACBE22238E9500DF7F37 /* SearchFeedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8477ACBD22238E9500DF7F37 /* SearchFeedDelegate.swift */; };
|
||||
847CD6CA232F4CBF00FAC46D /* TimelineIconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847CD6C9232F4CBF00FAC46D /* TimelineIconView.swift */; };
|
||||
847CD6CA232F4CBF00FAC46D /* IconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847CD6C9232F4CBF00FAC46D /* IconView.swift */; };
|
||||
847E64A02262783000E00365 /* NSAppleEventDescriptor+UserRecordFields.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847E64942262782F00E00365 /* NSAppleEventDescriptor+UserRecordFields.swift */; };
|
||||
848362FD2262A30800DA1D35 /* styleSheet.css in Resources */ = {isa = PBXBuildFile; fileRef = 848362FC2262A30800DA1D35 /* styleSheet.css */; };
|
||||
848362FF2262A30E00DA1D35 /* template.html in Resources */ = {isa = PBXBuildFile; fileRef = 848362FE2262A30E00DA1D35 /* template.html */; };
|
||||
|
@ -1407,7 +1409,7 @@
|
|||
8472058020142E8900AD578B /* FeedInspectorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedInspectorViewController.swift; sourceTree = "<group>"; };
|
||||
847752FE2008879500D93690 /* CoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreServices.framework; path = System/Library/Frameworks/CoreServices.framework; sourceTree = SDKROOT; };
|
||||
8477ACBD22238E9500DF7F37 /* SearchFeedDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchFeedDelegate.swift; sourceTree = "<group>"; };
|
||||
847CD6C9232F4CBF00FAC46D /* TimelineIconView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineIconView.swift; sourceTree = "<group>"; };
|
||||
847CD6C9232F4CBF00FAC46D /* IconView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconView.swift; sourceTree = "<group>"; };
|
||||
847E64942262782F00E00365 /* NSAppleEventDescriptor+UserRecordFields.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSAppleEventDescriptor+UserRecordFields.swift"; sourceTree = "<group>"; };
|
||||
848362FC2262A30800DA1D35 /* styleSheet.css */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.css; path = styleSheet.css; sourceTree = "<group>"; };
|
||||
848362FE2262A30E00DA1D35 /* template.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = template.html; sourceTree = "<group>"; };
|
||||
|
@ -1910,7 +1912,6 @@
|
|||
children = (
|
||||
51C4527E2265092C00C03939 /* ArticleViewController.swift */,
|
||||
517630222336657E00E15FFF /* ArticleViewControllerWebViewProvider.swift */,
|
||||
5141E7552374A2890013FF27 /* ArticleIconSchemeHandler.swift */,
|
||||
51102164233A7D6C0007A5F7 /* ArticleExtractorButton.swift */,
|
||||
5142192923522B5500E07E2C /* ImageViewController.swift */,
|
||||
514219362352510100E07E2C /* ImageScrollView.swift */,
|
||||
|
@ -1934,10 +1935,11 @@
|
|||
51C452A822650DA100C03939 /* Article Rendering */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
49F40DEF2335B71000552BF4 /* newsfoot.js */,
|
||||
5141E7552374A2890013FF27 /* ArticleIconSchemeHandler.swift */,
|
||||
849A977D1ED9EC42007D329B /* ArticleRenderer.swift */,
|
||||
848362FE2262A30E00DA1D35 /* template.html */,
|
||||
517630032336215100E15FFF /* main.js */,
|
||||
49F40DEF2335B71000552BF4 /* newsfoot.js */,
|
||||
848362FE2262A30E00DA1D35 /* template.html */,
|
||||
);
|
||||
path = "Article Rendering";
|
||||
sourceTree = "<group>";
|
||||
|
@ -2068,6 +2070,7 @@
|
|||
519B8D322143397200FA689C /* SharingServiceDelegate.swift */,
|
||||
849EE72020391F560082A1EA /* SharingServicePickerDelegate.swift */,
|
||||
51FA73B62332D5F70090D516 /* ArticleExtractorButton.swift */,
|
||||
847CD6C9232F4CBF00FAC46D /* IconView.swift */,
|
||||
844B5B6B1FEA224B00C7C76A /* Keyboard */,
|
||||
849A975F1ED9EB95007D329B /* Sidebar */,
|
||||
849A97681ED9EBC8007D329B /* Timeline */,
|
||||
|
@ -2245,7 +2248,6 @@
|
|||
84E185C2203BB12600F69BFA /* MultilineTextFieldSizer.swift */,
|
||||
849A97711ED9EC04007D329B /* TimelineCellData.swift */,
|
||||
849A97751ED9EC04007D329B /* UnreadIndicatorView.swift */,
|
||||
847CD6C9232F4CBF00FAC46D /* TimelineIconView.swift */,
|
||||
);
|
||||
path = Cell;
|
||||
sourceTree = "<group>";
|
||||
|
@ -3718,7 +3720,7 @@
|
|||
files = (
|
||||
65ED3FB7235DEF6C0081F399 /* ArticleArray.swift in Sources */,
|
||||
65ED3FB8235DEF6C0081F399 /* CrashReporter.swift in Sources */,
|
||||
65ED3FB9235DEF6C0081F399 /* TimelineIconView.swift in Sources */,
|
||||
65ED3FB9235DEF6C0081F399 /* IconView.swift in Sources */,
|
||||
65ED3FBA235DEF6C0081F399 /* ArticleExtractorConfig.swift in Sources */,
|
||||
65ED3FBB235DEF6C0081F399 /* InspectorWindowController.swift in Sources */,
|
||||
65ED3FBC235DEF6C0081F399 /* ColorHash.swift in Sources */,
|
||||
|
@ -3768,6 +3770,7 @@
|
|||
65ED3FE9235DEF6C0081F399 /* FaviconURLFinder.swift in Sources */,
|
||||
65ED3FEA235DEF6C0081F399 /* SidebarViewController+ContextualMenus.swift in Sources */,
|
||||
65ED3FEC235DEF6C0081F399 /* RSHTMLMetadata+Extension.swift in Sources */,
|
||||
518C3194237B00DA004D740F /* ArticleIconSchemeHandler.swift in Sources */,
|
||||
65ED3FED235DEF6C0081F399 /* SendToMarsEditCommand.swift in Sources */,
|
||||
65ED3FEE235DEF6C0081F399 /* UserNotificationManager.swift in Sources */,
|
||||
65ED3FEF235DEF6C0081F399 /* ScriptingObjectContainer.swift in Sources */,
|
||||
|
@ -4003,7 +4006,7 @@
|
|||
files = (
|
||||
84F204E01FAACBB30076E152 /* ArticleArray.swift in Sources */,
|
||||
848B937221C8C5540038DC0D /* CrashReporter.swift in Sources */,
|
||||
847CD6CA232F4CBF00FAC46D /* TimelineIconView.swift in Sources */,
|
||||
847CD6CA232F4CBF00FAC46D /* IconView.swift in Sources */,
|
||||
51FA73AA2332C2FD0090D516 /* ArticleExtractorConfig.swift in Sources */,
|
||||
84BBB12E20142A4700F054F5 /* InspectorWindowController.swift in Sources */,
|
||||
51EF0F7A22771B890050506E /* ColorHash.swift in Sources */,
|
||||
|
@ -4098,6 +4101,7 @@
|
|||
848D578E21543519005FFAD5 /* PasteboardFeed.swift in Sources */,
|
||||
5144EA2F2279FAB600D19003 /* AccountsDetailViewController.swift in Sources */,
|
||||
849A97801ED9EC42007D329B /* DetailViewController.swift in Sources */,
|
||||
518C3193237B00D9004D740F /* ArticleIconSchemeHandler.swift in Sources */,
|
||||
84C9FC6722629B9000D921D6 /* AppDelegate.swift in Sources */,
|
||||
84C9FC7A22629E1200D921D6 /* AccountsTableViewBackgroundView.swift in Sources */,
|
||||
84CAFCAF22BC8C35007694F0 /* FetchRequestOperation.swift in Sources */,
|
||||
|
|
|
@ -31,9 +31,8 @@ struct ArticleRenderer {
|
|||
private let title: String
|
||||
private let body: String
|
||||
private let baseURL: String?
|
||||
private let useImageIcon: Bool
|
||||
|
||||
private init(article: Article?, extractedArticle: ExtractedArticle?, style: ArticleStyle, useImageIcon: Bool = false) {
|
||||
private init(article: Article?, extractedArticle: ExtractedArticle?, style: ArticleStyle) {
|
||||
self.article = article
|
||||
self.extractedArticle = extractedArticle
|
||||
self.articleStyle = style
|
||||
|
@ -45,13 +44,12 @@ struct ArticleRenderer {
|
|||
self.body = article?.body ?? ""
|
||||
self.baseURL = article?.baseURL?.absoluteString
|
||||
}
|
||||
self.useImageIcon = useImageIcon
|
||||
}
|
||||
|
||||
// MARK: - API
|
||||
|
||||
static func articleHTML(article: Article, extractedArticle: ExtractedArticle? = nil, style: ArticleStyle, useImageIcon: Bool = false) -> Rendering {
|
||||
let renderer = ArticleRenderer(article: article, extractedArticle: extractedArticle, style: style, useImageIcon: useImageIcon)
|
||||
let renderer = ArticleRenderer(article: article, extractedArticle: extractedArticle, style: style)
|
||||
return (renderer.styleString(), renderer.articleHTML)
|
||||
}
|
||||
|
||||
|
@ -104,9 +102,6 @@ private extension ArticleRenderer {
|
|||
return renderHTML(withBody: "")
|
||||
}
|
||||
|
||||
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)
|
||||
|
@ -146,13 +141,7 @@ private extension ArticleRenderer {
|
|||
d["title"] = title
|
||||
|
||||
d["body"] = body
|
||||
|
||||
d["avatars"] = ""
|
||||
var didAddAvatar = false
|
||||
if let avatarHTML = avatarImgTag() {
|
||||
d["avatars"] = "<td class=\"header rightAlign avatar\">\(avatarHTML)</td>";
|
||||
didAddAvatar = true
|
||||
}
|
||||
d["avatars"] = "<td class=\"header rightAlign avatar\"><img src=\"\(ArticleRenderer.imageIconScheme)://\" height=48 width=48 /></td>";
|
||||
|
||||
var feedLink = ""
|
||||
if let feedTitle = article.feed?.nameForDisplay {
|
||||
|
@ -163,12 +152,6 @@ private extension ArticleRenderer {
|
|||
}
|
||||
d["feedlink"] = feedLink
|
||||
|
||||
if !didAddAvatar, let feed = article.feed {
|
||||
if let favicon = faviconImgTag(forFeed: feed) {
|
||||
d["avatars"] = "<td class=\"header rightAlign\">\(favicon)</td>";
|
||||
}
|
||||
}
|
||||
|
||||
let datePublished = article.logicalDatePublished
|
||||
let longDate = dateString(datePublished, .long, .medium)
|
||||
let mediumDate = dateString(datePublished, .medium, .short)
|
||||
|
@ -200,111 +183,6 @@ private extension ArticleRenderer {
|
|||
return permalink != preferredLink // Make date a link if it’s a different link from the title’s link
|
||||
}
|
||||
|
||||
func faviconImgTag(forFeed feed: Feed) -> String? {
|
||||
|
||||
if let cachedImgTag = ArticleRenderer.faviconImgTagCache[feed] {
|
||||
return cachedImgTag
|
||||
}
|
||||
|
||||
if let iconImage = appDelegate.faviconDownloader.faviconAsIcon(for: feed) {
|
||||
if let s = base64String(forImage: iconImage.image) {
|
||||
var dimension = min(iconImage.image.size.height, CGFloat(ArticleRenderer.avatarDimension)) // Assuming square images.
|
||||
dimension = max(dimension, 16) // Some favicons say they’re < 16. Force them larger.
|
||||
if dimension >= CGFloat(ArticleRenderer.avatarDimension) * 0.8 { //Close enough to scale up.
|
||||
dimension = CGFloat(ArticleRenderer.avatarDimension)
|
||||
}
|
||||
|
||||
let imgTag: String
|
||||
if dimension >= CGFloat(ArticleRenderer.avatarDimension) {
|
||||
// Use rounded corners.
|
||||
imgTag = "<img src=\"data:image/tiff;base64, " + s + "\" height=\(Int(dimension)) width=\(Int(dimension)) style=\"border-radius:4px\" />"
|
||||
}
|
||||
else {
|
||||
imgTag = "<img src=\"data:image/tiff;base64, " + s + "\" height=\(Int(dimension)) width=\(Int(dimension)) />"
|
||||
}
|
||||
ArticleRenderer.faviconImgTagCache[feed] = imgTag
|
||||
return imgTag
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func feedIconImgTag(forFeed feed: Feed) -> String? {
|
||||
if let cachedImgTag = ArticleRenderer.feedIconImgTagCache[feed] {
|
||||
return cachedImgTag
|
||||
}
|
||||
|
||||
if useImageIcon {
|
||||
return "<img src=\"\(ArticleRenderer.imageIconScheme)://article.png\" height=48 width=48 />"
|
||||
}
|
||||
|
||||
if let iconImage = appDelegate.feedIconDownloader.icon(for: feed) {
|
||||
if let s = base64String(forImage: iconImage.image) {
|
||||
#if os(macOS)
|
||||
let imgTag = "<img src=\"data:image/tiff;base64, " + s + "\" height=48 width=48 />"
|
||||
#else
|
||||
let imgTag = "<img src=\"data:image/png;base64, " + s + "\" height=48 width=48 />"
|
||||
#endif
|
||||
ArticleRenderer.feedIconImgTagCache[feed] = imgTag
|
||||
return imgTag
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func base64String(forImage image: RSImage) -> String? {
|
||||
return image.dataRepresentation()?.base64EncodedString()
|
||||
}
|
||||
|
||||
func singleArticleSpecifiedAuthor() -> Author? {
|
||||
// The author of this article, if just one.
|
||||
if let authors = article?.authors, authors.count == 1 {
|
||||
return authors.first!
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func singleFeedSpecifiedAuthor() -> Author? {
|
||||
if let authors = article?.feed?.authors, authors.count == 1 {
|
||||
return authors.first!
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
static let avatarDimension = 48
|
||||
|
||||
struct Avatar {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
func avatarImgTag() -> String? {
|
||||
if let author = singleArticleSpecifiedAuthor(), let authorImageURL = author.avatarURL {
|
||||
let imageURL = useImageIcon ? ArticleRenderer.imageIconScheme : authorImageURL
|
||||
return Avatar(imageURL: imageURL, url: author.url).html(dimension: ArticleRenderer.avatarDimension)
|
||||
}
|
||||
if let feed = article?.feed, let imgTag = feedIconImgTag(forFeed: feed) {
|
||||
return imgTag
|
||||
}
|
||||
if let feedIconURL = article?.feed?.iconURL {
|
||||
return Avatar(imageURL: feedIconURL, url: article?.feed?.homePageURL ?? article?.feed?.url).html(dimension: ArticleRenderer.avatarDimension)
|
||||
}
|
||||
if let author = singleFeedSpecifiedAuthor(), let imageURL = author.avatarURL {
|
||||
return Avatar(imageURL: imageURL, url: author.url).html(dimension: ArticleRenderer.avatarDimension)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func byline() -> String {
|
||||
guard let authors = article?.authors ?? article?.feed?.authors, !authors.isEmpty else {
|
||||
return ""
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 6b9d5ace8ba71ab4c59663a50c673a6211ac5ed6
|
||||
Subproject commit ff2072b8da8f3a716524e87165010301e78a72ab
|
Loading…
Reference in New Issue