@wordpress/block-editor
Version:
258 lines (210 loc) • 8.63 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
exports.useClipboardHandler = useClipboardHandler;
exports.useNotifyCopy = useNotifyCopy;
var _element = require("@wordpress/element");
var _blocks = require("@wordpress/blocks");
var _dom = require("@wordpress/dom");
var _data = require("@wordpress/data");
var _i18n = require("@wordpress/i18n");
var _notices = require("@wordpress/notices");
var _compose = require("@wordpress/compose");
var _pasting = require("../../utils/pasting");
var _store = require("../../store");
/**
* WordPress dependencies
*/
/**
* Internal dependencies
*/
function useNotifyCopy() {
const {
getBlockName
} = (0, _data.useSelect)(_store.store);
const {
getBlockType
} = (0, _data.useSelect)(_blocks.store);
const {
createSuccessNotice
} = (0, _data.useDispatch)(_notices.store);
return (0, _element.useCallback)((eventType, selectedBlockClientIds) => {
let notice = '';
if (selectedBlockClientIds.length === 1) {
const clientId = selectedBlockClientIds[0];
const title = getBlockType(getBlockName(clientId))?.title;
notice = eventType === 'copy' ? (0, _i18n.sprintf)( // Translators: Name of the block being copied, e.g. "Paragraph".
(0, _i18n.__)('Copied "%s" to clipboard.'), title) : (0, _i18n.sprintf)( // Translators: Name of the block being cut, e.g. "Paragraph".
(0, _i18n.__)('Moved "%s" to clipboard.'), title);
} else {
notice = eventType === 'copy' ? (0, _i18n.sprintf)( // Translators: %d: Number of blocks being copied.
(0, _i18n._n)('Copied %d block to clipboard.', 'Copied %d blocks to clipboard.', selectedBlockClientIds.length), selectedBlockClientIds.length) : (0, _i18n.sprintf)( // Translators: %d: Number of blocks being cut.
(0, _i18n._n)('Moved %d block to clipboard.', 'Moved %d blocks to clipboard.', selectedBlockClientIds.length), selectedBlockClientIds.length);
}
createSuccessNotice(notice, {
type: 'snackbar'
});
}, []);
}
function useClipboardHandler() {
const {
getBlocksByClientId,
getSelectedBlockClientIds,
hasMultiSelection,
getSettings,
__unstableIsFullySelected,
__unstableIsSelectionCollapsed,
__unstableIsSelectionMergeable,
__unstableGetSelectedBlocksWithPartialSelection,
canInsertBlockType
} = (0, _data.useSelect)(_store.store);
const {
flashBlock,
removeBlocks,
replaceBlocks,
__unstableDeleteSelection,
__unstableExpandSelection,
insertBlocks
} = (0, _data.useDispatch)(_store.store);
const notifyCopy = useNotifyCopy();
return (0, _compose.useRefEffect)(node => {
function handler(event) {
const selectedBlockClientIds = getSelectedBlockClientIds();
if (selectedBlockClientIds.length === 0) {
return;
} // Always handle multiple selected blocks.
if (!hasMultiSelection()) {
const {
target
} = event;
const {
ownerDocument
} = target; // If copying, only consider actual text selection as selection.
// Otherwise, any focus on an input field is considered.
const hasSelection = event.type === 'copy' || event.type === 'cut' ? (0, _dom.documentHasUncollapsedSelection)(ownerDocument) : (0, _dom.documentHasSelection)(ownerDocument); // Let native copy behaviour take over in input fields.
if (hasSelection) {
return;
}
}
if (!node.contains(event.target.ownerDocument.activeElement)) {
return;
}
const eventDefaultPrevented = event.defaultPrevented;
event.preventDefault();
const isSelectionMergeable = __unstableIsSelectionMergeable();
const shouldHandleWholeBlocks = __unstableIsSelectionCollapsed() || __unstableIsFullySelected();
const expandSelectionIsNeeded = !shouldHandleWholeBlocks && !isSelectionMergeable;
if (event.type === 'copy' || event.type === 'cut') {
if (selectedBlockClientIds.length === 1) {
flashBlock(selectedBlockClientIds[0]);
} // If we have a partial selection that is not mergeable, just
// expand the selection to the whole blocks.
if (expandSelectionIsNeeded) {
__unstableExpandSelection();
} else {
notifyCopy(event.type, selectedBlockClientIds);
let blocks; // Check if we have partial selection.
if (shouldHandleWholeBlocks) {
blocks = getBlocksByClientId(selectedBlockClientIds);
} else {
const [head, tail] = __unstableGetSelectedBlocksWithPartialSelection();
const inBetweenBlocks = getBlocksByClientId(selectedBlockClientIds.slice(1, selectedBlockClientIds.length - 1));
blocks = [head, ...inBetweenBlocks, tail];
}
const wrapperBlockName = event.clipboardData.getData('__unstableWrapperBlockName');
if (wrapperBlockName) {
blocks = (0, _blocks.createBlock)(wrapperBlockName, JSON.parse(event.clipboardData.getData('__unstableWrapperBlockAttributes')), blocks);
}
const serialized = (0, _blocks.serialize)(blocks);
event.clipboardData.setData('text/plain', toPlainText(serialized));
event.clipboardData.setData('text/html', serialized);
}
}
if (event.type === 'cut') {
// We need to also check if at the start we needed to
// expand the selection, as in this point we might have
// programmatically fully selected the blocks above.
if (shouldHandleWholeBlocks && !expandSelectionIsNeeded) {
removeBlocks(selectedBlockClientIds);
} else {
__unstableDeleteSelection();
}
} else if (event.type === 'paste') {
if (eventDefaultPrevented) {
// This was likely already handled in rich-text/use-paste-handler.js.
return;
}
const {
__experimentalCanUserUseUnfilteredHTML: canUserUseUnfilteredHTML
} = getSettings();
const {
plainText,
html,
files
} = (0, _pasting.getPasteEventData)(event);
let blocks = [];
if (files.length) {
const fromTransforms = (0, _blocks.getBlockTransforms)('from');
blocks = files.reduce((accumulator, file) => {
const transformation = (0, _blocks.findTransform)(fromTransforms, transform => transform.type === 'files' && transform.isMatch([file]));
if (transformation) {
accumulator.push(transformation.transform([file]));
}
return accumulator;
}, []).flat();
} else {
blocks = (0, _blocks.pasteHandler)({
HTML: html,
plainText,
mode: 'BLOCKS',
canUserUseUnfilteredHTML
});
}
if (selectedBlockClientIds.length === 1) {
const [selectedBlockClientId] = selectedBlockClientIds;
if (blocks.every(block => canInsertBlockType(block.name, selectedBlockClientId))) {
insertBlocks(blocks, undefined, selectedBlockClientId);
return;
}
}
replaceBlocks(selectedBlockClientIds, blocks, blocks.length - 1, -1);
}
}
node.ownerDocument.addEventListener('copy', handler);
node.ownerDocument.addEventListener('cut', handler);
node.ownerDocument.addEventListener('paste', handler);
return () => {
node.ownerDocument.removeEventListener('copy', handler);
node.ownerDocument.removeEventListener('cut', handler);
node.ownerDocument.removeEventListener('paste', handler);
};
}, []);
}
function CopyHandler({
children
}) {
return (0, _element.createElement)("div", {
ref: useClipboardHandler()
}, children);
}
/**
* Given a string of HTML representing serialized blocks, returns the plain
* text extracted after stripping the HTML of any tags and fixing line breaks.
*
* @param {string} html Serialized blocks.
* @return {string} The plain-text content with any html removed.
*/
function toPlainText(html) {
// Manually handle BR tags as line breaks prior to `stripHTML` call
html = html.replace(/<br>/g, '\n');
const plainText = (0, _dom.__unstableStripHTML)(html).trim(); // Merge any consecutive line breaks
return plainText.replace(/\n\n+/g, '\n\n');
}
/**
* @see https://github.com/WordPress/gutenberg/blob/HEAD/packages/block-editor/src/components/copy-handler/README.md
*/
var _default = CopyHandler;
exports.default = _default;
//# sourceMappingURL=index.js.map