press-this.js 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899
  1. /**
  2. * PressThis App
  3. *
  4. */
  5. ( function( $, window ) {
  6. var PressThis = function() {
  7. var editor, $mediaList, $mediaThumbWrap,
  8. $window = $( window ),
  9. $document = $( document ),
  10. saveAlert = false,
  11. sidebarIsOpen = false,
  12. settings = window.wpPressThisConfig || {},
  13. data = window.wpPressThisData || {},
  14. smallestWidth = 128,
  15. hasSetFocus = false,
  16. catsCache = [],
  17. isOffScreen = 'is-off-screen',
  18. isHidden = 'is-hidden',
  19. offscreenHidden = isOffScreen + ' ' + isHidden,
  20. iOS = /iPad|iPod|iPhone/.test( window.navigator.userAgent ),
  21. $textEditor = $( '#pressthis' ),
  22. textEditor = $textEditor[0],
  23. textEditorMinHeight = 600,
  24. textLength = 0,
  25. transitionEndEvent = ( function() {
  26. var style = document.documentElement.style;
  27. if ( typeof style.transition !== 'undefined' ) {
  28. return 'transitionend';
  29. }
  30. if ( typeof style.WebkitTransition !== 'undefined' ) {
  31. return 'webkitTransitionEnd';
  32. }
  33. return false;
  34. }() );
  35. /* ***************************************************************
  36. * HELPER FUNCTIONS
  37. *************************************************************** */
  38. /**
  39. * Emulates our PHP __() gettext function, powered by the strings exported in pressThisL10n.
  40. *
  41. * @param key string Key of the string to be translated, as found in pressThisL10n.
  42. * @returns string Original or translated string, or empty string if no key.
  43. */
  44. function __( key ) {
  45. if ( key && window.pressThisL10n ) {
  46. return window.pressThisL10n[key] || key;
  47. }
  48. return key || '';
  49. }
  50. /**
  51. * Allow only HTTP or protocol relative URLs.
  52. *
  53. * @param url string The URL.
  54. * @returns string Processed URL.
  55. */
  56. function checkUrl( url ) {
  57. url = $.trim( url || '' );
  58. if ( /^(?:https?:)?\/\//.test( url ) ) {
  59. url = wp.sanitize.stripTags( url );
  60. return url.replace( /["\\]+/g, '' );
  61. }
  62. return '';
  63. }
  64. /**
  65. * Show UX spinner
  66. */
  67. function showSpinner() {
  68. $( '.spinner' ).addClass( 'is-active' );
  69. $( '.post-actions button' ).attr( 'disabled', 'disabled' );
  70. }
  71. /**
  72. * Hide UX spinner
  73. */
  74. function hideSpinner() {
  75. $( '.spinner' ).removeClass( 'is-active' );
  76. $( '.post-actions button' ).removeAttr( 'disabled' );
  77. }
  78. function textEditorResize( reset ) {
  79. var pageYOffset, height;
  80. if ( editor && ! editor.isHidden() ) {
  81. return;
  82. }
  83. reset = ( reset === 'reset' ) || ( textLength && textLength > textEditor.value.length );
  84. height = textEditor.style.height;
  85. if ( reset ) {
  86. pageYOffset = window.pageYOffset;
  87. textEditor.style.height = 'auto';
  88. textEditor.style.height = Math.max( textEditor.scrollHeight, textEditorMinHeight ) + 'px';
  89. window.scrollTo( window.pageXOffset, pageYOffset );
  90. } else if ( parseInt( textEditor.style.height, 10 ) < textEditor.scrollHeight ) {
  91. textEditor.style.height = textEditor.scrollHeight + 'px';
  92. }
  93. textLength = textEditor.value.length;
  94. }
  95. function mceGetCursorOffset() {
  96. if ( ! editor ) {
  97. return false;
  98. }
  99. var node = editor.selection.getNode(),
  100. range, view, offset;
  101. if ( editor.wp && editor.wp.getView && ( view = editor.wp.getView( node ) ) ) {
  102. offset = view.getBoundingClientRect();
  103. } else {
  104. range = editor.selection.getRng();
  105. try {
  106. offset = range.getClientRects()[0];
  107. } catch( er ) {}
  108. if ( ! offset ) {
  109. offset = node.getBoundingClientRect();
  110. }
  111. }
  112. return offset.height ? offset : false;
  113. }
  114. // Make sure the caret is always visible.
  115. function mceKeyup( event ) {
  116. var VK = window.tinymce.util.VK,
  117. key = event.keyCode;
  118. // Bail on special keys.
  119. if ( key <= 47 && ! ( key === VK.SPACEBAR || key === VK.ENTER || key === VK.DELETE || key === VK.BACKSPACE || key === VK.UP || key === VK.LEFT || key === VK.DOWN || key === VK.UP ) ) {
  120. return;
  121. // OS keys, function keys, num lock, scroll lock
  122. } else if ( ( key >= 91 && key <= 93 ) || ( key >= 112 && key <= 123 ) || key === 144 || key === 145 ) {
  123. return;
  124. }
  125. mceScroll( key );
  126. }
  127. function mceScroll( key ) {
  128. var cursorTop, cursorBottom, editorBottom,
  129. offset = mceGetCursorOffset(),
  130. bufferTop = 50,
  131. bufferBottom = 65,
  132. VK = window.tinymce.util.VK;
  133. if ( ! offset ) {
  134. return;
  135. }
  136. cursorTop = offset.top + editor.iframeElement.getBoundingClientRect().top;
  137. cursorBottom = cursorTop + offset.height;
  138. cursorTop = cursorTop - bufferTop;
  139. cursorBottom = cursorBottom + bufferBottom;
  140. editorBottom = $window.height();
  141. // Don't scroll if the node is taller than the visible part of the editor
  142. if ( editorBottom < offset.height ) {
  143. return;
  144. }
  145. if ( cursorTop < 0 && ( key === VK.UP || key === VK.LEFT || key === VK.BACKSPACE ) ) {
  146. window.scrollTo( window.pageXOffset, cursorTop + window.pageYOffset );
  147. } else if ( cursorBottom > editorBottom ) {
  148. window.scrollTo( window.pageXOffset, cursorBottom + window.pageYOffset - editorBottom );
  149. }
  150. }
  151. /**
  152. * Replace emoji images with chars and sanitize the text content.
  153. */
  154. function getTitleText() {
  155. var $element = $( '#title-container' );
  156. $element.find( 'img.emoji' ).each( function() {
  157. var $image = $( this );
  158. $image.replaceWith( $( '<span>' ).text( $image.attr( 'alt' ) ) );
  159. });
  160. return wp.sanitize.stripTagsAndEncodeText( $element.text() );
  161. }
  162. /**
  163. * Prepare the form data for saving.
  164. */
  165. function prepareFormData() {
  166. var $form = $( '#pressthis-form' ),
  167. $input = $( '<input type="hidden" name="post_category[]" value="">' );
  168. editor && editor.save();
  169. $( '#post_title' ).val( getTitleText() );
  170. // Make sure to flush out the tags with tagBox before saving
  171. if ( window.tagBox ) {
  172. $( 'div.tagsdiv' ).each( function() {
  173. window.tagBox.flushTags( this, false, 1 );
  174. } );
  175. }
  176. // Get selected categories
  177. $( '.categories-select .category' ).each( function( i, element ) {
  178. var $cat = $( element );
  179. if ( $cat.hasClass( 'selected' ) ) {
  180. // Have to append a node as we submit the actual form on preview
  181. $form.append( $input.clone().val( $cat.attr( 'data-term-id' ) || '' ) );
  182. }
  183. });
  184. }
  185. /**
  186. * Submit the post form via AJAX, and redirect to the proper screen if published vs saved as a draft.
  187. *
  188. * @param action string publish|draft
  189. */
  190. function submitPost( action ) {
  191. var data;
  192. saveAlert = false;
  193. showSpinner();
  194. if ( 'publish' === action ) {
  195. $( '#post_status' ).val( 'publish' );
  196. }
  197. prepareFormData();
  198. data = $( '#pressthis-form' ).serialize();
  199. $.ajax( {
  200. type: 'post',
  201. url: window.ajaxurl,
  202. data: data
  203. }).always( function() {
  204. hideSpinner();
  205. clearNotices();
  206. $( '.publish-button' ).removeClass( 'is-saving' );
  207. }).done( function( response ) {
  208. if ( ! response.success ) {
  209. renderError( response.data.errorMessage );
  210. } else if ( response.data.redirect ) {
  211. if ( window.opener && ( settings.redirInParent || response.data.force ) ) {
  212. try {
  213. window.opener.location.href = response.data.redirect;
  214. window.setTimeout( function() {
  215. window.self.close();
  216. }, 200 );
  217. } catch( er ) {
  218. window.location.href = response.data.redirect;
  219. }
  220. } else {
  221. window.location.href = response.data.redirect;
  222. }
  223. }
  224. }).fail( function() {
  225. renderError( __( 'serverError' ) );
  226. });
  227. }
  228. /**
  229. * Inserts the media a user has selected from the presented list inside the editor, as an image or embed, based on type
  230. *
  231. * @param type string img|embed
  232. * @param src string Source URL
  233. * @param link string Optional destination link, for images (defaults to src)
  234. */
  235. function insertSelectedMedia( $element ) {
  236. var src, link, newContent = '';
  237. src = checkUrl( $element.attr( 'data-wp-src' ) || '' );
  238. link = checkUrl( data.u );
  239. if ( $element.hasClass( 'is-image' ) ) {
  240. if ( ! link ) {
  241. link = src;
  242. }
  243. newContent = '<a href="' + link + '"><img class="alignnone size-full" src="' + src + '" alt="" /></a>';
  244. } else {
  245. newContent = '[embed]' + src + '[/embed]';
  246. }
  247. if ( editor && ! editor.isHidden() ) {
  248. if ( ! hasSetFocus ) {
  249. editor.setContent( '<p>' + newContent + '</p>' + editor.getContent() );
  250. } else {
  251. editor.execCommand( 'mceInsertContent', false, newContent );
  252. }
  253. } else if ( window.QTags ) {
  254. window.QTags.insertContent( newContent );
  255. }
  256. }
  257. /**
  258. * Save a new user-generated category via AJAX
  259. */
  260. function saveNewCategory() {
  261. var data,
  262. name = $( '#new-category' ).val();
  263. if ( ! name ) {
  264. return;
  265. }
  266. data = {
  267. action: 'press-this-add-category',
  268. post_id: $( '#post_ID' ).val() || 0,
  269. name: name,
  270. new_cat_nonce: $( '#_ajax_nonce-add-category' ).val() || '',
  271. parent: $( '#new-category-parent' ).val() || 0
  272. };
  273. $.post( window.ajaxurl, data, function( response ) {
  274. if ( ! response.success ) {
  275. renderError( response.data.errorMessage );
  276. } else {
  277. var $parent, $ul,
  278. $wrap = $( 'ul.categories-select' );
  279. $.each( response.data, function( i, newCat ) {
  280. var $node = $( '<li>' ).append( $( '<div class="category selected" tabindex="0" role="checkbox" aria-checked="true">' )
  281. .attr( 'data-term-id', newCat.term_id )
  282. .text( newCat.name ) );
  283. if ( newCat.parent ) {
  284. if ( ! $ul || ! $ul.length ) {
  285. $parent = $wrap.find( 'div[data-term-id="' + newCat.parent + '"]' ).parent();
  286. $ul = $parent.find( 'ul.children:first' );
  287. if ( ! $ul.length ) {
  288. $ul = $( '<ul class="children">' ).appendTo( $parent );
  289. }
  290. }
  291. $ul.prepend( $node );
  292. } else {
  293. $wrap.prepend( $node );
  294. }
  295. $node.focus();
  296. } );
  297. refreshCatsCache();
  298. }
  299. } );
  300. }
  301. /* ***************************************************************
  302. * RENDERING FUNCTIONS
  303. *************************************************************** */
  304. /**
  305. * Hide the form letting users enter a URL to be scanned, if a URL was already passed.
  306. */
  307. function renderToolsVisibility() {
  308. if ( data.hasData ) {
  309. $( '#scanbar' ).hide();
  310. }
  311. }
  312. /**
  313. * Render error notice
  314. *
  315. * @param msg string Notice/error message
  316. * @param error string error|notice CSS class for display
  317. */
  318. function renderNotice( msg, error ) {
  319. var $alerts = $( '.editor-wrapper div.alerts' ),
  320. className = error ? 'is-error' : 'is-notice';
  321. $alerts.append( $( '<p class="alert ' + className + '">' ).text( msg ) );
  322. }
  323. /**
  324. * Render error notice
  325. *
  326. * @param msg string Error message
  327. */
  328. function renderError( msg ) {
  329. renderNotice( msg, true );
  330. }
  331. function clearNotices() {
  332. $( 'div.alerts' ).empty();
  333. }
  334. /**
  335. * Render notices on page load, if any already
  336. */
  337. function renderStartupNotices() {
  338. // Render errors sent in the data, if any
  339. if ( data.errors ) {
  340. $.each( data.errors, function( i, msg ) {
  341. renderError( msg );
  342. } );
  343. }
  344. }
  345. /**
  346. * Add an image to the list of found images.
  347. */
  348. function addImg( src, displaySrc, i ) {
  349. var $element = $mediaThumbWrap.clone().addClass( 'is-image' );
  350. $element.attr( 'data-wp-src', src ).css( 'background-image', 'url(' + displaySrc + ')' )
  351. .find( 'span' ).text( __( 'suggestedImgAlt' ).replace( '%d', i + 1 ) );
  352. $mediaList.append( $element );
  353. }
  354. /**
  355. * Render the detected images and embed for selection, if any
  356. */
  357. function renderDetectedMedia() {
  358. var found = 0;
  359. $mediaList = $( 'ul.media-list' );
  360. $mediaThumbWrap = $( '<li class="suggested-media-thumbnail" tabindex="0"><span class="screen-reader-text"></span></li>' );
  361. if ( data._embeds ) {
  362. $.each( data._embeds, function ( i, src ) {
  363. var displaySrc = '',
  364. cssClass = '',
  365. $element = $mediaThumbWrap.clone().addClass( 'is-embed' );
  366. src = checkUrl( src );
  367. if ( src.indexOf( 'youtube.com/' ) > -1 ) {
  368. displaySrc = 'https://i.ytimg.com/vi/' + src.replace( /.+v=([^&]+).*/, '$1' ) + '/hqdefault.jpg';
  369. cssClass += ' is-video';
  370. } else if ( src.indexOf( 'youtu.be/' ) > -1 ) {
  371. displaySrc = 'https://i.ytimg.com/vi/' + src.replace( /\/([^\/])$/, '$1' ) + '/hqdefault.jpg';
  372. cssClass += ' is-video';
  373. } else if ( src.indexOf( 'dailymotion.com' ) > -1 ) {
  374. displaySrc = src.replace( '/video/', '/thumbnail/video/' );
  375. cssClass += ' is-video';
  376. } else if ( src.indexOf( 'soundcloud.com' ) > -1 ) {
  377. cssClass += ' is-audio';
  378. } else if ( src.indexOf( 'twitter.com' ) > -1 ) {
  379. cssClass += ' is-tweet';
  380. } else {
  381. cssClass += ' is-video';
  382. }
  383. $element.attr( 'data-wp-src', src ).find( 'span' ).text( __( 'suggestedEmbedAlt' ).replace( '%d', i + 1 ) );
  384. if ( displaySrc ) {
  385. $element.css( 'background-image', 'url(' + displaySrc + ')' );
  386. }
  387. $mediaList.append( $element );
  388. found++;
  389. } );
  390. }
  391. if ( data._images ) {
  392. $.each( data._images, function( i, src ) {
  393. var displaySrc, img = new Image();
  394. src = checkUrl( src );
  395. displaySrc = src.replace( /^(http[^\?]+)(\?.*)?$/, '$1' );
  396. if ( src.indexOf( 'files.wordpress.com/' ) > -1 ) {
  397. displaySrc = displaySrc.replace( /\?.*$/, '' ) + '?w=' + smallestWidth;
  398. } else if ( src.indexOf( 'gravatar.com/' ) > -1 ) {
  399. displaySrc = displaySrc.replace( /\?.*$/, '' ) + '?s=' + smallestWidth;
  400. } else {
  401. displaySrc = src;
  402. }
  403. img.onload = function() {
  404. if ( ( img.width && img.width < 256 ) ||
  405. ( img.height && img.height < 128 ) ) {
  406. return;
  407. }
  408. addImg( src, displaySrc, i );
  409. };
  410. img.src = src;
  411. found++;
  412. } );
  413. }
  414. if ( found ) {
  415. $( '.media-list-container' ).addClass( 'has-media' );
  416. }
  417. }
  418. /* ***************************************************************
  419. * MONITORING FUNCTIONS
  420. *************************************************************** */
  421. /**
  422. * Interactive navigation behavior for the options modal (post format, tags, categories)
  423. */
  424. function monitorOptionsModal() {
  425. var $postOptions = $( '.post-options' ),
  426. $postOption = $( '.post-option' ),
  427. $settingModal = $( '.setting-modal' ),
  428. $modalClose = $( '.modal-close' );
  429. $postOption.on( 'click', function() {
  430. var index = $( this ).index(),
  431. $targetSettingModal = $settingModal.eq( index );
  432. $postOptions.addClass( isOffScreen )
  433. .one( transitionEndEvent, function() {
  434. $( this ).addClass( isHidden );
  435. } );
  436. $targetSettingModal.removeClass( offscreenHidden )
  437. .one( transitionEndEvent, function() {
  438. $( this ).find( '.modal-close' ).focus();
  439. } );
  440. } );
  441. $modalClose.on( 'click', function() {
  442. var $targetSettingModal = $( this ).parent(),
  443. index = $targetSettingModal.index();
  444. $postOptions.removeClass( offscreenHidden );
  445. $targetSettingModal.addClass( isOffScreen );
  446. if ( transitionEndEvent ) {
  447. $targetSettingModal.one( transitionEndEvent, function() {
  448. $( this ).addClass( isHidden );
  449. $postOption.eq( index - 1 ).focus();
  450. } );
  451. } else {
  452. setTimeout( function() {
  453. $targetSettingModal.addClass( isHidden );
  454. $postOption.eq( index - 1 ).focus();
  455. }, 350 );
  456. }
  457. } );
  458. }
  459. /**
  460. * Interactive behavior for the sidebar toggle, to show the options modals
  461. */
  462. function openSidebar() {
  463. sidebarIsOpen = true;
  464. $( '.options' ).removeClass( 'closed' ).addClass( 'open' );
  465. $( '.press-this-actions, #scanbar' ).addClass( isHidden );
  466. $( '.options-panel-back' ).removeClass( isHidden );
  467. $( '.options-panel' ).removeClass( offscreenHidden )
  468. .one( transitionEndEvent, function() {
  469. $( '.post-option:first' ).focus();
  470. } );
  471. }
  472. function closeSidebar() {
  473. sidebarIsOpen = false;
  474. $( '.options' ).removeClass( 'open' ).addClass( 'closed' );
  475. $( '.options-panel-back' ).addClass( isHidden );
  476. $( '.press-this-actions, #scanbar' ).removeClass( isHidden );
  477. $( '.options-panel' ).addClass( isOffScreen )
  478. .one( transitionEndEvent, function() {
  479. $( this ).addClass( isHidden );
  480. // Reset to options list
  481. $( '.post-options' ).removeClass( offscreenHidden );
  482. $( '.setting-modal').addClass( offscreenHidden );
  483. });
  484. }
  485. /**
  486. * Interactive behavior for the post title's field placeholder
  487. */
  488. function monitorPlaceholder() {
  489. var $titleField = $( '#title-container' ),
  490. $placeholder = $( '.post-title-placeholder' );
  491. $titleField.on( 'focus', function() {
  492. $placeholder.addClass( 'is-hidden' );
  493. }).on( 'blur', function() {
  494. if ( ! $titleField.text() && ! $titleField.html() ) {
  495. $placeholder.removeClass( 'is-hidden' );
  496. }
  497. }).on( 'keyup', function() {
  498. saveAlert = true;
  499. }).on( 'paste', function( event ) {
  500. var text, range,
  501. clipboard = event.originalEvent.clipboardData || window.clipboardData;
  502. if ( clipboard ) {
  503. try{
  504. text = clipboard.getData( 'Text' ) || clipboard.getData( 'text/plain' );
  505. if ( text ) {
  506. text = $.trim( text.replace( /\s+/g, ' ' ) );
  507. if ( window.getSelection ) {
  508. range = window.getSelection().getRangeAt(0);
  509. if ( range ) {
  510. if ( ! range.collapsed ) {
  511. range.deleteContents();
  512. }
  513. range.insertNode( document.createTextNode( text ) );
  514. }
  515. } else if ( document.selection ) {
  516. range = document.selection.createRange();
  517. if ( range ) {
  518. range.text = text;
  519. }
  520. }
  521. }
  522. } catch ( er ) {}
  523. event.preventDefault();
  524. }
  525. saveAlert = true;
  526. setTimeout( function() {
  527. $titleField.text( getTitleText() );
  528. }, 50 );
  529. });
  530. if ( $titleField.text() || $titleField.html() ) {
  531. $placeholder.addClass('is-hidden');
  532. }
  533. }
  534. function toggleCatItem( $element ) {
  535. if ( $element.hasClass( 'selected' ) ) {
  536. $element.removeClass( 'selected' ).attr( 'aria-checked', 'false' );
  537. } else {
  538. $element.addClass( 'selected' ).attr( 'aria-checked', 'true' );
  539. }
  540. }
  541. function monitorCatList() {
  542. $( '.categories-select' ).on( 'click.press-this keydown.press-this', function( event ) {
  543. var $element = $( event.target );
  544. if ( $element.is( 'div.category' ) ) {
  545. if ( event.type === 'keydown' && event.keyCode !== 32 ) {
  546. return;
  547. }
  548. toggleCatItem( $element );
  549. event.preventDefault();
  550. }
  551. });
  552. }
  553. function splitButtonClose() {
  554. $( '.split-button' ).removeClass( 'is-open' );
  555. $( '.split-button-toggle' ).attr( 'aria-expanded', 'false' );
  556. }
  557. /* ***************************************************************
  558. * PROCESSING FUNCTIONS
  559. *************************************************************** */
  560. /**
  561. * Calls all the rendring related functions to happen on page load
  562. */
  563. function render(){
  564. // We're on!
  565. renderToolsVisibility();
  566. renderDetectedMedia();
  567. renderStartupNotices();
  568. if ( window.tagBox ) {
  569. window.tagBox.init();
  570. }
  571. // iOS doesn't fire click events on "standard" elements without this...
  572. if ( iOS ) {
  573. $( document.body ).css( 'cursor', 'pointer' );
  574. }
  575. }
  576. /**
  577. * Set app events and other state monitoring related code.
  578. */
  579. function monitor() {
  580. var $splitButton = $( '.split-button' );
  581. $document.on( 'tinymce-editor-init', function( event, ed ) {
  582. editor = ed;
  583. editor.on( 'nodechange', function() {
  584. hasSetFocus = true;
  585. });
  586. editor.on( 'focus', function() {
  587. splitButtonClose();
  588. });
  589. editor.on( 'show', function() {
  590. setTimeout( function() {
  591. editor.execCommand( 'wpAutoResize' );
  592. }, 300 );
  593. });
  594. editor.on( 'hide', function() {
  595. setTimeout( function() {
  596. textEditorResize( 'reset' );
  597. }, 100 );
  598. });
  599. editor.on( 'keyup', mceKeyup );
  600. editor.on( 'undo redo', mceScroll );
  601. }).on( 'click.press-this keypress.press-this', '.suggested-media-thumbnail', function( event ) {
  602. if ( event.type === 'click' || event.keyCode === 13 ) {
  603. insertSelectedMedia( $( this ) );
  604. }
  605. }).on( 'click.press-this', function( event ) {
  606. if ( ! $( event.target ).closest( 'button' ).hasClass( 'split-button-toggle' ) ) {
  607. splitButtonClose();
  608. }
  609. });
  610. // Publish, Draft and Preview buttons
  611. $( '.post-actions' ).on( 'click.press-this', function( event ) {
  612. var location,
  613. $target = $( event.target ),
  614. $button = $target.closest( 'button' );
  615. if ( $button.length ) {
  616. if ( $button.hasClass( 'draft-button' ) ) {
  617. $( '.publish-button' ).addClass( 'is-saving' );
  618. submitPost( 'draft' );
  619. } else if ( $button.hasClass( 'publish-button' ) ) {
  620. $button.addClass( 'is-saving' );
  621. if ( window.history.replaceState ) {
  622. location = window.location.href;
  623. location += ( location.indexOf( '?' ) !== -1 ) ? '&' : '?';
  624. location += 'wp-press-this-reload=true';
  625. window.history.replaceState( null, null, location );
  626. }
  627. submitPost( 'publish' );
  628. } else if ( $button.hasClass( 'preview-button' ) ) {
  629. prepareFormData();
  630. window.opener && window.opener.focus();
  631. $( '#wp-preview' ).val( 'dopreview' );
  632. $( '#pressthis-form' ).attr( 'target', '_blank' ).submit().attr( 'target', '' );
  633. $( '#wp-preview' ).val( '' );
  634. } else if ( $button.hasClass( 'standard-editor-button' ) ) {
  635. $( '.publish-button' ).addClass( 'is-saving' );
  636. $( '#pt-force-redirect' ).val( 'true' );
  637. submitPost( 'draft' );
  638. } else if ( $button.hasClass( 'split-button-toggle' ) ) {
  639. if ( $splitButton.hasClass( 'is-open' ) ) {
  640. $splitButton.removeClass( 'is-open' );
  641. $button.attr( 'aria-expanded', 'false' );
  642. } else {
  643. $splitButton.addClass( 'is-open' );
  644. $button.attr( 'aria-expanded', 'true' );
  645. }
  646. }
  647. }
  648. });
  649. monitorOptionsModal();
  650. monitorPlaceholder();
  651. monitorCatList();
  652. $( '.options' ).on( 'click.press-this', function() {
  653. if ( $( this ).hasClass( 'open' ) ) {
  654. closeSidebar();
  655. } else {
  656. openSidebar();
  657. }
  658. });
  659. // Close the sidebar when focus moves outside of it.
  660. $( '.options-panel, .options-panel-back' ).on( 'focusout.press-this', function() {
  661. setTimeout( function() {
  662. var node = document.activeElement,
  663. $node = $( node );
  664. if ( sidebarIsOpen && node && ! $node.hasClass( 'options-panel-back' ) &&
  665. ( node.nodeName === 'BODY' ||
  666. ( ! $node.closest( '.options-panel' ).length &&
  667. ! $node.closest( '.options' ).length ) ) ) {
  668. closeSidebar();
  669. }
  670. }, 50 );
  671. });
  672. $( '#post-formats-select input' ).on( 'change', function() {
  673. var $this = $( this );
  674. if ( $this.is( ':checked' ) ) {
  675. $( '#post-option-post-format' ).text( $( 'label[for="' + $this.attr( 'id' ) + '"]' ).text() || '' );
  676. }
  677. } );
  678. $window.on( 'beforeunload.press-this', function() {
  679. if ( saveAlert || ( editor && editor.isDirty() ) ) {
  680. return __( 'saveAlert' );
  681. }
  682. } ).on( 'resize.press-this', function() {
  683. if ( ! editor || editor.isHidden() ) {
  684. textEditorResize( 'reset' );
  685. }
  686. });
  687. $( 'button.add-cat-toggle' ).on( 'click.press-this', function() {
  688. var $this = $( this );
  689. $this.toggleClass( 'is-toggled' );
  690. $this.attr( 'aria-expanded', 'false' === $this.attr( 'aria-expanded' ) ? 'true' : 'false' );
  691. $( '.setting-modal .add-category, .categories-search-wrapper' ).toggleClass( 'is-hidden' );
  692. } );
  693. $( 'button.add-cat-submit' ).on( 'click.press-this', saveNewCategory );
  694. $( '.categories-search' ).on( 'keyup.press-this', function() {
  695. var search = $( this ).val().toLowerCase() || '';
  696. // Don't search when less thasn 3 extended ASCII chars
  697. if ( /[\x20-\xFF]+/.test( search ) && search.length < 2 ) {
  698. return;
  699. }
  700. $.each( catsCache, function( i, cat ) {
  701. cat.node.removeClass( 'is-hidden searched-parent' );
  702. } );
  703. if ( search ) {
  704. $.each( catsCache, function( i, cat ) {
  705. if ( cat.text.indexOf( search ) === -1 ) {
  706. cat.node.addClass( 'is-hidden' );
  707. } else {
  708. cat.parents.addClass( 'searched-parent' );
  709. }
  710. } );
  711. }
  712. } );
  713. $textEditor.on( 'focus.press-this input.press-this propertychange.press-this', textEditorResize );
  714. return true;
  715. }
  716. function refreshCatsCache() {
  717. $( '.categories-select' ).find( 'li' ).each( function() {
  718. var $this = $( this );
  719. catsCache.push( {
  720. node: $this,
  721. parents: $this.parents( 'li' ),
  722. text: $this.children( '.category' ).text().toLowerCase()
  723. } );
  724. } );
  725. }
  726. // Let's go!
  727. $document.ready( function() {
  728. render();
  729. monitor();
  730. refreshCatsCache();
  731. });
  732. // Expose public methods?
  733. return {
  734. renderNotice: renderNotice,
  735. renderError: renderError
  736. };
  737. };
  738. window.wp = window.wp || {};
  739. window.wp.pressThis = new PressThis();
  740. }( jQuery, window ));