This repository was archived by the owner on Nov 26, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 784
/
Copy pathFolioReaderWebView.swift
380 lines (309 loc) · 14.2 KB
/
FolioReaderWebView.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
//
// FolioReaderWebView.swift
// FolioReaderKit
//
// Created by Hans Seiffert on 21.09.16.
// Copyright (c) 2016 Folio Reader. All rights reserved.
//
import UIKit
/// The custom WebView used in each page
open class FolioReaderWebView: UIWebView {
var isColors = false
var isShare = false
var isOneWord = false
fileprivate weak var readerContainer: FolioReaderContainer?
fileprivate var readerConfig: FolioReaderConfig {
guard let readerContainer = readerContainer else { return FolioReaderConfig() }
return readerContainer.readerConfig
}
fileprivate var book: FRBook {
guard let readerContainer = readerContainer else { return FRBook() }
return readerContainer.book
}
fileprivate var folioReader: FolioReader {
guard let readerContainer = readerContainer else { return FolioReader() }
return readerContainer.folioReader
}
override init(frame: CGRect) {
fatalError("use init(frame:readerConfig:book:) instead.")
}
init(frame: CGRect, readerContainer: FolioReaderContainer) {
self.readerContainer = readerContainer
super.init(frame: frame)
}
required public init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - UIMenuController
open override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
guard readerConfig.useReaderMenuController else {
return super.canPerformAction(action, withSender: sender)
}
if isShare {
return false
} else if isColors {
return false
} else {
if action == #selector(highlight(_:))
|| action == #selector(highlightWithNote(_:))
|| action == #selector(updateHighlightNote(_:))
|| (action == #selector(define(_:)) && isOneWord)
|| (action == #selector(play(_:)) && (book.hasAudio || readerConfig.enableTTS))
|| (action == #selector(share(_:)) && readerConfig.allowSharing)
|| (action == #selector(copy(_:)) && readerConfig.allowSharing) {
return true
}
return false
}
}
// MARK: - UIMenuController - Actions
@objc func share(_ sender: UIMenuController) {
let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
let shareImage = UIAlertAction(title: self.readerConfig.localizedShareImageQuote, style: .default, handler: { (action) -> Void in
if self.isShare {
if let textToShare = self.js("getHighlightContent()") {
self.folioReader.readerCenter?.presentQuoteShare(textToShare)
}
} else {
if let textToShare = self.js("getSelectedText()") {
self.folioReader.readerCenter?.presentQuoteShare(textToShare)
self.clearTextSelection()
}
}
self.setMenuVisible(false)
})
let shareText = UIAlertAction(title: self.readerConfig.localizedShareTextQuote, style: .default) { (action) -> Void in
if self.isShare {
if let textToShare = self.js("getHighlightContent()") {
self.folioReader.readerCenter?.shareHighlight(textToShare, rect: sender.menuFrame)
}
} else {
if let textToShare = self.js("getSelectedText()") {
self.folioReader.readerCenter?.shareHighlight(textToShare, rect: sender.menuFrame)
}
}
self.setMenuVisible(false)
}
let cancel = UIAlertAction(title: self.readerConfig.localizedCancel, style: .cancel, handler: nil)
alertController.addAction(shareImage)
alertController.addAction(shareText)
alertController.addAction(cancel)
if let alert = alertController.popoverPresentationController {
alert.sourceView = self.folioReader.readerCenter?.currentPage
alert.sourceRect = sender.menuFrame
}
self.folioReader.readerCenter?.present(alertController, animated: true, completion: nil)
}
func colors(_ sender: UIMenuController?) {
isColors = true
createMenu(options: false)
setMenuVisible(true)
}
func remove(_ sender: UIMenuController?) {
if let removedId = js("removeThisHighlight()") {
Highlight.removeById(withConfiguration: self.readerConfig, highlightId: removedId)
}
setMenuVisible(false)
}
@objc func highlight(_ sender: UIMenuController?) {
let highlightAndReturn = js("highlightString('\(HighlightStyle.classForStyle(self.folioReader.currentHighlightStyle))')")
let jsonData = highlightAndReturn?.data(using: String.Encoding.utf8)
do {
let json = try JSONSerialization.jsonObject(with: jsonData!, options: []) as! NSArray
let dic = json.firstObject as! [String: String]
let rect = NSCoder.cgRect(for: dic["rect"]!)
guard let startOffset = dic["startOffset"] else {
return
}
guard let endOffset = dic["endOffset"] else {
return
}
createMenu(options: true)
setMenuVisible(true, andRect: rect)
// Persist
guard
let html = js("getHTML()"),
let identifier = dic["id"],
let bookId = (self.book.name as NSString?)?.deletingPathExtension else {
return
}
let pageNumber = folioReader.readerCenter?.currentPageNumber ?? 0
let match = Highlight.MatchingHighlight(text: html, id: identifier, startOffset: startOffset, endOffset: endOffset, bookId: bookId, currentPage: pageNumber)
let highlight = Highlight.matchHighlight(match)
highlight?.persist(withConfiguration: self.readerConfig)
} catch {
print("Could not receive JSON")
}
}
@objc func highlightWithNote(_ sender: UIMenuController?) {
let highlightAndReturn = js("highlightStringWithNote('\(HighlightStyle.classForStyle(self.folioReader.currentHighlightStyle))')")
let jsonData = highlightAndReturn?.data(using: String.Encoding.utf8)
do {
let json = try JSONSerialization.jsonObject(with: jsonData!, options: []) as! NSArray
let dic = json.firstObject as! [String: String]
guard let startOffset = dic["startOffset"] else { return }
guard let endOffset = dic["endOffset"] else { return }
self.clearTextSelection()
guard let html = js("getHTML()") else { return }
guard let identifier = dic["id"] else { return }
guard let bookId = (self.book.name as NSString?)?.deletingPathExtension else { return }
let pageNumber = folioReader.readerCenter?.currentPageNumber ?? 0
let match = Highlight.MatchingHighlight(text: html, id: identifier, startOffset: startOffset, endOffset: endOffset, bookId: bookId, currentPage: pageNumber)
if let highlight = Highlight.matchHighlight(match) {
self.folioReader.readerCenter?.presentAddHighlightNote(highlight, edit: false)
}
} catch {
print("Could not receive JSON")
}
}
@objc func updateHighlightNote (_ sender: UIMenuController?) {
guard let highlightId = js("getHighlightId()") else { return }
guard let highlightNote = Highlight.getById(withConfiguration: readerConfig, highlightId: highlightId) else { return }
self.folioReader.readerCenter?.presentAddHighlightNote(highlightNote, edit: true)
}
@objc func define(_ sender: UIMenuController?) {
guard let selectedText = js("getSelectedText()") else {
return
}
self.setMenuVisible(false)
self.clearTextSelection()
let vc = UIReferenceLibraryViewController(term: selectedText)
vc.view.tintColor = self.readerConfig.tintColor
guard let readerContainer = readerContainer else { return }
readerContainer.show(vc, sender: nil)
}
@objc func play(_ sender: UIMenuController?) {
self.folioReader.readerAudioPlayer?.play()
self.clearTextSelection()
}
func setYellow(_ sender: UIMenuController?) {
changeHighlightStyle(sender, style: .yellow)
}
func setGreen(_ sender: UIMenuController?) {
changeHighlightStyle(sender, style: .green)
}
func setBlue(_ sender: UIMenuController?) {
changeHighlightStyle(sender, style: .blue)
}
func setPink(_ sender: UIMenuController?) {
changeHighlightStyle(sender, style: .pink)
}
func setUnderline(_ sender: UIMenuController?) {
changeHighlightStyle(sender, style: .underline)
}
func changeHighlightStyle(_ sender: UIMenuController?, style: HighlightStyle) {
self.folioReader.currentHighlightStyle = style.rawValue
if let updateId = js("setHighlightStyle('\(HighlightStyle.classForStyle(style.rawValue))')") {
Highlight.updateById(withConfiguration: self.readerConfig, highlightId: updateId, type: style)
}
//FIX: https://github.com/FolioReader/FolioReaderKit/issues/316
setMenuVisible(false)
}
// MARK: - Create and show menu
func createMenu(options: Bool) {
guard (self.readerConfig.useReaderMenuController == true) else {
return
}
isShare = options
let colors = UIImage(readerImageNamed: "colors-marker")
let share = UIImage(readerImageNamed: "share-marker")
let remove = UIImage(readerImageNamed: "no-marker")
let yellow = UIImage(readerImageNamed: "yellow-marker")
let green = UIImage(readerImageNamed: "green-marker")
let blue = UIImage(readerImageNamed: "blue-marker")
let pink = UIImage(readerImageNamed: "pink-marker")
let underline = UIImage(readerImageNamed: "underline-marker")
let menuController = UIMenuController.shared
let highlightItem = UIMenuItem(title: self.readerConfig.localizedHighlightMenu, action: #selector(highlight(_:)))
let highlightNoteItem = UIMenuItem(title: self.readerConfig.localizedHighlightNote, action: #selector(highlightWithNote(_:)))
let editNoteItem = UIMenuItem(title: self.readerConfig.localizedHighlightNote, action: #selector(updateHighlightNote(_:)))
let playAudioItem = UIMenuItem(title: self.readerConfig.localizedPlayMenu, action: #selector(play(_:)))
let defineItem = UIMenuItem(title: self.readerConfig.localizedDefineMenu, action: #selector(define(_:)))
let colorsItem = UIMenuItem(title: "C", image: colors) { [weak self] _ in
self?.colors(menuController)
}
let shareItem = UIMenuItem(title: "S", image: share) { [weak self] _ in
self?.share(menuController)
}
let removeItem = UIMenuItem(title: "R", image: remove) { [weak self] _ in
self?.remove(menuController)
}
let yellowItem = UIMenuItem(title: "Y", image: yellow) { [weak self] _ in
self?.setYellow(menuController)
}
let greenItem = UIMenuItem(title: "G", image: green) { [weak self] _ in
self?.setGreen(menuController)
}
let blueItem = UIMenuItem(title: "B", image: blue) { [weak self] _ in
self?.setBlue(menuController)
}
let pinkItem = UIMenuItem(title: "P", image: pink) { [weak self] _ in
self?.setPink(menuController)
}
let underlineItem = UIMenuItem(title: "U", image: underline) { [weak self] _ in
self?.setUnderline(menuController)
}
var menuItems: [UIMenuItem] = []
// menu on existing highlight
if isShare {
menuItems = [colorsItem, editNoteItem, removeItem]
if (self.readerConfig.allowSharing == true) {
menuItems.append(shareItem)
}
isShare = false
} else if isColors {
// menu for selecting highlight color
menuItems = [yellowItem, greenItem, blueItem, pinkItem, underlineItem]
} else {
// default menu
menuItems = [highlightItem, defineItem, highlightNoteItem]
if self.book.hasAudio || self.readerConfig.enableTTS {
menuItems.insert(playAudioItem, at: 0)
}
if (self.readerConfig.allowSharing == true) {
menuItems.append(shareItem)
}
}
menuController.menuItems = menuItems
}
open func setMenuVisible(_ menuVisible: Bool, animated: Bool = true, andRect rect: CGRect = CGRect.zero) {
if !menuVisible && isShare || !menuVisible && isColors {
isColors = false
isShare = false
}
if menuVisible {
if !rect.equalTo(CGRect.zero) {
UIMenuController.shared.setTargetRect(rect, in: self)
}
}
UIMenuController.shared.setMenuVisible(menuVisible, animated: animated)
}
// MARK: - Java Script Bridge
@discardableResult open func js(_ script: String) -> String? {
let callback = self.stringByEvaluatingJavaScript(from: script)
if callback!.isEmpty { return nil }
return callback
}
// MARK: WebView
func clearTextSelection() {
// Forces text selection clearing
// @NOTE: this doesn't seem to always work
self.isUserInteractionEnabled = false
self.isUserInteractionEnabled = true
}
func setupScrollDirection() {
switch self.readerConfig.scrollDirection {
case .vertical, .defaultVertical, .horizontalWithVerticalContent:
scrollView.isPagingEnabled = false
paginationMode = .unpaginated
scrollView.bounces = true
break
case .horizontal:
scrollView.isPagingEnabled = true
paginationMode = .leftToRight
paginationBreakingMode = .page
scrollView.bounces = false
break
}
}
}