1 2var Scroller = function(node) { 3 4 var 5 wrapper = node.find('.scroller-text-wrapper'), 6 currentTextInfo = null, 7 locationInfo = {}, 8 ignoreScrollEvent = false, 9 speedLastPos = null, 10 speedDelta = 0; 11 12 speedIndicator = $('<div class="scroller-speed" style="z-index: 50; position: absolute; top: 0; left: 0; width: 50; background: black; padding: 5px;color:#fff"></div>') 13 .appendTo(node.parent()) 14 .hide(); 15 16 17 var globalTimeout = null; 18 19 function startGlobalTimeout() { 20 if (globalTimeout == null) { 21 setTimeout(triggerGlobalEvent, 10); 22 } 23 } 24 25 function triggerGlobalEvent() { 26 27 ext.trigger('globalmessage', {type: 'globalmessage', target: this, data: {messagetype: 'nav', type: currentTextInfo.type.toLowerCase(), locationInfo: locationInfo}}); 28 29 clearTimeout(globalTimeout); 30 globalTimeout = null; 31 } 32 33 node.on('scroll', function() { 34 35 if (ignoreScrollEvent) { 36 return; 37 } 38 //console.log('sendingscroll'); 39 40 update_location_info(); 41 42 ext.trigger('scroll', {type: 'scroll', target: this, data: {locationInfo: locationInfo}}); 43 startGlobalTimeout(); 44 45 start_speed_test(); 46 }); 47 48 49 var speedInterval = null; 50 51 function start_speed_test() { 52 if (speedInterval == null) { 53 54 speedInterval = setInterval(checkSpeed, 100); 55 } 56 } 57 58 function stop_speed_test() { 59 if (speedInterval != null) { 60 clearInterval(speedInterval); 61 speedInterval = null; 62 } 63 64 } 65 66 function checkSpeed() { 67 68 var speedNewPos = node.scrollTop(); 69 if ( speedLastPos != null ){ // && newPos < maxScroll 70 speedDelta = speedNewPos - speedLastPos; 71 } 72 speedLastPos = speedNewPos; 73 74 if (speedDelta === 0) { 75 load_more(); 76 stop_speed_test(); 77 } 78 79/* 80 if (speedDelta != null) { 81 speedIndicator.html(speedDelta); 82 } 83*/ 84 } 85 86 87 88 // find the top most visible node (verse, page, etc.) 89 // pass it up as an event 90 function update_location_info() { 91 92 // reset info 93 var 94 topOfContentArea = node.offset().top, 95 sectionid = '', 96 fragmentid = '', 97 label = '', 98 labelLong = '', 99 fragmentSelector = currentTextInfo.fragmentSelector, 100 newLocationInfo = null; 101 102 // magic for bibles or books 103 if (typeof fragmentSelector == 'undefined' || fragmentSelector == '') { 104 switch (currentTextInfo.type.toLowerCase()) { 105 case 'videobible': 106 case 'deafbible': 107 case 'bible': 108 case 'commentary': 109 // find top 110 fragmentSelector = '.verse, .v'; 111 112 break; 113 case 'book': 114 // find top 115 fragmentSelector = '.page'; 116 117 break; 118 } 119 } 120 121 122 var fragments = node.find( fragmentSelector ); 123 124 if (fragments.length == 1) { 125 fragments = node.find('.section'); 126 } 127 128 // look through all the markers and find the first one that is fully visible 129 fragments.each(function(e) { 130 var fragment = $(this), 131 isFirstVisibleFragment = false; 132 133 // is the top of the fragment at the top of the scroll pane 134 if (fragment.offset().top - topOfContentArea > -2) { 135 isFirstVisibleFragment = true; 136 137 fragmentid = fragment.attr('data-id'); 138 var totalFragments = fragment.parent().find('.' + fragmentid); 139 140 // multi-line verses 141 if (totalFragments.length > 1) { 142 143 fragment = totalFragments.first(); 144 145 if (fragment.offset().top - topOfContentArea > -2) { 146 isFirstVisibleFragment = true; 147 } else { 148 isFirstVisibleFragment = false; 149 } 150 } 151 152 } 153 154 if (isFirstVisibleFragment) { 155 156 // TODO: are there other fragments with this id 157 158 fragmentid = fragment.attr('data-id'); 159 160 161 switch (currentTextInfo.type.toLowerCase()) { 162 case 'videobible': 163 case 'deafbible': 164 case 'bible': 165 case 'commentary': 166 // find top 167 var bibleref = new bible.Reference( fragmentid ); 168 bibleref.language = currentTextInfo.lang; 169 170 label = bibleref.toString(); 171 labelLong = label + ' (' + currentTextInfo.abbr + ')'; 172 173 break; 174 case 'book': 175 labelLong = label = currentTextInfo.name + ' ' + sectionid; 176 177 break; 178 } 179 180 181 // pass the marker data 182 newLocationInfo = { 183 // verse ID 184 fragmentid: fragment.attr('data-id'), 185 186 sectionid: fragment.hasClass('section') ? fragment.attr('data-id') : fragment.closest('.section').attr('data-id'), 187 188 // extra positioning info 189 offset: topOfContentArea - fragment.offset().top, 190 191 label: label, 192 193 labelLong: labelLong, 194 195 textid: currentTextInfo.id 196 197 }; 198 return false;
199 } 200 201 // means "keep looking" :) 202 return true; 203 }); 204 205 // found a fragment 206 if (newLocationInfo != null && (locationInfo == null || newLocationInfo.fragmentid != locationInfo.fragmentid)) { 207 ext.trigger('locationchange', {type:'locationchange', target: this, data: newLocationInfo}); 208 } 209 210 //console.log('new location', newLocationInfo); 211 212 locationInfo = newLocationInfo; 213 }; 214 215 216 /* 217 var load_more_timeout = null; 218 function start_load_more_timeout() { 219 220 if (load_more_timeout === null) { 221 222 load_more_timeout = setTimeout(function() { 223 load_more(); 224 clearTimeout( load_more_timeout ); 225 load_more_timeout = null; 226 }, 100); 227 228 } 229 } 230 */ 231 232 function load_more() { 233 234 // measure top and bottom height 235 var 236 fragmentid = null; 237 wrapper_height = wrapper.height(), 238 node_height = node.height(), 239 above_top = node_scrolltop = node.scrollTop(), 240 sections = wrapper.find( '.section' ), 241 sections_count = sections.length, 242 total_height = 0, 243 below_bottom = wrapper_height /*total_height*/ - node_height - node_scrolltop; 244 245 //console.log('load_more', speedDelta, below_bottom < node_height*2, above_top < node_height*2, above_top > node_height*15, sections_count > 4 && below_bottom > node_height*15) 246 247 248 // only load if stopped 249 if (speedDelta == 0) { 250 251 // add below 252 if (below_bottom < node_height*2) { 253 254 fragmentid = sections 255 .last() // the last chapter (bottom) 256 .attr( 'data-nextid' ); 257 258 //console.log('load prev1', fragmentid); 259 260 if (fragmentid != null && fragmentid != 'null' && sections.length < 50) { 261 load('next', fragmentid); 262 } 263 264 265 } 266 267 // add above 268 else if (above_top < node_height*2) { 269 270 fragmentid = sections 271 .first() // the first chapter (top) 272 .attr( 'data-previd' ); 273 274 //console.log('load prev2', fragmentid, sections.length); 275 276 if (fragmentid != null && fragmentid != 'null' && sections.length < 50) { 277 load('prev',fragmentid); 278 } 279 280 } 281 282 283 284 // remove above 285 else if (above_top > node_height*50) { // lpnrmw was 15, but sometimes in commentaries (Jonah 1 in TSK) it kept removing the text above immediately after creating it 286 //console.log('remove above', above_top, node_height); 287 288 if (wrapper.children().length >= 2) { 289 290 // we're removing the first section, so we need to find the first one and 291 // measure where its first child should appear 292 var first_node_of_second_section = wrapper.find('.section:eq(1)').children().first(), 293 first_node_offset_before = first_node_of_second_section.offset().top; 294 295 wrapper.find('.section:first').remove(); 296 297 // now, remeasure where the first node appears and adjust the scrolltop 298 var 299 first_node_offset_after = first_node_of_second_section.offset().top, 300 offset_difference = first_node_offset_after - first_node_offset_before; 301 new_scrolltop = node.scrollTop(); 302 updated_scrolltop = new_scrolltop - Math.abs(offset_difference); 303 304 node.scrollTop(updated_scrolltop); 305 } 306 } 307 308 309 // remove below 310 else if (sections_count > 4 && below_bottom > node_height*15) { 311 //console.log('remove below', below_bottom, node_height); 312 313 wrapper.find('.section:last').remove(); 314 } 315 } else { 316 317 318 } 319 320 } 321 322 function load(loadType, sectionid, fragmentid) { 323//console.log(loadType, sectionid, fragmentid); 324 325 if (sectionid === 'null' || sectionid === null || sectionid == '') { 326 return; 327 } 328 329 // check if this exists 330 //if ( wrapper.find('[data-id="' + sectionid + '"]').length > 0 ) { 331 if ( wrapper.find('.' + sectionid).length > 0 ) { 332 333 if (fragmentid && fragmentid.trim() != '' && wrapper.find('.' + fragmentid).length > 0) { 334 scrollTo(fragmentid); 335 } 336 return; 337 } 338 339 if (loadType == 'text') { 340 wrapper.html('<div class="loading-indicator" style="height:' + node.height() + 'px;"></div>'); 341 node.scrollTop(0); 342 } 343 344 //console.log('load', loadType, sectionid, fragmentid); 345 346 TextLoader.loadSection( currentTextInfo, sectionid, function(content) { 347 348 // check if this exists 349 //if ( wrapper.find('[data-id="' + sectionid + '"]').length > 0 ) { 350 if ( wrapper.find('.' + sectionid).length > 0 ) { 351 352 if (fragmentid && fragmentid.trim() != '' && wrapper.find('.' + fragmentid).length > 0) { 353 scrollTo(fragmentid); 354 } 355 return; 356 } 357 358 ignoreScrollEvent = true; 359 360 switch (loadType) { 361 default: 362 case 'text': 363 364 // clear out and reset 365 wrapper.html(''); 366 node.scrollTop(0); 367 wrapper.append(content); 368 369 370 371 // TODO: scrollto fragmentid 372 if (fragmentid) { 373 scrollTo(fragmentid); 374 } 375 376 locationInfo = null; 377 378 update_location_info(); 379 //ext.trigger('scroll', {type: 'scroll', target: this, data: {locationInfo: locationInfo}}); 380 381 break; 382
383 case 'next': 384 385 wrapper.append(content); 386 387 388 break; 389 390 case 'prev': 391 392 // add to top, measure, then reset scroll position 393 /* 394 var node_scrolltop_before = node.scrollTop(), 395 first_item = node.find('.section').children().first(); 396 397 if (first_item.length > 0) { 398 var first_item_offset_top_before = first_item.offset().top; 399 400 // add to top and measure 401 wrapper.prepend(content); 402 403 var first_item_offset_top_after = first_item.offset().top, 404 offest_difference = first_item_offset_top_after - first_item_offset_top_before, 405 new_scrolltop = node_scrolltop_before + offest_difference; 406 407 node.scrollTop( Math.abs(new_scrolltop)); 408 } 409 */ 410 411 // add to bottom, then move up 412 //console.log(content); 413 414 var node_scrolltop_before = node.scrollTop(); 415 var wrapper_height_before = wrapper.height(); 416 wrapper.append(content); 417 var wrapper_height_after = wrapper.height(); 418 wrapper.prepend(content); 419 420 var height_difference = wrapper_height_after - wrapper_height_before, 421 new_scrolltop = node_scrolltop_before + height_difference; 422 423 node.scrollTop( Math.abs(new_scrolltop )); 424 425 break; 426 427 } 428 429 ignoreScrollEvent = false; 430 431 // send load event up to Window/App 432 //console.log('loaded', sectionid); 433 ext.trigger('globalmessage', {type: 'globalmessage', 434 target: this, 435 data: { 436 messagetype: 'textload', 437 texttype: currentTextInfo.type.toLowerCase(), 438 type: currentTextInfo.type.toLowerCase(), 439 textid: currentTextInfo.id, 440 abbr: currentTextInfo.abbr, 441 sectionid: sectionid, 442 fragmentid: fragmentid, 443 content:content 444 } 445 }); 446 447 load_more(); 448 //start_load_more_timeout(); 449 }); 450 451 } 452 453 function scrollTo(fragmentid, offset) { 454 455 if (typeof fragmentid == 'undefined') { 456 return; 457 } 458 459 460 // find the fragment 461 var fragment = wrapper.find('.' + fragmentid); 462 463 //console.log('scrollTo', fragmentid, fragment.length); 464 465 // if it exists, we'll move to it 466 if (fragment.length > 0) { 467 468 var 469 // calculate node position 470 paneTop = node.offset().top, 471 scrollTop = node.scrollTop(), 472 nodeTop = fragment.offset().top, 473 nodeTopAdjusted = nodeTop - paneTop + scrollTop; 474 475 // go to it 476 ignoreScrollEvent = true; 477 node.scrollTop(nodeTopAdjusted + (offset || 0)); 478 ignoreScrollEvent = false; 479 } 480 // if it's not there, we'll see if we can load it for this book (or bible version) 481 else { 482 // need to load it! 483 //console.log('need to load', fragmentid); 484 485 var sectionid = fragmentid.split('_')[0], 486 hasSection = currentTextInfo != null ? currentTextInfo.sections.indexOf(sectionid) > -1 : false; 487 488 if (hasSection) { 489 load('text', sectionid, fragmentid); 490 } 491 492 } 493 } 494 495 function size(width, height) { 496 node 497 .width(width) 498 .height(height); 499 } 500 501 502 function getTextInfo() { 503 return currentTextInfo; 504 } 505 506 function setTextInfo(textinfo) { 507 508 if (typeof textinfo.stylesheet != 'undefined' ) { 509 510 var styleId = 'style-' + textinfo.id, 511 styleLink = $('#' + styleId); 512 513 if (styleLink.length == 0) { 514 styleLink = $('<link id="' + styleId + '" rel="stylesheet" href="' + sofia.config.baseContentUrl + 'content/texts/' + textinfo.id + '/' + textinfo.stylesheet + '" />') 515 .appendTo($('head')); 516 } 517 } 518 519 520 currentTextInfo = textinfo; 521 } 522 523 function getLocationInfo() { 524 return locationInfo; 525 } 526 527 function setFocus(newHasFocus) { 528 hasFocus = newHasFocus; 529 } 530 531 532 var ext = { 533 load_more: load_more, 534 load: load, 535 size: size, 536 getTextInfo: getTextInfo, 537 setTextInfo: setTextInfo, 538 getLocationInfo: getLocationInfo, 539 scrollTo: scrollTo, 540 setFocus: setFocus 541 }; 542 543 ext = $.extend(true, ext, EventEmitter); 544 545 return ext; 546 547};
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.