Skip to content

Commit

Permalink
HTML/EPUB reader implemented
Browse files Browse the repository at this point in the history
  • Loading branch information
michalrentka committed Mar 1, 2024
1 parent d49f849 commit bb0272b
Show file tree
Hide file tree
Showing 19 changed files with 3,064 additions and 23 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,5 @@ fastlane/test_output

bundled/translators
bundled/styles
bundled/locales
bundled/locales
bundled/reader
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,6 @@
[submodule "translators"]
path = translators
url = https://github.com/zotero/translators.git
[submodule "reader"]
path = reader
url = https://github.com/zotero/reader.git
72 changes: 72 additions & 0 deletions Zotero.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
//
// CreateHtmlEpubAnnotationsDbRequest.swift
// Zotero
//
// Created by Michal Rentka on 28.09.2023.
// Copyright © 2023 Corporation for Digital Scholarship. All rights reserved.
//

import Foundation

import RealmSwift

struct CreateHtmlEpubAnnotationsDbRequest: DbRequest {
let attachmentKey: String
let libraryId: LibraryIdentifier
let annotations: [HtmlEpubAnnotation]
let userId: Int

unowned let schemaController: SchemaController

var needsWrite: Bool { return true }

func process(in database: Realm) throws {
guard let parent = database.objects(RItem.self).filter(.key(self.attachmentKey, in: self.libraryId)).first else { return }

for annotation in self.annotations {
self.create(annotation: annotation, parent: parent, in: database)
}
}

private func create(annotation: HtmlEpubAnnotation, parent: RItem, in database: Realm) {
let item: RItem

if let _item = database.objects(RItem.self).filter(.key(annotation.key, in: self.libraryId)).first {
if !_item.deleted {
// If item exists and is not deleted locally, we can ignore this request
return
}

// If item exists and was already deleted locally and not yet synced, we re-add the item
item = _item
item.deleted = false
} else {
// If item didn't exist, create it
item = RItem()
item.key = annotation.key
item.rawType = ItemTypes.annotation
item.localizedType = self.schemaController.localized(itemType: ItemTypes.annotation) ?? ""
item.libraryId = self.libraryId
item.dateAdded = annotation.dateCreated
database.add(item)
}

item.syncState = .synced
item.changeType = .user
item.htmlFreeContent = annotation.comment.isEmpty ? nil : annotation.comment.strippedRichTextTags
item.dateModified = annotation.dateModified
item.parent = parent

if annotation.isAuthor {
item.createdBy = database.object(ofType: RUser.self, forPrimaryKey: self.userId)
}

// We need to submit tags on creation even if they are empty, so we need to mark them as changed
self.addFields(for: annotation, to: item, database: database)
let changes: RItemChanges = [.parent, .fields, .type, .tags]
item.changes.append(RObjectChange.create(changes: changes))
}

private func addFields(for annotation: HtmlEpubAnnotation, to item: RItem, database: Realm) {
for field in FieldKeys.Item.Annotation.allHtmlEpubFields(for: annotation.type) {
let rField = RItemField()
rField.key = field.key
rField.baseKey = field.baseKey
rField.changed = true

switch (field.key, field.baseKey) {
case (FieldKeys.Item.Annotation.type, nil):
rField.value = annotation.type.rawValue

case (FieldKeys.Item.Annotation.color, nil):
rField.value = annotation.color

case (FieldKeys.Item.Annotation.comment, nil):
rField.value = annotation.comment

case (FieldKeys.Item.Annotation.pageLabel, nil):
rField.value = annotation.pageLabel

case (FieldKeys.Item.Annotation.sortIndex, nil):
rField.value = annotation.sortIndex
item.annotationSortIndex = annotation.sortIndex

case (FieldKeys.Item.Annotation.text, nil):
rField.value = annotation.text ?? ""

case (FieldKeys.Item.Annotation.Position.htmlEpubType, FieldKeys.Item.Annotation.position):
guard let value = annotation.position[FieldKeys.Item.Annotation.Position.htmlEpubType] as? String else { continue }
rField.value = value

case (FieldKeys.Item.Annotation.Position.htmlEpubValue, FieldKeys.Item.Annotation.position):
guard let value = annotation.position[FieldKeys.Item.Annotation.Position.htmlEpubValue] as? String else { continue }
rField.value = value

default: break
}

item.fields.append(rField)
}
}

private func addTags(for annotation: HtmlEpubAnnotation, to item: RItem, database: Realm) {
let allTags = database.objects(RTag.self)

for tag in annotation.tags {
guard let rTag = allTags.filter(.name(tag.name)).first else { continue }

let rTypedTag = RTypedTag()
rTypedTag.type = .manual
database.add(rTypedTag)

rTypedTag.item = item
rTypedTag.tag = rTag
}
}
}
5 changes: 5 additions & 0 deletions Zotero/Models/Defaults.swift
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,11 @@ final class Defaults {

@CodableUserDefault(key: "PDFReaderSettings", defaultValue: PDFSettings.default, encoder: Defaults.jsonEncoder, decoder: Defaults.jsonDecoder, defaults: .standard)
var pdfSettings: PDFSettings

// MARK: - HTML / Epub Settings

@CodableUserDefault(key: "HtmlEpubReaderSettings", defaultValue: HtmlEpubSettings.default, encoder: Defaults.jsonEncoder, decoder: Defaults.jsonDecoder, defaults: .standard)
var htmlEpubSettings: HtmlEpubSettings
#endif

// MARK: - Citation / Bibliography Export
Expand Down
28 changes: 28 additions & 0 deletions Zotero/Models/FieldKeys.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ struct FieldKeys {
static let rects = "rects"
static let paths = "paths"
static let lineWidth = "width"
static let htmlEpubType = "type"
static let htmlEpubValue = "value"
}

static let type = "annotationType"
Expand Down Expand Up @@ -122,6 +124,32 @@ struct FieldKeys {
KeyBaseKeyPair(key: Annotation.Position.pageIndex, baseKey: Annotation.position)]
}
}

