You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

1268 lines
36 KiB

  1. /* global postL10n, ajaxurl, wpAjax, setPostThumbnailL10n, postboxes, pagenow, tinymce, alert, deleteUserSetting */
  2. /* global theList:true, theExtraList:true, getUserSetting, setUserSetting, commentReply */
  3. /**
  4. * Contains all dynamic functionality needed on post and term pages.
  5. *
  6. * @summary Control page and term functionality.
  7. */
  8. var commentsBox, WPSetThumbnailHTML, WPSetThumbnailID, WPRemoveThumbnail, wptitlehint, makeSlugeditClickable, editPermalink;
  9. // Backwards compatibility: prevent fatal errors.
  10. makeSlugeditClickable = editPermalink = function(){};
  11. // Make sure the wp object exists.
  12. window.wp = window.wp || {};
  13. ( function( $ ) {
  14. var titleHasFocus = false;
  15. /**
  16. * Control loading of comments on the post and term edit pages.
  17. *
  18. * @type {{st: number, get: commentsBox.get, load: commentsBox.load}}
  19. *
  20. * @namespace commentsBox
  21. */
  22. commentsBox = {
  23. // Comment offset to use when fetching new comments.
  24. st : 0,
  25. /**
  26. * Fetch comments using AJAX and display them in the box.
  27. *
  28. * @param {int} total Total number of comments for this post.
  29. * @param {int} num Optional. Number of comments to fetch, defaults to 20.
  30. * @returns {boolean} Always returns false.
  31. *
  32. * @memberof commentsBox
  33. */
  34. get : function(total, num) {
  35. var st = this.st, data;
  36. if ( ! num )
  37. num = 20;
  38. this.st += num;
  39. this.total = total;
  40. $( '#commentsdiv .spinner' ).addClass( 'is-active' );
  41. data = {
  42. 'action' : 'get-comments',
  43. 'mode' : 'single',
  44. '_ajax_nonce' : $('#add_comment_nonce').val(),
  45. 'p' : $('#post_ID').val(),
  46. 'start' : st,
  47. 'number' : num
  48. };
  49. $.post(
  50. ajaxurl,
  51. data,
  52. function(r) {
  53. r = wpAjax.parseAjaxResponse(r);
  54. $('#commentsdiv .widefat').show();
  55. $( '#commentsdiv .spinner' ).removeClass( 'is-active' );
  56. if ( 'object' == typeof r && r.responses[0] ) {
  57. $('#the-comment-list').append( r.responses[0].data );
  58. theList = theExtraList = null;
  59. $( 'a[className*=\':\']' ).unbind();
  60. // If the offset is over the total number of comments we cannot fetch any more, so hide the button.
  61. if ( commentsBox.st > commentsBox.total )
  62. $('#show-comments').hide();
  63. else
  64. $('#show-comments').show().children('a').html(postL10n.showcomm);
  65. return;
  66. } else if ( 1 == r ) {
  67. $('#show-comments').html(postL10n.endcomm);
  68. return;
  69. }
  70. $('#the-comment-list').append('<tr><td colspan="2">'+wpAjax.broken+'</td></tr>');
  71. }
  72. );
  73. return false;
  74. },
  75. /**
  76. * Load the next batch of comments.
  77. *
  78. * @param {int} total Total number of comments to load.
  79. *
  80. * @memberof commentsBox
  81. */
  82. load: function(total){
  83. this.st = jQuery('#the-comment-list tr.comment:visible').length;
  84. this.get(total);
  85. }
  86. };
  87. /**
  88. * Overwrite the content of the Featured Image postbox
  89. *
  90. * @param {string} html New HTML to be displayed in the content area of the postbox.
  91. *
  92. * @global
  93. */
  94. WPSetThumbnailHTML = function(html){
  95. $('.inside', '#postimagediv').html(html);
  96. };
  97. /**
  98. * Set the Image ID of the Featured Image
  99. *
  100. * @param {int} id The post_id of the image to use as Featured Image.
  101. *
  102. * @global
  103. */
  104. WPSetThumbnailID = function(id){
  105. var field = $('input[value="_thumbnail_id"]', '#list-table');
  106. if ( field.length > 0 ) {
  107. $('#meta\\[' + field.attr('id').match(/[0-9]+/) + '\\]\\[value\\]').text(id);
  108. }
  109. };
  110. /**
  111. * Remove the Featured Image
  112. *
  113. * @param {string} nonce Nonce to use in the request.
  114. *
  115. * @global
  116. */
  117. WPRemoveThumbnail = function(nonce){
  118. $.post(ajaxurl, {
  119. action: 'set-post-thumbnail', post_id: $( '#post_ID' ).val(), thumbnail_id: -1, _ajax_nonce: nonce, cookie: encodeURIComponent( document.cookie )
  120. },
  121. /**
  122. * Handle server response
  123. *
  124. * @param {string} str Response, will be '0' when an error occurred otherwise contains link to add Featured Image.
  125. */
  126. function(str){
  127. if ( str == '0' ) {
  128. alert( setPostThumbnailL10n.error );
  129. } else {
  130. WPSetThumbnailHTML(str);
  131. }
  132. }
  133. );
  134. };
  135. /**
  136. * Heartbeat locks.
  137. *
  138. * Used to lock editing of an object by only one user at a time.
  139. *
  140. * When the user does not send a heartbeat in a heartbeat-time
  141. * the user is no longer editing and another user can start editing.
  142. */
  143. $(document).on( 'heartbeat-send.refresh-lock', function( e, data ) {
  144. var lock = $('#active_post_lock').val(),
  145. post_id = $('#post_ID').val(),
  146. send = {};
  147. if ( ! post_id || ! $('#post-lock-dialog').length )
  148. return;
  149. send.post_id = post_id;
  150. if ( lock )
  151. send.lock = lock;
  152. data['wp-refresh-post-lock'] = send;
  153. }).on( 'heartbeat-tick.refresh-lock', function( e, data ) {
  154. // Post locks: update the lock string or show the dialog if somebody has taken over editing.
  155. var received, wrap, avatar;
  156. if ( data['wp-refresh-post-lock'] ) {
  157. received = data['wp-refresh-post-lock'];
  158. if ( received.lock_error ) {
  159. // Show "editing taken over" message.
  160. wrap = $('#post-lock-dialog');
  161. if ( wrap.length && ! wrap.is(':visible') ) {
  162. if ( wp.autosave ) {
  163. // Save the latest changes and disable.
  164. $(document).one( 'heartbeat-tick', function() {
  165. wp.autosave.server.suspend();
  166. wrap.removeClass('saving').addClass('saved');
  167. $(window).off( 'beforeunload.edit-post' );
  168. });
  169. wrap.addClass('saving');
  170. wp.autosave.server.triggerSave();
  171. }
  172. if ( received.lock_error.avatar_src ) {
  173. avatar = $( '<img class="avatar avatar-64 photo" width="64" height="64" alt="" />' ).attr( 'src', received.lock_error.avatar_src.replace( /&amp;/g, '&' ) );
  174. wrap.find('div.post-locked-avatar').empty().append( avatar );
  175. }
  176. wrap.show().find('.currently-editing').text( received.lock_error.text );
  177. wrap.find('.wp-tab-first').focus();
  178. }
  179. } else if ( received.new_lock ) {
  180. $('#active_post_lock').val( received.new_lock );
  181. }
  182. }
  183. }).on( 'before-autosave.update-post-slug', function() {
  184. titleHasFocus = document.activeElement && document.activeElement.id === 'title';
  185. }).on( 'after-autosave.update-post-slug', function() {
  186. /*
  187. * Create slug area only if not already there
  188. * and the title field was not focused (user was not typing a title) when autosave ran.
  189. */
  190. if ( ! $('#edit-slug-box > *').length && ! titleHasFocus ) {
  191. $.post( ajaxurl, {
  192. action: 'sample-permalink',
  193. post_id: $('#post_ID').val(),
  194. new_title: $('#title').val(),
  195. samplepermalinknonce: $('#samplepermalinknonce').val()
  196. },
  197. function( data ) {
  198. if ( data != '-1' ) {
  199. $('#edit-slug-box').html(data);
  200. }
  201. }
  202. );
  203. }
  204. });
  205. }(jQuery));
  206. /**
  207. * Heartbeat refresh nonces.
  208. */
  209. (function($) {
  210. var check, timeout;
  211. /**
  212. * Only allow to check for nonce refresh every 30 seconds.
  213. */
  214. function schedule() {
  215. check = false;
  216. window.clearTimeout( timeout );
  217. timeout = window.setTimeout( function(){ check = true; }, 300000 );
  218. }
  219. $(document).on( 'heartbeat-send.wp-refresh-nonces', function( e, data ) {
  220. var post_id,
  221. $authCheck = $('#wp-auth-check-wrap');
  222. if ( check || ( $authCheck.length && ! $authCheck.hasClass( 'hidden' ) ) ) {
  223. if ( ( post_id = $('#post_ID').val() ) && $('#_wpnonce').val() ) {
  224. data['wp-refresh-post-nonces'] = {
  225. post_id: post_id
  226. };
  227. }
  228. }
  229. }).on( 'heartbeat-tick.wp-refresh-nonces', function( e, data ) {
  230. var nonces = data['wp-refresh-post-nonces'];
  231. if ( nonces ) {
  232. schedule();
  233. if ( nonces.replace ) {
  234. $.each( nonces.replace, function( selector, value ) {
  235. $( '#' + selector ).val( value );
  236. });
  237. }
  238. if ( nonces.heartbeatNonce )
  239. window.heartbeatSettings.nonce = nonces.heartbeatNonce;
  240. }
  241. }).ready( function() {
  242. schedule();
  243. });
  244. }(jQuery));
  245. /**
  246. * All post and postbox controls and functionality.
  247. */
  248. jQuery(document).ready( function($) {
  249. var stamp, visibility, $submitButtons, updateVisibility, updateText,
  250. sticky = '',
  251. $textarea = $('#content'),
  252. $document = $(document),
  253. postId = $('#post_ID').val() || 0,
  254. $submitpost = $('#submitpost'),
  255. releaseLock = true,
  256. $postVisibilitySelect = $('#post-visibility-select'),
  257. $timestampdiv = $('#timestampdiv'),
  258. $postStatusSelect = $('#post-status-select'),
  259. isMac = window.navigator.platform ? window.navigator.platform.indexOf( 'Mac' ) !== -1 : false;
  260. postboxes.add_postbox_toggles(pagenow);
  261. /*
  262. * Clear the window name. Otherwise if this is a former preview window where the user navigated to edit another post,
  263. * and the first post is still being edited, clicking Preview there will use this window to show the preview.
  264. */
  265. window.name = '';
  266. // Post locks: contain focus inside the dialog. If the dialog is shown, focus the first item.
  267. $('#post-lock-dialog .notification-dialog').on( 'keydown', function(e) {
  268. // Don't do anything when [tab] is pressed.
  269. if ( e.which != 9 )
  270. return;
  271. var target = $(e.target);
  272. // [shift] + [tab] on first tab cycles back to last tab.
  273. if ( target.hasClass('wp-tab-first') && e.shiftKey ) {
  274. $(this).find('.wp-tab-last').focus();
  275. e.preventDefault();
  276. // [tab] on last tab cycles back to first tab.
  277. } else if ( target.hasClass('wp-tab-last') && ! e.shiftKey ) {
  278. $(this).find('.wp-tab-first').focus();
  279. e.preventDefault();
  280. }
  281. }).filter(':visible').find('.wp-tab-first').focus();
  282. // Set the heartbeat interval to 15 sec. if post lock dialogs are enabled.
  283. if ( wp.heartbeat && $('#post-lock-dialog').length ) {
  284. wp.heartbeat.interval( 15 );
  285. }
  286. // The form is being submitted by the user.
  287. $submitButtons = $submitpost.find( ':submit, a.submitdelete, #post-preview' ).on( 'click.edit-post', function( event ) {
  288. var $button = $(this);
  289. if ( $button.hasClass('disabled') ) {
  290. event.preventDefault();
  291. return;
  292. }
  293. if ( $button.hasClass('submitdelete') || $button.is( '#post-preview' ) ) {
  294. return;
  295. }
  296. // The form submission can be blocked from JS or by using HTML 5.0 validation on some fields.
  297. // Run this only on an actual 'submit'.
  298. $('form#post').off( 'submit.edit-post' ).on( 'submit.edit-post', function( event ) {
  299. if ( event.isDefaultPrevented() ) {
  300. return;
  301. }
  302. // Stop auto save.
  303. if ( wp.autosave ) {
  304. wp.autosave.server.suspend();
  305. }
  306. if ( typeof commentReply !== 'undefined' ) {
  307. /*
  308. * Warn the user they have an unsaved comment before submitting
  309. * the post data for update.
  310. */
  311. if ( ! commentReply.discardCommentChanges() ) {
  312. return false;
  313. }
  314. /*
  315. * Close the comment edit/reply form if open to stop the form
  316. * action from interfering with the post's form action.
  317. */
  318. commentReply.close();
  319. }
  320. releaseLock = false;
  321. $(window).off( 'beforeunload.edit-post' );
  322. $submitButtons.addClass( 'disabled' );
  323. if ( $button.attr('id') === 'publish' ) {
  324. $submitpost.find( '#major-publishing-actions .spinner' ).addClass( 'is-active' );
  325. } else {
  326. $submitpost.find( '#minor-publishing .spinner' ).addClass( 'is-active' );
  327. }
  328. });
  329. });
  330. // Submit the form saving a draft or an autosave, and show a preview in a new tab
  331. $('#post-preview').on( 'click.post-preview', function( event ) {
  332. var $this = $(this),
  333. $form = $('form#post'),
  334. $previewField = $('input#wp-preview'),
  335. target = $this.attr('target') || 'wp-preview',
  336. ua = navigator.userAgent.toLowerCase();
  337. event.preventDefault();
  338. if ( $this.hasClass('disabled') ) {
  339. return;
  340. }
  341. if ( wp.autosave ) {
  342. wp.autosave.server.tempBlockSave();
  343. }
  344. $previewField.val('dopreview');
  345. $form.attr( 'target', target ).submit().attr( 'target', '' );
  346. // Workaround for WebKit bug preventing a form submitting twice to the same action.
  347. // https://bugs.webkit.org/show_bug.cgi?id=28633
  348. if ( ua.indexOf('safari') !== -1 && ua.indexOf('chrome') === -1 ) {
  349. $form.attr( 'action', function( index, value ) {
  350. return value + '?t=' + ( new Date() ).getTime();
  351. });
  352. }
  353. $previewField.val('');
  354. });
  355. // This code is meant to allow tabbing from Title to Post content.
  356. $('#title').on( 'keydown.editor-focus', function( event ) {
  357. var editor;
  358. if ( event.keyCode === 9 && ! event.ctrlKey && ! event.altKey && ! event.shiftKey ) {
  359. editor = typeof tinymce != 'undefined' && tinymce.get('content');
  360. if ( editor && ! editor.isHidden() ) {
  361. editor.focus();
  362. } else if ( $textarea.length ) {
  363. $textarea.focus();
  364. } else {
  365. return;
  366. }
  367. event.preventDefault();
  368. }
  369. });
  370. // Auto save new posts after a title is typed.
  371. if ( $( '#auto_draft' ).val() ) {
  372. $( '#title' ).blur( function() {
  373. var cancel;
  374. if ( ! this.value || $('#edit-slug-box > *').length ) {
  375. return;
  376. }
  377. // Cancel the auto save when the blur was triggered by the user submitting the form.
  378. $('form#post').one( 'submit', function() {
  379. cancel = true;
  380. });
  381. window.setTimeout( function() {
  382. if ( ! cancel && wp.autosave ) {
  383. wp.autosave.server.triggerSave();
  384. }
  385. }, 200 );
  386. });
  387. }
  388. $document.on( 'autosave-disable-buttons.edit-post', function() {
  389. $submitButtons.addClass( 'disabled' );
  390. }).on( 'autosave-enable-buttons.edit-post', function() {
  391. if ( ! wp.heartbeat || ! wp.heartbeat.hasConnectionError() ) {
  392. $submitButtons.removeClass( 'disabled' );
  393. }
  394. }).on( 'before-autosave.edit-post', function() {
  395. $( '.autosave-message' ).text( postL10n.savingText );
  396. }).on( 'after-autosave.edit-post', function( event, data ) {
  397. $( '.autosave-message' ).text( data.message );
  398. if ( $( document.body ).hasClass( 'post-new-php' ) ) {
  399. $( '.submitbox .submitdelete' ).show();
  400. }
  401. });
  402. /*
  403. * When the user is trying to load another page, or reloads current page
  404. * show a confirmation dialog when there are unsaved changes.
  405. */
  406. $(window).on( 'beforeunload.edit-post', function() {
  407. var editor = typeof tinymce !== 'undefined' && tinymce.get('content');
  408. if ( ( editor && ! editor.isHidden() && editor.isDirty() ) ||
  409. ( wp.autosave && wp.autosave.server.postChanged() ) ) {
  410. return postL10n.saveAlert;
  411. }
  412. }).on( 'unload.edit-post', function( event ) {
  413. if ( ! releaseLock ) {
  414. return;
  415. }
  416. /*
  417. * Unload is triggered (by hand) on removing the Thickbox iframe.
  418. * Make sure we process only the main document unload.
  419. */
  420. if ( event.target && event.target.nodeName != '#document' ) {
  421. return;
  422. }
  423. var postID = $('#post_ID').val();
  424. var postLock = $('#active_post_lock').val();
  425. if ( ! postID || ! postLock ) {
  426. return;
  427. }
  428. var data = {
  429. action: 'wp-remove-post-lock',
  430. _wpnonce: $('#_wpnonce').val(),
  431. post_ID: postID,
  432. active_post_lock: postLock
  433. };
  434. if ( window.FormData && window.navigator.sendBeacon ) {
  435. var formData = new window.FormData();
  436. $.each( data, function( key, value ) {
  437. formData.append( key, value );
  438. });
  439. if ( window.navigator.sendBeacon( ajaxurl, formData ) ) {
  440. return;
  441. }
  442. }
  443. // Fall back to a synchronous POST request.
  444. // See https://developer.mozilla.org/en-US/docs/Web/API/Navigator/sendBeacon
  445. $.post({
  446. async: false,
  447. data: data,
  448. url: ajaxurl
  449. });
  450. });
  451. // Multiple Taxonomies.
  452. if ( $('#tagsdiv-post_tag').length ) {
  453. window.tagBox && window.tagBox.init();
  454. } else {
  455. $('.meta-box-sortables').children('div.postbox').each(function(){
  456. if ( this.id.indexOf('tagsdiv-') === 0 ) {
  457. window.tagBox && window.tagBox.init();
  458. return false;
  459. }
  460. });
  461. }
  462. // Handle categories.
  463. $('.categorydiv').each( function(){
  464. var this_id = $(this).attr('id'), catAddBefore, catAddAfter, taxonomyParts, taxonomy, settingName;
  465. taxonomyParts = this_id.split('-');
  466. taxonomyParts.shift();
  467. taxonomy = taxonomyParts.join('-');
  468. settingName = taxonomy + '_tab';
  469. if ( taxonomy == 'category' ) {
  470. settingName = 'cats';
  471. }
  472. // TODO: move to jQuery 1.3+, support for multiple hierarchical taxonomies, see wp-lists.js
  473. $('a', '#' + taxonomy + '-tabs').click( function( e ) {
  474. e.preventDefault();
  475. var t = $(this).attr('href');
  476. $(this).parent().addClass('tabs').siblings('li').removeClass('tabs');
  477. $('#' + taxonomy + '-tabs').siblings('.tabs-panel').hide();
  478. $(t).show();
  479. if ( '#' + taxonomy + '-all' == t ) {
  480. deleteUserSetting( settingName );
  481. } else {
  482. setUserSetting( settingName, 'pop' );
  483. }
  484. });
  485. if ( getUserSetting( settingName ) )
  486. $('a[href="#' + taxonomy + '-pop"]', '#' + taxonomy + '-tabs').click();
  487. // Add category button controls.
  488. $('#new' + taxonomy).one( 'focus', function() {
  489. $( this ).val( '' ).removeClass( 'form-input-tip' );
  490. });
  491. // On [enter] submit the taxonomy.
  492. $('#new' + taxonomy).keypress( function(event){
  493. if( 13 === event.keyCode ) {
  494. event.preventDefault();
  495. $('#' + taxonomy + '-add-submit').click();
  496. }
  497. });
  498. // After submitting a new taxonomy, re-focus the input field.
  499. $('#' + taxonomy + '-add-submit').click( function() {
  500. $('#new' + taxonomy).focus();
  501. });
  502. /**
  503. * Before adding a new taxonomy, disable submit button.
  504. *
  505. * @param {Object} s Taxonomy object which will be added.
  506. *
  507. * @returns {Object}
  508. */
  509. catAddBefore = function( s ) {
  510. if ( !$('#new'+taxonomy).val() ) {
  511. return false;
  512. }
  513. s.data += '&' + $( ':checked', '#'+taxonomy+'checklist' ).serialize();
  514. $( '#' + taxonomy + '-add-submit' ).prop( 'disabled', true );
  515. return s;
  516. };
  517. /**
  518. * Re-enable submit button after a taxonomy has been added.
  519. *
  520. * Re-enable submit button.
  521. * If the taxonomy has a parent place the taxonomy underneath the parent.
  522. *
  523. * @param {Object} r Response.
  524. * @param {Object} s Taxonomy data.
  525. *
  526. * @returns void
  527. */
  528. catAddAfter = function( r, s ) {
  529. var sup, drop = $('#new'+taxonomy+'_parent');
  530. $( '#' + taxonomy + '-add-submit' ).prop( 'disabled', false );
  531. if ( 'undefined' != s.parsed.responses[0] && (sup = s.parsed.responses[0].supplemental.newcat_parent) ) {
  532. drop.before(sup);
  533. drop.remove();
  534. }
  535. };
  536. $('#' + taxonomy + 'checklist').wpList({
  537. alt: '',
  538. response: taxonomy + '-ajax-response',
  539. addBefore: catAddBefore,
  540. addAfter: catAddAfter
  541. });
  542. // Add new taxonomy button toggles input form visibility.
  543. $('#' + taxonomy + '-add-toggle').click( function( e ) {
  544. e.preventDefault();
  545. $('#' + taxonomy + '-adder').toggleClass( 'wp-hidden-children' );
  546. $('a[href="#' + taxonomy + '-all"]', '#' + taxonomy + '-tabs').click();
  547. $('#new'+taxonomy).focus();
  548. });
  549. // Sync checked items between "All {taxonomy}" and "Most used" lists.
  550. $('#' + taxonomy + 'checklist, #' + taxonomy + 'checklist-pop').on( 'click', 'li.popular-category > label input[type="checkbox"]', function() {
  551. var t = $(this), c = t.is(':checked'), id = t.val();
  552. if ( id && t.parents('#taxonomy-'+taxonomy).length )
  553. $('#in-' + taxonomy + '-' + id + ', #in-popular-' + taxonomy + '-' + id).prop( 'checked', c );
  554. });
  555. }); // end cats
  556. // Custom Fields postbox.
  557. if ( $('#postcustom').length ) {
  558. $( '#the-list' ).wpList( {
  559. /**
  560. * Add current post_ID to request to fetch custom fields
  561. *
  562. * @param {Object} s Request object.
  563. *
  564. * @returns {Object} Data modified with post_ID attached.
  565. */
  566. addBefore: function( s ) {
  567. s.data += '&post_id=' + $('#post_ID').val();
  568. return s;
  569. },
  570. /**
  571. * Show the listing of custom fields after fetching.
  572. */
  573. addAfter: function() {
  574. $('table#list-table').show();
  575. }
  576. });
  577. }
  578. /*
  579. * Publish Post box (#submitdiv)
  580. */
  581. if ( $('#submitdiv').length ) {
  582. stamp = $('#timestamp').html();
  583. visibility = $('#post-visibility-display').html();
  584. /**
  585. * When the visibility of a post changes sub-options should be shown or hidden.
  586. *
  587. * @returns void
  588. */
  589. updateVisibility = function() {
  590. // Show sticky for public posts.
  591. if ( $postVisibilitySelect.find('input:radio:checked').val() != 'public' ) {
  592. $('#sticky').prop('checked', false);
  593. $('#sticky-span').hide();
  594. } else {
  595. $('#sticky-span').show();
  596. }
  597. // Show password input field for password protected post.
  598. if ( $postVisibilitySelect.find('input:radio:checked').val() != 'password' ) {
  599. $('#password-span').hide();
  600. } else {
  601. $('#password-span').show();
  602. }
  603. };
  604. /**
  605. * Make sure all labels represent the current settings.
  606. *
  607. * @returns {boolean} False when an invalid timestamp has been selected, otherwise True.
  608. */
  609. updateText = function() {
  610. if ( ! $timestampdiv.length )
  611. return true;
  612. var attemptedDate, originalDate, currentDate, publishOn, postStatus = $('#post_status'),
  613. optPublish = $('option[value="publish"]', postStatus), aa = $('#aa').val(),
  614. mm = $('#mm').val(), jj = $('#jj').val(), hh = $('#hh').val(), mn = $('#mn').val();
  615. attemptedDate = new Date( aa, mm - 1, jj, hh, mn );
  616. originalDate = new Date( $('#hidden_aa').val(), $('#hidden_mm').val() -1, $('#hidden_jj').val(), $('#hidden_hh').val(), $('#hidden_mn').val() );
  617. currentDate = new Date( $('#cur_aa').val(), $('#cur_mm').val() -1, $('#cur_jj').val(), $('#cur_hh').val(), $('#cur_mn').val() );
  618. // Catch unexpected date problems.
  619. if ( attemptedDate.getFullYear() != aa || (1 + attemptedDate.getMonth()) != mm || attemptedDate.getDate() != jj || attemptedDate.getMinutes() != mn ) {
  620. $timestampdiv.find('.timestamp-wrap').addClass('form-invalid');
  621. return false;
  622. } else {
  623. $timestampdiv.find('.timestamp-wrap').removeClass('form-invalid');
  624. }
  625. // Determine what the publish should be depending on the date and post status.
  626. if ( attemptedDate > currentDate && $('#original_post_status').val() != 'future' ) {
  627. publishOn = postL10n.publishOnFuture;
  628. $('#publish').val( postL10n.schedule );
  629. } else if ( attemptedDate <= currentDate && $('#original_post_status').val() != 'publish' ) {
  630. publishOn = postL10n.publishOn;
  631. $('#publish').val( postL10n.publish );
  632. } else {
  633. publishOn = postL10n.publishOnPast;
  634. $('#publish').val( postL10n.update );
  635. }
  636. // If the date is the same, set it to trigger update events.
  637. if ( originalDate.toUTCString() == attemptedDate.toUTCString() ) {
  638. // Re-set to the current value.
  639. $('#timestamp').html(stamp);
  640. } else {
  641. $('#timestamp').html(
  642. '\n' + publishOn + ' <b>' +
  643. postL10n.dateFormat
  644. .replace( '%1$s', $( 'option[value="' + mm + '"]', '#mm' ).attr( 'data-text' ) )
  645. .replace( '%2$s', parseInt( jj, 10 ) )
  646. .replace( '%3$s', aa )
  647. .replace( '%4$s', ( '00' + hh ).slice( -2 ) )
  648. .replace( '%5$s', ( '00' + mn ).slice( -2 ) ) +
  649. '</b> '
  650. );
  651. }
  652. // Add "privately published" to post status when applies.
  653. if ( $postVisibilitySelect.find('input:radio:checked').val() == 'private' ) {
  654. $('#publish').val( postL10n.update );
  655. if ( 0 === optPublish.length ) {
  656. postStatus.append('<option value="publish">' + postL10n.privatelyPublished + '</option>');
  657. } else {
  658. optPublish.html( postL10n.privatelyPublished );
  659. }
  660. $('option[value="publish"]', postStatus).prop('selected', true);
  661. $('#misc-publishing-actions .edit-post-status').hide();
  662. } else {
  663. if ( $('#original_post_status').val() == 'future' || $('#original_post_status').val() == 'draft' ) {
  664. if ( optPublish.length ) {
  665. optPublish.remove();
  666. postStatus.val($('#hidden_post_status').val());
  667. }
  668. } else {
  669. optPublish.html( postL10n.published );
  670. }
  671. if ( postStatus.is(':hidden') )
  672. $('#misc-publishing-actions .edit-post-status').show();
  673. }
  674. // Update "Status:" to currently selected status.
  675. $('#post-status-display').html($('option:selected', postStatus).text());
  676. // Show or hide the "Save Draft" button.
  677. if ( $('option:selected', postStatus).val() == 'private' || $('option:selected', postStatus).val() == 'publish' ) {
  678. $('#save-post').hide();
  679. } else {
  680. $('#save-post').show();
  681. if ( $('option:selected', postStatus).val() == 'pending' ) {
  682. $('#save-post').show().val( postL10n.savePending );
  683. } else {
  684. $('#save-post').show().val( postL10n.saveDraft );
  685. }
  686. }
  687. return true;
  688. };
  689. // Show the visibility options and hide the toggle button when opened.
  690. $( '#visibility .edit-visibility').click( function( e ) {
  691. e.preventDefault();
  692. if ( $postVisibilitySelect.is(':hidden') ) {
  693. updateVisibility();
  694. $postVisibilitySelect.slideDown( 'fast', function() {
  695. $postVisibilitySelect.find( 'input[type="radio"]' ).first().focus();
  696. } );
  697. $(this).hide();
  698. }
  699. });
  700. // Cancel visibility selection area and hide it from view.
  701. $postVisibilitySelect.find('.cancel-post-visibility').click( function( event ) {
  702. $postVisibilitySelect.slideUp('fast');
  703. $('#visibility-radio-' + $('#hidden-post-visibility').val()).prop('checked', true);
  704. $('#post_password').val($('#hidden-post-password').val());
  705. $('#sticky').prop('checked', $('#hidden-post-sticky').prop('checked'));
  706. $('#post-visibility-display').html(visibility);
  707. $('#visibility .edit-visibility').show().focus();
  708. updateText();
  709. event.preventDefault();
  710. });
  711. // Set the selected visibility as current.
  712. $postVisibilitySelect.find('.save-post-visibility').click( function( event ) { // crazyhorse - multiple ok cancels
  713. $postVisibilitySelect.slideUp('fast');
  714. $('#visibility .edit-visibility').show().focus();
  715. updateText();
  716. if ( $postVisibilitySelect.find('input:radio:checked').val() != 'public' ) {
  717. $('#sticky').prop('checked', false);
  718. }
  719. if ( $('#sticky').prop('checked') ) {
  720. sticky = 'Sticky';
  721. } else {
  722. sticky = '';
  723. }
  724. $('#post-visibility-display').html( postL10n[ $postVisibilitySelect.find('input:radio:checked').val() + sticky ] );
  725. event.preventDefault();
  726. });
  727. // When the selection changes, update labels.
  728. $postVisibilitySelect.find('input:radio').change( function() {
  729. updateVisibility();
  730. });
  731. // Edit publish time click.
  732. $timestampdiv.siblings('a.edit-timestamp').click( function( event ) {
  733. if ( $timestampdiv.is( ':hidden' ) ) {
  734. $timestampdiv.slideDown( 'fast', function() {
  735. $( 'input, select', $timestampdiv.find( '.timestamp-wrap' ) ).first().focus();
  736. } );
  737. $(this).hide();
  738. }
  739. event.preventDefault();
  740. });
  741. // Cancel editing the publish time and hide the settings.
  742. $timestampdiv.find('.cancel-timestamp').click( function( event ) {
  743. $timestampdiv.slideUp('fast').siblings('a.edit-timestamp').show().focus();
  744. $('#mm').val($('#hidden_mm').val());
  745. $('#jj').val($('#hidden_jj').val());
  746. $('#aa').val($('#hidden_aa').val());
  747. $('#hh').val($('#hidden_hh').val());
  748. $('#mn').val($('#hidden_mn').val());
  749. updateText();
  750. event.preventDefault();
  751. });
  752. // Save the changed timestamp.
  753. $timestampdiv.find('.save-timestamp').click( function( event ) { // crazyhorse - multiple ok cancels
  754. if ( updateText() ) {
  755. $timestampdiv.slideUp('fast');
  756. $timestampdiv.siblings('a.edit-timestamp').show().focus();
  757. }
  758. event.preventDefault();
  759. });
  760. // Cancel submit when an invalid timestamp has been selected.
  761. $('#post').on( 'submit', function( event ) {
  762. if ( ! updateText() ) {
  763. event.preventDefault();
  764. $timestampdiv.show();
  765. if ( wp.autosave ) {
  766. wp.autosave.enableButtons();
  767. }
  768. $( '#publishing-action .spinner' ).removeClass( 'is-active' );
  769. }
  770. });
  771. // Post Status edit click.
  772. $postStatusSelect.siblings('a.edit-post-status').click( function( event ) {
  773. if ( $postStatusSelect.is( ':hidden' ) ) {
  774. $postStatusSelect.slideDown( 'fast', function() {
  775. $postStatusSelect.find('select').focus();
  776. } );
  777. $(this).hide();
  778. }
  779. event.preventDefault();
  780. });
  781. // Save the Post Status changes and hide the options.
  782. $postStatusSelect.find('.save-post-status').click( function( event ) {
  783. $postStatusSelect.slideUp( 'fast' ).siblings( 'a.edit-post-status' ).show().focus();
  784. updateText();
  785. event.preventDefault();
  786. });
  787. // Cancel Post Status editing and hide the options.
  788. $postStatusSelect.find('.cancel-post-status').click( function( event ) {
  789. $postStatusSelect.slideUp( 'fast' ).siblings( 'a.edit-post-status' ).show().focus();
  790. $('#post_status').val( $('#hidden_post_status').val() );
  791. updateText();
  792. event.preventDefault();
  793. });
  794. }
  795. /**
  796. * Handle the editing of the post_name. Create the required HTML elements and update the changes via AJAX.
  797. *
  798. * @summary Permalink aka slug aka post_name editing
  799. *
  800. * @global
  801. *
  802. * @returns void
  803. */
  804. function editPermalink() {
  805. var i, slug_value,
  806. $el, revert_e,
  807. c = 0,
  808. real_slug = $('#post_name'),
  809. revert_slug = real_slug.val(),
  810. permalink = $( '#sample-permalink' ),
  811. permalinkOrig = permalink.html(),
  812. permalinkInner = $( '#sample-permalink a' ).html(),
  813. buttons = $('#edit-slug-buttons'),
  814. buttonsOrig = buttons.html(),
  815. full = $('#editable-post-name-full');
  816. // Deal with Twemoji in the post-name.
  817. full.find( 'img' ).replaceWith( function() { return this.alt; } );
  818. full = full.html();
  819. permalink.html( permalinkInner );
  820. // Save current content to revert to when cancelling.
  821. $el = $( '#editable-post-name' );
  822. revert_e = $el.html();
  823. buttons.html( '<button type="button" class="save button button-small">' + postL10n.ok + '</button> <button type="button" class="cancel button-link">' + postL10n.cancel + '</button>' );
  824. // Save permalink changes.
  825. buttons.children( '.save' ).click( function() {
  826. var new_slug = $el.children( 'input' ).val();
  827. if ( new_slug == $('#editable-post-name-full').text() ) {
  828. buttons.children('.cancel').click();
  829. return;
  830. }
  831. $.post(
  832. ajaxurl,
  833. {
  834. action: 'sample-permalink',
  835. post_id: postId,
  836. new_slug: new_slug,
  837. new_title: $('#title').val(),
  838. samplepermalinknonce: $('#samplepermalinknonce').val()
  839. },
  840. function(data) {
  841. var box = $('#edit-slug-box');
  842. box.html(data);
  843. if (box.hasClass('hidden')) {
  844. box.fadeIn('fast', function () {
  845. box.removeClass('hidden');
  846. });
  847. }
  848. buttons.html(buttonsOrig);
  849. permalink.html(permalinkOrig);
  850. real_slug.val(new_slug);
  851. $( '.edit-slug' ).focus();
  852. wp.a11y.speak( postL10n.permalinkSaved );
  853. }
  854. );
  855. });
  856. // Cancel editing of permalink.
  857. buttons.children( '.cancel' ).click( function() {
  858. $('#view-post-btn').show();
  859. $el.html(revert_e);
  860. buttons.html(buttonsOrig);
  861. permalink.html(permalinkOrig);
  862. real_slug.val(revert_slug);
  863. $( '.edit-slug' ).focus();
  864. });
  865. // If more than 1/4th of 'full' is '%', make it empty.
  866. for ( i = 0; i < full.length; ++i ) {
  867. if ( '%' == full.charAt(i) )
  868. c++;
  869. }
  870. slug_value = ( c > full.length / 4 ) ? '' : full;
  871. $el.html( '<input type="text" id="new-post-slug" value="' + slug_value + '" autocomplete="off" />' ).children( 'input' ).keydown( function( e ) {
  872. var key = e.which;
  873. // On [enter], just save the new slug, don't save the post.
  874. if ( 13 === key ) {
  875. e.preventDefault();
  876. buttons.children( '.save' ).click();
  877. }
  878. // On [esc] cancel the editing.
  879. if ( 27 === key ) {
  880. buttons.children( '.cancel' ).click();
  881. }
  882. } ).keyup( function() {
  883. real_slug.val( this.value );
  884. }).focus();
  885. }
  886. $( '#titlediv' ).on( 'click', '.edit-slug', function() {
  887. editPermalink();
  888. });
  889. /**
  890. * Add screen reader text to the title prompt when needed.
  891. *
  892. * @summary Title screen reader text handler.
  893. *
  894. * @param {string} id Optional. HTML ID to add the screen reader helper text to.
  895. *
  896. * @global
  897. *
  898. * @returns void
  899. */
  900. wptitlehint = function(id) {
  901. id = id || 'title';
  902. var title = $('#' + id), titleprompt = $('#' + id + '-prompt-text');
  903. if ( '' === title.val() )
  904. titleprompt.removeClass('screen-reader-text');
  905. titleprompt.click(function(){
  906. $(this).addClass('screen-reader-text');
  907. title.focus();
  908. });
  909. title.blur(function(){
  910. if ( '' === this.value )
  911. titleprompt.removeClass('screen-reader-text');
  912. }).focus(function(){
  913. titleprompt.addClass('screen-reader-text');
  914. }).keydown(function(e){
  915. titleprompt.addClass('screen-reader-text');
  916. $(this).unbind(e);
  917. });
  918. };
  919. wptitlehint();
  920. // Resize the WYSIWYG and plain text editors.
  921. ( function() {
  922. var editor, offset, mce,
  923. $handle = $('#post-status-info'),
  924. $postdivrich = $('#postdivrich');
  925. // If there are no textareas or we are on a touch device, we can't do anything.
  926. if ( ! $textarea.length || 'ontouchstart' in window ) {
  927. // Hide the resize handle.
  928. $('#content-resize-handle').hide();
  929. return;
  930. }
  931. /**
  932. * Handle drag event.
  933. *
  934. * @param {Object} event Event containing details about the drag.
  935. */
  936. function dragging( event ) {
  937. if ( $postdivrich.hasClass( 'wp-editor-expand' ) ) {
  938. return;
  939. }
  940. if ( mce ) {
  941. editor.theme.resizeTo( null, offset + event.pageY );
  942. } else {
  943. $textarea.height( Math.max( 50, offset + event.pageY ) );
  944. }
  945. event.preventDefault();
  946. }
  947. /**
  948. * When the dragging stopped make sure we return focus and do a sanity check on the height.
  949. */
  950. function endDrag() {
  951. var height, toolbarHeight;
  952. if ( $postdivrich.hasClass( 'wp-editor-expand' ) ) {
  953. return;
  954. }
  955. if ( mce ) {
  956. editor.focus();
  957. toolbarHeight = parseInt( $( '#wp-content-editor-container .mce-toolbar-grp' ).height(), 10 );
  958. if ( toolbarHeight < 10 || toolbarHeight > 200 ) {
  959. toolbarHeight = 30;
  960. }
  961. height = parseInt( $('#content_ifr').css('height'), 10 ) + toolbarHeight - 28;
  962. } else {
  963. $textarea.focus();
  964. height = parseInt( $textarea.css('height'), 10 );
  965. }
  966. $document.off( '.wp-editor-resize' );
  967. // Sanity check: normalize height to stay within acceptable ranges.
  968. if ( height && height > 50 && height < 5000 ) {
  969. setUserSetting( 'ed_size', height );
  970. }
  971. }
  972. $handle.on( 'mousedown.wp-editor-resize', function( event ) {
  973. if ( typeof tinymce !== 'undefined' ) {
  974. editor = tinymce.get('content');
  975. }
  976. if ( editor && ! editor.isHidden() ) {
  977. mce = true;
  978. offset = $('#content_ifr').height() - event.pageY;
  979. } else {
  980. mce = false;
  981. offset = $textarea.height() - event.pageY;
  982. $textarea.blur();
  983. }
  984. $document.on( 'mousemove.wp-editor-resize', dragging )
  985. .on( 'mouseup.wp-editor-resize mouseleave.wp-editor-resize', endDrag );
  986. event.preventDefault();
  987. }).on( 'mouseup.wp-editor-resize', endDrag );
  988. })();
  989. // TinyMCE specific handling of Post Format changes to reflect in the editor.
  990. if ( typeof tinymce !== 'undefined' ) {
  991. // When changing post formats, change the editor body class.
  992. $( '#post-formats-select input.post-format' ).on( 'change.set-editor-class', function() {
  993. var editor, body, format = this.id;
  994. if ( format && $( this ).prop( 'checked' ) && ( editor = tinymce.get( 'content' ) ) ) {
  995. body = editor.getBody();
  996. body.className = body.className.replace( /\bpost-format-[^ ]+/, '' );
  997. editor.dom.addClass( body, format == 'post-format-0' ? 'post-format-standard' : format );
  998. $( document ).trigger( 'editor-classchange' );
  999. }
  1000. });
  1001. // When changing page template, change the editor body class
  1002. $( '#page_template' ).on( 'change.set-editor-class', function() {
  1003. var editor, body, pageTemplate = $( this ).val() || '';
  1004. pageTemplate = pageTemplate.substr( pageTemplate.lastIndexOf( '/' ) + 1, pageTemplate.length )
  1005. .replace( /\.php$/, '' )
  1006. .replace( /\./g, '-' );
  1007. if ( pageTemplate && ( editor = tinymce.get( 'content' ) ) ) {
  1008. body = editor.getBody();
  1009. body.className = body.className.replace( /\bpage-template-[^ ]+/, '' );
  1010. editor.dom.addClass( body, 'page-template-' + pageTemplate );
  1011. $( document ).trigger( 'editor-classchange' );
  1012. }
  1013. });
  1014. }
  1015. // Save on pressing [ctrl]/[command] + [s] in the Text editor.
  1016. $textarea.on( 'keydown.wp-autosave', function( event ) {
  1017. // Key [s] has code 83.
  1018. if ( event.which === 83 ) {
  1019. if ( event.shiftKey || event.altKey || ( isMac && ( ! event.metaKey || event.ctrlKey ) ) || ( ! isMac && ! event.ctrlKey ) ) {
  1020. return;
  1021. }
  1022. wp.autosave && wp.autosave.server.triggerSave();
  1023. event.preventDefault();
  1024. }
  1025. });
  1026. // If the last status was auto-draft and the save is triggered, edit the current URL.
  1027. if ( $( '#original_post_status' ).val() === 'auto-draft' && window.history.replaceState ) {
  1028. var location;
  1029. $( '#publish' ).on( 'click', function() {
  1030. location = window.location.href;
  1031. location += ( location.indexOf( '?' ) !== -1 ) ? '&' : '?';
  1032. location += 'wp-post-new-reload=true';
  1033. window.history.replaceState( null, null, location );
  1034. });
  1035. }
  1036. });
  1037. /**
  1038. * TinyMCE word count display
  1039. */
  1040. ( function( $, counter ) {
  1041. $( function() {
  1042. var $content = $( '#content' ),
  1043. $count = $( '#wp-word-count' ).find( '.word-count' ),
  1044. prevCount = 0,
  1045. contentEditor;
  1046. /**
  1047. * Get the word count from TinyMCE and display it
  1048. */
  1049. function update() {
  1050. var text, count;
  1051. if ( ! contentEditor || contentEditor.isHidden() ) {
  1052. text = $content.val();
  1053. } else {
  1054. text = contentEditor.getContent( { format: 'raw' } );
  1055. }
  1056. count = counter.count( text );
  1057. if ( count !== prevCount ) {
  1058. $count.text( count );
  1059. }
  1060. prevCount = count;
  1061. }
  1062. /**
  1063. * Bind the word count update triggers.
  1064. *
  1065. * When a node change in the main TinyMCE editor has been triggered.
  1066. * When a key has been released in the plain text content editor.
  1067. */
  1068. $( document ).on( 'tinymce-editor-init', function( event, editor ) {
  1069. if ( editor.id !== 'content' ) {
  1070. return;
  1071. }
  1072. contentEditor = editor;
  1073. editor.on( 'nodechange keyup', _.debounce( update, 1000 ) );
  1074. } );
  1075. $content.on( 'input keyup', _.debounce( update, 1000 ) );
  1076. update();
  1077. } );
  1078. } )( jQuery, new wp.utils.WordCounter() );