0

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>
Feelsbadman
  • 1,163
  • 4
  • 17
  • 37

2 Answers2

1

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">
Bibberty
  • 4,670
  • 2
  • 8
  • 23
  • Ah nice, I didn't know you can insert node into range -- this is definitely more elegant than the way I'm doing it. You can add `sel.collapseToEnd()` to move the cursor to last line as well, that'll allow user to continue to write normal text immediately – Derek Nguyen Dec 31 '18 at 06:12
  • @DerekNguyen added code to reflect your great suggestion. – Bibberty Dec 31 '18 at 06:48
  • could we do the same if the selected content is the first in div? e.g: if u select "One" and then surround it then try to type before that, it wont let you. – Feelsbadman Dec 31 '18 at 15:42
  • For the start, we could just add another div. Would be better to convert the space div to a lambda and call. Want me to edit ? – Bibberty Dec 31 '18 at 17:11
  • @Bibberty yes, please – Feelsbadman Jan 01 '19 at 14:16
  • Updated to support addition of space to start and end – Bibberty Jan 01 '19 at 19:39
  • @Bibberty the ` ` is causing a bug in nodejs, content is splitted up when i use ` ` e.g: `posterContent: '', 'nbsp;hello': '', 'nbsp;
    ': ''` any clue why this happens ?
    – Feelsbadman Jan 02 '19 at 21:43
  • 1
    Ok, in node you are trying to save this to a database ? If so, you might want to encode/decode on write/read. Alternatively, we could strip out these spacers before saving, they are only there to assist with editing. – Bibberty Jan 02 '19 at 21:45
  • @Bibberty and im guessing that the stripping should be right before sending the contents to the server? – Feelsbadman Jan 02 '19 at 21:52
  • It would make sense. These `span` elements the we add are really only there to help the user during editing. We could go an extra step and give them a class to correctly identify them and then `querySelectAll` of them when submission is taking place and remove them. – Bibberty Jan 02 '19 at 22:07
  • @Bibberty this is what i've tried so far: https://stackoverflow.com/questions/54013934/typeerror-cannot-convert-object-to-primitive-value – Feelsbadman Jan 02 '19 at 22:24
  • Are you comfortable with adding an `id` or `class` identifier to the editable `div`. If so lets add a `getContentForSave()` method. – Bibberty Jan 02 '19 at 22:32
  • sure, that would be fine. – Feelsbadman Jan 02 '19 at 22:39
  • @Bibberty I tried `$('.editor span').html(" ");` and it works fine now. is this an OK fix? – Feelsbadman Jan 02 '19 at 22:57
  • 1
    I edited code. But yes. also I read the other post and agree with Taplar. You should look at the server and find out the real error here. Typically if you are saving content like HTML it is good practice to encode it when saving and then decode it when returning for presentation. Worth researching. – Bibberty Jan 02 '19 at 23:04
1

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();
  }
Derek Nguyen
  • 11,294
  • 1
  • 40
  • 64