Diff: STRATO-apps/wordpress_03/app/wp-admin/js/image-edit.js
Keine Baseline-Datei – Diff nur gegen leer.
1
-
1
+
/**
2
+
* The functions necessary for editing images.
3
+
*
4
+
* @since 2.9.0
5
+
* @output wp-admin/js/image-edit.js
6
+
*/
7
+
8
+
/* global ajaxurl, confirm */
9
+
10
+
(function($) {
11
+
var __ = wp.i18n.__;
12
+
13
+
/**
14
+
* Contains all the methods to initialize and control the image editor.
15
+
*
16
+
* @namespace imageEdit
17
+
*/
18
+
var imageEdit = window.imageEdit = {
19
+
iasapi : {},
20
+
hold : {},
21
+
postid : '',
22
+
_view : false,
23
+
24
+
/**
25
+
* Enable crop tool.
26
+
*/
27
+
toggleCropTool: function( postid, nonce, cropButton ) {
28
+
var img = $( '#image-preview-' + postid ),
29
+
selection = this.iasapi.getSelection();
30
+
31
+
imageEdit.toggleControls( cropButton );
32
+
var $el = $( cropButton );
33
+
var state = ( $el.attr( 'aria-expanded' ) === 'true' ) ? 'true' : 'false';
34
+
// Crop tools have been closed.
35
+
if ( 'false' === state ) {
36
+
// Cancel selection, but do not unset inputs.
37
+
this.iasapi.cancelSelection();
38
+
imageEdit.setDisabled($('.imgedit-crop-clear'), 0);
39
+
} else {
40
+
imageEdit.setDisabled($('.imgedit-crop-clear'), 1);
41
+
// Get values from inputs to restore previous selection.
42
+
var startX = ( $( '#imgedit-start-x-' + postid ).val() ) ? $('#imgedit-start-x-' + postid).val() : 0;
43
+
var startY = ( $( '#imgedit-start-y-' + postid ).val() ) ? $('#imgedit-start-y-' + postid).val() : 0;
44
+
var width = ( $( '#imgedit-sel-width-' + postid ).val() ) ? $('#imgedit-sel-width-' + postid).val() : img.innerWidth();
45
+
var height = ( $( '#imgedit-sel-height-' + postid ).val() ) ? $('#imgedit-sel-height-' + postid).val() : img.innerHeight();
46
+
// Ensure selection is available, otherwise reset to full image.
47
+
if ( isNaN( selection.x1 ) ) {
48
+
this.setCropSelection( postid, { 'x1': startX, 'y1': startY, 'x2': width, 'y2': height, 'width': width, 'height': height } );
49
+
selection = this.iasapi.getSelection();
50
+
}
51
+
52
+
// If we don't already have a selection, select the entire image.
53
+
if ( 0 === selection.x1 && 0 === selection.y1 && 0 === selection.x2 && 0 === selection.y2 ) {
54
+
this.iasapi.setSelection( 0, 0, img.innerWidth(), img.innerHeight(), true );
55
+
this.iasapi.setOptions( { show: true } );
56
+
this.iasapi.update();
57
+
} else {
58
+
this.iasapi.setSelection( startX, startY, width, height, true );
59
+
this.iasapi.setOptions( { show: true } );
60
+
this.iasapi.update();
61
+
}
62
+
}
63
+
},
64
+
65
+
/**
66
+
* Handle crop tool clicks.
67
+
*/
68
+
handleCropToolClick: function( postid, nonce, cropButton ) {
69
+
70
+
if ( cropButton.classList.contains( 'imgedit-crop-clear' ) ) {
71
+
this.iasapi.cancelSelection();
72
+
imageEdit.setDisabled($('.imgedit-crop-apply'), 0);
73
+
74
+
$('#imgedit-sel-width-' + postid).val('');
75
+
$('#imgedit-sel-height-' + postid).val('');
76
+
$('#imgedit-start-x-' + postid).val('0');
77
+
$('#imgedit-start-y-' + postid).val('0');
78
+
$('#imgedit-selection-' + postid).val('');
79
+
} else {
80
+
// Otherwise, perform the crop.
81
+
imageEdit.crop( postid, nonce , cropButton );
82
+
}
83
+
},
84
+
85
+
/**
86
+
* Converts a value to an integer.
87
+
*
88
+
* @since 2.9.0
89
+
*
90
+
* @memberof imageEdit
91
+
*
92
+
* @param {number} f The float value that should be converted.
93
+
*
94
+
* @return {number} The integer representation from the float value.
95
+
*/
96
+
intval : function(f) {
97
+
/*
98
+
* Bitwise OR operator: one of the obscure ways to truncate floating point figures,
99
+
* worth reminding JavaScript doesn't have a distinct "integer" type.
100
+
*/
101
+
return f | 0;
102
+
},
103
+
104
+
/**
105
+
* Adds the disabled attribute and class to a single form element or a field set.
106
+
*
107
+
* @since 2.9.0
108
+
*
109
+
* @memberof imageEdit
110
+
*
111
+
* @param {jQuery} el The element that should be modified.
112
+
* @param {boolean|number} s The state for the element. If set to true
113
+
* the element is disabled,
114
+
* otherwise the element is enabled.
115
+
* The function is sometimes called with a 0 or 1
116
+
* instead of true or false.
117
+
*
118
+
* @return {void}
119
+
*/
120
+
setDisabled : function( el, s ) {
121
+
/*
122
+
* `el` can be a single form element or a fieldset. Before #28864, the disabled state on
123
+
* some text fields was handled targeting $('input', el). Now we need to handle the
124
+
* disabled state on buttons too so we can just target `el` regardless if it's a single
125
+
* element or a fieldset because when a fieldset is disabled, its descendants are disabled too.
126
+
*/
127
+
if ( s ) {
128
+
el.removeClass( 'disabled' ).prop( 'disabled', false );
129
+
} else {
130
+
el.addClass( 'disabled' ).prop( 'disabled', true );
131
+
}
132
+
},
133
+
134
+
/**
135
+
* Initializes the image editor.
136
+
*
137
+
* @since 2.9.0
138
+
*
139
+
* @memberof imageEdit
140
+
*
141
+
* @param {number} postid The post ID.
142
+
*
143
+
* @return {void}
144
+
*/
145
+
init : function(postid) {
146
+
var t = this, old = $('#image-editor-' + t.postid);
147
+
148
+
if ( t.postid !== postid && old.length ) {
149
+
t.close(t.postid);
150
+
}
151
+
152
+
t.hold.sizer = parseFloat( $('#imgedit-sizer-' + postid).val() );
153
+
t.postid = postid;
154
+
$('#imgedit-response-' + postid).empty();
155
+
156
+
$('#imgedit-panel-' + postid).on( 'keypress', function(e) {
157
+
var nonce = $( '#imgedit-nonce-' + postid ).val();
158
+
if ( e.which === 26 && e.ctrlKey ) {
159
+
imageEdit.undo( postid, nonce );
160
+
}
161
+
162
+
if ( e.which === 25 && e.ctrlKey ) {
163
+
imageEdit.redo( postid, nonce );
164
+
}
165
+
});
166
+
167
+
$('#imgedit-panel-' + postid).on( 'keypress', 'input[type="text"]', function(e) {
168
+
var k = e.keyCode;
169
+
170
+
// Key codes 37 through 40 are the arrow keys.
171
+
if ( 36 < k && k < 41 ) {
172
+
$(this).trigger( 'blur' );
173
+
}
174
+
175
+
// The key code 13 is the Enter key.
176
+
if ( 13 === k ) {
177
+
e.preventDefault();
178
+
e.stopPropagation();
179
+
return false;
180
+
}
181
+
});
182
+
183
+
$( document ).on( 'image-editor-ui-ready', this.focusManager );
184
+
},
185
+
186
+
/**
187
+
* Calculate the image size and save it to memory.
188
+
*
189
+
* @since 6.7.0
190
+
*
191
+
* @memberof imageEdit
192
+
*
193
+
* @param {number} postid The post ID.
194
+
*
195
+
* @return {void}
196
+
*/
197
+
calculateImgSize: function( postid ) {
198
+
var t = this,
199
+
x = t.intval( $( '#imgedit-x-' + postid ).val() ),
200
+
y = t.intval( $( '#imgedit-y-' + postid ).val() );
201
+
202
+
t.hold.w = t.hold.ow = x;
203
+
t.hold.h = t.hold.oh = y;
204
+
t.hold.xy_ratio = x / y;
205
+
t.hold.sizer = parseFloat( $( '#imgedit-sizer-' + postid ).val() );
206
+
t.currentCropSelection = null;
207
+
},
208
+
209
+
/**
210
+
* Toggles the wait/load icon in the editor.
211
+
*
212
+
* @since 2.9.0
213
+
* @since 5.5.0 Added the triggerUIReady parameter.
214
+
*
215
+
* @memberof imageEdit
216
+
*
217
+
* @param {number} postid The post ID.
218
+
* @param {number} toggle Is 0 or 1, fades the icon in when 1 and out when 0.
219
+
* @param {boolean} triggerUIReady Whether to trigger a custom event when the UI is ready. Default false.
220
+
*
221
+
* @return {void}
222
+
*/
223
+
toggleEditor: function( postid, toggle, triggerUIReady ) {
224
+
var wait = $('#imgedit-wait-' + postid);
225
+
226
+
if ( toggle ) {
227
+
wait.fadeIn( 'fast' );
228
+
} else {
229
+
wait.fadeOut( 'fast', function() {
230
+
if ( triggerUIReady ) {
231
+
$( document ).trigger( 'image-editor-ui-ready' );
232
+
}
233
+
} );
234
+
}
235
+
},
236
+
237
+
/**
238
+
* Shows or hides image menu popup.
239
+
*
240
+
* @since 6.3.0
241
+
*
242
+
* @memberof imageEdit
243
+
*
244
+
* @param {HTMLElement} el The activated control element.
245
+
*
246
+
* @return {boolean} Always returns false.
247
+
*/
248
+
togglePopup : function(el) {
249
+
var $el = $( el );
250
+
var $targetEl = $( el ).attr( 'aria-controls' );
251
+
var $target = $( '#' + $targetEl );
252
+
$el
253
+
.attr( 'aria-expanded', 'false' === $el.attr( 'aria-expanded' ) ? 'true' : 'false' );
254
+
// Open menu and set z-index to appear above image crop area if it is enabled.
255
+
$target
256
+
.toggleClass( 'imgedit-popup-menu-open' ).slideToggle( 'fast' ).css( { 'z-index' : 200000 } );
257
+
// Move focus to first item in menu when opening menu.
258
+
if ( 'true' === $el.attr( 'aria-expanded' ) ) {
259
+
$target.find( 'button' ).first().trigger( 'focus' );
260
+
}
261
+
262
+
return false;
263
+
},
264
+
265
+
/**
266
+
* Observes whether the popup should remain open based on focus position.
267
+
*
268
+
* @since 6.4.0
269
+
*
270
+
* @memberof imageEdit
271
+
*
272
+
* @param {HTMLElement} el The activated control element.
273
+
*
274
+
* @return {boolean} Always returns false.
275
+
*/
276
+
monitorPopup : function() {
277
+
var $parent = document.querySelector( '.imgedit-rotate-menu-container' );
278
+
var $toggle = document.querySelector( '.imgedit-rotate-menu-container .imgedit-rotate' );
279
+
280
+
setTimeout( function() {
281
+
var $focused = document.activeElement;
282
+
var $contains = $parent.contains( $focused );
283
+
284
+
// If $focused is defined and not inside the menu container, close the popup.
285
+
if ( $focused && ! $contains ) {
286
+
if ( 'true' === $toggle.getAttribute( 'aria-expanded' ) ) {
287
+
imageEdit.togglePopup( $toggle );
288
+
}
289
+
}
290
+
}, 100 );
291
+
292
+
return false;
293
+
},
294
+
295
+
/**
296
+
* Navigate popup menu by arrow keys.
297
+
*
298
+
* @since 6.3.0
299
+
* @since 6.7.0 Added the event parameter.
300
+
*
301
+
* @memberof imageEdit
302
+
*
303
+
* @param {Event} event The key or click event.
304
+
* @param {HTMLElement} el The current element.
305
+
*
306
+
* @return {boolean} Always returns false.
307
+
*/
308
+
browsePopup : function(event, el) {
309
+
var $el = $( el );
310
+
var $collection = $( el ).parent( '.imgedit-popup-menu' ).find( 'button' );
311
+
var $index = $collection.index( $el );
312
+
var $prev = $index - 1;
313
+
var $next = $index + 1;
314
+
var $last = $collection.length;
315
+
if ( $prev < 0 ) {
316
+
$prev = $last - 1;
317
+
}
318
+
if ( $next === $last ) {
319
+
$next = 0;
320
+
}
321
+
var target = false;
322
+
if ( event.keyCode === 40 ) {
323
+
target = $collection.get( $next );
324
+
} else if ( event.keyCode === 38 ) {
325
+
target = $collection.get( $prev );
326
+
}
327
+
if ( target ) {
328
+
target.focus();
329
+
event.preventDefault();
330
+
}
331
+
332
+
return false;
333
+
},
334
+
335
+
/**
336
+
* Close popup menu and reset focus on feature activation.
337
+
*
338
+
* @since 6.3.0
339
+
*
340
+
* @memberof imageEdit
341
+
*
342
+
* @param {HTMLElement} el The current element.
343
+
*
344
+
* @return {boolean} Always returns false.
345
+
*/
346
+
closePopup : function(el) {
347
+
var $parent = $(el).parent( '.imgedit-popup-menu' );
348
+
var $controlledID = $parent.attr( 'id' );
349
+
var $target = $( 'button[aria-controls="' + $controlledID + '"]' );
350
+
$target
351
+
.attr( 'aria-expanded', 'false' ).trigger( 'focus' );
352
+
$parent
353
+
.toggleClass( 'imgedit-popup-menu-open' ).slideToggle( 'fast' );
354
+
355
+
return false;
356
+
},
357
+
358
+
/**
359
+
* Shows or hides the image edit help box.
360
+
*
361
+
* @since 2.9.0
362
+
*
363
+
* @memberof imageEdit
364
+
*
365
+
* @param {HTMLElement} el The element to create the help window in.
366
+
*
367
+
* @return {boolean} Always returns false.
368
+
*/
369
+
toggleHelp : function(el) {
370
+
var $el = $( el );
371
+
$el
372
+
.attr( 'aria-expanded', 'false' === $el.attr( 'aria-expanded' ) ? 'true' : 'false' )
373
+
.parents( '.imgedit-group-top' ).toggleClass( 'imgedit-help-toggled' ).find( '.imgedit-help' ).slideToggle( 'fast' );
374
+
375
+
return false;
376
+
},
377
+
378
+
/**
379
+
* Shows or hides image edit input fields when enabled.
380
+
*
381
+
* @since 6.3.0
382
+
*
383
+
* @memberof imageEdit
384
+
*
385
+
* @param {HTMLElement} el The element to trigger the edit panel.
386
+
*
387
+
* @return {boolean} Always returns false.
388
+
*/
389
+
toggleControls : function(el) {
390
+
var $el = $( el );
391
+
var $target = $( '#' + $el.attr( 'aria-controls' ) );
392
+
$el
393
+
.attr( 'aria-expanded', 'false' === $el.attr( 'aria-expanded' ) ? 'true' : 'false' );
394
+
$target
395
+
.parent( '.imgedit-group' ).toggleClass( 'imgedit-panel-active' );
396
+
397
+
return false;
398
+
},
399
+
400
+
/**
401
+
* Gets the value from the image edit target.
402
+
*
403
+
* The image edit target contains the image sizes where the (possible) changes
404
+
* have to be applied to.
405
+
*
406
+
* @since 2.9.0
407
+
*
408
+
* @memberof imageEdit
409
+
*
410
+
* @param {number} postid The post ID.
411
+
*
412
+
* @return {string} The value from the imagedit-save-target input field when available,
413
+
* 'full' when not selected, or 'all' if it doesn't exist.
414
+
*/
415
+
getTarget : function( postid ) {
416
+
var element = $( '#imgedit-save-target-' + postid );
417
+
418
+
if ( element.length ) {
419
+
return element.find( 'input[name="imgedit-target-' + postid + '"]:checked' ).val() || 'full';
420
+
}
421
+
422
+
return 'all';
423
+
},
424
+
425
+
/**
426
+
* Recalculates the height or width and keeps the original aspect ratio.
427
+
*
428
+
* If the original image size is exceeded a red exclamation mark is shown.
429
+
*
430
+
* @since 2.9.0
431
+
*
432
+
* @memberof imageEdit
433
+
*
434
+
* @param {number} postid The current post ID.
435
+
* @param {number} x Is 0 when it applies the y-axis
436
+
* and 1 when applicable for the x-axis.
437
+
* @param {jQuery} el Element.
438
+
*
439
+
* @return {void}
440
+
*/
441
+
scaleChanged : function( postid, x, el ) {
442
+
var w = $('#imgedit-scale-width-' + postid), h = $('#imgedit-scale-height-' + postid),
443
+
warn = $('#imgedit-scale-warn-' + postid), w1 = '', h1 = '',
444
+
scaleBtn = $('#imgedit-scale-button');
445
+
446
+
if ( false === this.validateNumeric( el ) ) {
447
+
return;
448
+
}
449
+
450
+
if ( x ) {
451
+
h1 = ( w.val() !== '' ) ? Math.round( w.val() / this.hold.xy_ratio ) : '';
452
+
h.val( h1 );
453
+
} else {
454
+
w1 = ( h.val() !== '' ) ? Math.round( h.val() * this.hold.xy_ratio ) : '';
455
+
w.val( w1 );
456
+
}
457
+
458
+
if ( ( h1 && h1 > this.hold.oh ) || ( w1 && w1 > this.hold.ow ) ) {
459
+
warn.css('visibility', 'visible');
460
+
scaleBtn.prop('disabled', true);
461
+
} else {
462
+
warn.css('visibility', 'hidden');
463
+
scaleBtn.prop('disabled', false);
464
+
}
465
+
},
466
+
467
+
/**
468
+
* Gets the selected aspect ratio.
469
+
*
470
+
* @since 2.9.0
471
+
*
472
+
* @memberof imageEdit
473
+
*
474
+
* @param {number} postid The post ID.
475
+
*
476
+
* @return {string} The aspect ratio.
477
+
*/
478
+
getSelRatio : function(postid) {
479
+
var x = this.hold.w, y = this.hold.h,
480
+
X = this.intval( $('#imgedit-crop-width-' + postid).val() ),
481
+
Y = this.intval( $('#imgedit-crop-height-' + postid).val() );
482
+
483
+
if ( X && Y ) {
484
+
return X + ':' + Y;
485
+
}
486
+
487
+
if ( x && y ) {
488
+
return x + ':' + y;
489
+
}
490
+
491
+
return '1:1';
492
+
},
493
+
494
+
/**
495
+
* Removes the last action from the image edit history.
496
+
* The history consist of (edit) actions performed on the image.
497
+
*
498
+
* @since 2.9.0
499
+
*
500
+
* @memberof imageEdit
501
+
*
502
+
* @param {number} postid The post ID.
503
+
* @param {number} setSize 0 or 1, when 1 the image resets to its original size.
504
+
*
505
+
* @return {string} JSON string containing the history or an empty string if no history exists.
506
+
*/
507
+
filterHistory : function(postid, setSize) {
508
+
// Apply undo state to history.
509
+
var history = $('#imgedit-history-' + postid).val(), pop, n, o, i, op = [];
510
+
511
+
if ( history !== '' ) {
512
+
// Read the JSON string with the image edit history.
513
+
history = JSON.parse(history);
514
+
pop = this.intval( $('#imgedit-undone-' + postid).val() );
515
+
if ( pop > 0 ) {
516
+
while ( pop > 0 ) {
517
+
history.pop();
518
+
pop--;
519
+
}
520
+
}
521
+
522
+
// Reset size to its original state.
523
+
if ( setSize ) {
524
+
if ( !history.length ) {
525
+
this.hold.w = this.hold.ow;
526
+
this.hold.h = this.hold.oh;
527
+
return '';
528
+
}
529
+
530
+
// Restore original 'o'.
531
+
o = history[history.length - 1];
532
+
533
+
// c = 'crop', r = 'rotate', f = 'flip'.
534
+
o = o.c || o.r || o.f || false;
535
+
536
+
if ( o ) {
537
+
// fw = Full image width.
538
+
this.hold.w = o.fw;
539
+
// fh = Full image height.
540
+
this.hold.h = o.fh;
541
+
}
542
+
}
543
+
544
+
// Filter the last step/action from the history.
545
+
for ( n in history ) {
546
+
i = history[n];
547
+
if ( i.hasOwnProperty('c') ) {
548
+
op[n] = { 'c': { 'x': i.c.x, 'y': i.c.y, 'w': i.c.w, 'h': i.c.h, 'r': i.c.r } };
549
+
} else if ( i.hasOwnProperty('r') ) {
550
+
op[n] = { 'r': i.r.r };
551
+
} else if ( i.hasOwnProperty('f') ) {
552
+
op[n] = { 'f': i.f.f };
553
+
}
554
+
}
555
+
return JSON.stringify(op);
556
+
}
557
+
return '';
558
+
},
559
+
/**
560
+
* Binds the necessary events to the image.
561
+
*
562
+
* When the image source is reloaded the image will be reloaded.
563
+
*
564
+
* @since 2.9.0
565
+
*
566
+
* @memberof imageEdit
567
+
*
568
+
* @param {number} postid The post ID.
569
+
* @param {string} nonce The nonce to verify the request.
570
+
* @param {function} callback Function to execute when the image is loaded.
571
+
*
572
+
* @return {void}
573
+
*/
574
+
refreshEditor : function(postid, nonce, callback) {
575
+
var t = this, data, img;
576
+
577
+
t.toggleEditor(postid, 1);
578
+
data = {
579
+
'action': 'imgedit-preview',
580
+
'_ajax_nonce': nonce,
581
+
'postid': postid,
582
+
'history': t.filterHistory(postid, 1),
583
+
'rand': t.intval(Math.random() * 1000000)
584
+
};
585
+
586
+
img = $( '<img id="image-preview-' + postid + '" alt="" />' )
587
+
.on( 'load', { history: data.history }, function( event ) {
588
+
var max1, max2,
589
+
parent = $( '#imgedit-crop-' + postid ),
590
+
t = imageEdit,
591
+
historyObj;
592
+
593
+
// Checks if there already is some image-edit history.
594
+
if ( '' !== event.data.history ) {
595
+
historyObj = JSON.parse( event.data.history );
596
+
// If last executed action in history is a crop action.
597
+
if ( historyObj[historyObj.length - 1].hasOwnProperty( 'c' ) ) {
598
+
/*
599
+
* A crop action has completed and the crop button gets disabled
600
+
* ensure the undo button is enabled.
601
+
*/
602
+
t.setDisabled( $( '#image-undo-' + postid) , true );
603
+
// Move focus to the undo button to avoid a focus loss.
604
+
$( '#image-undo-' + postid ).trigger( 'focus' );
605
+
}
606
+
}
607
+
608
+
parent.empty().append(img);
609
+
610
+
// w, h are the new full size dimensions.
611
+
max1 = Math.max( t.hold.w, t.hold.h );
612
+
max2 = Math.max( $(img).width(), $(img).height() );
613
+
t.hold.sizer = max1 > max2 ? max2 / max1 : 1;
614
+
615
+
t.initCrop(postid, img, parent);
616
+
617
+
if ( (typeof callback !== 'undefined') && callback !== null ) {
618
+
callback();
619
+
}
620
+
621
+
if ( $('#imgedit-history-' + postid).val() && $('#imgedit-undone-' + postid).val() === '0' ) {
622
+
$('button.imgedit-submit-btn', '#imgedit-panel-' + postid).prop('disabled', false);
623
+
} else {
624
+
$('button.imgedit-submit-btn', '#imgedit-panel-' + postid).prop('disabled', true);
625
+
}
626
+
var successMessage = __( 'Image updated.' );
627
+
628
+
t.toggleEditor(postid, 0);
629
+
wp.a11y.speak( successMessage, 'assertive' );
630
+
})
631
+
.on( 'error', function() {
632
+
var errorMessage = __( 'Could not load the preview image. Please reload the page and try again.' );
633
+
634
+
$( '#imgedit-crop-' + postid )
635
+
.empty()
636
+
.append( '<div class="notice notice-error" tabindex="-1" role="alert"><p>' + errorMessage + '</p></div>' );
637
+
638
+
t.toggleEditor( postid, 0, true );
639
+
wp.a11y.speak( errorMessage, 'assertive' );
640
+
} )
641
+
.attr('src', ajaxurl + '?' + $.param(data));
642
+
},
643
+
/**
644
+
* Performs an image edit action.
645
+
*
646
+
* @since 2.9.0
647
+
*
648
+
* @memberof imageEdit
649
+
*
650
+
* @param {number} postid The post ID.
651
+
* @param {string} nonce The nonce to verify the request.
652
+
* @param {string} action The action to perform on the image.
653
+
* The possible actions are: "scale" and "restore".
654
+
*
655
+
* @return {boolean|void} Executes a post request that refreshes the page
656
+
* when the action is performed.
657
+
* Returns false if an invalid action is given,
658
+
* or when the action cannot be performed.
659
+
*/
660
+
action : function(postid, nonce, action) {
661
+
var t = this, data, w, h, fw, fh;
662
+
663
+
if ( t.notsaved(postid) ) {
664
+
return false;
665
+
}
666
+
667
+
data = {
668
+
'action': 'image-editor',
669
+
'_ajax_nonce': nonce,
670
+
'postid': postid
671
+
};
672
+
673
+
if ( 'scale' === action ) {
674
+
w = $('#imgedit-scale-width-' + postid),
675
+
h = $('#imgedit-scale-height-' + postid),
676
+
fw = t.intval(w.val()),
677
+
fh = t.intval(h.val());
678
+
679
+
if ( fw < 1 ) {
680
+
w.trigger( 'focus' );
681
+
return false;
682
+
} else if ( fh < 1 ) {
683
+
h.trigger( 'focus' );
684
+
return false;
685
+
}
686
+
687
+
if ( fw === t.hold.ow || fh === t.hold.oh ) {
688
+
return false;
689
+
}
690
+
691
+
data['do'] = 'scale';
692
+
data.fwidth = fw;
693
+
data.fheight = fh;
694
+
} else if ( 'restore' === action ) {
695
+
data['do'] = 'restore';
696
+
} else {
697
+
return false;
698
+
}
699
+
700
+
t.toggleEditor(postid, 1);
701
+
$.post( ajaxurl, data, function( response ) {
702
+
$( '#image-editor-' + postid ).empty().append( response.data.html );
703
+
t.toggleEditor( postid, 0, true );
704
+
// Refresh the attachment model so that changes propagate.
705
+
if ( t._view ) {
706
+
t._view.refresh();
707
+
}
708
+
} ).done( function( response ) {
709
+
// Whether the executed action was `scale` or `restore`, the response does have a message.
710
+
if ( response && response.data.message.msg ) {
711
+
wp.a11y.speak( response.data.message.msg );
712
+
return;
713
+
}
714
+
715
+
if ( response && response.data.message.error ) {
716
+
wp.a11y.speak( response.data.message.error );
717
+
}
718
+
} );
719
+
},
720
+
721
+
/**
722
+
* Stores the changes that are made to the image.
723
+
*
724
+
* @since 2.9.0
725
+
*
726
+
* @memberof imageEdit
727
+
*
728
+
* @param {number} postid The post ID to get the image from the database.
729
+
* @param {string} nonce The nonce to verify the request.
730
+
*
731
+
* @return {boolean|void} If the actions are successfully saved a response message is shown.
732
+
* Returns false if there is no image editing history,
733
+
* thus there are not edit-actions performed on the image.
734
+
*/
735
+
save : function(postid, nonce) {
736
+
var data,
737
+
target = this.getTarget(postid),
738
+
history = this.filterHistory(postid, 0),
739
+
self = this;
740
+
741
+
if ( '' === history ) {
742
+
return false;
743
+
}
744
+
745
+
this.toggleEditor(postid, 1);
746
+
data = {
747
+
'action': 'image-editor',
748
+
'_ajax_nonce': nonce,
749
+
'postid': postid,
750
+
'history': history,
751
+
'target': target,
752
+
'context': $('#image-edit-context').length ? $('#image-edit-context').val() : null,
753
+
'do': 'save'
754
+
};
755
+
// Post the image edit data to the backend.
756
+
$.post( ajaxurl, data, function( response ) {
757
+
// If a response is returned, close the editor and show an error.
758
+
if ( response.data.error ) {
759
+
$( '#imgedit-response-' + postid )
760
+
.html( '<div class="notice notice-error" tabindex="-1" role="alert"><p>' + response.data.error + '</p></div>' );
761
+
762
+
imageEdit.close(postid);
763
+
wp.a11y.speak( response.data.error );
764
+
return;
765
+
}
766
+
767
+
if ( response.data.fw && response.data.fh ) {
768
+
$( '#media-dims-' + postid ).html( response.data.fw + ' × ' + response.data.fh );
769
+
}
770
+
771
+
if ( response.data.thumbnail ) {
772
+
$( '.thumbnail', '#thumbnail-head-' + postid ).attr( 'src', '' + response.data.thumbnail );
773
+
}
774
+
775
+
if ( response.data.msg ) {
776
+
$( '#imgedit-response-' + postid )
777
+
.html( '<div class="notice notice-success" tabindex="-1" role="alert"><p>' + response.data.msg + '</p></div>' );
778
+
779
+
wp.a11y.speak( response.data.msg );
780
+
}
781
+
782
+
if ( self._view ) {
783
+
self._view.save();
784
+
} else {
785
+
imageEdit.close(postid);
786
+
}
787
+
});
788
+
},
789
+
790
+
/**
791
+
* Creates the image edit window.
792
+
*
793
+
* @since 2.9.0
794
+
*
795
+
* @memberof imageEdit
796
+
*
797
+
* @param {number} postid The post ID for the image.
798
+
* @param {string} nonce The nonce to verify the request.
799
+
* @param {Object} view The image editor view to be used for the editing.
800
+
*
801
+
* @return {void|promise} Either returns void if the button was already activated
802
+
* or returns an instance of the image editor, wrapped in a promise.
803
+
*/
804
+
open : function( postid, nonce, view ) {
805
+
this._view = view;
806
+
807
+
var dfd, data,
808
+
elem = $( '#image-editor-' + postid ),
809
+
head = $( '#media-head-' + postid ),
810
+
btn = $( '#imgedit-open-btn-' + postid ),
811
+
spin = btn.siblings( '.spinner' );
812
+
813
+
/*
814
+
* Instead of disabling the button, which causes a focus loss and makes screen
815
+
* readers announce "unavailable", return if the button was already clicked.
816
+
*/
817
+
if ( btn.hasClass( 'button-activated' ) ) {
818
+
return;
819
+
}
820
+
821
+
spin.addClass( 'is-active' );
822
+
823
+
data = {
824
+
'action': 'image-editor',
825
+
'_ajax_nonce': nonce,
826
+
'postid': postid,
827
+
'do': 'open'
828
+
};
829
+
830
+
dfd = $.ajax( {
831
+
url: ajaxurl,
832
+
type: 'post',
833
+
data: data,
834
+
beforeSend: function() {
835
+
btn.addClass( 'button-activated' );
836
+
}
837
+
} ).done( function( response ) {
838
+
var errorMessage;
839
+
840
+
if ( '-1' === response ) {
841
+
errorMessage = __( 'Could not load the preview image.' );
842
+
elem.html( '<div class="notice notice-error" tabindex="-1" role="alert"><p>' + errorMessage + '</p></div>' );
843
+
}
844
+
845
+
if ( response.data && response.data.html ) {
846
+
elem.html( response.data.html );
847
+
}
848
+
849
+
head.fadeOut( 'fast', function() {
850
+
elem.fadeIn( 'fast', function() {
851
+
if ( errorMessage ) {
852
+
$( document ).trigger( 'image-editor-ui-ready' );
853
+
}
854
+
} );
855
+
btn.removeClass( 'button-activated' );
856
+
spin.removeClass( 'is-active' );
857
+
} );
858
+
// Initialize the Image Editor now that everything is ready.
859
+
imageEdit.init( postid );
860
+
} );
861
+
862
+
return dfd;
863
+
},
864
+
865
+
/**
866
+
* Initializes the cropping tool and sets a default cropping selection.
867
+
*
868
+
* @since 2.9.0
869
+
*
870
+
* @memberof imageEdit
871
+
*
872
+
* @param {number} postid The post ID.
873
+
*
874
+
* @return {void}
875
+
*/
876
+
imgLoaded : function(postid) {
877
+
var img = $('#image-preview-' + postid), parent = $('#imgedit-crop-' + postid);
878
+
879
+
// Ensure init has run even when directly loaded.
880
+
if ( 'undefined' === typeof this.hold.sizer ) {
881
+
this.init( postid );
882
+
}
883
+
this.calculateImgSize( postid );
884
+
885
+
this.initCrop(postid, img, parent);
886
+
this.setCropSelection( postid, { 'x1': 0, 'y1': 0, 'x2': 0, 'y2': 0, 'width': img.innerWidth(), 'height': img.innerHeight() } );
887
+
888
+
this.toggleEditor( postid, 0, true );
889
+
},
890
+
891
+
/**
892
+
* Manages keyboard focus in the Image Editor user interface.
893
+
*
894
+
* @since 5.5.0
895
+
*
896
+
* @return {void}
897
+
*/
898
+
focusManager: function() {
899
+
/*
900
+
* Editor is ready. Move focus to one of the admin alert notices displayed
901
+
* after a user action or to the first focusable element. Since the DOM
902
+
* update is pretty large, the timeout helps browsers update their
903
+
* accessibility tree to better support assistive technologies.
904
+
*/
905
+
setTimeout( function() {
906
+
var elementToSetFocusTo = $( '.notice[role="alert"]' );
907
+
908
+
if ( ! elementToSetFocusTo.length ) {
909
+
elementToSetFocusTo = $( '.imgedit-wrap' ).find( ':tabbable:first' );
910
+
}
911
+
912
+
elementToSetFocusTo.attr( 'tabindex', '-1' ).trigger( 'focus' );
913
+
}, 100 );
914
+
},
915
+
916
+
/**
917
+
* Initializes the cropping tool.
918
+
*
919
+
* @since 2.9.0
920
+
*
921
+
* @memberof imageEdit
922
+
*
923
+
* @param {number} postid The post ID.
924
+
* @param {HTMLElement} image The preview image.
925
+
* @param {HTMLElement} parent The preview image container.
926
+
*
927
+
* @return {void}
928
+
*/
929
+
initCrop : function(postid, image, parent) {
930
+
var t = this,
931
+
selW = $('#imgedit-sel-width-' + postid),
932
+
selH = $('#imgedit-sel-height-' + postid),
933
+
$image = $( image ),
934
+
$img;
935
+
936
+
// Already initialized?
937
+
if ( $image.data( 'imgAreaSelect' ) ) {
938
+
return;
939
+
}
940
+
941
+
t.iasapi = $image.imgAreaSelect({
942
+
parent: parent,
943
+
instance: true,
944
+
handles: true,
945
+
keys: true,
946
+
minWidth: 3,
947
+
minHeight: 3,
948
+
949
+
/**
950
+
* Sets the CSS styles and binds events for locking the aspect ratio.
951
+
*
952
+
* @ignore
953
+
*
954
+
* @param {jQuery} img The preview image.
955
+
*/
956
+
onInit: function( img ) {
957
+
// Ensure that the imgAreaSelect wrapper elements are position:absolute
958
+
// (even if we're in a position:fixed modal).
959
+
$img = $( img );
960
+
$img.next().css( 'position', 'absolute' )
961
+
.nextAll( '.imgareaselect-outer' ).css( 'position', 'absolute' );
962
+
/**
963
+
* Binds mouse down event to the cropping container.
964
+
*
965
+
* @return {void}
966
+
*/
967
+
parent.children().on( 'mousedown touchstart', function(e) {
968
+
var ratio = false,
969
+
sel = t.iasapi.getSelection(),
970
+
cx = t.intval( $( '#imgedit-crop-width-' + postid ).val() ),
971
+
cy = t.intval( $( '#imgedit-crop-height-' + postid ).val() );
972
+
973
+
if ( cx && cy ) {
974
+
ratio = t.getSelRatio( postid );
975
+
} else if ( e.shiftKey && sel && sel.width && sel.height ) {
976
+
ratio = sel.width + ':' + sel.height;
977
+
}
978
+
979
+
t.iasapi.setOptions({
980
+
aspectRatio: ratio
981
+
});
982
+
});
983
+
},
984
+
985
+
/**
986
+
* Event triggered when starting a selection.
987
+
*
988
+
* @ignore
989
+
*
990
+
* @return {void}
991
+
*/
992
+
onSelectStart: function() {
993
+
imageEdit.setDisabled($('#imgedit-crop-sel-' + postid), 1);
994
+
imageEdit.setDisabled($('.imgedit-crop-clear'), 1);
995
+
imageEdit.setDisabled($('.imgedit-crop-apply'), 1);
996
+
},
997
+
/**
998
+
* Event triggered when the selection is ended.
999
+
*
1000
+
* @ignore
1001
+
*
1002
+
* @param {Object} img jQuery object representing the image.
1003
+
* @param {Object} c The selection.
1004
+
*
1005
+
* @return {Object}
1006
+
*/
1007
+
onSelectEnd: function(img, c) {
1008
+
imageEdit.setCropSelection(postid, c);
1009
+
if ( ! $('#imgedit-crop > *').is(':visible') ) {
1010
+
imageEdit.toggleControls($('.imgedit-crop.button'));
1011
+
}
1012
+
},
1013
+
1014
+
/**
1015
+
* Event triggered when the selection changes.
1016
+
*
1017
+
* @ignore
1018
+
*
1019
+
* @param {Object} img jQuery object representing the image.
1020
+
* @param {Object} c The selection.
1021
+
*
1022
+
* @return {void}
1023
+
*/
1024
+
onSelectChange: function(img, c) {
1025
+
var sizer = imageEdit.hold.sizer,
1026
+
oldSel = imageEdit.currentCropSelection;
1027
+
1028
+
if ( oldSel != null && oldSel.width == c.width && oldSel.height == c.height ) {
1029
+
return;
1030
+
}
1031
+
1032
+
selW.val( Math.min( imageEdit.hold.w, imageEdit.round( c.width / sizer ) ) );
1033
+
selH.val( Math.min( imageEdit.hold.h, imageEdit.round( c.height / sizer ) ) );
1034
+
1035
+
t.currentCropSelection = c;
1036
+
}
1037
+
});
1038
+
},
1039
+
1040
+
/**
1041
+
* Stores the current crop selection.
1042
+
*
1043
+
* @since 2.9.0
1044
+
*
1045
+
* @memberof imageEdit
1046
+
*
1047
+
* @param {number} postid The post ID.
1048
+
* @param {Object} c The selection.
1049
+
*
1050
+
* @return {boolean}
1051
+
*/
1052
+
setCropSelection : function(postid, c) {
1053
+
var sel,
1054
+
selW = $( '#imgedit-sel-width-' + postid ),
1055
+
selH = $( '#imgedit-sel-height-' + postid ),
1056
+
sizer = this.hold.sizer,
1057
+
hold = this.hold;
1058
+
1059
+
c = c || 0;
1060
+
1061
+
if ( !c || ( c.width < 3 && c.height < 3 ) ) {
1062
+
this.setDisabled( $( '.imgedit-crop', '#imgedit-panel-' + postid ), 1 );
1063
+
this.setDisabled( $( '#imgedit-crop-sel-' + postid ), 1 );
1064
+
$('#imgedit-sel-width-' + postid).val('');
1065
+
$('#imgedit-sel-height-' + postid).val('');
1066
+
$('#imgedit-start-x-' + postid).val('0');
1067
+
$('#imgedit-start-y-' + postid).val('0');
1068
+
$('#imgedit-selection-' + postid).val('');
1069
+
return false;
1070
+
}
1071
+
1072
+
// adjust the selection within the bounds of the image on 100% scale
1073
+
var excessW = hold.w - ( Math.round( c.x1 / sizer ) + parseInt( selW.val() ) );
1074
+
var excessH = hold.h - ( Math.round( c.y1 / sizer ) + parseInt( selH.val() ) );
1075
+
var x = Math.round( c.x1 / sizer ) + Math.min( 0, excessW );
1076
+
var y = Math.round( c.y1 / sizer ) + Math.min( 0, excessH );
1077
+
1078
+
// use 100% scaling to prevent rounding errors
1079
+
sel = { 'r': 1, 'x': x, 'y': y, 'w': selW.val(), 'h': selH.val() };
1080
+
1081
+
this.setDisabled($('.imgedit-crop', '#imgedit-panel-' + postid), 1);
1082
+
$('#imgedit-selection-' + postid).val( JSON.stringify(sel) );
1083
+
},
1084
+
1085
+
1086
+
/**
1087
+
* Closes the image editor.
1088
+
*
1089
+
* @since 2.9.0
1090
+
*
1091
+
* @memberof imageEdit
1092
+
*
1093
+
* @param {number} postid The post ID.
1094
+
* @param {boolean} warn Warning message.
1095
+
*
1096
+
* @return {void|boolean} Returns false if there is a warning.
1097
+
*/
1098
+
close : function(postid, warn) {
1099
+
warn = warn || false;
1100
+
1101
+
if ( warn && this.notsaved(postid) ) {
1102
+
return false;
1103
+
}
1104
+
1105
+
this.iasapi = {};
1106
+
this.hold = {};
1107
+
1108
+
// If we've loaded the editor in the context of a Media Modal,
1109
+
// then switch to the previous view, whatever that might have been.
1110
+
if ( this._view ){
1111
+
this._view.back();
1112
+
}
1113
+
1114
+
// In case we are not accessing the image editor in the context of a View,
1115
+
// close the editor the old-school way.
1116
+
else {
1117
+
$('#image-editor-' + postid).fadeOut('fast', function() {
1118
+
$( '#media-head-' + postid ).fadeIn( 'fast', function() {
1119
+
// Move focus back to the Edit Image button. Runs also when saving.
1120
+
$( '#imgedit-open-btn-' + postid ).trigger( 'focus' );
1121
+
});
1122
+
$(this).empty();
1123
+
});
1124
+
}
1125
+
1126
+
1127
+
},
1128
+
1129
+
/**
1130
+
* Checks if the image edit history is saved.
1131
+
*
1132
+
* @since 2.9.0
1133
+
*
1134
+
* @memberof imageEdit
1135
+
*
1136
+
* @param {number} postid The post ID.
1137
+
*
1138
+
* @return {boolean} Returns true if the history is not saved.
1139
+
*/
1140
+
notsaved : function(postid) {
1141
+
var h = $('#imgedit-history-' + postid).val(),
1142
+
history = ( h !== '' ) ? JSON.parse(h) : [],
1143
+
pop = this.intval( $('#imgedit-undone-' + postid).val() );
1144
+
1145
+
if ( pop < history.length ) {
1146
+
if ( confirm( $('#imgedit-leaving-' + postid).text() ) ) {
1147
+
return false;
1148
+
}
1149
+
return true;
1150
+
}
1151
+
return false;
1152
+
},
1153
+
1154
+
/**
1155
+
* Adds an image edit action to the history.
1156
+
*
1157
+
* @since 2.9.0
1158
+
*
1159
+
* @memberof imageEdit
1160
+
*
1161
+
* @param {Object} op The original position.
1162
+
* @param {number} postid The post ID.
1163
+
* @param {string} nonce The nonce.
1164
+
*
1165
+
* @return {void}
1166
+
*/
1167
+
addStep : function(op, postid, nonce) {
1168
+
var t = this, elem = $('#imgedit-history-' + postid),
1169
+
history = ( elem.val() !== '' ) ? JSON.parse( elem.val() ) : [],
1170
+
undone = $( '#imgedit-undone-' + postid ),
1171
+
pop = t.intval( undone.val() );
1172
+
1173
+
while ( pop > 0 ) {
1174
+
history.pop();
1175
+
pop--;
1176
+
}
1177
+
undone.val(0); // Reset.
1178
+
1179
+
history.push(op);
1180
+
elem.val( JSON.stringify(history) );
1181
+
1182
+
t.refreshEditor(postid, nonce, function() {
1183
+
t.setDisabled($('#image-undo-' + postid), true);
1184
+
t.setDisabled($('#image-redo-' + postid), false);
1185
+
});
1186
+
},
1187
+
1188
+
/**
1189
+
* Rotates the image.
1190
+
*
1191
+
* @since 2.9.0
1192
+
*
1193
+
* @memberof imageEdit
1194
+
*
1195
+
* @param {string} angle The angle the image is rotated with.
1196
+
* @param {number} postid The post ID.
1197
+
* @param {string} nonce The nonce.
1198
+
* @param {Object} t The target element.
1199
+
*
1200
+
* @return {boolean}
1201
+
*/
1202
+
rotate : function(angle, postid, nonce, t) {
1203
+
if ( $(t).hasClass('disabled') ) {
1204
+
return false;
1205
+
}
1206
+
this.closePopup(t);
1207
+
this.addStep({ 'r': { 'r': angle, 'fw': this.hold.h, 'fh': this.hold.w }}, postid, nonce);
1208
+
1209
+
// Clear the selection fields after rotating.
1210
+
$( '#imgedit-sel-width-' + postid ).val( '' );
1211
+
$( '#imgedit-sel-height-' + postid ).val( '' );
1212
+
this.currentCropSelection = null;
1213
+
},
1214
+
1215
+
/**
1216
+
* Flips the image.
1217
+
*
1218
+
* @since 2.9.0
1219
+
*
1220
+
* @memberof imageEdit
1221
+
*
1222
+
* @param {number} axis The axle the image is flipped on.
1223
+
* @param {number} postid The post ID.
1224
+
* @param {string} nonce The nonce.
1225
+
* @param {Object} t The target element.
1226
+
*
1227
+
* @return {boolean}
1228
+
*/
1229
+
flip : function (axis, postid, nonce, t) {
1230
+
if ( $(t).hasClass('disabled') ) {
1231
+
return false;
1232
+
}
1233
+
this.closePopup(t);
1234
+
this.addStep({ 'f': { 'f': axis, 'fw': this.hold.w, 'fh': this.hold.h }}, postid, nonce);
1235
+
1236
+
// Clear the selection fields after flipping.
1237
+
$( '#imgedit-sel-width-' + postid ).val( '' );
1238
+
$( '#imgedit-sel-height-' + postid ).val( '' );
1239
+
this.currentCropSelection = null;
1240
+
},
1241
+
1242
+
/**
1243
+
* Crops the image.
1244
+
*
1245
+
* @since 2.9.0
1246
+
*
1247
+
* @memberof imageEdit
1248
+
*
1249
+
* @param {number} postid The post ID.
1250
+
* @param {string} nonce The nonce.
1251
+
* @param {Object} t The target object.
1252
+
*
1253
+
* @return {void|boolean} Returns false if the crop button is disabled.
1254
+
*/
1255
+
crop : function (postid, nonce, t) {
1256
+
var sel = $('#imgedit-selection-' + postid).val(),
1257
+
w = this.intval( $('#imgedit-sel-width-' + postid).val() ),
1258
+
h = this.intval( $('#imgedit-sel-height-' + postid).val() );
1259
+
1260
+
if ( $(t).hasClass('disabled') || sel === '' ) {
1261
+
return false;
1262
+
}
1263
+
1264
+
sel = JSON.parse(sel);
1265
+
if ( sel.w > 0 && sel.h > 0 && w > 0 && h > 0 ) {
1266
+
sel.fw = w;
1267
+
sel.fh = h;
1268
+
this.addStep({ 'c': sel }, postid, nonce);
1269
+
}
1270
+
1271
+
// Clear the selection fields after cropping.
1272
+
$( '#imgedit-sel-width-' + postid ).val( '' );
1273
+
$( '#imgedit-sel-height-' + postid ).val( '' );
1274
+
$( '#imgedit-start-x-' + postid ).val( '0' );
1275
+
$( '#imgedit-start-y-' + postid ).val( '0' );
1276
+
this.currentCropSelection = null;
1277
+
},
1278
+
1279
+
/**
1280
+
* Undoes an image edit action.
1281
+
*
1282
+
* @since 2.9.0
1283
+
*
1284
+
* @memberof imageEdit
1285
+
*
1286
+
* @param {number} postid The post ID.
1287
+
* @param {string} nonce The nonce.
1288
+
*
1289
+
* @return {void|false} Returns false if the undo button is disabled.
1290
+
*/
1291
+
undo : function (postid, nonce) {
1292
+
var t = this, button = $('#image-undo-' + postid), elem = $('#imgedit-undone-' + postid),
1293
+
pop = t.intval( elem.val() ) + 1;
1294
+
1295
+
if ( button.hasClass('disabled') ) {
1296
+
return;
1297
+
}
1298
+
1299
+
elem.val(pop);
1300
+
t.refreshEditor(postid, nonce, function() {
1301
+
var elem = $('#imgedit-history-' + postid),
1302
+
history = ( elem.val() !== '' ) ? JSON.parse( elem.val() ) : [];
1303
+
1304
+
t.setDisabled($('#image-redo-' + postid), true);
1305
+
t.setDisabled(button, pop < history.length);
1306
+
// When undo gets disabled, move focus to the redo button to avoid a focus loss.
1307
+
if ( history.length === pop ) {
1308
+
$( '#image-redo-' + postid ).trigger( 'focus' );
1309
+
}
1310
+
});
1311
+
},
1312
+
1313
+
/**
1314
+
* Reverts a undo action.
1315
+
*
1316
+
* @since 2.9.0
1317
+
*
1318
+
* @memberof imageEdit
1319
+
*
1320
+
* @param {number} postid The post ID.
1321
+
* @param {string} nonce The nonce.
1322
+
*
1323
+
* @return {void}
1324
+
*/
1325
+
redo : function(postid, nonce) {
1326
+
var t = this, button = $('#image-redo-' + postid), elem = $('#imgedit-undone-' + postid),
1327
+
pop = t.intval( elem.val() ) - 1;
1328
+
1329
+
if ( button.hasClass('disabled') ) {
1330
+
return;
1331
+
}
1332
+
1333
+
elem.val(pop);
1334
+
t.refreshEditor(postid, nonce, function() {
1335
+
t.setDisabled($('#image-undo-' + postid), true);
1336
+
t.setDisabled(button, pop > 0);
1337
+
// When redo gets disabled, move focus to the undo button to avoid a focus loss.
1338
+
if ( 0 === pop ) {
1339
+
$( '#image-undo-' + postid ).trigger( 'focus' );
1340
+
}
1341
+
});
1342
+
},
1343
+
1344
+
/**
1345
+
* Sets the selection for the height and width in pixels.
1346
+
*
1347
+
* @since 2.9.0
1348
+
*
1349
+
* @memberof imageEdit
1350
+
*
1351
+
* @param {number} postid The post ID.
1352
+
* @param {jQuery} el The element containing the values.
1353
+
*
1354
+
* @return {void|boolean} Returns false when the x or y value is lower than 1,
1355
+
* void when the value is not numeric or when the operation
1356
+
* is successful.
1357
+
*/
1358
+
setNumSelection : function( postid, el ) {
1359
+
var sel, elX = $('#imgedit-sel-width-' + postid), elY = $('#imgedit-sel-height-' + postid),
1360
+
elX1 = $('#imgedit-start-x-' + postid), elY1 = $('#imgedit-start-y-' + postid),
1361
+
xS = this.intval( elX1.val() ), yS = this.intval( elY1.val() ),
1362
+
x = this.intval( elX.val() ), y = this.intval( elY.val() ),
1363
+
img = $('#image-preview-' + postid), imgh = img.height(), imgw = img.width(),
1364
+
sizer = this.hold.sizer, x1, y1, x2, y2, ias = this.iasapi;
1365
+
1366
+
this.currentCropSelection = null;
1367
+
1368
+
if ( false === this.validateNumeric( el ) ) {
1369
+
return;
1370
+
}
1371
+
1372
+
if ( x < 1 ) {
1373
+
elX.val('');
1374
+
return false;
1375
+
}
1376
+
1377
+
if ( y < 1 ) {
1378
+
elY.val('');
1379
+
return false;
1380
+
}
1381
+
1382
+
if ( ( ( x && y ) || ( xS && yS ) ) && ( sel = ias.getSelection() ) ) {
1383
+
x2 = sel.x1 + Math.round( x * sizer );
1384
+
y2 = sel.y1 + Math.round( y * sizer );
1385
+
x1 = ( xS === sel.x1 ) ? sel.x1 : Math.round( xS * sizer );
1386
+
y1 = ( yS === sel.y1 ) ? sel.y1 : Math.round( yS * sizer );
1387
+
1388
+
if ( x2 > imgw ) {
1389
+
x1 = 0;
1390
+
x2 = imgw;
1391
+
elX.val( Math.min( this.hold.w, Math.round( x2 / sizer ) ) );
1392
+
}
1393
+
1394
+
if ( y2 > imgh ) {
1395
+
y1 = 0;
1396
+
y2 = imgh;
1397
+
elY.val( Math.min( this.hold.h, Math.round( y2 / sizer ) ) );
1398
+
}
1399
+
1400
+
ias.setSelection( x1, y1, x2, y2 );
1401
+
ias.update();
1402
+
this.setCropSelection(postid, ias.getSelection());
1403
+
this.currentCropSelection = ias.getSelection();
1404
+
}
1405
+
},
1406
+
1407
+
/**
1408
+
* Rounds a number to a whole.
1409
+
*
1410
+
* @since 2.9.0
1411
+
*
1412
+
* @memberof imageEdit
1413
+
*
1414
+
* @param {number} num The number.
1415
+
*
1416
+
* @return {number} The number rounded to a whole number.
1417
+
*/
1418
+
round : function(num) {
1419
+
var s;
1420
+
num = Math.round(num);
1421
+
1422
+
if ( this.hold.sizer > 0.6 ) {
1423
+
return num;
1424
+
}
1425
+
1426
+
s = num.toString().slice(-1);
1427
+
1428
+
if ( '1' === s ) {
1429
+
return num - 1;
1430
+
} else if ( '9' === s ) {
1431
+
return num + 1;
1432
+
}
1433
+
1434
+
return num;
1435
+
},
1436
+
1437
+
/**
1438
+
* Sets a locked aspect ratio for the selection.
1439
+
*
1440
+
* @since 2.9.0
1441
+
*
1442
+
* @memberof imageEdit
1443
+
*
1444
+
* @param {number} postid The post ID.
1445
+
* @param {number} n The ratio to set.
1446
+
* @param {jQuery} el The element containing the values.
1447
+
*
1448
+
* @return {void}
1449
+
*/
1450
+
setRatioSelection : function(postid, n, el) {
1451
+
var sel, r, x = this.intval( $('#imgedit-crop-width-' + postid).val() ),
1452
+
y = this.intval( $('#imgedit-crop-height-' + postid).val() ),
1453
+
h = $('#image-preview-' + postid).height();
1454
+
1455
+
if ( false === this.validateNumeric( el ) ) {
1456
+
this.iasapi.setOptions({
1457
+
aspectRatio: null
1458
+
});
1459
+
1460
+
return;
1461
+
}
1462
+
1463
+
if ( x && y ) {
1464
+
this.iasapi.setOptions({
1465
+
aspectRatio: x + ':' + y
1466
+
});
1467
+
1468
+
if ( sel = this.iasapi.getSelection(true) ) {
1469
+
r = Math.ceil( sel.y1 + ( ( sel.x2 - sel.x1 ) / ( x / y ) ) );
1470
+
1471
+
if ( r > h ) {
1472
+
r = h;
1473
+
var errorMessage = __( 'Selected crop ratio exceeds the boundaries of the image. Try a different ratio.' );
1474
+
1475
+
$( '#imgedit-crop-' + postid )
1476
+
.prepend( '<div class="notice notice-error" tabindex="-1" role="alert"><p>' + errorMessage + '</p></div>' );
1477
+
1478
+
wp.a11y.speak( errorMessage, 'assertive' );
1479
+
if ( n ) {
1480
+
$('#imgedit-crop-height-' + postid).val( '' );
1481
+
} else {
1482
+
$('#imgedit-crop-width-' + postid).val( '');
1483
+
}
1484
+
} else {
1485
+
var error = $( '#imgedit-crop-' + postid ).find( '.notice-error' );
1486
+
if ( 'undefined' !== typeof( error ) ) {
1487
+
error.remove();
1488
+
}
1489
+
}
1490
+
1491
+
this.iasapi.setSelection( sel.x1, sel.y1, sel.x2, r );
1492
+
this.iasapi.update();
1493
+
}
1494
+
}
1495
+
},
1496
+
1497
+
/**
1498
+
* Validates if a value in a jQuery.HTMLElement is numeric.
1499
+
*
1500
+
* @since 4.6.0
1501
+
*
1502
+
* @memberof imageEdit
1503
+
*
1504
+
* @param {jQuery} el The html element.
1505
+
*
1506
+
* @return {void|boolean} Returns false if the value is not numeric,
1507
+
* void when it is.
1508
+
*/
1509
+
validateNumeric: function( el ) {
1510
+
if ( false === this.intval( $( el ).val() ) ) {
1511
+
$( el ).val( '' );
1512
+
return false;
1513
+
}
1514
+
}
1515
+
};
1516
+
})(jQuery);
1517
+