diff --git a/Evergreen/MainWindow/Timeline/ArticlePasteboardWriter.swift b/Evergreen/MainWindow/Timeline/ArticlePasteboardWriter.swift index 503d07461..67ed7a540 100644 --- a/Evergreen/MainWindow/Timeline/ArticlePasteboardWriter.swift +++ b/Evergreen/MainWindow/Timeline/ArticlePasteboardWriter.swift @@ -12,6 +12,15 @@ import Data @objc final class ArticlePasteboardWriter: NSObject, NSPasteboardWriting { private let article: Article + static let articleUTI = "com.ranchero.article" + static let articleUTIType = NSPasteboard.PasteboardType(rawValue: articleUTI) + static let articleUTIInternal = "com.ranchero.evergreen.internal.article" + static let articleUTIInternalType = NSPasteboard.PasteboardType(rawValue: articleUTIInternal) + + private lazy var renderedHTML: String = { + let articleRenderer = ArticleRenderer(article: article, style: ArticleStylesManager.shared.currentStyle) + return articleRenderer.html + }() init(article: Article) { @@ -22,31 +31,31 @@ import Data func writableTypes(for pasteboard: NSPasteboard) -> [NSPasteboard.PasteboardType] { - // TODO: add more types + var types = [ArticlePasteboardWriter.articleUTIType, .string] - var types = [NSPasteboard.PasteboardType]() - - if let _ = article.title { - types += [.string] - } if let link = article.preferredLink, let _ = URL(string: link) { types += [.URL] } + types += [.html, ArticlePasteboardWriter.articleUTIInternalType] - return types // TODO: add types + return types } func pasteboardPropertyList(forType type: NSPasteboard.PasteboardType) -> Any? { - // TODO: write data for all types declared in writableTypes. - let plist: Any? switch type { + case .html: + return renderedHTML case .string: - plist = article.title ?? "" + plist = plainText() case .URL: - plist = article.preferredLink ?? "" + return article.preferredLink ?? "" + case ArticlePasteboardWriter.articleUTIType: + plist = exportDictionary() + case ArticlePasteboardWriter.articleUTIInternalType: + plist = internalDictionary() default: plist = nil } @@ -54,3 +63,163 @@ import Data 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.rs_stringByConvertingToPlainText() + s += "\(convertedHTML)\n\n" + } + + if let url = article.url { + s += "URL: \(url)\n\n" + } + if let externalURL = article.externalURL { + s += "external URL: \(externalURL)\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 userDeleted = "userDeleted" + static let authors = "authors" + static let attachments = "attachments" + + // Author + static let authorName = "name" + static let authorURL = "url" + static let authorAvatarURL = "avatarURL" + static let authorEmailAddress = "emailAddress" + + // Attachment + static let attachmentURL = "url" + static let attachmentMimeType = "mimeType" + static let attachmentTitle = "title" + static let attachmentSizeInBytes = "sizeInBytes" + static let attachmentDurationInSeconds = "durationInSeconds" + + // 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.url ?? nil + d[Key.externalURL] = article.externalURL ?? nil + d[Key.summary] = article.summary ?? nil + d[Key.imageURL] = article.imageURL ?? nil + d[Key.bannerImageURL] = article.bannerImageURL ?? nil + d[Key.datePublished] = article.datePublished ?? nil + d[Key.dateModified] = article.dateModified ?? nil + d[Key.dateArrived] = article.status.dateArrived + d[Key.authors] = authorDictionaries() ?? nil + d[Key.attachments] = attachmentDictionaries() ?? 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 attachmentDictionary(_ attachment: Attachment) -> [String: Any] { + + var d = [String: Any]() + + d[Key.attachmentURL] = attachment.url + d[Key.attachmentMimeType] = attachment.mimeType ?? nil + d[Key.attachmentTitle] = attachment.title ?? nil + d[Key.attachmentSizeInBytes] = attachment.sizeInBytes ?? nil + d[Key.attachmentDurationInSeconds] = attachment.durationInSeconds ?? nil + + return d + } + + func authorDictionaries() -> [[String: Any]]? { + + guard let authors = article.authors, !authors.isEmpty else { + return nil + } + return authors.map{ authorDictionary($0) } + } + + func attachmentDictionaries() -> [[String: Any]]? { + + guard let attachments = article.attachments, !attachments.isEmpty else { + return nil + } + return attachments.map { attachmentDictionary($0) } + } +} +