]> scripts.mit.edu Git - autoinstalls/wordpress.git/blob - wp-includes/js/mce-view.js
Wordpress 3.6-scripts
[autoinstalls/wordpress.git] / wp-includes / js / mce-view.js
1 // Ensure the global `wp` object exists.
2 window.wp = window.wp || {};
3
4 (function($){
5         var views = {},
6                 instances = {};
7
8         // Create the `wp.mce` object if necessary.
9         wp.mce = wp.mce || {};
10
11         // wp.mce.view
12         // -----------
13         // A set of utilities that simplifies adding custom UI within a TinyMCE editor.
14         // At its core, it serves as a series of converters, transforming text to a
15         // custom UI, and back again.
16         wp.mce.view = {
17                 // ### defaults
18                 defaults: {
19                         // The default properties used for objects with the `pattern` key in
20                         // `wp.mce.view.add()`.
21                         pattern: {
22                                 view: Backbone.View,
23                                 text: function( instance ) {
24                                         return instance.options.original;
25                                 },
26
27                                 toView: function( content ) {
28                                         if ( ! this.pattern )
29                                                 return;
30
31                                         this.pattern.lastIndex = 0;
32                                         var match = this.pattern.exec( content );
33
34                                         if ( ! match )
35                                                 return;
36
37                                         return {
38                                                 index:   match.index,
39                                                 content: match[0],
40                                                 options: {
41                                                         original: match[0],
42                                                         results:  match
43                                                 }
44                                         };
45                                 }
46                         },
47
48                         // The default properties used for objects with the `shortcode` key in
49                         // `wp.mce.view.add()`.
50                         shortcode: {
51                                 view: Backbone.View,
52                                 text: function( instance ) {
53                                         return instance.options.shortcode.string();
54                                 },
55
56                                 toView: function( content ) {
57                                         var match = wp.shortcode.next( this.shortcode, content );
58
59                                         if ( ! match )
60                                                 return;
61
62                                         return {
63                                                 index:   match.index,
64                                                 content: match.content,
65                                                 options: {
66                                                         shortcode: match.shortcode
67                                                 }
68                                         };
69                                 }
70                         }
71                 },
72
73                 // ### add( id, options )
74                 // Registers a new TinyMCE view.
75                 //
76                 // Accepts a unique `id` and an `options` object.
77                 //
78                 // `options` accepts the following properties:
79                 //
80                 // * `pattern` is the regular expression used to scan the content and
81                 // detect matching views.
82                 //
83                 // * `view` is a `Backbone.View` constructor. If a plain object is
84                 // provided, it will automatically extend the parent constructor
85                 // (usually `Backbone.View`). Views are instantiated when the `pattern`
86                 // is successfully matched. The instance's `options` object is provided
87                 // with the `original` matched value, the match `results` including
88                 // capture groups, and the `viewType`, which is the constructor's `id`.
89                 //
90                 // * `extend` an existing view by passing in its `id`. The current
91                 // view will inherit all properties from the parent view, and if
92                 // `view` is set to a plain object, it will extend the parent `view`
93                 // constructor.
94                 //
95                 // * `text` is a method that accepts an instance of the `view`
96                 // constructor and transforms it into a text representation.
97                 add: function( id, options ) {
98                         var parent, remove, base, properties;
99
100                         // Fetch the parent view or the default options.
101                         if ( options.extend )
102                                 parent = wp.mce.view.get( options.extend );
103                         else if ( options.shortcode )
104                                 parent = wp.mce.view.defaults.shortcode;
105                         else
106                                 parent = wp.mce.view.defaults.pattern;
107
108                         // Extend the `options` object with the parent's properties.
109                         _.defaults( options, parent );
110                         options.id = id;
111
112                         // Create properties used to enhance the view for use in TinyMCE.
113                         properties = {
114                                 // Ensure the wrapper element and references to the view are
115                                 // removed. Otherwise, removed views could randomly restore.
116                                 remove: function() {
117                                         delete instances[ this.el.id ];
118                                         this.$el.parent().remove();
119
120                                         // Trigger the inherited `remove` method.
121                                         if ( remove )
122                                                 remove.apply( this, arguments );
123
124                                         return this;
125                                 }
126                         };
127
128                         // If the `view` provided was an object, use the parent's
129                         // `view` constructor as a base. If a `view` constructor
130                         // was provided, treat that as the base.
131                         if ( _.isFunction( options.view ) ) {
132                                 base = options.view;
133                         } else {
134                                 base   = parent.view;
135                                 remove = options.view.remove;
136                                 _.defaults( properties, options.view );
137                         }
138
139                         // If there's a `remove` method on the `base` view that wasn't
140                         // created by this method, inherit it.
141                         if ( ! remove && ! base._mceview )
142                                 remove = base.prototype.remove;
143
144                         // Automatically create the new `Backbone.View` constructor.
145                         options.view = base.extend( properties, {
146                                 // Flag that the new view has been created by `wp.mce.view`.
147                                 _mceview: true
148                         });
149
150                         views[ id ] = options;
151                 },
152
153                 // ### get( id )
154                 // Returns a TinyMCE view options object.
155                 get: function( id ) {
156                         return views[ id ];
157                 },
158
159                 // ### remove( id )
160                 // Unregisters a TinyMCE view.
161                 remove: function( id ) {
162                         delete views[ id ];
163                 },
164
165                 // ### toViews( content )
166                 // Scans a `content` string for each view's pattern, replacing any
167                 // matches with wrapper elements, and creates a new view instance for
168                 // every match.
169                 //
170                 // To render the views, call `wp.mce.view.render( scope )`.
171                 toViews: function( content ) {
172                         var pieces = [ { content: content } ],
173                                 current;
174
175                         _.each( views, function( view, viewType ) {
176                                 current = pieces.slice();
177                                 pieces  = [];
178
179                                 _.each( current, function( piece ) {
180                                         var remaining = piece.content,
181                                                 result;
182
183                                         // Ignore processed pieces, but retain their location.
184                                         if ( piece.processed ) {
185                                                 pieces.push( piece );
186                                                 return;
187                                         }
188
189                                         // Iterate through the string progressively matching views
190                                         // and slicing the string as we go.
191                                         while ( remaining && (result = view.toView( remaining )) ) {
192                                                 // Any text before the match becomes an unprocessed piece.
193                                                 if ( result.index )
194                                                         pieces.push({ content: remaining.substring( 0, result.index ) });
195
196                                                 // Add the processed piece for the match.
197                                                 pieces.push({
198                                                         content:   wp.mce.view.toView( viewType, result.options ),
199                                                         processed: true
200                                                 });
201
202                                                 // Update the remaining content.
203                                                 remaining = remaining.slice( result.index + result.content.length );
204                                         }
205
206                                         // There are no additional matches. If any content remains,
207                                         // add it as an unprocessed piece.
208                                         if ( remaining )
209                                                 pieces.push({ content: remaining });
210                                 });
211                         });
212
213                         return _.pluck( pieces, 'content' ).join('');
214                 },
215
216                 toView: function( viewType, options ) {
217                         var view = wp.mce.view.get( viewType ),
218                                 instance, id;
219
220                         if ( ! view )
221                                 return '';
222
223                         // Create a new view instance.
224                         instance = new view.view( _.extend( options || {}, {
225                                 viewType: viewType
226                         }) );
227
228                         // Use the view's `id` if it already exists. Otherwise,
229                         // create a new `id`.
230                         id = instance.el.id = instance.el.id || _.uniqueId('__wpmce-');
231                         instances[ id ] = instance;
232
233                         // Create a dummy `$wrapper` property to allow `$wrapper` to be
234                         // called in the view's `render` method without a conditional.
235                         instance.$wrapper = $();
236
237                         return wp.html.string({
238                                 // If the view is a span, wrap it in a span.
239                                 tag: 'span' === instance.tagName ? 'span' : 'div',
240
241                                 attrs: {
242                                         'class':           'wp-view-wrap wp-view-type-' + viewType,
243                                         'data-wp-view':    id,
244                                         'contenteditable': false
245                                 }
246                         });
247                 },
248
249                 // ### render( scope )
250                 // Renders any view instances inside a DOM node `scope`.
251                 //
252                 // View instances are detected by the presence of wrapper elements.
253                 // To generate wrapper elements, pass your content through
254                 // `wp.mce.view.toViews( content )`.
255                 render: function( scope ) {
256                         $( '.wp-view-wrap', scope ).each( function() {
257                                 var wrapper = $(this),
258                                         view = wp.mce.view.instance( this );
259
260                                 if ( ! view )
261                                         return;
262
263                                 // Link the real wrapper to the view.
264                                 view.$wrapper = wrapper;
265                                 // Render the view.
266                                 view.render();
267                                 // Detach the view element to ensure events are not unbound.
268                                 view.$el.detach();
269
270                                 // Empty the wrapper, attach the view element to the wrapper,
271                                 // and add an ending marker to the wrapper to help regexes
272                                 // scan the HTML string.
273                                 wrapper.empty().append( view.el ).append('<span data-wp-view-end class="wp-view-end"></span>');
274                         });
275                 },
276
277                 // ### toText( content )
278                 // Scans an HTML `content` string and replaces any view instances with
279                 // their respective text representations.
280                 toText: function( content ) {
281                         return content.replace( /<(?:div|span)[^>]+data-wp-view="([^"]+)"[^>]*>.*?<span[^>]+data-wp-view-end[^>]*><\/span><\/(?:div|span)>/g, function( match, id ) {
282                                 var instance = instances[ id ],
283                                         view;
284
285                                 if ( instance )
286                                         view = wp.mce.view.get( instance.options.viewType );
287
288                                 return instance && view ? view.text( instance ) : '';
289                         });
290                 },
291
292                 // ### Remove internal TinyMCE attributes.
293                 removeInternalAttrs: function( attrs ) {
294                         var result = {};
295                         _.each( attrs, function( value, attr ) {
296                                 if ( -1 === attr.indexOf('data-mce') )
297                                         result[ attr ] = value;
298                         });
299                         return result;
300                 },
301
302                 // ### Parse an attribute string and removes internal TinyMCE attributes.
303                 attrs: function( content ) {
304                         return wp.mce.view.removeInternalAttrs( wp.html.attrs( content ) );
305                 },
306
307                 // ### instance( scope )
308                 //
309                 // Accepts a MCE view wrapper `node` (i.e. a node with the
310                 // `wp-view-wrap` class).
311                 instance: function( node ) {
312                         var id = $( node ).data('wp-view');
313
314                         if ( id )
315                                 return instances[ id ];
316                 },
317
318                 // ### Select a view.
319                 //
320                 // Accepts a MCE view wrapper `node` (i.e. a node with the
321                 // `wp-view-wrap` class).
322                 select: function( node ) {
323                         var $node = $(node);
324
325                         // Bail if node is already selected.
326                         if ( $node.hasClass('selected') )
327                                 return;
328
329                         $node.addClass('selected');
330                         $( node.firstChild ).trigger('select');
331                 },
332
333                 // ### Deselect a view.
334                 //
335                 // Accepts a MCE view wrapper `node` (i.e. a node with the
336                 // `wp-view-wrap` class).
337                 deselect: function( node ) {
338                         var $node = $(node);
339
340                         // Bail if node is already selected.
341                         if ( ! $node.hasClass('selected') )
342                                 return;
343
344                         $node.removeClass('selected');
345                         $( node.firstChild ).trigger('deselect');
346                 }
347         };
348
349 }(jQuery));