Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I restrict the style of the text pasted in a contenteditable area?

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> 
like image 713
MikeMichaels Avatar asked Jul 23 '20 10:07

MikeMichaels


People also ask

How do I make a div text editable?

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.

How do I make an editable span?

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 .

Is Contenteditable safe to use?

Its totally secure. The contenteditable attribute is an enumerated attribute whose keywords are the empty string, true, and false.

How do you make an editable content in HTML?

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.


Video Answer


2 Answers

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>
like image 141
ikiK Avatar answered Oct 24 '22 15:10

ikiK


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

  1. How to generate a DOM element from a HTML string
  2. How to add CSS classes at runtime using Javascript
  3. How to generate a random string (unique ID) in Javascript
  4. Drag&drop data transfer
like image 43
Mihail Feraru Avatar answered Oct 24 '22 15:10

Mihail Feraru