@eightshift/frontend-libs
Version:
A collection of useful frontend utility modules. powered by Eightshift
157 lines (130 loc) • 5 kB
JavaScript
import { createBlock } from '@wordpress/blocks';
import { select, dispatch } from '@wordpress/data';
/**
* Paste event handler.
*
* @param {Event} event - Passed event parameters.
* @param {object} attributes - Component attributes.
* @param {function} setAttributes - Component attribute setter.
* @param {array} allowedTags - Tags allowed in the parsed output.
* @param {string} [splitOnElement=p] - Element tag name that will cause a block to be split.
*
* @access public
*
* @returns {void}
*
* Usage:
* ```js
* pasteInto(event, attributes, setAttributes, manifestOptions.pasteAllowTags, 'p')}
* ```
*/
export const pasteInto = (event, attributes, setAttributes, allowedTags, splitOnElement = 'p') => {
event.preventDefault();
const [copiedData, copiedHtml] = event.clipboardData.items;
// If HTML content is in clipboard, process that...
if (copiedHtml) {
copiedHtml.getAsString((inputTextParam) => {
let inputText = inputTextParam;
// Get all tags from the text
const allTags = [...inputText.matchAll(/<.+?>/g)];
// Filter out attributes and disallowed tags,
// make <splitOnElement> and <br> elements give newline
for (const tag of allTags) {
const tagInnardsRegex = /(\S+)=["']?((?:.(?!["']?\s+(?:\S+)=|\s*\/?[>"']))+.)["']?/g;
const parsedTag = tag[0].replace(tagInnardsRegex, '');
const filteredTagName = parsedTag.replaceAll(/[<>/\s]+/g, '');
if (allowedTags.includes(filteredTagName)) {
inputText = inputText.replace(tag[0], parsedTag).replace(' >', '>');
} else if ((filteredTagName === splitOnElement && !tag[0].includes('/')) || filteredTagName === 'br') {
inputText = inputText.replace(tag[0], '\n');
} else {
inputText = inputText.replace(tag[0], '');
}
}
handlePaste(inputText, attributes, setAttributes);
});
}
// ...else handle plaintext
else if (copiedData) {
copiedData.getAsString((inputText) => handlePaste(inputText, attributes, setAttributes));
}
};
//---------------------------------------------------------------
// Private methods
/**
* Returns string parsed as nodes, ready for inserting into RichText.
*
* @param {string} inputText - HTML string for parsing.
*
* @access private
*
* @returns {array} Items to insert into RichText.
*/
export const processTag = (inputTextParam) => {
// If it's only text, wrap it in span for processing
const inputText = !inputTextParam.startsWith('<') ? `<span>${inputTextParam}</span>` : inputTextParam;
let output = [];
const matches = [...inputText.matchAll(/<.+?>.+?<\/.+?>/g)];
for (const [match] of matches) {
const elementTag = match.match(/<.+?>/g)[0];
const filteredTagName = elementTag.replaceAll(/[<>/\s]+/g, '');
const innerText = match.replaceAll(/<.+?>/g, '');
if (filteredTagName === 'span') {
output = [...output, document.createTextNode(`${innerText}`)];
} else {
let newElement = document.createElement(filteredTagName);
newElement.appendChild(document.createTextNode(innerText));
output = [...output, newElement];
}
}
return output;
};
/**
* Process content and paste in into blocks smartly.
*
* @param {string} textFromClipboard - Text retrieved from clipboard.
* @param {object} attributes - Component attributes.
* @param {function} setAttributes - Component attribute setter.
*
* @access private
*
* @returns {void}
*/
export const handlePaste = (textFromClipboard, attributes, setAttributes) => {
const componentName = attributes.componentName ?? attributes.blockName;
// Split by newLine, discard empty lines
let parsedParagraphs = textFromClipboard.split('\n').filter((line) => line.length > 0);
// If something is selected replace it...
if (window.getSelection) {
const selection = window.getSelection();
let selectedRange = selection.getRangeAt(0);
// Process elements into nodes
const processedElements = processTag(parsedParagraphs[0]);
// Remove current text
if (!selectedRange.collapsed) {
selectedRange.deleteContents();
}
// Insert them one by one and keep spaces
for (const element of processedElements) {
selectedRange = selectedRange.cloneRange();
selectedRange.insertNode(element);
selectedRange.setStartAfter(element);
selectedRange.collapse(true);
}
}
// ...else append at the end
else {
setAttributes({ [`${componentName}Content`]: `${attributes[`${componentName}Content`]}${parsedParagraphs[0]}` });
}
for (const paragraph of parsedParagraphs.slice(1)) {
// Create a new block with the same attributes, but different content
const block = createBlock(`eightshift-boilerplate/${componentName}`, {
...attributes,
[`${componentName}Content`]: paragraph
});
const currentBlockClientId = select('core/block-editor').getSelectedBlockClientId();
const blockIndex = select('core/block-editor').getBlockIndex(currentBlockClientId);
// Insert the block
dispatch('core/block-editor').insertBlock(block, blockIndex + 1);
}
};