Migrate assets to use Webpacker
This commit is contained in:
147
app/javascript/vendor/jquery-dropdown.js
vendored
Normal file
147
app/javascript/vendor/jquery-dropdown.js
vendored
Normal file
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
* jQuery Dropdown: A simple dropdown plugin
|
||||
*
|
||||
* Contribute: https://github.com/claviska/jquery-dropdown
|
||||
*
|
||||
* @license: MIT license: http://opensource.org/licenses/MIT
|
||||
*
|
||||
*/
|
||||
if (jQuery) (function ($) {
|
||||
|
||||
$.extend($.fn, {
|
||||
jqDropdown: function (method, data) {
|
||||
|
||||
switch (method) {
|
||||
case 'show':
|
||||
show(null, $(this));
|
||||
return $(this);
|
||||
case 'hide':
|
||||
hide();
|
||||
return $(this);
|
||||
case 'attach':
|
||||
return $(this).attr('data-jq-dropdown', data);
|
||||
case 'detach':
|
||||
hide();
|
||||
return $(this).removeAttr('data-jq-dropdown');
|
||||
case 'disable':
|
||||
return $(this).addClass('jq-dropdown-disabled');
|
||||
case 'enable':
|
||||
hide();
|
||||
return $(this).removeClass('jq-dropdown-disabled');
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
function show(event, object) {
|
||||
|
||||
var trigger = event ? $(this) : object,
|
||||
jqDropdown = $(trigger.attr('data-jq-dropdown')),
|
||||
isOpen = trigger.hasClass('jq-dropdown-open');
|
||||
|
||||
// In some cases we don't want to show it
|
||||
if (event) {
|
||||
if ($(event.target).hasClass('jq-dropdown-ignore')) return;
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
} else {
|
||||
if (trigger !== object.target && $(object.target).hasClass('jq-dropdown-ignore')) return;
|
||||
}
|
||||
hide();
|
||||
|
||||
if (isOpen || trigger.hasClass('jq-dropdown-disabled')) return;
|
||||
|
||||
// Show it
|
||||
trigger.addClass('jq-dropdown-open');
|
||||
jqDropdown
|
||||
.data('jq-dropdown-trigger', trigger)
|
||||
.show();
|
||||
|
||||
// Position it
|
||||
position();
|
||||
|
||||
// Trigger the show callback
|
||||
jqDropdown
|
||||
.trigger('show', {
|
||||
jqDropdown: jqDropdown,
|
||||
trigger: trigger
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
function hide(event) {
|
||||
|
||||
// In some cases we don't hide them
|
||||
var targetGroup = event ? $(event.target).parents().addBack() : null;
|
||||
|
||||
// Are we clicking anywhere in a jq-dropdown?
|
||||
if (targetGroup && targetGroup.is('.jq-dropdown')) {
|
||||
// Is it a jq-dropdown menu?
|
||||
if (targetGroup.is('.jq-dropdown-menu')) {
|
||||
// Did we click on an option? If so close it.
|
||||
if (!targetGroup.is('A')) return;
|
||||
} else {
|
||||
// Nope, it's a panel. Leave it open.
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Trigger the event early, so that it might be prevented on the visible popups
|
||||
var hideEvent = jQuery.Event("hide");
|
||||
|
||||
$(document).find('.jq-dropdown:visible').each(function () {
|
||||
var jqDropdown = $(this);
|
||||
jqDropdown
|
||||
.hide()
|
||||
.removeData('jq-dropdown-trigger')
|
||||
.trigger('hide', { jqDropdown: jqDropdown });
|
||||
});
|
||||
|
||||
if(!hideEvent.isDefaultPrevented()) {
|
||||
// Hide any jq-dropdown that may be showing
|
||||
$(document).find('.jq-dropdown:visible').each(function () {
|
||||
var jqDropdown = $(this);
|
||||
jqDropdown
|
||||
.hide()
|
||||
.removeData('jq-dropdown-trigger')
|
||||
.trigger('hide', { jqDropdown: jqDropdown });
|
||||
});
|
||||
|
||||
// Remove all jq-dropdown-open classes
|
||||
$(document).find('.jq-dropdown-open').removeClass('jq-dropdown-open');
|
||||
}
|
||||
}
|
||||
|
||||
function position() {
|
||||
|
||||
var jqDropdown = $('.jq-dropdown:visible').eq(0),
|
||||
trigger = jqDropdown.data('jq-dropdown-trigger'),
|
||||
hOffset = trigger ? parseInt(trigger.attr('data-horizontal-offset') || 0, 10) : null,
|
||||
vOffset = trigger ? parseInt(trigger.attr('data-vertical-offset') || 0, 10) : null;
|
||||
|
||||
if (jqDropdown.length === 0 || !trigger) return;
|
||||
|
||||
// Position the jq-dropdown relative-to-parent...
|
||||
if (jqDropdown.hasClass('jq-dropdown-relative')) {
|
||||
jqDropdown.css({
|
||||
left: jqDropdown.hasClass('jq-dropdown-anchor-right') ?
|
||||
trigger.position().left - (jqDropdown.outerWidth(true) - trigger.outerWidth(true)) - parseInt(trigger.css('margin-right'), 10) + hOffset :
|
||||
trigger.position().left + parseInt(trigger.css('margin-left'), 10) + hOffset,
|
||||
top: trigger.position().top + trigger.outerHeight(true) - parseInt(trigger.css('margin-top'), 10) + vOffset
|
||||
});
|
||||
} else {
|
||||
// ...or relative to document
|
||||
jqDropdown.css({
|
||||
left: jqDropdown.hasClass('jq-dropdown-anchor-right') ?
|
||||
trigger.offset().left - (jqDropdown.outerWidth() - trigger.outerWidth()) + hOffset : trigger.offset().left + hOffset,
|
||||
top: trigger.offset().top + trigger.outerHeight() + vOffset
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$(document).on('click.jq-dropdown', '[data-jq-dropdown]', show);
|
||||
$(document).on('click.jq-dropdown', hide);
|
||||
$(window).on('resize', position);
|
||||
|
||||
})(jQuery);
|
||||
204
app/javascript/vendor/jquery-hotkeys.js
vendored
Normal file
204
app/javascript/vendor/jquery-hotkeys.js
vendored
Normal file
@@ -0,0 +1,204 @@
|
||||
/*jslint browser: true*/
|
||||
/*jslint jquery: true*/
|
||||
|
||||
/*
|
||||
* jQuery Hotkeys Plugin
|
||||
* Copyright 2010, John Resig
|
||||
* Dual licensed under the MIT or GPL Version 2 licenses.
|
||||
*
|
||||
* Based upon the plugin by Tzury Bar Yochay:
|
||||
* https://github.com/tzuryby/jquery.hotkeys
|
||||
*
|
||||
* Original idea by:
|
||||
* Binny V A, http://www.openjs.com/scripts/events/keyboard_shortcuts/
|
||||
*/
|
||||
|
||||
/*
|
||||
* One small change is: now keys are passed by object { keys: '...' }
|
||||
* Might be useful, when you want to pass some other data to your handler
|
||||
*/
|
||||
|
||||
(function(jQuery) {
|
||||
|
||||
jQuery.hotkeys = {
|
||||
version: "0.8",
|
||||
|
||||
specialKeys: {
|
||||
8: "backspace",
|
||||
9: "tab",
|
||||
10: "return",
|
||||
13: "return",
|
||||
16: "shift",
|
||||
17: "ctrl",
|
||||
18: "alt",
|
||||
19: "pause",
|
||||
20: "capslock",
|
||||
27: "esc",
|
||||
32: "space",
|
||||
33: "pageup",
|
||||
34: "pagedown",
|
||||
35: "end",
|
||||
36: "home",
|
||||
37: "left",
|
||||
38: "up",
|
||||
39: "right",
|
||||
40: "down",
|
||||
45: "insert",
|
||||
46: "del",
|
||||
59: ";",
|
||||
61: "=",
|
||||
96: "0",
|
||||
97: "1",
|
||||
98: "2",
|
||||
99: "3",
|
||||
100: "4",
|
||||
101: "5",
|
||||
102: "6",
|
||||
103: "7",
|
||||
104: "8",
|
||||
105: "9",
|
||||
106: "*",
|
||||
107: "+",
|
||||
109: "-",
|
||||
110: ".",
|
||||
111: "/",
|
||||
112: "f1",
|
||||
113: "f2",
|
||||
114: "f3",
|
||||
115: "f4",
|
||||
116: "f5",
|
||||
117: "f6",
|
||||
118: "f7",
|
||||
119: "f8",
|
||||
120: "f9",
|
||||
121: "f10",
|
||||
122: "f11",
|
||||
123: "f12",
|
||||
144: "numlock",
|
||||
145: "scroll",
|
||||
173: "-",
|
||||
186: ";",
|
||||
187: "=",
|
||||
188: ",",
|
||||
189: "-",
|
||||
190: ".",
|
||||
191: "/",
|
||||
192: "`",
|
||||
219: "[",
|
||||
220: "\\",
|
||||
221: "]",
|
||||
222: "'"
|
||||
},
|
||||
|
||||
shiftNums: {
|
||||
"`": "~",
|
||||
"1": "!",
|
||||
"2": "@",
|
||||
"3": "#",
|
||||
"4": "$",
|
||||
"5": "%",
|
||||
"6": "^",
|
||||
"7": "&",
|
||||
"8": "*",
|
||||
"9": "(",
|
||||
"0": ")",
|
||||
"-": "_",
|
||||
"=": "+",
|
||||
";": ": ",
|
||||
"'": "\"",
|
||||
",": "<",
|
||||
".": ">",
|
||||
"/": "?",
|
||||
"\\": "|"
|
||||
},
|
||||
|
||||
// excludes: button, checkbox, file, hidden, image, password, radio, reset, search, submit, url
|
||||
textAcceptingInputTypes: [
|
||||
"text", "password", "number", "email", "url", "range", "date", "month", "week", "time", "datetime",
|
||||
"datetime-local", "search", "color", "tel"],
|
||||
|
||||
// default input types not to bind to unless bound directly
|
||||
textInputTypes: /textarea|input|select/i,
|
||||
|
||||
options: {
|
||||
filterInputAcceptingElements: true,
|
||||
filterTextInputs: true,
|
||||
filterContentEditable: true
|
||||
}
|
||||
};
|
||||
|
||||
function keyHandler(handleObj) {
|
||||
if (typeof handleObj.data === "string") {
|
||||
handleObj.data = {
|
||||
keys: handleObj.data
|
||||
};
|
||||
}
|
||||
|
||||
// Only care when a possible input has been specified
|
||||
if (!handleObj.data || !handleObj.data.keys || typeof handleObj.data.keys !== "string") {
|
||||
return;
|
||||
}
|
||||
|
||||
var origHandler = handleObj.handler,
|
||||
keys = handleObj.data.keys.toLowerCase().split(" ");
|
||||
|
||||
handleObj.handler = function(event) {
|
||||
// Don't fire in text-accepting inputs that we didn't directly bind to
|
||||
if (this !== event.target &&
|
||||
(jQuery.hotkeys.options.filterInputAcceptingElements &&
|
||||
jQuery.hotkeys.textInputTypes.test(event.target.nodeName) ||
|
||||
(jQuery.hotkeys.options.filterContentEditable && jQuery(event.target).attr('contenteditable')) ||
|
||||
(jQuery.hotkeys.options.filterTextInputs &&
|
||||
jQuery.inArray(event.target.type, jQuery.hotkeys.textAcceptingInputTypes) > -1))) {
|
||||
return;
|
||||
}
|
||||
|
||||
var special = event.type !== "keypress" && jQuery.hotkeys.specialKeys[event.which],
|
||||
character = String.fromCharCode(event.which).toLowerCase(),
|
||||
modif = "",
|
||||
possible = {};
|
||||
|
||||
jQuery.each(["alt", "ctrl", "shift"], function(index, specialKey) {
|
||||
|
||||
if (event[specialKey + 'Key'] && special !== specialKey) {
|
||||
modif += specialKey + '+';
|
||||
}
|
||||
});
|
||||
|
||||
// metaKey is triggered off ctrlKey erronously
|
||||
if (event.metaKey && !event.ctrlKey && special !== "meta") {
|
||||
modif += "meta+";
|
||||
}
|
||||
|
||||
if (event.metaKey && special !== "meta" && modif.indexOf("alt+ctrl+shift+") > -1) {
|
||||
modif = modif.replace("alt+ctrl+shift+", "hyper+");
|
||||
}
|
||||
|
||||
if (special) {
|
||||
possible[modif + special] = true;
|
||||
}
|
||||
else {
|
||||
possible[modif + character] = true;
|
||||
possible[modif + jQuery.hotkeys.shiftNums[character]] = true;
|
||||
|
||||
// "$" can be triggered as "Shift+4" or "Shift+$" or just "$"
|
||||
if (modif === "shift+") {
|
||||
possible[jQuery.hotkeys.shiftNums[character]] = true;
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0, l = keys.length; i < l; i++) {
|
||||
if (possible[keys[i]]) {
|
||||
return origHandler.apply(this, arguments);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
jQuery.each(["keydown", "keyup", "keypress"], function() {
|
||||
jQuery.event.special[this] = {
|
||||
add: keyHandler
|
||||
};
|
||||
});
|
||||
|
||||
})(jQuery || this.jQuery || window.jQuery);
|
||||
47
app/javascript/vendor/jquery-timeout.js
vendored
Normal file
47
app/javascript/vendor/jquery-timeout.js
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* jquery.timeout.js
|
||||
*
|
||||
* Copyright (c) 2011 Thomas Kemmer <tkemmer@computer.org>
|
||||
*
|
||||
* http://code.google.com/p/jquery-timeout/
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
* files (the "Software"), to deal in the Software without
|
||||
* restriction, including without limitation the rights to use, copy,
|
||||
* modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
* of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
;(function($) {
|
||||
$.timeout = function(delay) {
|
||||
var deferred = $.Deferred(function(deferred) {
|
||||
deferred.timeoutID = window.setTimeout(function() {
|
||||
deferred.resolve();
|
||||
}, delay);
|
||||
|
||||
deferred.fail(function() {
|
||||
window.clearTimeout(deferred.timeoutID);
|
||||
});
|
||||
});
|
||||
|
||||
return $.extend(deferred.promise(), {
|
||||
clear: function() {
|
||||
deferred.reject();
|
||||
}
|
||||
});
|
||||
};
|
||||
})(jQuery);
|
||||
521
app/javascript/vendor/pixiv-ugoira-player.js
vendored
Normal file
521
app/javascript/vendor/pixiv-ugoira-player.js
vendored
Normal file
@@ -0,0 +1,521 @@
|
||||
// Source: https://github.com/pixiv/zip_player
|
||||
|
||||
// Required for iOS <6, where Blob URLs are not available. This is slow...
|
||||
// Source: https://gist.github.com/jonleighton/958841
|
||||
function base64ArrayBuffer(arrayBuffer, off, byteLength) {
|
||||
var base64 = '';
|
||||
var encodings = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
||||
var bytes = new Uint8Array(arrayBuffer);
|
||||
var byteRemainder = byteLength % 3;
|
||||
var mainLength = off + byteLength - byteRemainder;
|
||||
var a, b, c, d;
|
||||
var chunk;
|
||||
// Main loop deals with bytes in chunks of 3
|
||||
for (var i = off; i < mainLength; i = i + 3) {
|
||||
// Combine the three bytes into a single integer
|
||||
chunk = (bytes[i] << 16) | (bytes[i + 1] << 8) | bytes[i + 2];
|
||||
|
||||
// Use bitmasks to extract 6-bit segments from the triplet
|
||||
a = (chunk & 16515072) >> 18; // 16515072 = (2^6 - 1) << 18
|
||||
b = (chunk & 258048) >> 12; // 258048 = (2^6 - 1) << 12
|
||||
c = (chunk & 4032) >> 6; // 4032 = (2^6 - 1) << 6
|
||||
d = chunk & 63; // 63 = 2^6 - 1
|
||||
|
||||
// Convert the raw binary segments to the appropriate ASCII encoding
|
||||
base64 += encodings[a] + encodings[b] + encodings[c] + encodings[d];
|
||||
}
|
||||
|
||||
// Deal with the remaining bytes and padding
|
||||
if (byteRemainder == 1) {
|
||||
chunk = bytes[mainLength];
|
||||
|
||||
a = (chunk & 252) >> 2; // 252 = (2^6 - 1) << 2
|
||||
|
||||
// Set the 4 least significant bits to zero
|
||||
b = (chunk & 3) << 4; // 3 = 2^2 - 1
|
||||
|
||||
base64 += encodings[a] + encodings[b] + '==';
|
||||
} else if (byteRemainder == 2) {
|
||||
chunk = (bytes[mainLength] << 8) | bytes[mainLength + 1];
|
||||
|
||||
a = (chunk & 64512) >> 10; // 64512 = (2^6 - 1) << 10
|
||||
b = (chunk & 1008) >> 4; // 1008 = (2^6 - 1) << 4
|
||||
|
||||
// Set the 2 least significant bits to zero
|
||||
c = (chunk & 15) << 2; // 15 = 2^4 - 1
|
||||
|
||||
base64 += encodings[a] + encodings[b] + encodings[c] + '=';
|
||||
}
|
||||
|
||||
return base64;
|
||||
}
|
||||
|
||||
|
||||
function ZipImagePlayer(options) {
|
||||
this.op = options;
|
||||
this._URL = (window.URL || window.webkitURL || window.MozURL
|
||||
|| window.MSURL);
|
||||
this._Blob = (window.Blob || window.WebKitBlob || window.MozBlob
|
||||
|| window.MSBlob);
|
||||
this._BlobBuilder = (window.BlobBuilder || window.WebKitBlobBuilder
|
||||
|| window.MozBlobBuilder || window.MSBlobBuilder);
|
||||
this._Uint8Array = (window.Uint8Array || window.WebKitUint8Array
|
||||
|| window.MozUint8Array || window.MSUint8Array);
|
||||
this._DataView = (window.DataView || window.WebKitDataView
|
||||
|| window.MozDataView || window.MSDataView);
|
||||
this._ArrayBuffer = (window.ArrayBuffer || window.WebKitArrayBuffer
|
||||
|| window.MozArrayBuffer || window.MSArrayBuffer);
|
||||
this._maxLoadAhead = 0;
|
||||
if (!this._URL) {
|
||||
this._debugLog("No URL support! Will use slower data: URLs.");
|
||||
// Throttle loading to avoid making playback stalling completely while
|
||||
// loading images...
|
||||
this._maxLoadAhead = 10;
|
||||
}
|
||||
if (!this._Blob) {
|
||||
this._error("No Blob support");
|
||||
}
|
||||
if (!this._Uint8Array) {
|
||||
this._error("No Uint8Array support");
|
||||
}
|
||||
if (!this._DataView) {
|
||||
this._error("No DataView support");
|
||||
}
|
||||
if (!this._ArrayBuffer) {
|
||||
this._error("No ArrayBuffer support");
|
||||
}
|
||||
this._isSafari = Object.prototype.toString.call(
|
||||
window.HTMLElement).indexOf('Constructor') > 0;
|
||||
this._loadingState = 0;
|
||||
this._dead = false;
|
||||
this._context = options.canvas.getContext("2d");
|
||||
this._files = {};
|
||||
this._frameCount = this.op.metadata.frames.length;
|
||||
this._debugLog("Frame count: " + this._frameCount);
|
||||
this._frame = 0;
|
||||
this._loadFrame = 0;
|
||||
this._frameImages = [];
|
||||
this._paused = false;
|
||||
this._loadTimer = null;
|
||||
this._startLoad();
|
||||
if (this.op.autoStart) {
|
||||
this.play();
|
||||
} else {
|
||||
this._paused = true;
|
||||
}
|
||||
}
|
||||
|
||||
ZipImagePlayer.prototype = {
|
||||
_trailerBytes: 30000,
|
||||
_failed: false,
|
||||
_mkerr: function(msg) {
|
||||
var _this = this;
|
||||
return function() {
|
||||
_this._error(msg);
|
||||
}
|
||||
},
|
||||
_error: function(msg) {
|
||||
this._failed = true;
|
||||
throw Error("ZipImagePlayer error: " + msg);
|
||||
},
|
||||
_debugLog: function(msg) {
|
||||
if (this.op.debug) {
|
||||
console.log(msg);
|
||||
}
|
||||
},
|
||||
_load: function(offset, length, callback) {
|
||||
var _this = this;
|
||||
// Unfortunately JQuery doesn't support ArrayBuffer XHR
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.addEventListener("load", function(ev) {
|
||||
if (_this._dead) {
|
||||
return;
|
||||
}
|
||||
_this._debugLog("Load: " + offset + " " + length + " status=" +
|
||||
xhr.status);
|
||||
if (xhr.status == 200) {
|
||||
_this._debugLog("Range disabled or unsupported, complete load");
|
||||
offset = 0;
|
||||
length = xhr.response.byteLength;
|
||||
_this._len = length;
|
||||
_this._buf = xhr.response;
|
||||
_this._bytes = new _this._Uint8Array(_this._buf);
|
||||
} else {
|
||||
if (xhr.status != 206) {
|
||||
_this._error("Unexpected HTTP status " + xhr.status);
|
||||
}
|
||||
if (xhr.response.byteLength != length) {
|
||||
_this._error("Unexpected length " +
|
||||
xhr.response.byteLength +
|
||||
" (expected " + length + ")");
|
||||
}
|
||||
_this._bytes.set(new _this._Uint8Array(xhr.response), offset);
|
||||
}
|
||||
if (callback) {
|
||||
callback.apply(_this, [offset, length]);
|
||||
}
|
||||
}, false);
|
||||
xhr.addEventListener("error", this._mkerr("Fetch failed"), false);
|
||||
xhr.open("GET", this.op.source);
|
||||
xhr.responseType = "arraybuffer";
|
||||
if (offset != null && length != null) {
|
||||
var end = offset + length;
|
||||
xhr.setRequestHeader("Range", "bytes=" + offset + "-" + (end - 1));
|
||||
if (this._isSafari) {
|
||||
// Range request caching is broken in Safari
|
||||
// https://bugs.webkit.org/show_bug.cgi?id=82672
|
||||
xhr.setRequestHeader("Cache-control", "no-cache");
|
||||
xhr.setRequestHeader("If-None-Match", Math.random().toString());
|
||||
}
|
||||
}
|
||||
/*this._debugLog("Load: " + offset + " " + length);*/
|
||||
xhr.send();
|
||||
},
|
||||
_startLoad: function() {
|
||||
var _this = this;
|
||||
if (!this.op.source) {
|
||||
// Unpacked mode (individiual frame URLs) - just load the frames.
|
||||
this._loadNextFrame();
|
||||
return;
|
||||
}
|
||||
$.ajax({
|
||||
url: this.op.source,
|
||||
type: "HEAD"
|
||||
}).done(function(data, status, xhr) {
|
||||
if (_this._dead) {
|
||||
return;
|
||||
}
|
||||
_this._pHead = 0;
|
||||
_this._pNextHead = 0;
|
||||
_this._pFetch = 0;
|
||||
var len = parseInt(xhr.getResponseHeader("Content-Length"));
|
||||
if (!len) {
|
||||
_this._debugLog("HEAD request failed: invalid file length.");
|
||||
_this._debugLog("Falling back to full file mode.");
|
||||
_this._load(null, null, function(off, len) {
|
||||
_this._pTail = 0;
|
||||
_this._pHead = len;
|
||||
_this._findCentralDirectory();
|
||||
});
|
||||
return;
|
||||
}
|
||||
_this._debugLog("Len: " + len);
|
||||
_this._len = len;
|
||||
_this._buf = new _this._ArrayBuffer(len);
|
||||
_this._bytes = new _this._Uint8Array(_this._buf);
|
||||
var off = len - _this._trailerBytes;
|
||||
if (off < 0) {
|
||||
off = 0;
|
||||
}
|
||||
_this._pTail = len;
|
||||
_this._load(off, len - off, function(off, len) {
|
||||
_this._pTail = off;
|
||||
_this._findCentralDirectory();
|
||||
});
|
||||
}).fail(this._mkerr("Length fetch failed"));
|
||||
},
|
||||
_findCentralDirectory: function() {
|
||||
// No support for ZIP file comment
|
||||
var dv = new this._DataView(this._buf, this._len - 22, 22);
|
||||
if (dv.getUint32(0, true) != 0x06054b50) {
|
||||
this._error("End of Central Directory signature not found");
|
||||
}
|
||||
var cd_count = dv.getUint16(10, true);
|
||||
var cd_size = dv.getUint32(12, true);
|
||||
var cd_off = dv.getUint32(16, true);
|
||||
if (cd_off < this._pTail) {
|
||||
this._load(cd_off, this._pTail - cd_off, function() {
|
||||
this._pTail = cd_off;
|
||||
this._readCentralDirectory(cd_off, cd_size, cd_count);
|
||||
});
|
||||
} else {
|
||||
this._readCentralDirectory(cd_off, cd_size, cd_count);
|
||||
}
|
||||
},
|
||||
_readCentralDirectory: function(offset, size, count) {
|
||||
var dv = new this._DataView(this._buf, offset, size);
|
||||
var p = 0;
|
||||
for (var i = 0; i < count; i++ ) {
|
||||
if (dv.getUint32(p, true) != 0x02014b50) {
|
||||
this._error("Invalid Central Directory signature");
|
||||
}
|
||||
var compMethod = dv.getUint16(p + 10, true);
|
||||
var uncompSize = dv.getUint32(p + 24, true);
|
||||
var nameLen = dv.getUint16(p + 28, true);
|
||||
var extraLen = dv.getUint16(p + 30, true);
|
||||
var cmtLen = dv.getUint16(p + 32, true);
|
||||
var off = dv.getUint32(p + 42, true);
|
||||
if (compMethod != 0) {
|
||||
this._error("Unsupported compression method");
|
||||
}
|
||||
p += 46;
|
||||
var nameView = new this._Uint8Array(this._buf, offset + p, nameLen);
|
||||
var name = "";
|
||||
for (var j = 0; j < nameLen; j++) {
|
||||
name += String.fromCharCode(nameView[j]);
|
||||
}
|
||||
p += nameLen + extraLen + cmtLen;
|
||||
/*this._debugLog("File: " + name + " (" + uncompSize +
|
||||
" bytes @ " + off + ")");*/
|
||||
this._files[name] = {off: off, len: uncompSize};
|
||||
}
|
||||
// Two outstanding fetches at any given time.
|
||||
// Note: the implementation does not support more than two.
|
||||
if (this._pHead >= this._pTail) {
|
||||
this._pHead = this._len;
|
||||
$(this).triggerHandler("loadProgress", [this._pHead / this._len]);
|
||||
this._loadNextFrame();
|
||||
} else {
|
||||
this._loadNextChunk();
|
||||
this._loadNextChunk();
|
||||
}
|
||||
},
|
||||
_loadNextChunk: function() {
|
||||
if (this._pFetch >= this._pTail) {
|
||||
return;
|
||||
}
|
||||
var off = this._pFetch;
|
||||
var len = this.op.chunkSize;
|
||||
if (this._pFetch + len > this._pTail) {
|
||||
len = this._pTail - this._pFetch;
|
||||
}
|
||||
this._pFetch += len;
|
||||
this._load(off, len, function() {
|
||||
if (off == this._pHead) {
|
||||
if (this._pNextHead) {
|
||||
this._pHead = this._pNextHead;
|
||||
this._pNextHead = 0;
|
||||
} else {
|
||||
this._pHead = off + len;
|
||||
}
|
||||
if (this._pHead >= this._pTail) {
|
||||
this._pHead = this._len;
|
||||
}
|
||||
/*this._debugLog("New pHead: " + this._pHead);*/
|
||||
$(this).triggerHandler("loadProgress",
|
||||
[this._pHead / this._len]);
|
||||
if (!this._loadTimer) {
|
||||
this._loadNextFrame();
|
||||
}
|
||||
} else {
|
||||
this._pNextHead = off + len;
|
||||
}
|
||||
this._loadNextChunk();
|
||||
});
|
||||
},
|
||||
_fileDataStart: function(offset) {
|
||||
var dv = new DataView(this._buf, offset, 30);
|
||||
var nameLen = dv.getUint16(26, true);
|
||||
var extraLen = dv.getUint16(28, true);
|
||||
return offset + 30 + nameLen + extraLen;
|
||||
},
|
||||
_isFileAvailable: function(name) {
|
||||
var info = this._files[name];
|
||||
if (!info) {
|
||||
this._error("File " + name + " not found in ZIP");
|
||||
}
|
||||
if (this._pHead < (info.off + 30)) {
|
||||
return false;
|
||||
}
|
||||
return this._pHead >= (this._fileDataStart(info.off) + info.len);
|
||||
},
|
||||
_loadNextFrame: function() {
|
||||
if (this._dead) {
|
||||
return;
|
||||
}
|
||||
var frame = this._loadFrame;
|
||||
if (frame >= this._frameCount) {
|
||||
return;
|
||||
}
|
||||
var meta = this.op.metadata.frames[frame];
|
||||
if (!this.op.source) {
|
||||
// Unpacked mode (individiual frame URLs)
|
||||
this._loadFrame += 1;
|
||||
this._loadImage(frame, meta.file, false);
|
||||
return;
|
||||
}
|
||||
if (!this._isFileAvailable(meta.file)) {
|
||||
return;
|
||||
}
|
||||
this._loadFrame += 1;
|
||||
var off = this._fileDataStart(this._files[meta.file].off);
|
||||
var end = off + this._files[meta.file].len;
|
||||
var url;
|
||||
var mime_type = this.op.metadata.mime_type || "image/png";
|
||||
if (this._URL) {
|
||||
var slice;
|
||||
if (!this._buf.slice) {
|
||||
slice = new this._ArrayBuffer(this._files[meta.file].len);
|
||||
var view = new this._Uint8Array(slice);
|
||||
view.set(this._bytes.subarray(off, end));
|
||||
} else {
|
||||
slice = this._buf.slice(off, end);
|
||||
}
|
||||
var blob;
|
||||
try {
|
||||
blob = new this._Blob([slice], {type: mime_type});
|
||||
}
|
||||
catch (err) {
|
||||
this._debugLog("Blob constructor failed. Trying BlobBuilder..."
|
||||
+ " (" + err.message + ")");
|
||||
var bb = new this._BlobBuilder();
|
||||
bb.append(slice);
|
||||
blob = bb.getBlob();
|
||||
}
|
||||
/*_this._debugLog("Loading " + meta.file + " to frame " + frame);*/
|
||||
url = this._URL.createObjectURL(blob);
|
||||
this._loadImage(frame, url, true);
|
||||
} else {
|
||||
url = ("data:" + mime_type + ";base64,"
|
||||
+ base64ArrayBuffer(this._buf, off, end - off));
|
||||
this._loadImage(frame, url, false);
|
||||
}
|
||||
},
|
||||
_loadImage: function(frame, url, isBlob) {
|
||||
var _this = this;
|
||||
var image = new Image();
|
||||
var meta = this.op.metadata.frames[frame];
|
||||
image.addEventListener('load', function() {
|
||||
_this._debugLog("Loaded " + meta.file + " to frame " + frame);
|
||||
if (isBlob) {
|
||||
_this._URL.revokeObjectURL(url);
|
||||
}
|
||||
if (_this._dead) {
|
||||
return;
|
||||
}
|
||||
_this._frameImages[frame] = image;
|
||||
$(_this).triggerHandler("frameLoaded", frame);
|
||||
if (_this._loadingState == 0) {
|
||||
_this._displayFrame.apply(_this);
|
||||
}
|
||||
if (frame >= (_this._frameCount - 1)) {
|
||||
_this._setLoadingState(2);
|
||||
_this._buf = null;
|
||||
_this._bytes = null;
|
||||
} else {
|
||||
if (!_this._maxLoadAhead ||
|
||||
(frame - _this._frame) < _this._maxLoadAhead) {
|
||||
_this._loadNextFrame();
|
||||
} else if (!_this._loadTimer) {
|
||||
_this._loadTimer = setTimeout(function() {
|
||||
_this._loadTimer = null;
|
||||
_this._loadNextFrame();
|
||||
}, 200);
|
||||
}
|
||||
}
|
||||
});
|
||||
image.src = url;
|
||||
},
|
||||
_setLoadingState: function(state) {
|
||||
if (this._loadingState != state) {
|
||||
this._loadingState = state;
|
||||
$(this).triggerHandler("loadingStateChanged", [state]);
|
||||
}
|
||||
},
|
||||
_displayFrame: function() {
|
||||
if (this._dead) {
|
||||
return;
|
||||
}
|
||||
var _this = this;
|
||||
var meta = this.op.metadata.frames[this._frame];
|
||||
this._debugLog("Displaying frame: " + this._frame + " " + meta.file);
|
||||
var image = this._frameImages[this._frame];
|
||||
if (!image) {
|
||||
this._debugLog("Image not available!");
|
||||
this._setLoadingState(0);
|
||||
return;
|
||||
}
|
||||
if (this._loadingState != 2) {
|
||||
this._setLoadingState(1);
|
||||
}
|
||||
if (this.op.autosize) {
|
||||
if (this._context.canvas.width != image.width || this._context.canvas.height != image.height) {
|
||||
// make the canvas autosize itself according to the images drawn on it
|
||||
// should set it once, since we don't have variable sized frames
|
||||
this._context.canvas.width = image.width;
|
||||
this._context.canvas.height = image.height;
|
||||
}
|
||||
};
|
||||
this._context.clearRect(0, 0, this.op.canvas.width,
|
||||
this.op.canvas.height);
|
||||
this._context.drawImage(image, 0, 0);
|
||||
$(this).triggerHandler("frame", this._frame);
|
||||
if (!this._paused) {
|
||||
this._timer = setTimeout(function() {
|
||||
_this._timer = null;
|
||||
_this._nextFrame.apply(_this);
|
||||
}, meta.delay);
|
||||
}
|
||||
},
|
||||
_nextFrame: function(frame) {
|
||||
if (this._frame >= (this._frameCount - 1)) {
|
||||
if (this.op.loop) {
|
||||
this._frame = 0;
|
||||
} else {
|
||||
this.pause();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
this._frame += 1;
|
||||
}
|
||||
this._displayFrame();
|
||||
},
|
||||
play: function() {
|
||||
if (this._dead) {
|
||||
return;
|
||||
}
|
||||
if (this._paused) {
|
||||
$(this).triggerHandler("play", [this._frame]);
|
||||
this._paused = false;
|
||||
this._displayFrame();
|
||||
}
|
||||
},
|
||||
pause: function() {
|
||||
if (this._dead) {
|
||||
return;
|
||||
}
|
||||
if (!this._paused) {
|
||||
if (this._timer) {
|
||||
clearTimeout(this._timer);
|
||||
}
|
||||
this._paused = true;
|
||||
$(this).triggerHandler("pause", [this._frame]);
|
||||
}
|
||||
},
|
||||
rewind: function() {
|
||||
if (this._dead) {
|
||||
return;
|
||||
}
|
||||
this._frame = 0;
|
||||
if (this._timer) {
|
||||
clearTimeout(this._timer);
|
||||
}
|
||||
this._displayFrame();
|
||||
},
|
||||
stop: function() {
|
||||
this._debugLog("Stopped!");
|
||||
this._dead = true;
|
||||
if (this._timer) {
|
||||
clearTimeout(this._timer);
|
||||
}
|
||||
if (this._loadTimer) {
|
||||
clearTimeout(this._loadTimer);
|
||||
}
|
||||
this._frameImages = null;
|
||||
this._buf = null;
|
||||
this._bytes = null;
|
||||
$(this).triggerHandler("stop");
|
||||
},
|
||||
getCurrentFrame: function() {
|
||||
return this._frame;
|
||||
},
|
||||
getLoadedFrames: function() {
|
||||
return this._frameImages.length;
|
||||
},
|
||||
getFrameCount: function() {
|
||||
return this._frameCount;
|
||||
},
|
||||
hasError: function() {
|
||||
return this._failed;
|
||||
}
|
||||
}
|
||||
281
app/javascript/vendor/stupidtable.js
vendored
Normal file
281
app/javascript/vendor/stupidtable.js
vendored
Normal file
@@ -0,0 +1,281 @@
|
||||
// Stupid jQuery table plugin.
|
||||
|
||||
(function($) {
|
||||
$.fn.stupidtable = function(sortFns) {
|
||||
return this.each(function() {
|
||||
var $table = $(this);
|
||||
sortFns = sortFns || {};
|
||||
sortFns = $.extend({}, $.fn.stupidtable.default_sort_fns, sortFns);
|
||||
$table.data('sortFns', sortFns);
|
||||
$table.stupidtable_build();
|
||||
|
||||
$table.on("click.stupidtable", "thead th", function() {
|
||||
$(this).stupidsort();
|
||||
});
|
||||
|
||||
// Sort th immediately if data-sort-onload="yes" is specified. Limit to
|
||||
// the first one found - only one default sort column makes sense anyway.
|
||||
var $th_onload_sort = $table.find("th[data-sort-onload=yes]").eq(0);
|
||||
$th_onload_sort.stupidsort();
|
||||
});
|
||||
};
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Default settings
|
||||
// ------------------------------------------------------------------
|
||||
$.fn.stupidtable.default_settings = {
|
||||
should_redraw: function(sort_info){
|
||||
return true;
|
||||
},
|
||||
will_manually_build_table: false
|
||||
};
|
||||
$.fn.stupidtable.dir = {ASC: "asc", DESC: "desc"};
|
||||
$.fn.stupidtable.default_sort_fns = {
|
||||
"int": function(a, b) {
|
||||
return parseInt(a, 10) - parseInt(b, 10);
|
||||
},
|
||||
"float": function(a, b) {
|
||||
return parseFloat(a) - parseFloat(b);
|
||||
},
|
||||
"string": function(a, b) {
|
||||
return a.toString().localeCompare(b.toString());
|
||||
},
|
||||
"string-ins": function(a, b) {
|
||||
a = a.toString().toLocaleLowerCase();
|
||||
b = b.toString().toLocaleLowerCase();
|
||||
return a.localeCompare(b);
|
||||
}
|
||||
};
|
||||
|
||||
// Allow specification of settings on a per-table basis. Call on a table
|
||||
// jquery object. Call *before* calling .stuidtable();
|
||||
$.fn.stupidtable_settings = function(settings) {
|
||||
return this.each(function() {
|
||||
var $table = $(this);
|
||||
var final_settings = $.extend({}, $.fn.stupidtable.default_settings, settings);
|
||||
$table.stupidtable.settings = final_settings;
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// Expects $("#mytable").stupidtable() to have already been called.
|
||||
// Call on a table header.
|
||||
$.fn.stupidsort = function(force_direction){
|
||||
var $this_th = $(this);
|
||||
var datatype = $this_th.data("sort") || null;
|
||||
|
||||
// No datatype? Nothing to do.
|
||||
if (datatype === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var dir = $.fn.stupidtable.dir;
|
||||
var $table = $this_th.closest("table");
|
||||
|
||||
var sort_info = {
|
||||
$th: $this_th,
|
||||
$table: $table,
|
||||
datatype: datatype
|
||||
};
|
||||
|
||||
|
||||
// Bring in default settings if none provided
|
||||
if(!$table.stupidtable.settings){
|
||||
$table.stupidtable.settings = $.extend({}, $.fn.stupidtable.default_settings);
|
||||
}
|
||||
|
||||
sort_info.compare_fn = $table.data('sortFns')[datatype];
|
||||
sort_info.th_index = calculateTHIndex(sort_info);
|
||||
sort_info.sort_dir = calculateSortDir(force_direction, sort_info);
|
||||
|
||||
$this_th.data("sort-dir", sort_info.sort_dir);
|
||||
$table.trigger("beforetablesort", {column: sort_info.th_index, direction: sort_info.sort_dir, $th: $this_th});
|
||||
|
||||
// More reliable method of forcing a redraw
|
||||
$table.css("display");
|
||||
|
||||
// Run sorting asynchronously on a timout to force browser redraw after
|
||||
// `beforetablesort` callback. Also avoids locking up the browser too much.
|
||||
setTimeout(function() {
|
||||
if(!$table.stupidtable.settings.will_manually_build_table){
|
||||
$table.stupidtable_build();
|
||||
}
|
||||
var table_structure = sortTable(sort_info);
|
||||
var trs = getTableRowsFromTableStructure(table_structure, sort_info);
|
||||
|
||||
if(!$table.stupidtable.settings.should_redraw(sort_info)){
|
||||
return;
|
||||
}
|
||||
$table.children("tbody").append(trs);
|
||||
|
||||
updateElementData(sort_info);
|
||||
$table.trigger("aftertablesort", {column: sort_info.th_index, direction: sort_info.sort_dir, $th: $this_th});
|
||||
$table.css("display");
|
||||
|
||||
}, 10);
|
||||
return $this_th;
|
||||
};
|
||||
|
||||
// Call on a sortable td to update its value in the sort. This should be the
|
||||
// only mechanism used to update a cell's sort value. If your display value is
|
||||
// different from your sort value, use jQuery's .text() or .html() to update
|
||||
// the td contents, Assumes stupidtable has already been called for the table.
|
||||
$.fn.updateSortVal = function(new_sort_val){
|
||||
var $this_td = $(this);
|
||||
if($this_td.is('[data-sort-value]')){
|
||||
// For visual consistency with the .data cache
|
||||
$this_td.attr('data-sort-value', new_sort_val);
|
||||
}
|
||||
$this_td.data("sort-value", new_sort_val);
|
||||
return $this_td;
|
||||
};
|
||||
|
||||
|
||||
$.fn.stupidtable_build = function(){
|
||||
return this.each(function() {
|
||||
var $table = $(this);
|
||||
var table_structure = [];
|
||||
var trs = $table.children("tbody").children("tr");
|
||||
trs.each(function(index,tr) {
|
||||
|
||||
// ====================================================================
|
||||
// Transfer to using internal table structure
|
||||
// ====================================================================
|
||||
var ele = {
|
||||
$tr: $(tr),
|
||||
columns: [],
|
||||
index: index
|
||||
};
|
||||
|
||||
$(tr).children('td').each(function(idx, td){
|
||||
var sort_val = $(td).data("sort-value");
|
||||
|
||||
// Store and read from the .data cache for display text only sorts
|
||||
// instead of looking through the DOM every time
|
||||
if(typeof(sort_val) === "undefined"){
|
||||
var txt = $(td).text();
|
||||
$(td).data('sort-value', txt);
|
||||
sort_val = txt;
|
||||
}
|
||||
ele.columns.push(sort_val);
|
||||
});
|
||||
table_structure.push(ele);
|
||||
});
|
||||
$table.data('stupidsort_internaltable', table_structure);
|
||||
});
|
||||
};
|
||||
|
||||
// ====================================================================
|
||||
// Private functions
|
||||
// ====================================================================
|
||||
var sortTable = function(sort_info){
|
||||
var table_structure = sort_info.$table.data('stupidsort_internaltable');
|
||||
var th_index = sort_info.th_index;
|
||||
var $th = sort_info.$th;
|
||||
|
||||
var multicolumn_target_str = $th.data('sort-multicolumn');
|
||||
var multicolumn_targets;
|
||||
if(multicolumn_target_str){
|
||||
multicolumn_targets = multicolumn_target_str.split(',');
|
||||
}
|
||||
else{
|
||||
multicolumn_targets = [];
|
||||
}
|
||||
var multicolumn_th_targets = $.map(multicolumn_targets, function(identifier, i){
|
||||
return get_th(sort_info.$table, identifier);
|
||||
});
|
||||
|
||||
table_structure.sort(function(e1, e2){
|
||||
var multicolumns = multicolumn_th_targets.slice(0); // shallow copy
|
||||
var diff = sort_info.compare_fn(e1.columns[th_index], e2.columns[th_index]);
|
||||
while(diff === 0 && multicolumns.length){
|
||||
var multicolumn = multicolumns[0];
|
||||
var datatype = multicolumn.$e.data("sort");
|
||||
var multiCloumnSortMethod = sort_info.$table.data('sortFns')[datatype];
|
||||
diff = multiCloumnSortMethod(e1.columns[multicolumn.index], e2.columns[multicolumn.index]);
|
||||
multicolumns.shift();
|
||||
}
|
||||
// Sort by position in the table if values are the same. This enforces a
|
||||
// stable sort across all browsers. See https://bugs.chromium.org/p/v8/issues/detail?id=90
|
||||
if (diff === 0)
|
||||
return e1.index - e2.index;
|
||||
else
|
||||
return diff;
|
||||
|
||||
});
|
||||
|
||||
if (sort_info.sort_dir != $.fn.stupidtable.dir.ASC){
|
||||
table_structure.reverse();
|
||||
}
|
||||
return table_structure;
|
||||
};
|
||||
|
||||
var get_th = function($table, identifier){
|
||||
// identifier can be a th id or a th index number;
|
||||
var $table_ths = $table.find('th');
|
||||
var index = parseInt(identifier, 10);
|
||||
var $th;
|
||||
if(!index && index !== 0){
|
||||
$th = $table_ths.siblings('#' + identifier);
|
||||
index = $table_ths.index($th);
|
||||
}
|
||||
else{
|
||||
$th = $table_ths.eq(index);
|
||||
}
|
||||
return {index: index, $e: $th};
|
||||
};
|
||||
|
||||
var getTableRowsFromTableStructure = function(table_structure, sort_info){
|
||||
// Gather individual column for callbacks
|
||||
var column = $.map(table_structure, function(ele, i){
|
||||
return [[ele.columns[sort_info.th_index], ele.$tr, i]];
|
||||
});
|
||||
|
||||
/* Side effect */
|
||||
sort_info.column = column;
|
||||
|
||||
// Replace the content of tbody with the sorted rows. Strangely
|
||||
// enough, .append accomplishes this for us.
|
||||
return $.map(table_structure, function(ele) { return ele.$tr; });
|
||||
|
||||
};
|
||||
|
||||
var updateElementData = function(sort_info){
|
||||
var $table = sort_info.$table;
|
||||
var $this_th = sort_info.$th;
|
||||
var sort_dir = $this_th.data('sort-dir');
|
||||
var th_index = sort_info.th_index;
|
||||
|
||||
|
||||
// Reset siblings
|
||||
$table.find("th").data("sort-dir", null).removeClass("sorting-desc sorting-asc");
|
||||
$this_th.data("sort-dir", sort_dir).addClass("sorting-"+sort_dir);
|
||||
};
|
||||
|
||||
var calculateSortDir = function(force_direction, sort_info){
|
||||
var sort_dir;
|
||||
var $this_th = sort_info.$th;
|
||||
var dir = $.fn.stupidtable.dir;
|
||||
|
||||
if(!!force_direction){
|
||||
sort_dir = force_direction;
|
||||
}
|
||||
else{
|
||||
sort_dir = force_direction || $this_th.data("sort-default") || dir.ASC;
|
||||
if ($this_th.data("sort-dir"))
|
||||
sort_dir = $this_th.data("sort-dir") === dir.ASC ? dir.DESC : dir.ASC;
|
||||
}
|
||||
return sort_dir;
|
||||
};
|
||||
|
||||
var calculateTHIndex = function(sort_info){
|
||||
var th_index = 0;
|
||||
var base_index = sort_info.$th.index();
|
||||
sort_info.$th.parents("tr").find("th").slice(0, base_index).each(function() {
|
||||
var cols = $(this).attr("colspan") || 1;
|
||||
th_index += parseInt(cols,10);
|
||||
});
|
||||
return th_index;
|
||||
};
|
||||
|
||||
})(jQuery);
|
||||
Reference in New Issue
Block a user