Feelsbadman
Feelsbadman

Reputation: 1165

Contenteditable surround element by tag; can't get cursor out of newly surrounded selection

I have a div which is being used as a user-friendly text editor on my school-project website. However if I select all of the text inside the editor and attemp to surround it with <code> tags </code> I can not get the cursor out of it, it keeps adding whatever i type into the same tag, which is <code>.

After some time I gave up on it, but then I found this answer and tried to modify the jsfiddle that is mentioned there in the hopes of getting it to work. But no luck.

If I, lets say, select the last word in any line and surround it with <code> tags, I am able to get the cursor out of it where there is non-surrounded text but on the end there's no plaintext to jump onto so the cursor is stuck in <code>

How can I make sure that the cursor doesn't get stuck in added tags? maybe a way to move the cursor out of it?

jsFiddle

function surroundSelection() {
    var code = document.createElement("code");
    code.style.fontStyle = "italic";
    code.style.color = "#333";
    code.style.background = "#ddd";
    
    if (window.getSelection) {
        var sel = window.getSelection();
        if (sel.rangeCount) {
            var range = sel.getRangeAt(0).cloneRange();
            range.surroundContents(code);
            sel.removeAllRanges();
            sel.addRange(range);
        }
    }
}
div, input {
  padding:10px; 
  
}

div {border:1px solid;}
<input type="button" onclick="surroundSelection()" value="Surround">
<div contenteditable="true">One two three four</div>

Upvotes: 0

Views: 476

Answers (2)

Bibberty
Bibberty

Reputation: 4768

A slightly quirky workaround here. No longer using surround but instead copying contents into the new node. We then add a space at the end. It it now possible to click after the code block, even if all text was selected.

EDIT: Added sel.collapseToEnd(); as per comment (thanks). It improves the experience by putting the user straight into normal typing context. However, the space is still required, it does not work without it.

EDIT: Now adds space to the start and end.

EDIT: Remove spacers for send to server.

function surroundSelection() {
  let code = document.createElement("code");
  code.style.fontStyle = "italic";
  code.style.color = "#333";
  code.style.background = "#ddd";
  const newSpan = () => {
    let span = document.createElement('span');
    span.classList.add('codespacer');
    span.innerHTML = '&nbsp;';
    return span;
  }

  if (window.getSelection) {
    let sel = window.getSelection();
    if (sel.rangeCount) {
      let range = sel.getRangeAt(0).cloneRange();
      code.innerHTML = range.toString();
      range.deleteContents();
      range.insertNode(newSpan());
      range.insertNode(code);
      range.insertNode(newSpan());
      sel.removeAllRanges();
      sel.addRange(range);
      sel.collapseToEnd();
    }
  }
}


function getContentForSave() {
  let codeSection = document.querySelector('#codeSection');
  // Copy into a temporary element (otherwise the text will change in the UI)
  let tempSection = document.createElement('div');
  tempSection.innerHTML = codeSection.innerHTML;
  console.log(`Before: ${tempSection.innerHTML}`);
  
  // Remove all codespacers 
  let spacers = tempSection.querySelectorAll('.codespacer');
  for(let space of spacers) {
    tempSection.removeChild(space);
  }
  console.log(`After: ${tempSection.innerHTML}`);
  
  return tempSection.innerHTML;
}
div,
input {
  padding: 10px;
}

div {
  border: 1px solid;
}
<input type="button" onclick="surroundSelection()" value="Surround">
<div contenteditable="true" id="codeSection">One two three four</div>
<input type="button" onclick="getContentForSave();" value="Test For Save">

Upvotes: 1

Derek Nguyen
Derek Nguyen

Reputation: 11577

I think a way you can get over it is to append a empty space textNode right after the code and then move the cursor to its position. This way user can immediately continue to write normal text.

Here's the break down:

  • Create a textNode containing an empty space character with document.createTextNode('\u00A0'). Note, a literal ' ' didn't work on my browser (Chrome 70).
  • Add the new empty text node to the div.
  • Add the new empty text node to current range with range.setEnd() so we can use selection.collapseToEnd() method to move the cursor to the very end.

Altogether (fiddle)

+ function addPad() {
+   var $input = document.querySelector('div');
+   var pad = document.createTextNode('\u00A0');
+   $input.appendChild(pad);
+   return pad;
+ }

  function surroundSelection() {
    var sel = window.getSelection();
    if (!sel || !sel.rangeCount) return;

    var code = document.createElement("code");  
    var range = sel.getRangeAt(0).cloneRange();
    range.surroundContents(code);
    sel.removeAllRanges();
    sel.addRange(range);

+   // create empty node + add it to div
+   var padNode = addPad();

+   // update the current range to include the empty node.
+   // since we only add 1 space, the `offset` is set to 1.
+   range.setEnd(padNode, 1);

+   // move the cursor to very end of range
+   sel.collapseToEnd();
  }

Upvotes: 1

Related Questions