PageSourceSearch

https://www.railfuturescotland.org.uk/lib/jquery_tiki/tiki-jquery.js

js railfuturescotland.org.uk collected 2026-10-02 14:26:09 UTC 86,397 bytes, 3,111 lines download raw bytes

1// $Id: tiki-jquery.js 42029 2012-06-22 16:39:32Z robertplummer $
2// JavaScript glue for jQuery in Tiki
3//
4// Tiki 6 - $ is now initialised in jquery.js
5// but let's keep $jq available too for legacy custom code
6
7var $jq = $,
8$window = $(window)
9$document = $(document);
10
11// Escape a string for use as a jQuery selector value, for example an id or a class
12function escapeJquery(str) {
13	return str.replace(/([\!"#\$%&'\(\)\*\+,\.\/:;\?@\[\\\]\^`\{\|\}\~=>])/g, "\\$1");
14}
15
16// Check / Uncheck all Checkboxes - overriden from tiki-js.js
17function switchCheckboxes (tform, elements_name, state) {
18	// checkboxes need to have the same name elements_name
19	// e.g. <input type="checkbox" name="my_ename[]">, will arrive as Array in php.
20	$(tform).contents().find('input[name="' + escapeJquery(elements_name) + '"]:visible').attr('checked', state).change();
21}
22
23
24// override existing show/hide routines here
25
26// add id's of any elements that don't like being animated here
27var jqNoAnimElements = ['help_sections', 'ajaxLoading'];
28
29function show(foo, f, section) {
30	if ($.inArray(foo, jqNoAnimElements) > -1 || typeof jqueryTiki === 'undefined') {		// exceptions that don't animate reliably
31		$("#" + foo).show();
32	} else if ($("#" + foo).hasClass("tabcontent")) {		// different anim prefs for tabs
33		showJQ("#" + foo, jqueryTiki.effect_tabs, jqueryTiki.effect_tabs_speed, jqueryTiki.effect_tabs_direction);
34	} else {
35		if ($.browser.webkit && !jqueryTiki.effect && $("#role_main #" + foo).length) {	// safari/chrome does strange things with default amination in central column
36			showJQ("#" + foo, "slide", jqueryTiki.effect_speed, jqueryTiki.effect_direction);
37		} else {
38			showJQ("#" + foo, jqueryTiki.effect, jqueryTiki.effect_speed, jqueryTiki.effect_direction);
39		}
40	}
41	if (f) {setCookie(foo, "o", section);}
42}
43
44function hide(foo, f, section) {
45	if ($.inArray(foo, jqNoAnimElements) > -1 || typeof jqueryTiki === 'undefined') {		// exceptions
46		$("#" + foo).hide();
47	} else if ($("#" + foo).hasClass("tabcontent")) {
48		hideJQ("#" + foo, jqueryTiki.effect_tabs, jqueryTiki.effect_tabs_speed, jqueryTiki.effect_tabs_direction);
49	} else {
50		hideJQ("#" + foo, jqueryTiki.effect, jqueryTiki.effect_speed, jqueryTiki.effect_direction);
51	}
52	if (f) {
53//		var wasnot = getCookie(foo, section, 'x') == 'x';
54		setCookie(foo, "c", section);
55//		if (wasnot) {
56//			history.go(0);	// used to reload the page with all menu items closed - broken since 3.x
57//		}
58	}
59}
60
61// flip function... unfortunately didn't use show/hide (ay?)
62function flip(foo, style) {
63	if (style && style !== 'block' || foo === 'help_sections' || foo === 'fgalexplorer' || typeof jqueryTiki === 'undefined') {	// TODO find a better way?
64		$("#" + foo).toggle();	// inlines don't animate reliably (yet) (also help)
65		if ($("#" + foo).css('display') === 'none') {
66			setSessionVar('show_' + escape(foo), 'n');
67		} else {
68			setSessionVar('show_' + escape(foo), 'y');
69		}
70	} else {
71		if ($("#" + foo).css("display") === "none") {
72			setSessionVar('show_' + escape(foo), 'y');
73			show(foo);
74		}
75		else {
76			setSessionVar('show_' + escape(foo), 'n');
77			hide(foo);
78		}
79	}
80}
81
82// handle JQ effects
83function showJQ(selector, effect, speed, dir) {
84	if (effect === 'none') {
85		$(selector).show();
86	} else if (effect === '' || effect === 'normal') {
87		$(selector).show(400);	// jquery 1.4 no longer seems to understand 'nnormal' as a speed
88	} else if (effect == 'slide') {
89		// With jquery 1.4.2 (and less) and IE7, the function slidedown is buggy
90		// See: http://dev.jquery.com/ticket/3120
91		if ($.browser.msie && parseInt($.browser.version, 10) == 7)	{
92			$(selector).show(speed);
93		} else {
94			$(selector).slideDown(speed);
95		}
96	} else if (effect === 'fade') {
97		$(selector).fadeIn(speed);
98	} else if (effect.match(/(.*)_ui$/).length > 1) {
99		$(selector).show(effect.match(/(.*)_ui$/)[1], {direction: dir}, speed);
100	} else {
101		$(selector).show();
102	}
103}
104
105function hideJQ(selector, effect, speed, dir) {
106	if (effect === 'none') {
107		$(selector).hide();
108	} else if (effect === '' || effect === 'normal') {
109		$(selector).hide(400);	// jquery 1.4 no longer seems to understand 'nnormal' as a speed
110	} else if (effect === 'slide') {
111		$(selector).slideUp(speed);
112	} else if (effect === 'fade') {
113		$(selector).fadeOut(speed);
114	} else if (effect.match(/(.*)_ui$/).length > 1) {
115		$(selector).hide(effect.match(/(.*)_ui$/)[1], {direction: dir}, speed);
116	} else {
117		$(selector).hide();
118	}
119}
120
121// override overlib
122function convertOverlib(element, tip, params) {	// process modified overlib event fn to cluetip from {popup} smarty func
123	
124	if ($(element).data('processed') || typeof $(element).cluetip != "function") {return false;}
125	if (typeof params == "undefined") {params = [];}
126	
127	var options = {};
128	options.clickThrough = true;
129	for (var param = 0; param < params.length; param++) {
130		var val = "", pam;
131		var i = params[param].indexOf("=");
132		if (i > -1) {
133			var arr = params[param].split("=", 2);
134			pam = params[param].substring(0, i).toLowerCase();
135			val = params[param].substring(i+1);
136		} else {
137			pam = params[param].toLowerCase();
138		}
139		switch (pam) {
140			case "sticky":
141				options.sticky = true;
142				break;
143			case "fullhtml":
144				options.cluetipClass = 'fullhtml';
145				break;
146			case "background":
147				options.cluetipClass = 'fullhtml';
148				tip = '<div style="background-image: url(' + val + '); height:' + options.height + 'px">' + tip + '</div>';
149				break;
150			case "onclick":
151				options.activation = 'click';
152				options.clickThrough = false;
153				break;
154			case "width":
155				options.width = val;
156				break;
157			case "height":
158				options.height = val;
159				break;
160			default:
161				break;
162		}
163	}
164	
165	options.splitTitle = '|';
166	options.showTitle = false;
167	options.cluezIndex = 400;
168	options.dropShadow = true;
169	options.fx = {open: 'fadeIn', openSpeed: 'fast'};
170	options.closeText = 'x';
171	options.closePosition = 'title';
172	options.mouseOutClose = true;
173	//options.positionBy = 'mouse';	// TODO - add a param for this one if desired
174	
175	// attach new tip
176	
177	if (element.tipWidth) {
178		options.width = element.tipWidth;
179	} else if (!options.width || options.width === 'auto') {
180		// hack to calculate div width
181		var $el = $("<div />")
182			.css('display', 'none')
183			.insertBefore("#main")
184			.html(tip);
185		
186		if ($el.width() > $window.width()) {
187			$el.width($window.width() * 0.8);
188		}
189		options.width = $el.width() + 26;	// add space for css padding
190		$el.remove();
191		
192		element.tipWidth = options.width;
193	}
194	
195	var prefix = "|";
196	$(element).attr('title', prefix + tip);
197
198	if (options.activation !== 'click') {
199		options.hoverIntent = {sensitivity: 3, interval: 50, timeout: 0};
200	}
201	$(element).data('processed', true);	
202
203//options.sticky = true; //useful for css work
204	$(element).cluetip(options);
205
206	if (options.activation === 'click') {
207		$(element).trigger('click');
208	} else {
209		$(element).trigger('mouseover');
210	}
211	setTimeout(function () {$("#cluetip").show();}, 200);	// IE doesn't necessarily display
212	$(element).attr("title", "");	// remove temporary title attribute to avoid built in browser tips
213	return false;
214}
215
216function nd() {
217	$("#cluetip").hide();
218}
219
220// ajax loading indicator
221
222function ajaxLoadingShow(destName) {
223	var $dest, $loading, pos, x, y, w, h;
224	
225	if (typeof destName === 'string') {
226		$dest = $('#' + destName);
227	} else {
228		$dest = $(destName);
229	}
230	if ($dest.length === 0 || $dest.parents(":hidden").length > 0) {
231		return;
232	}
233	$loading = $('#ajaxLoading');
234
235	// find area of destination element
236	pos = $dest.offset();
237	// clip to page
238	if (pos.left + $dest.width() > $window.width()) {
239		w = $window.width() - pos.left;
240	} else {
241		w = $dest.width();
242	}
243	if (pos.top + $dest.height() > $window.height()) {
244		h = $window.height() - pos.top;
245	} else {
246		h = $dest.height();
247	}
248	x = pos.left + (w / 2) - ($loading.width() / 2);
249	y = pos.top + (h / 2) - ($loading.height() / 2);
250	
251
252	// position loading div
253	$loading.css('left', x).css('top', y);
254	// now BG
255	x = pos.left + ccsValueToInteger($dest.css("margin-left"));
256	y = pos.top + ccsValueToInteger($dest.css("margin-top"));
257	w = ccsValueToInteger($dest.css("padding-left")) + $dest.width() + ccsValueToInteger($dest.css("padding-right"));
258	h = ccsValueToInteger($dest.css("padding-top")) + $dest.height() + ccsValueToInteger($dest.css("padding-bottom"));
259	$('#ajaxLoadingBG').css('left', pos.left).css('top', pos.top).width(w).height(h).fadeIn("fast");
260	
261	show('ajaxLoading');
262
263	
264}
265
266function ajaxLoadingHide() {
267	hide('ajaxLoading');
268	$('#ajaxLoadingBG').fadeOut("fast");
269}
270
271
272function checkDuplicateRows( button, columnSelector, rowSelector ) {
273	if (typeof columnSelector === 'undefined') {
274		columnSelector = "td";
275	}
276	if (typeof rowSelector === 'undefined') {
277		rowSelector = "table:first tr:not(:first)";
278	}
279	var $rows = $(button).parents(rowSelector);
280	$rows.each(function( ix, el ){
281		if ($("input:checked", el).length === 0) {
282			var $el = $(el);
283			var line = $el.find(columnSelector).text();
284			$rows.each(function( ix, el ){
285				if ($el[0] !== el && $("input:checked", el).length === 0) {
286					if (line === $(el).find(columnSelector).text()) {
287						$(":checkbox:first", el).attr("checked", true);
288					}
289				}
290			});
291		}
292	});
293}
294
295function setUpClueTips() {
296	var ctOptions = {splitTitle: '|', cluezIndex: 2000, width: 'auto', fx: {open: 'fadeIn', openSpeed: 'fast'},
297		clickThrough: true, hoverIntent: {sensitivity: 3, interval: 100, timeout: 0}};
298
299	$.cluetip.setup = {insertionType: 'insertBefore', insertionElement: '#main'};
300	
301	$('.tips[title!=""]').cluetip($.extend(ctOptions, {}));
302	$('.titletips[title!=""]').cluetip($.extend(ctOptions, {}));
303	$('.tikihelp[title!=""]').cluetip($.extend(ctOptions, {splitTitle: ':'})); // , width: '150px'
304	
305	// unused?
306	$('.stickytips').cluetip($.extend(ctOptions, {showTitle: false, sticky: false, local: true, hideLocal: true, activation: 'click', cluetipClass: 'fullhtml'}));
307	
308	// repeats for "tiki" buttons as you cannot set the class and title on the same element with that function (it seems?)
309	//$('span.button.tips a').cluetip({splitTitle: '|', showTitle: false, width: '150px', cluezIndex: 400, fx: {open: 'fadeIn', openSpeed: 'fast'}, clickThrough: true});
310	//$('span.button.titletips a').cluetip({splitTitle: '|', cluezIndex: 400, fx: {open: 'fadeIn', openSpeed: 'fast'}, clickThrough: true});
311	// TODO after 5.0 - these need changes in the {button} Smarty fn
312}
313
314$(function() { // JQuery's DOM is ready event - before onload
315	if (!window.jqueryTiki) window.jqueryTiki = {};
316
317	// tooltip functions and setup
318	if (jqueryTiki.tooltips) {	// apply "cluetips" to all .tips class anchors
319		
320		setUpClueTips();
321		
322	}	// end cluetip setup
323	
324	// Reflections
325	if (jqueryTiki.replection) {
326		$("img.reflect").reflect({});
327	}
328
329	// superfish setup (CSS menu effects)
330	if (jqueryTiki.superfish) {
331		$('ul.cssmenu_horiz').supersubs({ 
332            minWidth:    11,   // minimum width of sub-menus in em units 
333            maxWidth:    20,   // maximum width of sub-menus in em units 
334            extraWidth:  1     // extra width can ensure lines don't sometimes turn over 
335                               // due to slight rounding differences and font-family 
336		});
337		$('ul.cssmenu_vert').supersubs({ 
338            minWidth:    11,   // minimum width of sub-menus in em units 
339            maxWidth:    20,   // maximum width of sub-menus in em units 
340            extraWidth:  1     // extra width can ensure lines don't sometimes turn over 
341                               // due to slight rounding differences and font-family 
342		});
343		$('ul.cssmenu_horiz').superfish({
344			animation: {opacity:'show', height:'show'},	// fade-in and slide-down animation
345			speed: 'fast',								// faster animation speed
346			onShow: function(){
347				$(this).moveToWithinWindow();
348			}
349		});
350		$('ul.cssmenu_vert').superfish({
351			animation: {opacity:'show', height:'show'},	// fade-in and slide-down animation
352			speed: 'fast',								// faster animation speed
353			onShow: function(){
354				$(this).moveToWithinWindow();
355			}
356		});
357	}
358	
359	// tablesorter setup (sortable tables?)
360	if (jqueryTiki.tablesorter) {
361		$('.sortable').tablesorter({
362			widthFixed: true							// ??
363//			widgets: ['zebra'],							// stripes (coming soon)
364		});
365	}
366	
367	// ColorBox setup (Shadowbox, actually "<any>box" replacement)
368	if (jqueryTiki.colorbox) {
369		$().bind('cbox_complete', function(){	
370			$("#cboxTitle").wrapInner("<div></div>");
371		});
372				
373		// Tiki defaults for ColorBox
374		
375		// for every link containing 'shadowbox' or 'colorbox' in rel attribute
376		$("a[rel*='box']").colorbox({
377			transition: "elastic",
378			maxHeight:"95%",
379			maxWidth:"95%",
380			overlayClose: true,
381			title: true,
382			current: jqueryTiki.cboxCurrent
383		});
384		
385		// now, first let suppose that we want to display images in ColorBox by default:
386		
387		// this matches rel containg type=img or no type= specified
388		$("a[rel*='box'][rel*='type=img'], a[rel*='box'][rel!='type=']").colorbox({
389			photo: true
390		});
391		// rel containg slideshow (this one must be without #col1)
392		$("a[rel*='box'][rel*='slideshow']").colorbox({
393			photo: true,
394			slideshow: true,
395			slideshowSpeed: 3500,
396			preloading: false,
397			width: "100%",
398			height: "100%"
399		});
400		// this are the defaults matching all *box links which are not obviously links to images...
401		// (if we need to support more, add here... otherwise it is possible to override with type=iframe in rel attribute of a link)
402		//  (from here one to speed it up, matches any link in #col1 only - the main content column)
403		
404		$("#col1 a[rel*='box']:not([rel*='type=img']):not([href*='display']):not([href*='preview']):not([href*='thumb']):not([rel*='slideshow']):not([href*='image']):not([href$='\.jpg']):not([href$='\.jpeg']):not([href$='\.png']):not([href$='\.gif'])").colorbox({
405			iframe: true,
406			width: "95%",
407			height: "95%"
408		});
409		// hrefs starting with ftp(s)
410		$("#col1 a[rel*='box'][href^='ftp://'], #col1 a[rel*='box'][href^='ftps://']").colorbox({
411			iframe: true,
412			width: "95%",
413			height: "95%"
414		});
415		// rel containg type=flash
416		$("#col1 a[rel*='box'][rel*='type=flash']").colorbox({
417			flash: true,
418			iframe: false
419		});
420		// rel with type=iframe (if someone needs to override anything above)
421		$("#col1 a[rel*='box'][rel*='type=iframe']").colorbox({
422			iframe: true
423		});
424		// inline content: hrefs starting with #
425		$("#col1 a[rel*='box'][href^='#']").colorbox({
426			inline: true,
427			width: "50%",
428			height: "50%",
429			href: function(){ 
430				return $(this).attr('href');
431			}
432		});
433		
434		// titles (for captions):
435		
436		// by default get title from the title attribute of the link (in all columns)
437		$("a[rel*='box'][title]").colorbox({
438			title: function(){ 
439				return $(this).attr('title');
440			}
441		});
442		// but prefer the title from title attribute of a wrapped image if any (in all columns)
443		$("a[rel*='box'] img[title]").colorbox({
444			title: function(){ 
445				return $(this).attr('title');
446			},
447			photo: true,				// and if you take title from the image you need photo 
448			href: function(){			// and href as well (for colobox 1.3.6 tiki 5.0)
449				return $(this).parent().attr("href");
450			}
451		});
452		
453		/* Shadowbox params compatibility extracted using regexp functions */
454		var re, ret;
455		// rel containg title param overrides title attribute of the link (shadowbox compatible)
456		$("#col1 a[rel*='box'][rel*='title=']").colorbox({
457			title: function () {
458				re = /(title=([^;\"]+))/i;
459				ret = $(this).attr("rel").match(re);
460				return ret[2];
461			}
462		});
463		// rel containg height param (shadowbox compatible)
464		$("#col1 a[rel*='box'][rel*='height=']").colorbox({
465			height: function () {
466				re = /(height=([^;\"]+))/i;
467				ret = $(this).attr("rel").match(re);
468				return ret[2];
469			}
470		});
471		// rel containg width param (shadowbox compatible)
472		$("#col1 a[rel*='box'][rel*='width=']").colorbox({
473			width: function () {
474				re = /(width=([^;\"]+))/i;
475				ret = $(this).attr("rel").match(re);
476				return ret[2];
477			}
478		});	
479		
480		// links generated by the {COLORBOX} plugin
481		if (jqueryTiki.colorbox) {
482			$("a[rel^='shadowbox[colorbox']").each(function () {$(this).attr('savedTitle', $(this).attr('title'));});
483			if (jqueryTiki.tooltips) {
484				$("a[rel^='shadowbox[colorbox']").cluetip({
485					splitTitle: '<br />', 
486					cluezIndex: 400, 
487					width: 'auto', 
488					fx: {open: 'fadeIn', openSpeed: 'fast'}, 
489					clickThrough: true
490				});
491			}
492			$("a[rel^='shadowbox[colorbox']").colorbox({
493				title: function() {
494					return $(this).attr('savedTitle');	// this fix not required is colorbox was disabled
495				}
496			});
497		}
498		
499	}	// end if (jqueryTiki.colorbox)
500
501	// make all selects into ui selectmenus depending on $prefs['jquery_ui_selectmenu_all']
502	if (jqueryTiki.selectmenu) {
503		var $smenus, hidden = [];
504		if (jqueryTiki.selectmenuAll) {
505			$smenus = $("select")
506		} else {
507			$smenus = $("select.selectmenu");
508		}
509		if ($smenus.length) {
510			$smenus.each ( function () {
511				$.merge( hidden, $(this).parents("fieldset:hidden:last, div:hidden:last"));
512			});
513			hidden = $.unique($(hidden));
514			hidden.show();
515			$smenus.tiki("selectmenu");
516			hidden.hide();
517		}
518	}
519	
520	$( function() {
521		$("#keepOpenCbx").click(function() {
522			if (this.checked) {
523				setCookie("fgalKeepOpen", "1");
524			} else {
525				setCookie("fgalKeepOpen", "");
526			}
527		})
528		var keepopen = getCookie("fgalKeepOpen");
529		if (keepopen) {
530			$("#keepOpenCbx").attr("checked", "checked");
531		} else {
532			$("#keepOpenCbx").removeAttr("checked");
533		}
534	});
535	// end fgal fns
536
537
538	$.paginationHelper();	
539});		// end $document.ready
540
541//For ajax/custom search
542$document.bind('pageSearchReady', function() {
543	$.paginationHelper();
544});
545
546// moved from tiki-list_file_gallery.tpl in tiki 6
547function checkClose() {
548	if (!$("#keepOpenCbx").attr("checked")) {
549		window.close();
550	} else {
551		window.blur();
552		if (window.opener) {
553			window.opener.focus();
554		}
555	}
556};
557
558
559/* Autocomplete assistants */
560
561function parseAutoJSON(data) {
562	var parsed = [];
563	return $.map(data, function(row) {
564		return {
565			data: row,
566			value: row,
567			result: row
568		};
569	});
570}
571
572/// jquery ui dialog replacements for popup form code
573/// need to keep the old non-jq version in tiki-js.js as jq-ui is optional (Tiki 4.0)
574/// TODO refactor for 4.n
575
576/* wikiplugin editor */
577function popupPluginForm(area_id, type, index, pageName, pluginArgs, bodyContent, edit_icon, selectedMod){
578    if (!$.ui) {
579		alert("dev notice: no jq.ui here?");
580        return popup_plugin_form(area_id, type, index, pageName, pluginArgs, bodyContent, edit_icon); // ??
581    }
582	if ($("#" + area_id).length && $("#" + area_id)[0].createTextRange) {	// save selection for IE
583		storeTASelection(area_id);
584	}
585
586    var container = $('<div class="plugin"></div>');
587
588    if (!index) {
589        index = 0;
590    }
591    if (!pageName) {
592        pageName = '';
593    }
594	var textarea = $('#' + area_id)[0];
595	var replaceText = false;
596	
597	if (!pluginArgs && !bodyContent) {
598		pluginArgs = {};
599		bodyContent = "";
600		
601		dialogSelectElement( area_id, '{' + type.toUpperCase(), '{' + type.toUpperCase() + '}' ) ;
602		var sel = getTASelection( textarea );
603		if (sel && sel.length > 0) {
604			sel = sel.replace(/^\s\s*/, "").replace(/\s\s*$/g, "");	// trim
605			//alert(sel.length);
606			if (sel.length > 0 && sel.substring(0, 1) === '{') { // whole plugin selected
607				var l = type.length;
608				if (sel.substring(1, l + 1).toUpperCase() === type.toUpperCase()) { // same plugin
609					var rx = new RegExp("{" + type + "[\\(]?([\\s\\S^\\)]*?)[\\)]?}([\\s\\S]*){" + type + "}", "mi"); // using \s\S matches all chars including lineends
610					var m = sel.match(rx);
611					if (!m) {
612						rx = new RegExp("{" + type + "[\\(]?([\\s\\S^\\)]*?)[\\)]?}([\\s\\S]*)", "mi"); // no closing tag
613						m = sel.match(rx);
614					}
615					if (m) {
616						var paramStr = m[1];
617						bodyContent = m[2];
618						
619						var pm = paramStr.match(/([^=]*)=\"([^\"]*)\"\s?/gi);
620						if (pm) {
621							for (var i = 0; i < pm.length; i++) {
622								var ar = pm[i].split("=");
623								if (ar.length) { // add cleaned vals to params object
624									pluginArgs[ar[0].replace(/^[,\s\"\(\)]*/g, "")] = ar[1].replace(/^[,\s\"\(\)]*/g, "").replace(/[,\s\"\(\)]*$/g, "");
625								}
626							}
627						}
628					}
629					replaceText = sel;
630				} else {
631					if (!confirm("You appear to have selected text for a different plugin, do you wish to continue?")) {
632						return false;
633					}
634					bodyContent = sel;
635					replaceText = true;
636				}
637			} else { // not (this) plugin
638				if (type == 'mouseover') { // For MOUSEOVER, we want the selected text as label instead of body
639					bodyContent = '';
640					pluginArgs = {};
641					pluginArgs['label'] = sel;
642				} else {
643					bodyContent = sel;
644				}
645				replaceText = true;
646			}
647		} else {	// no selection
648			replaceText = false;
649		}
650    }
651    var form = build_plugin_form(type, index, pageName, pluginArgs, bodyContent, selectedMod);
652    
653    //with PluginModule, if the user selects another module while the edit form is open
654    //replace the form with a new one with fields to match the parameters for the module selected
655	$(form).find('tr select[name="params[module]"]').change(function() {
656		var npluginArgs = $.parseJSON($(form).find('input[name="args"][type="hidden"]').val());
657		//this is the newly selected module
658		var selectedMod = $(form).find('tr select[name="params[module]"]').val();
659		$('div.plugin input[name="type"][value="' + type + '"]').parent().parent().remove();
660		popupPluginForm(area_id, type, index, pageName, npluginArgs, bodyContent, edit_icon, selectedMod);
661	});
662    var $form = $(form).find('tr input[type=submit]').remove();
663    
664    container.append(form);
665    document.body.appendChild(container[0]);
666	
667    handlePluginFieldsHierarchy(type);
668
669	var pfc = container.find('table tr').length;	// number of rows (plugin form contents)
670	var t = container.find('textarea:visible').length;
671	if (t) {pfc += t * 3;}
672	if (pfc > 9) {pfc = 9;}
673	if (pfc < 2) {pfc = 2;}
674	pfc = pfc / 10;			// factor to scale dialog height
675	
676	var btns = {};
677	var closeText = tr("Close");
678	btns[closeText] = function() {
679		$(this).dialog("close");
680	};
681	
682	btns[replaceText ? tr("Replace") : edit_icon ? tr("Submit") : tr("Insert")] = function() {
683        var meta = tiki_plugins[type];
684        var params = [];
685        var edit = edit_icon;
686        // whether empty required params exist or not
687        var emptyRequiredParam = false;
688        
689        for (var i = 0; i < form.elements.length; i++) {
690            var element = form.elements[i].name;
691            
692            var matches = element.match(/params\[(.*)\]/);
693            
694            if (matches === null) {
695                // it's not a parameter, skip 
696                continue;
697            }
698            var param = matches[1];
699            
700            var val = form.elements[i].value;
701            
702            // check if fields that are required and visible are not empty
703			if (meta.params[param]) {
704				if (meta.params[param].required) {
705					if (val === '' && $(form.elements[i]).is(':visible')) {
706						$(form.elements[i]).css('border-color', 'red');
707						if ($(form.elements[i]).next('.required_param').length === 0) {
708							$(form.elements[i]).after('<div class="required_param" style="font-size: x-small; color: red;">(required)</div>');
709						}
710						emptyRequiredParam = true;
711					}
712					else {
713						// remove required feedback if present
714						$(form.elements[i]).css('border-color', '');
715						$(form.elements[i]).next('.required_param').remove();
716					}
717				}
718			}
719			
720            if (val !== '') {
721                params.push(param + '="' + val + '"');
722            }
723        }
724
725        if (emptyRequiredParam) {
726        	return false;
727        }
728       
729		var blob, pluginContentTextarea = $("[name=content]", form), pluginContentTextareaEditor = syntaxHighlighter.get(pluginContentTextarea);
730		var cont = (pluginContentTextareaEditor ? pluginContentTextareaEditor.getValue() : pluginContentTextarea.val());
731		
732		if (cont.length > 0) {
733			blob = '{' + type.toUpperCase() + '(' + params.join(' ') + ')}' + cont + '{' + type.toUpperCase() + '}';
734		} else {
735			blob = '{' + type.toLowerCase() + ' ' + params.join(' ') + '}';
736		}
737        
738        if (edit) {
739            container.children('form').submit();
740        } else {
741            insertAt(area_id, blob, false, false, replaceText);
742        }
743		$(this).dialog("close");
744		$('div.plugin input[name="type"][value="' + type + '"]').parent().parent().remove();
745	        
746		return false;
747    };
748
749	var heading = container.find('h3').hide();
750
751	try {
752		if (container.dialog) {
753			container.dialog('destroy');
754		}
755	} catch( e ) {
756		// IE throws errors destroying a non-existant dialog
757	}
758	container.dialog({
759		width: $window.width() * 0.6,
760		height: $window.height() * pfc,
761		zIndex: 10000,
762		title: heading.text(),
763		autoOpen: false,
764		close: function() {
765			$('div.plugin input[name="type"][value="' + type + '"]').parent().parent().remove();		
766
767			var ta = $('#' + area_id);
768			if (ta) {ta.focus();}
769		}
770	}).dialog('option', 'buttons', btns).dialog("open");
771	
772	
773	//This allows users to create plugin snippets for any plugin using the jQuery event 'plugin_#type#_ready' for document
774	$document
775		.trigger({
776			type: 'plugin_' + type + '_ready',
777			container: container,
778			arguments: arguments,
779			btns: btns
780		})
781		.trigger({
782			type: 'plugin_ready',
783			container: container,
784			arguments: arguments,
785			btns: btns,
786			type: type
787		});
788}
789
790/*
791 * Hides all children fields in a wiki-plugin form and
792 * add javascript events to display them when the appropriate
793 * values are selected in the parent fields. 
794 */
795function handlePluginFieldsHierarchy(type) {
796	var pluginParams = tiki_plugins[type]['params'];
797	
798	var parents = {};
799	
800	$.each(pluginParams, function(paramName, paramValues) {
801		if (paramValues.parent) {
802			var $parent = $('[name$="params[' + paramValues.parent.name + ']"]', '.wikiplugin_edit');
803			
804			$('.wikiplugin_edit').find('#param_' + paramName).addClass('parent_' + paramValues.parent.name + '_' + paramValues.parent.value);
805			
806			if ($parent.val() != paramValues.parent.value) {
807				$('.wikiplugin_edit').find('#param_' + paramName).hide();
808			}
809			
810			if (!parents[paramValues.parent.name]) {
811				parents[paramValues.parent.name] = {};
812				parents[paramValues.parent.name]['children'] = [];
813				parents[paramValues.parent.name]['parentElement'] = $parent;
814			}
815			
816			parents[paramValues.parent.name]['children'].push(paramName);
817		}
818	});
819	
820	$.each(parents, function(parentName, parent) {
821		parent.parentElement.change(function() {
822			$.each(parent.children, function() {
823				$('.wikiplugin_edit #param_' + this).hide();
824			});
825			$('.wikiplugin_edit .parent_' + parentName + '_' + this.value).show();
826		});
827	}); 
828}
829
830/*
831 * JS only textarea fullscreen function (for Tiki 5+)
832 */
833
834$(function() {	// if in translation-diff-mode go fullscreen automatically
835	if ($("#diff_outer").length && !$.trim($(".wikipreview .wikitext").html()).length) {	// but not if previewing (TODO better)
836		toggleFullScreen("editwiki");
837	}
838});
839
840function toggleFullScreen(area_id) {
841	var textarea = $("#" + area_id);
842	
843	//codemirror interation and preservation
844	var textareaEditor = syntaxHighlighter.get(textarea);
845	if (textareaEditor) {
846		syntaxHighlighter.fullscreen(textarea);
847		return;
848	}
849
850	$('.TextArea-fullscreen').add(textarea).css('height', '');
851
852	//removes wiki command buttons (save, cancel, preview) from fullscreen view
853	$('.TextArea-fullscreen .wikiaction').remove();
854
855	var textareaParent = textarea.parent().parent().toggleClass('TextArea-fullscreen');
856	$('body').toggleClass('noScroll');
857	$('.tabs,.rbox-title').toggle();
858
859	if (textareaParent.hasClass('TextArea-fullscreen')) {
860		var win = $window
861			.data('cm-resize', true),
862			screen = $('.TextArea-fullscreen'),
863			toolbar = $('#editwiki_toolbar');
864
865		win.resize(function() {
866			if (win.data('cm-resize') && screen) {
867				screen.css('height', win.height() + 'px');
868
869				textarea
870					.css('height', ((win.height() - toolbar.height()) - 30) + 'px');
871			}
872		})
873			.resize();
874
875		//adds wiki command buttons (save, cancel, preview) from fullscreen view
876		$('#role_main input.wikiaction').clone().appendTo('.TextArea-fullscreen');
877	} else {
878		$window.removeData('cm-resize');
879	}
880}
881
882/* Simple tiki plugin for jQuery
883 * Helpers for autocomplete and sheet
884 */
885var xhrCache = {}, lastXhr;	// for jq-ui autocomplete
886
887$.fn.tiki = function(func, type, options) {
888	var opts = {}, opt;
889	switch (func) {
890		case "autocomplete":
891			if (jqueryTiki.autocomplete) {
892				if (typeof type === 'undefined') { // func and type given
893					// setup error - alert here?
894					return null;
895				}
896				options = options || {};
897				var requestData = {};
898
899				var url = "";
900				switch (type) {
901					case "pagename":
902						url = "tiki-listpages.php?listonly";
903						break;
904					case "groupname":
905						url = "tiki-ajax_services.php?listonly=groups";
906						break;
907					case "username":
908						url = "tiki-ajax_services.php?listonly=users";
909						break;
910					case "usersandcontacts":
911						url = "tiki-ajax_services.php?listonly=usersandcontacts";
912						break;
913					case "userrealname":
914						url = "tiki-ajax_services.php?listonly=userrealnames";
915						break;
916					case "tag":
917						url = "tiki-ajax_services.php?listonly=tags&separator=+";
918						break;
919					case "icon":
920						url = "tiki-ajax_services.php?listonly=icons&max=" + (opts.max ? opts.max: 10);
921						opts.formatItem = function(data, i, n, value) {
922							var ps = value.lastIndexOf("/");
923							var pd = value.lastIndexOf(".");
924							return "<img src='" + value + "' /> " + value.substring(ps + 1, pd).replace(/_/m, " ");
925						};
926						opts.formatResult = function(data, value) {
927							return value;
928						};
929						break;
930					case 'trackername':
931						url = "tiki-ajax_services.php?listonly=trackername";
932						break;
933					case 'trackervalue':
934						if (typeof options.fieldId === "undefined") {
935							// error
936							return null;
937						}
938						$.extend( requestData, options );
939						options = {};
940						url = "list-tracker_field_values_ajax.php";
941						break;
942				}
943				$.extend( opts, {		//  default options for autocompletes in tiki
944					minLength: 2,
945					source: function( request, response ) {
946						if (options.tiki_replace_term) {
947							request.term = options.tiki_replace_term.apply(null, [request.term]);
948						}
949						var cacheKey = "ac." + type + "." + request.term;
950						if ( cacheKey in xhrCache ) {
951							response( xhrCache[ cacheKey ] );
952							return;
953						}
954						request.q = request.term;
955						$.extend( request, requestData );
956						lastXhr = $.getJSON( url, request, function( data, status, xhr ) {
957							xhrCache[ cacheKey ] = data;
958							if ( xhr === lastXhr ) {
959								response( data );
960							}
961						});
962					}
963				});
964				$.extend(opts, options);
965
966		 		return this.each(function() {
967					$(this).autocomplete(opts);
968//					.click( function () {
969//						$(".ac_results").hide();	// hide the drop down if input clicked on again
970//					});
971				});
972			}
973			break;
974		case "carousel":
975			if (jqueryTiki.carousel) {
976				opts = {
977						imagePath: "lib/jquery/infinitecarousel/images/",
978						autoPilot: true
979					};
980				$.extend(opts, options);
981		 		return this.each(function() {
982					$(this).infiniteCarousel(opts);
983				});
984			}
985			break;
986		case "datepicker":
987		case "datetimepicker":
988			if (jqueryTiki.ui) {
989				switch (type) {
990					case "jscalendar":	// replacements for jscalendar
991										// timestamp result goes in the options.altField
992						if (typeof options.altField === "undefined") {
993							alert("jQuery.ui datepicker jscalendar replacement setup error: options.altField not set for " + $(this).attr("id"));
994							debugger;
995						}
996						opts = {
997							showOn: "both",
998							buttonImage: "img/icons/calendar.png",
999							buttonImageOnly: true,
1000							dateFormat: "yy-mm-dd",
1001							showButtonPanel: true,
1002							altFormat: "@",
1003							onClose: function(dateText, inst) {
1004								$.datepicker._updateAlternate(inst);	// make sure the hidden field is up to date
1005								var timestamp = parseInt($(inst.settings.altField).val() / 1000, 10);
1006								if (!timestamp) {
1007									$.datepicker._setDateFromField(inst);	// seems to need reminding when starting empty
1008									$.datepicker._updateAlternate(inst);
1009									timestamp = parseInt($(inst.settings.altField).val() / 1000, 10);
1010								}
1011								if (timestamp && inst.settings && inst.settings.timepicker) {	// if it's a datetimepicker add on the time
1012									var time = inst.settings.timepicker.hour * 3600 +
1013											   inst.settings.timepicker.minute * 60 +
1014											   inst.settings.timepicker.second;
1015									timestamp += time;
1016								}
1017								$(inst.settings.altField).val(timestamp ? timestamp : "");
1018							}
1019						};
1020						break;
1021					default:
1022						opts = {
1023							showOn: "both",
1024							buttonImage: "img/icons/calendar.png",
1025							buttonImageOnly: true,
1026							dateFormat: "yy-mm-dd",
1027							showButtonPanel: true
1028						};
1029						break;
1030				}
1031				$.extend(opts, options);
1032				if (func === "datetimepicker") {
1033					return this.each(function() {
1034							$(this).datetimepicker(opts);
1035						});
1036				} else {
1037					return this.each(function() {
1038						$(this).datepicker(opts);
1039					});
1040				}
1041			}
1042			break;
1043		case "accordion":
1044			if (jqueryTiki.ui) {
1045				opts = {
1046						autoHeight: false,
1047						collapsible: true,
1048						navigation: true
1049//						change: function(event, ui) {
1050//							// sadly accordion active property is broken in 1.7, but fix is coming in 1.8 so TODO 
1051//							setCookie(ui, ui.options.active, "accordion");
1052//						}
1053					};
1054				$.extend(opts, options);
1055		 		return this.each(function() {
1056					$(this).accordion(opts);			
1057				});
1058			}
1059			break;
1060		case "selectmenu":
1061			if (jqueryTiki.selectmenu) {
1062				opts = {
1063						style: 'dropdown',
1064						wrapperElement: "<span />"
1065					};
1066				$.extend(opts, options);
1067		 		return this.each(function() {
1068					$(this).selectmenu(opts);
1069				});
1070			}
1071			break;
1072	}	// end switch(func)
1073};
1074
1075/******************************
1076 * Functions for dialog tools *
1077 ******************************/
1078
1079// shared
1080
1081window.dialogData = [];
1082var dialogDiv;
1083
1084function displayDialog( ignored, list, area_id ) {
1085	var i, item, el, obj, tit = "";
1086
1087	var $is_cked =  $('#cke_contents_' + area_id).length !== 0;
1088
1089	if (!dialogDiv) {
1090		dialogDiv = document.createElement('div');
1091		document.body.appendChild( dialogDiv );
1092	}
1093	$(dialogDiv).empty();
1094	
1095	for( i = 0; i < window.dialogData[list].length; i++ ) {
1096		item = window.dialogData[list][i];
1097		if (item.indexOf("<") === 0) {	// form element
1098			el = $(item);
1099			$(dialogDiv).append( el );
1100		} else if (item.indexOf("{") === 0) {
1101			try {
1102				//obj = JSON.parse(item);	// safer, but need json2.js lib
1103				obj = eval("("+item+")");
1104			} catch (e) {
1105				alert(e.name + ' - ' + e.message);
1106			}
1107		} else if (item.length > 0) {
1108			tit = item;
1109		}
1110	}
1111	
1112	// Selection will be unavailable after context menu shows up - in IE, lock it now.
1113	if ( typeof CKEDITOR !== "undefined" && CKEDITOR.env.ie ) {
1114		var editor = CKEDITOR.instances[area_id];
1115		var selection = editor.getSelection();
1116		if (selection) {selection.lock();}
1117	} else if ($("#" + area_id)[0].createTextRange) {	// save selection for IE
1118		storeTASelection(area_id);
1119	}
1120	
1121	if (!obj) { obj = {}; }
1122	if (!obj.width) {obj.width = 210;}
1123	obj.bgiframe = true;
1124	obj.autoOpen = false;
1125	obj.zIndex = 10000;
1126	try {
1127		if ($(dialogDiv).dialog) {
1128			$(dialogDiv).dialog('destroy');
1129		}
1130	} catch( e ) {
1131		// IE throws errors destroying a non-existant dialog
1132	}
1133	$(dialogDiv).dialog(obj).dialog('option', 'title', tit).dialog('open');
1134
1135	return false;
1136}
1137
1138window.pickerData = [];
1139var pickerDiv = {};
1140
1141function displayPicker( closeTo, list, area_id, isSheet, styleType ) {
1142	$('div.toolbars-picker').remove();	// simple toggle
1143	var $closeTo = $(closeTo);
1144	
1145	if ($closeTo.hasClass('toolbars-picker-open')) {
1146		$('.toolbars-picker-open').removeClass('toolbars-picker-open');
1147		return false;
1148	}
1149	
1150	$closeTo.addClass('toolbars-picker-open');
1151	var textarea = $('#' +  area_id);
1152	
1153	var coord = $closeTo.offset();
1154	coord.bottom = coord.top + $closeTo.height();
1155	
1156	pickerDiv = $('<div class="toolbars-picker ' + list + '" />')
1157		.css('left', coord.left + 'px')
1158		.css('top', (coord.bottom + 8) + 'px')
1159		.appendTo('body');
1160
1161	var prepareLink = function(ins, disp ) {
1162		disp = $(disp);
1163		
1164		var link = $( '<a href="#" />' ).append(disp);
1165			
1166		if (disp.attr('reset') && isSheet) {
1167			var bgColor = $('div.tiki_sheet:first').css(styleType);
1168			var color = $('div.tiki_sheet:first').css(styleType == 'color' ? 'background-color' : 'color');
1169			disp
1170				.css('background-color', bgColor)
1171				.css('color', color);
1172			
1173			link
1174				.addClass('toolbars-picker-reset');
1175		}
1176		
1177		if ( isSheet ) {
1178			link
1179				.click(function() {
1180					var I = $(closeTo).attr('instance');
1181					I = parseInt( I ? I : 0, 10 );
1182					
1183					if (disp.attr('reset')) {
1184						$.sheet.instance[I].cellChangeStyle(styleType, '');
1185					} else {
1186						$.sheet.instance[I].cellChangeStyle(styleType, disp.css('background-color'));
1187					}
1188					
1189					$closeTo.click();
1190					return false;
1191				});
1192		} else {
1193			link.click(function() {				
1194				insertAt(area_id, ins);
1195				
1196				var textarea = $('#' + area_id);
1197				// quick fix for Firefox 3.5 losing selection on changes to popup
1198				if (typeof textarea.selectionStart != 'undefined') {
1199					var tempSelectionStart = textarea.selectionStart;
1200					var tempSelectionEnd = textarea.selectionEnd;
1201				}
1202				
1203				$closeTo.click();
1204				
1205				// quick fix for Firefox 3.5 losing selection on changes to popup
1206				if (typeof textarea.selectionStart != 'undefined' && textarea.selectionStart != tempSelectionStart) {
1207					textarea.selectionStart = tempSelectionStart;
1208				}
1209				if (typeof textarea.selectionEnd != 'undefined' && textarea.selectionEnd != tempSelectionEnd) {
1210					textarea.selectionEnd = tempSelectionEnd;
1211				}
1212				
1213				return false;
1214			});
1215		}
1216		return link;
1217	};
1218	var chr, $a;
1219	for( var i in window.pickerData[list] ) {
1220		chr = window.pickerData[list][i];
1221		if (list === "specialchar") {
1222			chr = $("<span>" + chr + "</span>");
1223		}
1224		$a = prepareLink( i, chr );
1225		if ($a.length) {
1226			pickerDiv.append($a);
1227		}
1228	}
1229	
1230	return false;
1231}
1232
1233
1234function dialogSelectElement( area_id, elementStart, elementEnd ) {
1235	if ($('#cke_contents_' + area_id).length !== 0) {return;}	// TODO for ckeditor
1236	
1237	var $textarea = $('#' + area_id);
1238	var textareaEditor = syntaxHighlighter.get($textarea);
1239	var val = ( textareaEditor ? textareaEditor.getValue() : $textarea.val() );
1240	var pairs = [], pos = 0, s = 0, e = 0;
1241	
1242	while (s > -1 && e > -1) {	// positions of start/end markers
1243		s = val.indexOf(elementStart, e);
1244		if (s > -1) {
1245			e = val.indexOf(elementEnd, s + elementStart.length);
1246			if (e > -1) {
1247				e += elementEnd.length;
1248				pairs[pairs.length] = [ s, e ];
1249			}
1250		}
1251	}
1252	
1253	(textareaEditor ? textareaEditor : $textarea[0]).focus();
1254	
1255	var selection = ( textareaEditor ? syntaxHighlighter.selection(textareaEditor, true) : $textarea.selection() );
1256
1257	s = selection.start;
1258	e = selection.end;
1259	var st = $textarea.attr('scrollTop');
1260
1261	for (var i = 0; i < pairs.length; i++) {
1262		if (s >= pairs[i][0] && e <= pairs[i][1]) {
1263			setSelectionRange($textarea[0], pairs[i][0], pairs[i][1]);
1264			break;
1265		}
1266	}
1267	
1268}
1269
1270
1271function dialogSharedClose( area_id, dialog ) {
1272	$(dialog).dialog("close");
1273}
1274
1275// Internal Link
1276
1277function dialogInternalLinkOpen( area_id ) {
1278	$("#tbWLinkPage").tiki("autocomplete", "pagename");
1279	dialogSelectElement( area_id, '((', '))' ) ;
1280	var s = getTASelection($('#' + area_id)[0]);
1281	var m = /\((.*)\(([^\|]*)\|?([^\|]*)\|?([^\|]*)\|?\)\)/g.exec(s);
1282	if (m && m.length > 4) {
1283		if ($("#tbWLinkRel")) {
1284			$("#tbWLinkRel").val(m[1]);
1285		}
1286		$("#tbWLinkPage").val(m[2]);
1287		if (m[4]) {
1288			if ($("#tbWLinkAnchor")) {
1289				$("#tbWLinkAnchor").val(m[3]);
1290			}
1291			$("#tbWLinkDesc").val(m[4]);
1292		} else {
1293			$("#tbWLinkDesc").val(m[3]);
1294		}
1295	} else {
1296		$("#tbWLinkDesc").val(s);
1297		if ($("#tbWLinkAnchor")) {
1298			$("#tbWLinkAnchor").val("");
1299		}
1300	}
1301}
1302
1303function dialogInternalLinkInsert( area_id, dialog ) {
1304	if (!$("#tbWLinkPage").val()) {
1305		alert(tr("Please enter a page name"));
1306		return;
1307	}
1308	var s = "(";
1309	if ($("#tbWLinkRel") && $("#tbWLinkRel").val()) {
1310		s += $("#tbWLinkRel").val();
1311	}
1312	s += "(" + $("#tbWLinkPage").val();
1313	if ($("#tbWLinkAnchor") && $("#tbWLinkAnchor").val()) {
1314		s += "|" + ($("#tbWLinkAnchor").val().indexOf("#") !== 0 ? "#" : "") + $("#tbWLinkAnchor").val();
1315	}
1316	if ($("#tbWLinkDesc").val()) {
1317		s += "|" + $("#tbWLinkDesc").val();
1318	}
1319	s += "))";
1320	insertAt(area_id, s, false, false, true);
1321	
1322	dialogSharedClose( area_id, dialog );
1323	
1324}
1325
1326// External Link
1327
1328function dialogExternalLinkOpen( area_id ) {
1329	$("#tbWLinkPage").tiki("autocomplete", "pagename");
1330	dialogSelectElement( area_id, '[', ']' ) ;
1331	var s = getTASelection($('#' + area_id)[0]);
1332	var m = /\[([^\|]*)\|?([^\|]*)\|?([^\|]*)\]/g.exec(s);
1333	if (m && m.length > 3) {
1334		$("#tbLinkURL").val(m[1]);
1335		$("#tbLinkDesc").val(m[2]);
1336		if (m[3]) {
1337			if ($("#tbLinkNoCache") && m[3] == "nocache") {
1338				$("#tbLinkNoCache").attr("checked", "checked");
1339			} else {
1340				$("#tbLinkRel").val(m[3]);
1341			}
1342		} else {
1343			$("#tbWLinkDesc").val(m[3]);
1344		}
1345	} else {
1346		if (s.match(/(http|https|ftp)([^ ]+)/ig) == s) { // v simple URL match
1347			$("#tbLinkURL").val(s);
1348		} else {
1349			$("#tbLinkDesc").val(s);
1350		}
1351	}
1352	if (!$("#tbLinkURL").val()) {
1353		$("#tbLinkURL").val("http://");
1354	}
1355}
1356
1357function dialogExternalLinkInsert(area_id, dialog) {
1358
1359	var s = "[" + $("#tbLinkURL").val();
1360	if ($("#tbLinkDesc").val()) {
1361		s += "|" + $("#tbLinkDesc").val();
1362	}
1363	if ($("#tbLinkRel").val()) {
1364		s += "|" + $("#tbLinkRel").val();
1365	}
1366	if ($("#tbLinkNoCache") && $("#tbLinkNoCache").attr("checked")) {
1367		s += "|nocache";
1368	}
1369	s += "]";
1370	insertAt(area_id, s, false, false, true);
1371	
1372	dialogSharedClose( area_id, dialog );
1373	
1374}
1375
1376// Table
1377
1378function dialogTableOpen(area_id, dialog) {
1379
1380	dialogSelectElement( area_id, '||', '||' ) ;
1381
1382	dialog = $(dialog);
1383
1384	var s = getTASelection($('#' + area_id)[0]);
1385	var m = /\|\|([\s\S]*?)\|\|/mg.exec(s);
1386	var vals = [], rows = 3, cols = 3, c, r, i, j;
1387	if (m) {
1388		m = m[1];
1389		m = m.split("\n");
1390		rows = 0;
1391		cols = 1;
1392		for (i = 0; i < m.length; i++) {
1393			var a2 = m[i].split("|");
1394			var a = [];
1395			for (j = 0; j < a2.length; j++) { // links can have | chars in
1396				if (a2[j].indexOf("[") > -1 && a2[j].indexOf("[[") == -1 && a2[j].indexOf("]") == -1) { // external link
1397					a[a.length] = a2[j];
1398					j++;
1399					var k = true;
1400					while (j < a2.length && k) {
1401						a[a.length - 1] += "|" + a2[j];
1402						if (a2[j].indexOf("]") > -1) { // closed
1403							k = false;
1404						} else {
1405							j++;
1406						}
1407					}
1408				} else if (a2[j].search(/\(\S*\(/) > -1 && a2[j].indexOf("))") == -1) {
1409					a[a.length] = a2[j];
1410					j++;
1411					k = true;
1412					while (j < a2.length && k) {
1413						a[a.length - 1] += "|" + a2[j];
1414						if (a2[j].indexOf("))") > -1) { // closed
1415							k = false;
1416						} else {
1417							j++;
1418						}
1419					}
1420				} else {
1421					a[a.length] = a2[j];
1422				}
1423			}
1424			vals[vals.length] = a;
1425			if (a.length > cols) {
1426				cols = a.length;
1427			}
1428			if (a.length) {
1429				rows++;
1430			}
1431		}
1432	}
1433	for (r = 1; r <= rows; r++) {
1434		for (c = 1; c <= cols; c++) {
1435			var v = "";
1436			if (vals.length) {
1437				if (vals[r - 1] && vals[r - 1][c - 1]) {
1438					v = vals[r - 1][c - 1];
1439				} else {
1440					v = "   ";
1441				}
1442			} else {
1443				v = "   "; //row " + r + ",col " + c + "";
1444			}
1445			var el = $("<input type=\"text\" id=\"tbTableR" + r + "C" + c + "\" class=\"ui-widget-content ui-corner-all\" size=\"10\" style=\"width:" + (90 / cols) + "%\" />")
1446				.val($.trim(v))
1447				.appendTo(dialog);
1448		}
1449		if (r == 1) {
1450			el = $("<img src=\"img/icons/add.png\" />")
1451				.click(function() {
1452					dialog.data("cols", dialog.data("cols") + 1);
1453					for (r = 1; r <= dialog.data("rows"); r++) {
1454						v = "";
1455						var el = $("<input type=\"text\" id=\"tbTableR" + r + "C" + dialog.data("cols") + "\" class=\"ui-widget-content ui-corner-all\" size=\"10\" style=\"width:" + (90 / dialog.data("cols")) + "%\" />")
1456							.val(v);
1457						$("#tbTableR" + r + "C" + (dialog.data("cols") - 1)).after(el);
1458					}
1459					dialog.find("input").width(90 / dialog.data("cols") + "%");
1460				})
1461				.appendTo(dialog);
1462		}
1463		dialog.append("<br />");
1464	}
1465	el = $("<img src=\"img/icons/add.png\" />")
1466		.click(function() {
1467			dialog.data("rows", dialog.data("rows") + 1);
1468			for (c = 1; c <= dialog.data("cols"); c++) {
1469				v = "";
1470				var el = $("<input type=\"text\" id=\"tbTableR" + dialog.data("rows") + "C" + c + "\" class=\"ui-widget-content ui-corner-all\" size=\"10\" value=\"" + v + "\" style=\"width:" + (90 / dialog.data("cols")) + "%\" />")
1471					.insertBefore(this);
1472			}
1473			$(this).before("<br />");
1474			dialog.dialog("option", "height", (dialog.data("rows") + 1) * 1.2 * $("#tbTableR1C1").height() + 130);
1475		})
1476		.appendTo(dialog);
1477	
1478	dialog
1479			.data('rows', rows)
1480			.data('cols', cols)
1481			.dialog("option", "width", (cols + 1) * 120 + 50)
1482			.dialog("option", "position", "center");
1483
1484	$("#tbTableR1C1").focus();
1485}
1486
1487function dialogTableInsert(area_id, dialog) {
1488	var s = "||", rows, cols, c, r, rows2 = 1, cols2 = 1;
1489	dialog = $(dialog);
1490
1491	rows = dialog.data('rows') || 3;
1492	cols = dialog.data('cols') || 3;
1493	for (r = 1; r <= rows; r++) {
1494		for (c = 1; c <= cols; c++) {
1495			if ($.trim($("#tbTableR" + r + "C" + c).val())) {
1496				if (r > rows2) {
1497					rows2 = r;
1498				}
1499				if (c > cols2) {
1500					cols2 = c;
1501				}
1502			}
1503		}
1504	}
1505	for (r = 1; r <= rows2; r++) {
1506		for (c = 1; c <= cols2; c++) {
1507			var tableData = $("#tbTableR" + r + "C" + c).val();
1508			s += tableData;
1509			if (c < cols2) {
1510				s += (tableData ? '|' : ' | ');
1511			}
1512		}
1513		if (r < rows2) {
1514			s += "\n";
1515		}
1516	}
1517	s += "||";
1518	insertAt(area_id, s, false, false, true);
1519	
1520	dialogSharedClose( area_id, dialog );
1521}
1522
1523// Find
1524
1525function dialogFindOpen(area_id) {
1526	
1527	var s = getTASelection($('#' + area_id)[0]);
1528	$("#tbFindSearch").val(s).focus();			  
1529}
1530
1531function dialogFindFind( area_id ) {
1532	var ta = $('#' + area_id);
1533	var findInput = $("#tbFindSearch").removeClass("ui-state-error");
1534	
1535	var $textareaEditor = syntaxHighlighter.get(ta); //codemirror functionality
1536	if ($textareaEditor) {
1537		syntaxHighlighter.find($textareaEditor, findInput.val());
1538	}
1539	else { //standard functionality
1540		var s, opt, str, re, p = 0, m;
1541		s = findInput.val();
1542		opt = "";
1543		if ($("#tbFindCase").attr("checked")) {
1544			opt += "i";
1545		}
1546		str = ta.val();
1547		re = new RegExp(s, opt);
1548		p = getCaretPos(ta[0]);
1549		if (p && p < str.length) {
1550			m = re.exec(str.substring(p));
1551		}
1552		else {
1553			p = 0;
1554		}
1555		if (!m) {
1556			m = re.exec(str);
1557			p = 0;
1558		}
1559		if (m) {
1560			setSelectionRange(ta[0], m.index + p, m.index + s.length + p);
1561		}
1562		else {
1563			findInput.addClass("ui-state-error");
1564		}
1565	}
1566}
1567
1568// Replace
1569
1570function dialogReplaceOpen(area_id) {
1571
1572	var s = getTASelection($('#' + area_id)[0]);
1573	$("#tbReplaceSearch").val(s).focus();
1574	  		  
1575}
1576
1577function dialogReplaceReplace( area_id ) {
1578	var findInput = $("#tbReplaceSearch").removeClass("ui-state-error");
1579	var s = findInput.val();
1580	var r = $("#tbReplaceReplace").val();
1581	var opt = "";
1582	if ($("#tbReplaceAll").attr("checked")) {
1583		opt += "g";
1584	}
1585	if ($("#tbReplaceCase").attr("checked")) {
1586		opt += "i";
1587	}
1588	var ta = $('#' + area_id);
1589	var str = ta.val();
1590	var re = new RegExp(s,opt);
1591	
1592	var textareaEditor = syntaxHighlighter.get(ta); //codemirror functionality
1593	if (textareaEditor) {
1594		syntaxHighlighter.replace(textareaEditor, s, r);
1595	}
1596	else { //standard functionality
1597		ta.val(str.replace(re, r));
1598	}
1599
1600}
1601
1602
1603(function($) {
1604	/**
1605	 * Adds annotations to the content of text in ''container'' based on the
1606	 * content found in selected dts.
1607	 *
1608	 * Used in comments.tpl
1609	 */
1610	$.fn.addnotes = function( container ) {
1611		return this.each(function(){
1612			var comment = this;
1613			var text = $('dt:contains("note")', comment).next('dd').text();
1614			var title = $('h6:first', comment).clone();
1615			var body = $('.body:first', comment).clone();
1616			body.find('dt:contains("note")').closest('dl').remove();
1617
1618			if( text.length > 0 ) {
1619				var parents = container.find(':contains("' + text + '")').parent();
1620				var node = container.find(':contains("' + text + '")').not(parents)
1621					.addClass('note-editor-text')
1622					.each( function() {
1623						var child = $('dl.note-list',this);
1624						if( ! child.length ) {
1625							child = $('<dl class="note-list"/>')
1626								.appendTo(this)
1627								.hide();
1628
1629							$(this).click( function() {
1630								child.toggle();
1631							} );
1632						}
1633
1634						child.append( title )
1635							.append( $('<dd/>').append(body) );
1636					} );
1637			}
1638		});
1639	};
1640
1641	/**
1642	 * Convert a zone to a note editor by attaching handlers on mouse events.
1643	 */
1644	$.fn.noteeditor = function (editlink, link) {
1645		var hiddenParents = null;
1646		var annote = $(link)
1647			.click( function( e ) {
1648				e.preventDefault();
1649
1650				var $block = $('<div/>');
1651				var annotation = $(this).attr('annotation');
1652				$(this).fadeOut(100);
1653
1654				$block.load(editlink.attr('href'), function () {
1655					var msg = "";
1656					if (annotation.length < 20) {
1657						msg = tr("The text you have selected is quite short. Select a longer piece to ensure the note is associated with the correct text.") + "<br />";
1658					}
1659
1660					msg = "<p class='description comment-info'>" + msg + tr("Tip: Leave the first line as it is, starting with \";note:\". This is required") + "</p>";
1661					$block.prepend($(msg));
1662					$('textarea', this)
1663						.val(';note:' + annotation + "\n\n").focus();
1664
1665					$('form', this).submit(function () {
1666						$.post($(this).attr('action'), $(this).serialize(), function () {
1667							$block.dialog('destroy');
1668						});
1669						return false;
1670					});
1671
1672					$block.dialog({
1673						modal: true,
1674						width: 500,
1675						height: 400
1676					});
1677				});
1678			} )
1679			.appendTo(document.body);
1680
1681			$(this).mouseup(function( e ) {
1682				var range;
1683				if( window.getSelection && window.getSelection().rangeCount ) {
1684					range = window.getSelection().getRangeAt(0);
1685				} else if( window.selection ) {
1686					range = window.selection.getRangeAt(0);
1687				}
1688
1689				if( range ) {
1690					var str = $.trim( range.toString() );
1691
1692					if( str.length && -1 === str.indexOf( "\n" ) ) {
1693						annote.attr('annotation', str);
1694						annote.fadeIn(100).position( {
1695							of: e,
1696							at: 'bottom left',
1697							my: 'top left',
1698							offset: '20 20'
1699						} );
1700					} else {
1701						if (annote.css("display") != "none") {
1702							annote.fadeOut(100);
1703						}
1704						if ($("form.comments").css("display") == "none") {
1705							$("form.comments").show();
1706						}
1707						if (hiddenParents) {
1708							hiddenParents.hide();
1709							hiddenParents = null;
1710						}
1711					}
1712				}
1713			});
1714	};
1715
1716	$.fn.browse_tree = function () {
1717		this.each(function () {
1718			$('.treenode:not(.done)', this)
1719				.addClass('done')
1720				.each(function () {
1721					if (getCookie($('ul:first', this).attr('data-id'), $('ul:first', this).attr('data-prefix')) !== 'o') {
1722						$('ul:first', this).css('display', 'none');
1723					}
1724					if ($('ul:first', this).length) {
1725						var dir = $('ul:first', this).css('display') === 'block' ? 's' : 'e';
1726						$(this).prepend('<span class="flipper ui-icon ui-icon-triangle-1-' + dir + '" style="float: left;"/>');
1727					} else {
1728						$(this).prepend('<span style="float:left;width:16px;height:16px;"/>');
1729					}
1730				});
1731
1732			$('.flipper:not(.done)')
1733				.addClass('done')
1734				.css('cursor', 'pointer')
1735				.click(function () {
1736					var body = $(this).parent().find('ul:first');
1737					if ('block' === body.css('display')) {
1738						$(this).removeClass('ui-icon-triangle-1-s').addClass('ui-icon-triangle-1-e');
1739						body.hide('fast');
1740						setCookie(body.data("id"), "", body.data("prefix"));
1741					} else {
1742						$(this).removeClass('ui-icon-triangle-1-e').addClass('ui-icon-triangle-1-s');
1743						body.show('fast');
1744						setCookie(body.data("id"), "o", body.data("prefix"));
1745					}
1746				});
1747		});
1748
1749		return this;
1750	};
1751
1752	var fancy_filter_create_token = function(value, label) {
1753		var close, token;
1754
1755		close = $('<span class="ui-icon ui-icon-close"/>')
1756			.click(function () {
1757				var ed = $(this).parent().parent();
1758				$(this).parent().remove();
1759				ed.change();
1760				return false;
1761			});
1762
1763		token = $('<span class="token"/>')
1764			.attr('data-value', value)
1765			.text(label)
1766			.attr('contenteditable', false)
1767			.disableSelection()
1768			.append(close);
1769
1770		return token[0];
1771	};
1772
1773	var fancy_filter_build_init = function(editable, str, options) {
1774		if (str === '') {
1775			str = '&nbsp;';
1776		}
1777
1778		editable.html(str.replace(/(\d+)/g, '<span>$1</span>'));
1779
1780		if (options && options.map) {
1781			editable.find('span').each(function () {
1782				var val = $(this).text();
1783				$(this).replaceWith(fancy_filter_create_token(val, options.map[val] ? options.map[val] : val));
1784			});
1785		}
1786	};
1787
1788	$jq.fn.fancy_filter = function (operation, options) {
1789		this.each(function () {
1790			switch (operation) {
1791			case 'init':
1792				var editable = $('<div class="fancyfilter"/>'), input = this;
1793
1794				if (editable[0].contentEditable !== null) {
1795					fancy_filter_build_init(editable, $(this).val(), options);
1796					editable.attr('contenteditable', true);
1797					$(this).after(editable).hide();
1798				}
1799
1800				editable
1801					.keyup(function() {
1802						$(this).change();
1803						$(this).mouseup();
1804					})
1805					.change(function () {
1806						$(input).val($('<span/>')
1807							.html(editable.html())
1808							.find('span').each(function() {
1809								$(this).replaceWith(' ' + $(this).attr('data-value') + ' ');
1810							})
1811							.end().text().replace(/\s+/g, ' '));
1812					})
1813					.mouseup(function () {
1814						input.lastRange = window.getSelection().getRangeAt(0);
1815					});
1816
1817				break;
1818			case 'add':
1819				var node = fancy_filter_create_token(options.token, options.label);
1820				if (this.lastRange) {
1821					this.lastRange.deleteContents();
1822					this.lastRange.insertNode(node);
1823					this.lastRange.insertNode(document.createTextNode(options.join));
1824				} else {
1825					$(this).next().append(options.join).append(node);
1826				}
1827				$(this).next().change();
1828				break;
1829			}
1830		});
1831
1832		return this;
1833	};
1834
1835	$.fn.drawGraph = function () {
1836		this.each(function () {
1837			var $this = $(this);
1838			var width = $this.width();
1839			var height = Math.ceil( width * 9 / 16 );
1840			var nodes = $this.data('graph-nodes');
1841			var edges = $this.data('graph-edges');
1842
1843			var g = new Graph;
1844			$.each(nodes, function (k, i) {
1845				g.addNode(i);
1846			});
1847			$.each(edges, function (k, i) {
1848				var style = { directed: true };
1849				if( i.preserve ) {
1850					style.color = 'red';
1851				}
1852				g.addEdge( i.from, i.to, style );
1853			});
1854
1855			var layouter = new Graph.Layout.Spring(g);
1856			layouter.layout();
1857			
1858			var renderer = new Graph.Renderer.Raphael($this.attr('id'), g, width, height );
1859			renderer.draw();
1860		});
1861
1862		return this;
1863	};
1864
1865	/**
1866	 * Handle textarea and input text selections
1867	 * Code from:
1868	 *
1869	 * jQuery Autocomplete plugin 1.1
1870	 * Copyright (c) 2009 Jörn Zaefferer
1871	 *
1872	 * Dual licensed under the MIT and GPL licenses:
1873	 *   http://www.opensource.org/licenses/mit-license.php
1874	 *   http://www.gnu.org/licenses/gpl.html
1875	 *
1876	 * Now deprecated and replaced in Tiki 7 by jquery-ui autocomplete
1877	 */
1878	$.fn.selection = function(start, end) {
1879		if (start !== undefined) {
1880			if (end === undefined) {
1881				end = start;
1882			}
1883			return this.each(function() {
1884				if( this.createTextRange ){
1885					var selRange = this.createTextRange();
1886					if (start == end) {
1887						selRange.move("character", start);
1888						selRange.select();
1889					} else {
1890						selRange.collapse(true);
1891						selRange.moveStart("character", start);
1892						selRange.moveEnd("character", end - start);	// moveEnd is relative
1893						selRange.select();
1894					}
1895				} else if( this.setSelectionRange ){
1896					this.setSelectionRange(start, end);
1897				} else if( this.selectionStart ){
1898					this.selectionStart = start;
1899					this.selectionEnd = end;
1900				}
1901			});
1902		}
1903		var field = this[0];
1904		if ( field.createTextRange ) {
1905			// from http://the-stickman.com/web-development/javascript/finding-selection-cursor-position-in-a-textarea-in-internet-explorer/
1906			// The current selection
1907			var range = document.selection.createRange();
1908			// We'll use this as a 'dummy'
1909			var stored_range = range.duplicate();
1910			// Select all text
1911			stored_range.moveToElementText( field );
1912			// Now move 'dummy' end point to end point of original range
1913			stored_range.setEndPoint( 'EndToEnd', range );
1914			// Now we can calculate start and end points
1915			var selectionStart = stored_range.text.length - range.text.length;
1916			var selectionEnd = selectionStart + range.text.length;
1917			return {
1918				start: selectionStart,
1919				end: selectionEnd
1920			}
1921		
1922		} else if( field.selectionStart !== undefined ){
1923			return {
1924				start: field.selectionStart,
1925				end: field.selectionEnd
1926			}
1927		}
1928	};
1929
1930	$.fn.comment_toggle = function () {
1931		this.each(function () {
1932			var $target = $(this.hash);
1933			$target.hide();
1934
1935			$(this).click(function () {
1936				if ($target.is(':visible')) {
1937					$target.hide(function () {
1938						$(this).empty();
1939					});
1940				} else {
1941					$target.comment_load($(this).attr('href'));
1942				}
1943
1944				return false;
1945			});
1946		});
1947
1948		return this;
1949	};
1950
1951	$.fn.comment_load = function (url) {
1952		$('#top .note-list').remove();
1953
1954		this.each(function () {
1955			var comment_container = this;
1956			$(this).load(url, function (response, status) {
1957				$(this).show();
1958				$('.comment.inline dt:contains("note")', this)
1959					.closest('.comment')
1960					.addnotes( $('#top') );
1961
1962				$('#top').noteeditor($('.comment-form:last a', comment_container), '#note-editor-comment');
1963
1964				$('.comment-form a', this).click(function () {
1965					$(this).parent().empty().removeClass('button').load($(this).attr('href'), function () {
1966						$('form', this).submit(function () {
1967							var form = this, errors;
1968							$.post($(form).attr('action'), $(this).serialize(), function (data, st) {
1969								if (data.threadId) {
1970									$(comment_container).empty().comment_load(url);
1971								} else {
1972									errors = $('ol.errors', form).empty();
1973									if (! errors.length) {
1974										$(':submit', form).after(errors = $('<ol class="errors"/>'));
1975									}
1976									
1977									$.each(data.errors, function (k, v) {
1978										errors.append($('<li/>').text(v));
1979									});
1980								}
1981							}, 'json');
1982							return false;
1983						});
1984					});
1985					return false;
1986				});
1987
1988				$('.button.comment-form.autoshow a').click(); // allow autoshowing of comment forms through autoshow css class 
1989
1990				$('.confirm-prompt', this).requireConfirm({
1991					success: function (data) { 
1992						if (data.status === 'DONE') {
1993							$(comment_container).empty().comment_load(url);
1994						}
1995					}
1996				});
1997			});
1998		});
1999
2000		return this;
2001	};
2002
2003	$.fn.input_csv = function (operation, separator, value) {
2004		this.each(function () {
2005			var values = $(this).val().split(separator);
2006			if (values[0] === '') {
2007				values.shift();
2008			}
2009
2010			if (operation === 'add') {
2011				values.push(value);
2012			} else if (operation === 'delete') {
2013				value = String(value);
2014				while (-1 !== $.inArray(value, values)) {
2015					values.splice($.inArray(value, values), 1);
2016				}
2017			}
2018
2019			$(this).val(values.join(separator));
2020		});
2021
2022		return this;
2023	};
2024
2025	$.service = function (controller, action, query) {
2026		var append = '';
2027
2028		if (query) {
2029			append = '?' + $.map(query, function (v, k) {
2030				return k + '=' + escape(v);
2031			}).join('&');
2032		}
2033
2034		if (action) {
2035			return 'tiki-' + controller + '-' + action + append;
2036		} else {
2037			return 'tiki-' + controller + append;
2038		}
2039	};
2040
2041	$.fn.serviceDialog = function (options) {
2042		this.each(function () {
2043			var $dialog = $('<div/>'), origin = this, buttons = {};
2044			$(this).append($dialog).data('serviceDialog', $dialog);
2045
2046			buttons[tr('OK')] = function () {
2047				$dialog.find('form:visible').submit();
2048			};
2049			buttons[tr('Cancel')] = function () {
2050				$dialog
2051					.dialog('close')
2052					.dialog('destroy');
2053			};
2054
2055			$dialog.dialog({
2056				title: options.title,
2057				minWidth: 500,
2058				height: (options.fullscreen ? $window.height() - 20 : 600),
2059				width: (options.fullscreen ? $window.width() - 20 : null),
2060				close: function () {
2061					if (options.close) {
2062						options.close.apply([], this);
2063					}
2064					$(this).dialog('destroy');
2065				},
2066				buttons: buttons,
2067				modal: options.modal,
2068				zIndex: options.zIndex
2069			});
2070
2071			$dialog.loadService(options.data, $.extend(options, {origin: origin}));
2072		});
2073
2074		return this;
2075	};
2076	$.fn.loadService =  function (data, options) {
2077		var $dialog = this, controller = options.controller, action = options.action, url;
2078
2079		if (data && data.controller) {
2080			controller = data.controller;
2081		}
2082
2083		if (data && data.action) {
2084			action = data.action;
2085		}
2086
2087		if (options.origin && $(options.origin).is('a')) {
2088			url = $(options.origin).attr('href');
2089		} else {
2090			url = $.service(controller, action);
2091		}
2092
2093		$dialog.load(url, data, function (data) {
2094			$dialog.find('form .submit').hide();
2095
2096			$dialog.find('form:not(.no-ajax)').submit(function (e) {
2097				var form = this, act;
2098				act = $(form).attr('action');
2099	
2100				if (! act) {
2101					act = url;
2102				}
2103
2104				$.ajax(act, {
2105					type: 'POST',
2106					dataType: 'json',
2107					data: $(form).serialize(),
2108					success: function (data) {
2109						data = (data ? data : {});
2110						
2111						if (data.FORWARD) {
2112							$dialog.loadService(data.FORWARD, options);
2113						} else {
2114							$dialog.dialog('destroy');
2115						}
2116
2117						if (options.success) {
2118							options.success.apply(options.origin, [data]);
2119						}
2120					},
2121					error: function (jqxhr) {
2122						$(form.name).showError(jqxhr);
2123					}
2124				});
2125
2126				return false;
2127			});
2128
2129			if (options.load) {
2130				options.load.apply($dialog[0], [data]);
2131			}
2132
2133			$('.confirm-prompt', this).requireConfirm({
2134				success: function (data) {
2135					if (data.FORWARD) {
2136						$dialog.loadService(data.FORWARD, options);
2137					} else {
2138						$dialog.loadService(options.data, options);
2139					}
2140				}
2141			});
2142		});
2143	};
2144
2145	$.fn.requireConfirm = function (options) {
2146		this.click(function (e) {
2147			e.preventDefault();
2148			var message = options.message, link = this;
2149
2150			if (! message) {
2151				message = $(this).data('confirm');
2152			}
2153
2154			if (confirm (message)) {
2155				$.ajax($(this).attr('href'), {
2156					type: 'POST',
2157					dataType: 'json',
2158					data: {
2159						'confirm': 1
2160					},
2161					success: function (data) {
2162						options.success.apply(link, [data]);
2163					},
2164					error: function (jqxhr) {
2165						$(link).closest('form').showError(jqxhr);
2166					}
2167				});
2168			}
2169			return false;
2170		});
2171
2172		return this;
2173	};
2174
2175	$.fn.showError = function (message) {
2176		if (message.responseText) {
2177			var data = $.parseJSON(message.responseText);
2178			message = data.message;
2179		}
2180		this.each(function () {
2181			var validate = $(this).closest('form').validate(), errors = {}, field;
2182
2183			if (validate) {
2184				if (! $(this).attr('name')) {
2185					$(this).attr('name', $(this).attr('id'));
2186				}
2187
2188				field = $(this).attr('name');
2189
2190				var parts;
2191				if (parts = message.match(/^<!--field\[([^\]]+)\]-->(.*)$/)) {
2192					field = parts[1];
2193					message = parts[2];
2194				}
2195				
2196				errors[field] = message;
2197				validate.showErrors(errors);
2198
2199				setTimeout(function () {
2200					$('#error_report li').filter(function () {
2201						return $(this).text() === message;
2202					}).remove();
2203
2204					if ($('#error_report ul').is(':empty')) {
2205						$('#error_report').empty();
2206					}
2207				}, 100);
2208			}
2209		});
2210
2211		return this;
2212	};
2213
2214	$.fn.clearError = function () {
2215		this.each(function () {
2216			$(this).closest('form').find('label.error[for="' + $(this).attr('name') + '"]').remove();
2217		});
2218
2219		return this;
2220	};
2221
2222	$.fn.object_selector = function (filter, threshold) {
2223		var input = this;
2224		this.each(function () {
2225			var $spinner = $(this).parent().modal(" ");
2226			$.getJSON('tiki-searchindex.php', {
2227				filter: filter
2228			}, function (data) {
2229				var $select = $('<select/>'), $autocomplete = $('<input type="text"/>');
2230				$(input).wrap('<div class="object_selector"/>');
2231				$(input).hide();
2232				$select.append('<option/>');
2233				$.each(data, function (key, value) {
2234					$select.append($('<option/>').attr('value', value.object_type + ':' + value.object_id).text(value.title));
2235				});
2236
2237				$(input).after($select);
2238				$select.change(function () {
2239					$(input).data('label', $select.find('option:selected').text());
2240					$(input).val($select.val()).change();
2241				});
2242
2243				if (jqueryTiki.selectmenu) {
2244					var $hidden = $select.parents("fieldset:hidden:last").show();
2245					$select.css("font-size", $.browser.webkit ? "1.4em" : "1.1em")	// not sure why webkit is so different, it just is :(
2246						.attr("id", input.attr("id") + "_sel")						// bug in selectmenu when no id
2247						.tiki("selectmenu");
2248					$hidden.hide();
2249				}
2250				$spinner.modal();
2251
2252				if ($select.children().length > threshold) {
2253					var filterField = $('<input type="text"/>').width(120).css('marginRight', '1em');
2254
2255					$(input).after(filterField);
2256					filterField.wrap('<label/>').before(tr('Search:')+ ' ');
2257
2258					filterField.keypress(function (e) {
2259						var field = this;
2260
2261						if (e.which === 0 || e.which === 13) {
2262							return false;
2263						}
2264
2265						if (this.searching) {
2266							clearTimeout(this.searching);
2267							this.searching = null;
2268						}
2269
2270						if (this.ajax) {
2271							this.ajax.abort();
2272							this.ajax = null;
2273							$spinner.modal();
2274						}
2275
2276						if (jqueryTiki.selectmenu) {
2277							$select.selectmenu('open');
2278							$(field).focus();
2279						}
2280
2281						this.searching = setTimeout(function () {
2282							var loaded = $(field).val();
2283							if (!loaded || loaded === " ") {	// let them get the whole list back?
2284								loaded = "*";
2285							}
2286							if ((loaded === "*" || loaded.length >= 3) && loaded !== $select.data('loaded')) {
2287								$spinner = $(field).parent().modal(" ");
2288								field.ajax = $.getJSON('tiki-searchindex.php', {
2289									filter: $.extend(filter, {autocomplete: loaded})
2290								}, function (data) {
2291									$select.empty();
2292									$select.data('loaded', loaded);
2293									$select.append('<option/>');
2294									$.each(data, function (key, value) {
2295										$select.append($('<option/>').attr('value', value.object_type + ':' + value.object_id).text(value.title));
2296									});
2297									if (jqueryTiki.selectmenu) {
2298										$select.tiki("selectmenu");
2299										$select.selectmenu('open');
2300										$(field).focus();
2301									}
2302									$spinner.modal();
2303								});
2304							}
2305						}, 500);
2306					});
2307				}
2308			});
2309		});
2310
2311		return this;
2312	};
2313
2314	$.fn.sortList = function () {
2315		var list = $(this), items = list.children('li').get();
2316
2317		items.sort(function(a, b) {
2318			var compA = $(a).text().toUpperCase();
2319			var compB = $(b).text().toUpperCase();
2320			return (compA < compB) ? -1 : (compA > compB) ? 1 : 0;
2321		})
2322
2323		$.each(items, function(idx, itm) {
2324			list.append(itm);
2325		});
2326	};
2327	$.localStorage = {
2328		store: function (key, value) {
2329			if (window.localStorage) {
2330				window.localStorage[key] = $.toJSON(favoriteList);
2331			}
2332		},
2333		load: function (key, callback, fetch) {
2334			if (window.localStorage && window.localStorage[key]) {
2335				callback($.parseJSON(window.localStorage[key]));
2336			} else {
2337				fetch(function (data) {
2338					window.localStorage[key] = $.toJSON(data);
2339					callback(data);
2340				});
2341			}
2342		}
2343	};
2344
2345	var favoriteList = [];
2346	$.fn.favoriteToggle = function () {
2347		this.find('a')
2348			.each(function () {
2349				var type, obj, isFavorite, link = this;
2350				type = $(this).queryParam('type');
2351				obj = $(this).queryParam('object');
2352				
2353
2354				function isFavorite() {
2355					var ret = false;
2356					$.each(favoriteList, function (k, v) {
2357						if (v === type + ':' + obj) {
2358							ret = true;
2359							return false;
2360						}
2361					});
2362
2363					return ret;
2364				}
2365
2366				$(this).empty();
2367				$(this).append(tr('Favorite'));
2368				$(this).prepend($('<img style="vertical-align: top; margin-right: .2em;" />').attr('src', isFavorite() ? 'img/icons/star.png' : 'img/icons/star_grey.png'));
2369				// Toggle class of closest surrounding div for css customization
2370				if (isFavorite()) {
2371					$(this).closest('div').addClass( 'favorite_selected' );
2372					$(this).closest('div').removeClass( 'favorite_unselected' ); 
2373				} else {
2374					$(this).closest('div').addClass( 'favorite_unselected' );
2375					$(this).closest('div').removeClass( 'favorite_selected' );	
2376				}
2377				$(this)
2378					.filter(':not(".register")')
2379					.addClass('register')
2380					.click(function () {
2381						$.getJSON($(this).attr('href'), {
2382							target: isFavorite() ? 0 : 1
2383						}, function (data) {
2384							favoriteList = data.list;
2385							$.localStorage.store('favorites', favoriteList);
2386
2387							$(link).parent().favoriteToggle();
2388						});
2389						return false;
2390					});
2391			});
2392
2393		return this;
2394	};
2395
2396	$.fn.queryParam = function (name) {
2397		name = name.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]");
2398		var regex = new RegExp("[\\?&]" + name + "=([^&#]*)");
2399		var results = regex.exec(this[0].href);
2400
2401		if(results == null) {
2402			return "";
2403		} else {
2404			return decodeURIComponent(results[1].replace(/\+/g, " "));
2405		}
2406	};
2407
2408	$(function () {
2409		var list = $('.favorite-toggle');
2410
2411		if (list.length > 0) {
2412			$.localStorage.load(
2413				'favorites',
2414				function (data) {
2415					favoriteList = data;
2416					list
2417						.favoriteToggle()
2418						.removeClass('favorite-toggle');
2419				}, 
2420				function (recv) {
2421					$.getJSON($.service('favorite', 'list'), recv);
2422				}
2423			);
2424		}
2425	});
2426
2427	$.ajaxSetup({
2428		complete: function () {
2429			$('.favorite-toggle')
2430				.favoriteToggle()
2431				.removeClass('favorite-toggle');
2432		}
2433	});
2434
2435	/**
2436	 * Show a loading spinner on top of a button (or whatever)
2437	 *
2438	 * @param empty or jq object $spinner		if empty, spinner is added and returned and element "disabled"
2439	 * 											if spinner then spinner is removed and element returned to normal
2440	 *
2441	 * @return jq object $spinner being shown or null when removing
2442	 */
2443
2444	$.fn.showBusy = function( $spinner ) {
2445		if (!$spinner) {
2446			var pos = $(this).position();
2447			$spinner = $("<img src='img/spinner.gif' alt='" + tr("Wait") + "' class='ajax-spinner' />").
2448					css({
2449						"position": "absolute",
2450						"top": pos.top + ($(this).height() / 2),
2451						"left": pos.left + ($(this).width() / 2) - 8
2452					}).data("target", this);
2453			$(this).parent().find(".ajax-spinner").remove();
2454			$(this).parent().append($spinner);
2455			$(this).attr("disabled", true).css("opacity", 0.5);
2456			return $spinner;
2457		} else {
2458			$($spinner.data("target")).attr("disabled", false).css("opacity", 1);
2459			$spinner.remove();
2460			return null;
2461		}
2462	}
2463
2464})($jq);
2465
2466// Prevent memory leaks in IE
2467// Window isn't included so as not to unbind existing unload events
2468// More info:
2469//	- http://isaacschlueter.com/2006/10/msie-memory-leaks/
2470if ( window.attachEvent && !window.addEventListener ) {
2471	window.attachEvent("onunload", function() {
2472		for ( var id in jQuery.cache ) {
2473			var item = jQuery.cache[ id ];
2474			if ( item.handle ) {
2475				if ( item.handle.elem === window ) {
2476					for ( var type in item.events ) {
2477						if ( type !== "unload" ) {
2478							// Try/Catch is to handle iframes being unloaded, see #4280
2479							try {
2480								jQuery.event.remove( item.handle.elem, type );
2481							} catch(e) {}
2482						}
2483					}
2484				} else {
2485					// Try/Catch is to handle iframes being unloaded, see #4280
2486					try {
2487						jQuery.event.remove( item.handle.elem );
2488					} catch(e) {}
2489				}
2490			}
2491		}
2492	});
2493}
2494
2495$.modal = function(msg) {
2496	return $('body').modal(msg, {
2497		isEverything: true
2498	});
2499};
2500
2501//Makes modal over window or object so ajax can load and user can't prevent action
2502$.fn.modal = function(msg, s) {
2503	var obj = $(this);
2504	var lastModal = obj.attr('lastModal');
2505	
2506	if (!lastModal) {
2507		lastModal = Math.floor(Math.random() * 1000);
2508		obj.attr('lastModal', lastModal);
2509	}
2510
2511	var mid = obj.height() / 2 + obj.offset().top;
2512	if (mid > $window.height()) {
2513		mid = ($window.height() - obj.offset().top) / 2 + obj.offset().top;
2514	}
2515	s = $.extend({
2516		isEverything: false,
2517		top: 	obj.offset().top,
2518		left: 	obj.offset().left,
2519		height: obj.height(), //we try to get height here
2520		width: 	obj.width(),
2521		middle: (mid),
2522		center: (obj.width() / 2 + obj.offset().left)
2523	}, s);
2524	
2525	var modal = $('body').find('#modal_' + lastModal);
2526	var spinner = $('<img src="img/spinner.gif" />');
2527	
2528	if (!msg) {
2529		modal
2530			.fadeOut(function() {
2531				$(this).remove();
2532			});
2533		obj.removeAttr('lastModal');
2534		return;
2535	}
2536	
2537	if (modal.length) {
2538		modal
2539			.find('.dialog')
2540			.html(spinner)
2541			.append(msg);
2542		return;
2543	}
2544	
2545	modal = $('<div>' + 
2546		    '<div class="dialog"></div>' +
2547		    '<div class="mask"></div>' +
2548		'</div>')
2549		.addClass('boxes')
2550		.attr('id', 'modal_' + lastModal)
2551		.prependTo('body');
2552	
2553	var size = {};
2554	
2555	if (s.isEverything) { //if the modal includes the whole page
2556		s = $.extend(s, {
2557			top: 	0,
2558			left:	0,
2559			height: $document.height(),
2560			width: 	$window.width(),
2561			middle: $window.height() / 2,
2562			center: $window.width() / 2
2563		});
2564	}
2565	 
2566	//Set height and width to mask to fill up the whole screen or the single element
2567	modal.find('.mask')
2568		.css('width', 	s.width + 'px')
2569		.css('height', 	s.height + 'px')
2570		.css('top', 	s.top + 'px')
2571		.css('left', 	s.left + 'px')
2572		.css('position', 'absolute')
2573		.fadeTo(1,0.01)
2574		.fadeTo(1000,0.8);
2575	
2576	var dialog = modal.find('.dialog');
2577	dialog
2578		.append(spinner)
2579		.append(msg)
2580		.css('top',  (s.middle - dialog.height() / 2) + 'px')
2581		.css('left', (s.center - dialog.width() / 2) + 'px')
2582		.css('position', 'absolute');
2583
2584	if (s.isEverything) {
2585		dialog
2586			.css('top', (s.middle - dialog.height() / 2) + 'px')
2587			.css('left', (s.center - dialog.width() / 2) + 'px')
2588			.css('position', 'fixed');
2589	}
2590	
2591	return obj;
2592};
2593
2594//makes the width of an input change to the value
2595$.fn.valWidth = function() {
2596	var me = $(this);
2597	return me.ready(function() {
2598		var h = me.height();
2599		if (!h) {
2600			h = me.offsetParent().css("font-size");
2601			if (h) {
2602				h = parseInt(h.replace("px", ""));
2603			}
2604		}
2605		me.keyup(function() {
2606			var width = me.val().length * h;
2607
2608			me
2609				.stop()
2610				.animate({
2611					width: (width > h ? width : h)
2612				}, 200);
2613		})
2614		.keyup();
2615	});
2616};
2617
2618//For making pagination have the ability to enter page/offset number and go
2619$.paginationHelper = function() {
2620	$('.pagenums').each(function() {
2621		var me = $(this);
2622		var step = me.find('input.pagenumstep');
2623		var endOffset = (me.find('input.pagenumend').val() - 1) * step.data('step');
2624		var url = step.data('url');
2625		var offset_jsvar = step.data('offset_jsvar');
2626		
2627		me.find('span.pagenumstep').replaceWith(
2628			$('<input type="text" style="font-size: inherit; " />')
2629				.val(step.val())
2630				.change(function() {
2631					var newOffset = step.data('step') * ($(this).val() - 1);
2632					
2633					if (newOffset >= 0) {
2634						//make sure the offset isn't too high
2635						newOffset = (newOffset > endOffset ? endOffset : newOffset);
2636						
2637						//THis is for custom/ajax search handling
2638						window[offset_jsvar] = newOffset;
2639						if (step[0]) {
2640							if (step.attr('onclick')) {
2641								step[0].onclick();
2642								return;
2643							}
2644						}
2645						
2646						//if the above behavior isn't there, we update location
2647						document.location = url + "offset=" + newOffset;
2648					}
2649				})
2650				.keyup(function(e) {
2651					switch(e.which) {
2652						case 13: $(this).blur();
2653					}
2654				})
2655				.valWidth()
2656		);
2657	});
2658};
2659
2660//a sudo "onvisible" event
2661$.fn.visible = function(fn, isOne) {
2662	if (fn) {
2663		$(this).each(function() {
2664			var me = $(this);
2665			if (isOne) {
2666				me.one('visible', fn);
2667			} else {
2668				me.bind('visible', fn);
2669			}
2670			
2671			function visibilityHelper() {
2672				if (!me.is(':visible')) {
2673					setTimeout(visibilityHelper, 500);
2674				} else {
2675					me.trigger('visible');
2676				}
2677			}
2678			
2679			visibilityHelper();
2680		});
2681	} else {
2682		$(this).trigger('visible');
2683	}
2684	
2685	return this;
2686};
2687
2688$.download = function(url, data, method){
2689	//url and data options required
2690	if( url && data ){ 
2691		//data can be string of parameters or array/object
2692		data = typeof data == 'string' ? data : jQuery.param(data);
2693		//split params into form inputs
2694		var inputs = '';
2695		jQuery.each(data.split('&'), function(){ 
2696			var pair = this.split('=');
2697			inputs+='<input type="hidden" name="'+ pair[0] +'" value="'+ pair[1] +'" />'; 
2698		});
2699		//send request
2700		jQuery('<form action="'+ url +'" method="'+ (method||'post') +'">'+inputs+'</form>')
2701		.appendTo('body').submit().remove();
2702	};
2703};
2704
2705$.uiIcon = function(type) {
2706	return $('<div style="width: 1.4em; height: 1.4em; margin: .2em; display: inline-block; cursor: pointer;">' +
2707		'<span class="ui-icon ui-icon-' + type + '">&nbsp;</span>' + 
2708	'</div>')
2709	.hover(function(){
2710		$(this).addClass('ui-state-highlight');
2711	}, function() {
2712		$(this).removeClass('ui-state-highlight');
2713	});
2714};
2715
2716$.uiIconButton = function(type) {
2717	return $.uiIcon(type).addClass('ui-state-default ui-corner-all');
2718};
2719
2720$.rangySupported = function(fn) {
2721	if (window.rangy) {
2722		rangy.init();
2723		var cssClassApplierModule = rangy.modules.CssClassApplier;
2724		return fn();
2725	}
2726};
2727
2728$.fn.rangy = function(fn) {
2729	var me = $(this);
2730	$.rangySupported(function() {
2731		$document.mouseup(function(e) {
2732			if (me.data('rangyBusy')) return;
2733			
2734			var selection = rangy.getSelection();
2735			var html = selection.toHtml();
2736			var text = selection.toString();
2737			
2738			if (text.length > 3 && rangy.isUnique(me[0], text)) {
2739					if (fn)
2740						if ($.isFunction(fn))
2741							fn({
2742								text: text,
2743								x: e.pageX,
2744								y: e.pageY
2745							});
2746			}
2747		});
2748	});
2749	return this;
2750};
2751
2752$.fn.rangyRestore = function(phrase, fn) {
2753	var me = $(this);
2754	$.rangySupported(function() {
2755		phrase = rangy.setPhrase(me[0], phrase);
2756		
2757		if (fn)
2758			if ($.isFunction(fn))
2759				fn(phrase);
2760	});
2761	return this;
2762};
2763
2764$.fn.rangyRestoreSelection = function(phrase, fn) {
2765	var me = $(this);
2766	$.rangySupported(function() {
2767		phrase = rangy.setPhraseSelection(me[0], phrase);
2768		
2769		if (fn)
2770			if ($.isFunction(fn))
2771				fn(phrase);
2772	});
2773	return this;
2774};
2775
2776$.fn.ajaxEditDraw = function(options) {
2777	var me = $(this).attr('href', 'tiki-ajax_services.php');
2778
2779	//defaults
2780	options = $.extend({
2781		saved: function() {},
2782		closed: function() {}
2783	}, options);
2784
2785	$.modal(tr('Loading editor'));
2786
2787	me.serviceDialog({
2788		title: me.attr('title'),
2789		data: {
2790			controller: 'draw',
2791			action: 'edit',
2792			fileId: me.data('fileid'),
2793			galleryId: me.data('galleryid'),
2794			raw: true
2795		},
2796		modal: true,
2797		zIndex: 9999,
2798		fullscreen: true,
2799		load: function (data) {
2800			//prevent from happeneing over and over again
2801			if (me.data('drawLoaded')) return false;
2802			
2803			me.data('drawLoaded', true);
2804
2805			me.drawing = $('#tiki_draw')
2806				.loadDraw({
2807					fileId: me.data('fileid'),
2808					galleryId: me.data('galleryid'),
2809					name: me.data('name'),
2810					data: $('#fileData').val()
2811				})
2812				.bind('savedDraw', function(e, o) {
2813					me.data('drawLoaded', false);
2814					me.drawing.parent().dialog('destroy');
2815					me.drawing.remove();
2816
2817					//update the image that did exist in the page with the new one that now exists
2818					var img = $('.pluginImg' + me.data('fileid')).show();
2819					var w = img.width(), h = img.height();
2820
2821					if (img.hasClass('regImage')) {
2822						var replacement = $('<div />')
2823							.attr('class', img.attr('class'))
2824							.attr('style', img.attr('style'))
2825							.attr('id', img.attr('id'))
2826							.insertAfter(img);
2827
2828						img.remove();
2829						img = replacement;
2830					}
2831
2832					$('<div class=\"svgImage\" />')
2833						.load('tiki-download_file.php?fileId=' + o.fileId + '&display', function() {
2834
2835							$(this)
2836								.css('position', 'absolute')
2837								.fadeTo(0, 0.01)
2838								.prependTo('body')
2839								.find('img,svg')
2840								.scaleImg({
2841									width: w,
2842									height: h
2843								});
2844
2845							img.html($(this).html());
2846
2847							$(this).remove();
2848						});
2849
2850					if (!options.saved) return;
2851
2852					options.saved(o.fileId);
2853				})
2854				.submit(function() {
2855					me.drawing.saveDraw();
2856					return false;
2857				})
2858				.bind('loadedDraw', function() {
2859					//kill the padding around the dialog so it looks like svg-edit is one single box
2860					me.drawing
2861						.parent()
2862						.css('padding', '0px');
2863
2864					var serviceDialog = me.data('serviceDialog');
2865					if (serviceDialog) {
2866						var drawFrame = $('#svgedit');
2867						serviceDialog
2868							.bind('dialogresize', function() {
2869								drawFrame.height(serviceDialog.height() - 4);
2870							})
2871							.trigger('dialogresize');
2872					}
2873
2874					$.modal();
2875				});
2876
2877			me.drawing.find('#drawMenu').remove();
2878		},
2879		close: function() {
2880			if (me.data('drawLoaded')) {
2881				me.data('drawLoaded', false);
2882				me.drawing.remove();
2883
2884				if (!options.closed) return;
2885				options.closed(me);
2886			}
2887		}
2888	});
2889	
2890	return false;
2891};
2892
2893$.notify = function(msg, settings) {
2894	settings = $.extend({
2895		speed: 10000
2896	},settings);
2897	
2898	var notify = $('#notify');
2899	
2900	if (!notify.length) {
2901		notify = $('<div id="notify" />')
2902			.css('top', '5px')
2903			.css('right', '5px')
2904			.css('position', 'fixed')
2905			.css('z-index', 9999999)
2906			.css('padding', '5px')
2907			.width($window.width() / 5)
2908			.prependTo('body');
2909	}
2910	
2911	var note = $('<div class="notify ui-state-error ui-corner-all ui-widget ui-widget-content" />')
2912		.append(msg)
2913		.css('padding', '5px')
2914		.css('margin', '5px')
2915		.mousedown(function() {
2916			return false;
2917		})
2918		.hover(function() {
2919			$(this)
2920				.stop()
2921				.fadeTo(500, 0.3)
2922		}, function() {
2923			$(this)
2924				.stop()
2925				.fadeTo(500, 1)
2926		})
2927		.prependTo(notify);
2928	
2929	setTimeout(function() {
2930		note
2931			.fadeOut()
2932			.slideUp();
2933		
2934		//added outside of fadeOut to ensure removal 
2935		setTimeout(function() {
2936			note.remove();
2937		}, 1000);
2938		
2939	}, settings.speed);
2940};
2941
2942/**
2943*   Close (user) sidebar column(s) if no modules are displayed.
2944*   Modules can be hidden at runtime. So, check after the page/DOM model is loaded.
2945*/
2946$(function () {
2947
2948    // Do final client side adjustment of the sidebars
2949    /////////////////////////////////////////
2950    var maincol = 'col1';
2951
2952    // Hide left side panel, if no modules are displayed
2953    var left_mods = document.getElementById('left_modules');
2954    if (left_mods != null) {
2955        if (isEmptyText(left_mods.innerHTML)) {
2956            var col = document.getElementById('col2');
2957            if (col != null) {
2958                col.style.display = "none"
2959            }
2960            document.getElementById(maincol).style.marginLeft = '0';
2961
2962            var toggle = document.getElementById("showhide_left_column")
2963            if (toggle != null) {
2964                toggle.style.display = "none";
2965            }
2966        }
2967    }
2968
2969    // Hide right side panel, if no modules are displayed
2970    var right_mods = document.getElementById('right_modules');
2971    if (right_mods != null) {
2972
2973        //        alert("right_mods.innerHTML=" + right_mods.innerHTML);
2974        //alert("right_mods.innerText=" + right_mods.innerText);
2975
2976        if (isEmptyText(right_mods.innerHTML)) {
2977            var col = document.getElementById('col3');
2978            if (col != null) {
2979                col.style.display = "none"
2980            }
2981            document.getElementById(maincol).style.marginRight = '0';
2982
2983            var toggle = document.getElementById("showhide_right_column")
2984            if (toggle != null) {
2985                toggle.style.display = "none";
2986            }
2987        }
2988    }
2989
2990    // FF does not support obj.innerText. So, analyze innerHTML, which all browsers seem to support
2991    function isEmptyText(html) {
2992
2993        // Strip HTML tags
2994        /////////////////////////
2995        var strInputCode = html;
2996
2997        // Replace coded-< with <, and coded-> with >
2998        strInputCode = strInputCode.replace(/&(lt|gt);/g, function (strMatch, p1) {
2999            return (p1 == "lt") ? "<" : ">";
3000        });
3001        // Strip tags
3002        var strTagStrippedText = strInputCode.replace(/<\/?[^>]+(>|$)/g, "");
3003
3004        // Trim whitespace
3005        var text = strTagStrippedText.replace(/^\s+|\s+$/g, "");
3006
3007        return text == null || text.length == 0;
3008    }
3009});
3010
3011$.trackerForm = function (trackerId, itemId, remove) {
3012	if (itemId && !remove) {
3013		return $('<form id="editItemForm" method="get" action="tiki-view_tracker_item.php" enctype="multipart/form-data" novalidate="novalidate" />')
3014			.append('<input type="hidden" name="trackerId" value="' + trackerId + '" />')
3015			.append('<input type="hidden" name="itemId" value="' + itemId + '" />')
3016			.append('<input type="hidden" name="save" value="Save" />');
3017	} else if (!remove) {
3018		return $('<form id="newItemForm" method="get" action="tiki-view_tracker.php" enctype="multipart/form-data" novalidate="novalidate" />')
3019			.append('<input type="hidden" name="trackerId" value="' + trackerId + '" />')
3020			.append('<input type="hidden" name="save" value="Save" />')
3021			.append('<input type="hidden" name="viewitem" value="new" />');
3022	} else {
3023		return $('<form id="deleteItemForm" method="get" action="tiki-view_tracker.php" enctype="multipart/form-data" novalidate="novalidate" />')
3024			.append('<input type="hidden" name="trackerId" value="' + trackerId + '" />')
3025			.append('<input type="hidden" name="remove" value="' + itemId + '" />')
3026			.append('<input type="hidden" name="force" value="y" />');
3027	}
3028};
3029
3030// try and reposition the menu ul within the browser window
3031$.fn.moveToWithinWindow = function() {
3032	var $el = $(this);
3033	h = $el.height(),
3034	w = $el.width(),
3035	o = $el.offset(),
3036	po = $el.parent().offset(),
3037	st = $window.scrollTop(),
3038	sl = $window.scrollLeft(),
3039	wh = $window.height(),
3040	ww = $window.width();
3041
3042	if (w + o.left > sl + ww) {
3043		$el.animate({'left': sl + ww - w - po.left}, 'fast');
3044	}
3045	if (h + o.top > st + wh) {
3046		$el.animate({'top': st + wh - (h > wh ? wh : h) - po.top}, 'fast');
3047	} else if (o.top < st) {
3048		$el.animate({'top': st - po.top}, 'fast');
3049	}
3050};
3051
3052$.fn.scaleImg = function (max) {
3053	$(this).each(function() {
3054		//Here we want to make sure that the displayed contents is the right size
3055		var img = $(this),
3056		actual = {
3057			height: img.height(),
3058			width: img.width()
3059		},
3060		original = $(this).clone(),
3061		parent = img.parent();
3062
3063		var winner = '';
3064
3065		if (actual.height > max.height) {
3066			winner = 'height';
3067		} else if (actual.width > max.width) {
3068			winner = 'width';
3069		}
3070
3071		//if there is no winner, there is no need to resize
3072		if (winner) {
3073			//we resize both images and svg, we check svg first
3074			var g = img.find('g');
3075			if (g.length) {
3076				img
3077					.attr('preserveAspectRatio', 'xMinYMin meet');
3078
3079				parent
3080					.css('overflow', 'hidden')
3081					.width(max.width)
3082					.height(max.height);
3083
3084				g.attr('transform', 'scale( ' + (100 / (actual[winner] / max[winner]) * 0.01)  + ' )');
3085			} else {
3086				//now we resize regular images
3087				if (actual.height > actual.width) {
3088					var h = max.height;
3089					var w = Math.ceil(actual.width / actual.height * max.height);
3090				} else {
3091					var w = max.width;
3092					var h = Math.ceil(actual.height / actual.width * max.width);
3093				}
3094				img.css({ height: h, width: w });
3095			}
3096
3097			img
3098				.css('cursor', "url(img/icons/zoom.gif),auto")
3099				.click(function () {
3100					$('<div/>').append(original).dialog({
3101						modal: true,
3102						width: Math.min($(window).width(), actual.width + 20),
3103						height: Math.min($(window).height(), actual.height + 50)
3104					});
3105					return false;
3106				});
3107		}
3108	});
3109
3110	return this;
3111};

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.