angelovcom.net

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

shortcode.js (10758B)


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