--- /dev/null
+/**
+ * PressThis App
+ *
+ */
+( function( $, window ) {
+ var PressThis = function() {
+ var editor, $mediaList, $mediaThumbWrap,
+ saveAlert = false,
+ editLinkVisible = false,
+ textarea = document.createElement( 'textarea' ),
+ sidebarIsOpen = false,
+ settings = window.wpPressThisConfig || {},
+ data = window.wpPressThisData || {},
+ smallestWidth = 128,
+ hasSetFocus = false,
+ catsCache = [],
+ isOffScreen = 'is-off-screen',
+ isHidden = 'is-hidden',
+ offscreenHidden = isOffScreen + ' ' + isHidden,
+ transitionEndEvent = ( function() {
+ var style = document.documentElement.style;
+
+ if ( typeof style.transition !== 'undefined' ) {
+ return 'transitionend';
+ }
+
+ if ( typeof style.WebkitTransition !== 'undefined' ) {
+ return 'webkitTransitionEnd';
+ }
+
+ return false;
+ }() );
+
+ /* ***************************************************************
+ * HELPER FUNCTIONS
+ *************************************************************** */
+
+ /**
+ * Emulates our PHP __() gettext function, powered by the strings exported in pressThisL10n.
+ *
+ * @param key string Key of the string to be translated, as found in pressThisL10n.
+ * @returns string Original or translated string, or empty string if no key.
+ */
+ function __( key ) {
+ if ( key && window.pressThisL10n ) {
+ return window.pressThisL10n[key] || key;
+ }
+
+ return key || '';
+ }
+
+ /**
+ * Strips HTML tags
+ *
+ * @param string string Text to have the HTML tags striped out of.
+ * @returns string Stripped text.
+ */
+ function stripTags( string ) {
+ string = string || '';
+
+ return string
+ .replace( /<!--[\s\S]*?(-->|$)/g, '' )
+ .replace( /<(script|style)[^>]*>[\s\S]*?(<\/\1>|$)/ig, '' )
+ .replace( /<\/?[a-z][\s\S]*?(>|$)/ig, '' );
+ }
+
+ /**
+ * Strip HTML tags and convert HTML entities.
+ *
+ * @param text string Text.
+ * @returns string Sanitized text.
+ */
+ function sanitizeText( text ) {
+ var _text = stripTags( text );
+
+ try {
+ textarea.innerHTML = _text;
+ _text = stripTags( textarea.value );
+ } catch ( er ) {}
+
+ return _text;
+ }
+
+ /**
+ * Allow only HTTP or protocol relative URLs.
+ *
+ * @param url string The URL.
+ * @returns string Processed URL.
+ */
+ function checkUrl( url ) {
+ url = $.trim( url || '' );
+
+ if ( /^(?:https?:)?\/\//.test( url ) ) {
+ url = stripTags( url );
+ return url.replace( /["\\]+/g, '' );
+ }
+
+ return '';
+ }
+
+ /**
+ * Show UX spinner
+ */
+ function showSpinner() {
+ $( '.spinner' ).addClass( 'is-active' );
+ $( '.post-actions button' ).attr( 'disabled', 'disabled' );
+ }
+
+ /**
+ * Hide UX spinner
+ */
+ function hideSpinner() {
+ $( '.spinner' ).removeClass( 'is-active' );
+ $( '.post-actions button' ).removeAttr( 'disabled' );
+ }
+
+ /**
+ * Replace emoji images with chars and sanitize the text content.
+ */
+ function getTitleText() {
+ var $element = $( '#title-container' );
+
+ $element.find( 'img.emoji' ).each( function() {
+ var $image = $( this );
+ $image.replaceWith( $( '<span>' ).text( $image.attr( 'alt' ) ) );
+ });
+
+ return sanitizeText( $element.text() );
+ }
+
+ /**
+ * Prepare the form data for saving.
+ */
+ function prepareFormData() {
+ var $form = $( '#pressthis-form' ),
+ $input = $( '<input type="hidden" name="post_category[]" value="">' );
+
+ editor && editor.save();
+
+ $( '#post_title' ).val( getTitleText() );
+
+ // Make sure to flush out the tags with tagBox before saving
+ if ( window.tagBox ) {
+ $( 'div.tagsdiv' ).each( function() {
+ window.tagBox.flushTags( this, false, 1 );
+ } );
+ }
+
+ // Get selected categories
+ $( '.categories-select .category' ).each( function( i, element ) {
+ var $cat = $( element );
+
+ if ( $cat.hasClass( 'selected' ) ) {
+ // Have to append a node as we submit the actual form on preview
+ $form.append( $input.clone().val( $cat.attr( 'data-term-id' ) || '' ) );
+ }
+ });
+ }
+
+ /**
+ * Submit the post form via AJAX, and redirect to the proper screen if published vs saved as a draft.
+ *
+ * @param action string publish|draft
+ */
+ function submitPost( action ) {
+ var data,
+ keepFocus = $( document.activeElement ).hasClass( 'draft-button' );
+
+ saveAlert = false;
+ showSpinner();
+
+ if ( 'publish' === action ) {
+ $( '#post_status' ).val( 'publish' );
+ }
+
+ prepareFormData();
+ data = $( '#pressthis-form' ).serialize();
+
+ $.ajax( {
+ type: 'post',
+ url: window.ajaxurl,
+ data: data
+ }).always( function() {
+ hideSpinner();
+ clearNotices();
+ }).done( function( response ) {
+ var $link, $button;
+
+ if ( ! response.success ) {
+ renderError( response.data.errorMessage );
+ } else if ( response.data.redirect ) {
+ if ( window.opener && settings.redirInParent ) {
+ try {
+ window.opener.location.href = response.data.redirect;
+ } catch( er ) {}
+
+ window.self.close();
+ } else {
+ window.location.href = response.data.redirect;
+ }
+ } else if ( response.data.postSaved ) {
+ $link = $( '.edit-post-link' );
+ $button = $( '.draft-button' );
+ editLinkVisible = true;
+
+ $button.fadeOut( 200, function() {
+ $button.removeClass( 'is-saving' );
+ $link.fadeIn( 200, function() {
+ var active = document.activeElement;
+ // Different browsers move the focus to different places when the button is disabled.
+ if ( keepFocus && ( active === $button[0] || $( active ).hasClass( 'post-actions' ) || active.nodeName === 'BODY' ) ) {
+ $link.focus();
+ }
+ });
+ });
+ }
+ }).fail( function() {
+ renderError( __( 'serverError' ) );
+ });
+ }
+
+ function resetDraftButton() {
+ if ( editLinkVisible ) {
+ editLinkVisible = false;
+
+ $( '.edit-post-link' ).fadeOut( 200, function() {
+ $( '.draft-button' ).removeClass( 'is-saving' ).fadeIn( 200 );
+ });
+ }
+ }
+
+ /**
+ * Inserts the media a user has selected from the presented list inside the editor, as an image or embed, based on type
+ *
+ * @param type string img|embed
+ * @param src string Source URL
+ * @param link string Optional destination link, for images (defaults to src)
+ */
+ function insertSelectedMedia( $element ) {
+ var src, link, newContent = '';
+
+ if ( ! editor ) {
+ return;
+ }
+
+ src = checkUrl( $element.attr( 'data-wp-src' ) || '' );
+ link = checkUrl( data.u );
+
+ if ( $element.hasClass( 'is-image' ) ) {
+ if ( ! link ) {
+ link = src;
+ }
+
+ newContent = '<a href="' + link + '"><img class="alignnone size-full" src="' + src + '" /></a>';
+ } else {
+ newContent = '[embed]' + src + '[/embed]';
+ }
+
+ if ( ! hasSetFocus ) {
+ editor.setContent( '<p>' + newContent + '</p>' + editor.getContent() );
+ } else {
+ editor.execCommand( 'mceInsertContent', false, newContent );
+ }
+ }
+
+ /**
+ * Save a new user-generated category via AJAX
+ */
+ function saveNewCategory() {
+ var data,
+ name = $( '#new-category' ).val();
+
+ if ( ! name ) {
+ return;
+ }
+
+ data = {
+ action: 'press-this-add-category',
+ post_id: $( '#post_ID' ).val() || 0,
+ name: name,
+ new_cat_nonce: $( '#_ajax_nonce-add-category' ).val() || '',
+ parent: $( '#new-category-parent' ).val() || 0
+ };
+
+ $.post( window.ajaxurl, data, function( response ) {
+ if ( ! response.success ) {
+ renderError( response.data.errorMessage );
+ } else {
+ var $parent, $ul,
+ $wrap = $( 'ul.categories-select' );
+
+ $.each( response.data, function( i, newCat ) {
+ var $node = $( '<li>' ).append( $( '<div class="category selected" tabindex="0" role="checkbox" aria-checked="true">' )
+ .attr( 'data-term-id', newCat.term_id )
+ .text( newCat.name ) );
+
+ if ( newCat.parent ) {
+ if ( ! $ul || ! $ul.length ) {
+ $parent = $wrap.find( 'div[data-term-id="' + newCat.parent + '"]' ).parent();
+ $ul = $parent.find( 'ul.children:first' );
+
+ if ( ! $ul.length ) {
+ $ul = $( '<ul class="children">' ).appendTo( $parent );
+ }
+ }
+
+ $ul.prepend( $node );
+ } else {
+ $wrap.prepend( $node );
+ }
+
+ $node.focus();
+ } );
+
+ refreshCatsCache();
+ }
+ } );
+ }
+
+ /* ***************************************************************
+ * RENDERING FUNCTIONS
+ *************************************************************** */
+
+ /**
+ * Hide the form letting users enter a URL to be scanned, if a URL was already passed.
+ */
+ function renderToolsVisibility() {
+ if ( data.hasData ) {
+ $( '#scanbar' ).hide();
+ }
+ }
+
+ /**
+ * Render error notice
+ *
+ * @param msg string Notice/error message
+ * @param error string error|notice CSS class for display
+ */
+ function renderNotice( msg, error ) {
+ var $alerts = $( '.editor-wrapper div.alerts' ),
+ className = error ? 'is-error' : 'is-notice';
+
+ $alerts.append( $( '<p class="alert ' + className + '">' ).text( msg ) );
+ }
+
+ /**
+ * Render error notice
+ *
+ * @param msg string Error message
+ */
+ function renderError( msg ) {
+ renderNotice( msg, true );
+ }
+
+ function clearNotices() {
+ $( 'div.alerts' ).empty();
+ }
+
+ /**
+ * Render notices on page load, if any already
+ */
+ function renderStartupNotices() {
+ // Render errors sent in the data, if any
+ if ( data.errors ) {
+ $.each( data.errors, function( i, msg ) {
+ renderError( msg );
+ } );
+ }
+ }
+
+ /**
+ * Add an image to the list of found images.
+ */
+ function addImg( src, displaySrc, i ) {
+ var $element = $mediaThumbWrap.clone().addClass( 'is-image' );
+
+ $element.attr( 'data-wp-src', src ).css( 'background-image', 'url(' + displaySrc + ')' )
+ .find( 'span' ).text( __( 'suggestedImgAlt' ).replace( '%d', i + 1 ) );
+
+ $mediaList.append( $element );
+ }
+
+ /**
+ * Render the detected images and embed for selection, if any
+ */
+ function renderDetectedMedia() {
+ var found = 0;
+
+ $mediaList = $( 'ul.media-list' );
+ $mediaThumbWrap = $( '<li class="suggested-media-thumbnail" tabindex="0"><span class="screen-reader-text"></span></li>' );
+
+ if ( data._embeds ) {
+ $.each( data._embeds, function ( i, src ) {
+ var displaySrc = '',
+ cssClass = '',
+ $element = $mediaThumbWrap.clone().addClass( 'is-embed' );
+
+ src = checkUrl( src );
+
+ if ( src.indexOf( 'youtube.com/' ) > -1 ) {
+ displaySrc = 'https://i.ytimg.com/vi/' + src.replace( /.+v=([^&]+).*/, '$1' ) + '/hqdefault.jpg';
+ cssClass += ' is-video';
+ } else if ( src.indexOf( 'youtu.be/' ) > -1 ) {
+ displaySrc = 'https://i.ytimg.com/vi/' + src.replace( /\/([^\/])$/, '$1' ) + '/hqdefault.jpg';
+ cssClass += ' is-video';
+ } else if ( src.indexOf( 'dailymotion.com' ) > -1 ) {
+ displaySrc = src.replace( '/video/', '/thumbnail/video/' );
+ cssClass += ' is-video';
+ } else if ( src.indexOf( 'soundcloud.com' ) > -1 ) {
+ cssClass += ' is-audio';
+ } else if ( src.indexOf( 'twitter.com' ) > -1 ) {
+ cssClass += ' is-tweet';
+ } else {
+ cssClass += ' is-video';
+ }
+
+ $element.attr( 'data-wp-src', src ).find( 'span' ).text( __( 'suggestedEmbedAlt' ).replace( '%d', i + 1 ) );
+
+ if ( displaySrc ) {
+ $element.css( 'background-image', 'url(' + displaySrc + ')' );
+ }
+
+ $mediaList.append( $element );
+ found++;
+ } );
+ }
+
+ if ( data._images ) {
+ $.each( data._images, function( i, src ) {
+ var displaySrc, img = new Image();
+
+ src = checkUrl( src );
+ displaySrc = src.replace( /^(http[^\?]+)(\?.*)?$/, '$1' );
+
+ if ( src.indexOf( 'files.wordpress.com/' ) > -1 ) {
+ displaySrc = displaySrc.replace( /\?.*$/, '' ) + '?w=' + smallestWidth;
+ } else if ( src.indexOf( 'gravatar.com/' ) > -1 ) {
+ displaySrc = displaySrc.replace( /\?.*$/, '' ) + '?s=' + smallestWidth;
+ } else {
+ displaySrc = src;
+ }
+
+ img.onload = function() {
+ if ( ( img.width && img.width < 256 ) ||
+ ( img.height && img.height < 128 ) ) {
+
+ return;
+ }
+
+ addImg( src, displaySrc, i );
+ };
+
+ img.src = src;
+ found++;
+ } );
+ }
+
+ if ( found ) {
+ $( '.media-list-container' ).addClass( 'has-media' );
+ }
+ }
+
+ /* ***************************************************************
+ * MONITORING FUNCTIONS
+ *************************************************************** */
+
+ /**
+ * Interactive navigation behavior for the options modal (post format, tags, categories)
+ */
+ function monitorOptionsModal() {
+ var $postOptions = $( '.post-options' ),
+ $postOption = $( '.post-option' ),
+ $settingModal = $( '.setting-modal' ),
+ $modalClose = $( '.modal-close' );
+
+ $postOption.on( 'click', function() {
+ var index = $( this ).index(),
+ $targetSettingModal = $settingModal.eq( index );
+
+ $postOptions.addClass( isOffScreen )
+ .one( transitionEndEvent, function() {
+ $( this ).addClass( isHidden );
+ } );
+
+ $targetSettingModal.removeClass( offscreenHidden )
+ .one( transitionEndEvent, function() {
+ $( this ).find( '.modal-close' ).focus();
+ } );
+ } );
+
+ $modalClose.on( 'click', function() {
+ var $targetSettingModal = $( this ).parent(),
+ index = $targetSettingModal.index();
+
+ $postOptions.removeClass( offscreenHidden );
+ $targetSettingModal.addClass( isOffScreen );
+
+ if ( transitionEndEvent ) {
+ $targetSettingModal.one( transitionEndEvent, function() {
+ $( this ).addClass( isHidden );
+ $postOption.eq( index - 1 ).focus();
+ } );
+ } else {
+ setTimeout( function() {
+ $targetSettingModal.addClass( isHidden );
+ $postOption.eq( index - 1 ).focus();
+ }, 350 );
+ }
+ } );
+ }
+
+ /**
+ * Interactive behavior for the sidebar toggle, to show the options modals
+ */
+ function openSidebar() {
+ sidebarIsOpen = true;
+
+ $( '.options' ).removeClass( 'closed' ).addClass( 'open' );
+ $( '.press-this-actions, #scanbar' ).addClass( isHidden );
+ $( '.options-panel-back' ).removeClass( isHidden );
+
+ $( '.options-panel' ).removeClass( offscreenHidden )
+ .one( transitionEndEvent, function() {
+ $( '.post-option:first' ).focus();
+ } );
+ }
+
+ function closeSidebar() {
+ sidebarIsOpen = false;
+
+ $( '.options' ).removeClass( 'open' ).addClass( 'closed' );
+ $( '.options-panel-back' ).addClass( isHidden );
+ $( '.press-this-actions, #scanbar' ).removeClass( isHidden );
+
+ $( '.options-panel' ).addClass( isOffScreen )
+ .one( transitionEndEvent, function() {
+ $( this ).addClass( isHidden );
+ // Reset to options list
+ $( '.post-options' ).removeClass( offscreenHidden );
+ $( '.setting-modal').addClass( offscreenHidden );
+ });
+ }
+
+ /**
+ * Interactive behavior for the post title's field placeholder
+ */
+ function monitorPlaceholder() {
+ var $titleField = $( '#title-container' ),
+ $placeholder = $( '.post-title-placeholder' );
+
+ $titleField.on( 'focus', function() {
+ $placeholder.addClass( 'is-hidden' );
+ resetDraftButton();
+ }).on( 'blur', function() {
+ if ( ! $titleField.text() && ! $titleField.html() ) {
+ $placeholder.removeClass( 'is-hidden' );
+ }
+ }).on( 'keyup', function() {
+ saveAlert = true;
+ }).on( 'paste', function( event ) {
+ var text, range,
+ clipboard = event.originalEvent.clipboardData || window.clipboardData;
+
+ if ( clipboard ) {
+ try{
+ text = clipboard.getData( 'Text' ) || clipboard.getData( 'text/plain' );
+
+ if ( text ) {
+ text = $.trim( text.replace( /\s+/g, ' ' ) );
+
+ if ( window.getSelection ) {
+ range = window.getSelection().getRangeAt(0);
+
+ if ( range ) {
+ if ( ! range.collapsed ) {
+ range.deleteContents();
+ }
+
+ range.insertNode( document.createTextNode( text ) );
+ }
+ } else if ( document.selection ) {
+ range = document.selection.createRange();
+
+ if ( range ) {
+ range.text = text;
+ }
+ }
+ }
+ } catch ( er ) {}
+
+ event.preventDefault();
+ }
+
+ saveAlert = true;
+
+ setTimeout( function() {
+ $titleField.text( getTitleText() );
+ }, 50 );
+ });
+
+ if ( $titleField.text() || $titleField.html() ) {
+ $placeholder.addClass('is-hidden');
+ }
+ }
+
+ function toggleCatItem( $element ) {
+ if ( $element.hasClass( 'selected' ) ) {
+ $element.removeClass( 'selected' ).attr( 'aria-checked', 'false' );
+ } else {
+ $element.addClass( 'selected' ).attr( 'aria-checked', 'true' );
+ }
+ }
+
+ function monitorCatList() {
+ $( '.categories-select' ).on( 'click.press-this keydown.press-this', function( event ) {
+ var $element = $( event.target );
+
+ if ( $element.is( 'div.category' ) ) {
+ if ( event.type === 'keydown' && event.keyCode !== 32 ) {
+ return;
+ }
+
+ toggleCatItem( $element );
+ event.preventDefault();
+ }
+ });
+ }
+
+ /* ***************************************************************
+ * PROCESSING FUNCTIONS
+ *************************************************************** */
+
+ /**
+ * Calls all the rendring related functions to happen on page load
+ */
+ function render(){
+ // We're on!
+ renderToolsVisibility();
+ renderDetectedMedia();
+ renderStartupNotices();
+
+ if ( window.tagBox ) {
+ window.tagBox.init();
+ }
+ }
+
+ /**
+ * Set app events and other state monitoring related code.
+ */
+ function monitor() {
+ $( document ).on( 'tinymce-editor-init', function( event, ed ) {
+ editor = ed;
+
+ editor.on( 'nodechange', function() {
+ hasSetFocus = true;
+ resetDraftButton();
+ } );
+ }).on( 'click.press-this keypress.press-this', '.suggested-media-thumbnail', function( event ) {
+ if ( event.type === 'click' || event.keyCode === 13 ) {
+ insertSelectedMedia( $( this ) );
+ }
+ });
+
+ // Publish, Draft and Preview buttons
+ $( '.post-actions' ).on( 'click.press-this', function( event ) {
+ var $target = $( event.target ),
+ $button = $target.closest( 'button' );
+
+ if ( $button.length ) {
+ if ( $button.hasClass( 'draft-button' ) ) {
+ $button.addClass( 'is-saving' );
+ submitPost( 'draft' );
+ } else if ( $button.hasClass( 'publish-button' ) ) {
+ submitPost( 'publish' );
+ } else if ( $button.hasClass( 'preview-button' ) ) {
+ prepareFormData();
+ window.opener && window.opener.focus();
+
+ $( '#wp-preview' ).val( 'dopreview' );
+ $( '#pressthis-form' ).attr( 'target', '_blank' ).submit().attr( 'target', '' );
+ $( '#wp-preview' ).val( '' );
+ }
+ } else if ( $target.hasClass( 'edit-post-link' ) && window.opener ) {
+ window.opener.focus();
+ window.self.close();
+ }
+ });
+
+ monitorOptionsModal();
+ monitorPlaceholder();
+ monitorCatList();
+
+ $( '.options' ).on( 'click.press-this', function() {
+ if ( $( this ).hasClass( 'open' ) ) {
+ closeSidebar();
+ } else {
+ openSidebar();
+ }
+ });
+
+ // Close the sidebar when focus moves outside of it.
+ $( '.options-panel, .options-panel-back' ).on( 'focusout.press-this', function() {
+ setTimeout( function() {
+ var node = document.activeElement,
+ $node = $( node );
+
+ if ( sidebarIsOpen && node && ! $node.hasClass( 'options-panel-back' ) &&
+ ( node.nodeName === 'BODY' ||
+ ( ! $node.closest( '.options-panel' ).length &&
+ ! $node.closest( '.options' ).length ) ) ) {
+
+ closeSidebar();
+ }
+ }, 50 );
+ });
+
+ $( '#post-formats-select input' ).on( 'change', function() {
+ var $this = $( this );
+
+ if ( $this.is( ':checked' ) ) {
+ $( '#post-option-post-format' ).text( $( 'label[for="' + $this.attr( 'id' ) + '"]' ).text() || '' );
+ }
+ } );
+
+ $( window ).on( 'beforeunload.press-this', function() {
+ if ( saveAlert || ( editor && editor.isDirty() ) ) {
+ return __( 'saveAlert' );
+ }
+ } );
+
+ $( 'button.add-cat-toggle' ).on( 'click.press-this', function() {
+ var $this = $( this );
+
+ $this.toggleClass( 'is-toggled' );
+ $this.attr( 'aria-expanded', 'false' === $this.attr( 'aria-expanded' ) ? 'true' : 'false' );
+ $( '.setting-modal .add-category, .categories-search-wrapper' ).toggleClass( 'is-hidden' );
+ } );
+
+ $( 'button.add-cat-submit' ).on( 'click.press-this', saveNewCategory );
+
+ $( '.categories-search' ).on( 'keyup.press-this', function() {
+ var search = $( this ).val().toLowerCase() || '';
+
+ // Don't search when less thasn 3 extended ASCII chars
+ if ( /[\x20-\xFF]+/.test( search ) && search.length < 2 ) {
+ return;
+ }
+
+ $.each( catsCache, function( i, cat ) {
+ cat.node.removeClass( 'is-hidden searched-parent' );
+ } );
+
+ if ( search ) {
+ $.each( catsCache, function( i, cat ) {
+ if ( cat.text.indexOf( search ) === -1 ) {
+ cat.node.addClass( 'is-hidden' );
+ } else {
+ cat.parents.addClass( 'searched-parent' );
+ }
+ } );
+ }
+ } );
+
+ return true;
+ }
+
+ function refreshCatsCache() {
+ $( '.categories-select' ).find( 'li' ).each( function() {
+ var $this = $( this );
+
+ catsCache.push( {
+ node: $this,
+ parents: $this.parents( 'li' ),
+ text: $this.children( '.category' ).text().toLowerCase()
+ } );
+ } );
+ }
+
+ // Let's go!
+ $( document ).ready( function() {
+ render();
+ monitor();
+ refreshCatsCache();
+ });
+
+ // Expose public methods?
+ return {
+ renderNotice: renderNotice,
+ renderError: renderError
+ };
+ };
+
+ window.wp = window.wp || {};
+ window.wp.pressThis = new PressThis();
+
+}( jQuery, window ));