Move images-related code into Images module.

This commit is contained in:
Brent Simmons 2024-04-15 22:21:17 -07:00
parent 553b57c09d
commit 1368f3dace
51 changed files with 343 additions and 259 deletions

View File

@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1530"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "Images"
BuildableName = "Images"
BlueprintName = "Images"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "Images"
BuildableName = "Images"
BlueprintName = "Images"
ReferencedContainer = "container:">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -10,11 +10,18 @@ let package = Package(
name: "Images",
targets: ["Images"]),
],
dependencies: [
.package(path: "../Core"),
.package(path: "../Articles"),
.package(path: "../Account")
],
targets: [
.target(
name: "Images",
dependencies: [
"Core",
"Articles",
"Account"
],
swiftSettings: [
.enableExperimentalFeature("StrictConcurrency")

View File

@ -10,28 +10,28 @@ import Foundation
import Articles
import Core
extension Notification.Name {
public extension Notification.Name {
static let AvatarDidBecomeAvailable = Notification.Name("AvatarDidBecomeAvailableNotification") // UserInfoKey.imageURL (which is an avatarURL)
}
@MainActor final class AuthorAvatarDownloader {
@MainActor public final class AuthorAvatarDownloader {
private let imageDownloader: ImageDownloader
private var cache = [String: IconImage]() // avatarURL: RSImage
private var waitingForAvatarURLs = Set<String>()
init(imageDownloader: ImageDownloader) {
public init(imageDownloader: ImageDownloader) {
self.imageDownloader = imageDownloader
NotificationCenter.default.addObserver(self, selector: #selector(imageDidBecomeAvailable(_:)), name: .ImageDidBecomeAvailable, object: imageDownloader)
}
func resetCache() {
public func resetCache() {
cache = [String: IconImage]()
}
func image(for author: Author) -> IconImage? {
public func image(for author: Author) -> IconImage? {
guard let avatarURL = author.avatarURL else {
return nil
@ -52,7 +52,7 @@ extension Notification.Name {
}
@objc func imageDidBecomeAvailable(_ note: Notification) {
guard let avatarURL = note.userInfo?[UserInfoKey.url] as? String else {
guard let avatarURL = note.userInfo?[ImageDownloader.imageURLKey] as? String else {
return
}
guard waitingForAvatarURLs.contains(avatarURL) else {
@ -88,7 +88,7 @@ private extension AuthorAvatarDownloader {
func postAvatarDidBecomeAvailableNotification(_ avatarURL: String) {
DispatchQueue.main.async {
NotificationCenter.default.post(name: .AvatarDidBecomeAvailable, object: self, userInfo: [UserInfoKey.url: avatarURL])
NotificationCenter.default.post(name: .AvatarDidBecomeAvailable, object: self, userInfo: [ImageDownloader.imageURLKey: avatarURL])
}
}
}

View File

@ -12,12 +12,20 @@ import Articles
import Account
import UniformTypeIdentifiers
import Core
import ParserObjC
extension Notification.Name {
public extension Notification.Name {
static let FaviconDidBecomeAvailable = Notification.Name("FaviconDidBecomeAvailableNotification") // userInfo key: FaviconDownloader.UserInfoKey.faviconURL
}
@MainActor final class FaviconDownloader {
public protocol FaviconDownloaderDelegate {
var appIconImage: IconImage? { get }
func downloadMetadata(_ url: String) async throws -> RSHTMLMetadata?
}
@MainActor public final class FaviconDownloader {
private static let saveQueue = CoalescingQueue(name: "Cache Save Queue", interval: 1.0)
@ -46,11 +54,13 @@ extension Notification.Name {
private let queue: DispatchQueue
private var cache = [Feed: IconImage]() // faviconURL: RSImage
public var delegate: FaviconDownloaderDelegate?
struct UserInfoKey {
static let faviconURL = "faviconURL"
}
init(folder: String) {
public init(folder: String) {
self.folder = folder
self.diskCache = BinaryDiskCache(folder: folder)
@ -70,7 +80,7 @@ extension Notification.Name {
cache = [Feed: IconImage]()
}
func favicon(for feed: Feed) -> IconImage? {
public func favicon(for feed: Feed) -> IconImage? {
assert(Thread.isMainThread)
@ -92,8 +102,8 @@ extension Notification.Name {
return nil
}
func faviconAsIcon(for feed: Feed) -> IconImage? {
public func faviconAsIcon(for feed: Feed) -> IconImage? {
if let image = cache[feed] {
return image
}
@ -120,7 +130,7 @@ extension Notification.Name {
if let url = URL(string: homePageURL) {
if url.host == "nnw.ranchero.com" || url.host == "netnewswire.blog" {
return IconImage.appIcon
return delegate?.appIconImage
}
}
@ -203,7 +213,7 @@ private extension FaviconDownloader {
guard let url = URL(unicodeString: homePageURL) else {
return nil
}
guard let faviconURLs = await FaviconURLFinder.findFaviconURLs(with: homePageURL) else {
guard let faviconURLs = await FaviconURLFinder.findFaviconURLs(with: homePageURL, downloadMetadata: delegate!.downloadMetadata(_:)) else {
return nil
}

View File

@ -8,27 +8,26 @@
import Foundation
import Account
import Images
import Core
@MainActor final class FaviconGenerator {
@MainActor public final class FaviconGenerator {
private static var faviconGeneratorCache = [String: IconImage]() // feedURL: RSImage
public static var faviconTemplateImage: RSImage! // Must be set at startup
static func favicon(_ feed: Feed) -> IconImage {
public static func favicon(_ feed: Feed) -> IconImage {
if let favicon = FaviconGenerator.faviconGeneratorCache[feed.url] {
return favicon
}
let colorHash = ColorHash(feed.url)
if let favicon = AppAssets.faviconTemplateImage.maskWithColor(color: colorHash.color.cgColor) {
if let favicon = faviconTemplateImage.maskWithColor(color: colorHash.color.cgColor) {
let iconImage = IconImage(favicon, isBackgroundSupressed: true)
FaviconGenerator.faviconGeneratorCache[feed.url] = iconImage
return iconImage
} else {
return IconImage(AppAssets.faviconTemplateImage, isBackgroundSupressed: true)
return IconImage(faviconTemplateImage, isBackgroundSupressed: true)
}
}
}

View File

@ -22,14 +22,14 @@ import UniformTypeIdentifiers
/// - Parameters:
/// - homePageURL: The page to search.
/// - urls: An array of favicon URLs as strings.
static func findFaviconURLs(with homePageURL: String) async -> [String]? {
static func findFaviconURLs(with homePageURL: String, downloadMetadata: ((String) async throws -> RSHTMLMetadata?)) async -> [String]? {
guard let _ = URL(unicodeString: homePageURL) else {
return nil
}
// If the favicon has an explicit type, check that for an ignored type; otherwise, check the file extension.
let htmlMetadata = try? await HTMLMetadataDownloader.downloadMetadata(for: homePageURL)
let htmlMetadata = try? await downloadMetadata(homePageURL)
let faviconURLs = htmlMetadata?.favicons.compactMap { favicon -> String? in
shouldAllowFavicon(favicon) ? favicon.urlString : nil

View File

@ -0,0 +1,100 @@
////
//// FeaturedImageDownloader.swift
//// NetNewsWire
////
//// Created by Brent Simmons on 11/26/17.
//// Copyright © 2017 Ranchero Software. All rights reserved.
////
//
//import Foundation
//import Articles
//import Parser
//import Core
//
//final class FeaturedImageDownloader {
//
// private let imageDownloader: ImageDownloader
// private var articleURLToFeaturedImageURLCache = [String: String]()
// private var articleURLsWithNoFeaturedImage = Set<String>()
// private var urlsInProgress = Set<String>()
//
// init(imageDownloader: ImageDownloader) {
//
// self.imageDownloader = imageDownloader
// }
//
// func image(for article: Article) -> RSImage? {
//
// if let imageLink = article.imageLink {
// return image(forFeaturedImageURL: imageLink)
// }
// if let link = article.link {
// return image(forArticleURL: link)
// }
// return nil
// }
//
// func image(forArticleURL articleURL: String) -> RSImage? {
//
// if articleURLsWithNoFeaturedImage.contains(articleURL) {
// return nil
// }
//
// if let featuredImageURL = cachedURL(for: articleURL) {
// return image(forFeaturedImageURL: featuredImageURL)
// }
// findFeaturedImageURL(for: articleURL)
// return nil
// }
//
// func image(forFeaturedImageURL featuredImageURL: String) -> RSImage? {
// if let data = imageDownloader.image(for: featuredImageURL) {
// return RSImage(data: data)
// }
// return nil
// }
//
//}
//
//private extension FeaturedImageDownloader {
//
// func cachedURL(for articleURL: String) -> String? {
//
// return articleURLToFeaturedImageURLCache[articleURL]
// }
//
// func cacheURL(for articleURL: String, _ featuredImageURL: String) {
//
// articleURLsWithNoFeaturedImage.remove(articleURL)
// articleURLToFeaturedImageURLCache[articleURL] = featuredImageURL
// }
//
// func findFeaturedImageURL(for articleURL: String) {
//
// guard !urlsInProgress.contains(articleURL) else {
// return
// }
// urlsInProgress.insert(articleURL)
//
// HTMLMetadataDownloader.downloadMetadata(for: articleURL) { (metadata) in
//
// self.urlsInProgress.remove(articleURL)
//
// guard let metadata = metadata else {
// return
// }
// self.pullFeaturedImageURL(from: metadata, articleURL: articleURL)
// }
// }
//
// func pullFeaturedImageURL(from metadata: RSHTMLMetadata, articleURL: String) {
//
// if let url = metadata.bestFeaturedImageURL() {
// cacheURL(for: articleURL, url)
// let _ = image(forFeaturedImageURL: url)
// return
// }
//
// articleURLsWithNoFeaturedImage.insert(articleURL)
// }
//}

View File

@ -13,13 +13,22 @@ import Web
import Parser
import Core
extension Notification.Name {
public extension Notification.Name {
static let FeedIconDidBecomeAvailable = Notification.Name("FeedIconDidBecomeAvailableNotification") // UserInfoKey.feed
}
public protocol FeedIconDownloaderDelegate {
var appIconImage: IconImage? { get }
func downloadMetadata(_ url: String) async throws -> RSHTMLMetadata?
}
@MainActor public final class FeedIconDownloader {
public static let feedKey = "url"
private static let saveQueue = CoalescingQueue(name: "Cache Save Queue", interval: 1.0)
private let imageDownloader: ImageDownloader
@ -56,7 +65,9 @@ extension Notification.Name {
private var cache = [Feed: IconImage]()
private var waitingForFeedURLs = [String: Feed]()
init(imageDownloader: ImageDownloader, folder: String) {
public var delegate: FeedIconDownloaderDelegate?
public init(imageDownloader: ImageDownloader, folder: String) {
self.imageDownloader = imageDownloader
self.feedURLToIconURLCachePath = (folder as NSString).appendingPathComponent("FeedURLToIconURLCache.plist")
self.homePageToIconURLCachePath = (folder as NSString).appendingPathComponent("HomePageToIconURLCache.plist")
@ -71,14 +82,14 @@ extension Notification.Name {
cache = [Feed: IconImage]()
}
func icon(for feed: Feed) -> IconImage? {
public func icon(for feed: Feed) -> IconImage? {
if let cachedImage = cache[feed] {
return cachedImage
}
if let hpURLString = feed.homePageURL, let hpURL = URL(string: hpURLString), (hpURL.host == "nnw.ranchero.com" || hpURL.host == "netnewswire.blog") {
return IconImage.appIcon
return delegate?.appIconImage
}
@MainActor func checkHomePageURL() {
@ -87,7 +98,7 @@ extension Notification.Name {
}
icon(forHomePageURL: homePageURL, feed: feed) { (image) in
Task { @MainActor in
if let image = image {
if let image {
self.postFeedIconDidBecomeAvailableNotification(feed)
self.cache[feed] = IconImage(image)
}
@ -99,7 +110,7 @@ extension Notification.Name {
if let iconURL = feed.iconURL {
icon(forURL: iconURL, feed: feed) { (image) in
Task { @MainActor in
if let image = image {
if let image {
self.postFeedIconDidBecomeAvailableNotification(feed)
self.cache[feed] = IconImage(image)
} else {
@ -130,7 +141,7 @@ extension Notification.Name {
}
@objc func imageDidBecomeAvailable(_ note: Notification) {
guard let url = note.userInfo?[UserInfoKey.url] as? String, let feed = waitingForFeedURLs[url] else {
guard let url = note.userInfo?[ImageDownloader.imageURLKey] as? String, let feed = waitingForFeedURLs[url] else {
return
}
waitingForFeedURLs[url] = nil
@ -171,7 +182,7 @@ private extension FeedIconDownloader {
return
}
findIconURLForHomePageURL(homePageURL, feed: feed)
findIconURLForHomePageURL(homePageURL, feed: feed, downloadMetadata: delegate!.downloadMetadata(_:))
}
func icon(forURL url: String, feed: Feed, _ imageResultBlock: @Sendable @escaping (RSImage?) -> Void) {
@ -190,7 +201,7 @@ private extension FeedIconDownloader {
func postFeedIconDidBecomeAvailableNotification(_ feed: Feed) {
DispatchQueue.main.async {
let userInfo: [AnyHashable: Any] = [UserInfoKey.feed: feed]
let userInfo: [AnyHashable: Any] = [Self.feedKey: feed]
NotificationCenter.default.post(name: .FeedIconDidBecomeAvailable, object: self, userInfo: userInfo)
}
}
@ -207,7 +218,7 @@ private extension FeedIconDownloader {
homePageToIconURLCacheDirty = true
}
func findIconURLForHomePageURL(_ homePageURL: String, feed: Feed) {
func findIconURLForHomePageURL(_ homePageURL: String, feed: Feed, downloadMetadata: @escaping (String) async throws -> RSHTMLMetadata?) {
guard !urlsInProgress.contains(homePageURL) else {
return
@ -216,7 +227,7 @@ private extension FeedIconDownloader {
Task { @MainActor in
let metadata = try? await HTMLMetadataDownloader.downloadMetadata(for: homePageURL)
let metadata = try? await downloadMetadata(homePageURL)
self.urlsInProgress.remove(homePageURL)
guard let metadata else {

View File

@ -13,22 +13,22 @@ import UIKit
#endif
import Core
final class IconImage {
public final class IconImage {
lazy var isDark: Bool = {
public lazy var isDark: Bool = {
return image.isDark()
}()
lazy var isBright: Bool = {
public lazy var isBright: Bool = {
return image.isBright()
}()
let image: RSImage
let isSymbol: Bool
let isBackgroundSupressed: Bool
let preferredColor: CGColor?
public let image: RSImage
public let isSymbol: Bool
public let isBackgroundSupressed: Bool
public let preferredColor: CGColor?
init(_ image: RSImage, isSymbol: Bool = false, isBackgroundSupressed: Bool = false, preferredColor: CGColor? = nil) {
public init(_ image: RSImage, isSymbol: Bool = false, isBackgroundSupressed: Bool = false, preferredColor: CGColor? = nil) {
self.image = image
self.isSymbol = isSymbol
self.preferredColor = preferredColor
@ -161,7 +161,7 @@ extension CGImage {
}
enum IconSize: Int, CaseIterable {
public enum IconSize: Int, CaseIterable {
case small = 1
case medium = 2
case large = 3
@ -170,7 +170,7 @@ enum IconSize: Int, CaseIterable {
private static let mediumDimension = CGFloat(integerLiteral: 36)
private static let largeDimension = CGFloat(integerLiteral: 48)
var size: CGSize {
public var size: CGSize {
switch self {
case .small:
return CGSize(width: IconSize.smallDimension, height: IconSize.smallDimension)

View File

@ -12,12 +12,14 @@ import Web
import FoundationExtras
import Core
extension Notification.Name {
public extension Notification.Name {
static let ImageDidBecomeAvailable = Notification.Name("ImageDidBecomeAvailableNotification") // UserInfoKey.url
}
final class ImageDownloader {
public final class ImageDownloader {
public static let imageURLKey = "url"
private var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "ImageDownloader")
@ -28,7 +30,7 @@ final class ImageDownloader {
private var urlsInProgress = Set<String>()
private var badURLs = Set<String>() // That return a 404 or whatever. Just skip them in the future.
init(folder: String) {
public init(folder: String) {
self.folder = folder
self.diskCache = BinaryDiskCache(folder: folder)
@ -36,7 +38,7 @@ final class ImageDownloader {
}
@discardableResult
func image(for url: String) -> Data? {
public func image(for url: String) -> Data? {
if let data = imageCache[url] {
return data
@ -140,7 +142,7 @@ private extension ImageDownloader {
func postImageDidBecomeAvailableNotification(_ url: String) {
DispatchQueue.main.async {
NotificationCenter.default.post(name: .ImageDidBecomeAvailable, object: self, userInfo: [UserInfoKey.url: url])
NotificationCenter.default.post(name: .ImageDidBecomeAvailable, object: self, userInfo: [Self.imageURLKey: url])
}
}
}

View File

@ -8,6 +8,7 @@
import AppKit
import Account
import Images
struct AppAssets {

View File

@ -17,6 +17,7 @@ import Secrets
import OSLog
import Core
import CrashReporter
import Images
// If we're not going to import Sparkle, provide dummy protocols to make it easy
// for AppDelegate to comply
@ -170,7 +171,9 @@ import Sparkle
let faviconsFolder = (cacheFolder as NSString).appendingPathComponent("Favicons")
let faviconsFolderURL = URL(fileURLWithPath: faviconsFolder)
try! FileManager.default.createDirectory(at: faviconsFolderURL, withIntermediateDirectories: true, attributes: nil)
faviconDownloader = FaviconDownloader(folder: faviconsFolder)
faviconDownloader.delegate = self
let imagesFolder = (cacheFolder as NSString).appendingPathComponent("Images")
let imagesFolderURL = URL(fileURLWithPath: imagesFolder)
@ -179,7 +182,8 @@ import Sparkle
authorAvatarDownloader = AuthorAvatarDownloader(imageDownloader: imageDownloader)
feedIconDownloader = FeedIconDownloader(imageDownloader: imageDownloader, folder: cacheFolder)
feedIconDownloader.delegate = self
appName = (Bundle.main.infoDictionary!["CFBundleExecutable"]! as! String)
}
@ -206,6 +210,9 @@ import Sparkle
if isFirstRun {
os_log(.debug, "Is first run.")
}
FaviconGenerator.faviconTemplateImage = AppAssets.faviconTemplateImage
let localAccount = accountManager.defaultAccount
if isFirstRun && !accountManager.anyAccountHasAtLeastOneFeed() {

View File

@ -7,6 +7,7 @@
//
import AppKit
import Images
final class IconView: NSView {

View File

@ -9,6 +9,7 @@
import Foundation
import Account
import Tree
import Images
class SidebarCell : NSTableCellView {

View File

@ -11,6 +11,7 @@ import Tree
import Articles
import Account
import Core
import Images
extension Notification.Name {
static let appleSideBarDefaultIconSizeChanged = Notification.Name("AppleSideBarDefaultIconSizeChanged")
@ -198,7 +199,7 @@ protocol SidebarDelegate: AnyObject {
}
@objc func feedIconDidBecomeAvailable(_ note: Notification) {
guard let feed = note.userInfo?[UserInfoKey.feed] as? Feed else { return }
guard let feed = note.userInfo?[FeedIconDownloader.feedKey] as? Feed else { return }
configureCellsForRepresentedObject(feed)
}

View File

@ -8,6 +8,7 @@
import AppKit
import Articles
import Images
@MainActor struct TimelineCellData {

View File

@ -11,6 +11,7 @@ import Articles
import Account
import os.log
import Core
import Images
protocol TimelineDelegate: AnyObject {
@ -601,7 +602,7 @@ final class TimelineViewController: NSViewController, UndoableCommandRunner, Unr
}
@objc func feedIconDidBecomeAvailable(_ note: Notification) {
guard showIcons, let feed = note.userInfo?[UserInfoKey.feed] as? Feed else {
guard showIcons, let feed = note.userInfo?[FeedIconDownloader.feedKey] as? Feed else {
return
}
let indexesToReload = tableView.indexesOfAvailableRowsPassingTest { (row) -> Bool in

View File

@ -93,7 +93,6 @@
510FFAB326EEA22C00F32265 /* ArticleThemesTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510FFAB226EEA22C00F32265 /* ArticleThemesTableViewController.swift */; };
51102165233A7D6C0007A5F7 /* ArticleExtractorButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51102164233A7D6C0007A5F7 /* ArticleExtractorButton.swift */; };
5110C37D2373A8D100A9C04F /* InspectorIconHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5110C37C2373A8D100A9C04F /* InspectorIconHeaderView.swift */; };
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 */; };
511B148924E5DBDD00C919BD /* Account in Frameworks */ = {isa = PBXBuildFile; productRef = 511B148824E5DBDD00C919BD /* Account */; };
@ -169,7 +168,6 @@
5148F4552336DB7000F8CD8B /* TimelineTitleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5148F4542336DB7000F8CD8B /* TimelineTitleView.swift */; };
514B7C8323205EFB00BAC947 /* RootSplitViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 514B7C8223205EFB00BAC947 /* RootSplitViewController.swift */; };
514C16CE24D2E63F009A3AFA /* Account in Frameworks */ = {isa = PBXBuildFile; productRef = 514C16CD24D2E63F009A3AFA /* Account */; };
5154368B229404D1005E1CDF /* FaviconGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51EF0F76227716200050506E /* FaviconGenerator.swift */; };
515D4FCA23257CB500EE1167 /* Node-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97971ED9EFAA007D329B /* Node-Extensions.swift */; };
515D4FCC2325815A00EE1167 /* SafariExt.js in Resources */ = {isa = PBXBuildFile; fileRef = 515D4FCB2325815A00EE1167 /* SafariExt.js */; };
516244E3241E19F000B61C47 /* ColorPaletteTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 516244E2241E19F000B61C47 /* ColorPaletteTableViewController.swift */; };
@ -182,8 +180,6 @@
516A09402361240900EAE89B /* Account.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 516A093F2361240900EAE89B /* Account.storyboard */; };
516A09422361248000EAE89B /* Inspector.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 516A09412361248000EAE89B /* Inspector.storyboard */; };
516AE9B32371C372007DEEAA /* FeedTableViewSectionHeaderLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 516AE9B22371C372007DEEAA /* FeedTableViewSectionHeaderLayout.swift */; };
516AE9DF2372269A007DEEAA /* IconImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 516AE9DE2372269A007DEEAA /* IconImage.swift */; };
516AE9E02372269A007DEEAA /* IconImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 516AE9DE2372269A007DEEAA /* IconImage.swift */; };
516B695F24D2F33B00B5702F /* Account in Frameworks */ = {isa = PBXBuildFile; productRef = 516B695E24D2F33B00B5702F /* Account */; };
51707439232AA97100A461A3 /* ShareFolderPickerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51707438232AA97100A461A3 /* ShareFolderPickerController.swift */; };
517630042336215100E15FFF /* main.js in Resources */ = {isa = PBXBuildFile; fileRef = 517630032336215100E15FFF /* main.js */; };
@ -226,7 +222,6 @@
51A9A5E82380CA130033AADF /* ShareFolderPickerCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A9A5E72380CA130033AADF /* ShareFolderPickerCell.swift */; };
51A9A5ED2380D6000033AADF /* AppAssets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C45254226507D200C03939 /* AppAssets.swift */; };
51A9A5EE2380D6080033AADF /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 84C9FC9B2262A1A900D921D6 /* Assets.xcassets */; };
51A9A5EF2380D63B0033AADF /* IconImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 516AE9DE2372269A007DEEAA /* IconImage.swift */; };
51A9A5F22380DE520033AADF /* AddFeedDefaultContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A66684238075AE00CB272D /* AddFeedDefaultContainer.swift */; };
51A9A5F32380DE530033AADF /* AddFeedDefaultContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A66684238075AE00CB272D /* AddFeedDefaultContainer.swift */; };
51A9A5F52380F6A60033AADF /* ModalNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51A9A5F42380F6A60033AADF /* ModalNavigationController.swift */; };
@ -284,18 +279,10 @@
51C45297226509E300C03939 /* DefaultFeedsImporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97591ED9EB0D007D329B /* DefaultFeedsImporter.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 */; };
51C4529E22650A1900C03939 /* ImageDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845213221FCA5B10003B6E93 /* ImageDownloader.swift */; };
51C4529F22650A1900C03939 /* AuthorAvatarDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E850851FCB60CE0072EA88 /* AuthorAvatarDownloader.swift */; };
51C452A022650A1900C03939 /* FeedIconDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842611891FCB67AA0086A189 /* FeedIconDownloader.swift */; };
51C452A222650A1900C03939 /* RSHTMLMetadata+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842611A11FCB769D0086A189 /* RSHTMLMetadata+Extension.swift */; };
51C452A322650A1E00C03939 /* HTMLMetadataDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8426119D1FCB6ED40086A189 /* HTMLMetadataDownloader.swift */; };
51C452A422650A2D00C03939 /* ArticleUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97581ED9EB0D007D329B /* ArticleUtilities.swift */; };
51C452A522650A2D00C03939 /* SmallIconProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84411E701FE5FBFA004B527F /* SmallIconProvider.swift */; };
51C452A622650A3500C03939 /* Node-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97971ED9EFAA007D329B /* Node-Extensions.swift */; };
51C452A722650A3D00C03939 /* RSImage-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51126DA3225FDE2F00722696 /* RSImage-Extensions.swift */; };
51C452A922650DC600C03939 /* ArticleRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A977D1ED9EC42007D329B /* ArticleRenderer.swift */; };
51C452AB22650DC600C03939 /* template.html in Resources */ = {isa = PBXBuildFile; fileRef = 848362FE2262A30E00DA1D35 /* template.html */; };
51C452AC22650FD200C03939 /* AppNotifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842E45CD1ED8C308000A8B52 /* AppNotifications.swift */; };
@ -336,7 +323,6 @@
51E595A6228CC36500FCC42B /* ArticleStatusSyncTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51E595A4228CC36500FCC42B /* ArticleStatusSyncTimer.swift */; };
51EAED96231363EF00A9EEE3 /* NonIntrinsicButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51EAED95231363EF00A9EEE3 /* NonIntrinsicButton.swift */; };
51EC114C2149FE3300B296E3 /* FolderTreeMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51EC114B2149FE3300B296E3 /* FolderTreeMenu.swift */; };
51EF0F77227716200050506E /* FaviconGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51EF0F76227716200050506E /* FaviconGenerator.swift */; };
51EF0F7E2277A57D0050506E /* TimelineAccessibilityCellLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51EF0F7D2277A57D0050506E /* TimelineAccessibilityCellLayout.swift */; };
51EF0F802277A8330050506E /* TimelineCellLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51EF0F7F2277A8330050506E /* TimelineCellLayout.swift */; };
51EFDA1B24E6D16A0085C3D6 /* SafariExt.js in Resources */ = {isa = PBXBuildFile; fileRef = 515D4FCB2325815A00EE1167 /* SafariExt.js */; };
@ -365,7 +351,6 @@
653813182680E152007A082C /* AccountType+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 173A64162547BE0900267F6E /* AccountType+Helpers.swift */; };
653813192680E15B007A082C /* CacheCleaner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5108F6B52375E612001ABC45 /* CacheCleaner.swift */; };
6538131A2680E16C007A082C /* ExportOPMLWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849C78912362AB04009A71E4 /* ExportOPMLWindowController.swift */; };
6538131B2680E176007A082C /* IconImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 516AE9DE2372269A007DEEAA /* IconImage.swift */; };
6538131C2680E17F007A082C /* UserInfoKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 511B9805237DCAC90028BCAA /* UserInfoKey.swift */; };
6538131E2680E1CA007A082C /* Account in Frameworks */ = {isa = PBXBuildFile; productRef = 6538131D2680E1CA007A082C /* Account */; };
6538131F2680E1CA007A082C /* Account in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 6538131D2680E1CA007A082C /* Account */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
@ -432,7 +417,6 @@
65ED3FDC235DEF6C0081F399 /* UnreadCountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97631ED9EB96007D329B /* UnreadCountView.swift */; };
65ED3FDD235DEF6C0081F399 /* ActivityType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51D87EE02311D34700E63F03 /* ActivityType.swift */; };
65ED3FDE235DEF6C0081F399 /* CrashReportWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840BEE4021D70E64009BBAFA /* CrashReportWindowController.swift */; };
65ED3FDF235DEF6C0081F399 /* FeedIconDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842611891FCB67AA0086A189 /* FeedIconDownloader.swift */; };
65ED3FE0235DEF6C0081F399 /* PreferencesControlsBackgroundView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84C9FC7122629E1200D921D6 /* PreferencesControlsBackgroundView.swift */; };
65ED3FE1235DEF6C0081F399 /* MarkCommandValidationStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84162A142038C12C00035290 /* MarkCommandValidationStatus.swift */; };
65ED3FE2235DEF6C0081F399 /* ArticlePasteboardWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E95D231FB1087500552D99 /* ArticlePasteboardWriter.swift */; };
@ -442,9 +426,7 @@
65ED3FE6235DEF6C0081F399 /* RenameWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A37CB4201ECD610087C5AF /* RenameWindowController.swift */; };
65ED3FE7235DEF6C0081F399 /* SendToMicroBlogCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A14FF220048CA70046AD9A /* SendToMicroBlogCommand.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 */; };
@ -466,7 +448,6 @@
65ED3FFF235DEF6C0081F399 /* SidebarOutlineDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84AD1EBB2032AF5C00BC20B7 /* SidebarOutlineDataSource.swift */; };
65ED4000235DEF6C0081F399 /* SidebarCellAppearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845A29231FC9255E007B49E3 /* SidebarCellAppearance.swift */; };
65ED4001235DEF6C0081F399 /* StarredFeedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845EE7B01FC2366500854A1F /* StarredFeedDelegate.swift */; };
65ED4002235DEF6C0081F399 /* FaviconDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848F6AE41FC29CFA002D422E /* FaviconDownloader.swift */; };
65ED4003235DEF6C0081F399 /* AdvancedPreferencesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84C9FC6B22629E1200D921D6 /* AdvancedPreferencesViewController.swift */; };
65ED4004235DEF6C0081F399 /* SharingServicePickerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849EE72020391F560082A1EA /* SharingServicePickerDelegate.swift */; };
65ED4005235DEF6C0081F399 /* Node-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97971ED9EFAA007D329B /* Node-Extensions.swift */; };
@ -478,7 +459,6 @@
65ED400B235DEF6C0081F399 /* TodayFeedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F2D5361FC22FCB00998D64 /* TodayFeedDelegate.swift */; };
65ED400C235DEF6C0081F399 /* FolderInspectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 841ABA5D20145E9200980E11 /* FolderInspectorViewController.swift */; };
65ED400D235DEF6C0081F399 /* SmartFeedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84DEE56422C32CA4005FC42C /* SmartFeedDelegate.swift */; };
65ED400E235DEF6C0081F399 /* ImageDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845213221FCA5B10003B6E93 /* ImageDownloader.swift */; };
65ED400F235DEF6C0081F399 /* LegacyArticleExtractorButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51FA73B62332D5F70090D516 /* LegacyArticleExtractorButton.swift */; };
65ED4011235DEF6C0081F399 /* AddFolderWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97421ED9EAA9007D329B /* AddFolderWindowController.swift */; };
65ED4012235DEF6C0081F399 /* TimelineContainerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8405DDA422168C62008CE1BF /* TimelineContainerViewController.swift */; };
@ -491,7 +471,6 @@
65ED4019235DEF6C0081F399 /* FetchRequestOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CAFCAE22BC8C35007694F0 /* FetchRequestOperation.swift */; };
65ED401A235DEF6C0081F399 /* HTMLMetadataDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8426119D1FCB6ED40086A189 /* HTMLMetadataDownloader.swift */; };
65ED401B235DEF6C0081F399 /* TimelineViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A976B1ED9EBC8007D329B /* TimelineViewController.swift */; };
65ED401C235DEF6C0081F399 /* FaviconGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51EF0F76227716200050506E /* FaviconGenerator.swift */; };
65ED401D235DEF6C0081F399 /* RefreshInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5183CCE4226F4DFA0010922C /* RefreshInterval.swift */; };
65ED401E235DEF6C0081F399 /* TimelineCellData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97711ED9EC04007D329B /* TimelineCellData.swift */; };
65ED401F235DEF6C0081F399 /* BuiltinSmartFeedInspectorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 841ABA5F20145EC100980E11 /* BuiltinSmartFeedInspectorViewController.swift */; };
@ -517,10 +496,7 @@
65ED4035235DEF6C0081F399 /* FolderTreeMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51EC114B2149FE3300B296E3 /* FolderTreeMenu.swift */; };
65ED4036235DEF6C0081F399 /* NNW3ImportController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849ADEE02359817D000E1B81 /* NNW3ImportController.swift */; };
65ED4037235DEF6C0081F399 /* FolderTreeControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97A11ED9F180007D329B /* FolderTreeControllerDelegate.swift */; };
65ED4038235DEF6C0081F399 /* RSImage-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51126DA3225FDE2F00722696 /* RSImage-Extensions.swift */; };
65ED4039235DEF6C0081F399 /* SingleFaviconDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845A29081FC74B8E007B49E3 /* SingleFaviconDownloader.swift */; };
65ED403A235DEF6C0081F399 /* Feed+Scriptability.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F4EDB620074D6500B9E363 /* Feed+Scriptability.swift */; };
65ED403B235DEF6C0081F399 /* AuthorAvatarDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E850851FCB60CE0072EA88 /* AuthorAvatarDownloader.swift */; };
65ED403C235DEF6C0081F399 /* SingleLineTextFieldSizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E185B2203B74E500F69BFA /* SingleLineTextFieldSizer.swift */; };
65ED403D235DEF6C0081F399 /* TimelineTableCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97741ED9EC04007D329B /* TimelineTableCellView.swift */; };
65ED403E235DEF6C0081F399 /* TimelineCellAppearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97701ED9EC04007D329B /* TimelineCellAppearance.swift */; };
@ -581,9 +557,7 @@
841CECDC2BAD04BF0001EE72 /* Tree in Frameworks */ = {isa = PBXBuildFile; productRef = 841CECDB2BAD04BF0001EE72 /* Tree */; };
841CECDE2BAD06D10001EE72 /* Tree in Frameworks */ = {isa = PBXBuildFile; productRef = 841CECDD2BAD06D10001EE72 /* Tree */; };
84216D0322128B9D0049B9B9 /* DetailWebViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84216D0222128B9D0049B9B9 /* DetailWebViewController.swift */; };
8426118A1FCB67AA0086A189 /* FeedIconDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842611891FCB67AA0086A189 /* FeedIconDownloader.swift */; };
8426119E1FCB6ED40086A189 /* HTMLMetadataDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8426119D1FCB6ED40086A189 /* HTMLMetadataDownloader.swift */; };
842611A21FCB769D0086A189 /* RSHTMLMetadata+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842611A11FCB769D0086A189 /* RSHTMLMetadata+Extension.swift */; };
842E45CE1ED8C308000A8B52 /* AppNotifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842E45CD1ED8C308000A8B52 /* AppNotifications.swift */; };
842E45DD1ED8C54B000A8B52 /* Browser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 842E45DC1ED8C54B000A8B52 /* Browser.swift */; };
8438C2DB2BABE0B00040C9EE /* CoreResources in Frameworks */ = {isa = PBXBuildFile; productRef = 8438C2DA2BABE0B00040C9EE /* CoreResources */; };
@ -603,7 +577,6 @@
845122722B8CEA9100480DB0 /* SidebarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8451226E2B8CEA9100480DB0 /* SidebarItem.swift */; };
845122732B8CEA9100480DB0 /* SidebarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8451226E2B8CEA9100480DB0 /* SidebarItem.swift */; };
845122742B8CEA9100480DB0 /* SidebarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8451226E2B8CEA9100480DB0 /* SidebarItem.swift */; };
845213231FCA5B11003B6E93 /* ImageDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845213221FCA5B10003B6E93 /* ImageDownloader.swift */; };
845479881FEB77C000AD8B59 /* TimelineKeyboardShortcuts.plist in Resources */ = {isa = PBXBuildFile; fileRef = 845479871FEB77C000AD8B59 /* TimelineKeyboardShortcuts.plist */; };
8454C3F3263F2D8700E3F9C7 /* IconImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8454C3F2263F2D8700E3F9C7 /* IconImageCache.swift */; };
8454C3F8263F3AD400E3F9C7 /* IconImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8454C3F2263F2D8700E3F9C7 /* IconImageCache.swift */; };
@ -616,7 +589,6 @@
845611722BBD145D00507B73 /* Parser in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 845611702BBD145D00507B73 /* Parser */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
845611742BBD145D00507B73 /* ParserObjC in Frameworks */ = {isa = PBXBuildFile; productRef = 845611732BBD145D00507B73 /* ParserObjC */; };
845611752BBD145D00507B73 /* ParserObjC in Embed Frameworks */ = {isa = PBXBuildFile; productRef = 845611732BBD145D00507B73 /* ParserObjC */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
845A29091FC74B8E007B49E3 /* SingleFaviconDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845A29081FC74B8E007B49E3 /* SingleFaviconDownloader.swift */; };
845A29221FC9251E007B49E3 /* SidebarCellLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845A29211FC9251E007B49E3 /* SidebarCellLayout.swift */; };
845A29241FC9255E007B49E3 /* SidebarCellAppearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845A29231FC9255E007B49E3 /* SidebarCellAppearance.swift */; };
845EE7B11FC2366500854A1F /* StarredFeedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845EE7B01FC2366500854A1F /* StarredFeedDelegate.swift */; };
@ -640,7 +612,6 @@
848565512B9E910200F4BAE0 /* FMDB in Frameworks */ = {isa = PBXBuildFile; productRef = 848565502B9E910200F4BAE0 /* FMDB */; };
848B937221C8C5540038DC0D /* CrashReporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848B937121C8C5540038DC0D /* CrashReporter.swift */; };
848D578E21543519005FFAD5 /* PasteboardFeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848D578D21543519005FFAD5 /* PasteboardFeed.swift */; };
848F6AE51FC29CFB002D422E /* FaviconDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 848F6AE41FC29CFA002D422E /* FaviconDownloader.swift */; };
849830252BBBA6130024FB5B /* Web in Frameworks */ = {isa = PBXBuildFile; productRef = 849830242BBBA6130024FB5B /* Web */; };
849A97431ED9EAA9007D329B /* AddFolderWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97421ED9EAA9007D329B /* AddFolderWindowController.swift */; };
849A97531ED9EAC0007D329B /* AddFeedController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97511ED9EAC0007D329B /* AddFeedController.swift */; };
@ -717,6 +688,11 @@
84D9582C2BABE53B0053E7B2 /* FoundationExtras in Frameworks */ = {isa = PBXBuildFile; productRef = 84D9582B2BABE53B0053E7B2 /* FoundationExtras */; };
84DC5FFA2BCE31D200F04682 /* Images in Frameworks */ = {isa = PBXBuildFile; productRef = 84DC5FF92BCE31D200F04682 /* Images */; };
84DC5FFC2BCE31DB00F04682 /* Images in Frameworks */ = {isa = PBXBuildFile; productRef = 84DC5FFB2BCE31DB00F04682 /* Images */; };
84DC5FFE2BCE37A300F04682 /* AppDelegate+Shared.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84DC5FFD2BCE37A300F04682 /* AppDelegate+Shared.swift */; };
84DC5FFF2BCE37A300F04682 /* AppDelegate+Shared.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84DC5FFD2BCE37A300F04682 /* AppDelegate+Shared.swift */; };
84DC60002BCE37A300F04682 /* AppDelegate+Shared.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84DC5FFD2BCE37A300F04682 /* AppDelegate+Shared.swift */; };
84DC60022BCE40B200F04682 /* Images in Frameworks */ = {isa = PBXBuildFile; productRef = 84DC60012BCE40B200F04682 /* Images */; };
84DC60042BCE40D000F04682 /* ParserObjC in Frameworks */ = {isa = PBXBuildFile; productRef = 84DC60032BCE40D000F04682 /* ParserObjC */; };
84DCA5122BABB75600792720 /* FoundationExtras in Frameworks */ = {isa = PBXBuildFile; productRef = 84DCA5112BABB75600792720 /* FoundationExtras */; };
84DCA5142BABB76100792720 /* AppKitExtras in Frameworks */ = {isa = PBXBuildFile; productRef = 84DCA5132BABB76100792720 /* AppKitExtras */; };
84DCA5162BABB76B00792720 /* CloudKitExtras in Frameworks */ = {isa = PBXBuildFile; productRef = 84DCA5152BABB76B00792720 /* CloudKitExtras */; };
@ -734,7 +710,6 @@
84E185B3203B74E500F69BFA /* SingleLineTextFieldSizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E185B2203B74E500F69BFA /* SingleLineTextFieldSizer.swift */; };
84E185C3203BB12600F69BFA /* MultilineTextFieldSizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E185C2203BB12600F69BFA /* MultilineTextFieldSizer.swift */; };
84E46C7D1F75EF7B005ECFB3 /* AppDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E46C7C1F75EF7B005ECFB3 /* AppDefaults.swift */; };
84E850861FCB60CE0072EA88 /* AuthorAvatarDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E850851FCB60CE0072EA88 /* AuthorAvatarDownloader.swift */; };
84E8E0DB202EC49300562D8F /* TimelineViewController+ContextualMenus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E8E0DA202EC49300562D8F /* TimelineViewController+ContextualMenus.swift */; };
84E8E0EB202F693600562D8F /* DetailWebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E8E0EA202F693600562D8F /* DetailWebView.swift */; };
84E95D241FB1087500552D99 /* ArticlePasteboardWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E95D231FB1087500552D99 /* ArticlePasteboardWriter.swift */; };
@ -758,7 +733,6 @@
84F9EAF3213660A100CF2DE4 /* testCurrentArticleIsNil.applescript in Sources */ = {isa = PBXBuildFile; fileRef = 84F9EAE0213660A100CF2DE4 /* testCurrentArticleIsNil.applescript */; };
84F9EAF4213660A100CF2DE4 /* testGenericScript.applescript in Sources */ = {isa = PBXBuildFile; fileRef = 84F9EAE1213660A100CF2DE4 /* testGenericScript.applescript */; };
84F9EAF5213660A100CF2DE4 /* establishMainWindowStartingState.applescript in Sources */ = {isa = PBXBuildFile; fileRef = 84F9EAE2213660A100CF2DE4 /* establishMainWindowStartingState.applescript */; };
84FF69B11FC3793300DC198E /* FaviconURLFinder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84FF69B01FC3793300DC198E /* FaviconURLFinder.swift */; };
B24E9ADC245AB88400DA5718 /* NSAttributedString+NetNewsWire.swift in Sources */ = {isa = PBXBuildFile; fileRef = B24E9ABA245AB88300DA5718 /* NSAttributedString+NetNewsWire.swift */; };
B24E9ADD245AB88400DA5718 /* NSAttributedString+NetNewsWire.swift in Sources */ = {isa = PBXBuildFile; fileRef = B24E9ABA245AB88300DA5718 /* NSAttributedString+NetNewsWire.swift */; };
B24E9ADE245AB88400DA5718 /* NSAttributedString+NetNewsWire.swift in Sources */ = {isa = PBXBuildFile; fileRef = B24E9ABA245AB88300DA5718 /* NSAttributedString+NetNewsWire.swift */; };
@ -1093,7 +1067,6 @@
51102164233A7D6C0007A5F7 /* ArticleExtractorButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleExtractorButton.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>"; };
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>"; };
511D43EE231FBDE800FB1562 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreenPad.storyboard; sourceTree = "<group>"; };
@ -1152,7 +1125,6 @@
516A093F2361240900EAE89B /* Account.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Account.storyboard; sourceTree = "<group>"; };
516A09412361248000EAE89B /* Inspector.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Inspector.storyboard; sourceTree = "<group>"; };
516AE9B22371C372007DEEAA /* FeedTableViewSectionHeaderLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedTableViewSectionHeaderLayout.swift; sourceTree = "<group>"; };
516AE9DE2372269A007DEEAA /* IconImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconImage.swift; sourceTree = "<group>"; };
51707438232AA97100A461A3 /* ShareFolderPickerController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareFolderPickerController.swift; sourceTree = "<group>"; };
517630032336215100E15FFF /* main.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = main.js; sourceTree = "<group>"; };
517A745A2443665000B553B9 /* UIPageViewController-Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIPageViewController-Extensions.swift"; sourceTree = "<group>"; };
@ -1243,7 +1215,6 @@
51E595A4228CC36500FCC42B /* ArticleStatusSyncTimer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleStatusSyncTimer.swift; sourceTree = "<group>"; };
51EAED95231363EF00A9EEE3 /* NonIntrinsicButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonIntrinsicButton.swift; sourceTree = "<group>"; };
51EC114B2149FE3300B296E3 /* FolderTreeMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = FolderTreeMenu.swift; path = AddFeed/FolderTreeMenu.swift; sourceTree = "<group>"; };
51EF0F76227716200050506E /* FaviconGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FaviconGenerator.swift; sourceTree = "<group>"; };
51EF0F7D2277A57D0050506E /* TimelineAccessibilityCellLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineAccessibilityCellLayout.swift; sourceTree = "<group>"; };
51EF0F7F2277A8330050506E /* TimelineCellLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineCellLayout.swift; sourceTree = "<group>"; };
51F805D32428499E0022C792 /* NetNewsWire-dev.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "NetNewsWire-dev.entitlements"; sourceTree = "<group>"; };
@ -1304,10 +1275,7 @@
841ABA5F20145EC100980E11 /* BuiltinSmartFeedInspectorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuiltinSmartFeedInspectorViewController.swift; sourceTree = "<group>"; };
841CECD62BAD03C60001EE72 /* Tree */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = Tree; sourceTree = "<group>"; };
84216D0222128B9D0049B9B9 /* DetailWebViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailWebViewController.swift; sourceTree = "<group>"; };
842611891FCB67AA0086A189 /* FeedIconDownloader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedIconDownloader.swift; sourceTree = "<group>"; };
8426119D1FCB6ED40086A189 /* HTMLMetadataDownloader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HTMLMetadataDownloader.swift; sourceTree = "<group>"; };
8426119F1FCB72600086A189 /* FeaturedImageDownloader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeaturedImageDownloader.swift; sourceTree = "<group>"; };
842611A11FCB769D0086A189 /* RSHTMLMetadata+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RSHTMLMetadata+Extension.swift"; sourceTree = "<group>"; };
842E45CD1ED8C308000A8B52 /* AppNotifications.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppNotifications.swift; sourceTree = "<group>"; };
842E45DC1ED8C54B000A8B52 /* Browser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Browser.swift; sourceTree = "<group>"; };
84411E701FE5FBFA004B527F /* SmallIconProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SmallIconProvider.swift; sourceTree = "<group>"; };
@ -1320,10 +1288,8 @@
844B5B681FEA20DF00C7C76A /* SidebarKeyboardShortcuts.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = SidebarKeyboardShortcuts.plist; sourceTree = "<group>"; };
8451226D2B8CEA9100480DB0 /* SidebarItemIdentifier.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SidebarItemIdentifier.swift; path = Shared/SidebarItem/SidebarItemIdentifier.swift; sourceTree = SOURCE_ROOT; };
8451226E2B8CEA9100480DB0 /* SidebarItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SidebarItem.swift; path = Shared/SidebarItem/SidebarItem.swift; sourceTree = SOURCE_ROOT; };
845213221FCA5B10003B6E93 /* ImageDownloader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageDownloader.swift; sourceTree = "<group>"; };
845479871FEB77C000AD8B59 /* TimelineKeyboardShortcuts.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = TimelineKeyboardShortcuts.plist; sourceTree = "<group>"; };
8454C3F2263F2D8700E3F9C7 /* IconImageCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconImageCache.swift; sourceTree = "<group>"; };
845A29081FC74B8E007B49E3 /* SingleFaviconDownloader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleFaviconDownloader.swift; sourceTree = "<group>"; };
845A29211FC9251E007B49E3 /* SidebarCellLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarCellLayout.swift; sourceTree = "<group>"; };
845A29231FC9255E007B49E3 /* SidebarCellAppearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarCellAppearance.swift; sourceTree = "<group>"; };
845B14A51FC2299E0013CF92 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
@ -1345,7 +1311,6 @@
8483630A2262A3F000DA1D35 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Mac/Base.lproj/RenameSheet.xib; sourceTree = SOURCE_ROOT; };
848B937121C8C5540038DC0D /* CrashReporter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CrashReporter.swift; sourceTree = "<group>"; };
848D578D21543519005FFAD5 /* PasteboardFeed.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasteboardFeed.swift; sourceTree = "<group>"; };
848F6AE41FC29CFA002D422E /* FaviconDownloader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FaviconDownloader.swift; sourceTree = "<group>"; };
849A97421ED9EAA9007D329B /* AddFolderWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddFolderWindowController.swift; sourceTree = "<group>"; };
849A97511ED9EAC0007D329B /* AddFeedController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AddFeedController.swift; path = AddFeed/AddFeedController.swift; sourceTree = "<group>"; };
849A97521ED9EAC0007D329B /* AddFeedWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AddFeedWindowController.swift; path = AddFeed/AddFeedWindowController.swift; sourceTree = "<group>"; };
@ -1426,6 +1391,7 @@
84D2200922B0BC4B0019E085 /* CONTRIBUTING.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CONTRIBUTING.md; sourceTree = "<group>"; };
84D52E941FE588BB00D14F5B /* DetailStatusBarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailStatusBarView.swift; sourceTree = "<group>"; };
84DC5FF82BCE308500F04682 /* Images */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = Images; sourceTree = "<group>"; };
84DC5FFD2BCE37A300F04682 /* AppDelegate+Shared.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppDelegate+Shared.swift"; sourceTree = "<group>"; };
84DCA50D2BAB643700792720 /* FoundationExtras */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = FoundationExtras; sourceTree = "<group>"; };
84DCA50E2BABB5D800792720 /* AppKitExtras */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = AppKitExtras; sourceTree = "<group>"; };
84DCA50F2BABB65600792720 /* CloudKitExtras */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = CloudKitExtras; sourceTree = "<group>"; };
@ -1435,7 +1401,6 @@
84E185B2203B74E500F69BFA /* SingleLineTextFieldSizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleLineTextFieldSizer.swift; sourceTree = "<group>"; };
84E185C2203BB12600F69BFA /* MultilineTextFieldSizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultilineTextFieldSizer.swift; sourceTree = "<group>"; };
84E46C7C1F75EF7B005ECFB3 /* AppDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDefaults.swift; sourceTree = "<group>"; };
84E850851FCB60CE0072EA88 /* AuthorAvatarDownloader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthorAvatarDownloader.swift; sourceTree = "<group>"; };
84E8E0DA202EC49300562D8F /* TimelineViewController+ContextualMenus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TimelineViewController+ContextualMenus.swift"; sourceTree = "<group>"; };
84E8E0EA202F693600562D8F /* DetailWebView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailWebView.swift; sourceTree = "<group>"; };
84E95D231FB1087500552D99 /* ArticlePasteboardWriter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticlePasteboardWriter.swift; sourceTree = "<group>"; };
@ -1464,7 +1429,6 @@
84FB9FAC2BC33AFE00B7AFC3 /* NewsBlur */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = NewsBlur; sourceTree = "<group>"; };
84FB9FAD2BC344F800B7AFC3 /* Feedbin */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = Feedbin; sourceTree = "<group>"; };
84FB9FAE2BC3494B00B7AFC3 /* FeedFinder */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = FeedFinder; sourceTree = "<group>"; };
84FF69B01FC3793300DC198E /* FaviconURLFinder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FaviconURLFinder.swift; sourceTree = "<group>"; };
B24E9ABA245AB88300DA5718 /* NSAttributedString+NetNewsWire.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSAttributedString+NetNewsWire.swift"; sourceTree = "<group>"; };
B24EFD482330FF99006C6242 /* NetNewsWire-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NetNewsWire-Bridging-Header.h"; sourceTree = "<group>"; };
B24EFD5923310109006C6242 /* WKPreferencesPrivate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WKPreferencesPrivate.h; sourceTree = "<group>"; };
@ -1531,8 +1495,10 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
84DC60022BCE40B200F04682 /* Images in Frameworks */,
841CECDE2BAD06D10001EE72 /* Tree in Frameworks */,
51BC2F3824D3439A00E90810 /* Account in Frameworks */,
84DC60042BCE40D000F04682 /* ParserObjC in Frameworks */,
84D9582C2BABE53B0053E7B2 /* FoundationExtras in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -2161,18 +2127,6 @@
path = SidebarItem;
sourceTree = "<group>";
};
845213211FCA5B10003B6E93 /* Images */ = {
isa = PBXGroup;
children = (
845213221FCA5B10003B6E93 /* ImageDownloader.swift */,
84E850851FCB60CE0072EA88 /* AuthorAvatarDownloader.swift */,
842611891FCB67AA0086A189 /* FeedIconDownloader.swift */,
8426119F1FCB72600086A189 /* FeaturedImageDownloader.swift */,
842611A11FCB769D0086A189 /* RSHTMLMetadata+Extension.swift */,
);
path = Images;
sourceTree = "<group>";
};
845A29251FC928C7007B49E3 /* Cell */ = {
isa = PBXGroup;
children = (
@ -2203,17 +2157,6 @@
path = CrashReporter;
sourceTree = "<group>";
};
848F6AE31FC29CFA002D422E /* Favicons */ = {
isa = PBXGroup;
children = (
848F6AE41FC29CFA002D422E /* FaviconDownloader.swift */,
51EF0F76227716200050506E /* FaviconGenerator.swift */,
84FF69B01FC3793300DC198E /* FaviconURLFinder.swift */,
845A29081FC74B8E007B49E3 /* SingleFaviconDownloader.swift */,
);
path = Favicons;
sourceTree = "<group>";
};
849A97411ED9EAA9007D329B /* Add Folder */ = {
isa = PBXGroup;
children = (
@ -2242,12 +2185,10 @@
849A97731ED9EC04007D329B /* ArticleStringFormatter.swift */,
849A97581ED9EB0D007D329B /* ArticleUtilities.swift */,
5108F6B52375E612001ABC45 /* CacheCleaner.swift */,
516AE9DE2372269A007DEEAA /* IconImage.swift */,
849A97971ED9EFAA007D329B /* Node-Extensions.swift */,
B24E9ABA245AB88300DA5718 /* NSAttributedString+NetNewsWire.swift */,
8405DD9B22153BD7008CE1BF /* NSView-Extensions.swift */,
B2B8075D239C49D300F191E0 /* RSImage-AppIcons.swift */,
51126DA3225FDE2F00722696 /* RSImage-Extensions.swift */,
84411E701FE5FBFA004B527F /* SmallIconProvider.swift */,
51BC4ADD247277DF000A6ED8 /* URL-Extensions.swift */,
847120D62B8AE6AF00BBFC34 /* UTType+Extensions.swift */,
@ -2451,6 +2392,7 @@
511B9805237DCAC90028BCAA /* UserInfoKey.swift */,
844933D12BA953590068AC51 /* ArticlePathInfo.swift */,
8454C3F2263F2D8700E3F9C7 /* IconImageCache.swift */,
84DC5FFD2BCE37A300F04682 /* AppDelegate+Shared.swift */,
845122752B8CEA9B00480DB0 /* SidebarItem */,
51C452AD2265102800C03939 /* Timeline */,
84702AB31FA27AE8006B8943 /* Commands */,
@ -2462,8 +2404,6 @@
51FE0FF9234552490056195D /* UserNotifications */,
84F2D5341FC22FCB00998D64 /* SmartFeeds */,
51B5C85A23F22A7A00032075 /* ShareExtension */,
848F6AE31FC29CFA002D422E /* Favicons */,
845213211FCA5B10003B6E93 /* Images */,
8426119C1FCB6ED40086A189 /* HTMLMetadata */,
5183CCEA226F70350010922C /* Timer */,
512E08DD22687FA000BDCFDD /* Tree */,
@ -2826,6 +2766,8 @@
51BC2F3724D3439A00E90810 /* Account */,
84D9582B2BABE53B0053E7B2 /* FoundationExtras */,
841CECDD2BAD06D10001EE72 /* Tree */,
84DC60012BCE40B200F04682 /* Images */,
84DC60032BCE40D000F04682 /* ParserObjC */,
);
productName = "NetNewsWire iOS Share Extension";
productReference = 513C5CE6232571C2003D4054 /* NetNewsWire iOS Share Extension.appex */;
@ -3689,7 +3631,6 @@
51B5C8BA23F368D000032075 /* ExtensionContainersFile.swift in Sources */,
51B5C8BB23F368D000032075 /* ExtensionFeedAddRequest.swift in Sources */,
51A9A5E82380CA130033AADF /* ShareFolderPickerCell.swift in Sources */,
51A9A5EF2380D63B0033AADF /* IconImage.swift in Sources */,
51A9A5ED2380D6000033AADF /* AppAssets.swift in Sources */,
51B5C8C123F3A0DB00032075 /* ExtensionFeedAddRequestFile.swift in Sources */,
51A9A5E12380C4FE0033AADF /* AppDefaults.swift in Sources */,
@ -3766,13 +3707,13 @@
65ED3FD7235DEF6C0081F399 /* NSApplication+Scriptability.swift in Sources */,
65ED3FD8235DEF6C0081F399 /* NSView-Extensions.swift in Sources */,
5103A9F824225E4C00410853 /* AccountsAddCloudKitWindowController.swift in Sources */,
84DC5FFF2BCE37A300F04682 /* AppDelegate+Shared.swift in Sources */,
65ED3FD9235DEF6C0081F399 /* SidebarCell.swift in Sources */,
65ED3FDA235DEF6C0081F399 /* ArticleStatusSyncTimer.swift in Sources */,
65ED3FDB235DEF6C0081F399 /* FeedTreeControllerDelegate.swift in Sources */,
65ED3FDC235DEF6C0081F399 /* UnreadCountView.swift in Sources */,
65ED3FDD235DEF6C0081F399 /* ActivityType.swift in Sources */,
65ED3FDE235DEF6C0081F399 /* CrashReportWindowController.swift in Sources */,
65ED3FDF235DEF6C0081F399 /* FeedIconDownloader.swift in Sources */,
B24E9ADD245AB88400DA5718 /* NSAttributedString+NetNewsWire.swift in Sources */,
510C417C24E5D1AE008226FD /* ExtensionFeedAddRequestFile.swift in Sources */,
510C417D24E5D1AE008226FD /* ExtensionContainers.swift in Sources */,
@ -3785,10 +3726,8 @@
65ED3FE6235DEF6C0081F399 /* RenameWindowController.swift in Sources */,
65ED3FE7235DEF6C0081F399 /* SendToMicroBlogCommand.swift in Sources */,
65ED3FE8235DEF6C0081F399 /* ArticleTheme.swift in Sources */,
65ED3FE9235DEF6C0081F399 /* FaviconURLFinder.swift in Sources */,
6538131C2680E17F007A082C /* UserInfoKey.swift in Sources */,
65ED3FEA235DEF6C0081F399 /* SidebarViewController+ContextualMenus.swift in Sources */,
65ED3FEC235DEF6C0081F399 /* RSHTMLMetadata+Extension.swift in Sources */,
518C3194237B00DA004D740F /* DetailIconSchemeHandler.swift in Sources */,
65ED3FED235DEF6C0081F399 /* SendToMarsEditCommand.swift in Sources */,
65ED3FEE235DEF6C0081F399 /* UserNotificationManager.swift in Sources */,
@ -3815,7 +3754,6 @@
65ED3FFF235DEF6C0081F399 /* SidebarOutlineDataSource.swift in Sources */,
65ED4000235DEF6C0081F399 /* SidebarCellAppearance.swift in Sources */,
65ED4001235DEF6C0081F399 /* StarredFeedDelegate.swift in Sources */,
65ED4002235DEF6C0081F399 /* FaviconDownloader.swift in Sources */,
65ED4003235DEF6C0081F399 /* AdvancedPreferencesViewController.swift in Sources */,
65ED4004235DEF6C0081F399 /* SharingServicePickerDelegate.swift in Sources */,
65ED4005235DEF6C0081F399 /* Node-Extensions.swift in Sources */,
@ -3828,7 +3766,6 @@
65ED400B235DEF6C0081F399 /* TodayFeedDelegate.swift in Sources */,
65ED400C235DEF6C0081F399 /* FolderInspectorViewController.swift in Sources */,
65ED400D235DEF6C0081F399 /* SmartFeedDelegate.swift in Sources */,
65ED400E235DEF6C0081F399 /* ImageDownloader.swift in Sources */,
65ED400F235DEF6C0081F399 /* LegacyArticleExtractorButton.swift in Sources */,
65ED4011235DEF6C0081F399 /* AddFolderWindowController.swift in Sources */,
65ED4012235DEF6C0081F399 /* TimelineContainerViewController.swift in Sources */,
@ -3843,7 +3780,6 @@
65ED4019235DEF6C0081F399 /* FetchRequestOperation.swift in Sources */,
65ED401A235DEF6C0081F399 /* HTMLMetadataDownloader.swift in Sources */,
65ED401B235DEF6C0081F399 /* TimelineViewController.swift in Sources */,
65ED401C235DEF6C0081F399 /* FaviconGenerator.swift in Sources */,
65ED401D235DEF6C0081F399 /* RefreshInterval.swift in Sources */,
65ED401E235DEF6C0081F399 /* TimelineCellData.swift in Sources */,
65ED401F235DEF6C0081F399 /* BuiltinSmartFeedInspectorViewController.swift in Sources */,
@ -3871,7 +3807,6 @@
847120D82B8AE6AF00BBFC34 /* UTType+Extensions.swift in Sources */,
510C417E24E5D1AE008226FD /* ExtensionFeedAddRequest.swift in Sources */,
51868BF2254386630011A17B /* SidebarDeleteItemsAlert.swift in Sources */,
6538131B2680E176007A082C /* IconImage.swift in Sources */,
65ED4032235DEF6C0081F399 /* FetchRequestQueue.swift in Sources */,
65ED4033235DEF6C0081F399 /* SidebarKeyboardDelegate.swift in Sources */,
65ED4034235DEF6C0081F399 /* AccountsPreferencesViewController.swift in Sources */,
@ -3879,10 +3814,7 @@
65ED4036235DEF6C0081F399 /* NNW3ImportController.swift in Sources */,
65ED4037235DEF6C0081F399 /* FolderTreeControllerDelegate.swift in Sources */,
845122732B8CEA9100480DB0 /* SidebarItem.swift in Sources */,
65ED4038235DEF6C0081F399 /* RSImage-Extensions.swift in Sources */,
65ED4039235DEF6C0081F399 /* SingleFaviconDownloader.swift in Sources */,
65ED403A235DEF6C0081F399 /* Feed+Scriptability.swift in Sources */,
65ED403B235DEF6C0081F399 /* AuthorAvatarDownloader.swift in Sources */,
65ED403C235DEF6C0081F399 /* SingleLineTextFieldSizer.swift in Sources */,
65ED403D235DEF6C0081F399 /* TimelineTableCellView.swift in Sources */,
65ED403E235DEF6C0081F399 /* TimelineCellAppearance.swift in Sources */,
@ -3927,7 +3859,6 @@
179D280D26F73D83003B2E0A /* ArticleThemePlist.swift in Sources */,
51C45296226509D300C03939 /* OPMLExporter.swift in Sources */,
51C45291226509C800C03939 /* SmartFeed.swift in Sources */,
51C452A722650A3D00C03939 /* RSImage-Extensions.swift in Sources */,
511B9807237DCAC90028BCAA /* UserInfoKey.swift in Sources */,
51C45269226508F600C03939 /* FeedTableViewCell.swift in Sources */,
51F85BFD2275DCA800C787DC /* SingleLineUILabelSizer.swift in Sources */,
@ -3956,7 +3887,6 @@
51C4526B226508F600C03939 /* SidebarViewController.swift in Sources */,
5126EE97226CB48A00C22AFC /* SceneCoordinator.swift in Sources */,
84CAFCB022BC8C35007694F0 /* FetchRequestOperation.swift in Sources */,
51EF0F77227716200050506E /* FaviconGenerator.swift in Sources */,
51938DF3231AFC660055A1A0 /* SearchTimelineFeedDelegate.swift in Sources */,
51C4525A226508D600C03939 /* UIStoryboard-Extensions.swift in Sources */,
517A745B2443665000B553B9 /* UIPageViewController-Extensions.swift in Sources */,
@ -3968,20 +3898,16 @@
5F323809231DF9F000706F6B /* VibrantTableViewCell.swift in Sources */,
51FE10042345529D0056195D /* UserNotificationManager.swift in Sources */,
51C4CFF224D37D1F00AF9874 /* Secrets.swift in Sources */,
51C452A022650A1900C03939 /* FeedIconDownloader.swift in Sources */,
845122712B8CEA9100480DB0 /* SidebarItemIdentifier.swift in Sources */,
51C4529E22650A1900C03939 /* ImageDownloader.swift in Sources */,
51A66685238075AE00CB272D /* AddFeedDefaultContainer.swift in Sources */,
176813E92564BAE200D98635 /* WidgetDeepLinks.swift in Sources */,
51B5C87723F22B8200032075 /* ExtensionContainers.swift in Sources */,
51C45292226509C800C03939 /* TodayFeedDelegate.swift in Sources */,
51C452A222650A1900C03939 /* RSHTMLMetadata+Extension.swift in Sources */,
51B5C87B23F2317700032075 /* ExtensionFeedAddRequest.swift in Sources */,
51627A93238A3836007B3B4B /* CroppingPreviewParameters.swift in Sources */,
512AF9DD236F05230066F8BE /* InteractiveLabel.swift in Sources */,
51E3EB3D229AB08300645299 /* ErrorHandler.swift in Sources */,
5183CCE5226F4DFA0010922C /* RefreshInterval.swift in Sources */,
51C4529D22650A1000C03939 /* FaviconURLFinder.swift in Sources */,
5142192A23522B5500E07E2C /* ImageViewController.swift in Sources */,
516244E3241E19F000B61C47 /* ColorPaletteTableViewController.swift in Sources */,
51C45258226508CF00C03939 /* AppAssets.swift in Sources */,
@ -4006,7 +3932,6 @@
C5A6ED6D23C9B0C800AB6BE2 /* UIActivityViewController-Extensions.swift in Sources */,
5108F6D42375EEEF001ABC45 /* TimelinePreviewTableViewController.swift in Sources */,
84CAFCA522BC8C08007694F0 /* FetchRequestQueue.swift in Sources */,
51C4529C22650A1000C03939 /* SingleFaviconDownloader.swift in Sources */,
17D643B226F8A436008D4C05 /* ArticleThemeDownloader.swift in Sources */,
51E595A6228CC36500FCC42B /* ArticleStatusSyncTimer.swift in Sources */,
51F9F3F723DF6DB200A314FD /* ArticleIconSchemeHandler.swift in Sources */,
@ -4022,6 +3947,7 @@
51C452AC22650FD200C03939 /* AppNotifications.swift in Sources */,
51EF0F7E2277A57D0050506E /* TimelineAccessibilityCellLayout.swift in Sources */,
51A1699B235E10D700EB091F /* AccountInspectorViewController.swift in Sources */,
84DC60002BCE37A300F04682 /* AppDelegate+Shared.swift in Sources */,
512D554423C804DE0023FFFA /* OpenInSafariActivity.swift in Sources */,
51C452762265091600C03939 /* TimelineViewController.swift in Sources */,
5195C1DC2720BD3000888867 /* FeedRowIdentifier.swift in Sources */,
@ -4044,15 +3970,12 @@
17D7586F2679C21800B17787 /* OnePasswordExtension.m in Sources */,
844933D42BA953590068AC51 /* ArticlePathInfo.swift in Sources */,
17071EF126F8137400F5E71D /* ArticleTheme+Notifications.swift in Sources */,
51C4529B22650A1000C03939 /* FaviconDownloader.swift in Sources */,
84DEE56622C32CA4005FC42C /* SmartFeedDelegate.swift in Sources */,
512E09012268907400BDCFDD /* FeedTableViewSectionHeader.swift in Sources */,
516AE9E02372269A007DEEAA /* IconImage.swift in Sources */,
51C45268226508F600C03939 /* FeedUnreadCountView.swift in Sources */,
D3A39865246505DF00F9A366 /* FindInArticleActivity.swift in Sources */,
5183CCD0226E1E880010922C /* NonIntrinsicLabel.swift in Sources */,
5137C2EA26F63AE6009EFEDB /* ArticleThemeImporter.swift in Sources */,
51C4529F22650A1900C03939 /* AuthorAvatarDownloader.swift in Sources */,
5108F6D22375EED2001ABC45 /* TimelineCustomizerViewController.swift in Sources */,
519E743D22C663F900A78E47 /* SceneDelegate.swift in Sources */,
FFD43E412340F488009E5CA3 /* MarkAsReadAlertController.swift in Sources */,
@ -4097,7 +4020,6 @@
84F2D53A1FC2308B00998D64 /* UnreadFeed.swift in Sources */,
845A29221FC9251E007B49E3 /* SidebarCellLayout.swift in Sources */,
510C418224E5D1AE008226FD /* ExtensionFeedAddRequest.swift in Sources */,
516AE9DF2372269A007DEEAA /* IconImage.swift in Sources */,
84AD1EBA2031649C00BC20B7 /* SmartFeedPasteboardWriter.swift in Sources */,
849C78922362AB04009A71E4 /* ExportOPMLWindowController.swift in Sources */,
84CC88181FE59CBF00644329 /* SmartFeedsController.swift in Sources */,
@ -4121,7 +4043,6 @@
510C418024E5D1AE008226FD /* ExtensionFeedAddRequestFile.swift in Sources */,
51FE10092346739D0056195D /* ActivityType.swift in Sources */,
840BEE4121D70E64009BBAFA /* CrashReportWindowController.swift in Sources */,
8426118A1FCB67AA0086A189 /* FeedIconDownloader.swift in Sources */,
84C9FC7B22629E1200D921D6 /* PreferencesControlsBackgroundView.swift in Sources */,
84162A152038C12C00035290 /* MarkCommandValidationStatus.swift in Sources */,
17D643B126F8A436008D4C05 /* ArticleThemeDownloader.swift in Sources */,
@ -4133,9 +4054,7 @@
84A14FF320048CA70046AD9A /* SendToMicroBlogCommand.swift in Sources */,
B2B8075E239C49D300F191E0 /* RSImage-AppIcons.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 */,
84A1500520048DDF0046AD9A /* SendToMarsEditCommand.swift in Sources */,
51FE10032345529D0056195D /* UserNotificationManager.swift in Sources */,
D5907DB22004BB37005947E5 /* ScriptingObjectContainer.swift in Sources */,
@ -4163,7 +4082,6 @@
84AD1EBC2032AF5C00BC20B7 /* SidebarOutlineDataSource.swift in Sources */,
845A29241FC9255E007B49E3 /* SidebarCellAppearance.swift in Sources */,
845EE7B11FC2366500854A1F /* StarredFeedDelegate.swift in Sources */,
848F6AE51FC29CFB002D422E /* FaviconDownloader.swift in Sources */,
511B9806237DCAC90028BCAA /* UserInfoKey.swift in Sources */,
84C9FC7722629E1200D921D6 /* AdvancedPreferencesViewController.swift in Sources */,
849EE72120391F560082A1EA /* SharingServicePickerDelegate.swift in Sources */,
@ -4179,11 +4097,11 @@
841ABA5E20145E9200980E11 /* FolderInspectorViewController.swift in Sources */,
84DEE56522C32CA4005FC42C /* SmartFeedDelegate.swift in Sources */,
845122722B8CEA9100480DB0 /* SidebarItem.swift in Sources */,
845213231FCA5B11003B6E93 /* ImageDownloader.swift in Sources */,
51FA73B72332D5F70090D516 /* LegacyArticleExtractorButton.swift in Sources */,
849A97431ED9EAA9007D329B /* AddFolderWindowController.swift in Sources */,
8405DDA522168C62008CE1BF /* TimelineContainerViewController.swift in Sources */,
844B5B671FEA18E300C7C76A /* MainWIndowKeyboardHandler.swift in Sources */,
84DC5FFE2BCE37A300F04682 /* AppDelegate+Shared.swift in Sources */,
848D578E21543519005FFAD5 /* PasteboardFeed.swift in Sources */,
5144EA2F2279FAB600D19003 /* AccountsDetailViewController.swift in Sources */,
849A97801ED9EC42007D329B /* DetailViewController.swift in Sources */,
@ -4195,7 +4113,6 @@
84CAFCAF22BC8C35007694F0 /* FetchRequestOperation.swift in Sources */,
8426119E1FCB6ED40086A189 /* HTMLMetadataDownloader.swift in Sources */,
849A976E1ED9EBC8007D329B /* TimelineViewController.swift in Sources */,
5154368B229404D1005E1CDF /* FaviconGenerator.swift in Sources */,
5183CCE6226F4E110010922C /* RefreshInterval.swift in Sources */,
849A97771ED9EC04007D329B /* TimelineCellData.swift in Sources */,
841ABA6020145EC100980E11 /* BuiltinSmartFeedInspectorViewController.swift in Sources */,
@ -4229,10 +4146,7 @@
849ADEE42359817E000E1B81 /* NNW3ImportController.swift in Sources */,
179C39EB26F76B3800D4E741 /* ArticleThemePlist.swift in Sources */,
849A97A31ED9F180007D329B /* FolderTreeControllerDelegate.swift in Sources */,
51126DA4225FDE2F00722696 /* RSImage-Extensions.swift in Sources */,
845A29091FC74B8E007B49E3 /* SingleFaviconDownloader.swift in Sources */,
D5F4EDB720074D6500B9E363 /* Feed+Scriptability.swift in Sources */,
84E850861FCB60CE0072EA88 /* AuthorAvatarDownloader.swift in Sources */,
84E185B3203B74E500F69BFA /* SingleLineTextFieldSizer.swift in Sources */,
849A977A1ED9EC04007D329B /* TimelineTableCellView.swift in Sources */,
849A97761ED9EC04007D329B /* TimelineCellAppearance.swift in Sources */,
@ -4957,6 +4871,14 @@
isa = XCSwiftPackageProductDependency;
productName = Images;
};
84DC60012BCE40B200F04682 /* Images */ = {
isa = XCSwiftPackageProductDependency;
productName = Images;
};
84DC60032BCE40D000F04682 /* ParserObjC */ = {
isa = XCSwiftPackageProductDependency;
productName = ParserObjC;
};
84DCA5112BABB75600792720 /* FoundationExtras */ = {
isa = XCSwiftPackageProductDependency;
productName = FoundationExtras;

View File

@ -12,6 +12,7 @@ import Account
import Articles
import Intents
import UniformTypeIdentifiers
import Images
#if os(iOS)
@preconcurrency import CoreSpotlight
@ -159,7 +160,7 @@ import CoreSpotlight
#endif
@objc func feedIconDidBecomeAvailable(_ note: Notification) {
guard let feed = note.userInfo?[UserInfoKey.feed] as? Feed, let activityFeedID = selectingActivity?.userInfo?[ArticlePathKey.feedID] as? String else {
guard let feed = note.userInfo?[FeedIconDownloader.feedKey] as? Feed, let activityFeedID = selectingActivity?.userInfo?[ArticlePathKey.feedID] as? String else {
return
}

View File

@ -0,0 +1,23 @@
//
// AppDelegate+Shared.swift
// NetNewsWire
//
// Created by Brent Simmons on 4/15/24.
// Copyright © 2024 Ranchero Software. All rights reserved.
//
import Foundation
import Images
import ParserObjC
extension AppDelegate: FaviconDownloaderDelegate, FeedIconDownloaderDelegate {
var appIconImage: IconImage? {
IconImage.appIcon
}
func downloadMetadata(_ url: String) async throws -> RSHTMLMetadata? {
try await HTMLMetadataDownloader.downloadMetadata(for: url)
}
}

View File

@ -9,6 +9,7 @@
import Foundation
import Articles
import Account
import Images
// These handle multiple accounts.

View File

@ -8,6 +8,7 @@
import Foundation
import Core
import Images
extension RSImage {
static let appIconImage: RSImage? = {

View File

@ -9,6 +9,7 @@
import Foundation
import Articles
import Account
import Images
protocol SmallIconProvider {

View File

@ -9,6 +9,7 @@
import Foundation
import Account
import Articles
import Images
@MainActor final class IconImageCache {

View File

@ -1,99 +0,0 @@
//
// FeaturedImageDownloader.swift
// NetNewsWire
//
// Created by Brent Simmons on 11/26/17.
// Copyright © 2017 Ranchero Software. All rights reserved.
//
import Foundation
import Articles
import Parser
final class FeaturedImageDownloader {
private let imageDownloader: ImageDownloader
private var articleURLToFeaturedImageURLCache = [String: String]()
private var articleURLsWithNoFeaturedImage = Set<String>()
private var urlsInProgress = Set<String>()
init(imageDownloader: ImageDownloader) {
self.imageDownloader = imageDownloader
}
func image(for article: Article) -> RSImage? {
if let imageLink = article.imageLink {
return image(forFeaturedImageURL: imageLink)
}
if let link = article.link {
return image(forArticleURL: link)
}
return nil
}
func image(forArticleURL articleURL: String) -> RSImage? {
if articleURLsWithNoFeaturedImage.contains(articleURL) {
return nil
}
if let featuredImageURL = cachedURL(for: articleURL) {
return image(forFeaturedImageURL: featuredImageURL)
}
findFeaturedImageURL(for: articleURL)
return nil
}
func image(forFeaturedImageURL featuredImageURL: String) -> RSImage? {
if let data = imageDownloader.image(for: featuredImageURL) {
return RSImage(data: data)
}
return nil
}
}
private extension FeaturedImageDownloader {
func cachedURL(for articleURL: String) -> String? {
return articleURLToFeaturedImageURLCache[articleURL]
}
func cacheURL(for articleURL: String, _ featuredImageURL: String) {
articleURLsWithNoFeaturedImage.remove(articleURL)
articleURLToFeaturedImageURLCache[articleURL] = featuredImageURL
}
func findFeaturedImageURL(for articleURL: String) {
guard !urlsInProgress.contains(articleURL) else {
return
}
urlsInProgress.insert(articleURL)
HTMLMetadataDownloader.downloadMetadata(for: articleURL) { (metadata) in
self.urlsInProgress.remove(articleURL)
guard let metadata = metadata else {
return
}
self.pullFeaturedImageURL(from: metadata, articleURL: articleURL)
}
}
func pullFeaturedImageURL(from metadata: RSHTMLMetadata, articleURL: String) {
if let url = metadata.bestFeaturedImageURL() {
cacheURL(for: articleURL, url)
let _ = image(forFeaturedImageURL: url)
return
}
articleURLsWithNoFeaturedImage.insert(articleURL)
}
}

View File

@ -10,6 +10,7 @@ import Foundation
import Account
import Articles
import ArticlesDatabase
import Images
@MainActor struct SearchFeedDelegate: SmartFeedDelegate {

View File

@ -10,6 +10,7 @@ import Foundation
import Account
import Articles
import ArticlesDatabase
import Images
@MainActor struct SearchTimelineFeedDelegate: SmartFeedDelegate {

View File

@ -12,6 +12,7 @@ import ArticlesDatabase
import Account
import Database
import Core
import Images
final class SmartFeed: PseudoFeed {

View File

@ -10,8 +10,7 @@ import Foundation
import Articles
import ArticlesDatabase
import Account
// Main thread only.
import Images
@MainActor struct StarredFeedDelegate: SmartFeedDelegate {

View File

@ -10,6 +10,7 @@ import Foundation
import Articles
import ArticlesDatabase
import Account
import Images
@MainActor struct TodayFeedDelegate: SmartFeedDelegate {

View File

@ -14,6 +14,7 @@ import Foundation
import Account
import Articles
import ArticlesDatabase
import Images
// This just shows the global unread count, which appDelegate already has. Easy.

View File

@ -9,6 +9,7 @@
import UIKit
import Account
import Core
import Images
struct AppAssets {

View File

@ -7,6 +7,7 @@
//
import UIKit
import Images
enum UserInterfaceColorPalette: Int, CustomStringConvertible, CaseIterable {
case automatic = 0

View File

@ -14,6 +14,7 @@ import os.log
import Secrets
import WidgetKit
import Core
import Images
@MainActor var appDelegate: AppDelegate!
@ -95,6 +96,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
DefaultFeedsImporter.importDefaultFeeds(account: localAccount)
}
FaviconGenerator.faviconTemplateImage = AppAssets.faviconTemplateImage
registerBackgroundTasks()
CacheCleaner.purgeIfNecessary()
initializeDownloaders()
@ -241,7 +244,8 @@ private extension AppDelegate {
let faviconsFolder = faviconsFolderURL.absoluteString
let faviconsFolderPath = faviconsFolder.suffix(from: faviconsFolder.index(faviconsFolder.startIndex, offsetBy: 7))
faviconDownloader = FaviconDownloader(folder: String(faviconsFolderPath))
faviconDownloader.delegate = self
let imagesFolder = imagesFolderURL.absoluteString
let imagesFolderPath = imagesFolder.suffix(from: imagesFolder.index(imagesFolder.startIndex, offsetBy: 7))
try! FileManager.default.createDirectory(at: imagesFolderURL, withIntermediateDirectories: true, attributes: nil)
@ -252,6 +256,7 @@ private extension AppDelegate {
let tempFolder = tempDir.absoluteString
let tempFolderPath = tempFolder.suffix(from: tempFolder.index(tempFolder.startIndex, offsetBy: 7))
feedIconDownloader = FeedIconDownloader(imageDownloader: imageDownloader, folder: String(tempFolderPath))
feedIconDownloader.delegate = self
}
private func initializeHomeScreenQuickActions() {

View File

@ -8,6 +8,7 @@
import Foundation
import WebKit
import Images
protocol ArticleIconSchemeHandlerDelegate: AnyObject {

View File

@ -14,6 +14,7 @@ import SafariServices
import MessageUI
import Core
import ArticleExtractor
import Images
protocol WebViewControllerDelegate: AnyObject {
func webViewController(_: WebViewController, articleExtractorButtonStateDidUpdate: ArticleExtractorButtonState)

View File

@ -9,6 +9,7 @@
import UIKit
import Account
import Tree
import Images
protocol FeedTableViewCellDelegate: AnyObject {
func feedTableViewCellDisclosureDidToggle(_ sender: FeedTableViewCell, expanding: Bool)

View File

@ -12,6 +12,7 @@ import Articles
import Tree
import SafariServices
import Core
import Images
class SidebarViewController: UITableViewController, UndoableCommandRunner {
@ -126,7 +127,7 @@ class SidebarViewController: UITableViewController, UndoableCommandRunner {
}
@objc func feedIconDidBecomeAvailable(_ note: Notification) {
guard let feed = note.userInfo?[UserInfoKey.feed] as? Feed else {
guard let feed = note.userInfo?[FeedIconDownloader.feedKey] as? Feed else {
return
}
applyToCellsForRepresentedObject(feed, configureIcon(_:_:))

View File

@ -7,6 +7,7 @@
//
import UIKit
import Images
final class IconView: UIView {

View File

@ -10,6 +10,7 @@ import UIKit
import Account
import SafariServices
import UserNotifications
import Images
class FeedInspectorViewController: UITableViewController {

View File

@ -14,6 +14,7 @@ import Tree
import SafariServices
import SwiftUI
import Core
import Images
protocol MainControllerIdentifiable {
var mainControllerIdentifier: MainControllerIdentifier { get }

View File

@ -7,6 +7,7 @@
//
import UIKit
import Images
class TimelineCustomizerViewController: UIViewController {

View File

@ -8,6 +8,7 @@
import UIKit
import Articles
import Images
class TimelinePreviewTableViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {

View File

@ -8,6 +8,7 @@
import UIKit
import Articles
import Images
@MainActor struct TimelineCellData {

View File

@ -7,6 +7,7 @@
//
import UIKit
import Images
protocol TimelineCellLayout {

View File

@ -7,6 +7,7 @@
//
import UIKit
import Images
class TimelineTableViewCell: VibrantTableViewCell {

View File

@ -10,6 +10,7 @@ import UIKit
import Account
import Articles
import Core
import Images
class TimelineViewController: UITableViewController, UndoableCommandRunner {
@ -446,7 +447,7 @@ class TimelineViewController: UITableViewController, UndoableCommandRunner {
titleView.iconView.iconImage = coordinator.timelineIconImage
}
guard let feed = note.userInfo?[UserInfoKey.feed] as? Feed else {
guard let feed = note.userInfo?[FeedIconDownloader.feedKey] as? Feed else {
return
}