1!function($) { 2 "use strict"; 3 4 var FileDropbox = function(element, options) { 5 this.$element = $(element); 6 this.uploads = []; 7 8 var events = { 9 uploadStarted: $.proxy(this.uploadStarted, this), 10 uploadFinished: $.proxy(this.uploadFinished, this), 11 progressUpdated: $.proxy(this.progressUpdated, this), 12 speedUpdated: $.proxy(this.speedUpdated, this), 13 dragOver: $.proxy(this.dragOver, this), 14 dragLeave: $.proxy(this.dragLeave, this), 15 drop: $.proxy(this.drop, this), 16 beforeSend: $.proxy(this.beforeSend, this), 17 beforeEach: $.proxy(this.beforeEach, this), 18 error: $.proxy(this.handleError, this), 19 afterAll: $.proxy(this.afterAll, this) 20 }; 21 22 this.options = $.extend({}, $.fn.filedropbox.defaults, events, options); 23 this.$element.filedrop(this.options); 24 if (this.options.shim) { 25 $('input[type=file]', this.$element).attr('name', this.options.name) 26 .addClass('shim').css('display', 'inline-block').show(); 27 $('a.manual', this.$element).hide(); 28 } 29 (this.options.files || []).forEach($.proxy(this.addNode, this)); 30 }; 31 32 FileDropbox.prototype = { 33 dragLeave: function(e) { 34 this.$element.removeAttr('style'); 35 }, 36 drop: function(e) { 37 this.$element.removeAttr('style'); 38 }, 39 dragOver: function(box, e) { 40 this.$element.css('background-color', 'rgba(0, 0, 0, 0.3)'); 41 }, 42 beforeEach: function (file) { 43 if (this.options.maxfiles && this.uploads.length >= this.options.maxfiles) { 44 // This file is not allowed to be added to the list. It's over the 45 // limit 46 this.handleError('TooManyFiles', file); 47 return false; 48 } 49 var node = this.addNode(file).data('file', file); 50 node.find('.progress').show(); 51 node.find('.progress-bar').width('100%').addClass('progress-bar-striped active'); 52 node.find('.trash').hide(); 53 }, 54 beforeSend: function (file, i, reader) { 55 var URL = window.webkitURL || window.URL; 56 this.uploads.some(function(e) { 57 if (e.data('file') == file) { 58 if (file.type.indexOf('image/') === 0 && file.size < 1e6) { 59 var img = e.find('.preview') 60 .tooltip({items:'img', 61 tooltipClass: 'tooltip-preview', 62 content:function(){ return $(this).clone().wrap('<div>'); }} 63 ) 64 .get()[0]; 65 img.src = URL.createObjectURL(file); 66 } 67 return true; 68 } 69 }); 70 }, 71 speedUpdated: function(i, file, speed) { 72 var that = this; 73 this.uploads.some(function(e) { 74 if (e.data('file') == file) { 75 e.find('.upload-rate').text(that.fileSize(speed * 1024)+'/s'); 76 return true; 77 } 78 }); 79 }, 80 progressUpdated: function(i, file, value) { 81 this.uploads.some(function(e) { 82 if (e.data('file') == file) { 83 e.find('.progress-bar') 84 .attr({'aria-valuenow': value}) 85 .width(value + '%'); 86 if (value == 100) 87 e.find('.progress-bar').addClass('progress-bar-striped active'); 88 return true; 89 } 90 }); 91 }, 92 uploadStarted: function(i, file, n, xhr) { 93 var that = this; 94 this.uploads.some(function(e) { 95 if (e.data('file') == file) { 96 e.data('xhr', xhr); 97 e.find('.cancel').show(); 98 that.lockSubmit(1); 99 that.progressUpdated(i, file, 0); 100 setTimeout(function() { 101 e.find('.progress-bar') 102 .removeClass('progress-bar-striped active'); 103 }, 50); 104 return true; 105 } 106 }); 107 }, 108 uploadFinished: function(i, file, json, time, xhr) { 109 var that = this; 110 this.uploads.some(function(e) { 111 if (e.data('file') == file) { 112 if (!json || !json.id) 113 // Upload failed. TODO: Add a button to the UI to retry on 114 // HTTP 500 115 return e.remove(); 116 e.find('[name="'+that.options.name+'"]').val(''+json.id+','+file.name); 117 e.data('fileId', json.id); 118 e.find('.progress-bar') 119 .width('100%') 120 .attr({'aria-valuenow': 100}); 121 e.find('.trash').show(); 122 e.find('.upload-rate').hide(); 123 e.find('.cancel').hide(); 124 setTimeout(function() { e.find('.progress').hide(); }, 600); 125 return true; 126 } 127 }); 128 }, 129 fileSize: function(size) { 130 var sizes = ['k','M','G','T'], 131 suffix = ''; 132 while (size > 900) { 133 size /= 1024; 134 suffix = sizes.shift(); 135 } 136 return (suffix ? size.toPrecision(3) + suffix : size) + 'B'; 137 }, 138 findNode: function(file) { 139 var nodes = $.grep(this.uploads, function(e) { 140 return e.data('file') == file; 141 }); 142 return nodes ? nodes[0] : null; 143 }, 144 addNode: function(file) { 145 // Check if the file is already in the list of files for this dropbox 146 var already_added = false;
147 this.uploads.some(function(e) { 148 if (file.id && e.data('fileId') == file.id) { 149 already_added = true; 150 return true; 151 } 152 }); 153 if (already_added) 154 return; 155 156 var filenode = $('<div class="file"></div>'); 157 filenode 158 .append($('<div class="filetype"></div>').addClass()) 159 .append($('<img class="preview" />')) 160 .append($('<span class="filename ltr"></div>') 161 .append($('<span class="filesize"></span>').text( 162 this.fileSize(parseInt(file.size)) 163 )) 164 .append($('<div class="pull-right cancel"></div>') 165 .append($('<i class="icon-remove"></i>') 166 .attr('title', __('Cancel')) 167 ) 168 .click($.proxy(this.cancelUpload, this, filenode)) 169 .hide() 170 ) 171 .append($('<div class="upload-rate pull-right"></div>')) 172 ).append($('<div class="progress"></div>') 173 .append($('<div class="progress-bar"></div>')) 174 .attr({'aria-valuemin':0,'aria-valuemax':100}) 175 .hide()) 176 .append($('<input type="hidden"/>').attr('name', this.options.name) 177 .val(file.id)) 178 if (this.options.deletable) { 179 filenode.prepend($('<span><i class="icon-trash"></i></span>') 180 .addClass('trash pull-right') 181 .click($.proxy(this.deleteNode, this, filenode)) 182 ); 183 } 184 if (file.id) 185 filenode.data('fileId', file.id); 186 if (file.download_url) { 187 filenode.find('.filename').prepend( 188 $('<a class="no-pjax" target="_blank"></a>').text(file.name) 189 .attr('href', file.download_url) 190 ); 191 } 192 else 193 filenode.find('.filename').prepend($('<span>').text(file.name)); 194 this.$element.parent().find('.files').append(filenode); 195 this.uploads.push(filenode); 196 return filenode; 197 }, 198 deleteNode: function(filenode, e) { 199 if (!e || confirm(__('You sure?'))) { 200 var i = this.uploads.indexOf(filenode); 201 if (i !== -1) 202 this.uploads.splice(i,1); 203 filenode.slideUp('fast', function() { $(this).remove(); }); 204 } 205 }, 206 cancelUpload: function(node) { 207 if (node.data('xhr')) { 208 node.data('xhr').abort(); 209 var img = node.find('.preview').get()[0]; 210 if (img) (window.webkitURL || window.URL).revokeObjectURL(img.src); 211 } 212 return this.deleteNode(node, false); 213 }, 214 handleError: function(err, file, i, status) { 215 var message = $.fn.filedropbox.messages[err], 216 filenode = this.findNode(file); 217 if (file instanceof File) { 218 message = '<b>' + file.name + '</b><br/>' + message + '<br/>' + status; 219 } 220 $.sysAlert(__('File Upload Error'), message); 221 if (filenode) this.cancelUpload(filenode); 222 }, 223 afterAll: function() { 224 var submit = this.$element.closest('form').find('input[type=submit]'), 225 $submit = $(submit); 226 if ($submit.data('original')) { 227 $submit.val($submit.data('original')).prop('disabled', false); 228 } 229 }, 230 lockSubmit: function() { 231 var submit = this.$element.closest('form').find('input[type=submit]'), 232 $submit = $(submit); 233 if (!$submit.data('original')) { 234 $submit.data('original', $submit.val()); 235 } 236 $submit.val(__('Uploading ...')).prop('disabled', true); 237 } 238 }; 239 240 $.fn.filedropbox = function ( option ) { 241 return this.each(function () { 242 var $this = $(this), 243 data = $this.data('dropbox'), 244 options = typeof option == 'object' && option; 245 if (!data) $this.data('dropbox', (data = new FileDropbox(this, options))); 246 if (typeof option == 'string') data[option](); 247 }); 248 }; 249 250 $.fn.filedropbox.defaults = { 251 files: [], 252 deletable: true, 253 shim: !window.FileReader, 254 queuefiles: 1 255 }; 256 257 $.fn.filedropbox.messages = { 258 'BrowserNotSupported': __('Your browser is not supported'), 259 'TooManyFiles': __('You are trying to upload too many files'), 260 'FileTooLarge': __('File is too large'), 261 'FileTypeNotAllowed': __('This type of file is not allowed'),
262 'FileExtensionNotAllowed': __('This type of file is not allowed'), 263 'NotFound': __('Could not find or read this file'), 264 'NotReadable': __('Could not find or read this file'), 265 'AbortError': __('Could not find or read this file') 266 }; 267 268 $.fn.filedropbox.Constructor = FileDropbox; 269 270}(jQuery); 271 272/* 273 * Default text - jQuery plugin for html5 dragging files from desktop to browser 274 * 275 * Author: Weixi Yen 276 * 277 * Email: [Firstname][Lastname]@gmail.com 278 * 279 * Copyright (c) 2010 Resopollution 280 * 281 * Licensed under the MIT license: 282 * http://www.opensource.org/licenses/mit-license.php 283 * 284 * Project home: 285 * http://www.github.com/weixiyen/jquery-filedrop 286 * 287 * Version: 0.1.0 288 * 289 * Features: 290 * Allows sending of extra parameters with file. 291 * Works with Firefox 3.6+ 292 * Future-compliant with HTML5 spec (will work with Webkit browsers and IE9) 293 * Usage: 294 * See README at project homepage 295 * 296 */ 297;(function($) { 298 299 var default_opts = { 300 fallback_id: '', 301 link: false, 302 url: '', 303 refresh: 1000, 304 paramname: 'userfile', 305 requestType: 'POST', // just in case you want to use another HTTP verb 306 allowedfileextensions:[], 307 allowedfiletypes:[], 308 maxfiles: 25, // Ignored if queuefiles is set > 0 309 maxfilesize: 1, // MB file size limit 310 queuefiles: 0, // Max files before queueing (for large volume uploads) 311 queuewait: 200, // Queue wait time if full 312 data: {}, 313 headers: {}, 314 drop: empty, 315 dragStart: empty, 316 dragEnter: empty, 317 dragOver: empty, 318 dragLeave: empty, 319 docEnter: empty, 320 docOver: empty, 321 docLeave: empty, 322 beforeEach: empty, 323 afterAll: empty, 324 rename: empty, 325 error: function(err, file, i, status) { 326 alert(err); 327 }, 328 uploadStarted: empty, 329 uploadFinished: empty, 330 progressUpdated: empty, 331 globalProgressUpdated: empty, 332 speedUpdated: empty 333 }, 334 errors = ["BrowserNotSupported", "TooManyFiles", "FileTooLarge", "FileTypeNotAllowed", "NotFound", "NotReadable", "AbortError", "ReadError", "FileExtensionNotAllowed"], 335 Blob = window.WebKitBlob || window.MozBlob || window.Blob; 336 337 $.fn.filedrop = function(options) { 338 var opts = $.extend({}, default_opts, options), 339 global_progress = [], 340 doc_leave_timer, stop_loop = false, 341 files_count = 0, 342 files; 343 344 if (window.FileReader) 345 $('#' + opts.fallback_id).css({ 346 display: 'none', 347 width: 0, 348 height: 0 349 }); 350 351 this.on('drop', drop).on('dragstart', opts.dragStart).on('dragenter', dragEnter).on('dragover', dragOver).on('dragleave', dragLeave); 352 $(document).on('drop', docDrop).on('dragenter', docEnter).on('dragover', docOver).on('dragleave', docLeave); 353 354 (opts.link || this).click(function(e) { 355 $('#' + opts.fallback_id).trigger('click'); 356 return false; 357 }); 358 359 $('#' + opts.fallback_id).change(function(e) { 360 opts.drop(e); 361 files = e.target.files; 362 files_count = files.length; 363 upload(); 364 }); 365 366 function drop(e) { 367 if( opts.drop.call(this, e) === false ) return false; 368 if(!e.originalEvent.dataTransfer) 369 return; 370 files = e.originalEvent.dataTransfer.files; 371 if (files === null || files === undefined || files.length === 0) { 372 opts.error(errors[0]); 373 return false; 374 } 375 files_count = files.length; 376 upload(); 377 e.preventDefault(); 378 return false; 379 } 380 381 function getBuilder(filename, filedata, mime, boundary) { 382 var dashdash = '--', 383 crlf = '\r\n', 384 builder = [], 385 paramname = opts.paramname; 386 387 if (opts.data) { 388 var params = $.param(opts.data).replace(/\+/g, '%20').split(/&/); 389 390 $.each(params, function() { 391 var pair = this.split("=", 2), 392 name = decodeURIComponent(pair[0]), 393 val = decodeURIComponent(pair[1]); 394 395 if (pair.length !== 2) { 396 return; 397 } 398 399 builder.push(dashdash 400 + boundary 401 + crlf 402 + 'Content-Disposition: form-data; name="' + name + '"' 403 + crlf 404 + crlf 405 + val 406 + crlf); 407 }); 408 } 409 410 if (jQuery.isFunction(paramname)){ 411 paramname = paramname(filename); 412 } 413 414 builder.push(dashdash 415 + boundary 416 + crlf 417 + 'Content-Disposition: form-data; name="' + (paramname||"") + '"' 418 + '; filename="' + encodeURIComponent(filename) + '"' 419 + crlf 420 421 + 'Content-Type: ' + mime 422 + crlf 423 + crlf); 424 425 builder.push(filedata); 426 builder.push(crlf 427 + dashdash 428 + boundary 429 + dashdash 430 + crlf); 431 return new Blob(builder); 432 } 433 434 function progress(e) { 435 if (e.lengthComputable) { 436 var percentage = ((e.loaded * 100) / e.total).toFixed(1); 437 if (this.currentProgress != percentage) { 438 439 this.currentProgress = percentage; 440 opts.progressUpdated(this.index, this.file, this.currentProgress); 441 442 global_progress[this.global_progress_index] = this.currentProgress; 443 globalProgress(); 444
445 var elapsed = new Date().getTime(); 446 var diffTime = elapsed - this.currentStart; 447 if (diffTime >= opts.refresh) { 448 var diffData = e.loaded - this.startData; 449 var speed = diffData / diffTime; // KB per second 450 opts.speedUpdated(this.index, this.file, speed); 451 this.startData = e.loaded; 452 this.currentStart = elapsed; 453 } 454 } 455 } 456 } 457 458 function globalProgress() { 459 if (global_progress.length === 0) { 460 return; 461 } 462 463 var total = 0, index; 464 for (index in global_progress) { 465 if(global_progress.hasOwnProperty(index)) { 466 total = total + global_progress[index]; 467 } 468 } 469 470 opts.globalProgressUpdated(Math.round(total / global_progress.length)); 471 } 472 473 // Respond to an upload 474 function upload() { 475 stop_loop = false; 476 477 if (!files) { 478 opts.error(errors[0]); 479 return false; 480 } 481 if (typeof Blob === "undefined") { 482 opts.error(errors[0]); 483 return false; 484 } 485 486 if (opts.allowedfiletypes.push && opts.allowedfiletypes.length) { 487 for(var fileIndex = files.length;fileIndex--;) { 488 if(!files[fileIndex].type || $.inArray(files[fileIndex].type, opts.allowedfiletypes) < 0) { 489 opts.error(errors[3], files[fileIndex], fileIndex); 490 return false; 491 } 492 } 493 } 494 495 if (opts.allowedfileextensions.push && opts.allowedfileextensions.length) { 496 for(var fileIndex = files.length;fileIndex--;) { 497 var allowedextension = false; 498 for (i=0;i<opts.allowedfileextensions.length;i++){ 499 if (files[fileIndex].name.substr(files[fileIndex].name.length-opts.allowedfileextensions[i].length).toLowerCase() 500 == opts.allowedfileextensions[i].toLowerCase() 501 ) { 502 allowedextension = true; 503 } 504 } 505 if (!allowedextension){ 506 opts.error(errors[8], files[fileIndex], fileIndex); 507 return false; 508 } 509 } 510 } 511 512 var filesDone = 0, 513 filesRejected = 0; 514 515 if (files_count > opts.maxfiles && opts.queuefiles === 0) { 516 opts.error(errors[1]); 517 return false; 518 } 519 520 // Define queues to manage upload process 521 var workQueue = []; 522 var processingQueue = []; 523 var doneQueue = []; 524 525 // Add everything to the workQueue 526 for (var i = 0; i < files_count; i++) { 527 workQueue.push(i); 528 } 529 530 // Helper function to enable pause of processing to wait 531 // for in process queue to complete 532 var pause = function(timeout) { 533 setTimeout(process, timeout); 534 return; 535 }; 536 537 // Process an upload, recursive 538 var process = function() { 539 540 var fileIndex; 541 542 if (stop_loop) { 543 return false; 544 } 545 546 // Check to see if are in queue mode 547 if (opts.queuefiles > 0 && processingQueue.length >= opts.queuefiles) { 548 return pause(opts.queuewait); 549 } else { 550 // Take first thing off work queue 551 fileIndex = workQueue[0]; 552 workQueue.splice(0, 1); 553 554 // Add to processing queue 555 processingQueue.push(fileIndex); 556 } 557 558 try { 559 if (beforeEach(files[fileIndex]) !== false) { 560 if (fileIndex === files_count) { 561 return; 562 } 563 var reader = new window.FileReader(), 564 max_file_size = 1048576 * opts.maxfilesize; 565 566 reader.index = fileIndex; 567 if (files[fileIndex].size > max_file_size) { 568 opts.error(errors[2], files[fileIndex], fileIndex); 569 // Remove from queue 570 processingQueue.forEach(function(value, key) { 571 if (value === fileIndex) { 572 processingQueue.splice(key, 1); 573 } 574 }); 575 filesRejected++; 576 return true; 577 } 578 579 reader.onerror = function(e) { 580 switch(e.target.error.code) { 581 case e.target.error.NOT_FOUND_ERR: 582 opts.error(errors[4], files[fileIndex], fileIndex); 583 return false;
584 case e.target.error.NOT_READABLE_ERR: 585 opts.error(errors[5], files[fileIndex], fileIndex); 586 return false; 587 case e.target.error.ABORT_ERR: 588 opts.error(errors[6], files[fileIndex], fileIndex); 589 return false; 590 default: 591 opts.error(errors[7], files[fileIndex], fileIndex); 592 return false; 593 }; 594 }; 595 596 reader.onloadend = function(e) { 597 if (!opts.beforeSend 598 || false !== opts.beforeSend(files[fileIndex], fileIndex, e.target)) 599 return send(e); 600 }; 601 602 reader.readAsArrayBuffer(files[fileIndex]); 603 604 } else { 605 filesRejected++; 606 } 607 } catch (err) { 608 // Remove from queue 609 processingQueue.forEach(function(value, key) { 610 if (value === fileIndex) { 611 processingQueue.splice(key, 1); 612 } 613 }); 614 opts.error(errors[0], files[fileIndex], fileIndex, err); 615 return false; 616 } 617 618 // If we still have work to do, 619 if (workQueue.length > 0) { 620 process(); 621 } 622 }; 623 624 var send = function(e) { 625 626 var fileIndex = (e.srcElement || e.target).index; 627 628 // Sometimes the index is not attached to the 629 // event object. Find it by size. Hack for sure. 630 if (e.target.index === undefined) { 631 e.target.index = getIndexBySize(e.total); 632 } 633 634 var xhr = new XMLHttpRequest(), 635 upload = xhr.upload, 636 file = files[e.target.index], 637 index = e.target.index, 638 start_time = new Date().getTime(), 639 boundary = '------multipartformboundary' + (new Date()).getTime(), 640 global_progress_index = global_progress.length, 641 builder, 642 newName = rename(file.name), 643 mime = file.type; 644 645 if (opts.withCredentials) { 646 xhr.withCredentials = opts.withCredentials; 647 } 648 649 var data = e.target.result; 650 if (typeof newName === "string") { 651 builder = getBuilder(newName, data, mime, boundary); 652 } else { 653 builder = getBuilder(file.name, data, mime, boundary); 654 } 655 656 upload.index = index; 657 upload.file = file; 658 upload.downloadStartTime = start_time; 659 upload.currentStart = start_time; 660 upload.currentProgress = 0; 661 upload.global_progress_index = global_progress_index; 662 upload.startData = 0; 663 upload.addEventListener("progress", progress, false); 664 665 // Allow url to be a method 666 if (jQuery.isFunction(opts.url)) { 667 xhr.open(opts.requestType, opts.url(), true); 668 } else { 669 xhr.open(opts.requestType, opts.url, true); 670 } 671 672 xhr.setRequestHeader('content-type', 'multipart/form-data; boundary=' + boundary); 673 xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest"); 674 675 // Add headers 676 $.each(opts.headers, function(k, v) { 677 xhr.setRequestHeader(k, v); 678 }); 679 680 xhr.send(builder); 681 682 global_progress[global_progress_index] = 0; 683 globalProgress(); 684 685 opts.uploadStarted(index, file, files_count, xhr); 686 687 var afterComplete = function(result) { 688 filesDone++; 689 690 // Remove from processing queue 691 processingQueue.forEach(function(value, key) { 692 if (value === fileIndex) { 693 processingQueue.splice(key, 1); 694 } 695 }); 696 697 // Add to donequeue 698 doneQueue.push(fileIndex); 699 700 // Make sure the global progress is updated 701 global_progress[global_progress_index] = 100; 702 globalProgress(); 703 704 if (filesDone === (files_count - filesRejected)) { 705 afterAll(); 706 } 707 if (result === false) { 708 stop_loop = true; 709 } 710 }; 711 712 xhr.onabort = afterComplete; 713 xhr.onload = function() { 714 var serverResponse = null; 715 716 if (xhr.responseText) { 717 try { 718 serverResponse = jQuery.parseJSON(xhr.responseText); 719 } 720 catch (e) { 721 serverResponse = xhr.responseText; 722 } 723 } 724 725 var now = new Date().getTime(), 726 timeDiff = now - start_time, 727 result = opts.uploadFinished(index, file, serverResponse, timeDiff, xhr); 728 729 afterComplete(result); 730 731 // Pass any errors to the error option 732 if (xhr.status < 200 || xhr.status > 299) { 733 opts.error(xhr.statusText, file, fileIndex, xhr.status); 734 } 735 }; 736 }; 737 738 // Initiate the processing loop 739 process(); 740 } 741 742 function getIndexBySize(size) { 743 for (var i = 0; i < files_count; i++) { 744 if (files[i].size === size) { 745 return i; 746 } 747 } 748 749 return undefined; 750 } 751 752 function rename(name) { 753 return opts.rename(name); 754 } 755 756 function beforeEach(file) { 757 return opts.beforeEach(file); 758 } 759 760 function afterAll() { 761 return opts.afterAll(); 762 } 763 764 function dragEnter(e) { 765 clearTimeout(doc_leave_timer); 766 e.preventDefault(); 767 opts.dragEnter.call(this, e); 768 } 769 770 function dragOver(e) { 771 clearTimeout(doc_leave_timer); 772 e.preventDefault(); 773 opts.docOver.call(this, e); 774 opts.dragOver.call(this, e); 775 } 776 777 function dragLeave(e) { 778 clearTimeout(doc_leave_timer); 779 opts.dragLeave.call(this, e); 780 e.stopPropagation(); 781 } 782 783 function docDrop(e) { 784 e.preventDefault(); 785 opts.docLeave.call(this, e); 786 return false;
787 } 788 789 function docEnter(e) { 790 clearTimeout(doc_leave_timer); 791 e.preventDefault(); 792 opts.docEnter.call(this, e); 793 return false; 794 } 795 796 function docOver(e) { 797 clearTimeout(doc_leave_timer); 798 e.preventDefault(); 799 opts.docOver.call(this, e); 800 return false; 801 } 802 803 function docLeave(e) { 804 doc_leave_timer = setTimeout((function(_this) { 805 return function() { 806 opts.docLeave.call(_this, e); 807 }; 808 })(this), 200); 809 } 810 811 return this; 812 }; 813 814 function empty() {} 815 816})(jQuery);
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.