[PLUGINS] +crayons
[lhc/web/clavette_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).px('fontSize'), // eviter un bug MSIE sur fontSize
118 'class': me.className,
119 'color': $(this).css('color'),
120 'font-size': $(this).px('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 if ($.browser.msie) $(me).css({'zoom':1});
170 var pos = $(me).offset();
171 $('<div class="crayon-html" id="crayon_'+id_crayon+'"></div>')
172 .css({
173 'position':position,
174 'top':pos['top']-1,
175 'left':pos['left']-1
176 })
177 .appendTo('body')
178 .html(c.$html);
179 $(me)
180 .activatecrayon(percent);
181 // Si le crayon a une taille mini qui le fait deborder
182 // a droite de l'ecran, recadrer vers la gauche
183 var diff = $('#crayon_'+id_crayon).offset().left + $('#crayon_'+id_crayon).width() - $(window).width();
184 if (diff>0) {
185 $('#crayon_'+id_crayon)
186 .css({'left': parseInt(pos['left'])-diff});
187 }
188 }
189 );
190 }
191 });
192 };
193
194 // annule le crayon ouvert (fonction destructive)
195 $.fn.cancelcrayon = function() {
196 this
197 .filter('.crayon-has')
198 .css('visibility','visible')
199 .removeClass('crayon-has')
200 .removeClass('crayon-changed')
201 .crayon()
202 .remove();
203 return this;
204 };
205
206 // masque le crayon ouvert
207 $.fn.hidecrayon = function() {
208 this
209 .filter('.crayon-has')
210 .css('visibility','visible')
211 .crayon()
212 .hide()
213 .removeClass('crayon-hover');
214 return this;
215 };
216
217 // active un crayon qui vient d'etre charge
218 $.fn.activatecrayon = function(percent) {
219 var focus = false;
220 this
221 .crayon()
222 .click(function(e){
223 e.stopPropagation();
224 });
225 this
226 .each(function(){
227 var me = $(this);
228 var crayon = $(this).crayon();
229 crayon
230 .find('form')
231 .append(
232 $('<input type="hidden" name="self" />')
233 .attr('value',configCrayons.self)
234 )
235 .ajaxForm({
236 "dataType":"json",
237 "error": function(d) {
238 uniAlert('erreur de communication');
239 crayon
240 .empty()
241 .append(
242 $('<div class="error">')
243 .html(d.responseText || d.error || 'erreur inconnue')
244 )
245 .css({
246 background: 'white',
247 color: 'black',
248 width: '480px',
249 border: 'red solid 2px',
250 padding: '10px'}
251 );
252 },
253 "success": function(d) {
254 // parfois le JSON n'est pas renvoye sous forme d'objet
255 // mais d'une chaine encadree de <pre>...</pre>
256 if (typeof d == "string") {
257 try {
258 d = $.parseJSON(d.replace(/^<pre>/,'').replace(/<[/]pre>$/,''));
259 } catch(e) {
260 d = {'$erreur': 'erreur de communication :' + ' ' + e.message, '$html':''};
261 }
262 }
263 me
264 .find("em.crayon-searching")
265 .remove();
266
267 //Remise a zero des warnings invalides (unwrap)
268 crayon
269 .find("span.crayon-invalide p")
270 .remove();
271 crayon
272 .find("span.crayon-invalide")
273 .each(function(){
274 $(this).replaceWith( this.childNodes );
275 }
276 );
277
278 if(d.$invalides) {
279 for (invalide in d.$invalides) {
280 //Affichage des warnings invalides
281 d.$invalides[invalide]['retour']?retour=d.$invalides[invalide]['retour']:retour='';
282 d.$invalides[invalide]['msg']?msg=d.$invalides[invalide]['msg']:msg='';
283 crayon
284 .find("*[name='content_"+invalide+"']")
285 .wrap("<span class=\"crayon-invalide\"></span>")
286 .parent()
287 .append("<p>"
288 + retour
289 + " "
290 + msg
291 + "</p>"
292 );
293 }
294
295 }
296
297 if (d.$erreur > '') {
298 if (d.$annuler) {
299 if (d.$erreur > ' ') {
300 uniAlert(d.$erreur);
301 }
302 me
303 .cancelcrayon();
304 } else {
305 uniAlert(d.$erreur+'\n'+configCrayons.txt.error);
306 }
307 }
308
309 if (d.erreur > '' || d.$invalides) {
310 crayon
311 .find('form')
312 .css('opacity', 1.0)
313 .find(".crayon-boutons,.resizehandle")
314 .show()
315 .end()
316 .find('.crayon-searching')
317 .remove();
318 return false;
319 }
320 // Desactive celui pour qui on vient de recevoir les nouvelles donnees
321 $(me)
322 .cancelcrayon();
323 // Insere les donnees dans *tous* les elements ayant le meme code
324 var tous = $(
325 '.crayon.crayon-autorise.' +
326 me[0].className.match(/crayon ([^ ]+)/)[1]
327 )
328 .html(
329 d[$('input.crayon-id', crayon).val()]
330 )
331 .iconecrayon();
332 // Declencher le onAjaxLoad normal de SPIP
333 if (typeof jQuery.spip == 'object' && typeof jQuery.spip.triggerAjaxLoad == 'function') {
334 jQuery.spip.triggerAjaxLoad(tous.get());
335 }
336 // SPIP 2.x
337 else if (typeof triggerAjaxLoad == 'function') {
338 triggerAjaxLoad(tous.get());
339 }
340 }})
341 .bind('form-submit-validate',function(form,a, e, options, veto){
342 if(!veto.veto)
343 crayon
344 .find('form')
345 .css('opacity', 0.5)
346 .after(configCrayons.mkimg('searching')) // icone d'attente
347 .find(".crayon-boutons,.resizehandle")
348 .hide();
349 })
350 // keyup pour les input et textarea ...
351 .keyup(function(e){
352 crayon
353 .find(".crayon-boutons")
354 .show();
355 me
356 .addClass('crayon-changed');
357 e.cancelBubble = true; // ne pas remonter l'evenement vers la page
358 })
359 // ... change pour les select : ici on submit direct, pourquoi pas
360 .change(function(e){
361 crayon
362 .find(".crayon-boutons")
363 .show();
364 me
365 .addClass('crayon-changed');
366 e.cancelBubble = true;
367 })
368 .keypress(function(e){
369 e.cancelBubble = true;
370 })
371 // focus par defaut (crayons sans textarea/text, mais uniquement menus ou fichiers)
372 .find('input:visible:not(:disabled):not([readonly]):first').focus().end()
373 .find("textarea.crayon-active,input.crayon-active[type=text]")
374 .each(function(n){
375 // focus pour commencer a taper son texte directement dans le champ
376 // sur le premier textarea non readonly ni disabled
377 // on essaie de positionner la selection (la saisie) au niveau du clic
378 // ne pas le faire sur un input de [type=file]
379 if (n==0) {
380 if(!$(this).is(':disabled, [readonly]')){
381 this.focus();
382 focus = true;
383 }
384 // premiere approximation, en fonction de la hauteur du clic
385 var position = parseInt(percent * this.textLength);
386 this.selectionStart=position;
387 this.selectionEnd=position;
388 }else if(!focus && !$(this).is(':disabled, [readonly]'))
389 this.focus();
390 })
391 .end()
392 .keydown(function(e){
393 if(!e.charCode && e.keyCode == 119 /* F8, windows */) {
394 crayon
395 .find("form.formulaire_crayon")
396 .submit();
397 }
398 if (e.keyCode == 27) { /* esc */
399 me
400 .cancelcrayon();
401 }
402 })
403 .keypress(function(e){
404 // Clavier pour sauver
405 if (
406 (e.ctrlKey && (
407 /* ctrl-s ou ctrl-maj-S, firefox */
408 ((e.charCode||e.keyCode) == 115) || ((e.charCode||e.keyCode) == 83))
409 /* ctrl-s, safari */
410 || (e.charCode==19 && e.keyCode==19)
411 ) ||
412 (
413 e.shiftKey && (e.keyCode == 13) /* shift-return */
414 )
415 ) {
416 crayon
417 .find("form.formulaire_crayon")
418 .submit();
419 }
420 var maxh = this.className.match(/\bmaxheight(\d+)?\b/);
421 if (maxh) {
422 maxh = maxh[1] ? parseInt(maxh[1]) : 200;
423 maxh = this.scrollHeight < maxh ? this.scrollHeight : maxh;
424 if (maxh > this.clientHeight) {
425 $(this).css('height', maxh + 'px');
426 }
427 }
428 })
429 .find(".crayon-submit")
430 .click(function(e){
431 e.stopPropagation();
432 $(this)
433 .parents("form:eq(0)")
434 .submit();
435 })
436 .end()
437 .find(".crayon-cancel")
438 .click(function(e){
439 e.stopPropagation();
440 me
441 .cancelcrayon();
442 })
443 .end()
444 // decaler verticalement si la fenetre d'edition n'est pas visible
445 .each(function(){
446 var offset = $(this).offset();
447 var hauteur = parseInt($(this).css('height'));
448 var scrolltop = $(window).scrollTop();
449 var h = $(window).height();
450 if (offset['top'] - 5 <= scrolltop)
451 $(window).scrollTop(offset['top'] - 5);
452 else if (offset['top'] + hauteur - h + 20 > scrolltop)
453 $(window).scrollTop(offset['top'] + hauteur - h + 30);
454 // Si c'est textarea, on essaie de caler verticalement son contenu
455 // et on lui ajoute un resizehandle
456 $("textarea", this)
457 .each(function(){
458 if (percent && this.scrollHeight > hauteur) {
459 this.scrollTop = this.scrollHeight * percent - hauteur;
460 }
461 })
462 .resizehandle()
463 // decaler les boutons qui suivent un resizer de 16px vers le haut
464 .next('.resizehandle')
465 .next('.crayon-boutons')
466 .addClass('resizehandle_boutons');
467 })
468 .end();
469 // Declencher le onAjaxLoad normal de SPIP
470 // (apres donc le chargement de la page de saisie (controleur))
471 if (typeof jQuery.spip == 'object' && typeof jQuery.spip.triggerAjaxLoad == 'function') {
472 jQuery.spip.triggerAjaxLoad(crayon.get());
473 }
474 // SPIP 2.x
475 else if (typeof triggerAjaxLoad == 'function') {
476 triggerAjaxLoad(crayon.get());
477 }
478 });
479 };
480
481 // insere les icones et le type de crayon (optionnel) dans l'element
482 $.fn.iconecrayon = function(){
483 return this.each(function() {
484 var ctype = this.className.match(/\b[^-]type_(\w+)\b/);
485 type = (ctype) ? ctype[1] : 'crayon';
486 if (ctype) this.type = type; // Affecte son type a l'objet crayon
487 $(this).prepend(configCrayons.iconclick(this.className, type))
488 .find('.crayon-' + type + ', .crayon-img-changed') // le crayon a clicker lui-meme et sa memoire
489 .click(function(e){
490 $(this).parents('.crayon:eq(0)').opencrayon(e);
491 });
492 });
493 };
494
495 // initialise les crayons
496 $.fn.initcrayon = function(){
497 var editme = function(e){
498 timeme=null;
499 $(this).opencrayon(e,
500 // calcul du "percent" du click par rapport a la hauteur totale du div
501 ((e.pageY ? e.pageY : e.clientY) - document.body.scrollTop - this.offsetTop)
502 / this.clientHeight);
503 };
504 var timeme;
505 this
506 .addClass('crayon-autorise')
507 .dblclick(editme)
508 .bind("touchstart",function(e){var me=this;timeme=setTimeout(function(){editme.apply(me,[e]);},800);})
509 .bind("touchend",function(e){if (timeme) {clearTimeout(timeme);timeme=null;}})
510 .iconecrayon()
511 .hover( // :hover pour MSIE
512 function(){
513 $(this)
514 .addClass('crayon-hover')
515 .find('>span.crayon-icones')
516 .find('>span>em.crayon-' + (this.type||'crayon') + ',>span>em.crayon-edit')
517 .show();//'visibility','visible');
518 },function(){
519 $(this)
520 .removeClass('crayon-hover')
521 .find('>span.crayon-icones')
522 .find('>span>em.crayon-' + (this.type||'crayon') + ',>span>em.crayon-edit')
523 .hide();//('visibility','hidden');
524 }
525 );
526 return this;
527 };
528
529 // demarrage
530 $.fn.crayonsstart = function() {
531 if (!configCrayons.droits) return;
532 id_crayon = 0; // global
533
534 // sortie, demander pour sauvegarde si oubli
535 if (configCrayons.txt.sauvegarder) {
536 $(window).unload(function(e) {
537 var chg = $(".crayon-changed");
538 if (chg.length && uniConfirm(configCrayons.txt.sauvegarder)) {
539 chg.crayon().find('form').submit();
540 }
541 });
542 }
543
544 // demarrer les crayons
545 if ((typeof crayons_init_dynamique == 'undefined') || (crayons_init_dynamique==false)) {
546
547 // compat jQuery 1.9
548 if (typeof $.fn.live == 'undefined') {
549 $.fn.live = function( types, data, fn ) {
550 $( this.context ).on( types, this.selector, data, fn );
551 return this;
552 };
553 }
554 $('.crayon:not(.crayon-init)')
555 .live('mouseover touchstart', function(e) {
556 $(this)
557 .addClass('crayon-init')
558 .filter(configCrayons.droits)
559 .initcrayon()
560 .trigger('mouseover');
561 if (e.type=='touchstart')
562 $(this).trigger('touchstart');
563 });
564 }
565
566 // un clic en dehors ferme tous les crayons ouverts ?
567 if (configCrayons.cfg.clickhide)
568 $("html")
569 .click(function(){
570 $('.crayon-has')
571 .hidecrayon();
572 });
573 };
574
575 })(jQuery);