I came across this Stack Overflow post where was discussed the exact thing I needed: to be able to paste text into a contenteditable area, retaining only a few styles. I ran the code snippet there and it work fine. However, when I tried it on my page, all styles were being removed, including those I wanted to keep, like bold and italic. After comparing the codes and a few experimentations, I realized that the reason it was not working was because I was using external CSS, instead of inline.
Is there any way I can make it work with external CSS? I will never know the origin of the text users will post in that contenteditable, and how was style applied to it, so I am looking to address all possibilities.
Also, is there a way to make it work with dragged and dropped text, instead of just pasted text? I tried replacing the event it is listening to from "paste" to "drop", but I get the error e.clipboardData is undefined
const el = document.querySelector('p');
el.addEventListener('paste', (e) => {
// Get user's pasted data
let data = e.clipboardData.getData('text/html') ||
e.clipboardData.getData('text/plain');
// Filter out everything except simple text and allowable HTML elements
let regex = /<(?!(\/\s*)?(b|i|em|strong|u)[>,\s])([^>])*>/g;
data = data.replace(regex, '');
// Insert the filtered content
document.execCommand('insertHTML', false, data);
// Prevent the standard paste behavior
e.preventDefault();
});
.editable {
width: 100%;
min-height: 20px;
font-size: 14px;
color: black;
font-family: arial;
line-height: 1.5;
border: solid 1px black;
margin-bottom: 30px;
}
.big {
font-size: 20px;
}
.red {
color: red;
}
.bold {
font-weight: bold;
}
.italic {
text-decoration: italic;
}
<p class="editable" contenteditable></p>
<p class="notEditable">
Try pasting this paragraph into the contenteditable paragraph above. This text includes <b>BOLD</b>, <i>ITALIC</i>, <s>STRIKE</s>, <u>UNDERLINE</u>, a <a href='#'>LINK</a>, and <span style="font-size:30px; color:red; font-family:Times New Roman">a few other styles.</span> All styles are inline, and it works as expected.
</p>
<p>Now, try pasting this paragraph with external styles. <span class="big">Big</span > <span class="red">red</span> <span class="bold">bold</span> <span class="italic">italic</span>. It no longer works.</p>
Answer: Use the HTML5 contenteditable Attribute You can set the HTML5 contenteditable attribute with the value true (i.e. contentEditable="true" ) to make an element editable in HTML, such as <div> or <p> element.
To make a span element editable with JavaScript, we can set the contentEditable property of the span to true . to add a span and a button. to make the span editable when we click on the button. To do this, we select the span and button with querySelector .
Its totally secure. The contenteditable attribute is an enumerated attribute whose keywords are the empty string, true, and false.
To edit the content in HTML, we will use contenteditable attribute. The contenteditable is used to specify whether the element's content is editable by the user or not. This attribute has two values. true: If the value of the contenteditable attribute is set to true then the element is editable.
As other answer pointed out I don't know any way of getting CSS styles out of other pages using clipboard. . But at your own you could do something like this:
Get getComputedStyle
(CSS only) of all elements filter out wanted style, in this example fontStyle
and fontWeight
. Then you can condition if fontStyle==="italic"
or fontweight==="700"
(bold), textDecoration==="underline rgb(0, 0, 0)"
and wrap that elements into its HTML tags.
You do this because your regex function is only targeting tags, not even inline CSS property font-style: italic;
. Witch is a shame, it would make things a bit easier as you could just read every elements CSS class style and apply it inline, but this way you need to condition it and apply HTML tag.
if ( style.fontStyle==="italic"){
element.innerHTML = "<i>" + element.innerHTML + "</i>";
;}
if ( style.fontWeight==="700"){
element.innerHTML = "<b>" + element.innerHTML + "</b>";
;}
if (style.textDecoration==="underline rgb(0, 0, 0)"){
element.innerHTML = "<u>" + element.innerHTML + "</u>";
;}
In example below if you copy Now, try pasting this paragraph with external styles. Big red bold italic. It no longer works.
you will get bold,underline and italic. You can do the same for rest of your filtering options.
const el = document.querySelector('p');
el.addEventListener('paste', (e) => {
// Get user's pasted data
let data = e.clipboardData.getData('text/html') ||
e.clipboardData.getData('text/plain');
//console.log(data)
// Filter out everything except simple text and allowable HTML elements
let regex = /<(?!(\/\s*)?(b|i|em|strong|u)[>,\s])([^>])*>/g;
data = data.replace(regex, '');
//console.log(data)
// Insert the filtered content
document.execCommand('insertHTML', false, data);
// Prevent the standard paste behavior
e.preventDefault();
});
[...document.querySelectorAll('body *')].forEach(element=>{
const style = getComputedStyle(element)
if ( style.fontStyle==="italic"){
element.innerHTML = "<i>" + element.innerHTML + "</i>";
;}
if ( style.fontWeight==="700"){
element.innerHTML = "<b>" + element.innerHTML + "</b>";
;}
if (style.textDecoration==="underline rgb(0, 0, 0)"){
element.innerHTML = "<u>" + element.innerHTML + "</u>";
;}
});
.editable {
width: 100%;
min-height: 20px;
font-size: 14px;
color: black;
font-family: arial;
line-height: 1.5;
border: solid 1px black;
margin-bottom: 30px;
}
.big {
font-size: 20px;
}
.red {
color: red;
}
.bold {
font-weight: bold;
}
.underline{
text-decoration: underline;
}
.italic {
font-style: italic;
}
<p class="editable" contenteditable></p>
<p class="notEditable">
Try pasting this paragraph into the contenteditable paragraph above. This text includes <b>BOLD</b>, <i>ITALIC</i>, <s>STRIKE</s>, <u>UNDERLINE</u>, a <a href='#'>LINK</a>, and <span style="font-size:30px; color:red; font-family:Times New Roman">a few other styles.</span> All styles are inline, and it works as expected.
</p>
<p id="container"><span class="underline">Now</span>, try pasting this paragraph with external styles. <span class="big">Big</span > <span class="red">red</span> <span class="bold" >bold</span> <span class="italic">italic</span>. It no longer works.</p>
Unfortunately, there is no way to keep the properties of a class from an external source. If you would print the content of the clipboard, you will see that you receive the raw HTML content as it is on the external page, for example:
<div class="some-class">this is the text</div>
The class properties would not be inlined by the browser! And as the content is from an external source, you have no power over it.
On the other hand, if the content is from your page (so the class is defined), you could parse the received HTML and filter the CSS properties, keeping only what you want. Here you have a code sample using vanilla Javascript, no libraries required (also available on Codepen):
const targetEditable = document.querySelector('p');
targetEditable.addEventListener('paste', (event) => {
let data = event.clipboardData.getData('text/html') ||
event.clipboardData.getData('text/plain');
// Filter the string using your already existing rules
// But allow <p> and <div>
let regex = /<(?!(\/\s*)?(div|b|i|em|strong|u|p)[>,\s])([^>])*>/g;
data = data.replace(regex, '');
const newElement = createElementFromHTMLString(data);
const cssContent = generateFilteredCSS(newElement);
addCssToDocument(cssContent);
document.execCommand('insertHTML', false, newElement.innerHTML);
event.preventDefault();
});
// Scan the HTML elements recursively and generate CSS classes containing only the allowed properties
function generateFilteredCSS(node) {
const newClassName = randomString(5);
let content = `.${newClassName}{\n`;
if (node.className !== undefined && node.className !== '') {
// Get an element that has the class
const elemOfClass = document.getElementsByClassName(node.className)[0];
// Get the computed style properties
const styles = window.getComputedStyle(elemOfClass);
// Properties whitelist, keep only those
const propertiesToKeep = ['font-weight'];
for (const property of propertiesToKeep) {
content += `${property}: ${styles.getPropertyValue(property)};\n`;
}
}
content += '}\n';
node.className = newClassName;
for (const child of node.childNodes) {
content += generateFilteredCSS(child);
}
return content;
}
function createElementFromHTMLString(htmlString) {
var div = document.createElement('div');
div.innerHTML = htmlString.trim();
return div;
}
function addCssToDocument(cssContent) {
var element = document.createElement("style");
element.innerHTML = cssContent;
var header = document.getElementsByTagName("HEAD")[0];
header.appendChild(element);
}
function randomString(length) {
var result = '';
var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
var charactersLength = characters.length;
for (var i = 0; i < length; i++) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
}
.editable {
width: 100%;
min-height: 20px;
font-size: 14px;
color: black;
font-family: arial;
line-height: 1.5;
border: solid 1px black;
margin-bottom: 30px;
}
.red-bg {
background-color: red;
font-weight: bold;
}
<p class="editable" contenteditable></p>
<p class="red-bg test">
This is some text
</p>
About the drag and drop functionality, you have to use event.dataTransfer.getData()
in the drop
event listener, the rest is the same.
References
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With