Update mce-view.js and the `wpview` TinyMCE plugin, and use them to show gallery previews in the Visual editor, props gcorne, see #26959
Built from https://develop.svn.wordpress.org/trunk@27408 git-svn-id: http://core.svn.wordpress.org/trunk@27255 1a063a9b-81f0-0310-95a4-ce76da25c4cd
This commit is contained in:
parent
0e9ef2491c
commit
6074dfa272
|
@ -242,6 +242,7 @@ final class _WP_Editors {
|
|||
'wpgallery',
|
||||
'wplink',
|
||||
'wpdialogs',
|
||||
'wpview',
|
||||
) ) );
|
||||
|
||||
if ( ( $key = array_search( 'spellchecker', $plugins ) ) !== false ) {
|
||||
|
@ -501,6 +502,9 @@ final class _WP_Editors {
|
|||
if ( self::$has_medialib ) {
|
||||
add_thickbox();
|
||||
wp_enqueue_script('media-upload');
|
||||
|
||||
if ( self::$has_tinymce )
|
||||
wp_enqueue_script('mce-view');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,173 +1,95 @@
|
|||
/* global tinymce */
|
||||
|
||||
// Ensure the global `wp` object exists.
|
||||
window.wp = window.wp || {};
|
||||
|
||||
(function($){
|
||||
var views = {},
|
||||
instances = {};
|
||||
instances = {},
|
||||
media = wp.media,
|
||||
viewOptions = ['encodedText'];
|
||||
|
||||
// Create the `wp.mce` object if necessary.
|
||||
wp.mce = wp.mce || {};
|
||||
|
||||
// wp.mce.view
|
||||
// -----------
|
||||
// A set of utilities that simplifies adding custom UI within a TinyMCE editor.
|
||||
// At its core, it serves as a series of converters, transforming text to a
|
||||
// custom UI, and back again.
|
||||
wp.mce.view = {
|
||||
// ### defaults
|
||||
defaults: {
|
||||
// The default properties used for objects with the `pattern` key in
|
||||
// `wp.mce.view.add()`.
|
||||
pattern: {
|
||||
view: Backbone.View,
|
||||
text: function( instance ) {
|
||||
return instance.options.original;
|
||||
},
|
||||
/**
|
||||
* wp.mce.View
|
||||
*
|
||||
* A Backbone-like View constructor intended for use when rendering a TinyMCE View. The main difference is
|
||||
* that the TinyMCE View is not tied to a particular DOM node.
|
||||
*/
|
||||
wp.mce.View = function( options ) {
|
||||
options || (options = {});
|
||||
_.extend(this, _.pick(options, viewOptions));
|
||||
this.initialize.apply(this, arguments);
|
||||
};
|
||||
|
||||
toView: function( content ) {
|
||||
if ( ! this.pattern )
|
||||
return;
|
||||
|
||||
this.pattern.lastIndex = 0;
|
||||
var match = this.pattern.exec( content );
|
||||
|
||||
if ( ! match )
|
||||
return;
|
||||
|
||||
return {
|
||||
index: match.index,
|
||||
content: match[0],
|
||||
options: {
|
||||
original: match[0],
|
||||
results: match
|
||||
}
|
||||
};
|
||||
_.extend( wp.mce.View.prototype, {
|
||||
initialize: function() {},
|
||||
html: function() {},
|
||||
render: function() {
|
||||
var html = this.getHtml();
|
||||
// Search all tinymce editor instances and update the placeholders
|
||||
_.each( tinymce.editors, function( editor ) {
|
||||
var doc;
|
||||
if ( editor.plugins.wpview ) {
|
||||
doc = editor.getDoc();
|
||||
$( doc ).find( '[data-wpview-text="' + this.encodedText + '"]' ).html( html );
|
||||
}
|
||||
},
|
||||
}, this );
|
||||
}
|
||||
} );
|
||||
|
||||
// The default properties used for objects with the `shortcode` key in
|
||||
// `wp.mce.view.add()`.
|
||||
shortcode: {
|
||||
view: Backbone.View,
|
||||
text: function( instance ) {
|
||||
return instance.options.shortcode.string();
|
||||
},
|
||||
// take advantage of the Backbone extend method
|
||||
wp.mce.View.extend = Backbone.View.extend;
|
||||
|
||||
toView: function( content ) {
|
||||
var match = wp.shortcode.next( this.shortcode, content );
|
||||
/**
|
||||
* wp.mce.views
|
||||
*
|
||||
* A set of utilities that simplifies adding custom UI within a TinyMCE editor.
|
||||
* At its core, it serves as a series of converters, transforming text to a
|
||||
* custom UI, and back again.
|
||||
*/
|
||||
wp.mce.views = {
|
||||
|
||||
if ( ! match )
|
||||
return;
|
||||
|
||||
return {
|
||||
index: match.index,
|
||||
content: match.content,
|
||||
options: {
|
||||
shortcode: match.shortcode
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
/**
|
||||
* wp.mce.views.register( type, view )
|
||||
*
|
||||
* Registers a new TinyMCE view.
|
||||
*
|
||||
* @param type
|
||||
* @param constructor
|
||||
*
|
||||
*/
|
||||
register: function( type, constructor ) {
|
||||
views[ type ] = constructor;
|
||||
},
|
||||
|
||||
// ### add( id, options )
|
||||
// Registers a new TinyMCE view.
|
||||
//
|
||||
// Accepts a unique `id` and an `options` object.
|
||||
//
|
||||
// `options` accepts the following properties:
|
||||
//
|
||||
// * `pattern` is the regular expression used to scan the content and
|
||||
// detect matching views.
|
||||
//
|
||||
// * `view` is a `Backbone.View` constructor. If a plain object is
|
||||
// provided, it will automatically extend the parent constructor
|
||||
// (usually `Backbone.View`). Views are instantiated when the `pattern`
|
||||
// is successfully matched. The instance's `options` object is provided
|
||||
// with the `original` matched value, the match `results` including
|
||||
// capture groups, and the `viewType`, which is the constructor's `id`.
|
||||
//
|
||||
// * `extend` an existing view by passing in its `id`. The current
|
||||
// view will inherit all properties from the parent view, and if
|
||||
// `view` is set to a plain object, it will extend the parent `view`
|
||||
// constructor.
|
||||
//
|
||||
// * `text` is a method that accepts an instance of the `view`
|
||||
// constructor and transforms it into a text representation.
|
||||
add: function( id, options ) {
|
||||
var parent, remove, base, properties;
|
||||
|
||||
// Fetch the parent view or the default options.
|
||||
if ( options.extend )
|
||||
parent = wp.mce.view.get( options.extend );
|
||||
else if ( options.shortcode )
|
||||
parent = wp.mce.view.defaults.shortcode;
|
||||
else
|
||||
parent = wp.mce.view.defaults.pattern;
|
||||
|
||||
// Extend the `options` object with the parent's properties.
|
||||
_.defaults( options, parent );
|
||||
options.id = id;
|
||||
|
||||
// Create properties used to enhance the view for use in TinyMCE.
|
||||
properties = {
|
||||
// Ensure the wrapper element and references to the view are
|
||||
// removed. Otherwise, removed views could randomly restore.
|
||||
remove: function() {
|
||||
delete instances[ this.el.id ];
|
||||
this.$el.parent().remove();
|
||||
|
||||
// Trigger the inherited `remove` method.
|
||||
if ( remove )
|
||||
remove.apply( this, arguments );
|
||||
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
// If the `view` provided was an object, use the parent's
|
||||
// `view` constructor as a base. If a `view` constructor
|
||||
// was provided, treat that as the base.
|
||||
if ( _.isFunction( options.view ) ) {
|
||||
base = options.view;
|
||||
} else {
|
||||
base = parent.view;
|
||||
remove = options.view.remove;
|
||||
_.defaults( properties, options.view );
|
||||
}
|
||||
|
||||
// If there's a `remove` method on the `base` view that wasn't
|
||||
// created by this method, inherit it.
|
||||
if ( ! remove && ! base._mceview )
|
||||
remove = base.prototype.remove;
|
||||
|
||||
// Automatically create the new `Backbone.View` constructor.
|
||||
options.view = base.extend( properties, {
|
||||
// Flag that the new view has been created by `wp.mce.view`.
|
||||
_mceview: true
|
||||
});
|
||||
|
||||
views[ id ] = options;
|
||||
/**
|
||||
* wp.mce.views.get( id )
|
||||
*
|
||||
* Returns a TinyMCE view constructor.
|
||||
*/
|
||||
get: function( type ) {
|
||||
return views[ type ];
|
||||
},
|
||||
|
||||
// ### get( id )
|
||||
// Returns a TinyMCE view options object.
|
||||
get: function( id ) {
|
||||
return views[ id ];
|
||||
/**
|
||||
* wp.mce.views.unregister( type )
|
||||
*
|
||||
* Unregisters a TinyMCE view.
|
||||
*/
|
||||
unregister: function( type ) {
|
||||
delete views[ type ];
|
||||
},
|
||||
|
||||
// ### remove( id )
|
||||
// Unregisters a TinyMCE view.
|
||||
remove: function( id ) {
|
||||
delete views[ id ];
|
||||
},
|
||||
|
||||
// ### toViews( content )
|
||||
// Scans a `content` string for each view's pattern, replacing any
|
||||
// matches with wrapper elements, and creates a new view instance for
|
||||
// every match.
|
||||
//
|
||||
// To render the views, call `wp.mce.view.render( scope )`.
|
||||
/**
|
||||
* toViews( content )
|
||||
* Scans a `content` string for each view's pattern, replacing any
|
||||
* matches with wrapper elements, and creates a new instance for
|
||||
* every match, which triggers the related data to be fetched.
|
||||
*
|
||||
*/
|
||||
toViews: function( content ) {
|
||||
var pieces = [ { content: content } ],
|
||||
current;
|
||||
|
@ -190,12 +112,13 @@ window.wp = window.wp || {};
|
|||
// and slicing the string as we go.
|
||||
while ( remaining && (result = view.toView( remaining )) ) {
|
||||
// Any text before the match becomes an unprocessed piece.
|
||||
if ( result.index )
|
||||
if ( result.index ) {
|
||||
pieces.push({ content: remaining.substring( 0, result.index ) });
|
||||
}
|
||||
|
||||
// Add the processed piece for the match.
|
||||
pieces.push({
|
||||
content: wp.mce.view.toView( viewType, result.options ),
|
||||
content: wp.mce.views.toView( viewType, result.content, result.options ),
|
||||
processed: true
|
||||
});
|
||||
|
||||
|
@ -205,145 +128,178 @@ window.wp = window.wp || {};
|
|||
|
||||
// There are no additional matches. If any content remains,
|
||||
// add it as an unprocessed piece.
|
||||
if ( remaining )
|
||||
if ( remaining ) {
|
||||
pieces.push({ content: remaining });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return _.pluck( pieces, 'content' ).join('');
|
||||
},
|
||||
|
||||
toView: function( viewType, options ) {
|
||||
var view = wp.mce.view.get( viewType ),
|
||||
instance, id;
|
||||
/**
|
||||
* Create a placeholder for a particular view type
|
||||
*
|
||||
* @param viewType
|
||||
* @param text
|
||||
* @param options
|
||||
*
|
||||
*/
|
||||
toView: function( viewType, text, options ) {
|
||||
var view = wp.mce.views.get( viewType ),
|
||||
encodedText = window.encodeURIComponent( text ),
|
||||
instance, viewOptions;
|
||||
|
||||
if ( ! view )
|
||||
return '';
|
||||
|
||||
// Create a new view instance.
|
||||
instance = new view.view( _.extend( options || {}, {
|
||||
viewType: viewType
|
||||
}) );
|
||||
if ( ! view ) {
|
||||
return text;
|
||||
}
|
||||
|
||||
// Use the view's `id` if it already exists. Otherwise,
|
||||
// create a new `id`.
|
||||
id = instance.el.id = instance.el.id || _.uniqueId('__wpmce-');
|
||||
instances[ id ] = instance;
|
||||
|
||||
// Create a dummy `$wrapper` property to allow `$wrapper` to be
|
||||
// called in the view's `render` method without a conditional.
|
||||
instance.$wrapper = $();
|
||||
if ( ! wp.mce.views.getInstance( encodedText ) ) {
|
||||
viewOptions = options;
|
||||
viewOptions.encodedText = encodedText;
|
||||
instance = new view.View( viewOptions );
|
||||
instances[ encodedText ] = instance;
|
||||
}
|
||||
|
||||
return wp.html.string({
|
||||
// If the view is a span, wrap it in a span.
|
||||
tag: 'span' === instance.tagName ? 'span' : 'div',
|
||||
tag: 'div',
|
||||
|
||||
attrs: {
|
||||
'class': 'wp-view-wrap wp-view-type-' + viewType,
|
||||
'data-wp-view': id,
|
||||
'contenteditable': false
|
||||
}
|
||||
'class': 'wpview-wrap wpview-type-' + viewType,
|
||||
'data-wpview-text': encodedText,
|
||||
'data-wpview-type': viewType,
|
||||
'contenteditable': 'false'
|
||||
},
|
||||
|
||||
content: '\u00a0'
|
||||
});
|
||||
},
|
||||
|
||||
// ### render( scope )
|
||||
// Renders any view instances inside a DOM node `scope`.
|
||||
//
|
||||
// View instances are detected by the presence of wrapper elements.
|
||||
// To generate wrapper elements, pass your content through
|
||||
// `wp.mce.view.toViews( content )`.
|
||||
render: function( scope ) {
|
||||
$( '.wp-view-wrap', scope ).each( function() {
|
||||
var wrapper = $(this),
|
||||
view = wp.mce.view.instance( this );
|
||||
/**
|
||||
* Refresh views after an update is made
|
||||
*
|
||||
* @param view {object} being refreshed
|
||||
* @param text {string} textual representation of the view
|
||||
*/
|
||||
refreshView: function( view, text ) {
|
||||
var encodedText = window.encodeURIComponent( text ),
|
||||
viewOptions,
|
||||
result, instance;
|
||||
|
||||
if ( ! view )
|
||||
return;
|
||||
instance = wp.mce.views.getInstance( encodedText );
|
||||
|
||||
// Link the real wrapper to the view.
|
||||
view.$wrapper = wrapper;
|
||||
// Render the view.
|
||||
view.render();
|
||||
// Detach the view element to ensure events are not unbound.
|
||||
view.$el.detach();
|
||||
if ( ! instance ) {
|
||||
result = view.toView( text );
|
||||
viewOptions = result.options;
|
||||
viewOptions.encodedText = encodedText;
|
||||
instance = new view.View( viewOptions );
|
||||
instances[ encodedText ] = instance;
|
||||
}
|
||||
|
||||
// Empty the wrapper, attach the view element to the wrapper,
|
||||
// and add an ending marker to the wrapper to help regexes
|
||||
// scan the HTML string.
|
||||
wrapper.empty().append( view.el ).append('<span data-wp-view-end class="wp-view-end"></span>');
|
||||
});
|
||||
wp.mce.views.render();
|
||||
},
|
||||
|
||||
// ### toText( content )
|
||||
// Scans an HTML `content` string and replaces any view instances with
|
||||
// their respective text representations.
|
||||
toText: function( content ) {
|
||||
return content.replace( /<(?:div|span)[^>]+data-wp-view="([^"]+)"[^>]*>.*?<span[^>]+data-wp-view-end[^>]*><\/span><\/(?:div|span)>/g, function( match, id ) {
|
||||
var instance = instances[ id ],
|
||||
view;
|
||||
|
||||
if ( instance )
|
||||
view = wp.mce.view.get( instance.options.viewType );
|
||||
|
||||
return instance && view ? view.text( instance ) : '';
|
||||
});
|
||||
getInstance: function( encodedText ) {
|
||||
return instances[ encodedText ];
|
||||
},
|
||||
|
||||
// ### Remove internal TinyMCE attributes.
|
||||
removeInternalAttrs: function( attrs ) {
|
||||
var result = {};
|
||||
_.each( attrs, function( value, attr ) {
|
||||
if ( -1 === attr.indexOf('data-mce') )
|
||||
result[ attr ] = value;
|
||||
});
|
||||
return result;
|
||||
/**
|
||||
* render( scope )
|
||||
*
|
||||
* Renders any view instances inside a DOM node `scope`.
|
||||
*
|
||||
* View instances are detected by the presence of wrapper elements.
|
||||
* To generate wrapper elements, pass your content through
|
||||
* `wp.mce.view.toViews( content )`.
|
||||
*/
|
||||
render: function() {
|
||||
_.each( instances, function( instance ) {
|
||||
instance.render();
|
||||
} );
|
||||
},
|
||||
|
||||
// ### Parse an attribute string and removes internal TinyMCE attributes.
|
||||
attrs: function( content ) {
|
||||
return wp.mce.view.removeInternalAttrs( wp.html.attrs( content ) );
|
||||
},
|
||||
edit: function( node ) {
|
||||
var viewType = $( node ).data('wpview-type'),
|
||||
view = wp.mce.views.get( viewType );
|
||||
|
||||
// ### instance( scope )
|
||||
//
|
||||
// Accepts a MCE view wrapper `node` (i.e. a node with the
|
||||
// `wp-view-wrap` class).
|
||||
instance: function( node ) {
|
||||
var id = $( node ).data('wp-view');
|
||||
|
||||
if ( id )
|
||||
return instances[ id ];
|
||||
},
|
||||
|
||||
// ### Select a view.
|
||||
//
|
||||
// Accepts a MCE view wrapper `node` (i.e. a node with the
|
||||
// `wp-view-wrap` class).
|
||||
select: function( node ) {
|
||||
var $node = $(node);
|
||||
|
||||
// Bail if node is already selected.
|
||||
if ( $node.hasClass('selected') )
|
||||
return;
|
||||
|
||||
$node.addClass('selected');
|
||||
$( node.firstChild ).trigger('select');
|
||||
},
|
||||
|
||||
// ### Deselect a view.
|
||||
//
|
||||
// Accepts a MCE view wrapper `node` (i.e. a node with the
|
||||
// `wp-view-wrap` class).
|
||||
deselect: function( node ) {
|
||||
var $node = $(node);
|
||||
|
||||
// Bail if node is already selected.
|
||||
if ( ! $node.hasClass('selected') )
|
||||
return;
|
||||
|
||||
$node.removeClass('selected');
|
||||
$( node.firstChild ).trigger('deselect');
|
||||
if ( view ) {
|
||||
view.edit( node );
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}(jQuery));
|
||||
wp.mce.gallery = {
|
||||
shortcode: 'gallery',
|
||||
toView: function( content ) {
|
||||
var match = wp.shortcode.next( this.shortcode, content );
|
||||
|
||||
if ( ! match ) {
|
||||
return;
|
||||
}
|
||||
|
||||
return {
|
||||
index: match.index,
|
||||
content: match.content,
|
||||
options: {
|
||||
shortcode: match.shortcode
|
||||
}
|
||||
};
|
||||
},
|
||||
View: wp.mce.View.extend({
|
||||
className: 'editor-gallery',
|
||||
template: media.template('editor-gallery'),
|
||||
|
||||
// The fallback post ID to use as a parent for galleries that don't
|
||||
// specify the `ids` or `include` parameters.
|
||||
//
|
||||
// Uses the hidden input on the edit posts page by default.
|
||||
postID: $('#post_ID').val(),
|
||||
|
||||
initialize: function( options ) {
|
||||
this.shortcode = options.shortcode;
|
||||
this.fetch();
|
||||
},
|
||||
|
||||
fetch: function() {
|
||||
this.attachments = wp.media.gallery.attachments( this.shortcode, this.postID );
|
||||
this.attachments.more().done( _.bind( this.render, this ) );
|
||||
},
|
||||
|
||||
getHtml: function() {
|
||||
var attrs = this.shortcode.attrs.named,
|
||||
options;
|
||||
|
||||
if ( ! this.attachments.length ) {
|
||||
return;
|
||||
}
|
||||
|
||||
options = {
|
||||
attachments: this.attachments.toJSON(),
|
||||
columns: attrs.columns ? parseInt( attrs.columns, 10 ) : 3
|
||||
};
|
||||
|
||||
return this.template( options );
|
||||
|
||||
}
|
||||
}),
|
||||
|
||||
edit: function( node ) {
|
||||
var gallery = wp.media.gallery,
|
||||
self = this,
|
||||
frame, data;
|
||||
|
||||
data = window.decodeURIComponent( $( node ).data('wpview-text') );
|
||||
frame = gallery.edit( data );
|
||||
|
||||
frame.state('gallery-edit').on( 'update', function( selection ) {
|
||||
var shortcode = gallery.shortcode( selection ).string();
|
||||
$( node ).attr( 'data-wpview-text', window.encodeURIComponent( shortcode ) );
|
||||
wp.mce.views.refreshView( self, shortcode );
|
||||
frame.detach();
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
wp.mce.views.register( 'gallery', wp.mce.gallery );
|
||||
}(jQuery));
|
||||
|
|
|
@ -1 +1 @@
|
|||
window.wp=window.wp||{},function(a){var b={},c={};wp.mce=wp.mce||{},wp.mce.view={defaults:{pattern:{view:Backbone.View,text:function(a){return a.options.original},toView:function(a){if(this.pattern){this.pattern.lastIndex=0;var b=this.pattern.exec(a);if(b)return{index:b.index,content:b[0],options:{original:b[0],results:b}}}}},shortcode:{view:Backbone.View,text:function(a){return a.options.shortcode.string()},toView:function(a){var b=wp.shortcode.next(this.shortcode,a);if(b)return{index:b.index,content:b.content,options:{shortcode:b.shortcode}}}}},add:function(a,d){var e,f,g,h;e=d.extend?wp.mce.view.get(d.extend):d.shortcode?wp.mce.view.defaults.shortcode:wp.mce.view.defaults.pattern,_.defaults(d,e),d.id=a,h={remove:function(){return delete c[this.el.id],this.$el.parent().remove(),f&&f.apply(this,arguments),this}},_.isFunction(d.view)?g=d.view:(g=e.view,f=d.view.remove,_.defaults(h,d.view)),f||g._mceview||(f=g.prototype.remove),d.view=g.extend(h,{_mceview:!0}),b[a]=d},get:function(a){return b[a]},remove:function(a){delete b[a]},toViews:function(a){var c,d=[{content:a}];return _.each(b,function(a,b){c=d.slice(),d=[],_.each(c,function(c){var e,f=c.content;if(c.processed)return void d.push(c);for(;f&&(e=a.toView(f));)e.index&&d.push({content:f.substring(0,e.index)}),d.push({content:wp.mce.view.toView(b,e.options),processed:!0}),f=f.slice(e.index+e.content.length);f&&d.push({content:f})})}),_.pluck(d,"content").join("")},toView:function(b,d){var e,f,g=wp.mce.view.get(b);return g?(e=new g.view(_.extend(d||{},{viewType:b})),f=e.el.id=e.el.id||_.uniqueId("__wpmce-"),c[f]=e,e.$wrapper=a(),wp.html.string({tag:"span"===e.tagName?"span":"div",attrs:{"class":"wp-view-wrap wp-view-type-"+b,"data-wp-view":f,contenteditable:!1}})):""},render:function(b){a(".wp-view-wrap",b).each(function(){var b=a(this),c=wp.mce.view.instance(this);c&&(c.$wrapper=b,c.render(),c.$el.detach(),b.empty().append(c.el).append('<span data-wp-view-end class="wp-view-end"></span>'))})},toText:function(a){return a.replace(/<(?:div|span)[^>]+data-wp-view="([^"]+)"[^>]*>.*?<span[^>]+data-wp-view-end[^>]*><\/span><\/(?:div|span)>/g,function(a,b){var d,e=c[b];return e&&(d=wp.mce.view.get(e.options.viewType)),e&&d?d.text(e):""})},removeInternalAttrs:function(a){var b={};return _.each(a,function(a,c){-1===c.indexOf("data-mce")&&(b[c]=a)}),b},attrs:function(a){return wp.mce.view.removeInternalAttrs(wp.html.attrs(a))},instance:function(b){var d=a(b).data("wp-view");return d?c[d]:void 0},select:function(b){var c=a(b);c.hasClass("selected")||(c.addClass("selected"),a(b.firstChild).trigger("select"))},deselect:function(b){var c=a(b);c.hasClass("selected")&&(c.removeClass("selected"),a(b.firstChild).trigger("deselect"))}}}(jQuery);
|
||||
window.wp=window.wp||{},function(a){var b={},c={},d=wp.media,e=["encodedText"];wp.mce=wp.mce||{},wp.mce.View=function(a){a||(a={}),_.extend(this,_.pick(a,e)),this.initialize.apply(this,arguments)},_.extend(wp.mce.View.prototype,{initialize:function(){},html:function(){},render:function(){var b=this.getHtml();_.each(tinymce.editors,function(c){var d;c.plugins.wpview&&(d=c.getDoc(),a(d).find('[data-wpview-text="'+this.encodedText+'"]').html(b))},this)}}),wp.mce.View.extend=Backbone.View.extend,wp.mce.views={register:function(a,c){b[a]=c},get:function(a){return b[a]},unregister:function(a){delete b[a]},toViews:function(a){var c,d=[{content:a}];return _.each(b,function(a,b){c=d.slice(),d=[],_.each(c,function(c){var e,f=c.content;if(c.processed)return void d.push(c);for(;f&&(e=a.toView(f));)e.index&&d.push({content:f.substring(0,e.index)}),d.push({content:wp.mce.views.toView(b,e.content,e.options),processed:!0}),f=f.slice(e.index+e.content.length);f&&d.push({content:f})})}),_.pluck(d,"content").join("")},toView:function(a,b,d){var e,f,g=wp.mce.views.get(a),h=window.encodeURIComponent(b);return g?(wp.mce.views.getInstance(h)||(f=d,f.encodedText=h,e=new g.View(f),c[h]=e),wp.html.string({tag:"div",attrs:{"class":"wpview-wrap wpview-type-"+a,"data-wpview-text":h,"data-wpview-type":a,contenteditable:"false"},content:" "})):b},refreshView:function(a,b){var d,e,f,g=window.encodeURIComponent(b);f=wp.mce.views.getInstance(g),f||(e=a.toView(b),d=e.options,d.encodedText=g,f=new a.View(d),c[g]=f),wp.mce.views.render()},getInstance:function(a){return c[a]},render:function(){_.each(c,function(a){a.render()})},edit:function(b){var c=a(b).data("wpview-type"),d=wp.mce.views.get(c);d&&d.edit(b)}},wp.mce.gallery={shortcode:"gallery",toView:function(a){var b=wp.shortcode.next(this.shortcode,a);if(b)return{index:b.index,content:b.content,options:{shortcode:b.shortcode}}},View:wp.mce.View.extend({className:"editor-gallery",template:d.template("editor-gallery"),postID:a("#post_ID").val(),initialize:function(a){this.shortcode=a.shortcode,this.fetch()},fetch:function(){this.attachments=wp.media.gallery.attachments(this.shortcode,this.postID),this.attachments.more().done(_.bind(this.render,this))},getHtml:function(){var a,b=this.shortcode.attrs.named;if(this.attachments.length)return a={attachments:this.attachments.toJSON(),columns:b.columns?parseInt(b.columns,10):3},this.template(a)}}),edit:function(b){var c,d,e=wp.media.gallery,f=this;d=window.decodeURIComponent(a(b).data("wpview-text")),c=e.edit(d),c.state("gallery-edit").on("update",function(d){var g=e.shortcode(d).string();a(b).attr("data-wpview-text",window.encodeURIComponent(g)),wp.mce.views.refreshView(f,g),c.detach()})}},wp.mce.views.register("gallery",wp.mce.gallery)}(jQuery);
|
|
@ -59,7 +59,7 @@ tinymce.PluginManager.add('wpgallery', function( editor ) {
|
|||
return;
|
||||
}
|
||||
|
||||
// Check if the `wp.media.gallery` API exists.
|
||||
// Check if the `wp.media` API exists.
|
||||
if ( typeof wp === 'undefined' || ! wp.media ) {
|
||||
return;
|
||||
}
|
||||
|
@ -166,7 +166,11 @@ tinymce.PluginManager.add('wpgallery', function( editor ) {
|
|||
});
|
||||
|
||||
editor.on( 'BeforeSetContent', function( event ) {
|
||||
event.content = replaceGalleryShortcodes( event.content );
|
||||
// 'wpview' handles the gallery shortcode when present
|
||||
if ( ! editor.plugins.wpview ) {
|
||||
event.content = replaceGalleryShortcodes( event.content );
|
||||
}
|
||||
|
||||
event.content = replaceAVShortcodes( event.content );
|
||||
});
|
||||
|
||||
|
|
|
@ -1 +1 @@
|
|||
tinymce.PluginManager.add("wpgallery",function(a){function b(a){return a.replace(/\[gallery([^\]]*)\]/g,function(a){return c("wp-gallery",a)})}function c(a,b){return b=window.encodeURIComponent(b),'<img src="'+tinymce.Env.transparentSrc+'" class="wp-media mceItem '+a+'" data-wp-media="'+b+'" data-mce-resize="false" data-mce-placeholder="1" />'}function d(a,b,d){var e;return d&&d.indexOf("["+b)>-1?(e=a.length-d.length,c("wp-"+b,a.substring(0,e))+a.substring(e)):c("wp-"+b,a)}function e(a){for(var b=/\[(video-playlist|audio|video|playlist)[^\]]*\]/,c=/\[(video-playlist|audio|video|playlist)[^\]]*\]([\s\S]*?\[\/\1\])?/;b.test(a);)a=a.replace(c,d);return a}function f(a){function b(a,b){return b=new RegExp(b+'="([^"]+)"').exec(a),b?window.decodeURIComponent(b[1]):""}return a.replace(/(?:<p(?: [^>]+)?>)*(<img [^>]+>)(?:<\/p>)*/g,function(a,c){var d=b(c,"data-wp-media");return d?"<p>"+d+"</p>":a})}function g(b){var c,d,e;"IMG"===b.nodeName&&"undefined"!=typeof wp&&wp.media&&(a.dom.hasClass(b,"wp-gallery")&&wp.media.gallery?(c=wp.media.gallery,e=window.decodeURIComponent(a.dom.getAttrib(b,"data-wp-media")),d=c.edit(e),d.state("gallery-edit").on("update",function(e){var f=c.shortcode(e).string();a.dom.setAttrib(b,"data-wp-media",window.encodeURIComponent(f)),d.detach()})):a.dom.hasClass(b,"wp-playlist")&&wp.media.playlist?(e=window.decodeURIComponent(a.dom.getAttrib(b,"data-wp-media")),d=wp.media.playlist.edit(e),d.state("playlist-edit").on("update",function(c){var e=wp.media.playlist.shortcode(c).string();a.dom.setAttrib(b,"data-wp-media",window.encodeURIComponent(e)),d.detach()})):a.dom.hasClass(b,"wp-video-playlist")&&wp.media["video-playlist"]?(e=window.decodeURIComponent(a.dom.getAttrib(b,"data-wp-media")),d=wp.media["video-playlist"].edit(e),d.state("video-playlist-edit").on("update",function(c){var e=wp.media["video-playlist"].shortcode(c).string();a.dom.setAttrib(b,"data-wp-media",window.encodeURIComponent(e)),d.detach()})):window.console&&window.console.log("Edit AV shortcode "+window.decodeURIComponent(a.dom.getAttrib(b,"data-wp-media"))))}a.addCommand("WP_Gallery",function(){g(a.selection.getNode())}),a.on("mouseup",function(b){function c(){d.removeClass(d.select("img.wp-media-selected"),"wp-media-selected")}var d=a.dom,e=b.target;"IMG"===e.nodeName&&d.getAttrib(e,"data-wp-media")?2!==b.button&&(d.hasClass(e,"wp-media-selected")?g(e):(c(),d.addClass(e,"wp-media-selected"))):c()}),a.on("ResolveName",function(b){var c=a.dom,d=b.target;"IMG"===d.nodeName&&c.getAttrib(d,"data-wp-media")&&(c.hasClass(d,"wp-gallery")?b.name="gallery":c.hasClass(d,"wp-video")?b.name="video":c.hasClass(d,"wp-audio")?b.name="audio":c.hasClass(d,"wp-playlist")?b.name="playlist":c.hasClass(d,"wp-video-playlist")&&(b.name="video-playlist"))}),a.on("BeforeSetContent",function(a){a.content=b(a.content),a.content=e(a.content)}),a.on("PostProcess",function(a){a.get&&(a.content=f(a.content))})});
|
||||
tinymce.PluginManager.add("wpgallery",function(a){function b(a){return a.replace(/\[gallery([^\]]*)\]/g,function(a){return c("wp-gallery",a)})}function c(a,b){return b=window.encodeURIComponent(b),'<img src="'+tinymce.Env.transparentSrc+'" class="wp-media mceItem '+a+'" data-wp-media="'+b+'" data-mce-resize="false" data-mce-placeholder="1" />'}function d(a,b,d){var e;return d&&d.indexOf("["+b)>-1?(e=a.length-d.length,c("wp-"+b,a.substring(0,e))+a.substring(e)):c("wp-"+b,a)}function e(a){for(var b=/\[(video-playlist|audio|video|playlist)[^\]]*\]/,c=/\[(video-playlist|audio|video|playlist)[^\]]*\]([\s\S]*?\[\/\1\])?/;b.test(a);)a=a.replace(c,d);return a}function f(a){function b(a,b){return b=new RegExp(b+'="([^"]+)"').exec(a),b?window.decodeURIComponent(b[1]):""}return a.replace(/(?:<p(?: [^>]+)?>)*(<img [^>]+>)(?:<\/p>)*/g,function(a,c){var d=b(c,"data-wp-media");return d?"<p>"+d+"</p>":a})}function g(b){var c,d,e;"IMG"===b.nodeName&&"undefined"!=typeof wp&&wp.media&&(a.dom.hasClass(b,"wp-gallery")&&wp.media.gallery?(c=wp.media.gallery,e=window.decodeURIComponent(a.dom.getAttrib(b,"data-wp-media")),d=c.edit(e),d.state("gallery-edit").on("update",function(e){var f=c.shortcode(e).string();a.dom.setAttrib(b,"data-wp-media",window.encodeURIComponent(f)),d.detach()})):a.dom.hasClass(b,"wp-playlist")&&wp.media.playlist?(e=window.decodeURIComponent(a.dom.getAttrib(b,"data-wp-media")),d=wp.media.playlist.edit(e),d.state("playlist-edit").on("update",function(c){var e=wp.media.playlist.shortcode(c).string();a.dom.setAttrib(b,"data-wp-media",window.encodeURIComponent(e)),d.detach()})):a.dom.hasClass(b,"wp-video-playlist")&&wp.media["video-playlist"]?(e=window.decodeURIComponent(a.dom.getAttrib(b,"data-wp-media")),d=wp.media["video-playlist"].edit(e),d.state("video-playlist-edit").on("update",function(c){var e=wp.media["video-playlist"].shortcode(c).string();a.dom.setAttrib(b,"data-wp-media",window.encodeURIComponent(e)),d.detach()})):window.console&&window.console.log("Edit AV shortcode "+window.decodeURIComponent(a.dom.getAttrib(b,"data-wp-media"))))}a.addCommand("WP_Gallery",function(){g(a.selection.getNode())}),a.on("mouseup",function(b){function c(){d.removeClass(d.select("img.wp-media-selected"),"wp-media-selected")}var d=a.dom,e=b.target;"IMG"===e.nodeName&&d.getAttrib(e,"data-wp-media")?2!==b.button&&(d.hasClass(e,"wp-media-selected")?g(e):(c(),d.addClass(e,"wp-media-selected"))):c()}),a.on("ResolveName",function(b){var c=a.dom,d=b.target;"IMG"===d.nodeName&&c.getAttrib(d,"data-wp-media")&&(c.hasClass(d,"wp-gallery")?b.name="gallery":c.hasClass(d,"wp-video")?b.name="video":c.hasClass(d,"wp-audio")?b.name="audio":c.hasClass(d,"wp-playlist")?b.name="playlist":c.hasClass(d,"wp-video-playlist")&&(b.name="video-playlist"))}),a.on("BeforeSetContent",function(c){a.plugins.wpview||(c.content=b(c.content)),c.content=e(c.content)}),a.on("PostProcess",function(a){a.get&&(a.content=f(a.content))})});
|
|
@ -2,190 +2,366 @@
|
|||
/**
|
||||
* WordPress View plugin.
|
||||
*/
|
||||
|
||||
(function() {
|
||||
var VK = tinymce.VK,
|
||||
tinymce.PluginManager.add( 'wpview', function( editor ) {
|
||||
var selected,
|
||||
VK = tinymce.util.VK,
|
||||
TreeWalker = tinymce.dom.TreeWalker,
|
||||
selected;
|
||||
toRemove = false;
|
||||
|
||||
tinymce.create('tinymce.plugins.wpView', {
|
||||
init : function( editor ) {
|
||||
var wpView = this;
|
||||
|
||||
// Check if the `wp.mce` API exists.
|
||||
if ( typeof wp === 'undefined' || ! wp.mce ) {
|
||||
return;
|
||||
function getParentView( node ) {
|
||||
while ( node && node.nodeName !== 'BODY' ) {
|
||||
if ( isView( node ) ) {
|
||||
return node;
|
||||
}
|
||||
|
||||
editor.on( 'PreInit', function() {
|
||||
// Add elements so we can set `contenteditable` to false.
|
||||
editor.schema.addValidElements('div[*],span[*]');
|
||||
});
|
||||
node = node.parentNode;
|
||||
}
|
||||
}
|
||||
|
||||
// When the editor's content changes, scan the new content for
|
||||
// matching view patterns, and transform the matches into
|
||||
// view wrappers. Since the editor's DOM is outdated at this point,
|
||||
// we'll wait to render the views.
|
||||
editor.on( 'BeforeSetContent', function( e ) {
|
||||
if ( ! e.content ) {
|
||||
return;
|
||||
}
|
||||
function isView( node ) {
|
||||
return node && /\bwpview-wrap\b/.test( node.className );
|
||||
}
|
||||
|
||||
e.content = wp.mce.view.toViews( e.content );
|
||||
});
|
||||
function createPadNode() {
|
||||
return editor.dom.create( 'p', { 'data-wpview-pad': 1 },
|
||||
( tinymce.Env.ie && tinymce.Env.ie < 11 ) ? '' : '<br data-mce-bogus="1" />' );
|
||||
}
|
||||
|
||||
// When the editor's content has been updated and the DOM has been
|
||||
// processed, render the views in the document.
|
||||
editor.on( 'SetContent', function() {
|
||||
wp.mce.view.render( editor.getDoc() );
|
||||
});
|
||||
/**
|
||||
* Get the text/shortcode string for a view.
|
||||
*
|
||||
* @param view The view wrapper's HTML id or node
|
||||
* @returns string The text/shoercode string of the view
|
||||
*/
|
||||
function getViewText( view ) {
|
||||
view = getParentView( typeof view === 'string' ? editor.dom.get( view ) : view );
|
||||
|
||||
editor.on( 'init', function() {
|
||||
var selection = editor.selection;
|
||||
// When a view is selected, ensure content that is being pasted
|
||||
// or inserted is added to a text node (instead of the view).
|
||||
editor.on( 'BeforeSetContent', function() {
|
||||
var walker, target,
|
||||
view = wpView.getParentView( selection.getNode() );
|
||||
if ( view ) {
|
||||
return window.decodeURIComponent( editor.dom.getAttrib( view, 'data-wpview-text' ) || '' );
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
// If the selection is not within a view, bail.
|
||||
if ( ! view ) {
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* Set the view's original text/shortcode string
|
||||
*
|
||||
* @param view The view wrapper's HTML id or node
|
||||
* @param text The text string to be set
|
||||
*/
|
||||
function setViewText( view, text ) {
|
||||
view = getParentView( typeof view === 'string' ? editor.dom.get( view ) : view );
|
||||
|
||||
// If there are no additional nodes or the next node is a
|
||||
// view, create a text node after the current view.
|
||||
if ( ! view.nextSibling || wpView.isView( view.nextSibling ) ) {
|
||||
target = editor.getDoc().createTextNode('');
|
||||
editor.dom.insertAfter( target, view );
|
||||
if ( view ) {
|
||||
editor.dom.setAttrib( view, 'data-wpview-text', window.encodeURIComponent( text || '' ) );
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Otherwise, find the next text node.
|
||||
} else {
|
||||
walker = new TreeWalker( view.nextSibling, view.nextSibling );
|
||||
target = walker.next();
|
||||
}
|
||||
function _stop( event ) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
// Select the `target` text node.
|
||||
selection.select( target );
|
||||
selection.collapse( true );
|
||||
});
|
||||
function select( viewNode ) {
|
||||
var clipboard,
|
||||
dom = editor.dom;
|
||||
|
||||
// When the selection's content changes, scan any new content
|
||||
// for matching views and immediately render them.
|
||||
//
|
||||
// Runs on paste and on inserting nodes/html.
|
||||
editor.on( 'SetContent', function( e ) {
|
||||
if ( ! e.context ) {
|
||||
return;
|
||||
}
|
||||
// Bail if node is already selected.
|
||||
if ( viewNode === selected ) {
|
||||
return;
|
||||
}
|
||||
|
||||
var node = selection.getNode();
|
||||
deselect();
|
||||
selected = viewNode;
|
||||
dom.addClass( viewNode, 'selected' );
|
||||
|
||||
if ( ! node.innerHTML ) {
|
||||
return;
|
||||
}
|
||||
clipboard = dom.create( 'div', {
|
||||
'class': 'wpview-clipboard',
|
||||
'contenteditable': 'true'
|
||||
}, getViewText( viewNode ) );
|
||||
|
||||
node.innerHTML = wp.mce.view.toViews( node.innerHTML );
|
||||
wp.mce.view.render( node );
|
||||
});
|
||||
});
|
||||
viewNode.appendChild( clipboard );
|
||||
|
||||
// When the editor's contents are being accessed as a string,
|
||||
// transform any views back to their text representations.
|
||||
editor.on( 'PostProcess', function( e ) {
|
||||
if ( ( ! e.get && ! e.save ) || ! e.content ) {
|
||||
return;
|
||||
}
|
||||
// Both of the following are necessary to prevent manipulating the selection/focus
|
||||
editor.dom.bind( clipboard, 'beforedeactivate focusin focusout', _stop );
|
||||
editor.dom.bind( selected, 'beforedeactivate focusin focusout', _stop );
|
||||
|
||||
e.content = wp.mce.view.toText( e.content );
|
||||
});
|
||||
// select the hidden div
|
||||
editor.selection.select( clipboard, true );
|
||||
}
|
||||
|
||||
// Triggers when the selection is changed.
|
||||
// Add the event handler to the top of the stack.
|
||||
editor.on( 'NodeChange', function( e ) {
|
||||
var view = wpView.getParentView( e.element );
|
||||
/**
|
||||
* Deselect a selected view and remove clipboard
|
||||
*/
|
||||
function deselect() {
|
||||
var clipboard,
|
||||
dom = editor.dom;
|
||||
|
||||
// Update the selected view.
|
||||
if ( view ) {
|
||||
wpView.select( view );
|
||||
if ( selected ) {
|
||||
clipboard = editor.dom.select( '.wpview-clipboard', selected )[0];
|
||||
dom.unbind( clipboard );
|
||||
dom.remove( clipboard );
|
||||
|
||||
// Prevent the selection from propagating to other plugins.
|
||||
return false;
|
||||
dom.unbind( selected, 'beforedeactivate focusin focusout click mouseup', _stop );
|
||||
dom.removeClass( selected, 'selected' );
|
||||
|
||||
// If we've clicked off of the selected view, deselect it.
|
||||
} else {
|
||||
wpView.deselect();
|
||||
}
|
||||
});
|
||||
editor.selection.select( selected.nextSibling );
|
||||
editor.selection.collapse();
|
||||
|
||||
editor.on( 'keydown', function( event ) {
|
||||
var keyCode = event.keyCode,
|
||||
view, instance;
|
||||
}
|
||||
|
||||
// If a view isn't selected, let the event go on its merry way.
|
||||
if ( ! selected ) {
|
||||
return;
|
||||
}
|
||||
selected = null;
|
||||
}
|
||||
|
||||
// If the caret is not within the selected view, deselect the
|
||||
// view and bail.
|
||||
view = wpView.getParentView( editor.selection.getNode() );
|
||||
if ( view !== selected ) {
|
||||
wpView.deselect();
|
||||
return;
|
||||
}
|
||||
// Check if the `wp.mce` API exists.
|
||||
if ( typeof wp === 'undefined' || ! wp.mce ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If delete or backspace is pressed, delete the view.
|
||||
if ( keyCode === VK.DELETE || keyCode === VK.BACKSPACE ) {
|
||||
if ( (instance = wp.mce.view.instance( selected )) ) {
|
||||
instance.remove();
|
||||
wpView.deselect();
|
||||
}
|
||||
}
|
||||
|
||||
// Let keypresses that involve the command or control keys through.
|
||||
// Also, let any of the F# keys through.
|
||||
if ( event.metaKey || event.ctrlKey || ( keyCode >= 112 && keyCode <= 123 ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
});
|
||||
},
|
||||
|
||||
getParentView : function( node ) {
|
||||
while ( node ) {
|
||||
if ( this.isView( node ) ) {
|
||||
return node;
|
||||
}
|
||||
|
||||
node = node.parentNode;
|
||||
}
|
||||
},
|
||||
|
||||
isView : function( node ) {
|
||||
return (/(?:^|\s)wp-view-wrap(?:\s|$)/).test( node.className );
|
||||
},
|
||||
|
||||
select : function( view ) {
|
||||
if ( view === selected ) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.deselect();
|
||||
selected = view;
|
||||
wp.mce.view.select( selected );
|
||||
},
|
||||
|
||||
deselect : function() {
|
||||
if ( selected ) {
|
||||
wp.mce.view.deselect( selected );
|
||||
}
|
||||
|
||||
selected = null;
|
||||
editor.on( 'BeforeAddUndo', function( event ) {
|
||||
if ( selected && ! toRemove ) {
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
// Register plugin
|
||||
tinymce.PluginManager.add( 'wpview', tinymce.plugins.wpView );
|
||||
})();
|
||||
// When the editor's content changes, scan the new content for
|
||||
// matching view patterns, and transform the matches into
|
||||
// view wrappers.
|
||||
editor.on( 'BeforeSetContent', function( e ) {
|
||||
if ( ! e.content ) {
|
||||
return;
|
||||
}
|
||||
|
||||
e.content = wp.mce.views.toViews( e.content );
|
||||
});
|
||||
|
||||
// When the editor's content has been updated and the DOM has been
|
||||
// processed, render the views in the document.
|
||||
editor.on( 'SetContent', function( event ) {
|
||||
var body, padNode;
|
||||
|
||||
wp.mce.views.render();
|
||||
|
||||
// Add padding <p> if the noneditable node is last
|
||||
if ( event.load || ! event.set ) {
|
||||
body = editor.getBody();
|
||||
|
||||
if ( isView( body.lastChild ) ) {
|
||||
padNode = createPadNode();
|
||||
body.appendChild( padNode );
|
||||
editor.selection.setCursorLocation( padNode, 0 );
|
||||
}
|
||||
}
|
||||
|
||||
// refreshEmptyContentNode();
|
||||
});
|
||||
|
||||
// Detect mouse down events that are adjacent to a view when a view is the first view or the last view
|
||||
editor.on( 'click', function( event ) {
|
||||
var body = editor.getBody(),
|
||||
doc = editor.getDoc(),
|
||||
scrollTop = doc.documentElement.scrollTop || body.scrollTop || 0,
|
||||
x, y, firstNode, lastNode, padNode;
|
||||
|
||||
if ( event.target.nodeName === 'HTML' && ! event.metaKey && ! event.ctrlKey ) {
|
||||
firstNode = body.firstChild;
|
||||
lastNode = body.lastChild;
|
||||
x = event.clientX;
|
||||
y = event.clientY;
|
||||
|
||||
if ( isView( firstNode ) && ( ( x < firstNode.offsetLeft && y < ( firstNode.offsetHeight - scrollTop ) ) ||
|
||||
y < firstNode.offsetTop ) ) {
|
||||
// detect events above or to the left of the first view
|
||||
|
||||
padNode = createPadNode();
|
||||
body.insertBefore( padNode, firstNode );
|
||||
} else if ( isView( lastNode ) && ( x > ( lastNode.offsetLeft + lastNode.offsetWidth ) ||
|
||||
( ( scrollTop + y ) - ( lastNode.offsetTop + lastNode.offsetHeight ) ) > 0 ) ) {
|
||||
// detect events to the right and below the last view
|
||||
|
||||
padNode = createPadNode();
|
||||
body.appendChild( padNode );
|
||||
}
|
||||
|
||||
if ( padNode ) {
|
||||
editor.selection.setCursorLocation( padNode, 0 );
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
editor.on( 'init', function() {
|
||||
var selection = editor.selection;
|
||||
// When a view is selected, ensure content that is being pasted
|
||||
// or inserted is added to a text node (instead of the view).
|
||||
editor.on( 'BeforeSetContent', function() {
|
||||
var walker, target,
|
||||
view = getParentView( selection.getNode() );
|
||||
|
||||
// If the selection is not within a view, bail.
|
||||
if ( ! view ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! view.nextSibling || isView( view.nextSibling ) ) {
|
||||
// If there are no additional nodes or the next node is a
|
||||
// view, create a text node after the current view.
|
||||
target = editor.getDoc().createTextNode('');
|
||||
editor.dom.insertAfter( target, view );
|
||||
} else {
|
||||
// Otherwise, find the next text node.
|
||||
walker = new TreeWalker( view.nextSibling, view.nextSibling );
|
||||
target = walker.next();
|
||||
}
|
||||
|
||||
// Select the `target` text node.
|
||||
selection.select( target );
|
||||
selection.collapse( true );
|
||||
});
|
||||
|
||||
// When the selection's content changes, scan any new content
|
||||
// for matching views.
|
||||
//
|
||||
// Runs on paste and on inserting nodes/html.
|
||||
editor.on( 'SetContent', function( e ) {
|
||||
if ( ! e.context ) {
|
||||
return;
|
||||
}
|
||||
|
||||
var node = selection.getNode();
|
||||
|
||||
if ( ! node.innerHTML ) {
|
||||
return;
|
||||
}
|
||||
|
||||
node.innerHTML = wp.mce.views.toViews( node.innerHTML );
|
||||
});
|
||||
|
||||
editor.dom.bind( editor.getBody(), 'mousedown mouseup click', function( event ) {
|
||||
var view = getParentView( event.target );
|
||||
|
||||
// Contain clicks inside the view wrapper
|
||||
if ( view ) {
|
||||
event.stopPropagation();
|
||||
|
||||
if ( event.type === 'click' ) {
|
||||
if ( ! event.metaKey && ! event.ctrlKey ) {
|
||||
if ( editor.dom.hasClass( event.target, 'edit' ) ) {
|
||||
wp.mce.views.edit( view );
|
||||
} else if ( editor.dom.hasClass( event.target, 'remove' ) ) {
|
||||
editor.dom.remove( view );
|
||||
}
|
||||
}
|
||||
}
|
||||
select( view );
|
||||
// returning false stops the ugly bars from appearing in IE11 and stops the view being selected as a range in FF
|
||||
// unfortunately, it also inhibits the dragging fo views to a new location
|
||||
return false;
|
||||
} else {
|
||||
if ( event.type === 'click' ) {
|
||||
deselect();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
editor.on( 'PreProcess', function( event ) {
|
||||
var dom = editor.dom;
|
||||
|
||||
// Remove empty padding nodes
|
||||
tinymce.each( dom.select( 'p[data-wpview-pad]', event.node ), function( node ) {
|
||||
if ( dom.isEmpty( node ) ) {
|
||||
dom.remove( node );
|
||||
} else {
|
||||
dom.setAttrib( node, 'data-wpview-pad', null );
|
||||
}
|
||||
});
|
||||
|
||||
// Replace the wpview node with the wpview string/shortcode?
|
||||
tinymce.each( dom.select( 'div[data-wpview-text]', event.node ), function( node ) {
|
||||
// Empty the wrap node
|
||||
if ( 'textContent' in node ) {
|
||||
node.textContent = '';
|
||||
} else {
|
||||
node.innerText = '';
|
||||
}
|
||||
|
||||
// TODO: that makes all views into block tags (as we use <div>).
|
||||
// Can use 'PostProcess' and a regex instead.
|
||||
dom.replace( dom.create( 'p', null, window.decodeURIComponent( dom.getAttrib( node, 'data-wpview-text' ) ) ), node );
|
||||
});
|
||||
});
|
||||
|
||||
editor.on( 'keydown', function( event ) {
|
||||
var keyCode = event.keyCode,
|
||||
view;
|
||||
|
||||
// If a view isn't selected, let the event go on its merry way.
|
||||
if ( ! selected ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Let keypresses that involve the command or control keys through.
|
||||
// Also, let any of the F# keys through.
|
||||
if ( event.metaKey || event.ctrlKey || ( keyCode >= 112 && keyCode <= 123 ) ) {
|
||||
if ( ( event.metaKey || event.ctrlKey ) && keyCode === 88 ) {
|
||||
toRemove = selected;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// If the caret is not within the selected view, deselect the
|
||||
// view and bail.
|
||||
view = getParentView( editor.selection.getNode() );
|
||||
|
||||
if ( view !== selected ) {
|
||||
deselect();
|
||||
return;
|
||||
}
|
||||
|
||||
// If delete or backspace is pressed, delete the view.
|
||||
if ( keyCode === VK.DELETE || keyCode === VK.BACKSPACE ) {
|
||||
editor.dom.remove( selected );
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
});
|
||||
|
||||
editor.on( 'keyup', function( event ) {
|
||||
var padNode,
|
||||
keyCode = event.keyCode,
|
||||
body = editor.getBody(),
|
||||
range;
|
||||
|
||||
if ( toRemove ) {
|
||||
editor.dom.remove( toRemove );
|
||||
toRemove = false;
|
||||
}
|
||||
|
||||
if ( keyCode === VK.DELETE || keyCode === VK.BACKSPACE ) {
|
||||
// Make sure there is padding if the last element is a view
|
||||
if ( isView( body.lastChild ) ) {
|
||||
padNode = createPadNode();
|
||||
body.appendChild( padNode );
|
||||
|
||||
if ( body.childNodes.length === 2 ) {
|
||||
editor.selection.setCursorLocation( padNode, 0 );
|
||||
}
|
||||
}
|
||||
|
||||
range = editor.selection.getRng();
|
||||
|
||||
// Allow an initial element in the document to be removed when it is before a view
|
||||
if ( body.firstChild === range.startContainer && range.collapsed === true &&
|
||||
isView( range.startContainer.nextSibling ) && range.startOffset === 0 ) {
|
||||
|
||||
editor.dom.remove( range.startContainer );
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
getViewText: getViewText,
|
||||
setViewText: setViewText
|
||||
};
|
||||
});
|
||||
|
|
|
@ -1 +1 @@
|
|||
!function(){var a,b=tinymce.VK,c=tinymce.dom.TreeWalker;tinymce.create("tinymce.plugins.wpView",{init:function(d){var e=this;"undefined"!=typeof wp&&wp.mce&&(d.on("PreInit",function(){d.schema.addValidElements("div[*],span[*]")}),d.on("BeforeSetContent",function(a){a.content&&(a.content=wp.mce.view.toViews(a.content))}),d.on("SetContent",function(){wp.mce.view.render(d.getDoc())}),d.on("init",function(){var a=d.selection;d.on("BeforeSetContent",function(){var b,f,g=e.getParentView(a.getNode());g&&(!g.nextSibling||e.isView(g.nextSibling)?(f=d.getDoc().createTextNode(""),d.dom.insertAfter(f,g)):(b=new c(g.nextSibling,g.nextSibling),f=b.next()),a.select(f),a.collapse(!0))}),d.on("SetContent",function(b){if(b.context){var c=a.getNode();c.innerHTML&&(c.innerHTML=wp.mce.view.toViews(c.innerHTML),wp.mce.view.render(c))}})}),d.on("PostProcess",function(a){(a.get||a.save)&&a.content&&(a.content=wp.mce.view.toText(a.content))}),d.on("NodeChange",function(a){var b=e.getParentView(a.element);return b?(e.select(b),!1):void e.deselect()}),d.on("keydown",function(c){var f,g,h=c.keyCode;if(a){if(f=e.getParentView(d.selection.getNode()),f!==a)return void e.deselect();(h===b.DELETE||h===b.BACKSPACE)&&(g=wp.mce.view.instance(a))&&(g.remove(),e.deselect()),c.metaKey||c.ctrlKey||h>=112&&123>=h||c.preventDefault()}}))},getParentView:function(a){for(;a;){if(this.isView(a))return a;a=a.parentNode}},isView:function(a){return/(?:^|\s)wp-view-wrap(?:\s|$)/.test(a.className)},select:function(b){b!==a&&(this.deselect(),a=b,wp.mce.view.select(a))},deselect:function(){a&&wp.mce.view.deselect(a),a=null}}),tinymce.PluginManager.add("wpview",tinymce.plugins.wpView)}();
|
||||
tinymce.PluginManager.add("wpview",function(a){function b(a){for(;a&&"BODY"!==a.nodeName;){if(c(a))return a;a=a.parentNode}}function c(a){return a&&/\bwpview-wrap\b/.test(a.className)}function d(){return a.dom.create("p",{"data-wpview-pad":1},tinymce.Env.ie&&tinymce.Env.ie<11?"":'<br data-mce-bogus="1" />')}function e(c){return c=b("string"==typeof c?a.dom.get(c):c),c?window.decodeURIComponent(a.dom.getAttrib(c,"data-wpview-text")||""):""}function f(c,d){return c=b("string"==typeof c?a.dom.get(c):c),c?(a.dom.setAttrib(c,"data-wpview-text",window.encodeURIComponent(d||"")),!0):!1}function g(a){a.stopPropagation()}function h(b){var c,d=a.dom;b!==j&&(i(),j=b,d.addClass(b,"selected"),c=d.create("div",{"class":"wpview-clipboard",contenteditable:"true"},e(b)),b.appendChild(c),a.dom.bind(c,"beforedeactivate focusin focusout",g),a.dom.bind(j,"beforedeactivate focusin focusout",g),a.selection.select(c,!0))}function i(){var b,c=a.dom;j&&(b=a.dom.select(".wpview-clipboard",j)[0],c.unbind(b),c.remove(b),c.unbind(j,"beforedeactivate focusin focusout click mouseup",g),c.removeClass(j,"selected"),a.selection.select(j.nextSibling),a.selection.collapse()),j=null}var j,k=tinymce.util.VK,l=tinymce.dom.TreeWalker,m=!1;if("undefined"!=typeof wp&&wp.mce)return a.on("BeforeAddUndo",function(a){j&&!m&&a.preventDefault()}),a.on("BeforeSetContent",function(a){a.content&&(a.content=wp.mce.views.toViews(a.content))}),a.on("SetContent",function(b){var e,f;wp.mce.views.render(),(b.load||!b.set)&&(e=a.getBody(),c(e.lastChild)&&(f=d(),e.appendChild(f),a.selection.setCursorLocation(f,0)))}),a.on("click",function(b){var e,f,g,h,i,j=a.getBody(),k=a.getDoc(),l=k.documentElement.scrollTop||j.scrollTop||0;"HTML"!==b.target.nodeName||b.metaKey||b.ctrlKey||(g=j.firstChild,h=j.lastChild,e=b.clientX,f=b.clientY,c(g)&&(e<g.offsetLeft&&f<g.offsetHeight-l||f<g.offsetTop)?(i=d(),j.insertBefore(i,g)):c(h)&&(e>h.offsetLeft+h.offsetWidth||l+f-(h.offsetTop+h.offsetHeight)>0)&&(i=d(),j.appendChild(i)),i&&a.selection.setCursorLocation(i,0))}),a.on("init",function(){var d=a.selection;a.on("BeforeSetContent",function(){var e,f,g=b(d.getNode());g&&(!g.nextSibling||c(g.nextSibling)?(f=a.getDoc().createTextNode(""),a.dom.insertAfter(f,g)):(e=new l(g.nextSibling,g.nextSibling),f=e.next()),d.select(f),d.collapse(!0))}),a.on("SetContent",function(a){if(a.context){var b=d.getNode();b.innerHTML&&(b.innerHTML=wp.mce.views.toViews(b.innerHTML))}}),a.dom.bind(a.getBody(),"mousedown mouseup click",function(c){var d=b(c.target);return d?(c.stopPropagation(),"click"===c.type&&(c.metaKey||c.ctrlKey||(a.dom.hasClass(c.target,"edit")?wp.mce.views.edit(d):a.dom.hasClass(c.target,"remove")&&a.dom.remove(d))),h(d),!1):void("click"===c.type&&i())})}),a.on("PreProcess",function(b){var c=a.dom;tinymce.each(c.select("p[data-wpview-pad]",b.node),function(a){c.isEmpty(a)?c.remove(a):c.setAttrib(a,"data-wpview-pad",null)}),tinymce.each(c.select("div[data-wpview-text]",b.node),function(a){"textContent"in a?a.textContent="":a.innerText="",c.replace(c.create("p",null,window.decodeURIComponent(c.getAttrib(a,"data-wpview-text"))),a)})}),a.on("keydown",function(c){var d,e=c.keyCode;if(j){if(c.metaKey||c.ctrlKey||e>=112&&123>=e)return void((c.metaKey||c.ctrlKey)&&88===e&&(m=j));if(d=b(a.selection.getNode()),d!==j)return void i();(e===k.DELETE||e===k.BACKSPACE)&&a.dom.remove(j),c.preventDefault()}}),a.on("keyup",function(b){var e,f,g=b.keyCode,h=a.getBody();m&&(a.dom.remove(m),m=!1),(g===k.DELETE||g===k.BACKSPACE)&&(c(h.lastChild)&&(e=d(),h.appendChild(e),2===h.childNodes.length&&a.selection.setCursorLocation(e,0)),f=a.selection.getRng(),h.firstChild===f.startContainer&&f.collapsed===!0&&c(f.startContainer.nextSibling)&&0===f.startOffset&&a.dom.remove(f.startContainer))}),{getViewText:e,setViewText:f}});
|
|
@ -198,6 +198,141 @@ img::selection {
|
|||
outline: 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* WP Views
|
||||
*/
|
||||
|
||||
/* IE hasLayout. Needed for all IE incl. 11 (ugh, not again!!) */
|
||||
.wpview-wrap {
|
||||
width: 99.99%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* delegate the handling of the selection to the wpview tinymce plugin */
|
||||
.wpview-wrap,
|
||||
.wpview-wrap * {
|
||||
-moz-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
/* hide the shortcode content, but allow the content to still be selected */
|
||||
.wpview-wrap .wpview-clipboard {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: -1;
|
||||
clip: rect(1px, 1px, 1px, 1px);
|
||||
overflow: hidden;
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gallery preview
|
||||
*/
|
||||
.wpview-type-gallery {
|
||||
position: relative;
|
||||
padding: 0 0 12px;
|
||||
margin-bottom: 16px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.wpview-type-gallery:after {
|
||||
content: '';
|
||||
display: block;
|
||||
height: 0;
|
||||
clear: both;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.wpview-type-gallery.selected {
|
||||
background-color: #efefef;
|
||||
}
|
||||
|
||||
.wpview-type-gallery .toolbar {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background-color: #333;
|
||||
color: white;
|
||||
padding: 4px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.wpview-type-gallery.selected .toolbar {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.wpview-type-gallery .toolbar span {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.gallery img[data-mce-selected]:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.gallery a {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.gallery {
|
||||
margin: auto;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.gallery .gallery-item {
|
||||
float: left;
|
||||
margin: 10px 0 0 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.gallery .gallery-caption,
|
||||
.gallery .gallery-icon {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.gallery-columns-1 .gallery-item {
|
||||
width: 99%;
|
||||
}
|
||||
|
||||
.gallery-columns-2 .gallery-item {
|
||||
width: 49.5%;
|
||||
}
|
||||
|
||||
.gallery-columns-3 .gallery-item {
|
||||
width: 33%;
|
||||
}
|
||||
|
||||
.gallery-columns-4 .gallery-item {
|
||||
width: 24.75%;
|
||||
}
|
||||
|
||||
.gallery-columns-5 .gallery-item {
|
||||
width: 19.825%;
|
||||
}
|
||||
|
||||
.gallery-columns-6 .gallery-item {
|
||||
width: 16%;
|
||||
}
|
||||
|
||||
.gallery-columns-7 .gallery-item {
|
||||
width: 14%;
|
||||
}
|
||||
|
||||
.gallery-columns-8 .gallery-item {
|
||||
width: 12%;
|
||||
}
|
||||
|
||||
.gallery-columns-9 .gallery-item {
|
||||
width: 11%;
|
||||
}
|
||||
|
||||
.gallery img {
|
||||
border: 1px solid #cfcfcf;
|
||||
}
|
||||
|
||||
img.wp-oembed {
|
||||
border: 1px dashed #888;
|
||||
background: #f7f5f2 url(images/embedded.png) no-repeat scroll center center;
|
||||
|
|
Binary file not shown.
|
@ -648,6 +648,36 @@ function wp_print_media_templates() {
|
|||
</div>
|
||||
</div>
|
||||
</script>
|
||||
<?php
|
||||
|
||||
//TODO: do we want to deal with the fact that the elements used for gallery items are filterable and can be overriden via shortcode attributes
|
||||
// do we want to deal with the difference between display and edit context at all? (e.g. wptexturize() being applied to the caption.
|
||||
?>
|
||||
|
||||
<script type="text/html" id="tmpl-editor-gallery">
|
||||
<div class="toolbar">
|
||||
<div class="dashicons dashicons-format-gallery edit"></div>
|
||||
<div class="dashicons dashicons-no-alt remove"></div>
|
||||
</div>
|
||||
<div class="gallery gallery-columns-{{{ data.columns }}}">
|
||||
<# _.each( data.attachments, function( attachment, index ) { #>
|
||||
<dl class="gallery-item">
|
||||
<dt class="gallery-icon">
|
||||
<?php // TODO: need to figure out the best way to make sure that we have thumbnails ?>
|
||||
<img src="{{{ attachment.sizes.thumbnail.url }}}" />
|
||||
</dt>
|
||||
<dd class="wp-caption-text gallery-caption">
|
||||
{{ attachment.caption }}
|
||||
</dd>
|
||||
</dl>
|
||||
<?php // this is kind silly, but copied from the gallery shortcode. Maybe it should be removed ?>
|
||||
<# if ( index % data.columns === data.columns - 1 ) { #>
|
||||
<br style="clear: both;">
|
||||
<# } #>
|
||||
|
||||
<# } ); #>
|
||||
</div>
|
||||
</script>
|
||||
<?php
|
||||
|
||||
/**
|
||||
|
|
|
@ -18,7 +18,7 @@ $wp_db_version = 26691;
|
|||
*
|
||||
* @global string $tinymce_version
|
||||
*/
|
||||
$tinymce_version = '4018-20140303';
|
||||
$tinymce_version = '4018-20140304';
|
||||
|
||||
/**
|
||||
* Holds the required PHP version
|
||||
|
|
Loading…
Reference in New Issue