plugin-install.js 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. /**
  2. * @file Functionality for the plugin install screens.
  3. *
  4. * @output wp-admin/js/plugin-install.js
  5. */
  6. /* global tb_click, tb_remove, tb_position */
  7. jQuery( function( $ ) {
  8. var tbWindow,
  9. $iframeBody,
  10. $tabbables,
  11. $firstTabbable,
  12. $lastTabbable,
  13. $focusedBefore = $(),
  14. $uploadViewToggle = $( '.upload-view-toggle' ),
  15. $wrap = $ ( '.wrap' ),
  16. $body = $( document.body );
  17. window.tb_position = function() {
  18. var width = $( window ).width(),
  19. H = $( window ).height() - ( ( 792 < width ) ? 60 : 20 ),
  20. W = ( 792 < width ) ? 772 : width - 20;
  21. tbWindow = $( '#TB_window' );
  22. if ( tbWindow.length ) {
  23. tbWindow.width( W ).height( H );
  24. $( '#TB_iframeContent' ).width( W ).height( H );
  25. tbWindow.css({
  26. 'margin-left': '-' + parseInt( ( W / 2 ), 10 ) + 'px'
  27. });
  28. if ( typeof document.body.style.maxWidth !== 'undefined' ) {
  29. tbWindow.css({
  30. 'top': '30px',
  31. 'margin-top': '0'
  32. });
  33. }
  34. }
  35. return $( 'a.thickbox' ).each( function() {
  36. var href = $( this ).attr( 'href' );
  37. if ( ! href ) {
  38. return;
  39. }
  40. href = href.replace( /&width=[0-9]+/g, '' );
  41. href = href.replace( /&height=[0-9]+/g, '' );
  42. $(this).attr( 'href', href + '&width=' + W + '&height=' + ( H ) );
  43. });
  44. };
  45. $( window ).on( 'resize', function() {
  46. tb_position();
  47. });
  48. /*
  49. * Custom events: when a Thickbox iframe has loaded and when the Thickbox
  50. * modal gets removed from the DOM.
  51. */
  52. $body
  53. .on( 'thickbox:iframe:loaded', tbWindow, function() {
  54. /*
  55. * Return if it's not the modal with the plugin details iframe. Other
  56. * thickbox instances might want to load an iframe with content from
  57. * an external domain. Avoid to access the iframe contents when we're
  58. * not sure the iframe loads from the same domain.
  59. */
  60. if ( ! tbWindow.hasClass( 'plugin-details-modal' ) ) {
  61. return;
  62. }
  63. iframeLoaded();
  64. })
  65. .on( 'thickbox:removed', function() {
  66. // Set focus back to the element that opened the modal dialog.
  67. // Note: IE 8 would need this wrapped in a fake setTimeout `0`.
  68. $focusedBefore.trigger( 'focus' );
  69. });
  70. function iframeLoaded() {
  71. var $iframe = tbWindow.find( '#TB_iframeContent' );
  72. // Get the iframe body.
  73. $iframeBody = $iframe.contents().find( 'body' );
  74. // Get the tabbable elements and handle the keydown event on first load.
  75. handleTabbables();
  76. // Set initial focus on the "Close" button.
  77. $firstTabbable.trigger( 'focus' );
  78. /*
  79. * When the "Install" button is disabled (e.g. the Plugin is already installed)
  80. * then we can't predict where the last focusable element is. We need to get
  81. * the tabbable elements and handle the keydown event again and again,
  82. * each time the active tab panel changes.
  83. */
  84. $( '#plugin-information-tabs a', $iframeBody ).on( 'click', function() {
  85. handleTabbables();
  86. });
  87. // Close the modal when pressing Escape.
  88. $iframeBody.on( 'keydown', function( event ) {
  89. if ( 27 !== event.which ) {
  90. return;
  91. }
  92. tb_remove();
  93. });
  94. }
  95. /*
  96. * Get the tabbable elements and detach/attach the keydown event.
  97. * Called after the iframe has fully loaded so we have all the elements we need.
  98. * Called again each time a Tab gets clicked.
  99. * @todo Consider to implement a WordPress general utility for this and don't use jQuery UI.
  100. */
  101. function handleTabbables() {
  102. var $firstAndLast;
  103. // Get all the tabbable elements.
  104. $tabbables = $( ':tabbable', $iframeBody );
  105. // Our first tabbable element is always the "Close" button.
  106. $firstTabbable = tbWindow.find( '#TB_closeWindowButton' );
  107. // Get the last tabbable element.
  108. $lastTabbable = $tabbables.last();
  109. // Make a jQuery collection.
  110. $firstAndLast = $firstTabbable.add( $lastTabbable );
  111. // Detach any previously attached keydown event.
  112. $firstAndLast.off( 'keydown.wp-plugin-details' );
  113. // Attach again the keydown event on the first and last focusable elements.
  114. $firstAndLast.on( 'keydown.wp-plugin-details', function( event ) {
  115. constrainTabbing( event );
  116. });
  117. }
  118. // Constrain tabbing within the plugin modal dialog.
  119. function constrainTabbing( event ) {
  120. if ( 9 !== event.which ) {
  121. return;
  122. }
  123. if ( $lastTabbable[0] === event.target && ! event.shiftKey ) {
  124. event.preventDefault();
  125. $firstTabbable.trigger( 'focus' );
  126. } else if ( $firstTabbable[0] === event.target && event.shiftKey ) {
  127. event.preventDefault();
  128. $lastTabbable.trigger( 'focus' );
  129. }
  130. }
  131. /*
  132. * Open the Plugin details modal. The event is delegated to get also the links
  133. * in the plugins search tab, after the Ajax search rebuilds the HTML. It's
  134. * delegated on the closest ancestor and not on the body to avoid conflicts
  135. * with other handlers, see Trac ticket #43082.
  136. */
  137. $( '.wrap' ).on( 'click', '.thickbox.open-plugin-details-modal', function( e ) {
  138. // The `data-title` attribute is used only in the Plugin screens.
  139. var title = $( this ).data( 'title' ) ?
  140. wp.i18n.sprintf(
  141. // translators: %s: Plugin name.
  142. wp.i18n.__( 'Plugin: %s' ),
  143. $( this ).data( 'title' )
  144. ) :
  145. wp.i18n.__( 'Plugin details' );
  146. e.preventDefault();
  147. e.stopPropagation();
  148. // Store the element that has focus before opening the modal dialog, i.e. the control which opens it.
  149. $focusedBefore = $( this );
  150. tb_click.call(this);
  151. // Set ARIA role, ARIA label, and add a CSS class.
  152. tbWindow
  153. .attr({
  154. 'role': 'dialog',
  155. 'aria-label': wp.i18n.__( 'Plugin details' )
  156. })
  157. .addClass( 'plugin-details-modal' );
  158. // Set title attribute on the iframe.
  159. tbWindow.find( '#TB_iframeContent' ).attr( 'title', title );
  160. });
  161. /* Plugin install related JS */
  162. $( '#plugin-information-tabs a' ).on( 'click', function( event ) {
  163. var tab = $( this ).attr( 'name' );
  164. event.preventDefault();
  165. // Flip the tab.
  166. $( '#plugin-information-tabs a.current' ).removeClass( 'current' );
  167. $( this ).addClass( 'current' );
  168. // Only show the fyi box in the description section, on smaller screen,
  169. // where it's otherwise always displayed at the top.
  170. if ( 'description' !== tab && $( window ).width() < 772 ) {
  171. $( '#plugin-information-content' ).find( '.fyi' ).hide();
  172. } else {
  173. $( '#plugin-information-content' ).find( '.fyi' ).show();
  174. }
  175. // Flip the content.
  176. $( '#section-holder div.section' ).hide(); // Hide 'em all.
  177. $( '#section-' + tab ).show();
  178. });
  179. /*
  180. * When a user presses the "Upload Plugin" button, show the upload form in place
  181. * rather than sending them to the devoted upload plugin page.
  182. * The `?tab=upload` page still exists for no-js support and for plugins that
  183. * might access it directly. When we're in this page, let the link behave
  184. * like a link. Otherwise we're in the normal plugin installer pages and the
  185. * link should behave like a toggle button.
  186. */
  187. if ( ! $wrap.hasClass( 'plugin-install-tab-upload' ) ) {
  188. $uploadViewToggle
  189. .attr({
  190. role: 'button',
  191. 'aria-expanded': 'false'
  192. })
  193. .on( 'click', function( event ) {
  194. event.preventDefault();
  195. $body.toggleClass( 'show-upload-view' );
  196. $uploadViewToggle.attr( 'aria-expanded', $body.hasClass( 'show-upload-view' ) );
  197. });
  198. }
  199. });