@wordpress/block-library
Version:
Block library for the WordPress editor.
359 lines (317 loc) • 12.6 kB
JavaScript
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