Reputation: 37
Even a TipTap editor playground does that - when you paste a text with line breaks, it will remove line breaks and will display text without them.
How can I make editor to retain the line breaks when pasting a text?
Edit: this is how it's done
const editor = {
editorProps: {
handlePaste: (view, event, slice) => {
// Prevent the default paste behavior
event.preventDefault();
// Get plain text from clipboard
const text = event.clipboardData?.getData("text/plain");
if (text) {
// Split text by newlines
const lines = text.split(/\n/);
// Insert each line with proper paragraph formatting
const tr = view.state.tr;
lines.forEach((line, index) => {
// Insert line
tr.insertText(line);
// Add newline between paragraphs (except for last line)
if (index < lines.length - 1) {
tr.split(tr.selection.from);
}
});
view.dispatch(tr);
}
return true;
},
},
}
Upvotes: 0
Views: 313
Reputation: 137
Here is what I did to achieve consistent line breaks from copy-paste events without the tradeoffs of:
This goes in place of your solution:
const editor = {
editorProps: {
handlePaste: (_view, event) => {
// Prevent the default paste behaviour
event.preventDefault();
// Get html text from clipboard
const htmlContents = event.clipboardData?.getData("text/html");
// Let the editor handle things instead
if (!htmlContents) return false;
const cleanedCliboardData = getCleanedHtmlClipboardValue(
htmlContent,
initalValueOfGlobalStlyeFontFamily,
);
// Let the editor handle things instead
if (!cleanedCliboardData) return false;
// Inserting content needs a wrapper component... might as well apply initial global styles
const cleanedHtmlWithWrapper = `<span style="font-family:
${selectedFontFamily};>${cleanedCliboardData}<span>`;
editor?.commands.insertContent(cleanedHtmlWithWrapper);
return true;
},
},
}
The actual cleanup method:
const getCleanedHtmlClipboardValue = (htmlContent, initalValueOfGlobalStlyeFontFamily) => {
const textNodes = ["p", "span"];
// This extends the list of allowed elements that won't be removed
const allowedTags = [...textNodes, "br", "ul", "ol", "li"];
try {
const parser = new DOMParser();
const doc = parser.parseFromString(htmlContent, "text/html");
// ! VERY IMPORTANT !
// This is a NodeListOf<Element>, which is a list of all elements in body that keeps track of parent-child relations
// This means there is no need to travel the DOM via recursions
const documentBodyElements = doc.body.querySelectorAll("*");
documentBodyElements.forEach((element: Element) => {
const elementTagName = element.tagName.toLowerCase();
const isAllowedTag = allowedTags.includes(elementTagName);
const isTextNode = textNodes.includes(elementTagName);
// START OVERRIDE STYLES BELOW - that also removes all unwanted style
if (isAllowedTag && isTextNode) {
// Keep text alignment
if (elementTagName=== "p") {
const paragraphTextAlignment = el.style.textAlign;
el.setAttribute("style", `text-align: ${paragraphTextAlignment}`);
}
// Apply global font-family
if (elementTagName === "span") {
el.setAttribute("style", `font-family: ${initalValueOfGlobalStlyeFontFamily};`);
}
} else if (!isAllowedTag) {
// "Unbox elements" to remove element that are not allowed leaving their content behind
element.replaceWith(...element.childNodes);
}
});
return doc.body.innerHTML;
} catch {
console.error("Clipboard data is invalid text/html");
return null;
}
}
}
allowedTags
so those won't be removed. For example you might want to leave links in. Then add a
. But don't forget to override styles for it too.p
and span
which then can be changed interactively from a UI component by the user if needed.Upvotes: 0