about summary refs log tree commit diff stats
path: root/app/assets/javascripts/ckeditor/plugins/image2/plugin.js
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/ckeditor/plugins/image2/plugin.js')
-rw-r--r--app/assets/javascripts/ckeditor/plugins/image2/plugin.js1712
1 files changed, 0 insertions, 1712 deletions
diff --git a/app/assets/javascripts/ckeditor/plugins/image2/plugin.js b/app/assets/javascripts/ckeditor/plugins/image2/plugin.js deleted file mode 100644 index 3a55255..0000000 --- a/app/assets/javascripts/ckeditor/plugins/image2/plugin.js +++ /dev/null
@@ -1,1712 +0,0 @@
1/**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5
6'use strict';
7
8( function() {
9
10 var template = '<img alt="" src="" />',
11 templateBlock = new CKEDITOR.template(
12 '<figure class="{captionedClass}">' +
13 template +
14 '<figcaption>{captionPlaceholder}</figcaption>' +
15 '</figure>' ),
16 alignmentsObj = { left: 0, center: 1, right: 2 },
17 regexPercent = /^\s*(\d+\%)\s*$/i;
18
19 CKEDITOR.plugins.add( 'image2', {
20 // jscs:disable maximumLineLength
21 lang: 'af,ar,az,bg,bn,bs,ca,cs,cy,da,de,de-ch,el,en,en-au,en-ca,en-gb,eo,es,es-mx,et,eu,fa,fi,fo,fr,fr-ca,gl,gu,he,hi,hr,hu,id,is,it,ja,ka,km,ko,ku,lt,lv,mk,mn,ms,nb,nl,no,oc,pl,pt,pt-br,ro,ru,si,sk,sl,sq,sr,sr-latn,sv,th,tr,tt,ug,uk,vi,zh,zh-cn', // %REMOVE_LINE_CORE%
22 // jscs:enable maximumLineLength
23 requires: 'widget,dialog',
24 icons: 'image',
25 hidpi: true,
26
27 onLoad: function() {
28 CKEDITOR.addCss(
29 '.cke_image_nocaption{' +
30 // This is to remove unwanted space so resize
31 // wrapper is displayed property.
32 'line-height:0' +
33 '}' +
34 '.cke_editable.cke_image_sw, .cke_editable.cke_image_sw *{cursor:sw-resize !important}' +
35 '.cke_editable.cke_image_se, .cke_editable.cke_image_se *{cursor:se-resize !important}' +
36 '.cke_image_resizer{' +
37 'display:none;' +
38 'position:absolute;' +
39 'width:10px;' +
40 'height:10px;' +
41 'bottom:-5px;' +
42 'right:-5px;' +
43 'background:#000;' +
44 'outline:1px solid #fff;' +
45 // Prevent drag handler from being misplaced (http://dev.ckeditor.com/ticket/11207).
46 'line-height:0;' +
47 'cursor:se-resize;' +
48 '}' +
49 '.cke_image_resizer_wrapper{' +
50 'position:relative;' +
51 'display:inline-block;' +
52 'line-height:0;' +
53 '}' +
54 // Bottom-left corner style of the resizer.
55 '.cke_image_resizer.cke_image_resizer_left{' +
56 'right:auto;' +
57 'left:-5px;' +
58 'cursor:sw-resize;' +
59 '}' +
60 '.cke_widget_wrapper:hover .cke_image_resizer,' +
61 '.cke_image_resizer.cke_image_resizing{' +
62 'display:block' +
63 '}' +
64 // Expand widget wrapper when linked inline image.
65 '.cke_widget_wrapper>a{' +
66 'display:inline-block' +
67 '}' );
68 },
69
70 init: function( editor ) {
71 // Adapts configuration from original image plugin. Should be removed
72 // when we'll rename image2 to image.
73 var config = editor.config,
74 lang = editor.lang.image2,
75 image = widgetDef( editor );
76
77 // Since filebrowser plugin discovers config properties by dialog (plugin?)
78 // names (sic!), this hack will be necessary as long as Image2 is not named
79 // Image. And since Image2 will never be Image, for sure some filebrowser logic
80 // got to be refined.
81 config.filebrowserImage2BrowseUrl = config.filebrowserImageBrowseUrl;
82 config.filebrowserImage2UploadUrl = config.filebrowserImageUploadUrl;
83
84 // Add custom elementspath names to widget definition.
85 image.pathName = lang.pathName;
86 image.editables.caption.pathName = lang.pathNameCaption;
87
88 // Register the widget.
89 editor.widgets.add( 'image', image );
90
91 // Add toolbar button for this plugin.
92 editor.ui.addButton && editor.ui.addButton( 'Image', {
93 label: editor.lang.common.image,
94 command: 'image',
95 toolbar: 'insert,10'
96 } );
97
98 // Register context menu option for editing widget.
99 if ( editor.contextMenu ) {
100 editor.addMenuGroup( 'image', 10 );
101
102 editor.addMenuItem( 'image', {
103 label: lang.menu,
104 command: 'image',
105 group: 'image'
106 } );
107 }
108
109 CKEDITOR.dialog.add( 'image2', this.path + 'dialogs/image2.js' );
110 },
111
112 afterInit: function( editor ) {
113 // Integrate with align commands (justify plugin).
114 var align = { left: 1, right: 1, center: 1, block: 1 },
115 integrate = alignCommandIntegrator( editor );
116
117 for ( var value in align )
118 integrate( value );
119
120 // Integrate with link commands (link plugin).
121 linkCommandIntegrator( editor );
122 }
123 } );
124
125 // Wiget states (forms) depending on alignment and configuration.
126 //
127 // Non-captioned widget (inline styles)
128 // ┌──────┬───────────────────────────────┬─────────────────────────────┐
129 // │Align │Internal form │Data │
130 // ├──────┼───────────────────────────────┼─────────────────────────────┤
131 // │none │<wrapper> │<img /> │
132 // │ │ <img /> │ │
133 // │ │</wrapper> │ │
134 // ├──────┼───────────────────────────────┼─────────────────────────────┤
135 // │left │<wrapper style=”float:left”> │<img style=”float:left” /> │
136 // │ │ <img /> │ │
137 // │ │</wrapper> │ │
138 // ├──────┼───────────────────────────────┼─────────────────────────────┤
139 // │center│<wrapper> │<p style=”text-align:center”>│
140 // │ │ <p style=”text-align:center”> │ <img /> │
141 // │ │ <img /> │</p> │
142 // │ │ </p> │ │
143 // │ │</wrapper> │ │
144 // ├──────┼───────────────────────────────┼─────────────────────────────┤
145 // │right │<wrapper style=”float:right”> │<img style=”float:right” /> │
146 // │ │ <img /> │ │
147 // │ │</wrapper> │ │
148 // └──────┴───────────────────────────────┴─────────────────────────────┘
149 //
150 // Non-captioned widget (config.image2_alignClasses defined)
151 // ┌──────┬───────────────────────────────┬─────────────────────────────┐
152 // │Align │Internal form │Data │
153 // ├──────┼───────────────────────────────┼─────────────────────────────┤
154 // │none │<wrapper> │<img /> │
155 // │ │ <img /> │ │
156 // │ │</wrapper> │ │
157 // ├──────┼───────────────────────────────┼─────────────────────────────┤
158 // │left │<wrapper class=”left”> │<img class=”left” /> │
159 // │ │ <img /> │ │
160 // │ │</wrapper> │ │
161 // ├──────┼───────────────────────────────┼─────────────────────────────┤
162 // │center│<wrapper> │<p class=”center”> │
163 // │ │ <p class=”center”> │ <img /> │
164 // │ │ <img /> │</p> │
165 // │ │ </p> │ │
166 // │ │</wrapper> │ │
167 // ├──────┼───────────────────────────────┼─────────────────────────────┤
168 // │right │<wrapper class=”right”> │<img class=”right” /> │
169 // │ │ <img /> │ │
170 // │ │</wrapper> │ │
171 // └──────┴───────────────────────────────┴─────────────────────────────┘
172 //
173 // Captioned widget (inline styles)
174 // ┌──────┬────────────────────────────────────────┬────────────────────────────────────────┐
175 // │Align │Internal form │Data │
176 // ├──────┼────────────────────────────────────────┼────────────────────────────────────────┤
177 // │none │<wrapper> │<figure /> │
178 // │ │ <figure /> │ │
179 // │ │</wrapper> │ │
180 // ├──────┼────────────────────────────────────────┼────────────────────────────────────────┤
181 // │left │<wrapper style=”float:left”> │<figure style=”float:left” /> │
182 // │ │ <figure /> │ │
183 // │ │</wrapper> │ │
184 // ├──────┼────────────────────────────────────────┼────────────────────────────────────────┤
185 // │center│<wrapper style=”text-align:center”> │<div style=”text-align:center”> │
186 // │ │ <figure style=”display:inline-block” />│ <figure style=”display:inline-block” />│
187 // │ │</wrapper> │</p> │
188 // ├──────┼────────────────────────────────────────┼────────────────────────────────────────┤
189 // │right │<wrapper style=”float:right”> │<figure style=”float:right” /> │
190 // │ │ <figure /> │ │
191 // │ │</wrapper> │ │
192 // └──────┴────────────────────────────────────────┴────────────────────────────────────────┘
193 //
194 // Captioned widget (config.image2_alignClasses defined)
195 // ┌──────┬────────────────────────────────────────┬────────────────────────────────────────┐
196 // │Align │Internal form │Data │
197 // ├──────┼────────────────────────────────────────┼────────────────────────────────────────┤
198 // │none │<wrapper> │<figure /> │
199 // │ │ <figure /> │ │
200 // │ │</wrapper> │ │
201 // ├──────┼────────────────────────────────────────┼────────────────────────────────────────┤
202 // │left │<wrapper class=”left”> │<figure class=”left” /> │
203 // │ │ <figure /> │ │
204 // │ │</wrapper> │ │
205 // ├──────┼────────────────────────────────────────┼────────────────────────────────────────┤
206 // │center│<wrapper class=”center”> │<div class=”center”> │
207 // │ │ <figure /> │ <figure /> │
208 // │ │</wrapper> │</p> │
209 // ├──────┼────────────────────────────────────────┼────────────────────────────────────────┤
210 // │right │<wrapper class=”right”> │<figure class=”right” /> │
211 // │ │ <figure /> │ │
212 // │ │</wrapper> │ │
213 // └──────┴────────────────────────────────────────┴────────────────────────────────────────┘
214 //
215 // @param {CKEDITOR.editor}
216 // @returns {Object}
217 function widgetDef( editor ) {
218 var alignClasses = editor.config.image2_alignClasses,
219 captionedClass = editor.config.image2_captionedClass;
220
221 function deflate() {
222 if ( this.deflated )
223 return;
224
225 // Remember whether widget was focused before destroyed.
226 if ( editor.widgets.focused == this.widget )
227 this.focused = true;
228
229 editor.widgets.destroy( this.widget );
230
231 // Mark widget was destroyed.
232 this.deflated = true;
233 }
234
235 function inflate() {
236 var editable = editor.editable(),
237 doc = editor.document;
238
239 // Create a new widget. This widget will be either captioned
240 // non-captioned, block or inline according to what is the
241 // new state of the widget.
242 if ( this.deflated ) {
243 this.widget = editor.widgets.initOn( this.element, 'image', this.widget.data );
244
245 // Once widget was re-created, it may become an inline element without
246 // block wrapper (i.e. when unaligned, end not captioned). Let's do some
247 // sort of autoparagraphing here (http://dev.ckeditor.com/ticket/10853).
248 if ( this.widget.inline && !( new CKEDITOR.dom.elementPath( this.widget.wrapper, editable ).block ) ) {
249 var block = doc.createElement( editor.activeEnterMode == CKEDITOR.ENTER_P ? 'p' : 'div' );
250 block.replace( this.widget.wrapper );
251 this.widget.wrapper.move( block );
252 }
253
254 // The focus must be transferred from the old one (destroyed)
255 // to the new one (just created).
256 if ( this.focused ) {
257 this.widget.focus();
258 delete this.focused;
259 }
260
261 delete this.deflated;
262 }
263
264 // If now widget was destroyed just update wrapper's alignment.
265 // According to the new state.
266 else {
267 setWrapperAlign( this.widget, alignClasses );
268 }
269 }
270
271 return {
272 allowedContent: getWidgetAllowedContent( editor ),
273
274 requiredContent: 'img[src,alt]',
275
276 features: getWidgetFeatures( editor ),
277
278 styleableElements: 'img figure',
279
280 // This widget converts style-driven dimensions to attributes.
281 contentTransformations: [
282 [ 'img[width]: sizeToAttribute' ]
283 ],
284
285 // This widget has an editable caption.
286 editables: {
287 caption: {
288 selector: 'figcaption',
289 allowedContent: 'br em strong sub sup u s; a[!href,target]'
290 }
291 },
292
293 parts: {
294 image: 'img',
295 caption: 'figcaption'
296 // parts#link defined in widget#init
297 },
298
299 // The name of this widget's dialog.
300 dialog: 'image2',
301
302 // Template of the widget: plain image.
303 template: template,
304
305 data: function() {
306 var features = this.features;
307
308 // Image can't be captioned when figcaption is disallowed (http://dev.ckeditor.com/ticket/11004).
309 if ( this.data.hasCaption && !editor.filter.checkFeature( features.caption ) )
310 this.data.hasCaption = false;
311
312 // Image can't be aligned when floating is disallowed (http://dev.ckeditor.com/ticket/11004).
313 if ( this.data.align != 'none' && !editor.filter.checkFeature( features.align ) )
314 this.data.align = 'none';
315
316 // Convert the internal form of the widget from the old state to the new one.
317 this.shiftState( {
318 widget: this,
319 element: this.element,
320 oldData: this.oldData,
321 newData: this.data,
322 deflate: deflate,
323 inflate: inflate
324 } );
325
326 // Update widget.parts.link since it will not auto-update unless widget
327 // is destroyed and re-inited.
328 if ( !this.data.link ) {
329 if ( this.parts.link )
330 delete this.parts.link;
331 } else {
332 if ( !this.parts.link )
333 this.parts.link = this.parts.image.getParent();
334 }
335
336 this.parts.image.setAttributes( {
337 src: this.data.src,
338
339 // This internal is required by the editor.
340 'data-cke-saved-src': this.data.src,
341
342 alt: this.data.alt
343 } );
344
345 // If shifting non-captioned -> captioned, remove classes
346 // related to styles from <img/>.
347 if ( this.oldData && !this.oldData.hasCaption && this.data.hasCaption ) {
348 for ( var c in this.data.classes )
349 this.parts.image.removeClass( c );
350 }
351
352 // Set dimensions of the image according to gathered data.
353 // Do it only when the attributes are allowed (http://dev.ckeditor.com/ticket/11004).
354 if ( editor.filter.checkFeature( features.dimension ) )
355 setDimensions( this );
356
357 // Cache current data.
358 this.oldData = CKEDITOR.tools.extend( {}, this.data );
359 },
360
361 init: function() {
362 var helpers = CKEDITOR.plugins.image2,
363 image = this.parts.image,
364 data = {
365 hasCaption: !!this.parts.caption,
366 src: image.getAttribute( 'src' ),
367 alt: image.getAttribute( 'alt' ) || '',
368 width: image.getAttribute( 'width' ) || '',
369 height: image.getAttribute( 'height' ) || '',
370
371 // Lock ratio is on by default (http://dev.ckeditor.com/ticket/10833).
372 lock: this.ready ? helpers.checkHasNaturalRatio( image ) : true
373 };
374
375 // If we used 'a' in widget#parts definition, it could happen that
376 // selected element is a child of widget.parts#caption. Since there's no clever
377 // way to solve it with CSS selectors, it's done like that. (http://dev.ckeditor.com/ticket/11783).
378 var link = image.getAscendant( 'a' );
379
380 if ( link && this.wrapper.contains( link ) )
381 this.parts.link = link;
382
383 // Depending on configuration, read style/class from element and
384 // then remove it. Removed style/class will be set on wrapper in #data listener.
385 // Note: Center alignment is detected during upcast, so only left/right cases
386 // are checked below.
387 if ( !data.align ) {
388 var alignElement = data.hasCaption ? this.element : image;
389
390 // Read the initial left/right alignment from the class set on element.
391 if ( alignClasses ) {
392 if ( alignElement.hasClass( alignClasses[ 0 ] ) ) {
393 data.align = 'left';
394 } else if ( alignElement.hasClass( alignClasses[ 2 ] ) ) {
395 data.align = 'right';
396 }
397
398 if ( data.align ) {
399 alignElement.removeClass( alignClasses[ alignmentsObj[ data.align ] ] );
400 } else {
401 data.align = 'none';
402 }
403 }
404 // Read initial float style from figure/image and then remove it.
405 else {
406 data.align = alignElement.getStyle( 'float' ) || 'none';
407 alignElement.removeStyle( 'float' );
408 }
409 }
410
411 // Update data.link object with attributes if the link has been discovered.
412 if ( editor.plugins.link && this.parts.link ) {
413 data.link = helpers.getLinkAttributesParser()( editor, this.parts.link );
414
415 // Get rid of cke_widget_* classes in data. Otherwise
416 // they might appear in link dialog.
417 var advanced = data.link.advanced;
418 if ( advanced && advanced.advCSSClasses ) {
419 advanced.advCSSClasses = CKEDITOR.tools.trim( advanced.advCSSClasses.replace( /cke_\S+/, '' ) );
420 }
421 }
422
423 // Get rid of extra vertical space when there's no caption.
424 // It will improve the look of the resizer.
425 this.wrapper[ ( data.hasCaption ? 'remove' : 'add' ) + 'Class' ]( 'cke_image_nocaption' );
426
427 this.setData( data );
428
429 // Setup dynamic image resizing with mouse.
430 // Don't initialize resizer when dimensions are disallowed (http://dev.ckeditor.com/ticket/11004).
431 if ( editor.filter.checkFeature( this.features.dimension ) && editor.config.image2_disableResizer !== true )
432 setupResizer( this );
433
434 this.shiftState = helpers.stateShifter( this.editor );
435
436 // Add widget editing option to its context menu.
437 this.on( 'contextMenu', function( evt ) {
438 evt.data.image = CKEDITOR.TRISTATE_OFF;
439
440 // Integrate context menu items for link.
441 // Note that widget may be wrapped in a link, which
442 // does not belong to that widget (http://dev.ckeditor.com/ticket/11814).
443 if ( this.parts.link || this.wrapper.getAscendant( 'a' ) )
444 evt.data.link = evt.data.unlink = CKEDITOR.TRISTATE_OFF;
445 } );
446
447 // Pass the reference to this widget to the dialog.
448 this.on( 'dialog', function( evt ) {
449 evt.data.widget = this;
450 }, this );
451 },
452
453 // Overrides default method to handle internal mutability of Image2.
454 // @see CKEDITOR.plugins.widget#addClass
455 addClass: function( className ) {
456 getStyleableElement( this ).addClass( className );
457 },
458
459 // Overrides default method to handle internal mutability of Image2.
460 // @see CKEDITOR.plugins.widget#hasClass
461 hasClass: function( className ) {
462 return getStyleableElement( this ).hasClass( className );
463 },
464
465 // Overrides default method to handle internal mutability of Image2.
466 // @see CKEDITOR.plugins.widget#removeClass
467 removeClass: function( className ) {
468 getStyleableElement( this ).removeClass( className );
469 },
470
471 // Overrides default method to handle internal mutability of Image2.
472 // @see CKEDITOR.plugins.widget#getClasses
473 getClasses: ( function() {
474 var classRegex = new RegExp( '^(' + [].concat( captionedClass, alignClasses ).join( '|' ) + ')$' );
475
476 return function() {
477 var classes = this.repository.parseElementClasses( getStyleableElement( this ).getAttribute( 'class' ) );
478
479 // Neither config.image2_captionedClass nor config.image2_alignClasses
480 // do not belong to style classes.
481 for ( var c in classes ) {
482 if ( classRegex.test( c ) )
483 delete classes[ c ];
484 }
485
486 return classes;
487 };
488 } )(),
489
490 upcast: upcastWidgetElement( editor ),
491 downcast: downcastWidgetElement( editor ),
492
493 getLabel: function() {
494 var label = ( this.data.alt || '' ) + ' ' + this.pathName;
495
496 return this.editor.lang.widget.label.replace( /%1/, label );
497 }
498 };
499 }
500
501 /**
502 * A set of Enhanced Image (image2) plugin helpers.
503 *
504 * @class
505 * @singleton
506 */
507 CKEDITOR.plugins.image2 = {
508 stateShifter: function( editor ) {
509 // Tag name used for centering non-captioned widgets.
510 var doc = editor.document,
511 alignClasses = editor.config.image2_alignClasses,
512 captionedClass = editor.config.image2_captionedClass,
513 editable = editor.editable(),
514
515 // The order that stateActions get executed. It matters!
516 shiftables = [ 'hasCaption', 'align', 'link' ];
517
518 // Atomic procedures, one per state variable.
519 var stateActions = {
520 align: function( shift, oldValue, newValue ) {
521 var el = shift.element;
522
523 // Alignment changed.
524 if ( shift.changed.align ) {
525 // No caption in the new state.
526 if ( !shift.newData.hasCaption ) {
527 // Changed to "center" (non-captioned).
528 if ( newValue == 'center' ) {
529 shift.deflate();
530 shift.element = wrapInCentering( editor, el );
531 }
532
533 // Changed to "non-center" from "center" while caption removed.
534 if ( !shift.changed.hasCaption && oldValue == 'center' && newValue != 'center' ) {
535 shift.deflate();
536 shift.element = unwrapFromCentering( el );
537 }
538 }
539 }
540
541 // Alignment remains and "center" removed caption.
542 else if ( newValue == 'center' && shift.changed.hasCaption && !shift.newData.hasCaption ) {
543 shift.deflate();
544 shift.element = wrapInCentering( editor, el );
545 }
546
547 // Finally set display for figure.
548 if ( !alignClasses && el.is( 'figure' ) ) {
549 if ( newValue == 'center' )
550 el.setStyle( 'display', 'inline-block' );
551 else
552 el.removeStyle( 'display' );
553 }
554 },
555
556 hasCaption: function( shift, oldValue, newValue ) {
557 // This action is for real state change only.
558 if ( !shift.changed.hasCaption )
559 return;
560
561 // Get <img/> or <a><img/></a> from widget. Note that widget element might itself
562 // be what we're looking for. Also element can be <p style="text-align:center"><a>...</a></p>.
563 var imageOrLink;
564 if ( shift.element.is( { img: 1, a: 1 } ) )
565 imageOrLink = shift.element;
566 else
567 imageOrLink = shift.element.findOne( 'a,img' );
568
569 // Switching hasCaption always destroys the widget.
570 shift.deflate();
571
572 // There was no caption, but the caption is to be added.
573 if ( newValue ) {
574 // Create new <figure> from widget template.
575 var figure = CKEDITOR.dom.element.createFromHtml( templateBlock.output( {
576 captionedClass: captionedClass,
577 captionPlaceholder: editor.lang.image2.captionPlaceholder
578 } ), doc );
579
580 // Replace element with <figure>.
581 replaceSafely( figure, shift.element );
582
583 // Use old <img/> or <a><img/></a> instead of the one from the template,
584 // so we won't lose additional attributes.
585 imageOrLink.replace( figure.findOne( 'img' ) );
586
587 // Update widget's element.
588 shift.element = figure;
589 }
590
591 // The caption was present, but now it's to be removed.
592 else {
593 // Unwrap <img/> or <a><img/></a> from figure.
594 imageOrLink.replace( shift.element );
595
596 // Update widget's element.
597 shift.element = imageOrLink;
598 }
599 },
600
601 link: function( shift, oldValue, newValue ) {
602 if ( shift.changed.link ) {
603 var img = shift.element.is( 'img' ) ?
604 shift.element : shift.element.findOne( 'img' ),
605 link = shift.element.is( 'a' ) ?
606 shift.element : shift.element.findOne( 'a' ),
607 // Why deflate:
608 // If element is <img/>, it will be wrapped into <a>,
609 // which becomes a new widget.element.
610 // If element is <a><img/></a>, it will be unlinked
611 // so <img/> becomes a new widget.element.
612 needsDeflate = ( shift.element.is( 'a' ) && !newValue ) || ( shift.element.is( 'img' ) && newValue ),
613 newEl;
614
615 if ( needsDeflate )
616 shift.deflate();
617
618 // If unlinked the image, returned element is <img>.
619 if ( !newValue )
620 newEl = unwrapFromLink( link );
621 else {
622 // If linked the image, returned element is <a>.
623 if ( !oldValue )
624 newEl = wrapInLink( img, shift.newData.link );
625
626 // Set and remove all attributes associated with this state.
627 var attributes = CKEDITOR.plugins.image2.getLinkAttributesGetter()( editor, newValue );
628
629 if ( !CKEDITOR.tools.isEmpty( attributes.set ) )
630 ( newEl || link ).setAttributes( attributes.set );
631
632 if ( attributes.removed.length )
633 ( newEl || link ).removeAttributes( attributes.removed );
634 }
635
636 if ( needsDeflate )
637 shift.element = newEl;
638 }
639 }
640 };
641
642 function wrapInCentering( editor, element ) {
643 var attribsAndStyles = {};
644
645 if ( alignClasses )
646 attribsAndStyles.attributes = { 'class': alignClasses[ 1 ] };
647 else
648 attribsAndStyles.styles = { 'text-align': 'center' };
649
650 // There's no gentle way to center inline element with CSS, so create p/div
651 // that wraps widget contents and does the trick either with style or class.
652 var center = doc.createElement(
653 editor.activeEnterMode == CKEDITOR.ENTER_P ? 'p' : 'div', attribsAndStyles );
654
655 // Replace element with centering wrapper.
656 replaceSafely( center, element );
657 element.move( center );
658
659 return center;
660 }
661
662 function unwrapFromCentering( element ) {
663 var imageOrLink = element.findOne( 'a,img' );
664
665 imageOrLink.replace( element );
666
667 return imageOrLink;
668 }
669
670 // Wraps <img/> -> <a><img/></a>.
671 // Returns reference to <a>.
672 //
673 // @param {CKEDITOR.dom.element} img
674 // @param {Object} linkData
675 // @returns {CKEDITOR.dom.element}
676 function wrapInLink( img, linkData ) {
677 var link = doc.createElement( 'a', {
678 attributes: {
679 href: linkData.url
680 }
681 } );
682
683 link.replace( img );
684 img.move( link );
685
686 return link;
687 }
688
689 // De-wraps <a><img/></a> -> <img/>.
690 // Returns the reference to <img/>
691 //
692 // @param {CKEDITOR.dom.element} link
693 // @returns {CKEDITOR.dom.element}
694 function unwrapFromLink( link ) {
695 var img = link.findOne( 'img' );
696
697 img.replace( link );
698
699 return img;
700 }
701
702 function replaceSafely( replacing, replaced ) {
703 if ( replaced.getParent() ) {
704 var range = editor.createRange();
705
706 range.moveToPosition( replaced, CKEDITOR.POSITION_BEFORE_START );
707
708 // Remove old element. Do it before insertion to avoid a case when
709 // element is moved from 'replaced' element before it, what creates
710 // a tricky case which insertElementIntorRange does not handle.
711 replaced.remove();
712
713 editable.insertElementIntoRange( replacing, range );
714 }
715 else {
716 replacing.replace( replaced );
717 }
718 }
719
720 return function( shift ) {
721 var name, i;
722
723 shift.changed = {};
724
725 for ( i = 0; i < shiftables.length; i++ ) {
726 name = shiftables[ i ];
727
728 shift.changed[ name ] = shift.oldData ?
729 shift.oldData[ name ] !== shift.newData[ name ] : false;
730 }
731
732 // Iterate over possible state variables.
733 for ( i = 0; i < shiftables.length; i++ ) {
734 name = shiftables[ i ];
735
736 stateActions[ name ]( shift,
737 shift.oldData ? shift.oldData[ name ] : null,
738 shift.newData[ name ] );
739 }
740
741 shift.inflate();
742 };
743 },
744
745 /**
746 * Checks whether the current image ratio matches the natural one
747 * by comparing dimensions.
748 *
749 * @param {CKEDITOR.dom.element} image
750 * @returns {Boolean}
751 */
752 checkHasNaturalRatio: function( image ) {
753 var $ = image.$,
754 natural = this.getNatural( image );
755
756 // The reason for two alternative comparisons is that the rounding can come from
757 // both dimensions, e.g. there are two cases:
758 // 1. height is computed as a rounded relation of the real height and the value of width,
759 // 2. width is computed as a rounded relation of the real width and the value of heigh.
760 return Math.round( $.clientWidth / natural.width * natural.height ) == $.clientHeight ||
761 Math.round( $.clientHeight / natural.height * natural.width ) == $.clientWidth;
762 },
763
764 /**
765 * Returns natural dimensions of the image. For modern browsers
766 * it uses natural(Width|Height). For old ones (IE8) it creates
767 * a new image and reads the dimensions.
768 *
769 * @param {CKEDITOR.dom.element} image
770 * @returns {Object}
771 */
772 getNatural: function( image ) {
773 var dimensions;
774
775 if ( image.$.naturalWidth ) {
776 dimensions = {
777 width: image.$.naturalWidth,
778 height: image.$.naturalHeight
779 };
780 } else {
781 var img = new Image();
782 img.src = image.getAttribute( 'src' );
783
784 dimensions = {
785 width: img.width,
786 height: img.height
787 };
788 }
789
790 return dimensions;
791 },
792
793 /**
794 * Returns an attribute getter function. Default getter comes from the Link plugin
795 * and is documented by {@link CKEDITOR.plugins.link#getLinkAttributes}.
796 *
797 * **Note:** It is possible to override this method and use a custom getter e.g.
798 * in the absence of the Link plugin.
799 *
800 * **Note:** If a custom getter is used, a data model format it produces
801 * must be compatible with {@link CKEDITOR.plugins.link#getLinkAttributes}.
802 *
803 * **Note:** A custom getter must understand the data model format produced by
804 * {@link #getLinkAttributesParser} to work correctly.
805 *
806 * @returns {Function} A function that gets (composes) link attributes.
807 * @since 4.5.5
808 */
809 getLinkAttributesGetter: function() {
810 // http://dev.ckeditor.com/ticket/13885
811 return CKEDITOR.plugins.link.getLinkAttributes;
812 },
813
814 /**
815 * Returns an attribute parser function. Default parser comes from the Link plugin
816 * and is documented by {@link CKEDITOR.plugins.link#parseLinkAttributes}.
817 *
818 * **Note:** It is possible to override this method and use a custom parser e.g.
819 * in the absence of the Link plugin.
820 *
821 * **Note:** If a custom parser is used, a data model format produced by the parser
822 * must be compatible with {@link #getLinkAttributesGetter}.
823 *
824 * **Note:** If a custom parser is used, it should be compatible with the
825 * {@link CKEDITOR.plugins.link#parseLinkAttributes} data model format. Otherwise the
826 * Link plugin dialog may not be populated correctly with parsed data. However
827 * as long as Enhanced Image is **not** used with the Link plugin dialog, any custom data model
828 * will work, being stored as an internal property of Enhanced Image widget's data only.
829 *
830 * @returns {Function} A function that parses attributes.
831 * @since 4.5.5
832 */
833 getLinkAttributesParser: function() {
834 // http://dev.ckeditor.com/ticket/13885
835 return CKEDITOR.plugins.link.parseLinkAttributes;
836 }
837 };
838
839 function setWrapperAlign( widget, alignClasses ) {
840 var wrapper = widget.wrapper,
841 align = widget.data.align,
842 hasCaption = widget.data.hasCaption;
843
844 if ( alignClasses ) {
845 // Remove all align classes first.
846 for ( var i = 3; i--; )
847 wrapper.removeClass( alignClasses[ i ] );
848
849 if ( align == 'center' ) {
850 // Avoid touching non-captioned, centered widgets because
851 // they have the class set on the element instead of wrapper:
852 //
853 // <div class="cke_widget_wrapper">
854 // <p class="center-class">
855 // <img />
856 // </p>
857 // </div>
858 if ( hasCaption ) {
859 wrapper.addClass( alignClasses[ 1 ] );
860 }
861 } else if ( align != 'none' ) {
862 wrapper.addClass( alignClasses[ alignmentsObj[ align ] ] );
863 }
864 } else {
865 if ( align == 'center' ) {
866 if ( hasCaption )
867 wrapper.setStyle( 'text-align', 'center' );
868 else
869 wrapper.removeStyle( 'text-align' );
870
871 wrapper.removeStyle( 'float' );
872 }
873 else {
874 if ( align == 'none' )
875 wrapper.removeStyle( 'float' );
876 else
877 wrapper.setStyle( 'float', align );
878
879 wrapper.removeStyle( 'text-align' );
880 }
881 }
882 }
883
884 // Returns a function that creates widgets from all <img> and
885 // <figure class="{config.image2_captionedClass}"> elements.
886 //
887 // @param {CKEDITOR.editor} editor
888 // @returns {Function}
889 function upcastWidgetElement( editor ) {
890 var isCenterWrapper = centerWrapperChecker( editor ),
891 captionedClass = editor.config.image2_captionedClass;
892
893 // @param {CKEDITOR.htmlParser.element} el
894 // @param {Object} data
895 return function( el, data ) {
896 var dimensions = { width: 1, height: 1 },
897 name = el.name,
898 image;
899
900 // http://dev.ckeditor.com/ticket/11110 Don't initialize on pasted fake objects.
901 if ( el.attributes[ 'data-cke-realelement' ] )
902 return;
903
904 // If a center wrapper is found, there are 3 possible cases:
905 //
906 // 1. <div style="text-align:center"><figure>...</figure></div>.
907 // In this case centering is done with a class set on widget.wrapper.
908 // Simply replace centering wrapper with figure (it's no longer necessary).
909 //
910 // 2. <p style="text-align:center"><img/></p>.
911 // Nothing to do here: <p> remains for styling purposes.
912 //
913 // 3. <div style="text-align:center"><img/></div>.
914 // Nothing to do here (2.) but that case is only possible in enterMode different
915 // than ENTER_P.
916 if ( isCenterWrapper( el ) ) {
917 if ( name == 'div' ) {
918 var figure = el.getFirst( 'figure' );
919
920 // Case #1.
921 if ( figure ) {
922 el.replaceWith( figure );
923 el = figure;
924 }
925 }
926 // Cases #2 and #3 (handled transparently)
927
928 // If there's a centering wrapper, save it in data.
929 data.align = 'center';
930
931 // Image can be wrapped in link <a><img/></a>.
932 image = el.getFirst( 'img' ) || el.getFirst( 'a' ).getFirst( 'img' );
933 }
934
935 // No center wrapper has been found.
936 else if ( name == 'figure' && el.hasClass( captionedClass ) ) {
937 image = el.getFirst( 'img' ) || el.getFirst( 'a' ).getFirst( 'img' );
938
939 // Upcast linked image like <a><img/></a>.
940 } else if ( isLinkedOrStandaloneImage( el ) ) {
941 image = el.name == 'a' ? el.children[ 0 ] : el;
942 }
943
944 if ( !image )
945 return;
946
947 // If there's an image, then cool, we got a widget.
948 // Now just remove dimension attributes expressed with %.
949 for ( var d in dimensions ) {
950 var dimension = image.attributes[ d ];
951
952 if ( dimension && dimension.match( regexPercent ) )
953 delete image.attributes[ d ];
954 }
955
956 return el;
957 };
958 }
959
960 // Returns a function which transforms the widget to the external format
961 // according to the current configuration.
962 //
963 // @param {CKEDITOR.editor}
964 function downcastWidgetElement( editor ) {
965 var alignClasses = editor.config.image2_alignClasses;
966
967 // @param {CKEDITOR.htmlParser.element} el
968 return function( el ) {
969 // In case of <a><img/></a>, <img/> is the element to hold
970 // inline styles or classes (image2_alignClasses).
971 var attrsHolder = el.name == 'a' ? el.getFirst() : el,
972 attrs = attrsHolder.attributes,
973 align = this.data.align;
974
975 // De-wrap the image from resize handle wrapper.
976 // Only block widgets have one.
977 if ( !this.inline ) {
978 var resizeWrapper = el.getFirst( 'span' );
979
980 if ( resizeWrapper )
981 resizeWrapper.replaceWith( resizeWrapper.getFirst( { img: 1, a: 1 } ) );
982 }
983
984 if ( align && align != 'none' ) {
985 var styles = CKEDITOR.tools.parseCssText( attrs.style || '' );
986
987 // When the widget is captioned (<figure>) and internally centering is done
988 // with widget's wrapper style/class, in the external data representation,
989 // <figure> must be wrapped with an element holding an style/class:
990 //
991 // <div style="text-align:center">
992 // <figure class="image" style="display:inline-block">...</figure>
993 // </div>
994 // or
995 // <div class="some-center-class">
996 // <figure class="image">...</figure>
997 // </div>
998 //
999 if ( align == 'center' && el.name == 'figure' ) {
1000 el = el.wrapWith( new CKEDITOR.htmlParser.element( 'div',
1001 alignClasses ? { 'class': alignClasses[ 1 ] } : { style: 'text-align:center' } ) );
1002 }
1003
1004 // If left/right, add float style to the downcasted element.
1005 else if ( align in { left: 1, right: 1 } ) {
1006 if ( alignClasses )
1007 attrsHolder.addClass( alignClasses[ alignmentsObj[ align ] ] );
1008 else
1009 styles[ 'float' ] = align;
1010 }
1011
1012 // Update element styles.
1013 if ( !alignClasses && !CKEDITOR.tools.isEmpty( styles ) )
1014 attrs.style = CKEDITOR.tools.writeCssText( styles );
1015 }
1016
1017 return el;
1018 };
1019 }
1020
1021 // Returns a function that checks if an element is a centering wrapper.
1022 //
1023 // @param {CKEDITOR.editor} editor
1024 // @returns {Function}
1025 function centerWrapperChecker( editor ) {
1026 var captionedClass = editor.config.image2_captionedClass,
1027 alignClasses = editor.config.image2_alignClasses,
1028 validChildren = { figure: 1, a: 1, img: 1 };
1029
1030 return function( el ) {
1031 // Wrapper must be either <div> or <p>.
1032 if ( !( el.name in { div: 1, p: 1 } ) )
1033 return false;
1034
1035 var children = el.children;
1036
1037 // Centering wrapper can have only one child.
1038 if ( children.length !== 1 )
1039 return false;
1040
1041 var child = children[ 0 ];
1042
1043 // Only <figure> or <img /> can be first (only) child of centering wrapper,
1044 // regardless of its type.
1045 if ( !( child.name in validChildren ) )
1046 return false;
1047
1048 // If centering wrapper is <p>, only <img /> can be the child.
1049 // <p style="text-align:center"><img /></p>
1050 if ( el.name == 'p' ) {
1051 if ( !isLinkedOrStandaloneImage( child ) )
1052 return false;
1053 }
1054 // Centering <div> can hold <img/> or <figure>, depending on enterMode.
1055 else {
1056 // If a <figure> is the first (only) child, it must have a class.
1057 // <div style="text-align:center"><figure>...</figure><div>
1058 if ( child.name == 'figure' ) {
1059 if ( !child.hasClass( captionedClass ) )
1060 return false;
1061 } else {
1062 // Centering <div> can hold <img/> or <a><img/></a> only when enterMode
1063 // is ENTER_(BR|DIV).
1064 // <div style="text-align:center"><img /></div>
1065 // <div style="text-align:center"><a><img /></a></div>
1066 if ( editor.enterMode == CKEDITOR.ENTER_P )
1067 return false;
1068
1069 // Regardless of enterMode, a child which is not <figure> must be
1070 // either <img/> or <a><img/></a>.
1071 if ( !isLinkedOrStandaloneImage( child ) )
1072 return false;
1073 }
1074 }
1075
1076 // Centering wrapper got to be... centering. If image2_alignClasses are defined,
1077 // check for centering class. Otherwise, check the style.
1078 if ( alignClasses ? el.hasClass( alignClasses[ 1 ] ) :
1079 CKEDITOR.tools.parseCssText( el.attributes.style || '', true )[ 'text-align' ] == 'center' )
1080 return true;
1081
1082 return false;
1083 };
1084 }
1085
1086 // Checks whether element is <img/> or <a><img/></a>.
1087 //
1088 // @param {CKEDITOR.htmlParser.element}
1089 function isLinkedOrStandaloneImage( el ) {
1090 if ( el.name == 'img' )
1091 return true;
1092 else if ( el.name == 'a' )
1093 return el.children.length == 1 && el.getFirst( 'img' );
1094
1095 return false;
1096 }
1097
1098 // Sets width and height of the widget image according to current widget data.
1099 //
1100 // @param {CKEDITOR.plugins.widget} widget
1101 function setDimensions( widget ) {
1102 var data = widget.data,
1103 dimensions = { width: data.width, height: data.height },
1104 image = widget.parts.image;
1105
1106 for ( var d in dimensions ) {
1107 if ( dimensions[ d ] )
1108 image.setAttribute( d, dimensions[ d ] );
1109 else
1110 image.removeAttribute( d );
1111 }
1112 }
1113
1114 // Defines all features related to drag-driven image resizing.
1115 //
1116 // @param {CKEDITOR.plugins.widget} widget
1117 function setupResizer( widget ) {
1118 var editor = widget.editor,
1119 editable = editor.editable(),
1120 doc = editor.document,
1121
1122 // Store the resizer in a widget for testing (http://dev.ckeditor.com/ticket/11004).
1123 resizer = widget.resizer = doc.createElement( 'span' );
1124
1125 resizer.addClass( 'cke_image_resizer' );
1126 resizer.setAttribute( 'title', editor.lang.image2.resizer );
1127 resizer.append( new CKEDITOR.dom.text( '\u200b', doc ) );
1128
1129 // Inline widgets don't need a resizer wrapper as an image spans the entire widget.
1130 if ( !widget.inline ) {
1131 var imageOrLink = widget.parts.link || widget.parts.image,
1132 oldResizeWrapper = imageOrLink.getParent(),
1133 resizeWrapper = doc.createElement( 'span' );
1134
1135 resizeWrapper.addClass( 'cke_image_resizer_wrapper' );
1136 resizeWrapper.append( imageOrLink );
1137 resizeWrapper.append( resizer );
1138 widget.element.append( resizeWrapper, true );
1139
1140 // Remove the old wrapper which could came from e.g. pasted HTML
1141 // and which could be corrupted (e.g. resizer span has been lost).
1142 if ( oldResizeWrapper.is( 'span' ) )
1143 oldResizeWrapper.remove();
1144 } else {
1145 widget.wrapper.append( resizer );
1146 }
1147
1148 // Calculate values of size variables and mouse offsets.
1149 resizer.on( 'mousedown', function( evt ) {
1150 var image = widget.parts.image,
1151
1152 // "factor" can be either 1 or -1. I.e.: For right-aligned images, we need to
1153 // subtract the difference to get proper width, etc. Without "factor",
1154 // resizer starts working the opposite way.
1155 factor = widget.data.align == 'right' ? -1 : 1,
1156
1157 // The x-coordinate of the mouse relative to the screen
1158 // when button gets pressed.
1159 startX = evt.data.$.screenX,
1160 startY = evt.data.$.screenY,
1161
1162 // The initial dimensions and aspect ratio of the image.
1163 startWidth = image.$.clientWidth,
1164 startHeight = image.$.clientHeight,
1165 ratio = startWidth / startHeight,
1166
1167 listeners = [],
1168
1169 // A class applied to editable during resizing.
1170 cursorClass = 'cke_image_s' + ( !~factor ? 'w' : 'e' ),
1171
1172 nativeEvt, newWidth, newHeight, updateData,
1173 moveDiffX, moveDiffY, moveRatio;
1174
1175 // Save the undo snapshot first: before resizing.
1176 editor.fire( 'saveSnapshot' );
1177
1178 // Mousemove listeners are removed on mouseup.
1179 attachToDocuments( 'mousemove', onMouseMove, listeners );
1180
1181 // Clean up the mousemove listener. Update widget data if valid.
1182 attachToDocuments( 'mouseup', onMouseUp, listeners );
1183
1184 // The entire editable will have the special cursor while resizing goes on.
1185 editable.addClass( cursorClass );
1186
1187 // This is to always keep the resizer element visible while resizing.
1188 resizer.addClass( 'cke_image_resizing' );
1189
1190 // Attaches an event to a global document if inline editor.
1191 // Additionally, if classic (`iframe`-based) editor, also attaches the same event to `iframe`'s document.
1192 function attachToDocuments( name, callback, collection ) {
1193 var globalDoc = CKEDITOR.document,
1194 listeners = [];
1195
1196 if ( !doc.equals( globalDoc ) )
1197 listeners.push( globalDoc.on( name, callback ) );
1198
1199 listeners.push( doc.on( name, callback ) );
1200
1201 if ( collection ) {
1202 for ( var i = listeners.length; i--; )
1203 collection.push( listeners.pop() );
1204 }
1205 }
1206
1207 // Calculate with first, and then adjust height, preserving ratio.
1208 function adjustToX() {
1209 newWidth = startWidth + factor * moveDiffX;
1210 newHeight = Math.round( newWidth / ratio );
1211 }
1212
1213 // Calculate height first, and then adjust width, preserving ratio.
1214 function adjustToY() {
1215 newHeight = startHeight - moveDiffY;
1216 newWidth = Math.round( newHeight * ratio );
1217 }
1218
1219 // This is how variables refer to the geometry.
1220 // Note: x corresponds to moveOffset, this is the position of mouse
1221 // Note: o corresponds to [startX, startY].
1222 //
1223 // +--------------+--------------+
1224 // | | |
1225 // | I | II |
1226 // | | |
1227 // +------------- o -------------+ _ _ _
1228 // | | | ^
1229 // | VI | III | | moveDiffY
1230 // | | x _ _ _ _ _ v
1231 // +--------------+---------|----+
1232 // | |
1233 // <------->
1234 // moveDiffX
1235 function onMouseMove( evt ) {
1236 nativeEvt = evt.data.$;
1237
1238 // This is how far the mouse is from the point the button was pressed.
1239 moveDiffX = nativeEvt.screenX - startX;
1240 moveDiffY = startY - nativeEvt.screenY;
1241
1242 // This is the aspect ratio of the move difference.
1243 moveRatio = Math.abs( moveDiffX / moveDiffY );
1244
1245 // Left, center or none-aligned widget.
1246 if ( factor == 1 ) {
1247 if ( moveDiffX <= 0 ) {
1248 // Case: IV.
1249 if ( moveDiffY <= 0 )
1250 adjustToX();
1251
1252 // Case: I.
1253 else {
1254 if ( moveRatio >= ratio )
1255 adjustToX();
1256 else
1257 adjustToY();
1258 }
1259 } else {
1260 // Case: III.
1261 if ( moveDiffY <= 0 ) {
1262 if ( moveRatio >= ratio )
1263 adjustToY();
1264 else
1265 adjustToX();
1266 }
1267
1268 // Case: II.
1269 else {
1270 adjustToY();
1271 }
1272 }
1273 }
1274
1275 // Right-aligned widget. It mirrors behaviours, so I becomes II,
1276 // IV becomes III and vice-versa.
1277 else {
1278 if ( moveDiffX <= 0 ) {
1279 // Case: IV.
1280 if ( moveDiffY <= 0 ) {
1281 if ( moveRatio >= ratio )
1282 adjustToY();
1283 else
1284 adjustToX();
1285 }
1286
1287 // Case: I.
1288 else {
1289 adjustToY();
1290 }
1291 } else {
1292 // Case: III.
1293 if ( moveDiffY <= 0 )
1294 adjustToX();
1295
1296 // Case: II.
1297 else {
1298 if ( moveRatio >= ratio ) {
1299 adjustToX();
1300 } else {
1301 adjustToY();
1302 }
1303 }
1304 }
1305 }
1306
1307 // Don't update attributes if less than 10.
1308 // This is to prevent images to visually disappear.
1309 if ( newWidth >= 15 && newHeight >= 15 ) {
1310 image.setAttributes( { width: newWidth, height: newHeight } );
1311 updateData = true;
1312 } else {
1313 updateData = false;
1314 }
1315 }
1316
1317 function onMouseUp() {
1318 var l;
1319
1320 while ( ( l = listeners.pop() ) )
1321 l.removeListener();
1322
1323 // Restore default cursor by removing special class.
1324 editable.removeClass( cursorClass );
1325
1326 // This is to bring back the regular behaviour of the resizer.
1327 resizer.removeClass( 'cke_image_resizing' );
1328
1329 if ( updateData ) {
1330 widget.setData( { width: newWidth, height: newHeight } );
1331
1332 // Save another undo snapshot: after resizing.
1333 editor.fire( 'saveSnapshot' );
1334 }
1335
1336 // Don't update data twice or more.
1337 updateData = false;
1338 }
1339 } );
1340
1341 // Change the position of the widget resizer when data changes.
1342 widget.on( 'data', function() {
1343 resizer[ widget.data.align == 'right' ? 'addClass' : 'removeClass' ]( 'cke_image_resizer_left' );
1344 } );
1345 }
1346
1347 // Integrates widget alignment setting with justify
1348 // plugin's commands (execution and refreshment).
1349 // @param {CKEDITOR.editor} editor
1350 // @param {String} value 'left', 'right', 'center' or 'block'
1351 function alignCommandIntegrator( editor ) {
1352 var execCallbacks = [],
1353 enabled;
1354
1355 return function( value ) {
1356 var command = editor.getCommand( 'justify' + value );
1357
1358 // Most likely, the justify plugin isn't loaded.
1359 if ( !command )
1360 return;
1361
1362 // This command will be manually refreshed along with
1363 // other commands after exec.
1364 execCallbacks.push( function() {
1365 command.refresh( editor, editor.elementPath() );
1366 } );
1367
1368 if ( value in { right: 1, left: 1, center: 1 } ) {
1369 command.on( 'exec', function( evt ) {
1370 var widget = getFocusedWidget( editor );
1371
1372 if ( widget ) {
1373 widget.setData( 'align', value );
1374
1375 // Once the widget changed its align, all the align commands
1376 // must be refreshed: the event is to be cancelled.
1377 for ( var i = execCallbacks.length; i--; )
1378 execCallbacks[ i ]();
1379
1380 evt.cancel();
1381 }
1382 } );
1383 }
1384
1385 command.on( 'refresh', function( evt ) {
1386 var widget = getFocusedWidget( editor ),
1387 allowed = { right: 1, left: 1, center: 1 };
1388
1389 if ( !widget )
1390 return;
1391
1392 // Cache "enabled" on first use. This is because filter#checkFeature may
1393 // not be available during plugin's afterInit in the future — a moment when
1394 // alignCommandIntegrator is called.
1395 if ( enabled === undefined )
1396 enabled = editor.filter.checkFeature( editor.widgets.registered.image.features.align );
1397
1398 // Don't allow justify commands when widget alignment is disabled (http://dev.ckeditor.com/ticket/11004).
1399 if ( !enabled )
1400 this.setState( CKEDITOR.TRISTATE_DISABLED );
1401 else {
1402 this.setState(
1403 ( widget.data.align == value ) ? (
1404 CKEDITOR.TRISTATE_ON
1405 ) : (
1406 ( value in allowed ) ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED
1407 )
1408 );
1409 }
1410
1411 evt.cancel();
1412 } );
1413 };
1414 }
1415
1416 function linkCommandIntegrator( editor ) {
1417 // Nothing to integrate with if link is not loaded.
1418 if ( !editor.plugins.link )
1419 return;
1420
1421 CKEDITOR.on( 'dialogDefinition', function( evt ) {
1422 var dialog = evt.data;
1423
1424 if ( dialog.name == 'link' ) {
1425 var def = dialog.definition;
1426
1427 var onShow = def.onShow,
1428 onOk = def.onOk;
1429
1430 def.onShow = function() {
1431 var widget = getFocusedWidget( editor ),
1432 displayTextField = this.getContentElement( 'info', 'linkDisplayText' ).getElement().getParent().getParent();
1433
1434 // Widget cannot be enclosed in a link, i.e.
1435 // <a>foo<inline widget/>bar</a>
1436 if ( widget && ( widget.inline ? !widget.wrapper.getAscendant( 'a' ) : 1 ) ) {
1437 this.setupContent( widget.data.link || {} );
1438
1439 // Hide the display text in case of linking image2 widget.
1440 displayTextField.hide();
1441 } else {
1442 // Make sure that display text is visible, as it might be hidden by image2 integration
1443 // before.
1444 displayTextField.show();
1445 onShow.apply( this, arguments );
1446 }
1447 };
1448
1449 // Set widget data if linking the widget using
1450 // link dialog (instead of default action).
1451 // State shifter handles data change and takes
1452 // care of internal DOM structure of linked widget.
1453 def.onOk = function() {
1454 var widget = getFocusedWidget( editor );
1455
1456 // Widget cannot be enclosed in a link, i.e.
1457 // <a>foo<inline widget/>bar</a>
1458 if ( widget && ( widget.inline ? !widget.wrapper.getAscendant( 'a' ) : 1 ) ) {
1459 var data = {};
1460
1461 // Collect data from fields.
1462 this.commitContent( data );
1463
1464 // Set collected data to widget.
1465 widget.setData( 'link', data );
1466 } else {
1467 onOk.apply( this, arguments );
1468 }
1469 };
1470 }
1471 } );
1472
1473 // Overwrite default behaviour of unlink command.
1474 editor.getCommand( 'unlink' ).on( 'exec', function( evt ) {
1475 var widget = getFocusedWidget( editor );
1476
1477 // Override unlink only when link truly belongs to the widget.
1478 // If wrapped inline widget in a link, let default unlink work (http://dev.ckeditor.com/ticket/11814).
1479 if ( !widget || !widget.parts.link )
1480 return;
1481
1482 widget.setData( 'link', null );
1483
1484 // Selection (which is fake) may not change if unlinked image in focused widget,
1485 // i.e. if captioned image. Let's refresh command state manually here.
1486 this.refresh( editor, editor.elementPath() );
1487
1488 evt.cancel();
1489 } );
1490
1491 // Overwrite default refresh of unlink command.
1492 editor.getCommand( 'unlink' ).on( 'refresh', function( evt ) {
1493 var widget = getFocusedWidget( editor );
1494
1495 if ( !widget )
1496 return;
1497
1498 // Note that widget may be wrapped in a link, which
1499 // does not belong to that widget (http://dev.ckeditor.com/ticket/11814).
1500 this.setState( widget.data.link || widget.wrapper.getAscendant( 'a' ) ?
1501 CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED );
1502
1503 evt.cancel();
1504 } );
1505 }
1506
1507 // Returns the focused widget, if of the type specific for this plugin.
1508 // If no widget is focused, `null` is returned.
1509 //
1510 // @param {CKEDITOR.editor}
1511 // @returns {CKEDITOR.plugins.widget}
1512 function getFocusedWidget( editor ) {
1513 var widget = editor.widgets.focused;
1514
1515 if ( widget && widget.name == 'image' )
1516 return widget;
1517
1518 return null;
1519 }
1520
1521 // Returns a set of widget allowedContent rules, depending
1522 // on configurations like config#image2_alignClasses or
1523 // config#image2_captionedClass.
1524 //
1525 // @param {CKEDITOR.editor}
1526 // @returns {Object}
1527 function getWidgetAllowedContent( editor ) {
1528 var alignClasses = editor.config.image2_alignClasses,
1529 rules = {
1530 // Widget may need <div> or <p> centering wrapper.
1531 div: {
1532 match: centerWrapperChecker( editor )
1533 },
1534 p: {
1535 match: centerWrapperChecker( editor )
1536 },
1537 img: {
1538 attributes: '!src,alt,width,height'
1539 },
1540 figure: {
1541 classes: '!' + editor.config.image2_captionedClass
1542 },
1543 figcaption: true
1544 };
1545
1546 if ( alignClasses ) {
1547 // Centering class from the config.
1548 rules.div.classes = alignClasses[ 1 ];
1549 rules.p.classes = rules.div.classes;
1550
1551 // Left/right classes from the config.
1552 rules.img.classes = alignClasses[ 0 ] + ',' + alignClasses[ 2 ];
1553 rules.figure.classes += ',' + rules.img.classes;
1554 } else {
1555 // Centering with text-align.
1556 rules.div.styles = 'text-align';
1557 rules.p.styles = 'text-align';
1558
1559 rules.img.styles = 'float';
1560 rules.figure.styles = 'float,display';
1561 }
1562
1563 return rules;
1564 }
1565
1566 // Returns a set of widget feature rules, depending
1567 // on editor configuration. Note that the following may not cover
1568 // all the possible cases since requiredContent supports a single
1569 // tag only.
1570 //
1571 // @param {CKEDITOR.editor}
1572 // @returns {Object}
1573 function getWidgetFeatures( editor ) {
1574 var alignClasses = editor.config.image2_alignClasses,
1575 features = {
1576 dimension: {
1577 requiredContent: 'img[width,height]'
1578 },
1579 align: {
1580 requiredContent: 'img' +
1581 ( alignClasses ? '(' + alignClasses[ 0 ] + ')' : '{float}' )
1582 },
1583 caption: {
1584 requiredContent: 'figcaption'
1585 }
1586 };
1587
1588 return features;
1589 }
1590
1591 // Returns element which is styled, considering current
1592 // state of the widget.
1593 //
1594 // @see CKEDITOR.plugins.widget#applyStyle
1595 // @param {CKEDITOR.plugins.widget} widget
1596 // @returns {CKEDITOR.dom.element}
1597 function getStyleableElement( widget ) {
1598 return widget.data.hasCaption ? widget.element : widget.parts.image;
1599 }
1600} )();
1601
1602/**
1603 * A CSS class applied to the `<figure>` element of a captioned image.
1604 *
1605 * Read more in the [documentation](#!/guide/dev_captionedimage) and see the
1606 * [SDK sample](http://sdk.ckeditor.com/samples/captionedimage.html).
1607 *
1608 * // Changes the class to "captionedImage".
1609 * config.image2_captionedClass = 'captionedImage';
1610 *
1611 * @cfg {String} [image2_captionedClass='image']
1612 * @member CKEDITOR.config
1613 */
1614CKEDITOR.config.image2_captionedClass = 'image';
1615
1616/**
1617 * Determines whether dimension inputs should be automatically filled when the image URL changes in the Enhanced Image
1618 * plugin dialog window.
1619 *
1620 * Read more in the [documentation](#!/guide/dev_captionedimage) and see the
1621 * [SDK sample](http://sdk.ckeditor.com/samples/captionedimage.html).
1622 *
1623 * config.image2_prefillDimensions = false;
1624 *
1625 * @since 4.5
1626 * @cfg {Boolean} [image2_prefillDimensions=true]
1627 * @member CKEDITOR.config
1628 */
1629
1630/**
1631 * Disables the image resizer. By default the resizer is enabled.
1632 *
1633 * Read more in the [documentation](#!/guide/dev_captionedimage) and see the
1634 * [SDK sample](http://sdk.ckeditor.com/samples/captionedimage.html).
1635 *
1636 * config.image2_disableResizer = true;
1637 *
1638 * @since 4.5
1639 * @cfg {Boolean} [image2_disableResizer=false]
1640 * @member CKEDITOR.config
1641 */
1642
1643/**
1644 * CSS classes applied to aligned images. Useful to take control over the way
1645 * the images are aligned, i.e. to customize output HTML and integrate external stylesheets.
1646 *
1647 * Classes should be defined in an array of three elements, containing left, center, and right
1648 * alignment classes, respectively. For example:
1649 *
1650 * config.image2_alignClasses = [ 'align-left', 'align-center', 'align-right' ];
1651 *
1652 * **Note**: Once this configuration option is set, the plugin will no longer produce inline
1653 * styles for alignment. It means that e.g. the following HTML will be produced:
1654 *
1655 * <img alt="My image" class="custom-center-class" src="foo.png" />
1656 *
1657 * instead of:
1658 *
1659 * <img alt="My image" style="float:left" src="foo.png" />
1660 *
1661 * **Note**: Once this configuration option is set, corresponding style definitions
1662 * must be supplied to the editor:
1663 *
1664 * * For [classic editor](#!/guide/dev_framed) it can be done by defining additional
1665 * styles in the {@link CKEDITOR.config#contentsCss stylesheets loaded by the editor}. The same
1666 * styles must be provided on the target page where the content will be loaded.
1667 * * For [inline editor](#!/guide/dev_inline) the styles can be defined directly
1668 * with `<style> ... <style>` or `<link href="..." rel="stylesheet">`, i.e. within the `<head>`
1669 * of the page.
1670 *
1671 * For example, considering the following configuration:
1672 *
1673 * config.image2_alignClasses = [ 'align-left', 'align-center', 'align-right' ];
1674 *
1675 * CSS rules can be defined as follows:
1676 *
1677 * .align-left {
1678 * float: left;
1679 * }
1680 *
1681 * .align-right {
1682 * float: right;
1683 * }
1684 *
1685 * .align-center {
1686 * text-align: center;
1687 * }
1688 *
1689 * .align-center > figure {
1690 * display: inline-block;
1691 * }
1692 *
1693 * Read more in the [documentation](#!/guide/dev_captionedimage) and see the
1694 * [SDK sample](http://sdk.ckeditor.com/samples/captionedimage.html).
1695 *
1696 * @since 4.4
1697 * @cfg {String[]} [image2_alignClasses=null]
1698 * @member CKEDITOR.config
1699 */
1700
1701/**
1702 * Determines whether alternative text is required for the captioned image.
1703 *
1704 * config.image2_altRequired = true;
1705 *
1706 * Read more in the [documentation](#!/guide/dev_captionedimage) and see the
1707 * [SDK sample](http://sdk.ckeditor.com/samples/captionedimage.html).
1708 *
1709 * @since 4.6.0
1710 * @cfg {Boolean} [image2_altRequired=false]
1711 * @member CKEDITOR.config
1712 */