diff --git a/src/extensions/behavior/Clipboard/clipboard.ts b/src/extensions/behavior/Clipboard/clipboard.ts index 61d79b140..844bbf77d 100644 --- a/src/extensions/behavior/Clipboard/clipboard.ts +++ b/src/extensions/behavior/Clipboard/clipboard.ts @@ -19,7 +19,7 @@ import {isTextSelection, isNodeSelection, isWholeSelection} from '../../../utils import {BaseNode, pType} from '../../base/BaseSchema'; import {isInsideCode} from './code'; -import {DataTransferType, isIosSafariShare} from './utils'; +import {DataTransferType, extractTextContentFromHtml, isIosSafariShare} from './utils'; export type ClipboardPluginOptions = { yfmParser: Parser; @@ -99,12 +99,31 @@ export const clipboard = ({ !e.clipboardData.types.includes(DataTransferType.Yfm) && (data = e.clipboardData.getData(DataTransferType.Html)) ) { - return false; // default html pasting + const textFromHtml = extractTextContentFromHtml(data); + if (textFromHtml) { + const res = tryCatch(() => textParser.parse(textFromHtml)); + if (res.success) { + const docNode = res.result; + const slice = getSliceFromMarkupFragment(docNode.content); + view.dispatch( + trackTransactionMetrics( + view.state.tr.replaceSelection(slice), + 'paste', + {clipboardDataFormat: DataTransferType.Html}, + ), + ); + isPasteHandled = true; + } else { + logger.error(res.error); + console.error(res.error); + } + } else return false; // default html pasting } if ( - (data = e.clipboardData.getData(DataTransferType.Yfm)) || - (data = e.clipboardData.getData(DataTransferType.Text)) + !isPasteHandled && + ((data = e.clipboardData.getData(DataTransferType.Yfm)) || + (data = e.clipboardData.getData(DataTransferType.Text))) ) { let parser: Parser; let dataFormat: string; diff --git a/src/extensions/behavior/Clipboard/utils.ts b/src/extensions/behavior/Clipboard/utils.ts index 0f36f6444..abb010d1d 100644 --- a/src/extensions/behavior/Clipboard/utils.ts +++ b/src/extensions/behavior/Clipboard/utils.ts @@ -33,3 +33,25 @@ export function isFilesFromHtml({types}: DataTransfer): boolean { export function isImageFile(file: File): boolean { return file.type.startsWith('image/'); } + +export function extractTextContentFromHtml(html: string) { + const element = document.createElement('div'); + element.innerHTML = html; + element.replaceChildren(...Array.from(element.children).filter((v) => v.nodeName !== 'META')); + + // Look if all nodes are div or span. That they don't have any classname and have only text child. + if ( + Array.from(element.children).every(({nodeName, classList, childNodes}) => { + return ( + (nodeName === 'DIV' || nodeName === 'SPAN') && + !classList.length && + childNodes.length === 1 && + (childNodes[0].nodeName === '#text' || childNodes[0].nodeName === 'BR') + ); + }) + ) { + return Array.from(element.children).reduce((a, v) => a + v.textContent + '\n', ''); + } + + return null; +}