UNPKG

@wordpress/block-editor

Version:
258 lines (235 loc) 8.13 kB
import { createElement, Fragment } from "@wordpress/element"; /** * WordPress dependencies */ import { __ } from '@wordpress/i18n'; import { useRef, useState, useCallback } from '@wordpress/element'; import { ToolbarButton, Button, NavigableMenu, MenuItem, ToggleControl, TextControl, SVG, Path, __experimentalVStack as VStack } from '@wordpress/components'; import { link as linkIcon, close } from '@wordpress/icons'; /** * Internal dependencies */ import URLPopover from './index'; const LINK_DESTINATION_NONE = 'none'; const LINK_DESTINATION_CUSTOM = 'custom'; const LINK_DESTINATION_MEDIA = 'media'; const LINK_DESTINATION_ATTACHMENT = 'attachment'; const NEW_TAB_REL = ['noreferrer', 'noopener']; const icon = createElement(SVG, { viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg" }, createElement(Path, { d: "M0,0h24v24H0V0z", fill: "none" }), createElement(Path, { d: "m19 5v14h-14v-14h14m0-2h-14c-1.1 0-2 0.9-2 2v14c0 1.1 0.9 2 2 2h14c1.1 0 2-0.9 2-2v-14c0-1.1-0.9-2-2-2z" }), createElement(Path, { d: "m14.14 11.86l-3 3.87-2.14-2.59-3 3.86h12l-3.86-5.14z" })); const ImageURLInputUI = ({ linkDestination, onChangeUrl, url, mediaType = 'image', mediaUrl, mediaLink, linkTarget, linkClass, rel }) => { const [isOpen, setIsOpen] = useState(false); // Use internal state instead of a ref to make sure that the component // re-renders when the popover's anchor updates. const [popoverAnchor, setPopoverAnchor] = useState(null); const openLinkUI = useCallback(() => { setIsOpen(true); }); const [isEditingLink, setIsEditingLink] = useState(false); const [urlInput, setUrlInput] = useState(null); const autocompleteRef = useRef(null); const startEditLink = useCallback(() => { if (linkDestination === LINK_DESTINATION_MEDIA || linkDestination === LINK_DESTINATION_ATTACHMENT) { setUrlInput(''); } setIsEditingLink(true); }); const stopEditLink = useCallback(() => { setIsEditingLink(false); }); const closeLinkUI = useCallback(() => { setUrlInput(null); stopEditLink(); setIsOpen(false); }); const getUpdatedLinkTargetSettings = value => { const newLinkTarget = value ? '_blank' : undefined; let updatedRel; if (newLinkTarget) { const rels = (rel !== null && rel !== void 0 ? rel : '').split(' '); NEW_TAB_REL.forEach(relVal => { if (!rels.includes(relVal)) { rels.push(relVal); } }); updatedRel = rels.join(' '); } else { const rels = (rel !== null && rel !== void 0 ? rel : '').split(' ').filter(relVal => NEW_TAB_REL.includes(relVal) === false); updatedRel = rels.length ? rels.join(' ') : undefined; } return { linkTarget: newLinkTarget, rel: updatedRel }; }; const onFocusOutside = useCallback(() => { return event => { // The autocomplete suggestions list renders in a separate popover (in a portal), // so onFocusOutside fails to detect that a click on a suggestion occurred in the // LinkContainer. Detect clicks on autocomplete suggestions using a ref here, and // return to avoid the popover being closed. const autocompleteElement = autocompleteRef.current; if (autocompleteElement && autocompleteElement.contains(event.target)) { return; } setIsOpen(false); setUrlInput(null); stopEditLink(); }; }); const onSubmitLinkChange = useCallback(() => { return event => { if (urlInput) { // It is possible the entered URL actually matches a named link destination. // This check will ensure our link destination is correct. const selectedDestination = getLinkDestinations().find(destination => destination.url === urlInput)?.linkDestination || LINK_DESTINATION_CUSTOM; onChangeUrl({ href: urlInput, linkDestination: selectedDestination }); } stopEditLink(); setUrlInput(null); event.preventDefault(); }; }); const onLinkRemove = useCallback(() => { onChangeUrl({ linkDestination: LINK_DESTINATION_NONE, href: '' }); }); const getLinkDestinations = () => { const linkDestinations = [{ linkDestination: LINK_DESTINATION_MEDIA, title: __('Media File'), url: mediaType === 'image' ? mediaUrl : undefined, icon }]; if (mediaType === 'image' && mediaLink) { linkDestinations.push({ linkDestination: LINK_DESTINATION_ATTACHMENT, title: __('Attachment Page'), url: mediaType === 'image' ? mediaLink : undefined, icon: createElement(SVG, { viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg" }, createElement(Path, { d: "M0 0h24v24H0V0z", fill: "none" }), createElement(Path, { d: "M14 2H6c-1.1 0-1.99.9-1.99 2L4 20c0 1.1.89 2 1.99 2H18c1.1 0 2-.9 2-2V8l-6-6zM6 20V4h7v5h5v11H6z" })) }); } return linkDestinations; }; const onSetHref = value => { const linkDestinations = getLinkDestinations(); let linkDestinationInput; if (!value) { linkDestinationInput = LINK_DESTINATION_NONE; } else { linkDestinationInput = (linkDestinations.find(destination => { return destination.url === value; }) || { linkDestination: LINK_DESTINATION_CUSTOM }).linkDestination; } onChangeUrl({ linkDestination: linkDestinationInput, href: value }); }; const onSetNewTab = value => { const updatedLinkTarget = getUpdatedLinkTargetSettings(value); onChangeUrl(updatedLinkTarget); }; const onSetLinkRel = value => { onChangeUrl({ rel: value }); }; const onSetLinkClass = value => { onChangeUrl({ linkClass: value }); }; const advancedOptions = createElement(VStack, { spacing: "3" }, createElement(ToggleControl, { __nextHasNoMarginBottom: true, label: __('Open in new tab'), onChange: onSetNewTab, checked: linkTarget === '_blank' }), createElement(TextControl, { __nextHasNoMarginBottom: true, label: __('Link rel'), value: rel !== null && rel !== void 0 ? rel : '', onChange: onSetLinkRel }), createElement(TextControl, { __nextHasNoMarginBottom: true, label: __('Link CSS Class'), value: linkClass || '', onChange: onSetLinkClass })); const linkEditorValue = urlInput !== null ? urlInput : url; const urlLabel = (getLinkDestinations().find(destination => destination.linkDestination === linkDestination) || {}).title; return createElement(Fragment, null, createElement(ToolbarButton, { icon: linkIcon, className: "components-toolbar__control", label: url ? __('Edit link') : __('Insert link'), "aria-expanded": isOpen, onClick: openLinkUI, ref: setPopoverAnchor }), isOpen && createElement(URLPopover, { anchor: popoverAnchor, onFocusOutside: onFocusOutside(), onClose: closeLinkUI, renderSettings: () => advancedOptions, additionalControls: !linkEditorValue && createElement(NavigableMenu, null, getLinkDestinations().map(link => createElement(MenuItem, { key: link.linkDestination, icon: link.icon, onClick: () => { setUrlInput(null); onSetHref(link.url); stopEditLink(); } }, link.title))) }, (!url || isEditingLink) && createElement(URLPopover.LinkEditor, { className: "block-editor-format-toolbar__link-container-content", value: linkEditorValue, onChangeInputValue: setUrlInput, onSubmit: onSubmitLinkChange(), autocompleteRef: autocompleteRef }), url && !isEditingLink && createElement(Fragment, null, createElement(URLPopover.LinkViewer, { className: "block-editor-format-toolbar__link-container-content", url: url, onEditLinkClick: startEditLink, urlLabel: urlLabel }), createElement(Button, { icon: close, label: __('Remove link'), onClick: onLinkRemove })))); }; export { ImageURLInputUI as __experimentalImageURLInputUI }; //# sourceMappingURL=image-url-input-ui.js.map