Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.
 
 
 
 
 

702 rindas
17 KiB

  1. /* global imageEditL10n, ajaxurl, confirm */
  2. (function($) {
  3. var imageEdit = window.imageEdit = {
  4. iasapi : {},
  5. hold : {},
  6. postid : '',
  7. _view : false,
  8. intval : function(f) {
  9. /*
  10. * Bitwise OR operator: one of the obscure ways to truncate floating point figures,
  11. * worth reminding JavaScript doesn't have a distinct "integer" type.
  12. */
  13. return f | 0;
  14. },
  15. setDisabled : function( el, s ) {
  16. /*
  17. * `el` can be a single form element or a fieldset. Before #28864, the disabled state on
  18. * some text fields was handled targeting $('input', el). Now we need to handle the
  19. * disabled state on buttons too so we can just target `el` regardless if it's a single
  20. * element or a fieldset because when a fieldset is disabled, its descendants are disabled too.
  21. */
  22. if ( s ) {
  23. el.removeClass( 'disabled' ).prop( 'disabled', false );
  24. } else {
  25. el.addClass( 'disabled' ).prop( 'disabled', true );
  26. }
  27. },
  28. init : function(postid) {
  29. var t = this, old = $('#image-editor-' + t.postid),
  30. x = t.intval( $('#imgedit-x-' + postid).val() ),
  31. y = t.intval( $('#imgedit-y-' + postid).val() );
  32. if ( t.postid !== postid && old.length ) {
  33. t.close(t.postid);
  34. }
  35. t.hold.w = t.hold.ow = x;
  36. t.hold.h = t.hold.oh = y;
  37. t.hold.xy_ratio = x / y;
  38. t.hold.sizer = parseFloat( $('#imgedit-sizer-' + postid).val() );
  39. t.postid = postid;
  40. $('#imgedit-response-' + postid).empty();
  41. $('input[type="text"]', '#imgedit-panel-' + postid).keypress(function(e) {
  42. var k = e.keyCode;
  43. if ( 36 < k && k < 41 ) {
  44. $(this).blur();
  45. }
  46. if ( 13 === k ) {
  47. e.preventDefault();
  48. e.stopPropagation();
  49. return false;
  50. }
  51. });
  52. },
  53. toggleEditor : function(postid, toggle) {
  54. var wait = $('#imgedit-wait-' + postid);
  55. if ( toggle ) {
  56. wait.fadeIn( 'fast' );
  57. } else {
  58. wait.fadeOut('fast');
  59. }
  60. },
  61. toggleHelp : function(el) {
  62. var $el = $( el );
  63. $el
  64. .attr( 'aria-expanded', 'false' === $el.attr( 'aria-expanded' ) ? 'true' : 'false' )
  65. .parents( '.imgedit-group-top' ).toggleClass( 'imgedit-help-toggled' ).find( '.imgedit-help' ).slideToggle( 'fast' );
  66. return false;
  67. },
  68. getTarget : function(postid) {
  69. return $('input[name="imgedit-target-' + postid + '"]:checked', '#imgedit-save-target-' + postid).val() || 'full';
  70. },
  71. scaleChanged : function( postid, x, el ) {
  72. var w = $('#imgedit-scale-width-' + postid), h = $('#imgedit-scale-height-' + postid),
  73. warn = $('#imgedit-scale-warn-' + postid), w1 = '', h1 = '';
  74. if ( false === this.validateNumeric( el ) ) {
  75. return;
  76. }
  77. if ( x ) {
  78. h1 = ( w.val() !== '' ) ? Math.round( w.val() / this.hold.xy_ratio ) : '';
  79. h.val( h1 );
  80. } else {
  81. w1 = ( h.val() !== '' ) ? Math.round( h.val() * this.hold.xy_ratio ) : '';
  82. w.val( w1 );
  83. }
  84. if ( ( h1 && h1 > this.hold.oh ) || ( w1 && w1 > this.hold.ow ) ) {
  85. warn.css('visibility', 'visible');
  86. } else {
  87. warn.css('visibility', 'hidden');
  88. }
  89. },
  90. getSelRatio : function(postid) {
  91. var x = this.hold.w, y = this.hold.h,
  92. X = this.intval( $('#imgedit-crop-width-' + postid).val() ),
  93. Y = this.intval( $('#imgedit-crop-height-' + postid).val() );
  94. if ( X && Y ) {
  95. return X + ':' + Y;
  96. }
  97. if ( x && y ) {
  98. return x + ':' + y;
  99. }
  100. return '1:1';
  101. },
  102. filterHistory : function(postid, setSize) {
  103. // apply undo state to history
  104. var history = $('#imgedit-history-' + postid).val(), pop, n, o, i, op = [];
  105. if ( history !== '' ) {
  106. history = JSON.parse(history);
  107. pop = this.intval( $('#imgedit-undone-' + postid).val() );
  108. if ( pop > 0 ) {
  109. while ( pop > 0 ) {
  110. history.pop();
  111. pop--;
  112. }
  113. }
  114. if ( setSize ) {
  115. if ( !history.length ) {
  116. this.hold.w = this.hold.ow;
  117. this.hold.h = this.hold.oh;
  118. return '';
  119. }
  120. // restore
  121. o = history[history.length - 1];
  122. o = o.c || o.r || o.f || false;
  123. if ( o ) {
  124. this.hold.w = o.fw;
  125. this.hold.h = o.fh;
  126. }
  127. }
  128. // filter the values
  129. for ( n in history ) {
  130. i = history[n];
  131. if ( i.hasOwnProperty('c') ) {
  132. op[n] = { 'c': { 'x': i.c.x, 'y': i.c.y, 'w': i.c.w, 'h': i.c.h } };
  133. } else if ( i.hasOwnProperty('r') ) {
  134. op[n] = { 'r': i.r.r };
  135. } else if ( i.hasOwnProperty('f') ) {
  136. op[n] = { 'f': i.f.f };
  137. }
  138. }
  139. return JSON.stringify(op);
  140. }
  141. return '';
  142. },
  143. refreshEditor : function(postid, nonce, callback) {
  144. var t = this, data, img;
  145. t.toggleEditor(postid, 1);
  146. data = {
  147. 'action': 'imgedit-preview',
  148. '_ajax_nonce': nonce,
  149. 'postid': postid,
  150. 'history': t.filterHistory(postid, 1),
  151. 'rand': t.intval(Math.random() * 1000000)
  152. };
  153. img = $( '<img id="image-preview-' + postid + '" alt="" />' )
  154. .on( 'load', { history: data.history }, function( event ) {
  155. var max1, max2,
  156. parent = $( '#imgedit-crop-' + postid ),
  157. t = imageEdit,
  158. historyObj;
  159. if ( '' !== event.data.history ) {
  160. historyObj = JSON.parse( event.data.history );
  161. // If last executed action in history is a crop action.
  162. if ( historyObj[historyObj.length - 1].hasOwnProperty( 'c' ) ) {
  163. /*
  164. * A crop action has completed and the crop button gets disabled
  165. * ensure the undo button is enabled.
  166. */
  167. t.setDisabled( $( '#image-undo-' + postid) , true );
  168. // Move focus to the undo button to avoid a focus loss.
  169. $( '#image-undo-' + postid ).focus();
  170. }
  171. }
  172. parent.empty().append(img);
  173. // w, h are the new full size dims
  174. max1 = Math.max( t.hold.w, t.hold.h );
  175. max2 = Math.max( $(img).width(), $(img).height() );
  176. t.hold.sizer = max1 > max2 ? max2 / max1 : 1;
  177. t.initCrop(postid, img, parent);
  178. t.setCropSelection(postid, 0);
  179. if ( (typeof callback !== 'undefined') && callback !== null ) {
  180. callback();
  181. }
  182. if ( $('#imgedit-history-' + postid).val() && $('#imgedit-undone-' + postid).val() === '0' ) {
  183. $('input.imgedit-submit-btn', '#imgedit-panel-' + postid).removeAttr('disabled');
  184. } else {
  185. $('input.imgedit-submit-btn', '#imgedit-panel-' + postid).prop('disabled', true);
  186. }
  187. t.toggleEditor(postid, 0);
  188. })
  189. .on('error', function() {
  190. $('#imgedit-crop-' + postid).empty().append('<div class="error"><p>' + imageEditL10n.error + '</p></div>');
  191. t.toggleEditor(postid, 0);
  192. })
  193. .attr('src', ajaxurl + '?' + $.param(data));
  194. },
  195. action : function(postid, nonce, action) {
  196. var t = this, data, w, h, fw, fh;
  197. if ( t.notsaved(postid) ) {
  198. return false;
  199. }
  200. data = {
  201. 'action': 'image-editor',
  202. '_ajax_nonce': nonce,
  203. 'postid': postid
  204. };
  205. if ( 'scale' === action ) {
  206. w = $('#imgedit-scale-width-' + postid),
  207. h = $('#imgedit-scale-height-' + postid),
  208. fw = t.intval(w.val()),
  209. fh = t.intval(h.val());
  210. if ( fw < 1 ) {
  211. w.focus();
  212. return false;
  213. } else if ( fh < 1 ) {
  214. h.focus();
  215. return false;
  216. }
  217. if ( fw === t.hold.ow || fh === t.hold.oh ) {
  218. return false;
  219. }
  220. data['do'] = 'scale';
  221. data.fwidth = fw;
  222. data.fheight = fh;
  223. } else if ( 'restore' === action ) {
  224. data['do'] = 'restore';
  225. } else {
  226. return false;
  227. }
  228. t.toggleEditor(postid, 1);
  229. $.post(ajaxurl, data, function(r) {
  230. $('#image-editor-' + postid).empty().append(r);
  231. t.toggleEditor(postid, 0);
  232. // refresh the attachment model so that changes propagate
  233. if ( t._view ) {
  234. t._view.refresh();
  235. }
  236. });
  237. },
  238. save : function(postid, nonce) {
  239. var data,
  240. target = this.getTarget(postid),
  241. history = this.filterHistory(postid, 0),
  242. self = this;
  243. if ( '' === history ) {
  244. return false;
  245. }
  246. this.toggleEditor(postid, 1);
  247. data = {
  248. 'action': 'image-editor',
  249. '_ajax_nonce': nonce,
  250. 'postid': postid,
  251. 'history': history,
  252. 'target': target,
  253. 'context': $('#image-edit-context').length ? $('#image-edit-context').val() : null,
  254. 'do': 'save'
  255. };
  256. $.post(ajaxurl, data, function(r) {
  257. var ret = JSON.parse(r);
  258. if ( ret.error ) {
  259. $('#imgedit-response-' + postid).html('<div class="error"><p>' + ret.error + '</p></div>');
  260. imageEdit.close(postid);
  261. return;
  262. }
  263. if ( ret.fw && ret.fh ) {
  264. $('#media-dims-' + postid).html( ret.fw + ' &times; ' + ret.fh );
  265. }
  266. if ( ret.thumbnail ) {
  267. $('.thumbnail', '#thumbnail-head-' + postid).attr('src', ''+ret.thumbnail);
  268. }
  269. if ( ret.msg ) {
  270. $('#imgedit-response-' + postid).html('<div class="updated"><p>' + ret.msg + '</p></div>');
  271. }
  272. if ( self._view ) {
  273. self._view.save();
  274. } else {
  275. imageEdit.close(postid);
  276. }
  277. });
  278. },
  279. open : function( postid, nonce, view ) {
  280. this._view = view;
  281. var dfd, data, elem = $('#image-editor-' + postid), head = $('#media-head-' + postid),
  282. btn = $('#imgedit-open-btn-' + postid), spin = btn.siblings('.spinner');
  283. /*
  284. * Instead of disabling the button, which causes a focus loss and makes screen
  285. * readers announce "unavailable", return if the button was already clicked.
  286. */
  287. if ( btn.hasClass( 'button-activated' ) ) {
  288. return;
  289. }
  290. spin.addClass( 'is-active' );
  291. data = {
  292. 'action': 'image-editor',
  293. '_ajax_nonce': nonce,
  294. 'postid': postid,
  295. 'do': 'open'
  296. };
  297. dfd = $.ajax({
  298. url: ajaxurl,
  299. type: 'post',
  300. data: data,
  301. beforeSend: function() {
  302. btn.addClass( 'button-activated' );
  303. }
  304. }).done(function( html ) {
  305. elem.html( html );
  306. head.fadeOut('fast', function(){
  307. elem.fadeIn('fast');
  308. btn.removeClass( 'button-activated' );
  309. spin.removeClass( 'is-active' );
  310. });
  311. // Initialise the Image Editor now that everything is ready.
  312. imageEdit.init( postid );
  313. });
  314. return dfd;
  315. },
  316. imgLoaded : function(postid) {
  317. var img = $('#image-preview-' + postid), parent = $('#imgedit-crop-' + postid);
  318. this.initCrop(postid, img, parent);
  319. this.setCropSelection(postid, 0);
  320. this.toggleEditor(postid, 0);
  321. // Editor is ready, move focus to the first focusable element.
  322. $( '.imgedit-wrap .imgedit-help-toggle' ).eq( 0 ).focus();
  323. },
  324. initCrop : function(postid, image, parent) {
  325. var t = this,
  326. selW = $('#imgedit-sel-width-' + postid),
  327. selH = $('#imgedit-sel-height-' + postid),
  328. $img;
  329. t.iasapi = $(image).imgAreaSelect({
  330. parent: parent,
  331. instance: true,
  332. handles: true,
  333. keys: true,
  334. minWidth: 3,
  335. minHeight: 3,
  336. onInit: function( img ) {
  337. // Ensure that the imgareaselect wrapper elements are position:absolute
  338. // (even if we're in a position:fixed modal)
  339. $img = $( img );
  340. $img.next().css( 'position', 'absolute' )
  341. .nextAll( '.imgareaselect-outer' ).css( 'position', 'absolute' );
  342. parent.children().mousedown(function(e){
  343. var ratio = false, sel, defRatio;
  344. if ( e.shiftKey ) {
  345. sel = t.iasapi.getSelection();
  346. defRatio = t.getSelRatio(postid);
  347. ratio = ( sel && sel.width && sel.height ) ? sel.width + ':' + sel.height : defRatio;
  348. }
  349. t.iasapi.setOptions({
  350. aspectRatio: ratio
  351. });
  352. });
  353. },
  354. onSelectStart: function() {
  355. imageEdit.setDisabled($('#imgedit-crop-sel-' + postid), 1);
  356. },
  357. onSelectEnd: function(img, c) {
  358. imageEdit.setCropSelection(postid, c);
  359. },
  360. onSelectChange: function(img, c) {
  361. var sizer = imageEdit.hold.sizer;
  362. selW.val( imageEdit.round(c.width / sizer) );
  363. selH.val( imageEdit.round(c.height / sizer) );
  364. }
  365. });
  366. },
  367. setCropSelection : function(postid, c) {
  368. var sel;
  369. c = c || 0;
  370. if ( !c || ( c.width < 3 && c.height < 3 ) ) {
  371. this.setDisabled($('.imgedit-crop', '#imgedit-panel-' + postid), 0);
  372. this.setDisabled($('#imgedit-crop-sel-' + postid), 0);
  373. $('#imgedit-sel-width-' + postid).val('');
  374. $('#imgedit-sel-height-' + postid).val('');
  375. $('#imgedit-selection-' + postid).val('');
  376. return false;
  377. }
  378. sel = { 'x': c.x1, 'y': c.y1, 'w': c.width, 'h': c.height };
  379. this.setDisabled($('.imgedit-crop', '#imgedit-panel-' + postid), 1);
  380. $('#imgedit-selection-' + postid).val( JSON.stringify(sel) );
  381. },
  382. close : function(postid, warn) {
  383. warn = warn || false;
  384. if ( warn && this.notsaved(postid) ) {
  385. return false;
  386. }
  387. this.iasapi = {};
  388. this.hold = {};
  389. // If we've loaded the editor in the context of a Media Modal, then switch to the previous view,
  390. // whatever that might have been.
  391. if ( this._view ){
  392. this._view.back();
  393. }
  394. // In case we are not accessing the image editor in the context of a View, close the editor the old-skool way
  395. else {
  396. $('#image-editor-' + postid).fadeOut('fast', function() {
  397. $( '#media-head-' + postid ).fadeIn( 'fast', function() {
  398. // Move focus back to the Edit Image button. Runs also when saving.
  399. $( '#imgedit-open-btn-' + postid ).focus();
  400. });
  401. $(this).empty();
  402. });
  403. }
  404. },
  405. notsaved : function(postid) {
  406. var h = $('#imgedit-history-' + postid).val(),
  407. history = ( h !== '' ) ? JSON.parse(h) : [],
  408. pop = this.intval( $('#imgedit-undone-' + postid).val() );
  409. if ( pop < history.length ) {
  410. if ( confirm( $('#imgedit-leaving-' + postid).html() ) ) {
  411. return false;
  412. }
  413. return true;
  414. }
  415. return false;
  416. },
  417. addStep : function(op, postid, nonce) {
  418. var t = this, elem = $('#imgedit-history-' + postid),
  419. history = ( elem.val() !== '' ) ? JSON.parse( elem.val() ) : [],
  420. undone = $( '#imgedit-undone-' + postid ),
  421. pop = t.intval( undone.val() );
  422. while ( pop > 0 ) {
  423. history.pop();
  424. pop--;
  425. }
  426. undone.val(0); // reset
  427. history.push(op);
  428. elem.val( JSON.stringify(history) );
  429. t.refreshEditor(postid, nonce, function() {
  430. t.setDisabled($('#image-undo-' + postid), true);
  431. t.setDisabled($('#image-redo-' + postid), false);
  432. });
  433. },
  434. rotate : function(angle, postid, nonce, t) {
  435. if ( $(t).hasClass('disabled') ) {
  436. return false;
  437. }
  438. this.addStep({ 'r': { 'r': angle, 'fw': this.hold.h, 'fh': this.hold.w }}, postid, nonce);
  439. },
  440. flip : function (axis, postid, nonce, t) {
  441. if ( $(t).hasClass('disabled') ) {
  442. return false;
  443. }
  444. this.addStep({ 'f': { 'f': axis, 'fw': this.hold.w, 'fh': this.hold.h }}, postid, nonce);
  445. },
  446. crop : function (postid, nonce, t) {
  447. var sel = $('#imgedit-selection-' + postid).val(),
  448. w = this.intval( $('#imgedit-sel-width-' + postid).val() ),
  449. h = this.intval( $('#imgedit-sel-height-' + postid).val() );
  450. if ( $(t).hasClass('disabled') || sel === '' ) {
  451. return false;
  452. }
  453. sel = JSON.parse(sel);
  454. if ( sel.w > 0 && sel.h > 0 && w > 0 && h > 0 ) {
  455. sel.fw = w;
  456. sel.fh = h;
  457. this.addStep({ 'c': sel }, postid, nonce);
  458. }
  459. },
  460. undo : function (postid, nonce) {
  461. var t = this, button = $('#image-undo-' + postid), elem = $('#imgedit-undone-' + postid),
  462. pop = t.intval( elem.val() ) + 1;
  463. if ( button.hasClass('disabled') ) {
  464. return;
  465. }
  466. elem.val(pop);
  467. t.refreshEditor(postid, nonce, function() {
  468. var elem = $('#imgedit-history-' + postid),
  469. history = ( elem.val() !== '' ) ? JSON.parse( elem.val() ) : [];
  470. t.setDisabled($('#image-redo-' + postid), true);
  471. t.setDisabled(button, pop < history.length);
  472. // When undo gets disabled, move focus to the redo button to avoid a focus loss.
  473. if ( history.length === pop ) {
  474. $( '#image-redo-' + postid ).focus();
  475. }
  476. });
  477. },
  478. redo : function(postid, nonce) {
  479. var t = this, button = $('#image-redo-' + postid), elem = $('#imgedit-undone-' + postid),
  480. pop = t.intval( elem.val() ) - 1;
  481. if ( button.hasClass('disabled') ) {
  482. return;
  483. }
  484. elem.val(pop);
  485. t.refreshEditor(postid, nonce, function() {
  486. t.setDisabled($('#image-undo-' + postid), true);
  487. t.setDisabled(button, pop > 0);
  488. // When redo gets disabled, move focus to the undo button to avoid a focus loss.
  489. if ( 0 === pop ) {
  490. $( '#image-undo-' + postid ).focus();
  491. }
  492. });
  493. },
  494. setNumSelection : function( postid, el ) {
  495. var sel, elX = $('#imgedit-sel-width-' + postid), elY = $('#imgedit-sel-height-' + postid),
  496. x = this.intval( elX.val() ), y = this.intval( elY.val() ),
  497. img = $('#image-preview-' + postid), imgh = img.height(), imgw = img.width(),
  498. sizer = this.hold.sizer, x1, y1, x2, y2, ias = this.iasapi;
  499. if ( false === this.validateNumeric( el ) ) {
  500. return;
  501. }
  502. if ( x < 1 ) {
  503. elX.val('');
  504. return false;
  505. }
  506. if ( y < 1 ) {
  507. elY.val('');
  508. return false;
  509. }
  510. if ( x && y && ( sel = ias.getSelection() ) ) {
  511. x2 = sel.x1 + Math.round( x * sizer );
  512. y2 = sel.y1 + Math.round( y * sizer );
  513. x1 = sel.x1;
  514. y1 = sel.y1;
  515. if ( x2 > imgw ) {
  516. x1 = 0;
  517. x2 = imgw;
  518. elX.val( Math.round( x2 / sizer ) );
  519. }
  520. if ( y2 > imgh ) {
  521. y1 = 0;
  522. y2 = imgh;
  523. elY.val( Math.round( y2 / sizer ) );
  524. }
  525. ias.setSelection( x1, y1, x2, y2 );
  526. ias.update();
  527. this.setCropSelection(postid, ias.getSelection());
  528. }
  529. },
  530. round : function(num) {
  531. var s;
  532. num = Math.round(num);
  533. if ( this.hold.sizer > 0.6 ) {
  534. return num;
  535. }
  536. s = num.toString().slice(-1);
  537. if ( '1' === s ) {
  538. return num - 1;
  539. } else if ( '9' === s ) {
  540. return num + 1;
  541. }
  542. return num;
  543. },
  544. setRatioSelection : function(postid, n, el) {
  545. var sel, r, x = this.intval( $('#imgedit-crop-width-' + postid).val() ),
  546. y = this.intval( $('#imgedit-crop-height-' + postid).val() ),
  547. h = $('#image-preview-' + postid).height();
  548. if ( false === this.validateNumeric( el ) ) {
  549. return;
  550. }
  551. if ( x && y ) {
  552. this.iasapi.setOptions({
  553. aspectRatio: x + ':' + y
  554. });
  555. if ( sel = this.iasapi.getSelection(true) ) {
  556. r = Math.ceil( sel.y1 + ( ( sel.x2 - sel.x1 ) / ( x / y ) ) );
  557. if ( r > h ) {
  558. r = h;
  559. if ( n ) {
  560. $('#imgedit-crop-height-' + postid).val('');
  561. } else {
  562. $('#imgedit-crop-width-' + postid).val('');
  563. }
  564. }
  565. this.iasapi.setSelection( sel.x1, sel.y1, sel.x2, r );
  566. this.iasapi.update();
  567. }
  568. }
  569. },
  570. validateNumeric: function( el ) {
  571. if ( ! this.intval( $( el ).val() ) ) {
  572. $( el ).val( '' );
  573. return false;
  574. }
  575. }
  576. };
  577. })(jQuery);