dashboard.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542
  1. /* global pagenow, ajaxurl, postboxes, wpActiveEditor:true */
  2. var ajaxWidgets, ajaxPopulateWidgets, quickPressLoad;
  3. window.wp = window.wp || {};
  4. jQuery(document).ready( function($) {
  5. var welcomePanel = $( '#welcome-panel' ),
  6. welcomePanelHide = $('#wp_welcome_panel-hide'),
  7. updateWelcomePanel;
  8. updateWelcomePanel = function( visible ) {
  9. $.post( ajaxurl, {
  10. action: 'update-welcome-panel',
  11. visible: visible,
  12. welcomepanelnonce: $( '#welcomepanelnonce' ).val()
  13. });
  14. };
  15. if ( welcomePanel.hasClass('hidden') && welcomePanelHide.prop('checked') ) {
  16. welcomePanel.removeClass('hidden');
  17. }
  18. $('.welcome-panel-close, .welcome-panel-dismiss a', welcomePanel).click( function(e) {
  19. e.preventDefault();
  20. welcomePanel.addClass('hidden');
  21. updateWelcomePanel( 0 );
  22. $('#wp_welcome_panel-hide').prop('checked', false);
  23. });
  24. welcomePanelHide.click( function() {
  25. welcomePanel.toggleClass('hidden', ! this.checked );
  26. updateWelcomePanel( this.checked ? 1 : 0 );
  27. });
  28. var tryGutenbergPanel = $( '#try-gutenberg-panel' ),
  29. tryGutenbergPanelHide = $('#wp_try_gutenberg_panel-hide'),
  30. updateTryGutenbergPanel, installGutenbergSuccess;
  31. updateTryGutenbergPanel = function( visible ) {
  32. $.post( ajaxurl, {
  33. action: 'update-try-gutenberg-panel',
  34. visible: visible,
  35. trygutenbergpanelnonce: $( '#trygutenbergpanelnonce' ).val()
  36. });
  37. };
  38. installGutenbergSuccess = function( response ) {
  39. response.activateUrl += '&from=try-gutenberg';
  40. response.activateLabel = wp.updates.l10n.activatePluginLabel.replace( '%s', response.pluginName );
  41. wp.updates.installPluginSuccess( response );
  42. };
  43. if ( tryGutenbergPanel.hasClass( 'hidden' ) && tryGutenbergPanelHide.prop( 'checked' ) ) {
  44. tryGutenbergPanel.removeClass( 'hidden' );
  45. }
  46. $( '.try-gutenberg-panel-close, .try-gutenberg-panel-dismiss a', tryGutenbergPanel ).click( function( e ) {
  47. e.preventDefault();
  48. tryGutenbergPanel.addClass( 'hidden' );
  49. updateTryGutenbergPanel( 0 );
  50. $('#wp_try_gutenberg_panel-hide').prop( 'checked', false );
  51. });
  52. tryGutenbergPanelHide.click( function() {
  53. tryGutenbergPanel.toggleClass( 'hidden', ! this.checked );
  54. updateTryGutenbergPanel( this.checked ? 1 : 0 );
  55. });
  56. tryGutenbergPanel.on( 'click', '.install-now', function( e ) {
  57. e.preventDefault();
  58. var args = {
  59. slug: $( e.target ).data( 'slug' ),
  60. success: installGutenbergSuccess
  61. };
  62. wp.updates.installPlugin( args );
  63. } );
  64. // These widgets are sometimes populated via ajax
  65. ajaxWidgets = ['dashboard_primary'];
  66. ajaxPopulateWidgets = function(el) {
  67. function show(i, id) {
  68. var p, e = $('#' + id + ' div.inside:visible').find('.widget-loading');
  69. if ( e.length ) {
  70. p = e.parent();
  71. setTimeout( function(){
  72. p.load( ajaxurl + '?action=dashboard-widgets&widget=' + id + '&pagenow=' + pagenow, '', function() {
  73. p.hide().slideDown('normal', function(){
  74. $(this).css('display', '');
  75. });
  76. });
  77. }, i * 500 );
  78. }
  79. }
  80. if ( el ) {
  81. el = el.toString();
  82. if ( $.inArray(el, ajaxWidgets) !== -1 ) {
  83. show(0, el);
  84. }
  85. } else {
  86. $.each( ajaxWidgets, show );
  87. }
  88. };
  89. ajaxPopulateWidgets();
  90. postboxes.add_postbox_toggles(pagenow, { pbshow: ajaxPopulateWidgets } );
  91. /* QuickPress */
  92. quickPressLoad = function() {
  93. var act = $('#quickpost-action'), t;
  94. $( '#quick-press .submit input[type="submit"], #quick-press .submit input[type="reset"]' ).prop( 'disabled' , false );
  95. t = $('#quick-press').submit( function( e ) {
  96. e.preventDefault();
  97. $('#dashboard_quick_press #publishing-action .spinner').show();
  98. $('#quick-press .submit input[type="submit"], #quick-press .submit input[type="reset"]').prop('disabled', true);
  99. $.post( t.attr( 'action' ), t.serializeArray(), function( data ) {
  100. // Replace the form, and prepend the published post.
  101. $('#dashboard_quick_press .inside').html( data );
  102. $('#quick-press').removeClass('initial-form');
  103. quickPressLoad();
  104. highlightLatestPost();
  105. $('#title').focus();
  106. });
  107. function highlightLatestPost () {
  108. var latestPost = $('.drafts ul li').first();
  109. latestPost.css('background', '#fffbe5');
  110. setTimeout(function () {
  111. latestPost.css('background', 'none');
  112. }, 1000);
  113. }
  114. } );
  115. $('#publish').click( function() { act.val( 'post-quickpress-publish' ); } );
  116. $('#title, #tags-input, #content').each( function() {
  117. var input = $(this), prompt = $('#' + this.id + '-prompt-text');
  118. if ( '' === this.value ) {
  119. prompt.removeClass('screen-reader-text');
  120. }
  121. prompt.click( function() {
  122. $(this).addClass('screen-reader-text');
  123. input.focus();
  124. });
  125. input.blur( function() {
  126. if ( '' === this.value ) {
  127. prompt.removeClass('screen-reader-text');
  128. }
  129. });
  130. input.focus( function() {
  131. prompt.addClass('screen-reader-text');
  132. });
  133. });
  134. $('#quick-press').on( 'click focusin', function() {
  135. wpActiveEditor = 'content';
  136. });
  137. autoResizeTextarea();
  138. };
  139. quickPressLoad();
  140. $( '.meta-box-sortables' ).sortable( 'option', 'containment', '#wpwrap' );
  141. function autoResizeTextarea() {
  142. if ( document.documentMode && document.documentMode < 9 ) {
  143. return;
  144. }
  145. // Add a hidden div. We'll copy over the text from the textarea to measure its height.
  146. $('body').append( '<div class="quick-draft-textarea-clone" style="display: none;"></div>' );
  147. var clone = $('.quick-draft-textarea-clone'),
  148. editor = $('#content'),
  149. editorHeight = editor.height(),
  150. // 100px roughly accounts for browser chrome and allows the
  151. // save draft button to show on-screen at the same time.
  152. editorMaxHeight = $(window).height() - 100;
  153. // Match up textarea and clone div as much as possible.
  154. // Padding cannot be reliably retrieved using shorthand in all browsers.
  155. clone.css({
  156. 'font-family': editor.css('font-family'),
  157. 'font-size': editor.css('font-size'),
  158. 'line-height': editor.css('line-height'),
  159. 'padding-bottom': editor.css('paddingBottom'),
  160. 'padding-left': editor.css('paddingLeft'),
  161. 'padding-right': editor.css('paddingRight'),
  162. 'padding-top': editor.css('paddingTop'),
  163. 'white-space': 'pre-wrap',
  164. 'word-wrap': 'break-word',
  165. 'display': 'none'
  166. });
  167. // propertychange is for IE < 9
  168. editor.on('focus input propertychange', function() {
  169. var $this = $(this),
  170. // &nbsp; is to ensure that the height of a final trailing newline is included.
  171. textareaContent = $this.val() + '&nbsp;',
  172. // 2px is for border-top & border-bottom
  173. cloneHeight = clone.css('width', $this.css('width')).text(textareaContent).outerHeight() + 2;
  174. // Default to having scrollbars
  175. editor.css('overflow-y', 'auto');
  176. // Only change the height if it has indeed changed and both heights are below the max.
  177. if ( cloneHeight === editorHeight || ( cloneHeight >= editorMaxHeight && editorHeight >= editorMaxHeight ) ) {
  178. return;
  179. }
  180. // Don't allow editor to exceed height of window.
  181. // This is also bound in CSS to a max-height of 1300px to be extra safe.
  182. if ( cloneHeight > editorMaxHeight ) {
  183. editorHeight = editorMaxHeight;
  184. } else {
  185. editorHeight = cloneHeight;
  186. }
  187. // No scrollbars as we change height, not for IE < 9
  188. editor.css('overflow', 'hidden');
  189. $this.css('height', editorHeight + 'px');
  190. });
  191. }
  192. } );
  193. jQuery( function( $ ) {
  194. 'use strict';
  195. var communityEventsData = window.communityEventsData || {},
  196. app;
  197. app = window.wp.communityEvents = {
  198. initialized: false,
  199. model: null,
  200. /**
  201. * Initializes the wp.communityEvents object.
  202. *
  203. * @since 4.8.0
  204. */
  205. init: function() {
  206. if ( app.initialized ) {
  207. return;
  208. }
  209. var $container = $( '#community-events' );
  210. /*
  211. * When JavaScript is disabled, the errors container is shown, so
  212. * that "This widget requires JavaScript" message can be seen.
  213. *
  214. * When JS is enabled, the container is hidden at first, and then
  215. * revealed during the template rendering, if there actually are
  216. * errors to show.
  217. *
  218. * The display indicator switches from `hide-if-js` to `aria-hidden`
  219. * here in order to maintain consistency with all the other fields
  220. * that key off of `aria-hidden` to determine their visibility.
  221. * `aria-hidden` can't be used initially, because there would be no
  222. * way to set it to false when JavaScript is disabled, which would
  223. * prevent people from seeing the "This widget requires JavaScript"
  224. * message.
  225. */
  226. $( '.community-events-errors' )
  227. .attr( 'aria-hidden', 'true' )
  228. .removeClass( 'hide-if-js' );
  229. $container.on( 'click', '.community-events-toggle-location, .community-events-cancel', app.toggleLocationForm );
  230. $container.on( 'submit', '.community-events-form', function( event ) {
  231. var location = $.trim( $( '#community-events-location' ).val() );
  232. event.preventDefault();
  233. /*
  234. * Don't trigger a search if the search field is empty or the
  235. * search term was made of only spaces before being trimmed.
  236. */
  237. if ( ! location ) {
  238. return;
  239. }
  240. app.getEvents({
  241. location: location
  242. });
  243. });
  244. if ( communityEventsData && communityEventsData.cache && communityEventsData.cache.location && communityEventsData.cache.events ) {
  245. app.renderEventsTemplate( communityEventsData.cache, 'app' );
  246. } else {
  247. app.getEvents();
  248. }
  249. app.initialized = true;
  250. },
  251. /**
  252. * Toggles the visibility of the Edit Location form.
  253. *
  254. * @since 4.8.0
  255. *
  256. * @param {event|string} action 'show' or 'hide' to specify a state;
  257. * or an event object to flip between states.
  258. */
  259. toggleLocationForm: function( action ) {
  260. var $toggleButton = $( '.community-events-toggle-location' ),
  261. $cancelButton = $( '.community-events-cancel' ),
  262. $form = $( '.community-events-form' ),
  263. $target = $();
  264. if ( 'object' === typeof action ) {
  265. // The action is the event object: get the clicked element.
  266. $target = $( action.target );
  267. /*
  268. * Strict comparison doesn't work in this case because sometimes
  269. * we explicitly pass a string as value of aria-expanded and
  270. * sometimes a boolean as the result of an evaluation.
  271. */
  272. action = 'true' == $toggleButton.attr( 'aria-expanded' ) ? 'hide' : 'show';
  273. }
  274. if ( 'hide' === action ) {
  275. $toggleButton.attr( 'aria-expanded', 'false' );
  276. $cancelButton.attr( 'aria-expanded', 'false' );
  277. $form.attr( 'aria-hidden', 'true' );
  278. /*
  279. * If the Cancel button has been clicked, bring the focus back
  280. * to the toggle button so users relying on screen readers don't
  281. * lose their place.
  282. */
  283. if ( $target.hasClass( 'community-events-cancel' ) ) {
  284. $toggleButton.focus();
  285. }
  286. } else {
  287. $toggleButton.attr( 'aria-expanded', 'true' );
  288. $cancelButton.attr( 'aria-expanded', 'true' );
  289. $form.attr( 'aria-hidden', 'false' );
  290. }
  291. },
  292. /**
  293. * Sends REST API requests to fetch events for the widget.
  294. *
  295. * @since 4.8.0
  296. *
  297. * @param {object} requestParams
  298. */
  299. getEvents: function( requestParams ) {
  300. var initiatedBy,
  301. app = this,
  302. $spinner = $( '.community-events-form' ).children( '.spinner' );
  303. requestParams = requestParams || {};
  304. requestParams._wpnonce = communityEventsData.nonce;
  305. requestParams.timezone = window.Intl ? window.Intl.DateTimeFormat().resolvedOptions().timeZone : '';
  306. initiatedBy = requestParams.location ? 'user' : 'app';
  307. $spinner.addClass( 'is-active' );
  308. wp.ajax.post( 'get-community-events', requestParams )
  309. .always( function() {
  310. $spinner.removeClass( 'is-active' );
  311. })
  312. .done( function( response ) {
  313. if ( 'no_location_available' === response.error ) {
  314. if ( requestParams.location ) {
  315. response.unknownCity = requestParams.location;
  316. } else {
  317. /*
  318. * No location was passed, which means that this was an automatic query
  319. * based on IP, locale, and timezone. Since the user didn't initiate it,
  320. * it should fail silently. Otherwise, the error could confuse and/or
  321. * annoy them.
  322. */
  323. delete response.error;
  324. }
  325. }
  326. app.renderEventsTemplate( response, initiatedBy );
  327. })
  328. .fail( function() {
  329. app.renderEventsTemplate({
  330. 'location' : false,
  331. 'error' : true
  332. }, initiatedBy );
  333. });
  334. },
  335. /**
  336. * Renders the template for the Events section of the Events & News widget.
  337. *
  338. * @since 4.8.0
  339. *
  340. * @param {Object} templateParams The various parameters that will get passed to wp.template.
  341. * @param {string} initiatedBy 'user' to indicate that this was triggered manually by the user;
  342. * 'app' to indicate it was triggered automatically by the app itself.
  343. */
  344. renderEventsTemplate: function( templateParams, initiatedBy ) {
  345. var template,
  346. elementVisibility,
  347. l10nPlaceholder = /%(?:\d\$)?s/g, // Match `%s`, `%1$s`, `%2$s`, etc.
  348. $toggleButton = $( '.community-events-toggle-location' ),
  349. $locationMessage = $( '#community-events-location-message' ),
  350. $results = $( '.community-events-results' );
  351. /*
  352. * Hide all toggleable elements by default, to keep the logic simple.
  353. * Otherwise, each block below would have to turn hide everything that
  354. * could have been shown at an earlier point.
  355. *
  356. * The exception to that is that the .community-events container is hidden
  357. * when the page is first loaded, because the content isn't ready yet,
  358. * but once we've reached this point, it should always be shown.
  359. */
  360. elementVisibility = {
  361. '.community-events' : true,
  362. '.community-events-loading' : false,
  363. '.community-events-errors' : false,
  364. '.community-events-error-occurred' : false,
  365. '.community-events-could-not-locate' : false,
  366. '#community-events-location-message' : false,
  367. '.community-events-toggle-location' : false,
  368. '.community-events-results' : false
  369. };
  370. /*
  371. * Determine which templates should be rendered and which elements
  372. * should be displayed.
  373. */
  374. if ( templateParams.location.ip ) {
  375. /*
  376. * If the API determined the location by geolocating an IP, it will
  377. * provide events, but not a specific location.
  378. */
  379. $locationMessage.text( communityEventsData.l10n.attend_event_near_generic );
  380. if ( templateParams.events.length ) {
  381. template = wp.template( 'community-events-event-list' );
  382. $results.html( template( templateParams ) );
  383. } else {
  384. template = wp.template( 'community-events-no-upcoming-events' );
  385. $results.html( template( templateParams ) );
  386. }
  387. elementVisibility['#community-events-location-message'] = true;
  388. elementVisibility['.community-events-toggle-location'] = true;
  389. elementVisibility['.community-events-results'] = true;
  390. } else if ( templateParams.location.description ) {
  391. template = wp.template( 'community-events-attend-event-near' );
  392. $locationMessage.html( template( templateParams ) );
  393. if ( templateParams.events.length ) {
  394. template = wp.template( 'community-events-event-list' );
  395. $results.html( template( templateParams ) );
  396. } else {
  397. template = wp.template( 'community-events-no-upcoming-events' );
  398. $results.html( template( templateParams ) );
  399. }
  400. if ( 'user' === initiatedBy ) {
  401. wp.a11y.speak( communityEventsData.l10n.city_updated.replace( l10nPlaceholder, templateParams.location.description ), 'assertive' );
  402. }
  403. elementVisibility['#community-events-location-message'] = true;
  404. elementVisibility['.community-events-toggle-location'] = true;
  405. elementVisibility['.community-events-results'] = true;
  406. } else if ( templateParams.unknownCity ) {
  407. template = wp.template( 'community-events-could-not-locate' );
  408. $( '.community-events-could-not-locate' ).html( template( templateParams ) );
  409. wp.a11y.speak( communityEventsData.l10n.could_not_locate_city.replace( l10nPlaceholder, templateParams.unknownCity ) );
  410. elementVisibility['.community-events-errors'] = true;
  411. elementVisibility['.community-events-could-not-locate'] = true;
  412. } else if ( templateParams.error && 'user' === initiatedBy ) {
  413. /*
  414. * Errors messages are only shown for requests that were initiated
  415. * by the user, not for ones that were initiated by the app itself.
  416. * Showing error messages for an event that user isn't aware of
  417. * could be confusing or unnecessarily distracting.
  418. */
  419. wp.a11y.speak( communityEventsData.l10n.error_occurred_please_try_again );
  420. elementVisibility['.community-events-errors'] = true;
  421. elementVisibility['.community-events-error-occurred'] = true;
  422. } else {
  423. $locationMessage.text( communityEventsData.l10n.enter_closest_city );
  424. elementVisibility['#community-events-location-message'] = true;
  425. elementVisibility['.community-events-toggle-location'] = true;
  426. }
  427. // Set the visibility of toggleable elements.
  428. _.each( elementVisibility, function( isVisible, element ) {
  429. $( element ).attr( 'aria-hidden', ! isVisible );
  430. });
  431. $toggleButton.attr( 'aria-expanded', elementVisibility['.community-events-toggle-location'] );
  432. if ( templateParams.location && ( templateParams.location.ip || templateParams.location.latitude ) ) {
  433. // Hide the form when there's a valid location.
  434. app.toggleLocationForm( 'hide' );
  435. if ( 'user' === initiatedBy ) {
  436. /*
  437. * When the form is programmatically hidden after a user search,
  438. * bring the focus back to the toggle button so users relying
  439. * on screen readers don't lose their place.
  440. */
  441. $toggleButton.focus();
  442. }
  443. } else {
  444. app.toggleLocationForm( 'show' );
  445. }
  446. }
  447. };
  448. if ( $( '#dashboard_primary' ).is( ':visible' ) ) {
  449. app.init();
  450. } else {
  451. $( document ).on( 'postbox-toggled', function( event, postbox ) {
  452. var $postbox = $( postbox );
  453. if ( 'dashboard_primary' === $postbox.attr( 'id' ) && $postbox.is( ':visible' ) ) {
  454. app.init();
  455. }
  456. });
  457. }
  458. });