Bugfix : prendre le innerHTML du body du document n'est pas une bonne idée, dans...
[ckeditor.git] / skins / ckeditor / _source / plugins / image / dialogs / image.js
1 /*
2 Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved.
3 For licensing, see LICENSE.html or http://ckeditor.com/license
4 */
5
6 (function()
7 {
8 var imageDialog = function( editor, dialogType )
9 {
10 // Load image preview.
11 var IMAGE = 1,
12 LINK = 2,
13 PREVIEW = 4,
14 CLEANUP = 8,
15 regexGetSize = /^\s*(\d+)((px)|\%)?\s*$/i,
16 regexGetSizeOrEmpty = /(^\s*(\d+)((px)|\%)?\s*$)|^$/i,
17 pxLengthRegex = /^\d+px$/;
18
19 var onSizeChange = function()
20 {
21 var value = this.getValue(), // This = input element.
22 dialog = this.getDialog(),
23 aMatch = value.match( regexGetSize ); // Check value
24 if ( aMatch )
25 {
26 if ( aMatch[2] == '%' ) // % is allowed - > unlock ratio.
27 switchLockRatio( dialog, false ); // Unlock.
28 value = aMatch[1];
29 }
30
31 // Only if ratio is locked
32 if ( dialog.lockRatio )
33 {
34 var oImageOriginal = dialog.originalElement;
35 if ( oImageOriginal.getCustomData( 'isReady' ) == 'true' )
36 {
37 if ( this.id == 'txtHeight' )
38 {
39 if ( value && value != '0' )
40 value = Math.round( oImageOriginal.$.width * ( value / oImageOriginal.$.height ) );
41 if ( !isNaN( value ) )
42 dialog.setValueOf( 'info', 'txtWidth', value );
43 }
44 else //this.id = txtWidth.
45 {
46 if ( value && value != '0' )
47 value = Math.round( oImageOriginal.$.height * ( value / oImageOriginal.$.width ) );
48 if ( !isNaN( value ) )
49 dialog.setValueOf( 'info', 'txtHeight', value );
50 }
51 }
52 }
53 updatePreview( dialog );
54 };
55
56 var updatePreview = function( dialog )
57 {
58 //Don't load before onShow.
59 if ( !dialog.originalElement || !dialog.preview )
60 return 1;
61
62 // Read attributes and update imagePreview;
63 dialog.commitContent( PREVIEW, dialog.preview );
64 return 0;
65 };
66
67 // Custom commit dialog logic, where we're intended to give inline style
68 // field (txtdlgGenStyle) higher priority to avoid overwriting styles contribute
69 // by other fields.
70 function commitContent()
71 {
72 var args = arguments;
73 var inlineStyleField = this.getContentElement( 'advanced', 'txtdlgGenStyle' );
74 inlineStyleField && inlineStyleField.commit.apply( inlineStyleField, args );
75
76 this.foreach( function( widget )
77 {
78 if ( widget.commit && widget.id != 'txtdlgGenStyle' )
79 widget.commit.apply( widget, args );
80 });
81 }
82
83 // Avoid recursions.
84 var incommit;
85
86 // Synchronous field values to other impacted fields is required, e.g. border
87 // size change should alter inline-style text as well.
88 function commitInternally( targetFields )
89 {
90 if ( incommit )
91 return;
92
93 incommit = 1;
94
95 var dialog = this.getDialog(),
96 element = dialog.imageElement;
97 if ( element )
98 {
99 // Commit this field and broadcast to target fields.
100 this.commit( IMAGE, element );
101
102 targetFields = [].concat( targetFields );
103 var length = targetFields.length,
104 field;
105 for ( var i = 0; i < length; i++ )
106 {
107 field = dialog.getContentElement.apply( dialog, targetFields[ i ].split( ':' ) );
108 // May cause recursion.
109 field && field.setup( IMAGE, element );
110 }
111 }
112
113 incommit = 0;
114 }
115
116 var switchLockRatio = function( dialog, value )
117 {
118 if ( !dialog.getContentElement( 'info', 'ratioLock' ) )
119 return null;
120
121 var oImageOriginal = dialog.originalElement;
122
123 // Dialog may already closed. (#5505)
124 if( !oImageOriginal )
125 return null;
126
127 // Check image ratio and original image ratio, but respecting user's preference.
128 if ( value == 'check' )
129 {
130 if ( !dialog.userlockRatio && oImageOriginal.getCustomData( 'isReady' ) == 'true' )
131 {
132 var width = dialog.getValueOf( 'info', 'txtWidth' ),
133 height = dialog.getValueOf( 'info', 'txtHeight' ),
134 originalRatio = oImageOriginal.$.width * 1000 / oImageOriginal.$.height,
135 thisRatio = width * 1000 / height;
136 dialog.lockRatio = false; // Default: unlock ratio
137
138 if ( !width && !height )
139 dialog.lockRatio = true;
140 else if ( !isNaN( originalRatio ) && !isNaN( thisRatio ) )
141 {
142 if ( Math.round( originalRatio ) == Math.round( thisRatio ) )
143 dialog.lockRatio = true;
144 }
145 }
146 }
147 else if ( value != undefined )
148 dialog.lockRatio = value;
149 else
150 {
151 dialog.userlockRatio = 1;
152 dialog.lockRatio = !dialog.lockRatio;
153 }
154
155 var ratioButton = CKEDITOR.document.getById( btnLockSizesId );
156 if ( dialog.lockRatio )
157 ratioButton.removeClass( 'cke_btn_unlocked' );
158 else
159 ratioButton.addClass( 'cke_btn_unlocked' );
160
161 ratioButton.setAttribute( 'aria-checked', dialog.lockRatio );
162
163 // Ratio button hc presentation - WHITE SQUARE / BLACK SQUARE
164 if ( CKEDITOR.env.hc )
165 {
166 var icon = ratioButton.getChild( 0 );
167 icon.setHtml( dialog.lockRatio ? CKEDITOR.env.ie ? '\u25A0': '\u25A3' : CKEDITOR.env.ie ? '\u25A1' : '\u25A2' );
168 }
169
170 return dialog.lockRatio;
171 };
172
173 var resetSize = function( dialog )
174 {
175 var oImageOriginal = dialog.originalElement;
176 if ( oImageOriginal.getCustomData( 'isReady' ) == 'true' )
177 {
178 var widthField = dialog.getContentElement( 'info', 'txtWidth' ),
179 heightField = dialog.getContentElement( 'info', 'txtHeight' );
180 widthField && widthField.setValue( oImageOriginal.$.width );
181 heightField && heightField.setValue( oImageOriginal.$.height );
182 }
183 updatePreview( dialog );
184 };
185
186 var setupDimension = function( type, element )
187 {
188 if ( type != IMAGE )
189 return;
190
191 function checkDimension( size, defaultValue )
192 {
193 var aMatch = size.match( regexGetSize );
194 if ( aMatch )
195 {
196 if ( aMatch[2] == '%' ) // % is allowed.
197 {
198 aMatch[1] += '%';
199 switchLockRatio( dialog, false ); // Unlock ratio
200 }
201 return aMatch[1];
202 }
203 return defaultValue;
204 }
205
206 var dialog = this.getDialog(),
207 value = '',
208 dimension = this.id == 'txtWidth' ? 'width' : 'height',
209 size = element.getAttribute( dimension );
210
211 if ( size )
212 value = checkDimension( size, value );
213 value = checkDimension( element.getStyle( dimension ), value );
214
215 this.setValue( value );
216 };
217
218 var previewPreloader;
219
220 var onImgLoadEvent = function()
221 {
222 // Image is ready.
223 var original = this.originalElement;
224 original.setCustomData( 'isReady', 'true' );
225 original.removeListener( 'load', onImgLoadEvent );
226 original.removeListener( 'error', onImgLoadErrorEvent );
227 original.removeListener( 'abort', onImgLoadErrorEvent );
228
229 // Hide loader
230 CKEDITOR.document.getById( imagePreviewLoaderId ).setStyle( 'display', 'none' );
231
232 // New image -> new domensions
233 if ( !this.dontResetSize )
234 resetSize( this );
235
236 if ( this.firstLoad )
237 CKEDITOR.tools.setTimeout( function(){ switchLockRatio( this, 'check' ); }, 0, this );
238
239 this.firstLoad = false;
240 this.dontResetSize = false;
241 };
242
243 var onImgLoadErrorEvent = function()
244 {
245 // Error. Image is not loaded.
246 var original = this.originalElement;
247 original.removeListener( 'load', onImgLoadEvent );
248 original.removeListener( 'error', onImgLoadErrorEvent );
249 original.removeListener( 'abort', onImgLoadErrorEvent );
250
251 // Set Error image.
252 var noimage = CKEDITOR.getUrl( editor.skinPath + 'images/noimage.png' );
253
254 if ( this.preview )
255 this.preview.setAttribute( 'src', noimage );
256
257 // Hide loader
258 CKEDITOR.document.getById( imagePreviewLoaderId ).setStyle( 'display', 'none' );
259 switchLockRatio( this, false ); // Unlock.
260 };
261
262 var numbering = function( id )
263 {
264 return CKEDITOR.tools.getNextId() + '_' + id;
265 },
266 btnLockSizesId = numbering( 'btnLockSizes' ),
267 btnResetSizeId = numbering( 'btnResetSize' ),
268 imagePreviewLoaderId = numbering( 'ImagePreviewLoader' ),
269 imagePreviewBoxId = numbering( 'ImagePreviewBox' ),
270 previewLinkId = numbering( 'previewLink' ),
271 previewImageId = numbering( 'previewImage' );
272
273 return {
274 title : editor.lang.image[ dialogType == 'image' ? 'title' : 'titleButton' ],
275 minWidth : 420,
276 minHeight : 360,
277 onShow : function()
278 {
279 this.imageElement = false;
280 this.linkElement = false;
281
282 // Default: create a new element.
283 this.imageEditMode = false;
284 this.linkEditMode = false;
285
286 this.lockRatio = true;
287 this.userlockRatio = 0;
288 this.dontResetSize = false;
289 this.firstLoad = true;
290 this.addLink = false;
291
292 var editor = this.getParentEditor(),
293 sel = this.getParentEditor().getSelection(),
294 element = sel.getSelectedElement(),
295 link = element && element.getAscendant( 'a' );
296
297 //Hide loader.
298 CKEDITOR.document.getById( imagePreviewLoaderId ).setStyle( 'display', 'none' );
299 // Create the preview before setup the dialog contents.
300 previewPreloader = new CKEDITOR.dom.element( 'img', editor.document );
301 this.preview = CKEDITOR.document.getById( previewImageId );
302
303 // Copy of the image
304 this.originalElement = editor.document.createElement( 'img' );
305 this.originalElement.setAttribute( 'alt', '' );
306 this.originalElement.setCustomData( 'isReady', 'false' );
307
308 if ( link )
309 {
310 this.linkElement = link;
311 this.linkEditMode = true;
312
313 // Look for Image element.
314 var linkChildren = link.getChildren();
315 if ( linkChildren.count() == 1 ) // 1 child.
316 {
317 var childTagName = linkChildren.getItem( 0 ).getName();
318 if ( childTagName == 'img' || childTagName == 'input' )
319 {
320 this.imageElement = linkChildren.getItem( 0 );
321 if ( this.imageElement.getName() == 'img' )
322 this.imageEditMode = 'img';
323 else if ( this.imageElement.getName() == 'input' )
324 this.imageEditMode = 'input';
325 }
326 }
327 // Fill out all fields.
328 if ( dialogType == 'image' )
329 this.setupContent( LINK, link );
330 }
331
332 if ( element && element.getName() == 'img' && !element.data( 'cke-realelement' )
333 || element && element.getName() == 'input' && element.getAttribute( 'type' ) == 'image' )
334 {
335 this.imageEditMode = element.getName();
336 this.imageElement = element;
337 }
338
339 if ( this.imageEditMode )
340 {
341 // Use the original element as a buffer from since we don't want
342 // temporary changes to be committed, e.g. if the dialog is canceled.
343 this.cleanImageElement = this.imageElement;
344 this.imageElement = this.cleanImageElement.clone( true, true );
345
346 // Fill out all fields.
347 this.setupContent( IMAGE, this.imageElement );
348 }
349 else
350 this.imageElement = editor.document.createElement( 'img' );
351
352 // Refresh LockRatio button
353 switchLockRatio ( this, true );
354
355 // Dont show preview if no URL given.
356 if ( !CKEDITOR.tools.trim( this.getValueOf( 'info', 'txtUrl' ) ) )
357 {
358 this.preview.removeAttribute( 'src' );
359 this.preview.setStyle( 'display', 'none' );
360 }
361 },
362 onOk : function()
363 {
364 // Edit existing Image.
365 if ( this.imageEditMode )
366 {
367 var imgTagName = this.imageEditMode;
368
369 // Image dialog and Input element.
370 if ( dialogType == 'image' && imgTagName == 'input' && confirm( editor.lang.image.button2Img ) )
371 {
372 // Replace INPUT-> IMG
373 imgTagName = 'img';
374 this.imageElement = editor.document.createElement( 'img' );
375 this.imageElement.setAttribute( 'alt', '' );
376 editor.insertElement( this.imageElement );
377 }
378 // ImageButton dialog and Image element.
379 else if ( dialogType != 'image' && imgTagName == 'img' && confirm( editor.lang.image.img2Button ))
380 {
381 // Replace IMG -> INPUT
382 imgTagName = 'input';
383 this.imageElement = editor.document.createElement( 'input' );
384 this.imageElement.setAttributes(
385 {
386 type : 'image',
387 alt : ''
388 }
389 );
390 editor.insertElement( this.imageElement );
391 }
392 else
393 {
394 // Restore the original element before all commits.
395 this.imageElement = this.cleanImageElement;
396 delete this.cleanImageElement;
397 }
398 }
399 else // Create a new image.
400 {
401 // Image dialog -> create IMG element.
402 if ( dialogType == 'image' )
403 this.imageElement = editor.document.createElement( 'img' );
404 else
405 {
406 this.imageElement = editor.document.createElement( 'input' );
407 this.imageElement.setAttribute ( 'type' ,'image' );
408 }
409 this.imageElement.setAttribute( 'alt', '' );
410 }
411
412 // Create a new link.
413 if ( !this.linkEditMode )
414 this.linkElement = editor.document.createElement( 'a' );
415
416 // Set attributes.
417 this.commitContent( IMAGE, this.imageElement );
418 this.commitContent( LINK, this.linkElement );
419
420 // Remove empty style attribute.
421 if ( !this.imageElement.getAttribute( 'style' ) )
422 this.imageElement.removeAttribute( 'style' );
423
424 // Insert a new Image.
425 if ( !this.imageEditMode )
426 {
427 if ( this.addLink )
428 {
429 //Insert a new Link.
430 if ( !this.linkEditMode )
431 {
432 editor.insertElement( this.linkElement );
433 this.linkElement.append( this.imageElement, false );
434 }
435 else //Link already exists, image not.
436 editor.insertElement( this.imageElement );
437 }
438 else
439 editor.insertElement( this.imageElement );
440 }
441 else // Image already exists.
442 {
443 //Add a new link element.
444 if ( !this.linkEditMode && this.addLink )
445 {
446 editor.insertElement( this.linkElement );
447 this.imageElement.appendTo( this.linkElement );
448 }
449 //Remove Link, Image exists.
450 else if ( this.linkEditMode && !this.addLink )
451 {
452 editor.getSelection().selectElement( this.linkElement );
453 editor.insertElement( this.imageElement );
454 }
455 }
456 },
457 onLoad : function()
458 {
459 if ( dialogType != 'image' )
460 this.hidePage( 'Link' ); //Hide Link tab.
461 var doc = this._.element.getDocument();
462
463 if ( this.getContentElement( 'info', 'ratioLock' ) )
464 {
465 this.addFocusable( doc.getById( btnResetSizeId ), 5 );
466 this.addFocusable( doc.getById( btnLockSizesId ), 5 );
467 }
468
469 this.commitContent = commitContent;
470 },
471 onHide : function()
472 {
473 if ( this.preview )
474 this.commitContent( CLEANUP, this.preview );
475
476 if ( this.originalElement )
477 {
478 this.originalElement.removeListener( 'load', onImgLoadEvent );
479 this.originalElement.removeListener( 'error', onImgLoadErrorEvent );
480 this.originalElement.removeListener( 'abort', onImgLoadErrorEvent );
481 this.originalElement.remove();
482 this.originalElement = false; // Dialog is closed.
483 }
484
485 delete this.imageElement;
486 },
487 contents : [
488 {
489 id : 'info',
490 label : editor.lang.image.infoTab,
491 accessKey : 'I',
492 elements :
493 [
494 {
495 type : 'vbox',
496 padding : 0,
497 children :
498 [
499 {
500 type : 'hbox',
501 widths : [ '280px', '110px' ],
502 align : 'right',
503 children :
504 [
505 {
506 id : 'txtUrl',
507 type : 'text',
508 label : editor.lang.common.url,
509 required: true,
510 onChange : function()
511 {
512 var dialog = this.getDialog(),
513 newUrl = this.getValue();
514
515 //Update original image
516 if ( newUrl.length > 0 ) //Prevent from load before onShow
517 {
518 dialog = this.getDialog();
519 var original = dialog.originalElement;
520
521 dialog.preview.removeStyle( 'display' );
522
523 original.setCustomData( 'isReady', 'false' );
524 // Show loader
525 var loader = CKEDITOR.document.getById( imagePreviewLoaderId );
526 if ( loader )
527 loader.setStyle( 'display', '' );
528
529 original.on( 'load', onImgLoadEvent, dialog );
530 original.on( 'error', onImgLoadErrorEvent, dialog );
531 original.on( 'abort', onImgLoadErrorEvent, dialog );
532 original.setAttribute( 'src', newUrl );
533
534 // Query the preloader to figure out the url impacted by based href.
535 previewPreloader.setAttribute( 'src', newUrl );
536 dialog.preview.setAttribute( 'src', previewPreloader.$.src );
537 updatePreview( dialog );
538 }
539 // Dont show preview if no URL given.
540 else if ( dialog.preview )
541 {
542 dialog.preview.removeAttribute( 'src' );
543 dialog.preview.setStyle( 'display', 'none' );
544 }
545 },
546 setup : function( type, element )
547 {
548 if ( type == IMAGE )
549 {
550 var url = element.data( 'cke-saved-src' ) || element.getAttribute( 'src' );
551 var field = this;
552
553 this.getDialog().dontResetSize = true;
554
555 field.setValue( url ); // And call this.onChange()
556 // Manually set the initial value.(#4191)
557 field.setInitValue();
558 }
559 },
560 commit : function( type, element )
561 {
562 if ( type == IMAGE && ( this.getValue() || this.isChanged() ) )
563 {
564 element.data( 'cke-saved-src', this.getValue() );
565 element.setAttribute( 'src', this.getValue() );
566 }
567 else if ( type == CLEANUP )
568 {
569 element.setAttribute( 'src', '' ); // If removeAttribute doesn't work.
570 element.removeAttribute( 'src' );
571 }
572 },
573 validate : CKEDITOR.dialog.validate.notEmpty( editor.lang.image.urlMissing )
574 },
575 {
576 type : 'button',
577 id : 'browse',
578 // v-align with the 'txtUrl' field.
579 // TODO: We need something better than a fixed size here.
580 style : 'display:inline-block;margin-top:10px;',
581 align : 'center',
582 label : editor.lang.common.browseServer,
583 hidden : true,
584 filebrowser : 'info:txtUrl'
585 }
586 ]
587 }
588 ]
589 },
590 {
591 id : 'txtAlt',
592 type : 'text',
593 label : editor.lang.image.alt,
594 accessKey : 'T',
595 'default' : '',
596 onChange : function()
597 {
598 updatePreview( this.getDialog() );
599 },
600 setup : function( type, element )
601 {
602 if ( type == IMAGE )
603 this.setValue( element.getAttribute( 'alt' ) );
604 },
605 commit : function( type, element )
606 {
607 if ( type == IMAGE )
608 {
609 if ( this.getValue() || this.isChanged() )
610 element.setAttribute( 'alt', this.getValue() );
611 }
612 else if ( type == PREVIEW )
613 {
614 element.setAttribute( 'alt', this.getValue() );
615 }
616 else if ( type == CLEANUP )
617 {
618 element.removeAttribute( 'alt' );
619 }
620 }
621 },
622 {
623 type : 'hbox',
624 children :
625 [
626 {
627 id : 'basic',
628 type : 'vbox',
629 children :
630 [
631 {
632 type : 'hbox',
633 widths : [ '50%', '50%' ],
634 children :
635 [
636 {
637 type : 'vbox',
638 padding : 1,
639 children :
640 [
641 {
642 type : 'text',
643 width: '40px',
644 id : 'txtWidth',
645 label : editor.lang.common.width,
646 onKeyUp : onSizeChange,
647 onChange : function()
648 {
649 commitInternally.call( this, 'advanced:txtdlgGenStyle' );
650 },
651 validate : function()
652 {
653 var aMatch = this.getValue().match( regexGetSizeOrEmpty ),
654 isValid = !!( aMatch && parseInt( aMatch[1], 10 ) !== 0 );
655 if ( !isValid )
656 alert( editor.lang.common.invalidWidth );
657 return isValid;
658 },
659 setup : setupDimension,
660 commit : function( type, element, internalCommit )
661 {
662 var value = this.getValue();
663 if ( type == IMAGE )
664 {
665 if ( value )
666 element.setStyle( 'width', CKEDITOR.tools.cssLength( value ) );
667 else
668 element.removeStyle( 'width' );
669
670 !internalCommit && element.removeAttribute( 'width' );
671 }
672 else if ( type == PREVIEW )
673 {
674 var aMatch = value.match( regexGetSize );
675 if ( !aMatch )
676 {
677 var oImageOriginal = this.getDialog().originalElement;
678 if ( oImageOriginal.getCustomData( 'isReady' ) == 'true' )
679 element.setStyle( 'width', oImageOriginal.$.width + 'px');
680 }
681 else
682 element.setStyle( 'width', CKEDITOR.tools.cssLength( value ) );
683 }
684 else if ( type == CLEANUP )
685 {
686 element.removeAttribute( 'width' );
687 element.removeStyle( 'width' );
688 }
689 }
690 },
691 {
692 type : 'text',
693 id : 'txtHeight',
694 width: '40px',
695 label : editor.lang.common.height,
696 onKeyUp : onSizeChange,
697 onChange : function()
698 {
699 commitInternally.call( this, 'advanced:txtdlgGenStyle' );
700 },
701 validate : function()
702 {
703 var aMatch = this.getValue().match( regexGetSizeOrEmpty ),
704 isValid = !!( aMatch && parseInt( aMatch[1], 10 ) !== 0 );
705 if ( !isValid )
706 alert( editor.lang.common.invalidHeight );
707 return isValid;
708 },
709 setup : setupDimension,
710 commit : function( type, element, internalCommit )
711 {
712 var value = this.getValue();
713 if ( type == IMAGE )
714 {
715 if ( value )
716 element.setStyle( 'height', CKEDITOR.tools.cssLength( value ) );
717 else
718 element.removeStyle( 'height' );
719
720 !internalCommit && element.removeAttribute( 'height' );
721 }
722 else if ( type == PREVIEW )
723 {
724 var aMatch = value.match( regexGetSize );
725 if ( !aMatch )
726 {
727 var oImageOriginal = this.getDialog().originalElement;
728 if ( oImageOriginal.getCustomData( 'isReady' ) == 'true' )
729 element.setStyle( 'height', oImageOriginal.$.height + 'px' );
730 }
731 else
732 element.setStyle( 'height', CKEDITOR.tools.cssLength( value ) );
733 }
734 else if ( type == CLEANUP )
735 {
736 element.removeAttribute( 'height' );
737 element.removeStyle( 'height' );
738 }
739 }
740 }
741 ]
742 },
743 {
744 id : 'ratioLock',
745 type : 'html',
746 style : 'margin-top:30px;width:40px;height:40px;',
747 onLoad : function()
748 {
749 // Activate Reset button
750 var resetButton = CKEDITOR.document.getById( btnResetSizeId ),
751 ratioButton = CKEDITOR.document.getById( btnLockSizesId );
752 if ( resetButton )
753 {
754 resetButton.on( 'click', function( evt )
755 {
756 resetSize( this );
757 evt.data && evt.data.preventDefault();
758 }, this.getDialog() );
759 resetButton.on( 'mouseover', function()
760 {
761 this.addClass( 'cke_btn_over' );
762 }, resetButton );
763 resetButton.on( 'mouseout', function()
764 {
765 this.removeClass( 'cke_btn_over' );
766 }, resetButton );
767 }
768 // Activate (Un)LockRatio button
769 if ( ratioButton )
770 {
771 ratioButton.on( 'click', function(evt)
772 {
773 var locked = switchLockRatio( this ),
774 oImageOriginal = this.originalElement,
775 width = this.getValueOf( 'info', 'txtWidth' );
776
777 if ( oImageOriginal.getCustomData( 'isReady' ) == 'true' && width )
778 {
779 var height = oImageOriginal.$.height / oImageOriginal.$.width * width;
780 if ( !isNaN( height ) )
781 {
782 this.setValueOf( 'info', 'txtHeight', Math.round( height ) );
783 updatePreview( this );
784 }
785 }
786 evt.data && evt.data.preventDefault();
787 }, this.getDialog() );
788 ratioButton.on( 'mouseover', function()
789 {
790 this.addClass( 'cke_btn_over' );
791 }, ratioButton );
792 ratioButton.on( 'mouseout', function()
793 {
794 this.removeClass( 'cke_btn_over' );
795 }, ratioButton );
796 }
797 },
798 html : '<div>'+
799 '<a href="javascript:void(0)" tabindex="-1" title="' + editor.lang.image.lockRatio +
800 '" class="cke_btn_locked" id="' + btnLockSizesId + '" role="checkbox"><span class="cke_icon"></span><span class="cke_label">' + editor.lang.image.lockRatio + '</span></a>' +
801 '<a href="javascript:void(0)" tabindex="-1" title="' + editor.lang.image.resetSize +
802 '" class="cke_btn_reset" id="' + btnResetSizeId + '" role="button"><span class="cke_label">' + editor.lang.image.resetSize + '</span></a>'+
803 '</div>'
804 }
805 ]
806 },
807 {
808 type : 'vbox',
809 padding : 1,
810 children :
811 [
812 {
813 type : 'text',
814 id : 'txtBorder',
815 width: '60px',
816 label : editor.lang.image.border,
817 'default' : '',
818 onKeyUp : function()
819 {
820 updatePreview( this.getDialog() );
821 },
822 onChange : function()
823 {
824 commitInternally.call( this, 'advanced:txtdlgGenStyle' );
825 },
826 validate : CKEDITOR.dialog.validate.integer( editor.lang.image.validateBorder ),
827 setup : function( type, element )
828 {
829 if ( type == IMAGE )
830 {
831 var value,
832 borderStyle = element.getStyle( 'border-width' );
833 borderStyle = borderStyle && borderStyle.match( /^(\d+px)(?: \1 \1 \1)?$/ );
834 value = borderStyle && parseInt( borderStyle[ 1 ], 10 );
835 isNaN ( parseInt( value, 10 ) ) && ( value = element.getAttribute( 'border' ) );
836 this.setValue( value );
837 }
838 },
839 commit : function( type, element, internalCommit )
840 {
841 var value = parseInt( this.getValue(), 10 );
842 if ( type == IMAGE || type == PREVIEW )
843 {
844 if ( !isNaN( value ) )
845 {
846 element.setStyle( 'border-width', CKEDITOR.tools.cssLength( value ) );
847 element.setStyle( 'border-style', 'solid' );
848 }
849 else if ( !value && this.isChanged() )
850 {
851 element.removeStyle( 'border-width' );
852 element.removeStyle( 'border-style' );
853 element.removeStyle( 'border-color' );
854 }
855
856 if ( !internalCommit && type == IMAGE )
857 element.removeAttribute( 'border' );
858 }
859 else if ( type == CLEANUP )
860 {
861 element.removeAttribute( 'border' );
862 element.removeStyle( 'border-width' );
863 element.removeStyle( 'border-style' );
864 element.removeStyle( 'border-color' );
865 }
866 }
867 },
868 {
869 type : 'text',
870 id : 'txtHSpace',
871 width: '60px',
872 label : editor.lang.image.hSpace,
873 'default' : '',
874 onKeyUp : function()
875 {
876 updatePreview( this.getDialog() );
877 },
878 onChange : function()
879 {
880 commitInternally.call( this, 'advanced:txtdlgGenStyle' );
881 },
882 validate : CKEDITOR.dialog.validate.integer( editor.lang.image.validateHSpace ),
883 setup : function( type, element )
884 {
885 if ( type == IMAGE )
886 {
887 var value,
888 marginLeftPx,
889 marginRightPx,
890 marginLeftStyle = element.getStyle( 'margin-left' ),
891 marginRightStyle = element.getStyle( 'margin-right' );
892
893 marginLeftStyle = marginLeftStyle && marginLeftStyle.match( pxLengthRegex );
894 marginRightStyle = marginRightStyle && marginRightStyle.match( pxLengthRegex );
895 marginLeftPx = parseInt( marginLeftStyle, 10 );
896 marginRightPx = parseInt( marginRightStyle, 10 );
897
898 value = ( marginLeftPx == marginRightPx ) && marginLeftPx;
899 isNaN( parseInt( value, 10 ) ) && ( value = element.getAttribute( 'hspace' ) );
900
901 this.setValue( value );
902 }
903 },
904 commit : function( type, element, internalCommit )
905 {
906 var value = parseInt( this.getValue(), 10 );
907 if ( type == IMAGE || type == PREVIEW )
908 {
909 if ( !isNaN( value ) )
910 {
911 element.setStyle( 'margin-left', CKEDITOR.tools.cssLength( value ) );
912 element.setStyle( 'margin-right', CKEDITOR.tools.cssLength( value ) );
913 }
914 else if ( !value && this.isChanged( ) )
915 {
916 element.removeStyle( 'margin-left' );
917 element.removeStyle( 'margin-right' );
918 }
919
920 if ( !internalCommit && type == IMAGE )
921 element.removeAttribute( 'hspace' );
922 }
923 else if ( type == CLEANUP )
924 {
925 element.removeAttribute( 'hspace' );
926 element.removeStyle( 'margin-left' );
927 element.removeStyle( 'margin-right' );
928 }
929 }
930 },
931 {
932 type : 'text',
933 id : 'txtVSpace',
934 width : '60px',
935 label : editor.lang.image.vSpace,
936 'default' : '',
937 onKeyUp : function()
938 {
939 updatePreview( this.getDialog() );
940 },
941 onChange : function()
942 {
943 commitInternally.call( this, 'advanced:txtdlgGenStyle' );
944 },
945 validate : CKEDITOR.dialog.validate.integer( editor.lang.image.validateVSpace ),
946 setup : function( type, element )
947 {
948 if ( type == IMAGE )
949 {
950 var value,
951 marginTopPx,
952 marginBottomPx,
953 marginTopStyle = element.getStyle( 'margin-top' ),
954 marginBottomStyle = element.getStyle( 'margin-bottom' );
955
956 marginTopStyle = marginTopStyle && marginTopStyle.match( pxLengthRegex );
957 marginBottomStyle = marginBottomStyle && marginBottomStyle.match( pxLengthRegex );
958 marginTopPx = parseInt( marginTopStyle, 10 );
959 marginBottomPx = parseInt( marginBottomStyle, 10 );
960
961 value = ( marginTopPx == marginBottomPx ) && marginTopPx;
962 isNaN ( parseInt( value, 10 ) ) && ( value = element.getAttribute( 'vspace' ) );
963 this.setValue( value );
964 }
965 },
966 commit : function( type, element, internalCommit )
967 {
968 var value = parseInt( this.getValue(), 10 );
969 if ( type == IMAGE || type == PREVIEW )
970 {
971 if ( !isNaN( value ) )
972 {
973 element.setStyle( 'margin-top', CKEDITOR.tools.cssLength( value ) );
974 element.setStyle( 'margin-bottom', CKEDITOR.tools.cssLength( value ) );
975 }
976 else if ( !value && this.isChanged( ) )
977 {
978 element.removeStyle( 'margin-top' );
979 element.removeStyle( 'margin-bottom' );
980 }
981
982 if ( !internalCommit && type == IMAGE )
983 element.removeAttribute( 'vspace' );
984 }
985 else if ( type == CLEANUP )
986 {
987 element.removeAttribute( 'vspace' );
988 element.removeStyle( 'margin-top' );
989 element.removeStyle( 'margin-bottom' );
990 }
991 }
992 },
993 {
994 id : 'cmbAlign',
995 type : 'select',
996 widths : [ '35%','65%' ],
997 style : 'width:90px',
998 label : editor.lang.common.align,
999 'default' : '',
1000 items :
1001 [
1002 [ editor.lang.common.notSet , ''],
1003 [ editor.lang.common.alignLeft , 'left'],
1004 [ editor.lang.common.alignRight , 'right']
1005 // Backward compatible with v2 on setup when specified as attribute value,
1006 // while these values are no more available as select options.
1007 // [ editor.lang.image.alignAbsBottom , 'absBottom'],
1008 // [ editor.lang.image.alignAbsMiddle , 'absMiddle'],
1009 // [ editor.lang.image.alignBaseline , 'baseline'],
1010 // [ editor.lang.image.alignTextTop , 'text-top'],
1011 // [ editor.lang.image.alignBottom , 'bottom'],
1012 // [ editor.lang.image.alignMiddle , 'middle'],
1013 // [ editor.lang.image.alignTop , 'top']
1014 ],
1015 onChange : function()
1016 {
1017 updatePreview( this.getDialog() );
1018 commitInternally.call( this, 'advanced:txtdlgGenStyle' );
1019 },
1020 setup : function( type, element )
1021 {
1022 if ( type == IMAGE )
1023 {
1024 var value = element.getStyle( 'float' );
1025 switch( value )
1026 {
1027 // Ignore those unrelated values.
1028 case 'inherit':
1029 case 'none':
1030 value = '';
1031 }
1032
1033 !value && ( value = ( element.getAttribute( 'align' ) || '' ).toLowerCase() );
1034 this.setValue( value );
1035 }
1036 },
1037 commit : function( type, element, internalCommit )
1038 {
1039 var value = this.getValue();
1040 if ( type == IMAGE || type == PREVIEW )
1041 {
1042 if ( value )
1043 element.setStyle( 'float', value );
1044 else
1045 element.removeStyle( 'float' );
1046
1047 if ( !internalCommit && type == IMAGE )
1048 {
1049 value = ( element.getAttribute( 'align' ) || '' ).toLowerCase();
1050 switch( value )
1051 {
1052 // we should remove it only if it matches "left" or "right",
1053 // otherwise leave it intact.
1054 case 'left':
1055 case 'right':
1056 element.removeAttribute( 'align' );
1057 }
1058 }
1059 }
1060 else if ( type == CLEANUP )
1061 element.removeStyle( 'float' );
1062
1063 }
1064 }
1065 ]
1066 }
1067 ]
1068 },
1069 {
1070 type : 'vbox',
1071 height : '250px',
1072 children :
1073 [
1074 {
1075 type : 'html',
1076 id : 'htmlPreview',
1077 style : 'width:95%;',
1078 html : '<div>' + CKEDITOR.tools.htmlEncode( editor.lang.common.preview ) +'<br>'+
1079 '<div id="' + imagePreviewLoaderId + '" class="ImagePreviewLoader" style="display:none"><div class="loading">&nbsp;</div></div>'+
1080 '<div id="' + imagePreviewBoxId + '" class="ImagePreviewBox"><table><tr><td>'+
1081 '<a href="javascript:void(0)" target="_blank" onclick="return false;" id="' + previewLinkId + '">'+
1082 '<img id="' + previewImageId + '" alt="" /></a>' +
1083 ( editor.config.image_previewText ||
1084 'Lorem ipsum dolor sit amet, consectetuer adipiscing elit. '+
1085 'Maecenas feugiat consequat diam. Maecenas metus. Vivamus diam purus, cursus a, commodo non, facilisis vitae, '+
1086 'nulla. Aenean dictum lacinia tortor. Nunc iaculis, nibh non iaculis aliquam, orci felis euismod neque, sed ornare massa mauris sed velit. Nulla pretium mi et risus. Fusce mi pede, tempor id, cursus ac, ullamcorper nec, enim. Sed tortor. Curabitur molestie. Duis velit augue, condimentum at, ultrices a, luctus ut, orci. Donec pellentesque egestas eros. Integer cursus, augue in cursus faucibus, eros pede bibendum sem, in tempus tellus justo quis ligula. Etiam eget tortor. Vestibulum rutrum, est ut placerat elementum, lectus nisl aliquam velit, tempor aliquam eros nunc nonummy metus. In eros metus, gravida a, gravida sed, lobortis id, turpis. Ut ultrices, ipsum at venenatis fringilla, sem nulla lacinia tellus, eget aliquet turpis mauris non enim. Nam turpis. Suspendisse lacinia. Curabitur ac tortor ut ipsum egestas elementum. Nunc imperdiet gravida mauris.' ) +
1087 '</td></tr></table></div></div>'
1088 }
1089 ]
1090 }
1091 ]
1092 }
1093 ]
1094 },
1095 {
1096 id : 'Link',
1097 label : editor.lang.link.title,
1098 padding : 0,
1099 elements :
1100 [
1101 {
1102 id : 'txtUrl',
1103 type : 'text',
1104 label : editor.lang.common.url,
1105 style : 'width: 100%',
1106 'default' : '',
1107 setup : function( type, element )
1108 {
1109 if ( type == LINK )
1110 {
1111 var href = element.data( 'cke-saved-href' );
1112 if ( !href )
1113 href = element.getAttribute( 'href' );
1114 this.setValue( href );
1115 }
1116 },
1117 commit : function( type, element )
1118 {
1119 if ( type == LINK )
1120 {
1121 if ( this.getValue() || this.isChanged() )
1122 {
1123 var url = decodeURI( this.getValue() );
1124 element.data( 'cke-saved-href', url );
1125 element.setAttribute( 'href', url );
1126
1127 if ( this.getValue() || !editor.config.image_removeLinkByEmptyURL )
1128 this.getDialog().addLink = true;
1129 }
1130 }
1131 }
1132 },
1133 {
1134 type : 'button',
1135 id : 'browse',
1136 filebrowser :
1137 {
1138 action : 'Browse',
1139 target: 'Link:txtUrl',
1140 url: editor.config.filebrowserImageBrowseLinkUrl
1141 },
1142 style : 'float:right',
1143 hidden : true,
1144 label : editor.lang.common.browseServer
1145 },
1146 {
1147 id : 'cmbTarget',
1148 type : 'select',
1149 label : editor.lang.common.target,
1150 'default' : '',
1151 items :
1152 [
1153 [ editor.lang.common.notSet , ''],
1154 [ editor.lang.common.targetNew , '_blank'],
1155 [ editor.lang.common.targetTop , '_top'],
1156 [ editor.lang.common.targetSelf , '_self'],
1157 [ editor.lang.common.targetParent , '_parent']
1158 ],
1159 setup : function( type, element )
1160 {
1161 if ( type == LINK )
1162 this.setValue( element.getAttribute( 'target' ) || '' );
1163 },
1164 commit : function( type, element )
1165 {
1166 if ( type == LINK )
1167 {
1168 if ( this.getValue() || this.isChanged() )
1169 element.setAttribute( 'target', this.getValue() );
1170 }
1171 }
1172 }
1173 ]
1174 },
1175 {
1176 id : 'Upload',
1177 hidden : true,
1178 filebrowser : 'uploadButton',
1179 label : editor.lang.image.upload,
1180 elements :
1181 [
1182 {
1183 type : 'file',
1184 id : 'upload',
1185 label : editor.lang.image.btnUpload,
1186 style: 'height:40px',
1187 size : 38
1188 },
1189 {
1190 type : 'fileButton',
1191 id : 'uploadButton',
1192 filebrowser : 'info:txtUrl',
1193 label : editor.lang.image.btnUpload,
1194 'for' : [ 'Upload', 'upload' ]
1195 }
1196 ]
1197 },
1198 {
1199 id : 'advanced',
1200 label : editor.lang.common.advancedTab,
1201 elements :
1202 [
1203 {
1204 type : 'hbox',
1205 widths : [ '50%', '25%', '25%' ],
1206 children :
1207 [
1208 {
1209 type : 'text',
1210 id : 'linkId',
1211 label : editor.lang.common.id,
1212 setup : function( type, element )
1213 {
1214 if ( type == IMAGE )
1215 this.setValue( element.getAttribute( 'id' ) );
1216 },
1217 commit : function( type, element )
1218 {
1219 if ( type == IMAGE )
1220 {
1221 if ( this.getValue() || this.isChanged() )
1222 element.setAttribute( 'id', this.getValue() );
1223 }
1224 }
1225 },
1226 {
1227 id : 'cmbLangDir',
1228 type : 'select',
1229 style : 'width : 100px;',
1230 label : editor.lang.common.langDir,
1231 'default' : '',
1232 items :
1233 [
1234 [ editor.lang.common.notSet, '' ],
1235 [ editor.lang.common.langDirLtr, 'ltr' ],
1236 [ editor.lang.common.langDirRtl, 'rtl' ]
1237 ],
1238 setup : function( type, element )
1239 {
1240 if ( type == IMAGE )
1241 this.setValue( element.getAttribute( 'dir' ) );
1242 },
1243 commit : function( type, element )
1244 {
1245 if ( type == IMAGE )
1246 {
1247 if ( this.getValue() || this.isChanged() )
1248 element.setAttribute( 'dir', this.getValue() );
1249 }
1250 }
1251 },
1252 {
1253 type : 'text',
1254 id : 'txtLangCode',
1255 label : editor.lang.common.langCode,
1256 'default' : '',
1257 setup : function( type, element )
1258 {
1259 if ( type == IMAGE )
1260 this.setValue( element.getAttribute( 'lang' ) );
1261 },
1262 commit : function( type, element )
1263 {
1264 if ( type == IMAGE )
1265 {
1266 if ( this.getValue() || this.isChanged() )
1267 element.setAttribute( 'lang', this.getValue() );
1268 }
1269 }
1270 }
1271 ]
1272 },
1273 {
1274 type : 'text',
1275 id : 'txtGenLongDescr',
1276 label : editor.lang.common.longDescr,
1277 setup : function( type, element )
1278 {
1279 if ( type == IMAGE )
1280 this.setValue( element.getAttribute( 'longDesc' ) );
1281 },
1282 commit : function( type, element )
1283 {
1284 if ( type == IMAGE )
1285 {
1286 if ( this.getValue() || this.isChanged() )
1287 element.setAttribute( 'longDesc', this.getValue() );
1288 }
1289 }
1290 },
1291 {
1292 type : 'hbox',
1293 widths : [ '50%', '50%' ],
1294 children :
1295 [
1296 {
1297 type : 'text',
1298 id : 'txtGenClass',
1299 label : editor.lang.common.cssClass,
1300 'default' : '',
1301 setup : function( type, element )
1302 {
1303 if ( type == IMAGE )
1304 this.setValue( element.getAttribute( 'class' ) );
1305 },
1306 commit : function( type, element )
1307 {
1308 if ( type == IMAGE )
1309 {
1310 if ( this.getValue() || this.isChanged() )
1311 element.setAttribute( 'class', this.getValue() );
1312 }
1313 }
1314 },
1315 {
1316 type : 'text',
1317 id : 'txtGenTitle',
1318 label : editor.lang.common.advisoryTitle,
1319 'default' : '',
1320 onChange : function()
1321 {
1322 updatePreview( this.getDialog() );
1323 },
1324 setup : function( type, element )
1325 {
1326 if ( type == IMAGE )
1327 this.setValue( element.getAttribute( 'title' ) );
1328 },
1329 commit : function( type, element )
1330 {
1331 if ( type == IMAGE )
1332 {
1333 if ( this.getValue() || this.isChanged() )
1334 element.setAttribute( 'title', this.getValue() );
1335 }
1336 else if ( type == PREVIEW )
1337 {
1338 element.setAttribute( 'title', this.getValue() );
1339 }
1340 else if ( type == CLEANUP )
1341 {
1342 element.removeAttribute( 'title' );
1343 }
1344 }
1345 }
1346 ]
1347 },
1348 {
1349 type : 'text',
1350 id : 'txtdlgGenStyle',
1351 label : editor.lang.common.cssStyle,
1352 'default' : '',
1353 setup : function( type, element )
1354 {
1355 if ( type == IMAGE )
1356 {
1357 var genStyle = element.getAttribute( 'style' );
1358 if ( !genStyle && element.$.style.cssText )
1359 genStyle = element.$.style.cssText;
1360 this.setValue( genStyle );
1361
1362 var height = element.$.style.height,
1363 width = element.$.style.width,
1364 aMatchH = ( height ? height : '' ).match( regexGetSize ),
1365 aMatchW = ( width ? width : '').match( regexGetSize );
1366
1367 this.attributesInStyle =
1368 {
1369 height : !!aMatchH,
1370 width : !!aMatchW
1371 };
1372 }
1373 },
1374 onChange : function ()
1375 {
1376 commitInternally.call( this,
1377 [ 'info:cmbFloat', 'info:cmbAlign',
1378 'info:txtVSpace', 'info:txtHSpace',
1379 'info:txtBorder',
1380 'info:txtWidth', 'info:txtHeight' ] );
1381 updatePreview( this );
1382 },
1383 commit : function( type, element )
1384 {
1385 if ( type == IMAGE && ( this.getValue() || this.isChanged() ) )
1386 {
1387 element.setAttribute( 'style', this.getValue() );
1388 }
1389 }
1390 }
1391 ]
1392 }
1393 ]
1394 };
1395 };
1396
1397 CKEDITOR.dialog.add( 'image', function( editor )
1398 {
1399 return imageDialog( editor, 'image' );
1400 });
1401
1402 CKEDITOR.dialog.add( 'imagebutton', function( editor )
1403 {
1404 return imageDialog( editor, 'imagebutton' );
1405 });
1406 })();