1/** 2 * Copyright �� Dave Perrett and Malcolm Jarvis 3 * 4 * This file is licensed under the GPLv2. 5 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html 6 */ 7 8var transmission, 9 dialog, 10 isMobileDevice = RegExp("(iPhone|iPod|Android)").test(navigator.userAgent), 11 scroll_timeout; 12 13if (!Array.indexOf){ 14 Array.prototype.indexOf = function(obj){ 15 var i, len; 16 for (i=0, len=this.length; i<len; i++) 17 if (this[i]==obj) 18 return i; 19 return -1; 20 } 21} 22 23// http://forum.jquery.com/topic/combining-ui-dialog-and-tabs 24$.fn.tabbedDialog = function (dialog_opts) { 25 this.tabs({selected: 0}); 26 this.dialog(dialog_opts); 27 this.find('.ui-tab-dialog-close').append(this.parent().find('.ui-dialog-titlebar-close')); 28 this.find('.ui-tab-dialog-close').css({'position':'absolute','right':'0', 'top':'16px'}); 29 this.find('.ui-tab-dialog-close > a').css({'float':'none','padding':'0'}); 30 var tabul = this.find('ul:first'); 31 this.parent().addClass('ui-tabs').prepend(tabul).draggable('option','handle',tabul); 32 this.siblings('.ui-dialog-titlebar').remove(); 33 tabul.addClass('ui-dialog-titlebar'); 34} 35 36$(document).ready(function() { 37 38 // IE8 and below don���t support ES5 Date.now() 39 if (!Date.now) { 40 Date.now = function() { 41 return +new Date(); 42 }; 43 } 44 45 // IE specific fixes here 46 if ($.browser.msie) { 47 try { 48 document.execCommand("BackgroundImageCache", false, true); 49 } catch(err) {} 50 $('.dialog_container').css('height',$(window).height()+'px'); 51 } 52 53 if ($.browser.safari) { 54 // Move search field's margin down for the styled input 55 $('#torrent_search').css('margin-top', 3); 56 } 57 if (isMobileDevice){ 58 window.onload = function(){ setTimeout(function() { window.scrollTo(0,1); },500); }; 59 window.onorientationchange = function(){ setTimeout(function() { window.scrollTo(0,1); },100); }; 60 if (window.navigator.standalone) 61 // Fix min height for isMobileDevice when run in full screen mode from home screen 62 // so the footer appears in the right place 63 $('body div#torrent_container').css('min-height', '338px'); 64 $("label[for=torrent_upload_url]").text("URL: "); 65 } else { 66 // Fix for non-Safari-3 browsers: dark borders to replace shadows. 67 // Opera messes up the menu if we use a border on .trans_menu 68 // div.outerbox so use ul instead 69 $('.trans_menu ul, div#jqContextMenu, div.dialog_container div.dialog_window').css('border', '1px solid #777'); 70 // and this kills the border we used to have 71 $('.trans_menu div.outerbox').css('border', 'none'); 72 } 73 74 // Initialise the dialog controller 75 dialog = new Dialog(); 76 77 // Initialise the main Transmission controller 78 transmission = new Transmission(); 79}); 80 81/** 82 * Checks to see if the content actually changed before poking the DOM. 83 */ 84function setInnerHTML(e, html) 85{ 86 if (!e) 87 return; 88 89 /* innerHTML is listed as a string, but the browser seems to change it. 90 * For example, "∞" gets changed to "���" somewhere down the line. 91 * So, let's use an arbitrary different field to test our state... */ 92 if (e.currentHTML != html) 93 { 94 e.currentHTML = html; 95 e.innerHTML = html; 96 } 97}; 98 99function sanitizeText(text) 100{ 101 return text.replace(/</g, "<").replace(/>/g, ">"); 102}; 103 104/** 105 * Many of our text changes are triggered by periodic refreshes 106 * on torrents whose state hasn't changed since the last update, 107 * so see if the text actually changed before poking the DOM. 108 */ 109function setTextContent(e, text) 110{ 111 if (e && (e.textContent != text)) 112 e.textContent = text; 113}; 114 115/* 116 * Given a numerator and denominator, return a ratio string 117 */ 118Math.ratio = function(numerator, denominator) { 119 var result = Math.floor(100 * numerator / denominator) / 100; 120 121 // check for special cases 122 if (result==Number.POSITIVE_INFINITY || result==Number.NEGATIVE_INFINITY) result = -2; 123 else if (isNaN(result)) result = -1; 124 125 return result; 126}; 127 128/** 129 * Round a string of a number to a specified number of decimal places 130 */ 131Number.prototype.toTruncFixed = function(place) { 132 var ret = Math.floor(this * Math.pow (10, place)) / Math.pow(10, place); 133 return ret.toFixed(place); 134} 135 136Number.prototype.toStringWithCommas = function() { 137 return this.toString().replace(/\B(?=(?:\d{3})+(?!\d))/g, ","); 138} 139 140 141/* 142 * Trim whitespace from a string 143 */ 144String.prototype.trim = function () { 145 return this.replace(/^\s*/, "").replace(/\s*$/, ""); 146} 147 148/*** 149**** Preferences 150***/ 151 152function Prefs() { } 153Prefs.prototype = { }; 154 155Prefs._RefreshRate = 'refresh_rate'; 156 157Prefs._FilterMode = 'filter'; 158Prefs._FilterAll = 'all'; 159Prefs._FilterActive = 'active'; 160Prefs._FilterSeeding = 'seeding'; 161Prefs._FilterDownloading = 'downloading'; 162Prefs._FilterPaused = 'paused'; 163Prefs._FilterFinished = 'finished'; 164 165Prefs._SortDirection = 'sort_direction'; 166Prefs._SortAscending = 'ascending'; 167Prefs._SortDescending = 'descending'; 168 169Prefs._SortMethod = 'sort_method'; 170Prefs._SortByAge = 'age'; 171Prefs._SortByActivity = 'activity'; 172Prefs._SortByName = 'name'; 173Prefs._SortByQueue = 'queue_order'; 174Prefs._SortBySize = 'size'; 175Prefs._SortByProgress = 'percent_completed'; 176Prefs._SortByRatio = 'ratio'; 177Prefs._SortByState = 'state'; 178 179Prefs._CompactDisplayState= 'compact_display_state'; 180 181Prefs._Defaults = 182{ 183 'filter': 'all', 184 'refresh_rate' : 5, 185 'sort_direction': 'ascending', 186 'sort_method': 'name', 187 'turtle-state' : false, 188 'compact_display_state' : false 189}; 190 191/* 192 * Set a preference option 193 */ 194Prefs.setValue = function(key, val) 195{ 196 if (!(key in Prefs._Defaults)) 197 console.warn("unrecognized preference key '%s'", key); 198 199 var days = 30; 200 var date = new Date(); 201 date.setTime(date.getTime()+(days*24*60*60*1000)); 202 document.cookie = key+"="+val+"; expires="+date.toGMTString()+"; path=/"; 203}; 204 205/** 206 * Get a preference option 207 * 208 * @param key the preference's key 209 * @param fallback if the option isn't set, return this instead 210 */ 211Prefs.getValue = function(key, fallback) 212{ 213 var val; 214 215 if (!(key in Prefs._Defaults)) 216 console.warn("unrecognized preference key '%s'", key); 217 218 var lines = document.cookie.split(';'); 219 for (var i=0, len=lines.length; !val && i<len; ++i) { 220 var line = lines[i].trim(); 221 var delim = line.indexOf('='); 222 if ((delim === key.length) && line.indexOf(key) === 0) 223 val = line.substring(delim + 1); 224 } 225 226 // FIXME: we support strings and booleans... add number support too? 227 if (!val) val = fallback; 228 else if (val === 'true') val = true; 229 else if (val === 'false') val = false; 230 return val; 231}; 232 233/** 234 * Get an object with all the Clutch preferences set 235 * 236 * @pararm o object to be populated (optional) 237 */ 238Prefs.getClutchPrefs = function(o) 239{ 240 if (!o) 241 o = { }; 242 for (var key in Prefs._Defaults) 243 o[key] = Prefs.getValue(key, Prefs._Defaults[key]); 244 return o; 245}; 246 247 248// forceNumeric() plug-in implementation 249jQuery.fn.forceNumeric = function () { 250 return this.each(function () { 251 $(this).keydown(function (e) { 252 var key = e.which || e.keyCode; 253 return !e.shiftKey && !e.altKey && !e.ctrlKey && 254 // numbers 255 key >= 48 && key <= 57 || 256 // Numeric keypad 257 key >= 96 && key <= 105 || 258 // comma, period and minus, . on keypad 259 key === 190 || key === 188 || key === 109 || key === 110 || 260 // Backspace and Tab and Enter 261 key === 8 || key === 9 || key === 13 || 262 // Home and End 263 key === 35 || key === 36 || 264 // left and right arrows 265 key === 37 || key === 39 || 266 // Del and Ins 267 key === 46 || key === 45; 268 }); 269 }); 270} 271 272 273/** 274 * http://blog.stevenlevithan.com/archives/parseuri 275 * 276 * parseUri 1.2.2 277 * (c) Steven Levithan <stevenlevithan.com> 278 * MIT License 279 */ 280function parseUri (str) { 281 var o = parseUri.options, 282 m = o.parser[o.strictMode ? "strict" : "loose"].exec(str), 283 uri = {}, 284 i = 14; 285 286 while (i--) uri[o.key[i]] = m[i] || ""; 287 288 uri[o.q.name] = {}; 289 uri[o.key[12]].replace(o.q.parser, function ($0, $1, $2) { 290 if ($1) uri[o.q.name][$1] = $2; 291 }); 292 293 return uri; 294}; 295 296parseUri.options = { 297 strictMode: false, 298 key: ["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"], 299 q: { 300 name: "queryKey", 301 parser: /(?:^|&)([^&=]*)=?([^&]*)/g 302 }, 303 parser: { 304 strict: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/, 305 loose: /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/ 306 } 307}; 308