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