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
) {
228 var crayon
= $(this).crayon();
232 $('<input type="hidden" name="self" />')
233 .attr('value',configCrayons
.self
)
237 "error": function(d
) {
238 uniAlert('erreur de communication');
242 $('<div class="error">')
243 .html(d
.responseText
|| d
.error
|| 'erreur inconnue')
249 border
: 'red solid 2px',
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") {
258 d
= $.parseJSON(d
.replace(/^<pre>/,'').replace(/<[/]pre
>$/,''));
260 d
= {'$erreur': 'erreur de communication :' + ' ' + e
.message
, '$html':''};
264 .find("em.crayon-searching")
267 //Remise a zero des warnings invalides (unwrap)
269 .find("span.crayon-invalide p")
272 .find("span.crayon-invalide")
274 $(this).replaceWith( this.childNodes
);
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
='';
284 .find("*[name='content_"+invalide
+"']")
285 .wrap("<span class=\"crayon-invalide\"></span>")
297 if (d
.$erreur
> '') {
299 if (d
.$erreur
> ' ') {
305 uniAlert(d
.$erreur
+'\n'+configCrayons
.txt
.error
);
309 if (d
.erreur
> '' || d
.$invalides
) {
313 .find(".crayon-boutons,.resizehandle")
316 .find('.crayon-searching')
320 // Desactive celui pour qui on vient de recevoir les nouvelles donnees
323 // Insere les donnees dans *tous* les elements ayant le meme code
325 '.crayon.crayon-autorise.' +
326 me
[0].className
.match(/crayon ([^ ]+)/)[1]
329 d
[$('input.crayon-id', crayon
).val()]
332 // Declencher le onAjaxLoad normal de SPIP
333 if (typeof jQuery
.spip
== 'object' && typeof jQuery
.spip
.triggerAjaxLoad
== 'function') {
334 jQuery
.spip
.triggerAjaxLoad(tous
.get());
337 else if (typeof triggerAjaxLoad
== 'function') {
338 triggerAjaxLoad(tous
.get());
341 .bind('form-submit-validate',function(form
,a
, e
, options
, veto
){
346 .after(configCrayons
.mkimg('searching')) // icone d'attente
347 .find(".crayon-boutons,.resizehandle")
350 // keyup pour les input et textarea ...
353 .find(".crayon-boutons")
356 .addClass('crayon-changed');
357 e
.cancelBubble
= true; // ne pas remonter l'evenement vers la page
359 // ... change pour les select : ici on submit direct, pourquoi pas
362 .find(".crayon-boutons")
365 .addClass('crayon-changed');
366 e
.cancelBubble
= true;
368 .keypress(function(e
){
369 e
.cancelBubble
= true;
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]")
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]
380 if(!$(this).is(':disabled, [readonly]')){
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]'))
392 .keydown(function(e
){
393 if(!e
.charCode
&& e
.keyCode
== 119 /* F8, windows */) {
395 .find("form.formulaire_crayon")
398 if (e
.keyCode
== 27) { /* esc */
403 .keypress(function(e
){
404 // Clavier pour sauver
407 /* ctrl-s ou ctrl-maj-S, firefox */
408 ((e
.charCode
||e
.keyCode
) == 115) || ((e
.charCode
||e
.keyCode
) == 83))
410 || (e
.charCode
==19 && e
.keyCode
==19)
413 e
.shiftKey
&& (e
.keyCode
== 13) /* shift-return */
417 .find("form.formulaire_crayon")
420 var maxh
= this.className
.match(/\bmaxheight(\d+)?\b/);
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');
429 .find(".crayon-submit")
433 .parents("form:eq(0)")
437 .find(".crayon-cancel")
444 // decaler verticalement si la fenetre d'edition n'est pas visible
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
458 if (percent
&& this.scrollHeight
> hauteur
) {
459 this.scrollTop
= this.scrollHeight
* percent
- hauteur
;
463 // decaler les boutons qui suivent un resizer de 16px vers le haut
464 .next('.resizehandle')
465 .next('.crayon-boutons')
466 .addClass('resizehandle_boutons');
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());
475 else if (typeof triggerAjaxLoad
== 'function') {
476 triggerAjaxLoad(crayon
.get());
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
490 $(this).parents('.crayon:eq(0)').opencrayon(e
);
495 // initialise les crayons
496 $.fn
.initcrayon = function(){
497 var editme = function(e
){
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
);
506 .addClass('crayon-autorise')
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;}})
511 .hover( // :hover pour MSIE
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');
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');
530 $.fn
.crayonsstart = function() {
531 if (!configCrayons
.droits
) return;
532 id_crayon
= 0; // global
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();
544 // demarrer les crayons
545 if ((typeof crayons_init_dynamique
== 'undefined') || (crayons_init_dynamique
==false)) {
548 if (typeof $.fn
.live
== 'undefined') {
549 $.fn
.live = function( types
, data
, fn
) {
550 $( this.context
).on( types
, this.selector
, data
, fn
);
554 $('.crayon:not(.crayon-init)')
555 .live('mouseover touchstart', function(e
) {
557 .addClass('crayon-init')
558 .filter(configCrayons
.droits
)
560 .trigger('mouseover');
561 if (e
.type
=='touchstart')
562 $(this).trigger('touchstart');
566 // un clic en dehors ferme tous les crayons ouverts ?
567 if (configCrayons
.cfg
.clickhide
)