0

I need split element (inside editable div) from the cursor position into two parts, Then insert a custom element between it.

before making changes: (vertical line "|" Shows the position of the cursor. I used the "|" to show where the position of the cursor to you. But in reality, this character does not exist in the text.)

<div contenteditable="true">
  <span style="font-size='14px'">this is | a test</span>

  <p>other paragraphs .....</p>
</div>

after making changes:

<div contenteditable="true">
  <span style="font-size:'14px'">this is </span><span style="font-size:'16px'">new text|</span><span style="font-size:'14px'">a test</span>

<p>other paragraphs .....</p>
</div>

It should be noted that after making changes, the cursor must be inserted into the new custom element after the last letter.

I try it but :

  let sel = window.getSelection();
  let range = sel.getRangeAt(0);
  if (range.startContainer.parentNode.nodeName === 'SPAN') {
    let sel, range, cloneNode1, cloneNode2, cloneText, allContents, contentsBeforeCursor, contentsAfterCursor, newNode
    sel = window.getSelection();
    range = document.createRange();
    range.selectNode(sel.anchorNode);
    cloneNode1 = range.commonAncestorContainer.cloneNode();
    cloneNode2 = cloneNode1.cloneNode();
    cloneText = range.cloneContents();

    allContents = cloneText.textContent;
    contentsBeforeCursor = allContents.substring(0, sel.anchorOffset);
    contentsAfterCursor = allContents.substring(sel.anchorOffset, allContents.length);
    cloneNode1.textContent = contentsBeforeCursor
    cloneNode2.textContent = contentsAfterCursor
    newNode = document.createElement('SPAN');
    for (let i = 0; i < cloneNode1.attributes.length; i++) {
      let attr = cloneNode1.attributes.item(i);
      newNode.setAttribute(attr.nodeName, attr.nodeValue);
    }
    newNode.style.fontSize = '13px';

    range.selectNode(sel.anchorNode.parentNode);
    range.deleteContents();
    range.insertNode(cloneNode1)
    range.insertNode(newNode)
    range.insertNode(cloneNode2)
    newNode.focuse();

https://jsfiddle.net/nekooee/0h8zcfv4/43/

This is to change the font in the middle of a SPAN. The CKEditor does the same.

nekooee
  • 142
  • 2
  • 9
  • 1
    @ggorlen I edited my question and add minimal my try code. thank you – nekooee Jun 24 '20 at 17:09
  • Get the `innerHTML` of the div. Say: `var innerHTML=div.innerHTML`. Then: `innerHTML.split("|").join("element to be inserted here")`. Then: `div.innerHTML=innerHTML`. Or, one-line: `div.innerHTML=div.innerHTML.split("|").join("element to be inserted here")`. – iAmOren Jun 24 '20 at 19:10
  • 1
    If you want to split the surrounding element, you'll need to get the opening and closing parts and duplicate them: closing, inserted, opening. Use `split` & `join` or `.indexOf`... – iAmOren Jun 24 '20 at 19:13
  • @iAmOren thank you. Your first code cannot be used because you have considered all the div content. There are other elements there. The element can only be obtained based on the cursor. The second comment you made is correct. But I don't know how to write it? – nekooee Jun 25 '20 at 09:35
  • @iAmOren I used the "|" to show where the position of the cursor. But in reality, this character does not exist in the text. please help me. – nekooee Jun 27 '20 at 09:45
  • Does this answer your question? [JavaScript: get cursor position in contenteditable div](https://stackoverflow.com/questions/19620439/javascript-get-cursor-position-in-contenteditable-div) – ikiK Jun 27 '20 at 13:31
  • I've tried... This is what I've got: `sel.type` should be "Caret" for no selected text. In anycase, look into `sel.extentNode` for where the cursor is/end of selection. It's `outerHTML` could be split and duplicated and joined with injected element. I'm yet to figure out how to put the cursor at end of injected element. – iAmOren Jun 27 '20 at 14:10
  • @ikiK In that question, it only adds a new phrase. But I will split the tag (that surrounds the cursor) from the cursor position into two parts and then add a new tag in the middle. This is to change the font in the middle of a SPAN. The CKEditor does the same. – nekooee Jun 27 '20 at 15:21
  • Replace phrase TEST with your wanted span, i tried it and its working in fiddle from given answer. It si exactly same question and it has working example. – ikiK Jun 27 '20 at 15:22
  • @iAmOren Can you share your code with me? – nekooee Jun 27 '20 at 18:39
  • Open page with the `div`, click somewhere, open devTools, `sel=getSelection()`, play with what you get. Then, get `sel.extentNode`'s `outerHTML` and get the opener and closer, at offset insert closer, injected element's HTML, and opener - put all of that back in. I don't know how to put the cursor at the end of the injected element. – iAmOren Jun 27 '20 at 19:35

2 Answers2

0

Like I said in comment isn't this what you want?

It even has same HTML markup as yours.

Also your HTML markup is all wrong:

<span style="font-size='14px'">

Should be:

 <span style="font-size:14px">

Same goes for every style attributes in your examples.

function pasteHtmlAtCaret(html, selectPastedContent) {
    var sel, range;
    if (window.getSelection) {
        // IE9 and non-IE
        sel = window.getSelection();
        if (sel.getRangeAt && sel.rangeCount) {
            range = sel.getRangeAt(0);
            range.deleteContents();

            // Range.createContextualFragment() would be useful here but is
            // only relatively recently standardized and is not supported in
            // some browsers (IE9, for one)
            var el = document.createElement("div");
            el.innerHTML = html;
            var frag = document.createDocumentFragment(), node, lastNode;
            while ( (node = el.firstChild) ) {
                lastNode = frag.appendChild(node);
            }
            var firstNode = frag.firstChild;
            range.insertNode(frag);

            // Preserve the selection
          /*
            if (lastNode) {
                range = range.cloneRange();
                range.setStartAfter(lastNode);
                if (selectPastedContent) {
                    range.setStartBefore(firstNode);
                } else {
                    range.collapse(true);
                }
                sel.removeAllRanges();
                sel.addRange(range);
            }*/
        }
    } else if ( (sel = document.selection) && sel.type != "Control") {
        // IE < 9
        var originalRange = sel.createRange();
        originalRange.collapse(true);
        sel.createRange().pasteHTML(html);
        if (selectPastedContent) {
            range = sel.createRange();
            range.setEndPoint("StartToStart", originalRange);
            range.select();
        }
    }
}

function test()
    {
        var input = '<span style="font-size:25px;color:red"> new text </span>';
        //document.getElementById("myDiv").focus();
        pasteHtmlAtCaret(input, false);
    }
<div contenteditable="true">
  <span style="font-size:14px">this is a test</span>

  <p>other paragraphs .....</p>
  
<button type="button" onclick="test()" unselectable="on">Test</button>
</div>
ikiK
  • 6,328
  • 4
  • 20
  • 40
  • First of all, thank you very much. I think there is a problem. Because "new text" is added to the button! Also, the previous span is not divided into two parts. – nekooee Jun 27 '20 at 18:05
  • I moved the button from div, Now "new text" will be added to the cursor position. But instead of deleting the previous span and creating 3 new spans, a new span is created inside the previous span. Please refer to the "after making changes" section in the question. your code:https://jsfiddle.net/nekooee/L3srva9u/3/ – nekooee Jun 27 '20 at 18:28
  • It was very easy if I just wanted to insert my HTML. Even shorter and simpler than you wrote. However, the old SPAN must be split and an HTML insert between them. – nekooee Jun 27 '20 at 19:16
  • @nekooee how about you add aditional to "my" span: at biginng and at end, that will "split" it. Its basic html manipulation. – ikiK Jun 27 '20 at 19:21
  • I try add ' new text ' for input html but not correct split. see this sample code:https://jsfiddle.net/nekooee/L3srva9u/11/ – nekooee Jun 27 '20 at 19:39
  • @nekooee Its saturday night here, that should work, ill cheek it tomorow – ikiK Jun 27 '20 at 19:43
  • Ok, I'm waiting for you tomorrow. In this code, input HTML is a Node. And a Node cannot contain an unclosed tag or multi tag. – nekooee Jun 27 '20 at 20:03
  • Even if SPAN can be opened and closed, how do you transfer the attributes of the first SPAN? – nekooee Jun 28 '20 at 16:18
  • @nekooee Can you please elaborate why you need to "split" elements, nesting spans in each other is perfectly valid HTML syntax. And you are preserving styles. – ikiK Jun 28 '20 at 19:51
  • All professional editors like CKeditor do the same. The result will be standard. The code I wrote above works fine, but the order of adding span is not correct. I've always had problems with JavaScript in this area. The code is not executed line by line and the order is disrupted. – nekooee Jun 29 '20 at 15:41
0

I found the answer. The code should be written as follows:

let sel = window.getSelection();
let range = sel.getRangeAt(0);
if (range.startContainer.parentNode.nodeName === 'SPAN') {
    let newNode = document.createElement('SPAN');
   
    let cloneNode1, cloneNode2, cloneText, allContents, contentsBeforeCursor, contentsAfterCursor;
    const anchorOffset = sel.anchorOffset
    range.selectNode(sel.anchorNode);
    cloneNode1 = range.commonAncestorContainer.cloneNode();
    cloneNode2 = cloneNode1.cloneNode();
    cloneText = range.cloneContents();

    allContents = cloneText.textContent;
    contentsBeforeCursor = allContents.substring(0, anchorOffset);
    contentsAfterCursor = allContents.substring(anchorOffset, allContents.length);

    cloneNode1.textContent = contentsBeforeCursor;
    cloneNode2.textContent = contentsAfterCursor;

    for (let i = 0; i < cloneNode1.attributes.length; i++) {
        let attr = cloneNode1.attributes.item(i);
        newNode.setAttribute(attr.nodeName, attr.nodeValue);
    }
    newNode.style.fontSize = '13px';

    range.selectNode(sel.anchorNode);
    range.deleteContents();
    range.insertNode(cloneNode1);
    range.setStartAfter(cloneNode1);
    range.insertNode(cloneNode2)

    range.setStartAfter(cloneNode1);
    range.insertNode(newNode);
    const textNode = document.createTextNode('\u00A0');
    range.setStart(newNode, 0);
    range.insertNode(textNode);
    sel.removeAllRanges();
    range.selectNodeContents(newNode);
    sel.addRange(range);
}
nekooee
  • 142
  • 2
  • 9