UNPKG

@wordpress/block-library

Version:
359 lines (317 loc) 12.6 kB
import { createElement } from "@wordpress/element"; /** * Internal dependencies */ import { ASPECT_RATIOS, WP_EMBED_TYPE } from './constants'; /** * External dependencies */ import { kebabCase } from 'lodash'; import classnames from 'classnames/dedupe'; import memoize from 'memize'; /** * WordPress dependencies */ import { renderToString } from '@wordpress/element'; import { createBlock, getBlockType, getBlockVariations } from '@wordpress/blocks'; /** * Internal dependencies */ const metadata = { $schema: "https://schemas.wp.org/trunk/block.json", apiVersion: 2, name: "core/embed", title: "Embed", category: "embed", description: "Add a block that displays content pulled from other sites, like Twitter or YouTube.", textdomain: "default", attributes: { url: { type: "string", __experimentalRole: "content" }, caption: { type: "string", source: "html", selector: "figcaption", __experimentalRole: "content" }, type: { type: "string", __experimentalRole: "content" }, providerNameSlug: { type: "string", __experimentalRole: "content" }, allowResponsive: { type: "boolean", "default": true }, responsive: { type: "boolean", "default": false, __experimentalRole: "content" }, previewable: { type: "boolean", "default": true, __experimentalRole: "content" } }, supports: { align: true }, editorStyle: "wp-block-embed-editor", style: "wp-block-embed" }; const { name: DEFAULT_EMBED_BLOCK } = metadata; /** @typedef {import('@wordpress/blocks').WPBlockVariation} WPBlockVariation */ /** * Returns the embed block's information by matching the provided service provider * * @param {string} provider The embed block's provider * @return {WPBlockVariation} The embed block's information */ export const getEmbedInfoByProvider = provider => { var _getBlockVariations; return (_getBlockVariations = getBlockVariations(DEFAULT_EMBED_BLOCK)) === null || _getBlockVariations === void 0 ? void 0 : _getBlockVariations.find(_ref => { let { name } = _ref; return name === provider; }); }; /** * Returns true if any of the regular expressions match the URL. * * @param {string} url The URL to test. * @param {Array} patterns The list of regular expressions to test agains. * @return {boolean} True if any of the regular expressions match the URL. */ export const matchesPatterns = function (url) { let patterns = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : []; return patterns.some(pattern => url.match(pattern)); }; /** * Finds the block variation that should be used for the URL, * based on the provided URL and the variation's patterns. * * @param {string} url The URL to test. * @return {WPBlockVariation} The block variation that should be used for this URL */ export const findMoreSuitableBlock = url => { var _getBlockVariations2; return (_getBlockVariations2 = getBlockVariations(DEFAULT_EMBED_BLOCK)) === null || _getBlockVariations2 === void 0 ? void 0 : _getBlockVariations2.find(_ref2 => { let { patterns } = _ref2; return matchesPatterns(url, patterns); }); }; export const isFromWordPress = html => html && html.includes('class="wp-embedded-content"'); export const getPhotoHtml = photo => { // If full image url not found use thumbnail. const imageUrl = photo.url || photo.thumbnail_url; // 100% width for the preview so it fits nicely into the document, some "thumbnails" are // actually the full size photo. const photoPreview = createElement("p", null, createElement("img", { src: imageUrl, alt: photo.title, width: "100%" })); return renderToString(photoPreview); }; /** * Creates a more suitable embed block based on the passed in props * and attributes generated from an embed block's preview. * * We require `attributesFromPreview` to be generated from the latest attributes * and preview, and because of the way the react lifecycle operates, we can't * guarantee that the attributes contained in the block's props are the latest * versions, so we require that these are generated separately. * See `getAttributesFromPreview` in the generated embed edit component. * * @param {Object} props The block's props. * @param {Object} [attributesFromPreview] Attributes generated from the block's most up to date preview. * @return {Object|undefined} A more suitable embed block if one exists. */ export const createUpgradedEmbedBlock = function (props) { var _getBlockVariations3; let attributesFromPreview = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; const { preview, attributes = {} } = props; const { url, providerNameSlug, type, ...restAttributes } = attributes; if (!url || !getBlockType(DEFAULT_EMBED_BLOCK)) return; const matchedBlock = findMoreSuitableBlock(url); // WordPress blocks can work on multiple sites, and so don't have patterns, // so if we're in a WordPress block, assume the user has chosen it for a WordPress URL. const isCurrentBlockWP = providerNameSlug === 'wordpress' || type === WP_EMBED_TYPE; // If current block is not WordPress and a more suitable block found // that is different from the current one, create the new matched block. const shouldCreateNewBlock = !isCurrentBlockWP && matchedBlock && (matchedBlock.attributes.providerNameSlug !== providerNameSlug || !providerNameSlug); if (shouldCreateNewBlock) { return createBlock(DEFAULT_EMBED_BLOCK, { url, ...restAttributes, ...matchedBlock.attributes }); } const wpVariation = (_getBlockVariations3 = getBlockVariations(DEFAULT_EMBED_BLOCK)) === null || _getBlockVariations3 === void 0 ? void 0 : _getBlockVariations3.find(_ref3 => { let { name } = _ref3; return name === 'wordpress'; }); // We can't match the URL for WordPress embeds, we have to check the HTML instead. if (!wpVariation || !preview || !isFromWordPress(preview.html) || isCurrentBlockWP) { return; } // This is not the WordPress embed block so transform it into one. return createBlock(DEFAULT_EMBED_BLOCK, { url, ...wpVariation.attributes, // By now we have the preview, but when the new block first renders, it // won't have had all the attributes set, and so won't get the correct // type and it won't render correctly. So, we pass through the current attributes // here so that the initial render works when we switch to the WordPress // block. This only affects the WordPress block because it can't be // rendered in the usual Sandbox (it has a sandbox of its own) and it // relies on the preview to set the correct render type. ...attributesFromPreview }); }; /** * Removes all previously set aspect ratio related classes and return the rest * existing class names. * * @param {string} existingClassNames Any existing class names. * @return {string} The class names without any aspect ratio related class. */ export const removeAspectRatioClasses = existingClassNames => { if (!existingClassNames) { // Avoids extraneous work and also, by returning the same value as // received, ensures the post is not dirtied by a change of the block // attribute from `undefined` to an emtpy string. return existingClassNames; } const aspectRatioClassNames = ASPECT_RATIOS.reduce((accumulator, _ref4) => { let { className } = _ref4; accumulator[className] = false; return accumulator; }, { 'wp-has-aspect-ratio': false }); return classnames(existingClassNames, aspectRatioClassNames); }; /** * Returns class names with any relevant responsive aspect ratio names. * * @param {string} html The preview HTML that possibly contains an iframe with width and height set. * @param {string} existingClassNames Any existing class names. * @param {boolean} allowResponsive If the responsive class names should be added, or removed. * @return {string} Deduped class names. */ export function getClassNames(html, existingClassNames) { let allowResponsive = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true; if (!allowResponsive) { return removeAspectRatioClasses(existingClassNames); } const previewDocument = document.implementation.createHTMLDocument(''); previewDocument.body.innerHTML = html; const iframe = previewDocument.body.querySelector('iframe'); // If we have a fixed aspect iframe, and it's a responsive embed block. if (iframe && iframe.height && iframe.width) { const aspectRatio = (iframe.width / iframe.height).toFixed(2); // Given the actual aspect ratio, find the widest ratio to support it. for (let ratioIndex = 0; ratioIndex < ASPECT_RATIOS.length; ratioIndex++) { const potentialRatio = ASPECT_RATIOS[ratioIndex]; if (aspectRatio >= potentialRatio.ratio) { // Evaluate the difference between actual aspect ratio and closest match. // If the difference is too big, do not scale the embed according to aspect ratio. const ratioDiff = aspectRatio - potentialRatio.ratio; if (ratioDiff > 0.1) { // No close aspect ratio match found. return removeAspectRatioClasses(existingClassNames); } // Close aspect ratio match found. return classnames(removeAspectRatioClasses(existingClassNames), potentialRatio.className, 'wp-has-aspect-ratio'); } } } return existingClassNames; } /** * Fallback behaviour for unembeddable URLs. * Creates a paragraph block containing a link to the URL, and calls `onReplace`. * * @param {string} url The URL that could not be embedded. * @param {Function} onReplace Function to call with the created fallback block. */ export function fallback(url, onReplace) { const link = createElement("a", { href: url }, url); onReplace(createBlock('core/paragraph', { content: renderToString(link) })); } /*** * Gets block attributes based on the preview and responsive state. * * @param {Object} preview The preview data. * @param {string} title The block's title, e.g. Twitter. * @param {Object} currentClassNames The block's current class names. * @param {boolean} isResponsive Boolean indicating if the block supports responsive content. * @param {boolean} allowResponsive Apply responsive classes to fixed size content. * @return {Object} Attributes and values. */ export const getAttributesFromPreview = memoize(function (preview, title, currentClassNames, isResponsive) { let allowResponsive = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : true; if (!preview) { return {}; } const attributes = {}; // Some plugins only return HTML with no type info, so default this to 'rich'. let { type = 'rich' } = preview; // If we got a provider name from the API, use it for the slug, otherwise we use the title, // because not all embed code gives us a provider name. const { html, provider_name: providerName } = preview; const providerNameSlug = kebabCase((providerName || title).toLowerCase()); if (isFromWordPress(html)) { type = WP_EMBED_TYPE; } if (html || 'photo' === type) { attributes.type = type; attributes.providerNameSlug = providerNameSlug; } attributes.className = getClassNames(html, currentClassNames, isResponsive && allowResponsive); return attributes; }); /** * Returns the attributes derived from the preview, merged with the current attributes. * * @param {Object} currentAttributes The current attributes of the block. * @param {Object} preview The preview data. * @param {string} title The block's title, e.g. Twitter. * @param {boolean} isResponsive Boolean indicating if the block supports responsive content. * @param {boolean} ignorePreviousClassName Determines if the previous className attribute should be ignored when merging. * @return {Object} Merged attributes. */ export const getMergedAttributesWithPreview = function (currentAttributes, preview, title, isResponsive) { let ignorePreviousClassName = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false; const { allowResponsive, className } = currentAttributes; return { ...currentAttributes, ...getAttributesFromPreview(preview, title, ignorePreviousClassName ? undefined : className, isResponsive, allowResponsive) }; }; //# sourceMappingURL=util.js.map