1$exeExport = { 2 3 isTogglingBox: false, 4 delayLoadingPageTime: 200, 5 delayLoadingIdevicesJson: 50, 6 delayLoadScorm: 50, 7 scormAPIwrapper: 'SCORM_API_wrapper.js', 8 scormFunctions: 'SCOFunctions.js', 9 10 init: function () { 11 try { 12 this.addBoxToggleEvent(); 13 } catch (err) { 14 console.error('Error: Failed to initialize box toggle events'); 15 } 16 try { 17 this.setExe(); 18 this.initExe(); 19 this.initJsonIdevices(); 20 } catch (err) { 21 console.error('Error: Failed to initialize content'); 22 } 23 try { 24 this.loadScorm(); 25 } catch (err) { 26 console.error('Error: Failed to initialize SCORM'); 27 } 28 // setTimeout to allow custom button in style 29 setTimeout(function(){ 30 try { 31 $exeExport.teacherMode.init(); 32 } catch (err) { 33 console.error('Error: Failed to initialize Teacher Mode'); 34 } 35 }, 100); 36 setTimeout(() => { this.addClassJsExecutedToExeContent() }, this.delayLoadingPageTime); 37 setTimeout(() => { 38 try { 39 this.triggerPrintIfRequested(); 40 } catch (err) { 41 console.error('Error: Failed to trigger print dialog'); 42 } 43 }, this.delayLoadingPageTime); 44 }, 45 46 /** 47 * Set eXe object 48 */ 49 setExe: function () { 50 window.eXe = {}; 51 window.eXe.app = $exe; 52 }, 53 54 /** 55 * Init legacy $exe object 56 */ 57 initExe: function () { 58 window.eXe.app.init(); 59 }, 60 61 /** 62 * Teacher Mode 63 */ 64 teacherMode : { 65 STORAGE_KEY : 'exeTeacherMode', 66 init : function(){ 67 if (typeof(localStorage)!='object') return; 68 if ($(".box.teacher-only").length==0 && $(".idevice_node.teacher-only").length==0) return; 69 if (document.getElementById("teacher-mode-toggler")) return; 70 if ($("body").hasClass("exe-epub")) return; 71 document.body.classList.add('exe-teacher-mode-toggler'); 72 var btn = '<div class="form-check form-switch" id="teacher-mode-toggler-wrapper"><input class="form-check-input" type="checkbox" role="switch" id="teacher-mode-toggler"><label class="form-check-label" for="teacher-mode-toggler">'+$exe_i18n.teacher_mode+'</label></div>'; 73 $(".package-header").prepend(btn); 74 this.toggler = $("#teacher-mode-toggler"); 75 var enabled = this.isEnabled(); 76 if (enabled) { 77 this.toggler.prop("checked", true); 78 document.documentElement.classList.add('mode-teacher'); 79 } 80 this.toggler.on("change", function(){ 81 var root = document.documentElement; 82 var key = $exeExport.teacherMode.STORAGE_KEY; 83 if (this.checked) { 84 localStorage.setItem(key, '1'); 85 root.classList.add('mode-teacher'); 86 } else { 87 localStorage.removeItem(key); 88 root.classList.remove('mode-teacher'); 89 } 90 }); 91 }, 92 isEnabled : function(){ 93 try { 94 return localStorage.getItem(this.STORAGE_KEY) === '1'; 95 } catch (e) { 96 return false; 97 } 98 } 99 }, 100 101 /** 102 * Load SCO functions 103 */ 104 loadScorm: function () { 105 if (document.querySelector('body').classList.contains('exe-scorm')) { 106 var loadScormScriptInterval = setInterval(() => { 107 if (typeof window.scorm != 'undefined' && typeof window.loadPage == 'function') { 108 this.initScorm(); 109 clearInterval(loadScormScriptInterval); 110 } 111 }, this.delayLoadScorm) 112 } 113 }, 114 115 /** 116 * Load scorm page item 117 */ 118 initScorm: function () { 119 if (typeof window.scorm != 'undefined' && typeof window.loadPage == 'function') { 120 var isSCORM = false; 121 // We go through the activities to see if any save scorm data 122 let idevicesNodes = document.querySelectorAll('.idevice_node');
123 idevicesNodes.forEach(ideviceNode => { 124 let ideviceComponentType = ideviceNode.getAttribute('data-idevice-component-type'); 125 let ideviceType = ideviceNode.getAttribute('data-idevice-type'); 126 let ideviceObject = this.getIdeviceObject(ideviceType); 127 if (ideviceObject) { 128 // Check if idevice save scorm data 129 switch (ideviceComponentType) { 130 case 'js': 131 if (ideviceObject.options) { 132 ideviceObject.options.forEach(instanteOptions => { 133 if (instanteOptions.isScorm) isSCORM = true; 134 }) 135 } 136 break; 137 case 'json': 138 let jsonDataText = ideviceNode.getAttribute('data-idevice-json-data'); 139 let jsonData = null; 140 141 // Parse JSON data (sanitized) or create empty object if not valid 142 try { 143 if (jsonDataText) { 144 const sanitized = 145 $exeDevices.iDevice.gamification.helpers.sanitizeJSONString( 146 jsonDataText 147 ); 148 jsonData = JSON.parse(sanitized); 149 } 150 } catch (e) { 151 jsonData = null; 152 } 153 154 // Check for SCORM data if jsonData is valid 155 if (jsonData && jsonData.exportScorm && jsonData.exportScorm.saveScore) { 156 isSCORM = true; 157 } 158 break; 159 } 160 } 161 }) 162 window.loadPage() 163 window.addEventListener('unload', () => window.unloadPage(isSCORM)); 164 } 165 }, 166 167 /** 168 * Init export json idevices 169 */ 170 initJsonIdevices: function () { 171 // Get idevices 172 let idevicesTypes = {}; 173 let idevicesNodes = document.querySelectorAll('.idevice_node'); 174 idevicesNodes.forEach(ideviceNode => { 175 let ideviceComponentType = ideviceNode.getAttribute('data-idevice-component-type'); 176 if (ideviceComponentType == 'json') { 177 let ideviceType = ideviceNode.getAttribute('data-idevice-type'); 178 idevicesTypes[ideviceType] = true; 179 } 180 }) 181 // Init idevices 182 Object.keys(idevicesTypes).forEach(ideviceType => { 183 this.initJsonIdeviceInterval(ideviceType); 184 }) 185 }, 186 187 /** 188 * Init init export json idevice interval 189 * 190 * @param {*} ideviceType 191 */ 192 initJsonIdeviceInterval: function (ideviceType) { 193 let intervalName = 'eXe_idevice_init_interval_' + ideviceType; 194 window[intervalName] = setInterval( 195 () => this.initJsonIdevice(ideviceType, intervalName), 196 this.delayLoadingIdevicesJson); 197 }, 198 199 /** 200 * Init export json idevice 201 * 202 * @param {*} ideviceType 203 */ 204 initJsonIdevice: function (ideviceType, intervalName) { 205 // Idevice export object 206 let exportIdevice = this.getIdeviceObject(ideviceType); 207 if (exportIdevice === undefined) return false; 208 // Get json data and initializes each page component of the indicated type 209 let idevicesNodes = document.querySelectorAll(`.idevice_node.${ideviceType}`); 210 idevicesNodes.forEach(ideviceNode => { 211 // Loading class 212 ideviceNode.classList.add('loading'); 213 // Get json data 214 let jsonDataText = ideviceNode.getAttribute('data-idevice-json-data'); 215 let jsonData = null; 216 217 // Parse JSON data or create empty object if not valid 218 try { 219 if (jsonDataText) { 220 const sanitized = 221 $exeDevices.iDevice.gamification.helpers.sanitizeJSONString( 222 jsonDataText 223 ); 224 jsonData = JSON.parse(sanitized); 225 } 226 } catch (e) { 227 jsonData = null; 228 } 229 230 // If jsonData is not an object, create an empty one 231 if (!jsonData || typeof jsonData !== 'object' || Array.isArray(jsonData)) { 232 jsonData = {}; 233 } 234 235 jsonData.ideviceId = ideviceNode.id; 236 // Get accesibility 237 let accesibility = null; 238 // Get template 239 let template = ideviceNode.getAttribute('data-idevice-template'); 240 // Idevice export function 1: renderView 241 if (ideviceNode.classList.contains('db-no-data')) { 242 let htmlIdevice = exportIdevice.renderView(jsonData, accesibility, template); 243 if (htmlIdevice) ideviceNode.innerHTML = htmlIdevice; 244 } 245 // Idevice export function 2: renderBehaviour 246 exportIdevice.renderBehaviour(jsonData, accesibility); 247 // Idevice export function 3: init 248 exportIdevice.init(jsonData, accesibility); 249 // Loaded 250 ideviceNode.classList.add('loaded'); 251 setTimeout(() => { ideviceNode.classList.remove('loading') }, 100); 252 }) 253 // Clear interval 254 clearInterval(window[intervalName]); 255 }, 256 257 258 /** 259 * Get idevice export object 260 * 261 * @param {*} ideviceType 262 */ 263 getIdeviceObject: function (ideviceType) { 264 let exportIdeviceKey = this.getIdeviceObjectKey(ideviceType); 265 let exportIdevice = window[exportIdeviceKey]; 266 return exportIdevice; 267 }, 268 269 /** 270 * Get idevice export object key 271 * 272 * @param {*} ideviceType 273 */ 274 getIdeviceObjectKey: function (ideviceType) { 275 let exportIdeviceKey = `$${ideviceType.split("-").join("")}`;
276 return exportIdeviceKey; 277 }, 278 279 /** 280 * Add functionality to the boxes toggle button 281 */ 282 addBoxToggleEvent: function () { 283 284 $('article.box .box-head .box-toggle').on('click', function(){ 285 if ($exeExport.isTogglingBox) return; 286 $exeExport.isTogglingBox = true; 287 let box = $(this).parents('article.box'); 288 if (box.hasClass("minimized")) { 289 box.removeClass('minimized'); 290 $('.box-content', box).slideDown(function(){ 291 $exeExport.isTogglingBox = false; 292 }); 293 } else { 294 $('.box-content', box).slideUp(function(){ 295 box.addClass('minimized'); 296 $exeExport.isTogglingBox = false; 297 }); 298 } 299 }); 300 $('article.box .box-head').css('cursor', 'pointer').on('click', function(e){ 301 let t = $(e.target); 302 if (t.hasClass('box-toggle')) return false; 303 $('.box-toggle', this).trigger('click'); 304 }); 305 306 }, 307 308 /** 309 * Add class to page 310 */ 311 addClassJsExecutedToExeContent: function () { 312 let eXeContent = document.querySelector('.exe-content'); 313 if (eXeContent) { 314 eXeContent.classList.add('post-js'); 315 eXeContent.classList.remove('pre-js'); 316 } 317 }, 318 319 /** 320 * Trigger browser print dialog when `print=1` query parameter is present. 321 */ 322 triggerPrintIfRequested: function () { 323 const params = new URLSearchParams(window.location.search); 324 if (params.get('print') === '1' && typeof window.print === 'function') { 325 window.print(); 326 } 327 } 328} 329 330$(function () { 331 $exeExport.init(); 332}); 333 334/* To review: This should be in a different file (exe_search.js) */ 335$exeExport.searchBar = { 336 deepLinking : false, 337 markResults : false, 338 query : '', 339 init : function(){ 340 var searchWrapper = $('#exe-client-search'); 341 if (searchWrapper.length != 1) return; 342 $("body").addClass('exe-search-on'); 343 this.isIndex = $("html").attr('id') == 'exe-index'; 344 this.isPreview = window.location.href.indexOf('/file/resources?resource=/tmp/') != -1; 345 this.createSearchForm(); 346 this.data = JSON.parse(searchWrapper.attr('data-pages')); 347 348 let page = document.querySelector('.exe-content > .page'); 349 let searchContainer = document.querySelector('#exe-client-search'); 350 if (searchContainer) { 351 let searchForm = document.querySelector('form#exe-client-search-form'); 352 if (searchForm) { 353 searchForm.addEventListener('submit', event => { 354 event.preventDefault(); 355 let searchText = event.target.querySelector('#exe-client-search-text'); 356 if (searchText) { 357 let valueSearch = searchText.value; 358 let valueSearchTempElement = document.createElement("div"); 359 valueSearchTempElement.innerHTML = valueSearch; 360 valueSearch = valueSearchTempElement.textContent; 361 if (valueSearch) { 362 $exeExport.searchBar.query = valueSearch; 363 this.doSearch(); 364 page.classList.add('exe-client-search-results'); 365 } 366 } 367 }); 368 } 369 let searchHide = document.querySelector('#exe-client-search #exe-client-search-reset'); 370 if (searchHide) { 371 searchHide.addEventListener('click', event => { 372 event.preventDefault(); 373 $("main > header, main div.page-content").show(); 374 $("#exe-client-search-reset").removeClass("visible"); 375 $('#exe-client-search-results-list').html(''); 376 }) 377 } 378 } 379 }, 380 createSearchForm : function(){ 381 if (document.getElementById("exe-client-search-form")) return; 382 let html = ` 383 <form id="exe-client-search-form" action="#" method="GET"> 384 <p> 385 <label for="exe-client-search-text" class="sr-av">${$exe_i18n.search}</label> 386 <input id="exe-client-search-text" type="text" placeholder="${$exe_i18n.search}"> 387 <input id="exe-client-search-submit" type="submit" value="Búsqueda"> 388 <a id="exe-client-search-reset" href="#main" title="${$exe_i18n.hide}"><span>${$exe_i18n.hide}</span></a> 389 </p> 390 </form> 391 `; 392 $("#exe-client-search").prepend(html); 393 html = ` 394 <div id="exe-client-search-results"> 395 <div id="exe-client-search-results-list"> 396 </div> 397 </div> 398 `; 399 $("#exe-client-search").append(html); 400 }, 401 doSearch : function(){ 402 this.results = []; 403 var i; 404 let res = ''; 405 let str = $exeExport.searchBar.query; 406 str = str.toLowerCase(); 407 let data = this.data; 408 for (i in data) { 409 var node = data[i]; 410 var nodeTitle = node.name; 411 var nodetitle = nodeTitle.toLowerCase(); 412 if (nodetitle.indexOf(str) != -1) { 413 this.results.push(i); 414 let lnk = this.getLink(node.fileUrl); 415 res += '<li><a href="' + lnk +'">' + nodeTitle + '</a><span> ' + this.searchInBlocks(i, str, false) + '</span></li>'; 416 } else { 417 res += this.searchInBlocks(i, str, true); 418 } 419 } 420 if (res != '') { 421 res = '<ul>' + res + '</ul>'; 422 } else { 423 res = '<p>'+$("#exe-client-search").attr("data-no-results-string")+'</p>'; 424 } 425 $("#exe-client-search-results-list").html(res); 426 $("main > header, main div.page-content").hide(); 427 $("#exe-client-search-reset").addClass("visible"); 428 this.checkBlockLinks(); 429 }, 430 getLink : function(lnk){ 431 if (this.isPreview) return lnk; 432 if (!this.isIndex) { 433 lnk = lnk.replace('html/',''); 434 if (lnk == 'index.html') lnk = '../' + lnk; 435 } 436 return lnk; 437 }, 438 checkBlockLinks : function(){ 439 let spans = $("#exe-client-search-results-list span"); 440 if (this.deepLinking) { 441 spans.each(function(){ 442 var e = $(this); 443 var h = e.html(); 444 if (h != " ") { 445 h = h.replace(', ', '('); 446 if (!h.endsWith(')')) h = h + ')'; 447 e.html(h); 448 } else { 449 e.remove(); 450 } 451 }); 452 } else { 453 spans.remove(); 454 } 455 $("#exe-client-search-results-list a").on("click", function(){ 456 if (!$("#siteNav").is(":visible")) this.href += '?nav=false'; 457 }); 458 }, 459 searchInBlocks : function(i, str, fullLink) { 460 if (this.deepLinking == false && this.results.indexOf(i) != -1) { 461 return ''; 462 } 463 var x, z; 464 let res = ''; 465 var node = this.data[i]; 466 var nodeTitle = node.name; 467 var boxes = node.blocks; 468 var boxCounter = 0; 469 for (x in boxes) { 470 boxCounter ++; 471 } 472 for (x in boxes) { 473 var box = boxes[x]; 474 var boxOrder = box.order;
475 var boxTitle = box.name; 476 var boxtitle = boxTitle.toLowerCase(); 477 478 // Add the HTML of the iDevices to boxtitle so it searches there too 479 var iDevices = box.idevices; 480 for (z in iDevices) { 481 var iDevice = iDevices[z]; 482 if (typeof(iDevice.htmlView) == 'string') { 483 var iDeviceHTML = iDevice.htmlView; 484 iDeviceHTML = iDeviceHTML.replace(/<\/?[^>]+(>|$)/g, ""); 485 var tmp = $("<div></div>"); 486 tmp.html(iDeviceHTML); 487 var iDeviceText = tmp.text(); 488 iDeviceText = iDeviceText.toLowerCase(); 489 boxtitle += ' ' + iDeviceText; 490 } 491 } 492 493 if (boxtitle.indexOf(str) != -1) { 494 this.results.push(i); 495 let lnk = this.getLink(node.fileUrl); 496 if (fullLink) { 497 if (this.deepLinking) lnk += '#' + x; 498 res += '<li><a href="' + lnk+ '">' + nodeTitle + '</a>'; 499 if (boxCounter > 1) res += '<span> (bloque ' + boxOrder + ')</span></li>'; 500 } 501 else { 502 if (boxCounter > 1) res += ', <a href="' + lnk +'#' + x + '">bloque ' + boxOrder + '</a>'; 503 } 504 } 505 } 506 return res; 507 } 508}; 509 510$(function(){ 511 $exeExport.searchBar.init(); 512});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.