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