3 * crayons.js (c) Fil, toggg, 2006-2013 -- licence GPL
6 // le prototype configuration de Crayons
7 $.prototype.cfgCrayons = function (options
) {
8 this.url_crayons_html
= '?action=crayons_html';
10 'searching':{'txt':'En attente du serveur ...'},
11 'edit':{'txt':'Editer'},
12 'img-changed':{'txt':'Deja modifie'}
16 for (opt
in options
) {
17 this[opt
] = options
[opt
];
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>';
26 $.prototype.cfgCrayons
.prototype.iconclick = function(c
, type
) {
28 // le + qui passe en prive pour editer tout si classe type--id
29 var link
= c
.match(/\b(\w+)--(\d+)\b/);
31 '<a href="ecrire/?exec=' + link
[1] + 's_edit&id_' + link
[1] + '=' + link
[2] +
32 '">' + this.mkimg('edit', ' (' + link
[1] + ' ' + link
[2] + ')') + '</a>' : '';
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
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
43 var boite
= !cray
? '' : this.mkimg(type
, ' (' + cray
[1] + ')');
45 return "<span class='crayon-icones'><span>" + boite
+
46 this.mkimg('img-changed', cray
? ' (' + cray
[1] + ')': '') +
47 link
+"</span></span>";
50 function entity2unicode(txt
)
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
]));
59 function uniAlert(txt
)
61 alert(entity2unicode(txt
));
64 function uniConfirm(txt
)
66 return confirm(entity2unicode(txt
));
69 // donne le crayon d'un element
70 $.fn
.crayon = function(){
73 $.map(this, function(a
){
74 return '#'+($(a
).find('.crayon-icones').attr('rel'));
82 $.fn
.opencrayon = function(evt
, percent
) {
83 if (evt
&& evt
.stopPropagation
) {
84 evt
.stopPropagation();
88 // verifier que je suis un crayon
89 if (!$(this).is('.crayon'))
92 // voir si je dispose deja du crayon comme voisin
93 if ($(this).is('.crayon-has')) {
95 .css('visibility','hidden')
99 // sinon charger le formulaire
101 // sauf si je suis deja en train de le charger (lock)
102 if ($(this).find("em.crayon-searching").length
) {
106 .find('>span.crayon-icones span')
107 .append(configCrayons
.mkimg('searching')); // icone d'attente
109 var offset
= $(this).offset();
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
129 if (me
.type
) params
.type
= me
.type
;
130 if (params
['background-color'] == 'transparent'
131 || params
['background-color'] == 'rgba(0, 0, 0, 0)') {
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
;
141 $.post(configCrayons
.url_crayons_html
,
147 c
= {'$erreur': 'erreur de communication :' + ' ' + e
.message
, '$html':''};
150 .find("em.crayon-searching")
158 var position
= 'absolute';
159 $(me
).parents().each(function(){
160 if($(this).css("position") == "fixed")
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>')
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();
185 $('#crayon_'+id_crayon
)
186 .css({'left': parseInt(pos
['left'])-diff
});
194 // annule le crayon ouvert (fonction destructive)
195 $.fn
.cancelcrayon = function() {
197 .filter('.crayon-has')
198 .css('visibility','visible')
199 .removeClass('crayon-has')
200 .removeClass('crayon-changed')
206 // masque le crayon ouvert
207 $.fn
.hidecrayon = function() {
209 .filter('.crayon-has')
210 .css('visibility','visible')
213 .removeClass('crayon-hover');
217 // active un crayon qui vient d'etre charge
218 $.fn
.activatecrayon = function(percent
) {
227 var crayon
= $(this).crayon();
231 $('<input type="hidden" name="self" />')
232 .attr('value',configCrayons
.self
)
236 "error": function(d
) {
237 uniAlert('erreur de communication');
241 $('<div class="error">')
242 .html(d
.responseText
|| d
.error
|| 'erreur inconnue')
248 border
: 'red solid 2px',
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") {
257 d
= $.parseJSON(d
.replace(/^<pre>/,'').replace(/<[/]pre
>$/,''));
259 d
= {'$erreur': 'erreur de communication :' + ' ' + e
.message
, '$html':''};
263 .find("em.crayon-searching")
266 //Remise a zero des warnings invalides (unwrap)
268 .find("span.crayon-invalide p")
271 .find("span.crayon-invalide")
273 $(this).replaceWith( this.childNodes
);
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
='';
283 .find("*[name='content_"+invalide
+"']")
284 .wrap("<span class=\"crayon-invalide\"></span>")
296 if (d
.$erreur
> '') {
298 if (d
.$erreur
> ' ') {
304 uniAlert(d
.$erreur
+'\n'+configCrayons
.txt
.error
);
308 if (d
.erreur
> '' || d
.$invalides
) {
312 .find(".crayon-boutons,.resizehandle")
315 .find('.crayon-searching')
319 // Desactive celui pour qui on vient de recevoir les nouvelles donnees
322 // Insere les donnees dans *tous* les elements ayant le meme code
324 '.crayon.crayon-autorise.' +
325 me
[0].className
.match(/crayon ([^ ]+)/)[1]
328 d
[$('input.crayon-id', crayon
).val()]
331 // Declencher le onAjaxLoad normal de SPIP
332 if (typeof jQuery
.spip
== 'object' && typeof jQuery
.spip
.triggerAjaxLoad
== 'function') {
333 jQuery
.spip
.triggerAjaxLoad(tous
.get());
336 else if (typeof triggerAjaxLoad
== 'function') {
337 triggerAjaxLoad(tous
.get());
340 .bind('form-submit-validate',function(form
,a
, e
, options
, veto
){
345 .after(configCrayons
.mkimg('searching')) // icone d'attente
346 .find(".crayon-boutons,.resizehandle")
349 // keyup pour les input et textarea ...
352 .find(".crayon-boutons")
355 .addClass('crayon-changed');
356 e
.cancelBubble
= true; // ne pas remonter l'evenement vers la page
358 // ... change pour les select : ici on submit direct, pourquoi pas
361 .find(".crayon-boutons")
364 .addClass('crayon-changed');
365 e
.cancelBubble
= true;
367 .keypress(function(e
){
368 e
.cancelBubble
= true;
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]")
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]
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
;
386 .keydown(function(e
){
387 if(!e
.charCode
&& e
.keyCode
== 119 /* F8, windows */) {
389 .find("form.formulaire_crayon")
392 if (e
.keyCode
== 27) { /* esc */
397 .keypress(function(e
){
398 // Clavier pour sauver
401 /* ctrl-s ou ctrl-maj-S, firefox */
402 ((e
.charCode
||e
.keyCode
) == 115) || ((e
.charCode
||e
.keyCode
) == 83))
404 || (e
.charCode
==19 && e
.keyCode
==19)
407 e
.shiftKey
&& (e
.keyCode
== 13) /* shift-return */
411 .find("form.formulaire_crayon")
414 var maxh
= this.className
.match(/\bmaxheight(\d+)?\b/);
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');
423 .find(".crayon-submit")
427 .parents("form:eq(0)")
431 .find(".crayon-cancel")
438 // decaler verticalement si la fenetre d'edition n'est pas visible
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
452 if (percent
&& this.scrollHeight
> hauteur
) {
453 this.scrollTop
= this.scrollHeight
* percent
- hauteur
;
457 // decaler les boutons qui suivent un resizer de 16px vers le haut
458 .next('.resizehandle')
459 .next('.crayon-boutons')
460 .addClass('resizehandle_boutons');
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());
469 else if (typeof triggerAjaxLoad
== 'function') {
470 triggerAjaxLoad(crayon
.get());
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
484 $(this).parents('.crayon:eq(0)').opencrayon(e
);
489 // initialise les crayons
490 $.fn
.initcrayon = function(){
491 var editme = function(e
){
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
);
500 .addClass('crayon-autorise')
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;}})
505 .hover( // :hover pour MSIE
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');
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');
524 $.fn
.crayonsstart = function() {
525 if (!configCrayons
.droits
) return;
526 id_crayon
= 0; // global
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();
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
) {
543 .addClass('crayon-init')
544 .filter(configCrayons
.droits
)
546 .trigger('mouseover');
547 if (e
.type
=='touchstart')
548 $(this).trigger('touchstart');
552 // un clic en dehors ferme tous les crayons ouverts ?
553 if (configCrayons
.cfg
.clickhide
)