From 2d87f983adfc75587f544ab742619b380d9a8cdb Mon Sep 17 00:00:00 2001 From: Daryl Koopersmith Date: Mon, 12 Nov 2012 05:57:12 +0000 Subject: [PATCH] Media: Add Embed from URL tab. Note: There is a bug that currently blocks adding several successive embeds. see #21390. git-svn-id: http://core.svn.wordpress.org/trunk@22547 1a063a9b-81f0-0310-95a4-ce76da25c4cd --- wp-admin/includes/meta-boxes.php | 2 +- wp-admin/js/custom-background.js | 2 +- wp-admin/js/custom-header.js | 2 +- wp-admin/js/media-upload.js | 46 ++++ wp-includes/css/media-views.css | 124 +++++++++-- wp-includes/js/media-views.js | 348 ++++++++++++++++++++++++++++--- wp-includes/media.php | 63 +++++- 7 files changed, 539 insertions(+), 48 deletions(-) diff --git a/wp-admin/includes/meta-boxes.php b/wp-admin/includes/meta-boxes.php index f6dad298d6..fecc1d7915 100644 --- a/wp-admin/includes/meta-boxes.php +++ b/wp-admin/includes/meta-boxes.php @@ -1053,7 +1053,7 @@ function post_thumbnail_meta_box( $post ) { frame = wp.media( options ); frame.toolbar.on( 'activate:select', function() { - frame.toolbar.view().add({ + frame.toolbar.view().set({ select: { style: 'primary', text: update, diff --git a/wp-admin/js/custom-background.js b/wp-admin/js/custom-background.js index 08d09b1a17..7b0c7090ca 100644 --- a/wp-admin/js/custom-background.js +++ b/wp-admin/js/custom-background.js @@ -38,7 +38,7 @@ }); frame.toolbar.on( 'activate:select', function() { - frame.toolbar.view().add({ + frame.toolbar.view().set({ select: { style: 'primary', text: $el.data('update'), diff --git a/wp-admin/js/custom-header.js b/wp-admin/js/custom-header.js index 56fc7ec2e2..4cc55a71c0 100644 --- a/wp-admin/js/custom-header.js +++ b/wp-admin/js/custom-header.js @@ -25,7 +25,7 @@ }); frame.toolbar.on( 'activate:select', function() { - frame.toolbar.view().add({ + frame.toolbar.view().set({ select: { style: 'primary', text: $el.data('update'), diff --git a/wp-admin/js/media-upload.js b/wp-admin/js/media-upload.js index 5cb080ad3f..ed933cc699 100644 --- a/wp-admin/js/media-upload.js +++ b/wp-admin/js/media-upload.js @@ -144,6 +144,52 @@ var tb_position; this.insert( shortcode.string() ); }, this ); + workflow.get('embed').on( 'select', function() { + var embed = workflow.state().toJSON(), + options; + + if ( 'link' === embed.type ) { + this.insert( wp.html.string({ + tag: 'a', + content: embed.title || embed.url, + attrs: { + href: embed.url + } + }) ); + + } else if ( 'image' === embed.type ) { + _.defaults( embed, { + align: 'none', + url: '', + alt: '', + linkUrl: '', + link: 'none' + }); + + options = { + single: true, + tag: 'img', + attrs: { + 'class': 'align' + embed.align, + src: embed.url, + alt: embed.alt + } + }; + + if ( 'custom' === embed.link || 'file' === embed.link ) { + options = { + tag: 'a', + content: options, + attrs: { + href: 'custom' === embed.link ? embed.linkUrl : embed.url + } + }; + } + + this.insert( wp.html.string( options ) ); + } + }, this ); + return workflow; }, diff --git a/wp-includes/css/media-views.css b/wp-includes/css/media-views.css index 3a97321b2c..4e2b11ff9e 100644 --- a/wp-includes/css/media-views.css +++ b/wp-includes/css/media-views.css @@ -1,3 +1,20 @@ +/** + * Base Styles + */ + +.media-frame input, +.media-frame textarea { + padding: 6px 8px; + line-height: 16px; +} + +.media-frame select, +.wp-admin .media-frame select { + height: 28px; + line-height: 28px; + margin-top: 3px; +} + /** * Modal */ @@ -96,6 +113,7 @@ left: 200px; bottom: 0; border-width: 1px 0 0 0; + box-shadow: 0 -4px 4px -4px rgba( 0, 0, 0, 0.1 ); } .media-frame.hide-toolbar > .media-toolbar { @@ -216,14 +234,6 @@ text-shadow: 0 1px 0 #fff; } -.media-sidebar .setting input, -.media-sidebar .setting textarea, -.compat-item .field input, -.compat-item .field textarea { - padding: 6px 8px; - line-height: 16px; -} - .media-sidebar .setting input, .media-sidebar .setting textarea { width: 65%; @@ -236,10 +246,7 @@ resize: none; } -.media-sidebar .setting select, -.compat-item .field select { - height: 28px; - line-height: 28px; +.media-sidebar select { margin-top: 3px; } @@ -984,4 +991,97 @@ .gallery-settings { overflow: hidden; +} + +/** + * Embed from URL + */ +.embed-url { + display: block; + position: relative; + height: 75px; + padding: 16px 16px; + margin: 0; + z-index: 50; + border-bottom: 1px solid #dfdfdf; + box-shadow: 0 4px 4px -4px rgba( 0, 0, 0, 0.1 ); + font-size: 18px; + font-weight: 200; +} + +.embed-url span { + display: block; + padding: 4px 0 6px 2px; +} + +.embed-url input { + padding: 12px 14px; + width: 100%; + min-width: 200px; + /*max-width: 500px;*/ + box-shadow: inset 2px 2px 4px -2px rgba( 0, 0, 0, 0.1 ); +} + +.embed-link-settings, +.embed-image-settings { + position: absolute; + background: #f5f5f5; + top: 108px; + left: 0; + right: 0; + bottom: 0; + padding: 16px 16px 32px; + overflow: auto; +} + +.media-embed .thumbnail { + max-width: 100%; + max-height: 200px; + position: relative; + float: left; +} + +.media-embed .thumbnail img { + max-height: 200px; +} + +.media-embed .thumbnail:after { + content: ''; + display: block; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + box-shadow: inset 0 0 0 1px rgba( 0, 0, 0, 0.1 ); + overflow: hidden; +} + +.media-embed .setting { + width: 100%; + margin-top: 10px; + float: left; + display: block; + clear: both; +} + +.media-embed .setting span { + display: block; + width: 200px; + font-size: 13px; + line-height: 24px; + color: #999; + text-shadow: 0 1px 0 #fff; +} + +.media-embed .setting .button-group { + margin: 2px 0; +} + +.media-embed .setting input, +.media-embed .setting textarea { + display: block; + width: 100%; + max-width: 400px; + margin: 1px 0; } \ No newline at end of file diff --git a/wp-includes/js/media-views.js b/wp-includes/js/media-views.js index 8265f9d2af..8594f0738a 100644 --- a/wp-includes/js/media-views.js +++ b/wp-includes/js/media-views.js @@ -316,17 +316,19 @@ }, deactivate: function() { - this.off( 'change:library change:exclude', this.buildComposite, this ); - this.off( 'change:excludeState', this._excludeState, this ); - this.destroyComposite(); - - wp.Uploader.queue.off( 'add', this.selectUpload, this ); + this.off( 'change:empty', this.refresh, this ); + this.get('library').off( 'add remove reset', this._updateEmpty, this ); // Unbind all event handlers that use this state as the context // from the selection. this.get('selection').off( null, null, this ); - this.get('library').off( 'add remove reset', this._updateEmpty, this ); - this.off( 'change:empty', this.refresh, this ); + + wp.Uploader.queue.off( 'add', this.selectUpload, this ); + + this.off( 'change:excludeState', this._excludeState, this ); + this.off( 'change:library change:exclude', this.buildComposite, this ); + + this.destroyComposite(); }, reset: function() { @@ -525,6 +527,44 @@ } }); + + // wp.media.controller.Embed + // ------------------------- + media.controller.Embed = media.controller.State.extend({ + defaults: { + id: 'embed', + url: '', + menu: 'main', + content: 'embed', + toolbar: 'main-embed', + type: 'link' + }, + + initialize: function() { + this.on( 'change:url', this.scan, this ); + media.controller.State.prototype.initialize.apply( this, arguments ); + }, + + scan: function() { + var attributes = { type: 'link' }; + + if ( /(?:jpe?g|png|gif)$/i.test( this.get('url') ) ) + attributes.type = 'image'; + + this.trigger( 'scan', attributes ); + this.set( attributes ); + }, + + reset: function() { + _.each( _.without( _.keys( this.attributes ), _.keys( this.defaults ) ), function( key ) { + this.unset( key ); + }, this ); + + this.set( 'url', '' ); + this.frame.toolbar.view().refresh(); + } + }); + /** * ======================================================================== * VIEWS @@ -858,9 +898,17 @@ }, // Toolbars - selectToolbar: function() { + selectToolbar: function( options ) { + options = _.defaults( options || {}, { + event: 'select', + silent: false, + state: false + }); + this.toolbar.view( new media.view.Toolbar({ controller: this, + silent: options.silent, + items: { select: { style: 'primary', @@ -871,8 +919,10 @@ var controller = this.controller; controller.close(); - controller.state().trigger('select'); - controller.reset().state( controller.options.state ); + controller.state().trigger( options.event ); + controller.reset(); + if ( options.state ) + controller.state( options.state ); } } } @@ -941,6 +991,9 @@ new media.controller.Upload( main ), + // Embed states. + new media.controller.Embed(), + // Gallery states. new media.controller.Gallery({ library: options.selection, @@ -991,7 +1044,7 @@ }, content: { - embed: 'embedContent' + embed: 'embedContent' }, sidebar: { @@ -1121,12 +1174,24 @@ }, // Content - embedContent: function() {}, + embedContent: function() { + var view = new media.view.Embed({ + controller: this, + model: this.state() + }).render(); + + this.$el.addClass('hide-sidebar'); + this.content.view( view ); + view.url.focus(); + }, // Sidebars onSidebarGallerySettings: function( options ) { var library = this.state().get('library'); + if ( ! library ) + return; + library.gallery = library.gallery || new Backbone.Model(); this.sidebar.view().add({ @@ -1169,7 +1234,13 @@ }) ); }, - mainEmbedToolbar: function() {}, + mainEmbedToolbar: function() { + this.toolbar.view( new media.view.Toolbar.Embed({ + controller: this + }) ); + + this.$el.removeClass('hide-toolbar'); + }, batchEditToolbar: function() { this.toolbar.view( new media.view.Toolbar({ @@ -1495,7 +1566,7 @@ this.$secondary = $('
').prependTo( this.$el ); if ( this.options.items ) - this.add( this.options.items, { silent: true }); + this.set( this.options.items, { silent: true }); if ( ! this.options.silent ) this.render(); @@ -1503,6 +1574,15 @@ destroy: function() { this.remove(); + + if ( this.model ) + this.model.off( null, null, this ); + + if ( this.collection ) + this.collection.off( null, null, this ); + + this.controller.off( null, null, this ); + _.each( this._views, function( view ) { if ( view.destroy ) view.destroy(); @@ -1527,28 +1607,26 @@ return this; }, - add: function( id, view, options ) { + set: function( id, view, options ) { options = options || {}; // Accept an object with an `id` : `view` mapping. if ( _.isObject( id ) ) { _.each( id, function( view, id ) { - this.add( id, view, { silent: true }); + this.set( id, view, { silent: true }); }, this ); - if ( ! options.silent ) - this.render(); - return this; + } else { + if ( ! ( view instanceof Backbone.View ) ) { + view.classes = [ id ].concat( view.classes || [] ); + view = new media.view.Button( view ).render(); + } + + view.controller = view.controller || this.controller; + + this._views[ id ] = view; } - if ( ! ( view instanceof Backbone.View ) ) { - view.classes = [ id ].concat( view.classes || [] ); - view = new media.view.Button( view ).render(); - } - - view.controller = view.controller || this.controller; - - this._views[ id ] = view; if ( ! options.silent ) this.render(); return this; @@ -1558,7 +1636,7 @@ return this._views[ id ]; }, - remove: function( id, options ) { + unset: function( id, options ) { delete this._views[ id ]; if ( ! options || ! options.silent ) this.render(); @@ -1568,8 +1646,76 @@ refresh: function() {} }); + // wp.media.view.Toolbar.Select + // ---------------------------- + media.view.Toolbar.Select = media.view.Toolbar.extend({ + initialize: function() { + var options = this.options, + controller = options.controller, + selection = controller.state().get('selection'); + + _.bindAll( this, 'clickSelect' ); + + _.defaults( options, { + event: 'select', + state: false, + reset: true, + close: true, + text: l10n.select + }); + + options.items = _.defaults( options.items || {}, { + select: { + style: 'primary', + text: options.text, + priority: 80, + click: this.clickSelect + } + }); + + media.view.Toolbar.prototype.initialize.apply( this, arguments ); + }, + + clickSelect: function() { + var options = this.options, + controller = this.controller; + + if ( options.close ) + controller.close(); + + if ( options.event ) + controller.state().trigger( options.event ); + + if ( options.reset ) + controller.reset(); + + if ( options.state ) + controller.state( options.state ); + } + }); + + // wp.media.view.Toolbar.Embed + // --------------------------- + media.view.Toolbar.Embed = media.view.Toolbar.Select.extend({ + initialize: function() { + var controller = this.options.controller; + + _.defaults( this.options, { + text: l10n.insertEmbed + }); + + media.view.Toolbar.Select.prototype.initialize.apply( this, arguments ); + controller.on( 'change:url', this.refresh, this ); + }, + + refresh: function() { + var url = this.controller.state().get('url'); + this.get('select').model.set( 'disabled', ! url || /^https?:\/\/$/.test(url) ); + } + }); + // wp.media.view.Toolbar.Insert - // --------------------------------- + // ---------------------------- media.view.Toolbar.Insert = media.view.Toolbar.extend({ initialize: function() { var controller = this.options.controller, @@ -1886,6 +2032,7 @@ click: function() { var options = this.options; + if ( options.click ) options.click.call( this ); else if ( options.id ) @@ -2344,7 +2491,7 @@ }); if ( this.options.search ) { - this.toolbar.add( 'search', new media.view.Search({ + this.toolbar.set( 'search', new media.view.Search({ controller: this.controller, model: this.collection.props, priority: -60 @@ -2352,7 +2499,7 @@ } if ( this.options.sortable ) { - this.toolbar.add( 'dragInfo', new Backbone.View({ + this.toolbar.set( 'dragInfo', new Backbone.View({ el: $( '
' + l10n.dragInfo + '
' )[0], priority: -40 }) ); @@ -2564,6 +2711,11 @@ } else if ( $setting.hasClass('button-group') ) { $buttons = $setting.find('button').removeClass('active'); $buttons.filter( '[value="' + value + '"]' ).addClass('active'); + + // Handle text inputs and textareas. + } else if ( $setting.is('input[type="text"], textarea') ) { + if ( ! $setting.is(':focus') ) + $setting.val( value ); } }, @@ -2718,4 +2870,138 @@ return this; } }); + + /** + * wp.media.view.Embed + */ + media.view.Embed = Backbone.View.extend({ + className: 'media-embed', + + initialize: function() { + this.controller = this.options.controller; + + this.url = new media.view.EmbedUrl({ + controller: this.controller, + model: this.model + }).render(); + + this._settings = new Backbone.View(); + this.refresh(); + this.model.on( 'change:type', this.refresh, this ); + }, + + render: function() { + this.$el.html([ this.url.el, this._settings.el ]); + this.url.focus(); + return this; + }, + + settings: function( view ) { + view.render(); + this._settings.$el.replaceWith( view.$el ); + if ( this._settings.destroy ) + this._settings.destroy(); + this._settings.remove(); + this._settings = view; + }, + + refresh: function() { + var type = this.model.get('type'), + constructor; + + if ( 'image' === type ) + constructor = media.view.EmbedImage; + else if ( 'link' === type ) + constructor = media.view.EmbedLink; + else + return; + + this.settings( new constructor({ + controller: this.controller, + model: this.model, + priority: 40 + }) ); + } + }); + + /** + * wp.media.view.EmbedUrl + */ + media.view.EmbedUrl = Backbone.View.extend({ + tagName: 'label', + className: 'embed-url', + + events: { + 'keyup': 'url' + }, + + initialize: function() { + this.label = this.make( 'span', null, this.options.label || l10n.url ); + this.input = this.make( 'input', { + type: 'text', + value: this.model.get('url') || '' + }); + + this.$label = $( this.label ); + this.$input = $( this.input ); + this.$el.append([ this.label, this.input ]); + + this.model.on( 'change:url', this.render, this ); + }, + + destroy: function() { + this.model.off( null, null, this ); + }, + + render: function() { + var $input = this.$input; + + if ( $input.is(':focus') ) + return; + + this.input.value = this.model.get('url') || 'http://'; + return this; + }, + + url: function( event ) { + this.model.set( 'url', event.target.value ); + }, + + focus: function() { + var $input = this.$input; + // If the input is visible, focus and select its contents. + if ( $input.is(':visible') ) + $input.focus()[0].select(); + } + }); + + /** + * wp.media.view.EmbedLink + */ + media.view.EmbedLink = media.view.Settings.extend({ + className: 'embed-link-settings', + template: media.template('embed-link-settings') + }); + + /** + * wp.media.view.EmbedImage + */ + media.view.EmbedImage = media.view.Settings.AttachmentDisplay.extend({ + className: 'embed-image-settings', + template: media.template('embed-image-settings'), + + initialize: function() { + media.view.Settings.AttachmentDisplay.prototype.initialize.apply( this, arguments ); + this.model.on( 'change:url', this.updateImage, this ); + }, + + destroy: function() { + this.model.off( null, null, this ); + media.view.Settings.AttachmentDisplay.prototype.destroy.apply( this, arguments ); + }, + + updateImage: function() { + this.$('img').attr( 'src', this.model.get('url') ); + } + }); }(jQuery)); \ No newline at end of file diff --git a/wp-includes/media.php b/wp-includes/media.php index 9b8ae4427d..6197621410 100644 --- a/wp-includes/media.php +++ b/wp-includes/media.php @@ -1326,6 +1326,7 @@ function wp_enqueue_media() { 'settings' => $settings, // Generic + 'url' => __( 'URL' ), 'insertMedia' => __( 'Insert Media' ), 'search' => __( 'Search' ), 'select' => __( 'Select' ), @@ -1346,6 +1347,7 @@ function wp_enqueue_media() { // Embed 'embedFromUrlTitle' => __( 'Embed From URL' ), + 'insertEmbed' => __( 'Insert embed' ), // Batch 'batchInsert' => __( 'Batch insert' ), @@ -1570,7 +1572,7 @@ function wp_print_media_templates( $attachment ) {
- <# if ( ! _.isUndefined( sizes ) ) { #> + <# if ( 'undefined' !== typeof sizes ) { #>