713b8acb69d14fe6d44bb3a8e4f84720575cc6cc
[lhc/web/www.git] / www / plugins / crayons / js / crayons.js
1 (function($){
2 /*
3 * crayons.js (c) Fil, toggg, 2006-2013 -- licence GPL
4 */
5
6 // le prototype configuration de Crayons
7 $.prototype.cfgCrayons = function (options) {
8 this.url_crayons_html = '?action=crayons_html';
9 this.img = {
10 'searching':{'txt':'En attente du serveur ...'},
11 'edit':{'txt':'Editer'},
12 'img-changed':{'txt':'Deja modifie'}
13 };
14 this.txt = {
15 };
16 for (opt in options) {
17 this[opt] = options[opt];
18 }
19 };
20
21 $.prototype.cfgCrayons.prototype.mkimg = function(what, extra) {
22 var txt = this.img[what] ? this.img[what].txt : this.img['crayon'].txt;
23 return '<em class="crayon-'+what+'" title="'+ txt + (extra ? extra : '') + '"></em>';
24 };
25
26 $.prototype.cfgCrayons.prototype.iconclick = function(c, type) {
27
28 // le + qui passe en prive pour editer tout si classe type--id
29 var link = c.match(/\b(\w+)--(\d+)\b/);
30 link = link ?
31 '<a href="ecrire/?exec=' + link[1] + 's_edit&id_' + link[1] + '=' + link[2] +
32 '">' + this.mkimg('edit', ' (' + link[1] + ' ' + link[2] + ')') + '</a>' : '';
33
34 // on recherche une class du type type-champ-id
35 // comme article-texte-10 pour le texte de l'article 10
36 // ou meta-valeur-meta
37 var cray =
38 c.match(/\b\w+-(\w+)-\d(?:-\w+)+\b/) // numeros_lien-type-2-3-article (table-champ-cles)
39 || c.match(/\b\w+-(\w+)-\d+\b/) // article-texte-10 (inclu dans le precedent, mais bon)
40 || c.match(/\b\meta-valeur-(\w+)\b/) // meta-valeur-xx
41 ;
42
43 var boite = !cray ? '' : this.mkimg(type, ' (' + cray[1] + ')');
44
45 return "<span class='crayon-icones'><span>" + boite +
46 this.mkimg('img-changed', cray ? ' (' + cray[1] + ')': '') +
47 link +"</span></span>";
48 };
49
50 function entity2unicode(txt)
51 {
52 var reg = txt.split(/&#(\d+);/i);
53 for (var i = 1; i < reg.length; i+=2) {
54 reg[i] = String.fromCharCode(parseInt(reg[i]));
55 }
56 return reg.join('');
57 };
58
59 function uniAlert(txt)
60 {
61 alert(entity2unicode(txt));
62 };
63
64 function uniConfirm(txt)
65 {
66 return confirm(entity2unicode(txt));
67 };
68
69 // donne le crayon d'un element
70 $.fn.crayon = function(){
71 if (this.length)
72 return $(
73 $.map(this, function(a){
74 return '#'+($(a).find('.crayon-icones').attr('rel'));
75 })
76 .join(','));
77 else
78 return $([]);
79 };
80
81 // ouvre un crayon
82 $.fn.opencrayon = function(evt, percent) {
83 if (evt && evt.stopPropagation) {
84 evt.stopPropagation();
85 }
86 return this
87 .each(function(){
88 // verifier que je suis un crayon
89 if (!$(this).is('.crayon'))
90 return;
91
92 // voir si je dispose deja du crayon comme voisin
93 if ($(this).is('.crayon-has')) {
94 $(this)
95 .css('visibility','hidden')
96 .crayon()
97 .show();
98 }
99 // sinon charger le formulaire
100 else {
101 // sauf si je suis deja en train de le charger (lock)
102 if ($(this).find("em.crayon-searching").length) {
103 return;
104 }
105 $(this)
106 .find('>span.crayon-icones span')
107 .append(configCrayons.mkimg('searching')); // icone d'attente
108 var me=this;
109 var offset = $(this).offset();
110 var params = {
111 'top': offset.top,
112 'left': offset.left,
113 'w': $(this).width(),
114 'h': $(this).height(),
115 'ww': (window.innerWidth ? window.innerWidth : (document.documentElement.clientWidth ? document.documentElement.clientWidth : document.body.offsetWidth)),
116 'wh': (window.innerHeight ? window.innerHeight : (document.documentElement.clientHeight ? document.documentElement.clientHeight : document.body.offsetHeight)),
117 'em': $(this).css('fontSize'), // Bug de jquery resolu : http://bugs.jquery.com/ticket/760
118 'class': me.className,
119 'color': $(this).css('color'),
120 'font-size': $(this).css('fontSize'),
121 'font-family': $(this).css('fontFamily'),
122 'font-weight': $(this).css('fontWeight'),
123 'line-height': $(this).css('lineHeight'),
124 'min-height': $(this).css('lineHeight'),
125 'text-align': $(this).css('textAlign'),
126 'background-color': $(this).css('backgroundColor'),
127 'self': configCrayons.self
128 };
129 if (me.type) params.type = me.type;
130 if (params['background-color'] == 'transparent'
131 || params['background-color'] == 'rgba(0, 0, 0, 0)') {
132 $(me).parents()
133 .each(function(){
134 var bg = $(this).css('backgroundColor');
135 if (bg != 'transparent'
136 && (params['background-color'] == 'transparent'
137 || params['background-color'] == 'rgba(0, 0, 0, 0)'))
138 params['background-color'] = bg;
139 });
140 }
141 $.post(configCrayons.url_crayons_html,
142 params,
143 function (c) {
144 try {
145 c = $.parseJSON(c);
146 } catch(e) {
147 c = {'$erreur': 'erreur de communication :' + ' ' + e.message, '$html':''};
148 }
149 $(me)
150 .find("em.crayon-searching")
151 .remove();
152 if (c.$erreur) {
153 uniAlert(c.$erreur);
154 return false;
155 }
156 id_crayon++;
157
158 var position = 'absolute';
159 $(me).parents().each(function(){
160 if($(this).css("position") == "fixed")
161 position = 'fixed';
162 });
163
164 $(me)
165 .css('visibility','hidden')
166 .addClass('crayon-has')
167 .find('>.crayon-icones')
168 .attr('rel','crayon_'+id_crayon);
169 // Detection IE sur sa capacite a gerer zoom :
170 // http://www.sitepoint.com/detect-css3-property-browser-support/
171 if (document.createElement("detect").style.zoom === "") {
172 $(me).css({'zoom':1});
173 }
174 var pos = $(me).offset();
175 $('<div class="crayon-html" id="crayon_'+id_crayon+'"></div>')
176 .css({
177 'position':position,
178 'top':pos['top']-1,
179 'left':pos['left']-1
180 })
181 .appendTo('body')
182 .html(c.$html);
183 $(me)
184 .activatecrayon(percent);
185 // Si le crayon a une taille mini qui le fait deborder
186 // a droite de l'ecran, recadrer vers la gauche
187 var diff = $('#crayon_'+id_crayon).offset().left + $('#crayon_'+id_crayon).width() - $(window).width();
188 if (diff>0) {
189 $('#crayon_'+id_crayon)
190 .css({'left': parseInt(pos['left'])-diff});
191 }
192 }
193 );
194 }
195 });
196 };
197
198 // annule le crayon ouvert (fonction destructive)
199 $.fn.cancelcrayon = function() {
200 this
201 .filter('.crayon-has')
202 .css('visibility','visible')
203 .removeClass('crayon-has')
204 .removeClass('crayon-changed')
205 .crayon()
206 .remove();
207 return this;
208 };
209
210 // masque le crayon ouvert
211 $.fn.hidecrayon = function() {
212 this
213 .filter('.crayon-has')
214 .css('visibility','visible')
215 .crayon()
216 .hide()
217 .removeClass('crayon-hover');
218 return this;
219 };
220
221 // active un crayon qui vient d'etre charge
222 $.fn.activatecrayon = function(percent) {
223 var focus = false;
224 this
225 .crayon()
226 .click(function(e){
227 e.stopPropagation();
228 });
229 this
230 .each(function(){
231 var me = $(this);
232 var crayon = $(this).crayon();
233 crayon
234 .find('form')
235 .append(
236 $('<input type="hidden" name="self" />')
237 .attr('value',configCrayons.self)
238 )
239 .ajaxForm({
240 "dataType":"json",
241 "error": function(d) {
242 uniAlert('erreur de communication');
243 crayon
244 .empty()
245 .append(
246 $('<div class="error">')
247 .html(d.responseText || d.error || 'erreur inconnue')
248 )
249 .css({
250 background: 'white',
251 color: 'black',
252 width: '480px',
253 border: 'red solid 2px',
254 padding: '10px'}
255 );
256 },
257 "success": function(d) {
258 // parfois le JSON n'est pas renvoye sous forme d'objet
259 // mais d'une chaine encadree de <pre>...</pre>
260 if (typeof d == "string") {
261 try {
262 d = $.parseJSON(d.replace(/^<pre>/,'').replace(/<[/]pre>$/,''));
263 } catch(e) {
264 d = {'$erreur': 'erreur de communication :' + ' ' + e.message, '$html':''};
265 }
266 }
267 me
268 .find("em.crayon-searching")
269 .remove();
270
271 //Remise a zero des warnings invalides (unwrap)
272 crayon
273 .find("span.crayon-invalide p")
274 .remove();
275 crayon
276 .find("span.crayon-invalide")
277 .each(function(){
278 $(this).replaceWith( this.childNodes );
279 }
280 );
281
282 if(d.$invalides) {
283 for (invalide in d.$invalides) {
284 //Affichage des warnings invalides
285 d.$invalides[invalide]['retour']?retour=d.$invalides[invalide]['retour']:retour='';
286 d.$invalides[invalide]['msg']?msg=d.$invalides[invalide]['msg']:msg='';
287 crayon
288 .find("*[name='content_"+invalide+"']")
289 .wrap("<span class=\"crayon-invalide\"></span>")
290 .parent()
291 .append("<p>"
292 + retour
293 + " "
294 + msg
295 + "</p>"
296 );
297 }
298
299 }
300
301 if (d.$erreur > '') {
302 if (d.$annuler) {
303 if (d.$erreur > ' ') {
304 uniAlert(d.$erreur);
305 }
306 me
307 .cancelcrayon();
308 } else {
309 uniAlert(d.$erreur+'\n'+configCrayons.txt.error);
310 }
311 }
312
313 if (d.erreur > '' || d.$invalides) {
314 crayon
315 .find('form')
316 .css('opacity', 1.0)
317 .find(".crayon-boutons,.resizehandle")
318 .show()
319 .end()
320 .find('.crayon-searching')
321 .remove();
322 return false;
323 }
324 // Desactive celui pour qui on vient de recevoir les nouvelles donnees
325 $(me)
326 .cancelcrayon();
327 // Insere les donnees dans *tous* les elements ayant le meme code
328 var tous = $(
329 '.crayon.crayon-autorise.' +
330 me[0].className.match(/crayon ([^ ]+)/)[1]
331 )
332 .html(
333 d[$('input.crayon-id', crayon).val()]
334 )
335 .iconecrayon();
336
337 // Invalider des préchargements ajax
338 if (typeof jQuery.spip == 'object' && typeof jQuery.spip.preloaded_urls == 'object') {
339 jQuery.spip.preloaded_urls = {};
340 }
341
342 // Declencher le onAjaxLoad normal de SPIP
343 if (typeof jQuery.spip == 'object' && typeof jQuery.spip.triggerAjaxLoad == 'function') {
344 jQuery.spip.triggerAjaxLoad(tous.get());
345 }
346 // SPIP 2.x
347 else if (typeof triggerAjaxLoad == 'function') {
348 triggerAjaxLoad(tous.get());
349 }
350 }})
351 .bind('form-submit-validate',function(form,a, e, options, veto){
352 if(!veto.veto)
353 crayon
354 .find('form')
355 .css('opacity', 0.5)
356 .after(configCrayons.mkimg('searching')) // icone d'attente
357 .find(".crayon-boutons,.resizehandle")
358 .hide();
359 })
360 // keyup pour les input et textarea ...
361 .keyup(function(e){
362 crayon
363 .find(".crayon-boutons")
364 .show();
365 me
366 .addClass('crayon-changed');
367 e.cancelBubble = true; // ne pas remonter l'evenement vers la page
368 })
369 // ... change pour les select : ici on submit direct, pourquoi pas
370 .change(function(e){
371 crayon
372 .find(".crayon-boutons")
373 .show();
374 me
375 .addClass('crayon-changed');
376 e.cancelBubble = true;
377 })
378 .keypress(function(e){
379 e.cancelBubble = true;
380 })
381 // focus par defaut (crayons sans textarea/text, mais uniquement menus ou fichiers)
382 .find('input:visible:not(:disabled):not([readonly]):first').focus().end()
383 .find("textarea.crayon-active,input.crayon-active[type=text]")
384 .each(function(n){
385 // focus pour commencer a taper son texte directement dans le champ
386 // sur le premier textarea non readonly ni disabled
387 // on essaie de positionner la selection (la saisie) au niveau du clic
388 // ne pas le faire sur un input de [type=file]
389 if (n==0) {
390 if(!$(this).is(':disabled, [readonly]')){
391 this.focus();
392 focus = true;
393 }
394 // premiere approximation, en fonction de la hauteur du clic
395 var position = parseInt(percent * this.textLength);
396 this.selectionStart=position;
397 this.selectionEnd=position;
398 }else if(!focus && !$(this).is(':disabled, [readonly]'))
399 this.focus();
400 })
401 .end()
402 .keydown(function(e){
403 if(!e.charCode && e.keyCode == 119 /* F8, windows */) {
404 crayon
405 .find("form.formulaire_crayon")
406 .submit();
407 }
408 if (e.keyCode == 27) { /* esc */
409 me
410 .cancelcrayon();
411 }
412 })
413 .keypress(function(e){
414 // Clavier pour sauver
415 if (
416 (e.ctrlKey && (
417 /* ctrl-s ou ctrl-maj-S, firefox */
418 ((e.charCode||e.keyCode) == 115) || ((e.charCode||e.keyCode) == 83))
419 /* ctrl-s, safari */
420 || (e.charCode==19 && e.keyCode==19)
421 ) ||
422 (
423 e.shiftKey && (e.keyCode == 13) /* shift-return */
424 )
425 ) {
426 crayon
427 .find("form.formulaire_crayon")
428 .submit();
429 }
430 var maxh = this.className.match(/\bmaxheight(\d+)?\b/);
431 if (maxh) {
432 maxh = maxh[1] ? parseInt(maxh[1]) : 200;
433 maxh = this.scrollHeight < maxh ? this.scrollHeight : maxh;
434 if (maxh > this.clientHeight) {
435 $(this).css('height', maxh + 'px');
436 }
437 }
438 })
439 .find(".crayon-submit")
440 .click(function(e){
441 e.stopPropagation();
442 $(this)
443 .parents("form:eq(0)")
444 .submit();
445 })
446 .end()
447 .find(".crayon-cancel")
448 .click(function(e){
449 e.stopPropagation();
450 me
451 .cancelcrayon();
452 })
453 .end()
454 // decaler verticalement si la fenetre d'edition n'est pas visible
455 .each(function(){
456 var offset = $(this).offset();
457 var hauteur = parseInt($(this).css('height'));
458 var scrolltop = $(window).scrollTop();
459 var h = $(window).height();
460 if (offset['top'] - 5 <= scrolltop)
461 $(window).scrollTop(offset['top'] - 5);
462 else if (offset['top'] + hauteur - h + 20 > scrolltop)
463 $(window).scrollTop(offset['top'] + hauteur - h + 30);
464 // Si c'est textarea, on essaie de caler verticalement son contenu
465 // et on lui ajoute un resizehandle
466 $("textarea", this)
467 .each(function(){
468 if (percent && this.scrollHeight > hauteur) {
469 this.scrollTop = this.scrollHeight * percent - hauteur;
470 }
471 })
472 .resizehandle()
473 // decaler les boutons qui suivent un resizer de 16px vers le haut
474 .next('.resizehandle')
475 .next('.crayon-boutons')
476 .addClass('resizehandle_boutons');
477 })
478 .end();
479 // Declencher le onAjaxLoad normal de SPIP
480 // (apres donc le chargement de la page de saisie (controleur))
481 if (typeof jQuery.spip == 'object' && typeof jQuery.spip.triggerAjaxLoad == 'function') {
482 jQuery.spip.triggerAjaxLoad(crayon.get());
483 }
484 // SPIP 2.x
485 else if (typeof triggerAjaxLoad == 'function') {
486 triggerAjaxLoad(crayon.get());
487 }
488 });
489 };
490
491 // insere les icones et le type de crayon (optionnel) dans l'element
492 $.fn.iconecrayon = function(){
493 return this.each(function() {
494 var ctype = this.className.match(/\b[^-]type_(\w+)\b/);
495 type = (ctype) ? ctype[1] : 'crayon';
496 if (ctype) this.type = type; // Affecte son type a l'objet crayon
497 $(this).prepend(configCrayons.iconclick(this.className, type))
498 .find('.crayon-' + type + ', .crayon-img-changed') // le crayon a clicker lui-meme et sa memoire
499 .click(function(e){
500 $(this).parents('.crayon:eq(0)').opencrayon(e);
501 });
502 });
503 };
504
505 // initialise les crayons
506 $.fn.initcrayon = function(){
507 var editme = function(e){
508 timeme=null;
509 $(this).opencrayon(e,
510 // calcul du "percent" du click par rapport a la hauteur totale du div
511 ((e.pageY ? e.pageY : e.clientY) - document.body.scrollTop - this.offsetTop)
512 / this.clientHeight);
513 };
514 var timeme;
515 this
516 .addClass('crayon-autorise')
517 .dblclick(editme)
518 .bind("touchstart",function(e){var me=this;timeme=setTimeout(function(){editme.apply(me,[e]);},800);})
519 .bind("touchend",function(e){if (timeme) {clearTimeout(timeme);timeme=null;}})
520 .iconecrayon()
521 .hover( // :hover pour MSIE
522 function(){
523 $(this)
524 .addClass('crayon-hover')
525 .find('>span.crayon-icones')
526 .find('>span>em.crayon-' + (this.type||'crayon') + ',>span>em.crayon-edit')
527 .show();//'visibility','visible');
528 },function(){
529 $(this)
530 .removeClass('crayon-hover')
531 .find('>span.crayon-icones')
532 .find('>span>em.crayon-' + (this.type||'crayon') + ',>span>em.crayon-edit')
533 .hide();//('visibility','hidden');
534 }
535 );
536 return this;
537 };
538
539 // demarrage
540 $.fn.crayonsstart = function() {
541 if (!configCrayons.droits) return;
542 id_crayon = 0; // global
543
544 // sortie, demander pour sauvegarde si oubli
545 if (configCrayons.txt.sauvegarder) {
546 $(window).unload(function(e) {
547 var chg = $(".crayon-changed");
548 if (chg.length && uniConfirm(configCrayons.txt.sauvegarder)) {
549 chg.crayon().find('form').submit();
550 }
551 });
552 }
553
554 // demarrer les crayons
555 if ((typeof crayons_init_dynamique == 'undefined') || (crayons_init_dynamique==false)) {
556
557 // compat jQuery 1.9
558 if (typeof $.fn.live == 'undefined') {
559 $.fn.live = function( types, data, fn ) {
560 $( this.context ).on( types, this.selector, data, fn );
561 return this;
562 };
563 }
564 $('.crayon:not(.crayon-init)')
565 .live('mouseover touchstart', function(e) {
566 $(this)
567 .addClass('crayon-init')
568 .filter(configCrayons.droits)
569 .initcrayon()
570 .trigger('mouseover');
571 if (e.type=='touchstart')
572 $(this).trigger('touchstart');
573 });
574 }
575
576 // un clic en dehors ferme tous les crayons ouverts ?
577 if (configCrayons.cfg.clickhide)
578 $("html")
579 .click(function(){
580 $('.crayon-has')
581 .hidecrayon();
582 });
583 };
584
585 })(jQuery);