UNPKG

@gechiui/block-editor

Version:
260 lines (223 loc) 7.76 kB
/** * GeChiUI dependencies */ import { useRef } from '@gechiui/element'; import { useRefEffect } from '@gechiui/compose'; import { getFilesFromDataTransfer } from '@gechiui/dom'; import { pasteHandler } from '@gechiui/blocks'; import { isEmpty, insert, create, replace, __UNSTABLE_LINE_SEPARATOR as LINE_SEPARATOR } from '@gechiui/rich-text'; import { isURL } from '@gechiui/url'; /** * Internal dependencies */ import { filePasteHandler } from './file-paste-handler'; import { addActiveFormats, isShortcode } from './utils'; import { splitValue } from './split-value'; /** @typedef {import('@gechiui/rich-text').RichTextValue} RichTextValue */ /** * Replaces line separators with line breaks if not multiline. * Replaces line breaks with line separators if multiline. * * @param {RichTextValue} value Value to adjust. * @param {boolean} isMultiline Whether to adjust to multiline or not. * * @return {RichTextValue} Adjusted value. */ function adjustLines(value, isMultiline) { if (isMultiline) { return replace(value, /\n+/g, LINE_SEPARATOR); } return replace(value, new RegExp(LINE_SEPARATOR, 'g'), '\n'); } export function usePasteHandler(props) { const propsRef = useRef(props); propsRef.current = props; return useRefEffect(element => { function _onPaste(event) { const { isSelected, disableFormats, onChange, value, formatTypes, tagName, onReplace, onSplit, onSplitMiddle, __unstableEmbedURLOnPaste, multilineTag, preserveWhiteSpace, pastePlainText } = propsRef.current; if (!isSelected) { event.preventDefault(); return; } const { clipboardData } = event; let plainText = ''; let html = ''; // IE11 only supports `Text` as an argument for `getData` and will // otherwise throw an invalid argument error, so we try the standard // arguments first, then fallback to `Text` if they fail. try { plainText = clipboardData.getData('text/plain'); html = clipboardData.getData('text/html'); } catch (error1) { try { html = clipboardData.getData('Text'); } catch (error2) { // Some browsers like UC Browser paste plain text by default and // don't support clipboardData at all, so allow default // behaviour. return; } } // Remove Windows-specific metadata appended within copied HTML text. html = removeWindowsFragments(html); // Strip meta tag. html = removeCharsetMetaTag(html); event.preventDefault(); // Allows us to ask for this information when we get a report. window.console.log('Received HTML:\n\n', html); window.console.log('Received plain text:\n\n', plainText); if (disableFormats) { onChange(insert(value, plainText)); return; } const transformed = formatTypes.reduce((accumlator, _ref) => { let { __unstablePasteRule } = _ref; // Only allow one transform. if (__unstablePasteRule && accumlator === value) { accumlator = __unstablePasteRule(value, { html, plainText }); } return accumlator; }, value); if (transformed !== value) { onChange(transformed); return; } const files = [...getFilesFromDataTransfer(clipboardData)]; const isInternal = clipboardData.getData('rich-text') === 'true'; // If the data comes from a rich text instance, we can directly use it // without filtering the data. The filters are only meant for externally // pasted content and remove inline styles. if (isInternal) { const pastedMultilineTag = clipboardData.getData('rich-text-multi-line-tag') || undefined; let pastedValue = create({ html, multilineTag: pastedMultilineTag, multilineWrapperTags: pastedMultilineTag === 'li' ? ['ul', 'ol'] : undefined, preserveWhiteSpace }); pastedValue = adjustLines(pastedValue, !!multilineTag); addActiveFormats(pastedValue, value.activeFormats); onChange(insert(value, pastedValue)); return; } if (pastePlainText) { onChange(insert(value, create({ text: plainText }))); return; } // Only process file if no HTML is present. // Note: a pasted file may have the URL as plain text. if (files && files.length && !html) { const content = pasteHandler({ HTML: filePasteHandler(files), mode: 'BLOCKS', tagName, preserveWhiteSpace }); // Allows us to ask for this information when we get a report. // eslint-disable-next-line no-console window.console.log('Received items:\n\n', files); if (onReplace && isEmpty(value)) { onReplace(content); } else { splitValue({ value, pastedBlocks: content, onReplace, onSplit, onSplitMiddle, multilineTag }); } return; } let mode = onReplace && onSplit ? 'AUTO' : 'INLINE'; // Force the blocks mode when the user is pasting // on a new line & the content resembles a shortcode. // Otherwise it's going to be detected as inline // and the shortcode won't be replaced. if (mode === 'AUTO' && isEmpty(value) && isShortcode(plainText)) { mode = 'BLOCKS'; } if (__unstableEmbedURLOnPaste && isEmpty(value) && isURL(plainText.trim())) { mode = 'BLOCKS'; } const content = pasteHandler({ HTML: html, plainText, mode, tagName, preserveWhiteSpace }); if (typeof content === 'string') { let valueToInsert = create({ html: content }); // If the content should be multiline, we should process text // separated by a line break as separate lines. valueToInsert = adjustLines(valueToInsert, !!multilineTag); addActiveFormats(valueToInsert, value.activeFormats); onChange(insert(value, valueToInsert)); } else if (content.length > 0) { if (onReplace && isEmpty(value)) { onReplace(content, content.length - 1, -1); } else { splitValue({ value, pastedBlocks: content, onReplace, onSplit, onSplitMiddle, multilineTag }); } } } element.addEventListener('paste', _onPaste); return () => { element.removeEventListener('paste', _onPaste); }; }, []); } /** * Normalizes a given string of HTML to remove the Windows specific "Fragment" comments * and any preceeding and trailing whitespace. * * @param {string} html the html to be normalized * @return {string} the normalized html */ function removeWindowsFragments(html) { const startReg = /.*<!--StartFragment-->/s; const endReg = /<!--EndFragment-->.*/s; return html.replace(startReg, '').replace(endReg, ''); } /** * Removes the charset meta tag inserted by Chromium. * See: * - https://github.com/GeChiUI/gutenberg/issues/33585 * - https://bugs.chromium.org/p/chromium/issues/detail?id=1264616#c4 * * @param {string} html the html to be stripped of the meta tag. * @return {string} the cleaned html */ function removeCharsetMetaTag(html) { const metaTag = `<meta charset='utf-8'>`; if (html.startsWith(metaTag)) { return html.slice(metaTag.length); } return html; } //# sourceMappingURL=use-paste-handler.js.map