biso24-editor 1.3.0 → 1.3.1

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