1; //silence IE errors ... consider folding into log() or using Firebug lite 2try { console } catch (e) { console = { log: function () {} }; } 3 4O_O = { 5 com: {}, 6 utl: {}, 7 8 /*PHP-style utility functions*/ 9 isset: function (v) 10 { 11 return (typeof v == 'undefined') ? false : true; 12 }, 13 14 empty: function (obj) 15 { 16 if (obj === null) return true; 17 18 switch (typeof obj) { 19 case 'number': return (obj == 0); 20 case 'string': return (!obj || obj== ''); 21 case 'boolean': return obj !== true; 22 case 'undefined': return true; 23 case 'object': 24 if (is_array(obj)) { 25 for (var i in obj) return false; 26 return true; 27 } 28 case 'function': 29 default: return false; 30 } 31 }, 32 33 is_array: function (v) 34 { 35 if (v === null || typeof v === 'undefined') return false; 36 return v.constructor == Array; 37 }, 38 39 is_object: function (v) 40 { 41 if (v === null) return false; 42 return (typeof v == 'object'); 43 }, 44 45 is_function: function (v) 46 { 47 if (v === null) return false; 48 return (typeof v == 'function'); 49 }, 50 51 is_deferred: function (v) 52 { 53 return this.is_object(v) && isset(v.done); 54 }, 55 56 scopeC: function (fn, scope) 57 { 58 return function() { return fn.apply(scope, arguments); }; 59 }, 60 61 nvToAssoc: function (d) 62 { 63 var n, v; 64 var rA = {}; 65 for (var i in d) { 66 n = d[i].name; v = d[i].value; 67 if (isset(rA[n])) { 68 if (is_array(rA[n])) 69 rA[n].push(v); 70 else 71 rA[n] = [rA[n], v]; 72 } 73 else rA[n] = v; 74 } 75 return rA; 76 }, 77 78 obj: { 79 clone: function (obj, noRecursion) 80 { 81 if (!obj || typeof obj != 'object') return obj; 82 83 var newObj = (obj.constructor == Array) ? [] : {}; 84 85 for (var i in obj) 86 newObj[i] = noRecursion ? obj[i] : O_O.obj.clone(obj[i]); 87 88 return newObj; 89 }, 90 91 /*!\brief New object creation using argument array. 92 * \param obj object 93 * \param argA argument array 94 */ 95 create: function (obj, argA) 96 { 97 if (typeof obj === 'undefined') { 98 console.warn('obj:create', obj, argA); 99 } 100 var t = function (tA) { obj.apply(this, tA); }; 101 t.prototype = obj.prototype; 102 return new t(argA); 103 }, 104 105 /*!\brief Inherit class definition. This function must be run after the 106 * definition of the parent/child constructors and prototypes. 107 * The child constructor uses the Function.call(this, ...) 108 * method to call the parent constructor. 109 * \param parent parent class 110 * \param child child class 111 */ 112 inherit: function (parent, child) 113 { 114 if (!parent) 115 throw 'O_O.obj.inherit missing parent class' + parent; 116 var tmp = function () {}; 117 tmp.prototype = parent.prototype; 118 var old = function () {}; 119 old.prototype = child.prototype; 120 child.prototype = new tmp; 121 old.prototype = new old; 122 for (var i in old.prototype) child.prototype[i] = old.prototype[i]; 123 }, 124 125 del: function (obj) 126 { 127 if (O_O.is_array(obj) || O_O.is_object(obj)) { 128 for (var i in obj) this.del(obj) 129 } 130 131 obj = null; 132 }, 133 134 _cacheA: [], 135 136 cache: function (oName) 137 { 138 var a = O_O.obj._cacheA; 139 140 if (!this.isset(a[oName])) 141 a[oName] = O_O.mm.oNew(eval(oName)); 142 143 return a[oName]; 144 } 145 }, 146 147 str: { 148 /*!\brief Creates plural form of word 149 * \param n count or array to test against 150 * \param sing singular form (defaults to null) 151 * \param pl plural form (defaults to 's') 152 */ 153 pl: function (n, sing, pl) 154 { 155 if (!isset(sing)) sing = ''; 156 if (!isset(pl)) pl = 's'; 157 if (is_array(n)) n = n.length; 158 159 return (n == 1) ? sing : pl; 160 }, 161 162 /*!\brief universal whitespace trim 163 * \param str string to trim leading / trailing whitespace from 164 */ 165 trim: function (str) 166 { 167 return str.replace(/^\s\s*/, '').replace(/\s\s*$/, ''); 168 }, 169 170 /*!\brief universal whitespace trim 171 * \param str string to trim leading whitespace from 172 */ 173 ltrim: function (str) 174 { 175 return str.replace(/^\s\s*/, ''); 176 }, 177 178 /*!\brief universal whitespace right trim 179 * \param str string to trim trailing whitespace from 180 */ 181 rtrim: function (str) 182 { 183 return str.replace(/\s\s*$/, ''); 184 }, 185 186 // Taken from phpjs: http://phpjs.org/functions/rawurlencode/ 187 rawurlencode: function(str) { 188 str = (str+'').toString(); 189 return encodeURIComponent(str) 190 .replace(/!/g, '%21') 191 .replace(/'/g, '%27') 192 .replace(/\(/g, '%28') 193 .replace(/\)/g, '%29').replace(/\*/g, '%2A'); 194 }, 195 196 // Taken from MDN: https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/encodeURI 197 fixedEncodeURI: function(str) { 198 return encodeURI(str).replace(/%5B/g, '[').replace(/%5D/g, ']'); 199 }, 200 201 // Taken from phpjs: http://phpjs.org/functions/krsort/ 202 strtr: function(str, from, to) { 203 var fr = ''; 204 var i = 0; 205 var j = 0; 206 var lenStr = 0; 207 var lenFrom = 0; 208 var fromTypeStr = ''; 209 var toTypeStr = ''; 210 var istr = ''; 211 var tmpFrom = []; 212 var tmpTo = []; 213 var ret = ''; 214 var match = false;
215 216 // Received replace_pairs? 217 // Convert to normal from->to chars 218 if (typeof from === 'object') { 219 // Don't krsort for now since it's not implemented 220 //from = this.krsort(from); 221 222 for (fr in from) { 223 if (from.hasOwnProperty(fr)) { 224 tmpFrom.push(fr); 225 tmpTo.push(from[fr]); 226 } 227 } 228 229 from = tmpFrom; 230 to = tmpTo; 231 } 232 233 // Walk through subject and replace chars when needed 234 lenStr = str.length; 235 lenFrom = from.length; 236 fromTypeStr = typeof from === 'string'; 237 toTypeStr = typeof to === 'string'; 238 239 for (i = 0; i < lenStr; i++) { 240 match = false; 241 if (fromTypeStr) { 242 istr = str.charAt(i); 243 for (j = 0; j < lenFrom; j++) { 244 if (istr === from.charAt(j)) { 245 match = true; 246 break; 247 } 248 } 249 } else { 250 for (j = 0; j < lenFrom; j++) { 251 if (str.substr(i, from[j].length) === from[j]) { 252 match = true; 253 // Fast forward 254 i = (i + from[j].length) - 1; 255 break; 256 } 257 } 258 } 259 if (match) { 260 ret += toTypeStr ? to.charAt(j) : to[j]; 261 } else { 262 ret += str.charAt(i); 263 } 264 } 265 266 return ret; 267 }, 268 269 /*!\brief replaces non-alphas with -'s for SEO friendliness 270 * \note MUST mirror PHP macros.php dashify() function 271 * for consistency 272 * param str string to do replacement on 273 */ 274 dashify: function(str) { 275 var trA = {'\'': ''}; 276 277 trA["\xE1"] = "a"; trA["\xC1"] = "A"; 278 trA["\xE2"] = "a"; trA["\xC2"] = "A"; 279 trA["\xE0"] = "a"; trA["\xC0"] = "A"; 280 trA["\xE5"] = "a"; trA["\xC5"] = "A"; 281 trA["\xE3"] = "a"; trA["\xC3"] = "A"; 282 trA["\xC4"] = "A"; trA["\xE4"] = "a"; 283 trA["\xE7"] = "c"; trA["\xC7"] = "C"; 284 trA["\xD0"] = "E"; trA["\xF0"] = "e"; 285 trA["\xC9"] = "E"; trA["\xE9"] = "e"; 286 trA["\xCA"] = "E"; trA["\xC8"] = "E"; 287 trA["\xCB"] = "E"; trA["\xEB"] = "e"; 288 trA["\xCD"] = "I"; trA["\xED"] = "i"; 289 trA["\xCE"] = "I"; trA["\xEE"] = "i"; 290 trA["\xCC"] = "I"; trA["\xEC"] = "i"; 291 trA["\xCF"] = "I"; trA["\xEF"] = "i"; 292 trA["\xD1"] = "N"; trA["\xF1"] = "n"; 293 trA["\xD3"] = "O"; trA["\xE8"] = "e"; 294 trA["\xF3"] = "o"; trA["\xE6"] = "ae"; 295 trA["\xD4"] = "O"; trA["\xF4"] = "o"; 296 trA["\xF2"] = "o"; trA["\xD2"] = "O"; 297 trA["\xD8"] = "O"; trA["\xF5"] = "o"; 298 trA["\xF8"] = "o"; trA["\xD5"] = "O"; 299 trA["\xD6"] = "O"; trA["\xF6"] = "o"; 300 trA["\xFE"] = "t"; trA["\xDE"] = "T"; 301 trA["\xDA"] = "U"; trA["\xFA"] = "u"; 302 trA["\xDB"] = "U"; trA["\xFB"] = "u"; 303 trA["\xD9"] = "U"; trA["\xF9"] = "u"; 304 trA["\xDC"] = "U"; trA["\xFC"] = "u"; 305 trA["\xDD"] = "Y"; trA["\xFD"] = "y"; 306 trA["\xFF"] = "y"; 307 308 str = this.strtr(str, trA); 309 310 str = str.replace(/[^a-zA-Z0-9]+/g, '-'); 311 str = str.replace(/^[\-]+|(-$)+/g, ""); 312 if (str === '') str = '-'; 313 return this.fixedEncodeURI(str); 314 }, 315 316 /*!\brief replaces non-alphas with -'s for SEO friendliness and preserves file extension 317 * param str string to do replacement on 318 */ 319 dashifyFilename: function(str) { 320 var pos = str.lastIndexOf('.'); 321 if (pos === -1) return this.dashify(str); 322 323 var base = str.substr(0, pos); 324 var ext = str.substr(pos, str.length - pos + 1); 325 return this.dashify(base) + "." + this.dashify(ext); 326 } 327 }, 328 329 date: { 330 MO_STR2_A: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 331 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], 332 MO_STR_A: ['January', 'February', 'March', 'April', 'May', 'June', 333 'July', 'August', 'September', 'October', 'November', 334 'December'], 335 MO_NUM_A: ['01', '02', '03', '04', '05', '06', 336 '07', '08', '09', '10', '11', '12'], 337 338 STD_FMT: '%e %b %Y', 339 340 toDate: function (str) 341 { 342 var yr = str.substr(0, 4); 343 var mon = str.substr(5, 2) - 1; 344 var day = str.substr(8, 2); 345 var hr = str.substr(11, 2); 346 var min = str.substr(14, 2); 347 var sec = str.substr(17, 2); 348 // omit timezone so it won't screw up our app 349 //var tz = str.substr(20, 3); 350 351 return new Date(yr, mon, day, hr, min, sec); 352 }, 353 354 toString: function (d) { return d.strftime(this.STD_FMT); }, 355 356 normalize: function (d) 357 { 358 if (d) 359 return d.strftime('%F %T'); 360 else return ''; 361 }, 362 363 strNormalize: function (str) 364 { 365 var d = this.toDate(str); 366 return this.normalize(d); 367 }, 368 369 strFmt: function (str, fmt) 370 { 371 if (!fmt) fmt = this.STD_FMT; 372 if (!str || str == '') return str; 373 374 var date = this.toDate(str); 375 return date.strftime(fmt); 376 }, 377 378 compare: function (d1, d2, f_exact) 379 { 380 if (!f_exact && (this.toString(d1) == this.toString(d2))) 381 return 0; 382 else if (f_exact && (d1.valueOf() == d2.valueOf())) 383 return 0; 384 385 if (d1.valueOf() > d2.valueOf()) return 1; 386 else return -1; 387 }, 388 389 curTime: function () { return (new Date()).getTime(); } 390 }, 391 392 utf8: { 393 encode: function (str) 394 { 395 str = str.replace(/\r\n/g,"\n"); 396 var r = ""; 397 398 for (var n = 0; n < str.length; n++) { 399 var c = str.charCodeAt(n); 400 401 if (c < 128) { 402 r += String.fromCharCode(c); 403 } 404 else if((c > 127) && (c < 2048)) { 405 r += String.fromCharCode((c >> 6) | 192); 406 r += String.fromCharCode((c & 63) | 128); 407 } 408 else { 409 r += String.fromCharCode((c >> 12) | 224); 410 r += String.fromCharCode(((c >> 6) & 63) | 128); 411 r += String.fromCharCode((c & 63) | 128); 412 } 413 } 414 415 return r; 416 }, 417 418 decode : function (str) 419 { 420 var r = ""; 421 var i = 0; 422 var c = c1 = c2 = 0; 423 424 while ( i < str.length ) { 425 c = str.charCodeAt(i); 426 427 if (c < 128) { 428 r += String.fromCharCode(c); 429 i++; 430 } 431 else if((c > 191) && (c < 224)) { 432 c2 = str.charCodeAt(i+1); 433 r += String.fromCharCode(((c & 31) << 6) | (c2 & 63)); 434 i += 2; 435 } 436 else { 437 c2 = str.charCodeAt(i+1); 438 c3 = str.charCodeAt(i+2); 439 r += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63)); 440 i += 3; 441 } 442 } 443 444 return r; 445 } 446 }, 447 448 cookie: { 449 get: function (n) 450 { 451 var c = document.cookie, i, j; 452 n = escape(n); 453
454 if ((i = c.indexOf(n + '=')) < 0) return null; 455 456 i += n.length + 1; 457 458 if ((j = c.indexOf(';', i)) < 0) j = c.length; 459 460 return unescape(c.substring(i, j)); 461 }, 462 463 set: function (n, v, days) 464 { 465 var expStr = ''; 466 467 // set expiration 468 if (days && (days = parseInt(days, 10)) != NaN) { 469 var exp = new Date(); 470 exp.setDate(exp.getDate() + days); 471 expStr = '; expires=' + exp.toGMTString(); 472 } 473 document.cookie = n + '=' + escape(v) + expStr + '; path=/'; 474 } 475 }, 476 477 screen: { 478 size: function() 479 { 480 return { 481 width: window.screen.width, 482 height: window.screen.height 483 }; 484 }, 485 486 maxDim: function () 487 { 488 var sz = O_O.screen.size(); 489 var mult = isset(window.devicePixelRatio) ? 490 window.devicePixelRatio : 1; 491 492 return mult * Math.max(sz.width, sz.height); 493 } 494 }, 495 496 /** 497 * Simple templating borrowed from underscore.js 498 * @param str Template string to parse 499 * @param data 500 * (optional) data to evaluate with the template immediately. 501 * Nice for single-use templates, but should be avoided otherwise 502 * @return 503 * A "compiled" template in the form of a new function that takes 504 * one argument (a data object) and returns a string of that data 505 * parsed into the template. 506 * @throws 507 * Any number of javascript errors if data or function 508 * references required by the template do not exist. 509 */ 510 tpl: function(str, data) { 511 var c = O_O._tplSettings; 512 var tmpl = 'var __p=[],print=function(){__p.push.apply(__p,arguments);};' + 513 'with(obj||{}){__p.push(\'' + 514 str.replace(/\\/g, '\\\\') 515 .replace(/'/g, "\\'") 516 .replace(c.escape, function(match, code) { 517 return "',O_O.tplescape(" + code.replace(/\\'/g, "'") + "),'"; 518 }) 519 .replace(c.interpolate, function(match, code) { 520 return "'," + code.replace(/\\'/g, "'") + ",'"; 521 }) 522 .replace(c.evaluate || null, function(match, code) { 523 return "');" + code.replace(/\\'/g, "'") 524 .replace(/[\r\n\t]/g, ' ') + "__p.push('"; 525 }) 526 .replace(/\r/g, '\\r') 527 .replace(/\n/g, '\\n') 528 .replace(/\t/g, '\\t') 529 + "');}return __p.join('');"; 530 531 var func = new Function('obj', tmpl); 532 return data ? func(data) : func; 533 }, 534 535 tplescape: function(string) { 536 return (''+string).replace(/&(?!\w+;|#\d+;|#x[\da-f]+;)/gi, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''').replace(/\//g,'/'); 537 }, 538 539 _tplSettings: { 540 evaluate : /<%([\s\S]+?)%>/g, 541 interpolate : /<%=([\s\S]+?)%>/g, 542 escape : /<%-([\s\S]+?)%>/g 543 }, 544 545 fsizeHu: function (bytes) 546 { 547 if (bytes/(1024*1024) < 1) 548 return (bytes/(1024)).toFixed(1) + 'KB'; 549 else if (bytes/(1024*1024*1024) < 1) 550 return (bytes/(1024*1024)).toFixed(1) + 'MB'; 551 else if (bytes/(1024*1024*1024*1024) < 1) 552 return (bytes/(1024*1024*1024)).toFixed(1) + 'GB'; 553 else 554 return (bytes/(1000*1024*1024*1024)).toFixed(2) + 'TB'; 555 }, 556 557 fitToRange: function(val, min, max) 558 { 559 if (val < min) return min; 560 if (val > max) return max; 561 return val; 562 }, 563 564 popupWin: function (url, dimA, name) 565 { 566 if (!name) name = "popup"; 567 568 if (!dimA) dimA = [600,400]; 569 570 var foo = window.open(url, name, "resizable=yes,scrollbars=yes,location=no,menubar=no,status=yes,width="
570+ dimA[0] + ",height=" + dimA[1]); 571 if (foo) foo.focus(); 572 573 // detect popup blockers 574 setTimeout(function () { 575 if (!foo) alert('Your browser may be blocking popups. Please try again, or disable any popup blockers.'); 576 }, 200); 577 }, 578 579 mm: null //instantiated at runtime 580}; 581 582/*!\brief Memory management class that implements a pseudo destructor 583 * mechanism. 584 */ 585O_O.com.Delete = function () 586{ 587 this._delA = []; 588}; 589 590O_O.com.Delete.prototype = { 591 /*!\brief Add item for later cleanup. 592 * \param i item 593 */ 594 add: function (i) { this._delA.push(i); }, 595 596 _del: function (i) 597 { 598 if (!i || !i._delete) return; 599 600 i._delete(); 601 i._delete = null; 602 603 delete i; 604 }, 605 606 /*!\brief Execute pseudo-destructor and delete for item. 607 * \param obj item 608 */ 609 del: function (obj) 610 { 611 var i, l; 612 613 this._del(obj); 614 615 for (i=0, l=this._delA.length; i<l; i++) { 616 if (this._delA[i] != obj) continue; 617 this._delA.splice(i, 1); 618 return; 619 } 620 }, 621 622 /*!\brief Execute pseudo-destructor and delete for 623 * all items in the specified array. 624 * \param iA item array 625 */ 626 delArr: function (iA) 627 { 628 for (var i in iA) this._del(iA[i]); 629 delete iA; 630 }, 631 632 /*!\brief Returns object for specified object type (variable 633 * arguments) and caches in this._delA. 634 * \param obj object constructor 635 * \return object 636 */ 637 oNew: function (obj) 638 { 639 var t = []; 640 for (var i=1, l=arguments.length; i<l; i++) 641 t.push(arguments[i]); 642 t = O_O.obj.create(obj, t); 643 this.add(t); 644 return t; 645 }, 646 647 /*!\brief Pseudo destructor. */ 648 _delete: function () 649 { 650 this._delA.reverse(); 651 this.delArr(this._delA); 652 } 653}; 654 655O_O.mm = new O_O.com.Delete; 656 657/*!\brief MsgSys class that implements a simplistic message system. The 658 * system supports topic publish/[un]subscribe and broadcast. 659 * \param name name 660 */ 661O_O.com.MsgSys = function(name, debug) { 662 this._name = name; 663 this._debug = !!debug; 664 this._brd = {}; 665 this._sub = {}; 666}; 667 668O_O.com.MsgSys.prototype = { 669 _delete: function() { 670 this._name = null; 671 this._debug = null; 672 this._brd = null; 673 this._sub = null; 674 675 delete this._name; 676 delete this._debug; 677 delete this._brd; 678 delete this._sub; 679 }, 680 681 /*!\brief Publish a message to the specified topic. 682 * \param topic topic (null == broadcast) 683 * \param msg message 684 */ 685 pub: function(topic, msg) { 686 if (this._debug && console && console.log) { 687 console.log('MsgSys[' + this._name + ']::pub:' + topic, msg); 688 } 689 690 var a; 691 692 if (!topic) { 693 a = this._brd; 694 } 695 else { 696 if (!this._sub[topic]) return; 697 a = this._sub[topic]; 698 } 699 700 var $whenA = []; 701 for (var uid in a) { 702 if (a.hasOwnProperty(uid)) $whenA.push(a[uid](topic, msg)); 703 } 704 705 return ps$.when.apply(ps$, $whenA); 706 }, 707 708 /*!\brief Subscribe to topic with optional bind and data. 709 * \param topicA topic (null == broadcast) 710 * \param uid unique subscriber id (serves as key) 711 * \param f function 712 * \param bind bind scope (optional) 713 */ 714 sub: function(topicA, uid, f, bind) { 715 if (!topicA) { 716 this._brd[uid] = scopeC(f, bind); 717 return; 718 } 719 720 if (!is_array(topicA)) topicA = [topicA]; 721 722 var i, t; 723 for (i = 0, l = topicA.length; i < l; i++) { 724 t = topicA[i]; 725 726 if (!this._sub[t]) this._sub[t] = {}; 727 if (this._sub[t][uid]) { 728 console.log('MsgSys: uid ' + uid + ' already exists'); 729 return; 730 } 731 this._sub[t][uid] = scopeC(f, bind); 732 } 733 }, 734 735 /*!\brief Unsubscribe all from topic matching function and uid. 736 * \param topic topic (null == broadcast) 737 * \param uid unique subscriber id (serves as key) 738 */ 739 unsub: function(topic, uid) { 740 var a; 741 742 if (!topic) a = this._brd; 743 else if (!this._sub[topic]) return; 744 else a = this._sub[topic]; 745 746 a[uid] = null; 747 delete a[uid]; 748 }, 749 750 toString: function() { return 'MsgSys: ' + this._name; } 751}; 752 753O_O.com.Browser = function () 754{ 755 this.agent = this._getAgent(this.dataBrowser) || 'unknown'; 756 this.version = this._getVersion(navigator.userAgent) 757 || this._getVersion(navigator.appVersion) 758 || 'unknown'; 759 this.OS = this._getAgent(this.dataOS) || 'an unknown OS'; 760 761 this.isIE = (this.agent == 'Explorer'); 762 this.isSafari = (this.agent == 'Safari' || this.agent == 'Konqueror'); 763 this.isOpera = (this.agent == 'Opera'); 764 this.isChrome = (this.agent == 'Chrome'); 765}; 766 767O_O.com.Browser.prototype = { 768 _getAgent: function (data) 769 { 770 for (var i=0; i<data.length; i++) { 771 var dataStr = data[i].str; 772 var dataProp = data[i].prop; 773 this.verSrchStr = data[i].verSrch || data[i].id; 774 if (dataStr) { 775 if (dataStr.indexOf(data[i].substr) != -1) 776 return data[i].id; 777 } 778 else if (dataProp) return data[i].id; 779 } 780 }, 781 782 _getVersion: function (dataStr) 783 { 784 var index = dataStr.indexOf(this.verSrchStr); 785 if (index == -1) return; 786 return parseFloat(dataStr.substring(index+this.verSrchStr.length+1)); 787 }, 788 789 isValid: function (mode) 790 { 791 if (mode == 'Java' && this.OS == 'Mac') { 792 switch (this.agent) { 793 case 'Chrome': return true; 794 case 'Safari': return (this.version > 400); 795 case 'Firefox': return (this.version >= 1.5); 796 default: return false;
797 } 798 } 799 800 switch (this.agent) { 801 case 'Safari': return (this.version > 300); 802 case 'Chrome': return (this.version >= 0.2); 803 case 'Mozilla': return (this.version > 1.6); 804 case 'Firefox': return (this.version >= 1); 805 case 'Explorer': return (this.version > 5); 806 case 'Opera': return (this.version >= 9); 807 default: return false; 808 } 809 }, 810 811 dataBrowser: [ 812 { 813 str: navigator.vendor, 814 substr: 'Apple', 815 id: 'Safari' 816 }, 817 { 818 str: navigator.vendor, 819 substr: 'Google', 820 id: 'Chrome' 821 }, 822 { 823 prop: window.opera, 824 id: 'Opera' 825 }, 826 { 827 str: navigator.vendor, 828 substr: 'iCab', 829 id: 'iCab' 830 }, 831 { 832 str: navigator.vendor, 833 substr: 'KDE', 834 id: 'Konqueror' 835 }, 836 { 837 str: navigator.userAgent, 838 substr: 'Firefox', 839 id: 'Firefox' 840 }, 841 { // for newer Netscapes (6+) 842 str: navigator.userAgent, 843 substr: 'Netscape', 844 id: 'Netscape' 845 }, 846 { 847 str: navigator.userAgent, 848 substr: 'MSIE', 849 id: 'Explorer', 850 verSrch: 'MSIE' 851 }, 852 { 853 str: navigator.userAgent, 854 substr: 'Gecko', 855 id: 'Mozilla', 856 verSrch: 'rv' 857 }, 858 { // for older Netscapes (4-) 859 str: navigator.userAgent, 860 substr: 'Mozilla', 861 id: 'Netscape', 862 verSrch: 'Mozilla' 863 } 864 ], 865 866 dataOS : [ 867 { 868 str: navigator.platform, 869 substr: 'Win', 870 id: 'Windows' 871 }, 872 { 873 str: navigator.platform, 874 substr: 'Mac', 875 id: 'Mac' 876 }, 877 { 878 str: navigator.platform, 879 substr: 'Linux', 880 id: 'Linux' 881 } 882 ], 883 884 getResponsiveState: function(config) { 885 try { 886 return window.getComputedStyle(document.body, ':after').getPropertyValue('content').replace(/"/g,''); 887 } catch (e) { 888 var wWidth = $(window).width(), 889 responses = ['handhelds','tablets','medium-screens','wide-screens','mega-screens'], 890 stops = (isset(config) && isset(config.stops))?config.stops:[568,1024,1365,1800], 891 isLtIe9 = $('body').hasClass('lt-ie9'); //Set to a global signifier? 892 if(wWidth<=stops[0] && !isLtIe9) { 893 return responses[0]; 894 } else if (wWidth<stops[1] || isLtIe9) { 895 return responses[1]; 896 } else if (wWidth<=stops[2] && !isLtIe9) { 897 return responses[2]; 898 } else if (wWidth<=stops[3] && !isLtIe9) { 899 return responses[3]; 900 } else { 901 return responses[4]; 902 } 903 } 904 }, 905 906 transEndEventName: function() { 907 var transEndEventNames = { 908 'WebkitTransition' : 'webkitTransitionEnd', 909 'MozTransition' : 'transitionend', 910 'OTransition' : 'oTransitionEnd', 911 'msTransition' : 'MSTransitionEnd', 912 'transition' : 'transitionend' 913 }; 914 return transEndEventNames[ Modernizr.prefixed('transition') ]; 915 }, 916 917 animEndEventName: function() { 918 var animEndEventNames = { 919 'WebkitAnimation' : 'webkitAnimationEnd', 920 'MozAnimation' : 'transitionend', 921 'OAnimation' : 'oAnimationEnd', 922 'msAnimation' : 'MSAnimationEnd', 923 'animation' : 'animationend' 924 }; 925 return animEndEventNames[ Modernizr.prefixed('animation') ]; 926 }, 927 928 isTouch: false, 929 930 /** 931 * Normalized hide address bar for iOS & Android 932 * (c) Scott Jehl, scottjehl.com 933 * MIT License 934 */ 935 936 // If we split this up into two functions we can reuse 937 // this function if we aren't doing full page reloads. 938 939 // If we cache this we don't need to re-calibrate everytime we call 940 // the hide url bar 941 BODY_SCROLL_TOP: false, 942 943 // So we don't redefine this function everytime we 944 // we call hideUrlBar 945 getScrollTop: function() { 946 var win = window; 947 var doc = document; 948 949 return win.pageYOffset || doc.compatMode === 'CSS1Compat' && doc.documentElement.scrollTop || doc.body.scrollTop || 0; 950 }, 951 952 // It should be up to the mobile 953 hideUrlBar: function() { 954 var win = window; 955 956 // if there is a hash (skips for PS' O_O), or MBP.BODY_SCROLL_TOP hasn't been set yet, wait till that happens 957 if (/*!location.hash && */O_O.browser.BODY_SCROLL_TOP !== false) { 958 win.scrollTo( 0, (O_O.browser.BODY_SCROLL_TOP === 1 || /(iPad|iPhone|iPod)/g.test( navigator.userAgent )) ? 0 : 1 ); 959 } 960 }, 961 962 hideUrlBarOnLoad: function() { 963 var win = window; 964 var doc = win.document; 965 var bodycheck; 966 967 // If there's a hash, or addEventListener is undefined, stop here 968 if ( !location.hash && win.addEventListener ) { 969 970 // scroll to 1 971 window.scrollTo( 0, 1 ); 972 O_O.browser.BODY_SCROLL_TOP = 1; 973 974 // reset to 0 on bodyready, if needed 975 bodycheck = setInterval(function() { 976 if ( doc.body ) { 977 clearInterval( bodycheck ); 978 O_O.browser.BODY_SCROLL_TOP = O_O.browser.getScrollTop(); 979 O_O.browser.hideUrlBar(); 980 } 981 }, 15 ); 982 983 win.addEventListener('load', function() { 984 setTimeout(function() { 985 // at load, if user hasn't scrolled more than 20 or so... 986 if (O_O.browser.getScrollTop() < 20) { 987 // reset to hide addr bar at onload 988 O_O.browser.hideUrlBar(); 989 } 990 }, 0); 991 }); 992 } 993 }, 994 995 getScrollWidth: function() { 996 var outer = document.createElement('div'); 997 outer.style.visibility = 'hidden'; 998 outer.style.width = '100px'; 999 document.body.appendChild(outer); 1000 1001 var widthNoScroll = outer.offsetWidth; 1002 // force scrollbars 1003 outer.style.overflow = 'scroll'; 1004 1005 // add innerdiv 1006 var inner = document.createElement('div'); 1007 inner.style.width = '100%'; 1008 outer.appendChild(inner); 1009 1010 var widthWithScroll = inner.offsetWidth; 1011 1012 // remove divs 1013 outer.parentNode.removeChild(outer); 1014 1015 return widthNoScroll - widthWithScroll; 1016 1017 } 1018}; 1019 1020O_O.browser = O_O.mm.oNew(O_O.com.Browser); 1021 1022// shortcuts 1023empty = O_O.empty; 1024isset = O_O.isset; 1025is_array = O_O.is_array; 1026is_deferred = O_O.is_deferred; 1027is_function = O_O.is_function; 1028is_object = O_O.is_object; 1029scopeC = O_O.scopeC; 1030 1031// jQuery instantiate 1032ps$ = window.ps$ = jQuery.noConflict(); 1033 1034(function ($, undefined) { 1035/////////////////////////////////////// BEGIN jQuery wrap 1036 1037var psApp = { 1038 pv: [], 1039 _login: null, 1040 _loginDfr: null, 1041 1042 init: function(pv) 1043 { 1044 this.pv = pv; 1045 }, 1046 1047 pvGet: function(k) 1048 { 1049 return (this.pv[k] !== undefined) ? this.pv[k] : null; 1050 }, 1051 1052 realmPath: function() 1053 { 1054 var area = this.pvGet('area'); 1055 1056 switch (area) { 1057 case 'adm': 1058 case 'cli': 1059 case 'mem': 1060 case 'mu': 1061 return '/' + area; 1062 default: 1063 return ''; 1064 } 1065 }, 1066 1067 imgGet: function(id, t, host, qry, f_auth) 1068 { 1069 if (host && (host = this.pvGet('server.' + host))); 1070 else host = ''; 1071
1072 t = (t) ? ('/' + t) : '/s'; 1073 qry = (qry) ? '?' + qry: ''; 1074 1075 var iget = '/img-get'; 1076 if (f_auth) { 1077 var area = this.pvGet('area'); 1078 if (area == 'mu' || area == 'mem') 1079 iget = '/' + area + iget; 1080 } 1081 iget += '/'; 1082 1083 return host + iget + id + t + qry; 1084 }, 1085 1086 imgSzCalc: function(dim, sz, f_upscale) 1087 { 1088 if (!is_array(sz)) sz = [sz, sz]; 1089 1090 if (!f_upscale && (dim[0] <= sz[0] && dim[1] <= sz[1])) 1091 return dim; 1092 1093 var szAR = sz[0]/sz[1]; 1094 var dimAR = dim[0]/dim[1]; 1095 1096 if (szAR > dimAR) return [Math.round(sz[1]*dimAR), sz[1]]; 1097 else return [sz[0], Math.round(sz[0]/dimAR)]; 1098 }, 1099 1100 imgMkUrlFromData: function (user_opts, data) 1101 { 1102 if (!data || !data.link_elements) return null; 1103 1104 var opts = { 1105 sz: null, 1106 width: 500, 1107 height: 500, 1108 mode: 'fit', 1109 orig: false, 1110 f_public: true, 1111 }; 1112 $.extend(opts, user_opts); 1113 if (opts.sz) opts.width = opts.height = opts.sz; 1114 1115 var elems = (opts.f_public && data.public_link_elements) || data.link_elements; 1116 var segs = [elems.base, 'sec=' + elems.token]; 1117 1118 if (!opts.orig) { 1119 if (opts.height > 0) { 1120 segs.push(opts.mode + '=' + opts.width + 'x' + opts.height); 1121 } else { 1122 segs.push(opts.mode + '=' + opts.width); 1123 } 1124 } 1125 else { 1126 segs.push('src=orig'); // no dims change for original 1127 } 1128 1129 // set correct extension (jpg unless original) 1130 if (data.file_name) { 1131 var fn = O_O.str.dashifyFilename(data.file_name); 1132 if (!!opts.orig) segs.push(fn); 1133 else segs.push(fn.split('.')[0] + '.jpg'); 1134 } 1135 1136 return segs.join('/'); 1137 }, 1138 1139 imgMkIPTCCascadeFromData: function (data) { 1140 var result = data.II_HEADLINE || data.II_TITLE || data.I_FILE_NAME; 1141 if (data.II_HEADLINE && result.length > 30) { 1142 result = result.substr(0, 20) + "..."; 1143 } 1144 return result; 1145 }, 1146 1147 imgScaleCalc: function (orig, dest, fit_mode) 1148 { 1149 var iw = orig.width; 1150 var ih = orig.height; 1151 var tw = dest.width; 1152 var th = dest.height; 1153 1154 var ar = iw / ih; 1155 var ar_td = tw / th; 1156 1157 var scale; 1158 1159 switch (fit_mode) { 1160 case 'fill': 1161 if (ar_td < ar) scale = th / ih; 1162 else scale = tw / iw; 1163 break; 1164 case 'fit': 1165 if (iw > ih) { 1166 if (ar > ar_td) scale = tw / iw; 1167 else scale = th / ih; 1168 } 1169 else { 1170 if (ar < ar_td) scale = th / ih; 1171 else scale = tw / iw; 1172 } 1173 break; 1174 } 1175 1176 return scale; 1177 }, 1178 1179 videoMkUrlFromData: function (user_opts, data) 1180 { 1181 if (!data || !data.link_elements) return null; 1182 1183 var opts = { 1184 fmt: "poster" 1185 }; 1186 $.extend(opts, user_opts); 1187 1188 var elems = data.link_elements; 1189 var segs = [elems.base, 'sec=' + elems.token]; 1190 1191 segs.push('fmt=' + opts.fmt); 1192 1193 // set correct extension (jpg unless original) 1194 if (data.file_name) { 1195 var fn = O_O.str.dashifyFilename(data.file_name); 1196 if (opts.fmt == "hls") segs.push(fn.split('.')[0] + '.m3u8'); 1197 else segs.push(fn.split('.')[0] + '.jpg'); 1198 } 1199 1200 return segs.join('/'); 1201 }, 1202 1203 bsapi: { 1204 _actCB: function (data, settings) 1205 { 1206 settings.dfr.resolve(data); 1207 }, 1208 1209 _errCB: function (data, settings) 1210 { 1211 var dfr = settings.dfr; 1212 1213 if (!is_array(data)) data = [data]; 1214 1215 // add default handler 1216 // HACK: because JQ1.7 broke my other reject.length hack :( 1217 if (!settings.f_fail) 1218 dfr.fail(psApp.error); 1219 1220 dfr.reject(data); 1221 }, 1222 1223 _transportErrCB: function (evt, xhr, settings, excpt) 1224 { 1225 var err = {'class': "TransportErr", 'message': excpt.message, 'var': excpt}; 1226 1227 settings.dfr.reject([err]); 1228 }, 1229 1230 post: function (url, d, f_fail) 1231 { 1232 var dfr = $.Deferred(); 1233 1234 $.ajax({ 1235 url: url, 1236 type: 'POST', 1237 dataType: 'json', 1238 global: true, 1239 data: d, 1240 bsapiCB: psApp.bsapi._actCB, 1241 bsapiErr: psApp.bsapi._errCB, 1242 onTransportErr: psApp.bsapi._transportErrCB, 1243 dfr: dfr, 1244 f_fail: f_fail 1245 }); 1246 1247 return dfr.promise(); 1248 } 1249 }, 1250 1251 error: function(errA) 1252 { 1253 if (!empty(errA) && !is_array(errA)) 1254 errA = [{message: errA}]; 1255 else if (!errA || !errA.length) 1256 return; 1257 1258 1259 var html = '<ul>'; 1260 1261 for (var i=0, l=errA.length; i<l; i++) { 1262 html += '<li>' + errA[i].message + '</li>'; 1263 }; 1264 1265 html += '</ul>'; 1266 1267 // FIXME: UI dialog 1268 alert(html); 1269 } 1270}; 1271 1272O_O.app = psApp; 1273 1274// ######################################################## 1275// jquery methods 1276// ######################################################## 1277 1278// canonical jquery plugin generation 1279$.jqPlugin = function (plugin, args) 1280{ 1281 var method = args[0]; 1282 1283 if (method && plugin[method]) { 1284 return plugin[method].apply(this, Array.prototype.slice.call(args, 1)); 1285 } 1286 else if (typeof method === 'object' || !method) { 1287 return plugin.init.apply(this, args); 1288 } 1289 else { 1290 $.error('$.jqPlugin: undefined method [' + method + ']'); 1291 } 1292}; 1293 1294// plugin generation and util functions using Object prototypes 1295$.bsPlugin = function(name, object) 1296{ 1297 $.fn[name] = function(options) { 1298 return this.each(function() { 1299 if (!$.data(this, name)) { 1300 $.data(this, name, O_O.mm.oNew(object, options, this)); 1301 } 1302 }); 1303 }; 1304}; 1305 1306$.rgxEsc = function(txt) 1307{ 1308 var r = txt.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); 1309 //r = r.replace(/\\ /g," "); 1310 return r; 1311}; 1312 1313$.chain = function (queue, dfr, args) 1314{ 1315 if (!isset(dfr)) dfr = $.Deferred(); 1316 if (!isset(args)) args = []; 1317 1318 var r; 1319 var t = queue.splice(0, 1)[0]; 1320 if (!is_array(t)) t = [t]; 1321 1322 if (is_function(t[0])) { 1323 var fn = t[0]; 1324 var scope = (isset(t[1])) ? t[1] : null; 1325 1326 r = fn.apply(scope, args); 1327 } 1328 else r = t[0]; 1329 1330 if (is_deferred(r)) { 1331 r.done(function() { 1332 if (queue.length > 0) 1333 $.chain(queue, dfr, arguments) 1334 else dfr.resolve(arguments); 1335 }); 1336 r.fail(function() { dfr.reject.apply(dfr, arguments); }); 1337 } 1338 else { 1339 if (queue.length > 0) 1340 $.chain(queue, dfr, [r]) 1341 else dfr.resolve(r); 1342 } 1343 1344 // need true recursive delete here? 1345 t = r = null; 1346 1347 return dfr.promise(); 1348} 1349 1350 1351// ######################################################## 1352// jQuery plugins 1353// ######################################################## 1354 1355//**jquery [on|off] wrappers for context support 1356$.fn.onC = function () { 1357 var args = [].slice.call(arguments, 0, arguments.length-2); 1358 var cb = arguments[arguments.length-2]; 1359 var scope = arguments[arguments.length-1]; 1360 1361 args[args.length] = function() { 1362 return cb.apply(scope, arguments); 1363 }; 1364 1365 return this.on.apply(this, args); 1366}; 1367 1368$.fn.offC = function () { 1369 var args = [].slice.call(arguments, 0, arguments.length-2); 1370 var cb = arguments[arguments.length-2]; 1371 var scope = arguments[arguments.length-1]; 1372 1373 args[args.length] = function() { 1374 return cb.apply(scope, arguments); 1375 }; 1376 1377 return this.off.apply(this, args); 1378}; 1379 1380$.fn.oneC = function() { 1381 var args = [].slice.call(arguments, 0, arguments.length-2); 1382 var cb = arguments[arguments.length-2]; 1383 var scope = arguments[arguments.length-1]; 1384 1385 args[args.length] = function() { 1386 return cb.apply(scope, arguments); 1387 }; 1388 1389 return this.one.apply(this, args); 1390}; 1391 1392//Patch the remove function to avois leaks in IE 1393$.fn.remove = function() { 1394 return $(this).each(function(i,e){ 1395 if( e.parentNode ) 1396 e.parentNode.removeChild(e); 1397 }); 1398}; 1399 1400$.fn.serializeObject = function() 1401{ 1402 var o = {}; 1403 var a = this.serializeArray(); 1404 $.each(a, function() { 1405 if (o[this.name] !== undefined) { 1406 if (!o[this.name].push) { 1407 o[this.name] = [o[this.name]]; 1408 } 1409 o[this.name].push(this.value || ''); 1410 } else { 1411 o[this.name] = this.value || ''; 1412 } 1413 }); 1414 return o; 1415}; 1416 1417$.fn.formLoad = function (d) 1418{ 1419 var that = this; 1420 1421 $.each(d, function(k, v) { 1422 that.find("[name='" + k + "']").val(v); 1423 }); 1424 1425 return this; 1426}; 1427 1428// Image Utility plugins 1429// ######################################################## 1430 1431$.fn.getImgRatio = function(targetDiv, fit_mode) 1432{ 1433 var img = {}; 1434 img.width = this.width();
1435 img.height = this.height(); 1436 1437 var target = {}; 1438 target.width = $(targetDiv).width(); 1439 target.height = $(targetDiv).height(); 1440 1441 return O_O.app.imgScaleCalc(img, target, fit_mode); 1442}; 1443 1444$.fn.imgScale = function(factor) 1445{ 1446 var d = [this.width(), this.height()]; 1447 d = d.map(function(a) { return Math.round(a * factor); }); 1448 return this.width(d[0]).height(d[1]); 1449}; 1450 1451$.fn.imgSizeToDiv = function(targetDiv, fit_mode) 1452{ 1453 return this.imgScale(this.getImgRatio(targetDiv, fit_mode)); 1454}; 1455 1456$.fn.center = function(mode, parent) 1457{ 1458 var p = parent || this.parent(); 1459 1460 return this.css({ 1461 top: Math.round((p.height() - this.height()) / 2), 1462 left: Math.round((p.width() - this.width()) / 2) 1463 }); 1464}; 1465 1466$.fn.centerFocus = function(focal, parent) 1467{ 1468 //focal is a percentage based coordinate object representative of 1469 //a point in $(this) dom object to center within the parent. examples: 1470 // center focus - {x:0.5, y:0.5} 1471 // bottom left focus - {x:1, y:1} 1472 1473 var p = parent || this.parent(); 1474 1475 return this.css(viewport_center_focus(focal, p.dimensions(), this.dimensions())); 1476}; 1477 1478//center_focus helper 1479var viewport_center_focus = function(focal, viewport, content) { 1480 1481 var r = {}; 1482 1483 //convert from percentage units to px 1484 var focal_px = { 1485 y: Math.round(focal.y*content.height), 1486 x: Math.round(focal.x*content.width) 1487 } 1488 1489 var bounding = { 1490 y: content.height - viewport.height, 1491 x: content.width - viewport.width 1492 }; 1493 1494 var viewport_center = { 1495 y: Math.round(viewport.height/2), 1496 x: Math.round(viewport.width/2) 1497 } 1498 1499 r.top = O_O.fitToRange(viewport_center.y - focal_px.y, -bounding.y, 0); 1500 r.left = O_O.fitToRange(viewport_center.x - focal_px.x, -bounding.x, 0); 1501 1502 return r; 1503}; 1504 1505$.fn.truncateText = function(tieOff) { 1506 tieOff = tieOff || '...'; 1507 1508 $.each(this, function(i, v) { 1509 var $v = $(v), 1510 concat = tieOff, 1511 currText = $v.text(), 1512 innerHeight = 0, 1513 scrollHeight = 0, 1514 lineHeight = 0, 1515 hardCropped = false, 1516 trim = 6; 1517 if ($v.css('overflow') !== 'hidden') { 1518 return; 1519 } else if (!$v.data('originalText')) { 1520 $v.data('originalText', currText); 1521 if(currText==='') return; 1522 } else { 1523 if($v.data('originalText') === '') return; 1524 $v.html($v.data('originalText')); 1525 } 1526 concat = $v.data('originalText') + tieOff; 1527 innerHeight = $v.innerHeight(); 1528 scrollHeight = v.scrollHeight; 1529 lineHeight = parseFloat($v.css('line-height')); 1530 while(innerHeight < scrollHeight) { 1531 concat = concat.replace(tieOff, ''); 1532 if (!hardCropped && innerHeight + lineHeight < scrollHeight) { 1533 hardCropped = true; 1534 concat = concat.substring(0,Math.round(concat.length * (innerHeight / scrollHeight))); 1535 } else { 1536 concat = concat.substring(0,concat.length-trim); 1537 } 1538 while (concat[concat.length-1] === ' ') { 1539 concat = concat.substring(0,concat.length-2); 1540 } 1541 concat += tieOff; 1542 $v.html(concat); 1543 scrollHeight = v.scrollHeight; 1544 } 1545 }); 1546 1547 return this; 1548}; 1549 1550$.fn.dimensions = function () { 1551 return {width: this.width(), height: this.height()}; 1552}; 1553 1554// ######################################################## 1555// jQuery ready functions 1556// ######################################################## 1557 1558$().ready(function() { 1559 // Global AJAX transfer error handler 1560 $('body').ajaxError(function(evt, xhr, settings, excpt) { 1561 try { 1562 //make sure to prevent method-specific AJAX callback from firing (passed in settings['complete']) 1563 evt.stopImmediatePropagation(); 1564 1565 if (typeof xhr.responseText == 'string') { 1566 d = eval( "(" + xhr.responseText + ")" ); 1567 if (d && d.status == 'error') { 1568 if ($.isFunction(settings.bsapiErr)) { 1569 return settings.bsapiErr(d.error, settings); 1570 } 1571 } 1572 } 1573 1574 // no json? quietly error 1575 // console.log('jquery.PS.ajaxError', excpt, xhr.responseText); 1576 1577 // do something with a transfer error here.... 1578 // like throw a prompt with some messaging 1579 // or do the local onErr 1580 if ($.isFunction(settings.onTransportErr)) 1581 settings.onTransportErr(evt, xhr, settings, excpt); 1582 1583 throw 'Transfer Error'; 1584 } 1585 catch(err) { 1586 // quietly error 1587 // console.log('jquery.PS.ajaxError', excpt, xhr.responseText); 1588 if ($.isFunction(settings.onTransportErr)) 1589 settings.onTransportErr(evt, xhr, settings, err); 1590 } 1591 }); 1592 1593 //Global AJAX API error / exception handler (responseParse)
1594 $('body').ajaxSuccess(function(evt, xhr, settings) { 1595 var d; 1596 1597 // FIXME: need real content type detection? 1598 switch (settings.dataType) { 1599 case 'json': 1600 // can responseText be non-string??? 1601 if (typeof xhr.responseText == 'string') 1602 d = eval( "(" + xhr.responseText + ")" ); 1603 else if (xhr.responseText) 1604 d = xhr.responseText; 1605 //console.log(xhr); 1606 if (!d) return; 1607 if (typeof d.status == 'undefined') { 1608 // silently error 1609 //console.log('getJson has no data.response'); 1610 } 1611 else if ((typeof d.status) != 'undefined') { 1612 if ($.isFunction(settings.bsapiCB)) 1613 settings.bsapiCB(d['data'], settings); 1614 else return; 1615 } 1616 else if ((typeof d.response.error) != 'undefined') { 1617 var errA = d.response.error; 1618 1619 // stop method-specific callbacks from firing 1620 evt.stopImmediatePropagation(); 1621 1622 if ($.isFunction(settings.bsapiErr)) { 1623 settings.bsapiErr(errA, settings); 1624 } 1625 else { 1626 // default error handling 1627 psApp.error(errA); 1628 } 1629 } 1630 else throw new Error('invalid response'); 1631 break; 1632 1633 case 'html': 1634 if (xhr.responseText == '') { 1635 // HACK: used in older apps e.g. PS.cartAdd 1636 $("psWaitingMd").trigger('bsapi_error'); 1637 break; 1638 } 1639 default: 1640 if (typeof xhr.responseXML == 'object') { 1641 xml = xhr.responseXML; 1642 err = xml.getElementsByTagName('error'); 1643 if (err.length) { 1644 if ($.isFunction(settings.bsapiErr)) { 1645 settings.bsapiErr(err, settings); 1646 break; 1647 } 1648 else { 1649 psApp.error(errA); 1650 break; 1651 } 1652 } 1653 } 1654 if ($.isFunction(settings.bsapiCB)) 1655 settings.bsapiCB(xhr.responseText, settings); 1656 1657 break; 1658 } 1659 }); 1660 $('body').one('touchstart.pointerType', function(){ 1661 O_O.browser.isTouch = true; 1662 $('html').removeClass('can-click'); 1663 }); 1664 O_O.browser.BODY_SCROLL_TOP = O_O.browser.getScrollTop(); 1665 O_O.browser.hideUrlBarOnLoad(); 1666 $(window).on('orientationchange', O_O.browser.hideUrlBar); 1667}); 1668 1669})(ps$); 1670 1671 1672// ######################################################## 1673// native JS object extensions 1674// ######################################################## 1675 1676/* 1677 *\brief Extends js Date object with strftime() method 1678 */ 1679if (!Date.prototype.strftime) Date.prototype.strftime = function(str, utc) { 1680 var days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']; 1681 var months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; 1682 var formats = { // E, G, g, O, U, V, W, Z, and + not supported 1683 'A': function(d) { return days[d[get('Day')]()]; }, 1684 'a': function(d) { return d.strftime('%A', utc).substring(0,3); }, 1685 'B': function(d) { return months[d[get('Month')]()]; }, 1686 'b': function(d) { return d.strftime('%B', utc).substring(0,3); }, 1687 'C': function(d) { return parseInt(d[get('FullYear')]() / 100, 10); }, 1688 'c': function(d) { return d.strftime('%a %b %e %H:%M:%S %Y', utc); }, 1689 'D': function(d) { return d.strftime('%m/%d/%y', utc); }, 1690 'd': function(d) { return ('0' + d[get('Date')]()).match(/..$/); }, 1691 'e': function(d) { return (' ' + d[get('Date')]()).match(/..$/); }, 1692 'F': function(d) { return d.strftime('%Y-%m-%d', utc); }, 1693 'H': function(d) { return ('0' + d[get('Hours')]()).match(/..$/); }, 1694 'h': function(d) { return d.strftime('%b', utc); }, 1695 'I': function(d) { return ('0' + ((d[get('Hours')]() % 12) || 12)).match(/..$/); }, 1696 'j': function(d) { return ('00' + Math.floor(((d - new Date(d.getFullYear(),0,0)) + (utc ? (d.getTimezoneOffset() * 60000) : 0)) / 86400000)).match(/...$/); }, 1697 'k': function(d) { return (' ' + d[get('Hours')]()).match(/..$/); }, 1698 'l': function(d) { return (' ' + ((d[get('Hours')]() % 12) || 12)).match(/..$/); }, 1699 'M': function(d) { return ('0' + d[get('Minutes')]()).match(/..$/); }, 1700 'm': function(d) { return ('0' + (d[get('Month')]() + 1)).match(/..$/); }, 1701 'n': function(d) { return '\n'; }, 1702 'p': function(d) { return (d[get('Hours')]() >= 12) ? 'PM' : 'AM'; }, 1703 'R': function(d) { return d.strftime('%H:%M', utc); }, 1704 'r': function(d) { return d.strftime('%I:%M:%S %p', utc); }, 1705 'S': function(d) { return ('0' + d[get('Seconds')]()).match(/..$/); }, 1706 's': function(d) { return Math.round(d.getTime() / 1000); }, 1707 'T': function(d) { return d.strftime('%H:%M:%S', utc); }, 1708 't': function(d) { return '\t'; }, 1709 'u': function(d) { return (d[get('Day')]() || 7); }, 1710 'v': function(d) { return d.strftime('%e-%b-%y', utc); }, 1711 'w': function(d) { return d[get('Day')](); }, 1712 'X': function(d) { return d.strftime('%H:%M:%S', utc); }, 1713 'x': function(d) { return d.strftime('%m/%d/%y', utc); }, 1714 'Y': function(d) { return d[get('FullYear')](); }, 1715 'y': function(d) { return d.strftime('%Y', utc).substring(2); },
1716 'z': function(d) { return d.toTimeString().match(/[+-]\d{4}/); } 1717 }; 1718 1719 function get(str) { return (utc ? 'getUTC' : 'get') + str; } 1720 1721 var c, chars = str.split(''); 1722 var list = []; 1723 1724 while (c = chars.shift()) { 1725 if (c == '%') { 1726 c = chars.shift() || c; 1727 try { c = formats[c](this).toString(); } catch (e) {} 1728 } 1729 list.push(c); 1730 } 1731 1732 return list.join(''); 1733}; 1734;(function($, window, undefined) { 1735'use strict'; 1736 1737if (!O_O.lib) O_O.lib = {}; // since file loads before visualizer namespaces 1738 1739O_O.lib.Util = { 1740 /* constants */ 1741 1742 CACHE_BREAK_URLS: false, // FIXME: avoids aggressive touch device caching 1743 1744 LOAD_DIR: 'js', 1745 LOAD_REV: '20121210', // may be set externally (check O_O.app.Visualizer) 1746 LOAD_VERS: '2.0', 1747 LOAD_SUFFIX: '.js', 1748 1749 URL_KEYS: 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', 1750 URL_WORD_REGEX: /[_A-Za-z0-9]+/g, 1751 URL_LEX_REGEX: /[_A-Za-z0-9]+|[^_A-Za-z0-9]+/g, 1752 GAL_ID_REGEX: /^G0000[a-zA-Z0-9\._]{11}$/, 1753 CLC_ID_REGEX: /^C0000[a-zA-Z0-9\._]{11}$/, 1754 IMG_ID_REGEX: /^I0000[a-zA-Z0-9\._]{11}$/, 1755 VID_ID_REGEX: /^VD000[a-zA-Z0-9\._]{11}$/, 1756 AUD_ID_REGEX: /^AD000[a-zA-Z0-9\._]{11}$/, 1757 DOC_ID_REGEX: /^DO000[a-zA-Z0-9\._]{11}$/, 1758 1759 /* state */ 1760 1761 alreadyWarnedConcat: null, 1762 alreadyWarnedCache: null, 1763 1764 /* utils */ 1765 1766 makeUid: function() { 1767 var salt = Math.floor(1000 * Math.random()).toString(); 1768 while (salt.length < 3) salt = '0' + salt; 1769 1770 return (new Date()).getTime() + salt; // 16 digits as string 1771 }, 1772 1773 filterKeys: function(obj, removeA) { 1774 var filtered = {}; 1775 1776 for (var k in obj) { 1777 if (obj.hasOwnProperty(k) && removeA.indexOf(k) < 0) { 1778 filtered[k] = obj[k]; 1779 } 1780 } 1781 1782 return filtered; 1783 }, 1784 1785 getWinPropPath: function(path) { 1786 var pathA = path.split('.'); 1787 1788 // transform a property path into an object without eval 1789 var i, l, prop, obj = window; 1790 for (i = 0, l = pathA.length; i < l; i++) { 1791 prop = pathA[i]; 1792 if (typeof obj !== 'object' || !obj.hasOwnProperty(prop)) { 1793 return undefined; 1794 } 1795 obj = obj[prop]; 1796 } 1797 1798 return obj; 1799 }, 1800 1801 _ajax: function(url, checkA, failMsg) { 1802 if (!this.alreadyWarnedCache && this.CACHE_BREAK_URLS) { 1803 console.log('O_O.lib.Util._ajax: cache-breaking enabled'); 1804 this.alreadyWarnedCache = true; 1805 } 1806 1807 var $dfd = $.Deferred(); 1808 1809 // load file and check that certain objects created 1810 $.ajax({ 1811 cache: !this.CACHE_BREAK_URLS, // flag is to not cache break 1812 crossDomain: true, // load as script instead of using eval 1813 dataType: 'script', 1814 url: url 1815 }).fail(function() { 1816 $dfd.reject(); 1817 }).done(function() { 1818 if (typeof checkA === 'object' && checkA.length) { 1819 for (var i = 0, l = checkA.length; i < l; i++) { 1820 if (!O_O.lib.Util.getWinPropPath(checkA[i])) { 1821 $dfd.reject(); // reject if object not present 1822 } 1823 } 1824 } 1825 $dfd.resolve(); 1826 }); 1827 1828 return $dfd.promise().fail(function() { 1829 throw new Error(failMsg); 1830 }); 1831 }, 1832 1833 _objIsEmpty: function(o) { 1834 for (var k in o) { 1835 if (o.hasOwnProperty(k)) return false; 1836 } 1837 1838 return true; 1839 }, 1840 1841 /* url encoding */ 1842 1843 _getConcatUrlDict: function(url) { 1844 var keys = O_O.lib.Util.URL_KEYS.split(''); 1845 var dict = {}, words_count = {}; 1846 var i, j, word, count; 1847 1848 // Count the words. 1849 var words_arr = url.match(O_O.lib.Util.URL_WORD_REGEX); 1850 for (i = 0, j = words_arr.length; i < j; i++) { 1851 word = words_arr[i]; 1852 if (word in words_count) { 1853 words_count[word]++; 1854 } 1855 else { 1856 words_count[word] = 1; 1857 } 1858 } 1859 1860 // Create an array of the unique words, filtering out words that 1861 // are keys and adding them to the dict. 1862 var s_words = []; 1863 for (word in words_count) { 1864 if (words_count.hasOwnProperty(word)) { 1865 if (keys.indexOf(word) !== -1) { 1866 keys.splice(keys.indexOf(word), 1); 1867 } 1868 else { 1869 s_words.push(word); 1870 } 1871 } 1872 } 1873 1874 if (keys.length === 0) { 1875 return dict; 1876 } 1877 1878 // Sort the array of words by count in decending order. 1879 function order_by_count(x, y) 1880 { 1881 return words_count[y] - words_count[x]; 1882 } 1883 s_words.sort(order_by_count); 1884 1885 // Create words map. 1886 for (i = 0, j = s_words.length; i < j; i++) { 1887 word = s_words[i]; 1888 count = words_count[word]; 1889 1890 // Don't put words in the dictionary unless doing so 1891 // would make the overall url shorter. 1892 if (count === 1) { 1893 continue; 1894 } 1895 if (count === 2 && word.length < 5) { 1896 continue; 1897 } 1898 if (count === 3 && word.length < 3) { 1899 continue; 1900 } 1901
1902 dict[word] = keys.pop(); 1903 1904 if (keys.length === 0) { 1905 break; 1906 } 1907 } 1908 1909 return dict; 1910 }, 1911 1912 _encodeConcatUrl: function(url, dict) { 1913 var tokens = url.match(O_O.lib.Util.URL_LEX_REGEX); 1914 var str = escape('('); 1915 var first = true; 1916 1917 for (var key in dict) { 1918 if (dict.hasOwnProperty(key)) { 1919 if (first) { 1920 first = false; 1921 } 1922 else { 1923 str += ','; 1924 } 1925 1926 str += dict[key] + '=' + key; 1927 } 1928 } 1929 str += escape(')'); 1930 1931 var i, j, token; 1932 for (i = 0, j = tokens.length; i < j; i++) { 1933 token = tokens[i]; 1934 if (token in dict) { 1935 str += dict[token]; 1936 } 1937 else { 1938 str += token; 1939 } 1940 } 1941 return str; 1942 }, 1943 1944 /* structured file loading */ 1945 1946 _loadSeq: function(filesA) { 1947 var chainA = []; 1948 1949 var ajaxCb = scopeC(function(url, file, failMsg) { 1950 return scopeC(function() { 1951 return this._ajax(url, (file ? [file] : ''), failMsg); 1952 }, this); 1953 }, this); 1954 1955 var i, j, path, fileA, failMsg, k, l, url; 1956 for (i = 0, j = filesA.length; i < j; i++) { 1957 path = filesA[i].path; 1958 fileA = filesA[i].fileA; 1959 failMsg = filesA[i].failMsg; 1960 1961 var prefix = path ? 1962 (this.LOAD_DIR + '/' + this.LOAD_VERS + '/' + path + '/') : ''; 1963 for (k = 0, l = fileA.length; k < l; k++) { 1964 url = '/' + prefix + fileA[k] + this.LOAD_SUFFIX; 1965 1966 chainA.push(ajaxCb(url, (path ? fileA[k] : ''), failMsg)); 1967 } 1968 } 1969 1970 return $.chain(chainA); // guaranteed one-by-one loads 1971 }, 1972 1973 _loadConcat: function(filesA, host, push) { 1974 var url = '/include/' + this.LOAD_REV + '/'; // base and first arg 1975 var checkA = []; 1976 var failMsg = 'App failed to load concatenated files'; 1977 1978 var _url = ''; 1979 var i, j, path, fileA, k, l; 1980 for (i = 0, j = filesA.length; i < j; i++) { 1981 path = filesA[i].path; 1982 fileA = filesA[i].fileA; 1983 1984 var prefix = path ? 1985 (this.LOAD_DIR + '/' + this.LOAD_VERS + '/' + path + '/') : ''; 1986 for (k = 0, l = fileA.length; k < l; k++) { 1987 _url += prefix + fileA[k] + this.LOAD_SUFFIX; 1988 _url += (i === j - 1 && k === l - 1) ? '' : ','; 1989 1990 if (path) checkA.push(fileA[k]); 1991 } 1992 } 1993 1994 // second arg specifies encoding (none if no dict) 1995 var dict = O_O.lib.Util._getConcatUrlDict(_url); 1996 url += (O_O.lib.Util._objIsEmpty(dict) ? 1997 '0/' + _url : // no encoding 1998 '1/' + O_O.lib.Util._encodeConcatUrl(_url, dict)); 1999 2000 if (!empty(host)) url = host + url; 2001 2002 if (url.length > 500) { 2003 var middle, head, tail; 2004 2005 // need to split internal fileA object rather than filesA? 2006 if (filesA.length === 1) { 2007 var msg = filesA[0].failMsg + ''; // copy 2008 var fA = filesA[0].fileA; 2009 2010 middle = Math.floor(fA.length / 2); 2011 2012 // needed (however ugly) to get deep copy 2013 head = [{ 2014 failMsg: msg, 2015 fileA: fA.slice(0, middle), 2016 path: null 2017 }]; 2018 tail = [{ 2019 failMsg: msg, 2020 fileA: fA.slice(middle), 2021 path: null 2022 }]; 2023 } 2024 else { 2025 middle = Math.floor(filesA.length / 2); 2026 head = filesA.slice(0, middle); 2027 tail = filesA.slice(middle); 2028 } 2029 2030 this._loadConcat(head, host, push); 2031 this._loadConcat(tail, host, push); 2032 } 2033 else { 2034 var ajaxCb = scopeC(function(url, files, failMsg) { 2035 return scopeC(function() { 2036 return this._ajax(url, files, failMsg); 2037 }, this); 2038 }, this); 2039 push(ajaxCb(url, checkA, failMsg)); 2040 } 2041 }, 2042 2043 loadFiles: function(filesA, f_concat, host) { 2044 if (!this.alreadyWarnedConcat && !f_concat) { 2045 console.log('O_O.lib.Util.loadFiles: file concatenation disabled'); 2046 this.alreadyWarnedConcat = true; 2047 } 2048 2049 var chainA = []; 2050 function push(cb) { 2051 chainA.push(cb); 2052 } 2053 2054 if (f_concat) { 2055 this._loadConcat(filesA, host, push); 2056 return $.chain(chainA); 2057 } 2058 else { 2059 return this._loadSeq(filesA); 2060 } 2061 }, 2062 2063 // location on steroids!! 2064 // maps url values to app values 2065 // assembles valid "path" for use with the history API 2066 parseUrl: function(target) { 2067 target = target || window.location; 2068 var url = { hash: target.hash, 2069 host: target.host, 2070 hostname: target.hostname, 2071 href: target.href, 2072 pathname: target.pathname, 2073 port: target.port, 2074 protocol: target.protocol, 2075 search: target.search, 2076 2077 // some IE11 versions lack origin on links 2078 origin: target.origin || target.protocol + '//' + target.host, 2079 2080 // part of URL obj spec, but we're not using 2081 //username: target.username, 2082 //password: target.password, 2083
2084 legacy: false, 2085 argA: [], 2086 modeName: '', 2087 path: '', 2088 pathA: [], 2089 queryA: [], 2090 2091 // set by _parseUrlPathMap() 2092 clc_id: null, 2093 gal_id: null, 2094 img_id: null, 2095 vid_id: null, 2096 2097 // easily read query string values 2098 queryGet: function(key) { 2099 if (typeof this.queryA[key] !== 'undefined') 2100 return this.queryA[key]; 2101 else return null; 2102 } 2103 }, 2104 path = url.pathname, 2105 pathA = path.split('/'); 2106 2107 // hash > path 2108 // preserves compatibility with existing external links 2109 // (ex: label.photoshelter.com/#!/mode/foo/bar) 2110 // and sloppy internal links 2111 // (ex: #!/mode/foo/bar) 2112 // that use the deprecated hashbang syntax 2113 if (url.hash.substring(0, 3) === '#!/') { 2114 url.legacy = true; 2115 pathA = url.hash.split('/'); 2116 pathA.shift(); 2117 } 2118 2119 // ignore empty path values caused by leading/trailing slashes 2120 if (pathA[0] === '') pathA.shift(); 2121 if (pathA[pathA.length - 1] === '') pathA.pop(); 2122 2123 // guarantee a mode 2124 url.modeName = !!pathA[0] ? pathA[0] : 'index'; 2125 pathA[0] = url.modeName; 2126 url.argA = (pathA.length > 1) ? pathA.slice(1) : []; 2127 2128 url.pathA = pathA; 2129 url.path = this.makePath(url.modeName, url.argA, url.search); 2130 2131 // in the unlikely event we start using hashes in a new way 2132 if (!url.legacy && url.hash) { 2133 url.path += url.hash; 2134 } 2135 2136 this._parseUrlPathMap(url); 2137 this._parseUrlQueryMap(url); 2138 2139 return url; 2140 }, 2141 2142 // order-irrelevant inference of rec ids from path 2143 _parseUrlPathMap: function(url) { 2144 var pathA = url.pathA || []; 2145 2146 for (var i = 0; i < pathA.length; i++) { 2147 if (this.GAL_ID_REGEX.test(pathA[i])) url.gal_id = pathA[i]; 2148 else if (this.CLC_ID_REGEX.test(pathA[i])) url.clc_id = pathA[i]; 2149 else if (this.IMG_ID_REGEX.test(pathA[i])) url.img_id = pathA[i]; 2150 else if (this.VID_ID_REGEX.test(pathA[i])) url.vid_id = pathA[i]; 2151 } 2152 }, 2153 2154 // maps query/search string values to array 2155 _parseUrlQueryMap: function(url) { 2156 var queryA = url.search.replace('?', '').split('&'); 2157 2158 for (var i=0; i < queryA.length; i++) { 2159 var query = queryA[i].split('='); 2160 2161 switch (query.length) { 2162 case 1: 2163 // no value provided, set to true 2164 url.queryA[query[0]] = true; 2165 break; 2166 case 2: 2167 url.queryA[query[0]] = query[1]; 2168 break; 2169 } 2170 } 2171 }, 2172 2173 // constructs a relative URL path, respects our expected pattern 2174 makePath: function(modeName, argA, query) { 2175 query = query || location.search, 2176 argA = argA || []; 2177 2178 // strip null/empty args 2179 argA = argA.filter(function(arg) { 2180 return (arg !== null && arg !== ''); 2181 }); 2182 2183 var argStr = (argA.length > 0) ? '/' + argA.join('/') : '', 2184 path = '/' + modeName + argStr + query; 2185 2186 return path; 2187 }, 2188 2189 getTypeById: function (id) { 2190 var typeA = { 2191 'I0' : 'image', 2192 'VD' : 'video', 2193 'AD' : 'audio', 2194 'DO' : 'doc', 2195 'C0' : 'collection', 2196 'G0' : 'gallery' 2197 }; 2198 2199 var type; 2200 if (!id) type = false;
2201 else type = typeA[id.slice(0,2)]; 2202 2203 if (!type) console.error('unable to detect type of id ' + id); 2204 2205 return type; 2206 }, 2207 2208 getDisplayNameByType: function (type) { 2209 if (type == 'doc') return 'file'; 2210 else return type; 2211 }, 2212 2213 getModelPathForId: function (id) { 2214 var type = O_O.lib.Util.getTypeById(id); 2215 return 'O_O.model_v2.' + type[0].toUpperCase() + type.slice(1); 2216 }, 2217 2218 /** 2219 * Dispatches a bubbling, composed custom event to pass data along in a 2220 * platform native fashion 2221 * 2222 * CustomEvent is supported in all supported browsers except IE11 2223 * A polyfill for the CustomEvent constructor is jsIncluded on c2.tpl 2224 * 2225 * @param {Object} evtTarget event target node 2226 * @param {String} eventName custom event name 2227 * @param {Object} details custom event detail object 2228 */ 2229 customEventDispatch: function (evtTarget, eventName, details) { 2230 const customEvent = new CustomEvent(eventName, { 2231 detail: details, 2232 bubbles: true, 2233 composed: true 2234 }); 2235 evtTarget.dispatchEvent(customEvent); 2236 }, 2237 2238}; 2239 2240O_O.lib.str = { 2241 dashify: function(str) { 2242 var trA = {'\'': ''}; 2243 2244 trA["\xE1"] = "a"; trA["\xC1"] = "A"; 2245 trA["\xE2"] = "a"; trA["\xC2"] = "A"; 2246 trA["\xE0"] = "a"; trA["\xC0"] = "A"; 2247 trA["\xE5"] = "a"; trA["\xC5"] = "A"; 2248 trA["\xE3"] = "a"; trA["\xC3"] = "A"; 2249 trA["\xC4"] = "A"; trA["\xE4"] = "a"; 2250 trA["\xE7"] = "c"; trA["\xC7"] = "C"; 2251 trA["\xD0"] = "E"; trA["\xF0"] = "e"; 2252 trA["\xC9"] = "E"; trA["\xE9"] = "e"; 2253 trA["\xCA"] = "E"; trA["\xC8"] = "E"; 2254 trA["\xCB"] = "E"; trA["\xEB"] = "e"; 2255 trA["\xCD"] = "I"; trA["\xED"] = "i"; 2256 trA["\xCE"] = "I"; trA["\xEE"] = "i"; 2257 trA["\xCC"] = "I"; trA["\xEC"] = "i"; 2258 trA["\xCF"] = "I"; trA["\xEF"] = "i"; 2259 trA["\xD1"] = "N"; trA["\xF1"] = "n"; 2260 trA["\xD3"] = "O"; trA["\xE8"] = "e"; 2261 trA["\xF3"] = "o"; trA["\xE6"] = "ae"; 2262 trA["\xD4"] = "O"; trA["\xF4"] = "o"; 2263 trA["\xF2"] = "o"; trA["\xD2"] = "O"; 2264 trA["\xD8"] = "O"; trA["\xF5"] = "o"; 2265 trA["\xF8"] = "o"; trA["\xD5"] = "O"; 2266 trA["\xD6"] = "O"; trA["\xF6"] = "o"; 2267 trA["\xFE"] = "t"; trA["\xDE"] = "T"; 2268 trA["\xDA"] = "U"; trA["\xFA"] = "u"; 2269 trA["\xDB"] = "U"; trA["\xFB"] = "u"; 2270 trA["\xD9"] = "U"; trA["\xF9"] = "u"; 2271 trA["\xDC"] = "U"; trA["\xFC"] = "u"; 2272 trA["\xDD"] = "Y"; trA["\xFD"] = "y"; 2273 trA["\xFF"] = "y"; 2274 2275 str = this.strtr(str, trA); 2276 2277 str = str.replace(/[^a-zA-Z0-9]+/g, '-'); 2278 str = str.replace(/^[\-]+|(-$)+/g, ""); 2279 if (str === '') str = '-'; 2280 return this.fixedEncodeURI(str); 2281 } 2282}; 2283 2284}(ps$, window)); 2285;(function($, undefined) { 2286'use strict'; 2287 2288// set up app namespaces 2289// O_O.lib = {}; // in util now 2290// O_O.model = {}; // in model root 2291O_O.theme = {}; 2292O_O.widget = {}; 2293 2294O_O.app.Visualizer = function(elemId, cfg, opt) { 2295 if (this._redirectIfNeeded()) return; // /index/hash/foo => /#!/foo 2296 2297 this.elemId = elemId; 2298 2299 this.customEnv = cfg.customEnv; 2300 this.theme = cfg.theme; 2301 this.themeCfg = JSON.parse(cfg.themeCfg); // stringified 2302 2303 O_O.lib.Util.LOAD_REV = this.customEnv.js_include_rev; // HACK: rewrite 2304 2305 if (typeof cfg.customEnv !== 'object') { 2306 throw new Error('Cannot load Visualizer; customEnv undefined'); 2307 } 2308 if (typeof cfg.theme !== 'string') { 2309 throw new Error('Cannot load Visualizer; theme undefined'); 2310 } 2311 if (typeof this.themeCfg !== 'object') { 2312 throw new Error('Cannot load Visualizer; themeCfg is not JSON'); 2313 } 2314 if (typeof opt !== 'object') { 2315 throw new Error('Cannot load Visualizer; opt undefined'); 2316 } 2317 2318 this.widgetVers = this._widgetVersionsFromRequires(this.themeCfg.requires); 2319 //add in settings version 2320 this.widgetVers[this.SETTINGS_WIDGET] = this.SETTINGS_WIDGET_VERS; 2321 2322 this.opt = { 2323 f_edit: opt.f_edit || false, // edit mode 2324 f_debug: opt.f_debug || false, // true to get line numbers for debugging 2325 themeList: opt.themeList || null // for Settings/ThemePicker 2326 }; 2327 2328 O_O.lib.Util.CACHE_BREAK_URLS = this.opt.f_debug; // HACK: direct setter 2329 2330 var filesA = [ 2331 { 2332 path: this.PATH_LIB, 2333 fileA: [ 2334 'O_O.lib.ModeStack', 2335 'O_O.lib.Fetcher', 2336 'O_O.lib.Scheduler', 2337 'O_O.lib.Swiper', 2338 'O_O.lib.psImg', 2339 'O_O.lib.Dirty', 2340 'O_O.lib.Mode', 2341 'O_O.lib.Controller', 2342 'O_O.lib.View' 2343 ], 2344 failMsg: 'Visualizer failed to load app libraries' 2345 }, 2346 { 2347 path: this.PATH_MODEL, 2348 /* 2349 fileA: this.opt.f_edit ? 2350 ['O_O.model.Model', 'O_O.model.PSModel', 'O_O.model.Config'] : 2351 ['O_O.model.Model', 'O_O.model.PSModel'], 2352 */ 2353 fileA: ['O_O.model.Model', 'O_O.model.PSModel'], 2354 failMsg: 'Visualizer failed to load app models' 2355 }, 2356 { 2357 path: this.PATH_APP, 2358 fileA: ['O_O.app.Shell'], 2359 failMsg: 'Visualizer failed to load app shell' 2360 } 2361 ]; 2362 2363 this._init(filesA); 2364}; 2365 2366O_O.app.Visualizer.prototype = { 2367 /* constants */ 2368 2369 PATH_LIB: 'lib', 2370 PATH_MODEL: 'model', 2371 PATH_APP: 'app', 2372 PATH_THEME: 'theme', 2373 PATH_WIDGET: 'widget', 2374 2375 PREFIX_THEME: 'O_O.theme', 2376 PREFIX_MODEL: 'O_O.model', 2377 PREFIX_WIDGET: 'O_O.widget', 2378
2379 LOAD_DIR: 'js', 2380 LOAD_VERS: '2.0', 2381 2382 URL_THEME_UPD: '/psapi/v2/mem/user/theme/update', 2383 SETTINGS_WIDGET: 'Settings', // loaded in edit mode 2384 SETTINGS_WIDGET_VERS: '1.0', 2385 TPL_FILE_BASE: 'tpl', 2386 2387 /* state */ 2388 2389 elemId: null, 2390 2391 customEnv: null, 2392 theme: null, 2393 themeCfg: null, 2394 2395 opt: null, 2396 modeTpls: null, 2397 2398 /* redirect */ 2399 2400 _redirectIfNeeded: function() { 2401 var path = location.pathname; 2402 if (path.indexOf('/index/hash') === 0) { 2403 var newPath = location.origin || 2404 location.protocol + '//' + location.host; 2405 var search; 2406 if (path.indexOf('edit') >= 0) { 2407 path = path.replace('/edit', ''); 2408 search = '?edit'; 2409 } 2410 else { 2411 search = ''; 2412 } 2413 newPath += search + '#!' + path.replace('/index/hash', '') + 2414 location.hash.substr(1); 2415 window.location = newPath; 2416 return true; 2417 } 2418 return false; 2419 }, 2420 2421 /* tpl loading */ 2422 2423 _loadModeTpls: function() { 2424 var $dfd = $.Deferred(); 2425 var url = this.customEnv.server.js + '/include/' + 2426 this.customEnv.js_include_rev + '/0/' + 2427 this.LOAD_DIR + '/' + this.LOAD_VERS + '/' + 2428 this.PATH_THEME + '/' + this.theme + '/' + 2429 this.TPL_FILE_BASE + '-' + this.themeCfg.version + '.html'; 2430 2431 var $temp = $('<div>').load(url, scopeC(function() { 2432 var $script, modeName, tpls = {}; 2433 2434 $temp.children('script').each(function() { 2435 $script = $(this); 2436 modeName = $script.data('mode'); 2437 2438 tpls[modeName] = O_O.tpl($script.html()); // not yet invoked 2439 }); 2440 2441 $temp = null; 2442 this.modeTpls = tpls; 2443 $dfd.resolve(); 2444 }, this)); 2445 2446 return $dfd.promise().fail(scopeC(function() { 2447 throw new Error('Failed to load templates for theme ' + this.theme); 2448 })); 2449 }, 2450 2451 /* init */ 2452 2453 _serializeReqs: function(reqs) { 2454 var reqA = []; 2455 2456 var i, l, widget, vers; 2457 2458 // models 2459 var fileA = []; 2460 for (i = 0, l = reqs.models.length; i < l; i++) { 2461 fileA.push(this.PREFIX_MODEL + '.' + reqs.models[i]); 2462 } 2463 reqA.push({ 2464 path: this.PATH_MODEL, 2465 fileA: fileA, 2466 failMsg: 'Visualizer failed to load theme models' 2467 }); 2468 2469 // widget manifests 2470 if (this.opt.f_edit) { 2471 widget = this.SETTINGS_WIDGET; // add if in edit mode 2472 vers = this.SETTINGS_WIDGET_VERS; 2473 2474 reqA.push({ 2475 path: this.PATH_WIDGET + '/' + widget + '/' + vers, 2476 fileA: [this.PREFIX_WIDGET + '.' + widget], 2477 failMsg: 'Visualizer failed to load widget manifest ' + widget 2478 }); 2479 } 2480 for (i = 0, l = reqs.widgets.length; i < l; i++) { 2481 widget = reqs.widgets[i][0]; 2482 vers = reqs.widgets[i][1]; 2483 2484 reqA.push({ 2485 path: this.PATH_WIDGET + '/' + widget + '/' + vers, 2486 fileA: [this.PREFIX_WIDGET + '.' + widget], 2487 failMsg: 'Visualizer failed to load widget manifest ' + widget 2488 }); 2489 } 2490 2491 return reqA; 2492 }, 2493 2494 _widgetVersionsFromRequires: function(reqs) { 2495 var i, l, widgets, widget, vers, widgetVers = {}; 2496 2497 widgets = reqs.widgets; 2498 for (i = 0, l = widgets.length; i < l; i++) { 2499 widget = widgets[i][0]; 2500 vers = widgets[i][1]; 2501 2502 widgetVers[widget] = vers; 2503 } 2504 2505 return widgetVers; 2506 }, 2507 2508 _init: function(filesA) { 2509 var fA = filesA.concat(this._serializeReqs(this.themeCfg.requires)); 2510 2511 // short aliases for local use 2512 var pP = scopeC(O_O.lib.Util.getWinPropPath, O_O.lib.Util); 2513 var lF = scopeC(O_O.lib.Util.loadFiles, O_O.lib.Util); 2514 2515 // make theme config global property
2516 var themePath = pP(this.PREFIX_THEME); 2517 themePath[this.theme] = this.themeCfg; 2518 2519 $.when.apply($, [ 2520 lF(fA, !this.opt.f_debug, this.customEnv.server.js), 2521 this._loadModeTpls() 2522 ]).done(scopeC(function() { 2523 O_O.model.setEnv($.extend(true, {}, this.customEnv, this.opt)); 2524 this._startShell(); 2525 }, this)); 2526 }, 2527 2528 _startShell: function() { 2529 O_O.shell = O_O.mm.oNew(O_O.app.Shell, this.elemId, { 2530 customEnv: this.customEnv, 2531 themeCfg: this.themeCfg, 2532 widgetVers: this.widgetVers, 2533 modeTpls: this.modeTpls 2534 }, this.opt); 2535 } 2536}; 2537 2538}(ps$));
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.