Refactor Styles to now be Themes

This commit is contained in:
Maurice Parker 2021-09-07 16:58:06 -05:00
parent cfd2db58c5
commit 512e83d786
17 changed files with 277 additions and 275 deletions

View File

@ -16,6 +16,8 @@ enum FontSize: Int {
final class AppDefaults {
static let defaultThemeName = "Default"
static var shared = AppDefaults()
private init() {}
@ -39,6 +41,7 @@ final class AppDefaults {
static let importOPMLAccountID = "importOPMLAccountID"
static let exportOPMLAccountID = "exportOPMLAccountID"
static let defaultBrowserID = "defaultBrowserID"
static let currentThemeName = "currentThemeName"
// Hidden prefs
static let showDebugMenu = "ShowDebugMenu"
@ -209,6 +212,15 @@ final class AppDefaults {
}
}
var currentThemeName: String? {
get {
return AppDefaults.string(for: Key.currentThemeName)
}
set {
AppDefaults.setString(for: Key.currentThemeName, newValue)
}
}
var showTitleOnMainWindow: Bool {
return AppDefaults.bool(for: Key.showTitleOnMainWindow)
}
@ -311,7 +323,8 @@ final class AppDefaults {
Key.timelineGroupByFeed: false,
"NSScrollViewShouldScrollUnderTitlebar": false,
Key.refreshInterval: RefreshInterval.everyHour.rawValue,
Key.showDebugMenu: showDebugMenu]
Key.showDebugMenu: showDebugMenu,
Key.currentThemeName: Self.defaultThemeName]
UserDefaults.standard.register(defaults: defaults)

View File

@ -120,7 +120,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations,
SecretsManager.provider = Secrets()
AccountManager.shared = AccountManager(accountsFolder: Platform.dataSubfolder(forApplication: nil, folderName: "Accounts")!)
ArticleStylesManager.shared = ArticleStylesManager(folderPath: Platform.dataSubfolder(forApplication: nil, folderName: "Styles")!)
ArticleThemesManager.shared = ArticleThemesManager(folderPath: Platform.dataSubfolder(forApplication: nil, folderName: "Themes")!)
FeedProviderManager.shared.delegate = ExtensionPointManager.shared
NotificationCenter.default.addObserver(self, selector: #selector(unreadCountDidChange(_:)), name: .UnreadCountDidChange, object: nil)

View File

@ -257,22 +257,22 @@ private extension DetailWebViewController {
func reloadHTML() {
delegate?.mouseDidExit(self)
let style = ArticleStylesManager.shared.currentStyle
let theme = ArticleThemesManager.shared.currentTheme
let rendering: ArticleRenderer.Rendering
switch state {
case .noSelection:
rendering = ArticleRenderer.noSelectionHTML(style: style)
rendering = ArticleRenderer.noSelectionHTML(theme: theme)
case .multipleSelection:
rendering = ArticleRenderer.multipleSelectionHTML(style: style)
rendering = ArticleRenderer.multipleSelectionHTML(theme: theme)
case .loading:
rendering = ArticleRenderer.loadingHTML(style: style)
rendering = ArticleRenderer.loadingHTML(theme: theme)
case .article(let article):
detailIconSchemeHandler.currentArticle = article
rendering = ArticleRenderer.articleHTML(article: article, style: style)
rendering = ArticleRenderer.articleHTML(article: article, theme: theme)
case .extracted(let article, let extractedArticle):
detailIconSchemeHandler.currentArticle = article
rendering = ArticleRenderer.articleHTML(article: article, extractedArticle: extractedArticle, style: style)
rendering = ArticleRenderer.articleHTML(article: article, extractedArticle: extractedArticle, theme: theme)
}
let substitutions = [

View File

@ -25,7 +25,7 @@ extension Article: PasteboardWriterOwner {
static let articleUTIInternalType = NSPasteboard.PasteboardType(rawValue: articleUTIInternal)
private lazy var renderedHTML: String = {
let rendering = ArticleRenderer.articleHTML(article: article, style: ArticleStylesManager.shared.currentStyle)
let rendering = ArticleRenderer.articleHTML(article: article, theme: ArticleThemesManager.shared.currentTheme)
return rendering.html
}()

View File

@ -28,6 +28,8 @@ enum UserInterfaceColorPalette: Int, CustomStringConvertible, CaseIterable {
final class AppDefaults: ObservableObject {
static let defaultThemeName = "Default"
#if os(macOS)
static let store: UserDefaults = UserDefaults.standard
#endif
@ -61,7 +63,8 @@ final class AppDefaults: ObservableObject {
static let timelineGroupByFeed = "timelineGroupByFeed"
static let timelineIconDimensions = "timelineIconDimensions"
static let timelineNumberOfLines = "timelineNumberOfLines"
static let currentThemeName = "currentThemeName"
// Sidebar Defaults
static let sidebarConfirmDelete = "sidebarConfirmDelete"
@ -242,6 +245,8 @@ final class AppDefaults: ObservableObject {
var articleTextSize: ArticleTextSize {
ArticleTextSize(rawValue: articleTextSizeTag) ?? ArticleTextSize.large
}
@AppStorage(Key.currentThemeName, store: store) var currentThemeName: String?
// MARK: Refresh
var lastRefresh: Date? {
@ -334,7 +339,8 @@ final class AppDefaults: ObservableObject {
Key.articleFullscreenEnabled: false,
Key.confirmMarkAllAsRead: true,
"NSScrollViewShouldScrollUnderTitlebar": false,
Key.refreshInterval: RefreshInterval.everyHour.rawValue]
Key.refreshInterval: RefreshInterval.everyHour.rawValue,
Key.currentThemeName: Self.defaultThemeName]
AppDefaults.store.register(defaults: defaults)
}

View File

@ -62,10 +62,16 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
appDelegate = self
SecretsManager.provider = Secrets()
let documentAccountURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let documentAccountsFolder = documentAccountURL.appendingPathComponent("Accounts").absoluteString
let documentFolder = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let documentAccountsFolder = documentFolder.appendingPathComponent("Accounts").absoluteString
let documentAccountsFolderPath = String(documentAccountsFolder.suffix(from: documentAccountsFolder.index(documentAccountsFolder.startIndex, offsetBy: 7)))
AccountManager.shared = AccountManager(accountsFolder: documentAccountsFolderPath)
let documentStylesFolder = documentFolder.appendingPathComponent("Themes").absoluteString
let documentStylesFolderPath = String(documentStylesFolder.suffix(from: documentAccountsFolder.index(documentStylesFolder.startIndex, offsetBy: 7)))
ArticleThemesManager.shared = ArticleThemesManager(folderPath: documentStylesFolderPath)
FeedProviderManager.shared.delegate = ExtensionPointManager.shared
NotificationCenter.default.addObserver(self, selector: #selector(unreadCountDidChange(_:)), name: .UnreadCountDidChange, object: nil)

View File

@ -483,23 +483,23 @@ private extension WebViewController {
func renderPage(_ webView: PreloadedWebView?) {
guard let webView = webView else { return }
let style = ArticleStylesManager.shared.currentStyle
let theme = ArticleThemesManager.shared.currentTheme
let rendering: ArticleRenderer.Rendering
if let articleExtractor = articleExtractor, articleExtractor.state == .processing {
rendering = ArticleRenderer.loadingHTML(style: style)
rendering = ArticleRenderer.loadingHTML(theme: theme)
} else if let articleExtractor = articleExtractor, articleExtractor.state == .failedToParse, let article = article {
rendering = ArticleRenderer.articleHTML(article: article, style: style)
rendering = ArticleRenderer.articleHTML(article: article, theme: theme)
} else if let article = article, let extractedArticle = extractedArticle {
if isShowingExtractedArticle {
rendering = ArticleRenderer.articleHTML(article: article, extractedArticle: extractedArticle, style: style)
rendering = ArticleRenderer.articleHTML(article: article, extractedArticle: extractedArticle, theme: theme)
} else {
rendering = ArticleRenderer.articleHTML(article: article, style: style)
rendering = ArticleRenderer.articleHTML(article: article, theme: theme)
}
} else if let article = article {
rendering = ArticleRenderer.articleHTML(article: article, style: style)
rendering = ArticleRenderer.articleHTML(article: article, theme: theme)
} else {
rendering = ArticleRenderer.noSelectionHTML(style: style)
rendering = ArticleRenderer.noSelectionHTML(theme: theme)
}
let substitutions = [

View File

@ -72,6 +72,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele
SecretsManager.provider = Secrets()
AccountManager.shared = AccountManager(accountsFolder: Platform.dataSubfolder(forApplication: nil, folderName: "Accounts")!)
ArticleThemesManager.shared = ArticleThemesManager(folderPath: Platform.dataSubfolder(forApplication: nil, folderName: "Themes")!)
FeedProviderManager.shared.delegate = ExtensionPointManager.shared
NotificationCenter.default.addObserver(self, selector: #selector(unreadCountDidChange(_:)), name: .UnreadCountDidChange, object: nil)

View File

@ -261,25 +261,25 @@ private extension WebViewController {
}
func renderPage(_ webView: PreloadedWebView) {
let style = ArticleStylesManager.shared.currentStyle
let theme = ArticleThemesManager.shared.currentTheme
let rendering: ArticleRenderer.Rendering
if articles?.count ?? 0 > 1 {
rendering = ArticleRenderer.multipleSelectionHTML(style: style)
rendering = ArticleRenderer.multipleSelectionHTML(theme: theme)
} else if let articleExtractor = articleExtractor, articleExtractor.state == .processing {
rendering = ArticleRenderer.loadingHTML(style: style)
rendering = ArticleRenderer.loadingHTML(theme: theme)
} else if let articleExtractor = articleExtractor, articleExtractor.state == .failedToParse, let article = articles?.first {
rendering = ArticleRenderer.articleHTML(article: article, style: style)
rendering = ArticleRenderer.articleHTML(article: article, theme: theme)
} else if let article = articles?.first, let extractedArticle = extractedArticle {
if isShowingExtractedArticle {
rendering = ArticleRenderer.articleHTML(article: article, extractedArticle: extractedArticle, style: style)
rendering = ArticleRenderer.articleHTML(article: article, extractedArticle: extractedArticle, theme: theme)
} else {
rendering = ArticleRenderer.articleHTML(article: article, style: style)
rendering = ArticleRenderer.articleHTML(article: article, theme: theme)
}
} else if let article = articles?.first {
rendering = ArticleRenderer.articleHTML(article: article, style: style)
rendering = ArticleRenderer.articleHTML(article: article, theme: theme)
} else {
rendering = ArticleRenderer.noSelectionHTML(style: style)
rendering = ArticleRenderer.noSelectionHTML(theme: theme)
}
let substitutions = [

View File

@ -565,8 +565,8 @@
51C45294226509C800C03939 /* SearchFeedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8477ACBD22238E9500DF7F37 /* SearchFeedDelegate.swift */; };
51C45296226509D300C03939 /* OPMLExporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8444C8F11FED81840051386C /* OPMLExporter.swift */; };
51C45297226509E300C03939 /* DefaultFeedsImporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97591ED9EB0D007D329B /* DefaultFeedsImporter.swift */; };
51C4529922650A0000C03939 /* ArticleStylesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97881ED9ECEF007D329B /* ArticleStylesManager.swift */; };
51C4529A22650A0400C03939 /* ArticleStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97871ED9ECEF007D329B /* ArticleStyle.swift */; };
51C4529922650A0000C03939 /* ArticleThemesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97881ED9ECEF007D329B /* ArticleThemesManager.swift */; };
51C4529A22650A0400C03939 /* ArticleTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97871ED9ECEF007D329B /* ArticleTheme.swift */; };
51C4529B22650A1000C03939 /* FaviconDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848F6AE41FC29CFA002D422E /* FaviconDownloader.swift */; };
51C4529C22650A1000C03939 /* SingleFaviconDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845A29081FC74B8E007B49E3 /* SingleFaviconDownloader.swift */; };
51C4529D22650A1000C03939 /* FaviconURLFinder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84FF69B01FC3793300DC198E /* FaviconURLFinder.swift */; };
@ -736,10 +736,10 @@
51E4996324A875F400B667CB /* newsfoot.js in Resources */ = {isa = PBXBuildFile; fileRef = 49F40DEF2335B71000552BF4 /* newsfoot.js */; };
51E4996424A875F400B667CB /* shared.css in Resources */ = {isa = PBXBuildFile; fileRef = B27EEBDF244D15F2000932E6 /* shared.css */; };
51E4996524A875F400B667CB /* template.html in Resources */ = {isa = PBXBuildFile; fileRef = 848362FE2262A30E00DA1D35 /* template.html */; };
51E4996624A8760B00B667CB /* ArticleStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97871ED9ECEF007D329B /* ArticleStyle.swift */; };
51E4996724A8760B00B667CB /* ArticleStylesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97881ED9ECEF007D329B /* ArticleStylesManager.swift */; };
51E4996824A8760C00B667CB /* ArticleStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97871ED9ECEF007D329B /* ArticleStyle.swift */; };
51E4996924A8760C00B667CB /* ArticleStylesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97881ED9ECEF007D329B /* ArticleStylesManager.swift */; };
51E4996624A8760B00B667CB /* ArticleTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97871ED9ECEF007D329B /* ArticleTheme.swift */; };
51E4996724A8760B00B667CB /* ArticleThemesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97881ED9ECEF007D329B /* ArticleThemesManager.swift */; };
51E4996824A8760C00B667CB /* ArticleTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97871ED9ECEF007D329B /* ArticleTheme.swift */; };
51E4996924A8760C00B667CB /* ArticleThemesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97881ED9ECEF007D329B /* ArticleThemesManager.swift */; };
51E4996A24A8762D00B667CB /* ExtractedArticle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FA73A62332BE880090D516 /* ExtractedArticle.swift */; };
51E4996B24A8762D00B667CB /* ArticleExtractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FA73A32332BE110090D516 /* ArticleExtractor.swift */; };
51E4996C24A8762D00B667CB /* ExtractedArticle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FA73A62332BE880090D516 /* ExtractedArticle.swift */; };
@ -904,14 +904,14 @@
65ED3FE5235DEF6C0081F399 /* DefaultFeedsImporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97591ED9EB0D007D329B /* DefaultFeedsImporter.swift */; };
65ED3FE6235DEF6C0081F399 /* RenameWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A37CB4201ECD610087C5AF /* RenameWindowController.swift */; };
65ED3FE7235DEF6C0081F399 /* SendToMicroBlogCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A14FF220048CA70046AD9A /* SendToMicroBlogCommand.swift */; };
65ED3FE8235DEF6C0081F399 /* ArticleStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97871ED9ECEF007D329B /* ArticleStyle.swift */; };
65ED3FE8235DEF6C0081F399 /* ArticleTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97871ED9ECEF007D329B /* ArticleTheme.swift */; };
65ED3FE9235DEF6C0081F399 /* FaviconURLFinder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84FF69B01FC3793300DC198E /* FaviconURLFinder.swift */; };
65ED3FEA235DEF6C0081F399 /* SidebarViewController+ContextualMenus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B7178B201E66580091657D /* SidebarViewController+ContextualMenus.swift */; };
65ED3FEC235DEF6C0081F399 /* RSHTMLMetadata+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842611A11FCB769D0086A189 /* RSHTMLMetadata+Extension.swift */; };
65ED3FED235DEF6C0081F399 /* SendToMarsEditCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A1500420048DDF0046AD9A /* SendToMarsEditCommand.swift */; };
65ED3FEE235DEF6C0081F399 /* UserNotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FE10022345529D0056195D /* UserNotificationManager.swift */; };
65ED3FEF235DEF6C0081F399 /* ScriptingObjectContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5907DB12004BB37005947E5 /* ScriptingObjectContainer.swift */; };
65ED3FF0235DEF6C0081F399 /* ArticleStylesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97881ED9ECEF007D329B /* ArticleStylesManager.swift */; };
65ED3FF0235DEF6C0081F399 /* ArticleThemesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97881ED9ECEF007D329B /* ArticleThemesManager.swift */; };
65ED3FF1235DEF6C0081F399 /* DetailContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8405DD892213E0E3008CE1BF /* DetailContainerView.swift */; };
65ED3FF2235DEF6C0081F399 /* SharingServiceDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519B8D322143397200FA689C /* SharingServiceDelegate.swift */; };
65ED3FF3235DEF6C0081F399 /* ArticleSorter.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF3ABF1423259DDB0074C542 /* ArticleSorter.swift */; };
@ -1101,8 +1101,8 @@
849A977F1ED9EC42007D329B /* ArticleRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A977D1ED9EC42007D329B /* ArticleRenderer.swift */; };
849A97801ED9EC42007D329B /* DetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A977E1ED9EC42007D329B /* DetailViewController.swift */; };
849A97831ED9EC63007D329B /* SidebarStatusBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97821ED9EC63007D329B /* SidebarStatusBarView.swift */; };
849A97891ED9ECEF007D329B /* ArticleStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97871ED9ECEF007D329B /* ArticleStyle.swift */; };
849A978A1ED9ECEF007D329B /* ArticleStylesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97881ED9ECEF007D329B /* ArticleStylesManager.swift */; };
849A97891ED9ECEF007D329B /* ArticleTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97871ED9ECEF007D329B /* ArticleTheme.swift */; };
849A978A1ED9ECEF007D329B /* ArticleThemesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97881ED9ECEF007D329B /* ArticleThemesManager.swift */; };
849A97981ED9EFAA007D329B /* Node-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97971ED9EFAA007D329B /* Node-Extensions.swift */; };
849A979F1ED9F130007D329B /* SidebarCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A979E1ED9F130007D329B /* SidebarCell.swift */; };
849A97A31ED9F180007D329B /* FolderTreeControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97A11ED9F180007D329B /* FolderTreeControllerDelegate.swift */; };
@ -2021,8 +2021,8 @@
849A977D1ED9EC42007D329B /* ArticleRenderer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArticleRenderer.swift; sourceTree = "<group>"; };
849A977E1ED9EC42007D329B /* DetailViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DetailViewController.swift; sourceTree = "<group>"; };
849A97821ED9EC63007D329B /* SidebarStatusBarView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SidebarStatusBarView.swift; sourceTree = "<group>"; };
849A97871ED9ECEF007D329B /* ArticleStyle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArticleStyle.swift; sourceTree = "<group>"; };
849A97881ED9ECEF007D329B /* ArticleStylesManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArticleStylesManager.swift; sourceTree = "<group>"; };
849A97871ED9ECEF007D329B /* ArticleTheme.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArticleTheme.swift; sourceTree = "<group>"; };
849A97881ED9ECEF007D329B /* ArticleThemesManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArticleThemesManager.swift; sourceTree = "<group>"; };
849A97971ED9EFAA007D329B /* Node-Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Node-Extensions.swift"; sourceTree = "<group>"; };
849A979E1ED9F130007D329B /* SidebarCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SidebarCell.swift; sourceTree = "<group>"; };
849A97A11ED9F180007D329B /* FolderTreeControllerDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FolderTreeControllerDelegate.swift; sourceTree = "<group>"; };
@ -3437,8 +3437,8 @@
849A97861ED9ECEF007D329B /* Article Styles */ = {
isa = PBXGroup;
children = (
849A97871ED9ECEF007D329B /* ArticleStyle.swift */,
849A97881ED9ECEF007D329B /* ArticleStylesManager.swift */,
849A97871ED9ECEF007D329B /* ArticleTheme.swift */,
849A97881ED9ECEF007D329B /* ArticleThemesManager.swift */,
);
name = "Article Styles";
path = Shared/ArticleStyles;
@ -4961,7 +4961,7 @@
51E4991724A8090400B667CB /* ArticleUtilities.swift in Sources */,
51E4991B24A8091000B667CB /* IconImage.swift in Sources */,
51E4995424A8734D00B667CB /* ExtensionPointIdentifer.swift in Sources */,
51E4996924A8760C00B667CB /* ArticleStylesManager.swift in Sources */,
51E4996924A8760C00B667CB /* ArticleThemesManager.swift in Sources */,
5177471E24B387E100EB0F74 /* ImageTransition.swift in Sources */,
51E498F324A8085D00B667CB /* PseudoFeed.swift in Sources */,
172412A5257BC01C00ACCEBC /* AddCloudKitAccountView.swift in Sources */,
@ -5005,7 +5005,7 @@
172412AF257BC0C300ACCEBC /* AccountType+Helpers.swift in Sources */,
51E4995624A8734D00B667CB /* TwitterFeedProvider-Extensions.swift in Sources */,
5125E6CA24AE461D002A7562 /* TimelineLayoutView.swift in Sources */,
51E4996824A8760C00B667CB /* ArticleStyle.swift in Sources */,
51E4996824A8760C00B667CB /* ArticleTheme.swift in Sources */,
51E4990024A808BB00B667CB /* FaviconGenerator.swift in Sources */,
51E4997124A8764C00B667CB /* ActivityType.swift in Sources */,
51E4991E24A8094300B667CB /* RSImage-AppIcons.swift in Sources */,
@ -5096,7 +5096,7 @@
51919FB024AA8EFA00541E64 /* SidebarItemView.swift in Sources */,
1769E33624BD9621000E1E8E /* EditAccountCredentialsModel.swift in Sources */,
51919FEF24AB85E400541E64 /* TimelineContainerView.swift in Sources */,
51E4996624A8760B00B667CB /* ArticleStyle.swift in Sources */,
51E4996624A8760B00B667CB /* ArticleTheme.swift in Sources */,
5171B4D424B7BABA00FB8D3B /* MarkStatusCommand.swift in Sources */,
17E4DBD624BFC53E00FE462A /* AdvancedPreferencesModel.swift in Sources */,
5177470724B2910300EB0F74 /* ArticleToolbarModifier.swift in Sources */,
@ -5180,7 +5180,7 @@
51E498FB24A808BA00B667CB /* FaviconGenerator.swift in Sources */,
17D5F19524B0C1DD00375168 /* SidebarToolbarModifier.swift in Sources */,
17241288257BBF7000ACCEBC /* AddNewsBlurViewModel.swift in Sources */,
51E4996724A8760B00B667CB /* ArticleStylesManager.swift in Sources */,
51E4996724A8760B00B667CB /* ArticleThemesManager.swift in Sources */,
1729529B24AA1FD200D65E66 /* MacSearchField.swift in Sources */,
51408B7F24A9EC6F0073CF4E /* SidebarItem.swift in Sources */,
514E6BDB24ACEA0400AC6F6E /* TimelineItemView.swift in Sources */,
@ -5320,7 +5320,7 @@
5193CD59245E44A90092735E /* RedditFeedProvider-Extensions.swift in Sources */,
65ED3FE6235DEF6C0081F399 /* RenameWindowController.swift in Sources */,
65ED3FE7235DEF6C0081F399 /* SendToMicroBlogCommand.swift in Sources */,
65ED3FE8235DEF6C0081F399 /* ArticleStyle.swift in Sources */,
65ED3FE8235DEF6C0081F399 /* ArticleTheme.swift in Sources */,
65ED3FE9235DEF6C0081F399 /* FaviconURLFinder.swift in Sources */,
6538131C2680E17F007A082C /* UserInfoKey.swift in Sources */,
65ED3FEA235DEF6C0081F399 /* SidebarViewController+ContextualMenus.swift in Sources */,
@ -5331,7 +5331,7 @@
65ED3FEE235DEF6C0081F399 /* UserNotificationManager.swift in Sources */,
653813182680E152007A082C /* AccountType+Helpers.swift in Sources */,
65ED3FEF235DEF6C0081F399 /* ScriptingObjectContainer.swift in Sources */,
65ED3FF0235DEF6C0081F399 /* ArticleStylesManager.swift in Sources */,
65ED3FF0235DEF6C0081F399 /* ArticleThemesManager.swift in Sources */,
65ED3FF1235DEF6C0081F399 /* DetailContainerView.swift in Sources */,
65ED3FF2235DEF6C0081F399 /* SharingServiceDelegate.swift in Sources */,
515A50E7243D07A90089E588 /* ExtensionPointManager.swift in Sources */,
@ -5532,14 +5532,14 @@
51FA73A82332BE880090D516 /* ExtractedArticle.swift in Sources */,
51C4527C2265091600C03939 /* MasterTimelineDefaultCellLayout.swift in Sources */,
51E4398023805EBC00015C31 /* AddComboTableViewCell.swift in Sources */,
51C4529A22650A0400C03939 /* ArticleStyle.swift in Sources */,
51C4529A22650A0400C03939 /* ArticleTheme.swift in Sources */,
51C4527F2265092C00C03939 /* ArticleViewController.swift in Sources */,
51C4526A226508F600C03939 /* MasterFeedTableViewCellLayout.swift in Sources */,
51C452AE2265104D00C03939 /* ArticleStringFormatter.swift in Sources */,
512E08E62268800D00BDCFDD /* FolderTreeControllerDelegate.swift in Sources */,
51A9A60A2382FD240033AADF /* PoppableGestureRecognizerDelegate.swift in Sources */,
51DC079A2552083500A3F79F /* ArticleTextSize.swift in Sources */,
51C4529922650A0000C03939 /* ArticleStylesManager.swift in Sources */,
51C4529922650A0000C03939 /* ArticleThemesManager.swift in Sources */,
51EF0F802277A8330050506E /* MasterTimelineCellLayout.swift in Sources */,
51F85BF722749FA100C787DC /* UIFont-Extensions.swift in Sources */,
51C452AF2265108300C03939 /* ArticleArray.swift in Sources */,
@ -5690,7 +5690,7 @@
84A37CB5201ECD610087C5AF /* RenameWindowController.swift in Sources */,
84A14FF320048CA70046AD9A /* SendToMicroBlogCommand.swift in Sources */,
B2B8075E239C49D300F191E0 /* RSImage-AppIcons.swift in Sources */,
849A97891ED9ECEF007D329B /* ArticleStyle.swift in Sources */,
849A97891ED9ECEF007D329B /* ArticleTheme.swift in Sources */,
84FF69B11FC3793300DC198E /* FaviconURLFinder.swift in Sources */,
84B7178C201E66580091657D /* SidebarViewController+ContextualMenus.swift in Sources */,
842611A21FCB769D0086A189 /* RSHTMLMetadata+Extension.swift in Sources */,
@ -5698,7 +5698,7 @@
51FE10032345529D0056195D /* UserNotificationManager.swift in Sources */,
D5907DB22004BB37005947E5 /* ScriptingObjectContainer.swift in Sources */,
51BC4AFF247277E0000A6ED8 /* URL-Extensions.swift in Sources */,
849A978A1ED9ECEF007D329B /* ArticleStylesManager.swift in Sources */,
849A978A1ED9ECEF007D329B /* ArticleThemesManager.swift in Sources */,
8405DD8A2213E0E3008CE1BF /* DetailContainerView.swift in Sources */,
51107746243BEE2500D97C8C /* ExtensionPointPreferencesViewController.swift in Sources */,
519B8D332143397200FA689C /* SharingServiceDelegate.swift in Sources */,

View File

@ -37,15 +37,15 @@ struct ArticleRenderer {
private let article: Article?
private let extractedArticle: ExtractedArticle?
private let articleStyle: ArticleStyle
private let articleTheme: ArticleTheme
private let title: String
private let body: String
private let baseURL: String?
private init(article: Article?, extractedArticle: ExtractedArticle?, style: ArticleStyle) {
private init(article: Article?, extractedArticle: ExtractedArticle?, theme: ArticleTheme) {
self.article = article
self.extractedArticle = extractedArticle
self.articleStyle = style
self.articleTheme = theme
self.title = article?.sanitizedTitle() ?? ""
if let content = extractedArticle?.content {
self.body = content
@ -58,28 +58,28 @@ struct ArticleRenderer {
// MARK: - API
static func articleHTML(article: Article, extractedArticle: ExtractedArticle? = nil, style: ArticleStyle) -> Rendering {
let renderer = ArticleRenderer(article: article, extractedArticle: extractedArticle, style: style)
static func articleHTML(article: Article, extractedArticle: ExtractedArticle? = nil, theme: ArticleTheme) -> Rendering {
let renderer = ArticleRenderer(article: article, extractedArticle: extractedArticle, theme: theme)
return (renderer.articleCSS, renderer.articleHTML, renderer.title, renderer.baseURL ?? "")
}
static func multipleSelectionHTML(style: ArticleStyle) -> Rendering {
let renderer = ArticleRenderer(article: nil, extractedArticle: nil, style: style)
static func multipleSelectionHTML(theme: ArticleTheme) -> Rendering {
let renderer = ArticleRenderer(article: nil, extractedArticle: nil, theme: theme)
return (renderer.articleCSS, renderer.multipleSelectionHTML, renderer.title, renderer.baseURL ?? "")
}
static func loadingHTML(style: ArticleStyle) -> Rendering {
let renderer = ArticleRenderer(article: nil, extractedArticle: nil, style: style)
static func loadingHTML(theme: ArticleTheme) -> Rendering {
let renderer = ArticleRenderer(article: nil, extractedArticle: nil, theme: theme)
return (renderer.articleCSS, renderer.loadingHTML, renderer.title, renderer.baseURL ?? "")
}
static func noSelectionHTML(style: ArticleStyle) -> Rendering {
let renderer = ArticleRenderer(article: nil, extractedArticle: nil, style: style)
static func noSelectionHTML(theme: ArticleTheme) -> Rendering {
let renderer = ArticleRenderer(article: nil, extractedArticle: nil, theme: theme)
return (renderer.articleCSS, renderer.noSelectionHTML, renderer.title, renderer.baseURL ?? "")
}
static func noContentHTML(style: ArticleStyle) -> Rendering {
let renderer = ArticleRenderer(article: nil, extractedArticle: nil, style: style)
static func noContentHTML(theme: ArticleTheme) -> Rendering {
let renderer = ArticleRenderer(article: nil, extractedArticle: nil, theme: theme)
return (renderer.articleCSS, renderer.noContentHTML, renderer.title, renderer.baseURL ?? "")
}
}
@ -128,11 +128,11 @@ private extension ArticleRenderer {
}()
func styleString() -> String {
return articleStyle.css ?? ArticleRenderer.defaultStyleSheet
return articleTheme.css ?? ArticleRenderer.defaultStyleSheet
}
func template() -> String {
return articleStyle.template ?? ArticleRenderer.defaultTemplate
return articleTheme.template ?? ArticleRenderer.defaultTemplate
}
func titleOrTitleLink() -> String {

View File

@ -1,187 +0,0 @@
//
// ArticleStylesManager.sqift
// NetNewsWire
//
// Created by Brent Simmons on 9/26/15.
// Copyright © 2015 Ranchero Software, LLC. All rights reserved.
//
#if os(macOS)
import AppKit
#else
import UIKit
#endif
import RSCore
let ArticleStyleNamesDidChangeNotification = "ArticleStyleNamesDidChangeNotification"
let CurrentArticleStyleDidChangeNotification = "CurrentArticleStyleDidChangeNotification"
private let styleKey = "style"
private let defaultStyleName = "Default"
private let stylesInResourcesFolderName = "Styles"
private let styleSuffix = ".netnewswirestyle"
private let nnwStyleSuffix = ".nnwstyle"
private let cssStyleSuffix = ".css"
private let styleSuffixes = [styleSuffix, nnwStyleSuffix, cssStyleSuffix];
final class ArticleStylesManager {
static var shared: ArticleStylesManager!
private let folderPath: String
var currentStyleName: String {
get {
return UserDefaults.standard.string(forKey: styleKey)!
}
set {
if newValue != currentStyleName {
UserDefaults.standard.set(newValue, forKey: styleKey)
}
}
}
var currentStyle: ArticleStyle {
didSet {
NotificationCenter.default.post(name: Notification.Name(rawValue: CurrentArticleStyleDidChangeNotification), object: self)
}
}
var styleNames = [defaultStyleName] {
didSet {
NotificationCenter.default.post(name: Notification.Name(rawValue: ArticleStyleNamesDidChangeNotification), object: self)
}
}
init(folderPath: String) {
self.folderPath = folderPath
do {
try FileManager.default.createDirectory(atPath: folderPath, withIntermediateDirectories: true, attributes: nil)
} catch {
assertionFailure("Could not create folder for Styles.")
abort()
}
UserDefaults.standard.register(defaults: [styleKey: defaultStyleName])
currentStyle = ArticleStyle.defaultStyle
updateStyleNames()
updateCurrentStyle()
#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
}
// MARK: Notifications
@objc dynamic func applicationDidBecomeActive(_ note: Notification) {
updateStyleNames()
updateCurrentStyle()
}
// MARK : Internal
private func updateStyleNames() {
let updatedStyleNames = allStylePaths(folderPath).map { styleNameForPath($0) }
if updatedStyleNames != styleNames {
styleNames = updatedStyleNames
}
}
private func articleStyleWithStyleName(_ styleName: String) -> ArticleStyle? {
if styleName == defaultStyleName {
return ArticleStyle.defaultStyle
}
guard let path = pathForStyleName(styleName, folder: folderPath) else {
return nil
}
return ArticleStyle(path: path)
}
private func defaultArticleStyle() -> ArticleStyle {
return articleStyleWithStyleName(defaultStyleName)!
}
private func updateCurrentStyle() {
var styleName = currentStyleName
if !styleNames.contains(styleName) {
styleName = defaultStyleName
currentStyleName = defaultStyleName
}
var articleStyle = articleStyleWithStyleName(styleName)
if articleStyle == nil {
articleStyle = defaultArticleStyle()
currentStyleName = defaultStyleName
}
if let articleStyle = articleStyle, articleStyle != currentStyle {
currentStyle = articleStyle
}
}
}
private func allStylePaths(_ folder: String) -> [String] {
let filepaths = FileManager.default.filePaths(inFolder: folder)
return filepaths?.filter { fileAtPathIsStyle($0) } ?? []
}
private func fileAtPathIsStyle(_ f: String) -> Bool {
if !f.hasSuffix(styleSuffix) && !f.hasSuffix(nnwStyleSuffix) && !f.hasSuffix(cssStyleSuffix) {
return false
}
if (f as NSString).lastPathComponent.hasPrefix(".") {
return false
}
return true
}
private func filenameWithStyleSuffixRemoved(_ filename: String) -> String {
for oneSuffix in styleSuffixes {
if filename.hasSuffix(oneSuffix) {
return filename.stripping(suffix: oneSuffix)
}
}
return filename
}
private func styleNameForPath(_ f: String) -> String {
let filename = (f as NSString).lastPathComponent
return filenameWithStyleSuffixRemoved(filename)
}
private func pathIsPathForStyleName(_ styleName: String, path: String) -> Bool {
let filename = (path as NSString).lastPathComponent
return filenameWithStyleSuffixRemoved(filename) == styleName
}
private func pathForStyleName(_ styleName: String, folder: String) -> String? {
for onePath in allStylePaths(folder) {
if pathIsPathForStyleName(styleName, path: onePath) {
return onePath
}
}
return nil
}

View File

@ -1,5 +1,5 @@
//
// ArticleStyle.swift
// ArticleTheme.swift
// NetNewsWire
//
// Created by Brent Simmons on 9/26/15.
@ -8,21 +8,18 @@
import Foundation
struct ArticleStyle: Equatable {
struct ArticleTheme: Equatable {
static let defaultStyle = ArticleStyle()
static let defaultTheme = ArticleTheme()
let path: String?
let template: String?
let css: String?
let emptyCSS: String?
let systemMessageCSS: String?
let info: NSDictionary?
init() {
//Default style
self.path = nil;
self.emptyCSS = nil
self.systemMessageCSS = nil
self.info = ["CreatorHomePage": "https://netnewswire.com/", "CreatorName": "Ranchero Software", "Version": "1.0"]
@ -53,8 +50,8 @@ struct ArticleStyle: Equatable {
let cssPath = (path as NSString).appendingPathComponent("stylesheet.css")
self.css = stringAtPath(cssPath)
let emptyCSSPath = (path as NSString).appendingPathComponent("stylesheet_empty.css")
self.emptyCSS = stringAtPath(emptyCSSPath)
let systemMessageCSSPath = (path as NSString).appendingPathComponent("system_message_stylesheet.css")
self.systemMessageCSS = stringAtPath(systemMessageCSSPath)
let templatePath = (path as NSString).appendingPathComponent("template.html")
self.template = stringAtPath(templatePath)
@ -64,7 +61,7 @@ struct ArticleStyle: Equatable {
self.css = stringAtPath(path)
self.template = nil
self.emptyCSS = nil
self.systemMessageCSS = nil
self.info = nil
}
}

View File

@ -0,0 +1,153 @@
//
// ArticleThemesManager.sqift
// NetNewsWire
//
// Created by Brent Simmons on 9/26/15.
// Copyright © 2015 Ranchero Software, LLC. All rights reserved.
//
#if os(macOS)
import AppKit
#else
import UIKit
#endif
import RSCore
let ArticleThemeNamesDidChangeNotification = "ArticleThemeNamesDidChangeNotification"
let CurrentArticleThemeDidChangeNotification = "CurrentArticleThemeDidChangeNotification"
private let themesInResourcesFolderName = "Themes"
private let nnwThemeSuffix = ".nnwtheme"
final class ArticleThemesManager {
static var shared: ArticleThemesManager!
private let folderPath: String
var currentThemeName: String {
get {
return AppDefaults.shared.currentThemeName ?? AppDefaults.defaultThemeName
}
set {
if newValue != currentThemeName {
AppDefaults.shared.currentThemeName = newValue
}
}
}
var currentTheme: ArticleTheme {
didSet {
NotificationCenter.default.post(name: Notification.Name(rawValue: CurrentArticleThemeDidChangeNotification), object: self)
}
}
var themeNames = [AppDefaults.defaultThemeName] {
didSet {
NotificationCenter.default.post(name: Notification.Name(rawValue: ArticleThemeNamesDidChangeNotification), object: self)
}
}
init(folderPath: String) {
self.folderPath = folderPath
do {
try FileManager.default.createDirectory(atPath: folderPath, withIntermediateDirectories: true, attributes: nil)
} catch {
assertionFailure("Could not create folder for Themes.")
abort()
}
currentTheme = ArticleTheme.defaultTheme
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
}
// MARK: Notifications
@objc dynamic func applicationDidBecomeActive(_ note: Notification) {
updateThemeNames()
updateCurrentTheme()
}
// MARK : Internal
private func updateThemeNames() {
let updatedThemeNames = allThemePaths(folderPath).map { themeNameForPath($0) }
if updatedThemeNames != themeNames {
themeNames = updatedThemeNames
}
}
private func articleThemeWithThemeName(_ themeName: String) -> ArticleTheme? {
if themeName == AppDefaults.defaultThemeName {
return ArticleTheme.defaultTheme
}
guard let path = pathForThemeName(themeName, folder: folderPath) else {
return nil
}
return ArticleTheme(path: path)
}
private func defaultArticleTheme() -> ArticleTheme {
return articleThemeWithThemeName(AppDefaults.defaultThemeName)!
}
private func updateCurrentTheme() {
var themeName = currentThemeName
if !themeNames.contains(themeName) {
themeName = AppDefaults.defaultThemeName
currentThemeName = AppDefaults.defaultThemeName
}
var articleTheme = articleThemeWithThemeName(themeName)
if articleTheme == nil {
articleTheme = defaultArticleTheme()
currentThemeName = AppDefaults.defaultThemeName
}
if let articleTheme = articleTheme, articleTheme != currentTheme {
currentTheme = articleTheme
}
}
}
private func allThemePaths(_ folder: String) -> [String] {
let filepaths = FileManager.default.filePaths(inFolder: folder)
return filepaths?.filter { $0.hasSuffix(nnwThemeSuffix) } ?? []
}
private func filenameWithThemeSuffixRemoved(_ filename: String) -> String {
return filename.stripping(suffix: nnwThemeSuffix)
}
private func themeNameForPath(_ f: String) -> String {
let filename = (f as NSString).lastPathComponent
return filenameWithThemeSuffixRemoved(filename)
}
private func pathIsPathForThemeName(_ themeName: String, path: String) -> Bool {
let filename = (path as NSString).lastPathComponent
return filenameWithThemeSuffixRemoved(filename) == themeName
}
private func pathForThemeName(_ themeName: String, folder: String) -> String? {
for onePath in allThemePaths(folder) {
if pathIsPathForThemeName(themeName, path: onePath) {
return onePath
}
}
return nil
}

View File

@ -28,6 +28,8 @@ enum UserInterfaceColorPalette: Int, CustomStringConvertible, CaseIterable {
final class AppDefaults {
static let defaultThemeName = "Defaults"
static let shared = AppDefaults()
private init() {}
@ -55,6 +57,7 @@ final class AppDefaults {
static let addWebFeedFolderName = "addWebFeedFolderName"
static let addFolderAccountID = "addFolderAccountID"
static let useSystemBrowser = "useSystemBrowser"
static let currentThemeName = "currentThemeName"
}
let isDeveloperBuild: Bool = {
@ -220,6 +223,15 @@ final class AppDefaults {
}
}
var currentThemeName: String? {
get {
return AppDefaults.string(for: Key.currentThemeName)
}
set {
AppDefaults.setString(for: Key.currentThemeName, newValue)
}
}
static func registerDefaults() {
let defaults: [String : Any] = [Key.userInterfaceColorPalette: UserInterfaceColorPalette.automatic.rawValue,
Key.timelineGroupByFeed: false,
@ -229,7 +241,8 @@ final class AppDefaults {
Key.timelineSortDirection: ComparisonResult.orderedDescending.rawValue,
Key.articleFullscreenAvailable: false,
Key.articleFullscreenEnabled: false,
Key.confirmMarkAllAsRead: true]
Key.confirmMarkAllAsRead: true,
Key.currentThemeName: Self.defaultThemeName]
AppDefaults.store.register(defaults: defaults)
}

View File

@ -68,9 +68,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
let documentAccountsFolderPath = String(documentAccountsFolder.suffix(from: documentAccountsFolder.index(documentAccountsFolder.startIndex, offsetBy: 7)))
AccountManager.shared = AccountManager(accountsFolder: documentAccountsFolderPath)
let documentStylesFolder = documentFolder.appendingPathComponent("Styles").absoluteString
let documentStylesFolder = documentFolder.appendingPathComponent("Themes").absoluteString
let documentStylesFolderPath = String(documentStylesFolder.suffix(from: documentAccountsFolder.index(documentStylesFolder.startIndex, offsetBy: 7)))
ArticleStylesManager.shared = ArticleStylesManager(folderPath: documentStylesFolderPath)
ArticleThemesManager.shared = ArticleThemesManager(folderPath: documentStylesFolderPath)
FeedProviderManager.shared.delegate = ExtensionPointManager.shared

View File

@ -543,23 +543,23 @@ private extension WebViewController {
func renderPage(_ webView: PreloadedWebView?) {
guard let webView = webView else { return }
let style = ArticleStylesManager.shared.currentStyle
let theme = ArticleThemesManager.shared.currentTheme
let rendering: ArticleRenderer.Rendering
if let articleExtractor = articleExtractor, articleExtractor.state == .processing {
rendering = ArticleRenderer.loadingHTML(style: style)
rendering = ArticleRenderer.loadingHTML(theme: theme)
} else if let articleExtractor = articleExtractor, articleExtractor.state == .failedToParse, let article = article {
rendering = ArticleRenderer.articleHTML(article: article, style: style)
rendering = ArticleRenderer.articleHTML(article: article, theme: theme)
} else if let article = article, let extractedArticle = extractedArticle {
if isShowingExtractedArticle {
rendering = ArticleRenderer.articleHTML(article: article, extractedArticle: extractedArticle, style: style)
rendering = ArticleRenderer.articleHTML(article: article, extractedArticle: extractedArticle, theme: theme)
} else {
rendering = ArticleRenderer.articleHTML(article: article, style: style)
rendering = ArticleRenderer.articleHTML(article: article, theme: theme)
}
} else if let article = article {
rendering = ArticleRenderer.articleHTML(article: article, style: style)
rendering = ArticleRenderer.articleHTML(article: article, theme: theme)
} else {
rendering = ArticleRenderer.noSelectionHTML(style: style)
rendering = ArticleRenderer.noSelectionHTML(theme: theme)
}
let substitutions = [