UNPKG

@wordpress/server-side-render

Version:

The component used with WordPress to server-side render a preview of dynamic blocks to display in the editor.

227 lines (221 loc) 7.32 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.default = ServerSideRender; exports.removeBlockSupportAttributes = removeBlockSupportAttributes; exports.rendererPath = rendererPath; var _es = _interopRequireDefault(require("fast-deep-equal/es6")); var _compose = require("@wordpress/compose"); var _element = require("@wordpress/element"); var _i18n = require("@wordpress/i18n"); var _apiFetch = _interopRequireDefault(require("@wordpress/api-fetch")); var _url = require("@wordpress/url"); var _components = require("@wordpress/components"); var _blocks = require("@wordpress/blocks"); var _jsxRuntime = require("react/jsx-runtime"); /** * External dependencies */ /** * WordPress dependencies */ const EMPTY_OBJECT = {}; function rendererPath(block, attributes = null, urlQueryArgs = {}) { return (0, _url.addQueryArgs)(`/wp/v2/block-renderer/${block}`, { context: 'edit', ...(null !== attributes ? { attributes } : {}), ...urlQueryArgs }); } function removeBlockSupportAttributes(attributes) { const { backgroundColor, borderColor, fontFamily, fontSize, gradient, textColor, className, ...restAttributes } = attributes; const { border, color, elements, spacing, typography, ...restStyles } = attributes?.style || EMPTY_OBJECT; return { ...restAttributes, style: restStyles }; } function DefaultEmptyResponsePlaceholder({ className }) { return /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.Placeholder, { className: className, children: (0, _i18n.__)('Block rendered as empty.') }); } function DefaultErrorResponsePlaceholder({ response, className }) { const errorMessage = (0, _i18n.sprintf)( // translators: %s: error message describing the problem (0, _i18n.__)('Error loading block: %s'), response.errorMsg); return /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.Placeholder, { className: className, children: errorMessage }); } function DefaultLoadingResponsePlaceholder({ children }) { const [showLoader, setShowLoader] = (0, _element.useState)(false); (0, _element.useEffect)(() => { // Schedule showing the Spinner after 1 second. const timeout = setTimeout(() => { setShowLoader(true); }, 1000); return () => clearTimeout(timeout); }, []); return /*#__PURE__*/(0, _jsxRuntime.jsxs)("div", { style: { position: 'relative' }, children: [showLoader && /*#__PURE__*/(0, _jsxRuntime.jsx)("div", { style: { position: 'absolute', top: '50%', left: '50%', marginTop: '-9px', marginLeft: '-9px' }, children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_components.Spinner, {}) }), /*#__PURE__*/(0, _jsxRuntime.jsx)("div", { style: { opacity: showLoader ? '0.3' : 1 }, children: children })] }); } function ServerSideRender(props) { const { className, EmptyResponsePlaceholder = DefaultEmptyResponsePlaceholder, ErrorResponsePlaceholder = DefaultErrorResponsePlaceholder, LoadingResponsePlaceholder = DefaultLoadingResponsePlaceholder } = props; const isMountedRef = (0, _element.useRef)(false); const fetchRequestRef = (0, _element.useRef)(); const [response, setResponse] = (0, _element.useState)(null); const prevProps = (0, _compose.usePrevious)(props); const [isLoading, setIsLoading] = (0, _element.useState)(false); const latestPropsRef = (0, _element.useRef)(props); (0, _element.useLayoutEffect)(() => { latestPropsRef.current = props; }, [props]); const fetchData = (0, _element.useCallback)(() => { var _sanitizedAttributes, _sanitizedAttributes2; if (!isMountedRef.current) { return; } const { attributes, block, skipBlockSupportAttributes = false, httpMethod = 'GET', urlQueryArgs } = latestPropsRef.current; setIsLoading(true); let sanitizedAttributes = attributes && (0, _blocks.__experimentalSanitizeBlockAttributes)(block, attributes); if (skipBlockSupportAttributes) { sanitizedAttributes = removeBlockSupportAttributes(sanitizedAttributes); } // If httpMethod is 'POST', send the attributes in the request body instead of the URL. // This allows sending a larger attributes object than in a GET request, where the attributes are in the URL. const isPostRequest = 'POST' === httpMethod; const urlAttributes = isPostRequest ? null : (_sanitizedAttributes = sanitizedAttributes) !== null && _sanitizedAttributes !== void 0 ? _sanitizedAttributes : null; const path = rendererPath(block, urlAttributes, urlQueryArgs); const data = isPostRequest ? { attributes: (_sanitizedAttributes2 = sanitizedAttributes) !== null && _sanitizedAttributes2 !== void 0 ? _sanitizedAttributes2 : null } : null; // Store the latest fetch request so that when we process it, we can // check if it is the current request, to avoid race conditions on slow networks. const fetchRequest = fetchRequestRef.current = (0, _apiFetch.default)({ path, data, method: isPostRequest ? 'POST' : 'GET' }).then(fetchResponse => { if (isMountedRef.current && fetchRequest === fetchRequestRef.current && fetchResponse) { setResponse(fetchResponse.rendered); } }).catch(error => { if (isMountedRef.current && fetchRequest === fetchRequestRef.current) { setResponse({ error: true, errorMsg: error.message }); } }).finally(() => { if (isMountedRef.current && fetchRequest === fetchRequestRef.current) { setIsLoading(false); } }); return fetchRequest; }, []); const debouncedFetchData = (0, _compose.useDebounce)(fetchData, 500); // When the component unmounts, set isMountedRef to false. This will // let the async fetch callbacks know when to stop. (0, _element.useEffect)(() => { isMountedRef.current = true; return () => { isMountedRef.current = false; }; }, []); (0, _element.useEffect)(() => { // Don't debounce the first fetch. This ensures that the first render // shows data as soon as possible. if (prevProps === undefined) { fetchData(); } else if (!(0, _es.default)(prevProps, props)) { debouncedFetchData(); } }); const hasResponse = !!response; const hasEmptyResponse = response === ''; const hasError = !!response?.error; if (isLoading) { return /*#__PURE__*/(0, _jsxRuntime.jsx)(LoadingResponsePlaceholder, { ...props, children: hasResponse && !hasError && /*#__PURE__*/(0, _jsxRuntime.jsx)(_element.RawHTML, { className: className, children: response }) }); } if (hasEmptyResponse || !hasResponse) { return /*#__PURE__*/(0, _jsxRuntime.jsx)(EmptyResponsePlaceholder, { ...props }); } if (hasError) { return /*#__PURE__*/(0, _jsxRuntime.jsx)(ErrorResponsePlaceholder, { response: response, ...props }); } return /*#__PURE__*/(0, _jsxRuntime.jsx)(_element.RawHTML, { className: className, children: response }); } //# sourceMappingURL=server-side-render.js.map