11 */ |
11 */ |
12 window.wp = window.wp || {}; |
12 window.wp = window.wp || {}; |
13 |
13 |
14 (function(){ |
14 (function(){ |
15 wp.shortcode = { |
15 wp.shortcode = { |
16 // ### Find the next matching shortcode |
16 /* |
17 // |
17 * ### Find the next matching shortcode. |
18 // Given a shortcode `tag`, a block of `text`, and an optional starting |
18 * |
19 // `index`, returns the next matching shortcode or `undefined`. |
19 * Given a shortcode `tag`, a block of `text`, and an optional starting |
20 // |
20 * `index`, returns the next matching shortcode or `undefined`. |
21 // Shortcodes are formatted as an object that contains the match |
21 * |
22 // `content`, the matching `index`, and the parsed `shortcode` object. |
22 * Shortcodes are formatted as an object that contains the match |
|
23 * `content`, the matching `index`, and the parsed `shortcode` object. |
|
24 */ |
23 next: function( tag, text, index ) { |
25 next: function( tag, text, index ) { |
24 var re = wp.shortcode.regexp( tag ), |
26 var re = wp.shortcode.regexp( tag ), |
25 match, result; |
27 match, result; |
26 |
28 |
27 re.lastIndex = index || 0; |
29 re.lastIndex = index || 0; |
55 } |
57 } |
56 |
58 |
57 return result; |
59 return result; |
58 }, |
60 }, |
59 |
61 |
60 // ### Replace matching shortcodes in a block of text |
62 /* |
61 // |
63 * ### Replace matching shortcodes in a block of text. |
62 // Accepts a shortcode `tag`, content `text` to scan, and a `callback` |
64 * |
63 // to process the shortcode matches and return a replacement string. |
65 * Accepts a shortcode `tag`, content `text` to scan, and a `callback` |
64 // Returns the `text` with all shortcodes replaced. |
66 * to process the shortcode matches and return a replacement string. |
65 // |
67 * Returns the `text` with all shortcodes replaced. |
66 // Shortcode matches are objects that contain the shortcode `tag`, |
68 * |
67 // a shortcode `attrs` object, the `content` between shortcode tags, |
69 * Shortcode matches are objects that contain the shortcode `tag`, |
68 // and a boolean flag to indicate if the match was a `single` tag. |
70 * a shortcode `attrs` object, the `content` between shortcode tags, |
|
71 * and a boolean flag to indicate if the match was a `single` tag. |
|
72 */ |
69 replace: function( tag, text, callback ) { |
73 replace: function( tag, text, callback ) { |
70 return text.replace( wp.shortcode.regexp( tag ), function( match, left, tag, attrs, slash, content, closing, right ) { |
74 return text.replace( wp.shortcode.regexp( tag ), function( match, left, tag, attrs, slash, content, closing, right ) { |
71 // If both extra brackets exist, the shortcode has been |
75 // If both extra brackets exist, the shortcode has been |
72 // properly escaped. |
76 // properly escaped. |
73 if ( left === '[' && right === ']' ) { |
77 if ( left === '[' && right === ']' ) { |
81 // weren't used to escape the shortcode. |
85 // weren't used to escape the shortcode. |
82 return result ? left + result + right : match; |
86 return result ? left + result + right : match; |
83 }); |
87 }); |
84 }, |
88 }, |
85 |
89 |
86 // ### Generate a string from shortcode parameters |
90 /* |
87 // |
91 * ### Generate a string from shortcode parameters. |
88 // Creates a `wp.shortcode` instance and returns a string. |
92 * |
89 // |
93 * Creates a `wp.shortcode` instance and returns a string. |
90 // Accepts the same `options` as the `wp.shortcode()` constructor, |
94 * |
91 // containing a `tag` string, a string or object of `attrs`, a boolean |
95 * Accepts the same `options` as the `wp.shortcode()` constructor, |
92 // indicating whether to format the shortcode using a `single` tag, and a |
96 * containing a `tag` string, a string or object of `attrs`, a boolean |
93 // `content` string. |
97 * indicating whether to format the shortcode using a `single` tag, and a |
|
98 * `content` string. |
|
99 */ |
94 string: function( options ) { |
100 string: function( options ) { |
95 return new wp.shortcode( options ).string(); |
101 return new wp.shortcode( options ).string(); |
96 }, |
102 }, |
97 |
103 |
98 // ### Generate a RegExp to identify a shortcode |
104 /* |
99 // |
105 * ### Generate a RegExp to identify a shortcode. |
100 // The base regex is functionally equivalent to the one found in |
106 * |
101 // `get_shortcode_regex()` in `wp-includes/shortcodes.php`. |
107 * The base regex is functionally equivalent to the one found in |
102 // |
108 * `get_shortcode_regex()` in `wp-includes/shortcodes.php`. |
103 // Capture groups: |
109 * |
104 // |
110 * Capture groups: |
105 // 1. An extra `[` to allow for escaping shortcodes with double `[[]]` |
111 * |
106 // 2. The shortcode name |
112 * 1. An extra `[` to allow for escaping shortcodes with double `[[]]`. |
107 // 3. The shortcode argument list |
113 * 2. The shortcode name. |
108 // 4. The self closing `/` |
114 * 3. The shortcode argument list. |
109 // 5. The content of a shortcode when it wraps some content. |
115 * 4. The self closing `/`. |
110 // 6. The closing tag. |
116 * 5. The content of a shortcode when it wraps some content. |
111 // 7. An extra `]` to allow for escaping shortcodes with double `[[]]` |
117 * 6. The closing tag. |
|
118 * 7. An extra `]` to allow for escaping shortcodes with double `[[]]`. |
|
119 */ |
112 regexp: _.memoize( function( tag ) { |
120 regexp: _.memoize( function( tag ) { |
113 return new RegExp( '\\[(\\[?)(' + tag + ')(?![\\w-])([^\\]\\/]*(?:\\/(?!\\])[^\\]\\/]*)*?)(?:(\\/)\\]|\\](?:([^\\[]*(?:\\[(?!\\/\\2\\])[^\\[]*)*)(\\[\\/\\2\\]))?)(\\]?)', 'g' ); |
121 return new RegExp( '\\[(\\[?)(' + tag + ')(?![\\w-])([^\\]\\/]*(?:\\/(?!\\])[^\\]\\/]*)*?)(?:(\\/)\\]|\\](?:([^\\[]*(?:\\[(?!\\/\\2\\])[^\\[]*)*)(\\[\\/\\2\\]))?)(\\]?)', 'g' ); |
114 }), |
122 }), |
115 |
123 |
116 |
124 |
117 // ### Parse shortcode attributes |
125 /* |
118 // |
126 * ### Parse shortcode attributes. |
119 // Shortcodes accept many types of attributes. These can chiefly be |
127 * |
120 // divided into named and numeric attributes: |
128 * Shortcodes accept many types of attributes. These can chiefly be |
121 // |
129 * divided into named and numeric attributes: |
122 // Named attributes are assigned on a key/value basis, while numeric |
130 * |
123 // attributes are treated as an array. |
131 * Named attributes are assigned on a key/value basis, while numeric |
124 // |
132 * attributes are treated as an array. |
125 // Named attributes can be formatted as either `name="value"`, |
133 * |
126 // `name='value'`, or `name=value`. Numeric attributes can be formatted |
134 * Named attributes can be formatted as either `name="value"`, |
127 // as `"value"` or just `value`. |
135 * `name='value'`, or `name=value`. Numeric attributes can be formatted |
|
136 * as `"value"` or just `value`. |
|
137 */ |
128 attrs: _.memoize( function( text ) { |
138 attrs: _.memoize( function( text ) { |
129 var named = {}, |
139 var named = {}, |
130 numeric = [], |
140 numeric = [], |
131 pattern, match; |
141 pattern, match; |
132 |
142 |
133 // This regular expression is reused from `shortcode_parse_atts()` |
143 /* |
134 // in `wp-includes/shortcodes.php`. |
144 * This regular expression is reused from `shortcode_parse_atts()` |
135 // |
145 * in `wp-includes/shortcodes.php`. |
136 // Capture groups: |
146 * |
137 // |
147 * Capture groups: |
138 // 1. An attribute name, that corresponds to... |
148 * |
139 // 2. a value in double quotes. |
149 * 1. An attribute name, that corresponds to... |
140 // 3. An attribute name, that corresponds to... |
150 * 2. a value in double quotes. |
141 // 4. a value in single quotes. |
151 * 3. An attribute name, that corresponds to... |
142 // 5. An attribute name, that corresponds to... |
152 * 4. a value in single quotes. |
143 // 6. an unquoted value. |
153 * 5. An attribute name, that corresponds to... |
144 // 7. A numeric attribute in double quotes. |
154 * 6. an unquoted value. |
145 // 8. A numeric attribute in single quotes. |
155 * 7. A numeric attribute in double quotes. |
146 // 9. An unquoted numeric attribute. |
156 * 8. A numeric attribute in single quotes. |
|
157 * 9. An unquoted numeric attribute. |
|
158 */ |
147 pattern = /([\w-]+)\s*=\s*"([^"]*)"(?:\s|$)|([\w-]+)\s*=\s*'([^']*)'(?:\s|$)|([\w-]+)\s*=\s*([^\s'"]+)(?:\s|$)|"([^"]*)"(?:\s|$)|'([^']*)'(?:\s|$)|(\S+)(?:\s|$)/g; |
159 pattern = /([\w-]+)\s*=\s*"([^"]*)"(?:\s|$)|([\w-]+)\s*=\s*'([^']*)'(?:\s|$)|([\w-]+)\s*=\s*([^\s'"]+)(?:\s|$)|"([^"]*)"(?:\s|$)|'([^']*)'(?:\s|$)|(\S+)(?:\s|$)/g; |
148 |
160 |
149 // Map zero-width spaces to actual spaces. |
161 // Map zero-width spaces to actual spaces. |
150 text = text.replace( /[\u00a0\u200b]/g, ' ' ); |
162 text = text.replace( /[\u00a0\u200b]/g, ' ' ); |
151 |
163 |
195 }); |
210 }); |
196 } |
211 } |
197 }; |
212 }; |
198 |
213 |
199 |
214 |
200 // Shortcode Objects |
215 /* |
201 // ----------------- |
216 * Shortcode Objects |
202 // |
217 * ----------------- |
203 // Shortcode objects are generated automatically when using the main |
218 * |
204 // `wp.shortcode` methods: `next()`, `replace()`, and `string()`. |
219 * Shortcode objects are generated automatically when using the main |
205 // |
220 * `wp.shortcode` methods: `next()`, `replace()`, and `string()`. |
206 // To access a raw representation of a shortcode, pass an `options` object, |
221 * |
207 // containing a `tag` string, a string or object of `attrs`, a string |
222 * To access a raw representation of a shortcode, pass an `options` object, |
208 // indicating the `type` of the shortcode ('single', 'self-closing', or |
223 * containing a `tag` string, a string or object of `attrs`, a string |
209 // 'closed'), and a `content` string. |
224 * indicating the `type` of the shortcode ('single', 'self-closing', |
|
225 * or 'closed'), and a `content` string. |
|
226 */ |
210 wp.shortcode = _.extend( function( options ) { |
227 wp.shortcode = _.extend( function( options ) { |
211 _.extend( this, _.pick( options || {}, 'tag', 'attrs', 'type', 'content' ) ); |
228 _.extend( this, _.pick( options || {}, 'tag', 'attrs', 'type', 'content' ) ); |
212 |
229 |
213 var attrs = this.attrs; |
230 var attrs = this.attrs; |
214 |
231 |
225 // Parse a string of attributes. |
242 // Parse a string of attributes. |
226 if ( _.isString( attrs ) ) { |
243 if ( _.isString( attrs ) ) { |
227 this.attrs = wp.shortcode.attrs( attrs ); |
244 this.attrs = wp.shortcode.attrs( attrs ); |
228 |
245 |
229 // Identify a correctly formatted `attrs` object. |
246 // Identify a correctly formatted `attrs` object. |
230 } else if ( _.isEqual( _.keys( attrs ), [ 'named', 'numeric' ] ) ) { |
247 } else if ( _.difference( _.keys( attrs ), [ 'named', 'numeric' ] ).length === 0 ) { |
231 this.attrs = attrs; |
248 this.attrs = _.defaults( attrs, this.attrs ); |
232 |
249 |
233 // Handle a flat object of attributes. |
250 // Handle a flat object of attributes. |
234 } else { |
251 } else { |
235 _.each( options.attrs, function( value, key ) { |
252 _.each( options.attrs, function( value, key ) { |
236 this.set( key, value ); |
253 this.set( key, value ); |
237 }, this ); |
254 }, this ); |
238 } |
255 } |
239 }, wp.shortcode ); |
256 }, wp.shortcode ); |
240 |
257 |
241 _.extend( wp.shortcode.prototype, { |
258 _.extend( wp.shortcode.prototype, { |
242 // ### Get a shortcode attribute |
259 /* |
243 // |
260 * ### Get a shortcode attribute. |
244 // Automatically detects whether `attr` is named or numeric and routes |
261 * |
245 // it accordingly. |
262 * Automatically detects whether `attr` is named or numeric and routes |
|
263 * it accordingly. |
|
264 */ |
246 get: function( attr ) { |
265 get: function( attr ) { |
247 return this.attrs[ _.isNumber( attr ) ? 'numeric' : 'named' ][ attr ]; |
266 return this.attrs[ _.isNumber( attr ) ? 'numeric' : 'named' ][ attr ]; |
248 }, |
267 }, |
249 |
268 |
250 // ### Set a shortcode attribute |
269 /* |
251 // |
270 * ### Set a shortcode attribute. |
252 // Automatically detects whether `attr` is named or numeric and routes |
271 * |
253 // it accordingly. |
272 * Automatically detects whether `attr` is named or numeric and routes |
|
273 * it accordingly. |
|
274 */ |
254 set: function( attr, value ) { |
275 set: function( attr, value ) { |
255 this.attrs[ _.isNumber( attr ) ? 'numeric' : 'named' ][ attr ] = value; |
276 this.attrs[ _.isNumber( attr ) ? 'numeric' : 'named' ][ attr ] = value; |
256 return this; |
277 return this; |
257 }, |
278 }, |
258 |
279 |
259 // ### Transform the shortcode match into a string |
280 // ### Transform the shortcode match into a string. |
260 string: function() { |
281 string: function() { |
261 var text = '[' + this.tag; |
282 var text = '[' + this.tag; |
262 |
283 |
263 _.each( this.attrs.numeric, function( value ) { |
284 _.each( this.attrs.numeric, function( value ) { |
264 if ( /\s/.test( value ) ) { |
285 if ( /\s/.test( value ) ) { |
291 return text + '[/' + this.tag + ']'; |
312 return text + '[/' + this.tag + ']'; |
292 } |
313 } |
293 }); |
314 }); |
294 }()); |
315 }()); |
295 |
316 |
296 // HTML utility functions |
317 /* |
297 // ---------------------- |
318 * HTML utility functions |
298 // |
319 * ---------------------- |
299 // Experimental. These functions may change or be removed in the future. |
320 * |
|
321 * Experimental. These functions may change or be removed in the future. |
|
322 */ |
300 (function(){ |
323 (function(){ |
301 wp.html = _.extend( wp.html || {}, { |
324 wp.html = _.extend( wp.html || {}, { |
302 // ### Parse HTML attributes. |
325 /* |
303 // |
326 * ### Parse HTML attributes. |
304 // Converts `content` to a set of parsed HTML attributes. |
327 * |
305 // Utilizes `wp.shortcode.attrs( content )`, which is a valid superset of |
328 * Converts `content` to a set of parsed HTML attributes. |
306 // the HTML attribute specification. Reformats the attributes into an |
329 * Utilizes `wp.shortcode.attrs( content )`, which is a valid superset of |
307 // object that contains the `attrs` with `key:value` mapping, and a record |
330 * the HTML attribute specification. Reformats the attributes into an |
308 // of the attributes that were entered using `empty` attribute syntax (i.e. |
331 * object that contains the `attrs` with `key:value` mapping, and a record |
309 // with no value). |
332 * of the attributes that were entered using `empty` attribute syntax (i.e. |
|
333 * with no value). |
|
334 */ |
310 attrs: function( content ) { |
335 attrs: function( content ) { |
311 var result, attrs; |
336 var result, attrs; |
312 |
337 |
313 // If `content` ends in a slash, strip it. |
338 // If `content` ends in a slash, strip it. |
314 if ( '/' === content[ content.length - 1 ] ) { |
339 if ( '/' === content[ content.length - 1 ] ) { |