UNPKG

@wordpress/block-library

Version:
8 lines (7 loc) 16.6 kB
{ "version": 3, "sources": ["../../src/embed/util.js"], "sourcesContent": ["/**\n * External dependencies\n */\nimport clsx from 'clsx';\nimport memoize from 'memize';\n\n/**\n * WordPress dependencies\n */\nimport { privateApis as componentsPrivateApis } from '@wordpress/components';\nimport { renderToString } from '@wordpress/element';\nimport {\n\tcreateBlock,\n\tgetBlockType,\n\tgetBlockVariations,\n} from '@wordpress/blocks';\n\n/**\n * Internal dependencies\n */\nimport metadata from './block.json';\nimport { ASPECT_RATIOS, WP_EMBED_TYPE } from './constants';\nimport { unlock } from '../lock-unlock';\n\nconst { name: DEFAULT_EMBED_BLOCK } = metadata;\nconst { kebabCase } = unlock( componentsPrivateApis );\n\n/** @typedef {import('@wordpress/blocks').WPBlockVariation} WPBlockVariation */\n\n/**\n * Returns the embed block's information by matching the provided service provider\n *\n * @param {string} provider The embed block's provider\n * @return {WPBlockVariation} The embed block's information\n */\nexport const getEmbedInfoByProvider = ( provider ) =>\n\tgetBlockVariations( DEFAULT_EMBED_BLOCK )?.find(\n\t\t( { name } ) => name === provider\n\t);\n\n/**\n * Returns true if any of the regular expressions match the URL.\n *\n * @param {string} url The URL to test.\n * @param {Array} patterns The list of regular expressions to test against.\n * @return {boolean} True if any of the regular expressions match the URL.\n */\nexport const matchesPatterns = ( url, patterns = [] ) =>\n\tpatterns.some( ( pattern ) => url.match( pattern ) );\n\n/**\n * Finds the block variation that should be used for the URL,\n * based on the provided URL and the variation's patterns.\n *\n * @param {string} url The URL to test.\n * @return {WPBlockVariation} The block variation that should be used for this URL\n */\nexport const findMoreSuitableBlock = ( url ) =>\n\tgetBlockVariations( DEFAULT_EMBED_BLOCK )?.find( ( { patterns } ) =>\n\t\tmatchesPatterns( url, patterns )\n\t);\n\nexport const isFromWordPress = ( html ) =>\n\thtml && html.includes( 'class=\"wp-embedded-content\"' );\n\nexport const getPhotoHtml = ( photo ) => {\n\t// If full image url not found use thumbnail.\n\tconst imageUrl = photo.url || photo.thumbnail_url;\n\n\t// 100% width for the preview so it fits nicely into the document, some \"thumbnails\" are\n\t// actually the full size photo.\n\tconst photoPreview = (\n\t\t<p>\n\t\t\t<img src={ imageUrl } alt={ photo.title } width=\"100%\" />\n\t\t</p>\n\t);\n\treturn renderToString( photoPreview );\n};\n\n/**\n * Creates a more suitable embed block based on the passed in props\n * and attributes generated from an embed block's preview.\n *\n * We require `attributesFromPreview` to be generated from the latest attributes\n * and preview, and because of the way the react lifecycle operates, we can't\n * guarantee that the attributes contained in the block's props are the latest\n * versions, so we require that these are generated separately.\n * See `getAttributesFromPreview` in the generated embed edit component.\n *\n * @param {Object} props The block's props.\n * @param {Object} [attributesFromPreview] Attributes generated from the block's most up to date preview.\n * @return {Object|undefined} A more suitable embed block if one exists.\n */\nexport const createUpgradedEmbedBlock = (\n\tprops,\n\tattributesFromPreview = {}\n) => {\n\tconst { preview, attributes = {} } = props;\n\tconst { url, providerNameSlug, type, ...restAttributes } = attributes;\n\n\tif ( ! url || ! getBlockType( DEFAULT_EMBED_BLOCK ) ) {\n\t\treturn;\n\t}\n\n\tconst matchedBlock = findMoreSuitableBlock( url );\n\n\t// WordPress blocks can work on multiple sites, and so don't have patterns,\n\t// so if we're in a WordPress block, assume the user has chosen it for a WordPress URL.\n\tconst isCurrentBlockWP =\n\t\tproviderNameSlug === 'wordpress' || type === WP_EMBED_TYPE;\n\t// If current block is not WordPress and a more suitable block found\n\t// that is different from the current one, create the new matched block.\n\tconst shouldCreateNewBlock =\n\t\t! isCurrentBlockWP &&\n\t\tmatchedBlock &&\n\t\t( matchedBlock.attributes.providerNameSlug !== providerNameSlug ||\n\t\t\t! providerNameSlug );\n\tif ( shouldCreateNewBlock ) {\n\t\treturn createBlock( DEFAULT_EMBED_BLOCK, {\n\t\t\turl,\n\t\t\t...restAttributes,\n\t\t\t...matchedBlock.attributes,\n\t\t} );\n\t}\n\n\tconst wpVariation = getBlockVariations( DEFAULT_EMBED_BLOCK )?.find(\n\t\t( { name } ) => name === 'wordpress'\n\t);\n\n\t// We can't match the URL for WordPress embeds, we have to check the HTML instead.\n\tif (\n\t\t! wpVariation ||\n\t\t! preview ||\n\t\t! isFromWordPress( preview.html ) ||\n\t\tisCurrentBlockWP\n\t) {\n\t\treturn;\n\t}\n\n\t// This is not the WordPress embed block so transform it into one.\n\treturn createBlock( DEFAULT_EMBED_BLOCK, {\n\t\turl,\n\t\t...wpVariation.attributes,\n\t\t// By now we have the preview, but when the new block first renders, it\n\t\t// won't have had all the attributes set, and so won't get the correct\n\t\t// type and it won't render correctly. So, we pass through the current attributes\n\t\t// here so that the initial render works when we switch to the WordPress\n\t\t// block. This only affects the WordPress block because it can't be\n\t\t// rendered in the usual Sandbox (it has a sandbox of its own) and it\n\t\t// relies on the preview to set the correct render type.\n\t\t...attributesFromPreview,\n\t} );\n};\n\n/**\n * Determine if the block already has an aspect ratio class applied.\n *\n * @param {string} existingClassNames Existing block classes.\n * @return {boolean} True or false if the classnames contain an aspect ratio class.\n */\nexport const hasAspectRatioClass = ( existingClassNames ) => {\n\tif ( ! existingClassNames ) {\n\t\treturn false;\n\t}\n\treturn ASPECT_RATIOS.some( ( { className } ) =>\n\t\texistingClassNames.includes( className )\n\t);\n};\n\n/**\n * Removes all previously set aspect ratio related classes and return the rest\n * existing class names.\n *\n * @param {string} existingClassNames Any existing class names.\n * @return {string} The class names without any aspect ratio related class.\n */\nexport const removeAspectRatioClasses = ( existingClassNames ) => {\n\tif ( ! existingClassNames ) {\n\t\t// Avoids extraneous work and also, by returning the same value as\n\t\t// received, ensures the post is not dirtied by a change of the block\n\t\t// attribute from `undefined` to an empty string.\n\t\treturn existingClassNames;\n\t}\n\tconst aspectRatioClassNames = ASPECT_RATIOS.reduce(\n\t\t( accumulator, { className } ) => {\n\t\t\taccumulator.push( className );\n\t\t\treturn accumulator;\n\t\t},\n\t\t[ 'wp-has-aspect-ratio' ]\n\t);\n\tlet outputClassNames = existingClassNames;\n\tfor ( const className of aspectRatioClassNames ) {\n\t\toutputClassNames = outputClassNames.replace( className, '' );\n\t}\n\treturn outputClassNames.trim();\n};\n\n/**\n * Checks if HTML already contains responsive aspect ratio styling.\n * Some embed providers (like Flickr) include their own responsive wrapper\n * with padding-bottom or padding-top percentages for aspect ratio.\n *\n * @param {string} html The embed HTML to check.\n * @return {boolean} True if the HTML already has responsive styling.\n */\nexport function hasInlineResponsivePadding( html ) {\n\t// Check for padding-bottom or padding-top with percentage values in style attributes\n\t// This pattern matches: padding-bottom: 56.25%; or padding-top: 50%; etc.\n\tconst paddingPattern = /padding-(top|bottom)\\s*:\\s*[\\d.]+%/i;\n\treturn paddingPattern.test( html );\n}\n\n/**\n * Returns class names with any relevant responsive aspect ratio names.\n *\n * @param {string} html The preview HTML that possibly contains an iframe with width and height set.\n * @param {string} existingClassNames Any existing class names.\n * @param {boolean} allowResponsive If the responsive class names should be added, or removed.\n * @return {string} Deduped class names.\n */\nexport function getClassNames(\n\thtml,\n\texistingClassNames,\n\tallowResponsive = true\n) {\n\tif ( ! allowResponsive ) {\n\t\treturn removeAspectRatioClasses( existingClassNames );\n\t}\n\n\t// If the embed HTML already contains responsive wrapper styling (like Flickr),\n\t// don't add our own aspect ratio classes to avoid double padding.\n\tif ( hasInlineResponsivePadding( html ) ) {\n\t\treturn removeAspectRatioClasses( existingClassNames );\n\t}\n\n\tconst previewDocument = document.implementation.createHTMLDocument( '' );\n\tpreviewDocument.body.innerHTML = html;\n\tconst iframe = previewDocument.body.querySelector( 'iframe' );\n\n\t// If we have a fixed aspect iframe, and it's a responsive embed block.\n\tif ( iframe && iframe.height && iframe.width ) {\n\t\tconst aspectRatio = ( iframe.width / iframe.height ).toFixed( 2 );\n\t\t// Given the actual aspect ratio, find the widest ratio to support it.\n\t\tfor (\n\t\t\tlet ratioIndex = 0;\n\t\t\tratioIndex < ASPECT_RATIOS.length;\n\t\t\tratioIndex++\n\t\t) {\n\t\t\tconst potentialRatio = ASPECT_RATIOS[ ratioIndex ];\n\t\t\tif ( aspectRatio >= potentialRatio.ratio ) {\n\t\t\t\t// Evaluate the difference between actual aspect ratio and closest match.\n\t\t\t\t// If the difference is too big, do not scale the embed according to aspect ratio.\n\t\t\t\tconst ratioDiff = aspectRatio - potentialRatio.ratio;\n\t\t\t\tif ( ratioDiff > 0.1 ) {\n\t\t\t\t\t// No close aspect ratio match found.\n\t\t\t\t\treturn removeAspectRatioClasses( existingClassNames );\n\t\t\t\t}\n\t\t\t\t// Close aspect ratio match found.\n\t\t\t\treturn clsx(\n\t\t\t\t\tremoveAspectRatioClasses( existingClassNames ),\n\t\t\t\t\tpotentialRatio.className,\n\t\t\t\t\t'wp-has-aspect-ratio'\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn existingClassNames;\n}\n\n/**\n * Fallback behaviour for unembeddable URLs.\n * Creates a paragraph block containing a link to the URL, and calls `onReplace`.\n *\n * @param {string} url The URL that could not be embedded.\n * @param {Function} onReplace Function to call with the created fallback block.\n */\nexport function fallback( url, onReplace ) {\n\tconst link = <a href={ url }>{ url }</a>;\n\tonReplace(\n\t\tcreateBlock( 'core/paragraph', { content: renderToString( link ) } )\n\t);\n}\n\n/***\n * Gets block attributes based on the preview and responsive state.\n *\n * @param {Object} preview The preview data.\n * @param {string} title The block's title, e.g. Twitter.\n * @param {Object} currentClassNames The block's current class names.\n * @param {boolean} isResponsive Boolean indicating if the block supports responsive content.\n * @param {boolean} allowResponsive Apply responsive classes to fixed size content.\n * @return {Object} Attributes and values.\n */\nexport const getAttributesFromPreview = memoize(\n\t(\n\t\tpreview,\n\t\ttitle,\n\t\tcurrentClassNames,\n\t\tisResponsive,\n\t\tallowResponsive = true\n\t) => {\n\t\tif ( ! preview ) {\n\t\t\treturn {};\n\t\t}\n\n\t\tconst attributes = {};\n\t\t// Some plugins only return HTML with no type info, so default this to 'rich'.\n\t\tlet { type = 'rich' } = preview;\n\t\t// If we got a provider name from the API, use it for the slug, otherwise we use the title,\n\t\t// because not all embed code gives us a provider name.\n\t\tconst { html, provider_name: providerName } = preview;\n\t\tconst providerNameSlug = kebabCase(\n\t\t\t( providerName || title ).toLowerCase()\n\t\t);\n\n\t\tif ( isFromWordPress( html ) ) {\n\t\t\ttype = WP_EMBED_TYPE;\n\t\t}\n\n\t\tif ( html || 'photo' === type ) {\n\t\t\tattributes.type = type;\n\t\t\tattributes.providerNameSlug = providerNameSlug;\n\t\t}\n\n\t\t// Aspect ratio classes are removed when the embed URL is updated.\n\t\t// If the embed already has an aspect ratio class, that means the URL has not changed.\n\t\t// Which also means no need to regenerate it with getClassNames.\n\t\tif ( hasAspectRatioClass( currentClassNames ) ) {\n\t\t\treturn attributes;\n\t\t}\n\n\t\tattributes.className = getClassNames(\n\t\t\thtml,\n\t\t\tcurrentClassNames,\n\t\t\tisResponsive && allowResponsive\n\t\t);\n\n\t\treturn attributes;\n\t}\n);\n\n/**\n * Returns the attributes derived from the preview, merged with the current attributes.\n *\n * @param {Object} currentAttributes The current attributes of the block.\n * @param {Object} preview The preview data.\n * @param {string} title The block's title, e.g. Twitter.\n * @param {boolean} isResponsive Boolean indicating if the block supports responsive content.\n * @return {Object} Merged attributes.\n */\nexport const getMergedAttributesWithPreview = (\n\tcurrentAttributes,\n\tpreview,\n\ttitle,\n\tisResponsive\n) => {\n\tconst { allowResponsive, className } = currentAttributes;\n\n\treturn {\n\t\t...currentAttributes,\n\t\t...getAttributesFromPreview(\n\t\t\tpreview,\n\t\t\ttitle,\n\t\t\tclassName,\n\t\t\tisResponsive,\n\t\t\tallowResponsive\n\t\t),\n\t};\n};\n"], "mappings": "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", "names": [] }