71

I have a problem with contenteditable line breaks on SAFARI/CHROME. When I press "return" on a contentEditable <div>, instead of creating a <br> (like Firefox), they create a new <div>:

<div>Something</div>
<div>Something</div>

That looks like (on the contentEditable DIV):

Something
Something

But after sanitization (removing <div>), I get this:

SomethingSomething

In Firefox, the contenteditable is:

Something
<br>
Something

And that after sanitization looks the same:

Something
Something

Is there any solution to "normalize" this across browsers?

I've found this code on Make a <br> instead of <div></div> by pressing Enter on a contenteditable

$(function(){

  $("#editable")

  // make sure br is always the lastChild of contenteditable
  .live("keyup mouseup", function(){
    if (!this.lastChild || this.lastChild.nodeName.toLowerCase() != "br") {
      this.appendChild(document.createChild("br"));
     }
  })

  // use br instead of div div
  .live("keypress", function(e){
    if (e.which == 13) {
      if (window.getSelection) {
        var selection = window.getSelection(),
          range = selection.getRangeAt(0),
          br = document.createElement("br");
        range.deleteContents();
        range.insertNode(br);
        range.setStartAfter(br);
        range.setEndAfter(br);
        range.collapse(false);
        selection.removeAllRanges();
        selection.addRange(range);
        return false;
      }
    }
  });
});

This works, but (in SAFARI and CHROME) I have to press two times the "return" key to get a new line...

Any idea?

Edit: With the code I found ( at the bottom of this question) is working fine except the function that "makes sure a <br> element is always the lastChild... Any idea on how to fix this?

Edit 2: I'm getting this error on the console: Uncaught TypeError: Object #<HTMLDocument> has no method 'createChild'

Edit 3: Ok, I changed the document.createChild("br"); to document.createElement("br"); and I think I got it working in FF/Safari/Chrome... All return <br> for new lines...

The problem is now that when I'm inside an Ordered or Unordered List, I need to get a new line without <br>...

Edit 4: If anyone interested in the solution of the last edit: Avoid createElement function if it's inside a <LI> element (contentEditable)

Community
  • 1
  • 1
