Added Android code
[wl-app.git] / Android / folioreader / src / main / assets / js / Bridge.js
1 //\r
2 //  Bridge.js\r
3 //  FolioReaderKit\r
4 //\r
5 //  Created by Heberti Almeida on 06/05/15.\r
6 //  Copyright (c) 2015 Folio Reader. All rights reserved.\r
7 //\r
8 \r
9 var thisHighlight;\r
10 var audioMarkClass;\r
11 var wordsPerMinute = 180;\r
12 \r
13 document.addEventListener("DOMContentLoaded", function(event) {\r
14 //    var lnk = document.getElementsByClassName("lnk");\r
15 //    for (var i=0; i<lnk.length; i++) {\r
16 //        lnk[i].setAttribute("onclick","return callVerseURL(this);");\r
17 //    }\r
18 });\r
19 \r
20 // Generate a GUID\r
21 function guid() {\r
22     function s4() {\r
23         return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1);\r
24     }\r
25     var guid = s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();\r
26     return guid.toUpperCase();\r
27 }\r
28 \r
29 // Get All HTML\r
30 function getHTML() {\r
31     Highlight.getHtmlAndSaveHighlight(document.documentElement.outerHTML);\r
32     //return document.documentElement.outerHTML;\r
33 }\r
34 \r
35 // Class manipulation\r
36 function hasClass(ele,cls) {\r
37   return !!ele.className.match(new RegExp('(\\s|^)'+cls+'(\\s|$)'));\r
38 }\r
39 \r
40 function addClass(ele,cls) {\r
41   if (!hasClass(ele,cls)) ele.className += " "+cls;\r
42 }\r
43 \r
44 function removeClass(ele,cls) {\r
45   if (hasClass(ele,cls)) {\r
46     var reg = new RegExp('(\\s|^)'+cls+'(\\s|$)');\r
47     ele.className=ele.className.replace(reg,' ');\r
48   }\r
49 }\r
50 \r
51 // Font name class\r
52 function setFontName(cls) {\r
53     var elm = document.documentElement;\r
54     removeClass(elm, "andada");\r
55     removeClass(elm, "lato");\r
56     removeClass(elm, "lora");\r
57     removeClass(elm, "raleway");\r
58     addClass(elm, cls);\r
59 }\r
60 \r
61 // Toggle night mode\r
62 function nightMode(enable) {\r
63     var elm = document.documentElement;\r
64     if(enable) {\r
65         addClass(elm, "nightMode");\r
66     } else {\r
67         removeClass(elm, "nightMode");\r
68     }\r
69 }\r
70 \r
71 // Set font size\r
72 function setFontSize(cls) {\r
73     var elm = document.documentElement;\r
74     removeClass(elm, "textSizeOne");\r
75     removeClass(elm, "textSizeTwo");\r
76     removeClass(elm, "textSizeThree");\r
77     removeClass(elm, "textSizeFour");\r
78     removeClass(elm, "textSizeFive");\r
79     addClass(elm, cls);\r
80 }\r
81 \r
82 \r
83 // Menu colors\r
84 function setHighlightStyle(style) {\r
85     Highlight.getUpdatedHighlightId(thisHighlight.id, style);\r
86 }\r
87 \r
88 function removeThisHighlight() {\r
89     return thisHighlight.id;\r
90 }\r
91 \r
92 function removeHighlightById(elmId) {\r
93     var elm = document.getElementById(elmId);\r
94     elm.outerHTML = elm.innerHTML;\r
95     return elm.id;\r
96 }\r
97 \r
98 function getHighlightContent() {\r
99     return thisHighlight.textContent\r
100 }\r
101 \r
102 function getBodyText() {\r
103     return document.body.innerText;\r
104 }\r
105 \r
106 // Method that returns only selected text plain\r
107 var getSelectedText = function() {\r
108     return window.getSelection().toString();\r
109 }\r
110 \r
111 // Method that gets the Rect of current selected text\r
112 // and returns in a JSON format\r
113 var getRectForSelectedText = function(elm) {\r
114     if (typeof elm === "undefined") elm = window.getSelection().getRangeAt(0);\r
115 \r
116     var rect = elm.getBoundingClientRect();\r
117     return "{{" + rect.left + "," + rect.top + "}, {" + rect.width + "," + rect.height + "}}";\r
118 }\r
119 \r
120 // Method that call that a hightlight was clicked\r
121 // with URL scheme and rect informations\r
122 var callHighlightURL = function(elm) {\r
123         event.stopPropagation();\r
124         var URLBase = "highlight://";\r
125     var currentHighlightRect = getRectForSelectedText(elm);\r
126     thisHighlight = elm;\r
127 \r
128     window.location = URLBase + encodeURIComponent(currentHighlightRect);\r
129 }\r
130 \r
131 // Reading time\r
132 function getReadingTime() {\r
133     var text = document.body.innerText;\r
134     var totalWords = text.trim().split(/\s+/g).length;\r
135     var wordsPerSecond = wordsPerMinute / 60; //define words per second based on words per minute\r
136     var totalReadingTimeSeconds = totalWords / wordsPerSecond; //define total reading time in seconds\r
137     var readingTimeMinutes = Math.round(totalReadingTimeSeconds / 60);\r
138 \r
139     return readingTimeMinutes;\r
140 }\r
141 \r
142 /**\r
143  Get Vertical or Horizontal paged #anchor offset\r
144  */\r
145 var getAnchorOffset = function(target, horizontal) {\r
146     var elem = document.getElementById(target);\r
147 \r
148     if (!elem) {\r
149         elem = document.getElementsByName(target)[0];\r
150     }\r
151 \r
152     if (horizontal) {\r
153         return document.body.clientWidth * Math.floor(elem.offsetTop / window.innerHeight);\r
154     }\r
155 \r
156     return elem.offsetTop;\r
157 }\r
158 \r
159 function scrollAnchor(id) {\r
160     window.location.hash = id;\r
161 }\r
162 \r
163 function findElementWithID(node) {\r
164     if( !node || node.tagName == "BODY")\r
165         return null\r
166     else if( node.id )\r
167         return node\r
168     else\r
169         return findElementWithID(node)\r
170 }\r
171 \r
172 function findElementWithIDInView() {\r
173 \r
174     if(audioMarkClass) {\r
175         // attempt to find an existing "audio mark"\r
176         var el = document.querySelector("."+audioMarkClass)\r
177 \r
178         // if that existing audio mark exists and is in view, use it\r
179         if( el && el.offsetTop > document.body.scrollTop && el.offsetTop < (window.innerHeight + document.body.scrollTop))\r
180             return el\r
181     }\r
182 \r
183     // @NOTE: is `span` too limiting?\r
184     var els = document.querySelectorAll("span[id]")\r
185 \r
186     for(indx in els) {\r
187         var element = els[indx];\r
188 \r
189         // Horizontal scroll\r
190         if (document.body.scrollTop == 0) {\r
191             var elLeft = document.body.clientWidth * Math.floor(element.offsetTop / window.innerHeight);\r
192             // document.body.scrollLeft = elLeft;\r
193 \r
194             if (elLeft == document.body.scrollLeft) {\r
195                 return element;\r
196             }\r
197 \r
198         // Vertical\r
199         } else if(element.offsetTop > document.body.scrollTop) {\r
200             return element;\r
201         }\r
202     }\r
203 \r
204     return null\r
205 }\r
206 \r
207 \r
208 /**\r
209  Play Audio - called by native UIMenuController when a user selects a bit of text and presses "play"\r
210  */\r
211 function playAudio() {\r
212     var sel = getSelection();\r
213     var node = null;\r
214 \r
215     // user selected text? start playing from the selected node\r
216     if (sel.toString() != "") {\r
217         node = sel.anchorNode ? findElementWithID(sel.anchorNode.parentNode) : null;\r
218 \r
219     // find the first ID'd element that is within view (it will\r
220     } else {\r
221         node = findElementWithIDInView()\r
222     }\r
223 \r
224     playAudioFragmentID(node ? node.id : null)\r
225 }\r
226 \r
227 \r
228 /**\r
229  Play Audio Fragment ID - tells page controller to begin playing audio from the following ID\r
230  */\r
231 function playAudioFragmentID(fragmentID) {\r
232     var URLBase = "play-audio://";\r
233     window.location = URLBase + (fragmentID?encodeURIComponent(fragmentID):"")\r
234 }\r
235 \r
236 /**\r
237  Go To Element - scrolls the webview to the requested element\r
238  */\r
239 function goToEl(el) {\r
240     var top = document.body.scrollTop;\r
241     var elTop = el.offsetTop - 20;\r
242     var bottom = window.innerHeight + document.body.scrollTop;\r
243     var elBottom = el.offsetHeight + el.offsetTop + 60\r
244 \r
245     if(elBottom > bottom || elTop < top) {\r
246         document.body.scrollTop = el.offsetTop - 20\r
247     }\r
248 \r
249     /* Set scroll left in case horz scroll is activated.\r
250 \r
251         The following works because el.offsetTop accounts for each page turned\r
252         as if the document was scrolling vertical. We then divide by the window\r
253         height to figure out what page the element should appear on and set scroll left\r
254         to scroll to that page.\r
255     */\r
256     if( document.body.scrollTop == 0 ){\r
257         var elLeft = document.body.clientWidth * Math.floor(el.offsetTop / window.innerHeight);\r
258         document.body.scrollLeft = elLeft;\r
259     }\r
260 \r
261     return el;\r
262 }\r
263 \r
264 /**\r
265  Remove All Classes - removes the given class from all elements in the DOM\r
266  */\r
267 function removeAllClasses(className) {\r
268     var els = document.body.getElementsByClassName(className)\r
269     if( els.length > 0 )\r
270     for( i = 0; i <= els.length; i++) {\r
271         els[i].classList.remove(className);\r
272     }\r
273 }\r
274 \r
275 /**\r
276  Audio Mark ID - marks an element with an ID with the given class and scrolls to it\r
277  */\r
278 function audioMarkID(className, id) {\r
279     if (audioMarkClass)\r
280         removeAllClasses(audioMarkClass);\r
281 \r
282     audioMarkClass = className\r
283     var el = document.getElementById(id);\r
284 \r
285     goToEl(el);\r
286     el.classList.add(className)\r
287 }\r
288 \r
289 function setMediaOverlayStyle(style){\r
290     document.documentElement.classList.remove("mediaOverlayStyle0", "mediaOverlayStyle1", "mediaOverlayStyle2")\r
291     document.documentElement.classList.add(style)\r
292 }\r
293 \r
294 function setMediaOverlayStyleColors(color, colorHighlight) {\r
295     var stylesheet = document.styleSheets[document.styleSheets.length-1];\r
296     stylesheet.insertRule(".mediaOverlayStyle0 span.epub-media-overlay-playing { background: "+colorHighlight+" !important }")\r
297     stylesheet.insertRule(".mediaOverlayStyle1 span.epub-media-overlay-playing { border-color: "+color+" !important }")\r
298     stylesheet.insertRule(".mediaOverlayStyle2 span.epub-media-overlay-playing { color: "+color+" !important }")\r
299 }\r
300 \r
301 var currentIndex = -1;\r
302 \r
303 \r
304 function findSentenceWithIDInView(els) {\r
305     // @NOTE: is `span` too limiting?\r
306     for(indx in els) {\r
307         var element = els[indx];\r
308 \r
309         // Horizontal scroll\r
310         if (document.body.scrollTop == 0) {\r
311             var elLeft = document.body.clientWidth * Math.floor(element.offsetTop / window.innerHeight);\r
312             // document.body.scrollLeft = elLeft;\r
313 \r
314             if (elLeft == document.body.scrollLeft) {\r
315                 currentIndex = indx;\r
316                 return element;\r
317             }\r
318 \r
319         // Vertical\r
320         } else if(element.offsetTop > document.body.scrollTop) {\r
321             currentIndex = indx;\r
322             return element;\r
323         }\r
324     }\r
325 \r
326     return null\r
327 }\r
328 \r
329 function findNextSentenceInArray(els) {\r
330     if(currentIndex >= 0) {\r
331         currentIndex ++;\r
332         return els[currentIndex];\r
333     }\r
334 \r
335     return null\r
336 }\r
337 \r
338 function resetCurrentSentenceIndex() {\r
339     currentIndex = -1;\r
340 }\r
341 \r
342 function rewindCurrentIndex() {\r
343     currentIndex = currentIndex-1;\r
344 }\r
345 \r
346 function getSentenceWithIndex(className) {\r
347     var sentence;\r
348     var sel = getSelection();\r
349     var node = null;\r
350     var elements = document.querySelectorAll("span.sentence");\r
351 \r
352     // Check for a selected text, if found start reading from it\r
353     if (sel.toString() != "") {\r
354         console.log(sel.anchorNode.parentNode);\r
355         node = sel.anchorNode.parentNode;\r
356 \r
357         if (node.className == "sentence") {\r
358             sentence = node\r
359 \r
360             for(var i = 0, len = elements.length; i < len; i++) {\r
361                 if (elements[i] === sentence) {\r
362                     currentIndex = i;\r
363                     break;\r
364                 }\r
365             }\r
366         } else {\r
367             sentence = findSentenceWithIDInView(elements);\r
368         }\r
369     } else if (currentIndex < 0) {\r
370         sentence = findSentenceWithIDInView(elements);\r
371     } else {\r
372         sentence = findNextSentenceInArray(elements);\r
373     }\r
374 \r
375     var text = sentence.innerText || sentence.textContent;\r
376 \r
377     goToEl(sentence);\r
378 \r
379     if (audioMarkClass){\r
380         removeAllClasses(audioMarkClass);\r
381     }\r
382 \r
383     audioMarkClass = className;\r
384     sentence.classList.add(className)\r
385     return text;\r
386 }\r
387 \r
388 function wrappingSentencesWithinPTags(){\r
389     currentIndex = -1;\r
390     "use strict";\r
391 \r
392     var rxOpen = new RegExp("<[^\\/].+?>"),\r
393     rxClose = new RegExp("<\\/.+?>"),\r
394     rxSupStart = new RegExp("^<sup\\b[^>]*>"),\r
395     rxSupEnd = new RegExp("<\/sup>"),\r
396     sentenceEnd = [],\r
397     rxIndex;\r
398 \r
399     sentenceEnd.push(new RegExp("[^\\d][\\.!\\?]+"));\r
400     sentenceEnd.push(new RegExp("(?=([^\\\"]*\\\"[^\\\"]*\\\")*[^\\\"]*?$)"));\r
401     sentenceEnd.push(new RegExp("(?![^\\(]*?\\))"));\r
402     sentenceEnd.push(new RegExp("(?![^\\[]*?\\])"));\r
403     sentenceEnd.push(new RegExp("(?![^\\{]*?\\})"));\r
404     sentenceEnd.push(new RegExp("(?![^\\|]*?\\|)"));\r
405     sentenceEnd.push(new RegExp("(?![^\\\\]*?\\\\)"));\r
406     //sentenceEnd.push(new RegExp("(?![^\\/.]*\\/)")); // all could be a problem, but this one is problematic\r
407 \r
408     rxIndex = new RegExp(sentenceEnd.reduce(function (previousValue, currentValue) {\r
409                                             return previousValue + currentValue.source;\r
410                                             }, ""));\r
411 \r
412     function indexSentenceEnd(html) {\r
413         var index = html.search(rxIndex);\r
414 \r
415         if (index !== -1) {\r
416             index += html.match(rxIndex)[0].length - 1;\r
417         }\r
418 \r
419         return index;\r
420     }\r
421 \r
422     function pushSpan(array, className, string, classNameOpt) {\r
423         if (!string.match('[a-zA-Z0-9]+')) {\r
424             array.push(string);\r
425         } else {\r
426             array.push('<span class="' + className + '">' + string + '</span>');\r
427         }\r
428     }\r
429 \r
430     function addSupToPrevious(html, array) {\r
431         var sup = html.search(rxSupStart),\r
432         end = 0,\r
433         last;\r
434 \r
435         if (sup !== -1) {\r
436             end = html.search(rxSupEnd);\r
437             if (end !== -1) {\r
438                 last = array.pop();\r
439                 end = end + 6;\r
440                 array.push(last.slice(0, -7) + html.slice(0, end) + last.slice(-7));\r
441             }\r
442         }\r
443 \r
444         return html.slice(end);\r
445     }\r
446 \r
447     function paragraphIsSentence(html, array) {\r
448         var index = indexSentenceEnd(html);\r
449 \r
450         if (index === -1 || index === html.length) {\r
451             pushSpan(array, "sentence", html, "paragraphIsSentence");\r
452             html = "";\r
453         }\r
454 \r
455         return html;\r
456     }\r
457 \r
458     function paragraphNoMarkup(html, array) {\r
459         var open = html.search(rxOpen),\r
460         index = 0;\r
461 \r
462         if (open === -1) {\r
463             index = indexSentenceEnd(html);\r
464             if (index === -1) {\r
465                 index = html.length;\r
466             }\r
467 \r
468             pushSpan(array, "sentence", html.slice(0, index += 1), "paragraphNoMarkup");\r
469         }\r
470 \r
471         return html.slice(index);\r
472     }\r
473 \r
474     function sentenceUncontained(html, array) {\r
475         var open = html.search(rxOpen),\r
476         index = 0,\r
477         close;\r
478 \r
479         if (open !== -1) {\r
480             index = indexSentenceEnd(html);\r
481             if (index === -1) {\r
482                 index = html.length;\r
483             }\r
484 \r
485             close = html.search(rxClose);\r
486             if (index < open || index > close) {\r
487                 pushSpan(array, "sentence", html.slice(0, index += 1), "sentenceUncontained");\r
488             } else {\r
489                 index = 0;\r
490             }\r
491         }\r
492 \r
493         return html.slice(index);\r
494     }\r
495 \r
496     function sentenceContained(html, array) {\r
497         var open = html.search(rxOpen),\r
498         index = 0,\r
499         close,\r
500         count;\r
501 \r
502         if (open !== -1) {\r
503             index = indexSentenceEnd(html);\r
504             if (index === -1) {\r
505                 index = html.length;\r
506             }\r
507 \r
508             close = html.search(rxClose);\r
509             if (index > open && index < close) {\r
510                 count = html.match(rxClose)[0].length;\r
511                 pushSpan(array, "sentence", html.slice(0, close + count), "sentenceContained");\r
512                 index = close + count;\r
513             } else {\r
514                 index = 0;\r
515             }\r
516         }\r
517 \r
518         return html.slice(index);\r
519     }\r
520 \r
521     function anythingElse(html, array) {\r
522         pushSpan(array, "sentence", html, "anythingElse");\r
523 \r
524         return "";\r
525     }\r
526 \r
527     function guessSenetences() {\r
528         var paragraphs = document.getElementsByTagName("p");\r
529 \r
530         Array.prototype.forEach.call(paragraphs, function (paragraph) {\r
531             var html = paragraph.innerHTML,\r
532                 length = html.length,\r
533                 array = [],\r
534                 safety = 100;\r
535 \r
536             while (length && safety) {\r
537                 html = addSupToPrevious(html, array);\r
538                 if (html.length === length) {\r
539                     if (html.length === length) {\r
540                         html = paragraphIsSentence(html, array);\r
541                         if (html.length === length) {\r
542                             html = paragraphNoMarkup(html, array);\r
543                             if (html.length === length) {\r
544                                 html = sentenceUncontained(html, array);\r
545                                 if (html.length === length) {\r
546                                     html = sentenceContained(html, array);\r
547                                     if (html.length === length) {\r
548                                         html = anythingElse(html, array);\r
549                                     }\r
550                                 }\r
551                             }\r
552                         }\r
553                     }\r
554                 }\r
555 \r
556                 length = html.length;\r
557                 safety -= 1;\r
558             }\r
559 \r
560             paragraph.innerHTML = array.join("");\r
561         });\r
562     }\r
563 \r
564     guessSenetences();\r
565 }\r
566 \r
567 // Class based onClick listener\r
568 \r
569 function addClassBasedOnClickListener(schemeName, querySelector, attributeName, selectAll) {\r
570         if (selectAll) {\r
571                 // Get all elements with the given query selector\r
572                 var elements = document.querySelectorAll(querySelector);\r
573                 for (elementIndex = 0; elementIndex < elements.length; elementIndex++) {\r
574                         var element = elements[elementIndex];\r
575                         addClassBasedOnClickListenerToElement(element, schemeName, attributeName);\r
576                 }\r
577         } else {\r
578                 // Get the first element with the given query selector\r
579                 var element = document.querySelector(querySelector);\r
580                 addClassBasedOnClickListenerToElement(element, schemeName, attributeName);\r
581         }\r
582 }\r
583 \r
584 function addClassBasedOnClickListenerToElement(element, schemeName, attributeName) {\r
585         // Get the content from the given attribute name\r
586         var attributeContent = element.getAttribute(attributeName);\r
587         // Add the on click logic\r
588         element.setAttribute("onclick", "onClassBasedListenerClick(\"" + schemeName + "\", \"" + encodeURIComponent(attributeContent) + "\");");\r
589 }\r
590 \r
591 var onClassBasedListenerClick = function(schemeName, attributeContent) {\r
592         // Prevent the browser from performing the default on click behavior\r
593         event.preventDefault();\r
594         // Don't pass the click event to other elemtents\r
595         event.stopPropagation();\r
596         // Create parameters containing the click position inside the web view.\r
597         var positionParameterString = "/clientX=" + event.clientX + "&clientY=" + event.clientY;\r
598         // Set the custom link URL to the event\r
599         window.location = schemeName + "://" + attributeContent + positionParameterString;\r
600 }\r
601 \r
602 function getHighlightString(style) {\r
603     var range = window.getSelection().getRangeAt(0);\r
604     var selectionContents = range.extractContents();\r
605     var elm = document.createElement("highlight");\r
606     var id = guid();\r
607 \r
608     elm.appendChild(selectionContents);\r
609     elm.setAttribute("id", id);\r
610     elm.setAttribute("onclick","callHighlightURL(this);");\r
611     elm.setAttribute("class", style);\r
612 \r
613     range.insertNode(elm);\r
614     thisHighlight = elm;\r
615 \r
616     var params = [];\r
617     params.push({id: id, rect: getRectForSelectedText(elm)});\r
618     Highlight.getHighlightJson(JSON.stringify(params));\r
619 }\r
620 \r
621 function gotoHighlight(highlightId){\r
622   var element = document.getElementById(highlightId.toString());\r
623   if(element != null) {\r
624     goToEl(element);\r
625   }\r
626 }\r
627 \r
628 $(function(){\r
629   window.ssReader = Class({\r
630     $singleton: true,\r
631 \r
632     init: function() {\r
633       rangy.init();\r
634 \r
635       this.highlighter = rangy.createHighlighter();\r
636 \r
637       this.highlighter.addClassApplier(rangy.createClassApplier("highlight_yellow", {\r
638         ignoreWhiteSpace: true,\r
639         tagNames: ["span", "a"]\r
640       }));\r
641 \r
642       this.highlighter.addClassApplier(rangy.createClassApplier("highlight_green", {\r
643         ignoreWhiteSpace: true,\r
644         tagNames: ["span", "a"]\r
645       }));\r
646 \r
647       this.highlighter.addClassApplier(rangy.createClassApplier("highlight_blue", {\r
648         ignoreWhiteSpace: true,\r
649         tagNames: ["span", "a"]\r
650       }));\r
651 \r
652       this.highlighter.addClassApplier(rangy.createClassApplier("highlight_pink", {\r
653         ignoreWhiteSpace: true,\r
654         tagNames: ["span", "a"]\r
655       }));\r
656 \r
657       this.highlighter.addClassApplier(rangy.createClassApplier("highlight_underline", {\r
658         ignoreWhiteSpace: true,\r
659         tagNames: ["span", "a"]\r
660       }));\r
661 \r
662     },\r
663 \r
664     setFontAndada: function(){\r
665       this.setFont("andada");\r
666     },\r
667 \r
668     setFontLato: function(){\r
669       this.setFont("lato");\r
670     },\r
671 \r
672     setFontPtSerif: function(){\r
673       this.setFont("pt-serif");\r
674     },\r
675 \r
676     setFontPtSans: function(){\r
677       this.setFont("pt-sans");\r
678     },\r
679 \r
680     base64encode: function(str){\r
681       return btoa(unescape(encodeURIComponent(str)));\r
682     },\r
683 \r
684     base64decode: function(str){\r
685       return decodeURIComponent(escape(atob(str)));\r
686     },\r
687 \r
688     clearSelection: function(){\r
689       if (window.getSelection) {\r
690         if (window.getSelection().empty) {  // Chrome\r
691           window.getSelection().empty();\r
692         } else if (window.getSelection().removeAllRanges) {  // Firefox\r
693           window.getSelection().removeAllRanges();\r
694         }\r
695       } else if (document.selection) {  // IE?\r
696         document.selection.empty();\r
697       }\r
698     },\r
699 \r
700     // Public methods\r
701 \r
702     setFont: function(fontName){\r
703       $("#ss-wrapper-font").removeClass().addClass("ss-wrapper-"+fontName);\r
704     },\r
705 \r
706     setSize: function(size){\r
707       $("#ss-wrapper-size").removeClass().addClass("ss-wrapper-"+size);\r
708     },\r
709 \r
710     setTheme: function(theme){\r
711       $("body, #ss-wrapper-theme").removeClass().addClass("ss-wrapper-"+theme);\r
712     },\r
713 \r
714     setComment: function(comment, inputId){\r
715       $("#"+inputId).val(ssReader.base64decode(comment));\r
716       $("#"+inputId).trigger("input", ["true"]);\r
717     },\r
718 \r
719     highlightSelection: function(color){\r
720       try {\r
721 \r
722         this.highlighter.highlightSelection("highlight_" + color, null);\r
723         var range = window.getSelection().toString();\r
724         var params = {content: range,rangy: this.getHighlights(),color: color};\r
725         this.clearSelection();\r
726         Highlight.onReceiveHighlights(JSON.stringify(params));\r
727       } catch(err){\r
728         console.log("highlightSelection : " + err);\r
729       }\r
730     },\r
731 \r
732     unHighlightSelection: function(){\r
733       try {\r
734         this.highlighter.unhighlightSelection();\r
735         Highlight.onReceiveHighlights(this.getHighlights());\r
736       } catch(err){}\r
737     },\r
738 \r
739     getHighlights: function(){\r
740       try {\r
741         return this.highlighter.serialize();\r
742       } catch(err){}\r
743     },\r
744 \r
745     setHighlights: function(serializedHighlight){\r
746       try {\r
747         this.highlighter.removeAllHighlights();\r
748         this.highlighter.deserialize(serializedHighlight);\r
749       } catch(err){}\r
750     },\r
751 \r
752     removeAll: function(){\r
753       try {\r
754         this.highlighter.removeAllHighlights();\r
755       } catch(err){}\r
756     },\r
757 \r
758     copy: function(){\r
759       SSBridge.onCopy(window.getSelection().toString());\r
760       this.clearSelection();\r
761     },\r
762 \r
763     share: function(){\r
764       SSBridge.onShare(window.getSelection().toString());\r
765       this.clearSelection();\r
766     },\r
767 \r
768     search: function(){\r
769       SSBridge.onSearch(window.getSelection().toString());\r
770       this.clearSelection();\r
771     }\r
772   });\r
773 \r
774    if(typeof ssReader !== "undefined"){\r
775       ssReader.init();\r
776     }\r
777 \r
778     $(".verse").click(function(){\r
779       SSBridge.onVerseClick(ssReader.base64encode($(this).attr("verse")));\r
780     });\r
781 \r
782     $("code").each(function(i){\r
783       var textarea = $("<textarea class='textarea'/>").attr("id", "input-"+i).on("input propertychange", function(event, isInit) {\r
784         $(this).css({'height': 'auto', 'overflow-y': 'hidden'}).height(this.scrollHeight);\r
785         $(this).next().css({'height': 'auto', 'overflow-y': 'hidden'}).height(this.scrollHeight);\r
786 \r
787         if (!isInit) {\r
788           var that = this;\r
789           if (timeout !== null) {\r
790             clearTimeout(timeout);\r
791           }\r
792           timeout = setTimeout(function () {\r
793             SSBridge.onCommentsClick(\r
794                 ssReader.base64encode($(that).val()),\r
795                 $(that).attr("id")\r
796             );\r
797           }, 1000);\r
798         }\r
799       });\r
800       var border = $("<div class='textarea-border' />");\r
801       var container = $("<div class='textarea-container' />");\r
802 \r
803       $(textarea).appendTo(container);\r
804       $(border).appendTo(container);\r
805 \r
806       $(this).after(container);\r
807     });\r
808   });\r
809 \r
810 function array_diff(array1, array2){\r
811     var difference = $.grep(array1, function(el) { return $.inArray(el,array2) < 0});\r
812     return difference.concat($.grep(array2, function(el) { return $.inArray(el,array1) < 0}));;\r
813 }\r