This commit is contained in:
Brent Simmons 2021-09-12 20:05:04 -07:00
commit ecf944c80a
24 changed files with 1224 additions and 245 deletions

View File

@ -320,7 +320,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations,
}
func application(_ sender: NSApplication, openFile filename: String) -> Bool {
guard filename.hasSuffix(".nnwtheme") else { return false }
guard filename.hasSuffix(ArticleTheme.nnwThemeSuffix) else { return false }
importTheme(filename: filename)
return true
}
@ -812,36 +812,71 @@ private extension AppDelegate {
attrs[.font] = NSFont.systemFont(ofSize: NSFont.smallSystemFontSize)
attrs[.foregroundColor] = NSColor.textColor
let titleParagraphStyle = NSMutableParagraphStyle()
titleParagraphStyle.alignment = .center
attrs[.paragraphStyle] = titleParagraphStyle
if #available(macOS 11.0, *) {
let titleParagraphStyle = NSMutableParagraphStyle()
titleParagraphStyle.alignment = .center
attrs[.paragraphStyle] = titleParagraphStyle
}
let websiteText = NSMutableAttributedString()
websiteText.append(NSAttributedString(string: NSLocalizedString("Author's Website", comment: "Author's Website"), attributes: attrs))
websiteText.append(NSAttributedString(string: "\n"))
if #available(macOS 11.0, *) {
websiteText.append(NSAttributedString(string: "\n"))
} else {
websiteText.append(NSAttributedString(string: " "))
}
attrs[.link] = theme.creatorHomePage
websiteText.append(NSAttributedString(string: theme.creatorHomePage, attributes: attrs))
let textView = NSTextView(frame: CGRect(x: 0, y: 0, width: 200, height: 15))
let textViewWidth: CGFloat
if #available(macOS 11.0, *) {
textViewWidth = 200
} else {
textViewWidth = 400
}
let textView = NSTextView(frame: CGRect(x: 0, y: 0, width: textViewWidth, height: 15))
textView.isEditable = false
textView.drawsBackground = false
textView.textStorage?.setAttributedString(websiteText)
alert.accessoryView = textView
alert.addButton(withTitle: NSLocalizedString("Install Style", comment: "Install Style"))
alert.addButton(withTitle: NSLocalizedString("Cancel", comment: "Cancel Install Style"))
alert.addButton(withTitle: NSLocalizedString("Install Theme", comment: "Install Theme"))
alert.addButton(withTitle: NSLocalizedString("Cancel", comment: "Cancel Install Theme"))
alert.beginSheetModal(for: window) { [weak self] result in
func importTheme() {
do {
try ArticleThemesManager.shared.importTheme(filename: filename)
confirmImportSuccess(themeName: theme.name)
} catch {
NSApplication.shared.presentError(error)
}
}
alert.beginSheetModal(for: window) { result in
if result == NSApplication.ModalResponse.alertFirstButtonReturn {
guard let self = self else { return }
do {
try ArticleThemesManager.shared.importTheme(filename: filename)
self.confirmImportSuccess(themeName: theme.name)
} catch {
NSApplication.shared.presentError(error)
if ArticleThemesManager.shared.themeExists(filename: filename) {
let alert = NSAlert()
alert.alertStyle = .warning
let localizedMessageText = NSLocalizedString("The theme “%@” already exists. Overwrite it?", comment: "Overwrite theme")
alert.messageText = NSString.localizedStringWithFormat(localizedMessageText as NSString, theme.name) as String
alert.addButton(withTitle: NSLocalizedString("Overwrite", comment: "Overwrite"))
alert.addButton(withTitle: NSLocalizedString("Cancel", comment: "Cancel Install Theme"))
alert.beginSheetModal(for: window) { result in
if result == NSApplication.ModalResponse.alertFirstButtonReturn {
importTheme()
}
}
} else {
importTheme()
}
}
}
}

View File

@ -157,6 +157,18 @@
<action selector="copy:" target="Ady-hI-5gd" id="G1f-GL-Joy"/>
</connections>
</menuItem>
<menuItem title="Copy Article URL" id="qNk-By-jKp">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="copyArticleURL:" target="Ady-hI-5gd" id="A5t-x7-Mmy"/>
</connections>
</menuItem>
<menuItem title="Copy External URL" id="fOF-99-6Iv">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="copyExternalURL:" target="Ady-hI-5gd" id="MkV-Do-Bc1"/>
</connections>
</menuItem>
<menuItem title="Paste" keyEquivalent="v" id="gVA-U4-sdL">
<connections>
<action selector="paste:" target="Ady-hI-5gd" id="UvS-8e-Qdg"/>

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="17506" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" initialViewController="B8D-0N-5wS">
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="17701" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" initialViewController="B8D-0N-5wS">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="17506"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="17701"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
@ -196,6 +196,24 @@
<action selector="cleanUp:" target="Oky-zY-oP4" id="UCH-DG-yk4"/>
</connections>
</toolbarItem>
<toolbarItem implicitItemIdentifier="596363B5-CE41-417C-B8AB-11CC2C99BCA5" label="Article Theme" paletteLabel="Article Theme" sizingBehavior="auto" id="3Hc-al-vK2">
<nil key="toolTip"/>
<popUpButton key="view" verticalHuggingPriority="750" id="MFT-nb-eLG">
<rect key="frame" x="0.0" y="14" width="100" height="24"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<popUpButtonCell key="cell" type="roundTextured" title="Item 1" bezelStyle="texturedRounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" selectedItem="xAs-IL-tMv" id="ior-Gb-LTq">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<menu key="menu" id="1Ac-Uq-Yeu">
<items>
<menuItem title="Item 1" state="on" id="xAs-IL-tMv"/>
<menuItem title="Item 2" id="T8e-ib-OTb"/>
<menuItem title="Item 3" id="Gfy-76-Njz"/>
</items>
</menu>
</popUpButtonCell>
</popUpButton>
</toolbarItem>
</allowedToolbarItems>
<defaultToolbarItems>
<toolbarItem reference="Skp-5r-70Q"/>
@ -221,6 +239,7 @@
</connections>
</window>
<connections>
<outlet property="articleThemePopUpButton" destination="MFT-nb-eLG" id="lHc-ej-PrT"/>
<segue destination="reS-fe-pD8" kind="relationship" relationship="window.shadowedContentViewController" id="WS2-WB-dc4"/>
</connections>
</windowController>

View File

@ -32,14 +32,14 @@
<objects>
<viewController title="General" storyboardIdentifier="General" id="iuH-lz-18x" customClass="GeneralPreferencesViewController" customModule="NetNewsWire" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" misplaced="YES" id="WnV-px-wCT">
<rect key="frame" x="0.0" y="0.0" width="509" height="306"/>
<rect key="frame" x="0.0" y="0.0" width="509" height="339"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<customView horizontalHuggingPriority="1000" verticalHuggingPriority="1000" horizontalCompressionResistancePriority="1000" verticalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="Ut3-yd-q6G">
<rect key="frame" x="54" y="16" width="399" height="274"/>
<rect key="frame" x="54" y="16" width="399" height="306"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="pR2-Bf-7Fd">
<rect key="frame" x="6" y="253" width="105" height="16"/>
<rect key="frame" x="6" y="285" width="105" height="16"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="Article Text Size:" id="xQu-QV-91i">
<font key="font" usesAppearanceFont="YES"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@ -47,7 +47,7 @@
</textFieldCell>
</textField>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Z6O-Zt-V1g">
<rect key="frame" x="114" y="246" width="289" height="25"/>
<rect key="frame" x="114" y="278" width="289" height="25"/>
<popUpButtonCell key="cell" type="push" title="Medium" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="2" imageScaling="proportionallyDown" inset="2" selectedItem="jMV-2o-5Oh" id="6pw-Vq-tjM">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
@ -76,7 +76,7 @@
</popUpButtonCell>
</popUpButton>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ISO-Wu-R60">
<rect key="frame" x="114" y="212" width="289" height="25"/>
<rect key="frame" x="114" y="244" width="289" height="25"/>
<popUpButtonCell key="cell" type="push" title="Default" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="Pkl-EA-Goa" id="vN9-pm-Gls">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
@ -90,6 +90,16 @@
<action selector="articleThemePopUpDidChange:" target="iuH-lz-18x" id="afz-Uu-a5b"/>
</connections>
</popUpButton>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="1w0-nA-DEO">
<rect key="frame" x="110" y="207" width="217" height="32"/>
<buttonCell key="cell" type="push" title="Open Themes Folder in Finder" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="ySX-5i-SP1">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="showThemesFolder:" target="iuH-lz-18x" id="WEP-Fe-cAR"/>
</connections>
</button>
<box verticalHuggingPriority="750" boxType="separator" translatesAutoresizingMaskIntoConstraints="NO" id="Tdg-6Y-gvW">
<rect key="frame" x="0.0" y="197" width="399" height="5"/>
</box>
@ -223,7 +233,7 @@
</connections>
</button>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="S2Z-bG-jYk">
<rect key="frame" x="18" y="219" width="93" height="16"/>
<rect key="frame" x="18" y="251" width="93" height="16"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="Article Theme:" id="MQe-Za-N8J">
<font key="font" usesAppearanceFont="YES"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
@ -255,6 +265,7 @@
<constraint firstItem="j0t-Wa-UTL" firstAttribute="leading" secondItem="Ubm-Pk-l7x" secondAttribute="leading" constant="19" id="UKq-8p-lyR"/>
<constraint firstItem="j0t-Wa-UTL" firstAttribute="top" secondItem="Ubm-Pk-l7x" secondAttribute="bottom" constant="8" id="XTw-Ef-FD3"/>
<constraint firstItem="wtY-Zd-Ps9" firstAttribute="trailing" secondItem="SFF-mL-yc8" secondAttribute="trailing" id="Zkn-zv-as5"/>
<constraint firstItem="1w0-nA-DEO" firstAttribute="top" secondItem="ISO-Wu-R60" secondAttribute="bottom" constant="14" id="ZlG-V3-AAd"/>
<constraint firstItem="pR2-Bf-7Fd" firstAttribute="firstBaseline" secondItem="Z6O-Zt-V1g" secondAttribute="firstBaseline" id="aO5-iE-L7A"/>
<constraint firstAttribute="trailing" secondItem="Z6O-Zt-V1g" secondAttribute="trailing" id="aS9-KA-vSH"/>
<constraint firstItem="wtY-Zd-Ps9" firstAttribute="top" secondItem="j0t-Wa-UTL" secondAttribute="bottom" constant="14" id="aod-td-Gim"/>
@ -262,13 +273,14 @@
<constraint firstItem="Tdg-6Y-gvW" firstAttribute="leading" secondItem="Ut3-yd-q6G" secondAttribute="leading" id="b3I-JF-If3"/>
<constraint firstItem="wtY-Zd-Ps9" firstAttribute="leading" secondItem="SFF-mL-yc8" secondAttribute="leading" id="dTq-cu-Z2s"/>
<constraint firstItem="Yrc-6Q-kx8" firstAttribute="trailing" secondItem="wtY-Zd-Ps9" secondAttribute="trailing" id="e6V-q6-WJq"/>
<constraint firstItem="Tdg-6Y-gvW" firstAttribute="top" secondItem="ISO-Wu-R60" secondAttribute="bottom" constant="16" id="eDO-fC-gaC"/>
<constraint firstItem="SFF-mL-yc8" firstAttribute="top" secondItem="hQy-ng-ijd" secondAttribute="bottom" constant="16" id="eM7-OM-Qsz"/>
<constraint firstItem="Yrc-6Q-kx8" firstAttribute="leading" secondItem="wtY-Zd-Ps9" secondAttribute="leading" id="gNX-Yc-DdD"/>
<constraint firstItem="1w0-nA-DEO" firstAttribute="leading" secondItem="ISO-Wu-R60" secondAttribute="leading" id="gWR-OU-qcO"/>
<constraint firstItem="Ci4-fW-KjU" firstAttribute="top" secondItem="Tdg-6Y-gvW" secondAttribute="bottom" constant="16" id="hXl-1D-lTD"/>
<constraint firstItem="wtY-Zd-Ps9" firstAttribute="leading" secondItem="yrg-M3-Dbz" secondAttribute="trailing" constant="6" symbolic="YES" id="hpP-sx-veV"/>
<constraint firstItem="hQy-ng-ijd" firstAttribute="top" secondItem="Yrc-6Q-kx8" secondAttribute="bottom" constant="16" id="i2g-cZ-EV4"/>
<constraint firstItem="ucw-vG-yLt" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="Ut3-yd-q6G" secondAttribute="leading" id="lDL-JN-ANP"/>
<constraint firstItem="Tdg-6Y-gvW" firstAttribute="top" secondItem="1w0-nA-DEO" secondAttribute="bottom" constant="14" id="lEK-yl-TCM"/>
<constraint firstItem="Z6O-Zt-V1g" firstAttribute="width" secondItem="SFF-mL-yc8" secondAttribute="width" id="noW-Jf-Xbs"/>
<constraint firstAttribute="trailing" secondItem="Tdg-6Y-gvW" secondAttribute="trailing" id="qzz-gu-8kO"/>
<constraint firstItem="Wsb-Lr-8Q7" firstAttribute="firstBaseline" secondItem="Ci4-fW-KjU" secondAttribute="firstBaseline" id="rPX-je-OG5"/>
@ -295,7 +307,7 @@
<customObject id="bSQ-tq-wd3" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
<userDefaultsController representsSharedInstance="YES" id="mAF-gO-1PI"/>
</objects>
<point key="canvasLocation" x="-568.5" y="451.5"/>
<point key="canvasLocation" x="-565.5" y="432"/>
</scene>
<!--Advanced Preferences View Controller-->
<scene sceneID="z1G-rc-sP5">
@ -475,16 +487,16 @@
<autoresizingMask key="autoresizingMask"/>
<subviews>
<customView translatesAutoresizingMaskIntoConstraints="NO" id="7UM-iq-OLB" customClass="PreferencesTableViewBackgroundView" customModule="NetNewsWire" customModuleProvider="target">
<rect key="frame" x="20" y="44" width="180" height="240"/>
<rect key="frame" x="20" y="44" width="180" height="228"/>
<subviews>
<scrollView borderType="none" autohidesScrollers="YES" horizontalLineScroll="26" horizontalPageScroll="10" verticalLineScroll="26" verticalPageScroll="10" hasHorizontalScroller="NO" horizontalScrollElasticity="none" translatesAutoresizingMaskIntoConstraints="NO" id="PaF-du-r3c">
<rect key="frame" x="1" y="1" width="178" height="238"/>
<rect key="frame" x="1" y="1" width="178" height="226"/>
<clipView key="contentView" id="cil-Gq-akO">
<rect key="frame" x="0.0" y="0.0" width="178" height="238"/>
<rect key="frame" x="0.0" y="0.0" width="178" height="226"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<tableView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" tableStyle="fullWidth" columnReordering="NO" columnSelection="YES" columnResizing="NO" multipleSelection="NO" autosaveColumns="NO" rowHeight="24" viewBased="YES" id="aTp-KR-y6b">
<rect key="frame" x="0.0" y="0.0" width="178" height="238"/>
<rect key="frame" x="0.0" y="0.0" width="178" height="226"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<size key="intercellSpacing" width="3" height="2"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
@ -591,7 +603,7 @@
<rect key="frame" x="83" y="20" width="117" height="24"/>
</customView>
<customView translatesAutoresizingMaskIntoConstraints="NO" id="Y7D-xQ-wep">
<rect key="frame" x="208" y="20" width="222" height="264"/>
<rect key="frame" x="208" y="20" width="222" height="252"/>
</customView>
</subviews>
<constraints>
@ -646,16 +658,16 @@
<autoresizingMask key="autoresizingMask"/>
<subviews>
<customView translatesAutoresizingMaskIntoConstraints="NO" id="pjs-G4-byk" customClass="PreferencesTableViewBackgroundView" customModule="NetNewsWire" customModuleProvider="target">
<rect key="frame" x="20" y="44" width="180" height="240"/>
<rect key="frame" x="20" y="44" width="180" height="228"/>
<subviews>
<scrollView borderType="none" autohidesScrollers="YES" horizontalLineScroll="26" horizontalPageScroll="10" verticalLineScroll="26" verticalPageScroll="10" hasHorizontalScroller="NO" horizontalScrollElasticity="none" translatesAutoresizingMaskIntoConstraints="NO" id="29T-r2-ckC">
<rect key="frame" x="1" y="1" width="178" height="238"/>
<rect key="frame" x="1" y="1" width="178" height="226"/>
<clipView key="contentView" id="dXw-GY-TP8">
<rect key="frame" x="0.0" y="0.0" width="178" height="238"/>
<rect key="frame" x="0.0" y="0.0" width="178" height="226"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<tableView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" tableStyle="fullWidth" columnReordering="NO" columnSelection="YES" columnResizing="NO" multipleSelection="NO" autosaveColumns="NO" rowHeight="24" viewBased="YES" id="dfn-Vn-oDp">
<rect key="frame" x="0.0" y="0.0" width="178" height="238"/>
<rect key="frame" x="0.0" y="0.0" width="178" height="226"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<size key="intercellSpacing" width="3" height="2"/>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
@ -758,7 +770,7 @@
<rect key="frame" x="83" y="20" width="117" height="24"/>
</customView>
<customView translatesAutoresizingMaskIntoConstraints="NO" id="N1N-pE-gBL">
<rect key="frame" x="208" y="20" width="222" height="264"/>
<rect key="frame" x="208" y="20" width="222" height="252"/>
</customView>
</subviews>
<constraints>

View File

@ -55,6 +55,7 @@ final class DetailWebViewController: NSViewController {
private let detailIconSchemeHandler = DetailIconSchemeHandler()
private var waitingForFirstReload = false
private var windowScrollY: CGFloat?
private let keyboardDelegate = DetailKeyboardDelegate()
private struct MessageName {
@ -143,7 +144,10 @@ final class DetailWebViewController: NSViewController {
}
@objc func currentArticleThemeDidChangeNotification(_ note: Notification) {
reloadHTML()
fetchScrollInfo() { scrollInfo in
self.windowScrollY = scrollInfo?.offsetY
self.reloadHTML()
}
}
// MARK: Media Functions
@ -226,6 +230,11 @@ extension DetailWebViewController: WKNavigationDelegate, WKUIDelegate {
webView.isHidden = false
}
}
if let windowScrollY = windowScrollY {
webView.evaluateJavaScript("window.scrollTo(0, \(windowScrollY));")
self.windowScrollY = nil
}
}
// WKUIDelegate

View File

@ -18,7 +18,9 @@ enum TimelineSourceMode {
class MainWindowController : NSWindowController, NSUserInterfaceValidations {
private var activityManager = ActivityManager()
@IBOutlet weak var articleThemePopUpButton: NSPopUpButton?
private var activityManager = ActivityManager()
private var isShowingExtractedArticle = false
private var articleExtractor: ArticleExtractor? = nil
@ -62,6 +64,8 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations {
sharingServicePickerDelegate = SharingServicePickerDelegate(self.window)
updateArticleThemeMenu()
if #available(macOS 11.0, *) {
let toolbar = NSToolbar(identifier: "MainWindowToolbar")
toolbar.allowsUserCustomization = true
@ -155,11 +159,11 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations {
}
@objc func articleThemeNamesDidChangeNotification(_ note: Notification) {
buildArticleThemeMenu()
updateArticleThemeMenu()
}
@objc func currentArticleThemeDidChangeNotification(_ note: Notification) {
buildArticleThemeMenu()
updateArticleThemeMenu()
}
private func updateWindowTitleIfNecessary(_ noteObject: Any?) {
@ -200,6 +204,14 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations {
public func validateUserInterfaceItem(_ item: NSValidatedUserInterfaceItem) -> Bool {
if item.action == #selector(copyArticleURL(_:)) {
return canCopyArticleURL()
}
if item.action == #selector(copyExternalURL(_:)) {
return canCopyExternalURL()
}
if item.action == #selector(openArticleInBrowser(_:)) {
if let item = item as? NSMenuItem, item.keyEquivalentModifierMask.contains(.shift) {
item.title = Browser.titleForOpenInBrowserInverted
@ -298,6 +310,18 @@ class MainWindowController : NSWindowController, NSUserInterfaceValidations {
}
@IBAction func copyArticleURL(_ sender: Any?) {
if let link = oneSelectedArticle?.preferredURL?.absoluteString {
URLPasteboardWriter.write(urlString: link, to: .general)
}
}
@IBAction func copyExternalURL(_ sender: Any?) {
if let link = oneSelectedArticle?.externalURL {
URLPasteboardWriter.write(urlString: link, to: .general)
}
}
@IBAction func openArticleInBrowser(_ sender: Any?) {
if let link = currentLink {
Browser.open(link, invertPreference: NSApp.currentEvent?.modifierFlags.contains(.shift) ?? false)
@ -817,7 +841,6 @@ extension MainWindowController: NSToolbarDelegate {
let description = NSLocalizedString("Article Theme", comment: "Article Theme")
articleThemeMenuToolbarItem.toolTip = description
articleThemeMenuToolbarItem.label = description
buildArticleThemeMenu()
return articleThemeMenuToolbarItem
case .search:
@ -1033,6 +1056,14 @@ private extension MainWindowController {
}
// MARK: - Command Validation
func canCopyArticleURL() -> Bool {
return currentLink != nil
}
func canCopyExternalURL() -> Bool {
return oneSelectedArticle?.externalURL != nil && oneSelectedArticle?.externalURL != currentLink
}
func canGoToNextUnread(wrappingToTop wrapping: Bool = false) -> Bool {
@ -1413,7 +1444,7 @@ private extension MainWindowController {
return menu
}
func buildArticleThemeMenu() {
func updateArticleThemeMenu() {
let articleThemeMenu = NSMenu()
let defaultThemeItem = NSMenuItem()
@ -1433,6 +1464,7 @@ private extension MainWindowController {
}
articleThemeMenuToolbarItem.menu = articleThemeMenu
articleThemePopUpButton?.menu = articleThemeMenu
}
}

View File

@ -90,6 +90,13 @@ extension TimelineViewController {
}
Browser.open(urlString, inBackground: false)
}
@objc func copyURLFromContextualMenu(_ sender: Any?) {
guard let menuItem = sender as? NSMenuItem, let urlString = menuItem.representedObject as? String else {
return
}
URLPasteboardWriter.write(urlString: urlString, to: .general)
}
@objc func performShareServiceFromContextualMenu(_ sender: Any?) {
guard let menuItem = sender as? NSMenuItem, let sharingCommandInfo = menuItem.representedObject as? SharingCommandInfo else {
@ -168,6 +175,12 @@ private extension TimelineViewController {
if articles.count == 1, let link = articles.first!.preferredLink {
menu.addSeparatorIfNeeded()
menu.addItem(openInBrowserMenuItem(link))
menu.addSeparatorIfNeeded()
menu.addItem(copyArticleURLMenuItem(link))
if let externalLink = articles.first?.externalURL, externalLink != link {
menu.addItem(copyExternalURLMenuItem(externalLink))
}
}
if let sharingMenu = shareMenu(for: articles) {
@ -260,6 +273,15 @@ private extension TimelineViewController {
return menuItem(NSLocalizedString("Open in Browser", comment: "Command"), #selector(openInBrowserFromContextualMenu(_:)), urlString)
}
func copyArticleURLMenuItem(_ urlString: String) -> NSMenuItem {
return menuItem(NSLocalizedString("Copy Article URL", comment: "Command"), #selector(copyURLFromContextualMenu(_:)), urlString)
}
func copyExternalURLMenuItem(_ urlString: String) -> NSMenuItem {
return menuItem(NSLocalizedString("Copy External URL", comment: "Command"), #selector(copyURLFromContextualMenu(_:)), urlString)
}
func menuItem(_ title: String, _ action: Selector, _ representedObject: Any) -> NSMenuItem {

View File

@ -46,6 +46,11 @@ final class GeneralPreferencesViewController: NSViewController {
// MARK: - Actions
@IBAction func showThemesFolder(_ sender: Any) {
let url = URL(fileURLWithPath: ArticleThemesManager.shared.folderPath)
NSWorkspace.shared.open(url)
}
@IBAction func articleThemePopUpDidChange(_ sender: Any) {
guard let menuItem = articleThemePopup.selectedItem else {
return

View File

@ -186,10 +186,16 @@
510C418624E5D1B4008226FD /* ExtensionFeedAddRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51B5C87A23F2317700032075 /* ExtensionFeedAddRequest.swift */; };
510C43F7243D035C009F70C3 /* ExtensionPoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510C43F6243D035C009F70C3 /* ExtensionPoint.swift */; };
510C43F8243D035C009F70C3 /* ExtensionPoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510C43F6243D035C009F70C3 /* ExtensionPoint.swift */; };
510FFAB326EEA22C00F32265 /* ArticleThemesTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510FFAB226EEA22C00F32265 /* ArticleThemesTableViewController.swift */; };
51102165233A7D6C0007A5F7 /* ArticleExtractorButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51102164233A7D6C0007A5F7 /* ArticleExtractorButton.swift */; };
51107746243BEE2500D97C8C /* ExtensionPointPreferencesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51107745243BEE2500D97C8C /* ExtensionPointPreferencesViewController.swift */; };
51107747243BEE2500D97C8C /* ExtensionPointPreferencesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51107745243BEE2500D97C8C /* ExtensionPointPreferencesViewController.swift */; };
5110C37D2373A8D100A9C04F /* InspectorIconHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5110C37C2373A8D100A9C04F /* InspectorIconHeaderView.swift */; };
5112435026EE629A002601D2 /* Sepia.nnwtheme in Resources */ = {isa = PBXBuildFile; fileRef = 5112434F26EE6291002601D2 /* Sepia.nnwtheme */; };
5112435126EE629B002601D2 /* Sepia.nnwtheme in Resources */ = {isa = PBXBuildFile; fileRef = 5112434F26EE6291002601D2 /* Sepia.nnwtheme */; };
5112435226EE629C002601D2 /* Sepia.nnwtheme in Resources */ = {isa = PBXBuildFile; fileRef = 5112434F26EE6291002601D2 /* Sepia.nnwtheme */; };
5112435326EE629F002601D2 /* Sepia.nnwtheme in Resources */ = {isa = PBXBuildFile; fileRef = 5112434F26EE6291002601D2 /* Sepia.nnwtheme */; };
5112435426EE629F002601D2 /* Sepia.nnwtheme in Resources */ = {isa = PBXBuildFile; fileRef = 5112434F26EE6291002601D2 /* Sepia.nnwtheme */; };
51126DA4225FDE2F00722696 /* RSImage-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51126DA3225FDE2F00722696 /* RSImage-Extensions.swift */; };
5117715524E1EA0F00A2A836 /* ArticleExtractorButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5117715424E1EA0F00A2A836 /* ArticleExtractorButton.swift */; };
5117715624E1EA0F00A2A836 /* ArticleExtractorButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5117715424E1EA0F00A2A836 /* ArticleExtractorButton.swift */; };
@ -595,6 +601,11 @@
51CE1C0923621EDA005548FC /* RefreshProgressView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 51CE1C0823621EDA005548FC /* RefreshProgressView.xib */; };
51CE1C0B23622007005548FC /* RefreshProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51CE1C0A23622006005548FC /* RefreshProgressView.swift */; };
51CE1C712367721A005548FC /* testURLsOfCurrentArticle.applescript in Sources */ = {isa = PBXBuildFile; fileRef = 84F9EADB213660A100CF2DE4 /* testURLsOfCurrentArticle.applescript */; };
51D0214626ED617100FF2E0F /* core.css in Resources */ = {isa = PBXBuildFile; fileRef = 51D0214526ED617100FF2E0F /* core.css */; };
51D0214726ED617100FF2E0F /* core.css in Resources */ = {isa = PBXBuildFile; fileRef = 51D0214526ED617100FF2E0F /* core.css */; };
51D0214826ED617100FF2E0F /* core.css in Resources */ = {isa = PBXBuildFile; fileRef = 51D0214526ED617100FF2E0F /* core.css */; };
51D0214926ED617100FF2E0F /* core.css in Resources */ = {isa = PBXBuildFile; fileRef = 51D0214526ED617100FF2E0F /* core.css */; };
51D0214A26ED617100FF2E0F /* core.css in Resources */ = {isa = PBXBuildFile; fileRef = 51D0214526ED617100FF2E0F /* core.css */; };
51D5948722668EFA00DFC836 /* MarkStatusCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84702AA31FA27AC0006B8943 /* MarkStatusCommand.swift */; };
51D6A5BC23199C85001C27D8 /* MasterTimelineDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51D6A5BB23199C85001C27D8 /* MasterTimelineDataSource.swift */; };
51D87EE12311D34700E63F03 /* ActivityType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51D87EE02311D34700E63F03 /* ActivityType.swift */; };
@ -1614,10 +1625,12 @@
510C416624E5CDE3008226FD /* ShareExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ShareExtension.entitlements; sourceTree = "<group>"; };
510C418724E5D2E3008226FD /* NetNewsWire_shareextension_target.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = NetNewsWire_shareextension_target.xcconfig; sourceTree = "<group>"; };
510C43F6243D035C009F70C3 /* ExtensionPoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionPoint.swift; sourceTree = "<group>"; };
510FFAB226EEA22C00F32265 /* ArticleThemesTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleThemesTableViewController.swift; sourceTree = "<group>"; };
51102164233A7D6C0007A5F7 /* ArticleExtractorButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleExtractorButton.swift; sourceTree = "<group>"; };
51107745243BEE2500D97C8C /* ExtensionPointPreferencesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionPointPreferencesViewController.swift; sourceTree = "<group>"; };
5110C37C2373A8D100A9C04F /* InspectorIconHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InspectorIconHeaderView.swift; sourceTree = "<group>"; };
51121AA12265430A00BC0EC1 /* NetNewsWire_iOSapp_target.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = NetNewsWire_iOSapp_target.xcconfig; sourceTree = "<group>"; };
5112434F26EE6291002601D2 /* Sepia.nnwtheme */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = Sepia.nnwtheme; sourceTree = "<group>"; };
51126DA3225FDE2F00722696 /* RSImage-Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RSImage-Extensions.swift"; sourceTree = "<group>"; };
5117715424E1EA0F00A2A836 /* ArticleExtractorButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleExtractorButton.swift; sourceTree = "<group>"; };
511B9805237DCAC90028BCAA /* UserInfoKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserInfoKey.swift; sourceTree = "<group>"; };
@ -1853,6 +1866,7 @@
51CD32C724D2E06C009ABAEF /* Secrets */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Secrets; sourceTree = "<group>"; };
51CE1C0823621EDA005548FC /* RefreshProgressView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = RefreshProgressView.xib; sourceTree = "<group>"; };
51CE1C0A23622006005548FC /* RefreshProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RefreshProgressView.swift; sourceTree = "<group>"; };
51D0214526ED617100FF2E0F /* core.css */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.css; path = core.css; sourceTree = "<group>"; };
51D6A5BB23199C85001C27D8 /* MasterTimelineDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MasterTimelineDataSource.swift; sourceTree = "<group>"; };
51D87EE02311D34700E63F03 /* ActivityType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityType.swift; sourceTree = "<group>"; };
51DC07972552083500A3F79F /* ArticleTextSize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleTextSize.swift; sourceTree = "<group>"; };
@ -2785,6 +2799,7 @@
51A16992235E10D600EB091F /* AddAccountViewController.swift */,
519ED47924482AEB007F8E94 /* EnableExtensionPointViewController.swift */,
519ED455244828C3007F8E94 /* AddExtensionPointViewController.swift */,
510FFAB226EEA22C00F32265 /* ArticleThemesTableViewController.swift */,
516244E2241E19F000B61C47 /* ColorPaletteTableViewController.swift */,
516A09382360A2AE00EAE89B /* SettingsComboTableViewCell.swift */,
516A091D23609A3600EAE89B /* SettingsComboTableViewCell.xib */,
@ -3083,6 +3098,7 @@
51C452A822650DA100C03939 /* Article Rendering */ = {
isa = PBXGroup;
children = (
51D0214526ED617100FF2E0F /* core.css */,
B27EEBDF244D15F2000932E6 /* stylesheet.css */,
848362FE2262A30E00DA1D35 /* template.html */,
517630032336215100E15FFF /* main.js */,
@ -3689,6 +3705,7 @@
84C9FC9C2262A1A900D921D6 /* Info.plist */,
84BB0F812333426400DED65E /* NetNewsWire.entitlements */,
51F805ED24284C1C0022C792 /* NetNewsWire-dev.entitlements */,
5112434F26EE6291002601D2 /* Sepia.nnwtheme */,
);
path = Resources;
sourceTree = "<group>";
@ -4378,6 +4395,7 @@
51E4995F24A875F300B667CB /* stylesheet.css in Resources */,
517B2EE824B3E8FE001AC46C /* main_multiplatform.js in Resources */,
51E4997324A8784300B667CB /* DefaultFeeds.opml in Resources */,
5112435326EE629F002601D2 /* Sepia.nnwtheme in Resources */,
51C0516224A77DF800194D5E /* Assets.xcassets in Resources */,
5177475F24B39AD500EB0F74 /* About.rtf in Resources */,
51E4996024A875F300B667CB /* template.html in Resources */,
@ -4385,6 +4403,7 @@
51E4995E24A875F300B667CB /* newsfoot.js in Resources */,
517B2EE424B3E8FE001AC46C /* blank.html in Resources */,
5177475C24B39AD500EB0F74 /* Credits.rtf in Resources */,
51D0214926ED617100FF2E0F /* core.css in Resources */,
51E4995D24A875F300B667CB /* main.js in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -4395,10 +4414,12 @@
files = (
517B2EE324B3E8FE001AC46C /* page.html in Resources */,
51E4996424A875F400B667CB /* stylesheet.css in Resources */,
5112435426EE629F002601D2 /* Sepia.nnwtheme in Resources */,
51E4997524A8784400B667CB /* DefaultFeeds.opml in Resources */,
51C0516324A77DF800194D5E /* Assets.xcassets in Resources */,
51E4996524A875F400B667CB /* template.html in Resources */,
517B2EE924B3E8FE001AC46C /* main_multiplatform.js in Resources */,
51D0214A26ED617100FF2E0F /* core.css in Resources */,
517B2EE524B3E8FE001AC46C /* blank.html in Resources */,
51E4996324A875F400B667CB /* newsfoot.js in Resources */,
51E4996224A875F400B667CB /* main.js in Resources */,
@ -4435,6 +4456,7 @@
65ED4051235DEF6C0081F399 /* TimelineKeyboardShortcuts.plist in Resources */,
65ED4052235DEF6C0081F399 /* template.html in Resources */,
65ED4054235DEF6C0081F399 /* Main.storyboard in Resources */,
5112435126EE629B002601D2 /* Sepia.nnwtheme in Resources */,
65ED4056235DEF6C0081F399 /* NetNewsWire.sdef in Resources */,
65ED4057235DEF6C0081F399 /* AccountsDetail.xib in Resources */,
65ED4058235DEF6C0081F399 /* main.js in Resources */,
@ -4445,6 +4467,7 @@
65ED405C235DEF6C0081F399 /* ImportOPMLSheet.xib in Resources */,
65ED405D235DEF6C0081F399 /* SidebarKeyboardShortcuts.plist in Resources */,
514A89A3244FD63F0085E65D /* AddTwitterFeedSheet.xib in Resources */,
51D0214726ED617100FF2E0F /* core.css in Resources */,
5103A9F5242258C600410853 /* AccountsAddCloudKit.xib in Resources */,
65ED405E235DEF6C0081F399 /* DefaultFeeds.opml in Resources */,
51333D3C2468615D00EB5C91 /* AddRedditFeedSheet.xib in Resources */,
@ -4485,6 +4508,7 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
5112435226EE629C002601D2 /* Sepia.nnwtheme in Resources */,
517630052336215100E15FFF /* main.js in Resources */,
5148F44B2336DB4700F8CD8B /* MasterTimelineTitleView.xib in Resources */,
511D43D0231FA62500FB1562 /* TimelineKeyboardShortcuts.plist in Resources */,
@ -4499,6 +4523,7 @@
516A09422361248000EAE89B /* Inspector.storyboard in Resources */,
1768140B2564BB8300D98635 /* NetNewsWire_iOSwidgetextension_target.xcconfig in Resources */,
5103A9B424216A4200410853 /* blank.html in Resources */,
51D0214826ED617100FF2E0F /* core.css in Resources */,
84C9FCA42262A1B800D921D6 /* LaunchScreenPhone.storyboard in Resources */,
51F85BEB22724CB600C787DC /* About.rtf in Resources */,
516A093B2360A4A000EAE89B /* SettingsTableViewCell.xib in Resources */,
@ -4532,9 +4557,11 @@
848363082262A3DD00DA1D35 /* Main.storyboard in Resources */,
84C9FC8F22629E8F00D921D6 /* NetNewsWire.sdef in Resources */,
84C9FC7D22629E1200D921D6 /* AccountsDetail.xib in Resources */,
5112435026EE629A002601D2 /* Sepia.nnwtheme in Resources */,
517630042336215100E15FFF /* main.js in Resources */,
65ED40A0235DEFF00081F399 /* container-migration.plist in Resources */,
5144EA362279FC3D00D19003 /* AccountsAddLocal.xib in Resources */,
51D0214626ED617100FF2E0F /* core.css in Resources */,
5142194B2353C1CF00E07E2C /* main_mac.js in Resources */,
84C9FC8C22629E8F00D921D6 /* KeyboardShortcuts.html in Resources */,
B27EEBF9244D15F3000932E6 /* stylesheet.css in Resources */,
@ -5601,6 +5628,7 @@
51627A6723861DA3007B3B4B /* MasterFeedViewController+Drag.swift in Sources */,
51FFF0C4235EE8E5002762AA /* VibrantButton.swift in Sources */,
51C45259226508D300C03939 /* AppDefaults.swift in Sources */,
510FFAB326EEA22C00F32265 /* ArticleThemesTableViewController.swift in Sources */,
51CE1C0B23622007005548FC /* RefreshProgressView.swift in Sources */,
511D4419231FC02D00FB1562 /* KeyboardManager.swift in Sources */,
51A1699D235E10D700EB091F /* SettingsViewController.swift in Sources */,

View File

@ -0,0 +1,137 @@
/* This is the activity indicator (currently iOS only) that is used while image zooming */
.activityIndicatorWrap {
position: relative;
}
.activityIndicator {
z-index: 1;
width: 64px;
height: 64px;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
/* see removeWpSmiley; this rule is kept in case a wp-smiley is encountered without alt text */
.wp-smiley {
height: 1em;
max-height: 1em;
}
/* Hide the external link at the bottom of Daring Fireball posts */
.x-netnewswire-hide {
display: none;
}
/*Block ads and junk*/
iframe[src*="feedads"],
iframe[src*="doubleclick"],
iframe[src*="plusone.google"] {
display: none !important;
}
a[href*=".ads."],
a[href*="feedads"],
a[href*="doubleclick"],
a[href*="//ads."],
a[href*="api.tweetmeme"],
a[href*="delicious.com/post?"],
a[href*="digg.com/submit?"],
a[href*="google.com/bookmarks/mark?"],
a[href*="posterous.com/share?"],
a[href*="tumblr.com/share?"],
a[href*="linkedin.com/shareArticle?"],
a[href*="facebook.com/share.php?"],
a[href*="http://twitter.com/home?"],
a[href*="addtoany.com/share_save"] {
display: none !important;
}
img[src*=".ads."],
img[src*="//ads."],
img[src*="doubleclick"],
img[src*="feedads"],
img[src*="feedburner"],
img[src*="feedblitz"],
img[src*="share-buttons"] {
display: none !important;
}
/* Newsfoot specific styles. Structural styles come first, theme styles second */
.newsfoot-footnote-container {
position: relative;
display: inline-block;
z-index: 9999;
}
.newsfoot-footnote-popover {
position: absolute;
display: block;
padding: 0em 1em;
margin: 1em;
top: 0.75em;
max-width: none;
border-radius: 0.3em;
box-sizing: border-box;
}
.newsfoot-footnote-popover {
left: calc(-1 * (50vw - 1em));
right: calc(-1 * (50vw - 1em));
}
.newsfoot-footnote-popover-arrow {
content: '';
display: block;
width: 1em;
position: absolute;
top: -0.5em;
left: calc(50% - 0.5em);
height: 1em !important;
transform: rotate(45deg);
z-index:0;
}
.newsfoot-footnote-popover-inner {
border-radius: calc(0.3em - 1px);
padding: 1em;
position: relative;
z-index: 1;
}
.newsfoot-footnote-popover-inner :first-child {
margin-top: 0;
}
.newsfoot-footnote-popover-inner :last-child {
margin-bottom: 0;
}
.newsfoot-footnote-popover .reversefootnote,
.newsfoot-footnote-popover .footnoteBackLink,
.newsfoot-footnote-popover .footnote-return,
.newsfoot-footnote-popover a[href*='#fn'] {
display: none;
}
sup[id^='fn'] {
vertical-align: baseline;
}
a.footnote {
display: inline-block;
text-decoration: none;
padding: 0.05em 0.75em;
border-radius: 1em;
min-width: 1em;
text-align: center;
font-size: 0.8em;
line-height: 1em;
position:relative;
top: -0.1em;
}

View File

@ -1,3 +1,5 @@
/* Shared iOS and macOS CSS rules. Platform specific rules are at the bottom of this file. */
body {
margin-left: auto;
margin-right: auto;
@ -9,12 +11,15 @@ body {
a {
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
.feedlink {
font-weight: bold;
}
.headerTable {
width: 100%;
height: 68px;
@ -56,9 +61,11 @@ body .headerTable {
border-bottom: 1px solid var(--header-table-border-color);
color: var(--header-color);
}
body .header {
color: var(--header-color);
}
body .header a:link, .header a:visited {
color: var(--header-color);
}
@ -82,9 +89,11 @@ body > .systemMessage {
.feedIcon {
border-radius: 4px;
}
.rightAlign {
text-align: end;
}
.leftAlign {
text-align: start;
}
@ -160,6 +169,7 @@ pre code {
.nnw-overflow {
overflow-x: auto;
}
/*
Instead of the last-child bits, border-collapse: collapse
could have been used. However, then the inter-cell borders
@ -171,10 +181,12 @@ pre code {
border: 1px solid var(--secondary-accent-color);
font-size: inherit;
}
.nnw-overflow table table {
margin-bottom: 0;
border: none;
}
.nnw-overflow td, .nnw-overflow th {
-webkit-hyphens: none;
word-break: normal;
@ -191,10 +203,12 @@ pre code {
.nnw-overflow :matches(thead, tbody, tfoot):last-child > tr:last-child :matches(td, th) {
border-bottom: none;
}
.nnw-overflow td pre {
border: none;
padding: 0;
}
.nnw-overflow table[border="0"] {
border-width: 0;
}
@ -267,19 +281,6 @@ blockquote {
border-top: 1px solid var(--header-table-border-color);
}
/* Hide the external link at the bottom of Daring Fireball posts */
.x-netnewswire-hide {
display: none;
}
/* see removeWpSmiley; this rule is kept in case a wp-smiley is encountered without alt text */
.wp-smiley {
height: 1em;
max-height: 1em;
}
/* Twitter */
.twitterAvatar {
@ -304,126 +305,23 @@ blockquote {
font-size: 66%;
}
/*Block ads and junk*/
iframe[src*="feedads"],
iframe[src*="doubleclick"],
iframe[src*="plusone.google"] {
display: none !important;
}
a[href*=".ads."],
a[href*="feedads"],
a[href*="doubleclick"],
a[href*="//ads."],
a[href*="api.tweetmeme"],
a[href*="delicious.com/post?"],
a[href*="digg.com/submit?"],
a[href*="google.com/bookmarks/mark?"],
a[href*="posterous.com/share?"],
a[href*="tumblr.com/share?"],
a[href*="linkedin.com/shareArticle?"],
a[href*="facebook.com/share.php?"],
a[href*="http://twitter.com/home?"],
a[href*="addtoany.com/share_save"] {
display: none !important;
}
img[src*=".ads."],
img[src*="//ads."],
img[src*="doubleclick"],
img[src*="feedads"],
img[src*="feedburner"],
img[src*="feedblitz"],
img[src*="share-buttons"] {
display: none !important;
}
/* Newsfoot specific styles. Structural styles come first, theme styles second */
.newsfoot-footnote-container {
position: relative;
display: inline-block;
z-index: 9999;
}
.newsfoot-footnote-popover {
position: absolute;
display: block;
padding: 0em 1em;
margin: 1em;
top: 0.75em;
max-width: none;
border-radius: 0.3em;
box-sizing: border-box;
}
.newsfoot-footnote-popover {
left: calc(-1 * (50vw - 1em));
right: calc(-1 * (50vw - 1em));
}
.newsfoot-footnote-popover-arrow {
content: '';
display: block;
width: 1em;
position: absolute;
top: -0.5em;
left: calc(50% - 0.5em);
height: 1em !important;
transform: rotate(45deg);
z-index:0;
}
.newsfoot-footnote-popover-inner {
border-radius: calc(0.3em - 1px);
padding: 1em;
position: relative;
z-index: 1;
}
.newsfoot-footnote-popover-inner :first-child {
margin-top: 0;
}
.newsfoot-footnote-popover-inner :last-child {
margin-bottom: 0;
}
.newsfoot-footnote-popover .reversefootnote,
.newsfoot-footnote-popover .footnoteBackLink,
.newsfoot-footnote-popover .footnote-return,
.newsfoot-footnote-popover a[href*='#fn'] {
display: none;
}
sup[id^='fn'] {
vertical-align: baseline;
}
a.footnote {
display: inline-block;
text-decoration: none;
padding: 0.05em 0.75em;
border-radius: 1em;
min-width: 1em;
text-align: center;
font-size: 0.8em;
line-height: 1em;
position:relative;
top: -0.1em;
}
/* light / default */
/* Newsfoot theme for light mode (default) */
.newsfoot-footnote-popover {
background: #ccc;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.5), 0 3px 6px rgba(0, 0, 0, 0.25);
color: black;
padding: 1px;
}
.newsfoot-footnote-popover-arrow {
background: #fafafa;
border: 1px solid #ccc;
}
.newsfoot-footnote-popover-inner {
background: #fafafa;
}
body a.footnote,
body a.footnote:visited,
.newsfoot-footnote-popover + a.footnote:hover {
@ -431,25 +329,29 @@ body a.footnote:visited,
color: white;
transition: background-color 200ms ease-out;
}
a.footnote:hover,
.newsfoot-footnote-popover + a.footnote {
background: #666;
transition: background-color 200ms ease-out;
}
/* dark */
/* Newsfoot theme for dark mode */
@media screen and (prefers-color-scheme: dark) {
.newsfoot-footnote-popover {
background: #444;
color: rgb(224, 224, 224);
}
.newsfoot-footnote-popover-arrow {
background: #242424;
border: 1px solid #444;
}
.newsfoot-footnote-popover-inner {
background: #242424;
}
body a.footnote,
body a.footnote:visited,
.newsfoot-footnote-popover + a.footnote:hover {
@ -457,11 +359,13 @@ a.footnote:hover,
color: white;
transition: background-color 200ms ease-out;
}
a.footnote:hover,
.newsfoot-footnote-popover + a.footnote {
background: #666;
transition: background-color 200ms ease-out;
}
}
/* iOS Specific */
@ -481,6 +385,7 @@ a.footnote:hover,
:root {
color-scheme: light dark;
font: -apple-system-body;
/* The font-size is replaced at runtime by the dynamic type size */
font-size: [[font-size]]px;
--primary-accent-color: #086AEE;
--secondary-accent-color: #086AEE;
@ -499,18 +404,16 @@ a.footnote:hover,
body a, body a:visited {
color: var(--secondary-accent-color);
}
body .header {
font: -apple-system-body;
font-size: [[font-size]]px;
}
body .header a:link, body .header a:visited {
color: var(--primary-accent-color);
}
.avatar img {
border-radius: 4px;
}
pre {
border: 1px solid var(--secondary-accent-color);
padding: 5px;
@ -520,20 +423,6 @@ a.footnote:hover,
border: 1px solid var(--secondary-accent-color);
}
.activityIndicatorWrap {
position: relative;
}
.activityIndicator {
z-index: 1;
width: 64px;
height: 64px;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
}
/* macOS Specific */

View File

@ -43,30 +43,30 @@ struct ArticleTheme: Equatable {
self.path = nil;
self.info = ["CreatorHomePage": "https://netnewswire.com/", "CreatorName": "Ranchero Software", "Version": "1.0"]
let cssPath = Bundle.main.path(forResource: "stylesheet", ofType: "css")!
css = Self.stringAtPath(cssPath)
let corePath = Bundle.main.path(forResource: "core", ofType: "css")!
let stylesheetPath = Bundle.main.path(forResource: "stylesheet", ofType: "css")!
css = Self.stringAtPath(corePath)! + "\n" + Self.stringAtPath(stylesheetPath)!
let templatePath = Bundle.main.path(forResource: "template", ofType: "html")!
template = Self.stringAtPath(templatePath)
template = Self.stringAtPath(templatePath)!
}
init(path: String) {
self.path = path
if FileManager.default.isFolder(atPath: path) {
let infoPath = (path as NSString).appendingPathComponent("Info.plist")
self.info = NSDictionary(contentsOfFile: infoPath)
let infoPath = (path as NSString).appendingPathComponent("Info.plist")
self.info = NSDictionary(contentsOfFile: infoPath)
let cssPath = (path as NSString).appendingPathComponent("stylesheet.css")
self.css = Self.stringAtPath(cssPath)
let templatePath = (path as NSString).appendingPathComponent("template.html")
self.template = Self.stringAtPath(templatePath)
let corePath = Bundle.main.path(forResource: "core", ofType: "css")!
let stylesheetPath = (path as NSString).appendingPathComponent("stylesheet.css")
if let stylesheetCSS = Self.stringAtPath(stylesheetPath) {
self.css = Self.stringAtPath(corePath)! + "\n" + stylesheetCSS
} else {
self.css = Self.stringAtPath(path)
self.template = nil
self.info = nil
self.css = nil
}
let templatePath = (path as NSString).appendingPathComponent("template.html")
self.template = Self.stringAtPath(templatePath)
}
static func stringAtPath(_ f: String) -> String? {

View File

@ -6,12 +6,7 @@
// Copyright © 2015 Ranchero Software, LLC. All rights reserved.
//
#if os(macOS)
import AppKit
#else
import UIKit
#endif
import Foundation
import RSCore
public extension Notification.Name {
@ -19,10 +14,15 @@ public extension Notification.Name {
static let CurrentArticleThemeDidChangeNotification = Notification.Name("CurrentArticleThemeDidChangeNotification")
}
final class ArticleThemesManager {
final class ArticleThemesManager: NSObject, NSFilePresenter {
static var shared: ArticleThemesManager!
private let folderPath: String
public let folderPath: String
lazy var presentedItemOperationQueue = OperationQueue.main
var presentedItemURL: URL? {
return URL(fileURLWithPath: folderPath)
}
var currentThemeName: String {
get {
@ -50,28 +50,45 @@ final class ArticleThemesManager {
init(folderPath: String) {
self.folderPath = folderPath
self.currentTheme = ArticleTheme.defaultTheme
super.init()
do {
try FileManager.default.createDirectory(atPath: folderPath, withIntermediateDirectories: true, attributes: nil)
} catch {
assertionFailure("Could not create folder for Themes.")
abort()
}
currentTheme = ArticleTheme.defaultTheme
let themeFilenames = Bundle.main.paths(forResourcesOfType: ArticleTheme.nnwThemeSuffix, inDirectory: nil)
let installedStyleSheets = readInstalledStyleSheets() ?? [String: Date]()
for themeFilename in themeFilenames {
let themeName = ArticleTheme.themeNameForPath(themeFilename)
if !installedStyleSheets.keys.contains(themeName) {
try? importTheme(filename: themeFilename)
}
}
updateThemeNames()
updateCurrentTheme()
#if os(macOS)
NotificationCenter.default.addObserver(self, selector: #selector(applicationDidBecomeActive(_:)), name: NSApplication.didBecomeActiveNotification, object: nil)
#else
NotificationCenter.default.addObserver(self, selector: #selector(applicationDidBecomeActive(_:)), name: UIApplication.didBecomeActiveNotification, object: nil)
#endif
NSFileCoordinator.addFilePresenter(self)
}
func presentedSubitemDidChange(at url: URL) {
updateThemeNames()
updateCurrentTheme()
}
// MARK: API
func themeExists(filename: String) -> Bool {
let filenameLastPathComponent = (filename as NSString).lastPathComponent
let toFilename = (folderPath as NSString).appendingPathComponent(filenameLastPathComponent)
return FileManager.default.fileExists(atPath: toFilename)
}
func importTheme(filename: String) throws {
let filenameLastPathComponent = (filename as NSString).lastPathComponent
let toFilename = (folderPath as NSString).appendingPathComponent(filenameLastPathComponent)
@ -81,15 +98,17 @@ final class ArticleThemesManager {
}
try FileManager.default.copyItem(atPath: filename, toPath: toFilename)
updateThemeNames()
let themeName = ArticleTheme.themeNameForPath(filename)
var installedStyleSheets = readInstalledStyleSheets() ?? [String: Date]()
installedStyleSheets[themeName] = Date()
writeInstalledStyleSheets(installedStyleSheets)
}
// MARK: Notifications
@objc dynamic func applicationDidBecomeActive(_ note: Notification) {
updateThemeNames()
updateCurrentTheme()
func deleteTheme(themeName: String) {
if let filename = pathForThemeName(themeName, folder: folderPath) {
try? FileManager.default.removeItem(atPath: filename)
}
}
}
@ -154,4 +173,14 @@ private extension ArticleThemesManager {
return nil
}
func readInstalledStyleSheets() -> [String: Date]? {
let filePath = (folderPath as NSString).appendingPathComponent("InstalledStyleSheets.plist")
return NSDictionary(contentsOfFile: filePath) as? [String: Date]
}
func writeInstalledStyleSheets(_ dict: [String: Date]) {
let filePath = (folderPath as NSString).appendingPathComponent("InstalledStyleSheets.plist")
(dict as NSDictionary).write(toFile: filePath, atomically: true)
}
}

View File

@ -70,7 +70,8 @@ class WebViewController: UIViewController {
NotificationCenter.default.addObserver(self, selector: #selector(webFeedIconDidBecomeAvailable(_:)), name: .WebFeedIconDidBecomeAvailable, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(avatarDidBecomeAvailable(_:)), name: .AvatarDidBecomeAvailable, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(faviconDidBecomeAvailable(_:)), name: .FaviconDidBecomeAvailable, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(currentArticleThemeDidChangeNotification(_:)), name: .CurrentArticleThemeDidChangeNotification, object: nil)
// Configure the tap zones
configureTopShowBarsView()
configureBottomShowBarsView()
@ -100,6 +101,10 @@ class WebViewController: UIViewController {
reloadArticleImage()
}
@objc func currentArticleThemeDidChangeNotification(_ note: Notification) {
loadWebView()
}
// MARK: Actions
@objc func showBars(_ sender: Any) {

View File

@ -377,6 +377,17 @@ class MasterTimelineViewController: UITableViewController, UndoableCommandRunner
menuElements.append(UIMenu(title: "", options: .displayInline, children: secondaryActions))
}
var copyActions = [UIAction]()
if let action = self.copyArticleURLAction(article) {
copyActions.append(action)
}
if let action = self.copyExternalURLAction(article) {
copyActions.append(action)
}
if !copyActions.isEmpty {
menuElements.append(UIMenu(title: "", options: .displayInline, children: copyActions))
}
if let action = self.openInBrowserAction(article) {
menuElements.append(UIMenu(title: "", options: .displayInline, children: [action]))
}
@ -902,6 +913,25 @@ private extension MasterTimelineViewController {
}
return action
}
func copyArticleURLAction(_ article: Article) -> UIAction? {
guard let url = article.preferredURL else { return nil }
let title = NSLocalizedString("Copy Article URL", comment: "Copy Article URL")
let action = UIAction(title: title, image: AppAssets.copyImage) { action in
UIPasteboard.general.url = url
}
return action
}
func copyExternalURLAction(_ article: Article) -> UIAction? {
guard let externalURL = article.externalURL, externalURL != article.preferredLink, let url = URL(string: externalURL) else { return nil }
let title = NSLocalizedString("Copy External URL", comment: "Copy External URL")
let action = UIAction(title: title, image: AppAssets.copyImage) { action in
UIPasteboard.general.url = url
}
return action
}
func openInBrowserAction(_ article: Article) -> UIAction? {
guard let _ = article.preferredURL else { return nil }

View File

@ -212,6 +212,8 @@
<true/>
</dict>
</array>
<key>LSSupportsOpeningDocumentsInPlace</key>
<false/>
<key>UserAgent</key>
<string>NetNewsWire (RSS Reader; https://netnewswire.com/)</string>
</dict>

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Name</key>
<string>Sepia</string>
<key>ThemeIdentifer</key>
<string>com.netnewswire.themes.sepia</string>
<key>CreatorHomePage</key>
<string>http://netnewswire.com/</string>
<key>CreatorName</key>
<string>Ranchero Software</string>
<key>Version</key>
<integer>1</integer>
</dict>
</plist>

View File

@ -0,0 +1,405 @@
/* Shared iOS and macOS CSS rules. Platform specific rules are at the bottom of this file. */
body {
margin-left: auto;
margin-right: auto;
word-wrap: break-word;
max-width: 44em;
background-color: #FBF0D9;
color: #704214;
}
a {
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
.feedlink {
font-weight: bold;
}
.headerTable {
width: 100%;
height: 68px;
}
.systemMessage {
position: absolute;
top: 45%;
left: 50%;
transform: translateX(-55%) translateY(-50%);
-webkit-user-select: none;
cursor: default;
}
:root {
--header-table-border-color: rgba(0, 0, 0, 0.3);
--header-color: rgba(0, 0, 0, 0.5);
--body-code-color: #704214;
--system-message-color: #704214;
--feedlink-color: rgba(255, 0, 0, 0.6);
--article-title-color: #704214;
--article-date-color: rgba(0, 0, 0, 0.5);
--table-cell-border-color: lightgray;
--primary-accent-color: #43350E;
--secondary-accent-color: #43350E;
--block-quote-border-color: rgba(0, 0, 0, 0.3);
}
body a, body a:visited {
text-decoration: underline;
color: var(--secondary-accent-color);
}
body .headerTable {
border-bottom: 1px solid var(--header-table-border-color);
color: var(--header-color);
}
body .header {
color: var(--header-color);
}
body .header a:link, .header a:visited {
color: var(--header-color);
}
body code, body pre {
color: var(--body-code-color);
}
body > .systemMessage {
color: var(--system-message-color);
}
.feedlink a:link, .feedlink a:visited {
color: var(--feedlink-color);
}
.avatar img {
border-radius: 4px;
}
.feedIcon {
border-radius: 4px;
}
.rightAlign {
text-align: end;
}
.leftAlign {
text-align: start;
}
.articleTitle a:link, .articleTitle a:visited {
color: var(--article-title-color);
margin-top: 26px;
}
.articleDateline {
margin-bottom: 5px;
font-weight: bold;
}
.articleDateline a:link, .articleDateline a:visited {
color: var(--article-date-color);
}
.articleDatelineTitle {
margin-bottom: 5px;
font-weight: bold;
}
.articleDatelineTitle a:link, .articleDatelineTitle a:visited {
color: var(--article-title-color);
}
.externalLink {
margin-bottom: 5px;
font-style: italic;
width: 100%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.articleBody {
margin-top: 20px;
line-height: 1.6em;
}
h1 {
line-height: 1.15em;
font-weight: bold;
padding-bottom: 0;
margin-bottom: 5px;
}
pre {
max-width: 100%;
margin: 0;
overflow: auto;
overflow-y: hidden;
word-wrap: normal;
word-break: normal;
}
pre {
line-height: 1.4286em;
}
code, pre {
font-family: "SF Mono", Menlo, "Courier New", Courier, monospace;
font-size: 1em;
-webkit-hyphens: none;
}
pre code {
letter-spacing: -.027em;
font-size: 0.9375em;
}
.nnw-overflow {
overflow-x: auto;
}
/*
Instead of the last-child bits, border-collapse: collapse
could have been used. However, then the inter-cell borders
overlap the table border, which looks bad.
*/
.nnw-overflow table {
margin-bottom: 1px;
border-spacing: 0;
border: 1px solid var(--secondary-accent-color);
font-size: inherit;
}
.nnw-overflow table table {
margin-bottom: 0;
border: none;
}
.nnw-overflow td, .nnw-overflow th {
-webkit-hyphens: none;
word-break: normal;
border: 1px solid var(--table-cell-border-color);
border-top: none;
border-left: none;
padding: 5px;
}
.nnw-overflow tr :matches(td, th):last-child {
border-right: none;
}
.nnw-overflow :matches(thead, tbody, tfoot):last-child > tr:last-child :matches(td, th) {
border-bottom: none;
}
.nnw-overflow td pre {
border: none;
padding: 0;
}
.nnw-overflow table[border="0"] {
border-width: 0;
}
img, figure, video, div, object {
max-width: 100%;
height: auto !important;
margin: 0 auto;
}
iframe {
max-width: 100%;
margin: 0 auto;
}
iframe.nnw-constrained {
max-height: 50vw;
}
figure {
margin-bottom: 1em;
margin-top: 1em;
}
figcaption {
font-size: 14px;
line-height: 1.3em;
}
sup {
vertical-align: top;
position: relative;
bottom: 0.2rem;
}
sub {
vertical-align: bottom;
position: relative;
top: 0.2rem;
}
hr {
border: 1.5px solid var(--table-cell-border-color);
}
.iframeWrap {
position: relative;
display: block;
padding-top: 56.25%;
}
.iframeWrap iframe {
position: absolute;
top: 0;
left: 0;
height: 100% !important;
width: 100% !important;
}
blockquote {
margin-inline-start: 0;
margin-inline-end: 0;
padding-inline-start: 15px;
border-inline-start: 3px solid var(--block-quote-border-color);
}
/* Feed Specific */
.feedbin--article-wrap {
border-top: 1px solid var(--header-table-border-color);
}
/* Twitter */
.twitterAvatar {
vertical-align: middle;
border-radius: 4px;
height: 1.7em;
width: 1.7em;
}
.twitterUsername {
line-height: 1.2;
margin-left: 4px;
display: inline-block;
vertical-align: middle;
}
.twitterScreenName {
font-size: 66%;
}
.twitterTimestamp {
font-size: 66%;
}
/* Newsfoot theme for light mode (default) */
.newsfoot-footnote-popover {
background: #ccc;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.5), 0 3px 6px rgba(0, 0, 0, 0.25);
color: #704214;
padding: 1px;
}
.newsfoot-footnote-popover-arrow {
background: #FBF0D9;
border: 1px solid #ccc;
}
.newsfoot-footnote-popover-inner {
background: #FBF0D9;
}
body a.footnote,
body a.footnote:visited,
.newsfoot-footnote-popover + a.footnote:hover {
background: #aaa;
color: white;
transition: background-color 200ms ease-out;
}
a.footnote:hover,
.newsfoot-footnote-popover + a.footnote {
background: #666;
transition: background-color 200ms ease-out;
}
/* iOS Specific */
@supports (-webkit-touch-callout: none) {
body {
margin-top: 3px;
margin-bottom: 20px;
padding-left: 20px;
padding-right: 20px;
word-break: break-word;
-webkit-hyphens: auto;
-webkit-text-size-adjust: none;
font: Georgia;
font-size: [[font-size]]px;
}
pre {
border: 1px solid var(--secondary-accent-color);
padding: 5px;
}
.nnw-overflow table {
border: 1px solid var(--secondary-accent-color);
}
}
/* macOS Specific */
@supports not (-webkit-touch-callout: none) {
body {
margin-top: 20px;
margin-bottom: 64px;
padding-left: 48px;
padding-right: 48px;
font-family: Georgia;
}
.smallText {
font-size: 14px;
}
.mediumText {
font-size: 16px;
}
.largeText {
font-size: 18px;
}
.xlargeText {
font-size: 20px;
}
.xxlargeText {
font-size: 22px;
}
pre {
border: 1px solid var(--primary-accent-color);
padding: 10px;
}
.nnw-overflow table {
border: 1px solid var(--primary-accent-color);
}
}

View File

@ -0,0 +1,15 @@
<header class="headerContainer">
<table cellpadding=0 cellspacing=0 border=0 class="headerTable">
<tr>
<td class="header leftAlign"><span class="feedlink">[[feedlink]]</span><br />[[byline]]</td>
[[avatars]]
</tr>
</table>
</header>
<article>
<div class="articleTitle"><h1>[[title]]</h1></div>
<div class="[[dateline_style]]">[[date_medium]]</div>
<div class="externalLink">[[external_link]]</div>
<div id="bodyContainer" class="articleBody [[text_size_class]]">[[body]]</div>
</article>

View File

@ -156,6 +156,21 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
private var exceptionArticleFetcher: ArticleFetcher?
private(set) var timelineFeed: Feed?
// We have to defer the selecting of the feed and article due to a behavior (bug?) in iOS 15.
// iOS 15 will crash if you are in landscape on an iPad and are restoring article state. We
// have no idea why this is, but it happens when you do a select on a UITableView right before
// doing a diffable datasource apply.
//
// Steps to recreate:
//
// * Try to relaunch the app in the sim.
// * Press the Stop button in Xcode
// * Wait for all the app suspension activities to complete (widget data, etc)
// * Once the article has loaded, navigate to the iPad home screen
// * While in landscape, select a feed and then select an article
// * Install a fresh build of NNW to an iPad simulator (11 or 12.9' will do) running iPadOS 15
private var deferredFeedAndArticleSelect: (feedIndexPath: IndexPath, articleID: String)?
var timelineMiddleIndexPath: IndexPath?
private(set) var showFeedNames = ShowFeedName.none
@ -437,6 +452,12 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
}
rebuildBackingStores(initialLoad: true)
treeControllerDelegate.resetFilterExceptions()
if let (feedIndexPath, articleID) = deferredFeedAndArticleSelect {
selectFeed(indexPath: feedIndexPath) {
self.selectArticleInCurrentFeed(articleID)
}
}
}
@objc func unreadCountDidChange(_ note: Notification) {
@ -1261,6 +1282,72 @@ class SceneCoordinator: NSObject, UndoableCommandRunner, UnreadCountProvider {
self.selectArticle(article)
}
}
func importTheme(filename: String) {
let theme = ArticleTheme(path: filename)
let localizedTitleText = NSLocalizedString("Install theme “%@” by %@?", comment: "Theme message text")
let title = NSString.localizedStringWithFormat(localizedTitleText as NSString, theme.name, theme.creatorName) as String
let localizedMessageText = NSLocalizedString("Author's Website:\n%@", comment: "Authors website")
let message = NSString.localizedStringWithFormat(localizedMessageText as NSString, theme.creatorHomePage) as String
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
let cancelTitle = NSLocalizedString("Cancel", comment: "Cancel")
alertController.addAction(UIAlertAction(title: cancelTitle, style: .cancel))
if let url = URL(string: theme.creatorHomePage) {
let visitSiteTitle = NSLocalizedString("Show Website", comment: "Show Website")
let visitSiteAction = UIAlertAction(title: visitSiteTitle, style: .default) { [weak self] action in
UIApplication.shared.open(url)
self?.importTheme(filename: filename)
}
alertController.addAction(visitSiteAction)
}
func importTheme() {
do {
try ArticleThemesManager.shared.importTheme(filename: filename)
confirmImportSuccess(themeName: theme.name)
} catch {
rootSplitViewController.presentError(error)
}
}
let installThemeTitle = NSLocalizedString("Install Theme", comment: "Install Theme")
let installThemeAction = UIAlertAction(title: installThemeTitle, style: .default) { [weak self] action in
if ArticleThemesManager.shared.themeExists(filename: filename) {
let title = NSLocalizedString("Duplicate Theme", comment: "Duplicate Theme")
let localizedMessageText = NSLocalizedString("The theme “%@” already exists. Overwrite it?", comment: "Overwrite theme")
let message = NSString.localizedStringWithFormat(localizedMessageText as NSString, theme.name) as String
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
let cancelTitle = NSLocalizedString("Cancel", comment: "Cancel")
alertController.addAction(UIAlertAction(title: cancelTitle, style: .cancel))
let overwriteAction = UIAlertAction(title: NSLocalizedString("Overwrite", comment: "Overwrite"), style: .default) { action in
importTheme()
}
alertController.addAction(overwriteAction)
alertController.preferredAction = overwriteAction
self?.rootSplitViewController.present(alertController, animated: true)
} else {
importTheme()
}
}
alertController.addAction(installThemeAction)
alertController.preferredAction = installThemeAction
rootSplitViewController.present(alertController, animated: true)
}
}
// MARK: UISplitViewControllerDelegate
@ -2269,12 +2356,24 @@ private extension SceneCoordinator {
func selectFeedAndArticle(feedNode: Node, articleID: String) -> Bool {
if let feedIndexPath = indexPathFor(feedNode) {
selectFeed(indexPath: feedIndexPath) {
self.selectArticleInCurrentFeed(articleID)
}
deferredFeedAndArticleSelect = (feedIndexPath, articleID)
return true
}
return false
}
func confirmImportSuccess(themeName: String) {
let title = NSLocalizedString("Theme installed", comment: "Theme installed")
let localizedMessageText = NSLocalizedString("The theme “%@” has been installed.", comment: "Theme installed")
let message = NSString.localizedStringWithFormat(localizedMessageText as NSString, themeName) as String
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
let doneTitle = NSLocalizedString("Done", comment: "Done")
alertController.addAction(UIAlertAction(title: doneTitle, style: .default))
rootSplitViewController.present(alertController, animated: true)
}
}

View File

@ -162,6 +162,11 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
}
}
let filename = context.url.standardizedFileURL.path
if filename.hasSuffix(ArticleTheme.nnwThemeSuffix) {
self.coordinator.importTheme(filename: filename)
}
}
}

View File

@ -0,0 +1,94 @@
//
// ArticleThemesTableViewController.swift
// NetNewsWire-iOS
//
// Created by Maurice Parker on 9/12/21.
// Copyright © 2021 Ranchero Software. All rights reserved.
//
import Foundation
import UIKit
class ArticleThemesTableViewController: UITableViewController {
override func viewDidLoad() {
NotificationCenter.default.addObserver(self, selector: #selector(articleThemeNamesDidChangeNotification(_:)), name: .ArticleThemeNamesDidChangeNotification, object: nil)
}
@objc func articleThemeNamesDidChangeNotification(_ note: Notification) {
tableView.reloadData()
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return ArticleThemesManager.shared.themeNames.count + 1
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
let themeName: String
if indexPath.row == 0 {
themeName = ArticleTheme.defaultTheme.name
} else {
themeName = ArticleThemesManager.shared.themeNames[indexPath.row - 1]
}
cell.textLabel?.text = themeName
if themeName == ArticleThemesManager.shared.currentTheme.name {
cell.accessoryType = .checkmark
} else {
cell.accessoryType = .none
}
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
guard let cell = tableView.cellForRow(at: indexPath), let themeName = cell.textLabel?.text else { return }
ArticleThemesManager.shared.currentThemeName = themeName
navigationController?.popViewController(animated: true)
}
override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
guard indexPath.row != 0,
let cell = tableView.cellForRow(at: indexPath),
let themeName = cell.textLabel?.text else { return nil }
let deleteTitle = NSLocalizedString("Delete", comment: "Delete")
let deleteAction = UIContextualAction(style: .normal, title: deleteTitle) { [weak self] (action, view, completion) in
let title = NSLocalizedString("Delete Theme?", comment: "Delete Theme")
let localizedMessageText = NSLocalizedString("Are you sure you want to delete the theme “%@”?.", comment: "Delete Theme Message")
let message = NSString.localizedStringWithFormat(localizedMessageText as NSString, themeName) as String
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
let cancelTitle = NSLocalizedString("Cancel", comment: "Cancel")
let cancelAction = UIAlertAction(title: cancelTitle, style: .cancel) { action in
completion(true)
}
alertController.addAction(cancelAction)
let deleteTitle = NSLocalizedString("Delete", comment: "Delete")
let deleteAction = UIAlertAction(title: deleteTitle, style: .destructive) { action in
ArticleThemesManager.shared.deleteTheme(themeName: themeName)
completion(true)
}
alertController.addAction(deleteAction)
self?.present(alertController, animated: true)
}
deleteAction.image = AppAssets.trashImage
deleteAction.backgroundColor = UIColor.systemRed
return UISwipeActionsConfiguration(actions: [deleteAction])
}
}

View File

@ -264,9 +264,41 @@
</tableViewSection>
<tableViewSection headerTitle="Articles" id="TRr-Ew-IvU">
<cells>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" id="SXs-NQ-y3U" customClass="VibrantTableViewCell" customModule="NetNewsWire" customModuleProvider="target">
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" id="WFP-zj-Pve" customClass="VibrantBasicTableViewCell" customModule="NetNewsWire" customModuleProvider="target">
<rect key="frame" x="20" y="739.5" width="374" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="WFP-zj-Pve" id="DCX-wc-LSo">
<rect key="frame" x="0.0" y="0.0" width="344.5" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Theme" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="bbw-L9-a3X">
<rect key="frame" x="20" y="13.5" width="45" height="17"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Default" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="DUf-DZ-3Nm">
<rect key="frame" x="290" y="13.5" width="46.5" height="17"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<constraints>
<constraint firstItem="DUf-DZ-3Nm" firstAttribute="centerY" secondItem="DCX-wc-LSo" secondAttribute="centerY" id="4Bc-RP-0ZF"/>
<constraint firstItem="bbw-L9-a3X" firstAttribute="leading" secondItem="DCX-wc-LSo" secondAttribute="leadingMargin" id="KuW-V7-ekH"/>
<constraint firstItem="DUf-DZ-3Nm" firstAttribute="trailing" secondItem="DCX-wc-LSo" secondAttribute="trailingMargin" id="dRF-FI-egK"/>
<constraint firstItem="bbw-L9-a3X" firstAttribute="centerY" secondItem="DCX-wc-LSo" secondAttribute="centerY" id="vHl-bd-8cN"/>
</constraints>
</tableViewCellContentView>
<connections>
<outlet property="detail" destination="DUf-DZ-3Nm" id="Eak-bS-PpA"/>
<outlet property="label" destination="bbw-L9-a3X" id="lGz-7X-px8"/>
</connections>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" id="SXs-NQ-y3U" customClass="VibrantTableViewCell" customModule="NetNewsWire" customModuleProvider="target">
<rect key="frame" x="20" y="783.5" width="374" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="SXs-NQ-y3U" id="BpI-Hz-KH2">
<rect key="frame" x="0.0" y="0.0" width="374" height="44"/>
<autoresizingMask key="autoresizingMask"/>
@ -298,7 +330,7 @@
</tableViewCellContentView>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" id="EYf-v1-lNi" customClass="VibrantBasicTableViewCell" customModule="NetNewsWire" customModuleProvider="target">
<rect key="frame" x="20" y="783.5" width="374" height="44"/>
<rect key="frame" x="20" y="827.5" width="374" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="EYf-v1-lNi" id="7nz-0Y-HaW">
<rect key="frame" x="0.0" y="0.0" width="374" height="44"/>
@ -332,7 +364,7 @@
</connections>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" id="WR6-xo-ty2" customClass="VibrantTableViewCell" customModule="NetNewsWire" customModuleProvider="target">
<rect key="frame" x="20" y="827.5" width="374" height="44"/>
<rect key="frame" x="20" y="871.5" width="374" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="WR6-xo-ty2" id="zX8-l2-bVH">
<rect key="frame" x="0.0" y="0.0" width="374" height="44"/>
@ -377,20 +409,20 @@
<tableViewSection headerTitle="Appearance" id="TkH-4v-yhk">
<cells>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" id="EvG-yE-gDF" customClass="VibrantBasicTableViewCell" customModule="NetNewsWire" customModuleProvider="target">
<rect key="frame" x="20" y="921.5" width="374" height="44"/>
<rect key="frame" x="20" y="965.5" width="374" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="EvG-yE-gDF" id="wBN-zJ-6pN">
<rect key="frame" x="0.0" y="0.0" width="344.5" height="44"/>
<rect key="frame" x="0.0" y="0.0" width="356.5" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Color Palette" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="2Fp-li-dGP">
<rect key="frame" x="20" y="13.5" width="83.5" height="17"/>
<rect key="frame" x="8" y="13.5" width="83.5" height="17"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Automatic" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="16m-Ns-Y8V">
<rect key="frame" x="271" y="13.5" width="65.5" height="17"/>
<rect key="frame" x="283" y="13.5" width="65.5" height="17"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
@ -413,7 +445,7 @@
<tableViewSection headerTitle="Help" id="CS8-fJ-ghn">
<cells>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" textLabel="uGk-2d-oFc" style="IBUITableViewCellStyleDefault" id="Tle-IV-D40" customClass="VibrantTableViewCell" customModule="NetNewsWire" customModuleProvider="target">
<rect key="frame" x="20" y="1015.5" width="374" height="44"/>
<rect key="frame" x="20" y="1059.5" width="374" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="Tle-IV-D40" id="IJD-ZB-8Wm">
<rect key="frame" x="0.0" y="0.0" width="374" height="44"/>
@ -430,7 +462,7 @@
</tableViewCellContentView>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" textLabel="6G3-yV-Eyh" style="IBUITableViewCellStyleDefault" id="Tbf-fE-nfx" customClass="VibrantTableViewCell" customModule="NetNewsWire" customModuleProvider="target">
<rect key="frame" x="20" y="1059.5" width="374" height="44"/>
<rect key="frame" x="20" y="1103.5" width="374" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="Tbf-fE-nfx" id="beV-vI-g3r">
<rect key="frame" x="0.0" y="0.0" width="374" height="44"/>
@ -447,7 +479,7 @@
</tableViewCellContentView>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" textLabel="NeD-y8-KrM" style="IBUITableViewCellStyleDefault" id="TIX-yK-rC6" customClass="VibrantTableViewCell" customModule="NetNewsWire" customModuleProvider="target">
<rect key="frame" x="20" y="1103.5" width="374" height="44"/>
<rect key="frame" x="20" y="1147.5" width="374" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="TIX-yK-rC6" id="qr8-EN-Ofg">
<rect key="frame" x="0.0" y="0.0" width="374" height="44"/>
@ -464,7 +496,7 @@
</tableViewCellContentView>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" textLabel="lfL-bQ-sOp" style="IBUITableViewCellStyleDefault" id="mFn-fE-zqa" customClass="VibrantTableViewCell" customModule="NetNewsWire" customModuleProvider="target">
<rect key="frame" x="20" y="1147.5" width="374" height="44"/>
<rect key="frame" x="20" y="1191.5" width="374" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="mFn-fE-zqa" id="jTe-mf-MRj">
<rect key="frame" x="0.0" y="0.0" width="374" height="44"/>
@ -481,7 +513,7 @@
</tableViewCellContentView>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" textLabel="DDJ-8P-3YY" style="IBUITableViewCellStyleDefault" id="iGs-ze-4gQ" customClass="VibrantTableViewCell" customModule="NetNewsWire" customModuleProvider="target">
<rect key="frame" x="20" y="1191.5" width="374" height="44"/>
<rect key="frame" x="20" y="1235.5" width="374" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="iGs-ze-4gQ" id="EqZ-rF-N0l">
<rect key="frame" x="0.0" y="0.0" width="374" height="44"/>
@ -498,7 +530,7 @@
</tableViewCellContentView>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" textLabel="DsV-Qv-X4K" style="IBUITableViewCellStyleDefault" id="taJ-sg-wnU" customClass="VibrantTableViewCell" customModule="NetNewsWire" customModuleProvider="target">
<rect key="frame" x="20" y="1235.5" width="374" height="44"/>
<rect key="frame" x="20" y="1279.5" width="374" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="taJ-sg-wnU" id="axB-si-1KM">
<rect key="frame" x="0.0" y="0.0" width="374" height="44"/>
@ -515,7 +547,7 @@
</tableViewCellContentView>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" textLabel="zMz-hU-UYU" style="IBUITableViewCellStyleDefault" id="OXi-cg-ab9" customClass="VibrantTableViewCell" customModule="NetNewsWire" customModuleProvider="target">
<rect key="frame" x="20" y="1279.5" width="374" height="44"/>
<rect key="frame" x="20" y="1323.5" width="374" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="OXi-cg-ab9" id="npR-a0-9wv">
<rect key="frame" x="0.0" y="0.0" width="374" height="44"/>
@ -532,7 +564,7 @@
</tableViewCellContentView>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" textLabel="T7x-zl-6Yf" style="IBUITableViewCellStyleDefault" id="VpI-0o-3Px" customClass="VibrantTableViewCell" customModule="NetNewsWire" customModuleProvider="target">
<rect key="frame" x="20" y="1323.5" width="374" height="44"/>
<rect key="frame" x="20" y="1367.5" width="374" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="VpI-0o-3Px" id="xRH-i4-vne">
<rect key="frame" x="0.0" y="0.0" width="374" height="44"/>
@ -549,7 +581,7 @@
</tableViewCellContentView>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" textLabel="76A-Ng-kfs" style="IBUITableViewCellStyleDefault" id="jK8-tv-hBD" customClass="VibrantTableViewCell" customModule="NetNewsWire" customModuleProvider="target">
<rect key="frame" x="20" y="1367.5" width="374" height="44"/>
<rect key="frame" x="20" y="1411.5" width="374" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="jK8-tv-hBD" id="I7Q-GQ-u8Y">
<rect key="frame" x="0.0" y="0.0" width="356.5" height="44"/>
@ -581,6 +613,7 @@
</barButtonItem>
</navigationItem>
<connections>
<outlet property="articleThemeDetailLabel" destination="DUf-DZ-3Nm" id="QHN-Le-zyw"/>
<outlet property="colorPaletteDetailLabel" destination="16m-Ns-Y8V" id="zdj-Ag-ZUw"/>
<outlet property="confirmMarkAllAsReadSwitch" destination="UOo-9z-IuL" id="yLZ-Kf-wDt"/>
<outlet property="groupByFeedSwitch" destination="JNi-Wz-RbU" id="TwH-Kd-o6N"/>
@ -962,10 +995,10 @@
</objects>
<point key="canvasLocation" x="3071" y="617"/>
</scene>
<!--Color Palette-->
<!--Article Themes-->
<scene sceneID="1mT-fg-ezs">
<objects>
<tableViewController storyboardIdentifier="ColorPaletteTableViewController" title="Color Palette" id="fO4-og-e41" customClass="ColorPaletteTableViewController" customModule="NetNewsWire" customModuleProvider="target" sceneMemberID="viewController">
<tableViewController storyboardIdentifier="ArticleThemesTableViewController" title="Article Themes" id="fO4-og-e41" customClass="ArticleThemesTableViewController" customModule="NetNewsWire" customModuleProvider="target" sceneMemberID="viewController">
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="insetGrouped" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="18" sectionFooterHeight="18" id="I8p-P9-C8h">
<rect key="frame" x="0.0" y="0.0" width="414" height="808"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
@ -1161,6 +1194,45 @@
</objects>
<point key="canvasLocation" x="1690" y="1512"/>
</scene>
<!--Color Palette-->
<scene sceneID="i2s-Vf-U8q">
<objects>
<tableViewController storyboardIdentifier="ColorPaletteTableViewController" title="Color Palette" id="4je-jl-or5" customClass="ColorPaletteTableViewController" customModule="NetNewsWire" customModuleProvider="target" sceneMemberID="viewController">
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="insetGrouped" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="18" sectionFooterHeight="18" id="nt4-zl-48k">
<rect key="frame" x="0.0" y="0.0" width="414" height="808"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" systemColor="groupTableViewBackgroundColor"/>
<prototypes>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="Cell" textLabel="lFx-ni-1UR" style="IBUITableViewCellStyleDefault" id="Dzq-cs-s7M" customClass="VibrantTableViewCell" customModule="NetNewsWire" customModuleProvider="target">
<rect key="frame" x="20" y="49.5" width="374" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="Dzq-cs-s7M" id="eAg-RP-lRc">
<rect key="frame" x="0.0" y="0.0" width="374" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Title" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontForContentSizeCategory="YES" adjustsFontSizeToFit="NO" id="lFx-ni-1UR">
<rect key="frame" x="20" y="0.0" width="334" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleBody"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
</tableViewCellContentView>
</tableViewCell>
</prototypes>
<sections/>
<connections>
<outlet property="dataSource" destination="4je-jl-or5" id="Dhv-6P-h0J"/>
<outlet property="delegate" destination="4je-jl-or5" id="SQg-Zw-nSl"/>
</connections>
</tableView>
<simulatedNavigationBarMetrics key="simulatedTopBarMetrics" translucent="NO" prompted="NO"/>
</tableViewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="yl7-jL-bYF" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="4403" y="151"/>
</scene>
</scenes>
<resources>
<image name="accountLocal" width="119" height="102"/>
@ -1172,7 +1244,7 @@
<color red="0.031372549019607843" green="0.41568627450980394" blue="0.93333333333333335" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</namedColor>
<systemColor name="groupTableViewBackgroundColor">
<color red="0.94901960784313721" green="0.94901960784313721" blue="0.96862745098039216" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color red="0.94901960780000005" green="0.94901960780000005" blue="0.96862745100000003" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</systemColor>
<systemColor name="labelColor">
<color white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>

View File

@ -19,18 +19,15 @@ class SettingsViewController: UITableViewController {
@IBOutlet weak var timelineSortOrderSwitch: UISwitch!
@IBOutlet weak var groupByFeedSwitch: UISwitch!
@IBOutlet weak var refreshClearsReadArticlesSwitch: UISwitch!
@IBOutlet weak var articleThemeDetailLabel: UILabel!
@IBOutlet weak var confirmMarkAllAsReadSwitch: UISwitch!
@IBOutlet weak var showFullscreenArticlesSwitch: UISwitch!
@IBOutlet weak var colorPaletteDetailLabel: UILabel!
@IBOutlet weak var openLinksInNetNewsWire: UISwitch!
var scrollToArticlesSection = false
weak var presentingParentController: UIViewController?
override func viewDidLoad() {
// This hack mostly works around a bug in static tables with dynamic type. See: https://spin.atomicobject.com/2018/10/15/dynamic-type-static-uitableview/
NotificationCenter.default.removeObserver(tableView!, name: UIContentSizeCategory.didChangeNotification, object: nil)
@ -69,6 +66,8 @@ class SettingsViewController: UITableViewController {
} else {
refreshClearsReadArticlesSwitch.isOn = false
}
articleThemeDetailLabel.text = ArticleThemesManager.shared.currentTheme.name
if AppDefaults.shared.confirmMarkAllAsRead {
confirmMarkAllAsReadSwitch.isOn = true
@ -128,7 +127,7 @@ class SettingsViewController: UITableViewController {
}
return defaultNumberOfRows
case 5:
return traitCollection.userInterfaceIdiom == .phone ? 3 : 2
return traitCollection.userInterfaceIdiom == .phone ? 4 : 3
default:
return super.tableView(tableView, numberOfRowsInSection: section)
}
@ -229,6 +228,14 @@ class SettingsViewController: UITableViewController {
default:
break
}
case 5:
switch indexPath.row {
case 0:
let articleThemes = UIStoryboard.settings.instantiateController(ofType: ArticleThemesTableViewController.self)
self.navigationController?.pushViewController(articleThemes, animated: true)
default:
break
}
case 6:
let colorPalette = UIStoryboard.settings.instantiateController(ofType: ColorPaletteTableViewController.self)
self.navigationController?.pushViewController(colorPalette, animated: true)