use persisted article content model to store html and highlights string

This commit is contained in:
Satindar Dhillon 2022-04-20 14:42:27 -07:00
parent dd55cd86ae
commit 6968e06da5
9 changed files with 106 additions and 42 deletions

View file

@ -6,7 +6,8 @@ import WebKit
#if os(iOS)
struct WebReader: UIViewRepresentable {
let articleContent: ArticleContentDep
let htmlContent: String
let highlightsJSONString: String
let item: FeedItemDep
let openLinkAction: (URL) -> Void
let webViewActionHandler: (WKScriptMessage, WKScriptMessageReplyHandler?) -> Void
@ -99,7 +100,8 @@ import WebKit
func loadContent(webView: WKWebView) {
webView.loadHTMLString(
WebReaderContent(
articleContent: articleContent,
htmlContent: htmlContent,
highlightsJSONString: highlightsJSONString,
item: item,
isDark: UITraitCollection.current.userInterfaceStyle == .dark,
fontSize: fontSize()

View file

@ -149,7 +149,8 @@ import WebKit
ZStack {
if let articleContent = viewModel.articleContent {
WebReader(
articleContent: articleContent,
htmlContent: articleContent.htmlContent,
highlightsJSONString: articleContent.highlightsJSONString,
item: item,
openLinkAction: {
#if os(macOS)

View file

@ -4,18 +4,21 @@ import Utils
struct WebReaderContent {
let textFontSize: Int
let articleContent: ArticleContentDep
let htmlContent: String
let highlightsJSONString: String
let item: FeedItemDep
let themeKey: String
init(
articleContent: ArticleContentDep,
htmlContent: String,
highlightsJSONString: String,
item: FeedItemDep,
isDark: Bool,
fontSize: Int
) {
self.textFontSize = fontSize
self.articleContent = articleContent
self.htmlContent = htmlContent
self.highlightsJSONString = highlightsJSONString
self.item = item
self.themeKey = isDark ? "Gray" : "LightGray"
}
@ -39,7 +42,7 @@ struct WebReaderContent {
<body>
<div id="root" />
<div id='_omnivore-htmlContent' style="display: none;">
\(articleContent.htmlContent)
\(htmlContent)
</div>
<script type="text/javascript">
window.omnivoreEnv = {
@ -63,7 +66,7 @@ struct WebReaderContent {
contentReader: "WEB",
readingProgressPercent: \(item.readingProgress),
readingProgressAnchorIndex: \(item.readingProgressAnchor),
highlights: \(articleContent.highlightsJSONString),
highlights: \(highlightsJSONString),
}
window.fontSize = \(textFontSize)

View file

@ -24,7 +24,6 @@
<attribute name="contentReader" optional="YES" attributeType="String"/>
<attribute name="createdAt" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="descriptionText" optional="YES" attributeType="String"/>
<attribute name="htmlContent" optional="YES" attributeType="String"/>
<attribute name="id" attributeType="String"/>
<attribute name="imageURLString" optional="YES" attributeType="String"/>
<attribute name="isArchived" attributeType="Boolean" usesScalarValueType="YES"/>
@ -90,7 +89,7 @@
</entity>
<elements>
<element name="Highlight" positionX="27" positionY="225" width="128" height="209"/>
<element name="LinkedItem" positionX="-18" positionY="63" width="128" height="314"/>
<element name="LinkedItem" positionX="-18" positionY="63" width="128" height="299"/>
<element name="LinkedItemLabel" positionX="-36" positionY="18" width="128" height="104"/>
<element name="NewsletterEmail" positionX="0" positionY="180" width="128" height="74"/>
<element name="PersistedArticleContent" positionX="9" positionY="108" width="128" height="74"/>

View file

@ -2,26 +2,13 @@ import Foundation
public struct ArticleContentDep {
public let htmlContent: String
public let highlights: [HighlightDep]
public let storedHighlightsJSONString: String?
public let highlightsJSONString: String
public init(
htmlContent: String,
highlights: [HighlightDep],
storedHighlightsJSONString: String?
highlightsJSONString: String
) {
self.htmlContent = htmlContent
self.highlights = highlights
self.storedHighlightsJSONString = storedHighlightsJSONString
}
public var highlightsJSONString: String {
if let storedHighlightsJSONString = storedHighlightsJSONString {
return storedHighlightsJSONString
}
let jsonData = try? JSONEncoder().encode(highlights)
guard let jsonData = jsonData else { return "[]" }
return String(data: jsonData, encoding: .utf8) ?? "[]"
self.highlightsJSONString = highlightsJSONString
}
}

View file

@ -77,15 +77,30 @@ public extension DataService {
}
func pageFromCache(slug: String) -> ArticleContentDep? {
// TODO: cerate highlightsJSON from stored highlights
// let fetchRequest: NSFetchRequest<Models.LinkedItem> = LinkedItem.fetchRequest()
// fetchRequest.predicate = NSPredicate(
// format: "slug == %@", slug
// )
//
// guard let linkedItem = try? persistentContainer.viewContext.fetch(fetchRequest).first else { return nil }
//
// let highlightsFetchRequest: NSFetchRequest<Models.Highlight> = Highlight.fetchRequest()
// fetchRequest.predicate = NSPredicate(
// format: "linkedItemId == %@", linkedItem.id ?? ""
// )
//
// let highlights = (try? persistentContainer.viewContext.fetch(highlightsFetchRequest)) ?? []
let fetchRequest: NSFetchRequest<Models.PersistedArticleContent> = PersistedArticleContent.fetchRequest()
fetchRequest.predicate = NSPredicate(
format: "slug = %@", slug
)
if let articleContent = try? persistentContainer.viewContext.fetch(fetchRequest).first {
if let articleContent = (try? persistentContainer.viewContext.fetch(fetchRequest))?.first {
return ArticleContentDep(
htmlContent: articleContent.htmlContent ?? "",
highlights: [],
storedHighlightsJSONString: articleContent.highlightsJSONString
highlightsJSONString: articleContent.highlightsJSONString ?? ""
)
} else {
return nil
@ -94,3 +109,13 @@ public extension DataService {
func invalidateCachedPage(slug _: String?) {}
}
// TODO: move to util file
extension Optional where Wrapped == NSSet {
func asArray<T: Hashable>(of _: T.Type) -> [T] {
if let set = self as? Set<T> {
return Array(set)
}
return [T]()
}
}

View file

@ -1,20 +1,25 @@
import Combine
import CoreData
import Foundation
import Models
import SwiftGraphQL
public extension DataService {
struct ArticleContent {
let htmlContent: String
let highlights: [InternalHighlight]
}
func articleContentPublisher(username: String, slug: String) -> AnyPublisher<ArticleContentDep, ServerError> {
enum QueryResult {
case success(result: ArticleContentDep)
case success(result: ArticleContent)
case error(error: String)
}
let articleSelection = Selection.Article {
ArticleContentDep(
ArticleContent(
htmlContent: try $0.content(),
highlights: try $0.highlights(selection: highlightDepSelection.list),
storedHighlightsJSONString: nil
highlights: try $0.highlights(selection: highlightSelection.list)
)
}
@ -44,8 +49,18 @@ public extension DataService {
switch payload.data {
case let .success(result: result):
// store result in core data
self?.persistArticleContent(htmlContent: result.htmlContent, slug: slug, highlights: result.highlights)
promise(.success(result))
let highlightsJSONString = result.highlights.asJSONString
self?.persistArticleContent(
htmlContent: result.htmlContent,
slug: slug,
highlightsJSONString: highlightsJSONString
)
promise(.success(
ArticleContentDep(
htmlContent: result.htmlContent,
highlightsJSONString: highlightsJSONString
))
)
case .error:
promise(.failure(.unknown))
}
@ -61,21 +76,30 @@ public extension DataService {
}
extension DataService {
func persistArticleContent(htmlContent: String, slug: String, highlights: [HighlightDep]) {
func persistArticleContent(htmlContent: String, slug: String, highlightsJSONString: String) {
let persistedArticleContent = PersistedArticleContent(context: persistentContainer.viewContext)
persistedArticleContent.htmlContent = htmlContent
persistedArticleContent.slug = slug
persistedArticleContent.highlightsJSONString = highlightsJSONString
if let jsonData = try? JSONEncoder().encode(highlights) {
persistedArticleContent.highlightsJSONString = String(data: jsonData, encoding: .utf8)
}
// TODO: store highlights and create json string at call time
// let fetchRequest: NSFetchRequest<Models.LinkedItem> = LinkedItem.fetchRequest()
// fetchRequest.predicate = NSPredicate(
// format: "slug == %@", slug
// )
//
// if let linkedItem = try? persistentContainer.viewContext.fetch(fetchRequest).first {
// _ = highlights.map {
// $0.asManagedObject(context: persistentContainer.viewContext, associatedItemID: linkedItem.id ?? "")
// }
// }
do {
try persistentContainer.viewContext.save()
print("PersistedArticleContent saved succesfully")
print("ArticleContent saved succesfully")
} catch {
persistentContainer.viewContext.rollback()
print("Failed to save PersistedArticleContent: \(error)")
print("Failed to save ArticleContent: \(error)")
}
}
}

View file

@ -31,6 +31,21 @@ struct InternalHighlight: Encodable {
return highlight
}
static func make(from highlight: Highlight) -> InternalHighlight {
InternalHighlight(
id: highlight.id ?? "",
shortId: highlight.shortId ?? "",
quote: highlight.quote ?? "",
prefix: highlight.prefix,
suffix: highlight.suffix,
patch: highlight.patch ?? "",
annotation: highlight.annotation,
createdAt: highlight.createdAt,
updatedAt: highlight.updatedAt,
createdByMe: highlight.createdByMe
)
}
func persist(
context: NSManagedObjectContext,
associatedItemID: String,
@ -62,3 +77,11 @@ struct InternalHighlight: Encodable {
return try? JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String: Any]
}
}
extension Array where Element == InternalHighlight {
var asJSONString: String {
let jsonData = try? JSONEncoder().encode(self)
guard let jsonData = jsonData else { return "[]" }
return String(data: jsonData, encoding: .utf8) ?? "[]"
}
}

View file

@ -8,7 +8,7 @@ public extension DataService {
func cachedHighlights(pdfID: String) -> [HighlightDep] {
let fetchRequest: NSFetchRequest<Models.Highlight> = Highlight.fetchRequest()
fetchRequest.predicate = NSPredicate(
format: "linkedItemId = %@ AND markedForDeletion = %@", pdfID, false
format: "linkedItemId == %@ AND markedForDeletion == %@", pdfID, false
)
let highlights = (try? persistentContainer.viewContext.fetch(fetchRequest)) ?? []