--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/wp/wp-includes/js/mce-view.js Wed Nov 06 03:21:17 2013 +0000
@@ -0,0 +1,349 @@
+// Ensure the global `wp` object exists.
+window.wp = window.wp || {};
+
+(function($){
+ var views = {},
+ instances = {};
+
+ // 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;
+ },
+
+ 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
+ }
+ };
+ }
+ },
+
+ // 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();
+ },
+
+ 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
+ }
+ };
+ }
+ }
+ },
+
+ // ### 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;
+ },
+
+ // ### get( id )
+ // Returns a TinyMCE view options object.
+ get: function( id ) {
+ return views[ id ];
+ },
+
+ // ### 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: function( content ) {
+ var pieces = [ { content: content } ],
+ current;
+
+ _.each( views, function( view, viewType ) {
+ current = pieces.slice();
+ pieces = [];
+
+ _.each( current, function( piece ) {
+ var remaining = piece.content,
+ result;
+
+ // Ignore processed pieces, but retain their location.
+ if ( piece.processed ) {
+ pieces.push( piece );
+ return;
+ }
+
+ // Iterate through the string progressively matching views
+ // 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 )
+ 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 ),
+ processed: true
+ });
+
+ // Update the remaining content.
+ remaining = remaining.slice( result.index + result.content.length );
+ }
+
+ // There are no additional matches. If any content remains,
+ // add it as an unprocessed piece.
+ if ( remaining )
+ pieces.push({ content: remaining });
+ });
+ });
+
+ return _.pluck( pieces, 'content' ).join('');
+ },
+
+ toView: function( viewType, options ) {
+ var view = wp.mce.view.get( viewType ),
+ instance, id;
+
+ if ( ! view )
+ return '';
+
+ // Create a new view instance.
+ instance = new view.view( _.extend( options || {}, {
+ viewType: viewType
+ }) );
+
+ // 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 = $();
+
+ return wp.html.string({
+ // If the view is a span, wrap it in a span.
+ tag: 'span' === instance.tagName ? 'span' : 'div',
+
+ attrs: {
+ 'class': 'wp-view-wrap wp-view-type-' + viewType,
+ 'data-wp-view': id,
+ 'contenteditable': false
+ }
+ });
+ },
+
+ // ### 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 );
+
+ if ( ! view )
+ return;
+
+ // 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();
+
+ // 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>');
+ });
+ },
+
+ // ### 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 ) : '';
+ });
+ },
+
+ // ### 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;
+ },
+
+ // ### Parse an attribute string and removes internal TinyMCE attributes.
+ attrs: function( content ) {
+ return wp.mce.view.removeInternalAttrs( wp.html.attrs( content ) );
+ },
+
+ // ### 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');
+ }
+ };
+
+}(jQuery));
\ No newline at end of file