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