static func allHtmlEpubFields(for type: AnnotationType) -> [KeyBaseKeyPair] {
switch type {
case .highlight:
return [KeyBaseKeyPair(key: Annotation.type, baseKey: nil),
KeyBaseKeyPair(key: Annotation.comment, baseKey: nil),
KeyBaseKeyPair(key: Annotation.color, baseKey: nil),
KeyBaseKeyPair(key: Annotation.pageLabel, baseKey: nil),
KeyBaseKeyPair(key: Annotation.sortIndex, baseKey: nil),
KeyBaseKeyPair(key: Annotation.text, baseKey: nil),
KeyBaseKeyPair(key: Annotation.Position.htmlEpubType, baseKey: Annotation.position),
KeyBaseKeyPair(key: Annotation.Position.htmlEpubValue, baseKey: Annotation.position)]

case .note:
return [KeyBaseKeyPair(key: Annotation.type, baseKey: nil),
KeyBaseKeyPair(key: Annotation.comment, baseKey: nil),
KeyBaseKeyPair(key: Annotation.color, baseKey: nil),
KeyBaseKeyPair(key: Annotation.pageLabel, baseKey: nil),
KeyBaseKeyPair(key: Annotation.sortIndex, baseKey: nil),
KeyBaseKeyPair(key: Annotation.Position.htmlEpubType, baseKey: Annotation.position),
KeyBaseKeyPair(key: Annotation.Position.htmlEpubValue, baseKey: Annotation.position)]

case .ink, .image:
return []
}
}
}

static func clean(doi: String) -> String {
Expand Down
18 changes: 15 additions & 3 deletions Zotero/Scenes/Detail/DetailCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -194,9 +194,9 @@ final class DetailCoordinator: Coordinator {
DDLogInfo("DetailCoordinator: show PDF \(attachment.key)")
self.showPdf(at: url, key: attachment.key, parentKey: parentKey, library: library)

case "text/html":
DDLogInfo("DetailCoordinator: show HTML \(attachment.key)")
self.showWebView(for: url)
case "text/html", "application/epub+zip":
DDLogInfo("DetailCoordinator: show HTML / EPUB \(attachment.key)")
self.showHtmlEpubReader(for: url, key: attachment.key, library: library)

case "text/plain":
let text = try? String(contentsOf: url, encoding: .utf8)
Expand Down Expand Up @@ -302,6 +302,18 @@ final class DetailCoordinator: Coordinator {
navigationController?.present(controller, animated: true, completion: nil)
}

private func showHtmlEpubReader(for url: URL, key: String, library: Library) {
let navigationController = NavigationViewController()
navigationController.modalPresentationStyle = .fullScreen

let coordinator = HtmlEpubCoordinator(key: key, library: library, url: url, navigationController: navigationController, controllers: controllers)
coordinator.parentCoordinator = self
self.childCoordinators.append(coordinator)
coordinator.start(animated: false)

self.navigationController?.present(navigationController, animated: true, completion: nil)
}

private func showWebView(for url: URL) {
guard let currentNavigationController = self.navigationController else { return }
let controller = WebViewController(url: url)
Expand Down
Loading

0 comments on commit bb0272b

Please sign in to comment.