Media: Add Embed from URL tab.

Note: There is a bug that currently blocks adding several successive embeds.

see #21390.


git-svn-id: https://develop.svn.wordpress.org/trunk@22547 602fd350-edb4-49c9-b593-d223f7449a82
This commit is contained in:
Daryl Koopersmith 2012-11-12 05:57:12 +00:00
parent ac3a589c8d
commit f6a737ed34
7 changed files with 539 additions and 48 deletions

View File

@ -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,

View File

@ -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'),

View File

@ -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'),

View File

@ -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;
},

View File

@ -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;
}

View File

@ -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 = $('<div class="media-toolbar-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: $( '<div class="instructions">' + l10n.dragInfo + '</div>' )[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));

View File

@ -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 ) {
<input type="text" class="link-to-custom" data-setting="linkUrl" />
</div>
<# if ( ! _.isUndefined( sizes ) ) { #>
<# if ( 'undefined' !== typeof sizes ) { #>
<label class="setting">
<span><?php _e('Size'); ?></span>
<select class="size" name="size"
@ -1587,7 +1589,7 @@ function wp_print_media_templates( $attachment ) {
) );
foreach ( $sizes as $value => $name ) : ?>
<# if ( ! _.isUndefined( sizes['<?php echo esc_js( $value ); ?>'] ) ) { #>
<# if ( sizes['<?php echo esc_js( $value ); ?>'] ) { #>
<option value="<?php echo esc_attr( $value ); ?>" <?php selected( $value, 'medium' ); ?>>
<?php echo esc_html( $name ); ?>
</option>
@ -1635,6 +1637,63 @@ function wp_print_media_templates( $attachment ) {
</label>
</script>
<script type="text/html" id="tmpl-embed-link-settings">
<label class="setting">
<span><?php _e('Title'); ?></span>
<input type="text" class="alignment" data-setting="title" />
</label>
</script>
<script type="text/html" id="tmpl-embed-image-settings">
<div class="thumbnail">
<img src="{{ model.url }}" draggable="false" />
</div>
<label class="setting caption">
<span><?php _e('Caption'); ?></span>
<textarea data-setting="caption" />
</label>
<label class="setting alt-text">
<span><?php _e('Alt Text'); ?></span>
<input type="text" data-setting="alt" />
</label>
<label class="setting align">
<span><?php _e('Align'); ?></span>
<div class="button-group button-large" data-setting="align">
<button class="button" value="left">
<?php esc_attr_e('Left'); ?>
</button>
<button class="button" value="center">
<?php esc_attr_e('Center'); ?>
</button>
<button class="button" value="right">
<?php esc_attr_e('Right'); ?>
</button>
<button class="button active" value="none">
<?php esc_attr_e('None'); ?>
</button>
</div>
</label>
<div class="setting link-to">
<span><?php _e('Link To'); ?></span>
<div class="button-group button-large" data-setting="link">
<button class="button" value="file">
<?php esc_attr_e('Image URL'); ?>
</button>
<button class="button" value="custom">
<?php esc_attr_e('Custom URL'); ?>
</button>
<button class="button active" value="none">
<?php esc_attr_e('None'); ?>
</button>
</div>
<input type="text" class="link-to-custom" data-setting="linkUrl" />
</div>
</script>
<script type="text/html" id="tmpl-editor-attachment">
<div class="editor-attachment-preview">
<# if ( url ) { #>