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