PageSourceSearch

https://oskar-kokoschka.ch/jsc/memo.js

js oskar-kokoschka.ch collected 2026-10-02 16:16:46 UTC 127,642 bytes, 2,413 lines download raw bytes

1$(document).ready(			function() { cms_init();});
2$(document).ajaxComplete(	function(event,jqXHR,options) { cms_init(/*event,jqXHR,options*/) });
3
4$.fn.loadOuter = function( url, callback ) {
5	var outer = $(this);
6	$("body").data("href",url);		// put the latest url request in the page's body, so it can be used for other actions (ex: export)
7	$.get(url, function( data, status, jqXHR ) {
8		contentType=jqXHR.getResponseHeader('content-type')
9		if (contentType.indexOf("json")>-1)
10			cms_return(outer, data);
11		else if (contentType.indexOf("html")>-1)
12			outer.replaceWith(data)
13		else alert("Undefined content-type received [455]")
14		//setTimeout(function(){alert(outer.find('[autofocus]').length);outer.find('[autofocus]').focus()},1000)
15		//alert(outer.find('[autofocus]').length)
16	});
17}
18
19$.fn.equaWidth = function( arr, reset ) {
20	context=$(this)
21	if (reset) arr.forEach(function(o,i){$(o,context).css({width:"auto"})})	// we reset the width
22	var obj={current:0,total:0,width:[],percent:[]}
23	arr
24		.map(function(o){return o+":visible"})		// we filter only visible items
25		.forEach(function(o,i){						// we calculate all width
26			obj.current=$(o,context).map(function(){return $(this).outerWidth()}).sort(function(a, b){return b-a})[0]||0
27			obj.width.push(obj.current)
28			obj.total+=obj.current
29		})
30	obj.width.forEach(function(o,i){obj.percent.push(o/obj.total*100)})
31	arr.forEach(function(o,i){$(o,context).css({width:obj.percent[i]+"%"})})
32}
33
34$.initchange = function( arg ) {
35	// ex: $.initchange({name:"login",value:"empty",callback:function(){alert(this.value)}}) will: 1:alert("empty"), 2:if input[name='login'] changes will alert(#value#)
36	arg.self=$("input[name='"+arg.name+"']")
37	arg.callback(arg)		// callback is done even if arg.self.length=0, important!
38	arg.self.on("change",function(e){
39		if (arg.self.length>1) {
40			arg.value=arg.self.filter(function(){return this.checked && ! this.disabled}).map(function(){return this.value}).toArray()
41		} else {
42			arg.value=arg.self.val()
43		}
44		arg.callback(arg)
45	})
46}
47
48var editable_field_file = function(div) {		// base on self.editable of cbyInpufile, here to a specific tag
49	div.find("li").eq(0).addClass("icon fontello icon-edit fright").on("click",function(){
50		var json=JSON.parse($(this).closest("div").find("input[type=hidden]").val())					// {name:"example.with.dot.gif",file:"tsdsdjfg.gif,...}
51		var parts=[json.name.split(".").slice(0,-1).join("."),json.name.split(".").pop()]		// ["example.with.dot","gif"]
52		var width=$(this).width()
53		$(this).hide().after($("<input/>").css({minWidth:width}).val(parts[0]).on("blur",function(){
54			json.name=[$(this).val(),parts[1]].join(".")
55			$(this).closest("div").find("input[type=hidden]").val(JSON.stringify(json))
56			$(this).prev().text(json.name).show()
57			$(this).remove()
58		}))
59		$(this).next().focus()
60	})
61}
62function cms_return(obj, data) {
63	// All ajax request should return (success) here (edit/view/save/...), obj is a jQuery object
64
65	/* first, set a min height to the parent to avoid "jump" if the content's height decreases */
66	var parent=obj.closest("div.dataset").parent();
67	//if (parent.length && parent.closest('#cbybox').length==0 && (parseInt(parent.css("min-height"))<parent.height()) ) parent.css("min-height",parent.height()+"px")
68	//if (obj.closest("div.dataset").hasClass("metaform-forms")) $(window).scrollTop(0)
69	if (typeof data=="object") {
70		if (data.xreferer) {
71			document.cookie="xreferer="+encodeURIComponent(data.xreferer)+"; path= /;"
72		}
73		if (data.message) if($.covr) $.covr.open(data.message.toString()); else alert(data.message.toString());
74		// if data.jsc, eval this string. Note: the "context" (this) is set by $.ajax as the button clicked before the ajax request (see
75		if (data.jsc) eval(data.jsc)
76		switch (data.status) {
77			case 1:		// normal behaviour -> just replace obj with data.html
78				obj.replaceWith( data.html||"Empty data [432]" );
79				return;
80			case 2:		// error (error on posted fiels, ...) -> show the message data.errors
81				if (data.errors) data.errors=data.errors.toString()
82				if ($('div.ds-form-errors',obj).length) {
83					$('div.ds-form-errors',obj).show().html( data.errors||"Empty data [432]");
84				} else {
85					data.errors=data.errors.replace(/(<([^>]+)>)/ig,"")||"Empty data [433]"
86					if($.covr) $.covr.open(data.errors); else alert(data.errors);
87				}
88				return;
89			case 3:		// return from a save -> replace the all dataset with new one
90				if (obj.closest("div.dataset").length && data.location && data.target!="top") {
91					obj.closest("div.dataset").loadOuter(data.location)
92				} else
93					document.location.href=data.location||(document.location.href.split("#")[0]);
94				//obj.parent().replaceWith( data.html||"Empty data [434]" );
95				return;
96			case 4:		// javascript reload (without the hash wich may cause problem by "jumping" into the page)
97				// always try to reload "top" (if frame), except if top:false is in the data
98				data.document=top||self
99				if (data.top===false) data.document=self
100				data.document.location=data.document.location.pathname+(data.document.location.search||"")
101				return;
102			case 5:		// just do nothing, used in combinaison of data.message or data.jsc
103				return;
104				break;
105			default:
106				alert("Error reading response:\n"+JSON.stringify(data,null,2));
107				return;
108		}
109	}
110	alert("Error reading response:\n"+JSON.stringify(data,null,2));
111}
112
113
114function cms_init(context) {
115	context=context||document
116	var iframe=(window !== window.parent)
117	if (iframe) $("html").css({"overflow-y":"auto"})
118	/*$("img.converting").css("background-image","url(/img/converting.gif)").on("load",function(){$(this).css("background-image","none")})
119	$("img",context).error(function() {
120		var self=this
121		$(self).attr("data-src",$(self).attr("src")).attr("src","data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7")
122		var image=new Image()
123		image.onerror=function(){setTimeout(reload,5000)}
124		image.onload=function(){self.src=image.src}
125		var reload=function(){
126			image.src=$(self).attr("data-src")
127		}
128		setTimeout(reload,5000)
129		//$(this).hide();
130	});*/
131	/*$('[data-xreferer]',context).onFirst("click",function(){
132		xreferer=$(this).attr('data-xreferer');document.cookie="xreferer="+encodeURIComponent(xreferer)+"; path=/"
133	})*/
134
135	// Move openclose button
136	$("div.dataset a.ds-button.openclose",context).each(function(){
137		var icon=$(this).find("i");$(this).html("").removeClass("ds-button").append(icon);
138		var head=$(this).closest('div.ds-form').find('div.ds-form-head');
139		// If head is only text (!&amp; is not text -> also test on contents), transform as a <span>#text</span> with a click acting like clicking on the icon (to make the text clikable)
140		if (head.text()==head.html() || head.contents().length==1) head.html($("<span/>").css({cursor:"pointer"}).text(head.text()).click(function(){$(this).prev().click()}))
141		head.prepend($(this).show())		// .show() because in css ds-button.openclose are display:none to avoid flicking
142	});
143
144	var plugins=
145		{ "data-clone":{position:"before",clone:"prev",on:"click"}
146		, "data-dropdown":{fn:"cbyDropdown"}
147		, "data-sort":{}
148		, "data-tabsize":{}
149		, "data-pagination":{dico:"entrée(s)"}
150		, "data-selection":{}
151		, "data-target":{keepafter:true}
152		, "data-inputfile":{fn:"cbyInputfile"}
153		, "data-redactor":{fn:"redactor",hide:true,loadsync:["/jsc/redactor/redactor.js","/jsc/redactor/redactor-memo.js","/jsc/redactor/redactor.css"]}
154		, "data-redactor3":{fn:"redactor",hide:false,loadsync:["/lib/redactor-317/redactor.js","/lib/redactor-317/plugins.js","/lib/redactor-317/redactor.css"]}
155		, "data-geturl":{}
156		, "data-hidemailto":{}
157		, "data-cookieconsent":{name: "cookieconsent",path: "/",domain: "",expires: 92,text:["This website uses cookies to ensure you get the best experience on our website.","Got it!"]}
158		, "data-cycl":{}
159		, "data-rich":{}
160		, "data-scrollto":{}
161		, "data-scrollmem":{}
162		, "data-textcomplete":{}
163		, "data-canvasjs":{loadsync:["/jsc/jquery.canvasjs.min.js?v1"]}
164		, "data-signature":{loadsync:["/jsc/jSignature.js"]}
165		, "data-memofs":{fn:"cbyMemofs",keepafter:false}
166		, "data-yaml":{fn:"cbyYaml",loadsync:["/jsc/jquery.yaml.js"]}
167		, "data-chat":{fn:"cbyChat", loadsync:["/jsc/jquery.cbyChat.js","/jsc/socket.io-1.2.0.js"]}
168		, "data-nanogallery2":{hide:true,fn:"nanogallery2", loadhead:["/lib/nanogallery2-2.3.0/dist/css/nanogallery2.min.css"], loadsync:["/lib/nanogallery2-2.3.0/dist/jquery.nanogallery2.min.js"]}
169	}
170//	$(".data",context).each(function(index) {		// maybe make for (var plugin in plugins) before $(".data",context).each... to see (before not working but because var plugin was missing) */
171//		var options={}
172//		for (var plugin in plugins) {
173	var options={}
174	for (var plugin in plugins) {
175		$(".data["+plugin+"]",context).each(function(index) {		// really faster than $(".data").filter("["+plugin+"]")
176			// to see which plugin take more time: console.log("cms_init for "+plugin+" in "+(new Date()))
177			var options=false		// changed 201709: added var to have local scope... to check!
178			if (options=$(this).attr(plugin)) {
179				if (plugins[plugin].hide) $(".data["+plugin+"]",context).hide();		// used when loadsync is there to avoid "flick". do it on all same objects as loadsync will "block" only the first one
180				if (plugins[plugin].debug) alert("DEBUG: plugin '"+plugin+"' with raw options: "+options)
181				if (options[0]=="{") {
182					options=JSON.parse(options);
183				} else {
184					options={}
185					options[plugin.replace("data-","")]=$(this).attr(plugin);		// ex: data-target="form" -> options={target:"form"}
186				}
187				if (plugins[plugin]) options=$.extend({},plugins[plugin],options)	// add a {} to avoid plugin option to be changed
188				if (plugins[plugin].debug) alert("DEBUG: plugin '"+plugin+"' with json options: \n"+JSON.stringify(options,null,2))
189				if (plugins[plugin].loadsync) {
190					plugins[plugin].loadsync.forEach(function(o){loadsync(o)})
191					delete plugins[plugin].loadsync
192				}
193				if (plugins[plugin].loadhead) {
194					plugins[plugin].loadhead.forEach(function(o){loadhead(o)})
195					delete plugins[plugin].loadhead
196				}
197				if (plugins[plugin].fn) {
198					$.fn[plugins[plugin].fn].apply($(this), [options]);
199					if (plugins[plugin].hide) $(this).show();
200				} else {
201					switch (plugin) {
202						case "data-clone":
203							// TODO: make other possibilities (after, on change, ...)
204							$(this).data("clone",$(this).prev().clone(true))
205							if (options.remove) $(this).prev().remove()
206							$(this).on(options.on,function(){$(this).before($(this).data("clone").clone(true));cms_init($(this).parent())})
207							break;
208						case "data-target":
209							var tag=this.tagName.toUpperCase();
210							// next line: if only one input type=[text], broser will submit the form with "enter" key -> suppress this
211							if ($(this).closest("form").find("input[type=text]").length==1) $(this).closest("form").append($("<input/>",{type:"text"}).hide())
212							switch (options.target) {
213								case "form":	// In general, this changes "view", "edit",... buttons to load content with ajax
214									switch (tag) {
215										case "FORM":$(this).unbind("submit.target").on("submit.target",function(e){$(this).closest("div.ds-form").loadOuter(this.action+"?"+$(this).serialize());return false});break
216										default:$(this).unbind("click.target").on("click.target",function(){$(this).closest("div.ds-form").loadOuter($(this).attr("href"));return false;});break;
217									}
218									break;
219								case "metaform":	// Never used...?
220									switch (tag) {
221										case "FORM":$(this).unbind("submit.target").on("submit.target",function(e){$(this).closest("div.metaform").loadOuter(this.action+"?"+$(this).serialize());return false});break;
222										default:$(this).unbind("click.target").on("click.target",function(){$(this).closest("div.metaform").loadOuter($(this).attr("href"));return false;});break;
223									}
224									break;
225								case "cbybox":
226									$(this).cbybox({
227										cssouter:{width:"80%"},cssinner:{padding:0},
228										onclose:function(){document.location.href=document.location.href;return true}
229									});
230									break;
231								default:		// allow non-A tag to be clickable
232									if (tag!=="A") $(this).unbind("click.target").on("click.target",function(){document.location.href=$(this).attr("href");})
233									break;
234							}
235							break;
236						case "data-sort":
237							// Note: for dates, direct in node, and here: dd=new Date(options.date)
238							switch (options.current) {
239								case 1:icon="icon-sort-up";break;
240								case -1:icon="icon-sort-down";break;
241								default:icon="icon-sort icon-disabled";break;
242							}
243							var icon=$("<i/>").addClass("icon "+icon).css({marginLeft:"0.5em"})
244							$(this).css({cursor:"pointer",position:"relative"/*,paddingRight:"1em"*/}).addData({target:"form"}).attr("href",options.href).append(icon)
245							break;
246						case "data-tabsize":		// $(this) is a textarea
247							var tab=options
248							if (options.tabsize && options.tabsize=="true") 		tab={type:"auto",	offset:4,	slice:1}				// if just "true", set to auto (slice: do not use the "slice" last column to calculate the max
249							if (options.tabsize && /^\d+$/.test(options.tabsize)) 	tab={type:"fixed",	size:parseInt(options.tabsize,10)}	// if number set to fixed
250							var max=function(val){		// val is a string - textarea value - with \n and \t, calculate the "max number of chars" of all virtual columns (except last)
251								val=val.split(/\n/g).map(function(o){return o.split(/\t/g)})
252								var max=val.reduce(function(r,o,i){return o.length>r?o.length:r},0)		// max number of columns - used to remove (.slice) the last colum from the calcul
253								return val.map(function(o){return o.slice(0,max-tab.slice)}).reduce(function(r,o,i){r=r.concat(o);return r},[]).reduce(function(r,o,i){return o.length>r?o.length:r},0)
254							}
255							// some default css
256							$(this).css({whiteSpace:"pre",width:"100%",minHeight:"10em",fontFamily:"monospace, 'courier new'"})
257							if (options.css) $(this).css(options.css)
258							// change "tab" key behaviour to allow inserting tab
259							$(this).on("keydown",function(e){if(e.keyCode===9){var v=this.value,s=this.selectionStart,e=this.selectionEnd;this.value=v.substring(0, s)+'\t'+v.substring(e);this.selectionStart=this.selectionEnd=s+1;return false;}})
260							if (tab.type=="fixed") $(this).css({tabSize:""+tab.size})
261							if (tab.type=="auto") $(this).css({tabSize:""+(max($(this).val())+tab.offset)}).on("keyup",function(){$(this).css({tabSize:""+(max($(this).val())+tab.offset)})})
262							break;
263						case "data-pagination":
264							//alert(JSON.stringify(options))
265							//options.maxp=parseInt(options.count+1/options.maxi)
266							var lis=[],input
267							//content.push($("<span></span>").html([options.count,options.dico,options.page+" / "+options.maxp].join(" ")))
268							if (options.search)  {
269								input=$("<input/>",{name:options.search,type:"text",autofocus:"autofocus",placeholder:"search",value:pipe.query(options.search)}).data("base",options.base).on("keyup",function(){
270									$(this).closest("div.ds-form").loadOuter($(this).data("base")+"&"+$(this).serialize())
271								}.debounce(300))
272								lis.push($("<li/>").append(input))
273							}
274							lis.push($("<li></li>").html([options.count,options.dico].join(" ")));
275							if (options.allowed && options.allowed.length>1) {
276								html=$("<div></div>")
277								$(options.allowed||[10,50,250]).each(function(i,maxi){html.append($("<a></a>").text(maxi+" / page").addClass((maxi==options.maxi)?"strong":"").addData({target:"form"}).attr("href",options.curr+"&safe.p=1&safe.maxi="+maxi))})
278
279								var data={dropdown:{layout:"html",html:html.html()}}
280								lis.push($("<li></li>")
281									.append($("<a></a>").css("padding","0 0.5em")
282										.html(((options.page-1)*options.maxi+1)+"-"+Math.min(options.count,((options.page)*options.maxi)))
283										.addData(data)))
284							}
285							// add a hidden li to reload current page, ex: used in action to reload current view at current page
286							lis.push($("<li class='none reload icon icon-cw'></li>").css({cursor:"pointer"}).addData({target:"form"}).attr("href",options.curr))
287							if (options.page>1)
288								lis.push($("<li class='icon icon-left-dir'></li>").css({cursor:"pointer"}).addData({target:"form"}).attr("href",options.prev))
289							else
290								lis.push($("<li class='icon icon-left-dir icon-disabled'></li>"))
291							if (options.page*options.maxi<options.count)
292								lis.push($("<li class='icon icon-right-dir'></li>").css({cursor:"pointer"}).addData({target:"form"}).attr("href",options.next))
293							else
294								lis.push($("<li class='icon icon-right-dir icon-disabled'></li>"))
295							var ul=$("<ul></ul>").addClass("ds-pagination").append(lis)
296
297							/*if (this.tagName.toUpperCase()=="TABLE") {
298								var colspan=$(this).find("tr").eq(0).find("td").length
299								var tr=$("<tr></tr>").addClass("ds-pagination").append($("<td></td>").attr("colspan",colspan).append(ul))
300								$(this).append($("<tfoot/>").append(tr.clone()))
301							}
302							if (this.tagName.toUpperCase()=="UL") {
303								var li=$("<li></li>").addClass("ds-pagination").append(ul)
304								$(this).append(li.clone())
305							}
306							if (this.tagName.toUpperCase()=="DIV") {
307								$(this).after(ul)
308							}*/
309							// TODO: check position: always after(ul?)
310							$(this).parent().after(ul)
311							cms_init(ul)
312							if (input) input.focus().data("val",input.val()).val("").val(input.data("val"))
313							//alert(parent.html())
314
315							break;
316						case "data-selection":
317							//alert(JSON.stringify(options))
318							var checkbox=function(options,multi,checked){
319								/* multi: 	0/false = it's a single item
320											"all" = it's a "multi-select" of the all selection
321											"current" = it's a "multi-select" of the current list selection
322								*/
323								var obj=$("<a></a>").attr("href","#")		// we construct a checkbox (icon) with some attribute coming from options
324									.data("multi",multi)
325									.data("id",options.id)
326									.addClass("icon icon-checkbox"+(checked?" checked":""))
327								// if it's a checkbox for a single line, add a data-item-id to be able to find() them later
328								if (!multi) obj.attr("data-item-id",options.id).css({marginRight:"5px"}); else obj.css({marginLeft:"5px"})
329								obj.click(function(e){
330									var ul=$(this).nearest("ul.ds-selection");		// look for selection container (above the table in general), it contains "data" for later
331									var self=$(this);
332									var last=ul.data("parent").find(".last[data-item-id]");
333									self.toggleClass("checked");					// toggle the checkbox
334									var checked=self.hasClass("checked");			// get the checked value from it
335									var id=[];										// will be the list of id to send to server
336									if (ul.data("url")) {
337										var list=ul.data("parent").find("[data-item-id]");		// contains all the "single" checkbox
338										if ($(this).data("multi")) {				// we clicked on the "multi" checkbox (to select current visible item or all session list)
339											list.each(function(i,o){$(o).removeClass("checked").addClass((checked?"checked":""))})
340											id=list.map(function(i,o){return $(o).data("id")}).toArray().join(",")
341										} else if (e.shiftKey && last.length) {		// we click with Shift key -> if we have a ".last" item -> make the selection between last and clicked items
342											var inside=-1;		// simple trick to filter the list to get all items between last and clicked (ex: [-1,-1,-1,1(=last),1,1,-1(current),1,1,1] -> filter on "==1" or itself
343											list=list.filter(function(i,o){if($(o).is(last)||$(o).is(self)) inside=-inside; return (inside==1||$(o).is(self))})
344											list.each(function(i,o){$(o).removeClass("checked").addClass((checked?"checked":""))})
345											id=list.map(function(i,o){return $(o).data("id")}).toArray().join(",")
346										} else {
347											list.removeClass("last")
348											$(this).addClass("last")
349											id=$(this).data("id")
350										}
351										// ajax to modify selection, like ?safe.id:1000,1001,1002&safe.checked=1&safe.multi=all -> will set id [1000,1001,1002] to "selected"
352										$.ajax( { url:ul.data("url")
353												, data:{"safe.id":id,"safe.checked":(checked)?1:0,"safe.multi":($(this).data("multi"))?$(this).data("multi"):0}
354												, success:function(data){	// adding class "data" and "data-selection" attribute on the ul, so, as ajaxComplete will be fired, the cms_init() will be called
355													ul.data("parent").addData({selection:data.selection})
356												}
357										})
358									}
359									return false;
360								})
361								return obj;
362							}
363							if (options.single) {	// on a single row
364								$(this).children().eq(0).css({whiteSpace:"nowrap"}).prepend(checkbox(options,false,options.checked))
365							} else {				// on the table itself -> show result
366								var ul=$("<ul></ul>").addClass("ds-selection").data("url",options.url).data("parent",$(this))
367									.append($("<li></li>").append(checkbox(options,"current",options.current)))
368								if (options.count) ul.append($("<li></li>").css({whiteSpace:"nowrap"}).addClass("ds-selected").append(["Sélection: ",options.count,checkbox(options,"all",options.count>0)]))
369								if (!options.count) ul.append($("<li></li>").css({whiteSpace:"nowrap"}).text("Sélection: 0"))
370
371								ul.append((options.actions||[]).map(function(o,i){
372									var a=false;
373									if (o.button) {
374										a=o.button;
375									} else {
376										var a=$("<a class='ds-button'></a>").append($("<div></div>").text(o.title))//.addData({target:"form"})
377										if (o.icon) a.prepend($("<i/>").addClass(o.icon))
378										if (o.confirm) a.attr("data-confirm",o.confirm)
379										if (o.ajax!=false) {
380											a.addClass("pointer").attr("data-href",o.href);		// Hide href
381											a.on("click",function(){
382												var confirmed=($(this).attr("data-confirm"))?(confirm($(this).attr("data-confirm"))):true
383												// Note: we setup context:this to allow cms_return to have a this pointing to this "a"
384												if (confirmed) $.ajax( {url:$(this).attr("data-href"), context:this, success:function(data){cms_return($(this),data)}, error:function(data){alert("Error reading response #4315"+data)}})
385												return false;
386											})
387										} else {
388											a.attr("href",o.href);		// Simple href to allow "SaveAs"
389											if (o.confirm) a.attr("onclick","return confirm($(this).attr('data-confirm'));")
390										}
391									}
392									return $("<li></li>").addClass("appear").append(a)
393								}));
394								$(this).parent().next(".ds-selection").remove()
395								$(this).parent().after(ul)
396								if (!$(this).data("appear") || $(this).data("appear")!=$(this).next().find(".appear").length) {
397									$(this).data("appear",$(this).next().find(".appear").length).next().find(".appear").hide().fadeIn()
398								}
399								cms_init(ul);
400							}
401							break;
402						case "data-geturl":
403							$(this).click(function(){
404								if (!$(this).attr("id")) $(this).attr("id","id_"+Math.floor(Math.random()*99999999))
405								var id=$(this).attr("id")
406								var lis=[$("<li><input/></li>")
407									, $("<li>")
408										.append($("<a>get</a>").attr("onclick","$(this).closest('ul').find('input').eq(0).val($(this).closest('ul').find('iframe')[0].contentWindow.location.href)"))
409										.append($("<a>set</a>").attr("onclick","$('#"+id+"').val($(this).closest('ul').find('input').eq(0).val())"))
410									, $("<li/>").append("<iframe src='/cms/' />")
411								]
412								$.cbybox.open($("<ul/>").addClass("iframe").append(lis),{cssouter:{width:"80%",height:"80%"},cssinner:{height:"100%"}})
413							})
414							break;
415						case "data-hidemailto":
416							var e=options.hidemailto.replace('#','@').split('').reverse().join('')
417							$(this).attr("href","mailto:"+e)
418							if ($(this).text()=="mailto:") $(this).text(e)
419							break;
420						case "data-cookieconsent":
421							if (pipe.cookie.get(options.name)!="agree") {
422								options.agree=function(){
423									pipe.cookie.set(options.name,"agree",options.expires,options.domain,options.path)
424									$(this).parent().remove()
425								}
426								$("body").append(
427									$("<div/>",{class:"c-cookieconsent"}).append(
428										$("<div/>",{class:"c-cookieconsent-left"}).append(options.text[0]),
429										(options.text[2])?$("<div/>",{class:"c-cookieconsent-middle"}).append(options.text[2]):"",
430										$("<div/>",{class:"c-cookieconsent-right"}).addClass("u-pointer").append(options.text[1]).on("click",options.agree)
431									)
432								)
433							}
434							break;
435						case "data-cycl":
436							var me=$(this), item=$(".o-cycl-item",me)
437							if (item.length==1) {$(".o-cycl-next, .o-cycl-prev",me).remove();item.eq(0).after(item.eq(0).clone());return false;}		// only 1 -> disabled
438							if (item.length==2) {item.eq(1).after(item.clone());item=$(".o-cycl-item",me)}		// only 2 -> make it 4 to allow cycling
439							var next=function(){item.eq(item.length-1).after(item.eq(0));item=$(".o-cycl-item",me);
439$('.c-cycl-news-body').slideUp()}
440							var prev=function(){item.eq(0).before(item.eq(item.length-1));item=$(".o-cycl-item",me);$('.c-cycl-news-body').slideUp()}
441							var close=function(){me.hide()}
442							$(".o-cycl-next",me).on("click",next)
443							$(".o-cycl-prev",me).on("click",prev)
444							$(".o-cycl-close",me).on("click",close)
445							break;
446						case "data-rich-light":
447							var dom={}
448								dom.text = $(this),
449								dom.wrap = dom.text.hide().wrap($("<div/>",{class:"o-rich"})).parent().attr("tabindex",1)
450								dom.html = $("<div/>",{class:"o-rich-html",contenteditable:!dom.text[0].disabled}).html(dom.text.val()||"\n").appendTo(dom.wrap)	// content editable
451								dom.tool = $("<div/>",{class:"o-rich-tool"}).css({fontFamily:"courier"}).toggle(!dom.text[0].disabled).appendTo(dom.wrap)			// content control
452							var events={
453								exec      : function(){var e=$.extend({},$(this).data("control"));if (e.arg && e.arg[0]=="prompt") e.arg=prompt(e.arg[1],e.arg[2])||false;if (e.arg!==false) document.execCommand(e.exec,false,e.arg)},
454								focus     : function(){dom.wrap.addClass("is-focus")},
455								blur      : function(){dom.wrap.removeClass("is-focus");events.update()},
456								paste     : function(e){e.preventDefault();var text = (e.originalEvent || e).clipboardData.getData('text/plain');window.document.execCommand('insertText', false, text)},
457								html2text : function(){dom.text.val(dom.html.html().replace(/(<\/li>|<br>|<ul>)/gi,"$1\n").replace(/\n+/g, '\n'))},
458								text2html : function(){dom.html.html(dom.text.val()||"\n")},
459								update    : function(){if(source) events.text2html(); else events.html2text()}
460							}
461							var source=false		// are we in source mode?
462
463							dom.tool.append([
464								{exec:"bold",					html:"<b>b</b>"},
465								{exec:"italic",					html:"<i>i</i>"},
466								{exec:"underline",				html:"<u>u</u>"},
467								{exec:"insertUnorderedList",	html:"<span>•</span>", name:"insert list"},
468								{exec:"createLink",				html:"<span>_</span>", name:"create link",	arg:["prompt","Enter URL","http://"]},
469								{name:"view source",			html:"<span>&lt;&gt;</span>",	mousedown:function(){events.update();source=!source;dom.text.css({height:dom.html.height()}).toggle(source);dom.html.toggle(!source);}}
470							].map(function(o){return $(o.html).addClass("o-rich-exec").attr("title",o.name||o.exec).data("control",o).on("mousedown",o.mousedown||events.exec)}))
471
472							dom.html.on("focus",events.focus).on("blur",events.blur).on("paste",events.paste)
473							dom.text.on("focus",events.focus).on("blur",events.blur)
474							break;
475						case "data-rich":
476							var me=$.extend(true,{
477								self:this,		// the textarea
478								tool:{			// rich-tools
479									box:	{name:"box style",				html:"<span>Box<i class='icon icon-down-dir'></i></span>", drop:1, mousedown:function(){$(this).parent().find(".js-style").toggle();return false;}},
480									p:		{exec:"formatBlock",			html:"<span>P - normal</span>", drop:-1, arg:"p",class:""},
481									//p2:		{exec:"formatBlock",			html:"<span>P - large</span>", drop:-1, arg:"p",class:"u-text--xl"},
482									h1:		{exec:"formatBlock",			html:"<span>H1</span>", drop:-1, arg:"h1"},
483									h2:		{exec:"formatBlock",			html:"<span>H2</span>", drop:-1, arg:"h2"},
484									h3:		{exec:"formatBlock",			html:"<span>H3</span>", drop:-1, arg:"h3"},
485									bold:	{exec:"bold",					html:"<b>b</b>"},
486									italic:	{exec:"italic",					html:"<i>i</i>"},
487									under:	{exec:"underline",				html:"<u>u</u>"},
488									li:		{exec:"insertUnorderedList",	html:"<span>•</span>", name:"insert list", keep:true},
489									code:	{name:"view source",			html:"<span style='float:right'>%</span>",	mousedown:function(){me.fct.update();source=!source;me.dom.text.css({height:me.dom.html.outerHeight()-3 /* box-shadow */}).toggle(source);me.dom.html.toggle(!source);(source)?me.dom.text.focus():me.dom.html.focus()}},
490									insert:	{name:"insert",					html:"<span>+</span>",	mousedown:function(){me.fct.range(0);$.covr.open(me.dom.covr,{clone:true,foot:{ok:""}})}},
491								},
492								hidetool:false,				// auto hide tool when not focus
493								dom:{},						// will contains all dom-related objects
494								css:{ 						// css for dom elements
495									wrap:{maxWidth:700,background:"white"},
496									html:{maxHeight:"50vh"}
497								},
498								pics:[], docs:[],			// images and documents cas be passed for "insert" action
499								range:false,				// save/restor the current range
500								fct:{						// all events functions
501									exec      : function(e){
502										if ($.covr.is("open")) {me.fct.range(1);$.covr.close()}
503										var c=$.extend({},$(this).data("control"));
504										if (c.exec && c.arg!==false) document.execCommand(c.exec,false,c.arg);
505										//console.log(c.class , me.range , me.range.commonAncestorContainer , me.range.commonAncestorContainer.parentNode)
506										if (c.exec=="formatBlock") me.fct.attr(me.fct.closest(document.getSelection().focusNode,c.arg),"class",c.class)
507										e.preventDefault();
508									},
509									normalize : function(){
510										//me.dom.html.contents().each(function(){if(this.nodeType==3) {$(this).next("br").remove();$(this).wrap($("<p/>"))}})
511										//me.dom.html.find("ul").filter(function(){return $(this).find("li").length==0}).remove()
512										//me.dom.html.find("p").filter(function(){return $(this).find("*").length==0 && $(this).text().trim()==""}).html("&nbsp;")
513										me.dom.html.find("img").each(function(){if($(this).parent().prop("tagName").toLowerCase()!="figure") $(this).wrap($("<figure/>"))});me.fct.html2text()
514									},
515									focus     : function(){me.dom.wrap.addClass("is-focus");if (me.hidetool) me.dom.tool.slideDown(100)},
516									blur      : function(e){
517										me.dom.wrap.removeClass("is-focus");me.fct.update();if (me.hidetool && !me.dom.wrap.hasClass("is-focus")) me.dom.tool.slideUp(100)
518									},
519									paste     : function(e){e.preventDefault();var text = (e.originalEvent || e).clipboardData.getData('text/plain');window.document.execCommand('insertText', false, text)},
520									html2text : function(){me.dom.text.val(me.dom.html.html().replace(/(<\/li>|<\/p>|<br>|<ul>)/gi,"$1\n").replace(/\n+/g, '\n'))},
521									text2html : function(){me.dom.html.html(me.dom.text.val()||"\n")},
522									update    : function(){if(source) me.fct.text2html(); else me.fct.html2text()},
523									closest   : function(elm,tag) {while (elm.nodeType==3 || elm.tagName.toLowerCase()!=tag.toLowerCase()) {elm=elm.parentNode;}; return elm||[]},
524									attr      : function(elm,att,val) {if(elm) (val)?elm.setAttribute(att,val):elm.removeAttribute(att)},
525									range     : function(flag){	// flag: 0|false = save current range in me.range, 1|true: restore me.range to current editor
526										if (!window.getSelection || !window.getSelection().rangeCount) return false;
527										var sel = window.getSelection();
528										switch(flag) {
529											case 0: /* save current range */ me.range=sel.getRangeAt(0);break;
530											case 1: /* re-set saved range */ sel.removeAllRanges(); if (me.range) {sel.addRange(me.range);}break;
531											case 2: return sel.getRangeAt(0);break;
532										}
533									}
534								},
535								insert:{
536									link: [
537										$("<div/>",{class:"u-grid-2"}).text("Link"),
538										$("<input/>",{class:"u-grid-8",placeholder:"http://www.google.ch"}).on("change",function(e){$(this).next().data("control").arg=this.value;}),
539										$("<a/>",{class:"u-grid-1 u-text--center u-pointer icon icon-ok"}).data("control",{exec:"createLink",arg:false}).click(function(){me.fct.exec.apply(this)}),
540										$("<a/>",{class:"u-grid-1 u-text--right u-pointer icon icon-cancel"}).click(function(){$.covr.close()})
541									],
542									mods: [
543										$("<div/>",{class:"u-grid-2"}).text("Modules")
544									],
545									docs: [
546										$("<div/>",{class:"u-grid-2"}).text("Documents")
547									],
548									pics: []
549								},
550								code:{}								/* mode : view source code ? */
551							},options);
552
553							document.execCommand('defaultParagraphSeparator', false, 'p');
554
555							me.dom.text  = $(this).addClass("o-rich-text").css({display:"block"}),
556							me.dom.wrap  = $("<div/>",{class:"o-rich o-rich-wrap",tabindex:1}).css(me.css.wrap)
557							me.dom.html  = $("<div/>",{class:"o-rich-html",contenteditable:!me.dom.text[0].disabled}).css(me.css.html).html(me.dom.text.val()||"\n")	// content editable
558							me.dom.drop  = ""		// used to create dropdown of tool
559							me.dom.tool  = $("<div/>",{class:"o-rich-tool"}).toggle(!me.dom.text[0].disabled).append(Object.keys(me.tool).map(function(o){		// content control
560								o=me.tool[o]
561								if (o.drop && o.drop>0) me.dom.drop=$("<div/>",{class:"o-rich-drop"}).data("drop",o.drop); else me.dom.drop=""
562								return $(o.html).addClass("o-rich-exec").attr("title",o.name||o.exec).data("c
562ontrol",o).on("mousedown",o.mousedown||me.fct.exec).addClass(o.drop?"o-rich-drop-"+o.drop:"").append(me.dom.drop)
563							}))
564							me.dom.tool.find(".o-rich-drop").each(function(){$(this).append(me.dom.tool.find(".o-rich-drop--"+$(this).data("drop")))})
565							me.dom.covr  = $("<div/>",{class:"o-rich-insert"}).append(Object.keys(me.insert).map(function(o){
566								if (o=="mods" && me.mods.length) me.insert[o].push($("<select/>",{class:"u-grid-10 t-icon"}).append($("<option/>").text("")).append(me.mods.map(function(p){
567									return $("<option/>").text(p).data("control",{exec:"insertHTML",arg:$("<div/>",{class:"o-rich-mods",title:p}).text(p).wrap($("<div/>")).parent().html()}).click(me.fct.exec)
568								})));
569								if (o=="docs" && me.docs.length) me.insert[o].push($("<select/>",{class:"u-grid-10 t-icon"}).append($("<option/>").text("")).append(me.docs.map(function(p){
570									return $("<option/>").text(p.name).data("control",{exec:"insertHTML",arg:$("<a/>",{href:p.path+p.file}).text(p.name).wrap($("<div/>")).parent().html()}).click(me.fct.exec)
571								})));
572								if (o=="pics" && me.pics.length) me.insert[o].push($("<div/>").append(me.pics.map(function(p){
573									return $("<div/>",{class:"o-rich-insert-pics-item"}).css({backgroundImage:"url("+p.path+p.file+")"}).data("control",{exec:"insertImage",arg:p.path+p.file}).click(me.fct.exec)
574								})));
575								return $("<div/>",{class:"o-rich-insert-"+o}).append(me.insert[o])
576							}))
577
578							me.dom.text.hide().wrap(me.dom.wrap).before(me.dom.tool).after(me.dom.html);
579							me.dom.tool.find(".o-rich-drop").each(function(){$(this).css({top:$(this).parent().outerHeight(),minWidth:$(this).parent().outerWidth()})})
580							if (me.hidetool) me.dom.tool.hide()
581							me.fct.normalize()
582							//events.range(0);dom.covr.prepend($("<div/>",{class:"u-grid-2"}).text("Range"),$("<div/>",{class:"u-grid-10"}).text(JSON.stringify(dom.range)))
583
584							var source=false		// are we in source mode?
585
586							me.dom.html.on("focus",me.fct.focus).on("blur",me.fct.blur).on("paste",me.fct.paste).on("mouseup",function(){
587								me.fct.range(0);var p=me.range.commonAncestorContainer
588								while (p.nodeType==3) {p=p.parentNode}
589								p=$(p)
590								console.log(p)
591							})
592							me.dom.text.on("focus",me.fct.focus).on("blur",me.fct.blur)
593							break;
594						case "data-canvasjs":
595							var self=this
596
597							self.options=options
598							setTimeout(function(){
599								$(self)
600									//.wrap($("<div/>").css({width:"100%"/*,maxWidth:800*/,padding:10,margin:"1em 0 3em 0",boxShadow:"1px 1px 3px #000"}))
601									.css({width:"100%",height:300})
602									.CanvasJSChart(self.options)
603								}
604								,(((self.options.animationDuration||1200)+100))*index
605							)
606							break;
607						case "data-scrollto":
608							var scrollto=false
609							switch (options.scrollto) {
610								case "self": 	scrollto=$(this).offset().top-Math.min($(this).height(),$(window).height());break;
611								case "bottom": 	scrollto=$("body").height();break;
612							}
613							if (scrollto!==false) window.scrollTo(0,scrollto);
614							break;
615						case "data-scrollmem":
616							// stores the obj's scrollTop value to re-set it after reload. Used on the obj which may scroll (a ul) by adding onclick on <a> children, ex:
617							// <ul class='data' data-scroll='whatever or options'><li><a>1</a></li><a>2</a></li></ul> -> clicking on a <a> will store scrollTop if not zero
618							// if options.id, take this id as localStorage id, otherwaise take obj'id or obj'className
619							if (!localStorage) return
620							var obj=$(this),	id=options.id || obj.attr("id") || (this.tagName.toLowerCase()+" "+this.className).replace(/ /g,".")
621							if (localStorage.getItem(id)) {obj.scrollTop(parseInt(localStorage.getItem(id)))}
622							obj.find(options.selector || "a").on("click",{id:id,obj:obj},function(e){
623								if (e.data.obj.scrollTop()) localStorage.setItem(e.data.id,e.data.obj.scrollTop()); else if (localStorage.getItem(e.data.id)) localStorage.removeItem(e.data.id)
624							})
625							break;
626						case "data-textcomplete":
627							var strategy={
628								href:options.href,
629								match: (options.regexp)?eval(options.regexp):/^(.{2,}
629)$/,		// eval because regexp will be .toString(), by default: at least 2 chars
630								search: function (term, callback, match) {
631									$.getJSON(this.href||document.location.href, { "safe.q": term })
632										.done(function (resp) {callback(resp);})
633										.fail(function () {callback([]);});
634								},
635								replace: function (value) {return value.text},
636								template: function (value) {return value.text},
637								index: 1
638							}
639							if (options.action==1) {strategy.replace=function(value){document.location.href=value.value;return "loading..."}}
640							strategies=[strategy]
641							if (options.sift) strategies.unshift({
642								match: /^\{(.*)/,		// eval because regexp will be .toString(), by default: at least 2 chars
643								search: function (term, callback, match) {/*console.log(this.el.value);*/term=[{isjson:this.isjson(this.el.value),text:this.el.value}];callback(term)},
644								replace: function (value) {if (!value.isjson) return undefined; else {document.location.search+="&safe.sift="+encodeURIComponent(value.text);return "loading...."}},
645								template: function (value) {return $("<div>").append($("<div>").text(value.text).css({fontSize:"1.5em",margin:"-2px -5px",padding:"2px 10px",background:"white",color:value.isjson?"green":"red"})).html()},
646								isjson: function(str){try {eval("["+str+"]");} catch (e) {return false;}return true;},
647								index: 1
648							})
649							//console.log(strategies)
650							$(this).attr("type","text").textcomplete(strategies,{placement:"left",noResultsMessage:"not found"})
651							break;
652						case "data-signature":
653							var signature=$(this).wrap($("<div/>")).jSignature(),
654								parent=signature.parent()
655							parent.css({position:"relative",border:"1px solid #999",background:"#fff",paddingBottom:"25%"	/* jSignature has a ration 1/4 */})
656							if (options.val) signature.jSignature("setData","data:image/jsignature;base30," + options.val)
657							if (options.mode=="edit") {
658								signature.css({position:"absolute",left:1,right:1,top:1,bottom:1,zIndex:1}).after([
659									$("<input/>").attr("type","text").css({position:"absolute",left:0,right:0,bottom:0,zIndex:-1}).attr("name",options.name).prop("required",options.mandatory),
660									$("<div/>").addClass("validation").css({position:"absolute",left:0,right:0,top:0,bottom:0,zIndex:0}),
661									$("<a href='#'/>").addClass("icon icon-arrows-cw").css({position:"absolute",right:0,top:0,zIndex:1}).click(function(){signature.jSignature("clear");return false})
662								]).bind("change",function(e){signature.next().val(signature.jSignature("getData","base30"))})
663							} else {
664								parent.html($("<img />").css({position:"absolute",display:"block",left:0,top:0,width:"100%",margin:0}).attr("src",signature.jSignature('getData', 'default')));
665							}
666							break;
667						}
668				}
669				if (!options.keepafter) $(this).removeAttr(plugin)
670			}
671		})
672	}
673
674	$(".o-tipr-trigger[title]",context).tipr();
675
676	if (!$.fn.dragsort) $.fn.dragsort=function(){return false}
677	$('.dragsort-items:not(.dragsort-done)',context).addClass("dragsort-done clearfix").dragsort({ 		// generic dragsort (parent: .dragsort-items, dragSelector: .dragsort-selector)
678		dragSelector: ".dragsort-selector", dragBetween: false, placeHolderTemplate: "<li></li>",dragEnd: function() {}
679	});
680	$('.dragsort-selector',context).cbyTouchmouse()
681	$('.dragsort-delete',context)
682		.css({cursor:"pointer"})
683		.unbind("click")
684		.click(function(){$(this).closest(".dragsort-item").remove()})
685/*	$('.dragsort-items .dragsort-addOLD',context)
686		.css({cursor:"pointer"})
687		.each(function(){$(this).attr("data-clone",$(this).closest(".dragsort-item").clone().wrap("<div/>").parent().html())})
688		.unbind("click")
689		.click(function(){$(this).removeClass("dragsort-add").addClass("dragsort-delete").html("<i class='icon icon-cancel'/>");var parent=$(this).closest(".dragsort-items");if ($(this).attr("data-max")=="none" || parseInt($(this).attr("data-max"))>parent.children().length) {parent.append($(this).attr("data-clone"));cms_init(context)}})
690	$('.dragsort-items .dragsort-add2',context)
691		.css({cursor:"pointer"})
692		.each(function(){$(this).attr("data-clone",$(this).closest(".dragsort-item").clone().wrap("<div/>").parent().html())})
693		.unbind("click")
694		.click(function(){$(this).removeClass("dragsort-add").addClass("dragsort-delete").html("<i class='icon icon-cancel'/>");var parent=$(this).closest(".dragsort-items");if ($(this).attr("data-max")=="none" || parseInt($(this).attr("data-max"))>parent.children().length) {parent.append($(this).attr("data-clone"));cms_init(context)}})
695	$('input.js-date',context).cbySelect({"type":"date"});
696*/
697	$('.js-group-children:not(.group-children-done)',context).addClass('group-children-done').each(function(){		// group a list of children in 1 child + a arrow to open the other children
698		if ($(this).children().length<2) return true;		// true so each will continue
699		$(this).css({position:"relative"})
700		var href=$(this).children().eq(0).attr("href")
701		$(this).children(':not(:eq(0))').filter(function(){return $(this).attr("href")==href}).addClass("selected");		// add a selected class on the hidden children which have the same href as the first
702		var div=$('<div/>').addClass('ds-popup ds-radius').css({display:"none"});		// makes a div to contain the hidden children
703		div.append($(this).children(':not(:eq(0))').css({display:"block",whiteSpace:"nowrap"}));			// append all the children (except the first one)
704		$(this).append(div)
705		var open=$('<a href="#"><i class="icon icon-down-dir"></i></a>')				// makes the a containing the open/close arrow
706		$(this).children().eq(0).after(open)											// append it after the first child
707		open.click(function(){
708			var padding=parseInt($(this).next().css("paddingLeft"))
709			$(this).next().toggle().css({top:-padding,left:-padding,paddingTop:$(this).prev().height()+2*padding,minWidth:$(this).prev().width()+$(this).width()+2*padding});
710			$(this).prev().css({position:"relative",zIndex:1500})
711			$(this).css({position:"relative",zIndex:1500})
712			return false
713		})					// click event to open the hidden children
714	})
715	// Shortcut add a label after <input class='collapse'>, used in next+next line
716	$('input.collapse').each(function(){if ($(this).next("label.collapse").length==0) $(this).after("<label class='toggle collapse'></label>")})
717	$('input[type=checkbox] + label.toggle').each(function(){		// on a "<input type='checkbox' name='...'><label class='toggle ...'></label>"
718		if ( (ie=/MSIE (\d+)/.exec(navigator.userAgent)) && !(ie[1]<6 || ie[1]>8) ) return false;
719		var id=$(this).prev().attr("id")||"id_"+Math.floor(Math.random()*9999999)
720		$(this).prev().attr("id",id).hide()	// add id (same if existing, see line before and hide it */
721		$(this).attr("for",id)				// add for to toggle the checkbox
722			.addClass("icon pointer")
723	})
724
725	$('.wysiwyg').cbyWysiwyg();
726
727	var hint=function(e){$(this).closest(".o-hint-item").toggleClass("is-hint",$(this).val()=="" && e.type!="focus")}
728	$(".o-hint-input, .o-hint-input input").filter(":input").on("focus blur change",hint).each(hint)
729
730	/* test open/close details */
731	$(".remoded .dsd-folderlist .ds-dropdown").each(function(){
732		a=$(this).prev()		// <a>
733		var tr=$(this).closest("tr")
734		var div=$(this).removeClass("ds-dropdown").hide()
735		div.find("td").css("border",0)
736		a.unbind("click focus").bind("click",function(e){
737			e.preventDefault()
738			var td=$("<td colspan='4' class='ds'></td>").append(div)
739			tr.after($("<tr><td class='ds'></td></tr>").append(td))
740			tr.css("background","#f3f3f3").next().css("background","#f3f3f3")
741			div.slideToggle()
742		})
743	})
744
745	setTimeout(function(){$(".no-transition-onload").removeClass("no-transition-onload")},100)
746}
747/* jquery stick: if not done, set the oject position to "fixed" and append a fake div to simulate the height */
748(function($) {
749    $.fn.stick = function(opts) {
750		opts=opts||{css:{}}
751		if (!$("body").data("stick-timeout")) $("body").data("stick-timeout",setInterval(function(){$(".fake-stick").each(function(){$(this).css({height:$(this).prev().outerHeight()})})},500))
752        return this.filter(function(){return !$(this).hasClass("stick")}).addClass("stick").each(function() {
753			$(this)
754				.css({position:"fixed",zIndex:1,width:"100%"}).css(opts.css)
755				.after($("<div/>").addClass("fake-stick").css({height:$(this).outerHeight()}))
756        });
757    };
758})(jQuery);
759
760function cms_submit(obj,data,step) {
761	// data: An object containing extra data that should be submitted along with the form, ex: data: { key1: 'value1', key2: 'value2' }
762	// step: A form can have .ds-form-step objects, in this case, send form multiple time, every step with on .ds-form-step "un-disabled"
763	// 1. search for the form
764	if (obj.tagName.toUpperCase=="FORM") form=$(obj); else form=$(obj).closest('form');
765	//if (form.attr("data-confirm") && confirm(form.attr("data-confirm"))!==true) return false
766	var progress={button:false,text:form.find(".ds-progress-text"),width:form.find(".ds-progress-width"),start:new Date()};		// all progress DOM object
767	// 2. if HTML5 "required" and "checkValidity" exists, use it (to simulate the HTML5 'errors tooltip', just create a submit button and click on it)
768	form.removeClass('invalid')
769	if (form.length && typeof(form[0].checkValidity)=="function" && !form[0].checkValidity()) {
770		form
771			.addClass('invalid')		// As iOS does not show a warning, we set up css rules to display a "red" box-shadow around the "invalid" fields (see dataset.css)
772			.on('submit',function(e){e.preventDefault();return false;})		// as iOS does not show warning, avoid to submit the form with the following line (click)
773		$('<input type="submit">').hide().appendTo(form).click().remove();	// simulate a "submit"
774		form.off('submit');		// remove on 'submit' for data-target=document (normal submit())
775		return false;
776	}
777
778	var uploadProgress=function(){return false;}
779	if (form.find("input[type=file]").length) uploadProgress = function(event,position,total,percentComplete) {
780		progress.second=(new Date()-progress.start)/1000
781		if (progress.second>0) progress.bitrate=position/progress.second
782		if (progress.button.length) progress.button.show().html(percentComplete+"%")
783		if (progress.text.length) progress.text.show().html(percentComplete+"%")
784		if (progress.width.length) progress.width.show().css({width:percentComplete+"%"})
785	}
786	var steps=false,annex=form.find("input.ds-form-step-annex")
787	if ( (steps=form.find(".ds-form-step")) && steps.length>0) {
788		steps.each(function(){this.disabled=true})
789		annex.each(function(){this.disabled=true})		// Annex to the file. Note: only one annex to each file
790		step=step||0
791		if (form.find("input[name='_mode']").val()!="add") {
792			// we are in an update ->
793			// first "step" (0) send all previous values in Json, following step send POST["_field"]=name that tell the server to only take care of this one
794			if (step>0) {
795				data=$.extend(data,{_step:step,_field:steps.get(step-1).name})		// note: _step is not used in server
796				form.find(":input").each(function(){this.disabled=true})			// disable all form elements
797				steps.get(step-1).disabled=false;									// un-disable current's step element
798				if (annex.length) annex.get(step-1).disabled=false;
799				progress.start=new Date()
800				progress.text=steps.eq(step-1).nearest(".ds-progress-text");
801				progress.width=steps.eq(step-1).nearest(".ds-progress-width");
802			}
803			step++
804			if (step==steps.length+1) step=0;		// to end loop*/
805		} else {
806			// we are in an insert (probably with a pivot!), just post every fields but steps files one by one
807			data=$.extend(data,{_step:step})
808			steps.get(step).disabled=false;
809			if (annex.length) annex.get(step).disabled=false;
810			progress.start=new Date()
811			progress.text=steps.eq(step).nearest(".ds-progress-text");
812			progress.width=steps.eq(step).nearest(".ds-progress-width");
813			step++
814			if (step==steps.length) step=0;		// to end loop
815		}
816	}
817
818	if (form.attr('data-xreferer')) {
819		xreferer=form.attr('data-xreferer');document.cookie="xreferer="+encodeURIComponent(xreferer)+"; path=/"
820	}
821
822	switch ($(obj).attr("data-target")) {
823		case "document":
824			form.submit();
825			break;
826		default:		// submit, ...
827			if ($(obj).hasClass("ds-once") && $(obj).find("i.icon").length==0) $(obj).prepend($("<i class='icon'/>"))	// if button is ds-once but don't have a icon, just a one
828			progress.button=$("<a href='#' />").addClass($(obj).attr("class")).html($(obj).html());		// clone the current button to avoid double click and to put a spinner
829			if ($(obj).find("i.icon").length) $(obj).hide().after(progress.button);
830			if (progress.button.find("i").hasClass("icon")) spin="icon icon-spinner icon-spin"; else if (progress.button.find("i").hasClass("mdi")) spin="mdi mdi-sync mdi-spin"; else spin=""
831			setTimeout(function(){progress.button.find("i").addClass(spin)},200)
832
833			form.ajaxSubmit({
834				data:data,
835				dataType:"json",
836				uploadProgress: uploadProgress,
837				/*beforeSerialize: function(form,options) {
838					$(":input",form).each(function(){alert($(this).val()+" "+$(this).attr("data-value"));if ($(this).attr("data-value")!==undefined) {$(this).val($(this).attr("data-value"));alert($(this).val())}})
839				},*/
840				success: function(responseText, statusText, xhr, $form){
841					progress.button.hide();$(obj).show();
842					if (progress.text.length) progress.text.html("100%")
843					if (progress.width.length) progress.width.animate({width:"100%"})
844					//alert('status: ' + statusText + '\n\nresponseText: \n' + responseText);
845					//alert("Error reading response:\n"+JSON.stringify(responseText,null,2));
846					//document.location.href=document.location.href
847					if (step) {
848						cms_submit(obj,data,step);
849					} else {
850						cms_return($form,responseText)
851					}
852				},
853				error: function(xhr){
854					//alert(xhr.responseText||"Error, permission denied or session expired [5426]")
855					console.log(xhr.responseText||"Error, permission denied or session expired [5426]")
856					document.location.href=document.location.href
857				}
858			});
859		break;
860	}
861}
862
863function js_keyup(event,obj) {
864	if (event && event.keyCode==13) $(obj).nearest("a.ds-submit").click()
865}
866
867var loadsync=function(src){		// will load a file (.js or .css) synchronously based on his extension and if not already loaded
868	if (document.querySelector && document.querySelector("[loadsync='"+src+"'], script[src='"+src+"'], link[href='"+src+"']")) return
869	var xhrObj = new XMLHttpRequest(),extension=(""+src).split("?")[0].split(".").pop(),dom=false;
870	xhrObj.open('GET', src, false);
871	xhrObj.send('');
872
873	if(xhrObj.status!=200) {
874		alert("Error "+xhrObj.status+" reading: "+src)
875	} else {
876		switch (extension) {
877			case "css":
878				dom = document.createElement("style");		// warning: relative url will not work here, use loadhead
879				dom.type = "text/css"
880				if (dom.styleSheet) dom.styleSheet.cssText = xhrObj.responseText;
881				else dom.appendChild(document.createTextNode(xhrObj.responseText));
882				break;
883			default:		// .js
884				dom = document.createElement("script");
885				dom.type = "text/javascript"
886				dom.text = xhrObj.responseText;
887				break;
888		}
889		if (dom) {
890			dom.setAttribute("loadsync",src)
891			document.getElementsByTagName('head')[0].appendChild(dom);
892		}
893	}
894}
895
896var loadhead=function(src){		// will load a file (.js or .css) by adding appropriate tag in <head>, generally use for .css file, not .js
897	// same job as loadsync, but as it's not sync:
898	// pros: do not show the warning "MLHttpRequest on the main thread is deprecated...", do all other loading in css file with relative url "src:url(relative/to/src)"
899	// cons: js function inside the src can't be call directly after loadhead() as they will not be ready (not sync)!
900	if (document.querySelector && document.querySelector("[loadsync='"+src+"'], script[src='"+src+"'], link[href='"+src+"']")) return
901	var extension=(""+src).split("?")[0].split(".").pop(),dom=false;
902	
903	switch (extension) {
904		case "css":
905			dom = document.createElement("link");
906			dom.rel = "stylesheet"
907			dom.href = src
908			break;
909		default:		// .js
910			dom = document.createElement("script");
911			dom.type = "text/javascript"
912			dom.src = src
913			break;
914	}
915	if (dom) {
916		document.getElementsByTagName('head')[0].appendChild(dom);
917	}
918}
919
920/*	jQuery plugins
921	Copyright (c) 2013 Stéphane Carion, ComputedBy (www.computedby.c
921om)
922
923	Dual licensed under the MIT or GPL Version 2 licenses.
924	http://www.opensource.org/licenses/mit-license.php
925	http://www.opensource.org/licenses/gpl-2.0.php
926
927*/
928
929/* cbySelect plugin */
930;(function($){
931	/* OO jquery based on http://www.virgentech.com/blog/2009/10/building-object-oriented-jquery-plugin.html */
932	$.fn.cbySelect = function(options) {
933		return this.each(function() {
934			var element = $(this);
935			// Return early if this element already has a plugin instance
936			if (element.data('cbySelect')) return;
937			// pass options to plugin constructor
938			var cbySelect = new cbySelectClass(this, options);
939			// Store plugin object in this element's data
940			element.data('cbySelect', cbySelect);
941		});
942	};
943
944	var cbySelectClass = function(element,options) {
945		// Start declaration (don't forget to put 'var' before every variables/functions!)
946		// Default settings
947		var settings = jQuery.extend({
948			type:"xselect",		// xselect||date
949			mode:"popup",		// popup||inline
950			value:"",
951			days:["di","lu","ma","me","je","ve","sa"],
952			range:[false,false]			// use for date, range=[date1,date2] -> must be between date1 and date2
953		}, options);
954		var self=this;
955		var input=$(element);				// The object
956		var container=input.parent();
957		settings.value=input.val();
958
959		self.pad = function(input,length) {
960			return (1e15+input+"").slice(-length||-2)
961		}
962		self.format = function(input,format) {
963			switch (format) {
964				case "dd.mm.yyyy":return self.pad(input.getDate())+"."+self.pad(input.getMonth()+1)+"."+input.getFullYear();break;
965			}
966		}
967		self.parse = function(text,format) {
968			switch (format) {
969				case "dd.mm.yyyy":return (text.split(".").length!=3)?new Date():new Date(text.split(".")[2], text.split(".")[1]-1, text.split(".")[0]);;break;
970			}
971			return new Date();
972		}
973
974		self.daycell = function(mode,d) {	// 0: outside the current month -> no number inside
975			if (mode==0) return [$("<span/>").attr("class","ds-name").text(settings.days[d]),$("<span/>").attr("class","ds-day").text()]
976			if (mode==1) return [$("<span/>").attr("class","ds-name").text(settings.days[d.getDay()]),$("<span/>").attr("class","ds-day").attr("data-date",self.format(d,"dd.mm.yyyy")).text(self.pad(d.getDate()))]
977		}
978
979		self.month = function(d) {			// return the html of a month, in ul/li
980			if (!d) firstday=new Date(); else firstday=new Date(d);	// clone d
981			firstday.setDate(1)				// first day of month
982			var offset=(firstday.getDay()+6)%7;	// offset of first day of month, monday as 0
983			var ulhead=$("<ul/>").attr("class","ds-head");
984			var ulbody=$("<ul/>").attr("class","ds-body");
985			var li=$("<li/>").attr("class","ds-cell");
986			// data-nav: Year,Month,Day,...
987			ulhead
988				.append(li.clone().addClass("ds-nav ds-prevmonth").attr('data-nav','0,-1').text("<"))
989				.append(li.clone().addClass("ds-nav ds-prevyear").attr('data-nav','-1,0').text("<<"))
990				.append(li.clone().addClass("ds-nav ds-name").text(self.pad(d.getMonth()+1)+"."+d.getFullYear()))
991				.append(li.clone().addClass("ds-nav ds-nextyear").attr('data-nav','1,0').text(">>"))
992				.append(li.clone().addClass("ds-nav ds-nextmonth").attr('data-nav','0,1').text(">"));
993			for (var i=0;i<offset;i++) ulbody.append(li.clone().addClass("ds-week0").html(self.daycell(0,(i+1)%7)));
994			while (d.getMonth()==firstday.getMonth()) {
995				ulbody.append(li.clone().addClass("ds-week"+parseInt(i/7)+((self.format(dates.current,"dd.mm.yyyy")==self.format(firstday,"dd.mm.yyyy"))?" selected":"")).html(self.daycell(1,firstday)));
996				firstday=new Date(firstday.getTime()+86400000);
997				i++;
998			}
999			$('.ds-nav[data-nav]',ulhead).click(function(){
1000				arr=$(this).attr('data-nav').split(',');
1001				dates.nav=new Date(dates.nav.getFullYear()+parseInt(arr[0],10),dates.nav.getMonth()+parseInt(arr[1],10),1);
1002				calendar.html(self.month(dates.nav));
1003			})
1004			$('.ds-day[data-date]',ulbody)
1005				.each(function(){				// add class if selected or today
1006					if ($(this).attr("data-date")==self.format((new Date()),"dd.mm.yyyy")) $(this).parent().addClass("ds-today")
1007					if ($(this).attr("data-date")==self.format(dates.current,"dd.mm.yyyy")) $(this).parent().addClass("selected")
1008					if (settings.range && settings.range[0] && settings.range[0].split(".").reverse().join("")>=$(this).attr("data-date").split(".").reverse().join("")) $(this).parent().addClass("disabled")
1009					if (settings.range && settings.range[1] && settings.range[1].split(".").reverse().join("")<$(this).attr("data-date").split(".").reverse().join("")) $(this).parent().addClass("disabled")
1010				})
1011				.filter(function(){return !$(this).parent().hasClass("disabled")})
1012				.click(function(){				// add the onclick event
1013					input.val($(this).attr("data-date"));
1014					dates.current=self.parse($(this).attr("data-date"),"dd.mm.yyyy")
1015					calendar.hide()
1016				})
1017			return [ulhead,ulbody];
1018		}
1019
1020		var dates={current:self.parse(settings.value,"dd.mm.yyyy"),nav:self.parse(settings.value,"dd.mm.yyyy")}
1021
1022		switch (settings.type) {
1023			case "date":
1024				var icon=$("<i class='icon icon-calendar'>")
1025				input.after(icon)
1026				//icon.css({marginLeft:-icon.width()})
1027				input.attr("autocomplete","off").css({width:"7.5em"})
1028				var calendar=$("<div/>").attr("class","ds-calendar ds-"+settings.mode);
1029				container.append(calendar);
1030				calendar.hide();
1031				if (input.attr("data-range")) {
1032					settings.range=JSON.parse(input.attr("data-range"))
1033					for (i=0;i<settings.range.length;i++) if (settings.range[i]==1) settings.range[i]=self.format((new Date()),"dd.mm.yyyy")
1034				}
1035				input.click(function(){calendar.html(self.month(dates.current)).toggle()})
1036				break;
1037		}
1038	}
1039})(jQuery);
1040
1041/* cbyDropdown plugin */
1042(function($){
1043	/* OO jquery based on http://www.virgentech.com/blog/2009/10/building-object-oriented-jquery-plugin.html */
1044	$.fn.cbyDropdown = function(options) {
1045		return this.each(function() {
1046			var element = $(this);
1047			// Return early if this element already has a plugin instance
1048			if (element.data('cbyDropdown')) return;
1049			// pass options to plugin constructor
1050			var cbyDropdown = new cbyDropdownClass(this, options);
1051			// Store plugin object in this element's data
1052			element.data('cbyDropdown', cbyDropdown);
1053		});
1054	};
1055
1056	var cbyDropdownClass = function(element,params) {
1057		// Start declaration (don't forget to put 'var' before every variables/functions!)
1058		var $el=$(element)
1059		var self = jQuery.extend(
1060			{
1061			// first part, normally used to be changed in params
1062			  mode: "edit"									// "view"|"edit"
1063			, layout:"options"								// options|date|datetime|html|file|ajax
1064			, source:"array"
1065			, array:["One","Two","Three","Four","Five"]		// example of array
1066			, multiple:false								// true, false, "post", {}, obj: {"drag":true,"delete":true,...}
1067			, free:false									// "edit": allow text input (if not false, this must be the name of the input)
1068			, minchar:0										// min char before open dropdown
1069			, maxitem:0										// max item displayed
1070			, values:{real:false,text:"",selected:[]}		// the values real value, text value, selected: $(obj) containing selected obj(s)
1071			, days:["di","lu","ma","me","je","ve","sa"]		// label for day name
1072			, group: {type:"count",after:" item(s)"}
1072		// manage what appear in the anchor or input
1073															/* 	{type:"count",after:" item(s)", before:""}: will count the selected items and add "item(s)" to this number
1074																{type:"data-resume"}: will count the different "data-resume", ex: "2 offices, 1 user"
1075																{type:"join", separator:","}: will array.join() the real values with separator
1076															*/
1077			, range:[false,false]							// use for date, range=[date1,date2] -> must be between date1 and date2, ex: ["05.05.2015","25.06.2015"]
1078			, icon:"auto"									// false to remove, $("<i class='...'>) to force
1079			, position:false								// add some class to be on the right
1080			, highlight:false								// when on options, add a input to filter (highlight) the list
1081			// second part, use internally but can also be override
1082			, wrap:false				// the wrap div
1083			, input:false				// the <input/> if existing
1084			, anchor:false				// when no input (view mode), anchor will replace the input and be clickable
1085			, dropdown:false			// the dropdown div
1086			, tmp:false
1087			, scroll:false				// scroll dropdown options
1088		}, params);
1089
1090		// Third part, function used internally but can be overridden by settings
1091		self.pad = self.pad || function(text,length) {
1092			return (1e15+text+"").slice(-length||-2)
1093		}
1094		self.format = self.format || function(date,format) {
1095			switch (format) {
1096				case "dd.mm.yyyy hh:mm":return self.pad(date.getDate())+"."+self.pad(date.getMonth()+1)+"."+date.getFullYear()+" "+self.pad(date.getHours())+":"+self.pad(date.getMinutes());break;
1097				case "dd.mm.yyyy hh:00":return self.pad(date.getDate())+"."+self.pad(date.getMonth()+1)+"."+date.getFullYear()+" "+self.pad(date.getHours())+":00";break;
1098				case "dd.mm.yyyy":return self.pad(date.getDate())+"."+self.pad(date.getMonth()+1)+"."+date.getFullYear();break;
1099				case "yyyymmdd":return date.getFullYear()+self.pad(date.getMonth()+1)+self.pad(date.getDate());break;
1100				case "hh:mm":return self.pad(date.getHours())+":"+self.pad(date.getMinutes());break;
1101			}
1102			alert("self.format error on "+date+" - "+format)
1103		}
1104		self.parse = self.parse || function(text,format) {
1105			text=""+text
1106			switch (format) {
1107				case "dd.mm.yyyy":
1108				case "dd.mm.yyyy hh:mm":
1109					if (text.split(" ").length<2) text+=" 00:00"
1110					var parts=text.split(" ");		// split datetime format
1111					if (!parts[0] || parts[0].split(".").length!=3) return new Date()
1112					var date=new Date(parts[0].split(".")[2], parts[0].split(".")[1]-1, parts[0].split(".")[0], parts[1].split(":")[0], parts[1].split(":")[1]);
1113					return date
1114					break;
1115			}
1116			alert("parse error on: "+text+" - "+format)
1117		}
1118
1119		self.reduce=function(arr) {
1120			// transform the array as : [["first","items[]","items[]"] -> ["first", "2 items"]
1121			return arr
1122				// first, reduce array as: [["first","items[]","items[]"] -> {pos:{"items(s)":2},arr:[[0,"first"],[2,"items[]"]]}
1123				.reduce(function(o,v,i){
1124					if (v.substr(-2)=="[]") {
1125						//field.reduced=true	// tell the field it has been reduced!
1126						if (o.pos[v]) o.arr[o.pos[v]-1][0]++; else {o.arr.push([1,v]);o.pos[v]=o.arr.length;}
1127					} else o.arr.push([0,v])
1128					return o
1129				},{pos:{},arr:[]})
1130				// only take the .arr property: [[0,"first"],[2,"items[]"]]
1131				.arr
1132				// sort the array as [0]=0 unchanged, then [0]>0 -> alphanumeric
1133				.sort(function(a,b){return (a[0]==0)?false:a[1]>b[1]})
1134				// Second, map it to ["first", "2 items"]
1135				.map(function(o,i){return !o[0]?o[1]:[o[0],o[1].substr(0,o[1].length-2)].join(" ")})
1136		}
1137
1138		self.resume = self.resume || function(arg){		// Only for layout=options
1139		//return false
1140			var resume="";
1141			if (self.mode=="edit") {
1142				self.values.selected=self.dropdown.find("input").filter(function(){return (this.checked && !this.disabled)})
1143				self.values.real=self.values.selected.toArray().map(function(o){return o.value})
1144			} else {
1145				self.values.selected=self.dropdown.find("li").filter(function(){return ($(this).hasClass("checked") || this.hasAttribute("data-resume")) && this.hasAttribute("data-resume")})
1146			}
1147			self.values.text=self.values.selected.toArray().map(function(o){return $(o).attr("data-resume")}).filter(function(o){return o}
1147)
1148			// try to group some value (those beginning with ##)
1149			/*self.values.groups={}
1150			self.values.text.forEach(function(label){if (label.indexOf("##")==0) self.values.groups[label]=(self.values.groups[label]||0)+1})
1151			self.values.text.forEach(function(label,i){
1152				if (self.values.groups[label]) {
1153					self.values.text[i]=(self.values.groups[label]!=-1)?label.replace("##",self.values.groups[label]):"";
1154					self.values.groups[label]=-1
1155				}
1156			})
1157			self.values.text=self.values.text.filter(function(label){return label})*/
1158			if (self.multiple.join) self.values.join=[].concat(self.multiple.join).concat(self.multiple.join); else self.values.join=[", ",", "]
1159			self.values.text=self.reduce(self.values.text)
1160			if (self.input && self.input.val()!=self.values.text.join(self.values.join[0])) // set the value only if different (to avoid :invalid on input when empty)
1161				self.input.val(self.values.text.join(self.values.join[0]))
1162
1163			self.values.text=self.values.text.join(self.values.join[1])
1164			if (self.anchor) {
1165				self.anchor.html(self.values.text || "")
1166				if (params.anchor && params.placeholder && self.anchor.html()=="") self.anchor.html(params.placeholder)
1167			}
1168		}
1169
1170		self.select = self.select || function(obj,set) {
1171			if (!obj[0]) return false;
1172			self.values.selected=obj
1173			if (!self.multiple) {
1174				obj.closest('ul').children().removeClass("selected")
1175				obj.addClass("selected");
1176			}
1177			if (self.scroll) {
1178				var d={y:parseInt(obj.position().top),h1:parseInt(obj.outerHeight()),h2:parseInt(self.scroll.height()),s:parseInt(self.scroll.scrollTop())}
1179				if (d.y+d.h1>d.h2) self.scroll.scrollTop(d.s+d.y-d.h2+d.h1)
1180				else if (d.y<0) self.scroll.scrollTop(d.s+d.y)
1181			}
1182			if (set) {
1183				switch (self.layout) {
1184					case "options":
1185						obj.find("input[type='radio']").click()
1186						self.resume();
1187						if (self.input) self.input.change();
1188						//self.dropdown.find("li").show()
1189						break;
1190					case "date":
1191						self.values.text=obj.attr("data-value");
1192						self.values.real=self.parse(self.values.text,"dd.mm.yyyy")
1193						if (self.input) self.input.val(self.values.text).change();
1194						self.hide();
1195						break;
1196					case "datetime":
1197						self.values.test=(set===2 || set===3)?self.values.date+" "+obj.val():obj.attr("data-value")+" "+self.values.time
1198						if (isNaN(self.parse(self.values.test,"dd.mm.yyyy hh:mm"))) return false;
1199						self.values.text=self.values.test
1200						self.values.real=self.parse(self.values.text,"dd.mm.yyyy hh:mm")
1201						self.values.date=self.values.text.split(" ")[0]
1202						self.values.time=self.values.text.split(" ")[1]
1203						if (self.input) self.input.val(self.values.text);
1204						if (set===3) self.hide();
1205						break;
1206				}
1207
1208				if (!self.multiple && !obj.hasClass("collapse") && (self.layout!="datetime" || obj.hasClass("datetime"))) {
1209					self.hide();
1210					if (self.input) self.input.blur();
1211				} else {
1212					//if (input) input.focus()		// removed because of focus on initialisation put wrong focus
1213				}
1214				if (self.onchange) self.onchange(self,obj)
1215				//input.change()
1216			}
1217		}
1218
1219		self.fill = self.fill || false					// will be replace by a function if needed for each settings.layout
1220
1221		self.OLDlist = function(value,regexp) {		// return the html to put in the main div (should be a ul)
1222			var ul=$("<ul/>").addClass("ds-body").attr("tabindex","-1");		// tabindex because of bug in firefox when user press "tab"
1223			scroll=ul
1224			var count=0,found=-1
1225			for (i=0,maxi=options.all.length;i<maxi;i++) {
1226				if (!regexp || regexp.test(options.all[i][1])) {
1227					if (regexp) html=options.all[i][1].replace(regexp, "<mark>$&</mark>"); else html=options.all[i][1]
1228					li=$("<li/>").addClass("ds-option").attr("data-value",options.all[i][0]).html(html)
1229					li.addClass("enabled").click(function(){self.select($(this),true)})
1230					ul.append(li);
1231					//if (options.all[i][0]==options.value0) found=count
1232					count++
1233				}
1234			}
1235			if (count==0) wrap.removeClass("ds-open"); else wrap.addClass("ds-open");
1236			//if (found) self.select(false,false)
1237			return ul
1238		}
1239
1240		self.calendar = self.calendar || function(d) {			// return the html of a month, in ul/li
1241			var dates={
1242				current:new Date()
1243				, cursor:new Date()
1244				, text:"dd.mm.yyyy"
1245				, today:self.format((new Date()),"dd.mm.yyyy")
1246			}
1247			if (self.values.real instanceof Date) dates.current=new Date(self.values.real);
1248			if (d instanceof Date) dates.current=new Date(d);
1249			dates.cursor=new Date(dates.current);		// a day to play with (clone with new Date(current))
1250			dates.cursor.setDate(1)						// first day of month
1251			var offset=(dates.cursor.getDay()+6)%7;	// offset of first day of month, monday as 0
1252			var ulhead=$("<ul/>").attr("class","ds-head");
1253			var ulbody=$("<ul/>").attr("class","ds-body");
1254			var ultime=false;			// only if type=datetime
1255			var litpl=$("<li/>").attr("class","ds-cell");		// template for all li's
1256			// add item for navigation (next month,...) and title
1257			ulhead
1258				.append(litpl.clone().addClass("ds-nav ds-prevmonth").attr('data-nav','0,-1').text("<"))
1259				.append(litpl.clone().addClass("ds-nav ds-prevyear").attr('data-nav','-1,0').text("«"))
1260				.append(litpl.clone().addClass("ds-nav ds-name").text(self.pad(dates.current.getMonth()+1)+"."+dates.current.getFullYear()))
1261				.append(litpl.clone().addClass("ds-nav ds-nextyear").attr('data-nav','1,0').text("»"))
1262				.append(litpl.clone().addClass("ds-nav ds-nextmonth").attr('data-nav','0,1').text(">"));
1263			// add event to navigation
1264			$('.ds-nav[data-nav]',ulhead).click(function(){
1265				arr=$(this).attr('data-nav').split(',');
1266				dates.nav=new Date(dates.current.getFullYear()+parseInt(arr[0],10),dates.current.getMonth()+parseInt(arr[1],10),1,dates.current.getHours(),dates.current.getMinutes());
1267				self.dropdown.html(self.calendar(dates.nav));
1268				return false;
1269			})
1270
1271			// add item for days in previous month
1272			for (var i=0;i<offset;i++) ulbody.append(litpl.clone().addClass("ds-week0").html("<span class='ds-name'>"+self.days[(i+1)%7]+"</span>"));
1273
1274			// add item for curent month
1275			var li=false,inrange=true
1276			while (dates.current.getMonth()==dates.cursor.getMonth()) {
1277				dates.text=self.format(dates.cursor,"dd.mm.yyyy")
1278				li=litpl.clone().addClass("ds-week"+parseInt(i/7)).attr("data-value",dates.text)
1279				if (dates.text==dates.today) li.addClass("ds-today")
1280				if (dates.text==self.values.text) li.addClass("selected");
1281				li.html("<span class='ds-name'>"+self.days[(i+1)%7]+"</span><span class='ds-day'>"+dates.cursor.getDate()+"</span>");
1282				inrange=true;
1283				if (inrange && self.range && self.range[0] && self.range[0]>=dates.text.split(".").reverse().join("")) inrange=false
1284				if (inrange && self.range && self.range[1] && self.range[1]<dates.text.split(".").reverse().join("")) inrange=false
1285				if (inrange) {
1286					li.addClass("enabled").click(function(){
1287						self.select($(this),true)
1288					})
1289				} else {
1290					li.addClass("disabled")
1291				}
1292				ulbody.append(li);
1293				dates.cursor.setDate(dates.cursor.getDate()+1);		// add one day. do not use dates.cursor=new Date(dates.cursor.getTime()+86400000); because of summer/winter time!
1294				i++;
1295			}
1296
1297			if (self.layout=="datetime") {
1298				ultime=$("<ul/>").attr("class","ds-time")
1299				ultime.append($("<li/>").text("hh:mm"))
1300				ultime.append($("<li/>").append($("<input tabindex='-1'/>").val(self.format(dates.current,"hh:mm")).change(function(){self.select($(this),2)})))
1301				ultime.append($("<li/>").addClass("icon icon-ok pointer").click(function(){self.select($(this).nearest('input'),3)}))
1302			}
1303			return [ulhead,ulbody,ultime];
1304		}
1305
1306		if (!self.wrap) self.wrap=$el.wrap("<div></div>").parent().addClass("ds-dropdown-wrap");
1307		else if ($("#"+self.wrap).length) self.wrap=$("#"+self.wrap);
1308		else {
1309			self.wrap=$("<div/>").attr("id",self.wrap);$("dody").append(self.wrap)
1310		}
1311
1312		switch (self.layout) {
1313			case "options":
1314				if ($el.prop("tagName")!="UL") {
1315					alert("dropdown 'options' on a non-UL tag not implemented")
1316					return false;
1317				}
1318				// Check that there is no problem with radio button (multiple groups)
1319				self.tmp=$el.find("input[type='radio']").attr("name")
1320				if (self.tmp && $el.find("input[type='radio'][name='"+self.tmp+"']").length != $("input[type='radio'][name='"+self.tmp+"']").length) {		//
1321					alert("Warning, radio buttons with name "+self.tmp+" are not unique on the page")
1322				}
1323
1324
1325				if (self.icon=="auto") self.icon=$("<i class='icon icon-down-dir'>")
1326
1327				if (!self.anchor) {
1328					if (self.mode=="edit") {
1329						if (self.wrap.next().prop("tagName")=="INPUT") self.input=self.wrap.next();
1330						else if (self.wrap.prev().prop("tagName")=="INPUT") self.input=self.wrap.prev();
1331						else self.input=self.wrap.next();
1332						self.wrap.prepend(self.input);
1333						if (self.input && !self.input.attr("type")) self.input.attr("type","text")
1334					} else {
1335						self.anchor=$("<a href='#'/>").addClass("anchor");
1336						self.wrap.prepend(self.anchor)
1337					}
1338				}
1339
1340				// here, we calculate the depth of the li > li > li. 1=only one level
1341				self.depth=0//Math.max.apply(Math, $el.find('li').map(function(){return $(this).parentsUntil($el,'li').length}))
1342				self.dropdown=$el.wrap("<div/>").parent().addClass("ds-dropdown ds-options");
1343				if (self.highlight) self.dropdown.prepend($("<input/>",{type:"text",placeholder:"filter", style:"position:relative;border:1px solid #ddd;margin:2px;width:calc(100% - 4px);",onfocus:"$(this).cbyHighlight('li',$(this).next())"}))
1344				if (self.free && self.input) self.input.attr("name",self.free)
1345				self.dropdown.find("input").attr("tabindex","-1");										// unreachable by "tab"
1346				self.dropdown.find("input[type='checkbox']").change(function(){self.select($(this),true)});
1347				self.dropdown.find("input[type='radio']").click(function(){self.select($(this),true)});
1348				self.dropdown.find("input[indeterminate='indeterminate']").prop("indeterminate","indeterminate")	// allow to set "indeterminate" attribute (not possible in html!)
1349				if (self.dropdown.find("input[type='radio']:checked").length) self.values.selected=[].concat(self.dropdown.find("input[type='radio']:checked"));
1350				self.dropdown.find("input + label").css({cursor:"pointer"}).each(function(i){			// set label clickable with "for" attribute
1351					var r="random"+Math.floor(999999*Math.random())+"_"+i
1352					$(this).attr("for",r).prev().attr("id",r)
1353				})
1354				if (self.dropdown.find("li li").length==0) self.dropdown.find("li").not(".head").addClass("enabled")		// allow up/down key if not multi level
1355				//if (self.dropdown.find("input:checked").length) self.resume()
1356
1357				if (self.multiple) self.resume({init:true});
1358				// NOTE: do not set values in javascript directly, set the correct input:checked with javascript
1359				/*if (self.values && self.values.real && self.values.real.length) self.dropdown.find("input").each(function(i,o){
1360					if(self.values.real.indexOf(o.value)>-1 || self.values.real.indexOf(parseInt(o.value,10))>-1) o.checked=true
1361				})*/
1362				if (self.input && !self.multiple && !self.free) self.input.on("keydown",function(e){ return false})		// TODO check this and next line! problem: show all options after user select
1363				if (self.input && !self.multiple && self.free) self.input.on("keyup",function(e){
1364					var regexp=false,collapse=false,textnode=false
1365					self.dropdown.find("li").show()
1366					if ($.trim(self.input.val())!="") regexp=RegExp(self.input.val().replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1"),"i");
1367					self.dropdown.find("li").not(".collapse").each(function(){
1368						if (regexp && !regexp.test($(this).text())) {		// also search inside children!
1369							$(this).hide();
1370						} else {
1371							if (collapse=$(this).closest("li").find(".collapse")) collapse.eq(0).prop("checked",true)		// only work for a double level, see for more
1372							textnode=$(this).find("label").eq(0);//.contents().filter(function(i,o){return o.nodeType==3}).wrap("<span/>")
1373							if (textnode[0].nodeType==3) textnode.html(regexp?textnode.text().replace(regexp, "<mark>$&</mark>"):textnode.text())
1374							//else textnode.html(regexp?textnode.text().replace(regexp, "<mark>$&</mark>"):textnode.text())
1375						}
1376					})
1377					if (!regexp) self.dropdown.find("mark").unwrap()
1378				})
1379				break;
1380			case "html":
1381				if ($el.prop("tagName")=="INPUT") self.input=$el; else self.anchor=$el;
1382				if (self.delayed) {
1383					self.dropdown=$("<div/>").addClass("ds-dropdown ds-html");
1384					$el.one("click focus",function(){self.dropdown.html(self.html);cms_init(self.dropdown)})
1385				} else {
1386					self.dropdown=$("<div/>").addClass("ds-dropdown ds-html").html(self.html);
1387					cms_init(self.dropdown)
1388				}
1389				self.wrap.append(self.dropdown);
1390				break;
1391			case "ajax":
1392				self.anchor=$el
1393				self.dropdown=$("<div/>").text("loading...")
1394				self.wrap.addClass("ds-dropdown-wrap")
1395				if (self.url) self.load=function(){
1396					self.wrap.find(".ds-form-head").addClass("icon icon-spin icon-spinner").html("")
1397					//self.wrap.html("loading...")
1398					if (self.json) {
1399						$.ajax({url:self.url,dataType:"json",success:function(data){if(data.status==1) self.wrap.html(data.html);cms_init(self.wrap)}})
1400					} else self.wrap.load(self.url),function(){cms_init(self.wrap)}}
1401				break;
1402			case "date":
1403			case "datetime":
1404				if ($el.prop("tagName")!="INPUT") {
1405					alert("dropdown 'date' ot 'datetime' on a non-INPUT tag not implemented")
1406					return false;
1407				}
1408				self.input=$el;
1409				if (self.input.val()) self.input.val(self.input.val().replace("/","").replace("  "," "))
1410				if (self.input.val()) self.values.text=self.input.val(); else self.values.text=self.format(new Date(),"dd.mm.yyyy hh:00")
1411				self.values.real=self.parse(self.values.text,"dd.mm.yyyy hh:mm")
1412				self.values.date=self.values.text.split(" ")[0]
1413				self.values.time=self.values.text.split(" ")[1]
1414				if (self.layout=="date") self.values.text=self.values.date
1415				if (self.icon=="auto") self.icon=$("<i class='icon icon-calendar'>");
1416
1417				if (self.range[0]||self.range[1]) {
1418					for (i=0;i<self.range.length;i++)
1419						if (self.range[i]==1) // today
1420							self.range[i]=self.format((new Date()),"yyyymmdd");
1421						else
1422							self.range[i]=self.format(self.parse(self.range[i],"dd.mm.yyyy"),"yyyymmdd");
1423				}
1424				self.dropdown=$("<div/>").addClass("ds-dropdown ds-calendar");
1425				self.wrap.append(self.dropdown);
1426				self.fill=self.calendar
1427				if (self.input) self.input.keyup=function(){
1428					if (self.input.val()==values.real) return false;
1429					if (/^[0-9]{2}\.[0-9]{2}\.[0-9]{4}/.test(self.input.val())) self.dropdown.html(self.calendar(self.parse(self.input.val(),"dd.mm.yyyy")))
1430				}
1431				break;
1432			case "next":
1433				self.anchor=$el
1434				self.dropdown=$("<div/>").addClass("ds-dropdown").append(self.wrap.next().show());
1435				self.wrap.append(self.dropdown);
1436				break;
1437			default:
1438				alert("dropdown '"+settings.layout+"' not implemented")
1439				return false;
1440		}
1441
1442		if (self.position=="right" && self.dropdown) self.dropdown.addClass("dropright")
1443
1444		if (self.input && self.placeholder) self.input.attr("placeholder",self.placeholder)
1445		if (self.input && self.icon!==false && self.icon!="auto") self.input.css({paddingRight:"1.5em"}).after(self.icon)
1446		//if (input && settings.multiple) input.attr("readonly","true");		// do not open keyboard on mobile devices, note: removed because this interfer with "required" attribute not fired
1447		if (self.input && self.width) self.input.css({width:self.width})
1448
1449		if (self.dropdown) {
1450			self.dropdown.addClass("ds-"+self.mode)
1451			//self.dropdown.on("mouseenter",function(){self.inside=true}).on("mouseleave",function(){self.inside=false})
1452		} else {
1453			//alert("Warning: no dropdown!")
1454		}
1455
1456		self.show = self.show || function(){
1457			if (self.input && self.layout!="date" && self.layout!="datetime") self.dropdown.css({minWidth:self.input.outerWidth()});
1458			self.wrap.addClass("ds-open");
1459			//self.dropdown.removeClass("dropright");
1460			//if(self.dropdown.offset().left+self.dropdown.width()>$(window).width()) self.dropdown.addClass("dropright");
1461		}
1462
1463		self.hide = self.hide || function(){self.wrap.removeClass("ds-open");}
1464
1465		self.toggle = self.toggle || function(){self.wrap.toggleClass("ds-open");}
1466
1467		self.cbybox = self.cbybox || function(){$.cbybox.open(dropdown.removeClass('ds-dropdown'),{cssouter:{width:"auto"}})}
1468
1469		// Manage opening dropdown
1470		if (self.open===true) self.show()
1471		if (self.anchor) self.anchor.css({cursor:"pointer"}).attr("tabindex",self.tabindex||"1")
1472		if (self.position!="cbybox") {
1473			self.skipevent=false;
1474			(self.input||self.anchor).on("click focus",function(e){
1475				if (! (self.skipevent=!self.skipevent)) return false;		// avoid double events (focus+click or double click)
1476				setTimeout(function(){self.skipevent=false},300);
1477				if (self.wrap.hasClass("ds-open")) {self.hide();return false}
1478				if (self.fill) self.dropdown.html(self.fill());
1479				if (self.load) self.load();
1480				self.show();
1481				e.preventDefault();
1482			})
1483		} else {
1484			(self.input||self.anchor).on("click",function(e){
1485				self.cbybox();
1486				e.preventDefault()
1487			})
1488		}
1489		return self
1490	}
1491	// Manage closing dropdown
1492	$(document).on('mousedown.dropdown keyup.dropdown', function(e) {
1493		//console.log(e.target,$(".ds-dropdown-wrap.ds-open").length)
1494		var target=$(e.target)
1495		$(".ds-dropdown-wrap.ds-open").filter(function(){return !$(this).has(target).length}).removeClass("ds-open")
1496	})
1497})(jQuery);
1498
1499/* cbyInputfile plugin */
1500(function($){
1501	/* OO jquery based on http://www.virgentech.com/blog/2009/10/building-object-oriented-jquery-plugin.html */
1502	$.fn.cbyInputfile = function(options) {
1503		return this.each(function() {
1504			var element = $(this);
1505			// Return early if this element already has a plugin instance
1506			if (element.data('cbyInputfile')) return;
1507			// pass options to plugin constructor
1508			var cbyInputfile = new cbyInputfileClass(this, options);
1509			// Store plugin object in this element's data
1510			element.data('cbyInputfile', cbyInputfile);
1511		});
1512	};
1513
1514	var cbyInputfileClass = function(element,options) {
1515		// Start declaration (don't forget to put 'var' before every variables/functions!)
1516		// Default settings
1517		var self=this;
1518		var input=$(element);				// The object
1519		var isIE = /(MSIE|Trident\/|Edge\/)/i.test(navigator.userAgent);
1520		var mode="edit"
1521		if (element.tagName.toLowerCase()!="input") mode="view"
1522		var multiple={
1523			attr:input.attr("multiple"),	// Is the input multiple (in the xml)
1524			support:input.multiple			// Is the multiple supported in the browser?
1525		}
1526		var settings = jQuery.extend(true,{
1527			placeholder:( (multiple.attr||options.large) && !options.small)?
1528				{text:"Click or drag",icon:"icon icon-upload",css:{fontSize:"2em",padding:"1em 0",textAlign:"center",color:"#aaa",border:"4px dashed #aaa",borderRadius:"5px"}}:
1529				{text:"Browse...",icon:"icon icon-upload",css:{padding:"2px 10px 4px", textAlign:"left",border:"1px solid #999",borderRadius:"3px",display:"inline-block"}},
1530			inputcss:{maxWidth:"none",position:"absolute",opacity:0.01,width:"100%",height:"100%",zIndex:1,cursor:"pointer"},
1531			//limit:{maxsize:40000000,maxfiles:100,extensions:[]},
1532			layout:1,
1533			annex:false		/* used to add some other field, ex: ["desc"]*/
1534		}, options);
1535		var count={maxfiles:0}
1536		var name=input.attr("name")
1537		var wrap=input.wrap("<div></div>").parent();
1538		var placeholder=$("<div></div>
1538").addClass("validation").css(settings.placeholder.css).html(settings.placeholder.text)
1539		wrap.on("dragover",function(){placeholder.css({borderColor:"#1e8cbe"})}).on("dragleave",function(){placeholder.css({borderColor:"#aaaaaa"})})
1540
1541		if (settings.placeholder.icon) placeholder.prepend($("<i></i>").addClass(settings.placeholder.icon))
1542		wrap.css({position:"relative"}).append(placeholder)
1543		input.css(settings.inputcss)
1544		var dropdown=$("<div></div>").addClass("ds-dropdown").css({position:"relative",minHeight:"2em",paddingTop:"0.5em",zIndex:"inherit"}).hide()
1545		var uldropdown=$("<ul></ul>").addClass("dragsort-items")
1546		wrap.after(dropdown.append(uldropdown))
1547		//cms_init(dropdown)
1548		input.on("change",function(){self.change(this,"input")});
1549		// For IE, we must catch the dragover to avoid going to the images and must watch the drop on the originalEvent (dataTransfer contains "files" property)
1550		if (isIE) input.on("drop",function(event){self.change(event.originalEvent.dataTransfer,"input")}).on("dragover",function(event){event.preventDefault()})
1551
1552		self.filesize = function(a,b,c,d,e){return (a==0)?"0 Byte":(b=Math,c=b.log,d=1024,e=b.min(c(a)/c(d),'kMGTPEZY'.length)|0,a/b.pow(d,e)).toFixed(2)+' '+(e?'kMGTPEZY'[--e]+'B':'Bytes')}
1553
1554		self.checkextension = function(name) {
1555			var ext="."+name.split('.').pop().toLowerCase();
1556			var extensions=
1557				{ allowed:(settings.limit.extensions||[]).filter(function(v){return v[0]=="+"}).map(function(v){return v.replace("+",".").toLowerCase()})
1558				, denied:(settings.limit.extensions||[]).filter(function(v){return v[0]=="-"}).map(function(v){return v.replace("-",".").toLowerCase()})
1559				, error:0
1560				}
1561			if (extensions.allowed.length && extensions.allowed.indexOf(ext)==-1) extensions.error="extension not allowed (allowed: "+extensions.allowed.join(", ")+")"
1562			if (extensions.denied.length && extensions.denied.indexOf(ext)>-1) extensions.error="extension not allowed (denied: "+ext+")"
1563			return extensions.error
1564		}
1565		self.checklimit = function(text,number) {		// text: maxsize (one by file), maxfiles (number of file)
1566			error=0
1567			if (settings.limit[text] && number>settings.limit[text]) error=text+" exceeded";
1568			if (text=="maxsize" && number==0) error=text+" empty";
1569			return error
1570		}
1571		self.change = function(obj,from) {		/* obj.files comes from input.files OR options.files */
1572			dropdown.show().parent().addClass("ds-open")
1573			var total=0,fake=false,li=false;
1574			if (!multiple.attr) uldropdown.html("")
1575			switch(settings.layout) {
1576				case 1:			// input.files splitted in lines, can be moved and deleted
1577					for (var i=0;i<obj.files.length;i++) {
1578						li=$("<li></li>").css({padding:"0 0.5em 0.5em 1em"}).addClass("clearfix");
1579						count={maxfiles:1}
1580						uldropdown.find(".ds-form-step").each(function(i,v){count.maxfiles++;})
1581						var error=self.checkextension(obj.files[i].name) || self.checklimit("maxsize",obj.files[i].size) || self.checklimit("maxfiles",count.maxfiles)
1582						if (error===0) {
1583							if (from!="options") {
1584								fake=$("<input/>").attr("type","file").attr("name",name)	// we add a fake input[type=file] used by jquery.form.js
1585								if (multiple.attr) fake.addClass("ds-form-step")			// if multiple, add a class to tell cms_submit we have a pivot in the form
1586								fake.hide()
1587								fake.get(0).cbyfiles={files:obj.files,index:i}				// as we can't set specific input.files, we copy all files and tell which index it is
1588							} else {
1589								fake=$("<input/>").attr("type","hidden").attr("name",name).val(JSON.stringify(obj.files[i]))	// we add a fake input[type=hidden] for previous values
1590							}
1591						} else {
1592							fake=$("<div></div>").css({paddingLeft:"1em",color:"#f00",fontSize:"0.9em"}).html(error)
1593						}
1594						li.append($("<span></span>").text(obj.files[i].name))
1595						if (from=="options" && obj.files[i].path) {
1596							a=$("<a/>").attr("target","_blank").attr("href",obj.files[i].path+obj.files[i].file)
1597							if (obj.files[i].type && (obj.files[i].type.indexOf("image")==0 || obj.files[i].type.indexOf("vimeo")==0)) a.attr("data-covr",obj.files[i].path+obj.files[i].file)
1598							li.children(0).wrapInner(a)
1599						}
1600						li.append($("<span></span>").addClass("right").html(
1601								[ self.filesize(obj.files[i].size)
1602								, (from!="options")?$("<span></span>").css({padding:"0 0.5em"}).addClass("ds-progress-text").html(""):""
1603								, $("<i></i>
1603").addClass("icon icon-sort pointer dragsort-selector")
1604								, $("<i></i>").addClass("icon icon-cancel right pointer").click(function(){$(this).closest("li").remove()})
1605								]))
1606						if (settings.annex) li.append($("<div/>").addClass("annex").append(settings.annex))
1607						if (from!="options") li.append($("<div></div>").addClass("ds-progress-width").hide().css({width:0,height:3,backgroundColor:"#0f0"}))
1608						li.append(fake)
1609						if (!multiple.attr) {
1610							li.find("i.icon-sort").remove()
1611							li.find("i.icon-cancel").unbind("click").click(function(){
1612								$(this).closest("li").find("input").remove()
1613								$(this).closest("li").append($("<input/>").attr("type","hidden").val("{}").attr("name",name))
1614								$(this).closest("li").find("> span").eq(0).css({textDecoration:"line-through"})
1615							})
1616						}
1617						li.find("[data-covr]").on("click",function(){$.covr.open($("<img/>",{src:$(this).attr("data-covr")}));return false})
1618						uldropdown.append(li)
1619					}
1620					input.hide().get(0).disabled=true		// tel jquery.form.js to skip this input
1621					break;
1622				case 2:			// input.files stay in "block", blockes can be deleted, TODO: size+ext check
1623					for (var i=0;i<obj.files.length;i++) total+=obj.files[i].size
1624					li=$("<li></li>").css({padding:"0 0.5em 0.5em 1em"}).addClass("clearfix")
1625					li.append($("<span></span>").text(obj.files.length+" fichier(s)"))
1626						.append($("<i></i>").addClass("icon icon-down-dir pointer").click(function(){$(this).toggleClass("icon-down-dir").toggleClass("icon-right-dir").nearest("ul").toggle()}))
1627						.append($("<span></span>").addClass("right").html([self.filesize(total),$("<i></i>").addClass("icon icon-cancel right pointer").click(function(){$(this).closest("li").remove()})]))
1628					var ul=$("<ul></ul>").css({fontSize:"0.8em",color:"#999",paddingLeft:"1.5em"})
1629					for (var i=0;i<obj.files.length;i++) {
1630						var file=obj.files[i]
1631						ul.append($("<li></li>").css({listStyle:"circle",paddingBottom:"0.2em"}).text(file.name+" - "+self.filesize(file.size)))
1632					}
1633					li.append(ul)
1634					li.append(input.attr("name",name).hide())		// real input are moved in the li, so containing all "files" in one input
1635					uldropdown.append(li)
1636					break;
1637			}
1638			// Create a new input with same attr but different name
1639			input=$("<input/>").attr("type","file").attr("name","tmp."+name).css(settings.inputcss).on("change",function(){self.change(this)})
1640			if (isIE) input.on("drop",function(event){self.change(event.originalEvent.dataTransfer,"input")}).on("dragover",function(event){event.preventDefault()})
1641			if (multiple.attr) input.attr("multiple","multiple")
1642			wrap.prepend(input)
1643
1644		}
1645
1646		self.vimeo=function(){
1647			var vid=prompt("Vimeo ID, ex:39206630")
1648			if (!vid) return false
1649			if (!/^[0-9]+$/.test(vid)) {alert("Wrong video ID");return false;}
1650			$.get("https://vimeo.com/api/v2/video/"+vid+".json").then(function(data){
1651				// [{"id":39206630,"title":"MAMCO, Museum of Contemporary Art of Geneva. Ad.","description":"","url":"https://vimeo.com/39206630","upload_date":"2012-03-26 11:56:31","mobile_url":"https://vimeo.com/39206630","thumbnail_small":"https://i.vimeocdn.com/video/270295514_100x75.jpg","thumbnail_medium":"https://i.vimeocdn.com/video/270295514_200x150.jpg","thumbnail_large":"https://i.vimeocdn.com/video/270295514_640.jpg","user_id":583391,"user_name":"Base Design","user_url":"https://vimeo.com/basedesign","user_portrait_small":"https://i.vimeocdn.com/portrait/6445559_30x30","user_portrait_medium":"https://i.vimeocdn.com/portrait/6445559_75x75","user_portrait_large":"https://i.vimeocdn.com/portrait/6445559_100x100","user_portrait_huge":"https://i.vimeocdn.com/portrait/6445559_300x300","stats_number_of_likes":4,"stats_number_of_plays":215,"stats_number_of_comments":0,"duration":15,"width":1280,"he
1651ight":720,"tags":"MAMCO, BaseMotion, BaseDesign, GVA STudio, Art, Museum, Spot, Humor, Still image","embed_privacy":"anywhere"}]
1652				if (data && data[0] && data[0]["thumbnail_large"]) {
1653					var fake=$("<input/>",{type:"hidden",name:name,val:JSON.stringify({request:data[0]["thumbnail_large"],name:data[0].title+".jpg",type:"vimeo",xtra:vid})})
1654					var li=$("<li>").css({padding:"0 0.5em 0.5em 1em"}).addClass("clearfix").append(
1655						$("<span/>").append($("<a/>",{href:"#","data-covr":data[0]["thumbnail_large"]}).text(data[0].title+".jpg")),
1656						$("<span/>",{class:"right"}).html([
1657							fake,
1658							$("<i></i>").addClass("icon icon-sort pointer dragsort-selector"),
1659							$("<i></i>").addClass("icon icon-cancel right pointer").click(function(){$(this).closest("li").remove()})
1660						])
1661					)
1662					li.find("[data-covr]").on("click",function(){$.covr.open($("<img/>",{src:$(this).attr("data-covr")}));return false})
1663					uldropdown.append(li)
1664				} else div.text("Error loading vimeo....")
1665			})
1666		}
1667		if (settings.vimeo) wrap.css({display:"inline-block",width:"80%"}).after(
1668			$("<div/>").css({display:"inline-block",width:"15%",float:"right"})
1669				.append(placeholder.clone().text("Vimeo")).css({cursor:"pointer"}).on("click",self.vimeo)
1670		)
1671
1672		// Init with current values. Note, if options.files is empty it may be [{}], so check also the first files[0] to have a .name key
1673		if (options.files && options.files.length && options.files[0].name) {
1674			dropdown.css({opacity:1,transition:"none 0s"})		// no transition if already filled with something
1675			self.change(options,"options")
1676		}
1677
1678	}
1679})(jQuery);
1680
1681/*
1682* jQuery.bind-first library v0.2.3
1683* Copyright (c) 2013 Vladimir Zhuravlev
1684*
1685* Released under MIT License
1686* @license
1687*
1688* Date: Thu Feb 6 10:13:59 ICT 2014
1689**/
1690(function(t){function e(e){return u?e.data("events"):t._data(e[0]).events}function n(t,n,r){var i=e(t),a=i[n];if(!u){var s=r?a.splice(a.delegateCount-1,1)[0]:a.pop();return a.splice(r?0:a.delegateCount||0,0,s),void 0}r?i.live.unshift(i.live.pop()):a.unshift(a.pop())}function r(e,r,i){var a=r.split(/\s+/);e.each(function(){for(var e=0;a.length>e;++e){var r=t.trim(a[e]).match(/[^\.]+/i)[0];n(t(this),r,i)}})}function i(e){t.fn[e+"First"]=function(){var n=t.makeArray(arguments),i=n.shift();return i&&(t.fn[e].apply(this,arguments),r(this,i)),this}}var a=t.fn.jquery.split("."),s=parseInt(a[0]),f=parseInt(a[1]),u=1>s||1==s&&7>f;i("bind"),i("one"),t.fn.delegateFirst=function(){var e=t.makeArray(arguments),n=e[1];return n&&(e.splice(0,2),t.fn.delegate.apply(this,arguments),r(this,n,!0)),this},t.fn.liveFirst=function(){var e=t.makeArray(arguments);return e.unshift(this.selector),t.fn.delegateFirst.apply(t(document),e),this},u||(t.fn.onFirst=function(e,n){var i=t(this),a="string"==typeof n;if(t.fn.on.apply(i,arguments),"object"==typeof e)for(type in e)e.hasOwnProperty(type)&&r(i,type,a);else"string"==typeof e&&r(i,e,a);return i})})(jQuery);
1691
1692/*
1693	cbyTouchmouse plugin (simulate mouse[events] from touch[event], ex: for drag&drop, use as $('document').cbyTouchmouse(), or $('div.touch').cbyTouchmouse()
1694	2013 - Stéphane Carion
1695	* Based on work by Shaun Inman
1696	*   http://ross.posterous.com/2008/08/19/iphone-touch-events-in-javascript/
1697*/
1698
1699(function($) {
1700    $.fn.cbyTouchmouse = function(options) {
1701        var settings = {
1702        };
1703        if(options) {
1704            $.extend(settings, options);
1705        };
1706        var container = $(this);
1707        return this.each(function() {
1708			var touchHandler = function(event) {
1709				var touch = event.changedTouches[0];
1710
1711				var simulatedEvent = document.createEvent("MouseEvent");
1712					simulatedEvent.initMouseEvent({
1713					touchstart: "mousedown",
1714					touchmove: "mousemove",
1715					touchend: "mouseup"
1716				}[event.type], true, true, window, 1,
1717					touch.screenX, touch.screenY,
1718					touch.clientX, touch.clientY, false,
1719					false, false, false, 0, null);
1720
1721				touch.target.dispatchEvent(simulatedEvent);
1722				event.preventDefault();
1723			}
1724
1725			this.addEventListener("touchstart", touchHandler, true);
1726			this.addEventListener("touchmove", touchHandler, true);
1727			this.addEventListener("touchend", touchHandler, true);
1728			this.addEventListener("touchcancel", touchHandler, true);
1729		})
1730	}
1731})(jQuery);
1732
1733(function($) {
1734    $.fn.cbyWysiwyg = function(options) {
1735        if(options) {
1736            $.extend(settings, options);
1737        };
1738        var container = $(this);
1739		var makeiteditable = function(obj,arg) {
1740			arg=arg||{}
1741			obj.attr("contenteditable","true")
1742
1743			if (arg.oneline) {
1744				obj.bind("keydown",function(e){		// disallow <return>
1745					var code = null;
1746					code = (e.keyCode ? e.keyCode : e.which);
1747					return (code == 13) ? false : true;
1748				})
1749				obj.bind("paste",function(e){		// on paste, juste take the first line
1750					var data = e.originalEvent.clipboardData.getData('Text');
1751					insertHtml(data.split(/\r\n|\r|\n/g)[0]);
1752					return false;
1753				})
1754			} else {
1755				obj.bind("paste",function(e){
1756					var data = e.originalEvent.clipboardData.getData('Text');
1757					insertHtml(data.replace(/\r\n|\r|\n/g,"<br>"));
1758					return false;
1759				})
1760			}
1761			var eventname="mouseup"
1762			if (window.chrome) eventname="mousedown mouseup"	// both mouseup and mousedown needed for Chrome
1763			switch (arg.exec||"full") {
1764				case "full":
1765					obj.addClass("exec execfull")
1766					obj.bind(eventname,function(e){
1767						if (!window.getSelection().isCollapsed) {
1768							var a=["bold","italic","underline","forecolor_#ff0000","forecolor_#00ff00","removeFormat_foreColor"];
1769							//var a=["bold","italic","underline","forecolor_#ff0000","insertunorderedlist","removeFormat_foreColor"];
1770							var position={imgw:150,imgh:30,x:e.offsetX-parseInt($(this).css("padding-left"),10),y:$(this).height()-e.offsetY}
1771							position.index=parseInt(position.x/position.imgw*a.length)
1772							console.log(position)
1773							if(position.y<position.imgh && position.index < a.length){
1774								document.execCommand(a[position.index].split("_")[0],false,a[position.index].split("_")[1])
1775								document.getSelection().removeAllRanges()
1776							}
1777						}
1778						$(this).toggleClass("execon",!window.getSelection().isCollapsed)
1779					})
1780					break;
1781				case "simple":
1782					obj.addClass("exec execsimple")
1783					obj.bind(eventname,function(e){
1784						if (!window.getSelection().isCollapsed) {
1785							var a=["forecolor_#ff0000","forecolor_#00ff00","removeFormat_foreColor"];
1786							var position={imgw:75,imgh:30,x:e.offsetX-parseInt($(this).css("padding-left"),10),y:$(this).height()-e.offsetY}
1787							position.index=parseInt(position.x/position.imgw*a.length)
1788							if(position.y<position.imgh && position.index < a.length){
1789								document.execCommand(a[position.index].split("_")[0],false,a[position.index].split("_")[1])
1790								document.getSelection().removeAllRanges()
1791							}
1792						}
1793						$(this).toggleClass("execon",!window.getSelection().isCollapsed)
1794					})
1795					break;
1796			}
1797		}
1798
1799		// paste  after caret position
1800		var insertHtml=function(html) {
1801			var sel, range;
1802			if (window.getSelection) {
1803				sel = window.getSelection();
1804				if (sel.getRangeAt && sel.rangeCount) {
1805					range = sel.getRangeAt(0);
1806					range.deleteContents();
1807					var el = document.createElement("div");
1808					el.innerHTML = html;
1809					var frag = document.createDocumentFragment(), node, lastNode;
1810					while ( (node = el.firstChild) ) {
1811						lastNode = frag.appendChild(node);
1812					}
1813					range.insertNode(frag);
1814					if (lastNode) {
1815						range = range.cloneRange();
1816						range.setStartAfter(lastNode);
1817						range.collapse(true);
1818						sel.removeAllRanges();
1819						sel.addRange(range);
1820					}
1821				}
1822			} else {
1823				if (document.selection && document.selection.type != "Control") document.selection.createRange().pasteHTML(html);
1824			}
1825		}
1826
1827		//Clear all character inside word document and other
1828		var cleanWordPaste=function(input) {
1829			var tmp = document.createElement("DIV");
1830			tmp.innerHTML = input;
1831			var newString = tmp.textContent||tmp.innerText;
1832			newString  = newString.replace(/\n\n/g, "<p />").replace(/.*<!--.*-->/g,"");
1833			for ( i=0; i<10; i++ ) {
1834				if ( newString.substr(0,6)=="<br />" ) {
1835					newString = newString.replace("<br />", "");
1836				}
1837			}
1838			return newString;
1839		}
1840
1841		$('body').on('focus', '.execwatch[contenteditable]', function() {
1842			var $this = $(this);
1843			$this.data('before', $this.html());
1844			return $this;
1845		}).on('blur paste', '.execwatch[contenteditable]', function(e) {
1846			//console.log(e.type)
1847			var $this = $(this);
1848			if ($this.data('before') !== $this.html()) {
1849				$this.data('before', $this.html());
1850
1851				$this.prev().val($this.data('before'))
1852				//console.log($this.html())
1853				$this.trigger('change');
1854			}
1855			return $this;
1856		});
1857
1858		return this.each(function() {
1859			var self=$(this)
1860			if ($(this).prop("tagName").toLowerCase()=="textarea") {
1861				$(this).wrap($("<div></div>").addClass("ds-inner"))//css({padding:"0.2em",border:"1px solid #999"}))
1862				self=$("<div></div>").addClass("execwatch").html($(this).val()||"<br>");
1863				$(this).after(self).hide()
1864			}
1865			makeiteditable(self,"full");
1866		})
1867
1868}})(jQuery);
1869
1870/* simple ping, ex: $.fn.ping(); or $.fn.ping(false), timeout is set to 55 min as session are 60 minutes */
1871jQuery.fn.ping = function(flag){
1872  if (flag===false) {clearTimeout($("html").data("ping"));$("html").data("ping",false);return}
1873  var ping=function(){$.get("/ping/"+$("html").data("ping"))}
1874  $("html").data("ping",$("html").data("ping")||setInterval(ping,1000*60*55))
1875}
1876
1877/* jquery addData, add a class "data" and an attribute "data-key" with JSON.encode string of value */
1878jQuery.fn.addData = function(data) {
1879	$(this).addClass("data");
1880	for (key in data) $(this).attr("data-"+key,($.type(data[key]) === "string")?data[key]:JSON.stringify(data[key]))
1881	return $(this);
1882};
1883/* jquery field, find the input[name='#name#'] in the nearest form */
1884jQuery.fn.field = function(name) {
1885	return $(this).nearest("form").find(":input").filter(function(){return name=="" || $(this).attr("name")==name});
1886};
1887
1888/*
1889	cbyTouchmouse plugin (simulate mouse[events] from touch[event], ex: for drag&drop, use as $('document').cbyTouchmouse(), or $('div.touch').cbyTouchmouse()
1890	2013 - Stéphane Carion
1891	* Based on work by Shaun Inman
1892	*   http://ross.posterous.com/2008/08/19/iphone-touch-events-in-javascript/
1893	NOTE: warning: scrolling with finger will be unavailable on the object!
1894*/
1895
1896(function($) {
1897    $.fn.cbyTouchmouse = function(options) {
1898        var settings = {
1899        };
1900        if(options) {
1901            $.extend(settings, options);
1902        };
1903        var container = $(this);
1904        return this.each(function() {
1905			var touchHandler = function(event) {
1906				var touch = event.changedTouches[0];
1907
1908				var simulatedEvent = document.createEvent("MouseEvent");
1909					simulatedEvent.initMouseEvent({
1910					touchstart: "mousedown",
1911					touchmove: "mousemove",
1912					touchend: "mouseup"
1913				}[event.type], true, true, window, 1,
1914					touch.screenX, touch.screenY,
1915					touch.clientX, touch.clientY, false,
1916					false, false, false, 0, null);
1917
1918				touch.target.dispatchEvent(simulatedEvent);
1919				event.preventDefault();
1920			}
1921
1922			this.addEventListener("touchstart", touchHandler, true);
1923			this.addEventListener("touchmove", touchHandler, true);
1924			this.addEventListener("touchend", touchHandler, true);
1925			this.addEventListener("touchcancel", touchHandler, true);
1926		})
1927	}
1928})(jQuery);
1929
1930/* jquery nearest, from https://github.com/fusionbox/jquery-nearest */
1931/* add by cby: just return the first match (.eq(0)) */
1932(function($) {
1933	$.fn.nearest = function(find_child, closest_parent) {
1934		if ( typeof closest_parent == 'undefined' ) {
1935			if ( $(document).find(find_child).length === 0 ) return $([]);
1936
1937			var current = $(this);
1938			var child = null;
1939			while ( current.length !== 0 ) {
1940				child = current.find(find_child);
1941				if ( child.length ) return child.eq(0);
1942
1943				if ( current.parent() == current )  break;
1944				current = current.parent();
1945			}
1946			//return an empty set:
1947			return $([]);
1948		}
1949		var closest = $(this).closest(closest_parent);
1950		if ( closest.filter(find_child).length )  return closest.filter(find_child).eq(0);
1951		return closest.find(find_child).eq(0);
1952	}
1953})(jQuery);
1954
1955(function($) {
1956	$.fn.bgi=function(uri,anim){
1957		// correct way the set a background image (even if uri is complexe - spaces, quote, slashes), ex: $("div.bg").bgi("/img/space éèà.gif")
1958		// note: if uri has a scheme, "contain:"|"cover:" use it to  set full background property
1959		var self=$(this)
1960		uri=uri.split(":")
1961		if (["contain","cover","initial"].indexOf(uri[0])>-1) {
1962			self.css({backgroundSize:uri.splice(0,1),backgroundRepeat:"no-repeat",backgroundPosition:"center"})
1963		}
1964		var url="url("+uri[0].split("/").map(function(o){return encodeURIComponent(o)}).join("/")+")"
1965		if (anim) {
1966			self.parent().css({overflow:"hidden"})
1967			var clone=self.clone().empty().css({position:"absolute",width:self.width(),left:anim/4+"px"}).offset(self.offset())
1968			self.after(clone.animate({left:(anim>0)?"100%":"-100%"},400,function(){$(this).remove()}))
1969		}
1970		return self.css({backgroundImage:url})
1971	}
1972})(jQuery);
1973
1974(function($) {
1975	$.fn.cbyHighlight = function(arg, path, regex) {
1976		// Ex: <input type="text" placeholder="Search" onfocus="$(this).cbyHighlight('.o-highlight-item')">
1977		// or $("input.o-highlight").cbyHighlight(".o-highlight-item")
1978		// or $('body').cbyHighlight(true,"text")
1979		var firsttime=true,visible=true
1980		function innerHighlight(node, path, regex) {
1981			var skip = 0;
1982			if (node.nodeType == 3) {
1983				var pos=node.data.search(regex)
1984				if (pos >= 0) {
1985					var mark = document.createElement('mark');
1986					var middlebit = node.splitText(pos);
1987					var endbit = middlebit.splitText(path.length);
1988					var middleclone = middlebit.cloneNode(true);
1989					mark.appendChild(middleclone);
1990					middlebit.parentNode.replaceChild(mark, middlebit);
1991					skip = 1;
1992				}
1993			} else if (node.nodeType == 1 && node.childNodes && !/(script|style|mark)/i.test(node.tagName)) {
1994				for (var i = 0; i < node.childNodes.length; ++i) {
1995					i += innerHighlight(node.childNodes[i], path, regex);
1996				}
1997			}
1998			return skip;
1999		}
2000		if (arg === true || arg[0]===true) {	// arg=[true] means path is an array of str
2000ing
2001			return this.length && path && path.length ? this.each(function() {
2002				if (arg[0]===true){
2003					for (var i=0;i<path.length;i++) innerHighlight(this,path[i],regex[i]);
2004				}else{
2005					innerHighlight(this,path,regex);
2006				}
2007			}) : this;
2008		} else if (arg === false) {
2009			return this.find("mark").each(function() {
2010				this.parentNode.firstChild.nodeName;
2011				with(this.parentNode) {
2012					replaceChild(this.firstChild, this);
2013					normalize();
2014				}
2015			}).end();
2016		} else this
2017			.filter(function(){return $(this).is("input") && !$(this).data("cbyhighlights")})
2018			.data("cbyhighlights", $(arg||".o-highlight-item",path))
2019			.on("keyup", function(e) {
2020				//if (firsttime) {firsttime=false;$(this).data("cbyhighlights").find("*").append(" ")}		// add a space to all node to avoid text() to return joined string (<p>aa</p><p>bb</p>).text() -> aabb and not aa bb
2021				if (firsttime) visible=$(this).data("cbyhighlights").eq(0).is(":visible");		// will show/hide as initial state when keyword=""
2022				firsttime=false
2023				console.log($(this).data("cbyhighlights-delimiter")||" ")
2024				$(this).data("cbyhighlights").hide()
2025				var keyword = $(this).val().toLowerCase().trim().replace(/\s\s+/g, ' ')
2026					,keywords = keyword.split($(this).data("cbyhighlights-delimiter")||" ")
2027					,count = 0
2028				if(keywords.length>1)
2029					var regex=keywords.map(function(r){
2030						return pipe.regsafe(r)
2031					})
2032				else
2033					var regex=pipe.regsafe(keyword)
2034
2035				if (keyword == "")
2036					$(this).data("cbyhighlights").toggle(visible).parent().cbyHighlight(false)
2037				else {
2038					$(this).data("cbyhighlights").hide().parent().cbyHighlight(false)
2039
2040					if(keywords.length>1) {
2041						$(this).data("cbyhighlights").each(function(){
2042							var and=true
2043								,tr=this
2044							regex.map(function(r){
2045								if($(tr).text().search(r)==-1)
2046									and=false
2047							})
2048							if(and){
2049								count++
2050								$(this).show().cbyHighlight([true], keywords, regex)
2051							}
2052						})
2053						if(count==0){
2054							var or=new RegExp((pipe.regsafe(keywords.join('---'))).toString().replace(/---/gi,'|').slice(1,-3),'gi')
2055
2056							$(this).data("cbyhighlights").each(function(){
2057								if ($(this).text().search(or)!=-1)
2058									$(this).show().cbyHighlight([true], keywords, regex)
2059							})
2060						}
2061					}else{
2062						$(this).data("cbyhighlights").each(function(){
2063							if ($(this).text().search(regex)!=-1)
2064								$(this).show().cbyHighlight(true, keyword ,regex)
2065						})
2066					}
2067				}
2068			})
2069
2070		return this;
2071	};
2072})(jQuery);
2073
2074;(function($, window){
2075	var dom  = {}
2076	dom.self = $(this);		// window or element which trigger the call to covr
2077	dom.back = $("<div/>",{class:"o-covr o-covr-back"}).hide();
2078	dom.main = $("<div/>",{class:"o-covr-main"}).appendTo(dom.back);
2079	dom.scrl = $("<div/>",{class:"o-covr-scrl"}).appendTo(dom.main);
2080	;["head.u-text--l","body","foot"].forEach(function(key){
2081		dom[key.split(".")[0]] = $("<div/>",{class:"o-covr-"+key.replace(/\./g," ")}).appendTo(dom.main)
2082	})
2083	var conf = { speed:100, foot: {"ok":function(){$.covr.close();}}}
2084	dom.back.on("click",function(e){
2085		var target=$(e.target);
2086		if (!dom.back.hasClass("js-modal") && !target.hasClass("o-covr-main") && target.closest(".o-covr-main").length==0) $.covr.close()
2087	})
2088	$.covr = {
2089		open: function(html,opts,event){
2090			if (event) dom.self = $(event.target)
2091			dom.focus = $(":focus").blur()
2092			opts=$.extend(true,{},conf,opts)
2093			dom.back.appendTo($("body")).toggleClass("js-modal",!!opts.modal)
2094			html={head:"",body:html,split:[]}
2095			if ($.type(html.body)=="string" && (html.split=html.body.split("|")) && html.split.length>1) {
2096				html.head=html.split[0]
2097				html.body=html.split[1]
2098				if (html.split.length>2) {opts.foot[html.split[2]]=opts.foot.ok;delete opts.foot.ok}
2099			}
2100			dom.head.empty().append(html.head)
2101			dom.body.empty().append(html.body)
2102			dom.foot.empty().append(Object.keys($.extend({},opts.foot)).filter(function(key){return opts.foot[key]}).map(function(key){
2103				return $("<a/>",{class:"o-button u-pointer"}).text(key).on("click",$.proxy(opts.foot[key],dom))
2104			}))
2105			if (opts.close) dom.main.prepend($("<div/>",{class:"o-covr-close u-pointer icon icon-cancel"}).on("click",function(){$.covr.close();return false;}))
2106			if (opts.scrl) dom.scrl.append(dom.head,dom.body,dom.foot)
2107			if (opts.wrap) dom.back.append(opts.wrap.append(dom.main))
2108			dom.back[0].className="o-covr o-covr-back "+(opts.class||"")
2109			dom.back.fadeIn(conf.speed)
2110			if (opts.scrl) dom.scrl.css({paddingRight:dom.scrl[0].offsetWidth-dom.scrl[0].clientWidth}
2110)
2111			return dom.back
2112		},
2113		close: function(){
2114			dom.foot.empty()
2115			dom.back.fadeOut(conf.speed);
2116			dom.focus.focus()
2117		},
2118		load: function(url,opts,event){
2119			if (event) dom.self = $(event.target)
2120			$.ajax({url:url,success:function(html){$.covr.open(html,opts)}})
2121			return false;
2122		},
2123		is: function(mode) {
2124			if (mode==1 || mode=="open")  return dom.back.is(":visible");
2125			if (mode==0 || mode=="close") return !dom.back.is(":visible");
2126		}
2127	}
2128	$.fn.covr = function(html,opts){return $.covr.open($(this))}
2129})(jQuery, this);
2130
2131/* tipr plugin */
2132;(function($, window){
2133	var dom  = {
2134		self: $(this),			// window or element which trigger the call to tipr
2135		main: $("<div/>",{class:"o-tipr"}),
2136		html: "Info",
2137		timeout: false,
2138		pos: {w:0,h:0,x:0,y:0,d:"nw"},				// position: width, height, x, y and direction
2139		all: "o-tipr-nw o-tipr-ne o-tipr-sw o-tipr-se"
2140	}
2141	var conf = { speed:100 }
2142	$("body").on("click.tipr",function(e){
2143		var target=$(e.target);
2144		if (!target.hasClass("o-tipr-main") && target.closest(".o-tipr-main").length==0) $.tipr.close()
2145	})
2146	$.tipr = {
2147		open: function(opts,event){
2148			if (event) dom.self = $(event.target)
2149			opts=$.extend(true,{},conf,opts)
2150			dom.html=dom.self.data("tipr-html")||dom.self.attr("title")
2151			dom.self.data("tipr-html",dom.html);dom.self.removeAttr("title")
2152			if ((""+dom.html).trim()=="") return 
2153			dom.pos.w=dom.self.outerWidth(); dom.pos.h=dom.self.outerHeight()
2154			dom.pos.x=dom.self.offset().left+dom.pos.w/2;dom.pos.y=dom.self.offset().top+dom.pos.h/2
2155			dom.pos.d=[(dom.pos.y>$("html").scrollTop()+$(window).height()/2)?"s":"n",(dom.pos.x>$("html").scrollLeft()+$(window).width()/2)?"e":"w"].join("")
2156			dom.main.empty().append(dom.html).appendTo($("body")).removeClass(dom.all).addClass("o-tipr-"+dom.pos.d).css({
2157				top: dom.pos.d[0]=="n"?dom.pos.y+dom.pos.h/2:"auto",
2158				left: dom.pos.d[1]=="w"?dom.pos.x:"auto",
2159				bottom: dom.pos.d[0]=="s"?$(window).height()-dom.pos.y+dom.pos.h/2:"auto",
2160				right: dom.pos.d[1]=="e"?$(window).width()-dom.pos.x:"auto"
2161			}).fadeIn(conf.speed)
2162			return dom.main
2163		},
2164		close: function(){
2165			dom.main.fadeOut(conf.speed);
2166		},
2167		delay: function(flag,opts){
2168			clearTimeout(dom.timeout)
2169			var func=function(){}
2170			if (flag=="on") func=function(){$.tipr.open(opts)}
2171			else func=function(){$.tipr.close()}
2172			dom.timeout = setTimeout(func,conf.speed)
2173		}
2174	}
2175	$.fn.tipr = function(opts){
2176		return this.each(function() {
2177			$(this).hover(
2178				function(){dom.self=$(this);$.tipr.delay("on")},
2179				function(){$.tipr.delay("off")}
2180			)
2181		})
2182	}
2183})(jQuery, this);
2184
2185// debounce and throttle prototypes
2186Function.prototype.debounce=function(delay, immediate) {
2187	// Ex: $input.on("keyup",function(){console.log(this.value)}.debounce(false)})
2188	// Ex: $input.on("change blur",function(){console.log(this.value)}.debounce(true)})
2189	// Warning: if immediate=true, the last event may not be fired!
2190	if (delay===true||delay===false) {immediate=delay;delay=false}
2191	var timeout,func=this;
2192	return function() {
2193		var context = this, args = arguments, delayed = function() {
2194			timeout = null;
2195			if (!immediate) func.apply(context, args);
2196		};
2197		var callNow = immediate && !timeout;
2198		clearTimeout(timeout);
2199		timeout = setTimeout(delayed, delay || 250);
2200		if (callNow) func.apply(context, args);
2201	};
2202};
2203Function.prototype.throttle = function(delay){
2204	// Ex: $(window).on("resize",function(){console.log($(this).width())}.throttle(250))
2205	var skip, func = this, clear = function() {
2206		skip = false;
2207	};
2208	return function() {
2209		if (!skip) {
2210			func.apply(this, arguments);
2211			setTimeout(clear, delay || 250);
2212			skip = true;
2213		};
2214	}
2215};
2216
2217var pipe={
2218	test: function() {
2219		console.log(pipe.gettype({test:1}),pipe.gettype(/$me^/))
2220		console.log(pipe.escape("this is a <i>italic</i>\n this is <bold>"))
2221		console.log(pipe.regsafe("c'est l'été!"))
2222		console.log(pipe.sanitize("Sanitize this àé$ string"))
2223		console.log(pipe.filesize(43274983))
2224	},
2225	gettype: function(obj){	// returns the type of the objet "boolean","number","string","array","object","regexp"
2226		return ({}).toString.call(obj).slice(8,-1).toLowerCase();
2227	},
2228	escape: function(s,br) {		// returns the xml escaped string of input ("<" becomes "&lt;"). Only 5 characters must be replaced - if br is not false, also replace "\n" with "<br />"
2229		return (""+s)
2230			.replace(pipe._escape[0], function (ch) {return pipe._escape[1][ch];})
2231			.replace((br===false)?pipe._escape[2]:pipe._escape[3],'<br />')
2232	},
2233	sanitize: function(s,lower) {	// returns a "sanitized" version of the string, ex: "This is an éà$" -> "this is an ea-"
2234		if (pipe.gettype(s)!="string") return "";
2235		if (lower) s=s.toLowerCase();
2236		return s.replace(pipe._sanitize[0],function(a){return pipe._sanitize[1][a.toLowerCase()]||"-"}).replace(pipe._sanitize[2],"-");
2237	},
2238	regsafe: function(s,check) {	// returns a RegExp with escaped's chars and make it case insentitive and accent insensitive, if check is true, the string s may be a regexp already -> check it!
2239		s=""+s
2240		var parts = s.split('/');
2241		if (check && parts.length > 1 && parts[0]=="") {
2242			try {return new RegExp(parts[1], parts[parts.length-1]);}
2243			catch(e) {return pipe.regsafe(s,false)}
2244		}
2245		// 1. remove all special meaning regexp char (. -> \.)
2246		s=s.replace(pipe._regsafe[0], "\\$1")
2247		// 2. replace all accent with non-accent (été -> ete)
2248		for (key in pipe._regsafe[1]) if (pipe._regsafe[1].hasOwnProperty(key)) s=s.replace(new RegExp(pipe._regsafe[1][key], 'g'),key)
2249		// 3. prepare new regexp, add key to regexp (e:[èéêë] -> e:[eèéêë])
2250		for (key in pipe._regsafe[1]) if (pipe._regsafe[1].hasOwnProperty(key)) pipe._regsafe[1][key]="[" + key + pipe._regsafe[1][key].slice(1);
2251		// 4. replace all possible accented char with regexp equivalent (ete -> [èéêë]t[eèéêë])
2252		for (key in pipe._regsafe[1]) if (pipe._regsafe[1].hasOwnProperty(key)) s=s.replace(new RegExp(key, 'g'),pipe._regsafe[1][key])
2253		return new RegExp(s,"gi")
2254	},
2255	filesize: function(n,arg) {
2256		arg=arg||pipe._filesize
2257		return function(a,b,c,d,e){return (a==0)?arg.zero:(b=Math,c=b.log,d=1024,e=b.min(c(a)/c(d),arg.suffix[1].length)|0,a/b.pow(d,e)).toFixed(arg.decimal)+arg.separator+(e?arg.suffix[1][--e]+'B':arg.suffix[0])}(n)
2258	},
2259	delimiter: function(number,chr) {			// 1000000.12345 -> 1'000'000.12345
2260		return number.toString().split(".").map(function(o,i){return (i>0)?o:o.replace(pipe._delimiter[0], "$1"+(chr||"'"))}).join(".")
2261	},
2262	pad: function(number,len,chr){
2263		var number=""+number,chr=chr||"0"
2264		while (number.length<len) number=chr+number
2265		return number;
2266	},
2267	date: {
2268		format: function(d,format){		// pipe.date.format(#date#,"DD.MM.YYYY")	=> "25.12.2016", allowed tag: Y,M,D,H,h,m,s
2269			var d=d||new Date(),self={Y:d.getFullYear(),M:d.getMonth()+1,D:d.getDate(),H:d.getHours(),h:d.getHours(),m:d.getMinutes(),s:d.getSeconds()}
2270			if (format===true) return self
2271			return (format||pipe._date.format||"DD.MM.YYYY").match(pipe._date.match)		// returns ["DD","HH","YYYY",...]
2272				.map(function(o){return self.hasOwnProperty(o[0])?pipe.pad(self[o[0]],o.length):o}).join("")
2273		},
2274		parse: function(s,format){
2275			var d={},format=format||pipe._date.parse||"DD.MM.YYYY hh:mm:ss"
2276			var m=format.match(pipe._date.match).filter(function(o){return "DMYHhms".indexOf(o[0])!=-1})
2277			s.match(/\d+/g).forEach(function(o,i){d[m[i][0]]=parseInt(o,10)})
2278			return new Date(d.Y||0,d.M-1||0,d.D||0,Math.min(d.H||d.h||0,24),Math.min(d.m||0,60),Math.min(d.s||0,60))
2279		}
2280	},
2281	cookie: {
2282		get:function (name) {
2283			var t = "; " + document.cookie,
2284			i = t.split("; " + name + "=");
2285			return 2 != i.length ? void 0 : i.pop().split(";").shift()
2286		},
2287		set:function (name, val, expires, domain, path) {
2288			var s = new Date;
2289			s.setDate(s.getDate() + (expires || 365));
2290			var r = [name + "=" + val, "expires=" + s.toUTCString(), "path=" + (path || "/")];
2291			domain && r.push("domain=" + domain),
2292			document.cookie = r.join(";")
2293		}
2294	},
2295	query: function (name,href) {		// pipe.query("safe.q") -> the query_string ?safe.q=test -> "test" (note: href may be $("body").data("href") in some case)
2296		var match = RegExp('[?&]' + name + '=([^&]*)').exec(href||$("body").data("href")||window.location.search);
2297		return match && decodeURIComponent(match[1].replace(/\+/g, ' '));
2298	},
2299	schr: function(n) {		// special characters - return &#n; - if n is a string, returns some prefefined char, ex: "euro", "nbsp"
2300		if (typeof n=="string") n={"euro":8364/*€*/,	"copy":169/*©*/,	"trade":174/*®*/,	"nbsp":160/*NonBreakingSpace= */,	"zwsp":8203/*ZeroWidthSpace*/}[n]||63/*?*/
2301		return String.fromCharCode(n);		// zero width space
2302	},
2303	dotget: function(obj, path, alt) { /* Search and return the nested property of an object, ex: var o={name:{first:"Jean",last:"Dupond"}} -> pipe.dotget(o,"name.first","Luc")=="Jean" */
2304		if (!obj) return alt;
2305		path = path.split('.');
2306		for (var i=0, maxi=path.length; i<maxi; i++) {
2307			if (!obj) return alt;
2308			if (obj.hasOwnProperty(path[i]))
2309				obj=obj[path[i]];
2310			else if (match = /(.+)\[(\d+)\]/.exec(path[i])) {
2311				obj = (obj.hasOwnProperty(match[1]))?obj[match[1]][parseInt(match[2])]:alt;
2312			} else obj=alt
2313		}
2314		return obj;
2315	},
2316
2317	_escape:[/[<>&"']/g,{'<': '&lt;', '>': '&gt;', '&': '&amp;', '"': '&quot;', "'": '&apos;'},/^[]/,/\r?\n/],
2318	_sanitize:[/[^a-zA-Z0-9\/]/g,{"á":"a","à":"a","â":"a","ç":"c","é":"e","è":"e","ê":"e","î":"i","ô":"o","ù":"u","û":"u"},/-+/g],
2319	_regsafe:[/([.*+?^=!:${}()|\[\]\/\\])/g,{a:"[àáâãäå]", c:"[ç]", e:"[èéêë]", i:"[ìíîï]", n:"[ñ]", o:"[òóôõö]", u:"[ùúûü]", y:"[ýÿ]", Y:"[ÝŸ]", A:"[ÀÁÂÃÄÅ]", C:"[Ç]", E:"[ÈÉÊË]", I:"[ÌÍÎÏ]", N:"[Ñ]", O:"[ÒÓÔÕÖ]", U:"[ÙÚÛÜ]"}],
2320	_filesize:{zero:"0 Byte",decimal:2,separator:" " /*Alt-0160*/,suffix:["Bytes","kMGTPEZY"]},
2321	_delimiter:[/(\d)(?=(\d{3})+(?!\d))/g],
2322	_date:{format:"DD.MM.YYYY",parse:"DD.MM.YYYY hh:mm:ss",match:/(Y+|M+|D+|H+|h+|m+|s+|[^YMDHhms]+)/g},
2323
2324	url:{
2325		queryvalue:function(name,url) {
2326			var match = RegExp('[?&]' + name + '=([^&]*)').exec(url||((window && window.location)?(window.location.search||window.location.href):""));
2327			return match && decodeURIComponent(match[1].replace(/\+/g, ' '));
2328		}
2329	},
2330	/* JSON extensions (to use with: JSON.stringify(obj,pipe.json.encode) and JSON.parse(obj,pip.json.decode)) : encode/decode functions and regexps
2331		Ex: JSON.stringify({r:/test/i,f:function(){a=2}},pipe.json.encode) -> '{"r":"__regexp /test/i","f":"__function (){a=2}"}"'                */
2332	json: {
2333		encode:function(key, value) {
2334			if (value instanceof RegExp) value="__regexp " + value.toString();
2335			if (value instanceof Function) value="__"+value.toString();
2336			return value;
2337		},
2338		decode:function(key, value) {
2339			if (typeof value=="string" && value.indexOf("__") == 0) {
2340				if (value.indexOf("__function")==0) value=eval("("+value.substring(2)+")")
2341				else if (value.indexOf("__regexp ") == 0) {
2342					var m = value.split("__regexp ")[1].match(/\/(.*)\/(.*)?/);
2343					if (m) value = new RegExp(m[1], m[2] || "");
2344				}
2345			}
2346			return value;
2347		},
2348		unflatten: function(data) {
2349			if (Object(data) !== data || Array.isArray(data))
2350				return data;
2351			var result = {}, cur, prop, idx, last, temp;
2352			for(var p in data) {
2353				cur = result, prop = "", last = 0;
2354				do {
2355					idx = p.indexOf(".", last);
2356					temp = p.substring(last, idx !== -1 ? idx : undefined);
2357					cur = cur[prop] || (cur[prop] = (!isNaN(parseInt(temp)) ? [] : {}));
2358					prop = temp;
2359					last = idx + 1;
2360				} while(idx >= 0);
2361				cur[prop] = data[p];
2362			}
2363			return result[""];
2364		},
2365		flatten: function(data,last) {
2366			// last: if key "last" exist and is string, return it directly, (used for language selection) ex:
2367			// flatten({first:{fr:"Premier",en:"First"},other:{fr:"Un",en:"One"}}) -> {"first.fr":"premier","first.en":"First","other.fr":"Un","other.en":One"}
2368			// flatten({first:{fr:"Premier",en:"First"},other:{fr:"Un",en:"One"}},"fr") -> {first:"premier",other:"Un"}
2369			var result = {};
2370			function recurse (cur, prop) {
2371				if (Object(cur) !== cur) {
2372					result[prop] = cur;
2373				} else if (Array.isArray(cur)) {
2374					 for(var i=0, l=cur.length; i<l; i++)
2375						 recurse(cur[i], prop ? prop+"."+i : ""+i);
2376					if (l == 0)
2377						result[prop] = [];
2378				} else {
2379					if( last && cur.hasOwnProperty(last) && typeof cur[last]=="string") {
2380						result[prop]=cur[last]; 
2381					} else {
2382						var isEmpty = true;
2383						for (var p in cur) {
2384							isEmpty = false;
2385							recurse(cur[p], prop ? prop+"."+p : p);
2386						}
2387						if (isEmpty)
2388							result[prop] = {};
2389					}
2390				}
2391			}
2392			recurse(data, "");
2393			return result;
2394		}
2395	},
2396
2397	/* reduce functions. Note: function(previousValue,currentValue,index,array) */
2398	max:function(p,c,i,a){	return (c>p)?c:p; },	// [1,2,3,4,5,1].reduce(pipe.max) => 5
2399	min:function(p,c,i,a){	return (c<p)?c:p; },	// [1,2,3,4,5,1].reduce(pipe.min) => 1
2400	sum:function(p,c,i,a){	return c+p; },			// [1,2,3,4,5,1].reduce(pipe.sum) => 16
2401	normalize:function(p,c,i,a){					// [1,2,3,4,5,1].reduce(pipe.normalize,[]) => [0.0625, 0.125, 0.1875, 0.25, 0.3125, 0.0625]
2402		if (i==1) {var s=a.reduce(pipe.sum);for (var i=0,l=a.length;i<l;i++) a[i]/=s;}
2403		return a
2404	},
2405	unique:function(p,c,i,a){						// ex: [1,2,3,4,5,3,2].reduce(pipe.unique,[]) => [1,2,3,4,5]
2406		if(p.indexOf(c)==-1) p.push(c);
2407		return p
2408	},
2409	shuffle:function(p,c,i,a){						// [1,2,3,4,5,6].reduce(pipe.shuffle,[]) => [5,3,2,1,6,4] (note: the real shuffle is done once on i==1, so not optimized but easy for chaining)
2410		if (i==1) for(var j, x, i = a.length; i; j = Math.floor(Math.random() * i), x = a[--i], a[i] = a[j], a[j] = x);
2411		return a
2412	}
2413}

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.