Added Android code
[wl-app.git] / iOS / Pods / FolioReaderKit / Source / Resources / Bridge.js
1 //
2 //  Bridge.js
3 //  FolioReaderKit
4 //
5 //  Created by Heberti Almeida on 06/05/15.
6 //  Copyright (c) 2015 Folio Reader. All rights reserved.
7 //
8
9 var thisHighlight;
10 var audioMarkClass;
11 var wordsPerMinute = 180;
12
13 document.addEventListener("DOMContentLoaded", function(event) {
14 //    var lnk = document.getElementsByClassName("lnk");
15 //    for (var i=0; i<lnk.length; i++) {
16 //        lnk[i].setAttribute("onclick","return callVerseURL(this);");
17 //    }
18 });
19
20 // Generate a GUID
21 function guid() {
22     function s4() {
23         return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1);
24     }
25     var guid = s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
26     return guid.toUpperCase();
27 }
28
29 // Get All HTML
30 function getHTML() {
31     return document.documentElement.outerHTML;
32 }
33
34 // Class manipulation
35 function hasClass(ele,cls) {
36   return !!ele.className.match(new RegExp('(\\s|^)'+cls+'(\\s|$)'));
37 }
38
39 function addClass(ele,cls) {
40   if (!hasClass(ele,cls)) ele.className += " "+cls;
41 }
42
43 function removeClass(ele,cls) {
44   if (hasClass(ele,cls)) {
45     var reg = new RegExp('(\\s|^)'+cls+'(\\s|$)');
46     ele.className=ele.className.replace(reg,' ');
47   }
48 }
49
50 // Font name class
51 function setFontName(cls) {
52     var elm = document.documentElement;
53     removeClass(elm, "andada");
54     removeClass(elm, "lato");
55     removeClass(elm, "lora");
56     removeClass(elm, "raleway");
57     addClass(elm, cls);
58 }
59
60 // Toggle night mode
61 function nightMode(enable) {
62     var elm = document.documentElement;
63     if(enable) {
64         addClass(elm, "nightMode");
65     } else {
66         removeClass(elm, "nightMode");
67     }
68 }
69
70 // Set font size
71 function setFontSize(cls) {
72     var elm = document.documentElement;
73     removeClass(elm, "textSizeOne");
74     removeClass(elm, "textSizeTwo");
75     removeClass(elm, "textSizeThree");
76     removeClass(elm, "textSizeFour");
77     removeClass(elm, "textSizeFive");
78     addClass(elm, cls);
79 }
80
81 // Set font size
82 function setMarginSize(cls) {
83     var elm = document.documentElement;
84     removeClass(elm, "marginSizeOne");
85     removeClass(elm, "marginSizeTwo");
86     removeClass(elm, "marginSizeThree");
87     removeClass(elm, "marginSizeFour");
88     removeClass(elm, "marginSizeFive");
89     addClass(elm, cls);
90 }
91
92 // Set font size
93 function setInterlineSize(cls) {
94     var elm = document.documentElement;
95     removeClass(elm, "interlineSizeOne");
96     removeClass(elm, "interlineSizeTwo");
97     removeClass(elm, "interlineSizeThree");
98     removeClass(elm, "interlineSizeFour");
99     removeClass(elm, "interlineSizeFive");
100     addClass(elm, cls);
101 }
102
103 /*
104  *      Native bridge Highlight text
105  */
106 function highlightString(style) {
107     var range = window.getSelection().getRangeAt(0);
108     var startOffset = range.startOffset;
109     var endOffset = range.endOffset;
110     var selectionContents = range.extractContents();
111     var elm = document.createElement("highlight");
112     var id = guid();
113     
114     elm.appendChild(selectionContents);
115     elm.setAttribute("id", id);
116     elm.setAttribute("onclick","callHighlightURL(this);");
117     elm.setAttribute("class", style);
118     
119     range.insertNode(elm);
120     thisHighlight = elm;
121     
122     var params = [];
123     params.push({id: id, rect: getRectForSelectedText(elm), startOffset: startOffset.toString(), endOffset: endOffset.toString()});
124     
125     return JSON.stringify(params);
126 }
127
128 // Menu colors
129 function setHighlightStyle(style) {
130     thisHighlight.className = style;
131     return thisHighlight.id;
132 }
133
134 function removeThisHighlight() {
135     thisHighlight.outerHTML = thisHighlight.innerHTML;
136     return thisHighlight.id;
137 }
138
139 function removeHighlightById(elmId) {
140     var elm = document.getElementById(elmId);
141     elm.outerHTML = elm.innerHTML;
142     return elm.id;
143 }
144
145 function getHighlightContent() {
146     return thisHighlight.textContent
147 }
148
149 function getBodyText() {
150     return document.body.innerText;
151 }
152
153 // Method that returns only selected text plain
154 var getSelectedText = function() {
155     return window.getSelection().toString();
156 }
157
158 // Method that gets the Rect of current selected text
159 // and returns in a JSON format
160 var getRectForSelectedText = function(elm) {
161     if (typeof elm === "undefined") elm = window.getSelection().getRangeAt(0);
162     
163     var rect = elm.getBoundingClientRect();
164     return "{{" + rect.left + "," + rect.top + "}, {" + rect.width + "," + rect.height + "}}";
165 }
166
167 // Method that call that a hightlight was clicked
168 // with URL scheme and rect informations
169 var callHighlightURL = function(elm) {
170         event.stopPropagation();
171         var URLBase = "highlight://";
172     var currentHighlightRect = getRectForSelectedText(elm);
173     thisHighlight = elm;
174     
175     window.location = URLBase + encodeURIComponent(currentHighlightRect);
176 }
177
178 // Reading time
179 function getReadingTime() {
180     var text = document.body.innerText;
181     var totalWords = text.trim().split(/\s+/g).length;
182     var wordsPerSecond = wordsPerMinute / 60; //define words per second based on words per minute
183     var totalReadingTimeSeconds = totalWords / wordsPerSecond; //define total reading time in seconds
184     var readingTimeMinutes = Math.round(totalReadingTimeSeconds / 60);
185
186     return readingTimeMinutes;
187 }
188
189 /**
190  Get Vertical or Horizontal paged #anchor offset
191  */
192 var getAnchorOffset = function(target, horizontal) {
193     var elem = document.getElementById(target);
194     
195     if (!elem) {
196         elem = document.getElementsByName(target)[0];
197     }
198     
199     if (horizontal) {
200         return window.innerWidth * Math.floor(elem.offsetTop / window.innerHeight);
201     }
202     
203     return elem.offsetTop;
204 }
205
206 function findElementWithID(node) {
207     if( !node || node.tagName == "BODY")
208         return null
209     else if( node.id )
210         return node
211     else
212         return findElementWithID(node)
213 }
214
215 function findElementWithIDInView() {
216
217     if(audioMarkClass) {
218         // attempt to find an existing "audio mark"
219         var el = document.querySelector("."+audioMarkClass)
220
221         // if that existing audio mark exists and is in view, use it
222         if( el && el.offsetTop > document.body.scrollTop && el.offsetTop < (window.innerHeight + document.body.scrollTop))
223             return el
224     }
225
226     // @NOTE: is `span` too limiting?
227     var els = document.querySelectorAll("span[id]")
228
229     for(indx in els) {
230         var element = els[indx];
231
232         // Horizontal scroll
233         if (document.body.scrollTop == 0) {
234             var elLeft = window.innerWidth * Math.floor(element.offsetTop / window.innerHeight);
235             // document.body.scrollLeft = elLeft;
236
237             if (elLeft == document.body.scrollLeft) {
238                 return element;
239             }
240
241         // Vertical
242         } else if(element.offsetTop > document.body.scrollTop) {
243             return element;
244         }
245     }
246
247     return null
248 }
249
250
251 /**
252  Play Audio - called by native UIMenuController when a user selects a bit of text and presses "play"
253  */
254 function playAudio() {
255     var sel = getSelection();
256     var node = null;
257
258     // user selected text? start playing from the selected node
259     if (sel.toString() != "") {
260         node = sel.anchorNode ? findElementWithID(sel.anchorNode.parentNode) : null;
261
262     // find the first ID'd element that is within view (it will
263     } else {
264         node = findElementWithIDInView()
265     }
266
267     playAudioFragmentID(node ? node.id : null)
268 }
269
270
271 /**
272  Play Audio Fragment ID - tells page controller to begin playing audio from the following ID
273  */
274 function playAudioFragmentID(fragmentID) {
275     var URLBase = "play-audio://";
276     window.location = URLBase + (fragmentID?encodeURIComponent(fragmentID):"")
277 }
278
279 /**
280  Go To Element - scrolls the webview to the requested element
281  */
282 function goToEl(el) {
283     var top = document.body.scrollTop;
284     var elTop = el.offsetTop - 20;
285     var bottom = window.innerHeight + document.body.scrollTop;
286     var elBottom = el.offsetHeight + el.offsetTop + 60
287
288     if(elBottom > bottom || elTop < top) {
289         document.body.scrollTop = el.offsetTop - 20
290     }
291     
292     /* Set scroll left in case horz scroll is activated.
293     
294         The following works because el.offsetTop accounts for each page turned
295         as if the document was scrolling vertical. We then divide by the window
296         height to figure out what page the element should appear on and set scroll left
297         to scroll to that page.
298     */
299     if( document.body.scrollTop == 0 ){
300         var elLeft = window.innerWidth * Math.floor(el.offsetTop / window.innerHeight);
301         document.body.scrollLeft = elLeft;
302     }
303
304     return el;
305 }
306
307 /**
308  Remove All Classes - removes the given class from all elements in the DOM
309  */
310 function removeAllClasses(className) {
311     var els = document.body.getElementsByClassName(className)
312     if( els.length > 0 )
313     for( i = 0; i <= els.length; i++) {
314         els[i].classList.remove(className);
315     }
316 }
317
318 /**
319  Audio Mark ID - marks an element with an ID with the given class and scrolls to it
320  */
321 function audioMarkID(className, id) {
322     if (audioMarkClass)
323         removeAllClasses(audioMarkClass);
324
325     audioMarkClass = className
326     var el = document.getElementById(id);
327
328     goToEl(el);
329     el.classList.add(className)
330 }
331
332 function setMediaOverlayStyle(style){
333     document.documentElement.classList.remove("mediaOverlayStyle0", "mediaOverlayStyle1", "mediaOverlayStyle2")
334     document.documentElement.classList.add(style)
335 }
336
337 function setMediaOverlayStyleColors(color, colorHighlight) {
338     var stylesheet = document.styleSheets[document.styleSheets.length-1];
339     stylesheet.insertRule(".mediaOverlayStyle0 span.epub-media-overlay-playing { background: "+colorHighlight+" !important }")
340     stylesheet.insertRule(".mediaOverlayStyle1 span.epub-media-overlay-playing { border-color: "+color+" !important }")
341     stylesheet.insertRule(".mediaOverlayStyle2 span.epub-media-overlay-playing { color: "+color+" !important }")
342 }
343
344 var currentIndex = -1;
345
346
347 function findSentenceWithIDInView(els) {
348     // @NOTE: is `span` too limiting?
349     for(indx in els) {
350         var element = els[indx];
351
352         // Horizontal scroll
353         if (document.body.scrollTop == 0) {
354             var elLeft = window.innerWidth * Math.floor(element.offsetTop / window.innerHeight);
355             // document.body.scrollLeft = elLeft;
356
357             if (elLeft == document.body.scrollLeft) {
358                 currentIndex = indx;
359                 return element;
360             }
361
362         // Vertical
363         } else if(element.offsetTop > document.body.scrollTop) {
364             currentIndex = indx;
365             return element;
366         }
367     }
368     
369     return null
370 }
371
372 function findNextSentenceInArray(els) {
373     if(currentIndex >= 0) {
374         currentIndex ++;
375         return els[currentIndex];
376     }
377     
378     return null
379 }
380
381 function resetCurrentSentenceIndex() {
382     currentIndex = -1;
383 }
384
385 function getSentenceWithIndex(className) {
386     var sentence;
387     var sel = getSelection();
388     var node = null;
389     var elements = document.querySelectorAll("span.sentence");
390
391     // Check for a selected text, if found start reading from it
392     if (sel.toString() != "") {
393         console.log(sel.anchorNode.parentNode);
394         node = sel.anchorNode.parentNode;
395
396         if (node.className == "sentence") {
397             sentence = node
398
399             for(var i = 0, len = elements.length; i < len; i++) {
400                 if (elements[i] === sentence) {
401                     currentIndex = i;
402                     break;
403                 }
404             }
405         } else {
406             sentence = findSentenceWithIDInView(elements);
407         }
408     } else if (currentIndex < 0) {
409         sentence = findSentenceWithIDInView(elements);
410     } else {
411         sentence = findNextSentenceInArray(elements);
412     }
413
414     var text = sentence.innerText || sentence.textContent;
415     
416     goToEl(sentence);
417     
418     if (audioMarkClass){
419         removeAllClasses(audioMarkClass);
420     }
421     
422     audioMarkClass = className;
423     sentence.classList.add(className)
424     return text;
425 }
426
427 function wrappingSentencesWithinPTags(){
428     currentIndex = -1;
429     "use strict";
430     
431     var rxOpen = new RegExp("<[^\\/].+?>"),
432     rxClose = new RegExp("<\\/.+?>"),
433     rxSupStart = new RegExp("^<sup\\b[^>]*>"),
434     rxSupEnd = new RegExp("<\/sup>"),
435     sentenceEnd = [],
436     rxIndex;
437     
438     sentenceEnd.push(new RegExp("[^\\d][\\.!\\?]+"));
439     sentenceEnd.push(new RegExp("(?=([^\\\"]*\\\"[^\\\"]*\\\")*[^\\\"]*?$)"));
440     sentenceEnd.push(new RegExp("(?![^\\(]*?\\))"));
441     sentenceEnd.push(new RegExp("(?![^\\[]*?\\])"));
442     sentenceEnd.push(new RegExp("(?![^\\{]*?\\})"));
443     sentenceEnd.push(new RegExp("(?![^\\|]*?\\|)"));
444     sentenceEnd.push(new RegExp("(?![^\\\\]*?\\\\)"));
445     //sentenceEnd.push(new RegExp("(?![^\\/.]*\\/)")); // all could be a problem, but this one is problematic
446     
447     rxIndex = new RegExp(sentenceEnd.reduce(function (previousValue, currentValue) {
448                                             return previousValue + currentValue.source;
449                                             }, ""));
450     
451     function indexSentenceEnd(html) {
452         var index = html.search(rxIndex);
453         
454         if (index !== -1) {
455             index += html.match(rxIndex)[0].length - 1;
456         }
457         
458         return index;
459     }
460
461     function pushSpan(array, className, string, classNameOpt) {
462         if (!string.match('[a-zA-Z0-9]+')) {
463             array.push(string);
464         } else {
465             array.push('<span class="' + className + '">' + string + '</span>');
466         }
467     }
468     
469     function addSupToPrevious(html, array) {
470         var sup = html.search(rxSupStart),
471         end = 0,
472         last;
473         
474         if (sup !== -1) {
475             end = html.search(rxSupEnd);
476             if (end !== -1) {
477                 last = array.pop();
478                 end = end + 6;
479                 array.push(last.slice(0, -7) + html.slice(0, end) + last.slice(-7));
480             }
481         }
482         
483         return html.slice(end);
484     }
485     
486     function paragraphIsSentence(html, array) {
487         var index = indexSentenceEnd(html);
488         
489         if (index === -1 || index === html.length) {
490             pushSpan(array, "sentence", html, "paragraphIsSentence");
491             html = "";
492         }
493         
494         return html;
495     }
496     
497     function paragraphNoMarkup(html, array) {
498         var open = html.search(rxOpen),
499         index = 0;
500         
501         if (open === -1) {
502             index = indexSentenceEnd(html);
503             if (index === -1) {
504                 index = html.length;
505             }
506             
507             pushSpan(array, "sentence", html.slice(0, index += 1), "paragraphNoMarkup");
508         }
509         
510         return html.slice(index);
511     }
512     
513     function sentenceUncontained(html, array) {
514         var open = html.search(rxOpen),
515         index = 0,
516         close;
517         
518         if (open !== -1) {
519             index = indexSentenceEnd(html);
520             if (index === -1) {
521                 index = html.length;
522             }
523             
524             close = html.search(rxClose);
525             if (index < open || index > close) {
526                 pushSpan(array, "sentence", html.slice(0, index += 1), "sentenceUncontained");
527             } else {
528                 index = 0;
529             }
530         }
531         
532         return html.slice(index);
533     }
534     
535     function sentenceContained(html, array) {
536         var open = html.search(rxOpen),
537         index = 0,
538         close,
539         count;
540         
541         if (open !== -1) {
542             index = indexSentenceEnd(html);
543             if (index === -1) {
544                 index = html.length;
545             }
546             
547             close = html.search(rxClose);
548             if (index > open && index < close) {
549                 count = html.match(rxClose)[0].length;
550                 pushSpan(array, "sentence", html.slice(0, close + count), "sentenceContained");
551                 index = close + count;
552             } else {
553                 index = 0;
554             }
555         }
556         
557         return html.slice(index);
558     }
559     
560     function anythingElse(html, array) {
561         pushSpan(array, "sentence", html, "anythingElse");
562         
563         return "";
564     }
565     
566     function guessSenetences() {
567         var paragraphs = document.getElementsByTagName("p");
568
569         Array.prototype.forEach.call(paragraphs, function (paragraph) {
570             var html = paragraph.innerHTML,
571                 length = html.length,
572                 array = [],
573                 safety = 100;
574
575             while (length && safety) {
576                 html = addSupToPrevious(html, array);
577                 if (html.length === length) {
578                     if (html.length === length) {
579                         html = paragraphIsSentence(html, array);
580                         if (html.length === length) {
581                             html = paragraphNoMarkup(html, array);
582                             if (html.length === length) {
583                                 html = sentenceUncontained(html, array);
584                                 if (html.length === length) {
585                                     html = sentenceContained(html, array);
586                                     if (html.length === length) {
587                                         html = anythingElse(html, array);
588                                     }
589                                 }
590                             }
591                         }
592                     }
593                 }
594
595                 length = html.length;
596                 safety -= 1;
597             }
598
599             paragraph.innerHTML = array.join("");
600         });
601     }
602     
603     guessSenetences();
604 }
605
606 // Class based onClick listener
607
608 function addClassBasedOnClickListener(schemeName, querySelector, attributeName, selectAll) {
609         if (selectAll) {
610                 // Get all elements with the given query selector
611                 var elements = document.querySelectorAll(querySelector);
612                 for (elementIndex = 0; elementIndex < elements.length; elementIndex++) {
613                         var element = elements[elementIndex];
614                         addClassBasedOnClickListenerToElement(element, schemeName, attributeName);
615                 }
616         } else {
617                 // Get the first element with the given query selector
618                 var element = document.querySelector(querySelector);
619                 addClassBasedOnClickListenerToElement(element, schemeName, attributeName);
620         }
621 }
622
623 function addClassBasedOnClickListenerToElement(element, schemeName, attributeName) {
624         // Get the content from the given attribute name
625         var attributeContent = element.getAttribute(attributeName);
626         // Add the on click logic
627         element.setAttribute("onclick", "onClassBasedListenerClick(\"" + schemeName + "\", \"" + encodeURIComponent(attributeContent) + "\");");
628 }
629
630 var onClassBasedListenerClick = function(schemeName, attributeContent) {
631         // Prevent the browser from performing the default on click behavior
632         event.preventDefault();
633         // Don't pass the click event to other elemtents
634         event.stopPropagation();
635         // Create parameters containing the click position inside the web view.
636         var positionParameterString = "/clientX=" + event.clientX + "&clientY=" + event.clientY;
637         // Set the custom link URL to the event
638         window.location = schemeName + "://" + attributeContent + positionParameterString;
639 }