Skip to content

Commit

Permalink
Based on discussion in #113 removed default popup implementation from…
Browse files Browse the repository at this point in the history
… navigator, made touch handling always send data across the bridge, noteref delegate method returns bool.
  • Loading branch information
tooolbox committed Apr 24, 2020
1 parent cc62715 commit 7514e06
Show file tree
Hide file tree
Showing 8 changed files with 108 additions and 154 deletions.
5 changes: 0 additions & 5 deletions r2-navigator-swift.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
/* Begin PBXBuildFile section */
0D77748B244F978A00A5E857 /* R2Shared.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0D777488244F977E00A5E857 /* R2Shared.framework */; };
0D77748D244F97F200A5E857 /* SwiftSoup.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0D77747E244F970E00A5E857 /* SwiftSoup.framework */; };
0D777495244FEB2200A5E857 /* BarButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D777494244FEB2200A5E857 /* BarButtonItem.swift */; };
CA0B3AC3222EE555006D9363 /* PDFNavigatorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA0B3AC2222EE555006D9363 /* PDFNavigatorViewController.swift */; };
CA1E4F4B240037E6009C4DE3 /* CompletionList.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA1E4F4A240037E6009C4DE3 /* CompletionList.swift */; };
CA26EF7E22803FE90011653E /* VisualNavigator.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA26EF7D22803FE90011653E /* VisualNavigator.swift */; };
Expand Down Expand Up @@ -58,7 +57,6 @@
03C3CC67222DBD8600A01731 /* R2Shared.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = R2Shared.framework; sourceTree = BUILT_PRODUCTS_DIR; };
0D77747E244F970E00A5E857 /* SwiftSoup.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftSoup.framework; path = "../r2-testapp-swift/Carthage/Build/iOS/SwiftSoup.framework"; sourceTree = "<group>"; };
0D777482244F977E00A5E857 /* r2-shared-swift.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = "r2-shared-swift.xcodeproj"; path = "../r2-shared-swift/r2-shared-swift.xcodeproj"; sourceTree = "<group>"; };
0D777494244FEB2200A5E857 /* BarButtonItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BarButtonItem.swift; sourceTree = "<group>"; };
0D82BF0D244EAC62006FDB31 /* SwiftSoup.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftSoup.framework; path = Carthage/Build/iOS/SwiftSoup.framework; sourceTree = "<group>"; };
0D82BF11244EAE04006FDB31 /* R2Shared.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = R2Shared.framework; path = Carthage/Build/iOS/R2Shared.framework; sourceTree = "<group>"; };
CA0B3AC2222EE555006D9363 /* PDFNavigatorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PDFNavigatorViewController.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -131,8 +129,6 @@
CAF1E3F422DF23F400E807EA /* PaginationView.swift */,
CAC2A6D62292E4BA000AA2A7 /* WebView.swift */,
CAD178B522B3B553004E6812 /* R2NavigatorLocalizedString.swift */,
CA1E4F4A240037E6009C4DE3 /* CompletionList.swift */,
0D777494244FEB2200A5E857 /* BarButtonItem.swift */,
);
path = Toolkit;
sourceTree = "<group>";
Expand Down Expand Up @@ -353,7 +349,6 @@
F3E7D3E61F4D84EF00DF166D /* EPUBSpreadView.swift in Sources */,
F3E7D42E1F4EE0FE00DF166D /* CBZNavigatorViewController.swift in Sources */,
CA479DC52264AEA20053445E /* UIColor.swift in Sources */,
0D777495244FEB2200A5E857 /* BarButtonItem.swift in Sources */,
CAD178B622B3B553004E6812 /* R2NavigatorLocalizedString.swift in Sources */,
CAEACA222272EFBD00476340 /* ImageViewController.swift in Sources */,
CACE84FB2254BFEE00E19E8B /* EditingAction.swift in Sources */,
Expand Down
7 changes: 3 additions & 4 deletions r2-navigator-swift/EPUB/EPUBFixedSpreadView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,9 @@ final class EPUBFixedSpreadView: EPUBSpreadView {
""")
}

override func pointFromTap(_ data: [String : Any]) -> CGPoint? {
guard let x = data["screenX"] as? Int, let y = data["screenY"] as? Int else {
return nil
}
override func pointFromTap(_ data: TapData) -> CGPoint? {
let x = data.screenX
let y = data.screenY

return CGPoint(
x: CGFloat(x) * scrollView.zoomScale - scrollView.contentOffset.x + webView.frame.minX,
Expand Down
58 changes: 38 additions & 20 deletions r2-navigator-swift/EPUB/EPUBNavigatorViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -367,49 +367,67 @@ extension EPUBNavigatorViewController: EPUBSpreadViewDelegate {
delegate?.navigator(self, presentExternalURL: url)
}

func spreadView(_ spreadView: EPUBSpreadView, didTapOnInternalLink href: String) {
func spreadView(_ spreadView: EPUBSpreadView, didTapOnInternalLink href: String, anchor: String?) {

// Check to see if this was a noteref link and give delegate the opportunity to display it.
if let anchor = anchor, let note = getNoteContent(anchor: anchor), let delegate = self.delegate {
if delegate.navigator(self,
shouldNavigateToNoteAt: Link(href: href),
content: note,
source: anchor) == false {
return
}
}

go(to: Link(href: href))
}

func spreadView(_ spreadView: EPUBSpreadView, didTapOnNoterefLink html: String, resource: URL) {

func getNoteContent(anchor: String) -> String? {
do {
let doc = try parse(html)
guard let link = try doc.select("a[epub:type=noteref]").first() else {
return log(.error, "Could not find noteref link")
}
let doc = try parse(anchor)
guard let link = try doc.select("a[epub:type=noteref]").first() else { return nil }

let href = try link.attr("href")
guard let hashIndex = href.lastIndex(of: "#") else {
return log(.error, "Could not find hash in link \(href)")
log(.error, "Could not find hash in link \(href)")
return nil
}
let id = String(href[href.index(hashIndex, offsetBy: 1)...])
let withoutFragment = String(href[..<hashIndex])

guard var loc = self.currentLocation?.href else {
log(.error, "Couldn't get current location")
return nil
}
if loc.hasPrefix("/") {
loc = String(loc.dropFirst())
}

guard let base = publication.baseURL else {
log(.error, "Couldn't get publication base URL")
return nil
}

let resource = base.appendingPathComponent(loc)
guard let absolute = URL(string: withoutFragment, relativeTo: resource) else {
log(.error, "Could not get absolute URL from \(withoutFragment) relative to \(resource)")
return
log(.error, "Could not get absolute URL from \(withoutFragment) relative to \(self.resourcesURL?.absoluteString ?? "(no self.resourcesURL)")")
return nil
}

log(.debug, "Fetching note contents from \(absolute.absoluteString)")
let contents = try String(contentsOf: absolute)
let document = try parse(contents)
guard let aside = try document.select("#\(id)").first() else {
log(.error, "Could not find the element '#\(id)' in document \(absolute)")
return
}

guard let safe = try clean(aside.html(), .relaxed()) else {
return log(.error, "Could not clean <aside>")
return nil
}

let from = try link.html()

delegate?.navigator(self, presentNote: safe, at: Link(href: absolute.absoluteString), from: from)
return try aside.html()

} catch {
log(.error, "Caught error while displaying noteref: \(error)")
log(.error, "Caught error while getting note content: \(error)")
return nil
}

}

func spreadViewPagesDidChange(_ spreadView: EPUBSpreadView) {
Expand Down
7 changes: 3 additions & 4 deletions r2-navigator-swift/EPUB/EPUBReflowableSpreadView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -108,10 +108,9 @@ final class EPUBReflowableSpreadView: EPUBSpreadView {
}
}

override func pointFromTap(_ data: [String : Any]) -> CGPoint? {
guard let x = data["clientX"] as? Int, let y = data["clientY"] as? Int else {
return nil
}
override func pointFromTap(_ data: TapData) -> CGPoint? {
let x = data.clientX
let y = data.clientY

var point = CGPoint(x: x, y: y)
if isScrollEnabled {
Expand Down
60 changes: 37 additions & 23 deletions r2-navigator-swift/EPUB/EPUBSpreadView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,7 @@ protocol EPUBSpreadViewDelegate: class {
func spreadView(_ spreadView: EPUBSpreadView, didTapOnExternalURL url: URL)

/// Called when the user tapped on an internal link.
func spreadView(_ spreadView: EPUBSpreadView, didTapOnInternalLink href: String)

/// Called when the user taps on a noteref link.
func spreadView(_ spreadView: EPUBSpreadView, didTapOnNoterefLink html: String, resource: URL)
func spreadView(_ spreadView: EPUBSpreadView, didTapOnInternalLink href: String, anchor: String?)

/// Called when the pages visible in the spread changed.
func spreadViewPagesDidChange(_ spreadView: EPUBSpreadView)
Expand All @@ -53,6 +50,8 @@ class EPUBSpreadView: UIView, Loggable {
let readingProgression: ReadingProgression
let userSettings: UserSettings
let editingActions: EditingActionsController

var lastTap: TapData? = nil

/// If YES, the content will be faded in once loaded.
let animatedLoad: Bool
Expand Down Expand Up @@ -196,27 +195,20 @@ class EPUBSpreadView: UIView, Loggable {
}

/// Called from the JS code when a tap is detected.
private func didTap(_ body: Any) {
guard let body = body as? [String: Any],
let point = pointFromTap(body) else
{
return
}

/// If the JS indicates the tap is being handled within the webview, don't take action,
/// just save the tap data for use by webView(_ webView:decidePolicyFor:decisionHandler:)
private func didTap(_ data: Any) {
let tapData = TapData(data: data)
lastTap = tapData

guard tapData.shouldHandle else { return }

guard let point = pointFromTap(tapData) else { return }
delegate?.spreadView(self, didTapAt: point)
}

/// Called from the JS code when a noteref is tapped.
private func didTapNoteref(_ html: Any) {
guard
let html = html as? String,
let url = self.webView.url
else { return }
delegate?.spreadView(self, didTapOnNoterefLink: html, resource: url)
}

/// Converts the touch data returned by the JavaScript `tap` event into a point in the webview's coordinate space.
func pointFromTap(_ data: [String: Any]) -> CGPoint? {
func pointFromTap(_ data: TapData) -> CGPoint? {
// To override in subclasses.
return nil
}
Expand Down Expand Up @@ -356,7 +348,6 @@ class EPUBSpreadView: UIView, Loggable {
registerJSMessage(named: "tap") { [weak self] in self?.didTap($0) }
registerJSMessage(named: "spreadLoaded") { [weak self] in self?.spreadDidLoad($0) }
registerJSMessage(named: "selectionChanged") { [weak self] in self?.selectionDidChange($0) }
registerJSMessage(named: "tapNoteref") { [weak self] in self?.didTapNoteref($0) }
}

/// Add the message handlers for incoming javascript events.
Expand Down Expand Up @@ -436,7 +427,7 @@ extension EPUBSpreadView: WKNavigationDelegate {
// Check if url is internal or external
if let baseURL = publication.baseURL, url.host == baseURL.host {
let href = url.absoluteString.replacingOccurrences(of: baseURL.absoluteString, with: "/")
delegate?.spreadView(self, didTapOnInternalLink: href)
delegate?.spreadView(self, didTapOnInternalLink: href, anchor: self.lastTap?.anchor)
} else {
delegate?.spreadView(self, didTapOnExternalURL: url)
}
Expand Down Expand Up @@ -517,3 +508,26 @@ private extension EPUBSpreadView {
}

}

/// Produced by gestures.js
struct TapData {
let shouldHandle: Bool
let screenX: Int
let screenY: Int
let clientX: Int
let clientY: Int
let anchor: String?

init(dict: [String: Any]) {
self.shouldHandle = dict["shouldHandle"] as? Bool ?? false
self.screenX = dict["screenX"] as? Int ?? 0
self.screenY = dict["screenY"] as? Int ?? 0
self.clientX = dict["clientX"] as? Int ?? 0
self.clientY = dict["clientY"] as? Int ?? 0
self.anchor = dict["anchor"] as? String
}

init(data: Any) {
self.init(dict: data as? [String: Any] ?? [String: Any]())
}
}
35 changes: 21 additions & 14 deletions r2-navigator-swift/EPUB/Resources/Scripts/gestures.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,28 +7,33 @@
});

function onClick(event) {
if (isNoteref(event.target)) {
event.stopPropagation();
event.preventDefault();

webkit.messageHandlers.tapNoteref.postMessage(event.target.outerHTML);
return;
}
// If the app should handle the tap.
// Examples of the app handling the tap would be
// navigating left/right, or show/hide the toolbar.
// If false, the tap is being handled within the webview,
// such as with a hyperlink or by an publication's JS handler.
let appShouldHandle = true;

if (event.defaultPrevented || isInteractiveElement(event.target)) {
return;
appShouldHandle = false;
}

if (!window.getSelection().isCollapsed) {
// There's an on-going selection, the tap will dismiss it so we don't forward it.
return;
}

// Send the tap data over the JS bridge even if it's been handled
// within the webview, so that it can be preserved and used
// by the WKNavigationDelegate if needed.
webkit.messageHandlers.tap.postMessage({
"shouldHandle": appShouldHandle,
"screenX": event.screenX,
"screenY": event.screenY,
"clientX": event.clientX,
"clientY": event.clientY,
"anchor": getNearestAnchor(event.target),
});

// We don't want to disable the default WebView behavior as it breaks some features without bringing any value.
Expand Down Expand Up @@ -69,16 +74,18 @@
return false;
}

function isNoteref(element) {
if (
element.nodeName.toLowerCase() === 'a' &&
element.getAttributeNS('http://www.idpf.org/2007/ops', 'type') === 'noteref'
) {
return true;
// Retrieves the markup of <a>...</a> if the tap was
// anywhere within such an element (i.e. even on an <em> tag within it).
// We return the markup rather than just a boolean as this could be more
// useful further up the line.
function getNearestAnchor(element) {
if (element.nodeName.toLowerCase() === 'a') {
return element.outerHTML;
}
if (element.parentElement) {
return isNoteref(element.parentElement);
return getNearestAnchor(element.parentElement);
}
return null;
}

})();
50 changes: 6 additions & 44 deletions r2-navigator-swift/Navigator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,9 @@
import Foundation
import SafariServices
import R2Shared
import SwiftSoup
import WebKit


public protocol Navigator: UIViewController {
public protocol Navigator {

/// Current position in the publication.
/// Can be used to save a bookmark to the current position.
Expand Down Expand Up @@ -88,8 +86,8 @@ public protocol NavigatorDelegate: AnyObject {
/// Called when the user tapped an external URL. The default implementation opens the URL with the default browser.
func navigator(_ navigator: Navigator, presentExternalURL url: URL)

/// Called when the user tapped on a noteref such as a footnote. The default implementation opens the target "aside" in a modal.
func navigator(_ navigator: Navigator, presentNote content: String, at link: Link, from source: String)
/// Called when the user taps on a noteref link.
func navigator(_ navigator: Navigator, shouldNavigateToNoteAt link: Link, content: String, source: String) -> Bool

}

Expand All @@ -101,47 +99,11 @@ public extension NavigatorDelegate {
UIApplication.shared.openURL(url)
}
}

}

public extension NavigatorDelegate {

func navigator(_ navigator: Navigator, presentNote content: String, at link: Link, from source: String) {

var title = try? clean(source, .none())
if title == "*" {
title = nil
}

let content = (try? clean(content, .none())) ?? ""
let page =
"""
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
\(content)
</body>
</html>
"""


let wk = WKWebView()
wk.loadHTMLString(page, baseURL: nil)

let vc = UIViewController()
vc.view = wk
vc.navigationItem.title = title
vc.navigationItem.leftBarButtonItem = BarButtonItem(barButtonSystemItem: .done, actionHandler: { (item) in
vc.dismiss(animated: true, completion: nil)
})

let nav = UINavigationController(rootViewController: vc)
nav.modalPresentationStyle = .formSheet
navigator.present(nav, animated: true, completion: nil)
func navigator(_ navigator: Navigator, shouldNavigateToNoteAt link: Link, content: String, source: String) -> Bool {
return true
}

}


Expand Down
Loading

0 comments on commit 7514e06

Please sign in to comment.