@wordpress/shortcode
Version:
Shortcode module for WordPress.
8 lines (7 loc) • 14.3 kB
Source Map (JSON)
{
"version": 3,
"sources": ["../src/index.ts"],
"sourcesContent": ["/**\n * External dependencies\n */\nimport memize from 'memize';\n\n/**\n * Internal dependencies\n */\nimport type {\n\tShortcodeAttrs,\n\tShortcodeMatch,\n\tShortcodeOptions,\n\tMatch,\n\tReplaceCallback,\n\tShortcodeInstance,\n} from './types';\n\nexport * from './types';\n\n/**\n * Find the next matching shortcode.\n *\n * @param tag Shortcode tag.\n * @param text Text to search.\n * @param index Index to start search from.\n *\n * @return Matched information.\n */\nexport function next(\n\ttag: string,\n\ttext: string,\n\tindex: number = 0\n): ShortcodeMatch | undefined {\n\tconst re = regexp( tag );\n\n\tre.lastIndex = index;\n\n\tconst match = re.exec( text );\n\n\tif ( ! match ) {\n\t\treturn;\n\t}\n\n\t// If we matched an escaped shortcode, try again.\n\tif ( '[' === match[ 1 ] && ']' === match[ 7 ] ) {\n\t\treturn next( tag, text, re.lastIndex );\n\t}\n\n\tconst result: ShortcodeMatch = {\n\t\tindex: match.index,\n\t\tcontent: match[ 0 ],\n\t\tshortcode: fromMatch( match ),\n\t};\n\n\t// If we matched a leading `[`, strip it from the match and increment the\n\t// index accordingly.\n\tif ( match[ 1 ] ) {\n\t\tresult.content = result.content.slice( 1 );\n\t\tresult.index++;\n\t}\n\n\t// If we matched a trailing `]`, strip it from the match.\n\tif ( match[ 7 ] ) {\n\t\tresult.content = result.content.slice( 0, -1 );\n\t}\n\n\treturn result;\n}\n\n/**\n * Replace matching shortcodes in a block of text.\n *\n * @param tag Shortcode tag.\n * @param text Text to search.\n * @param callback Function to process the match and return\n * replacement string.\n *\n * @return Text with shortcodes replaced.\n */\nexport function replace(\n\ttag: string,\n\ttext: string,\n\tcallback: ReplaceCallback\n) {\n\treturn text.replace(\n\t\tregexp( tag ),\n\t\t// Let us use spread syntax to capture the arguments object.\n\t\t( ...args: string[] ): string => {\n\t\t\tconst match = args[ 0 ];\n\t\t\tconst left = args[ 1 ];\n\t\t\tconst right = args[ 7 ];\n\n\t\t\t// If both extra brackets exist, the shortcode has been properly\n\t\t\t// escaped.\n\t\t\tif ( left === '[' && right === ']' ) {\n\t\t\t\treturn match;\n\t\t\t}\n\n\t\t\t// Create the match object and pass it through the callback.\n\t\t\tconst result = callback( fromMatch( args ) );\n\n\t\t\t// Make sure to return any of the extra brackets if they weren't used to\n\t\t\t// escape the shortcode.\n\t\t\treturn result || result === '' ? left + result + right : match;\n\t\t}\n\t);\n}\n\n/**\n * Generate a string from shortcode parameters.\n *\n * Creates a shortcode instance and returns a string.\n *\n * Accepts the same `options` as the `shortcode()` constructor, containing a\n * `tag` string, a string or object of `attrs`, a boolean indicating whether to\n * format the shortcode using a `single` tag, and a `content` string.\n *\n * @param options Shortcode options.\n *\n * @return String representation of the shortcode.\n */\nexport function string( options: ShortcodeOptions ): string {\n\treturn new Shortcode( options ).string();\n}\n\n/**\n * Generate a RegExp to identify a shortcode.\n *\n * The base regex is functionally equivalent to the one found in\n * `get_shortcode_regex()` in `wp-includes/shortcodes.php`.\n *\n * Capture groups:\n *\n * 1. An extra `[` to allow for escaping shortcodes with double `[[]]`\n * 2. The shortcode name\n * 3. The shortcode argument list\n * 4. The self closing `/`\n * 5. The content of a shortcode when it wraps some content.\n * 6. The closing tag.\n * 7. An extra `]` to allow for escaping shortcodes with double `[[]]`\n *\n * @param tag Shortcode tag.\n *\n * @return Shortcode RegExp.\n */\nexport function regexp( tag: string ): RegExp {\n\treturn new RegExp(\n\t\t'\\\\[(\\\\[?)(' +\n\t\t\ttag +\n\t\t\t')(?![\\\\w-])([^\\\\]\\\\/]*(?:\\\\/(?!\\\\])[^\\\\]\\\\/]*)*?)(?:(\\\\/)\\\\]|\\\\](?:([^\\\\[]*(?:\\\\[(?!\\\\/\\\\2\\\\])[^\\\\[]*)*)(\\\\[\\\\/\\\\2\\\\]))?)(\\\\]?)',\n\t\t'g'\n\t);\n}\n\n/**\n * Parse shortcode attributes.\n *\n * Shortcodes accept many types of attributes. These can chiefly be divided into\n * named and numeric attributes:\n *\n * Named attributes are assigned on a key/value basis, while numeric attributes\n * are treated as an array.\n *\n * Named attributes can be formatted as either `name=\"value\"`, `name='value'`,\n * or `name=value`. Numeric attributes can be formatted as `\"value\"` or just\n * `value`.\n *\n * @param {string} text Serialised shortcode attributes.\n *\n * @return {ShortcodeAttrs} Parsed shortcode attributes.\n */\nexport const attrs = memize( ( text: string ): ShortcodeAttrs => {\n\tconst named: Record< string, string | undefined > = {};\n\tconst numeric: string[] = [];\n\n\t// This regular expression is reused from `shortcode_parse_atts()` in\n\t// `wp-includes/shortcodes.php`.\n\t//\n\t// Capture groups:\n\t//\n\t// 1. An attribute name, that corresponds to...\n\t// 2. a value in double quotes.\n\t// 3. An attribute name, that corresponds to...\n\t// 4. a value in single quotes.\n\t// 5. An attribute name, that corresponds to...\n\t// 6. an unquoted value.\n\t// 7. A numeric attribute in double quotes.\n\t// 8. A numeric attribute in single quotes.\n\t// 9. An unquoted numeric attribute.\n\tconst pattern =\n\t\t/([\\w-]+)\\s*=\\s*\"([^\"]*)\"(?:\\s|$)|([\\w-]+)\\s*=\\s*'([^']*)'(?:\\s|$)|([\\w-]+)\\s*=\\s*([^\\s'\"]+)(?:\\s|$)|\"([^\"]*)\"(?:\\s|$)|'([^']*)'(?:\\s|$)|(\\S+)(?:\\s|$)/g;\n\n\t// Map zero-width spaces to actual spaces.\n\ttext = text.replace( /[\\u00a0\\u200b]/g, ' ' );\n\n\tlet match;\n\n\t// Match and normalize attributes.\n\twhile ( ( match = pattern.exec( text ) ) ) {\n\t\tif ( match[ 1 ] ) {\n\t\t\tnamed[ match[ 1 ].toLowerCase() ] = match[ 2 ];\n\t\t} else if ( match[ 3 ] ) {\n\t\t\tnamed[ match[ 3 ].toLowerCase() ] = match[ 4 ];\n\t\t} else if ( match[ 5 ] ) {\n\t\t\tnamed[ match[ 5 ].toLowerCase() ] = match[ 6 ];\n\t\t} else if ( match[ 7 ] ) {\n\t\t\tnumeric.push( match[ 7 ] );\n\t\t} else if ( match[ 8 ] ) {\n\t\t\tnumeric.push( match[ 8 ] );\n\t\t} else if ( match[ 9 ] ) {\n\t\t\tnumeric.push( match[ 9 ] );\n\t\t}\n\t}\n\n\treturn { named, numeric };\n} );\n\n/**\n * Generate a Shortcode Object from a RegExp match.\n *\n * Accepts a `match` object from calling `regexp.exec()` on a `RegExp` generated\n * by `regexp()`. `match` can also be set to the `arguments` from a callback\n * passed to `regexp.replace()`.\n *\n * @param match Match array.\n *\n * @return Shortcode instance.\n */\nexport function fromMatch( match: Match ): ShortcodeInstance {\n\tlet type: 'self-closing' | 'closed' | 'single';\n\n\tif ( match[ 4 ] ) {\n\t\ttype = 'self-closing';\n\t} else if ( match[ 6 ] ) {\n\t\ttype = 'closed';\n\t} else {\n\t\ttype = 'single';\n\t}\n\n\treturn new Shortcode( {\n\t\ttag: match[ 2 ],\n\t\tattrs: match[ 3 ],\n\t\ttype,\n\t\tcontent: match[ 5 ],\n\t} );\n}\n\n/**\n * Creates a shortcode instance.\n *\n * To access a raw representation of a shortcode, pass an `options` object,\n * containing a `tag` string, a string or object of `attrs`, a string indicating\n * the `type` of the shortcode ('single', 'self-closing', or 'closed'), and a\n * `content` string.\n */\nclass Shortcode implements ShortcodeInstance {\n\t// Instance properties\n\ttag: string;\n\ttype?: 'self-closing' | 'closed' | 'single';\n\tcontent?: string;\n\tattrs: ShortcodeAttrs;\n\n\t// Static methods\n\tstatic next = next;\n\tstatic replace = replace;\n\tstatic string = string;\n\tstatic regexp = regexp;\n\tstatic attrs = attrs;\n\tstatic fromMatch = fromMatch;\n\n\tconstructor( options: ShortcodeOptions ) {\n\t\tconst { tag, attrs: attributes, type, content } = options;\n\t\tthis.tag = tag;\n\t\tthis.type = type;\n\t\tthis.content = content;\n\n\t\t// Ensure we have a correctly formatted `attrs` object.\n\t\tthis.attrs = {\n\t\t\tnamed: {},\n\t\t\tnumeric: [],\n\t\t};\n\n\t\tif ( ! attributes ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Parse a string of attributes.\n\t\tif ( typeof attributes === 'string' ) {\n\t\t\tthis.attrs = attrs( attributes );\n\t\t\t// Identify a correctly formatted `attrs` object.\n\t\t} else if (\n\t\t\t'named' in attributes &&\n\t\t\t'numeric' in attributes &&\n\t\t\tattributes.named !== undefined &&\n\t\t\tattributes.numeric !== undefined\n\t\t) {\n\t\t\tthis.attrs = attributes as ShortcodeAttrs;\n\t\t\t// Handle a flat object of attributes (e.g., { foo: 'bar', baz: 'qux' }).\n\t\t} else {\n\t\t\tObject.entries( attributes ).forEach( ( [ key, value ] ) => {\n\t\t\t\tif ( value !== undefined ) {\n\t\t\t\t\t// Coerce non-string values to strings to maintain backward compatibility.\n\t\t\t\t\tthis.set( key, String( value ) );\n\t\t\t\t}\n\t\t\t} );\n\t\t}\n\t}\n\n\t/**\n\t * Get a shortcode attribute.\n\t *\n\t * Automatically detects whether `attr` is named or numeric and routes it\n\t * accordingly.\n\t *\n\t * @param attr Attribute key.\n\t *\n\t * @return Attribute value.\n\t */\n\tget( attr: string | number ): string | undefined {\n\t\tif ( typeof attr === 'number' ) {\n\t\t\treturn this.attrs.numeric[ attr ];\n\t\t}\n\t\treturn this.attrs.named[ attr ];\n\t}\n\n\t/**\n\t * Set a shortcode attribute.\n\t *\n\t * Automatically detects whether `attr` is named or numeric and routes it\n\t * accordingly.\n\t *\n\t * @param attr Attribute key.\n\t * @param value Attribute value.\n\t *\n\t * @return Shortcode instance.\n\t */\n\tset( attr: string | number, value: string ): this {\n\t\tif ( typeof attr === 'number' ) {\n\t\t\tthis.attrs.numeric[ attr ] = value;\n\t\t} else {\n\t\t\tthis.attrs.named[ attr ] = value;\n\t\t}\n\t\treturn this;\n\t}\n\n\t/**\n\t * Transform the shortcode into a string.\n\t *\n\t * @return String representation of the shortcode.\n\t */\n\tstring(): string {\n\t\tlet text = '[' + this.tag;\n\n\t\tthis.attrs.numeric.forEach( ( value ) => {\n\t\t\tif ( /\\s/.test( value ) ) {\n\t\t\t\ttext += ' \"' + value + '\"';\n\t\t\t} else {\n\t\t\t\ttext += ' ' + value;\n\t\t\t}\n\t\t} );\n\n\t\tObject.entries( this.attrs.named ).forEach( ( [ name, value ] ) => {\n\t\t\ttext += ' ' + name + '=\"' + value + '\"';\n\t\t} );\n\n\t\t// If the tag is marked as `single` or `self-closing`, close the tag and\n\t\t// ignore any additional content.\n\t\tif ( 'single' === this.type ) {\n\t\t\treturn text + ']';\n\t\t} else if ( 'self-closing' === this.type ) {\n\t\t\treturn text + ' /]';\n\t\t}\n\n\t\t// Complete the opening tag.\n\t\ttext += ']';\n\n\t\tif ( this.content ) {\n\t\t\ttext += this.content;\n\t\t}\n\n\t\t// Add the closing tag.\n\t\treturn text + '[/' + this.tag + ']';\n\t}\n}\n\nexport default Shortcode;\n"],
"mappings": "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",
"names": []
}