[ Index ]

PHP Cross Reference of WordPress

title

Body

[close]

/wp-includes/js/ -> shortcode.js (source)

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


Generated: Wed Sep 18 01:00:03 2019 Cross-referenced by PHPXref 0.7.1