This commit is contained in:
Brent Simmons 2018-09-14 19:33:56 -07:00
commit ff2086ccd4
6 changed files with 70 additions and 152 deletions

View File

@ -624,59 +624,9 @@
</customView> </customView>
<connections> <connections>
<outlet property="containerView" destination="cJ9-6s-66u" id="gXc-Pz-9sQ"/> <outlet property="containerView" destination="cJ9-6s-66u" id="gXc-Pz-9sQ"/>
<outlet property="noSelectionView" destination="aEc-1k-VmJ" id="KLS-ln-T1K"/>
</connections> </connections>
</viewController> </viewController>
<customObject id="vzM-Vn-mEn" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/> <customObject id="vzM-Vn-mEn" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
<customView id="aEc-1k-VmJ" customClass="NoSelectionView" customModule="NetNewsWire" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="613" height="96"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews>
<box boxType="custom" borderType="none" borderWidth="0.0" titlePosition="noTitle" translatesAutoresizingMaskIntoConstraints="NO" id="Zb8-hH-gIZ">
<rect key="frame" x="0.0" y="0.0" width="613" height="96"/>
<view key="contentView" id="Gmh-qL-lA5">
<rect key="frame" x="0.0" y="0.0" width="613" height="96"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" misplaced="YES" translatesAutoresizingMaskIntoConstraints="NO" id="ECt-Wi-zkb">
<rect key="frame" x="266" y="40" width="109" height="22"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="No selection" usesSingleLineMode="YES" id="oNn-so-m7W">
<font key="font" metaFont="systemSemibold" size="18"/>
<color key="textColor" name="tertiaryLabelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField hidden="YES" horizontalHuggingPriority="251" verticalHuggingPriority="750" misplaced="YES" translatesAutoresizingMaskIntoConstraints="NO" id="1AW-Ay-XS8">
<rect key="frame" x="408" y="39" width="154" height="22"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Multiple selection" usesSingleLineMode="YES" id="vhD-gM-NUy">
<font key="font" metaFont="systemSemibold" size="18"/>
<color key="textColor" name="tertiaryLabelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<constraints>
<constraint firstItem="1AW-Ay-XS8" firstAttribute="centerY" secondItem="Gmh-qL-lA5" secondAttribute="centerY" constant="-64" id="9uN-K3-d35"/>
<constraint firstItem="ECt-Wi-zkb" firstAttribute="centerX" secondItem="Gmh-qL-lA5" secondAttribute="centerX" id="QDc-Y3-Nn4"/>
<constraint firstItem="1AW-Ay-XS8" firstAttribute="centerX" secondItem="Gmh-qL-lA5" secondAttribute="centerX" id="QV5-qP-Ys6"/>
<constraint firstItem="ECt-Wi-zkb" firstAttribute="centerY" secondItem="Gmh-qL-lA5" secondAttribute="centerY" constant="-64" id="yQr-wJ-pBL"/>
</constraints>
</view>
<color key="borderColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<color key="fillColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</box>
</subviews>
<constraints>
<constraint firstItem="Zb8-hH-gIZ" firstAttribute="leading" secondItem="aEc-1k-VmJ" secondAttribute="leading" id="5Ay-6o-LZ1"/>
<constraint firstItem="Zb8-hH-gIZ" firstAttribute="top" secondItem="aEc-1k-VmJ" secondAttribute="top" id="Wb7-Zp-hj0"/>
<constraint firstAttribute="trailing" secondItem="Zb8-hH-gIZ" secondAttribute="trailing" id="cYM-up-kUN"/>
<constraint firstAttribute="bottom" secondItem="Zb8-hH-gIZ" secondAttribute="bottom" id="gCy-5R-aK2"/>
</constraints>
<connections>
<outlet property="multipleSelectionLabel" destination="1AW-Ay-XS8" id="gK2-Ya-Dz7"/>
<outlet property="noSelectionLabel" destination="ECt-Wi-zkb" id="Dvq-DU-MxC"/>
</connections>
</customView>
</objects> </objects>
<point key="canvasLocation" x="68" y="946"/> <point key="canvasLocation" x="68" y="946"/>
</scene> </scene>

