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:
parent
230671a190
commit
4f2f4a1ef4
|
@ -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>
|
||||
|
|
|
@ -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 it’s a different link from the title’s 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>"
|
||||
|
||||
|
|
|
@ -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: -
|
||||
|
|
|
@ -21,6 +21,13 @@ a:hover {
|
|||
height: 68px;
|
||||
}
|
||||
|
||||
.systemMessage {
|
||||
position: absolute;
|
||||
top: 45%;
|
||||
left: 50%;
|
||||
transform: translateX(-55%) translateY(-50%);
|
||||
}
|
||||
|
||||
/* Light mode */
|
||||
|
||||
body.light {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue