~canonical-sysadmins/wordpress/4.7.2

« back to all changes in this revision

Viewing changes to wp-includes/js/shortcode.js

  • Committer: Jacek Nykis
  • Date: 2015-01-05 16:17:05 UTC
  • Revision ID: jacek.nykis@canonical.com-20150105161705-w544l1h5mcg7u4w9
Initial commit

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
// Utility functions for parsing and handling shortcodes in Javascript.
 
2
 
 
3
// Ensure the global `wp` object exists.
 
4
window.wp = window.wp || {};
 
5
 
 
6
(function(){
 
7
        wp.shortcode = {
 
8
                // ### Find the next matching shortcode
 
9
                //
 
10
                // Given a shortcode `tag`, a block of `text`, and an optional starting
 
11
                // `index`, returns the next matching shortcode or `undefined`.
 
12
                //
 
13
                // Shortcodes are formatted as an object that contains the match
 
14
                // `content`, the matching `index`, and the parsed `shortcode` object.
 
15
                next: function( tag, text, index ) {
 
16
                        var re = wp.shortcode.regexp( tag ),
 
17
                                match, result;
 
18
 
 
19
                        re.lastIndex = index || 0;
 
20
                        match = re.exec( text );
 
21
 
 
22
                        if ( ! match ) {
 
23
                                return;
 
24
                        }
 
25
 
 
26
                        // If we matched an escaped shortcode, try again.
 
27
                        if ( '[' === match[1] && ']' === match[7] ) {
 
28
                                return wp.shortcode.next( tag, text, re.lastIndex );
 
29
                        }
 
30
 
 
31
                        result = {
 
32
                                index:     match.index,
 
33
                                content:   match[0],
 
34
                                shortcode: wp.shortcode.fromMatch( match )
 
35
                        };
 
36
 
 
37
                        // If we matched a leading `[`, strip it from the match
 
38
                        // and increment the index accordingly.
 
39
                        if ( match[1] ) {
 
40
                                result.content = result.content.slice( 1 );
 
41
                                result.index++;
 
42
                        }
 
43
 
 
44
                        // If we matched a trailing `]`, strip it from the match.
 
45
                        if ( match[7] ) {
 
46
                                result.content = result.content.slice( 0, -1 );
 
47
                        }
 
48
 
 
49
                        return result;
 
50
                },
 
51
 
 
52
                // ### Replace matching shortcodes in a block of text
 
53
                //
 
54
                // Accepts a shortcode `tag`, content `text` to scan, and a `callback`
 
55
                // to process the shortcode matches and return a replacement string.
 
56
                // Returns the `text` with all shortcodes replaced.
 
57
                //
 
58
                // Shortcode matches are objects that contain the shortcode `tag`,
 
59
                // a shortcode `attrs` object, the `content` between shortcode tags,
 
60
                // and a boolean flag to indicate if the match was a `single` tag.
 
61
                replace: function( tag, text, callback ) {
 
62
                        return text.replace( wp.shortcode.regexp( tag ), function( match, left, tag, attrs, slash, content, closing, right ) {
 
63
                                // If both extra brackets exist, the shortcode has been
 
64
                                // properly escaped.
 
65
                                if ( left === '[' && right === ']' ) {
 
66
                                        return match;
 
67
                                }
 
68
 
 
69
                                // Create the match object and pass it through the callback.
 
70
                                var result = callback( wp.shortcode.fromMatch( arguments ) );
 
71
 
 
72
                                // Make sure to return any of the extra brackets if they
 
73
                                // weren't used to escape the shortcode.
 
74
                                return result ? left + result + right : match;
 
75
                        });
 
76
                },
 
77
 
 
78
                // ### Generate a string from shortcode parameters
 
79
                //
 
80
                // Creates a `wp.shortcode` instance and returns a string.
 
81
                //
 
82
                // Accepts the same `options` as the `wp.shortcode()` constructor,
 
83
                // containing a `tag` string, a string or object of `attrs`, a boolean
 
84
                // indicating whether to format the shortcode using a `single` tag, and a
 
85
                // `content` string.
 
86
                string: function( options ) {
 
87
                        return new wp.shortcode( options ).string();
 
88
                },
 
89
 
 
90
                // ### Generate a RegExp to identify a shortcode
 
91
                //
 
92
                // The base regex is functionally equivalent to the one found in
 
93
                // `get_shortcode_regex()` in `wp-includes/shortcodes.php`.
 
94
                //
 
95
                // Capture groups:
 
96
                //
 
97
                // 1. An extra `[` to allow for escaping shortcodes with double `[[]]`
 
98
                // 2. The shortcode name
 
99
                // 3. The shortcode argument list
 
100
                // 4. The self closing `/`
 
101
                // 5. The content of a shortcode when it wraps some content.
 
102
                // 6. The closing tag.
 
103
                // 7. An extra `]` to allow for escaping shortcodes with double `[[]]`
 
104
                regexp: _.memoize( function( tag ) {
 
105
                        return new RegExp( '\\[(\\[?)(' + tag + ')(?![\\w-])([^\\]\\/]*(?:\\/(?!\\])[^\\]\\/]*)*?)(?:(\\/)\\]|\\](?:([^\\[]*(?:\\[(?!\\/\\2\\])[^\\[]*)*)(\\[\\/\\2\\]))?)(\\]?)', 'g' );
 
106
                }),
 
107
 
 
108
 
 
109
                // ### Parse shortcode attributes
 
110
                //
 
111
                // Shortcodes accept many types of attributes. These can chiefly be
 
112
                // divided into named and numeric attributes:
 
113
                //
 
114
                // Named attributes are assigned on a key/value basis, while numeric
 
115
                // attributes are treated as an array.
 
116
                //
 
117
                // Named attributes can be formatted as either `name="value"`,
 
118
                // `name='value'`, or `name=value`. Numeric attributes can be formatted
 
119
                // as `"value"` or just `value`.
 
120
                attrs: _.memoize( function( text ) {
 
121
                        var named   = {},
 
122
                                numeric = [],
 
123
                                pattern, match;
 
124
 
 
125
                        // This regular expression is reused from `shortcode_parse_atts()`
 
126
                        // in `wp-includes/shortcodes.php`.
 
127
                        //
 
128
                        // Capture groups:
 
129
                        //
 
130
                        // 1. An attribute name, that corresponds to...
 
131
                        // 2. a value in double quotes.
 
132
                        // 3. An attribute name, that corresponds to...
 
133
                        // 4. a value in single quotes.
 
134
                        // 5. An attribute name, that corresponds to...
 
135
                        // 6. an unquoted value.
 
136
                        // 7. A numeric attribute in double quotes.
 
137
                        // 8. An unquoted numeric attribute.
 
138
                        pattern = /(\w+)\s*=\s*"([^"]*)"(?:\s|$)|(\w+)\s*=\s*\'([^\']*)\'(?:\s|$)|(\w+)\s*=\s*([^\s\'"]+)(?:\s|$)|"([^"]*)"(?:\s|$)|(\S+)(?:\s|$)/g;
 
139
 
 
140
                        // Map zero-width spaces to actual spaces.
 
141
                        text = text.replace( /[\u00a0\u200b]/g, ' ' );
 
142
 
 
143
                        // Match and normalize attributes.
 
144
                        while ( (match = pattern.exec( text )) ) {
 
145
                                if ( match[1] ) {
 
146
                                        named[ match[1].toLowerCase() ] = match[2];
 
147
                                } else if ( match[3] ) {
 
148
                                        named[ match[3].toLowerCase() ] = match[4];
 
149
                                } else if ( match[5] ) {
 
150
                                        named[ match[5].toLowerCase() ] = match[6];
 
151
                                } else if ( match[7] ) {
 
152
                                        numeric.push( match[7] );
 
153
                                } else if ( match[8] ) {
 
154
                                        numeric.push( match[8] );
 
155
                                }
 
156
                        }
 
157
 
 
158
                        return {
 
159
                                named:   named,
 
160
                                numeric: numeric
 
161
                        };
 
162
                }),
 
163
 
 
164
                // ### Generate a Shortcode Object from a RegExp match
 
165
                // Accepts a `match` object from calling `regexp.exec()` on a `RegExp`
 
166
                // generated by `wp.shortcode.regexp()`. `match` can also be set to the
 
167
                // `arguments` from a callback passed to `regexp.replace()`.
 
168
                fromMatch: function( match ) {
 
169
                        var type;
 
170
 
 
171
                        if ( match[4] ) {
 
172
                                type = 'self-closing';
 
173
                        } else if ( match[6] ) {
 
174
                                type = 'closed';
 
175
                        } else {
 
176
                                type = 'single';
 
177
                        }
 
178
 
 
179
                        return new wp.shortcode({
 
180
                                tag:     match[2],
 
181
                                attrs:   match[3],
 
182
                                type:    type,
 
183
                                content: match[5]
 
184
                        });
 
185
                }
 
186
        };
 
187
 
 
188
 
 
189
        // Shortcode Objects
 
190
        // -----------------
 
191
        //
 
192
        // Shortcode objects are generated automatically when using the main
 
193
        // `wp.shortcode` methods: `next()`, `replace()`, and `string()`.
 
194
        //
 
195
        // To access a raw representation of a shortcode, pass an `options` object,
 
196
        // containing a `tag` string, a string or object of `attrs`, a string
 
197
        // indicating the `type` of the shortcode ('single', 'self-closing', or
 
198
        // 'closed'), and a `content` string.
 
199
        wp.shortcode = _.extend( function( options ) {
 
200
                _.extend( this, _.pick( options || {}, 'tag', 'attrs', 'type', 'content' ) );
 
201
 
 
202
                var attrs = this.attrs;
 
203
 
 
204
                // Ensure we have a correctly formatted `attrs` object.
 
205
                this.attrs = {
 
206
                        named:   {},
 
207
                        numeric: []
 
208
                };
 
209
 
 
210
                if ( ! attrs ) {
 
211
                        return;
 
212
                }
 
213
 
 
214
                // Parse a string of attributes.
 
215
                if ( _.isString( attrs ) ) {
 
216
                        this.attrs = wp.shortcode.attrs( attrs );
 
217
 
 
218
                // Identify a correctly formatted `attrs` object.
 
219
                } else if ( _.isEqual( _.keys( attrs ), [ 'named', 'numeric' ] ) ) {
 
220
                        this.attrs = attrs;
 
221
 
 
222
                // Handle a flat object of attributes.
 
223
                } else {
 
224
                        _.each( options.attrs, function( value, key ) {
 
225
                                this.set( key, value );
 
226
                        }, this );
 
227
                }
 
228
        }, wp.shortcode );
 
229
 
 
230
        _.extend( wp.shortcode.prototype, {
 
231
                // ### Get a shortcode attribute
 
232
                //
 
233
                // Automatically detects whether `attr` is named or numeric and routes
 
234
                // it accordingly.
 
235
                get: function( attr ) {
 
236
                        return this.attrs[ _.isNumber( attr ) ? 'numeric' : 'named' ][ attr ];
 
237
                },
 
238
 
 
239
                // ### Set a shortcode attribute
 
240
                //
 
241
                // Automatically detects whether `attr` is named or numeric and routes
 
242
                // it accordingly.
 
243
                set: function( attr, value ) {
 
244
                        this.attrs[ _.isNumber( attr ) ? 'numeric' : 'named' ][ attr ] = value;
 
245
                        return this;
 
246
                },
 
247
 
 
248
                // ### Transform the shortcode match into a string
 
249
                string: function() {
 
250
                        var text    = '[' + this.tag;
 
251
 
 
252
                        _.each( this.attrs.numeric, function( value ) {
 
253
                                if ( /\s/.test( value ) ) {
 
254
                                        text += ' "' + value + '"';
 
255
                                } else {
 
256
                                        text += ' ' + value;
 
257
                                }
 
258
                        });
 
259
 
 
260
                        _.each( this.attrs.named, function( value, name ) {
 
261
                                text += ' ' + name + '="' + value + '"';
 
262
                        });
 
263
 
 
264
                        // If the tag is marked as `single` or `self-closing`, close the
 
265
                        // tag and ignore any additional content.
 
266
                        if ( 'single' === this.type ) {
 
267
                                return text + ']';
 
268
                        } else if ( 'self-closing' === this.type ) {
 
269
                                return text + ' /]';
 
270
                        }
 
271
 
 
272
                        // Complete the opening tag.
 
273
                        text += ']';
 
274
 
 
275
                        if ( this.content ) {
 
276
                                text += this.content;
 
277
                        }
 
278
 
 
279
                        // Add the closing tag.
 
280
                        return text + '[/' + this.tag + ']';
 
281
                }
 
282
        });
 
283
}());
 
284
 
 
285
// HTML utility functions
 
286
// ----------------------
 
287
//
 
288
// Experimental. These functions may change or be removed in the future.
 
289
(function(){
 
290
        wp.html = _.extend( wp.html || {}, {
 
291
                // ### Parse HTML attributes.
 
292
                //
 
293
                // Converts `content` to a set of parsed HTML attributes.
 
294
                // Utilizes `wp.shortcode.attrs( content )`, which is a valid superset of
 
295
                // the HTML attribute specification. Reformats the attributes into an
 
296
                // object that contains the `attrs` with `key:value` mapping, and a record
 
297
                // of the attributes that were entered using `empty` attribute syntax (i.e.
 
298
                // with no value).
 
299
                attrs: function( content ) {
 
300
                        var result, attrs;
 
301
 
 
302
                        // If `content` ends in a slash, strip it.
 
303
                        if ( '/' === content[ content.length - 1 ] ) {
 
304
                                content = content.slice( 0, -1 );
 
305
                        }
 
306
 
 
307
                        result = wp.shortcode.attrs( content );
 
308
                        attrs  = result.named;
 
309
 
 
310
                        _.each( result.numeric, function( key ) {
 
311
                                if ( /\s/.test( key ) ) {
 
312
                                        return;
 
313
                                }
 
314
 
 
315
                                attrs[ key ] = '';
 
316
                        });
 
317
 
 
318
                        return attrs;
 
319
                },
 
320
 
 
321
                // ### Convert an HTML-representation of an object to a string.
 
322
                string: function( options ) {
 
323
                        var text = '<' + options.tag,
 
324
                                content = options.content || '';
 
325
 
 
326
                        _.each( options.attrs, function( value, attr ) {
 
327
                                text += ' ' + attr;
 
328
 
 
329
                                // Use empty attribute notation where possible.
 
330
                                if ( '' === value ) {
 
331
                                        return;
 
332
                                }
 
333
 
 
334
                                // Convert boolean values to strings.
 
335
                                if ( _.isBoolean( value ) ) {
 
336
                                        value = value ? 'true' : 'false';
 
337
                                }
 
338
 
 
339
                                text += '="' + value + '"';
 
340
                        });
 
341
 
 
342
                        // Return the result if it is a self-closing tag.
 
343
                        if ( options.single ) {
 
344
                                return text + ' />';
 
345
                        }
 
346
 
 
347
                        // Complete the opening tag.
 
348
                        text += '>';
 
349
 
 
350
                        // If `content` is an object, recursively call this function.
 
351
                        text += _.isObject( content ) ? wp.html.string( content ) : content;
 
352
 
 
353
                        return text + '</' + options.tag + '>';
 
354
                }
 
355
        });
 
356
}());