mirror of
https://github.com/omnivore-app/omnivore.git
synced 2026-03-11 08:54:26 +00:00
use persisted article content model to store html and highlights string
This commit is contained in:
parent
dd55cd86ae
commit
6968e06da5
9 changed files with 106 additions and 42 deletions
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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"/>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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]()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) ?? "[]"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)) ?? []
|
||||
|
|
|
|||
Loading…
Reference in a new issue