diff --git a/Mac/Base.lproj/MainWindow.storyboard b/Mac/Base.lproj/MainWindow.storyboard index 0a9cf634e..bb8b468c1 100644 --- a/Mac/Base.lproj/MainWindow.storyboard +++ b/Mac/Base.lproj/MainWindow.storyboard @@ -1,8 +1,8 @@ - + - + @@ -168,6 +168,22 @@ + + + + + + + + + @@ -180,6 +196,7 @@ + @@ -192,6 +209,7 @@ + @@ -276,7 +294,7 @@ - + @@ -488,6 +506,7 @@ + diff --git a/Mac/MainWindow/ArticleExtractorButton.swift b/Mac/MainWindow/ArticleExtractorButton.swift new file mode 100644 index 000000000..937cb70c3 --- /dev/null +++ b/Mac/MainWindow/ArticleExtractorButton.swift @@ -0,0 +1,109 @@ +// +// ArticleExtractorButton.swift +// NetNewsWire +// +// Created by Maurice Parker on 9/18/19. +// Copyright © 2019 Ranchero Software. All rights reserved. +// + +import Foundation + +class ArticleExtractorButton: NSButton { + + var isError = false { + didSet { + needsDisplay = true + } + } + + var isInProgress = false { + didSet { + needsDisplay = true + } + } + + override init(frame frameRect: NSRect) { + super.init(frame: frameRect) + wantsLayer = true + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + wantsLayer = true + } + + override func draw(_ dirtyRect: NSRect) { + + super.draw(dirtyRect) + + guard let hostedLayer = self.layer else { + return + } + + if let imageLayer = hostedLayer.sublayers?[0] { + if needsToDraw(imageLayer.bounds) { + imageLayer.removeFromSuperlayer() + } else { + return + } + } + + let opacity: Float = isEnabled ? 1.0 : 0.5 + + switch true { + case isError: + addImageSublayer(to: hostedLayer, imageName: "articleExtractorError", opacity: opacity) + case isInProgress: + addProgressSublayer(to: hostedLayer) + default: + addImageSublayer(to: hostedLayer, imageName: "articleExtractor", opacity: opacity) + } + + } + + private func makeLayerForImage(_ image: NSImage) -> CALayer { + let imageLayer = CALayer() + imageLayer.bounds = CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height) + imageLayer.position = CGPoint(x: bounds.midX, y: bounds.midY) + return imageLayer + } + + private func addImageSublayer(to hostedLayer: CALayer, imageName: String, opacity: Float = 1.0) { + + guard let image = NSImage(named: imageName) else { + fatalError("Image doesn't exist: \(imageName)") + } + + let imageLayer = makeLayerForImage(image) + imageLayer.contents = image + imageLayer.opacity = opacity + hostedLayer.addSublayer(imageLayer) + + } + + private func addProgressSublayer(to hostedLayer: CALayer) { + + let imageProgress1 = NSImage(named: "articleExtractorProgress1") + let imageProgress2 = NSImage(named: "articleExtractorProgress2") + let imageProgress3 = NSImage(named: "articleExtractorProgress3") + let imageProgress4 = NSImage(named: "articleExtractorProgress4") + let images = [imageProgress1, imageProgress2, imageProgress3, imageProgress4, imageProgress3, imageProgress2] + + let imageLayer = CALayer() + imageLayer.bounds = CGRect(x: 0, y: 0, width: imageProgress1?.size.width ?? 0, height: imageProgress1?.size.height ?? 0) + imageLayer.position = CGPoint(x: bounds.midX, y: bounds.midY) + + hostedLayer.addSublayer(imageLayer) + + let animation = CAKeyframeAnimation(keyPath: "contents") + animation.calculationMode = CAAnimationCalculationMode.linear + animation.keyTimes = [0, 0.2, 0.4, 0.6, 0.8, 1] + animation.duration = 2 + animation.values = images as [Any] + animation.repeatCount = HUGE + + imageLayer.add(animation, forKey: "contents") + + } + +} diff --git a/Mac/MainWindow/Detail/DetailViewController.swift b/Mac/MainWindow/Detail/DetailViewController.swift index c1d26e512..592322b0d 100644 --- a/Mac/MainWindow/Detail/DetailViewController.swift +++ b/Mac/MainWindow/Detail/DetailViewController.swift @@ -16,6 +16,7 @@ enum DetailState: Equatable { case noSelection case multipleSelection case article(Article) + case extracted(Article, ExtractedArticle) } final class DetailViewController: NSViewController, WKUIDelegate { diff --git a/Mac/MainWindow/Detail/DetailWebViewController.swift b/Mac/MainWindow/Detail/DetailWebViewController.swift index 9e889db4b..4b9cd8f82 100644 --- a/Mac/MainWindow/Detail/DetailWebViewController.swift +++ b/Mac/MainWindow/Detail/DetailWebViewController.swift @@ -176,6 +176,8 @@ private extension DetailWebViewController { html = ArticleRenderer.multipleSelectionHTML(style: style) case .article(let article): html = ArticleRenderer.articleHTML(article: article, style: style) + case .extracted(let article, let extractedArticle): + html = ArticleRenderer.articleHTML(article: article, extractedArticle: extractedArticle, style: style) } webView.loadHTMLString(html, baseURL: nil) diff --git a/Mac/MainWindow/MainWindowController.swift b/Mac/MainWindow/MainWindowController.swift index bc00c98d1..566106e0c 100644 --- a/Mac/MainWindow/MainWindowController.swift +++ b/Mac/MainWindow/MainWindowController.swift @@ -17,6 +17,9 @@ enum TimelineSourceMode { class MainWindowController : NSWindowController, NSUserInterfaceValidations { + @IBOutlet weak var articleExtractorButton: ArticleExtractorButton! + + private var articleExtractor: ArticleExtractor? = nil private var sharingServicePickerDelegate: NSSharingServicePickerDelegate? private let windowAutosaveName = NSWindow.FrameAutosaveName("MainWindow") @@ -206,6 +209,10 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations { return canMarkOlderArticlesAsRead() } + if item.action == #selector(toggleArticleExtractor(_:)) { + return validateToggleArticleExtractor(item) + } + if item.action == #selector(toolbarShowShareMenu(_:)) { return canShowShareMenu() } @@ -292,6 +299,34 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations { currentTimelineViewController?.toggleStarredStatusForSelectedArticles() } + @IBAction func toggleArticleExtractor(_ sender: Any?) { + + guard let currentLink = currentLink, let article = oneSelectedArticle else { + return + } + + guard articleExtractorButton.state == .on else { + let detailState = DetailState.article(article) + detailViewController?.setState(detailState, mode: timelineSourceMode) + return + } + + if let articleExtractor = articleExtractor, let extractedArticle = articleExtractor.article { + if currentLink == articleExtractor.articleLink { + let detailState = DetailState.extracted(article, extractedArticle) + detailViewController?.setState(detailState, mode: timelineSourceMode) + } + } else { + if let extractor = ArticleExtractor(currentLink) { + extractor.delegate = self + extractor.process() + articleExtractor = extractor + makeToolbarValidate() + } + } + + } + @IBAction func markAllAsReadAndGoToNextUnread(_ sender: Any?) { markAllAsRead(sender) nextUnread(sender) @@ -407,6 +442,11 @@ extension MainWindowController: SidebarDelegate { extension MainWindowController: TimelineContainerViewControllerDelegate { func timelineSelectionDidChange(_: TimelineContainerViewController, articles: [Article]?, mode: TimelineSourceMode) { + articleExtractorButton.isError = false + articleExtractorButton.isInProgress = false + articleExtractorButton.state = .off + articleExtractor = nil + let detailState: DetailState if let articles = articles { detailState = articles.count == 1 ? .article(articles.first!) : .multipleSelection @@ -414,6 +454,7 @@ extension MainWindowController: TimelineContainerViewControllerDelegate { else { detailState = .noSelection } + detailViewController?.setState(detailState, mode: mode) } } @@ -481,6 +522,24 @@ extension MainWindowController: NSSearchFieldDelegate { } } +// MARK: - ArticleExtractorDelegate + +extension MainWindowController: ArticleExtractorDelegate { + + func articleExtractionDidFail(with: Error) { + makeToolbarValidate() + } + + func articleExtractionDidComplete(extractedArticle: ExtractedArticle) { + makeToolbarValidate() + if articleExtractorButton.state == .on, let article = oneSelectedArticle { + let detailState = DetailState.extracted(article, extractedArticle) + detailViewController?.setState(detailState, mode: timelineSourceMode) + } + } + +} + // MARK: - Scripting Access /* @@ -632,6 +691,34 @@ private extension MainWindowController { return result } + func validateToggleArticleExtractor(_ item: NSValidatedUserInterfaceItem) -> Bool { + guard let articleExtractorState = articleExtractor?.state else { + articleExtractorButton.isError = false + articleExtractorButton.isInProgress = false + return currentLink != nil + } + + switch articleExtractorState { + case .ready: + articleExtractorButton.isError = false + articleExtractorButton.isInProgress = false + return currentLink != nil + case .processing: + articleExtractorButton.isError = false + articleExtractorButton.isInProgress = true + return true + case .failedToParse: + articleExtractorButton.isError = true + articleExtractorButton.isInProgress = false + articleExtractorButton.state = .off + return true + case .complete: + articleExtractorButton.isError = false + articleExtractorButton.isInProgress = false + return currentLink != nil + } + } + func canMarkOlderArticlesAsRead() -> Bool { return currentTimelineViewController?.canMarkOlderArticlesAsRead() ?? false diff --git a/Mac/Resources/Assets.xcassets/articleExtractor.imageset/Contents.json b/Mac/Resources/Assets.xcassets/articleExtractor.imageset/Contents.json new file mode 100644 index 000000000..20aecf925 --- /dev/null +++ b/Mac/Resources/Assets.xcassets/articleExtractor.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "FullArticle.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Mac/Resources/Assets.xcassets/articleExtractor.imageset/FullArticle.pdf b/Mac/Resources/Assets.xcassets/articleExtractor.imageset/FullArticle.pdf new file mode 100644 index 000000000..af687ae42 Binary files /dev/null and b/Mac/Resources/Assets.xcassets/articleExtractor.imageset/FullArticle.pdf differ diff --git a/Mac/Resources/Assets.xcassets/articleExtractorError.imageset/Contents.json b/Mac/Resources/Assets.xcassets/articleExtractorError.imageset/Contents.json new file mode 100644 index 000000000..100f19ee7 --- /dev/null +++ b/Mac/Resources/Assets.xcassets/articleExtractorError.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "FullArticleError.png" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Mac/Resources/Assets.xcassets/articleExtractorError.imageset/FullArticleError.png b/Mac/Resources/Assets.xcassets/articleExtractorError.imageset/FullArticleError.png new file mode 100644 index 000000000..25785ee6b Binary files /dev/null and b/Mac/Resources/Assets.xcassets/articleExtractorError.imageset/FullArticleError.png differ diff --git a/Mac/Resources/Assets.xcassets/articleExtractorProgress1.imageset/Contents.json b/Mac/Resources/Assets.xcassets/articleExtractorProgress1.imageset/Contents.json new file mode 100644 index 000000000..866dc9c65 --- /dev/null +++ b/Mac/Resources/Assets.xcassets/articleExtractorProgress1.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "FullArticleProgress1.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Mac/Resources/Assets.xcassets/articleExtractorProgress1.imageset/FullArticleProgress1.pdf b/Mac/Resources/Assets.xcassets/articleExtractorProgress1.imageset/FullArticleProgress1.pdf new file mode 100644 index 000000000..1a9e77b3d Binary files /dev/null and b/Mac/Resources/Assets.xcassets/articleExtractorProgress1.imageset/FullArticleProgress1.pdf differ diff --git a/Mac/Resources/Assets.xcassets/articleExtractorProgress2.imageset/Contents.json b/Mac/Resources/Assets.xcassets/articleExtractorProgress2.imageset/Contents.json new file mode 100644 index 000000000..8da6a4f2f --- /dev/null +++ b/Mac/Resources/Assets.xcassets/articleExtractorProgress2.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "FullArticleProgress2.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Mac/Resources/Assets.xcassets/articleExtractorProgress2.imageset/FullArticleProgress2.pdf b/Mac/Resources/Assets.xcassets/articleExtractorProgress2.imageset/FullArticleProgress2.pdf new file mode 100644 index 000000000..e58ad15a2 Binary files /dev/null and b/Mac/Resources/Assets.xcassets/articleExtractorProgress2.imageset/FullArticleProgress2.pdf differ diff --git a/Mac/Resources/Assets.xcassets/articleExtractorProgress3.imageset/Contents.json b/Mac/Resources/Assets.xcassets/articleExtractorProgress3.imageset/Contents.json new file mode 100644 index 000000000..67c09f9f7 --- /dev/null +++ b/Mac/Resources/Assets.xcassets/articleExtractorProgress3.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "FullArticleProgress3.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Mac/Resources/Assets.xcassets/articleExtractorProgress3.imageset/FullArticleProgress3.pdf b/Mac/Resources/Assets.xcassets/articleExtractorProgress3.imageset/FullArticleProgress3.pdf new file mode 100644 index 000000000..816f83071 Binary files /dev/null and b/Mac/Resources/Assets.xcassets/articleExtractorProgress3.imageset/FullArticleProgress3.pdf differ diff --git a/Mac/Resources/Assets.xcassets/articleExtractorProgress4.imageset/Contents.json b/Mac/Resources/Assets.xcassets/articleExtractorProgress4.imageset/Contents.json new file mode 100644 index 000000000..d13e86da2 --- /dev/null +++ b/Mac/Resources/Assets.xcassets/articleExtractorProgress4.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "FullArticleProgress4.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Mac/Resources/Assets.xcassets/articleExtractorProgress4.imageset/FullArticleProgress4.pdf b/Mac/Resources/Assets.xcassets/articleExtractorProgress4.imageset/FullArticleProgress4.pdf new file mode 100644 index 000000000..05d2949d8 Binary files /dev/null and b/Mac/Resources/Assets.xcassets/articleExtractorProgress4.imageset/FullArticleProgress4.pdf differ diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index 922cfbdd6..9caac5e83 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -199,6 +199,13 @@ 51F85BF92274AA7B00C787DC /* UIBarButtonItem-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51F85BF82274AA7B00C787DC /* UIBarButtonItem-Extensions.swift */; }; 51F85BFB2275D85000C787DC /* Array-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51F85BFA2275D85000C787DC /* Array-Extensions.swift */; }; 51F85BFD2275DCA800C787DC /* SingleLineUILabelSizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51F85BFC2275DCA800C787DC /* SingleLineUILabelSizer.swift */; }; + 51FA73A42332BE110090D516 /* ArticleExtractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FA73A32332BE110090D516 /* ArticleExtractor.swift */; }; + 51FA73A52332BE110090D516 /* ArticleExtractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FA73A32332BE110090D516 /* ArticleExtractor.swift */; }; + 51FA73A72332BE880090D516 /* ExtractedArticle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FA73A62332BE880090D516 /* ExtractedArticle.swift */; }; + 51FA73A82332BE880090D516 /* ExtractedArticle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FA73A62332BE880090D516 /* ExtractedArticle.swift */; }; + 51FA73AA2332C2FD0090D516 /* ArticleExtractorConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FA73A92332C2FD0090D516 /* ArticleExtractorConfig.swift */; }; + 51FA73AB2332C2FD0090D516 /* ArticleExtractorConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FA73A92332C2FD0090D516 /* ArticleExtractorConfig.swift */; }; + 51FA73B72332D5F70090D516 /* ArticleExtractorButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FA73B62332D5F70090D516 /* ArticleExtractorButton.swift */; }; 55E15BCB229D65A900D6602A /* AccountsReaderAPI.xib in Resources */ = {isa = PBXBuildFile; fileRef = 55E15BC1229D65A900D6602A /* AccountsReaderAPI.xib */; }; 55E15BCC229D65A900D6602A /* AccountsReaderAPIWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55E15BCA229D65A900D6602A /* AccountsReaderAPIWindowController.swift */; }; 5F323809231DF9F000706F6B /* NNWTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F323808231DF9F000706F6B /* NNWTableViewCell.swift */; }; @@ -863,6 +870,10 @@ 51F85BF82274AA7B00C787DC /* UIBarButtonItem-Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIBarButtonItem-Extensions.swift"; sourceTree = ""; }; 51F85BFA2275D85000C787DC /* Array-Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array-Extensions.swift"; sourceTree = ""; }; 51F85BFC2275DCA800C787DC /* SingleLineUILabelSizer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SingleLineUILabelSizer.swift; sourceTree = ""; }; + 51FA73A32332BE110090D516 /* ArticleExtractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleExtractor.swift; sourceTree = ""; }; + 51FA73A62332BE880090D516 /* ExtractedArticle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtractedArticle.swift; sourceTree = ""; }; + 51FA73A92332C2FD0090D516 /* ArticleExtractorConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleExtractorConfig.swift; sourceTree = ""; }; + 51FA73B62332D5F70090D516 /* ArticleExtractorButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleExtractorButton.swift; sourceTree = ""; }; 557EE1A522B6F4E1004206FA /* SettingsReaderAPIAccountView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsReaderAPIAccountView.swift; sourceTree = ""; }; 55E15BC1229D65A900D6602A /* AccountsReaderAPI.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = AccountsReaderAPI.xib; sourceTree = ""; }; 55E15BCA229D65A900D6602A /* AccountsReaderAPIWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountsReaderAPIWindowController.swift; sourceTree = ""; }; @@ -1391,6 +1402,16 @@ name = Frameworks; sourceTree = ""; }; + 51FA739A2332BDE70090D516 /* Article Extractor */ = { + isa = PBXGroup; + children = ( + 51FA73A92332C2FD0090D516 /* ArticleExtractorConfig.swift */, + 51FA73A32332BE110090D516 /* ArticleExtractor.swift */, + 51FA73A62332BE880090D516 /* ExtractedArticle.swift */, + ); + path = "Article Extractor"; + sourceTree = ""; + }; 6581C73620CED60100F4AD34 /* SafariExtension */ = { isa = PBXGroup; children = ( @@ -1456,6 +1477,7 @@ 849A975D1ED9EB72007D329B /* MainWindowController.swift */, 519B8D322143397200FA689C /* SharingServiceDelegate.swift */, 849EE72020391F560082A1EA /* SharingServicePickerDelegate.swift */, + 51FA73B62332D5F70090D516 /* ArticleExtractorButton.swift */, 844B5B6B1FEA224B00C7C76A /* Keyboard */, 849A975F1ED9EB95007D329B /* Sidebar */, 849A97681ED9EBC8007D329B /* Timeline */, @@ -1807,6 +1829,7 @@ 51C452AD2265102800C03939 /* Timeline */, 84702AB31FA27AE8006B8943 /* Commands */, 51934CCC231078DC006127BE /* Activity */, + 51FA739A2332BDE70090D516 /* Article Extractor */, 51C452A822650DA100C03939 /* Article Rendering */, 849A97861ED9ECEF007D329B /* Article Styles */, 84DAEE201F86CAE00058304B /* Importers */, @@ -2645,10 +2668,12 @@ 51322859232FDDB80033D4ED /* VibrantButtonStyle.swift in Sources */, 514B7C8323205EFB00BAC947 /* RootSplitViewController.swift in Sources */, 5152E0F923248F6200E5C7AD /* SettingsLocalAccountView.swift in Sources */, + 51FA73A52332BE110090D516 /* ArticleExtractor.swift in Sources */, FF3ABF162325AF5D0074C542 /* ArticleSorter.swift in Sources */, 510BD15D232D765D002692E4 /* SettingsReaderAPIAccountView.swift in Sources */, 51C4525C226508DF00C03939 /* String-Extensions.swift in Sources */, 51C452792265091600C03939 /* MasterTimelineTableViewCell.swift in Sources */, + 51FA73AB2332C2FD0090D516 /* ArticleExtractorConfig.swift in Sources */, 5132285B232FF2C40033D4ED /* SettingsRefreshSelectionView.swift in Sources */, 51C452852265093600C03939 /* FlattenedAccountFolderPickerData.swift in Sources */, 51C4526B226508F600C03939 /* MasterFeedViewController.swift in Sources */, @@ -2676,6 +2701,7 @@ 5183CCE5226F4DFA0010922C /* RefreshInterval.swift in Sources */, 51C4529D22650A1000C03939 /* FaviconURLFinder.swift in Sources */, 51C45258226508CF00C03939 /* AppAssets.swift in Sources */, + 51FA73A82332BE880090D516 /* ExtractedArticle.swift in Sources */, 51C4527C2265091600C03939 /* MasterTimelineDefaultCellLayout.swift in Sources */, 51C4529A22650A0400C03939 /* ArticleStyle.swift in Sources */, 51C4527F2265092C00C03939 /* DetailViewController.swift in Sources */, @@ -2736,6 +2762,7 @@ 84F204E01FAACBB30076E152 /* ArticleArray.swift in Sources */, 848B937221C8C5540038DC0D /* CrashReporter.swift in Sources */, 847CD6CA232F4CBF00FAC46D /* TimelineAvatarView.swift in Sources */, + 51FA73AA2332C2FD0090D516 /* ArticleExtractorConfig.swift in Sources */, 84BBB12E20142A4700F054F5 /* InspectorWindowController.swift in Sources */, 51EF0F7A22771B890050506E /* ColorHash.swift in Sources */, 84E46C7D1F75EF7B005ECFB3 /* AppDefaults.swift in Sources */, @@ -2815,6 +2842,7 @@ 841ABA5E20145E9200980E11 /* FolderInspectorViewController.swift in Sources */, 84DEE56522C32CA4005FC42C /* SmartFeedDelegate.swift in Sources */, 845213231FCA5B11003B6E93 /* ImageDownloader.swift in Sources */, + 51FA73B72332D5F70090D516 /* ArticleExtractorButton.swift in Sources */, 51EF0F922279CA620050506E /* AccountsAddTableCellView.swift in Sources */, 849A97431ED9EAA9007D329B /* AddFolderWindowController.swift in Sources */, 8405DDA522168C62008CE1BF /* TimelineContainerViewController.swift in Sources */, @@ -2838,6 +2866,7 @@ 84E8E0EB202F693600562D8F /* DetailWebView.swift in Sources */, 849A976C1ED9EBC8007D329B /* TimelineTableRowView.swift in Sources */, 849A977B1ED9EC04007D329B /* UnreadIndicatorView.swift in Sources */, + 51FA73A72332BE880090D516 /* ExtractedArticle.swift in Sources */, 84B99C9D1FAE83C600ECDEDB /* DeleteCommand.swift in Sources */, 849A97541ED9EAC0007D329B /* AddFeedWindowController.swift in Sources */, 5144EA40227A37EC00D19003 /* ImportOPMLWindowController.swift in Sources */, @@ -2846,6 +2875,7 @@ D5E4CC64202C1AC1009B4FFC /* MainWindowController+Scriptability.swift in Sources */, 84C9FC7922629E1200D921D6 /* PreferencesWindowController.swift in Sources */, 84411E711FE5FBFA004B527F /* SmallIconProvider.swift in Sources */, + 51FA73A42332BE110090D516 /* ArticleExtractor.swift in Sources */, 84CAFCA422BC8C08007694F0 /* FetchRequestQueue.swift in Sources */, 844B5B591FE9FE4F00C7C76A /* SidebarKeyboardDelegate.swift in Sources */, 84C9FC7C22629E1200D921D6 /* AccountsPreferencesViewController.swift in Sources */, diff --git a/NetNewsWire.xcodeproj/xcshareddata/xcschemes/NetNewsWire.xcscheme b/NetNewsWire.xcodeproj/xcshareddata/xcschemes/NetNewsWire.xcscheme index 230b6714a..20a782e7f 100644 --- a/NetNewsWire.xcodeproj/xcshareddata/xcschemes/NetNewsWire.xcscheme +++ b/NetNewsWire.xcodeproj/xcshareddata/xcschemes/NetNewsWire.xcscheme @@ -58,9 +58,6 @@ useCustomWorkingDirectory = "NO" ignoresPersistentStateOnLaunch = "NO" debugDocumentVersioning = "YES" - stopOnEveryThreadSanitizerIssue = "YES" - stopOnEveryUBSanitizerIssue = "YES" - stopOnEveryMainThreadCheckerIssue = "YES" migratedStopOnEveryIssue = "YES" debugServiceExtension = "internal" allowLocationSimulation = "YES"> @@ -74,6 +71,18 @@ ReferencedContainer = "container:NetNewsWire.xcodeproj"> + + + + + + String? { + let processInfo = ProcessInfo.processInfo + guard let value = processInfo.environment[named] else { + print("‼️ Missing Environment Variable: '\(named)'") + return nil + } + return value + } + +} diff --git a/Shared/Article Extractor/ExtractedArticle.swift b/Shared/Article Extractor/ExtractedArticle.swift new file mode 100644 index 000000000..460302477 --- /dev/null +++ b/Shared/Article Extractor/ExtractedArticle.swift @@ -0,0 +1,45 @@ +// +// ExtractedArticle.swift +// NetNewsWire +// +// Created by Maurice Parker on 9/18/19. +// Copyright © 2019 Ranchero Software. All rights reserved. +// + +import Foundation + +struct ExtractedArticle: Codable, Equatable { + + let title: String? + let author: String? + let datePublished: String? + let dek: String? + let leadImageURL: String? + let content: String? + let nextPageURL: String? + let url: String? + let domain: String? + let excerpt: String? + let wordCount: Int? + let direction: String? + let totalPages: Int? + let renderedPages: Int? + + enum CodingKeys: String, CodingKey { + case title = "title" + case author = "author" + case datePublished = "date_published" + case dek = "dek" + case leadImageURL = "lead_image_url" + case content = "content" + case nextPageURL = "next_page_url" + case url = "url" + case domain = "domain" + case excerpt = "excerpt" + case wordCount = "word_count" + case direction = "direction" + case totalPages = "total_pages" + case renderedPages = "rendered_pages" + } + +} diff --git a/Shared/Article Rendering/ArticleRenderer.swift b/Shared/Article Rendering/ArticleRenderer.swift index 49554ac25..5359bb414 100644 --- a/Shared/Article Rendering/ArticleRenderer.swift +++ b/Shared/Article Rendering/ArticleRenderer.swift @@ -14,36 +14,44 @@ import Account struct ArticleRenderer { private let article: Article? + private let extractedArticle: ExtractedArticle? private let articleStyle: ArticleStyle private let title: String + private let body: String private let baseURL: String? - private init(article: Article?, style: ArticleStyle) { + private init(article: Article?, extractedArticle: ExtractedArticle?, style: ArticleStyle) { self.article = article + self.extractedArticle = extractedArticle self.articleStyle = style self.title = article?.title ?? "" + if let content = extractedArticle?.content { + self.body = content + } else { + self.body = article?.body ?? "" + } self.baseURL = article?.baseURL?.absoluteString } // MARK: - API - static func articleHTML(article: Article, style: ArticleStyle) -> String { - let renderer = ArticleRenderer(article: article, style: style) + static func articleHTML(article: Article, extractedArticle: ExtractedArticle? = nil, style: ArticleStyle) -> String { + let renderer = ArticleRenderer(article: article, extractedArticle: extractedArticle, style: style) return renderer.articleHTML } static func multipleSelectionHTML(style: ArticleStyle) -> String { - let renderer = ArticleRenderer(article: nil, style: style) + let renderer = ArticleRenderer(article: nil, extractedArticle: nil, style: style) return renderer.multipleSelectionHTML } static func noSelectionHTML(style: ArticleStyle) -> String { - let renderer = ArticleRenderer(article: nil, style: style) + let renderer = ArticleRenderer(article: nil, extractedArticle: nil, style: style) return renderer.noSelectionHTML } static func noContentHTML(style: ArticleStyle) -> String { - let renderer = ArticleRenderer(article: nil, style: style) + let renderer = ArticleRenderer(article: nil, extractedArticle: nil, style: style) return renderer.noContentHTML } } @@ -53,7 +61,7 @@ struct ArticleRenderer { private extension ArticleRenderer { private var articleHTML: String { - let body = RSMacroProcessor.renderedText(withTemplate: template(), substitutions: substitutions(), macroStart: "[[", macroEnd: "]]") + let body = RSMacroProcessor.renderedText(withTemplate: template(), substitutions: articleSubstitutions(), macroStart: "[[", macroEnd: "]]") return renderHTML(withBody: body) } @@ -101,7 +109,7 @@ private extension ArticleRenderer { return title } - func substitutions() -> [String: String] { + func articleSubstitutions() -> [String: String] { var d = [String: String]() guard let article = article else { @@ -112,7 +120,6 @@ private extension ArticleRenderer { let title = titleOrTitleLink() d["title"] = title - let body = article.body ?? "" d["body"] = body d["avatars"] = "" diff --git a/submodules/RSCore b/submodules/RSCore index 98c050aca..4dbd31b09 160000 --- a/submodules/RSCore +++ b/submodules/RSCore @@ -1 +1 @@ -Subproject commit 98c050aca6e2c4f034b22c1d3d4f938893290543 +Subproject commit 4dbd31b090ab15c3966e9810a65edbf4abdbdd33