]> scripts.mit.edu Git - autoinstalls/wordpress.git/blob - wp-admin/js/press-this.js
WordPress 4.2.5-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                         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 ));