NetNewsWire/Mac/MainWindow/Timeline/ArticlePasteboardWriter.swift

198 lines
5.1 KiB
Swift

//
// ArticlePasteboardWriter.swift
// NetNewsWire
//
// Created by Brent Simmons on 11/6/17.
// Copyright © 2017 Ranchero Software. All rights reserved.
//
import AppKit
import Articles
import AppKitExtras
extension Article: PasteboardWriterOwner {
@MainActor public var pasteboardWriter: NSPasteboardWriting {
return ArticlePasteboardWriter(article: self)
}
}
@objc @MainActor final class ArticlePasteboardWriter: NSObject, NSPasteboardWriting {
let article: Article
static let articleUTI = "com.ranchero.article"
static let articleUTIType = NSPasteboard.PasteboardType(rawValue: articleUTI)
static let articleUTIInternal = "com.ranchero.NetNewsWire-Evergreen.internal.article"
static let articleUTIInternalType = NSPasteboard.PasteboardType(rawValue: articleUTIInternal)
private lazy var renderedHTML: String = {
let rendering = ArticleRenderer.articleHTML(article: article, theme: ArticleThemesManager.shared.currentTheme)
return rendering.html
}()
init(article: Article) {
self.article = article
}
// MARK: - NSPasteboardWriting
nonisolated func writableTypes(for pasteboard: NSPasteboard) -> [NSPasteboard.PasteboardType] {
MainActor.assumeIsolated {
var types = [ArticlePasteboardWriter.articleUTIType]
if let _ = article.preferredURL {
types += [.URL]
}
types += [.string, .html, ArticlePasteboardWriter.articleUTIInternalType]
return types
}
}
nonisolated func pasteboardPropertyList(forType type: NSPasteboard.PasteboardType) -> Any? {
MainActor.assumeIsolated {
let plist: Any?
switch type {
case .html:
return renderedHTML
case .string:
plist = plainText()
case .URL:
return article.preferredLink ?? ""
case ArticlePasteboardWriter.articleUTIType:
plist = exportDictionary()
case ArticlePasteboardWriter.articleUTIInternalType:
plist = internalDictionary()
default:
plist = nil
}
return plist
}
}
}
private extension ArticlePasteboardWriter {
func plainText() -> String {
var s = ""
if let title = article.title {
s += "\(title)\n\n"
}
if let text = article.contentText {
s += "\(text)\n\n"
}
else if let summary = article.summary {
s += "\(summary)\n\n"
}
else if let html = article.contentHTML {
let convertedHTML = html.convertingToPlainText()
s += "\(convertedHTML)\n\n"
}
if let link = article.link {
s += "URL: \(link)\n\n"
}
if let externalLink = article.externalLink {
s += "external URL: \(externalLink)\n\n"
}
s += "Date: \(article.logicalDatePublished)\n\n"
if let feed = article.feed {
s += "Feed: \(feed.nameForDisplay)\n"
if let homePageURL = feed.homePageURL {
s += "Home page: \(homePageURL)\n"
}
s += "URL: \(feed.url)"
}
return s
}
private struct Key {
static let articleID = "articleID" // database ID, unique per account
static let uniqueID = "uniqueID" // unique ID, unique per feed (guid, or possibly calculated)
static let feedURL = "feedURL"
static let feedID = "feedID" // may differ from feedURL if coming from a syncing system
static let title = "title"
static let contentHTML = "contentHTML"
static let contentText = "contentText"
static let url = "url" // usually a permalink
static let externalURL = "externalURL" // usually not a permalink
static let summary = "summary"
static let imageURL = "imageURL"
static let bannerImageURL = "bannerImageURL"
static let datePublished = "datePublished"
static let dateModified = "dateModified"
static let dateArrived = "dateArrived"
static let read = "read"
static let starred = "starred"
static let authors = "authors"
// Author
static let authorName = "name"
static let authorURL = "url"
static let authorAvatarURL = "avatarURL"
static let authorEmailAddress = "emailAddress"
// Internal
static let accountID = "accountID"
}
func exportDictionary() -> [String: Any] {
var d = [String: Any]()
d[Key.articleID] = article.articleID
d[Key.uniqueID] = article.uniqueID
if let feed = article.feed {
d[Key.feedURL] = feed.url
}
d[Key.feedID] = article.feedID
d[Key.title] = article.title ?? nil
d[Key.contentHTML] = article.contentHTML ?? nil
d[Key.contentText] = article.contentText ?? nil
d[Key.url] = article.rawLink ?? nil
d[Key.externalURL] = article.rawExternalLink ?? nil
d[Key.summary] = article.summary ?? nil
d[Key.imageURL] = article.rawImageLink ?? nil
d[Key.datePublished] = article.datePublished ?? nil
d[Key.dateModified] = article.dateModified ?? nil
d[Key.dateArrived] = article.status.dateArrived
d[Key.authors] = authorDictionaries() ?? nil
return d
}
func internalDictionary() -> [String: Any] {
var d = exportDictionary()
d[Key.accountID] = article.accountID
return d
}
func authorDictionary(_ author: Author) -> [String: Any] {
var d = [String: Any]()
d[Key.authorName] = author.name ?? nil
d[Key.authorURL] = author.url ?? nil
d[Key.authorAvatarURL] = author.avatarURL ?? nil
d[Key.authorEmailAddress] = author.emailAddress ?? nil
return d
}
func authorDictionaries() -> [[String: Any]]? {
guard let authors = article.authors, !authors.isEmpty else {
return nil
}
return authors.map{ authorDictionary($0) }
}
}