Added Android code
[wl-app.git] / Android / folioreader / src / main / assets / js / Bridge.js
diff --git a/Android/folioreader/src/main/assets/js/Bridge.js b/Android/folioreader/src/main/assets/js/Bridge.js
new file mode 100755 (executable)
index 0000000..4643631
--- /dev/null
@@ -0,0 +1,813 @@
+//\r
+//  Bridge.js\r
+//  FolioReaderKit\r
+//\r
+//  Created by Heberti Almeida on 06/05/15.\r
+//  Copyright (c) 2015 Folio Reader. All rights reserved.\r
+//\r
+\r
+var thisHighlight;\r
+var audioMarkClass;\r
+var wordsPerMinute = 180;\r
+\r
+document.addEventListener("DOMContentLoaded", function(event) {\r
+//    var lnk = document.getElementsByClassName("lnk");\r
+//    for (var i=0; i<lnk.length; i++) {\r
+//        lnk[i].setAttribute("onclick","return callVerseURL(this);");\r
+//    }\r
+});\r
+\r
+// Generate a GUID\r
+function guid() {\r
+    function s4() {\r
+        return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1);\r
+    }\r
+    var guid = s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();\r
+    return guid.toUpperCase();\r
+}\r
+\r
+// Get All HTML\r
+function getHTML() {\r
+    Highlight.getHtmlAndSaveHighlight(document.documentElement.outerHTML);\r
+    //return document.documentElement.outerHTML;\r
+}\r
+\r
+// Class manipulation\r
+function hasClass(ele,cls) {\r
+  return !!ele.className.match(new RegExp('(\\s|^)'+cls+'(\\s|$)'));\r
+}\r
+\r
+function addClass(ele,cls) {\r
+  if (!hasClass(ele,cls)) ele.className += " "+cls;\r
+}\r
+\r
+function removeClass(ele,cls) {\r
+  if (hasClass(ele,cls)) {\r
+    var reg = new RegExp('(\\s|^)'+cls+'(\\s|$)');\r
+    ele.className=ele.className.replace(reg,' ');\r
+  }\r
+}\r
+\r
+// Font name class\r
+function setFontName(cls) {\r
+    var elm = document.documentElement;\r
+    removeClass(elm, "andada");\r
+    removeClass(elm, "lato");\r
+    removeClass(elm, "lora");\r
+    removeClass(elm, "raleway");\r
+    addClass(elm, cls);\r
+}\r
+\r
+// Toggle night mode\r
+function nightMode(enable) {\r
+    var elm = document.documentElement;\r
+    if(enable) {\r
+        addClass(elm, "nightMode");\r
+    } else {\r
+        removeClass(elm, "nightMode");\r
+    }\r
+}\r
+\r
+// Set font size\r
+function setFontSize(cls) {\r
+    var elm = document.documentElement;\r
+    removeClass(elm, "textSizeOne");\r
+    removeClass(elm, "textSizeTwo");\r
+    removeClass(elm, "textSizeThree");\r
+    removeClass(elm, "textSizeFour");\r
+    removeClass(elm, "textSizeFive");\r
+    addClass(elm, cls);\r
+}\r
+\r
+\r
+// Menu colors\r
+function setHighlightStyle(style) {\r
+    Highlight.getUpdatedHighlightId(thisHighlight.id, style);\r
+}\r
+\r
+function removeThisHighlight() {\r
+    return thisHighlight.id;\r
+}\r
+\r
+function removeHighlightById(elmId) {\r
+    var elm = document.getElementById(elmId);\r
+    elm.outerHTML = elm.innerHTML;\r
+    return elm.id;\r
+}\r
+\r
+function getHighlightContent() {\r
+    return thisHighlight.textContent\r
+}\r
+\r
+function getBodyText() {\r
+    return document.body.innerText;\r
+}\r
+\r
+// Method that returns only selected text plain\r
+var getSelectedText = function() {\r
+    return window.getSelection().toString();\r
+}\r
+\r
+// Method that gets the Rect of current selected text\r
+// and returns in a JSON format\r
+var getRectForSelectedText = function(elm) {\r
+    if (typeof elm === "undefined") elm = window.getSelection().getRangeAt(0);\r
+\r
+    var rect = elm.getBoundingClientRect();\r
+    return "{{" + rect.left + "," + rect.top + "}, {" + rect.width + "," + rect.height + "}}";\r
+}\r
+\r
+// Method that call that a hightlight was clicked\r
+// with URL scheme and rect informations\r
+var callHighlightURL = function(elm) {\r
+       event.stopPropagation();\r
+       var URLBase = "highlight://";\r
+    var currentHighlightRect = getRectForSelectedText(elm);\r
+    thisHighlight = elm;\r
+\r
+    window.location = URLBase + encodeURIComponent(currentHighlightRect);\r
+}\r
+\r
+// Reading time\r
+function getReadingTime() {\r
+    var text = document.body.innerText;\r
+    var totalWords = text.trim().split(/\s+/g).length;\r
+    var wordsPerSecond = wordsPerMinute / 60; //define words per second based on words per minute\r
+    var totalReadingTimeSeconds = totalWords / wordsPerSecond; //define total reading time in seconds\r
+    var readingTimeMinutes = Math.round(totalReadingTimeSeconds / 60);\r
+\r
+    return readingTimeMinutes;\r
+}\r
+\r
+/**\r
+ Get Vertical or Horizontal paged #anchor offset\r
+ */\r
+var getAnchorOffset = function(target, horizontal) {\r
+    var elem = document.getElementById(target);\r
+\r
+    if (!elem) {\r
+        elem = document.getElementsByName(target)[0];\r
+    }\r
+\r
+    if (horizontal) {\r
+        return document.body.clientWidth * Math.floor(elem.offsetTop / window.innerHeight);\r
+    }\r
+\r
+    return elem.offsetTop;\r
+}\r
+\r
+function scrollAnchor(id) {\r
+    window.location.hash = id;\r
+}\r
+\r
+function findElementWithID(node) {\r
+    if( !node || node.tagName == "BODY")\r
+        return null\r
+    else if( node.id )\r
+        return node\r
+    else\r
+        return findElementWithID(node)\r
+}\r
+\r
+function findElementWithIDInView() {\r
+\r
+    if(audioMarkClass) {\r
+        // attempt to find an existing "audio mark"\r
+        var el = document.querySelector("."+audioMarkClass)\r
+\r
+        // if that existing audio mark exists and is in view, use it\r
+        if( el && el.offsetTop > document.body.scrollTop && el.offsetTop < (window.innerHeight + document.body.scrollTop))\r
+            return el\r
+    }\r
+\r
+    // @NOTE: is `span` too limiting?\r
+    var els = document.querySelectorAll("span[id]")\r
+\r
+    for(indx in els) {\r
+        var element = els[indx];\r
+\r
+        // Horizontal scroll\r
+        if (document.body.scrollTop == 0) {\r
+            var elLeft = document.body.clientWidth * Math.floor(element.offsetTop / window.innerHeight);\r
+            // document.body.scrollLeft = elLeft;\r
+\r
+            if (elLeft == document.body.scrollLeft) {\r
+                return element;\r
+            }\r
+\r
+        // Vertical\r
+        } else if(element.offsetTop > document.body.scrollTop) {\r
+            return element;\r
+        }\r
+    }\r
+\r
+    return null\r
+}\r
+\r
+\r
+/**\r
+ Play Audio - called by native UIMenuController when a user selects a bit of text and presses "play"\r
+ */\r
+function playAudio() {\r
+    var sel = getSelection();\r
+    var node = null;\r
+\r
+    // user selected text? start playing from the selected node\r
+    if (sel.toString() != "") {\r
+        node = sel.anchorNode ? findElementWithID(sel.anchorNode.parentNode) : null;\r
+\r
+    // find the first ID'd element that is within view (it will\r
+    } else {\r
+        node = findElementWithIDInView()\r
+    }\r
+\r
+    playAudioFragmentID(node ? node.id : null)\r
+}\r
+\r
+\r
+/**\r
+ Play Audio Fragment ID - tells page controller to begin playing audio from the following ID\r
+ */\r
+function playAudioFragmentID(fragmentID) {\r
+    var URLBase = "play-audio://";\r
+    window.location = URLBase + (fragmentID?encodeURIComponent(fragmentID):"")\r
+}\r
+\r
+/**\r
+ Go To Element - scrolls the webview to the requested element\r
+ */\r
+function goToEl(el) {\r
+    var top = document.body.scrollTop;\r
+    var elTop = el.offsetTop - 20;\r
+    var bottom = window.innerHeight + document.body.scrollTop;\r
+    var elBottom = el.offsetHeight + el.offsetTop + 60\r
+\r
+    if(elBottom > bottom || elTop < top) {\r
+        document.body.scrollTop = el.offsetTop - 20\r
+    }\r
+\r
+    /* Set scroll left in case horz scroll is activated.\r
+\r
+        The following works because el.offsetTop accounts for each page turned\r
+        as if the document was scrolling vertical. We then divide by the window\r
+        height to figure out what page the element should appear on and set scroll left\r
+        to scroll to that page.\r
+    */\r
+    if( document.body.scrollTop == 0 ){\r
+        var elLeft = document.body.clientWidth * Math.floor(el.offsetTop / window.innerHeight);\r
+        document.body.scrollLeft = elLeft;\r
+    }\r
+\r
+    return el;\r
+}\r
+\r
+/**\r
+ Remove All Classes - removes the given class from all elements in the DOM\r
+ */\r
+function removeAllClasses(className) {\r
+    var els = document.body.getElementsByClassName(className)\r
+    if( els.length > 0 )\r
+    for( i = 0; i <= els.length; i++) {\r
+        els[i].classList.remove(className);\r
+    }\r
+}\r
+\r
+/**\r
+ Audio Mark ID - marks an element with an ID with the given class and scrolls to it\r
+ */\r
+function audioMarkID(className, id) {\r
+    if (audioMarkClass)\r
+        removeAllClasses(audioMarkClass);\r
+\r
+    audioMarkClass = className\r
+    var el = document.getElementById(id);\r
+\r
+    goToEl(el);\r
+    el.classList.add(className)\r
+}\r
+\r
+function setMediaOverlayStyle(style){\r
+    document.documentElement.classList.remove("mediaOverlayStyle0", "mediaOverlayStyle1", "mediaOverlayStyle2")\r
+    document.documentElement.classList.add(style)\r
+}\r
+\r
+function setMediaOverlayStyleColors(color, colorHighlight) {\r
+    var stylesheet = document.styleSheets[document.styleSheets.length-1];\r
+    stylesheet.insertRule(".mediaOverlayStyle0 span.epub-media-overlay-playing { background: "+colorHighlight+" !important }")\r
+    stylesheet.insertRule(".mediaOverlayStyle1 span.epub-media-overlay-playing { border-color: "+color+" !important }")\r
+    stylesheet.insertRule(".mediaOverlayStyle2 span.epub-media-overlay-playing { color: "+color+" !important }")\r
+}\r
+\r
+var currentIndex = -1;\r
+\r
+\r
+function findSentenceWithIDInView(els) {\r
+    // @NOTE: is `span` too limiting?\r
+    for(indx in els) {\r
+        var element = els[indx];\r
+\r
+        // Horizontal scroll\r
+        if (document.body.scrollTop == 0) {\r
+            var elLeft = document.body.clientWidth * Math.floor(element.offsetTop / window.innerHeight);\r
+            // document.body.scrollLeft = elLeft;\r
+\r
+            if (elLeft == document.body.scrollLeft) {\r
+                currentIndex = indx;\r
+                return element;\r
+            }\r
+\r
+        // Vertical\r
+        } else if(element.offsetTop > document.body.scrollTop) {\r
+            currentIndex = indx;\r
+            return element;\r
+        }\r
+    }\r
+\r
+    return null\r
+}\r
+\r
+function findNextSentenceInArray(els) {\r
+    if(currentIndex >= 0) {\r
+        currentIndex ++;\r
+        return els[currentIndex];\r
+    }\r
+\r
+    return null\r
+}\r
+\r
+function resetCurrentSentenceIndex() {\r
+    currentIndex = -1;\r
+}\r
+\r
+function rewindCurrentIndex() {\r
+    currentIndex = currentIndex-1;\r
+}\r
+\r
+function getSentenceWithIndex(className) {\r
+    var sentence;\r
+    var sel = getSelection();\r
+    var node = null;\r
+    var elements = document.querySelectorAll("span.sentence");\r
+\r
+    // Check for a selected text, if found start reading from it\r
+    if (sel.toString() != "") {\r
+        console.log(sel.anchorNode.parentNode);\r
+        node = sel.anchorNode.parentNode;\r
+\r
+        if (node.className == "sentence") {\r
+            sentence = node\r
+\r
+            for(var i = 0, len = elements.length; i < len; i++) {\r
+                if (elements[i] === sentence) {\r
+                    currentIndex = i;\r
+                    break;\r
+                }\r
+            }\r
+        } else {\r
+            sentence = findSentenceWithIDInView(elements);\r
+        }\r
+    } else if (currentIndex < 0) {\r
+        sentence = findSentenceWithIDInView(elements);\r
+    } else {\r
+        sentence = findNextSentenceInArray(elements);\r
+    }\r
+\r
+    var text = sentence.innerText || sentence.textContent;\r
+\r
+    goToEl(sentence);\r
+\r
+    if (audioMarkClass){\r
+        removeAllClasses(audioMarkClass);\r
+    }\r
+\r
+    audioMarkClass = className;\r
+    sentence.classList.add(className)\r
+    return text;\r
+}\r
+\r
+function wrappingSentencesWithinPTags(){\r
+    currentIndex = -1;\r
+    "use strict";\r
+\r
+    var rxOpen = new RegExp("<[^\\/].+?>"),\r
+    rxClose = new RegExp("<\\/.+?>"),\r
+    rxSupStart = new RegExp("^<sup\\b[^>]*>"),\r
+    rxSupEnd = new RegExp("<\/sup>"),\r
+    sentenceEnd = [],\r
+    rxIndex;\r
+\r
+    sentenceEnd.push(new RegExp("[^\\d][\\.!\\?]+"));\r
+    sentenceEnd.push(new RegExp("(?=([^\\\"]*\\\"[^\\\"]*\\\")*[^\\\"]*?$)"));\r
+    sentenceEnd.push(new RegExp("(?![^\\(]*?\\))"));\r
+    sentenceEnd.push(new RegExp("(?![^\\[]*?\\])"));\r
+    sentenceEnd.push(new RegExp("(?![^\\{]*?\\})"));\r
+    sentenceEnd.push(new RegExp("(?![^\\|]*?\\|)"));\r
+    sentenceEnd.push(new RegExp("(?![^\\\\]*?\\\\)"));\r
+    //sentenceEnd.push(new RegExp("(?![^\\/.]*\\/)")); // all could be a problem, but this one is problematic\r
+\r
+    rxIndex = new RegExp(sentenceEnd.reduce(function (previousValue, currentValue) {\r
+                                            return previousValue + currentValue.source;\r
+                                            }, ""));\r
+\r
+    function indexSentenceEnd(html) {\r
+        var index = html.search(rxIndex);\r
+\r
+        if (index !== -1) {\r
+            index += html.match(rxIndex)[0].length - 1;\r
+        }\r
+\r
+        return index;\r
+    }\r
+\r
+    function pushSpan(array, className, string, classNameOpt) {\r
+        if (!string.match('[a-zA-Z0-9]+')) {\r
+            array.push(string);\r
+        } else {\r
+            array.push('<span class="' + className + '">' + string + '</span>');\r
+        }\r
+    }\r
+\r
+    function addSupToPrevious(html, array) {\r
+        var sup = html.search(rxSupStart),\r
+        end = 0,\r
+        last;\r
+\r
+        if (sup !== -1) {\r
+            end = html.search(rxSupEnd);\r
+            if (end !== -1) {\r
+                last = array.pop();\r
+                end = end + 6;\r
+                array.push(last.slice(0, -7) + html.slice(0, end) + last.slice(-7));\r
+            }\r
+        }\r
+\r
+        return html.slice(end);\r
+    }\r
+\r
+    function paragraphIsSentence(html, array) {\r
+        var index = indexSentenceEnd(html);\r
+\r
+        if (index === -1 || index === html.length) {\r
+            pushSpan(array, "sentence", html, "paragraphIsSentence");\r
+            html = "";\r
+        }\r
+\r
+        return html;\r
+    }\r
+\r
+    function paragraphNoMarkup(html, array) {\r
+        var open = html.search(rxOpen),\r
+        index = 0;\r
+\r
+        if (open === -1) {\r
+            index = indexSentenceEnd(html);\r
+            if (index === -1) {\r
+                index = html.length;\r
+            }\r
+\r
+            pushSpan(array, "sentence", html.slice(0, index += 1), "paragraphNoMarkup");\r
+        }\r
+\r
+        return html.slice(index);\r
+    }\r
+\r
+    function sentenceUncontained(html, array) {\r
+        var open = html.search(rxOpen),\r
+        index = 0,\r
+        close;\r
+\r
+        if (open !== -1) {\r
+            index = indexSentenceEnd(html);\r
+            if (index === -1) {\r
+                index = html.length;\r
+            }\r
+\r
+            close = html.search(rxClose);\r
+            if (index < open || index > close) {\r
+                pushSpan(array, "sentence", html.slice(0, index += 1), "sentenceUncontained");\r
+            } else {\r
+                index = 0;\r
+            }\r
+        }\r
+\r
+        return html.slice(index);\r
+    }\r
+\r
+    function sentenceContained(html, array) {\r
+        var open = html.search(rxOpen),\r
+        index = 0,\r
+        close,\r
+        count;\r
+\r
+        if (open !== -1) {\r
+            index = indexSentenceEnd(html);\r
+            if (index === -1) {\r
+                index = html.length;\r
+            }\r
+\r
+            close = html.search(rxClose);\r
+            if (index > open && index < close) {\r
+                count = html.match(rxClose)[0].length;\r
+                pushSpan(array, "sentence", html.slice(0, close + count), "sentenceContained");\r
+                index = close + count;\r
+            } else {\r
+                index = 0;\r
+            }\r
+        }\r
+\r
+        return html.slice(index);\r
+    }\r
+\r
+    function anythingElse(html, array) {\r
+        pushSpan(array, "sentence", html, "anythingElse");\r
+\r
+        return "";\r
+    }\r
+\r
+    function guessSenetences() {\r
+        var paragraphs = document.getElementsByTagName("p");\r
+\r
+        Array.prototype.forEach.call(paragraphs, function (paragraph) {\r
+            var html = paragraph.innerHTML,\r
+                length = html.length,\r
+                array = [],\r
+                safety = 100;\r
+\r
+            while (length && safety) {\r
+                html = addSupToPrevious(html, array);\r
+                if (html.length === length) {\r
+                    if (html.length === length) {\r
+                        html = paragraphIsSentence(html, array);\r
+                        if (html.length === length) {\r
+                            html = paragraphNoMarkup(html, array);\r
+                            if (html.length === length) {\r
+                                html = sentenceUncontained(html, array);\r
+                                if (html.length === length) {\r
+                                    html = sentenceContained(html, array);\r
+                                    if (html.length === length) {\r
+                                        html = anythingElse(html, array);\r
+                                    }\r
+                                }\r
+                            }\r
+                        }\r
+                    }\r
+                }\r
+\r
+                length = html.length;\r
+                safety -= 1;\r
+            }\r
+\r
+            paragraph.innerHTML = array.join("");\r
+        });\r
+    }\r
+\r
+    guessSenetences();\r
+}\r
+\r
+// Class based onClick listener\r
+\r
+function addClassBasedOnClickListener(schemeName, querySelector, attributeName, selectAll) {\r
+       if (selectAll) {\r
+               // Get all elements with the given query selector\r
+               var elements = document.querySelectorAll(querySelector);\r
+               for (elementIndex = 0; elementIndex < elements.length; elementIndex++) {\r
+                       var element = elements[elementIndex];\r
+                       addClassBasedOnClickListenerToElement(element, schemeName, attributeName);\r
+               }\r
+       } else {\r
+               // Get the first element with the given query selector\r
+               var element = document.querySelector(querySelector);\r
+               addClassBasedOnClickListenerToElement(element, schemeName, attributeName);\r
+       }\r
+}\r
+\r
+function addClassBasedOnClickListenerToElement(element, schemeName, attributeName) {\r
+       // Get the content from the given attribute name\r
+       var attributeContent = element.getAttribute(attributeName);\r
+       // Add the on click logic\r
+       element.setAttribute("onclick", "onClassBasedListenerClick(\"" + schemeName + "\", \"" + encodeURIComponent(attributeContent) + "\");");\r
+}\r
+\r
+var onClassBasedListenerClick = function(schemeName, attributeContent) {\r
+       // Prevent the browser from performing the default on click behavior\r
+       event.preventDefault();\r
+       // Don't pass the click event to other elemtents\r
+       event.stopPropagation();\r
+       // Create parameters containing the click position inside the web view.\r
+       var positionParameterString = "/clientX=" + event.clientX + "&clientY=" + event.clientY;\r
+       // Set the custom link URL to the event\r
+       window.location = schemeName + "://" + attributeContent + positionParameterString;\r
+}\r
+\r
+function getHighlightString(style) {\r
+    var range = window.getSelection().getRangeAt(0);\r
+    var selectionContents = range.extractContents();\r
+    var elm = document.createElement("highlight");\r
+    var id = guid();\r
+\r
+    elm.appendChild(selectionContents);\r
+    elm.setAttribute("id", id);\r
+    elm.setAttribute("onclick","callHighlightURL(this);");\r
+    elm.setAttribute("class", style);\r
+\r
+    range.insertNode(elm);\r
+    thisHighlight = elm;\r
+\r
+    var params = [];\r
+    params.push({id: id, rect: getRectForSelectedText(elm)});\r
+    Highlight.getHighlightJson(JSON.stringify(params));\r
+}\r
+\r
+function gotoHighlight(highlightId){\r
+  var element = document.getElementById(highlightId.toString());\r
+  if(element != null) {\r
+    goToEl(element);\r
+  }\r
+}\r
+\r
+$(function(){\r
+  window.ssReader = Class({\r
+    $singleton: true,\r
+\r
+    init: function() {\r
+      rangy.init();\r
+\r
+      this.highlighter = rangy.createHighlighter();\r
+\r
+      this.highlighter.addClassApplier(rangy.createClassApplier("highlight_yellow", {\r
+        ignoreWhiteSpace: true,\r
+        tagNames: ["span", "a"]\r
+      }));\r
+\r
+      this.highlighter.addClassApplier(rangy.createClassApplier("highlight_green", {\r
+        ignoreWhiteSpace: true,\r
+        tagNames: ["span", "a"]\r
+      }));\r
+\r
+      this.highlighter.addClassApplier(rangy.createClassApplier("highlight_blue", {\r
+        ignoreWhiteSpace: true,\r
+        tagNames: ["span", "a"]\r
+      }));\r
+\r
+      this.highlighter.addClassApplier(rangy.createClassApplier("highlight_pink", {\r
+        ignoreWhiteSpace: true,\r
+        tagNames: ["span", "a"]\r
+      }));\r
+\r
+      this.highlighter.addClassApplier(rangy.createClassApplier("highlight_underline", {\r
+        ignoreWhiteSpace: true,\r
+        tagNames: ["span", "a"]\r
+      }));\r
+\r
+    },\r
+\r
+    setFontAndada: function(){\r
+      this.setFont("andada");\r
+    },\r
+\r
+    setFontLato: function(){\r
+      this.setFont("lato");\r
+    },\r
+\r
+    setFontPtSerif: function(){\r
+      this.setFont("pt-serif");\r
+    },\r
+\r
+    setFontPtSans: function(){\r
+      this.setFont("pt-sans");\r
+    },\r
+\r
+    base64encode: function(str){\r
+      return btoa(unescape(encodeURIComponent(str)));\r
+    },\r
+\r
+    base64decode: function(str){\r
+      return decodeURIComponent(escape(atob(str)));\r
+    },\r
+\r
+    clearSelection: function(){\r
+      if (window.getSelection) {\r
+        if (window.getSelection().empty) {  // Chrome\r
+          window.getSelection().empty();\r
+        } else if (window.getSelection().removeAllRanges) {  // Firefox\r
+          window.getSelection().removeAllRanges();\r
+        }\r
+      } else if (document.selection) {  // IE?\r
+        document.selection.empty();\r
+      }\r
+    },\r
+\r
+    // Public methods\r
+\r
+    setFont: function(fontName){\r
+      $("#ss-wrapper-font").removeClass().addClass("ss-wrapper-"+fontName);\r
+    },\r
+\r
+    setSize: function(size){\r
+      $("#ss-wrapper-size").removeClass().addClass("ss-wrapper-"+size);\r
+    },\r
+\r
+    setTheme: function(theme){\r
+      $("body, #ss-wrapper-theme").removeClass().addClass("ss-wrapper-"+theme);\r
+    },\r
+\r
+    setComment: function(comment, inputId){\r
+      $("#"+inputId).val(ssReader.base64decode(comment));\r
+      $("#"+inputId).trigger("input", ["true"]);\r
+    },\r
+\r
+    highlightSelection: function(color){\r
+      try {\r
+\r
+        this.highlighter.highlightSelection("highlight_" + color, null);\r
+        var range = window.getSelection().toString();\r
+        var params = {content: range,rangy: this.getHighlights(),color: color};\r
+        this.clearSelection();\r
+        Highlight.onReceiveHighlights(JSON.stringify(params));\r
+      } catch(err){\r
+        console.log("highlightSelection : " + err);\r
+      }\r
+    },\r
+\r
+    unHighlightSelection: function(){\r
+      try {\r
+        this.highlighter.unhighlightSelection();\r
+        Highlight.onReceiveHighlights(this.getHighlights());\r
+      } catch(err){}\r
+    },\r
+\r
+    getHighlights: function(){\r
+      try {\r
+        return this.highlighter.serialize();\r
+      } catch(err){}\r
+    },\r
+\r
+    setHighlights: function(serializedHighlight){\r
+      try {\r
+        this.highlighter.removeAllHighlights();\r
+        this.highlighter.deserialize(serializedHighlight);\r
+      } catch(err){}\r
+    },\r
+\r
+    removeAll: function(){\r
+      try {\r
+        this.highlighter.removeAllHighlights();\r
+      } catch(err){}\r
+    },\r
+\r
+    copy: function(){\r
+      SSBridge.onCopy(window.getSelection().toString());\r
+      this.clearSelection();\r
+    },\r
+\r
+    share: function(){\r
+      SSBridge.onShare(window.getSelection().toString());\r
+      this.clearSelection();\r
+    },\r
+\r
+    search: function(){\r
+      SSBridge.onSearch(window.getSelection().toString());\r
+      this.clearSelection();\r
+    }\r
+  });\r
+\r
+   if(typeof ssReader !== "undefined"){\r
+      ssReader.init();\r
+    }\r
+\r
+    $(".verse").click(function(){\r
+      SSBridge.onVerseClick(ssReader.base64encode($(this).attr("verse")));\r
+    });\r
+\r
+    $("code").each(function(i){\r
+      var textarea = $("<textarea class='textarea'/>").attr("id", "input-"+i).on("input propertychange", function(event, isInit) {\r
+        $(this).css({'height': 'auto', 'overflow-y': 'hidden'}).height(this.scrollHeight);\r
+        $(this).next().css({'height': 'auto', 'overflow-y': 'hidden'}).height(this.scrollHeight);\r
+\r
+        if (!isInit) {\r
+          var that = this;\r
+          if (timeout !== null) {\r
+            clearTimeout(timeout);\r
+          }\r
+          timeout = setTimeout(function () {\r
+            SSBridge.onCommentsClick(\r
+                ssReader.base64encode($(that).val()),\r
+                $(that).attr("id")\r
+            );\r
+          }, 1000);\r
+        }\r
+      });\r
+      var border = $("<div class='textarea-border' />");\r
+      var container = $("<div class='textarea-container' />");\r
+\r
+      $(textarea).appendTo(container);\r
+      $(border).appendTo(container);\r
+\r
+      $(this).after(container);\r
+    });\r
+  });\r
+\r
+function array_diff(array1, array2){\r
+    var difference = $.grep(array1, function(el) { return $.inArray(el,array2) < 0});\r
+    return difference.concat($.grep(array2, function(el) { return $.inArray(el,array1) < 0}));;\r
+}\r