PageSourceSearch

https://www.claudialopezphotography.com/js/APP/1788533357/2.0/lib/…/O_O.lib.Util.js,app/O_O.app.Visualizer.js

js claudialopezphotography.com collected 2026-09-27 07:33:16 UTC 62,117 bytes, 2,538 lines download raw bytes

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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#x27;').replace(/\//g,'&#x2F;');
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.