diff options
Diffstat (limited to 'plugins/jetpack/modules/videopress/js')
-rw-r--r-- | plugins/jetpack/modules/videopress/js/editor-view.js | 261 | ||||
-rw-r--r-- | plugins/jetpack/modules/videopress/js/videopress-admin.js | 502 |
2 files changed, 763 insertions, 0 deletions
diff --git a/plugins/jetpack/modules/videopress/js/editor-view.js b/plugins/jetpack/modules/videopress/js/editor-view.js new file mode 100644 index 00000000..c77f5b6a --- /dev/null +++ b/plugins/jetpack/modules/videopress/js/editor-view.js @@ -0,0 +1,261 @@ +/* global tinyMCE, vpEditorView */ +(function( $, wp, vpEditorView ){ + wp.mce = wp.mce || {}; + wp.mce.videopress_wp_view_renderer = { + shortcode_string : 'videopress', + shortcode_data : {}, + defaults : { + w : '', + at : '', + permalink : true, + hd : false, + loop : false, + freedom : false, + autoplay : false, + flashonly : false + }, + coerce : wp.media.coerce, + template : wp.template( 'videopress_iframe_vnext' ), + getContent : function() { + var urlargs = 'for=' + encodeURIComponent( vpEditorView.home_url_host ), + named = this.shortcode.attrs.named, + options, key, width; + + for ( key in named ) { + switch ( key ) { + case 'at' : + if ( parseInt( named[ key ], 10 ) ) { + urlargs += '&' + key + '=' + parseInt( named[ key ], 10 ); + } // Else omit, as it's the default. + break; + case 'permalink' : + if ( 'false' === named[ key ] ) { + urlargs += '&' + key + '=0'; + } // Else omit, as it's the default. + break; + case 'hd' : + case 'loop' : + case 'autoplay' : + if ( 'true' === named[ key ] ) { + urlargs += '&' + key + '=1'; + } // Else omit, as it's the default. + break; + default: + // Unknown parameters? Ditch it! + break; + } + } + + options = { + width : vpEditorView.content_width, + height : ( vpEditorView.content_width * 0.5625 ), + guid : this.shortcode.attrs.numeric[0], + urlargs : urlargs + }; + + if ( typeof named.w !== 'undefined' ) { + width = parseInt( named.w, 10 ); + if ( width >= vpEditorView.min_content_width && width < vpEditorView.content_width ) { + options.width = width; + options.height = parseInt( width * 0.5625, 10 ); + } + } + + options.ratio = 100 * ( options.height / options.width ); + + return this.template( options ); + }, + edit: function( data ) { + var shortcode_data = wp.shortcode.next( this.shortcode_string, data), + named = shortcode_data.shortcode.attrs.named, + editor = tinyMCE.activeEditor, + renderer = this, + oldRenderFormItem = tinyMCE.ui.FormItem.prototype.renderHtml; + + /** + * Override TextBox renderHtml to support html5 attrs. + * @link https://github.com/tinymce/tinymce/pull/2784 + * + * @returns {string} + */ + tinyMCE.ui.TextBox.prototype.renderHtml = function() { + var self = this, + settings = self.settings, + element = document.createElement( settings.multiline ? 'textarea' : 'input' ), + extraAttrs = [ + 'rows', + 'spellcheck', + 'maxLength', + 'size', + 'readonly', + 'min', + 'max', + 'step', + 'list', + 'pattern', + 'placeholder', + 'required', + 'multiple' + ], + i, key; + + for ( i = 0; i < extraAttrs.length; i++ ) { + key = extraAttrs[ i ]; + if ( typeof settings[ key ] !== 'undefined' ) { + element.setAttribute( key, settings[ key ] ); + } + } + + if ( settings.multiline ) { + element.innerText = self.state.get( 'value' ); + } else { + element.setAttribute( 'type', settings.subtype ? settings.subtype : 'text' ); + element.setAttribute( 'value', self.state.get( 'value' ) ); + } + + element.id = self._id; + element.className = self.classes; + element.setAttribute( 'hidefocus', 1 ); + if ( self.disabled() ) { + element.disabled = true; + } + + return element.outerHTML; + }; + + tinyMCE.ui.FormItem.prototype.renderHtml = function() { + _.each( vpEditorView.modal_labels, function( value, key ) { + if ( value === this.settings.items.text ) { + this.classes.add( 'videopress-field-' + key ); + } + }, this ); + + if ( _.contains( [ + vpEditorView.modal_labels.hd, + vpEditorView.modal_labels.permalink, + vpEditorView.modal_labels.autoplay, + vpEditorView.modal_labels.loop, + vpEditorView.modal_labels.freedom, + vpEditorView.modal_labels.flashonly + ], this.settings.items.text ) ) { + this.classes.add( 'videopress-checkbox' ); + } + return oldRenderFormItem.call( this ); + }; + + /** + * Populate the defaults. + */ + _.each( this.defaults, function( value, key ) { + named[ key ] = this.coerce( named, key); + }, this ); + + /** + * Declare the fields that will show in the popup when editing the shortcode. + */ + editor.windowManager.open( { + title : vpEditorView.modal_labels.title, + id : 'videopress-shortcode-settings-modal', + width : 520, + height : 240, + body : [ + { + type : 'textbox', + disabled : true, + name : 'guid', + label : vpEditorView.modal_labels.guid, + value : shortcode_data.shortcode.attrs.numeric[0] + }, { + type : 'textbox', + subtype : 'number', + min : vpEditorView.min_content_width, // The `min` may supported be in the future. https://github.com/tinymce/tinymce/pull/2784 + name : 'w', + label : vpEditorView.modal_labels.w, + value : named.w + }, { + type : 'textbox', + subtype : 'number', + min : 0, // The `min` may supported be in the future. https://github.com/tinymce/tinymce/pull/2784 + name : 'at', + label : vpEditorView.modal_labels.at, + value : named.at + }, { + type : 'checkbox', + name : 'hd', + label : vpEditorView.modal_labels.hd, + checked : named.hd + }, { + type : 'checkbox', + name : 'permalink', + label : vpEditorView.modal_labels.permalink, + checked : named.permalink + }, { + type : 'checkbox', + name : 'autoplay', + label : vpEditorView.modal_labels.autoplay, + checked : named.autoplay + }, { + type : 'checkbox', + name : 'loop', + label : vpEditorView.modal_labels.loop, + checked : named.loop + }, { + type : 'checkbox', + name : 'freedom', + label : vpEditorView.modal_labels.freedom, + checked : named.freedom + }, { + type : 'checkbox', + name : 'flashonly', + label : vpEditorView.modal_labels.flashonly, + checked : named.flashonly + } + ], + onsubmit : function( e ) { + var args = { + tag : renderer.shortcode_string, + type : 'single', + attrs : { + named : _.pick( e.data, _.keys( renderer.defaults ) ), + numeric : [ e.data.guid ] + } + }; + + if ( '0' === args.attrs.named.at ) { + args.attrs.named.at = ''; + } + + _.each( renderer.defaults, function( value, key ) { + args.attrs.named[ key ] = this.coerce( args.attrs.named, key ); + + if ( value === args.attrs.named[ key ] ) { + delete args.attrs.named[ key ]; + } + }, renderer ); + + editor.insertContent( wp.shortcode.string( args ) ); + }, + onopen : function ( e ) { + var prefix = 'mce-videopress-field-'; + _.each( ['w', 'at'], function( value ) { + e.target.$el.find( '.' + prefix + value + ' .mce-container-body' ).append( '<span class="' + prefix + 'unit ' + prefix + 'unit-' + value + '">' + vpEditorView.modal_labels[ value + '_unit' ] ); + } ); + $('body').addClass( 'modal-open' ); + }, + onclose: function () { + $('body').removeClass( 'modal-open' ); + } + } ); + + // Set it back to its original renderer. + tinyMCE.ui.FormItem.prototype.renderHtml = oldRenderFormItem; + } + }; + wp.mce.views.register( 'videopress', wp.mce.videopress_wp_view_renderer ); + + // Extend the videopress one to also handle `wpvideo` instances. + wp.mce.wpvideo_wp_view_renderer = _.extend( {}, wp.mce.videopress_wp_view_renderer, { + shortcode_string : 'wpvideo' + }); + wp.mce.views.register( 'wpvideo', wp.mce.wpvideo_wp_view_renderer ); +}( jQuery, wp, vpEditorView )); diff --git a/plugins/jetpack/modules/videopress/js/videopress-admin.js b/plugins/jetpack/modules/videopress/js/videopress-admin.js new file mode 100644 index 00000000..c7851117 --- /dev/null +++ b/plugins/jetpack/modules/videopress/js/videopress-admin.js @@ -0,0 +1,502 @@ +/* jshint onevar: false, smarttabs: true, devel: true */ +/* global VideoPressAdminSettings, setUserSetting */ + +/** + * VideoPress Admin + * + * @todo i18n + */ +(function($) { + var media = wp.media; + var VideoPress = VideoPress || {}; + + VideoPress.caps = VideoPressAdminSettings.caps; + VideoPress.l10n = VideoPressAdminSettings.l10n; + + /** + * Create a new controller that simply adds a videopress key + * to the library query + */ + media.controller.VideoPress = media.controller.Library.extend({ + defaults: _.defaults({ + id: 'videopress', + router: 'videopress', + toolbar: 'videopress-toolbar', + title: 'VideoPress', + priority: 200, + searchable: true, + sortable: false + }, media.controller.Library.prototype.defaults ), + + initialize: function() { + if ( ! this.get('library') ) { + this.set( 'library', media.query({ videopress: true }) ); + } + + media.controller.Library.prototype.initialize.apply( this, arguments ); + }, + + /** + * The original function saves content for the browse router only, + * so we hi-jack it a little bit. + */ + saveContentMode: function() { + if ( 'videopress' !== this.get('router') ) { + return; + } + + var mode = this.frame.content.mode(), + view = this.frame.router.get(); + + if ( view && view.get( mode ) ) { + + // Map the Upload a Video back to the regular Upload Files. + if ( 'upload_videopress' === mode ) { + mode = 'upload'; + } + + setUserSetting( 'libraryContent', mode ); + } + } + }); + + /** + * VideoPress Uploader + */ + media.view.VideoPressUploader = media.View.extend({ + tagName: 'div', + className: 'uploader-videopress', + template: media.template('videopress-uploader'), + + events: { + 'submit .videopress-upload-form': 'submitForm' + }, + + initialize: function() { + var that = this; + + if ( ! window.addEventListener ) { + window.attachEvent( 'onmessage', function() { return that.messageHandler.apply( that, arguments ); } ); + } else { + window.addEventListener( 'message', function() { return that.messageHandler.apply( that, arguments ); }, false ); + } + + return media.View.prototype.initialize.apply( this, arguments ); + }, + + submitForm: function() { + var data = false; + + this.clearErrors(); + + if ( this.$( 'input[name="videopress_file"]').val().length < 1 ) { + this.error( VideoPress.l10n.selectVideoFile ); + return false; + } + + // Prevent multiple submissions. + this.$( '.videopress-upload-form .button' ).prop( 'disabled', true ); + + // A non-async request for an upload token. + media.ajax( 'videopress-get-upload-token', { async: false } ).done( function( response ) { + data = response; + data.success = true; + }).fail( function( response ) { + data = response; + data.success = false; + }); + + if ( ! data.success ) { + // Re-enable form elements. + this.$( '.videopress-upload-form .button' ).prop( 'disabled', false ); + + // Display an error message and cancel form submission. + this.error( data.message ); + return false; + } + + this.error( VideoPress.l10n.videoUploading, 'updated' ); + + // Set the form token. + this.$( 'input[name="videopress_blog_id"]' ).val( data.videopress_blog_id ); + this.$( 'input[name="videopress_token"]' ).val( data.videopress_token ); + this.$( '.videopress-upload-form' ).attr( 'action', data.videopress_action_url ); + return true; + }, + + error: function( message, type ) { + type = type || 'error'; + var div = $( '<div />' ).html( $( '<p />' ).text( message ) ).addClass( type ); + this.$( '.videopress-errors' ).html( div ); + return this; + }, + + success: function( message ) { + return this.error( message, 'updated' ); + }, + + clearErrors: function() { + this.$( '.videopress-errors' ).html(''); + return this; + }, + + messageHandler: function( event ) { + if ( ! event.origin.match( /\.wordpress\.com$/ ) ) { + return; + } + + if ( event.data.indexOf && event.data.indexOf( 'vpUploadResult::' ) === 0 ) { + var result = JSON.parse( event.data.substr( 16 ) ); + + if ( ! result || ! result.code ) { + this.error( VideoPress.l10n.unknownError ); + this.$( '.videopress-upload-form .button' ).prop( 'disabled', false ); + return; + } + + if ( 'success' === result.code && result.data ) { + var that = this, controller = this.controller, + state = controller.states.get( 'videopress' ); + + // Our new video has been added, so we need to reset the library. + // Since the Media API caches all queries, we add a random attribute + // to avoid the cache, then call more() to actually fetch the data. + + state.set( 'library', media.query({ videopress:true, vp_random:Math.random() }) ); + state.get( 'library' ).more().done(function(){ + var model = state.get( 'library' ).get( result.data.attachment_id ); + + // Clear errors and select the uploaded item. + that.clearErrors(); + state.get( 'selection' ).reset([ model ]); + controller.content.mode( 'browse' ); + }); + } else { + this.error( result.code ); + + // Re-enable form elements. + this.$( '.videopress-upload-form .button' ).prop( 'disabled', false ); + } + } + } + }); + + /** + * Add a custom sync function that would add a few extra + * options for models which are VideoPress videos. + */ + var attachmentSync = media.model.Attachment.prototype.sync; + media.model.Attachment.prototype.sync = function( method, model, options ) { + if ( model.get( 'vp_isVideoPress' ) ) { + console.log( 'syncing ' + model.get( 'vp_guid' ) ); + options.data = _.extend( options.data || {}, { + is_videopress: true, + vp_nonces: model.get( 'vp_nonces' ) + } ); + } + + // Call the original sync routine. + return attachmentSync.apply( this, arguments ); + }; + + /** + * Extend the default Attachment Details view. Check for vp_isVideoPress before + * adding anything to these methods. + */ + var AttachmentDetails = media.view.Attachment.Details; + media.view.Attachment.Details = AttachmentDetails.extend({ + + initialize: function() { + if ( this.model.get( 'vp_isVideoPress' ) ) { + _.extend( this.events, { + 'click a.videopress-preview': 'vpPreview', + 'change .vp-radio': 'vpRadioChange', + 'change .vp-checkbox': 'vpCheckboxChange' + }); + } + return AttachmentDetails.prototype.initialize.apply( this, arguments ); + }, + + render: function() { + var r = AttachmentDetails.prototype.render.apply( this, arguments ); + if ( this.model.get( 'vp_isVideoPress' ) ) { + var template = media.template( 'videopress-attachment' ); + var options = this.model.toJSON(); + + options.can = {}; + options.can.save = !! options.nonces.update; + + this.$el.append( template( options ) ); + } + return r; + }, + + // Handle radio buttons + vpRadioChange: function(e) { + $( e.target ).parents( '.vp-setting' ).find( '.vp-radio-text' ).val( e.target.value ).change(); + }, + + // And checkboxes + vpCheckboxChange: function(e) { + $( e.target ).parents( '.vp-setting' ).find( '.vp-checkbox-text' ).val( Number( e.target.checked ) ).change(); + }, + + vpPreview: function() { + VideoPressModal.render( this ); + return this; + } + }); + + /** + * Don't display the uploader dropzone for the VideoPress router. + */ + var UploaderWindow = media.view.UploaderWindow; + media.view.UploaderWindow = UploaderWindow.extend({ + show: function() { + if ( 'videopress' !== this.controller.state().get('id') ) { + UploaderWindow.prototype.show.apply( this, arguments ); + } + + return this; + } + }); + + /** + * Don't display the uploader in the attachments browser. + */ + var AttachmentsBrowser = media.view.AttachmentsBrowser; + media.view.AttachmentsBrowser = AttachmentsBrowser.extend({ + /** + * Snag the Core 3.9.2 versions as a quick fix to avoid + * the breakage introduced by r29364-core + */ + updateContent: function() { + var view = this; + + if( ! this.attachments ) { + this.createAttachments(); + } + + if ( ! this.collection.length ) { + this.toolbar.get( 'spinner' ).show(); + this.collection.more().done(function() { + if ( ! view.collection.length ) { + view.createUploader(); + } + view.toolbar.get( 'spinner' ).hide(); + }); + } else { + view.toolbar.get( 'spinner' ).hide(); + } + }, + /** + * Empty out to avoid breakage. + */ + toggleUploader: function() {}, + createUploader: function() { + if ( 'videopress' !== this.controller.state().get('id') ) { + return AttachmentsBrowser.prototype.createUploader.apply( this, arguments ); + } + } + }); + + /** + * Add VideoPress-specific methods for all frames. + */ + _.extend( media.view.MediaFrame.prototype, { VideoPress: { // this.VideoPress.method() + + // When the VideoPress router is activated. + activate: function() { + var view = _.first( this.views.get( '.media-frame-router' ) ), + viewSettings = {}; + + if ( VideoPress.caps.read_videos ) { + viewSettings.browse = { text: VideoPress.l10n.VideoPressLibraryRouter, priority: 40 }; + } + + if ( VideoPress.caps.upload_videos ) { + viewSettings.upload_videopress = { text: VideoPress.l10n.uploadVideoRouter, priority: 20 }; + } + + view.set( viewSettings ); + + // Intercept and clear all incoming uploads + wp.Uploader.queue.on( 'add', this.VideoPress.disableUpload, this ); + + // Map the Upload Files view to the Upload a Video one (upload_videopress vs. upload) + if ( 'upload' === this.content.mode() && VideoPress.caps.upload_videos ) { + this.content.mode( 'upload_videopress' ); + } else { + this.content.mode( 'browse' ); + } + }, + + // When navigated away from the VideoPress router. + deactivate: function( /*view*/ ) { + wp.Uploader.queue.off( 'add', this.VideoPress.disableUpload ); + }, + + // Disable dragdrop uploads in the VideoPress router. + disableUpload: function( attachment ) { + var uploader = this.uploader.uploader.uploader; + uploader.stop(); + uploader.splice(); + attachment.destroy(); + }, + + // Runs on videopress:insert event fired by our custom toolbar + insert: function( selection ) { + var guid = selection.models[0].get( 'vp_guid' ).replace( /[^a-zA-Z0-9]+/, '' ); + media.editor.insert( '[wpvideo ' + guid + ']' ); + return this; + }, + + // Triggered by the upload_videopress router item. + uploadVideo: function() { + this.content.set( new media.view.VideoPressUploader({ + controller: this + }) ); + return this; + }, + + // Create a custom toolbar + createToolbar: function( /*toolbar*/ ) { + // Alow an option to hide the toolbar. + if ( this.options.VideoPress && this.options.VideoPress.hideToolbar ) { + return this; + } + + var controller = this; + this.toolbar.set( new media.view.Toolbar({ + controller: this, + items: { + insert: { + style: 'primary', + text: VideoPress.l10n.insertVideoButton, + priority: 80, + requires: { + library: true, + selection: true + }, + + click: function() { + var state = controller.state(), + selection = state.get('selection'); + + controller.close(); + state.trigger( 'videopress:insert', selection ).reset(); + } + } + } + }) ); + } + }}); + + var MediaFrame = {}; + + /** + * Extend the selection media frame + */ + MediaFrame.Select = media.view.MediaFrame.Select; + media.view.MediaFrame.Select = MediaFrame.Select.extend({ + bindHandlers: function() { + MediaFrame.Select.prototype.bindHandlers.apply( this, arguments ); + + this.on( 'router:create:videopress', this.createRouter, this ); + this.on( 'router:activate:videopress', this.VideoPress.activate, this ); + this.on( 'router:deactivate:videopress', this.VideoPress.deactivate, this ); + + this.on( 'content:render:upload_videopress', this.VideoPress.uploadVideo, this ); + this.on( 'toolbar:create:videopress-toolbar', this.VideoPress.createToolbar, this ); + this.on( 'videopress:insert', this.VideoPress.insert, this ); + } + }); + + /** + * Extend the post editor media frame with our own + */ + MediaFrame.Post = media.view.MediaFrame.Post; + media.view.MediaFrame.Post = MediaFrame.Post.extend({ + createStates: function() { + MediaFrame.Post.prototype.createStates.apply( this, arguments ); + this.states.add([ new media.controller.VideoPress() ]); + } + }); + + /** + * A VideoPress Modal view that we can use to preview videos. + * Expects a controller object on render. + */ + var VideoPressModalView = Backbone.View.extend({ + 'className': 'videopress-modal-container', + 'template': wp.media.template( 'videopress-media-modal' ), + + // Render the VideoPress modal with a video object by guid. + render: function( controller ) { + this.delegateEvents( { + 'click .videopress-modal-close': 'closeModal', + 'click .videopress-modal-backdrop': 'closeModal' + } ); + + this.model = controller.model; + this.guid = this.model.get( 'vp_guid' ); + + if ( ! this.$frame ) { + this.$frame = $( '.media-frame-content' ); + } + + this.$el.html( this.template( { 'video' : this.model.get( 'vp_embed' ) } ) ); + this.$modal = this.$( '.videopress-modal' ); + this.$modal.hide(); + + this.$frame.append( this.$el ); + this.$modal.slideDown( 'fast' ); + + return this; + }, + + closeModal: function() { + var view = this; + this.$modal.slideUp( 'fast', function() { view.remove(); } ); + return this; + } + }); + + var VideoPressModal = new VideoPressModalView(); + + // Configuration screen behavior + $(document).on( 'ready', function() { + var $form = $( '#videopress-settings' ); + + // Not on a configuration screen + if ( ! $form.length ) { + return; + } + + var $access = $form.find( 'input[name="videopress-access"]' ), + $upload = $form.find( 'input[name="videopress-upload"]' ); + + $access.on( 'change', function() { + var access = $access.filter( ':checked' ).val(); + $upload.attr( 'disabled', ! access ); + + if ( ! access ) { + $upload.attr( 'checked', false ); + } + }); + + $access.trigger( 'change' ); + }); + + // Media -> VideoPress menu + $(document).on( 'click', '#videopress-browse', function() { + + wp.media({ + state: 'videopress', + states: [ new media.controller.VideoPress() ], + VideoPress: { hideToolbar: true } + }).open(); + + return false; + }); +})(jQuery); |