Santiago
  • 2,405
  • 6
  • 31
  • 43
  • what does your sanitization method look like? – MikeM May 16 '11 at 21:36
  • @mdmullinax It just removes all the
    ... I'm using https://github.com/gbirke/Sanitize.js The problem is that Safari/Chrome, when I press return, they just create a new DIV instead of making a
    like Firefox
    – Santiago May 16 '11 at 21:43
  • 1
    Maybe you can use $("div:empty").replaceWith("
    ");
    – Anders Lindén Aug 04 '12 at 22:39
  • Did you find anything useful for the bug of the two return keys? I have ran accross the same problem, the only thing I seem to be able to do is a hack (add an empty text and select it so if you continue to write it will automatically disappear), I'll be posting in on that thread just in case. It's not perfect but it works. – Micaël Félix Oct 18 '12 at 14:43
  • Firefox now has a hybrid approach: it wraps the
    s inside
    s. This makes it behave similarly to Chrome, I guess because react removes the
    s entirely, including the
    s.
    – trysis Jan 18 '19 at 20:15

8 Answers8

32

This technique appears to avoid the Chrome bug that does show BRs the first time (with the code you mentionned you need to push two times the Enter key).

It's not a perfect hack but it works: it adds a whitespace after your BR so it show properly. However, you will see that adding only a whitespace " " will NOT change anything, it works with other letters. The browser will not show it, probably because it is like a blank space inside an html page, it simply does not mean anything. To get rid of that bug I created a div with jQuery that includes a &nbsp; tag, and I take the text() property to put it in the textnode otherwise it doesn't work.

$editables = $('[contenteditable=true]');

$editables.filter("p,span").on('keypress',function(e){
 if(e.keyCode==13){ //enter && shift

  e.preventDefault(); //Prevent default browser behavior
  if (window.getSelection) {
      var selection = window.getSelection(),
          range = selection.getRangeAt(0),
          br = document.createElement("br"),
          textNode = document.createTextNode("\u00a0"); //Passing " " directly will not end up being shown correctly
      range.deleteContents();//required or not?
      range.insertNode(br);
      range.collapse(false);
      range.insertNode(textNode);
      range.selectNodeContents(textNode);

      selection.removeAllRanges();
      selection.addRange(range);
      return false;
  }

   }
});
Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455
Micaël Félix
  • 2,697
  • 5
  • 34
  • 46
  • 1
    Add range.deleteContents(); before selection.removeAllRanges();to get rid of those pesky   while keeping it all work ^^ – seahorsepip Oct 06 '14 at 06:24
  • 1
    Adding document.execCommand('delete') just before "return false" does the trick and does not show selected whitespace selection. – Priyesh Diukar Jun 27 '18 at 13:10
19

You can simply use following command document.execCommand('insertHTML',false,'<br>');

This command will prevent default behavior and add a tag you wish. That's all, 1 line of code

And even easier approach.

CSS ONLY.

To you contenteditable element apply display:inline-block rule and this will add brs only

Karb
  • 327
  • 3
  • 12
  • 2
    This css approach is awesome! Cheers @Karb – nevada_scout Sep 27 '18 at 10:51
  • None of these two solutions work for me. Tried with "keypress", "keydown" and "keyup", and with these two solutions at a time. But Firefox is still creating DIVS :/ – Ann MB May 14 '19 at 23:43
  • Note that you will never get the same behavior from the browsers if you only slap `contenteditable=true` because the end user visible behavior has not been specified and every browser vendor has different ideas. You have to use more or less full JS implementation to get browsers emit the output you want. One common option is to use TinyMCE which is now licensed with MIT license so using it (with open source plugins) is free for all. – Mikko Rantalainen Nov 18 '22 at 07:57
16

Listening to keystrokes seems like a lot of work. Would something as simple as this be feasible:

var html = $('.content').html().replace(/<div>/gi,'<br>').replace(/<\/div>/gi,'');

JSFiddle demo here.

Also, it looks like sanitize.js allows a custom configuration. Have you tried adding div as an allowed element?

Allowing HTML/CSS is dangerous, so ensure you are being extra cautious.

EDIT: Removed server side comments. Sanitising occurs at time of usage- if the client wants to bypass this locally it can be done so at their own risk. Thanks Vincent McNabb for pointing this out.

Community
  • 1
  • 1
Kurt
  • 7,102
  • 2
  • 19
  • 16
  • 3
    It looks like the sanitisation is done for display purposes, not security purposes, in which case it is perfectly reasonable to do it on the client. They can bypass it if they want, and all it would do is make things look ugly for them. – Vincent McNabb Dec 10 '13 at 03:31
  • I believe sanitisation is done for security purposes at the time of usage (e.g, stripping unsafe tokens for HTML output). You make an excellent point that if the client wants to bypass this locally it can be done so at their own risk. – Kurt Jan 17 '15 at 23:30
  • Are you trying to parse Html with Regex? before you do that, read advice here: https://stackoverflow.com/questions/1732348/regex-match-open-tags-except-xhtml-self-contained-tags/1732454#1732454 – Simon H Feb 20 '23 at 16:37
3

Webkits won’t render a single br if it’s the last (non-empty) child of a container. For Safari, the native keystroke to insert a line break is Ctrl + Return. If you look closely, you notice that Safari actually inserts two brs while only rendering one; however, it will insert a single br if there is some trailing text, or will get rid of double brs once you enter some (if there were doubles).

Apparently, it seems a solution for Webkits is to insert double brs as they themselves do, and let them handle the rest.

Klim Lee
  • 31
  • 1
2

You can use:

document.execCommand("defaultParagraphSeparator", false, "br");

The complete reference is here

Saeed
  • 123
  • 1
  • 1
  • 7
1

You might want to check out the css white-space property. Setting the style to white-space: pre-wrap allows you to get rid of all that javascript, because it changes the behavior of whitespace to display instead of collapse.

And see this answer: HTML - Newline char in DIV content editable?

Community
  • 1
  • 1
B T
  • 57,525
  • 34
  • 189
  • 207
  • This issue still happens no matter what white-space is set to, this has to do with contentEditable – rtaft Sep 06 '17 at 16:05
0

see this Fiddle

Or this post

How to parse editable DIV's text with browser compatibility

Community
  • 1
  • 1
user10
  • 5,186
  • 8
  • 43
  • 64
0

The use case generally occurs when you are prefilling the data in a content editable div & resending it to the server.

Eg. - Like editing a post on a social media website, to mitigate this I have created new div for each new line.

let conD =data.postDescription.split('\n');
let conH = "";
conD.forEach((d)=>{
        conH +=`<div>${d}</div>`;
       });
$("#contentbox").html(conH);
Moonis Abidi
  • 663
  • 10
  • 15