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).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
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 // 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});
174 var pos
= $(me
).offset();
175 $('<div class="crayon-html" id="crayon_'+id_crayon
+'"></div>')
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();
189 $('#crayon_'+id_crayon
)
190 .css({'left': parseInt(pos
['left'])-diff
});
198 // annule le crayon ouvert (fonction destructive)
199 $.fn
.cancelcrayon = function() {
201 .filter('.crayon-has')
202 .css('visibility','visible')
203 .removeClass('crayon-has')
204 .removeClass('crayon-changed')
210 // masque le crayon ouvert
211 $.fn
.hidecrayon = function() {
213 .filter('.crayon-has')
214 .css('visibility','visible')
217 .removeClass('crayon-hover');
221 // active un crayon qui vient d'etre charge
222 $.fn
.activatecrayon = function(percent
) {
232 var crayon
= $(this).crayon();
236 $('<input type="hidden" name="self" />')
237 .attr('value',configCrayons
.self
)
241 "error": function(d
) {
242 uniAlert('erreur de communication');
246 $('<div class="error">')
247 .html(d
.responseText
|| d
.error
|| 'erreur inconnue')
253 border
: 'red solid 2px',
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") {
262 d
= $.parseJSON(d
.replace(/^<pre>/,'').replace(/<[/]pre
>$/,''));
264 d
= {'$erreur': 'erreur de communication :' + ' ' + e
.message
, '$html':''};
268 .find("em.crayon-searching")
271 //Remise a zero des warnings invalides (unwrap)
273 .find("span.crayon-invalide p")
276 .find("span.crayon-invalide")
278 $(this).replaceWith( this.childNodes
);
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
='';
288 .find("*[name='content_"+invalide
+"']")
289 .wrap("<span class=\"crayon-invalide\"></span>")
301 if (d
.$erreur
> '') {
303 if (d
.$erreur
> ' ') {
309 uniAlert(d
.$erreur
+'\n'+configCrayons
.txt
.error
);
313 if (d
.erreur
> '' || d
.$invalides
) {
317 .find(".crayon-boutons,.resizehandle")
320 .find('.crayon-searching')
324 // Desactive celui pour qui on vient de recevoir les nouvelles donnees
327 // Insere les donnees dans *tous* les elements ayant le meme code
329 '.crayon.crayon-autorise.' +
330 me
[0].className
.match(/crayon ([^ ]+)/)[1]
333 d
[$('input.crayon-id', crayon
).val()]
337 // Invalider des préchargements ajax
338 if (typeof jQuery
.spip
== 'object' && typeof jQuery
.spip
.preloaded_urls
== 'object') {
339 jQuery
.spip
.preloaded_urls
= {};
342 // Declencher le onAjaxLoad normal de SPIP
343 if (typeof jQuery
.spip
== 'object' && typeof jQuery
.spip
.triggerAjaxLoad
== 'function') {
344 jQuery
.spip
.triggerAjaxLoad(tous
.get());
347 else if (typeof triggerAjaxLoad
== 'function') {
348 triggerAjaxLoad(tous
.get());
351 .bind('form-submit-validate',function(form
,a
, e
, options
, veto
){
356 .after(configCrayons
.mkimg('searching')) // icone d'attente
357 .find(".crayon-boutons,.resizehandle")
360 // keyup pour les input et textarea ...
363 .find(".crayon-boutons")
366 .addClass('crayon-changed');
367 e
.cancelBubble
= true; // ne pas remonter l'evenement vers la page
369 // ... change pour les select : ici on submit direct, pourquoi pas
372 .find(".crayon-boutons")
375 .addClass('crayon-changed');
376 e
.cancelBubble
= true;
378 .keypress(function(e
){
379 e
.cancelBubble
= true;
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]")
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]
390 if(!$(this).is(':disabled, [readonly]')){
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]'))
402 .keydown(function(e
){
403 if(!e
.charCode
&& e
.keyCode
== 119 /* F8, windows */) {
405 .find("form.formulaire_crayon")
408 if (e
.keyCode
== 27) { /* esc */
413 .keypress(function(e
){
414 // Clavier pour sauver
417 /* ctrl-s ou ctrl-maj-S, firefox */
418 ((e
.charCode
||e
.keyCode
) == 115) || ((e
.charCode
||e
.keyCode
) == 83))
420 || (e
.charCode
==19 && e
.keyCode
==19)
423 e
.shiftKey
&& (e
.keyCode
== 13) /* shift-return */
427 .find("form.formulaire_crayon")
430 var maxh
= this.className
.match(/\bmaxheight(\d+)?\b/);
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');
439 .find(".crayon-submit")
443 .parents("form:eq(0)")
447 .find(".crayon-cancel")
454 // decaler verticalement si la fenetre d'edition n'est pas visible
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
468 if (percent
&& this.scrollHeight
> hauteur
) {
469 this.scrollTop
= this.scrollHeight
* percent
- hauteur
;
473 // decaler les boutons qui suivent un resizer de 16px vers le haut
474 .next('.resizehandle')
475 .next('.crayon-boutons')
476 .addClass('resizehandle_boutons');
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());
485 else if (typeof triggerAjaxLoad
== 'function') {
486 triggerAjaxLoad(crayon
.get());
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
500 $(this).parents('.crayon:eq(0)').opencrayon(e
);
505 // initialise les crayons
506 $.fn
.initcrayon = function(){
507 var editme = function(e
){
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
);
516 .addClass('crayon-autorise')
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;}})
521 .hover( // :hover pour MSIE
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');
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');
540 $.fn
.crayonsstart = function() {
541 if (!configCrayons
.droits
) return;
542 id_crayon
= 0; // global
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();
554 // demarrer les crayons
555 if ((typeof crayons_init_dynamique
== 'undefined') || (crayons_init_dynamique
==false)) {
558 if (typeof $.fn
.live
== 'undefined') {
559 $.fn
.live = function( types
, data
, fn
) {
560 $( this.context
).on( types
, this.selector
, data
, fn
);
564 $('.crayon:not(.crayon-init)')
565 .live('mouseover touchstart', function(e
) {
567 .addClass('crayon-init')
568 .filter(configCrayons
.droits
)
570 .trigger('mouseover');
571 if (e
.type
=='touchstart')
572 $(this).trigger('touchstart');
576 // un clic en dehors ferme tous les crayons ouverts ?
577 if (configCrayons
.cfg
.clickhide
)