Merge branch 'main' into ios-ui-settings-localised

# Conflicts:
#	NetNewsWire.xcodeproj/project.pbxproj
#	Shared/Timer/AccountRefreshTimer.swift
#	iOS/Account/ReaderAPIAccountViewController.swift
This commit is contained in:
Stuart Breckenridge
2023-02-09 10:05:18 +08:00
125 changed files with 995 additions and 2599 deletions

View File

@@ -286,30 +286,6 @@ blockquote {
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;

View File

@@ -20,7 +20,7 @@ struct ArticleTheme: Equatable {
static let defaultTheme = ArticleTheme()
static let nnwThemeSuffix = ".nnwtheme"
private static let defaultThemeName = NSLocalizedString("Default", comment: "Default")
private static let defaultThemeName = "NetNewsWire"
private static let unknownValue = NSLocalizedString("Unknown", comment: "Unknown Value")
let path: String?

View File

@@ -77,6 +77,10 @@ public class ArticleThemeDownloader: Logging {
private func findThemeFile(in searchPath: String) -> String? {
if let directoryContents = FileManager.default.enumerator(atPath: searchPath) {
while let file = directoryContents.nextObject() as? String {
if file.hasPrefix("__MACOSX/") {
logger.debug("Ignoring theme file in __MACOSX folder.")
continue
}
if file.hasSuffix(".nnwtheme") {
return file
}

View File

@@ -80,11 +80,13 @@ final class ArticleThemesManager: NSObject, NSFilePresenter, Logging, Observable
}
func presentedSubitemDidChange(at url: URL) {
themeNames = buildThemeNames()
do {
currentTheme = try articleThemeWithThemeName(currentThemeName)
} catch {
appDelegate.presentThemeImportError(error)
if url.lastPathComponent.localizedCaseInsensitiveContains("nnwtheme") {
themeNames = buildThemeNames()
do {
currentTheme = try articleThemeWithThemeName(currentThemeName)
} catch {
appDelegate.presentThemeImportError(error)
}
}
}

View File

@@ -15,7 +15,6 @@ enum ExtensionPointIdentifer: Hashable {
case marsEdit
case microblog
#endif
case twitter(String)
case reddit(String)
var extensionPointType: ExtensionPoint.Type {
@@ -26,8 +25,6 @@ enum ExtensionPointIdentifer: Hashable {
case .microblog:
return SendToMicroBlogCommand.self
#endif
case .twitter:
return TwitterFeedProvider.self
case .reddit:
return RedditFeedProvider.self
}
@@ -45,11 +42,6 @@ enum ExtensionPointIdentifer: Hashable {
"type": "microblog"
]
#endif
case .twitter(let screenName):
return [
"type": "twitter",
"screenName": screenName
]
case .reddit(let username):
return [
"type": "reddit",
@@ -68,9 +60,6 @@ enum ExtensionPointIdentifer: Hashable {
case "microblog":
self = ExtensionPointIdentifer.microblog
#endif
case "twitter":
guard let screenName = userInfo["screenName"] as? String else { return nil }
self = ExtensionPointIdentifer.twitter(screenName)
case "reddit":
guard let username = userInfo["username"] as? String else { return nil }
self = ExtensionPointIdentifer.reddit(username)
@@ -87,9 +76,6 @@ enum ExtensionPointIdentifer: Hashable {
case .microblog:
hasher.combine("microblog")
#endif
case .twitter(let screenName):
hasher.combine("twitter")
hasher.combine(screenName)
case .reddit(let username):
hasher.combine("reddit")
hasher.combine(username)

View File

@@ -69,16 +69,12 @@ final class ExtensionPointManager: FeedProviderManagerDelegate {
return activeExtensionPoints.values.compactMap({ return $0 as? FeedProvider })
}
var isTwitterEnabled: Bool {
return activeExtensionPoints.values.contains(where: { $0 is TwitterFeedProvider })
}
var isRedditEnabled: Bool {
return activeExtensionPoints.values.contains(where: { $0 is RedditFeedProvider })
}
init() {
possibleExtensionPointTypes = [TwitterFeedProvider.self, RedditFeedProvider.self]
possibleExtensionPointTypes = [RedditFeedProvider.self]
loadExtensionPoints()
}
@@ -121,12 +117,6 @@ private extension ExtensionPointManager {
func extensionPoint(for extensionPointType: ExtensionPoint.Type, tokenSuccess: OAuthSwift.TokenSuccess?, completion: @escaping (Result<ExtensionPoint, Error>) -> Void) {
switch extensionPointType {
case is TwitterFeedProvider.Type:
if let tokenSuccess = tokenSuccess, let twitter = TwitterFeedProvider(tokenSuccess: tokenSuccess) {
completion(.success(twitter))
} else {
completion(.failure(ExtensionPointManagerError.unableToCreate))
}
case is RedditFeedProvider.Type:
if let tokenSuccess = tokenSuccess {
RedditFeedProvider.create(tokenSuccess: tokenSuccess) { result in
@@ -147,8 +137,6 @@ private extension ExtensionPointManager {
func extensionPoint(for extensionPointID: ExtensionPointIdentifer) -> ExtensionPoint? {
switch extensionPointID {
case .twitter(let screenName):
return TwitterFeedProvider(screenName: screenName)
case .reddit(let username):
return RedditFeedProvider(username: username)
#if os(macOS)

View File

@@ -4,6 +4,7 @@
<title>Default Feeds</title>
</head>
<body>
<<<<<<< HEAD
<outline text="Colossal" title="Colossal" type="rss" version="RSS" htmlUrl="https://www.thisiscolossal.com/" xmlUrl="https://www.thisiscolossal.com/feed/"/>
<outline text="Becky Hansmeyer" title="Becky Hansmeyer" type="rss" version="RSS" htmlUrl="https://beckyhansmeyer.com" xmlUrl="https://beckyhansmeyer.com/feed/"/>
<outline text="Maurice Parker" title="Maurice Parker" type="rss" version="RSS" htmlUrl="https://vincode.io/" xmlUrl="https://vincode.io/feed.xml"/>
@@ -21,5 +22,17 @@
<outline text="Craig Hockenberry" title="Craig Hockenberry" type="rss" version="RSS" htmlUrl="https://furbo.org/" xmlUrl="https://furbo.org/feed/json"/>
<outline text="Rose Orchard" title="Rose Orchard" type="rss" version="RSS" htmlUrl="https://rosemaryorchard.com/" xmlUrl="https://rosemaryorchard.com/feed.xml"/>
<outline text="Michael Tsai" title="Michael Tsai" type="rss" version="RSS" htmlUrl="https://mjtsai.com/blog/" xmlUrl="https://mjtsai.com/blog/feed/"/>
=======
<outline text="BBC News - World" title="BBC News - World" type="rss" version="RSS" htmlUrl="https://www.bbc.com/news" xmlUrl="https://feeds.bbci.co.uk/news/world/rss.xml"/>
<outline text="Becky Hansmeyer" title="Becky Hansmeyer" type="rss" version="RSS" htmlUrl="https://beckyhansmeyer.com" xmlUrl="https://beckyhansmeyer.com/feed/"/>
<outline text="Colossal" title="Colossal" type="rss" version="RSS" htmlUrl="https://www.thisiscolossal.com/" xmlUrl="https://www.thisiscolossal.com/feed/"/>
<outline text="Daring Fireball" title="Daring Fireball" type="rss" version="RSS" htmlUrl="https://daringfireball.net/" xmlUrl="https://daringfireball.net/feeds/json"/>
<outline text="inessential" title="inessential" type="rss" version="RSS" htmlUrl="https://inessential.com/" xmlUrl="https://inessential.com/feed.json"/>
<outline text="Jason Kottke" title="Jason Kottke" type="rss" version="RSS" htmlUrl="https://kottke.org/" xmlUrl="http://feeds.kottke.org/json"/>
<outline text="Maurice Parker" title="Maurice Parker" type="rss" version="RSS" htmlUrl="https://vincode.io/" xmlUrl="https://vincode.io/feed.xml"/>
<outline text="NetNewsWire Blog" title="NetNewsWire Blog" type="rss" version="RSS" htmlUrl="https://nnw.ranchero.com/" xmlUrl="https://nnw.ranchero.com/feed.json"/>
<outline text="One Foot Tsunami" title="One Foot Tsunami" type="rss" version="RSS" htmlUrl="https://onefoottsunami.com/" xmlUrl="https://onefoottsunami.com/feed/json/"/>
<outline text="Six Colors" title="Six Colors" type="rss" version="RSS" htmlUrl="https://sixcolors.com/" xmlUrl="https://feedpress.me/sixcolors?type=xml"/>
>>>>>>> ios-release
</body>
</opml>

View File

@@ -3,24 +3,26 @@
body {
margin-left: auto;
margin-right: auto;
word-wrap: break-word;
max-width: 44em;
background-color: #FBF0D9;
color: #704214;
background-color: rgb(248, 241, 227);
color: rgb(79, 50, 28);
}
a {
text-decoration: none;
}
a:hover {
text-decoration: underline;
text-shadow: 0 1px rgba(255, 255, 255, 2);
opacity: 0.8;
}
.feedlink {
font-weight: bold;
}
.headerTable {
width: 100%;
height: 68px;
#nnwImageIcon {
width: 32px;
height: 32px;
margin-right: 0.2em;
}
.systemMessage {
@@ -37,34 +39,48 @@ a:hover {
--header-color: rgba(0, 0, 0, 0.5);
--body-code-color: #704214;
--system-message-color: #704214;
--feedlink-color: rgba(255, 0, 0, 0.6);
--feedlink-color: #704214;
--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;
--primary-accent-color: #43350e;
--secondary-accent-color: #43350e;
--block-quote-border-color: rgba(0, 0, 0, 0.3);
}
body a, body a:visited, body a * {
body a,
body a:visited,
body a * {
color: var(--secondary-accent-color);
}
body .headerTable {
body > header {
border-bottom: 1px solid var(--header-table-border-color);
color: var(--header-color);
padding-bottom: 0.5rem;
}
body .header {
body > header a,
body > header a:link,
body > header a:visited {
color: var(--header-color);
}
body .header a:link, .header a:visited {
body > header .headerTable {
width: 100%;
}
body > header .headerTable td,
body > header .headerTable th {
color: var(--header-color);
padding: 0.2em;
border: none;
font-family: sans-serif;
font-size: 0.9rem;
}
body > header .headerTable td.avatar {
width: 33%;
}
body code, body pre {
body code,
body pre {
color: var(--body-code-color);
}
@@ -72,15 +88,11 @@ body > .systemMessage {
color: var(--system-message-color);
}
.headerContainer a:link, .headerContainer a:visited {
text-decoration: none;
.headerContainer a:link,
.headerContainer a:visited {
color: var(--feedlink-color);
}
.headerContainer a:hover {
text-decoration: underline;
}
.avatar img {
border-radius: 4px;
}
@@ -97,44 +109,35 @@ body > .systemMessage {
text-align: start;
}
.articleTitle a:link, .articleTitle a:visited {
.articleTitle a:link,
.articleTitle a:visited {
text-decoration: none;
color: var(--article-title-color);
margin-top: 26px;
}
.articleTitle a:hover {
text-decoration: underline;
}
.articleDateline {
margin-bottom: 5px;
font-weight: bold;
}
.articleDateline a:link, .articleDateline a:visited {
.articleDateline a:link,
.articleDateline a:visited {
text-decoration: none;
color: var(--article-date-color);
}
.articleDateline a:hover {
text-decoration: underline;
}
.articleDatelineTitle {
margin-bottom: 5px;
font-weight: bold;
}
.articleDatelineTitle a:link, .articleDatelineTitle a:visited {
.articleDatelineTitle a:link,
.articleDatelineTitle a:visited {
text-decoration: none;
color: var(--article-title-color);
}
.articleDatelineTitle a:hover {
text-decoration: underline;
}
.externalLink {
margin-bottom: 5px;
font-style: italic;
@@ -144,14 +147,11 @@ body > .systemMessage {
text-overflow: ellipsis;
}
.externalLink a:link, .externalLink a:visited {
.externalLink a:link,
.externalLink a:visited {
text-decoration: none;
}
.externalLink a:hover {
text-decoration: underline;
}
.articleBody {
margin-top: 20px;
line-height: 1.6em;
@@ -177,17 +177,14 @@ pre {
line-height: 1.4286em;
}
code, pre {
code,
pre {
font-family: "SF Mono", Menlo, "Courier New", Courier, monospace;
font-size: 1em;
font-size: 0.85rem;
letter-spacing: -0.027em;
-webkit-hyphens: none;
}
pre code {
letter-spacing: -.027em;
font-size: 0.9375em;
}
.nnw-overflow {
overflow-x: auto;
}
@@ -209,7 +206,8 @@ pre code {
border: none;
}
.nnw-overflow td, .nnw-overflow th {
.nnw-overflow td,
.nnw-overflow th {
-webkit-hyphens: none;
word-break: normal;
border: 1px solid var(--table-cell-border-color);
@@ -222,7 +220,10 @@ pre code {
border-right: none;
}
.nnw-overflow :matches(thead, tbody, tfoot):last-child > tr:last-child :matches(td, th) {
.nnw-overflow
:matches(thead, tbody, tfoot):last-child
> tr:last-child
:matches(td, th) {
border-bottom: none;
}
@@ -235,16 +236,16 @@ pre code {
border-width: 0;
}
img, figure, video, div, object {
img,
figure,
video,
div,
object {
max-width: 100%;
height: auto !important;
margin: 0 auto;
}
video {
width: 100% !important;
}
iframe {
max-width: 100%;
margin: 0 auto;
@@ -340,12 +341,12 @@ blockquote {
}
.newsfoot-footnote-popover-arrow {
background: #FBF0D9;
background: #fbf0d9;
border: 1px solid #ccc;
}
.newsfoot-footnote-popover-inner {
background: #FBF0D9;
background: #fbf0d9;
}
body a.footnote,
@@ -364,7 +365,6 @@ a.footnote:hover,
/* iOS Specific */
@supports (-webkit-touch-callout: none) {
body {
margin-top: 3px;
margin-bottom: 20px;
@@ -374,7 +374,7 @@ a.footnote:hover,
word-break: break-word;
-webkit-hyphens: auto;
-webkit-text-size-adjust: none;
font: Georgia;
font-family: Charter, Georgia, sans-serif;
font-size: [[font-size]]px;
}
@@ -386,18 +386,16 @@ a.footnote:hover,
.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;
font-family: Charter, Georgia, sans-serif;
}
.smallText {
@@ -428,5 +426,4 @@ a.footnote:hover,
.nnw-overflow table {
border: 1px solid var(--primary-accent-color);
}
}
}

View File

@@ -1,8 +1,8 @@
<header class="headerContainer">
<table cellpadding=0 cellspacing=0 border=0 class="headerTable">
<table class="headerTable">
<tr>
<td class="header leftAlign"><a class="feedlink" href="[[feed_link]]">[[feed_link_title]]</a><br />[[byline]]</td>
<td class="header rightAlign avatar"><img id="nnwImageIcon" src="[[avatar_src]]" height=48 width=48 /></td>
<td class="leftAlign"><a class="feedlink" href="[[feed_link]]">[[feed_link_title]]</a><br />[[byline]]</td>
<td class="rightAlign avatar"><img id="nnwImageIcon" src="[[avatar_src]]" /></td>
</tr>
</table>
</header>

View File

@@ -2,7 +2,15 @@
%{
import os
<<<<<<< HEAD
<<<<<<< HEAD
secrets = ['MERCURY_CLIENT_ID', 'MERCURY_CLIENT_SECRET', 'FEEDLY_CLIENT_ID', 'FEEDLY_CLIENT_SECRET', 'TWITTER_CONSUMER_KEY', 'TWITTER_CONSUMER_SECRET', 'REDDIT_CONSUMER_KEY', 'INOREADER_APP_ID', 'INOREADER_APP_KEY']
=======
secrets = ['FEED_WRANGLER_KEY', 'MERCURY_CLIENT_ID', 'MERCURY_CLIENT_SECRET', 'FEEDLY_CLIENT_ID', 'FEEDLY_CLIENT_SECRET', 'REDDIT_CONSUMER_KEY', 'INOREADER_APP_ID', 'INOREADER_APP_KEY']
>>>>>>> mac-release
=======
secrets = ['FEED_WRANGLER_KEY', 'MERCURY_CLIENT_ID', 'MERCURY_CLIENT_SECRET', 'FEEDLY_CLIENT_ID', 'FEEDLY_CLIENT_SECRET', 'REDDIT_CONSUMER_KEY', 'INOREADER_APP_ID', 'INOREADER_APP_KEY']
>>>>>>> ios-release
def chunks(seq, size):
return (seq[i:(i + size)] for i in range(0, len(seq), size))

View File

@@ -35,5 +35,10 @@ struct SearchFeedDelegate: SmartFeedDelegate {
func fetchUnreadCount(for: Account, completion: @escaping SingleUnreadCountCompletionBlock) {
// TODO: after 5.0
}
func fetchUnreadArticlesBetween(before: Date? = nil, after: Date? = nil) throws -> Set<Article> {
fatalError("Function not implemented.")
}
}

View File

@@ -35,4 +35,9 @@ struct SearchTimelineFeedDelegate: SmartFeedDelegate {
func fetchUnreadCount(for: Account, completion: @escaping SingleUnreadCountCompletionBlock) {
// TODO: after 5.0
}
func fetchUnreadArticlesBetween(before: Date? = nil, after: Date? = nil) throws -> Set<Article> {
fatalError("Function not implemented.")
}
}

View File

@@ -46,7 +46,7 @@ final class SmartFeed: PseudoFeed {
}
#endif
private let delegate: SmartFeedDelegate
public let delegate: SmartFeedDelegate
private var unreadCounts = [String: Int]()
init(delegate: SmartFeedDelegate) {
@@ -95,6 +95,10 @@ extension SmartFeed: ArticleFetcher {
return try delegate.fetchUnreadArticles()
}
func fetchUnreadArticlesBetween(before: Date? = nil, after: Date? = nil) throws -> Set<Article> {
return try delegate.fetchUnreadArticlesBetween(before: before, after: after)
}
func fetchUnreadArticlesAsync(_ completion: @escaping ArticleSetResultBlock) {
delegate.fetchUnreadArticlesAsync(completion)
}

View File

@@ -31,6 +31,10 @@ extension SmartFeedDelegate {
return try fetchArticles().unreadArticles()
}
func fetchUnreadArticlesBetween(before: Date? = nil, after: Date? = nil) throws -> Set<Article> {
return try AccountManager.shared.fetchUnreadArticlesBetween(limit: nil, before: before, after: after)
}
func fetchUnreadArticlesAsync(_ completion: @escaping ArticleSetResultBlock) {
fetchArticlesAsync{ articleSetResult in
switch articleSetResult {

View File

@@ -29,4 +29,8 @@ struct StarredFeedDelegate: SmartFeedDelegate {
func fetchUnreadCount(for account: Account, completion: @escaping SingleUnreadCountCompletionBlock) {
account.fetchUnreadCountForStarredArticles(completion)
}
func fetchUnreadArticlesBetween(before: Date? = nil, after: Date? = nil) throws -> Set<Article> {
return try AccountManager.shared.fetchUnreadArticlesBetween(limit: nil, before: before, after: after).filter({ $0.status.starred })
}
}

View File

@@ -78,6 +78,10 @@ extension UnreadFeed: ArticleFetcher {
return try AccountManager.shared.fetchArticles(fetchType)
}
func fetchUnreadArticlesBetween(before: Date? = nil, after: Date? = nil) throws -> Set<Article> {
return try AccountManager.shared.fetchUnreadArticlesBetween(limit: nil, before: before, after: after)
}
func fetchUnreadArticlesAsync(_ completion: @escaping ArticleSetResultBlock) {
AccountManager.shared.fetchArticlesAsync(fetchType, completion)
}

View File

@@ -69,7 +69,7 @@ public final class WidgetDataEncoder {
@available(iOS 14, *)
private func encodeWidgetData(completion: @escaping (WidgetData?) -> Void) {
var dispatchGroup = DispatchGroup()
let dispatchGroup = DispatchGroup()
var groupError: Error? = nil
var unread = [LatestArticle]()
@@ -143,9 +143,9 @@ public final class WidgetDataEncoder {
let latestData = WidgetData(currentUnreadCount: SmartFeedsController.shared.unreadFeed.unreadCount,
currentTodayCount: SmartFeedsController.shared.todayFeed.unreadCount,
currentStarredCount: (try? AccountManager.shared.fetchCountForStarredArticles()) ?? 0,
unreadArticles: unread,
starredArticles: starred,
todayArticles:today,
unreadArticles: unread.sorted(by: { $0.pubDate > $1.pubDate }),
starredArticles: starred.sorted(by: { $0.pubDate > $1.pubDate }),
todayArticles:today.sorted(by: { $0.pubDate > $1.pubDate }),
lastUpdateTime: Date())
completion(latestData)
}