我的LT阅读器一键高亮补丁-gemma版

自己看书使用

当前为 2025-05-23 提交的版本,查看 最新版本

// ==UserScript==
// @name         我的LT阅读器一键高亮补丁-gemma版
// @namespace    https://www.ellibrototal.com/
// @version      2025-05-23.10
// @license      MIT
// @description  自己看书使用
// @author       You
// @match        https://www.ellibrototal.com/ltotal/*
// @icon         none
// @run-at       document-idle
// @grant        GM_addStyle
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        unsafeWindow

// @require      https://cdnjs.cloudflare.com/ajax/libs/spin.js/2.3.2/spin.min.js
// ==/UserScript==

;(function () {
  "use strict"

  // console.log('from user.js')

  /**
   * 模块0: 使用GM_addStyle添加CSS样式
   */
  {
    GM_addStyle(`
        /* 这里是你的CSS规则 */

        /* 修复搜索框输入法闪退问题 */
        .boxSearchInput {
          position: static !important;
        }
 
        /* 自定义按钮样式 */
        .menuBtn {
          /* 现有样式 */
          border-radius: 10px !important;
 
          /* --- 新增立体效果 --- */
          /* 内部阴影:模拟按钮顶部的高光或凹陷感 */
          box-shadow:
              inset 0 1px 0 rgba(255, 255, 255, 0.4), /* 顶部亮光 */
              inset 0 -1px 0 rgba(0, 0, 0, 0.2),    /* 底部暗影 */
              0 2px 3px rgba(0, 0, 0, 0.4);         /* 外部投影,模拟按钮抬起 */
        }
 
        /* :active 状态:模拟按钮被按下的效果 */
        .menuBtn:active {
            background: linear-gradient(to top, #4a4a4a, #2a2a2a); /* 渐变反转,模拟凹陷 */
            box-shadow:
                inset 0 2px 4px rgba(0, 0, 0, 0.5), /* 内部阴影加深,模拟按下 */
                0 1px 2px rgba(0, 0, 0, 0.3);       /* 外部阴影减弱 */
            transform: translateY(0); /* 回到原位,模拟按下去 */
            border-bottom: 1px solid rgba(0, 0, 0, 0.2); /* 底部边框变薄 */
        }
 
        /* 笔记展示页面定制 */
        .cita_nota {
          display: none; /* 隐藏 cita_nota 元素 */
        }
 
        .note_signature {
          display: none; /* 隐藏 note_signature 元素 */
        }
 
        #pTimeStamp {
          font-size: 9px !important;
          color: gray;
          text-align: right !important;
        }
        
        .modalViewerLT .contentHtml {
          position: relative;
          width: 92% !important;
          height: 88%;
          margin: 0px !important;
          padding: 15px !important;
          overflow: hidden;
          background-repeat: no-repeat;
          background-size: contain;
        }
 
        .modalViewerLT .contentHtml p {
          font-size: 0.9em !important;
          text-align: left;
        }
        
        /* 阅读笔记界面 */
        .pWithColorDot {
          border-bottom-style: inset;
        }
 
        /* 编辑笔记界面 */
        #div_nota_visor .div_title input.nota_title {
          width: 90% !important;
        }
 
        #div_nota_visor .editor_area_notas {
          width: 90% !important;
        }
 
        .editor_area {
          font-family: DM_Sans_regular !important;
        }
        
        // 编辑按钮 上挪一些
        #div_nota_visor .btn_salvar {
          bottom: 30px;
        }
     `)
  }

  /**
   * 模块1: 重新创建 panelSocial 实例,打上猴子补丁;
   */
  {
    // 给猴子补丁补充原js文件中的变量
    var isSmartPhone = ltotalOS.isSmartPhone
    var isTablet = ltotalOS.isTablet
    var isTouch = isSmartPhone || isTablet
    var bodyDiv = document.body
    var SycCredentials = null

    function initPanelSocial() {
      var ltotalOSConfig = {
        bridged: false,
        //repoDir: "https://www.syc.com.co/estaticos/repo_ltotal",
        //queryURLPrefix: "EscritorioUniversal/LtotalBridge.aspx"
        //queryURLPrefix: "http://www.ellibrototal.com"
        //queryURLPrefix: "http://test.ellibrototal.com.co"
        queryURLPrefix: "",
      }
      ltotalOS.init(ltotalOSConfig)

      var btnCerrar = $.trim(gup("btnCerrar"))

      var settings = {
        withLogin: true,
        contMusicHostDiv: ".boxBread",
        btnCerrar: btnCerrar,
      }

      //////// 猴子补丁,重点!使用unsafeWindow防止原panelSocial被沙箱保护
      unsafeWindow.panelSocial = new PanelSOCIAL(settings)

      //////// 猴子补丁,去除编辑之后的黄色回调界面
      unsafeWindow.panelSocial.editNote = function (
        tipo_nota,
        id_nota,
        _extra
      ) {
        // 猴子补丁
        console.log("进入猴子补丁函数 editNote")

        // 以下为源代码
        if (!_extra) {
          _extra = {}
        }
        if (isTablet || isSmartPhone) {
          _extra.position = 4
        }

        var config = {
          containerClass: "editor_notas_edesk",
          parentContainer: bodyDiv,
          position: _extra.position,
          subrayar: false,
          activarSeleccion: false,
          mostrarBarra: true,
          bindTouch: isTouch,

          tipoItemRela: _extra.tipoItemRela,
          idItemRela: _extra.idItemRela,
          idItem2Rela: _extra.idItem2Rela,
          npagItemRela: _extra.npagItemRela,
          idNotaRela: _extra.idNotaRela,
          txtRela: _extra.txtRela,

          correccionEdicion: _extra.correccionEdicion,

          nsecc: _extra.nsecc,

          afterSaveCallback: function (_data, _obj) {
            _obj.closeCont()

            var openMyNotas = true

            var sb = panelSocial.selectedBook
            if (sb) {
              var sets = sb.settings
              var ss = sb.settings
              if (_obj.tipoComp == ss.tipoLibro && _obj.idLibro == ss.idLibro) {
                sb.goToPageAndHighLightNote(_obj.npag, _obj.idNota).done(
                  function () {
                    panelSocial
                      .getSeccionLibroByIDNota(27, 0, _obj.idNota)
                      .then(function (_nsec) {
                        var extra = { numeSeccion: _nsec }
                        // 猴子补丁 去掉openBook功能 防止黄屏回调界面
                        // panelSocial.openBook(27, 0, -1, extra);
                        console.log("patched")
                      })
                  }
                )
                openMyNotas = false
              } else {
                if (ss.tipoLibro == 27) {
                  var extra = {}
                  if (ss.modoLibro == 2) {
                    extra.modoLibro = 2
                  }
                  sb.bookGotoSection(_extra.nsecc, _extra.npagNote, extra)
                  openMyNotas = false
                }
              }
            }

            if (openMyNotas) {
              var extra = { numeSeccion: 1 }
              if (_extra.nsecc) {
                extra.numeSeccion = _extra.nsecc
              }
              panelSocial.openBook(27, 0, -1, extra)
            }
          },
          extraParams: SycCredentials,
        }

        //Notas de personaje
        if (tipo_nota == 15) {
          config.mostrarBarra = false

          if (id_nota == 0) {
            fisher.newNote(config)
          }
          if (id_nota > 0) {
            fisher.editNote(id_nota, _extra.position, config)
          }
        }

        //Notas de lector
        if (tipo_nota == 17) {
          if (id_nota == 0) {
            llector.newNote(_extra.toBook, _extra.idProy, null, null, config)
          }
          if (id_nota > 0) {
            if (!_extra.npagNote) {
              _extra.npagNote = "0"
            }
            llector.editNote(id_nota, _extra.npagNote, null, config)
          }
        }
      }

      //////// 猴子补丁,去除删除笔记是弹出对话框 DialogueLM
      unsafeWindow.panelSocial.deleteNote = function (
        tipo_nota,
        id_nota,
        _fncb
      ) {
        console.log("进入猴子补丁函数 deleteNote")
        var url = "/ltotal/lector/editNota.jsp"
        var params = { caso: 5, tipoNota: tipo_nota, idNota: id_nota }
        panelSocial.doPost(url, params).done(_fncb)
      }

      console.log("initPanelSocial 猴子补丁完成")
    }

    initPanelSocial()
  }

  /**
   * 模块2: Spin.js 加载动画的 DOM 容器和 CSS 样式
   */
  {
    const loadingSpinnerId = "my-gm-loading-spinner"
    let loadingSpinnerDiv = document.getElementById(loadingSpinnerId)

    // 如果容器不存在,则创建并添加到 body
    if (!loadingSpinnerDiv) {
      loadingSpinnerDiv = document.createElement("div")
      loadingSpinnerDiv.id = loadingSpinnerId
      // 样式以确保居中和覆盖
      loadingSpinnerDiv.style.cssText = `
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            z-index: 9999; /* 确保在最上层 */
            display: none; /* 默认隐藏 */
            background-color: rgba(255, 255, 255, 0);
            border-radius: 8px;
            padding: 15px;
        `
      document.body.appendChild(loadingSpinnerDiv)
    }

    // --- Spin.js 配置选项 ---
    const spinnerOptions = {
      lines: 8, // 加载动画中的线条数量
      length: 0, // 每条线的长度
      width: 10, // 每条线的粗细
      radius: 18, // 内圆的半径
      scale: 0.6, // 整体缩放因子
      corners: 1, // 圆角程度 (0 to 1)
      color: "#000", // 颜色
      opacity: 0, // 透明度
      rotate: 0, // 旋转度数
      direction: 1, // 1: 顺时针, -1: 逆时针
      speed: 1, // 每秒的旋转圈数
      trail: 60, // 尾迹的百分比
      fps: 20, // 每秒帧数
      zIndex: 2e9, // Z-index (默认值 2000000000)
      className: "spinner", // 自定义类名
      top: "50%", // 定位顶部距离
      left: "50%", // 定位左侧距离
      shadow: false, // 是否显示阴影
      hwaccel: false, // 是否使用硬件加速
      position: "absolute", // 相对于容器定位
    }

    let spinner // 用于存储 Spin.js 实例

    // --- Spinner 控制函数 ---
    function startSpinner() {
      if (!spinner) {
        // 注意:Spin.js 的 Spinner 构造函数在 @require 加载后会全局可用
        spinner = new Spinner(spinnerOptions)
      }
      spinner.spin(loadingSpinnerDiv) // 绑定到容器
      loadingSpinnerDiv.style.display = "block" // 显示容器
    }

    function stopSpinner() {
      if (spinner) {
        spinner.stop() // 停止动画并从 DOM 移除
        loadingSpinnerDiv.style.display = "none" // 隐藏容器
      }
    }

    // --- 劫持 (Hook) fetch API ---
    // 为了在每次 fetch 请求时自动显示和隐藏加载动画,我们需要劫持原生的 fetch 函数。
    const originalFetch = window.fetch

    window.fetch = async function (...args) {
      startSpinner() // 在 fetch 请求开始时显示 Spinner
      try {
        const response = await originalFetch(...args)
        // 这里可以添加一些检查,比如response.ok,但为了通用性,直接返回response
        return response
      } catch (error) {
        console.error("Fetch request failed:", error)
        // 可以在这里添加一些错误提示
        throw error // 重新抛出错误,让调用者可以捕获
      } finally {
        stopSpinner() // 无论成功或失败,都在 fetch 完成后隐藏 Spinner
      }
    }
  }

  /**
   * 模块3: 保存 选择高亮文本 至 selectedText
   */
  {
    function handleSelection() {
      const selectedText = window.getSelection().toString().trim()
      if (selectedText) {
        GM_setValue("sharedText", selectedText) // 存储到 GM_setValue
        console.log("选定的文本已存储到 GM_setValue:", selectedText)
      }
    }
    // 监听 selectionchange 事件
    document.addEventListener("selectionchange", handleSelection)
    // 监听 mouseup 事件
    document.addEventListener("mouseup", handleSelection)
  }

  /**
   * 模块4: 修改 "Nota" 按钮文字为 "🐵AI笔记"
   */
  {
    // 定义当目标元素出现时要执行的函数
    function handleContextualMenuAppeared(contextualMenuDiv) {
      console.log("ContextualMenu div 出现了!", contextualMenuDiv)
      // 在这里执行你想要的操作
      // 例如:
      // 1. 修改它的样式
      // contextualMenuDiv.style.border = '2px solid red';

      // 2. 查找并修改其中的子元素
      // const dicButton = contextualMenuDiv.querySelector('.menuBtn');
      // if (dicButton) {
      //     dicButton.textContent = '新的字典按钮文本';
      // }

      // 3. 添加事件监听器
      // contextualMenuDiv.addEventListener('click', function() {
      //     console.log('ContextualMenu 被点击了!');
      // });

      // 注意:如果你只想在它第一次出现时执行一次,可以在这里停止监听
      // observer.disconnect();
      // console.log("MutationObserver 已停止监听。");

      const children = contextualMenuDiv.children

      // 遍历所有子元素
      for (let i = 0; i < children.length; i++) {
        const childElement = children[i]
        // 获取子元素的文本内容,并去除首尾空白字符进行比较
        const text = childElement.textContent.trim()

        if (text === "Diccionario") {
          childElement.textContent = "字典"
          childElement.style.width = "70px"
        } else if (text === "Compartir cita") {
          childElement.textContent = "分享"
          childElement.style.width = "70px"
        } else if (text === "Nota") {
          childElement.textContent = "🐵 AI 笔记"
          childElement.style.width = "110px"
          childElement.id = "btnAI" // 添加 ID 以便后续使用
        }
      }
    }

    // 1. 创建 MutationObserver 实例
    // 第一个参数是回调函数,当观察到变化时会被调用
    const observer = new MutationObserver(function (mutationsList, observer) {
      // 遍历所有观察到的变化
      for (let mutation of mutationsList) {
        // 检查是否有节点被添加
        if (mutation.type === "childList" && mutation.addedNodes.length > 0) {
          // 遍历新添加的节点
          for (let node of mutation.addedNodes) {
            // 确保是元素节点 (Node.ELEMENT_NODE)
            if (node.nodeType === Node.ELEMENT_NODE) {
              // 检查新添加的节点本身是否是目标 div
              if (node.classList.contains("contextualMenu")) {
                handleContextualMenuAppeared(node)
                // 如果你想在找到一个就停止监听,可以在这里断开连接
                // observer.disconnect();
                // return; // 找到后退出循环和函数
              }
              // 或者,检查新添加的节点内是否包含目标 div (如果目标 div 是作为子元素添加的)
              // querySelectorAll 返回一个 NodeList,所以需要检查 length
              const foundElements = node.querySelectorAll(".contextualMenu")
              if (foundElements.length > 0) {
                foundElements.forEach((element) => {
                  handleContextualMenuAppeared(element)
                  // 如果你想在找到一个就停止监听,可以在这里断开连接
                  // observer.disconnect();
                  // return;
                })
              }
            }
          }
        }
      }
    })

    // 2. 配置 observer 选项
    const config = { childList: true, subtree: true }
    // 3. 开始观察 DOM 树
    observer.observe(document.body, config)
    console.log("MutationObserver 已开始监听 DOM 变化...")

    // 可以在这里添加一个可选的检查,以防目标元素在脚本运行之前就已存在(不常见,但保险起见)
    // 这通常会在 MutationObserver 启动后立即检查
    const existingMenu = document.querySelector(".contextualMenu")
    if (existingMenu) {
      console.log("ContextualMenu 在脚本启动时就已存在!")
      handleContextualMenuAppeared(existingMenu)
      // 如果只处理第一次出现,这里可以停止监听
      // observer.disconnect();
    }
  }

  /**
   * 模块5: 修改 编辑笔记对话框
   */
  {
    // 创建 MutationObserver 实例
    const observer1 = new MutationObserver((mutations) => {
      mutations.forEach((mutation) => {
        if (mutation.addedNodes) {
          mutation.addedNodes.forEach((node) => {
            if (
              node.nodeType === Node.ELEMENT_NODE &&
              node.id === "div_nota_visor"
            ) {
              console.log("div_nota_visor 元素已找到!")

              // 先获取元素,后面使用
              const divNotaVisor = document.querySelector("#div_nota_visor")

              // 隐藏 citaNotaDiv
              {
                // 找到目标元素
                const citaNotaDiv = document.querySelector("div.cita_nota")

                if (citaNotaDiv) {
                  // 将 display 属性设置为 'none'
                  citaNotaDiv.style.display = "none"
                  console.log("cita_nota 元素已设置为 display: none (不可见)。")
                } else {
                  console.log("未找到 class='cita_nota' 的 div 元素。")
                }
              }

              // 隐藏 toolbarContainerDiv
              {
                // 找到目标元素
                const toolbarContainerDiv = document.querySelector(
                  "div.toolbar_container"
                )

                if (toolbarContainerDiv) {
                  // 将 display 属性设置为 'none'
                  toolbarContainerDiv.style.display = "none"
                  console.log(
                    "toolbar_container 元素已设置为 display: none (不可见)。"
                  )
                } else {
                  console.log("未找到 class='toolbar_container' 的 div 元素。")
                }
              }

              // 修改 editor_area editor_area_notas 样式
              {
                // 使用 CSS 选择器同时匹配两个类名
                const editorAreaDiv = document.querySelector(
                  "div.editor_area.editor_area_notas"
                )

                if (editorAreaDiv) {
                  console.log(
                    "找到 editor_area editor_area_notas 元素:",
                    editorAreaDiv
                  )

                  // --- 在这里重新设置你想要的样式 ---
                  editorAreaDiv.style.position = "static" // 移除绝对定位
                  editorAreaDiv.style.marginTop = "5px"
                  editorAreaDiv.style.marginBottom = "10px"
                  editorAreaDiv.style.height = "75%" // auto高度
                  editorAreaDiv.style.paddingBottom = "10px"
                  //editorAreaDiv.style.minHeight = '75%';     // 强制设置一个固定高度
                  editorAreaDiv.style.overflow = "auto" // 确保内容溢出时可以滚动
                  editorAreaDiv.style.border = "1px solid #4CAF50" // 添加一个绿色边框
                  editorAreaDiv.style.backgroundColor = "#f0fff0" // 浅绿色背景
                  editorAreaDiv.style.fontSize = "0.9em" // 设置字体大小

                  console.log(
                    "editor_area editor_area_notas 元素样式已重新设置。"
                  )
                } else {
                  console.log(
                    "未找到 class='editor_area editor_area_notas' 的元素。"
                  )
                }

                const selectors = [
                  "#div_nota_visor p",
                  "#continuous_editor p",
                  "div.editingPage p",
                ]

                selectors.forEach((selector) => {
                  // 获取所有匹配的元素
                  const elements = document.querySelectorAll(selector)
                  elements.forEach((element) => {
                    // 将背景颜色设置为透明,覆盖原有样式
                    element.style.backgroundColor = "transparent"
                  })
                })

                // 获取所有具有 'editor_area' 类的元素
                const editorAreas = document.querySelectorAll(".editor_area")

                // 遍历所有找到的元素并修改它们的样式
                editorAreas.forEach((element) => {
                  element.style.textAlign = "left"
                })
              }

              function getLocalDateTimeISO() {
                const now = new Date()
                const year = now.getFullYear()
                const month = String(now.getMonth() + 1).padStart(2, "0")
                const day = String(now.getDate()).padStart(2, "0")
                const hours = String(now.getHours()).padStart(2, "0")
                const minutes = String(now.getMinutes()).padStart(2, "0")
                const seconds = String(now.getSeconds()).padStart(2, "0")

                return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
              }

              const targetElement = document.querySelector(
                "#div_nota_visor > div.div_section_top > div.editor_area.editor_area_notas > p"
              )

              const inputElement = document.querySelector("input.nota_title")

              if (targetElement && inputElement.value === "Titular nota") {
                // 尝试所有方法,发现android自带webview支持最好的是 visibility,但是还是会快闪
                // 奇数运行ok,偶数时会快闪,是内核webview的问题
                // 目标浏览器为 x浏览器,有这个问题
                // android firefox浏览器没有这个问题

                // 隐藏元素
                // divNotaVisor.style.display = "" // 隐藏 divNotaVisor
                // divNotaVisor.classList.toggle("hidden") // 隐藏 divNotaVisor
                // divNotaVisor.style.left = "-9999px" // 隐藏 divNotaVisor
                // divNotaVisor.style.opacity = "0" // 隐藏 divNotaVisor
                divNotaVisor.style.visibility = "hidden" // 隐藏 divNotaVisor

                // 修改标题,寻找 input title 元素
                // 如果找到,则设置其 value 属性为 剪切板文字
                if (inputElement) {
                  const notaTitle = GM_getValue("sharedText", "") // 从 GM_getValue 获取
                  inputElement.value = notaTitle
                }

                // 修改内部文本
                const timeStamp = getLocalDateTimeISO()
                const sentences = GM_getValue("sharedSentences", "") // 从 GM_getValue 获取

                const word = GM_getValue("sharedText", "") // 从 GM_getValue 获取
                const sentence = findSentence(sentences, word) // 查找独立句子

                const req = {
                  word: word,
                  sentence: sentence,
                }

                //////// 工具函数 根据单词查找独立句子
                function findSentence(text, word) {
                  // Split the text into sentences using common sentence endings.
                  const sentences = text.split(/[.¡!¿?]/)

                  for (const sentence of sentences) {
                    // Check if the current sentence includes the target word.
                    if (sentence.includes(word)) {
                      // Trim whitespace and add a period back for completeness.
                      return sentence.trim() + "."
                    }
                  }
                  // If no sentence containing the word is found, return null.
                  return null
                }

                //////// 工具函数,使用emoji替代圆圈数字
                function replaceCircleNumbers(text) {
                  // ① 单词翻译
                  // ② RAE西语释义原文
                  // ③ 句子翻译
                  // 不处理 ④ 句子语法及含义分析
                  const regex1 = /^①.*:/m
                  const regex2 = /^②.*:/m
                  const regex3 = /^③.*:/m
                  const regex4 = /^④.*:/m

                  text = text.replace(regex1, "🔴中文释义\n")
                  text = text.replace(regex2, "🟢RAE原文\n")
                  text = text.replace(regex3, "🔵句子翻译\n")
                  text = text.replace(regex4, "🟣语法分析\n")
                  return text
                }

                //////// 工具函数,处理 \n 转换为 <p>
                function replaceNewlinesWithParagraphs(text) {
                  // 检查输入是否为字符串,如果不是,直接返回空字符串
                  if (typeof text !== "string") {
                    return ""
                  }

                  // 使用 split('\n') 将文本按换行符分割成数组
                  // 现在我们只过滤掉完全为空的字符串,而不是空白字符的行
                  // 这样像 "  " 这种只有空格的行也会被包裹在 <p> 标签中
                  const paragraphs = text
                    .split("\n")
                    .map((line) => `<p>${line}</p>`) // 不再进行 filter,直接 map

                  return paragraphs.join("")
                }

                //////// 工具函数,转换 markdown 语法为 HTML
                function convertMarkdownBoldToHtml(text) {
                  // 检查输入是否为字符串,如果不是或为空,直接返回原始文本。
                  if (typeof text !== "string" || text === "") {
                    return text
                  }

                  // 使用正则表达式匹配 **...** 模式。
                  // 解释正则表达式:
                  // \*\* 匹配两个星号字面量
                  // (             开始捕获组 1
                  //   [^\*]+      匹配一个或多个非星号字符 (这是粗体内容的实际文本)
                  //   |           或者
                  //   \*(?!\*)    匹配单个星号,但后面不能跟着另一个星号 (处理像 **\*斜体\*** 的情况,虽然这里只关注粗体)
                  // )+            捕获组 1 匹配一个或多个这样的模式,确保能匹配到包含星号但在双星号内部的文本,例如 **a*b**
                  // \*\* 匹配结尾的两个星号字面量
                  // g             全局标志,表示查找所有匹配项,而不仅仅是第一个
                  // i             不区分大小写(虽然这里不是必须的,因为星号是区分大小写的,但有时在其他正则中会用到)

                  // 考虑到你的例子中,粗体内容不会包含星号,更简洁且准确的正则:
                  // /\*\*([^\*]+)\*\*/g
                  // \*\* 匹配字面量 "**"
                  // (            开始捕获组 1
                  //   [^\*]+     匹配一个或多个不是星号的字符 (这会是你的粗体内容)
                  // )            结束捕获组 1
                  // \*\* 匹配字面量 "**"
                  // g            全局标志,确保替换所有匹配项

                  // 最终选择的正则表达式:
                  const regex = /\*\*([^\*]+)\*\*/g

                  // 使用 replace 方法和捕获组来构建替换后的字符串。
                  // $1 代表正则表达式中第一个捕获组匹配到的内容(即 ** 之间的文本)。
                  return text.replace(regex, "<b>$1</b>")
                }

                //////// 工具函数 插入句子原文到AI内容之中
                function insertParagraphBeforeFourth(text, sentence) {
                  // 检查输入是否为字符串,如果不是或为空,直接返回原始文本
                  if (
                    typeof text !== "string" ||
                    typeof sentence !== "string" ||
                    text === ""
                  ) {
                    return text
                  }

                  // 定义要查找的模式
                  const targetPattern = "🔵句子翻译</p>"

                  // 定义要替换成的内容。使用模板字符串来插入变量 sentence。
                  // 注意:如果 sentence 本身包含 HTML 特殊字符(如 <, >, &),
                  // 在实际应用中可能需要对其进行 HTML 转义处理,以避免XSS或布局问题。
                  // 但根据你的需求,这里直接插入。
                  const replacement = `${targetPattern}<p>${sentence}</p>`

                  // 使用 replace() 方法进行替换。
                  // replace() 默认只替换第一次出现的匹配项。
                  // 如果你需要替换所有出现的 "<p>④",可以使用正则表达式 with global flag (g)。
                  // 例如:text.replace(/<p>④/g, replacement);
                  // 但根据你的示例,它似乎是针对特定位置的。
                  return text.replace(targetPattern, replacement)
                }

                //////// 工具函数,给带彩色点p段落添加class
                function addPWithColorDotClass(text) {
                  text = text.replace(/<p>🔴/m, '<p class="pWithColorDot">🔴')
                  text = text.replace(/<p>🟢/m, '<p class="pWithColorDot">🟢')
                  text = text.replace(/<p>🔵/m, '<p class="pWithColorDot">🔵')
                  text = text.replace(/<p>🟣/m, '<p class="pWithColorDot">🟣')
                  return text
                }

                //////// fetch API from Cloudflare Worker
                function fetchAI(req) {
                  // 定义请求的 URL
                  const url = "https://w1.chaosrecyclebin.workers.dev/analyze"

                  // 定义请求体
                  const requestBody = req

                  // 使用 fetch API 发送 POST 请求
                  fetch(url, {
                    method: "POST", // 指定请求方法为 POST
                    headers: {
                      "Content-Type": "application/json", // 告诉服务器我们发送的是 JSON 数据
                    },
                    body: JSON.stringify(requestBody), // 将 JavaScript 对象转换为 JSON 字符串
                  })
                    .then((response) => {
                      // 检查响应是否成功 (HTTP 状态码 200-299)
                      if (!response.ok) {
                        throw new Error(
                          `HTTP error! status: ${response.status}`
                        )
                      }
                      return response.json() // 解析 JSON 响应
                    })
                    .then((data) => {
                      // 处理响应数据
                      let res = data.analysis.response
                      // 处理res
                      res = replaceCircleNumbers(res)
                      res = replaceNewlinesWithParagraphs(res)
                      res = insertParagraphBeforeFourth(res, sentence)
                      res = convertMarkdownBoldToHtml(res)
                      res = addPWithColorDotClass(res)

                      console.log("Success:", data)
                      // return data
                      targetElement.innerHTML = `<p id="pTimeStamp">${timeStamp}</p>
                      <p></p>
                      <p></p>
                      <p>${res}</p>
                      `

                      // 显示 divNotaVisor
                      // divNotaVisor.style.display = "block"
                      // divNotaVisor.classList.toggle("hidden") // 显示 divNotaVisor
                      // divNotaVisor.style.left = "0px" // 显示 divNotaVisor
                      // divNotaVisor.style.opacity = "1" // 显示 divNotaVisor
                      divNotaVisor.style.visibility = "visible" // 显示 divNotaVisor
                    })
                    .catch((error) => {
                      // 捕获并处理请求过程中可能出现的错误
                      console.error("Error:", error)
                      alert("POST 请求失败,请查看控制台输出错误信息!")
                    })
                }

                fetchAI(req)

                console.log("元素文本已修改!")
              } else {
                console.log("未找到目标元素!")
              }

              const btnSalvar = document.querySelector(".btn_salvar")

              if (btnSalvar) {
                console.log("找到 btn_salvar 元素:", btnSalvar) // 在这里可以对找到的元素执行操作

                // btnSalvar.click()
              } else {
                console.log("未找到 btn_salvar 元素")
              }
            }
          })
        }
      })
    })

    // 配置观察选项
    const config1 = { childList: true, subtree: true }
    observer1.observe(document.body, config1)
    console.log("开始监测 div_nota_visor 元素...")
  }

  /**
   * 模块6: 点击 .boxViewerTXT 元素,获取p元素所有句子文本,存入 GM_setValue
   */
  {
    // 创建 MutationObserver 实例
    const observer = new MutationObserver((mutations) => {
      mutations.forEach((mutation) => {
        if (mutation.addedNodes) {
          mutation.addedNodes.forEach((node) => {
            if (
              node.nodeType === Node.ELEMENT_NODE &&
              node.classList.contains("boxViewerTXT")
            ) {
              console.log(".boxViewerTXT 元素已找到!")

              node.addEventListener("contextmenu", function (event) {
                // 检查点击事件的目标是否是P元素,或者P元素的子元素
                // event.target 是实际被点击的元素
                // .closest() 方法会从当前元素开始,向上查找最近的符合选择器的祖先元素(包括元素自身)
                const clickedP = event.target.closest("p")

                if (clickedP) {
                  // 确保 clickedP 是父元素 .boxViewerTXT 下的 P 元素
                  // 避免点击到其他地方的P元素也被触发
                  if (node.contains(clickedP)) {
                    let sentences = clickedP.textContent.trim()
                    console.log("contextmenu的P元素文本:", sentences)
                    GM_setValue("sharedSentences", sentences) // 存储到 GM_setValue
                  }
                }
              })

              node.addEventListener("touchend", function (event) {
                // 检查点击事件的目标是否是P元素,或者P元素的子元素
                // event.target 是实际被点击的元素
                // .closest() 方法会从当前元素开始,向上查找最近的符合选择器的祖先元素(包括元素自身)
                const clickedP = event.target.closest("p")

                if (clickedP) {
                  // 确保 clickedP 是父元素 .boxViewerTXT 下的 P 元素
                  // 避免点击到其他地方的P元素也被触发
                  if (node.contains(clickedP)) {
                    let sentences = clickedP.textContent.trim()
                    console.log("touchend的P元素文本:", sentences)
                    GM_setValue("sharedSentences", sentences) // 存储到 GM_setValue
                  }
                }
              })

              node.addEventListener("mouseup", function (event) {
                // 检查点击事件的目标是否是P元素,或者P元素的子元素
                // event.target 是实际被点击的元素
                // .closest() 方法会从当前元素开始,向上查找最近的符合选择器的祖先元素(包括元素自身)
                const clickedP = event.target.closest("p")

                if (clickedP) {
                  // 确保 clickedP 是父元素 .boxViewerTXT 下的 P 元素
                  // 避免点击到其他地方的P元素也被触发
                  if (node.contains(clickedP)) {
                    let sentences = clickedP.textContent.trim()
                    console.log("mouseup的P元素文本:", sentences)
                    GM_setValue("sharedSentences", sentences) // 存储到 GM_setValue
                  }
                }
              })

              node.addEventListener("click", function (event) {
                // 检查点击事件的目标是否是P元素,或者P元素的子元素
                // event.target 是实际被点击的元素
                // .closest() 方法会从当前元素开始,向上查找最近的符合选择器的祖先元素(包括元素自身)
                const clickedP = event.target.closest("p")

                if (clickedP) {
                  // 确保 clickedP 是父元素 .boxViewerTXT 下的 P 元素
                  // 避免点击到其他地方的P元素也被触发
                  if (node.contains(clickedP)) {
                    let sentences = clickedP.textContent.trim()
                    console.log("click的P元素文本:", sentences)
                    GM_setValue("sharedSentences", sentences) // 存储到 GM_setValue
                  }
                }
              })
            }
          })
        }
      })
    })

    // 配置观察选项
    const config = { childList: true, subtree: true } // 开始观察目标元素
    observer.observe(document.body, config)
    console.log("开始监测 .boxViewerTXT 元素...")
  }

  /**
   * 模块7: 点击 查看笔记文字,可以关闭笔记
   */
  {
    // 创建 MutationObserver 实例 (observer2)
    const observer2 = new MutationObserver((mutations) => {
      mutations.forEach((mutation) => {
        if (mutation.addedNodes) {
          mutation.addedNodes.forEach((node) => {
            if (
              node.nodeType === Node.ELEMENT_NODE &&
              node.classList.contains("modalViewerLT")
            ) {
              console.log("modalViewerLT 元素已出现!")

              // 找到 div 元素
              const closeModalViewerDiv = document.querySelector(
                "div.closeModalViewer"
              )

              // const toolDeleteElement = document.querySelector('.tool_delete');

              // 立即点击关闭
              // closeModalViewerDiv.click()

              // 定义点击事件处理函数
              function handleClick() {
                console.log("closeModalViewer 被点击了!")
                // 在这里添加你想要执行的操作
                closeModalViewerDiv.click()
              }

              // 找到 noteSignatureDiv 元素
              const noteSignatureDiv =
                document.querySelector("div.note_signature")
              // 找到 divNoteSignature 元素
              const contentHtmlDiv = document.querySelector("div.contentHtml")
              // 如果找到 noteSignatureDiv ,则 contentHtmlDiv 添加点击事件监听器
              if (noteSignatureDiv) {
                contentHtmlDiv.addEventListener("click", handleClick)
                console.log("点击事件监听器已添加。")
              } else {
                console.log("未找到 contentHtml div 元素。")
              }

              // 如果只需要执行一次,可以取消观察
              // observer2.disconnect();
            }
          })
        }
      })
    })

    // 配置观察选项 (config2)
    const config2 = { childList: true, subtree: true }
    observer2.observe(document.body, config2)
  }

  /**
   * 模块8: viewerTxt 打上猴子补丁
   */
  {
    //Visor de libros
    function ViewerText(opts) {
      //'use strict';
      this.parent = opts.parent

      var page = { 0: 1, 1: 1, 2: 1, 3: 1, 4: 1 },
        total = { 0: 0, 1: 0, 2: 0, 3: 0, 4: 0 },
        idxSecsToLoad = { 0: 0, 1: 0, 2: 0, 3: 0, 4: 0 },
        finLibro = { 0: false, 1: false, 2: false, 3: false, 4: false },
        audiolecturas = { 0: {}, 1: {}, 2: {}, 3: {}, 4: {} },
        contentIndex = {},
        contentIndexType = null,
        contentSearchIndex = {},
        bookMarks = {},
        morePages = {},
        relations = {},
        firstNodePages = {},
        numColumns = 0,
        columnGapSize = 0,
        columnSize = 0,
        pasoPagina = 0,
        diagramElements = [],
        visibleTextWidth = 0,
        visibleTextHeigh = 0,
        rectBoxViewer = null,
        markFinalElem = null,
        loadingData = false,
        timerRepaint = null,
        totalPags = 0,
        pagesLeft,
        pagesRight,
        pagesCenter,
        pagesBottom,
        containPagins,
        paginLeft,
        paginRight,
        closeOld,
        closeLight,
        titlePlays,
        labelIZQ,
        labelDER,
        toolsIZQ,
        toolsDER,
        toolsCenter,
        containCredits,
        contentTxt,
        contentTxtBK,
        nextArrow,
        backArrow,
        that = this,
        diamondCreditActive = null,
        boxBookContentIndex = null,
        boxBookMarksIndex = null,
        boxBookNotesIndex = null,
        settings = $.extend({}, opts),
        tradsData = null,
        timerShowBookCredits,
        hasBookVocabulary,
        bookHasRelatedContent

      that.settings = settings
      that.settings.that = that

      var isSmartPhone = ltotalOS.isSmartPhone
      var isTablet = ltotalOS.isTablet
      var isTouch = isSmartPhone || isTablet

      settings.isSmartPhone = isSmartPhone

      var pestaImagLeft, pestaNotaLeft, pestaIndex, pestaTrads
      var floatTextViewer = null
      var boxGlobe_1 = null,
        boxGlobeInner_1,
        boxGlobe_2 = null,
        boxSpeaker_1 = null,
        boxSpeaker_2 = null,
        rutaAudioLectura1 = null,
        rutaAudioLectura2 = null,
        lectorAudioLectura1 = "",
        lectorAudioLectura2 = "",
        labelLector1 = null,
        labelLector2 = null,
        timerLabelLector = null,
        nodeAudioLectura = null,
        nodeAudioLectura1 = null,
        nodeAudioLectura2 = null,
        boxGlobePlaying = false,
        currentAudioSide = null,
        currentAudioPgClass = null,
        modoPasoPagina = "manual",
        haPasadoPagina = false,
        timerAudioLibro = null,
        playbackAt = 0,
        isOnPlayBackAt = false,
        audioPaginate = 0,
        stoppedAudio = false,
        areTherePixelsInBothSides = false,
        tPixelsSideTwo = 0,
        timerGlobito
      var executeNative = ltotalUtils.executeNative("TEST"),
        executeNativePlatform = ltotalUtils.executeNative("PLATFORM"),
        executeNativeAudio = executeNative,
        lastNativeOrientation = panelSocial.getNativeOrientation(),
        nativeAudioMetadata = false
      var boxShareGift = null,
        boxVocabulary = null,
        boxRelatedContent,
        boxBookMark = null,
        boxToolTextLook1 = null
      var boxSearch = null,
        boxSearchContainer = null,
        boxSearchInput = null
      var favoriteIzq, favoriteDer
      var sleepLabelLector = isTouch ? 4000 : 7000
      var boxPagesSeekSlider = document.createElement("div")
      var mosaicoLibro = null

      var noveMarks = []

      var children1 = null,
        children2 = null,
        elementosEnVisual1 = [],
        elementosEnVisual2 = [],
        idxFirstNodoVisual1 = 0,
        idxFirstNodoVisual2 = 0

      var viewedPages = [],
        viewedPagesTimer = null,
        viewedPagesDoStats = true,
        showReadingProgressTimer = null,
        valuesForRepaint = null
      var fwe,
        frameworkEdicion,
        elementosParaEdicion = []

      that.bookData = {
        tipoItem: settings.tipoLibro,
        idItem: settings.idLibro,
        idItem2: settings.idTrad,
        modoItem: settings.modoLibro,
        npagItem: 1,
      }
      panelSocial.selectedBook = that

      var bodyDiv = panelSocial.bodyDiv

      this.boxViewerTXT = document.createElement("div")
      this.boxViewerTXT.setAttribute(
        "class",
        "boxViewerTXT " + ltotalOS.deviceResolution
      )
      $(settings.hostDiv).append(this.boxViewerTXT)

      panelSocial.colorText(panelSocial.textColor, that)
      panelSocial.changeFontText(panelSocial.tipoLetra, that)

      this.boxViewerTXT.that = that

      contentTxt = document.createElement("div")
      contentTxt.setAttribute("class", "contentTxt")
      $(this.boxViewerTXT).append(contentTxt)

      containCredits = document.createElement("div")
      containCredits.setAttribute("class", "containCredits")
      $(this.boxViewerTXT).append(containCredits)

      closeOld = document.createElement("div")
      closeOld.setAttribute("class", "closeOld pg_center_left")
      closeOld.title = "Cerrar"

      labelIZQ = document.createElement("div")
      labelIZQ.setAttribute("class", "labelTop labelIZQ")
      $(containCredits).append(labelIZQ)

      labelDER = document.createElement("div")
      labelDER.setAttribute("class", "labelTop labelDER")
      $(containCredits).append(labelDER)

      toolsIZQ = document.createElement("div")
      toolsIZQ.setAttribute("class", "toolsIZQ")
      $(containCredits).append(toolsIZQ)

      toolsDER = document.createElement("div")
      toolsDER.setAttribute("class", "toolsDER")
      $(containCredits).append(toolsDER)

      containPagins = document.createElement("div")
      containPagins.setAttribute("class", "containPagins")
      $(this.boxViewerTXT).append(containPagins)

      paginLeft = document.createElement("div")
      paginLeft.setAttribute("class", "paginBottom paginLeft")
      $(containPagins).append(paginLeft)

      paginRight = document.createElement("div")
      paginRight.setAttribute("class", "paginBottom paginRight")
      $(containPagins).append(paginRight)

      pagesRight = document.createElement("div")
      pagesRight.setAttribute("class", "pagesRight pagesBook")
      $(this.boxViewerTXT).append(pagesRight)

      pagesLeft = document.createElement("div")
      pagesLeft.setAttribute("class", "pagesLeft pagesBook")
      $(this.boxViewerTXT).append(pagesLeft)

      pagesCenter = document.createElement("div")
      pagesCenter.setAttribute("class", "pagesCenter")
      $(that.boxViewerTXT).append(pagesCenter)

      pagesBottom = document.createElement("div")
      pagesBottom.setAttribute("class", "pagesBottom")
      $(this.boxViewerTXT).append(pagesBottom)

      var boxBottomMenu = panelSocial.createBottomMenu(this.boxViewerTXT)

      //seccion elementos para indice.
      boxBookContentIndex = document.createElement("div")
      boxBookContentIndex.setAttribute("class", "boxBookIndex bookContentIndex")
      boxBookContentIndex.innerHTML = "Contenido"
      boxBookContentIndex.title = "Contenido"

      boxBookContentIndex.onclick = function () {
        showBookContentIndex("bookContentIndex")
      }

      boxBookMarksIndex = document.createElement("div")
      boxBookMarksIndex.setAttribute("class", "boxBookIndex bookMarksIndex")
      boxBookMarksIndex.innerHTML = "Marcadores"
      boxBookMarksIndex.title = "Marcadores"

      boxBookMarksIndex.onclick = function () {
        showBookContentIndex("bookMarksIndex")
      }

      boxBookNotesIndex = document.createElement("div")
      boxBookNotesIndex.setAttribute("class", "boxBookIndex bookNotesIndex")
      boxBookNotesIndex.innerHTML = "Notas"
      boxBookNotesIndex.title = "Notas"

      boxBookNotesIndex.onclick = function () {
        showBookContentIndex("bookNotesIndex")
      }

      boxVocabulary = document.createElement("div")
      boxVocabulary.setAttribute("class", "boxVocabulary")
      boxVocabulary.innerHTML = "Vocabulario"
      boxVocabulary.title = "Vocabulario"

      boxVocabulary.onclick = function () {
        panelSocial.openBookVocabulary()
      }

      boxRelatedContent = document.createElement("div")
      boxRelatedContent.setAttribute("class", "book_related_content")
      boxRelatedContent.innerHTML = "Contenidos relacionados"
      boxRelatedContent.title = "Contenidos relacionados"

      boxRelatedContent.onclick = getRelatedContent

      var resVector = function () {
        if (isSmartPhone) {
          return 30
        }
        if (isTablet) {
          return 100
        }

        var res = {
          res_ExtraFullHD: 200,
          res_FullHD: 200,
          res_HD: 120,
          res_H: 100,
          res_Proy: 100,
        }

        var mq = processMediaQueries(res)
        return mq ? mq : 100
      }

      function calcular() {
        var s = settings

        if (columnGapSize == 0) {
          columnGapSize = resVector()
        }

        if (!columnGapSize) {
          //Cuando no reconoce resolucion toma la diagramacion por defecto a dos columnas a 200
          if (numColumns == 2) {
            columnGapSize = 200
          } else {
            //Si no reconoce resolucion y es a una columna detiene la ejecucion del libro
            finLibro[s.modoLibro] = true
          }
        }

        if (visibleTextWidth == 0 || visibleTextHeigh == 0) {
          rectBoxViewer = that.boxViewerTXT.getBoundingClientRect()

          $(contentTxt).css({ "column-gap": columnGapSize })

          if (isTouch) {
            var ctxtW = parseInt($(contentTxt).width())
            var ctxtCS = ctxtW - columnGapSize
            $(contentTxt).css({ "column-width": ctxtCS + "px" })
          }

          var padW =
            parseInt($(contentTxt).css("padding-left")) +
            parseInt($(contentTxt).css("padding-right"))
          var padH =
            parseInt($(contentTxt).css("padding-bottom")) +
            parseInt($(contentTxt).css("padding-top"))

          visibleTextWidth = rectBoxViewer.width - padW
          visibleTextHeigh = rectBoxViewer.height - padH

          columnSize = (visibleTextWidth - columnGapSize) / 2
          pasoPagina = 2 * columnGapSize + 2 * columnSize
        }

        diagramateElements()

        if (!markFinalElem) {
          markFinalElem = $(document.createElement("div"))
          markFinalElem.css({ height: "1px", border: "1px solid red" })
        }

        var firstElem = $(contentTxt.firstElementChild)
        var lastElem = $(contentTxt.lastElementChild)

        lastElem.after(markFinalElem)

        var sectionSize =
          markFinalElem.position().left +
          markFinalElem[0].offsetWidth -
          firstElem.position().left

        total[s.modoLibro] =
          Math.floor(sectionSize / (visibleTextWidth + columnGapSize)) + 1

        markFinalElem.remove()

        var capRef = 1
        if (s.modoLibro == 1 && s.numCapituloRef) {
          capRef = s.numCapituloRef
        }

        //consola("page " + page[s.modoLibro] + " total " + total[s.modoLibro] + " capRef " + capRef);

        //Control de mostrar pasos de pagina
        var pgn = page[s.modoLibro]

        if (pgn == total[s.modoLibro] && finLibro[s.modoLibro]) {
          $(nextArrow).css("display", "none")
        } else {
          $(nextArrow).css("display", "block")
        }

        if (pgn == 1 && capRef == 1) {
          $(backArrow).css("display", "none")
        } else {
          $(backArrow).css("display", "block")
        }
      }

      function resetDiagramElements() {
        var clases = "di_bsc di_np di_npp di_img di_imge di_lcp"

        $.each(diagramElements, function () {
          $(this).removeClass(clases)
        })
      }

      function prepareDiagramElements(_cont, _from) {
        var clases =
          ".book_standard_credit, .nueva_pagina, .nueva_pagina_parrafo, .img_book_art, .img_html_embed, .letrero_centro_pagina"

        var el = _from
        if (!el) {
          el = _cont.firstElementChild
        }
        var elNext = null

        while (el) {
          elNext = el.nextElementSibling

          if ($(el).is(clases)) {
            if (!el.diagIndx) {
              diagramElements.push(el)
              el.diagIndx = true
            }
          }

          el = elNext
        }
      }

      function diagramateElements() {
        var sets = settings
        var modoLectura = sets.modoLibro == 1
        var modoArticulo = sets.tipoLibro == 27 && sets.modoLibro == 2
        var validMode = modoLectura || modoArticulo
        if (!validMode) {
          return null
        }

        $.each(diagramElements, function () {
          var $this = $(this)

          //Posicion de credito de la fundacion  y SYC en la primera y segunda pagina
          //Posicion del boton de vocabulario
          if (
            $this.hasClass("book_standard_credit") &&
            !$this.hasClass("di_bsc")
          ) {
            var bkt = $(".book_title", contentTxt)[0]
            var leftc = bkt.offsetLeft
            var widthc = bkt.offsetWidth
            if ($this.hasClass("bsc1")) {
              $this.css({
                width: widthc + "px",
                left: leftc + "px",
                "box-sizing": "border-box",
              })

              if (settings.tipoLibro == 1) {
                if (bookHasRelatedContent) {
                  $(boxRelatedContent).css({
                    width: widthc + "px",
                    left: leftc + "px",
                  })
                  $this.before(boxRelatedContent)
                }
                if (hasBookVocabulary) {
                  $(boxVocabulary).css({
                    width: widthc + "px",
                    left: leftc + "px",
                  })
                  $this.before(boxVocabulary)
                }
              }
            }
            if ($this.hasClass("bsc2")) {
              leftc += numColumns == 2 ? pasoPagina / 2 : pasoPagina
              $this.css({
                width: widthc + "px",
                left: leftc + "px",
                "box-sizing": "border-box",
              })
            }
            $this.addClass("di_bsc")
          }

          //Empuja los titulos.nueva_pagina hacia la nueva pagina
          if ($this.hasClass("nueva_pagina") && !$this.hasClass("di_np")) {
            var previous = viewerTxtServices.getPrevNodeWithText(this)
            if (previous != null) {
              //Evita empujar dos nueva_pagina seguidos o empuja el elemento que sigue a un letrero_centro_pagina
              if (
                !$(previous).hasClass("nueva_pagina") ||
                $(previous).hasClass("letrero_centro_pagina")
              ) {
                moveToNewPAge(this)
              }
            }
            $this.addClass("di_np")
          }

          //Empuja los parrafos.nueva_pagina_parrafo hacia la nueva pagina
          if (
            $this.hasClass("nueva_pagina_parrafo") &&
            !$this.hasClass("di_npp")
          ) {
            moveToNewPAge(this)
            $this.addClass("di_npp")
          }

          //Centra las imagenes incrustadas dinamicamente
          if ($this.hasClass("img_book_art") && !$this.hasClass("di_img")) {
            var w = parseInt(this.className.match(/w_\d+/)[0].match(/\d+/)[0])
            var h = parseInt(this.className.match(/h_\d+/)[0].match(/\d+/)[0])

            var W = numColumns == 2 ? columnSize : visibleTextWidth
            var H = this.offsetHeight

            //Factor reduccion
            var redFact = 30

            if (numColumns == 1) {
              W -= redFact
              H -= redFact
            }

            var size = viewerTxtServices.getAnchoAlto(W, H, w, h)

            var bgW = size[0]
            var bgH = size[1]

            if (numColumns == 1) {
              W += redFact
              H += redFact
            }

            var bgL = parseInt((W - bgW) / 2)
            var bgT = parseInt((H - bgH) / 2)

            $this.css({
              "background-size": bgW + "px " + bgH + "px",
              "background-position": bgL + "px " + bgT + "px",
            })
            $this.addClass("di_img")
          }

          //Centra las imagenes incrustadas en el html
          if ($this.hasClass("img_html_embed") && !$this.hasClass("di_imge")) {
            var w = parseInt(this.className.match(/w_\d+/)[0].match(/\d+/)[0])
            var h = parseInt(this.className.match(/h_\d+/)[0].match(/\d+/)[0])

            var W = numColumns == 2 ? columnSize : visibleTextWidth
            var H = visibleTextHeigh - 10

            //Se resta el padding (5px x 2) de los parrafos
            W -= 10

            if (w > W) {
              w = W
            }
            if (h > H) {
              h = H
            }

            $this.css({ width: w + "px", height: h + "px" })
            $this.addClass("di_imge")
          }

          var spanCredits = $this.find("span.credito_pie_foto")
          var getCreditsH = function (_spans) {
            var h = 0
            $.each(_spans, function () {
              h += this.offsetHeight
            })
            return h
          }

          $("img.img_html_embed", $this).each(function () {
            var w = parseInt(this.className.match(/w_\d+/)[0].match(/\d+/)[0])
            var h = parseInt(this.className.match(/h_\d+/)[0].match(/\d+/)[0])

            var ft = 80
            var sh = 0
            var W = (numColumns == 2 ? columnSize : visibleTextWidth) - ft
            var H = visibleTextHeigh - ft
            var HO = H

            if (spanCredits[0]) {
              sh = getCreditsH(spanCredits)
              H -= ft
            }

            var size = viewerTxtServices.getAnchoAlto(W, H, w, h)

            var bgW = size[0]
            var bgH = size[1]
            var mt = parseInt((HO - (bgH + sh)) / 2)

            $this.css({ height: HO + "px" })
            $(this).css({ "margin-top": mt + "px" })

            this.width = bgW
            this.height = bgH
          })

          //Centra verticalmente los letrero_centro_pagina en el texto
          if (
            $this.hasClass("letrero_centro_pagina") &&
            !$this.hasClass("di_lcp")
          ) {
            var pt = (visibleTextHeigh - this.offsetHeight) / 2
            $(this)
              .prev(".div_centro_pagina")
              .css({ height: pt + "px" })
            $(this).css({ margin: "0" })
            $this.addClass("di_lcp")
          }
        })
      }

      function moveToNewPAge(_elem) {
        $(_elem).css({ "margin-top": "" })
        var mt = contentTxt.offsetHeight - _elem.offsetTop
        $(_elem).css({ "margin-top": mt + "px" })
      }

      function createTools(_pag) {
        var tools = $(".tag_tool", _pag).detach()

        if (tools.length > 0) {
          var header = $(".titulo_2", _pag)[0]
          header.tools = []

          tools.each(function () {
            var $this = $(this)
            $this.text("")
            $this.css({ display: "inline-block" })

            if ($this.hasClass("tool_create")) {
              this.title = "Crear nueva nota"
            }
            if ($this.hasClass("tool_edit")) {
              this.title = "Editar nota"
            }
            if ($this.hasClass("tool_send")) {
              this.title = "Enviar nota"
            }
            if ($this.hasClass("tool_img")) {
              this.title = "Cargar Imagen"
            }
            if ($this.hasClass("tool_discard")) {
              this.title = "Descartar nota"
            }
            if ($this.hasClass("tool_delete")) {
              this.title = "Borrar nota"
            }
            if ($this.hasClass("tool_publish")) {
              this.title = "Publicar nota"
            }
            if ($this.hasClass("tool_unpublish")) {
              this.title = "Ocultar nota"
            }
            if ($this.hasClass("tool_edit_profile_concurso")) {
              this.title = "Editar datos de inscripci贸n"
            }
            if ($this.hasClass("tool_edit_project")) {
              this.title = "Editar Proyecto"
            }
            if ($this.hasClass("tool_edit_nota_perso")) {
              this.title = "Editar Personaje"
            }

            this.header = header
            this.onclick = activeTool

            header.tools.push(this)
          })
        }

        //NOTA!!
        //Para la edicion de libros, diamante (tipoLibro = 1 y 27)
        //siempre va a estar presente el 铆cono de editar en ambos lados?
        //o en los quiebres de pagina? *** pasando el numePagina como parametro
      }

      function tagText(_pag, npagClass) {
        if (npagClass) {
          npagClass = npagClass[0]

          //$(_pag).first().children().addClass(npagClass);
          $(_pag)
            .children()
            .each(function () {
              var childNodes = this.childNodes
              for (var i = 0; i < childNodes.length; i++) {
                var node = childNodes[i]
                if (node.nodeType == 3) {
                  $(node).wrap('<span class="' + npagClass + '"></span>')
                }
                if (node.nodeType == 1) {
                  $(node).addClass(npagClass)
                }
              }
            })
        }
      }

      function replaceNodeByTokens(_node, _umbral, _tokens) {
        _tokens = viewerTxtServices.cleanAccents(_tokens)
        var swLength = _tokens.length
        var tokensSW = _tokens.toLowerCase().split(/\s+/)
        var nTokens = tokensSW.length
        var nodeLength = _node.nodeValue.length
        var ctNodeText = _node.nodeValue

        var regExp = new RegExp("\\b" + _tokens + "\\b", "i")
        var countSW = (
          viewerTxtServices.cleanAccents(ctNodeText).match(regExp) || []
        ).length
        var mxTime = 0
        var result = ""

        var idxStart = 0
        var idxEnd = 0
        var spanNode1 = null
        var txtNode1 = null
        var spanNode2 = null
        var txtNode2 = null

        if (nTokens > 0 && countSW > 0) {
          while (
            (result = regExp.exec(
              viewerTxtServices.cleanAccents(ctNodeText).toLowerCase()
            ))
          ) {
            idxStart = result.index
            idxEnd = idxStart + _tokens.length
            spanNode1 = $(_node.parentNode).clone()
            txtNode1 = ""

            if (idxStart == 0) {
              if (swLength == nodeLength) {
                $(_node.parentNode).addClass("letraNombres letraNombresTextual")
                break
              } else {
                txtNode1 = ctNodeText.substring(idxStart, idxEnd)
                spanNode1.html(txtNode1).addClass("letraNombres")
                _node.nodeValue = ctNodeText.replace(txtNode1, "")
                $(spanNode1).insertBefore(_node.parentNode)
              }
            } else {
              txtNode1 = ctNodeText.substring(0, idxStart)
              spanNode1.html(txtNode1)
              $(spanNode1).insertBefore(_node.parentNode)

              spanNode2 = $(_node.parentNode).clone()
              txtNode2 = ctNodeText.substring(idxStart, idxEnd)
              spanNode2.html(txtNode2).addClass("letraNombres")
              $(spanNode2).insertBefore(_node.parentNode)

              _node.nodeValue = ctNodeText.replace(
                ctNodeText.substring(0, idxEnd),
                ""
              )
            }

            //Forzar la salida si no se cumple el criterio de busqueda
            if (mxTime > 10) {
              break
            }

            ctNodeText = _node.nodeValue
            countSW = (
              viewerTxtServices.cleanAccents(ctNodeText).match(regExp) || []
            ).length
            mxTime++
          }
        } else {
          ctNodeText = viewerTxtServices.cleanAccents(ctNodeText).toLowerCase()
          var tokensNode = ctNodeText
            .replace(/[鈥揬.;,:\'\"\鈥淺鈥漒(\)\[\]芦禄\?\驴隆\!-]/gi, "")
            .split(/\s+/)
          var tksData = []

          var setEquix = function (_n) {
            var rs = ""
            for (var i = 0; i < _n; i++) {
              rs += "x"
            }
            return rs
          }

          var distanciaD = function (t1, t2, d) {
            var dist = t2 - t1
            return dist <= d ? true : false
          }

          var getCercanos = function (distanciaCheck) {
            var tokensVecinos = []
            var huboCercano = false
            for (var i = 0; i < tksData.length; i++) {
              var obj1 = tksData[i]
              var t1 = obj1.idx

              var obj2 = tksData[i + 1]
              var t2 = obj2 ? obj2.idx : undefined

              if (distanciaCheck(t1, t2, _umbral)) {
                tokensVecinos.push(obj1)
                huboCercano = true
              } else {
                if (huboCercano) {
                  tokensVecinos.push(obj1)
                }
                tokensVecinos.push(null)
                huboCercano = false
              }
            }

            return tokensVecinos
          }

          for (var tk in tokensSW) {
            var token = tokensSW[tk]
            regExp = new RegExp("\\b" + token + "\\b", "i")
            var tknObj = { tkn: token, idx: -1, pos: -1 }

            while ((result = regExp.exec(ctNodeText))) {
              idxStart = result.index
              idxEnd = idxStart + token.length
              var aIdx = tokensNode.indexOf(token)
              txtNode1 = ctNodeText.substring(idxStart, idxEnd)
              ctNodeText = ctNodeText.replace(regExp, setEquix(token.length))

              tksData.push({ tkn: token, idx: aIdx, pos: idxStart })
              tokensNode = ctNodeText
                .replace(/[鈥揬.;,:\'\"\鈥淺鈥漒(\)\[\]芦禄\?\驴隆\!-]/gi, "")
                .split(/\s+/)
            }
            var rst = $.grep(tksData, function (a) {
              return a.tkn == token
            })

            if (rst.length < 1) {
              tksData.push(tknObj)
            }
          }

          tksData.sort(function (a, b) {
            return a.idx - b.idx
          })
          var allToken = $.grep(tksData, function (a) {
            return a.idx == -1
          })

          if (allToken.length < 1) {
            var rstGroups = getCercanos(distanciaD)

            var tmpGroups = []
            var tmpGroup = []
            var finalGroups = []
            var finalGroup = []

            for (var i in rstGroups) {
              var rangeG = rstGroups[i]
              if (rangeG) {
                tmpGroup.push(rangeG.tkn)
                finalGroup.push(rangeG)
              } else {
                if (tmpGroup.length > 0) {
                  tmpGroups.push(tmpGroup)
                  finalGroups.push(finalGroup)
                }
                tmpGroup = []
                finalGroup = []
              }
            }

            var deltaLg = 0
            for (var j in tmpGroups) {
              if (viewerTxtServices.isArraySubset(tmpGroups[j], tokensSW)) {
                var groupData = finalGroups[j]
                var lastG = groupData.length - 1

                idxStart = groupData[0].pos - deltaLg
                idxEnd =
                  groupData[lastG].pos + groupData[lastG].tkn.length - deltaLg

                spanNode1 = $(_node.parentNode).clone()
                txtNode1 = _node.nodeValue.substring(0, idxStart)
                spanNode1.html(txtNode1)

                $(spanNode1).insertBefore(_node.parentNode)

                spanNode2 = $(_node.parentNode).clone()
                txtNode2 = _node.nodeValue.substring(idxStart, idxEnd)
                spanNode2.html(txtNode2).addClass("letraNombres")
                $(spanNode2).insertBefore(_node.parentNode)

                _node.nodeValue = _node.nodeValue.replace(
                  _node.nodeValue.substring(0, idxEnd),
                  ""
                )
                deltaLg = nodeLength - _node.nodeValue.length
              }
            }
          }
        }
      }

      function underlineBySearchWords(_node, _umbral, _searchWords) {
        if (_node.nodeType == 1) {
          for (var i in _node.childNodes) {
            underlineBySearchWords(_node.childNodes[i], _umbral, _searchWords)
          }
        }

        if (_node.nodeType == 3) {
          replaceNodeByTokens(_node, _umbral, _searchWords)
        }
      }

      //Establece posicion contenedor indice paginas del vocabulario
      function setVocabularyPos() {
        var corpus = that.corpus

        if (!corpus) {
          return null
        }

        if (!isTouch && !corpus.isAlphabeticIndexMode) {
          var lnIzq = filterElementosEnVisual(
            "letraNombres",
            elementosEnVisual2,
            1
          )[0]
          var lnDer = filterElementosEnVisual(
            "letraNombres",
            elementosEnVisual2,
            2
          )[0]
          if (lnIzq) {
            corpus.changeContPosition(2)
          }
          if (lnDer) {
            corpus.changeContPosition(1)
          }
        }
      }

      function appendVocabulary() {
        if (!isTouch && that.corpus && that.corpus.cont.is(":visible")) {
          $(that.boxViewerTXT).append(that.corpus.cont)
        }
      }

      function setLetraNombresByIdx(_pag, _idx) {
        var wc = -1,
          allTokens = 0
        var shouldBeContinue = true
        var lastChild = null,
          isLastChild = false
        var regExp = /[鈥揬.;,:\'\"\鈥淺鈥漒(\)\[\]芦禄\?\驴隆\!-\*颅\%]/gi
        var vocaArr = settings.searchVocabulary.toLowerCase().split(/\s+/)
        var searchVoca = {}

        for (var i = 0; i < vocaArr.length; i++) {
          if (!searchVoca[vocaArr[i]]) {
            searchVoca[vocaArr[i]] = vocaArr[i]
          }
        }

        var abbrevs = {
          entlo: "entlo",
          etc: "etc",
          ej: "ej",
        }

        var underlineWord = function (_node, _arr, _idx) {
          var nodeTxt = _node.nodeValue
          var nodeSize = nodeTxt.length
          var posF = 0,
            j = 0

          var spanNode1 = null
          var txtNode1 = null
          var spanNode2 = null
          var txtNode2 = null

          while (j <= _idx) {
            posF += _arr[j].length + 1
            j++
          }
          posF--

          var posI = posF - _arr[_idx].length
          spanNode1 = $(_node.parentNode).clone()

          if (posI == 0) {
            var palSize = _arr[_idx].length
            if (palSize == nodeSize) {
              $(_node.parentNode).addClass("letraNombres")
            } else {
              txtNode1 = nodeTxt.substring(posI, posF)
              spanNode1.html(txtNode1).addClass("letraNombres")
              _node.nodeValue = nodeTxt.replace(txtNode1, "")
              $(spanNode1).insertBefore(_node.parentNode)
            }
          } else {
            txtNode1 = nodeTxt.substring(0, posI)
            spanNode1.html(txtNode1)
            $(spanNode1).insertBefore(_node.parentNode)

            spanNode2 = $(_node.parentNode).clone()
            txtNode2 = nodeTxt.substring(posI, posF)
            spanNode2.html(txtNode2).addClass("letraNombres")
            $(spanNode2).insertBefore(_node.parentNode)

            _node.nodeValue = nodeTxt.replace(nodeTxt.substring(0, posF), "")
          }
        }

        var containsToken = function (token) {
          var contMW = 0
          for (var i in searchVoca) {
            contMW = (token.match(searchVoca[i]) || []).length
            if (contMW > 0) {
              break
            }
          }
          return contMW
        }

        var findTokenByBruteForce = function (arr, ref, offset, node) {
          var li = ref - offset < 1 ? 0 : ref - offset
          var ls = ref + offset > arr.length ? arr.length : ref + offset
          var tryAgain = true

          for (var i = li; i < ls; i++) {
            var token = arr[i].replace(regExp, "").toLowerCase()
            if (searchVoca[token] || containsToken(token) > 0) {
              tryAgain = false
              underlineWord(node, arr, i)
              break
            }
          }

          if (offset <= 6 && tryAgain) {
            findTokenByBruteForce(arr, ref, ++offset, node)
          }
        }

        var selectWordToUnderline = function (_node, _ctTokens, _pos) {
          var ntTokens = _node.nodeValue.split(/\s/)

          if (ntTokens.length == _ctTokens.length) {
            var tkToFind = ntTokens[_pos].replace(regExp, "")
            if (searchVoca[tkToFind]) {
              underlineWord(_node, ntTokens, _pos)
            } else {
              findTokenByBruteForce(ntTokens, _pos, 1, _node)
            }
          } else {
            findTokenByBruteForce(ntTokens, _pos, 2, _node)
          }
        }

        var findNodeByTokenIdx = function (_node) {
          var nodeText = _node.nodeValue
          var cleanText = nodeText
            .replace(/-/g, " ")
            .replace(regExp, "")
            .replace(/\s+/g, " ")
            .trim()
          var ctTokens = cleanText.split(/\s/)

          if (cleanText.length > 0) {
            allTokens += ctTokens.length
            for (var k in ctTokens) {
              var token = ctTokens[k].toLowerCase()
              if (!abbrevs[token]) {
                ++wc
                if (
                  wc == _idx ||
                  (isLastChild && allTokens <= _idx && _idx <= wc + 3)
                ) {
                  shouldBeContinue = false
                  selectWordToUnderline(_node, ctTokens, k)
                  break
                }
              }
            }
          }
        }

        var goThroughOverNodes = function (_node) {
          if (_node.nodeType == 1) {
            for (var i in _node.childNodes) {
              goThroughOverNodes(_node.childNodes[i])
            }
          }

          if (_node.nodeType == 3) {
            findNodeByTokenIdx(_node)
          }
        }

        var children = $(_pag).children().children()
        lastChild = _pag[0].lastChild.lastChild

        for (var ch in children) {
          if (lastChild == children[ch]) {
            isLastChild = true
          }

          goThroughOverNodes(children[ch])
          if (!shouldBeContinue) {
            break
          }
        }
      }

      function setLetraNombres(_pag, _umbral) {
        var searchWords = viewerTxtServices.cleanAccents(settings.searchWords)
        var tokensSW = searchWords.toLowerCase().split(/\s+/)
        var tokenSWNSW = []

        $(_pag)
          .children()
          .children()
          .each(function () {
            underlineBySearchWords(this, _umbral, searchWords)
          })

        if (!$(_pag).find(".letraNombres")[0]) {
          $(_pag)
            .children()
            .children()
            .each(function () {
              for (var i in tokensSW) {
                if (viewerTxtServices.getStopWords().indexOf(tokensSW[i]) < 1) {
                  tokenSWNSW.push(tokensSW[i])
                  underlineBySearchWords(this, _umbral, tokensSW[i])
                }
              }
            })

          var el = _pag[0].firstElementChild
          var elNext = null
          var elPrev = null

          while (el) {
            elNext = el.nextElementSibling
            elPrev = el.previousElementSibling

            var hasLN = $(el).find(".letraNombres")
            var tokensLN = []

            var hasLN2 = $([el, elPrev]).find(".letraNombres")
            var tokensLN2 = []

            if (hasLN[0] || hasLN2[0]) {
              hasLN.each(function () {
                tokensLN.push(
                  viewerTxtServices.cleanAccents(
                    $.trim($(this).text()).toLowerCase()
                  )
                )
              })

              hasLN2.each(function () {
                tokensLN2.push(
                  viewerTxtServices.cleanAccents(
                    $.trim($(this).text()).toLowerCase()
                  )
                )
              })

              if (viewerTxtServices.isArraySubset(tokensLN, tokenSWNSW)) {
                hasLN.addClass("letraNombresTemp")
                el = elNext
                continue
              }

              if (
                elPrev &&
                viewerTxtServices.isArraySubset(tokensLN2, tokenSWNSW)
              ) {
                hasLN2.addClass("letraNombresTemp")
              }
            }

            el = elNext
          }

          var hasLNT = $(_pag).find(".letraNombresTemp")

          if (hasLNT[0]) {
            $(_pag)
              .find(".letraNombresTextual")
              .removeClass("letraNombresTextual")
            $(_pag).find(".letraNombres").removeClass("letraNombres")
            hasLNT.addClass("letraNombres").removeClass("letraNombresTemp")
          }
        }
      }

      function estructureText(_paragraph) {
        var htmlPage = $(document.createElement("div"))
        var el = _paragraph.firstElementChild
        var elNext = null
        var elPrev = null
        var canAddNode = false
        var maxSizeNode = 15
        var indexItemsClasses = [
          "index_search_item_preview",
          "index_search_page_preview",
          "letraNombresTextual",
        ]
        var htmlRsPage = $(document.createElement("div"))
        htmlRsPage.addClass(indexItemsClasses[0])

        var elemPage = $(document.createElement("div"))
        elemPage.addClass(indexItemsClasses[1])

        var getPageClass = function (_elem, _class) {
          var rs = 0
          var regex = new RegExp(_class + "_\\d+", "g")
          var pg = _elem.className.match(regex)
          if (pg[0]) {
            rs = pg[0].match(/\d+/)[0]
          }
          return rs
        }

        while (el) {
          elNext = el.nextElementSibling
          elPrev = el.previousElementSibling

          if (canAddNode) {
            var newNextNode = $(el).clone()
            viewerTxtServices.trimByTokenNodeValue(
              newNextNode[0],
              maxSizeNode,
              2
            )
            htmlRsPage.append(newNextNode)
            canAddNode = false
          }

          if ($(el).hasClass(indexItemsClasses[2])) {
            htmlRsPage = $(document.createElement("div"))
            htmlRsPage.addClass(indexItemsClasses[0])

            elemPage = $(document.createElement("div"))
            elemPage.addClass(indexItemsClasses[1])

            var txtTitlePage = "Ir a p谩gina: "
            var txtNumPage = getPageClass(el, "pg")

            if (settings.tipoLibro == 32) {
              txtTitlePage = "Ir a secci贸n: "
              txtNumPage = txtNumPage = getPageClass(el, "nsec")
            }

            elemPage.html(txtTitlePage + txtNumPage)

            var newPrevNode = $(elPrev).clone()
            if (newPrevNode[0]) {
              viewerTxtServices.trimByTokenNodeValue(
                newPrevNode[0],
                maxSizeNode,
                1
              )
            }

            htmlRsPage.append([newPrevNode, $(el).clone()])
            htmlRsPage.prepend(elemPage)
            htmlPage.append(htmlRsPage)

            canAddNode = true
          }

          el = elNext
        }

        return htmlPage.html()
      }

      function generateHtmlLEmptyNotes(_pag) {
        var html =
          '\
      <div class="empty_notes_section">\
          <div class="empty_notes_img"></div>\
          <div class="empty_notes_text">A煤n no has creado notas.<br>Tambi茅n podr谩s crear una nota seleccionando una regi贸n de texto y haciendo clic en la opci贸n Nota</div>\
      </div>\
      <div class="empty_notes_button">Crear nueva nota</div>\
      '
        _pag.append($(html))

        $(".titulo_2", _pag)
          .removeClass("titulo_2")
          .addClass("title_empty_notes")

        $(".empty_notes_button", _pag)[0].onclick = function () {
          panelSocial.editNote(17, 0, {
            idProy: 0,
            toBook: false,
            viewerText: that,
            nsecc: 1,
          })
        }
      }

      //Hace ajustes varios para completar la diagrmacion del libro
      function postDiagramate(_pag) {
        var $pag = $(_pag)

        //Letras capitales (Estos pedazos son temporales aqui: crear funcion de diagramacion)
        $("span.capital_letter", _pag).each(function () {
          $(this.parentNode).css({ "text-indent": "0px" })
        })

        //Ajusta portaditas y titulos en libros de investigacion
        $("img.bookCara", _pag).each(function () {
          var idLib = this.className.match(/\d+/)[0]
          $(this).replaceWith(
            '<div class="cell 1_' + idLib + '" align="center"></div>'
          )
        })

        //Ajusta el contenedor de thumbnails de imagenes relacionadas a una nota
        $("div.img_nota_holder", _pag).each(function () {
          $(this).css({ width: "100%", "text-align": "center" })

          var cells = $(".cell", this)
          if (cells.length > 0) {
            $(this).html(cells)
          } else {
            var idImg = $("img", this)[0]
              .src.match(/\d+\.jpg/)[0]
              .match(/\d+/)[0]
            $(this).html('<div class="cell 6_' + idImg + '"></div>')
          }
        })

        ltotalOS.loadPortada($("div.cell", _pag).toArray(), { hls: true })

        $("span.txt_ref_pag", _pag).each(function () {
          $(this).replaceWith(
            '<div class="txt_ref_pag">' + this.innerHTML + "</div>"
          )
        })
        $(".tituloMarker, .subTituloMarker", _pag).remove()

        //Activa los thumbnails de imagenes relacionadas a una nota
        $("div.img_nota_holder", _pag).each(function () {
          var cells = $(".cell", this)
          cells.click(function () {
            that.offCompruebaTecla()
            var ids = []
            ids.push(this.className.match(/6_\d+/)[0])

            $(this)
              .siblings(".cell")
              .each(function () {
                ids.push(this.className.match(/6_\d+/)[0])
              })

            viewerTxtServices.showImages(ids)
          })

          var timer = setInterval(function () {
            cells.css({ display: "inline-block" })

            var c = 0
            cells.each(function () {
              c += this.childElementCount
            })

            if (c > 0) {
              clearInterval(timer)
            }
          }, 100)
        })

        //Imagenes incrustadas dinamicamente
        $(".img_book_art", _pag).each(function () {
          //Pone las imagenes antes de nueva pagina
          var img = $(this).detach()
          $(".nueva_pagina, .book_title", _pag).eq(0).before(img)

          this.onclick = function () {
            var s = settings
            that.offCompruebaTecla()

            //Imagenes del libro
            if (s.tipoLibro == 1 || s.tipoLibro == 32) {
              var id = $(this)
                .css("background-image")
                .match(/\d+\.(jp(e)?g|png)/i)[0]
                .replace(/\.(jp(e)?g|png)/i, "")

              ltotalOS
                .loadData("imagenes", "librero", s.idLibro, {
                  tipo_item: s.tipoLibro,
                })
                .done(function (_data) {
                  var proc = $("#proceso").html(_data)
                  var ids = $("input", proc).val().split(",")

                  ids.unshift(id)
                  ids = viewerTxtServices.unique(ids)

                  viewerTxtServices.showImages(ids)
                })
            }
          }
        })

        //Imagenes incrustadas en el html
        $("img", _pag).each(function () {
          if (
            this.src.match(/\/ilustracion\/\d+\//) &&
            !$(this).hasClass("mceNonEditable")
          ) {
            var img = $(this)
            img.parents("p").css({ "text-indent": 0 })

            var w = parseInt(img.css("width"))
            var h = parseInt(img.css("height"))
            img.addClass("img_html_embed w_" + w + " h_" + h)
          }
        })

        $(".img_html_embed, .foto_promo, .foto_concursante", _pag)
          .not(".ltotal_link")
          .each(function () {
            var parent = $(this).parents(".ltotal_link")
            if (!parent[0]) {
              this.onclick = function () {
                panelSocial.openImageGeneric(1, 1, [this.src], {
                  modeImages: "generic_images",
                  titlesIMGS: [this.title],
                })
              }
            }
          })

        //Activa en enlace del autor del libro
        viewerTxtServices.activateAuthorsBook($(".book_autor", _pag), settings)
        viewerTxtServices.activateTranslatorBook(
          $(".book_traductor", _pag),
          settings
        )

        var pg0Class = $pag[0].className

        //Taggea numero de pagina
        var npagClass = pg0Class.match(/pg_-?\d+/)
        tagText(_pag, npagClass)

        //Taggea id de nota
        var idNotaClass = pg0Class.match(/id_nota_\d+/)
        tagText(_pag, idNotaClass)

        //Taggea id de recomendacion
        var idRecClass = pg0Class.match(/id_rec_\d+/)
        tagText(_pag, idRecClass)

        //Taggea numero de seccion
        var nsecClass = pg0Class.match(/nsec_\d+/)
        tagText(_pag, nsecClass)

        //Creditos de la segunda pagina
        //De libros de literatura (tipo 1) y libros art铆culos (tipo 27 modo 2)
        if (
          (settings.tipoLibro == 1 && npagClass == "pg_2") ||
          (settings.tipoLibro == 27 &&
            settings.modoLibro == 2 &&
            npagClass == "pg_-1")
        ) {
          $(_pag).prepend(
            '<p class="nueva_pagina" style="visibility:hidden; line-height:2px;">*</p>'
          )
        }

        //Fragmentos de parrafos busqueda textual
        if ($pag.hasClass("pg_txt_search")) {
          //Establece la clase letraNombres, umbral de 5 tokens de distancia.
          setLetraNombres(_pag, 5)

          //Remover nodos sin letraNombres, remover nodos anidados
          $pag.children().each(function () {
            var paragraNode = this
            var letraNombres = $(paragraNode).find(".letraNombres")
            if (!letraNombres[0]) {
              $(paragraNode).remove()
            }

            var setCorrectNested = function (_paragraph) {
              $(_paragraph)
                .children()
                .each(function () {
                  var span = $(this)
                  var spanLN = span.find(".letraNombres")
                  if (spanLN[0]) {
                    span.replaceWith(span[0].innerHTML)
                    tagText($pag[0], npagClass)
                    setCorrectNested(_paragraph)
                  }
                })
            }

            setCorrectNested(paragraNode)
          })

          var letraNombresTextual = $($pag).find(".letraNombresTextual")
          if (letraNombresTextual[0]) {
            $pag.children().each(function () {
              $(this).replaceWith(estructureText(this))
            })
          } else {
            var firstPara = $($pag[0].firstElementChild)
            var lastPara = $($pag[0].lastElementChild)

            var prevChild = $(".letraNombres", firstPara).first().prev()
            var nextChild = $(".letraNombres", lastPara).last().next()

            prevChild.prevAll().remove()
            nextChild.nextAll().remove()

            if (prevChild[0]) {
              viewerTxtServices.trimByTokenNodeValue(prevChild[0], 15, 1)
            }

            if (nextChild[0]) {
              viewerTxtServices.trimByTokenNodeValue(nextChild[0], 15, 2)
            }

            $pag.children().each(function () {
              $(this).replaceWith(
                '<div class="index_search_item_preview">' +
                  this.innerHTML +
                  "</div>"
              )
            })

            if (firstPara[0]) {
              var elemPage = $(document.createElement("div"))
              elemPage.addClass("index_search_page_preview")

              var txtTitlePage = "Ir a p谩gina: "
              var txtNumPage = firstPara[0].firstElementChild.className
                .match(/pg_\d+/)[0]
                .match(/\d+/)[0]

              if (settings.tipoLibro == 32) {
                txtTitlePage = "Ir a secci贸n: "
                txtNumPage = firstPara[0].firstElementChild.className
                  .match(/nsec_\d+/)[0]
                  .match(/\d+/)[0]
              }
              elemPage.html(txtTitlePage + txtNumPage)
              $($pag[0].firstElementChild).prepend(elemPage)
            }
          }

          $(".index_search_item_preview", $pag).each(function () {
            this.onclick = function () {
              contentSearchIndex["content"] = $(contentTxt).children().detach()
              var bookType = settings.tipoLibro
              var pg
              var nsec

              var getClassValue = function (clazzName, elem) {
                var regex = new RegExp(clazzName + "_\\d+", "g")
                return $("[class*=" + clazzName + "_]", elem)[0]
                  .className.match(regex)[0]
                  .match(/\d+/)[0]
              }

              if (bookType == 1) {
                pg = getClassValue("pg", this)
                settings.searchWordsPg = pg
                bookGotoPage(pg, null, null)
              }

              if (bookType == 27) {
                pg = getClassValue("pg", this)
                nsec = getClassValue("nsec", this)
                settings.searchWordsPg = pg
                bookGotoSection(nsec, pg)
              }

              if (bookType == 32) {
                nsec = getClassValue("nsec", this)
                settings.searchWordsPg = nsec - 1
                bookGotoSection(nsec)
              }
              settings.backSearchIndex = true
            }
            this.title = "Continuar leyendo"
          })
        }

        var emptyNotes = $(".index_empty_notes", $pag)
        if (emptyNotes[0]) {
          generateHtmlLEmptyNotes($pag)
        }

        //Indices
        if (settings.modoLibro == 0) {
          //Recoje el indice de contenidos, los procesa y almacena
          var indexItems = $(".index_item", $pag)
          if (indexItems[0]) {
            indexItems.each(function () {
              this.onclick = function () {
                ltotalUtils.checkConnection()
                valuesForRepaint = null

                if ($(this).hasClass("index_item_vocabulary")) {
                  return panelSocial.openBookVocabulary()
                }

                if ($(this).hasClass("index_item_related_content")) {
                  return getRelatedContent()
                }

                var npag = this.className.match(/npag_\d+/)
                haPasadoPagina = true

                if (npag) {
                  //Navega a una pagina del libro
                  npag = npag[0].match(/\d+/)[0]

                  var idxTxt = $.trim($(this).text())
                  bookGotoPage(npag, idxTxt, null)
                } else {
                  //Navega a una seccion del libro
                  var nsec = this.className.match(/nsec_\d+/)
                  if (nsec) {
                    nsec = parseInt(nsec[0].match(/\d+/)[0])
                    bookGotoSection(nsec)
                  }
                }

                if (settings.backSearchIndex) {
                  settings.backSearchIndex = false
                }
              }
            })
            setDataContentIndexType(indexItems, "bookContentIndex")
          }

          $(".index_item_create_note", $pag).click(function () {
            var idProy = this.className.match(/id_proy_\d+/)[0].match(/\d+/)[0]
            panelSocial.editNote(17, 0, {
              idProy: idProy,
              toBook: false,
              viewerText: that,
              nsecc: 1,
            })
          })

          $(".index_item_filter_personaje_lector", $pag).click(function () {
            if ($(this).hasClass("filter_1")) {
              settings.extraParamsLoadPages = { filter_personaje_lector: "1" }
            } else {
              delete settings.extraParamsLoadPages["filter_personaje_lector"]
            }
            bookIndex()
          })

          //Recoje el indice de marcadores, los procesa y almacena
          var bookMarksItems = $(".bookmark_item", $pag)
          if (bookMarksItems[0]) {
            bookMarksItems.each(function () {
              this.onclick = function () {
                ltotalUtils.checkConnection()

                var bmCDa = this.className.split(" ")
                var bmPag = bmCDa[1]
                var bmPer = bmCDa[2]
                if (bmPag && bmPer) {
                  bookGotoPage(bmPag, null, bmPer)
                }

                if (settings.backSearchIndex) {
                  settings.backSearchIndex = false
                }
              }
            })
            setDataContentIndexType(bookMarksItems, "bookMarksIndex")
          }

          //Recoje el indice de notas, los procesa y almacena
          var booknoteItems = $(".booknote_item", $pag)
          if (booknoteItems[0]) {
            booknoteItems.each(function () {
              this.onclick = function () {
                ltotalUtils.checkConnection()

                var bnCDa = this.className.split(" ")
                var ntPag = bnCDa[1]
                var ntIdN = bnCDa[2]
                if (ntPag) {
                  goToPageAndHighLightNote(ntPag, ntIdN)
                }

                if (settings.backSearchIndex) {
                  settings.backSearchIndex = false
                }
              }
            })
            setDataContentIndexType(booknoteItems, "bookNotesIndex")
          }
        }

        //Fragmentos
        if (settings.modoLibro == 4) {
          $(".fraction_data", $pag).each(function () {
            var frac = $(this)
            var cn = this.className
            var id_proy = cn.match(/id_proy_\d+/)[0].match(/\d+/)[0]
            var id_cred = cn.match(/id_cred_\d+/)[0].match(/\d+/)[0]
            var id_lib = cn.match(/id_lib_\d+/)[0].match(/\d+/)[0]
            var id_trad = cn.match(/id_trad_\d+/)[0].match(/\d+/)[0]
            var n_pag = cn.match(/n_pag_\d+/)[0].match(/\d+/)[0]

            frac.click(function () {
              var extra = {
                idProyCitaRela: id_proy,
                idCredCitaRela: id_cred,
                npagProyCitaRela: n_pag,
                numePagina: n_pag,
              }
              panelSocial.openBook(1, id_lib, id_trad, extra)
            })

            $(".tool_send_cita", frac).click(function (ev) {
              ev.stopPropagation()
              var txt = frac.prev().text() //PILAS Con los versos
              llector.grupos.sendTxtCitaToEditor(
                id_proy,
                id_lib,
                id_trad,
                n_pag,
                txt
              )
            })
          })
        }

        //Enlaces en caratulitas o citas al principio de las notas (17-->1, 27)
        if (settings.tipoLibro == 1 || settings.tipoLibro == 27) {
          $("div.cell, .cita_nota", _pag).each(function () {
            var $t = $(this)
            $t.css({ display: "block" })

            var p = null
            var div = null

            if ($t.hasClass("cita_nota")) {
              p = this
            } else {
              p = $t.parents("p")[0]
              div = $t.parents("div")[0]
            }

            //Ubica el item relacionado dejabo del titulo
            var cellElem = p || div
            var h3 = $(cellElem).prevAll("H3")[0]
            if (h3) {
              $(h3).after(cellElem)
            }

            if (p) {
              p.title = "Ir al Libro"
              p.onclick = function () {
                var data = this.outerHTML
                  .match(/ltotal.openBook\(\[\d+,\d+,\d+/)[0]
                  .replace("ltotal.openBook([", "")
                  .split(",")
                var tipo = 1
                var idLib = data[0]
                var idTrad = data[1]
                var npag = data[2]

                if (settings.tipoLibro == 1) {
                  var extra = {
                    idLibroRela: settings.idLibro,
                    idTradRela: settings.idTrad,
                    npagLibroRela: npag,
                    numePagina: npag,
                  }
                }
                if (settings.tipoLibro == 27) {
                  var dataElem = $(p).hasClass("cita_nota")
                    ? $(p).children()[0]
                    : $(".cell", p)[0]
                  var idNota = dataElem.className
                    .match(/id_nota_\d+/)[0]
                    .match(/\d+/)[0]
                  var extra = {
                    idNotaRela: idNota,
                    npagNotaRela: npag,
                    numePagina: npag,
                  }
                }

                panelSocial.openBook(tipo, idLib, idTrad, extra)
              }
            }

            if (div) {
              if ($(div).hasClass("caratula_musica_nota")) {
                div.onclick = function () {
                  var c = this.className
                  var idMusic = c.match(/id_music_\d+/)[0].match(/\d+/)[0]
                  var ruta = $(".music_single", this).attr("ruta")
                  ruta = ltotalOS.replaceRepoDir(ruta)
                  panelSocial.openMusic(ruta, {})
                }
              }

              if ($(div).hasClass("caratula_imagen_nota")) {
                div.onclick = function () {
                  var imgItem = $(".cell", div)[0]
                  if (imgItem) {
                    var idImg = imgItem.className
                      .match(/6_\d+/)[0]
                      .split("_")[1]
                    viewerTxtServices.showImages([idImg])
                  }
                }
              }
            }
          })
        }

        //Links en libros de personajes a libros y libros de investigacion
        if (settings.tipoLibro == 32) {
          $(".pesca", _pag).each(function () {
            this.onclick = function () {
              var c = this.className
              var idRec = c.match(/id_rec_\d+/)[0].match(/\d+/)[0]
              var tipo = c.match(/tipo_libro_\d+/)[0].match(/\d+/)[0]

              if (tipo == 1 || tipo == 27) {
                var idLib = c.match(/id_libro_\d+/)[0].match(/\d+/)[0]
                var npag = c.match(/n_pag_\d+/)[0].match(/\d+/)[0]

                var extra = {
                  idRecRela: idRec,
                  npagRecRela: npag,
                  idNotaRecRela: -1,
                }

                if (tipo == 1) {
                  var idTrad = c.match(/id_trad_\d+/)[0].match(/\d+/)[0]
                  extra.numePagina = npag
                }
                if (tipo == 27) {
                  var idTrad = -1
                  var nsecc = c.match(/n_secc_\d+/)[0].match(/\d+/)[0]
                  var idNota = c.match(/id_nota_\d+/)[0].match(/\d+/)[0]
                  extra.numeSeccion = nsecc
                  extra.numePaginaSeccion = npag
                  extra.idNotaRecRela = idNota
                }

                panelSocial.openBook(tipo, idLib, idTrad, extra)
              }

              if (tipo == 3) {
                var idAutor = c.match(/id_autor_\d+/)[0].match(/\d+/)[0]
                panelSocial.verMasInfoItem("autores", idAutor, null, null)
              }

              if (tipo == 10) {
                var idMusic = c.match(/id_music_\d+/)[0].match(/\d+/)[0]
                var ruta = $(this).attr("ruta")
                ruta = ltotalOS.replaceRepoDir(ruta)
                panelSocial.openMusic(ruta, { idRecRelaSkip: idRec })

                //Busca toda la musica del personaje
                ltotalOS
                  .loadData("personajes", "musica", settings.idLibro, {})
                  .done(function (_d) {
                    panelSocial.setRutasMusic(ruta, _d)
                  })
              }
            }
          })
        }

        //Enlaces en el libro tipo banner
        $(".ltotal_link", _pag).each(function () {
          generateMosaicLink(this)
        })

        $(".letrero_centro_pagina", _pag).each(function () {
          var nx = $(this).next()
          if (nx[0] && !nx.hasClass("letrero_centro_pagina")) {
            nx.addClass("nueva_pagina_parrafo")
          }

          $(this).before(
            '<div class="div_centro_pagina nueva_pagina" style="color:rgba(0, 0, 0, 0)">-</div>'
          )
        })

        //Concurso
        if (typeof concurso !== "undefined") {
          concurso.postDiagramateBookPage(_pag)
        }

        $(".resumenMarker", _pag).each(function () {
          $(this).css({ color: "" })
        })
      }

      this.generateMosaicLink = function (elem, _extra) {
        generateMosaicLink(elem, _extra)
      }

      function generateMosaicLink(elem, _extra) {
        var link = elem.className.match(/link_.+_link/)
        if (link) {
          link = link[0].replace("link_", "").replace("_link", "")
          elem.onclick = function () {
            var modalViewer = null
            if (_extra && _extra.modalViewer) {
              modalViewer = _extra.modalViewer
            }

            var openMosaic = true
            var linkObj = panelSocial.decodeMosaicLink(link)
            var tipItem = linkObj.tipoItem
            if (
              tipItem == 1 &&
              linkObj.dataLink.length > 3 &&
              linkObj.tipoPatron == 1
            ) {
              openMosaic = false
            }
            if (
              tipItem == 4 ||
              tipItem == 6 ||
              tipItem == 10 ||
              tipItem == 15 ||
              tipItem == 16 ||
              tipItem == 32
            ) {
              openMosaic = false
            }

            if (openMosaic) {
              if (!modalViewer) {
                var cont = paintRelatedContentModal({ newModal: true })

                $(".closeModalViewer", cont)[0].onclick = function () {
                  floatTextViewer.cerrar()
                  floatTextViewer.rePaintFunc = null

                  if (isTouch) {
                    panelSocial.mosaico.destroyTab(floatTextViewer.idTab)
                  }
                  mosaicoLibro = null
                }

                modalViewer = floatTextViewer
              }

              mosaicoLibro = new MosaicBooks({
                modePilars: false,
                backNavigationFunc: function () {
                  $(".back_nav", floatTextViewer.getContain()).css({
                    display: "block",
                  })
                },
                endBackNavigationFunc: function () {
                  $(".back_nav", floatTextViewer.getContain()).css({
                    display: "",
                  })
                },
              })

              var mosContain = mosaicoLibro.getContain()

              var boxMos = $(".boxMosaic", mosContain)
              boxMos.css({ height: "100%", "margin-top": 0 })

              modalViewer.addContent(mosContain)
            }

            panelSocial.openMosaicLink(link, { mosaic: mosaicoLibro })
          }
        }
      }

      function getRelatedContent() {
        var showRelatedContent = function (_extra) {
          var cont = paintRelatedContentModal(_extra)

          floatTextViewer.addContent(contentIndex["relatedContentIndex"])

          $(".closeModalViewer", cont)[0].onclick = function () {
            var hayMosaico = $(".boxMosaic", cont)[0]
            if (hayMosaico) {
              showRelatedContent()

              $(".back_nav", floatTextViewer.getContain()).css({ display: "" })
            } else {
              floatTextViewer.cerrar()
              floatTextViewer.rePaintFunc = null

              if (isTouch) {
                panelSocial.mosaico.destroyTab(floatTextViewer.idTab)
              }
            }
            mosaicoLibro = null
          }

          $(".related_content_item", cont).each(function () {
            generateMosaicLink(this, { modalViewer: floatTextViewer })
          })
        }

        if (!contentIndex["relatedContentIndex"]) {
          var url = "/ltotal/inicio/utils/ut_50.jsp"
          panelSocial
            .doPost(url, { idLibro: settings.idLibro })
            .done(function (_d) {
              setDataContentIndexType(
                $(".related_content_item", $("#proceso").html(_d)),
                "relatedContentIndex"
              )
              showRelatedContent({
                newModal: true,
                titulo: "Contenidos relacionados",
              })
            })
        } else {
          showRelatedContent({
            newModal: true,
            titulo: "Contenidos relacionados",
          })
        }
      }

      function paintRelatedContentModal(_extra) {
        if (_extra && _extra.newModal) {
          showFloatTextViewer("<p>&nbsp;</p>", "center")

          var toolsTop = $(".toolsTop", floatTextViewer.getContain())
          toolsTop.prepend(
            '<div class="back_nav"></div><div class="title_container"></div>'
          )

          $(".back_nav", floatTextViewer.getContain())[0].onclick =
            function () {
              mosaicoLibro.getBackNavigation()
            }

          if (isTouch) {
            panelSocial.mosaico.openObjectInTab(
              floatTextViewer,
              "Contenidos relacionados"
            )
          }

          floatTextViewer.rePaintFunc = function () {
            paintRelatedContentModal()
          }
        }

        var cont = floatTextViewer.getContain()
        var $cont = $(cont)

        if (_extra && _extra.titulo) {
          $(".title_container", cont).html(_extra.titulo)
        }

        $cont.addClass("book_html_related_content")
        $cont.css({ "background-color": "#0B1C2B" })
        if (isTouch) {
          $cont.css({ height: "100%", width: "100%", top: 0, left: 0 })
        } else {
          $cont.css({
            width: "80%",
            left: "10%",
            height: that.boxViewerTXT.offsetHeight - 20,
            top: "0px",
            "border-radius": "15px",
          })
        }

        var floatContent = $(".contentHtml", cont)
        floatContent.css({
          width: "90%",
          "margin-left": "5%",
          "margin-top": "2px",
        })
        if (isTouch) {
          floatContent.css({
            width: "100%",
            "margin-left": 0,
            height: "94%",
            "margin-top": "20px",
          })
        }

        var toolsTop = $(".toolsTop", cont)
        toolsTop.css({ "margin-top": "14px" })

        var floatClose = $(".closeModalViewer", cont)
        floatClose.css({ right: "10px", "background-position": "-110px 0" })

        $(".toolsFooter", cont).remove()
        $(".boxScroll", cont).remove()

        if (mosaicoLibro) {
          mosaicoLibro.rePaintBanner()
        }

        panelSocial.setNativeMainViewBGColor(11, 28, 43)

        return cont
      }

      function goToPageAndHighLightNote(_nPag, _idNote) {
        var procRel = function (elv) {
          var rel = $(".relacion.17_" + _idNote, elv)
          var ln = rel.parent()
          var r = rel[0] && ln.hasClass("letraNombres")
          if (r) {
            ln.removeClass("letraNombres")
          }
          return r
        }

        settings = $.extend(settings, {
          idNotaRela: _idNote,
          npagNotaRela: _nPag,
          numePagina: _nPag,
        })
        return bookGotoPage(_nPag, null, null).done(function () {
          settings = $.extend(settings, {
            idNotaRela: null,
            npagNotaRela: null,
            numePagina: null,
          })

          var hayRela = false
          for (var i = 0; i < elementosEnVisual1.length; i++) {
            hayRela = procRel(elementosEnVisual1[i][0])
          }
          if (!hayRela) {
            for (var i = 0; i < elementosEnVisual2.length; i++) {
              procRel(elementosEnVisual2[i][0])
            }
          }
        })
      }

      //Mapea las rutas de la audiolectura
      function audioLecturaData(_pag) {
        var s = settings

        var npagClass = $(_pag)[0].className.match(/pg_\d+/)
        if (npagClass) {
          npagClass = npagClass[0]
        }

        var tAudio = $(".hdn_sonido", _pag).detach()
        if (tAudio[0] && npagClass) {
          var data = tAudio[0].value.split("|")
          var rutaAudio = data[0]
          var lengthAudio = data[1]
          var lectorAudio = data[2]
          audiolecturas[s.modoLibro][npagClass] = [
            rutaAudio,
            lengthAudio,
            lectorAudio,
          ]
        }
      }

      //Mapea el dato del marcador
      function bookMarksData(_pag) {
        var s = settings

        if (s.tipoLibro != 1 || s.modoLibro != 1) {
          return null
        }

        var npagClass = $(_pag)[0].className.match(/pg_\d+/)
        var bmPag = null
        if (npagClass) {
          bmPag = parseInt(npagClass[0].match(/\d+/))
        }

        var marks = $(".marcadores_pagina", _pag).detach().text()
        if ($.trim(marks).length > 0 && npagClass) {
          marks = JSON.parse(marks)
          if (marks.length > 0) {
            bookMarks[bmPag] = marks
          }
        }
      }

      function loadPages(_extra) {
        var s = settings

        var direction = "forward"

        if (_extra) {
          if (_extra.direction) {
            direction = _extra.direction
          }
        } else {
          _extra = {}
        }

        _extra.direction = direction

        var idxSec = 1

        var msgNoConn =
          "Est谩s sin conexi贸n.<br>Para continuar leyendo debes conectarte a internet."

        if (direction == "forward") {
          //Evita que consulte mas texto si ya llego al final del libro
          //NOTA!! esta condicion es temporal pues falta tener en cuenta
          //cuando esta en medio de una seccion y no hay necesidad de traer mas paginas
          if (finLibro[s.modoLibro]) {
            showPages()
            return null
          }

          ltotalUtils.checkConnection(msgNoConn)

          _extra.modoSeccion = "paginas"

          if (s.numePaginaRef) {
            _extra.numePaginaRef = s.numePaginaRef
          }

          idxSecsToLoad[s.modoLibro] += 1
          idxSec = idxSecsToLoad[s.modoLibro]
        }

        if (direction == "backward") {
          ltotalUtils.checkConnection(msgNoConn)

          _extra.modoSeccion = "indice"

          s.numePaginaRef = null
          s.numCapituloRef--
          idxSec = s.numCapituloRef
          idxSecsToLoad[s.modoLibro] = idxSec

          diagramElements = []
        }

        //Libro literatura,    lectura: tipo = 1,  modo = 1
        //Libro literatura,    indice:  tipo = 1,  modo = 0
        //Libro investigacion, lectura: tipo = 27, modo = 1
        //Libro investigacion, indice:  tipo = 27, modo = 0

        var extraParams = {
          seccion: idxSec,
          tipoLibro: s.tipoLibro,
          modoLibro: s.modoLibro,
          searchWords: s.searchWords,
          txtsearch_exact: s.txtsearch_exact,

          idRecRela: s.idRecRela,
          npagRecRela: s.npagRecRela,
          idNotaRecRela: s.idNotaRecRela,

          idNotaRela: s.idNotaRela,
          npagNotaRela: s.npagNotaRela,

          idImagenRela: s.idImagenRela,
          npagImagenRela: s.npagImagenRela,

          idLibroRela: s.idLibroRela,
          idTradRela: s.idTradRela,
          npagLibroRela: s.npagLibroRela,

          idProyCitaRela: s.idProyCitaRela,
          npagProyCitaRela: s.npagProyCitaRela,
          idCredCitaRela: s.idCredCitaRela,

          idFotoPromo: s.idFotoPromo,

          idLectorConcurso: s.idLectorConcurso,
        }

        jQuery.extend(extraParams, _extra)
        jQuery.extend(extraParams, s.SycCredentials)
        jQuery.extend(extraParams, s.extraParamsLoadPages)

        //consola([s.tipoLibro, s.modoLibro, s.idLibro, s.idTrad, idxSec]);

        loadingData = true

        var xhr = ltotalOS
          .loadData(
            "libros",
            "paginas",
            s.idLibro + "," + s.idTrad,
            extraParams
          )
          .done(function (htmlData) {
            htmlData = htmlData.replace(/(<br>)+/gi, "<br>")

            var proc = $("#proceso").html(htmlData)

            //Senal de redirigir el libro a una pagina especifica
            var goToPage = $(".goToPage", proc).detach()[0]
            if (goToPage) {
              goToPage = goToPage.innerHTML
            }

            //Anclajes de referencia para navegacion por indice
            var nPagRef = $(".numePaginaRef", proc).detach()[0]
            var nCapRef = $(".numCapituloRef", proc).detach()[0]
            var sSecRef = $(".seccionRef", proc).detach()[0]
            if (nPagRef) {
              nPagRef = nPagRef.innerHTML
              s.numePaginaRef = nPagRef
            }
            if (nCapRef) {
              nCapRef = parseInt(nCapRef.innerHTML)
              s.numCapituloRef = nCapRef
            }
            if (sSecRef) {
              sSecRef = parseInt(sSecRef.innerHTML)
              idxSecsToLoad[s.modoLibro] = sSecRef
            }

            var fin = $(".tag_info.fin_libro", proc).detach()[0]
            if (fin) {
              finLibro[s.modoLibro] = true
            }

            if (settings.isProhibido) {
              s.numCapituloRef = 1
              if (_extra.numePagina) {
                var pgo = $(".paginaLibroOut.pg_" + _extra.numePagina)
                pgo.prev().prevAll(".paginaLibroOut").remove()
                pgo.next().nextAll(".paginaLibroOut").remove()
              } else {
                //Deja ver pgs 3, 4
                $(".paginaLibroOut.pg_4").nextAll(".paginaLibroOut").remove()
              }

              var msgProhibido =
                "<blockquote><em>Las leyes de Derechos de Autor autorizan publicar s贸lo un fragmento de esta obra. Sin embargo, en nuestra biblioteca podr谩 encontrar relaciones literarias, musicales, art铆sticas e investigaciones, que le permitir谩n profundizar y contextualizar la misma; una nueva forma de leer el mundo.</em></blockquote>"

              var ini_proh = $(".inicio_libro_prohibido", proc)[0]
              if (ini_proh) {
                $(".paginaLibroOut", proc)
                  .first()
                  .prepend('<h3 class="titulo_2">Aviso</h3>' + msgProhibido)
              }
              $(".paginaLibroOut", proc)
                .last()
                .append(
                  '<h3 class="titulo_2" style="margin-top:40px;">Aviso</h3>' +
                    msgProhibido
                )
            }

            var pagsOut = $(".paginaLibroOut", proc)

            pagsOut.each(function () {
              var pg = $(this)

              var relas = $(".relaciones_pagina", pg).detach().text()
              mapRelacionesPagina(this, relas)

              var edicionPaginas = $(".hdn_paginas_editar", pg).detach()
              var tagDicc = $(".tag_dicc", pg).detach()

              audioLecturaData(pg)
              bookMarksData(pg)

              viewerTxtServices.cleanExcessHTML(pg)
              viewerTxtServices.transformHeaders(pg)
              viewerTxtServices.cleanFontSize(pg)

              viewerTxtServices.markLastBlankLine(pg)

              viewerTxtServices.procStndCreds(pg)

              createTools(pg)

              postDiagramate(pg)

              mapRelacionesPaginaNoSubrayan(this, relas)

              //subrayar palabras en la pagina
              if (s.searchWords && s.modoLibro == 1) {
                var pgSW = "pg_" + s.searchWordsPg
                if (pg.hasClass(pgSW)) {
                  setLetraNombres(pg, 5)
                }
              }

              //subrayar palabras por id en la pagina
              if (s.searchVocabulary && s.modoLibro == 1) {
                var pgSV = "pg_" + s.searchVocabularyPg
                if (pg.hasClass(pgSV)) {
                  setLetraNombresByIdx(pg, s.searchVocabularyIdx)
                }
              }

              viewerTxtServices.setEditionPage(pg, edicionPaginas)
              viewerTxtServices.setTagDicc(pg, tagDicc)
            })

            //Transformaciones especiales de diagramacion
            if_ajusta_portada: if (
              s.tipoLibro == 1 &&
              s.modoLibro == 1 &&
              idxSec == 1
            ) {
              var pg_1 = $(".pg_1:first", pagsOut)[0]
              if (!pg_1) {
                break if_ajusta_portada
              }

              verticalSpacer(pagsOut)

              var pg0 = pagsOut.eq(0)
              var imgBookArt = $(".img_book_art", pg0)
              if (numColumns == 1 && imgBookArt[0]) {
                //Libro a una columna no lleva imagen al principio
                imgBookArt.remove()
              }

              //Marca como nueva_pagina el primer nodo con texto de la pagina 3
              var pg3 = pagsOut.eq(2)
              if (pg3[0]) {
                var node = pg3[0].firstElementChild
                var nodeImg = $("img", node)[0]

                while (node && $.trim($(node).text()).length == 0 && !nodeImg) {
                  node = node.nextElementSibling
                  nodeImg = $("img", node)[0]
                }

                if (!$(node).hasClass("letrero_centro_pagina")) {
                  $(node).addClass("nueva_pagina")
                }
              }
            }

            //Libros art铆culos (tipo 27 modo 2)
            if (s.tipoLibro == 27 && s.modoLibro == 2) {
              verticalSpacer(pagsOut)
            }

            if (s.tipoLibro == 32 && s.modoLibro == 1 && idxSec == 1) {
              var pg0 = pagsOut.eq(0)
              var imgBookArt = $(".img_book_art", pg0)

              if (numColumns == 1 && imgBookArt[0]) {
                //Libro a una columna no lleva imagen al principio
                imgBookArt.remove()
              }
            }

            pagsOut.detach()

            if (direction == "forward") {
              var lastNode = contentTxt.lastElementChild

              $(contentTxt).append(pagsOut.children())

              if (lastNode && lastNode.nextElementSibling) {
                lastNode = viewerTxtServices.getPrevNodeWithText(
                  lastNode.nextElementSibling
                )
              }

              var lastNodeS =
                lastNode && lastNode.previousElementSibling
                  ? lastNode.previousElementSibling
                  : lastNode

              viewerTxtServices.normalizeHTML(contentTxt, lastNode)

              prepareDiagramElements(contentTxt, lastNodeS)

              showPages()

              loadingData = false
            }
            if (direction == "backward") {
              morePages = {}
              resetIdxsFirstNodoVisual()
              $(contentTxt).html(pagsOut.children())
              viewerTxtServices.normalizeHTML(contentTxt, null)

              prepareDiagramElements(contentTxt, null)

              bookGotoPageUI(goToPage, null)

              loadingData = false
            }

            distributeIcons()
            shouldLoadMorePages()
          })

        panelSocial.createLoaderItem(that.boxViewerTXT, xhr, null)
        return xhr
      }

      function shouldLoadMorePages() {
        var s = settings

        if (!finLibro[s.modoLibro]) {
          setTimeout(function () {
            lastNode = contentTxt.lastElementChild

            var rects = lastNode.getClientRects()
            var rect = rects[rects.length - 1]

            var ratio = getVisualRatioRect(rect)
            if (ratio < 1) {
              loadPages()
            }
          }, 200)
        }
      }

      function verticalSpacer(_pags) {
        var pg0 = _pags.eq(0)
        var imgBookArt = $(".img_book_art", pg0)
        var portada_concurso = $(".portada_concurso, .portada_foto_promo", pg0)
        if (numColumns == 2 && !imgBookArt[0] && !portada_concurso[0]) {
          //Espaciador vertical para libros con portada
          pg0.prepend(
            '<div style="height:100%;" class="vertical_spacer"></div>'
          )
        }
      }

      function getVisualRatioRect(_rect) {
        var boxRef = rectBoxViewer
        return (_rect.left - boxRef.left) / boxRef.width
      }

      function loadBookInfo() {
        var s = settings

        var tipoItem = s.tipoLibro

        //Libros art铆culos (tipo 27 modo 2)
        if (tipoItem == 27 && s.modoLibro == 2) {
          tipoItem = 17
        }

        var extraParams = {
          tipo_item: tipoItem,
          id_traduccion: s.idTrad,
          idRegalo: s.idRegalo,
        }

        jQuery.extend(extraParams, s.SycCredentials)

        return ltotalOS
          .loadData("libros", "nombre", s.idLibro, extraParams)
          .done(function (htmlData) {
            var proc = $("#proceso").html(htmlData)
            fwe = $(".hdn_edicion_framework", proc)
            var isLectorAutorLibro =
              $(".isLectorAutorLibro", proc).val() == "true"
            var data = $(".nomb_libro", proc).val().split(/\|/)
            var bookHasdataAuthors = $(".dataAuthors", proc)[0] ? true : false
            var bookHasTranslator = $(".dataTranslator", proc)[0] ? true : false
            var cover = $(".ruta_caratula_libro", proc).val()
            var bookHasVersions = $(".hdn_versions", proc)[0] ? true : false
            var isFavoriteBook = $(".isFavoriteBook", proc).val() == "true"
            var isLectorInProyecto =
              $(".isLectorInProyecto", proc).val() == "true"
            var isProhibido = $(".prohibido", proc).val() == "true"
            var codigoIso = $(".hdn_trad_lang", proc)[0]
              ? $(".hdn_trad_lang", proc).val()
              : "es"
            hasBookVocabulary = $(".hasBookVocabulary", proc).val() == "true"
            bookHasRelatedContent =
              $(".bookHasRelatedContent", proc).val() == "true"

            var dedicatoria = $(".dedicatoria", proc)[0]
              ? $(".dedicatoria", proc).val()
              : null

            if (s.idTrad < 1 && $(".id_trad", proc)[0]) {
              settings.idTrad = $(".id_trad", proc).val()
            }

            totalPags = parseInt($(".total_pags", proc).val())

            if (bookHasVersions) {
              tradsData = JSON.parse($(".versions_data", proc).val())
            }

            contentTxt.setAttribute("lang", codigoIso)

            if (bookHasdataAuthors) {
              settings.dataAuthors = JSON.parse($(".dataAuthors", proc).val())
            }

            if (bookHasTranslator) {
              settings.dataTranslator = JSON.parse(
                $(".dataTranslator", proc).val()
              )
            }

            if (data.length == 1) {
              var titulo = data[0]
              var autor = data[0]
            }
            if (data.length == 2) {
              var titulo = data[0]
              var autor = data[1]
            }
            if (data.length == 3) {
              var titulo = data[2]
              var autor = $.trim(data[1])
              if (autor.length == 0) {
                autor = titulo
              }
            }

            var labelAutor = autor
            if (titulo == autor) {
              labelAutor = ""
            }

            labelIZQ.innerHTML = titulo
            labelDER.innerHTML = labelAutor

            if (settings.tipoLibro == 1) {
              if (bookHasRelatedContent) {
                labelIZQ.onclick = getRelatedContent
                $(labelIZQ).addClass("activated")
              }

              if (bookHasdataAuthors) {
                var nombAutUpp = labelAutor.toUpperCase()
                var validAutor =
                  nombAutUpp.indexOf("DESCONOCIDO") == -1 &&
                  nombAutUpp.indexOf("VARIOS AUTORES") == -1 &&
                  nombAutUpp.indexOf("VARIOS ARTISTAS") == -1
                if (validAutor) {
                  $(labelDER).addClass(
                    "activated link_3_" + settings.dataAuthors[0].Id + "_link"
                  )
                  generateMosaicLink(labelDER)
                }
              }
            }

            if (typeof settings.fnBookInfoReady == "function") {
              settings.fnBookInfoReady(titulo)
            }

            //Libro prohibido no lleva ciertas herramientas
            if (isProhibido) {
              $([pestaIndex, boxSearch, boxShareGift]).remove()
            }

            //Libros art铆culos (tipo 27 modo 2) no llevan ciertas herramientas
            if (settings.tipoLibro == 27 && s.modoLibro == 2) {
              $([pestaIndex, boxSearch]).remove()
            }

            settings.nombLibro = titulo
            settings.autorLibro = autor
            settings.cover = cover
            settings.isLectorAutorLibro = isLectorAutorLibro
            settings.isFavoriteBook = isFavoriteBook
            settings.hasChangedFavorite = false
            settings.isLectorInProyecto = isLectorInProyecto
            settings.isProhibido = isProhibido
            showNumbersPages()
            showFavorite()
            showDedicatoria(dedicatoria)
          })
      }

      function showPages() {
        var s = settings

        calcular()

        var left = pasoPagina * (page[s.modoLibro] - 1)
        $(contentTxt).css("left", "-" + left + "px")

        children1 = $(contentTxt).children()
        children2 = children1.children()

        elementosEnVisual1 = getElementosEnVisual(-1, children1, null, {
          idxFirstNodo: s.modoLibro == 1 ? idxFirstNodoVisual1 : null,
        })
        elementosEnVisual2 = getElementosEnVisual(-1, children2, null, {
          idxFirstNodo: s.modoLibro == 1 ? idxFirstNodoVisual2 : null,
        })

        if (s.modoLibro == 1 && elementosEnVisual1[0]) {
          idxFirstNodoVisual1 = elementosEnVisual1[0][2]
        }
        if (s.modoLibro == 1 && elementosEnVisual2[0]) {
          idxFirstNodoVisual2 = elementosEnVisual2[0][2]
        }

        clearTimeout(viewedPagesTimer)
        viewedPagesTimer = setTimeout(getStatsPages, 100)

        var callStack = getFuncStack(showPages)
        var esRepaint = callStack.indexOf("doTheRePaint") > -1

        if (!esRepaint) {
          showPestanasRelaciones()
        }

        showNumbersPages()
        showAudiolectura()
        showNovedades()
        showTools()
        showPageEdition()
        showDiamond()
        showBookMark()
        distributeIconsBottom()
        appendVocabulary()

        clearTimeout(showReadingProgressTimer)
        showReadingProgressTimer = setTimeout(showReadingProgress, 200)
      }

      function getStatsPages() {
        var s = settings

        if (!viewedPagesDoStats) {
          viewedPagesDoStats = true
          return null
        }

        if (!ltotalOS.isConnected) {
          return null
        }

        var modoArticulo = s.tipoLibro == 27 && s.modoLibro == 2
        var modoLectura = s.modoLibro == 1 || modoArticulo
        if (!modoLectura) {
          return null
        }

        ltotalOS.timeStats({ tipoEvento: "pags_libro" })

        var pages = []
        $.each(elementosEnVisual2, function (i, e) {
          var pgc = e[0].className.match(/pg_\d+/)
          if (pgc) {
            pgc = pgc[0].match(/\d+/)[0]

            if (s.tipoLibro == 1 && pgc == 2) {
              return false
            }
            if (s.tipoLibro == 27) {
              if (e[0].className.match(/id_nota_\d+/)) {
                pgc =
                  pgc +
                  "-" +
                  e[0].className.match(/id_nota_\d+/)[0].match(/\d+/)[0]
              }
            }
            if (s.tipoLibro == 32) {
              pgc = e[0].className.match(/id_rec_\d+/)[0].match(/\d+/)[0]
            }

            pages.push(pgc)
          }
        })
        var xorPages = viewerTxtServices.xor(viewedPages, pages)
        var netPages = viewerTxtServices.intersect(xorPages, pages)

        if (netPages.length > 0) {
          viewedPages = netPages

          $.each(netPages, function () {
            var tipLibro = s.tipoLibro

            if (modoArticulo) {
              tipLibro = 17
            }

            ltotalOS.statistics(
              88,
              "tipoLibro:" +
                tipLibro +
                " idLibro:" +
                s.idLibro +
                " nPag:" +
                this
            )
          })
        }
      }

      function initiate() {
        var s = settings

        $(contentTxt).addClass("selecTXT")
        contentTxt.selecTXTFunc = function (_ev) {
          //Calcula el numero de pagina donde pone el cursor el usuario
          var npag = -1

          var sel = rangy.getSelection()
          var selectedRange = sel.getRangeAt ? sel.getRangeAt(0) : sel
          var fNode = sel.focusNode
          if (!fNode) {
            return null
          }

          var nodo = fNode.parentNode
          if (hasClassRegex(/pg_\d+/, nodo)) {
            npag = nodo.className.match(/pg_\d+/)[0].match(/\d+/)[0]
          }

          //ID de diccionario configurado para las notas
          var idDicc = null
          var header = $(nodo).parents("p, .titulo_2")
          if (!header.hasClass("titulo_2")) {
            header = header.prevAll(".titulo_2")
          }
          if (header[0]) {
            idDicc = header[0].idDicc
          }

          var idnota = -1
          if (hasClassRegex(/id_nota_\d+/, nodo)) {
            idnota = nodo.className.match(/id_nota_\d+/)[0].match(/\d+/)[0]
          }

          var conf = {
            tipoItem: s.tipoLibro,
            idItem: s.idLibro,
            idItem2: s.idTrad,
            modoItem: s.modoLibro,
            npagItem: npag,
            idNotaItem: idnota,
            isLectorAutorLibro: s.isLectorAutorLibro,
            idDicc: idDicc,
            showInsertInNote: true,
          }
          panelSocial.showContextualMenu(_ev, that.getContain(), conf)

          //that.bookData lo usa fisher-1.0.0.js para crear relaciones base
          if (npag > -1) {
            that.bookData = conf
          }

          panelSocial.selectedBook = that

          prepareCredits(npag, selectedRange, { idNota: idnota })
        }

        bindTouch(that.boxViewerTXT, bindTouchHandlePasoPagina())

        showFechaLectura()
        showDedicatoria(settings.dedicatoria)

        //Libros de personajes
        if (s.tipoLibro != 1 && s.modoLibro == 1) {
          $(contentTxt).css({ "padding-bottom": "50px" })
        }

        //Mis notas
        if (s.tipoLibro == 27 && s.idLibro == 0) {
          $(that.boxViewerTXT).addClass("mis_notas_txt")
        }

        if (typeof settings.fnOpen == "function") {
          settings.fnOpen(settings)
        }
      }

      function prepareCredits(_npagFocus, _selectedRange, _extra) {
        //Seleccion de creditos para arrastre al editor
        var htmlSelect = $.trim(_selectedRange.toHtml())
        if (htmlSelect.length > 0) {
          var creds = {}
          var procCreds = function (_npag, _txt) {
            _txt = $.trim(_txt)
            if (_txt.length > 0 && typeof _npag != "undefined" && _npag >= 0) {
              var hayCred = creds[_npag]
              if (!hayCred) {
                creds[_npag] = []
              }
              creds[_npag].push(_txt)
            }
          }

          var proc = $("#proceso").html(htmlSelect)
          var pgs = $("[class*=pg_]", proc)
          if (pgs.length > 0) {
            pgs.each(function () {
              if (this.className.match(/pg_\d+/)) {
                procCreds(
                  this.className.match(/pg_\d+/)[0].match(/\d+/)[0],
                  $(this).text()
                )
              }
            })
          } else {
            procCreds(_npagFocus, proc.text())
          }

          var s = settings

          var tipo = s.tipoLibro

          llector.creditsIN = []
          for (var npag in creds) {
            if (creds.hasOwnProperty(npag)) {
              var txt = creds[npag].join("<br>")
              var creditIN = {
                txt: txt.replace(/\r/g, "").replace(/\n+/g, "<br>"),
                cargar: 0,
              }
              var rela = null

              if (tipo == 1) {
                rela = {
                  tipo: 1,
                  id: s.idLibro,
                  idLibro: s.idLibro,
                  idTrad: s.idTrad,
                  npag: npag,
                }
                creditIN.metadatos =
                  "--id_libro:" +
                  s.idLibro +
                  "--id_trad:" +
                  s.idTrad +
                  "--pag:" +
                  npag
              }

              if (tipo == 27) {
                rela = {
                  tipo: 17,
                  id: _extra.idNota,
                  idLibro: -1,
                  idTrad: -1,
                  npag: npag,
                }
                creditIN.metadatos =
                  "--id_nota:" + _extra.idNota + "--pag:" + npag
              }

              if (rela) {
                //Combina los objetos
                $.extend(creditIN, rela)
                llector.creditsIN.push(creditIN)
              }
            }
          }
        }
      }

      function toolsBook(opts) {
        var boxToolsBook,
          setts = $.extend({}, opts)

        boxToolsBook = document.createElement("div")
        $(boxToolsBook)
          .addClass("boxToolsBook " + setts.clase)
          .css({ display: "none" })

        if ($(boxToolsBook).is(".book_pesta_index")) {
          $(pagesCenter).append(boxToolsBook)
        } else {
          if (!$(boxToolsBook).is(".pestana.right")) {
            $(pagesBottom).append(boxToolsBook)
          }
        }

        boxToolsBook.title = setts.txt
        boxToolsBook.onclick = setts.onclick

        return boxToolsBook
      }

      //Touch horizontal y vertical paso de pagina
      function bindTouchHandlePasoPagina() {
        return {
          start: function (px, py) {
            that.px = px
            that.py = py
          },
          move: function (dx, dy) {},
          end: function (DX, DY, DT, ev) {
            //No ejecuta paso cuando se hace touch sobre
            //el modal incrustado en el libro
            if (
              $(ev.target).parents(
                ".modalViewerLT, .form_compartir, .words_frequency, .boxBottomMenu, .boxPagesSeekSlider"
              )[0]
            ) {
              return null
            }

            var text = panelSocial.getSelectionText()
            if (text.length < 1) {
              if (DT > 50 && DT < 1000 && (DX > 70 || DY > 70)) {
                if (areInputsFocused()) {
                  return null
                }

                clearAudioData()
                backPage()
              }

              if (DT > 50 && DT < 1000 && (DX < -70 || DY < -70)) {
                if (areInputsFocused()) {
                  return null
                }

                clearAudioData()
                nextPage()
              }

              if (
                isTouch &&
                DT > 0 &&
                DT < 200 &&
                Math.abs(DX) < 10 &&
                Math.abs(DY) < 10
              ) {
                //Calcula el area activa para activar los creditos
                var x = $(window).width()
                var y = $(window).height()
                var f = 0.1

                var xI = x * f
                var xF = x - xI

                var yI = y * f
                var yF = y - yI

                //Verifica que el toque est茅 dentro del area activa
                var areaToShowCredits =
                  that.px > xI && that.px < xF && that.py > yI && that.py < yF
                if (areaToShowCredits) {
                  if (
                    $(boxBottomMenu).is(":visible") &&
                    !$(boxBottomMenu).find(".box_audio_tools")[0]
                  ) {
                    panelSocial.closeBottomMenu(that.boxViewerTXT)
                  } else {
                    showBookCredits(400)
                  }
                }
              }
            }

            if (
              floatTextViewer &&
              $(floatTextViewer.getContain()).hasClass("box_text_look_tools")
            ) {
              floatTextViewer.destroy()
            }
          },
        }
      }

      function closeBottomMenu() {
        if (!boxGlobePlaying) {
          panelSocial.closeBottomMenu(that.boxViewerTXT)
        }
      }

      function doTheRePaint() {
        var rsnClass = ltotalOS.deviceResolution
        isSmartPhone = ltotalOS.isSmartPhone
        isTablet = ltotalOS.isTablet
        isTouch = isSmartPhone || isTablet

        removeClassRegex(/ltr_[a-z]*/g, that.boxViewerTXT)
        $(that.boxViewerTXT).addClass(rsnClass)

        that.clearAudioData()

        columnGapSize = 0
        visibleTextWidth = 0
        stepBackIdxsFirstNodoVisual()
        resetDiagramElements()

        viewedPagesDoStats = false

        var newAspect = $(window).width() / $(window).height()

        var pg = $.trim($(paginLeft).text())
        if (pg.length > 0) {
          var npg = pg.match(/\d+/)[0]
          var perc = 0

          if (settings.pgFin) {
            npg = settings.pgFin
          }
          if (settings.numePaginaPercent) {
            perc = settings.numePaginaPercent
          }

          if (!valuesForRepaint) {
            valuesForRepaint = [npg, perc]
          }

          var nphTo = valuesForRepaint[0]
          var percTo = valuesForRepaint[1]

          if (percTo == 0) {
            percTo = 0.001
          }

          //Evita que se devuelva a la pg1 estando en la pg3 en escritorio (2 columnas)
          var avoidGoTo =
            numColumns == 2 &&
            parseInt(nphTo) < 3 &&
            newAspect == that.boxViewerTXT.aspect
          if (!avoidGoTo) {
            bookGotoPageUI(nphTo, null, percTo)
          }
        } else {
          showPages()
        }

        distributeIcons()

        if (that.corpus && that.corpus.cont.is(":visible")) {
          if (!isTouch) {
            $(that.boxViewerTXT).append(that.corpus.cont)
          }
          that.corpus.rePaint()
        }

        var modalV = $(".modalViewerLT", that.boxViewerTXT)
        if (modalV[0] && modalV.is(":visible")) {
          $(that.boxViewerTXT).append(modalV)
          floatTextViewer.rePaint()
        }

        fixDiagramBugs()

        that.boxViewerTXT.aspect = newAspect
      }

      this.rePaint = function (_extra) {
        var forcedRepaint = _extra && _extra.forcedRepaint
        var stopRepaint =
          executeNativeAudio &&
          lastNativeOrientation == panelSocial.getNativeOrientation()
        if (!$(that.boxViewerTXT).is(":visible") || stopRepaint) {
          if (!forcedRepaint) {
            return null
          }
        }

        panelSocial.colorText(panelSocial.textColor, that)
        panelSocial.changeFontText(panelSocial.tipoLetra, that)

        lastNativeOrientation = panelSocial.getNativeOrientation()

        var buscadorActivado = $(boxSearch).hasClass("activated")
        if (buscadorActivado) {
          openTxtSearch()
        }

        clearTimeout(timerRepaint)
        timerRepaint = setTimeout(doTheRePaint, 300)

        panelSocial.selectedBook = that
      }

      this.configArrows = function () {
        nextArrow = document.createElement("div")
        nextArrow.setAttribute("class", "nextArrow arrow")
        nextArrow.title = "P谩gina siguiente"
        nextArrow.innerHTML = isTouch
          ? ""
          : '<div class="pg_corner next"></div>'
        $(nextArrow).css("display", "none")

        backArrow = document.createElement("div")
        backArrow.setAttribute("class", "backArrow arrow")
        backArrow.title = "P谩gina anterior"
        backArrow.innerHTML = isTouch
          ? ""
          : '<div class="pg_corner back"></div>'
        $(backArrow).css("display", "none")

        nextArrow.onclick = function () {
          clearAudioData()
          nextPage()
        }
        backArrow.onclick = function () {
          clearAudioData()
          backPage()
        }

        closeLight = document.createElement("div")
        closeLight.setAttribute("class", "closeLight pg_center_left")
        closeLight.onclick = that.cerrar

        if (isTouch) {
          that.themeLight()
        } else {
          that.themeClasic()
        }
      }

      this.configColumns = function (_fncb) {
        $(this.boxViewerTXT).css({
          overflow: "hidden",
        })

        columnGapSize = resVector()

        $(contentTxt).css({ "column-gap": columnGapSize })

        if (isTouch && columnGapSize) {
          numColumns = 1

          if ($(contentTxt).width() > 0) {
            visibleTextWidth = parseInt($(contentTxt).width())
            columnSize = visibleTextWidth - columnGapSize
            $(contentTxt).css({ "column-width": columnSize + "px" })
            _fncb()
          } else {
            setTimeout(function () {
              that.configColumns(_fncb)
            }, 100)
          }
        } else {
          numColumns = 2
          $(contentTxt).css({ columns: "" + numColumns })
          _fncb()
        }

        that.boxViewerTXT.aspect = $(window).width() / $(window).height()
      }

      function showTextLookTools() {
        panelSocial.showTextLookTools(that, {
          toolsTipoLetra: true,
          toolsColorTema: true,
          colorCont: "white",
        })
      }

      function nextPage() {
        var s = settings

        valuesForRepaint = null
        that.removeHighlightPage()
        closeBottomMenu()

        if (!$(nextArrow).is(":visible")) {
          return null
        }

        if (loadingData) {
          return null
        }

        var yaPaso = false

        if (page[s.modoLibro] < total[s.modoLibro]) {
          $(contentTxt).css("left", "-=" + pasoPagina)
          page[s.modoLibro]++
          setPageContentIndexType()
          yaPaso = true
        }

        var xhr = null

        var getMorePages = false
        if (s.tipoLibro == 1 && s.modoLibro == 1) {
          var p = getNumberPageVisual()
          var r = p / 10
          var rid = Math.floor(r)
          if (rid >= 1 && !morePages[rid]) {
            morePages[rid] = true
            if (ltotalOS.isConnected) {
              getMorePages = true
            }
          }
        }

        if (
          (page[s.modoLibro] >= total[s.modoLibro] - 1 || getMorePages) &&
          !finLibro[s.modoLibro]
        ) {
          xhr = loadPages()
          xhr.done(function () {
            if (!yaPaso) {
              page[s.modoLibro]++
              showPages()
            }
          })
        }

        haPasadoPagina = true

        if (!xhr) {
          showPages()
        }
      }

      function backPage() {
        var s = settings

        valuesForRepaint = null
        that.removeHighlightPage()
        closeBottomMenu()

        if (!$(backArrow).is(":visible")) {
          return null
        }

        if (loadingData) {
          return null
        }

        if (page[s.modoLibro] > 0) {
          if (!$(contentTxt).position().left == 0) {
            $(contentTxt).css("left", "+=" + pasoPagina)
          }
          page[s.modoLibro]--
          setPageContentIndexType()
        }

        var xhr = null

        var capRef = 1
        if (s.numCapituloRef) {
          capRef = s.numCapituloRef
        }

        stepBackIdxsFirstNodoVisual()

        if (page[s.modoLibro] == 0 && capRef > 1) {
          finLibro[s.modoLibro] = false
          xhr = loadPages({ direction: "backward" })
        }

        haPasadoPagina = true

        if (!xhr) {
          showPages()
        }
      }

      function setPageContentIndexType() {
        var s = settings
        if (s.modoLibro == 0 && contentIndex[contentIndexType]) {
          contentIndex[contentIndexType]["page"] = page[s.modoLibro]
        }

        if (s.modoLibro == 3) {
          contentSearchIndex["page"] = page[s.modoLibro]
        }
      }

      function setDataContentIndexType(_ctIndexItems, _indexType) {
        var pgIdx = contentIndex[_indexType]
          ? contentIndex[_indexType]["page"]
          : 1
        contentIndex[_indexType] = _ctIndexItems
        contentIndex[_indexType]["page"] = pgIdx
      }

      function bookGotoPage(_npag, _idxTitle, _numePaginaPercent, _extra) {
        var s = settings

        s.modoLibro = 1
        s.numePaginaRef = null
        s.numCapituloRef = null
        idxSecsToLoad[s.modoLibro] = 0
        finLibro[s.modoLibro] = false
        page[s.modoLibro] = 1
        morePages = {}
        relations = {}
        firstNodePages = {}
        diagramElements = []
        resetIdxsFirstNodoVisual()

        if (_extra && _extra.clearAudio) {
          clearAudioData()
        }

        $(pestaIndex).fadeIn()

        $(contentTxt).empty()

        return loadPages({ numePagina: _npag }).done(function () {
          bookVolverFromIndex()
          bookGotoPageUI(_npag, _idxTitle, _numePaginaPercent)
          //Establece posicion contenedor indice paginas del vocabulario
          setVocabularyPos()
        })
      }

      function bookGotoSection(_nsec, _npagsec, _extra) {
        var s = settings

        s.modoLibro = 1
        var numePaginaPercent = null

        if (_extra) {
          //Articulos de investigacion (tipo 27 modo 2)
          if (_extra.modoLibro == 2) {
            s.modoLibro = 2
          }

          if (_extra.numePaginaPercent) {
            numePaginaPercent = _extra.numePaginaPercent
          }
        }

        idxSecsToLoad[s.modoLibro] = _nsec - 1
        s.numCapituloRef = _nsec
        finLibro[s.modoLibro] = false
        page[s.modoLibro] = 1
        morePages = {}
        relations = {}
        firstNodePages = {}
        diagramElements = []
        resetIdxsFirstNodoVisual()

        $(pestaIndex).fadeIn()

        bookVolverFromIndex()

        $(contentTxt).empty()

        return loadPages().done(function () {
          var fn = contentTxt.firstElementChild
          if (numColumns == 1 && $(fn).hasClass("img_book_art")) {
            fn = fn.nextElementSibling
          }

          if (_npagsec) {
            bookGotoPageUI(_npagsec, null, numePaginaPercent)
          } else {
            goToPageNode(fn)
          }
        })
      }

      function bookGotoPageUI(_npag, _idxTitle, _numePaginaPercent) {
        var s = settings

        var pgs = $(".pg_" + _npag, contentTxt)
        var pg_n = pgs[0]

        if (_idxTitle) {
          pg_n = pgs.filter(function () {
            if (_idxTitle == $.trim($(this).text())) {
              return true
            }
          })[0]
          if (!pg_n) {
            pg_n = pgs[0]
          }
        } else {
          var pg_letra = pgs.filter(".letraNombres")[0]
          if (!pg_letra) {
            pg_letra = pgs.find(".letraNombres")[0]
          }
          if (pg_letra) {
            pg_n = pg_letra
          }
        }

        //Corrige cuando hay imagen incrustada
        if ($(pg_n).parent().hasClass("img_book_art")) {
          pg_n = $(pg_n).parent()[0]
        }

        //Corrige cuando hay nodo dentro de creditos estandar
        if ($(pg_n).parent().hasClass("book_standard_credit")) {
          pg_n = $(pg_n).parent()[0]
        }

        if (numColumns == 1 && $(pg_n).hasClass("img_book_art")) {
          pg_n = pg_n.nextElementSibling
        }

        if (settings.txtCoord) {
          pg_n = underlineTxtByCoord(settings.txtCoord)
          settings.txtCoord = null
        }

        goToPageNode(pg_n)
        if (_numePaginaPercent && _numePaginaPercent > 0) {
          goToPagePercent(_npag, _numePaginaPercent)
        }
      }

      function goToPageNode(_node) {
        var s = settings

        calcular()

        var np = total[s.modoLibro]
        if (_node) {
          np = Math.ceil(_node.offsetLeft / pasoPagina)
        }

        //Asegura que abra en la dedicatoria del recuerdo
        if (numColumns == 1 && s.idFotoPromo && s.idFotoPromo > 0) {
          np = 0
        }

        page[s.modoLibro] = np

        showPages()
      }

      function goToPagePercent(_npag, _numePaginaPercent) {
        var rectBox = rectBoxViewer
        var totalPx = getClientRectsVTxt("pg_" + _npag, rectBox, 1, 1)[2]
        areTherePixelsInBothSides = false
        var leftPercentPage = getLeftPercentPage(
          "pg_" + _npag,
          rectBox,
          _numePaginaPercent,
          totalPx
        )

        var np = total[settings.modoLibro]
        np = Math.ceil(leftPercentPage / pasoPagina)
        page[settings.modoLibro] = np
        showPages()
      }

      function bookIndex() {
        ltotalUtils.checkConnection()

        settings.modoLibro = 0
        idxSecsToLoad[settings.modoLibro] = 0
        finLibro[settings.modoLibro] = false

        $(pestaIndex).css({ display: "none" })

        contentTxtBK = $(contentTxt).children().detach()

        $([pagesCenter, toolsIZQ, toolsDER]).empty()
        $([boxBookMark, boxSearch]).detach()

        $(".labelTop", containCredits).css({ display: "none" })
        $(contentTxt).css({ "padding-bottom": "50px" })

        clearAudioData()
        loadPages().done(function () {
          if (settings.tipoLibro == 1) {
            showBookContentIndex(contentIndexType)
          }
        })
      }

      function bookVolver() {
        settings.modoLibro = 1

        $(pestaIndex).css({ display: "" })

        bookVolverFromIndex()

        $(contentTxt).html(contentTxtBK)
        showPages()
      }

      function bookVolverFromIndex() {
        //Repinta los iconos para el modo lectura
        $(pagesCenter).removeClass("multi_index").empty()
        $(toolsIZQ).removeClass("multi_index").empty()

        if (settings.tipoLibro == 1) {
          $(contentTxt).css({ "padding-bottom": "" })
        } else {
          $(contentTxt).css({ "padding-bottom": "50px" })
        }

        $(".labelTop", containCredits).css({ display: "" })

        $(boxSearchContainer).css({ display: "none" })
        $(boxSearch).removeClass("activated")
        showFavorite()
        distributeIcons()
      }

      function showBookContentIndex(_indexType) {
        var s = settings

        var btnsIndex = $([
          boxBookContentIndex,
          boxBookMarksIndex,
          boxBookNotesIndex,
        ])

        var contCxt = null
        if (isTouch) {
          $(closeLight).css({ margin: "3px 0 0 0" })
          $(pagesCenter).append(closeLight)
          $(pagesCenter).addClass("multi_index").append(btnsIndex)
          contCxt = pagesCenter
        } else {
          $(toolsIZQ).addClass("multi_index")
          $(toolsIZQ).append(btnsIndex)
          contCxt = toolsIZQ
        }

        contentIndexType = _indexType ? _indexType : "bookContentIndex"

        page[s.modoLibro] = contentIndex[contentIndexType]["page"]

        btnsIndex.removeClass("selected_item_index")
        $("." + contentIndexType, contCxt).addClass("selected_item_index")

        $(contentTxt).html(contentIndex[contentIndexType])
        showPages()

        if (page[s.modoLibro] > total[s.modoLibro]) {
          page[s.modoLibro] = total[s.modoLibro]
          showPages()
        }
      }

      function showNumbersPages() {
        var s = settings
        var mL = s.modoLibro
        var tL = s.tipoLibro

        var nl = null
        var nr = null

        //Libro de lectura
        if (mL == 1 && tL == 1 && totalPags > 0) {
          var p = getNumberPageVisual()
          if (p > 0) {
            if (p == 2) {
              p = 1
            }

            var perc = Math.floor((p / totalPags) * 100)
            if (perc < 1) {
              perc = 1
            }

            nl = p + " de " + totalPags
            nr = perc + "%"
          }

          posPagesSeek({ pg: p, tpgs: totalPags })
        } else {
          $(boxPagesSeekSlider).css({ display: "none" })

          nl = ""
          nr = ""
        }

        if (nl != null) {
          paginLeft.innerHTML = nl
          $(".audio_number_page", boxBottomMenu).html(nl)
        }
        if (nr != null) {
          paginRight.innerHTML = nr
        }
      }

      function posPagesSeek(_opts) {
        if (typeof _opts.pg !== "undefined" && _opts.pg == 0) {
          return null
        }

        var doConfig = false
        if (!boxPagesSeekSlider.configured) {
          boxPagesSeekSlider.setAttribute("class", "boxPagesSeekSlider")
          $(boxPagesSeekSlider).html(
            '<div class="left_bar"></div><div class="right_bar"></div><div class="seeker"><div class="seek_handler"></div></div><div class="pglabel"></div>'
          )
          $(boxBottomMenu).before(boxPagesSeekSlider)
          boxPagesSeekSlider.configured = true
          doConfig = true
        }

        var pd = parseInt($(contentTxt).css("padding-left"))
        var cont = boxPagesSeekSlider
        var seeker = $(".seeker", cont)

        if ($(pagesCenter).is(":visible")) {
          $(cont).css({ display: "", padding: "0 " + pd + "px" })
        }

        var cw = cont.offsetWidth
        var sw = seeker[0].offsetWidth
        var minLeft = pd
        var maxLeft = cw - pd - sw
        var rangeLeft = maxLeft - minLeft
        var finalLeft = minLeft

        if (_opts.pg && _opts.tpgs) {
          var relaLeft = (_opts.pg * rangeLeft) / _opts.tpgs
          finalLeft = relaLeft + minLeft

          cont.tpgs = _opts.tpgs
        }
        if (_opts.px) {
          finalLeft = _opts.px
        }

        if (finalLeft < minLeft) {
          finalLeft = minLeft
        }
        if (finalLeft > maxLeft) {
          finalLeft = maxLeft
        }

        seeker.css({ left: finalLeft + "px" })

        var leftBar = $(".left_bar", cont)
        var rightBar = $(".right_bar", cont)
        leftBar.css({ left: minLeft + "px", width: finalLeft - minLeft + "px" })
        rightBar.css({
          left: finalLeft + 5 + "px",
          width: cw - pd - finalLeft - 5 + "px",
        })

        var finalpg = 1

        var pglabel = $(".pglabel", cont)
        if (_opts.showpglabel) {
          var pgll = (cw - 100) / 2
          pglabel.css({ display: "block", left: pgll + "px", width: "100px" })

          var pxposrel = finalLeft - pd
          var percent = pxposrel / rangeLeft
          finalpg = Math.round(percent * cont.tpgs)

          if (finalpg < 1) {
            finalpg = 1
          }
          if (finalpg > cont.tpgs) {
            finalpg = cont.tpgs
          }
          pglabel.html(finalpg)
        }
        if (_opts.hidepglabel) {
          pglabel.css({ display: "" })
        }
        if (_opts.gotopage) {
          bookGotoPage(finalpg, null, null, { clearAudio: true })
        }

        if (doConfig) {
          var bindTouchDocument = function () {
            bindTouch(document, {
              move: function (dx, dy) {
                var btnLeft = seeker[0].offsetLeft
                var ox = btnLeft - dx
                posPagesSeek({ px: ox, showpglabel: true })
              },
              end: function (dx) {
                document.unBindTouch()
                posPagesSeek({
                  px: seeker[0].offsetLeft,
                  showpglabel: true,
                  hidepglabel: true,
                  gotopage: true,
                })
              },
            })
          }

          bindTouch(seeker[0], {
            start: function (px, py, ev) {
              bindTouchDocument()
            },
          })
        }
      }

      function getNumberPageVisual() {
        var p = 0
        var elems = elementosEnVisual2
        if (elems != null && elems.length > 0) {
          var elem = elems[elems.length - 1][0]
          var pgClass = elem.className.match(/pg_\d+/)
          if (pgClass) {
            p = parseInt(elem.className.match(/pg_\d+/)[0].match(/\d+/)[0])
          }
        }
        return p
      }

      function filtroRelas6(_rela) {
        return _rela.tipo == 6
      }

      function mapRelacionesPaginaNoSubrayan(_pg, _relas) {
        //De momento solo trabaja con relaciones de imagenes (tipo 6) que no subrayan
        if ($.trim(_relas).length > 0) {
          var pgn = _pg.className.match(/pg_\d+/)[0]
          var relaspg = JSON.parse(_relas).filter(filtroRelas6)
          var pgNodes = $("." + pgn, _pg)

          for (var i = 0; i < pgNodes.length; i++) {
            var pgi = pgNodes.eq(i).clone()
            viewerTxtServices.createRelations(pgi, null, { relas: relaspg })
          }

          var filtroNO = function (_rela) {
            return !_rela.subraya
          }
          var relasNO = relaspg.filter(filtroNO)
          if (relasNO.length > 0) {
            //El primer nodo de texto de cada pagina
            $("." + pgn, _pg)
              .filter(function () {
                var p = $(this).parent()
                return !p.hasClass("img_book_art") &&
                  !p.hasClass("erase_blank_line") &&
                  $.trim($(this).text()).length > 0
                  ? true
                  : false
              })
              .eq(0)
              .addClass("firstNodePage")

            firstNodePages[pgn] = { relasNO: relasNO }
          }
        }
      }

      function mapRelacionesPagina(_pg, _relas) {
        if ($.trim(_relas).length > 0) {
          var pgn = _pg.className.match(/pg_\d+/)[0]
          var relaspg = JSON.parse(_relas)
          if (relaspg.length > 0) {
            relations[pgn] = relaspg
          }
        }
      }

      function markRelacionesEnVisual(_filterFunc) {
        settings.relations = relations

        var firstNodeEnVisual = {}

        for (var i = 0; i < elementosEnVisual2.length; i++) {
          var elv = elementosEnVisual2[i][0]
          var pgn = elv.className.match(/pg_\d+/)
          if (pgn) {
            var relaspg = relations[pgn[0]]

            if (relaspg && _filterFunc) {
              relaspg = relaspg.filter(_filterFunc)
            }

            viewerTxtServices.createRelations($(elv), null, { relas: relaspg })

            var fnp = firstNodePages[pgn]
            if (fnp) {
              if ($(elv).hasClass("firstNodePage") && fnp.relasNO.length > 0) {
                firstNodePages[pgn].node = elv
                firstNodeEnVisual[pgn] = true
              }
            }
          }
        }

        if (_filterFunc == null || _filterFunc == filtroRelas6) {
          //Marca las relaciones que no subrayan en el primer nodo de la visual
          //De momento solo trabaja con relaciones de imagenes (tipo 6) que no subrayan
          var firstNodesKeys = Object.keys(firstNodeEnVisual)
          for (var i = 0; i < firstNodesKeys.length; i++) {
            var pgn = firstNodesKeys[i]
            var firstNode = firstNodePages[pgn].node
            var relasNO = firstNodePages[pgn].relasNO

            var nodeRela = $(".relacion.tipo_6", firstNode)
            if (!nodeRela[0]) {
              $(firstNode).html(
                '<span class="relacion tipo_6">' +
                  $(firstNode).html() +
                  "</span>"
              )
              nodeRela = $(".relacion.tipo_6", firstNode)
            }

            for (var j = 0; j < relasNO.length; j++) {
              var rn = relasNO[j]
              nodeRela.addClass(
                "tipo_" +
                  rn.tipo +
                  " nosub_tipo_" +
                  rn.tipo +
                  " " +
                  rn.tipo +
                  "_" +
                  rn.id
              )
            }
          }
        }
      }

      function getRelacionesEnVisual(_exceptionClasses) {
        var spansRelas = []
        for (var i = 0; i < elementosEnVisual2.length; i++) {
          var elv = elementosEnVisual2[i][0]
          var relasElv = $(".relacion", elv)

          for (var j = 0; j < relasElv.length; j++) {
            var rela = relasElv[j]
            var doGet = true

            if (_exceptionClasses) {
              for (var k = 0; k < _exceptionClasses.length; k++) {
                var ec = _exceptionClasses[k]
                if ($(rela).hasClass(ec)) {
                  doGet = false
                }
              }
            }

            if (doGet) {
              spansRelas.push(rela)
            }
          }
        }
        return spansRelas
      }

      function cleanRelacionesEnVisual(_exceptionClasses) {
        var spansRelas = getRelacionesEnVisual(_exceptionClasses)
        if (spansRelas.length > 0) {
          $(spansRelas).each(function () {
            var papa = this.parentNode
            $(this).replaceWith(this.innerHTML)

            papa.normalize()
          })
          cleanRelacionesEnVisual(_exceptionClasses)
        }
      }

      function showPestanasRelaciones() {
        var mapRelaPesta = function (_pesta) {
          $(_pesta).css({ display: "" })
        }

        cleanRelacionesEnVisual(null)
        markRelacionesEnVisual(null)

        var left = parseInt($(contentTxt).css("left"))

        //Resetea pestanas
        var pagesContext = $(pagesLeft).add($(pagesRight)).add($(pagesBottom))
        var pestanas = $(".pestana", pagesContext)

        pestanas.each(function () {
          this.activated = false
        })
        pestanas.css({ display: "none" })

        var spansRelas = getRelacionesEnVisual()
        $(spansRelas).each(function () {
          var offset = left + this.offsetLeft
          var hasRelaNumber = this.innerHTML.match(/\[\d+\]/)
          var position = ""

          //Left
          if (offset > 0 && offset < pasoPagina / numColumns) {
            position = "left"

            //Comentarios
            if ($(this).hasClass("tipo_5")) {
              if (hasRelaNumber) {
                showNumberComments(this, "left")
              } else {
                mapRelaPesta(pestaNotaLeft)
              }
            }

            //Imagenes (e Imagenes de concurso)
            if ($(this).hasClass("tipo_6") || $(this).hasClass("tipo_31")) {
              mapRelaPesta(pestaImagLeft)
            }

            //Notas de investigacion
            if ($(this).hasClass("tipo_15") || $(this).hasClass("tipo_27")) {
              mapRelaPesta(pestaNotaLeft)
            }
          }

          //Right
          if (offset > pasoPagina / numColumns && offset < pasoPagina) {
            position = "right"

            //Comentarios
            if ($(this).hasClass("tipo_5")) {
              if (hasRelaNumber) {
                showNumberComments(this, "right")
              } else {
                mapRelaPesta(pestaNotaLeft)
              }
            }

            //Imagenes (e Imagenes de concurso)
            if ($(this).hasClass("tipo_6") || $(this).hasClass("tipo_31")) {
              mapRelaPesta(pestaImagLeft)
            }

            //Notas de investigacion
            if ($(this).hasClass("tipo_15") || $(this).hasClass("tipo_27")) {
              mapRelaPesta(pestaNotaLeft)
            }
          }

          //Notas del lector
          if ($(this).hasClass("tipo_17")) {
            this.onclick = function (_ev) {
              _ev.stopPropagation()
              var ids17 = buscaIDsRelaciones([this], "tipo_17", /\b17_\d+/g)
              showNotas(ids17, position, this)
            }
          }
        })

        if (spansRelas.length > 0) {
          cleanRelacionesEnVisual(["tipo_17", "number_comment"])
        }

        if (floatTextViewer) {
          var ftvCont = $(floatTextViewer.getContain())
          if (
            !ftvCont.hasClass("ultima_lectura") &&
            !ftvCont.hasClass("dedicatoria_regalo")
          ) {
            floatTextViewer.destroy()
          }
        }
        panelSocial.destroyShareModal()

        var idLecConcur = settings.idLectorConcurso
        if (idLecConcur && idLecConcur > 0) {
          var pestasIMGs = pestanas.filter(".book_pesta_imagenes")
          pestasIMGs.each(function () {
            if ($(this).is(":visible")) {
              showRelaciones.call(this)
            }
          })
        }
      }

      function showRelaciones(_ev) {
        var t = this
        var pestana = $(this)

        cleanRelacionesEnVisual(["number_comment"])

        var pagesContext = $(pagesLeft).add($(pagesRight)).add($(pagesBottom))
        $(".boxToolsBook", pagesContext).each(function () {
          if (t != this && this.activated) {
            this.activated = false
          }
        })

        if (t.activated) {
          t.activated = false
          showPestanasRelaciones()
        } else {
          var filterFunc = null
          if (pestana.hasClass("book_pesta_imagenes")) {
            filterFunc = filtroRelas6
          }
          if (pestana.hasClass("book_pesta_notas")) {
            filterFunc = function (_rela) {
              var hasRelaNumber = _rela.texto.match(/\[\d+\]/)
              return (
                (_rela.tipo == 5 && !hasRelaNumber) ||
                _rela.tipo == 15 ||
                _rela.tipo == 27
              )
            }
          }
          markRelacionesEnVisual(filterFunc)

          var pestaSide = pestana.hasClass("right") ? 2 : 1
          var left = parseInt($(contentTxt).css("left"))
          var position = t.className.match(/(right)|(left)/)[0]
          var bgcolor = t.colore

          var relasToShow = []
          var relasNOSub = 0
          var relas = $(getRelacionesEnVisual(["tipo_17", "number_comment"]))
          relas.each(function () {
            var offset = left + this.offsetLeft

            var relaSide = 0
            if (offset > 0 && offset < pasoPagina / numColumns) {
              relaSide = 1
            }
            if (offset > pasoPagina / numColumns && offset < pasoPagina) {
              relaSide = 2
            }

            if (relaSide > 0) {
              ////Condicion para que subraye todo el libro
              //if (pestaSide==relaSide) {//Condicion para que subraye solo las relaciones del lado de la pestana izq o der

              //De momento solo trabaja con relaciones de imagenes (tipo 6) que no subrayan
              if ($(this).hasClass("nosub_tipo_6")) {
                relasNOSub++
              }
              relasToShow.push(this)
            } else {
              //Limpia las relaciones que no quedan en el lado de la pestana seleccionada
              $(this).replaceWith(this.innerHTML)
            }
          })

          if (relasToShow.length == relasNOSub) {
            //Abre inmediatamente las relaciones que no subrayam
            openRelaciones(pestana, relasToShow, position)
          } else {
            $.each(relasToShow, function () {
              $(this).css({ "background-color": bgcolor, cursor: "pointer" })
              this.onclick = function (_ev) {
                _ev.stopPropagation()
                openRelaciones(pestana, [this], position)
              }
            })
            t.activated = true
          }
        }
      }

      function openRelaciones(pestana, _subrayados, position) {
        //Imagenes (e Imagenes de concurso)
        if (pestana.hasClass("book_pesta_imagenes")) {
          var ids = []
          var ids6 = buscaIDsRelaciones(_subrayados, "tipo_6", /6_\d+/g)
          var ids31 = buscaIDsRelaciones(_subrayados, "tipo_31", /31_\d+/g)

          if (ids6 != null && ids6.length > 0) {
            ids = ids.concat(ids6)
          }
          if (ids31 != null && ids31.length > 0) {
            ids = ids.concat(ids31)
          }

          that.offCompruebaTecla()
          viewerTxtServices.showImages(ids)
        }

        //Notas
        if (pestana.hasClass("book_pesta_notas")) {
          var ids = []
          var ids5 = buscaIDsRelaciones(_subrayados, "tipo_5", /\b5_\d+/g)
          var ids15 = buscaIDsRelaciones(_subrayados, "tipo_15", /\b15_\d+/g)
          var ids27 = buscaIDsRelaciones(_subrayados, "tipo_27", /\b27_\d+/g)

          if (ids5 != null && ids5.length > 0) {
            ids = ids.concat(ids5)
          }
          if (ids15 != null && ids15.length > 0) {
            ids = ids.concat(ids15)
          }
          if (ids27 != null && ids27.length > 0) {
            ids = ids.concat(ids27)
          }

          showNotas(ids, position, _subrayados)
        }

        //..
      }

      function showComentarios(_comentsIds, _position, _subrayados) {
        if (_comentsIds.length == 0) {
          return null
        }

        var extraParams = {}
        jQuery.extend(extraParams, settings.SycCredentials)

        //Solo envia el primer ID
        _comentsIds = _comentsIds[0].replace(/5_/g, "")

        ltotalOS
          .loadData("comentarios", "comentario", _comentsIds, extraParams)
          .done(function (htmlData) {
            var proc = $("#proceso").html(htmlData)
            var coments = $(".comment_credito", proc).detach()

            proc.html(coments)

            coments.each(function () {
              var coment = $(this)
              viewerTxtServices.cleanExcessHTML(coment)

              $("style", coment).remove()

              $(".coment_autor", coment).css({ color: "" })

              $("p", coment).each(function () {
                var ctxt = $.trim($(this).text())
                var imgs = $("img", this)
                if (ctxt.length == 0 && imgs.length == 0) {
                  $(this).addClass("remove")
                }
              })

              $(".remove", coment).remove()

              $("div.Section1", coment).replaceWith(function () {
                return this.innerHTML
              })
            })

            showFloatTextViewer(coments, _position)
            var $cont = $(floatTextViewer.getContain())

            if (!isTouch && $(that.boxViewerTXT).hasClass("dark")) {
              $cont.addClass("dark")
            }
          })
      }

      function showNumberComments(_elem, _position) {
        $(_elem).addClass("number_comment")
        _elem.onclick = function (_ev) {
          var relas = [_elem]
          var ids = buscaIDsRelaciones(relas, "tipo_5", /5_\d+/g)
          showComentarios(ids, _position, relas)
        }
      }

      function showNotas(_notasIds, _position, _subrayados) {
        if (_notasIds.length == 0) {
          return null
        }

        //Solo envia el primer ID
        var notaId = _notasIds[0]

        if (notaId.match(/\b5_\d+/)) {
          return showComentarios(_notasIds, _position, _subrayados)
        }
        if (notaId.match(/\b27_\d+/)) {
          notaId = notaId.replace("27_", "17_")
        }

        if (notaId == "17_0") {
          return null
        }

        var extraParams = {}
        jQuery.extend(extraParams, settings.SycCredentials)

        ltotalOS
          .loadData("notas", "nota", notaId, extraParams)
          .done(function (htmlData) {
            var proc = $("#proceso").html(htmlData)

            $(".txt_ref_pag", proc).parent().remove()
            $(".tituloMarker", proc).remove()

            var toolEdit = $(".tool_edit", proc)
            var toolDelete = $(".tool_delete", proc)
            var msgCorreccion = $(".msg_correccion", proc)
            var novedad_mark = $(".novedad_mark", proc).detach()[0]

            toolEdit.click(function () {
              $(".closeModalViewer", floatTextViewer.mainFrame).trigger("click")

              var id_nota = notaId.replace("17_", "")
              panelSocial.editNote(17, id_nota, null)
            })
            toolDelete.click(function () {
              var id_nota = notaId.replace("17_", "")
              panelSocial.deleteNote(17, id_nota, function () {
                $(".closeModalViewer", floatTextViewer.mainFrame).trigger(
                  "click"
                )

                var sb = panelSocial.selectedBook
                if (sb) {
                  sb.bookReset()
                }
              })
            })

            //Elimina parrafos vacios
            $("p", proc)
              .filter(function () {
                if ($.trim($(this).text()).length == 0) {
                  return true
                }
              })
              .remove()

            $(".mceNonEditable", proc).each(function () {
              viewerTxtServices.generateDiamond(this, that.boxViewerTXT)
            })

            var citaNota = $(".cita_nota", proc)
            citaNota.replaceWith(
              '<div class="cita_nota">' + citaNota.html() + "</div>"
            )

            showFloatTextViewer(proc.children(), _position)

            var cont = floatTextViewer.getContain()
            var $cont = $(cont)

            if (!isTouch && $(that.boxViewerTXT).hasClass("dark")) {
              $cont.addClass("dark")
            }

            var repaintThis = function () {
              if (isTouch) {
                $cont.css({ height: "100%", width: "100%", top: 0, left: 0 })
              } else {
                $cont.css({
                  width: "40%",
                  left: "30%",
                  height: that.boxViewerTXT.offsetHeight - 20,
                  top: "0px",
                  "border-radius": "15px",
                })
              }
            }

            $(".toolsTop", cont).append([toolEdit, toolDelete])
            $(".toolsFooter", cont).append(msgCorreccion)

            if (isTouch) {
              panelSocial.mosaico.openObjectInTab(
                floatTextViewer,
                "Nota del lector"
              )
            }

            floatTextViewer.rePaintFunc = function () {
              repaintThis()
            }

            $(".closeModalViewer", cont)[0].onclick = function () {
              floatTextViewer.cerrar()
              floatTextViewer.rePaintFunc = null

              if (isTouch) {
                panelSocial.mosaico.destroyTab(floatTextViewer.idTab)
              }
            }

            repaintThis()

            if (novedad_mark) {
              //Borra la notificacion
              settings.fnDiscountNovedad(settings.tipoLibro, settings.idLibro)

              //NOTA!! Esta codigo se utilizaria para borrar notificaciones en otras situaciones de navegacion
              var cl = novedad_mark.className
              var tipo_notifica = cl
                .match(/tipo_notifica_\d+/)[0]
                .match(/\d+/)[0]
              var id_item = cl.match(/id_item_\d+/)[0].match(/\d+/)[0]
              var params = {
                caso: 23,
                tipo_notifica: tipo_notifica,
                id_item: id_item,
              }
              doPost(params)
            }
          })
      }

      function showFloatTextViewer(_content, _position) {
        var modalConfig = {
          hostDiv: isTouch ? bodyDiv : that.boxViewerTXT,
          topV: "20px",
          widthV: "30%",
          heightV: "500px",
          scrollSyc: isTouch ? false : true,
          claseScroll: "scrollModalNotas",
          draggable: false,
          clickClose: function () {
            $(that.boxViewerTXT).css({ display: "" })
          },
        }
        if (_position == "left") {
          modalConfig.rightV = "15%"
        }
        if (_position == "right") {
          modalConfig.leftV = "15%"
        }
        if (_position == "center") {
          modalConfig.leftV = "35%"
        }

        if (floatTextViewer) {
          floatTextViewer.destroy()
        }

        floatTextViewer = new ModalViewerLT(modalConfig)

        floatTextViewer.addContent(_content)

        if (isTouch) {
          var cont = floatTextViewer.getContain()
          $(cont).css({
            width: "100%",
            left: "0",
            top: "0",
            right: "",
            height: "100%",
            "border-radius": "0",
            border: "none",
          })

          var book_dec = $(".book_dedicatoria", cont)[0]
          if (!book_dec) {
            //Oculta el libro cuando se abre el modal excepto en dedicatoria
            $(that.boxViewerTXT).css({ display: "none" })
          }
        }

        return floatTextViewer
      }

      this.showFloatTextViewer = function (_content, _position) {
        return showFloatTextViewer(_content, _position)
      }

      this.getFloatTextViewer = function () {
        return floatTextViewer
      }

      this.changePlaybackRate = function (rate) {
        changePlaybackRate(rate)
      }

      function changePlaybackRate(rate) {
        globito.speedHasChanged =
          rate != globito.playbackRate && globito.hasPlaybackRate
        audio.playbackRate = rate
        globito.playbackRate = rate
        ltotalOS.setPreferenciaLector("audiolibroPlaybackRate", rate)
      }

      this.showAudiolibroTools = function (_extra) {
        showAudiolibroTools(_extra)
      }

      function showPlaybackSpeedMenu(_boxBottomMenu) {
        var toggleMenu = function (_menu) {
          if (_menu.is(":visible")) {
            _menu.css({ display: "none" })
          } else {
            _menu.css({ display: "inline-flex" })
          }
        }

        var configurePlaybackRate = function (_parent) {
          var html =
            '\
          <div class="speed_menu box_audio_tools">\
              <div class="speed_0_8x 0.8 playback_speed">0.8x</div>\
              <div class="speed_1_0x 1 playback_speed">1.0x</div>\
              <div class="speed_1_2x 1.2 playback_speed">1.2x</div>\
              <div class="speed_1_5x 1.5 playback_speed">1.5x</div>\
              <div class="speed_2_0x 2 playback_speed">2.0x</div>\
          </div>\
          '

          $(".contentBox", _parent).prepend(html)

          var speedMenu = $(".speed_menu.box_audio_tools", _parent)
          var playerSpeed = { 0.8: 0.8, 1: 1, 1.2: 1.2, 1.5: 1.5, 2: 2 }

          var speedBtns = $(".playback_speed", speedMenu)
          speedBtns.each(function () {
            var $this = $(this)

            $this[0].onclick = function () {
              speedBtns.removeClass("speed_bg")
              changePlaybackRate(playerSpeed[$this[0].className.split(" ")[1]])
              $this.addClass("speed_bg")
              toggleMenu(speedMenu)
              boxGlobeOne()
            }

            if ($this.hasClass(globito.playbackRate)) {
              $this.addClass("speed_bg")
            }
          })
          speedMenu.css({ display: "none" })
          return speedMenu
        }

        var menu = $(".speed_menu.box_audio_tools", _boxBottomMenu)
        if (!menu[0]) {
          menu = configurePlaybackRate()
        }

        toggleMenu(menu)
      }

      function showAudiolibroTools(_extra) {
        var time = (_extra && _extra.time) || 200
        clearTimeout(boxBottomMenu.toolsFnTimer)

        var showAudioTools = function () {
          if (globito.hasPlaybackRate && isTouch) {
            if (
              $(boxBottomMenu).is(":visible") &&
              $(".box_audio_tools", boxBottomMenu)[0]
            ) {
              return $(".speed_menu", boxBottomMenu).css({ display: "none" })
            }

            var html =
              '\
              <div class="main_menu box_audio_tools">\
                  <div class="audio_tool time_tool"></div>\
                  <div class="audio_tool speed_tool"></div>\
                  <div class="audio_tool audio_number_page"></div>\
                  <div class="audio_tool speaker_tool"></div>\
              </div>\
              '

            panelSocial.showBottomMenu(html, null, null)

            $(".speed_tool", boxBottomMenu)[0].onclick = function () {
              showPlaybackSpeedMenu(boxBottomMenu)
              if (boxGlobePlaying) {
                boxGlobeOne()
              }
            }

            $(".speaker_tool", boxBottomMenu)[0].onclick = function () {
              boxGlobeOne()
            }
            $(".audio_number_page", boxBottomMenu).html(paginLeft.innerHTML)
          }
        }

        boxBottomMenu.toolsFnTimer = setTimeout(showAudioTools, time)
      }

      //Muestra el globo de audio
      function showAudiolectura() {
        var s = settings
        //Detiene la audiolectura
        var audio = $("audio", that.boxViewerTXT)[0]
        if (audio && modoPasoPagina == "manual") {
          $(".text_audio_lec", contentTxt).removeClass("text_audio_lec")
          audio.ruta = ""
          audio.pause()
          boxGlobePlaying = false
          clearTimeout(timerAudioLibro)
          playbackAt = 0
          stoppedAudio = false

          if (executeNativeAudio) {
            globito.startAudioNative = true
          }
        }

        //Asigna el primer elemento de la visual a nodeAudioLectura
        var nodosVisual = elementosEnVisual2
        currentAudioPgClass = null

        nodeAudioLectura = nodosVisual[0]
        nodeAudioLectura1 = nodosVisual[0]
        nodeAudioLectura2 = filterElementosEnVisual(
          null,
          elementosEnVisual2,
          2
        )[0]

        var pgAudio = null
        var audilecData = null

        $([
          boxGlobe_1,
          boxGlobe_2,
          labelLector1,
          labelLector2,
          boxSpeaker_1,
          boxSpeaker_2,
        ]).css({ display: "none" })
        clearTimeout(timerLabelLector)

        if (nodeAudioLectura1) {
          pgAudio = nodeAudioLectura1[0].className.match(/pg_\d+/)
          if (pgAudio) {
            pgAudio = pgAudio[0]
          }
          if (pgAudio) {
            audilecData = audiolecturas[s.modoLibro][pgAudio]
            if (audilecData) {
              rutaAudioLectura1 = audilecData[0]
              lectorAudioLectura1 = audilecData[2]

              if (pgAudio != "pg_1") {
                $([boxGlobe_1, boxSpeaker_1]).css({ display: "inline-block" })
              }

              if (isTouch) {
                if (isSmartPhone) {
                  $(boxGlobe_1).css({ left: "initial" })
                }
                $([boxGlobe_1, boxSpeaker_1]).css({ display: "inline-block" })
              }
              globeCtrols_1.setAttribute("class", "play globectrl book_icon")
            }
          }
        }

        if (nodeAudioLectura2) {
          pgAudio = nodeAudioLectura2[0].className.match(/pg_\d+/)
          if (pgAudio) {
            pgAudio = pgAudio[0]
          }
          if (pgAudio) {
            audilecData = audiolecturas[s.modoLibro][pgAudio]
            if (audilecData) {
              rutaAudioLectura2 = audilecData[0]
              lectorAudioLectura2 = audilecData[2]

              $([boxGlobe_2, boxSpeaker_2]).css({ display: "inline-block" })
              globeCtrols_2.setAttribute("class", "play globectrl book_icon")
            }
          }
        }

        if (
          boxGlobePlaying &&
          (currentAudioSide === 1 || currentAudioSide === 2)
        ) {
          currentAudioSide = 1
          globeCtrols_1.setAttribute("class", "pause globectrl book_icon")
          globeCtrols_2.setAttribute("class", "play globectrl book_icon")
        }
      }

      //Muestra la fecha de ultima lectura
      function showFechaLectura() {
        var _txt = settings.fechaVisto
        var doIT = _txt && !settings.idFotoPromo && settings.modoLibro == 1
        if (doIT) {
          setTimeout(function () {
            var fechHTML =
              '<div class="last_lec" style="text-align:center;">脷ltima lectura,<br>' +
              _txt +
              "</div>"
            if (isTouch) {
              panelSocial.showBottomMenu(fechHTML, 2000, that.boxViewerTXT)
            } else {
              showFloatTextViewer(fechHTML, "center")

              var cont = floatTextViewer.getContain()
              $(cont).addClass("ultima_lectura")
              $(cont).css({
                width: "20%",
                left: "40%",
                height: "120px",
                "background-color": "white",
              })

              setTimeout(function () {
                if (
                  $(floatTextViewer.getContain()).hasClass("ultima_lectura")
                ) {
                  floatTextViewer.destroy()
                }
              }, 2000)
            }
          }, 400)
        }
        settings.fechaVisto = false
      }

      function showDedicatoria(_txt) {
        if (_txt) {
          setTimeout(function () {
            showFloatTextViewer(
              '<div class="book_dedicatoria">' + _txt + "</div>",
              "center"
            )

            var cont = floatTextViewer.getContain()
            var $cont = $(cont)

            $cont.addClass("dedicatoria_regalo")
            $cont.css({ "background-color": "#FFFFFF" })

            $(".book_dedicatoria", cont).prepend(
              '<div class="gift_icon"></div>'
            )
            $cont.prepend('<div class="backg_veil"></div>')

            if (isSmartPhone) {
              $cont.css({
                width: "100%",
                height: "100%",
                top: "0%",
                left: "0",
                "border-radius": "0",
              })
              $(".box_institut_promo", cont).css({ width: "110%", left: "-5%" })
              $(".backg_veil", cont).css({ "border-radius": "0" })
            } else {
              $cont.css({
                width: "",
                height: "",
                left: "",
                top: "",
                "border-radius": "",
              })
            }

            $(".closeModalViewer", cont).css({
              "background-size": "100% 100%",
              "background-position": "0",
            })
          }, 400)
        }
        settings.dedicatoria = false
      }

      function showNovedades() {
        var actualizarNovedad = function (_idPro, _idNota, _mark, _tipo) {
          if ($(_mark).hasClass("discount_nove")) {
            settings.fnDiscountNovedad(settings.tipoLibro, settings.idLibro)
          }

          //Conteos de lectura solo aplican cuando tipo == 1
          if (_tipo == 1) {
            var params = { caso: 14, id_nota: _idNota, id_pro: _idPro }
            doPost(params)
          }
        }

        var deleteNovedad = function (
          _idPro,
          _idNota,
          _ipParrafo,
          _tipo,
          _mark
        ) {
          if ($(_mark).hasClass("discount_nove")) {
            var params = {
              caso: 11,
              id_nota: _idNota,
              id_pro: _idPro,
              id_parr: _ipParrafo,
              tipo_novedad: _tipo,
            }
            doPost(params)
          }
        }

        var fns = {
          item_nota_proyecto: function (_mark, _id) {
            actualizarNovedad(_id[0], _id[1], _mark, 1)
          },
          item_parrafo_nota: function (_mark, _id, _clase) {
            var idParr = _clase.match(/pid_\d+/)[0]
            var id = _clase.match(/\d+_\d+/)[0].split("_")
            deleteNovedad(id[0], id[1], idParr, 2, _mark)
          },
          item_personaje: function (_mark, _id) {
            settings.fnDiscountNovedad(settings.tipoLibro, settings.idLibro)

            //Pone marca azul de novedad
            var header = $(_mark.previousElementSibling)
            header.css({ position: "relative" })
            header.append(
              '<div style="position: absolute; left: 0px; top: 0; width: 3px; height:100%; border-radius: 5px; background-color: rgba(43, 126, 182, 0.9);"></div>'
            )

            var params = { caso: 10, id_pesca: _id[0] }
            doPost(params)
          },
          item_credito_proyecto: function (_mark, _id, _clase) {
            var id_proy = _clase.match(/id_proy_\d+/)[0].match(/\d+/)[0]
            var id_lib_cred = _clase.match(/id_lib_cred_\d+/)[0].match(/\d+/)[0]
            var id_cred = _clase.match(/id_cred_\d+/)[0].match(/\d+/)[0]

            //Pone marca azul de novedad
            $(_mark)
              .prev()
              .append(
                '<div class="rec_novedad" style="left:inherit; right:-10px;">-</div>'
              )
            $(_mark).remove()

            var params = { caso: 24, idProyecto: id_proy, idCred: id_cred }
            doPost(params)
          },
        }

        var nove_marks = filterElementosEnVisual(
          "novedad_mark",
          elementosEnVisual1
        )
        $.each(nove_marks, function () {
          var mark = this[0]
          var clase = mark.className
          var tipo = clase.match(/item_\w+/)[0]
          var id = clase.match(/-?\d+(_\d+)?/)[0].split("_")

          if (!noveMarks[mark.className]) {
            fns[tipo](mark, id, clase)

            if ($(mark).hasClass("item_parrafo_nota")) {
              var height = 0
              var rects = mark.getClientRects()
              $.each(rects, function () {
                height += this.height
              })

              var div = document.createElement("div")
              div.className = "line_nove_mark"
              $(div).css({ height: height + "px" })
              $(mark).append(div)
            }
          }
          noveMarks[mark.className] = true
        })

        //Textos escritos por...
        var showIntervention = function () {
          var cred = this
          panelSocial.getIntervention(this).done(function (_data) {
            var nomb = _data[0]

            showFloatTextViewer(
              '<div style="text-align:center;">Escrito por,<br>' +
                nomb +
                "</div>",
              "center"
            )

            var cont = floatTextViewer.getContain()
            $(cont).css({ width: "20%", left: "40%", height: "120px" })

            //ID de diccionario configurado para las notas
            var idDicc = null
            var header = $(cred).parents("p").prevAll(".titulo_2")[0]
            if (header) {
              idDicc = header.idDicc
            }
            if (idDicc) {
              //Abre el diccionario y le pone el modal
              var dicc = panelSocial.openDiccTotal(
                $.trim($(cred).text()),
                idDicc,
                {
                  openInTab: true,
                  diccAlfabetico: true,
                  diccSYC: header.diccSYC,
                  publicDomain: header.diccPublicDomain,
                }
              )
              dicc.cont.append(cont)
              $(cont).css({
                width: "30%",
                height: "120px",
                top: "50px",
                left: "35%",
              })
              if (isSmartPhone) {
                $(cont).css({ width: "100%", left: "0" })
              }
            }
          })
        }

        var tipo = settings.tipoLibro
        var hayPermiso =
          (tipo == 32 && ltotalUtils.checkPermiso("personaje")) ||
          (tipo == 27 &&
            (ltotalUtils.checkPermiso("grupoinvestigacion") ||
              settings.isLectorInProyecto))
        if (hayPermiso && settings.modoLibro == 1) {
          $.each(children1, function () {
            llector.colorInterventions(this)
            $(".cred_lector", this).each(function () {
              this.onclick = showIntervention
            })
          })
        } else {
          if (tipo == 27 || tipo == 32) {
            $.each(children1, function () {
              $(".cred_lector", this).each(function () {
                removeClassRegex(/cred_lector(_\d+)?/g, this)
              })
            })
          }
        }
      }

      function showTools() {
        $(".tag_tool", containCredits).detach()
        $(".tag_tool", pagesCenter).detach()

        distributeIconsCenter()
        toolsCenter = []

        var headers = filterElementosEnVisual("titulo_2", elementosEnVisual1)
        $.each(headers, function () {
          var h = this[0]
          if (h.tools) {
            $.each(h.tools, function () {
              var rect = h.getClientRects()[0]
              var ratio = getVisualRatioRect(rect)
              if (ratio < 0.5) {
                if (ratio >= 0) {
                  $(this).addClass("izq")

                  if (isTouch) {
                    toolsCenter.push(this)
                  } else {
                    $(toolsIZQ).append(this)
                  }
                }
              } else {
                $(this).addClass("der")
                $(toolsDER).append(this)
              }
            })
          }
        })

        distributeIconsCenter()
      }

      function showPageEdition() {
        var s = settings

        if (!fwe || !fwe[0]) {
          return null
        }

        var edFw = $(".edicion_framework", fwe).detach()

        if (edFw[0]) {
          frameworkEdicion = edFw
        }

        var clazzName = "page_edition"
        var lIzq = filterElementosEnVisual(clazzName, elementosEnVisual2, 1)[0]
        var lDer = filterElementosEnVisual(clazzName, elementosEnVisual2, 2)[0]

        $(".edit_page", that.boxViewerTXT).remove()
        var sb = panelSocial.selectedBook

        var mb = sb.getContain()
        var extraParams = {
          tipoLibro: s.tipoLibro,
        }

        if (lIzq) {
          var pgI = getPgsToEdit([lIzq])
          var pgInit = 1

          if (s.tipoLibro == 27) {
            extraParams.idNota = parseInt(
              lIzq[0].className.match(/id_nota_\d+/)[0].match(/\d+/)[0]
            )
            extraParams.numSecc = parseInt(
              lIzq[0].className.match(/nsec_\d+/)[0].match(/\d+/)[0]
            )
            pgInit = 0
          }

          var toolIzq = $(
            '<div class="edit_page" title="Men煤 de edici贸n"></div>'
          )
          if (pgI > pgInit) {
            if (!isTouch) {
              $(toolIzq).addClass("pg_center_left")
            }
            toolsCenter.push(toolIzq[0])

            toolIzq[0].onclick = function () {
              if (s.tipoLibro == 27 && s.idLibro == 0) {
                if (
                  isTouch &&
                  settings.tipoLibro == 27 &&
                  settings.idLibro == 0
                ) {
                  that.cerrar()
                }
                panelSocial.editNote(17, extraParams.idNota, {
                  nsecc: extraParams.numSecc,
                })
              } else {
                var elemsIzq = filterElementosEnVisual(
                  clazzName,
                  elementosEnVisual2
                )
                var pgsIzq = viewerTxtServices.unique(getPgsToEdit(elemsIzq))
                edicion.libros.showEditMenu(
                  s.idLibro,
                  s.idTrad,
                  pgsIzq,
                  mb,
                  frameworkEdicion,
                  extraParams
                )
              }
            }
          }
        }

        if (lDer) {
          if (s.tipoLibro == 27) {
            extraParams.idNota = parseInt(
              lDer[0].className.match(/id_nota_\d+/)[0].match(/\d+/)[0]
            )
            extraParams.numSecc = parseInt(
              lDer[0].className.match(/nsec_\d+/)[0].match(/\d+/)[0]
            )
          }

          var toolDer = $(
            '<div class="edit_page" title="Men煤 de edici贸n"></div>'
          )
          if (!isTouch) {
            toolsCenter.push(toolDer[0])
          }

          toolDer[0].onclick = function () {
            if (s.tipoLibro == 27 && s.idLibro == 0) {
              if (
                isTouch &&
                settings.tipoLibro == 27 &&
                settings.idLibro == 0
              ) {
                that.cerrar()
              }
              panelSocial.editNote(17, extraParams.idNota, {
                nsecc: extraParams.numSecc,
              })
            } else {
              var elemsDer = filterElementosEnVisual(
                clazzName,
                elementosEnVisual2
              )
              var pgsDer = viewerTxtServices.unique(getPgsToEdit(elemsDer))
              edicion.libros.showEditMenu(
                s.idLibro,
                s.idTrad,
                pgsDer,
                mb,
                frameworkEdicion,
                extraParams
              )
            }
          }
        }

        distributeIconsCenter()
      }

      //Muestra si el libro es favorito
      function showFavorite() {
        var s = settings

        var btnsFavs = $([favoriteIzq, favoriteDer])

        var doShow = true
        if ((s.tipoLibro > 1 && s.idLibro <= 0) || s.modoLibro != 1) {
          doShow = false
        }
        if (s.isProhibido) {
          doShow = false
        }

        if (doShow) {
          btnsFavs.css({ display: "inline-block" })

          if (s.isFavoriteBook) {
            btnsFavs
              .addClass("favorite")
              .attr("title", "Eliminar este libro de  mis favoritos")
          } else {
            btnsFavs
              .removeClass("favorite")
              .attr("title", "Agregar este libro a mis favoritos")
          }
        } else {
          btnsFavs.css({ display: "none" })
        }
      }

      function doFavorite() {
        var s = settings
        if (!lectorLogueado) {
          return panelSocial.showAlertMiBiblioteca(44)
        }

        var idTrad = -1
        if (s.idTrad) {
          idTrad = s.idTrad
        }

        var tipoLibro = s.tipoLibro

        //Libros art铆culos (tipo 27 modo 2)
        if (tipoLibro == 27 && s.modoLibro == 2) {
          tipoLibro = 17
        }

        var estado = s.isFavoriteBook ? 0 : 1
        var saveFavorito = panelSocial.mosaico.saveFavorito(
          tipoLibro,
          s.idLibro,
          idTrad,
          estado
        )
        saveFavorito.done(function (d) {
          d = $.trim(d)
          if (d.indexOf("OK") > -1) {
            settings.isFavoriteBook = estado == 0 ? false : true
            settings.hasChangedFavorite = true
            showFavorite()
          }
        })
      }

      //Muestra si una pagina esta marcada
      function showBookMark() {
        var s = settings
        if (s.tipoLibro != 1 || s.modoLibro != 1) {
          return null
        }

        $(boxBookMark).removeClass("book_marked")

        var idTrad = -1
        if (s.idTrad) {
          idTrad = s.idTrad
        }

        var pgsIV = viewerTxtServices.unique(getPgsToEdit(elementosEnVisual2))
        var pgsBM = [],
          pgsBMP = []
        $.each(pgsIV, function (i, e) {
          var perData = getNumePaginaPercent("pg_" + e)
          var bmPPage = bookMarks[e]
          if (bmPPage) {
            for (var el in bmPPage) {
              var bmp = parseFloat(bmPPage[el][e])
              if (bmp >= perData[0] && bmp < perData[1]) {
                pgsBM.push(e)
                pgsBMP.push(bmp)
              }
            }
          }
        })

        if (pgsBM.length > 0) {
          $(boxBookMark).addClass("book_marked").fadeIn(0)
        }

        var doBookMark = function () {
          if (!lectorLogueado) {
            return panelSocial.showAlertMiBiblioteca(48)
          }
          var tipoLibro = s.tipoLibro

          var estado = pgsBM.length > 0 ? 0 : 1
          if (estado == 1) {
            var pgFin = pgsIV[0]
            var percent = getNumePaginaPercent("pg_" + pgFin)[0]
            pgsBM.push(pgFin)
            pgsBMP.push(percent)
          }

          panelSocial.mosaico
            .saveBookMark(
              tipoLibro,
              s.idLibro,
              idTrad,
              pgsBM.join(","),
              pgsBMP.join(","),
              estado
            )
            .done(function (_d) {
              _d = $.trim(_d)
              if (_d.indexOf("OK") > -1) {
                if (estado == 0) {
                  for (var i = 0; i < pgsBM.length; i++) {
                    var apgsBM = bookMarks[pgsBM[i]]
                    if (apgsBM) {
                      var rpgsBM = $.grep(apgsBM, function (e) {
                        return e[pgsBM[i]] != pgsBMP[i]
                      })
                      bookMarks[pgsBM[i]] = rpgsBM
                    }
                  }
                } else {
                  if (!bookMarks[pgsBM[0]]) {
                    bookMarks[pgsBM[0]] = []
                  }
                  var objBM = {}
                  objBM[pgsBM[0]] = pgsBMP[0]
                  bookMarks[pgsBM[0]].push(objBM)
                }
                showBookMark()
              }
            })
        }

        boxBookMark.onclick = doBookMark
      }

      function showDiamond() {
        var elems = []

        if (settings.modoLibro == 1) {
          $.each(elementosEnVisual2, function () {
            var este = this[0]
            if ($(este).hasClass("mceNonEditable")) {
              elems.push(este)
            }
            $(".mceNonEditable", este).each(function () {
              elems.push(this)
            })
          })

          $.each(elems, function () {
            viewerTxtServices.generateDiamond(this, that.boxViewerTXT)
          })
        }

        //NOTA!!
        //Este modelo se puede usar para las relaciones textuales
        //Imagenes, notas, etc - Reemplaza pestanas
      }

      function showReadingProgress() {
        var pgFin = 0
        var idItemFin = 0
        var modoLectura =
          settings.modoLibro == 1 ||
          (settings.tipoLibro == 27 && settings.modoLibro == 2)

        if (nodeAudioLectura && modoLectura) {
          var np = nodeAudioLectura[0].className.match(/pg_\d+/)

          var numePaginaPercent = 0.0
          var isProOrPer = settings.tipoLibro == 27 || settings.tipoLibro == 32

          if (settings.tipoLibro == 1 || isProOrPer) {
            if (np) {
              pgFin = parseInt(np[0].match(/\d+/)[0])
              numePaginaPercent = getNumePaginaPercent(np[0])[0]
              settings.numePaginaPercent = numePaginaPercent
              settings.pgFin = pgFin
            }

            if (isProOrPer) {
              var strItemFin =
                nodeAudioLectura[0].className.match(/id_(nota|rec)_\d+/)
              if (strItemFin) {
                idItemFin = parseInt(strItemFin[0].match(/\d+/)[0])
              }
              settings.idItemFin = idItemFin
            }
          }
        }

        if (typeof settings.fnReadingProgress == "function") {
          settings.fnReadingProgress(pgFin, idItemFin, settings)
        }
      }

      function getPgsToEdit(elemts) {
        var pgs = []
        $.each(elemts, function () {
          var pg = this[0].className.match(/pg_\d+/)
          if (pg) {
            pg = parseInt(pg[0].match(/\d+/)[0])
            pgs.push(pg)
          }
        })
        return pgs
      }

      //Muestra pestana de versions
      function showVersions() {
        var firstElem = elementosEnVisual2[0]
        var pg = 1

        if (firstElem) {
          pg = parseInt(
            firstElem[0].className.match(/pg_\d+/)[0].match(/\d+/)[0]
          )
        }

        var extraParams = {}
        if (tradsData) {
          extraParams = {
            versionsData: tradsData,
            pg: pg,
          }
        }
        settings.fnOpenBookComparado(
          settings.idLibro,
          settings.idTrad,
          extraParams
        )
      }

      function showBookCredits(_sleep) {
        clearTimeout(timerShowBookCredits)

        if (
          isTouch &&
          settings.modoLibro == 1 &&
          !$(boxSearch).hasClass("activated")
        ) {
          timerShowBookCredits = setTimeout(function () {
            if ($(pagesCenter).is(":visible")) {
              var elemsOut = [
                pagesCenter,
                pagesBottom,
                boxPagesSeekSlider,
                labelLector1,
              ]
              $(elemsOut).fadeOut(0)

              $(containCredits).fadeIn(600)
            } else {
              var elemsIn = [pagesCenter, pagesBottom, boxPagesSeekSlider]
              $(elemsIn).fadeIn(600)

              $([containCredits, labelLector1]).fadeOut(0)

              distributeIcons()
              showNumbersPages()
            }
          }, _sleep)
        }
      }

      function activeTool() {
        var $this = $(this)
        var id = this.className.match(/\d+_-?\d+/)[0].split("_")
        var nsecc = this.className.match(/nsec_\d+/)[0].match(/\d+/)[0]
        var isLastSecc = $this.hasClass("last_sec") ? true : false

        var extra = {
          viewerText: that,
          nsecc: nsecc,
          position: $this.hasClass("izq") ? 1 : 2,
        }

        if ($this.hasClass("tool_create")) {
          var idProy = this.className.match(/id_proy_\d+/)[0].match(/\d+/)[0]
          extra.idProy = idProy
          extra.toBook = false
          panelSocial.editNote(id[0], 0, extra)
        }

        if ($this.hasClass("tool_edit")) {
          if (isTouch && settings.tipoLibro == 27 && settings.idLibro == 0) {
            that.cerrar()
          }
          panelSocial.editNote(id[0], id[1], extra)
        }

        if ($this.hasClass("tool_send")) {
          panelSocial.sendNote(id[1])
        }

        if ($this.hasClass("tool_img")) {
          panelSocial.selectedBook.bookData.idNotaItem = id[1]
          extra.ocultarFormCapas = true
          edicion.imagenes.showLoad(0, extra)
        }

        if ($this.hasClass("tool_discard")) {
          var idProy = this.className.match(/id_proy_\d+/)[0].match(/\d+/)[0]
          panelSocial.discardNote(idProy, id[1])
        }

        if ($this.hasClass("tool_delete")) {
          panelSocial.deleteNote(id[0], id[1], function () {
            if (isLastSecc) {
              nsecc--
            }
            bookGotoSection(nsecc)
          })
        }

        if ($this.hasClass("tool_publish")) {
          var params = { caso: 2, tipoNota: id[0], idNota: id[1], estado: 1 }
          doPost(params).done(function () {
            bookGotoSection(nsecc)
          })
        }

        if ($this.hasClass("tool_unpublish")) {
          var params = { caso: 2, tipoNota: id[0], idNota: id[1], estado: 0 }
          doPost(params).done(function () {
            bookGotoSection(nsecc)
          })
        }

        if ($this.hasClass("tool_edit_profile_concurso")) {
          concurso.showEditRegister()
        }
      }

      function getFuncStack(_fn) {
        var callStack = []
        try {
          var c = _fn.caller
          while (c) {
            callStack.push(c.name)
            c = c.caller
          }
        } catch (e) {}
        return callStack.join()
      }

      function stepBackIdxsFirstNodoVisual() {
        if (settings.modoLibro == 1) {
          idxFirstNodoVisual1 = idxFirstNodoVisual1 - 100
          idxFirstNodoVisual2 = idxFirstNodoVisual2 - 200
          if (idxFirstNodoVisual1 < 0) {
            idxFirstNodoVisual1 = 0
          }
          if (idxFirstNodoVisual2 < 0) {
            idxFirstNodoVisual2 = 0
          }
        }
      }

      function resetIdxsFirstNodoVisual() {
        idxFirstNodoVisual1 = 0
        idxFirstNodoVisual2 = 0
      }

      //Obtiene lo elementos que estan en la visual del libro
      function getElementosEnVisual(_classRef, children, side, _extra) {
        var elementsInVisual = []

        var rectBox = rectBoxViewer
        rectBox.halfWidth = rectBox.width / 2

        var wasInVisual = false
        var fidx = 0
        if (_extra && _extra.idxFirstNodo) {
          fidx = _extra.idxFirstNodo
        }
        if (fidx < 0) {
          fidx = 0
        }

        for (var i = fidx; i < children.length; i++) {
          var child = children[i]

          var rect = child.getBoundingClientRect()

          var p1 = rect.left - rectBox.left
          var p2 = p1 + rect.width

          var p1Inside = p1 > 0 && p1 < rectBox.width
          var p2Inside = p2 > 0 && p2 < rectBox.width

          var p1Left = p1 < 0
          var p2Rigth = p2 > rectBox.width

          if (side && side == 1) {
            //Calcula elementos en la visual pagina izquierda
            p1Inside = p1 >= 0 && p1 <= rectBox.halfWidth
            p2Inside = p2 >= 0 && p2 <= rectBox.halfWidth
            p2Rigth = p2 > rectBox.halfWidth
          }

          if (side && side == 2) {
            //Calcula elementos en la visual pagina derecha
            p1Inside = p1 >= rectBox.halfWidth && p1 < rectBox.width
            p2Inside = p2 >= rectBox.halfWidth && p2 < rectBox.width
            p1Left = p1 < rectBox.halfWidth
          }

          var isInVisual = false
          if (p1Inside || p2Inside || (p1Left && p2Rigth)) {
            if (_classRef === -1) {
              elementsInVisual.push([child, p1, i - 1])
            } else {
              if (hasClassRegex(/pg_\d+/, child)) {
                //Este caso se usa para la audiolectura
                if (_classRef != null) {
                  if ($(child).hasClass(_classRef)) {
                    elementsInVisual.push([child, p1, i - 1])
                  }
                } else {
                  elementsInVisual.push([child, p1, i - 1])
                }
              } else {
                //Este caso se usa para capturar nodos en general en la visual
                if ($(child).hasClass(_classRef)) {
                  elementsInVisual.push([child, p1, i - 1])
                }
              }
            }
            isInVisual = true
            wasInVisual = true
          }

          if (wasInVisual && !isInVisual) {
            break
          }
        }
        return elementsInVisual
      }

      this.getElementosEnVisual = function (clase) {
        var children = $(contentTxt).children().children()
        return getElementosEnVisual(clase, children)
      }

      function filterElementosEnVisual(_classRef, _elems, side) {
        var f = []
        $.each(_elems, function () {
          var visElem = this

          if (side) {
            var rectBox = rectBoxViewer
            rectBox.halfWidth = rectBox.width / 2

            var rect = visElem[0].getBoundingClientRect()

            var p1 = rect.left - rectBox.left
            var p2 = p1 + rect.width

            var p1Inside = p1 > 0 && p1 < rectBox.width
            var p2Inside = p2 > 0 && p2 < rectBox.width

            var p1Left = p1 < 0
            var p2Rigth = p2 > rectBox.width

            if (side == 1) {
              //Calcula elementos en la visual pagina izquierda
              p1Inside = p1 >= 0 && p1 <= rectBox.halfWidth
              p2Inside = p2 >= 0 && p2 <= rectBox.halfWidth
              p2Rigth = p2 > rectBox.halfWidth
            }

            if (side == 2) {
              //Calcula elementos en la visual pagina derecha
              p1Inside = p1 >= rectBox.halfWidth && p1 < rectBox.width
              p2Inside = p2 >= rectBox.halfWidth && p2 < rectBox.width
              p1Left = p1 < rectBox.halfWidth
            }

            if (p1Inside || p2Inside || (p1Left && p2Rigth)) {
              if (_classRef != null) {
                if ($(visElem[0]).hasClass(_classRef)) {
                  f.push(visElem)
                }
              } else {
                f.push(visElem)
              }
            }
          }

          if (!side && _classRef != null && $(visElem[0]).hasClass(_classRef)) {
            f.push(visElem)
          }
        })
        return f
      }

      this.filterElementosEnVisual = function (_classRef, _elems, side) {
        return filterElementosEnVisual(_classRef, _elems, side)
      }

      //Busca (a fuerza bruta) los IDs de las relaciones de un elemento de texto
      function buscaIDsRelaciones(elems, clase, regex) {
        var ids = []

        var concatIDs = function (_elem) {
          var id = _elem.className.match(regex)
          if (id) {
            ids = ids.concat(id)
          }
        }

        $.each(elems, function () {
          var elem = this

          concatIDs(elem)

          //IDs de hijos del elemento
          $(elem)
            .find("." + clase)
            .each(function () {
              concatIDs(this)
            })

          var papas = $(elem).parents("." + clase)
          if (papas.length > 0) {
            papas.each(function () {
              //IDs de papas del elemento
              concatIDs(this)

              //IDs de hijos de papas del elemento
              $(this)
                .find("." + clase)
                .each(function () {
                  concatIDs(this)
                })
            })
          }
        })

        return viewerTxtServices.unique(ids)
      }

      function compruebaTecla(e) {
        var keyCode = document.all ? e.which : e.keyCode
        if (keyCode == 39) {
          if ($(nextArrow).is(":visible")) {
            if (areInputsFocused()) {
              return null
            }
            clearAudioData()
            nextPage()
          }
        }
        if (keyCode == 37) {
          if ($(backArrow).is(":visible")) {
            if (areInputsFocused()) {
              return null
            }
            clearAudioData()
            backPage()
          }
        }
      }

      this.onCompruebaTecla = function () {
        $(window).on("keydown", compruebaTecla)
      }

      this.offCompruebaTecla = function () {
        $(window).off("keydown", compruebaTecla)
      }

      function doPost(params) {
        var url = "/ltotal/lector/editNota.jsp"
        return panelSocial.doPost(url, params)
      }

      function areInputsFocused() {
        var r = false
        if ($(boxSearchInput).is(":focus")) {
          r = true
        }
        if (
          typeof edicion !== "undefined" &&
          edicion.libros.isEditorFocused()
        ) {
          r = true
        }
        if (typeof fisher !== "undefined" && fisher.isEditorFocused()) {
          r = true
        }
        if (typeof llector !== "undefined" && llector.isEditorFocused()) {
          r = true
        }
        return r
      }

      this.bookReset = function () {
        bookGotoPage(settings.pgFin, null, settings.numePaginaPercent)
      }

      this.showHighlightPage = function (_npag, _bgColor) {
        var clazzName = "pg_" + _npag
        var shhl = filterElementosEnVisual(clazzName, elementosEnVisual2)

        $.each(shhl, function () {
          var hl = this[0]
          elementosParaEdicion.push(hl)
          $(hl).css({ "background-color": _bgColor, cursor: "pointer" })
          hl.addEventListener("click", that.clickHighlightPage, false)
        })
      }

      this.removeHighlightPage = function () {
        $.each(elementosParaEdicion, function (i, e) {
          $(e).css({ "background-color": "", cursor: "" })
          e.removeEventListener("click", that.clickHighlightPage, false)
        })
        elementosParaEdicion = []
      }

      this.clickHighlightPage = function () {
        var s = settings
        var pg = this.className.match(/pg_\d+/)[0].match(/\d+/)[0]

        if (s.tipoLibro == 1) {
          edicion.libros.instanceContinuousEditor(
            s.idLibro,
            s.idTrad,
            pg,
            "modificar",
            0,
            true
          )
        }
        if (s.tipoLibro == 27) {
          var idNota = parseInt(
            this.className.match(/id_nota_\d+/)[0].match(/\d+/)[0]
          )
          var numSecc = parseInt(
            this.className.match(/nsec_\d+/)[0].match(/\d+/)[0]
          )

          var config = {
            viewerText: that,
            npagNote: pg,
            nsecc: numSecc,
          }
          panelSocial.editNote(17, idNota, config)
        }
      }

      this.bookGotoPage = function (_npag, _idxTitle, _numePaginaPercent) {
        return bookGotoPage(_npag, _idxTitle, _numePaginaPercent)
      }

      this.bookGotoPageUI = function (_npag, _idxTitle) {
        return bookGotoPageUI(_npag, _idxTitle)
      }

      this.bookGotoSection = function (_nsec, _npagsec, _extra) {
        return bookGotoSection(_nsec, _npagsec, _extra)
      }

      this.goToPageAndHighLightNote = function (_nPag, _idNote) {
        return goToPageAndHighLightNote(_nPag, _idNote)
      }

      this.showPestanasRelaciones = function () {
        showPestanasRelaciones()
      }

      this.loadBookInfo = function () {
        loadBookInfo()
      }

      that.onCompruebaTecla()

      this.themeClasic = function () {
        $(closeOld).css("display", "block")
        $(this.boxViewerTXT).addClass("classic")
        closeOld.onclick = that.cerrar

        $(pagesRight).append(nextArrow)
        $(pagesLeft).append(backArrow)
      }

      this.themeLight = function () {
        $(that.boxViewerTXT).append([nextArrow, backArrow])
      }

      this.getContain = function () {
        return this.boxViewerTXT
      }

      this.getDivContent = function () {
        return contentTxt
      }

      this.active = function (active) {
        if (active == "true") {
          $(that.boxViewerTXT).addClass("active")
        } else {
          $(that.boxViewerTXT).removeClass("active")
        }
      }

      this.show = function () {
        $(that.boxViewerTXT).fadeTo(200, 1)
        $(that.boxViewerTXT).addClass("active")
      }

      this.hide = function () {
        $(that.boxViewerTXT).fadeTo(200, 0)
        $(that.boxViewerTXT).removeClass("active")
      }

      function doCerrar() {
        clearAudioData()

        if (settings.backSearchIndex) {
          goToSearchIndex(false)
          settings.backSearchIndex = false
          $([toolsIZQ, pestaIndex]).css({ display: "none" })
          return
        }

        //Indice de libros o de busqueda de texto
        var enIndice = settings.modoLibro == 0 || settings.modoLibro == 3
        if (enIndice && contentTxtBK && contentTxtBK.children().length > 0) {
          bookVolver()
          return
        }

        $(that.getContain()).remove()
        that.offCompruebaTecla()

        if (floatTextViewer) {
          floatTextViewer.destroy()
        }

        if (typeof settings.fnClose == "function") {
          settings.fnClose(settings)
        }
      }

      function distributeIcons() {
        var modoIndice = settings.modoLibro == 0
        var modoLectura = settings.modoLibro == 1
        var modoArticulo = settings.tipoLibro == 27 && settings.modoLibro == 2
        var buscadorActivado = $(boxSearch).hasClass("activated")

        if (tradsData && modoLectura) {
          $(pestaTrads).css({ display: "" })
        } else {
          $(pestaTrads).css({ display: "none" })
        }

        if (isTouch) {
          $(pagesBottom).append(boxSpeaker_1)
        } else {
          $(pagesBottom).append(boxSpeaker_2)
        }

        $(that.boxViewerTXT).append(boxGlobe_1)
        if (!isTouch && modoLectura) {
          $(that.boxViewerTXT).append([boxGlobe_2, labelLector2])
        }

        if ((modoLectura || modoIndice || modoArticulo) && !buscadorActivado) {
          distributeIconsCenter()
          distributeIconsBottom()
        }
      }

      function distributeIconsCenter() {
        var cont = pagesCenter

        var buscadorActivado = $(boxSearch).hasClass("activated")
        if (buscadorActivado) {
          return null
        }

        if (pagesCenter.offsetWidth == 0) {
          return setTimeout(distributeIconsCenter, 1000)
        }

        if (
          $(pagesCenter).hasClass("multi_index") ||
          !$(pagesCenter).is(":visible")
        ) {
          return null
        }

        if (!isTouch) {
          var leftGlobo = that.boxViewerTXT.offsetWidth * 0.06
          var pdrTxt = parseInt($(contentTxt).css("padding-right"))
          var wPgCenter = that.boxViewerTXT.offsetWidth - leftGlobo - pdrTxt
          $(pagesCenter).css({
            width: wPgCenter + "px",
            left: leftGlobo + "px",
          })
        }

        var modoLectura = settings.modoLibro == 1
        var modoBusqueda = settings.modoLibro == 3
        var tipoLibro = settings.tipoLibro == 1
        var tipoInves = settings.tipoLibro == 27
        var tipoPerso = settings.tipoLibro == 32
        var modoArticulo = settings.tipoLibro == 27 && settings.modoLibro == 2

        var iconos = isTouch ? [closeLight] : [closeOld, boxSpeaker_1]

        if (tipoLibro && modoLectura) {
          iconos.push(boxBookMark)
        }
        if ((tipoLibro || tipoPerso) && modoLectura) {
          iconos.push(favoriteIzq)
        }
        if (modoLectura) {
          iconos.push(boxToolTextLook1)
          iconos.push(pestaIndex)
        }
        if (
          settings.idLibro > 0 &&
          modoLectura &&
          (tipoLibro || tipoInves || tipoPerso)
        ) {
          $(that.boxViewerTXT).append(boxSearchContainer)
          iconos.splice(2, 0, boxSearch)
        }
        if (!tipoLibro && modoLectura) {
          iconos.splice(iconos.length - 1, 0, boxShareGift)
        }
        /*
      if (modoArticulo) {
          iconos.push(boxShareGift);
      }
      */
        if (toolsCenter && toolsCenter.length > 0) {
          $.each(toolsCenter, function () {
            iconos.splice(1, 0, this)
          })
        }

        iconos = $(iconos).addClass("book_icon")
        $(cont).empty()
        $(cont).append(iconos)

        var pd = parseInt($(cont).css("padding-left"))
        var cw = cont.offsetWidth - pd * 2

        var procIcons = function (_wi, _hi, _ri) {
          var leftIni = pd
          var riacc = leftIni + _wi
          var riaccLeft = leftIni + _wi
          var firstPos = true
          var firstPosLeft = true

          iconos.each(function (i, e) {
            var $this = $(this)
            var disp = $(this).is(":visible") ? "block" : "none"
            $this.css({
              position: "absolute",
              display: disp,
              top: "0",
              width: _wi + "px",
              height: _hi + "px",
              margin: "0",
              cursor: "pointer",
              left: "",
              right: "",
            })

            if ($this.hasClass("pg_center_left")) {
              if (firstPosLeft) {
                $this.css({ left: leftIni + "px" })
                firstPosLeft = false
                riaccLeft += _ri
              } else {
                $this.css({ left: riaccLeft + "px" })
                riaccLeft += _wi + _ri
              }
            } else {
              if (firstPos) {
                $this.css({ right: leftIni + "px" })
                firstPos = false
                riacc += _ri
              } else {
                $this.css({ right: riacc + "px" })
                riacc += _wi + _ri
              }
            }
          })

          var th = riacc - _ri
          if (th >= cw) {
            procIcons(_wi, _hi, _ri * 0.8)
          }
        }

        var wi = isTouch ? 25 : 22
        var hi = wi / 1.25
        var ri = wi * 1.3
        procIcons(wi, hi, ri)

        //Ajusta el contenedor de los encabezados de las paginas para poder hacerles click
        if (!isTouch) {
          $(that.boxViewerTXT).append(pagesCenter)
          $(containCredits).css({ left: "5%", width: "90%" })

          if (tipoLibro == 1 && (modoLectura || modoBusqueda)) {
            $(that.boxViewerTXT).append(containCredits)

            var lastLeft = $(".pg_center_left", pagesCenter)
              .filter(function () {
                return $(this).is(":visible")
              })
              .last()[0]
            if (lastLeft) {
              var ccLeft =
                lastLeft.offsetLeft +
                lastLeft.offsetWidth +
                pagesCenter.offsetLeft
              var ccWidth =
                that.boxViewerTXT.offsetWidth / 2 -
                ccLeft -
                lastLeft.offsetLeft -
                20
              $(containCredits).css({
                left: ccLeft + "px",
                width: ccWidth + "px",
              })

              $(".labelTop", containCredits).css({ left: "0", width: "100%" })
            }
          }
        }
      }

      function distributeIconsBottom() {
        var cont = pagesBottom
        var iconos = $(cont).children().addClass("book_icon")

        if (pagesBottom.offsetWidth == 0) {
          return setTimeout(distributeIconsBottom, 1000)
        }

        if (!isTouch) {
          var pdr = parseInt($(contentTxt).css("padding-right"))
          $(pagesBottom).css({ width: columnSize + "px", right: pdr + "px" })
        }

        var pd = parseInt($(cont).css("padding-left"))
        var cw = cont.offsetWidth - pd * 2
        var ni = iconos.length
        var wi = isTouch ? 25 : 22
        var hi = wi / 1.25
        var mg = cw - ni * wi
        var mi = isTouch ? mg / (ni - 1) : wi * 1.3

        var li = pd
        if (isTouch) {
          iconos.each(function () {
            $(this).css({
              left: li + "px",
              margin: "0",
              position: "absolute",
              width: wi + "px",
              height: hi + "px",
              cursor: "pointer",
              top: 0,
            })
            li += wi + mi
          })
        } else {
          $(iconos.get().reverse()).each(function () {
            if ($(this).is(":visible")) {
              $(this).css({
                right: li + "px",
                margin: "0",
                position: "absolute",
                width: wi + "px",
                height: hi + "px",
                cursor: "pointer",
                top: 0,
              })
              li += wi + mi
            }
          })
        }
      }

      function fixDiagramBugs() {
        //Corrige un bug en la diagramacionn del texto que aparece en Chrome 102 (Escritorio)
        var platf = navigator.userAgent.toLowerCase()
        if (platf.indexOf("chrome") > -1 && !executeNative && !isTouch) {
          var percDec = that.boxViewerTXT.percDecHChrBug ? "00" : "01"
          $(that.boxViewerTXT).css({ height: "85." + percDec + "%" })
          that.boxViewerTXT.percDecHChrBug = percDec == "01" ? "01" : null
        }
      }

      this.cerrar = function () {
        if (ltotalOS.isConnected) {
          doCerrar()
        } else {
          DialogueLM({
            show: true,
            ModoBtn: true,
            btnClose: true,
            btnCancelar: "Cancelar",
            btnAceptar: "Cerrar libro",
            texto:
              "Est谩s sin conexi贸n.<br>Si cierras el libro perder谩s las p谩ginas precargadas para la lectura fuera de l铆nea.",
            fnAceptar: doCerrar,
            fnCancelar: function () {},
          })
        }
      }

      if (settings.content) {
        $(contentTxt).html(settings.content)
      }

      //Pesta帽as
      pestaIndex = toolsBook({
        txt: "脥ndice",
        clase: "right book_pesta_index",
        onclick: bookIndex,
      })
      $(pestaIndex).css({ display: "" })

      pestaImagLeft = toolsBook({
        txt: "Im谩genes",
        clase: "pestana left book_pesta_imagenes",
        onclick: showRelaciones,
      })
      pestaImagLeft.colore = "rgba(43, 64, 134, 0.5)"

      pestaNotaLeft = toolsBook({
        txt: "Notas",
        clase: "pestana left book_pesta_notas",
        onclick: showRelaciones,
      })
      pestaNotaLeft.colore = "rgba(75, 142, 68, 0.5)"

      pestaTrads = toolsBook({
        txt: "Confrontar idiomas",
        clase: "right book_pesta_trads",
        onclick: showVersions,
      })

      //Globo de audiolectura
      boxGlobe_1 = document.createElement("div")
      boxGlobe_1.setAttribute("class", "boxGlobe_1 book_icon")

      boxGlobeInner_1 = document.createElement("div")
      boxGlobeInner_1.setAttribute("class", "boxGlobeInner_1")
      boxGlobe_1.appendChild(boxGlobeInner_1)

      //Speaker de audiolectura
      boxSpeaker_1 = document.createElement("div")
      boxSpeaker_1.setAttribute(
        "class",
        "boxSpeaker_1 boxSpeaker pg_center_left"
      )
      boxSpeaker_1.title = "Reproducir audiolibro"

      boxSpeaker_2 = document.createElement("div")
      boxSpeaker_2.setAttribute("class", "boxSpeaker_2 boxSpeaker")
      boxSpeaker_2.title = "Reproducir audiolibro"

      labelLector1 = document.createElement("div")
      labelLector1.setAttribute("class", "label_lector_globe_1")
      $(this.boxViewerTXT).append(labelLector1)

      boxGlobe_2 = document.createElement("div")
      boxGlobe_2.setAttribute("class", "boxGlobe_2 book_icon")

      labelLector2 = document.createElement("div")
      labelLector2.setAttribute("class", "label_lector_globe_2")

      //Icono de compartir
      boxShareGift = document.createElement("div")
      boxShareGift.setAttribute("class", "share_gift")
      boxShareGift.title = "Compartir"

      //Icono de marcador
      boxBookMark = document.createElement("div")
      boxBookMark.setAttribute("class", "boxBookMark")
      boxBookMark.title = "Marcador de p谩gina"

      //Favoritos
      favoriteIzq = document.createElement("div")
      favoriteIzq.setAttribute("class", "tag_favorite")
      favoriteIzq.title = "Agregar este libro a mis favoritos"
      favoriteIzq.onclick = doFavorite

      favoriteDer = document.createElement("div")
      favoriteDer.setAttribute("class", "tag_favorite")
      favoriteDer.title = "Agregar este libro a mis favoritos"
      favoriteDer.onclick = doFavorite

      //Icono de buscar
      boxSearch = document.createElement("div")
      boxSearch.setAttribute("class", "boxSearch")
      boxSearch.title = "Buscar en este libro"

      boxSearchContainer = $(document.createElement("div"))
      boxSearchContainer[0].setAttribute("class", "boxSearchContainer")
      $(boxSearchContainer).css({ display: "none" })

      boxSearchInput = document.createElement("input")
      boxSearchInput.setAttribute("class", "boxSearchInput")
      boxSearchInput.setAttribute("placeholder", "Buscar en este libro")
      $(boxSearchContainer).append(boxSearchInput)
      //FIN. Icono de buscar

      //Iconos aA de apariencia del texto
      boxToolTextLook1 = document.createElement("div")
      boxToolTextLook1.setAttribute("class", "tool_text_look")
      boxToolTextLook1.title = "Apariencia del texto"

      //Globe controls1
      var globeCtrols_1 = document.createElement("div")
      globeCtrols_1.setAttribute("class", "play globectrl book_icon")
      boxGlobe_1.appendChild(globeCtrols_1)

      var globeCtrols_2 = document.createElement("div")
      globeCtrols_2.setAttribute("class", "play globectrl book_icon")
      boxGlobe_2.appendChild(globeCtrols_2)

      var audio = document.createElement("audio")

      audio.ruta = ""
      that.boxViewerTXT.appendChild(audio)

      function isContinuosReading(_pgClass, _rectBox) {
        var result = false

        $("." + _pgClass, contentTxt).each(function () {
          var rects = this.getClientRects()
          for (var i = 0; i < rects.length; i++) {
            var rec = rects[i]
            var rl = rec.left - _rectBox.left

            if (rl >= 0 && rl <= _rectBox.width) {
              result = true
              break
            }
          }

          if (result) {
            return false
          }
        })

        return result
      }

      if (!executeNativeAudio) {
        audio.oncanplay = function () {
          globito.canAnimate = true
        }

        audio.ontimeupdate = function () {
          if (audio.isPlaying()) {
            globito.ct = audio.currentTime * 1000
            globito.tt = audio.duration * 1000
          }

          if (
            !isNaN(globito.ct) &&
            globito.ct > 0 &&
            !isNaN(globito.tt) &&
            globito.tt > 0 &&
            globito.canAnimate &&
            boxGlobePlaying
          ) {
            globito.startAnimation()
          }
        }

        audio.onended = function () {
          audioOnended()
        }

        audio.isPlaying = function () {
          return !audio.paused
        }

        globito.hasPlaybackRate =
          "playbackRate" in audio && audio.playbackRate === 1

        if (globito.hasPlaybackRate && isTouch) {
          globito.playbackRate = ltotalOS.getPreferenciaLector(
            "audiolibroPlaybackRate",
            1
          )
        }
      } else {
        globito.checkPlaybackRate()
      }

      function audioOnended() {
        var numPage = globito.getNumPage() + 1
        var pgclazz = "pg_" + numPage

        globito.ct = 0
        globito.et = 0
        globito.tt = audiolecturas[settings.modoLibro][pgclazz][1]
        globito.canAnimate = false
        globito.resetDeltas()
        globito.playing = false

        if (isTouch) {
          var rectBox = rectBoxViewer
          var cr = isContinuosReading(pgclazz, rectBox)
          if (!cr) {
            globito.resetGlobes(that.boxViewerTXT)
          }
        }

        isOnPlayBackAt = false
        audioPaginate = 0
        continueAudioSystem()
      }

      this.audioOnended = function () {
        audioOnended()
      }

      function showLectorAudiolectura(_side) {
        var labelLector = null
        var lectorAudio = ""

        $([labelLector1, labelLector2]).fadeOut(0)
        clearTimeout(timerLabelLector)

        if (_side === 1 && !stoppedAudio) {
          labelLector = labelLector1
          lectorAudio = lectorAudioLectura1
        }

        if (_side === 2 && !stoppedAudio) {
          labelLector = labelLector2
          lectorAudio = lectorAudioLectura2
        }

        if (labelLector) {
          $(labelLector)
            .html(
              '<span class="tooltiptext">Le铆do por ' + lectorAudio + "</span>"
            )
            .fadeIn(0)
          timerLabelLector = setTimeout(function () {
            $([labelLector1, labelLector2]).fadeOut(1000)
          }, sleepLabelLector)
        }
      }

      function setPlayAudioToGlobes() {
        globeCtrols_1.setAttribute("class", "play globectrl book_icon")
        globeCtrols_2.setAttribute("class", "play globectrl book_icon")
      }

      this.continueAudioSystem = function () {
        continueAudioSystem()
      }

      function continueAudioSystem() {
        var s = settings
        setPlayAudioToGlobes()
        //Busca el elemento de la visual
        var rutaAudioLectura = null
        var nodeAudioLectura = null
        areTherePixelsInBothSides = false
        var rectBox = rectBoxViewer

        var buscaElementosEnVisual = function (clase) {
          var children = $(contentTxt).children().children()
          var elements = getElementosEnVisual(clase, children)

          var elementInVisual = null

          for (var i = 0; i < elements.length; i++) {
            var element = elements[i][0]
            var p1 = elements[i][1]
            if ($(element).hasClass(clase)) {
              elementInVisual = [element, p1]
              break
            }
          }
          return elementInVisual
        }

        //Ejecuta el audio
        var ejecutarAudio = function (pgClass) {
          var r = false

          var elemVisual = buscaElementosEnVisual(pgClass)

          if (elemVisual) {
            var s = settings
            rutaAudioLectura = audiolecturas[s.modoLibro][pgClass][0]
            nodeAudioLectura = elemVisual
            audio.ruta = rutaAudioLectura
            audio.src = rutaAudioLectura
            audio.playbackRate = globito.playbackRate

            //Define que globo ejecuta el audio
            var p1 = elemVisual[1]
            currentAudioPgClass = pgClass
            if (p1 < rectBox.width / 2) {
              nodeAudioLectura1 = nodeAudioLectura
              rutaAudioLectura1 = rutaAudioLectura
              startAudioSystem(
                nodeAudioLectura1,
                rutaAudioLectura1,
                globeCtrols_1,
                1,
                pgClass
              )
            }

            if (p1 > rectBox.width / 2) {
              nodeAudioLectura2 = nodeAudioLectura
              rutaAudioLectura2 = rutaAudioLectura
              startAudioSystem(
                nodeAudioLectura2,
                rutaAudioLectura2,
                globeCtrols_2,
                2,
                pgClass
              )
            }

            r = true
          }
          return r
        }

        var textAudioLec = $(".text_audio_lec", contentTxt)
        if (textAudioLec[0]) {
          //Detiene audio
          audio.ruta = ""
          audio.pause()
          playbackAt = 0
          boxGlobePlaying = false

          textAudioLec.removeClass("text_audio_lec")

          //Extrae el siguiente numero de pagina
          var pgAudio = parseInt(
            textAudioLec[0].className.match(/pg_\d+/)[0].split("_")[1]
          )
          pgAudio++
          var clasePag = "pg_" + pgAudio

          var executed = ejecutarAudio(clasePag)
          if (!executed) {
            //Busca audio en la siguiente pagina y asigna modo paso pagina: automatico
            modoPasoPagina = "automatico"
            globito.bottomMenuTime = 1500
            nextPage()
            var continueAudio = ejecutarAudio(clasePag)
            if (!continueAudio && page[s.modoLibro] >= total[s.modoLibro] - 1) {
              globito.audioHasFinished = true
              clearAudioData()
              showAudiolectura()
            }
          }
        }
      }

      this.nextPasoPaginaAudioNativo = function () {
        modoPasoPagina = "automatico"
        nextPage()
      }

      function getFirstElementInVisual() {
        var s = settings
        var children = $(contentTxt).children().children()
        var nodosVisual = elementosEnVisual2
        nodeAudioLectura1 = nodosVisual[0]
        nodeAudioLectura2 = getElementosEnVisual(null, children, 2)[0]
        var pgAudio = null
        var audilecData = null

        if (nodeAudioLectura1) {
          pgAudio = nodeAudioLectura1[0].className.match(/pg_\d+/)[0]
          if (pgAudio) {
            audilecData = audiolecturas[s.modoLibro][pgAudio]
            if (audilecData) {
              rutaAudioLectura1 = audilecData[0]
            }
          }
        }

        if (nodeAudioLectura2) {
          pgAudio = nodeAudioLectura2[0].className.match(/pg_\d+/)[0]
          if (pgAudio) {
            audilecData = audiolecturas[s.modoLibro][pgAudio]
            rutaAudioLectura2 = audilecData[0]
          }
        }
      }

      boxGlobe_1.onclick = function () {
        boxGlobeOne()
      }

      boxSpeaker_1.onclick = function () {
        boxGlobeOne()
      }

      function boxGlobeOne() {
        modoPasoPagina = "manual"
        getFirstElementInVisual()
        var side = 1

        if (currentAudioSide == 2) {
          currentAudioPgClass = null
          clearTimeout(timerAudioLibro)
          globito.et = 0
          globito.ct = 0
          playbackAt = 0
        }

        $.when(
          startAudioSystem(
            nodeAudioLectura1,
            rutaAudioLectura1,
            globeCtrols_1,
            side,
            currentAudioPgClass
          )
        ).done(showLectorAudiolectura(side))
      }

      boxGlobe_2.onclick = function () {
        boxGlobeTwo()
      }

      boxSpeaker_2.onclick = function () {
        boxGlobeTwo()
      }

      function boxGlobeTwo() {
        modoPasoPagina = "manual"
        getFirstElementInVisual()
        var side = 2

        if (currentAudioSide == 1) {
          currentAudioPgClass = null
          clearTimeout(timerAudioLibro)
          globito.et = 0
          globito.ct = 0
          playbackAt = 0
        }

        $.when(
          startAudioSystem(
            nodeAudioLectura2,
            rutaAudioLectura2,
            globeCtrols_2,
            side,
            currentAudioPgClass
          )
        ).done(showLectorAudiolectura(side))
      }

      function globeMotionControl(_side) {
        if (_side == 1) {
          boxGlobeOne()
        }
        if (_side == 2) {
          boxGlobeTwo()
        }
      }

      this.globeMotionControl = function (_side) {
        globeMotionControl(_side)
      }

      function getClientRectsVTxt(_pgClass, _rectBox, _side, _state) {
        var pixelsIn = 0
        var pixelsOut = 0
        var pixelsTotal = 0
        var pixelsGtV = 0
        var pixelsLtV = 0
        var side2 = _side && _side === 2
        var umbral = 0
        var pixelsTopIn = 0
        var pixelsTopOut = 0
        var pixelsInSideTwo = 0
        var pixelsTopSideTwo = 0
        var tpx = 0
        var pixelsTopTotal = 0
        var pixelsTopLtV = 0
        var topRects = []
        var lastTopIn = 0

        //$(".false_rect", that.boxViewerTXT).remove();

        //Filtra rectangulos que tengan altura
        var rectangulos = []
        $("." + _pgClass, contentTxt).each(function () {
          var rects = this.getClientRects()
          for (var i = 0; i < rects.length; i++) {
            var rec = rects[i]
            var rh = rec.height
            if (rh > 0) {
              rectangulos.push(rec)
            }
          }
        })

        for (var i = 0; i < rectangulos.length; i++) {
          var rec = rectangulos[i]

          var rl = rec.left - _rectBox.left
          var rw = rec.width
          var rh = rec.height
          var rt = rec.top
          topRects.push(rt)

          //Calcula areas de las lineas blancas (huecos)
          var holeH = 0
          if (rw > 0 && rh > 0 && isTouch) {
            //$(that.boxViewerTXT).append('<div class="false_rect" style="position:absolute; border:1px solid red; box-sizing:border-box; left:'+rl+'px; top:'+rt+'px; width:'+rw+'px; height:'+rh+'px;"></div>');
            if (i > 0) {
              var rectPrev = rectangulos[i - 1]
              var rpl = rectPrev.left - _rectBox.left
              var rpw = rectPrev.width
              var rpt = rectPrev.top
              var rph = rectPrev.height

              var hol = rpl
              var how = rpw
              var hot = rpt + rph
              var hoh = rt - hot
              if (hoh > 0) {
                holeH = hoh
                //$(that.boxViewerTXT).append('<div class="false_rect" style="position:absolute; border:1px solid green; box-sizing:border-box; left:'+hol+'px; top:'+hot+'px; width:'+how+'px; height:'+hoh+'px;"></div>');
              }
            }
          }

          var pixels = rw * rh
          var st = 0
          if (side2) {
            st = _rectBox.width / 2
          }

          if (_state === 1) {
            umbral = rl >= st && rl <= _rectBox.width
          }

          if (_state === 2) {
            umbral = rl < _rectBox.width
          }

          rh += holeH

          if (umbral) {
            pixelsIn += pixels
            lastTopIn = rt

            if (tpx != rt) {
              pixelsTopIn += rh
            }

            if (rl < 0) {
              pixelsLtV += pixels

              if (tpx != rt) {
                pixelsTopLtV += rh
              }
            }

            if (_side && _side === 1 && rl > _rectBox.width / 2) {
              areTherePixelsInBothSides = true
              pixelsInSideTwo += pixels

              if (tpx != rt) {
                pixelsTopIn -= rh
                pixelsTopSideTwo += rh
              }
            }
          } else {
            pixelsOut += pixels

            if (rl > _rectBox.width) {
              pixelsGtV += pixels

              if (tpx != rt) {
                pixelsTopOut += rh
              }
            }
          }

          tpx = rt
        }
        pixelsTotal = Math.floor(pixelsIn + pixelsOut)
        pixelsTopTotal = pixelsTopIn + pixelsTopOut
        return [
          pixelsIn,
          pixelsOut,
          pixelsTotal,
          pixelsGtV,
          pixelsLtV,
          pixelsTopIn,
          pixelsTopOut,
          pixelsInSideTwo,
          pixelsTopSideTwo,
          pixelsTopTotal,
          pixelsTopLtV,
          topRects,
          lastTopIn,
        ]
      }

      function getLeftPercentPage(
        _pgClass,
        _rectBox,
        _numePaginaPercent,
        _totalPx
      ) {
        var tPx = 0
        var ofslRect = 0
        var ctMainLoop = true

        var allElemts = $("." + _pgClass, contentTxt)
        var baseElem = allElemts[0]
        var rlArray = []

        if (!_numePaginaPercent) {
          _numePaginaPercent = 0.0
        }

        var getMinRl = function (_rlArray) {
          var r = 0
          var minRl = Math.min.apply(null, _rlArray)
          if (minRl < 0) {
            r = Math.abs(minRl)
          }
          return r
        }

        allElemts.each(function () {
          var rects = this.getClientRects()
          for (var i = 0; i < rects.length; i++) {
            var rec = rects[i]

            var rl = rec.left - _rectBox.left
            var rw = rec.width
            var rh = rec.height
            rlArray.push(rl)

            var pixels = rw * rh
            tPx += Math.floor(pixels)
            var pPx = parseFloat((tPx / _totalPx).toFixed(3))

            if (
              (isTouch && pPx > _numePaginaPercent) ||
              (!isTouch && pPx >= _numePaginaPercent)
            ) {
              ofslRect = baseElem.offsetLeft + rl + getMinRl(rlArray)
              ctMainLoop = false
              break
            }
          }

          if (!ctMainLoop) {
            return false
          }
        })
        return ofslRect
      }

      function getNumePaginaPercent(_pgClass) {
        var rectBox = rectBoxViewer
        var clientRects = getClientRectsVTxt(_pgClass, rectBox, 1, 1)
        areTherePixelsInBothSides = false
        var pixelsOut = clientRects[1]
        var pixelsTotal = clientRects[2]
        var pixelsGtV = clientRects[3]
        var pixelsLtV = pixelsOut - pixelsGtV

        var limIn = (Math.floor(pixelsLtV) / pixelsTotal).toFixed(3)
        var limSu = (1 - Math.floor(pixelsGtV) / pixelsTotal).toFixed(3)
        return [limIn, limSu]
      }

      this.getClientRectsVTxt = function (_pgClass, _rectBox, _side, _state) {
        return getClientRectsVTxt(_pgClass, _rectBox, _side, _state)
      }

      this.setGlobePlayingForeground = function (_side) {
        boxGlobePlaying = true
        currentAudioSide = _side
      }

      this.clearTimersAudioLibro = function () {
        clearTimeout(timerAudioLibro)
        clearTimeout(timerGlobito)
      }

      function startAudioSystem(
        _nodeAudioLectura,
        _rutaAudioLectura,
        _globeCtrols,
        _side,
        _pgClass
      ) {
        var s = settings
        var side2 = _side && _side == 2
        var isAudioPlaying = boxGlobePlaying && currentAudioSide === _side
        var pgClass = null
        var blockDeltaTimeGlobe = false
        clearTimeout(timerGlobito)
        clearTimeout(timerAudioLibro)
        clearTimeout(globito.timerNativeAudio)
        clearTimeout(globito.timerGlobitoAudioNativo)
        var parentGlobe = that.boxViewerTXT
        var numPage = 0
        var resetGlobes = false

        //Entorno aplicacion nativa
        var urlBase = "https://www.ellibrototal.com/testLtotal/sonido/"
        var urlPrefix =
          s.tipoLibro == 1
            ? urlBase + s.idLibro + "/" + s.idTrad + "/"
            : urlBase + "personajes/" + s.idLibro + "/"

        if (_pgClass) {
          pgClass = _pgClass
        } else {
          pgClass = _nodeAudioLectura[0].className.match(/pg_\d+/)[0]
        }

        if (globito.lockStartAudioNative) {
          pgClass = globito.pgClass
        }

        if (pgClass) {
          //Refresca la ruta de audio
          if (
            (!currentAudioPgClass && audio.ruta != _rutaAudioLectura) ||
            currentAudioSide !== _side
          ) {
            audio.ruta = _rutaAudioLectura
            audio.src = _rutaAudioLectura
            audio.playbackRate = globito.playbackRate
          }

          if (isAudioPlaying) {
            boxGlobePlaying = false
            if (executeNativeAudio) {
              globito.canAnimate = false
              var cmdObj = { Module: "AudioPlayer", Action: "Pause" }
              if (!globito.nativePlayPause) {
                var cmdNat = panelSocial.executeNative(cmdObj)
              }
              globito.nativePlayPause = false
              globito.nativePlaying = false
            } else {
              audio.pause()
            }

            if (
              !$(".speed_menu.box_audio_tools", boxBottomMenu).is(":visible")
            ) {
              closeBottomMenu()
            }

            stoppedAudio = true

            $("." + pgClass).removeClass("text_audio_lec")
            _globeCtrols.setAttribute("class", "play globectrl book_icon")

            //pause globo audiolectura
            globito.pause(_side, parentGlobe)
          } else {
            //Resetea los datos, cuando se ejecuta el audio del primer nodo de una de las dos paginas
            var textAudioLec = $(".text_audio_lec", contentTxt)
            if (textAudioLec[0] && !globito.lockStartAudioNative) {
              globito.resetGlobes(parentGlobe)
              audio.ruta = ""
              if (executeNativeAudio) {
                var cmdObj = { Module: "AudioPlayer", Action: "Pause" }
                globito.nativePlaying = false
                globito.startAudioNative = true
                if (!globito.nativePlayPause) {
                  var cmdNat = panelSocial.executeNative(cmdObj)
                }
                globito.nativePlayPause = false
              } else {
                audio.pause()
              }

              closeBottomMenu()
              textAudioLec.removeClass("text_audio_lec")
              setPlayAudioToGlobes()
            }

            numPage = parseInt(pgClass.match(/\d+/)[0])

            //Asegura que el audio que se reproduce coincide con el texto de la visual
            var indexNodeRef = parseInt(
              $("." + pgClass, contentTxt).index(_nodeAudioLectura[0])
            )
            var nodeLeftOut = _nodeAudioLectura[1]
            var imgNode = null
            var hasImage = null
            var rectBox = rectBoxViewer

            var clientRects = getClientRectsVTxt(pgClass, rectBox, _side, 1)
            var pixelsIn = clientRects[0]
            var pixelsTopIn = clientRects[5]
            var lengthAudio = audiolecturas[s.modoLibro][pgClass][1]
            var timeGlobe = lengthAudio - playbackAt * 1000

            if (areTherePixelsInBothSides && _side == 1 && !isTouch) {
              blockDeltaTimeGlobe = true
              tPixelsSideTwo = clientRects[7]
              var pt = clientRects[2]
              var timeP1 = Math.ceil(
                ((pixelsIn - tPixelsSideTwo) / pt) * lengthAudio
              )
              var timeP2 = Math.ceil((tPixelsSideTwo / pt) * lengthAudio)

              imgNode = _nodeAudioLectura[0].parentNode
              hasImage = imgNode && $(imgNode).hasClass("img_book_art")
              if (hasImage) {
                timeP2 += timeP1
                timeP1 = 1500
              }

              timeGlobe = timeP1 - (globito.ct - globito.et)
              var pixelsTopSideTwo = clientRects[8]

              timerGlobito = setTimeout(function () {
                globito.et = globito.ct
                boxGlobePlaying = true
                currentAudioSide = 2
                currentAudioPgClass = pgClass
                globeCtrols_1.setAttribute("class", "play globectrl book_icon")
                globeCtrols_2.setAttribute("class", "pause globectrl book_icon")

                globito.play(
                  2,
                  timeP2,
                  pixelsTopSideTwo,
                  blockDeltaTimeGlobe,
                  parentGlobe,
                  numPage,
                  resetGlobes
                )
                showLectorAudiolectura(currentAudioSide)
              }, timeGlobe)
            }

            if (areTherePixelsInBothSides && _side == 2 && !isTouch) {
              var pt = clientRects[2]
              timeGlobe = Math.ceil((tPixelsSideTwo / pt) * lengthAudio)

              if (globito.lockStartAudioNative) {
                blockDeltaTimeGlobe = true
                timeGlobe = globito.timeLeft()
              }
            }

            imgNode = _nodeAudioLectura[0].parentNode.previousElementSibling
            hasImage = imgNode && $(imgNode).hasClass("img_book_art")

            if (
              ((!hasImage && indexNodeRef != 0) ||
                (hasImage && indexNodeRef > 1) ||
                (side2 ? nodeLeftOut < rectBox.width / 2 : nodeLeftOut < 0)) &&
              ((stoppedAudio == false && modoPasoPagina == "manual") ||
                currentAudioSide !== _side)
            ) {
              //Calcular % de texto visible y no visible globo 1

              var pixelsOut = clientRects[1]
              var pixelsTotal = clientRects[2]
              var pixelsGtV = clientRects[3]

              var percPixelsOut = pixelsOut / pixelsTotal
              var percPixelsGtV = pixelsGtV / pixelsTotal
              playbackAt = Math.ceil(
                ((percPixelsOut - percPixelsGtV) * lengthAudio) / 1000
              )
              timeGlobe = lengthAudio - playbackAt * 1000
              globito.ct = playbackAt * 1000
              globito.et = globito.ct

              //Verificar si es necesario refrescar la ruta
              _rutaAudioLectura = audiolecturas[s.modoLibro][pgClass][0]
              audio.ruta = _rutaAudioLectura
              audio.src = _rutaAudioLectura
              audio.playbackRate = globito.playbackRate

              //Reproduce el audio segun porcentaje de la visual
              $(audio).on("canplaythrough", function () {
                this.currentTime = playbackAt
                $(this).off("canplaythrough")
              })
            }

            //Reproduce audiolibro con la aplicacion nativa
            if (executeNativeAudio) {
              var cmdSpeed = null
              var cmdObj = null

              if (globito.speedHasChanged) {
                cmdSpeed = {
                  Module: "AudioPlayer",
                  Action: "ChangePlaybackRate",
                  Parameters: [globito.playbackRate + ""],
                }
                globito.speedHasChanged = false
              }

              if (stoppedAudio) {
                cmdObj = { Module: "AudioPlayer", Action: "Resume" }
                globito.nativePlaying = true
              }

              if (globito.startAudioNative) {
                getAllRutasAudiosLibro()
                panelSocial.getPanelMusic().resetNativeAudio()

                var audiosUrls = []
                var pagesInVisual = viewerTxtServices.unique(
                  getPgsToEdit(elementosEnVisual2)
                )
                for (var pga in pagesInVisual) {
                  var _pga = pagesInVisual[pga]
                  var _rpga =
                    audiolecturas[s.modoLibro]["pg_" + _pga][0].split("/")
                  audiosUrls.push(_rpga[_rpga.length - 1])
                }

                cmdObj = {
                  Module: "AudioPlayer",
                  Action: "PlayList",
                  AudioParams: {
                    Urls: audiosUrls,
                    UrlPrefix: urlPrefix,
                    SkipInterval: 20,
                    StartIdx: 0,
                    StartSecond: playbackAt,
                    Author: s.autorLibro,
                    Title: s.nombLibro,
                    Cover: "https://www.ellibrototal.com" + s.cover,
                    LengthAudio: parseInt(lengthAudio),
                    ContentType: "reading",
                    PlaybackRate: globito.playbackRate,
                  },
                  Callback: "globito.audioLecturaCallback",
                }

                globito.rutaNativa =
                  cmdObj.AudioParams.UrlPrefix +
                  cmdObj.AudioParams.Urls[cmdObj.AudioParams.StartIdx]
                globito.startAudioNative = false
              }

              if (cmdSpeed) {
                panelSocial.executeNative(cmdSpeed)
              }

              if (cmdObj) {
                if (!globito.nativePlayPause) {
                  panelSocial.executeNative(cmdObj)
                }
                globito.nativePlayPause = false
              }
            } else {
              audio.play()
            }

            boxGlobePlaying = true
            stoppedAudio = false

            showAudiolibroTools({ time: globito.bottomMenuTime })
            globito.bottomMenuTime = 200

            $("." + pgClass).addClass("text_audio_lec")
            if (pgClass == "pg_2") {
              $(".text_audio_lec", contentTxt).css({
                "background-color": "transparent",
              })
            }

            _globeCtrols.setAttribute("class", "pause globectrl book_icon")

            //Calcular % de texto visible y no visible globo 2. Paso de pagina automatico
            var checkNextPasoPagina = function () {
              var rectBox = rectBoxViewer

              var clientRects = getClientRectsVTxt(pgClass, rectBox, _side, 2)
              var pixelsIn = clientRects[0]
              var pixelsOut = clientRects[1]
              var pixelsTotal = clientRects[2]
              var pixelsLtV = clientRects[4]
              var pixelsTopOut = clientRects[6]

              if (pixelsOut > 0) {
                if (audioPaginate > 0) {
                  blockDeltaTimeGlobe = false
                } else {
                  blockDeltaTimeGlobe = true
                }

                globito.tt =
                  audiolecturas[s.modoLibro][pgClass][1] -
                  globito.deltaTimeEmpalmeAudioWeb
                var percPixelsIn = (pixelsIn - pixelsLtV) / pixelsTotal
                var percPixelsOut = pixelsOut / pixelsTotal

                var timePasePage =
                  Math.ceil(percPixelsIn * globito.tt) -
                  (globito.ct - globito.et)
                timeGlobe = timePasePage
                timerAudioLibro = setTimeout(function () {
                  globito.et = globito.ct
                  modoPasoPagina = "automatico"
                  nextPage()
                  audioPaginate++

                  clientRects = getClientRectsVTxt(pgClass, rectBox, _side, 2)
                  pixelsIn = clientRects[0]
                  pixelsLtV = clientRects[4]
                  var pixelsTopIn = clientRects[5]
                  var pixelsTopLtV = clientRects[10]
                  var pixelsTop = pixelsTopIn - pixelsTopLtV

                  percPixelsIn = (pixelsIn - pixelsLtV) / pixelsTotal
                  timeGlobe = Math.ceil(percPixelsIn * lengthAudio)

                  //play globo audiolectura
                  isOnPlayBackAt = true
                  //resetea el globo
                  resetGlobes = true
                  globito.play(
                    1,
                    timeGlobe,
                    isTouch ? pixelsTop : pixelsTopOut,
                    false,
                    parentGlobe,
                    numPage,
                    resetGlobes
                  )

                  //Activa nuevamente la animacion
                  if (!executeNativeAudio) {
                    globito.canAnimate = true
                  }
                  checkNextPasoPagina()
                }, globito.changePlaybackRate(timePasePage))
              }
            }
            checkNextPasoPagina()

            //play globo audiolectura
            if (isOnPlayBackAt) {
              var pixelsOut = clientRects[1]
              var pixelsTotal = clientRects[2]
              var pixelsGtV = clientRects[3]

              var percPixelsOut = pixelsOut / pixelsTotal
              var percPixelsGtV = pixelsGtV / pixelsTotal
              var pba = Math.ceil(
                ((percPixelsOut - percPixelsGtV) * lengthAudio) / 1000
              )
              timeGlobe = lengthAudio - pba * 1000

              if (isTouch) {
                var pixelsIn = clientRects[0]
                var pixelsLtV = clientRects[4]
                var percPixelsIn = (pixelsIn - pixelsLtV) / pixelsTotal
                timeGlobe = Math.ceil(percPixelsIn * lengthAudio)
              }
            }
            globito.play(
              _side,
              timeGlobe,
              pixelsTopIn,
              blockDeltaTimeGlobe,
              parentGlobe,
              numPage,
              resetGlobes
            )
          }
          currentAudioSide = _side
          globito.isAudioChanging = false
        }
      }

      function clearAudioData() {
        boxGlobePlaying = false
        modoPasoPagina = "manual"
        globito.resetGlobes(that.boxViewerTXT)
        playbackAt = 0
        isOnPlayBackAt = false
        areTherePixelsInBothSides = false
        clearTimeout(timerGlobito)
        globito.tt = 0
        globito.ct = 0
        globito.et = 0
        audioPaginate = 0

        if (
          executeNativeAudio &&
          (globito.nativePlaying || globito.nativeHasPlayed)
        ) {
          globito.nativePlaying = false
          globito.nativeHasPlayed = false
          globito.startAudioNative = true

          globito.stopNativeAudio()
        }
      }

      this.clearAudioData = function () {
        clearAudioData()
      }

      function getAllRutasAudiosLibro() {
        var s = settings
        var idTrad = s.tipoLibro == 1 ? s.idTrad : "-1"
        return ltotalOS
          .loadData(
            "libros",
            "rutas_audio_libros",
            s.tipoLibro + "," + s.idLibro + "," + idTrad,
            { nativeAudioMetadata: 1 }
          )
          .done(function (d) {
            var dAudio = JSON.parse(d)
            globito.dataRutas = dAudio.dataRutas
            var obj = {
              Module: "AudioPlayer",
              Action: "ReplaceList",
              AudioParams: {
                Urls: dAudio.rutas,
                Smooth: true,
                Readers: dAudio.readers,
                List: dAudio.list,
                DurationList: dAudio.duration,
              },
            }
            globito.nativePlaying = true
            panelSocial.executeNative(obj)
          })
      }
      //FIN. Globo de audiolectura

      function favoriteClick() {
        var elements = elementosEnVisual2
        var lastElem = elements[elements.length - 1]
        var pg = 1

        var s = settings
        var shareOptions = {
          tipoItem: 1,
          idItem: s.idLibro,
          tipoLibro: s.tipoLibro,
          idLibro: s.idLibro,
          idTrad: s.idTrad,
          pg: pg,
          nombItem: s.nombLibro,
          idInstitutPromo: s.idInstitutPromo,
          hostDiv: isSmartPhone ? bodyDiv : that.boxViewerTXT,
          clickClose: function () {
            panelSocial.destroyShareModal()
            $(that.boxViewerTXT).css({ display: "" })
          },
          compruebaTecla: compruebaTecla,
          boxViewerTXT: that.boxViewerTXT,
        }

        if (s.tipoLibro == 27) {
          //En modoLibro=0 (indice) comparte todo el libro
          if (s.modoLibro == 1 || s.modoLibro == 2) {
            var idNota = parseInt(
              lastElem[0].className.match(/id_nota_\d+/)[0].match(/\d+/)[0]
            )
            shareOptions.idNota = idNota
            shareOptions.tipoLibro = 17
          }
        }

        if (s.tipoLibro == 32) {
          shareOptions.tipoLibro = 15
          var idNota = parseInt(
            lastElem[0].className.match(/id_rec_\d+/)[0].match(/\d+/)[0]
          )
          shareOptions.idNota = idNota
        }

        if (isSmartPhone && !panelSocial.executeNative("TEST")) {
          $(that.boxViewerTXT).css({ display: "none" })
        }

        panelSocial.shareGift(shareOptions)
      }

      //Antiguo click de compartir
      boxShareGift.onclick = favoriteClick

      //Nuevo click de compartir
      this.sharePageTxt = function (_cnf, _selData) {
        var txtCoord = viewerTxtServices.getTextCoordenate(_cnf, _selData)

        var s = settings
        var shareOptions = {
          tipoItem: 1,
          idItem: s.idLibro,
          tipoLibro: s.tipoLibro,
          idLibro: s.idLibro,
          idTrad: s.idTrad,
          pg: _cnf.npagItem,
          txtCoord: txtCoord,
          nombItem: s.nombLibro,
          autorLibro: s.autorLibro,
          idInstitutPromo: s.idInstitutPromo,
          hostDiv: isSmartPhone ? bodyDiv : that.boxViewerTXT,
          clickClose: function () {
            panelSocial.destroyShareModal()
            $(that.boxViewerTXT).css({ display: "" })
          },
          compruebaTecla: compruebaTecla,
          boxViewerTXT: that.boxViewerTXT,
        }

        if (isSmartPhone && !panelSocial.executeNative("TEST")) {
          $(that.boxViewerTXT).css({ display: "none" })
        }

        panelSocial.shareGift(shareOptions)
      }

      function underlineTxtByCoord(_coord) {
        var coorData = _coord.split("_")
        var coord1 = coorData[0].split(",")
        var coord2 = coorData[1].split(",")

        var allNodes = []
        for (var i = parseInt(coord1[0]); i <= parseInt(coord2[0]); i++) {
          $(contentTxt)
            .find(".pg_" + i)
            .each(function () {
              allNodes.push(this)
            })
        }

        var numNodeS = parseInt(coord1[1])
        var numNodeE = parseInt(coord2[1])
        var nOffsetS = parseInt(coord1[2])
        var nOffsetE = parseInt(coord2[2])

        var allNSel = numNodeE - numNodeS

        for (var i = 0; i < allNodes.length; ++i) {
          var cNode = allNodes[i]

          if (i >= numNodeS && i <= numNodeE) {
            var ltxt = cNode.innerHTML

            if (i == numNodeS) {
              var txt = ltxt.substring(
                nOffsetS,
                allNSel > 0 ? ltxt.length : nOffsetE
              )

              var prevNode = $(cNode).clone()

              prevNode.html(ltxt.substring(0, nOffsetS))
              $(prevNode).insertBefore(cNode)

              if (allNSel == 0) {
                var nextNode = $(cNode).clone()
                nextNode.html(ltxt.substring(nOffsetE, ltxt.length))
                $(nextNode).insertAfter(cNode)
              }

              $(cNode).html(txt)
            }

            if (numNodeE != numNodeS && i == numNodeE) {
              var nextNode = $(cNode).clone()
              nextNode.html(ltxt.substring(nOffsetE, ltxt.length))
              $(cNode).html(ltxt.substring(0, nOffsetE))
              $(nextNode).insertAfter(cNode)
            }

            $(cNode).addClass("share_txt_coord")
          }
        }

        return allNodes[numNodeS]
      }

      function openTxtSearch() {
        $(boxSearch).addClass("activated")

        boxSearchContainer.fadeIn()
        $(that.boxViewerTXT).append(boxSearchContainer)
        if (isTouch) {
          var lc = ($(window).width() - boxSearchContainer[0].offsetWidth) / 2
          boxSearchContainer.css({ left: lc + "px" })
        } else {
          boxSearchContainer.css({ right: "13%" })
        }

        $(pagesCenter)
          .children()
          .not(function () {
            return (
              $(this).hasClass("boxSearch") ||
              $(this).hasClass("closeLight") ||
              $(this).hasClass("closeOld")
            )
          })
          .detach()

        if (isTouch) {
          $(boxSearch).animate({ right: "25px" })
        } else {
          $(boxSearch).animate({ right: "0px" })
        }
      }

      //Busqueda de texto dentro del libro
      boxSearch.onclick = function (_ev) {
        if (!$(_ev.target).hasClass("boxSearch") || settings.modoLibro == 3) {
          return null
        }

        if (!boxSearchContainer.configured) {
          panelSocial.mosaico.makeRoundButton(boxSearchContainer)
          $(".squared", boxSearchContainer).append(boxSearchInput)
          boxSearchContainer.configured = true
        }

        if ($(boxSearch).hasClass("activated")) {
          $(boxSearch).removeClass("activated")

          boxSearchContainer.fadeOut(function () {
            distributeIcons()
          })
        } else {
          openTxtSearch()
        }
      }

      boxSearchInput.onkeyup = function (ev) {
        executeEnter(ev, function () {
          var txt = $.trim(boxSearchInput.value)
          if (txt.length > 0) {
            //Activa la busqueda
            if (settings.modoLibro == 1) {
              contentTxtBK = $(contentTxt).children().detach()
            }

            $([toolsIZQ, toolsDER]).empty()

            if (isTouch) {
              $(contentTxt).css({ "padding-bottom": "20px" })
            }

            settings.searchWords = txt
            settings.backSearchIndex = false
            goToSearchIndex(true)
            // 猴子补丁
            // $(boxSearchInput).blur()
          }
        })
      }

      function goToSearchIndex(_loadPages) {
        var s = settings

        clearAudioData()
        $(contentTxt).empty()
        s.modoLibro = 3

        if (_loadPages) {
          finLibro[s.modoLibro] = false
          page[s.modoLibro] = 1
          contentSearchIndex["page"] = 1
          idxSecsToLoad[s.modoLibro] = 0
          loadPages()
        } else {
          page[s.modoLibro] = contentSearchIndex["page"]
            ? contentSearchIndex["page"]
            : 1
          $(contentTxt).html(contentSearchIndex["content"])
          showPages()

          if (page[s.modoLibro] >= total[s.modoLibro]) {
            page[s.modoLibro] = total[s.modoLibro]
            showPages()
          }
        }
      }
      //FIN. Busqueda de texto dentro del libro

      boxToolTextLook1.onclick = showTextLookTools

      if (
        executeNative &&
        executeNativePlatform == "nativeAndroid" &&
        panelSocial.nativeCapabilitiesTxt.indexOf("AudioPlayer") == -1
      ) {
        executeNativeAudio = false
      }

      if (
        executeNative &&
        panelSocial.nativeCapabilitiesTxt.indexOf("AudioMetadata") > -1
      ) {
        nativeAudioMetadata = true
      }

      that.configArrows()

      that.configColumns(function () {
        initiate()
        loadBookInfo().done(function () {
          //Arranque del libro
          var numePagina = settings.numePagina
          var numePaginaPercent = settings.numePaginaPercent
          var openVocabulary = settings.openVocabulary
          var numeSeccion = settings.numeSeccion
          var numePaginaSeccion = settings.numePaginaSeccion
          var tipoLibro = settings.tipoLibro
          var modoLibro = settings.modoLibro
          var modoArticulo = tipoLibro == 27 && modoLibro == 2
          var modoSeccion = numeSeccion && numeSeccion >= 1

          if (modoLibro == 1 && openVocabulary) {
            return bookGotoPage(1, null, 0.0, null).done(function () {
              setTimeout(panelSocial.openBookVocabulary, 500)
              fixDiagramBugs()
            })
          }

          if (modoLibro == 1 && numePagina && numePagina > 0) {
            return bookGotoPage(numePagina, null, numePaginaPercent).done(
              function () {
                fixDiagramBugs()
              }
            )
          }

          if (modoLibro == 1 && modoSeccion) {
            return bookGotoSection(numeSeccion, numePaginaSeccion, {
              numePaginaPercent: numePaginaPercent,
            })
          }

          //El libro abre en indice
          if (modoLibro == 0) {
            return bookIndex()
          }

          loadPages().done(function () {
            fixDiagramBugs()
          })
        })
      })
    }
  }

})()

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址