function pgPostSelectorControl(prop, setAttributes, props, title, help, post_type) { const {__} = wp.i18n; const el = wp.element.createElement; const {Button, Popover, BaseControl, Panel, PanelBody, PanelRow} = wp.components; const {useState} = wp.element; const LinkControl = wp.blockEditor.LinkControl || wp.blockEditor.__experimentalLinkControl; const postId = prop + 'Id'; title = title || __('Select ' + prop); let [isVisible, setIsVisible] = useState(false); const toggleVisible = () => { setIsVisible((state) => !state); }; const findItemById = function (id) { if (props.attributes[prop]) { for (let i = 0; i < props.attributes[prop].length; i++) { if (props.attributes[prop][i].id === id) return i; } } return -1; } const moveItem = function (id, add) { const idx = findItemById(id); if (idx >= 0) { if (idx + add >= 0 && (idx + add) < props.attributes[prop].length) { const list = props.attributes[prop].slice(0); const mi = list[idx]; list.splice(idx, 1); list.splice(idx + add, 0, mi) const d = {} d[prop] = list; setAttributes(d); } } } const renderItems = function () { var r = []; if (props.attributes[prop]) { let i = 0; props.attributes[prop].forEach(function (item) { r.push(el('div', { className: 'pg-posts-control-list-item', }, [ props[prop + '_' + item.id] ? props[prop + '_' + item.id].title.raw : 'Loading...', el('a', { href: '#', className: 'pg-posts-control-list-item-remove', onClick: function () { const idx = findItemById(item.id); if (idx >= 0) { const list = props.attributes[prop].slice(0); list.splice(idx, 1); const d = {} d[prop] = list; setAttributes(d); } } }, '×'), el('a', { href: '#', className: 'pg-posts-control-list-item-move up dashicons dashicons-arrow-up-alt2', onClick: function () { moveItem(item.id, -1); } }, ''), el('a', { href: '#', className: 'pg-posts-control-list-item-move down dashicons dashicons-arrow-down-alt2', onClick: function () { moveItem(item.id, 1); } }, '') ])) i++; }) } return r; } return el(Panel, {}, el(PanelBody, { title: __(title) }, [ el(BaseControl, { help: help && __(help) }, [ el(PanelRow, {}, el('div', { className: 'pg-posts-control-list' }, [ props.attributes[prop].length === 0 && el('div', { className: 'pg-posts-control-list-item-empty' }, [__('No posts selected')]), props.attributes[prop].length > 0 && renderItems() ]) ), el(PanelRow, {}, [ el(Button, { onClick: toggleVisible, isSecondary: true }, [ __('Select a ' + post_type), ]) ]) ]), isVisible && el(Popover, { className: 'pg-control-popover' }, [ el('a', { href: '#', tabIndex: '-1', className: 'pg-control-close-popup', onClick: function () { setIsVisible(false); }, }, [ '×' ]), el(LinkControl, { onChange: function (val, p) { if (findItemById(val.id) < 0) { var d = {}; d[prop] = props.attributes[prop].slice(0); d[prop].push({id: val.id}); setAttributes(d); } setIsVisible(false); }, noDirectEntry: true, searchInputPlaceholder: __('Select a post'), showInitialSuggestions: true, settings: [], hasRichPreviews: true, suggestionsQuery: post_type ? { type: 'post', subtype: post_type, } : {} }), ]) ])) } function pgMediaImageControl(prop, setAttributes, props, image_size, inline_svg, title, help) { const propMedia = prop; const {__} = wp.i18n; const el = window.wp.element.createElement; const { BaseControl, Button, ResponsiveWrapper, Panel, PanelBody, PanelRow, TextareaControl, Popover } = wp.components; const {MediaUploadCheck, MediaUpload} = wp.blockEditor; const {RawHTML, useState} = wp.element; const ResolutionTool = wp.blockEditor?.privateApis?.ResolutionTool; const ImageSizeControl = ResolutionTool ? null : (wp.blockEditor.ImageSizeControl || wp.blockEditor.__experimentalImageSizeControl); image_size = image_size || 'full'; let [isEditSVGVisible, setIsEditSVGVisible] = useState({ visible: false, value: '' }); function onSelectMedia(media) { var d = {} d[prop] = { id: media.id, url: media?.sizes[image_size]?.url || media.url, size: image_size, svg: '', alt: media.alt || '' } setAttributes(d); } function setSVG(svg) { var d = {} d[prop] = { id: 0, url: '', size: '', svg: inline_svg ? svg : '', alt: '' } setAttributes(d); } function removeMedia() { var d = {} d[prop] = { id: 0, url: '', size: '', svg: '', alt: '' } setAttributes(d); } function selectMediaSize(url, size) { var d = {}; d[prop] = { id: props[prop].id, url: url, size: size, svg: '', alt: props[prop].alt || '' } setAttributes(d); } function getId() { return props.attributes[prop].id || 0; } function getUrl() { return props.attributes[prop].url || ''; } function getAlt() { return props.attributes[prop].alt || ''; } function getSVG() { if (!inline_svg) return ''; return props.attributes[prop].svg || ''; } function getResolutionOptions() { var r = []; Object.keys(props[propMedia].media_details.sizes).forEach(function (k) { r.push({ value: k, label: `${k} (${props[propMedia].media_details.sizes[k].width}x${props[propMedia].media_details.sizes[k].height})` }) }); return r; } return el(Panel, {}, el(PanelBody, { title: __(`${title || prop}`) }, [ el(PanelRow, {}, [ el(MediaUploadCheck, {}, [ el(MediaUpload, { onSelect: onSelectMedia, value: getId(), allowedTypes: ['image'], render: (open) => el(Button, { className: getUrl() === '' ? 'editor-post-featured-image__toggle' : 'editor-post-featured-image__preview', style: { maxHeight: '200px', overflow: 'hidden' }, onClick: open.open }, [ (getUrl() === '' && getSVG() === '') && __('Select an image'), !getSVG() && props[propMedia] && props[propMedia].source_url && el(ResponsiveWrapper, { naturalWidth: props[propMedia].media_details.width, naturalHeight: props[propMedia].media_details.height, isInline: true }, el('img', { src: props[propMedia].source_url }) ), getId() === 0 && getUrl() && el('img', { src: getUrl(), style: { width: '100%', display: 'block', objectFit: 'cover', height: '100%', objectPosition: 'center center' } }), inline_svg && getSVG() && el('div', { className: 'pg-control-svg-edit-preview' }, el(RawHTML, {}, getSVG())) ]) }, []), ]), ]), inline_svg && !getUrl() && !getSVG() && el(PanelRow, {}, [ el(Button, { onClick: function () { setIsEditSVGVisible({visible: true, value: getSVG()}); }, style: { marginRight: '4px', }, isLink: true }, [ __('Set inline SVG') ]), ]), (getUrl() || getSVG()) && el(PanelRow, {}, [ el(MediaUploadCheck, {}, [ el(MediaUpload, { title: __('Replace image'), onSelect: onSelectMedia, value: getId(), allowedTypes: ['image'], render: (open) => el(Button, { onClick: open.open, isSecondary: true }, [ __('Replace image'), ]) }, []), ]), inline_svg && el(Button, { onClick: function () { setIsEditSVGVisible({visible: true, value: getSVG()}); }, style: { marginRight: '6px', marginLeft: '6px' }, isSecondary: true }, [ __('Edit SVG') ]), el(Button, { onClick: removeMedia, isLink: true, isDestructive: false }, [ __('Remove image') ]) ]), getId() !== 0 && props[propMedia] && pgImageSizeControl(prop, setAttributes, props, getResolutionOptions(), function (value) { selectMediaSize(props[propMedia].media_details.sizes[value].source_url, value); }), isEditSVGVisible.visible && el(Popover, { className: 'pg-control-svg-edit', style: {} }, [ el(BaseControl, {}, [ el(TextareaControl, { value: getSVG(), help: __('Paste or edit the SVG image code.'), label: __('Edit inline SVG'), className: 'pg-control-svg-edit-code', onChange: function (val) { setSVG(val); }, }), ]), el(Button, { onClick: function () { setIsEditSVGVisible({...isEditSVGVisible, visible: false}); }, style: { marginRight: '6px' }, isSecondary: true }, [ __('Close') ]), el(Button, { onClick: function () { setSVG(isEditSVGVisible.value); setIsEditSVGVisible({...isEditSVGVisible, visible: false}); }, isLink: true }, [ __('Cancel') ]), ]), help && el(BaseControl, { help: __(help), }, []) ])) } function pgColorControl(prop, setAttributes, props, title, help) { const {__} = wp.i18n; const el = wp.element.createElement; const {Button, Popover, BaseControl, ColorPicker, PanelRow} = wp.components; const {useState} = wp.element; let [isVisible, setIsVisible] = useState(false); const toggleVisible = () => { setIsVisible((state) => !state); }; return el(BaseControl, { help: help && __(help) }, [ el(PanelRow, {}, [ el(Button, { onClick: toggleVisible, isSecondary: true }, [ el('span', {}, __(title || 'Select color')), el('span', { style: { backgroundColor: props.attributes[prop], border: '1px solid rgba(0,0,0,0.15)', width: '20px', height: '20px', borderRadius: '50%', marginLeft: '8px' } }) ]), props.attributes[prop] !== '' && el(Button, { onClick: function () { var d = {} d[prop] = ''; setAttributes(d); }, isLink: true, isDestructive: false }, [ __('Clear') ]), isVisible && el(Popover, { className: 'pg-control-color-popover' }, [ el('a', { href: '#', tabIndex: -1, className: 'pg-control-close-popup', onClick: function () { setIsVisible(false); }, }, [ '×' ]), el(ColorPicker, { color: props.attributes[prop], label: __(prop), onChangeComplete: function (value) { var d = {} d[prop] = value.color ? value.color.toRgbString() : ('rgba(' + value.rgb.r + ',' + value.rgb.g + ',' + value.rgb.b + ',' + value.rgb.a + ')'); setAttributes(d); } }), ]) ]), ]) } function pgUrlControl(prop, setAttributes, props, title, help, post_type) { const {__} = wp.i18n; const el = wp.element.createElement; const {Button, Popover, BaseControl, Panel, PanelBody, PanelRow, SelectControl, Icon} = wp.components; const {useState} = wp.element; const LinkControl = wp.blockEditor.LinkControl || wp.blockEditor.__experimentalLinkControl; const {withSelect, useSelect} = wp.data; title = title || prop; let linkControlKey = 1; let [isVisible, setIsVisible] = useState(false); const toggleVisible = () => { setIsVisible((state) => !state); }; const getPostTypeOptions = useSelect((select) => { const {getPostTypes} = select('core'); const items = (getPostTypes() || []).filter(function (item) { return !item.slug.startsWith('wp_'); }); const options = items.map(function (item) { return { label: item.labels.name, value: item.slug, }; }); options.unshift({value: null, label: '-'}); return options; }); return el(BaseControl, { help: help && __(help), label: __(title || prop) }, [ el(PanelRow, {}, [ el(Button, { onClick: toggleVisible, isSecondary: true, style: { flexGrow: 1, marginRight: '8px' } }, [ el(Icon, {icon: 'admin-links', style: {marginRight: '4px'}}), !props.attributes[prop].url && el('span', {}, __('Select link')), props.attributes[prop].url && el('span', { style: { maxWidth: '170px', display: 'inline-block', textOverflow: 'ellipsis', overflow: 'hidden' } }, props.attributes[prop].title || props.attributes[prop].url), props.attributes[prop].url && el(Button, { onClick: function (e) { e.preventDefault(); e.stopPropagation(); var d = {} d[prop] = { post_type: props.attributes[prop].post_type || null, post_id: 0, url: '' }; setAttributes(d); }, isLink: true, isDestructive: false, style: { fontSize: '16px', textDecoration: 'none', marginLeft: 'auto' } }, [ __('×') ]), ]), ]), isVisible && el(Popover, {}, [ el('div', { style: { margin: '0 16px 16px 16px' } }, [ el('a', { href: '#', style: { right: '-15px' }, tabIndex: -1, className: 'pg-control-close-popup', onClick: function () { setIsVisible(false); }, }, [ '×' ]), el(SelectControl, { value: props.attributes[prop].post_type, label: __('Post type'), disabled: post_type !== null, onChange: function (val) { var d = {} d[prop] = { post_type: val === '-' ? null : val, post_id: 0, url: '' }; linkControlKey++; //setIsVisible(false); setAttributes(d) }, options: getPostTypeOptions }), ]), el(LinkControl, { key: props.attributes[prop].post_type || 'none', value: { url: props.attributes[prop].url }, onChange: function (val) { var d = {}; d[prop] = { post_type: props.attributes[prop].post_type, post_id: val.id, url: val.url, title: val.title }; if (val.url === props.attributes[prop].url && !val.id) { d[prop].post_id = props.attributes[prop].post_id; d[prop].title = props.attributes[prop].title; } setIsVisible(false); setAttributes(d) }, noDirectEntry: false, searchInputPlaceholder: __('Select a post'), showInitialSuggestions: true, forceIsEditingLink: true, settings: [], suggestionsQuery: props.attributes[prop].post_type ? { type: 'post', subtype: props.attributes[prop].post_type, } : {} }), ]) ]) } function pgImageSizeControl(prop, setAttributes, props, options, onChange ) { const {__} = wp.i18n; const el = wp.element.createElement; const {ToolsPanelItem, Button, Popover, BaseControl, Panel, PanelBody, PanelRow, SelectControl, Icon} = wp.components; const title = __('Image size'); return el(PanelRow, {}, [el(BaseControl, { }, [ el(SelectControl, { label: title, value: props.attributes[prop].size, options: options, onChange: function(value) { onChange(value) } }) ]) ]) } function pgMergeInlineSVGAttributes(svg, props) { for (let prop in props) { let val = props[prop]; if (typeof val === 'object') { const r = []; for (const key in val) { r.push(`${key.replace(/([A-Z])/g, function (a) { return '-' + a.toLowerCase(); })}:${val[key]};`) } val = r.join(''); } if (prop === 'className') prop = 'class'; const re = new RegExp('(]*\\s*)(' + prop + '="[^"]*")', 'i'); if (svg.match(re)) { svg = svg.replace(re, '$1' + prop + '="' + val + '"'); } else { svg = svg.replace(/ 1) { const sp = b.shift().trim().replace(/-([a-z])/gi, function (s, g) { return g.toUpperCase(); }) styles[sp] = b.join(':').trim(); } }) props.style = styles; } else { props[name] = node.attributes[i].value === null ? '' : node.attributes[i].value; } } return props; } removeScripts(node) { var scripts = node.querySelectorAll('script'); for (let i = 0; i < scripts.length; i++) { scripts[i].parentNode.removeChild(scripts[i]); } } render(el, lib) { const _this = this; el = el || window.wp.element.createElement; lib = lib || {} const doNode = function (node) { if (node.nodeType === 1) { let tag = node.tagName.toLowerCase(); const props = _this.attributesToProps(node); const children = []; let skip_children = false; if (tag === 'svg') { props.dangerouslySetInnerHTML = {__html: node.innerHTML} skip_children = true; } if (!skip_children) { for (let i = 0; i < node.childNodes.length; i++) { const ch = doNode(node.childNodes[i]); ch && children.push(ch); } } if (lib[tag]) { tag = lib[tag]; } if (children.length) { return el(tag, props, children); } else { return el(tag, props); } } else if (node.nodeType === 3) { return node.data; } } return doNode(this.doc.querySelector('body').children[0]); } } (function() { const pgCreateSVGCache = {} function pgCreateSVG(dummytag, dummyprops, svg) { const pgReact = new PgHTMLToReact2(); const el = window.wp.element.createElement; let props = pgCreateSVGCache[svg] || null; if (!props) { const parser = new DOMParser(); const doc = parser.parseFromString(svg, "image/svg+xml"); const svgel = doc.querySelector('svg'); pgReact.removeScripts(doc); pgReact.cleanNode(svgel); props = pgReact.attributesToProps(svgel); props.dangerouslySetInnerHTML = {__html: svgel.innerHTML} pgCreateSVGCache[svg] = props; } return el('svg', props); } window.pgCreateSVG = pgCreateSVG; })() function PgGetServerSideRender2() { //Addapted ServerSideRender that properly supports InnerBlocks const {useDebounce, usePrevious} = wp.compose; const {RawHTML, useEffect, useRef, useState, Fragment} = wp.element; const {__, sprintf} = wp.i18n; const apiFetch = wp.apiFetch; const {addQueryArgs} = wp.url; const {Placeholder, Spinner} = wp.components; const {InnerBlocks} = wp.blockEditor; const SanitizeBlockAttributes = wp.blocks.SanitizeBlockAttributes || wp.blocks.__experimentalSanitizeBlockAttributes; const useInnerBlocksProps = wp.blockEditor.useInnerBlocksProps || wp.blockEditor.__experimentalUseInnerBlocksProps; const el = wp.element.createElement; //Addapted from fast-deep-equal function equal(a, b) { if (a === b) return true; if (a && b && typeof a == 'object' && typeof b == 'object') { if (a.constructor !== b.constructor) return false; var length, i, keys; if (Array.isArray(a)) { length = a.length; if (length != b.length) return false; for (i = length; i-- !== 0;) if (!equal(a[i], b[i])) return false; return true; } if (a.constructor === RegExp) return a.source === b.source && a.flags === b.flags; if (a.valueOf !== Object.prototype.valueOf) return a.valueOf() === b.valueOf(); if (a.toString !== Object.prototype.toString) return a.toString() === b.toString(); keys = Object.keys(a); length = keys.length; if (length !== Object.keys(b).length) return false; for (i = length; i-- !== 0;) if (!Object.prototype.hasOwnProperty.call(b, keys[i])) return false; for (i = length; i-- !== 0;) { var key = keys[i]; if (key === '_owner' && a.$$typeof) { continue; } if (!equal(a[key], b[key])) return false; } return true; } return a !== a && b !== b; } function rendererPath(block, attributes = null, urlQueryArgs = {}) { return addQueryArgs(`/wp/v2/block-renderer/${block}`, { context: 'edit', ...(null !== attributes ? {attributes} : {}), ...urlQueryArgs, }); } function DefaultEmptyResponsePlaceholder({className}) { return el(Placeholder, {className: className}, __('Block rendered as empty.')); } function DefaultErrorResponsePlaceholder({response, className}) { const errorMessage = sprintf( // translators: %s: error message describing the problem __('Error loading block: %s'), response.errorMsg ); return el(Placeholder, {className: className}, errorMessage); } function DefaultLoadingResponsePlaceholder({className}) { return el(Placeholder, {className: className}, el(Spinner, {})); } return function (props) { const { attributes, block, className, httpMethod = 'GET', urlQueryArgs, EmptyResponsePlaceholder = DefaultEmptyResponsePlaceholder, ErrorResponsePlaceholder = DefaultErrorResponsePlaceholder, LoadingResponsePlaceholder = DefaultLoadingResponsePlaceholder, innerBlocksProps, blockProps } = props; const isMountedRef = useRef(true); const fetchRequestRef = useRef(); const [response, setResponse] = useState(null); const prevProps = usePrevious(props); const innerAttribute = 'data-wp-inner-blocks'; const blocksPropsAttribute = 'data-wp-block-props'; let currentCode = null; let currentFunction = null; function pgel(tag, props, ...children) { if (tag === 'script') return null; if (props) { if (props.class) { props.className = props.class; delete props.class; } for (const prop in props) { if (props.hasOwnProperty(prop)) { if (prop.match(/^on[A-Z]+/)) { props[prop] = null; } } } if (innerAttribute in props) { delete props[innerAttribute]; props = {...props, ...innerBlocksProps}; return el(tag, props); } if (blocksPropsAttribute in props) { delete props[blocksPropsAttribute]; props = {...props, ...blockProps}; } } if (children.length) { return el(tag, props, children); } else { return el(tag, props); } } function fetchData() { isMountedRef.needsFetch = false; if (!isMountedRef.current) { return; } if (null !== response) { setResponse(null); } const sanitizedAttributes = attributes && SanitizeBlockAttributes(block, attributes); // 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 ?? null; const path = rendererPath(block, urlAttributes, urlQueryArgs); const data = isPostRequest ? {attributes: sanitizedAttributes ?? 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 = apiFetch({ 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, }); } })); return fetchRequest; } const debouncedFetchData = useDebounce(fetchData, 500); // When the component unmounts, set isMountedRef to false. This will // let the async fetch callbacks know when to stop. useEffect( () => () => { isMountedRef.current = false; isMountedRef.needsFetch = false; }, [] ); 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 (!equal(prevProps.attributes, props.attributes)) { isMountedRef.needsFetch = true; debouncedFetchData(); } else { if (isMountedRef.needsFetch) { fetchData(); } } }); if (response === '') { return el(EmptyResponsePlaceholder, {...props, ...blockProps}); } else if (!response) { return el(LoadingResponsePlaceholder, {...props, ...blockProps}); } else if (response.error) { return el(ErrorResponsePlaceholder, {response: response, ...props, ...blockProps}); } try { if (isMountedRef.currentCode !== response) { isMountedRef.currentFunction = new PgHTMLToReact2(); isMountedRef.currentFunction.loadHTML(response); isMountedRef.currentCode = response; } return isMountedRef.currentFunction.render(pgel); } catch (err) { //unable to render inline console.warn('Unable to compile dynamic block to JSX:', err); return el('div', {...blockProps}, [ el(RawHTML, {className: className}, response), el('div', {...innerBlocksProps}) ]) } } } window.PgServerSideRender2 = PgGetServerSideRender2();