View File

@ -18,7 +18,7 @@ var cachedTemplate = ""
class ArticleRenderer { class ArticleRenderer {
let article: Article let article: Article?
let articleStyle: ArticleStyle let articleStyle: ArticleStyle
let appearance: NSAppearance? let appearance: NSAppearance?
@ -47,7 +47,7 @@ class ArticleRenderer {
}() }()
lazy var title: String = { lazy var title: String = {
if let articleTitle = self.article.title { if let articleTitle = self.article?.title {
return articleTitle return articleTitle
} }
@ -56,12 +56,12 @@ class ArticleRenderer {
lazy var baseURL: URL? = { lazy var baseURL: URL? = {
var s = self.article.url var s = self.article?.url
if s == nil { if s == nil {
s = self.article.feed?.homePageURL s = self.article?.feed?.homePageURL
} }
if s == nil { if s == nil {
s = self.article.feed?.url s = self.article?.feed?.url
} }
if s == nil { if s == nil {
return nil return nil
@ -84,17 +84,25 @@ class ArticleRenderer {
return nil return nil
}() }()
var html: String { var articleHTML: String {
let body = RSMacroProcessor.renderedText(withTemplate: template(), substitutions: substitutions(), macroStart: "[[", macroEnd: "]]")
return renderedHTML() return renderHTML(withBody: body)
} }
init(article: Article, style: ArticleStyle, appearance: NSAppearance? = nil) { var multipleSelectionHTML: String {
let body = "<h3 class='systemMessage'>Multiple selection</h3>"
return renderHTML(withBody: body)
}
var noSelectionHTML: String {
let body = "<h3 class='systemMessage'>No selection</h3>"
return renderHTML(withBody: body)
}
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
} }
// MARK: Private // MARK: Private
@ -157,7 +165,7 @@ class ArticleRenderer {
private func titleOrTitleLink() -> String { private func titleOrTitleLink() -> String {
if let link = article.preferredLink { if let link = article?.preferredLink {
return linkWithText(title, link) return linkWithText(title, link)
} }
return title return title
@ -167,6 +175,11 @@ class ArticleRenderer {
var d = [String: String]() var d = [String: String]()
guard let article = article else {
assertionFailure("Article should have been set before calling this function.")
return d
}
let title = titleOrTitleLink() let title = titleOrTitleLink()
d["title"] = title d["title"] = title
@ -220,10 +233,10 @@ class ArticleRenderer {
} }
private func dateShouldBeLink() -> Bool { private func dateShouldBeLink() -> Bool {
guard let permalink = article.url else { guard let permalink = article?.url else {
return false return false
} }
guard let preferredLink = article.preferredLink else { // Title uses preferredLink guard let preferredLink = article?.preferredLink else { // Title uses preferredLink
return false return false
} }
return permalink != preferredLink // Make date a link if its a different link from the titles link return permalink != preferredLink // Make date a link if its a different link from the titles link
@ -301,7 +314,7 @@ class ArticleRenderer {
// 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
@ -309,7 +322,7 @@ class ArticleRenderer {
private func singleFeedSpecifiedAuthor() -> Author? { private 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
@ -317,10 +330,10 @@ class ArticleRenderer {
private func feedAvatar() -> Avatar? { private func feedAvatar() -> Avatar? {
guard let feedIconURL = article.feed?.iconURL else { guard let feedIconURL = article?.feed?.iconURL else {
return nil return nil
} }
return Avatar(imageURL: feedIconURL, url: article.feed?.homePageURL ?? article.feed?.url) return Avatar(imageURL: feedIconURL, url: article?.feed?.homePageURL ?? article?.feed?.url)
} }
private func authorAvatar() -> Avatar? { private func authorAvatar() -> Avatar? {
@ -354,8 +367,8 @@ class ArticleRenderer {
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)
} }
if let feedIconURL = article.feed?.iconURL { if let feedIconURL = article?.feed?.iconURL {
return Avatar(imageURL: feedIconURL, url: article.feed?.homePageURL ?? article.feed?.url) return Avatar(imageURL: feedIconURL, url: article?.feed?.homePageURL ?? article?.feed?.url)
} }
if let author = singleFeedSpecifiedAuthor(), let imageURL = author.avatarURL { if let author = singleFeedSpecifiedAuthor(), let imageURL = author.avatarURL {
return Avatar(imageURL: imageURL, url: author.url) return Avatar(imageURL: imageURL, url: author.url)
@ -370,11 +383,11 @@ class ArticleRenderer {
if let author = singleArticleSpecifiedAuthor(), let imageURL = author.avatarURL { if let author = singleArticleSpecifiedAuthor(), let imageURL = author.avatarURL {
return Avatar(imageURL: imageURL, url: author.url).html(dimension: avatarDimension) 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: 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: avatarDimension)
@ -400,7 +413,7 @@ class ArticleRenderer {
private func byline() -> String { 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 ""
} }
@ -408,7 +421,7 @@ class ArticleRenderer {
// This code assumes that multiple authors would never match the feed name so that // This code assumes that multiple authors would never match the feed name so that
// if there feed owner has an article co-author all authors are given the byline. // if there feed owner has an article co-author all authors are given the byline.
if authors.count == 1, let author = authors.first { if authors.count == 1, let author = authors.first {
if author.name == article.feed?.nameForDisplay { if author.name == article?.feed?.nameForDisplay {
return "" return ""
} }
} }
@ -447,7 +460,7 @@ class ArticleRenderer {
} }
private func renderedHTML() -> String { private 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 += textInsideTag(title, "title")
@ -480,7 +493,7 @@ 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 += RSMacroProcessor.renderedText(withTemplate: template(), substitutions: substitutions(), macroStart: "[[", macroEnd: "]]") s += body
s += "\n\n</body></html>" s += "\n\n</body></html>"

View File

@ -15,7 +15,6 @@ import RSWeb
final class DetailViewController: NSViewController, WKUIDelegate { final class DetailViewController: NSViewController, WKUIDelegate {
@IBOutlet var containerView: DetailContainerView! @IBOutlet var containerView: DetailContainerView!
@IBOutlet var noSelectionView: NoSelectionView!
var webview: DetailWebView! var webview: DetailWebView!
@ -26,19 +25,13 @@ final class DetailViewController: NSViewController, WKUIDelegate {
return return
} }
article = nil article = nil
if let _ = articles { reloadHTML()
noSelectionView.showMultipleSelection()
}
else {
noSelectionView.showNoSelection()
}
} }
} }
private var article: Article? { private var article: Article? {
didSet { didSet {
reloadHTML() reloadHTML()
showOrHideWebView()
} }
} }
@ -78,9 +71,10 @@ final class DetailViewController: NSViewController, WKUIDelegate {
webview.customUserAgent = userAgent webview.customUserAgent = userAgent
} }
reloadHTML()
containerView.contentView = webview
containerView.viewController = self containerView.viewController = self
showOrHideWebView()
} }
// MARK: - Scrolling // MARK: - Scrolling
@ -194,35 +188,21 @@ private extension DetailViewController {
func reloadHTML() { func reloadHTML() {
if let article = article { let articleRenderer = ArticleRenderer(article: article,
let articleRenderer = ArticleRenderer(article: article, style: ArticleStylesManager.shared.currentStyle,
style: ArticleStylesManager.shared.currentStyle, appearance: self.view.effectiveAppearance)
appearance: self.view.effectiveAppearance)
webview.loadHTMLString(articleRenderer.html, baseURL: articleRenderer.baseURL) if article != nil {
webview.loadHTMLString(articleRenderer.articleHTML, baseURL: articleRenderer.baseURL)
}
else if articles != nil {
webview.loadHTMLString(articleRenderer.multipleSelectionHTML, baseURL: nil)
} }
else { else {
webview.loadHTMLString("", baseURL: nil) webview.loadHTMLString(articleRenderer.noSelectionHTML, baseURL: nil)
} }
} }
func showOrHideWebView() {
if let _ = article {
switchToView(webview)
}
else {
switchToView(noSelectionView)
}
}
func switchToView(_ view: NSView) {
if containerView.contentView == view {
return
}
containerView.contentView = view
}
func fetchScrollInfo(_ callback: @escaping (ScrollInfo?) -> Void) { func fetchScrollInfo(_ callback: @escaping (ScrollInfo?) -> Void) {
let javascriptString = "var x = {contentHeight: document.body.scrollHeight, offsetY: document.body.scrollTop}; x" let javascriptString = "var x = {contentHeight: document.body.scrollHeight, offsetY: document.body.scrollTop}; x"
@ -251,12 +231,6 @@ final class DetailContainerView: NSView {
weak var viewController: DetailViewController? = nil weak var viewController: DetailViewController? = nil
// private var didConfigureLayer = false
//
// override var wantsUpdateLayer: Bool {
// return true
// }
var contentView: NSView? { var contentView: NSView? {
didSet { didSet {
if let oldContentView = oldValue { if let oldContentView = oldValue {
@ -284,37 +258,8 @@ final class DetailContainerView: NSView {
NSColor.textBackgroundColor.setFill() NSColor.textBackgroundColor.setFill()
dirtyRect.fill() dirtyRect.fill()
} }
// override func updateLayer() {
//
// guard !didConfigureLayer else {
// return
// }
// if let layer = layer {
// let color = appDelegate.currentTheme.color(forKey: "MainWindow.Detail.backgroundColor")
// layer.backgroundColor = color.cgColor
// didConfigureLayer = true
// }
// }
}
// MARK: -
final class NoSelectionView: NSView {
@IBOutlet var noSelectionLabel: NSTextField!
@IBOutlet var multipleSelectionLabel: NSTextField!
func showMultipleSelection() {
noSelectionLabel.isHidden = true
multipleSelectionLabel.isHidden = false
}
func showNoSelection() {
noSelectionLabel.isHidden = false
multipleSelectionLabel.isHidden = true
}
} }
// MARK: - // MARK: -

View File

@ -21,6 +21,13 @@ a:hover {
height: 68px; height: 68px;
} }
.systemMessage {
position: absolute;
top: 45%;
left: 50%;
transform: translateX(-55%) translateY(-50%);
}
/* Light mode */ /* Light mode */
body.light { body.light {
@ -44,6 +51,10 @@ body.light .articleDateline, body.light .articleDateLine.a:link, body.light .art
color: rgba(0, 0, 0, 0.3); color: rgba(0, 0, 0, 0.3);
} }
.light > .systemMessage {
color: #cbcbcb;
}
/* Dark mode */ /* Dark mode */
body.dark { body.dark {
@ -70,6 +81,10 @@ body.dark .articleDateline, body.dark .articleDateLine.a:link, body.dark .articl
color: #d2d2d2; color: #d2d2d2;
} }
.dark > .systemMessage {
color: #5f5f5f;
}
.feedlink a:link, .feedlink a:visited { .feedlink a:link, .feedlink a:visited {
color: rgba(0, 0, 0, 0.6); color: rgba(0, 0, 0, 0.6);
} }

View File

@ -27,7 +27,7 @@ extension Article: PasteboardWriterOwner {
private lazy var renderedHTML: String = { private lazy var renderedHTML: String = {
let articleRenderer = ArticleRenderer(article: article, style: ArticleStylesManager.shared.currentStyle) let articleRenderer = ArticleRenderer(article: article, style: ArticleStylesManager.shared.currentStyle)
return articleRenderer.html return articleRenderer.articleHTML
}() }()
init(article: Article) { init(article: Article) {

View File

@ -127,11 +127,6 @@
</dict> </dict>
<key>Detail</key> <key>Detail</key>
<dict> <dict>
<key>noSelectionView</key>
<dict>
<key>backgroundColor</key>
<string>FFFFFF</string>
</dict>
<key>statusBar</key> <key>statusBar</key>
<dict> <dict>
<key>backgroundColor</key> <key>backgroundColor</key>