Changed the article detail pane to always use the webview even when displaying system messages, for example "No selection" or "Multiple Selection". Issue #455

This commit is contained in:
Maurice Parker 2018-09-14 20:00:51 -05:00
parent 230671a190
commit 4f2f4a1ef4
6 changed files with 62 additions and 152 deletions

View File

@ -624,59 +624,9 @@
</customView>
<connections>
<outlet property="containerView" destination="cJ9-6s-66u" id="gXc-Pz-9sQ"/>
<outlet property="noSelectionView" destination="aEc-1k-VmJ" id="KLS-ln-T1K"/>
</connections>
</viewController>
<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>
<point key="canvasLocation" x="68" y="946"/>
</scene>

View File

@ -18,7 +18,7 @@ var cachedTemplate = ""
class ArticleRenderer {
let article: Article
let article: Article?
let articleStyle: ArticleStyle
let appearance: NSAppearance?
@ -47,7 +47,7 @@ class ArticleRenderer {
}()
lazy var title: String = {
if let articleTitle = self.article.title {
if let articleTitle = self.article?.title {
return articleTitle
}
@ -56,12 +56,12 @@ class ArticleRenderer {
lazy var baseURL: URL? = {
var s = self.article.url
var s = self.article?.url
if s == nil {
s = self.article.feed?.homePageURL
s = self.article?.feed?.homePageURL
}
if s == nil {
s = self.article.feed?.url
s = self.article?.feed?.url
}
if s == nil {
return nil
@ -84,17 +84,25 @@ class ArticleRenderer {
return nil
}()
var html: String {
return renderedHTML()
var articleHTML: String {
let body = RSMacroProcessor.renderedText(withTemplate: template(), substitutions: substitutions(), macroStart: "[[", macroEnd: "]]")
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.articleStyle = style
self.appearance = appearance
}
// MARK: Private
@ -157,7 +165,7 @@ class ArticleRenderer {
private func titleOrTitleLink() -> String {
if let link = article.preferredLink {
if let link = article?.preferredLink {
return linkWithText(title, link)
}
return title
@ -167,6 +175,11 @@ class ArticleRenderer {
var d = [String: String]()
guard let article = article else {
assertionFailure("Article should have been set before calling this function.")
return d
}
let title = titleOrTitleLink()
d["title"] = title
@ -220,10 +233,10 @@ class ArticleRenderer {
}
private func dateShouldBeLink() -> Bool {
guard let permalink = article.url else {
guard let permalink = article?.url else {
return false
}
guard let preferredLink = article.preferredLink else { // Title uses preferredLink
guard let preferredLink = article?.preferredLink else { // Title uses preferredLink
return false
}
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.
if let authors = article.authors, authors.count == 1 {
if let authors = article?.authors, authors.count == 1 {
return authors.first!
}
return nil
@ -309,7 +322,7 @@ class ArticleRenderer {
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 nil
@ -317,10 +330,10 @@ class ArticleRenderer {
private func feedAvatar() -> Avatar? {
guard let feedIconURL = article.feed?.iconURL else {
guard let feedIconURL = article?.feed?.iconURL else {
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? {
@ -354,8 +367,8 @@ class ArticleRenderer {
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 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)
@ -370,11 +383,11 @@ class ArticleRenderer {
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
}
if let feedIconURL = article.feed?.iconURL {
return Avatar(imageURL: feedIconURL, url: article.feed?.homePageURL ?? article.feed?.url).html(dimension: avatarDimension)
if let feedIconURL = article?.feed?.iconURL {
return Avatar(imageURL: feedIconURL, url: article?.feed?.homePageURL ?? article?.feed?.url).html(dimension: avatarDimension)
}
if let author = singleFeedSpecifiedAuthor(), let imageURL = author.avatarURL {
return Avatar(imageURL: imageURL, url: author.url).html(dimension: avatarDimension)
@ -400,7 +413,7 @@ class ArticleRenderer {
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 ""
}
@ -408,7 +421,7 @@ class ArticleRenderer {
// 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 authors.count == 1, let author = authors.first {
if author.name == article.feed?.nameForDisplay {
if author.name == article?.feed?.nameForDisplay {
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"
s += textInsideTag(title, "title")
@ -480,7 +493,7 @@ class ArticleRenderer {
let appearanceClass = appearance?.isDarkMode ?? false ? "dark" : "light"
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>"

View File

@ -15,7 +15,6 @@ import RSWeb
final class DetailViewController: NSViewController, WKUIDelegate {
@IBOutlet var containerView: DetailContainerView!
@IBOutlet var noSelectionView: NoSelectionView!
var webview: DetailWebView!
@ -26,19 +25,13 @@ final class DetailViewController: NSViewController, WKUIDelegate {
return
}
article = nil
if let _ = articles {
noSelectionView.showMultipleSelection()
}
else {
noSelectionView.showNoSelection()
}
reloadHTML()
}
}
private var article: Article? {
didSet {
reloadHTML()
showOrHideWebView()
}
}
@ -78,9 +71,10 @@ final class DetailViewController: NSViewController, WKUIDelegate {
webview.customUserAgent = userAgent
}
reloadHTML()
containerView.contentView = webview
containerView.viewController = self
showOrHideWebView()
}
// MARK: - Scrolling
@ -194,35 +188,21 @@ private extension DetailViewController {
func reloadHTML() {
if let article = article {
let articleRenderer = ArticleRenderer(article: article,
style: ArticleStylesManager.shared.currentStyle,
appearance: self.view.effectiveAppearance)
webview.loadHTMLString(articleRenderer.html, baseURL: articleRenderer.baseURL)
let articleRenderer = ArticleRenderer(article: article,
style: ArticleStylesManager.shared.currentStyle,
appearance: self.view.effectiveAppearance)
if article != nil {
webview.loadHTMLString(articleRenderer.articleHTML, baseURL: articleRenderer.baseURL)
}
else if articles != nil {
webview.loadHTMLString(articleRenderer.multipleSelectionHTML, baseURL: nil)
}
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) {
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
// private var didConfigureLayer = false
//
// override var wantsUpdateLayer: Bool {
// return true
// }
var contentView: NSView? {
didSet {
if let oldContentView = oldValue {
@ -284,37 +258,8 @@ final class DetailContainerView: NSView {
NSColor.textBackgroundColor.setFill()
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: -

View File

@ -21,6 +21,13 @@ a:hover {
height: 68px;
}
.systemMessage {
position: absolute;
top: 45%;
left: 50%;
transform: translateX(-55%) translateY(-50%);
}
/* Light mode */
body.light {

View File

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

View File

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