@wordpress/block-library
Version:
Block library for the WordPress editor.
243 lines (237 loc) • 7.95 kB
JavaScript
/**
* WordPress dependencies
*/
import { BlockControls, useBlockProps, store as blockEditorStore } from '@wordpress/block-editor';
import { debounce, useRefEffect } from '@wordpress/compose';
import { useSelect } from '@wordpress/data';
import { ToolbarGroup } from '@wordpress/components';
import { useEffect, useRef, useState } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import { BACKSPACE, DELETE, F10, isKeyboardEvent } from '@wordpress/keycodes';
/**
* Internal dependencies
*/
import ConvertToBlocksButton from './convert-to-blocks-button';
import ModalEdit from './modal';
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
const {
wp
} = window;
function isTmceEmpty(editor) {
// When tinyMce is empty the content seems to be:
// <p><br data-mce-bogus="1"></p>
// avoid expensive checks for large documents
const body = editor.getBody();
if (body.childNodes.length > 1) {
return false;
} else if (body.childNodes.length === 0) {
return true;
}
if (body.childNodes[0].childNodes.length > 1) {
return false;
}
return /^\n?$/.test(body.innerText || body.textContent);
}
export default function FreeformEdit(props) {
const {
clientId
} = props;
const canRemove = useSelect(select => select(blockEditorStore).canRemoveBlock(clientId), [clientId]);
const [isIframed, setIsIframed] = useState(false);
const ref = useRefEffect(element => {
setIsIframed(element.ownerDocument !== document);
}, []);
return /*#__PURE__*/_jsxs(_Fragment, {
children: [canRemove && /*#__PURE__*/_jsx(BlockControls, {
children: /*#__PURE__*/_jsx(ToolbarGroup, {
children: /*#__PURE__*/_jsx(ConvertToBlocksButton, {
clientId: clientId
})
})
}), /*#__PURE__*/_jsx("div", {
...useBlockProps({
ref
}),
children: isIframed ? /*#__PURE__*/_jsx(ModalEdit, {
...props
}) : /*#__PURE__*/_jsx(ClassicEdit, {
...props
})
})]
});
}
function ClassicEdit({
clientId,
attributes: {
content
},
setAttributes,
onReplace
}) {
const {
getMultiSelectedBlockClientIds
} = useSelect(blockEditorStore);
const didMountRef = useRef(false);
useEffect(() => {
if (!didMountRef.current) {
return;
}
const editor = window.tinymce.get(`editor-${clientId}`);
if (!editor) {
return;
}
const currentContent = editor.getContent();
if (currentContent !== content) {
editor.setContent(content || '');
}
}, [clientId, content]);
useEffect(() => {
const {
baseURL,
suffix
} = window.wpEditorL10n.tinymce;
didMountRef.current = true;
window.tinymce.EditorManager.overrideDefaults({
base_url: baseURL,
suffix
});
function onSetup(editor) {
let bookmark;
if (content) {
editor.on('loadContent', () => editor.setContent(content));
}
editor.on('blur', () => {
bookmark = editor.selection.getBookmark(2, true);
// There is an issue with Chrome and the editor.focus call in core at https://core.trac.wordpress.org/browser/trunk/src/js/_enqueues/lib/link.js#L451.
// This causes a scroll to the top of editor content on return from some content updating dialogs so tracking
// scroll position until this is fixed in core.
const scrollContainer = document.querySelector('.interface-interface-skeleton__content');
const scrollPosition = scrollContainer.scrollTop;
// Only update attributes if we aren't multi-selecting blocks.
// Updating during multi-selection can overwrite attributes of other blocks.
if (!getMultiSelectedBlockClientIds()?.length) {
setAttributes({
content: editor.getContent()
});
}
editor.once('focus', () => {
if (bookmark) {
editor.selection.moveToBookmark(bookmark);
if (scrollContainer.scrollTop !== scrollPosition) {
scrollContainer.scrollTop = scrollPosition;
}
}
});
return false;
});
editor.on('mousedown touchstart', () => {
bookmark = null;
});
const debouncedOnChange = debounce(() => {
const value = editor.getContent();
if (value !== editor._lastChange) {
editor._lastChange = value;
setAttributes({
content: value
});
}
}, 250);
editor.on('Paste Change input Undo Redo', debouncedOnChange);
// We need to cancel the debounce call because when we remove
// the editor (onUnmount) this callback is executed in
// another tick. This results in setting the content to empty.
editor.on('remove', debouncedOnChange.cancel);
editor.on('keydown', event => {
if (isKeyboardEvent.primary(event, 'z')) {
// Prevent the gutenberg undo kicking in so TinyMCE undo stack works as expected.
event.stopPropagation();
}
if ((event.keyCode === BACKSPACE || event.keyCode === DELETE) && isTmceEmpty(editor)) {
// Delete the block.
onReplace([]);
event.preventDefault();
event.stopImmediatePropagation();
}
const {
altKey
} = event;
/*
* Prevent Mousetrap from kicking in: TinyMCE already uses its own
* `alt+f10` shortcut to focus its toolbar.
*/
if (altKey && event.keyCode === F10) {
event.stopPropagation();
}
});
editor.on('init', () => {
const rootNode = editor.getBody();
// Create the toolbar by refocussing the editor.
if (rootNode.ownerDocument.activeElement === rootNode) {
rootNode.blur();
editor.focus();
}
});
}
function initialize() {
const {
settings
} = window.wpEditorL10n.tinymce;
wp.oldEditor.initialize(`editor-${clientId}`, {
tinymce: {
...settings,
inline: true,
content_css: false,
fixed_toolbar_container: `#toolbar-${clientId}`,
setup: onSetup
}
});
}
function onReadyStateChange() {
if (document.readyState === 'complete') {
initialize();
}
}
if (document.readyState === 'complete') {
initialize();
} else {
document.addEventListener('readystatechange', onReadyStateChange);
}
return () => {
document.removeEventListener('readystatechange', onReadyStateChange);
wp.oldEditor.remove(`editor-${clientId}`);
didMountRef.current = false;
};
}, []);
function focus() {
const editor = window.tinymce.get(`editor-${clientId}`);
if (editor) {
editor.focus();
}
}
function onToolbarKeyDown(event) {
// Prevent WritingFlow from kicking in and allow arrows navigation on the toolbar.
event.stopPropagation();
// Prevent Mousetrap from moving focus to the top toolbar when pressing `alt+f10` on this block toolbar.
event.nativeEvent.stopImmediatePropagation();
}
// Disable reasons:
//
// jsx-a11y/no-static-element-interactions
// - the toolbar itself is non-interactive, but must capture events
// from the KeyboardShortcuts component to stop their propagation.
/* eslint-disable jsx-a11y/no-static-element-interactions */
return /*#__PURE__*/_jsxs(_Fragment, {
children: [/*#__PURE__*/_jsx("div", {
id: `toolbar-${clientId}`,
className: "block-library-classic__toolbar",
onClick: focus,
"data-placeholder": __('Classic'),
onKeyDown: onToolbarKeyDown
}, "toolbar"), /*#__PURE__*/_jsx("div", {
id: `editor-${clientId}`,
className: "wp-block-freeform block-library-rich-text__tinymce"
}, "editor")]
});
/* eslint-enable jsx-a11y/no-static-element-interactions */
}
//# sourceMappingURL=edit.js.map