diff --git a/Mac/MainWindow/Detail/DetailWebViewController.swift b/Mac/MainWindow/Detail/DetailWebViewController.swift index 4ebf7a697..82dc20524 100644 --- a/Mac/MainWindow/Detail/DetailWebViewController.swift +++ b/Mac/MainWindow/Detail/DetailWebViewController.swift @@ -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) } diff --git a/Mac/MainWindow/Timeline/Cell/TimelineIconView.swift b/Mac/MainWindow/IconView.swift similarity index 94% rename from Mac/MainWindow/Timeline/Cell/TimelineIconView.swift rename to Mac/MainWindow/IconView.swift index f537c3124..92d9f30dd 100644 --- a/Mac/MainWindow/Timeline/Cell/TimelineIconView.swift +++ b/Mac/MainWindow/IconView.swift @@ -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) diff --git a/Mac/MainWindow/Timeline/Cell/TimelineTableCellView.swift b/Mac/MainWindow/Timeline/Cell/TimelineTableCellView.swift index 219e7bdc5..2271a7a53 100644 --- a/Mac/MainWindow/Timeline/Cell/TimelineTableCellView.swift +++ b/Mac/MainWindow/Timeline/Cell/TimelineTableCellView.swift @@ -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() diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index 471435d6b..1c8df4ab1 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -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 = ""; }; 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 = ""; }; - 847CD6C9232F4CBF00FAC46D /* TimelineIconView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineIconView.swift; sourceTree = ""; }; + 847CD6C9232F4CBF00FAC46D /* IconView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconView.swift; sourceTree = ""; }; 847E64942262782F00E00365 /* NSAppleEventDescriptor+UserRecordFields.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSAppleEventDescriptor+UserRecordFields.swift"; sourceTree = ""; }; 848362FC2262A30800DA1D35 /* styleSheet.css */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.css; path = styleSheet.css; sourceTree = ""; }; 848362FE2262A30E00DA1D35 /* template.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = template.html; sourceTree = ""; }; @@ -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 = ""; @@ -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 = ""; @@ -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 */, diff --git a/iOS/Article/ArticleIconSchemeHandler.swift b/Shared/Article Rendering/ArticleIconSchemeHandler.swift similarity index 100% rename from iOS/Article/ArticleIconSchemeHandler.swift rename to Shared/Article Rendering/ArticleIconSchemeHandler.swift diff --git a/Shared/Article Rendering/ArticleRenderer.swift b/Shared/Article Rendering/ArticleRenderer.swift index 93d1676cf..ae74fd84c 100644 --- a/Shared/Article Rendering/ArticleRenderer.swift +++ b/Shared/Article Rendering/ArticleRenderer.swift @@ -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"] = "\(avatarHTML)"; - didAddAvatar = true - } + d["avatars"] = ""; 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"] = "\(favicon)"; - } - } - 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 = "" - } - else { - imgTag = "" - } - 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 "" - } - - if let iconImage = appDelegate.feedIconDownloader.icon(for: feed) { - if let s = base64String(forImage: iconImage.image) { - #if os(macOS) - let imgTag = "" - #else - let imgTag = "" - #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 = "" - 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 "" diff --git a/submodules/RSCore b/submodules/RSCore index 6b9d5ace8..ff2072b8d 160000 --- a/submodules/RSCore +++ b/submodules/RSCore @@ -1 +1 @@ -Subproject commit 6b9d5ace8ba71ab4c59663a50c673a6211ac5ed6 +Subproject commit ff2072b8da8f3a716524e87165010301e78a72ab