dashboard.js 16 KB

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