/**
* The editor side of the protected-address block.
*
* What the canvas shows is deliberately NOT what the visitor gets. The front end
* is rendered on the server -- encrypted, per request -- and showing that here
* would be a wall of base64 that tells the author nothing about the thing they
* are editing. So the canvas shows the readable form, and says once, quietly,
* that the address is hidden on the published page.
*
* There is no ServerSideRender for the same reason: it would cost a REST round
* trip on every keystroke to render something unreadable.
*/
import { registerBlockType } from '@wordpress/blocks';
import {
useBlockProps,
InspectorControls,
BlockControls,
} from '@wordpress/block-editor';
// No __experimental* imports here on purpose. The settings screen carries one
// already, and an API that WordPress reserves the right to remove is a poor
// thing to hang an editor block on: when it goes, the block stops loading and
// the post it sits in cannot be edited. The controls below keep their default
// margins instead, which is what stacks them in an inspector panel anyway.
import {
PanelBody,
Placeholder,
TextControl,
TextareaControl,
ToolbarButton,
ToolbarGroup,
Button,
Notice,
} from '@wordpress/components';
import { useState } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import metadata from './block.json';
import './index.css';
/**
* Strips whatever a paste dragged in with it.
*
* An address never contains whitespace, so removing all of it is safe and
* saves the author a puzzle: copy one out of Word or a PDF and it usually
* arrives with a non-breaking space attached. PHP's trim() does not remove
* that one, so the address would be rejected on the server and the block would
* be absent from the published page -- over a character nobody can see.
*
* Cleaning it away here means the stored value is always one both sides agree
* about, whichever field it was typed or pasted into. The warning below then
* stays for addresses that are genuinely the wrong shape.
*
* @param {string} value The address as it arrived.
*
* @return {string} The address without invisible characters.
*/
// Written as escapes, not as the characters themselves: a literal
// non-breaking space in the source is invisible to the next reader and
// survives exactly one careless reformat.
const cleanAddress = ( value ) =>
value.replace( /[\s\u00A0\uFEFF\u200B-\u200D\u2060]+/g, '' );
/**
* The address as the author should see it while writing.
*
* @param {Object} props Component props.
* @param {Object} props.attributes The block attributes.
* @param {Function} props.setAttributes Attribute setter.
*
* @return {Element} The editor markup.
*/
function Edit( { attributes, setAttributes } ) {
const { address, linkText, subject, body, cc, bcc } = attributes;
const blockProps = useBlockProps();
// Kept separate from the attribute so a half-typed address is not stored on
// every keystroke -- and so the placeholder does not vanish mid-word.
const [ draft, setDraft ] = useState( '' );
const [ editing, setEditing ] = useState( false );
// PHP refuses to render anything it cannot recognise as an address, which
// is the right call -- there is nothing to protect, and printing it would
// only publish whatever was typed. But refusing quietly means the block is
// simply absent from the published page, with the editor still showing it.
// "My block is gone" is a bad way to find that out, so it is said here.
//
// The plugin's own pattern, character for character -- the one in
// CryptX\Exposure, which is what PHP gates on. Not is_email(): that accepts
// quotes and slashes in a local part, so it would stay silent about an
// address the server then refuses. Not a looser guess either, which would
// warn about addresses that work perfectly well. Both sides have to agree,
// or the warning is worse than none.
//
// cleanAddress() rather than a trim: both fields store through it, so an
// invisible character pasted in with the address never survives to be
// judged differently by the two sides.
const looksLikeAddress =
address === '' ||
/^[_a-zA-Z0-9-+]+(\.[_a-zA-Z0-9-+]+)*@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*(\.[a-zA-Z]{2,})$/.test(
cleanAddress( address )
);
const warning = ! looksLikeAddress && (