8

The problem is that when I click on the HTML code editor option / button -> < > and paste some code, content, text or whatever, the code HTML shown in it <textarea> is encoding to HTML entities.

    $(function() {
        function formatHtmlCode(str) {
            var p = document.createElement('p');
            p.innerHTML = str.trim();
            return format(p, 0).innerHTML;
        }
        function format(node, level) {
            var indentBefore = new Array(level++ + 1).join('  '),
            indentAfter = new Array(level - 1).join('  '),
            textNode;
            for (var i = 0; i < node.children.length; i++) {
                textNode = document.createTextNode('\n' + indentBefore);
                node.insertBefore(textNode, node.children[i]);
                format(node.children[i], level);
                if (node.lastElementChild == node.children[i]) {
                    textNode = document.createTextNode('\n' + indentAfter);
                    node.appendChild(textNode);
                }
            }
            return node;
        }
        $('#editControls a').click(function(e) {
            switch ($(this).data('role')) {
                case 'h2':
                case 'h3':
                case 'p':
                    document.execCommand('formatBlock', false, $(this).data('role'));
                    break;
                case 'code':
                    codeMode = !codeMode;
                    if (codeMode) {
                        var formattedHtml = formatHtmlCode(htmlDiv.html());
                        htmlDiv.css("white-space", "pre");
                        htmlDiv.text(formattedHtml);
                        var editor = $("#editor");
                        editor.addClass("black-bg-colr codeMode");
                        //editor.attr('id', 'editor newID');
                    } else {
                        htmlDiv.css("white-space", "normal");
                        htmlDiv.html(htmlDiv.text().replace(/\r?\n|\r/g, ""));
                        var editor = $("#editor");
                        editor.removeClass("black-bg-colr codeMode");
                        //editor.attr('id', 'editor');
                    }
                    break;
                default:
                    document.execCommand($(this).data('role'), false, null);
                    break;
            }
        });

        let codeMode = false;
        let htmlDiv = $("#editor");
        htmlDiv.on('keyup', function(e) {
            if (!e.shiftKey && e.keyCode === 13) {
                document.execCommand('formatBlock', false, 'p');
            } else if (e.shiftKey) {
                document.execCommand('formatBlock', false, 'p');
            }
        });

        htmlDiv.on("paste", function(e) {
            e.preventDefault();
            var text = (e.originalEvent || e).clipboardData.getData('text/plain');
            document.execCommand('formatBlock', false, 'p');
            document.execCommand('insertText', false, text);
        });

        htmlDiv.on("input", function(e) {
            $(".editor-preview").val(htmlDiv.html());
            $(".editor-preview").keyup();
        });
        
        $('.editor-preview').keyup(function() {
            var contentAttr = $(this).attr('class');
            if (!codeMode) {
                var value = $(this).val();
                $('.' + contentAttr).html(value);
            } else {
                $('.' + contentAttr).html(htmlDiv.text());
            }
        });

    });
.fieldsets {
    border: 1px solid #ccc;
    padding: 1em;
}
#editControls {
    overflow: auto;
    border-top: 1px solid transparent;
    border-left: 1px solid transparent;
    border-right: 1px solid transparent;
    border-color: silver;
    border-top-left-radius: 5px;
    border-top-right-radius: 5px;
    padding: .5em 1em .5em 1em;
    background-color: #fbfbfb;
    width: 100%;
    /* max-width: 950px; */
}
#editor {
    resize: vertical;
    overflow: auto;
    border: 1px solid silver;
    border-bottom-right-radius: 5px;
    border-bottom-left-radius: 5px;
    min-height: 100px;
    padding: 1em;
    background-color: white;
    width: 100%;
    color: #333;
    /* max-width: 950px; */
}
#preview {
    padding: 1em;
    margin: 0 auto;
    width: 97%;
    border-top: 1px dotted #c8ccd0;
    border-bottom: 1px dotted #c8ccd0;
    clear: both;
}
.btn-group>.btn-editor:first-child {
    margin-left: 0;
    -webkit-border-top-left-radius: 4px;
    -moz-border-radius-topleft: 4px;
    border-top-left-radius: 4px;
    -webkit-border-bottom-left-radius: 4px;
    -moz-border-radius-bottomleft: 4px;
    border-bottom-left-radius: 4px;
}

.btn-group a {
    text-decoration: none;
}
.btn-not-space {
    position: relative;
    float: left;
    margin-left: 0 !important;
    border-radius: inherit;
    border: 1px solid transparent;
    border-color: #ccc;
}
.btn-editor {
    height: 30px;
    display: inline-block;
    padding: 6px 12px;
    margin-bottom: 0;
    font-size: 11px;
    font-weight: normal;
    line-height: 1.42857143;
    text-align: center;
    white-space: nowrap;
    vertical-align: middle;
    -ms-touch-action: manipulation;
    touch-action: manipulation;
    cursor: pointer;
    -webkit-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
    user-select: none;
    background-image: none;
    border-radius: 4px;
    border: 1px solid transparent;
    color: #333;
    background-color: #fff;
    border-color: #ccc;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<fieldset class="fieldsets">
                                        <div class="form-group">
        <div class="editor-wrapper">
            <div id="editControls">
                <div class="btn-group">
                    <a class="btn-editor btn-not-space" data-role="bold" data-ref="#">
                        <i class="icon-bold">B</i>
                    </a>
                    <a class="btn-editor btn-not-space" data-role="italic" data-ref="#">
                        <i class="icon-italic">I</i>
                    </a>
                    <a class="btn-editor btn-not-space" data-role="underline" data-ref="#">
                        <i class="icon-underline">U</i>
                    </a>
                </div>
                <div class="btn-group">
                    <a class="btn-editor btn-not-space" data-role="code" data-ref="#">
                        <i class="icon-code-view"><></i>
                    </a>
                </div>
            </div>
            <div id="editor" contenteditable=""></div>
            <br><br><br>
            <textarea id="textarea" class="editor-preview" name="detail"></textarea>
            <br><br><br>
            <div id="preview" class="editor-preview"></div>
        </div>
    </div>                              </fieldset>

To be clearer I attach an image of the problem, in number 1, I write some text, you can see that in the <textarea> said text it is attached with a code HTML that text is written from the div editable, now in image 2, I click on the code editor function < > is the problem there, because when I paste some code or text the HTML code becomes HTML entities as can be seen in the <textarea> of ​​image 3.

enter image description here

In conclusion, what I want to achieve is that the textarea only keeps the HTML code without it being encoded in HTML entities, everything that was written in the editable div, that HTML code that is generated from there is kept in the textarea

2 Answers2

0

The problem is that when you set the .val() of a <textarea> to the contents of htmlDiv.html() (which contains HTML tags), it assumes you mean those literal characters, so they first get encoded (e.g. < becomes &lt;).

The solution is to first decode the HTML entities:

htmlDiv.on("input", function(e) {
    let html = htmlDecode( htmlDiv.html() );
    $(".editor-preview").val( html );
    $(".editor-preview").keyup();
});

function htmlDecode(input) {
  var doc = new DOMParser().parseFromString(input, "text/html");
  return doc.documentElement.textContent;
}

I used this solution to decode the entities: https://stackoverflow.com/a/34064434/378779

kmoser
  • 8,780
  • 3
  • 24
  • 40
  • Hi @kmoser checking the link there are so many comments of security problem using these methods ...? –  Dec 03 '20 at 19:44
  • @Valentina I've updated my answer to use the more secure answer on that page: https://stackoverflow.com/a/34064434/378779 – kmoser Dec 04 '20 at 17:23
0

And what about just this?

htmlDiv.on("input", function (e) {
  e.preventDefault();
  var text = (codeMode)?$.parseHTML(e.target.innerText):e.target.innerHTML
  preview.html(text)
});

I think you over complicated things. This parses the TEXT from the editor in codeMode and simply copies the HTML otherwize.

I removed your "in the middle" textarea which just was confusing.

So in the below snippet, if you type OR paste this <p>Hello</p>, it will be interpreted as HTML only if code mode is active and like plain text otherwize.

console.clear()

$(function () {
  function formatHtmlCode(str) {
    var p = document.createElement("p");
    p.innerHTML = str.trim();
    return format(p, 0).innerHTML;
  }
  function format(node, level) {
    var indentBefore = new Array(level++ + 1).join("  "),
      indentAfter = new Array(level - 1).join("  "),
      textNode;
    for (var i = 0; i < node.children.length; i++) {
      textNode = document.createTextNode("\n" + indentBefore);
      node.insertBefore(textNode, node.children[i]);
      format(node.children[i], level);
      if (node.lastElementChild == node.children[i]) {
        textNode = document.createTextNode("\n" + indentAfter);
        node.appendChild(textNode);
      }
    }
    return node;
  }
  $("#editControls a").click(function (e) {
    switch ($(this).data("role")) {
      case "h2":
      case "h3":
      case "p":
        document.execCommand("formatBlock", false, $(this).data("role"));
        break;
      case "code":
        codeMode = !codeMode;
        $(this).css("background",(codeMode)?"green":"initial")  // Just for this demo to SEE if the codeMode is active... You probably have some style for this already.
        if (codeMode) {
          var formattedHtml = formatHtmlCode(htmlDiv.html());
          htmlDiv.css("white-space", "pre");
          htmlDiv.text(formattedHtml);
          var editor = $("#editor");
          editor.addClass("black-bg-colr codeMode");
          //editor.attr('id', 'editor newID');
        } else {
          htmlDiv.css("white-space", "normal");
          htmlDiv.html(htmlDiv.text().replace(/\r?\n|\r/g, ""));
          var editor = $("#editor");
          editor.removeClass("black-bg-colr codeMode");
          //editor.attr('id', 'editor');
        }
        break;
      default:
        document.execCommand($(this).data("role"), false, null);
        break;
    }
  });

  let codeMode = false;
  let htmlDiv = $("#editor");
  let preview = $(".editor-preview")

  htmlDiv.on("input", function (e) {
    e.preventDefault();
    var text = (codeMode)?$.parseHTML(e.target.innerText):e.target.innerHTML
    preview.html(text)
  });

});
.fieldsets {
  border: 1px solid #ccc;
  padding: 1em;
}
#editControls {
  overflow: auto;
  border-top: 1px solid transparent;
  border-left: 1px solid transparent;
  border-right: 1px solid transparent;
  border-color: silver;
  border-top-left-radius: 5px;
  border-top-right-radius: 5px;
  padding: 0.5em 1em 0.5em 1em;
  background-color: #fbfbfb;
  width: 100%;
  /* max-width: 950px; */
}
#editor {
  resize: vertical;
  overflow: auto;
  border: 1px solid silver;
  border-bottom-right-radius: 5px;
  border-bottom-left-radius: 5px;
  min-height: 100px;
  padding: 1em;
  background-color: white;
  width: 100%;
  color: #333;
  /* max-width: 950px; */
}
#preview {
  padding: 1em;
  margin: 0 auto;
  width: 97%;
  border-top: 1px dotted #c8ccd0;
  border-bottom: 1px dotted #c8ccd0;
  clear: both;
}
.btn-group > .btn-editor:first-child {
  margin-left: 0;
  -webkit-border-top-left-radius: 4px;
  -moz-border-radius-topleft: 4px;
  border-top-left-radius: 4px;
  -webkit-border-bottom-left-radius: 4px;
  -moz-border-radius-bottomleft: 4px;
  border-bottom-left-radius: 4px;
}

.btn-group a {
  text-decoration: none;
}
.btn-not-space {
  position: relative;
  float: left;
  margin-left: 0 !important;
  border-radius: inherit;
  border: 1px solid transparent;
  border-color: #ccc;
}
.btn-editor {
  height: 30px;
  display: inline-block;
  padding: 6px 12px;
  margin-bottom: 0;
  font-size: 11px;
  font-weight: normal;
  line-height: 1.42857143;
  text-align: center;
  white-space: nowrap;
  vertical-align: middle;
  -ms-touch-action: manipulation;
  touch-action: manipulation;
  cursor: pointer;
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
  background-image: none;
  border-radius: 4px;
  border: 1px solid transparent;
  color: #333;
  background-color: #fff;
  border-color: #ccc;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<fieldset class="fieldsets">
  <div class="form-group">
    <div class="editor-wrapper">
      <div id="editControls">
        <div class="btn-group">
          <a class="btn-editor btn-not-space" data-role="bold" data-ref="#">
            <i class="icon-bold">B</i>
          </a>
          <a class="btn-editor btn-not-space" data-role="italic" data-ref="#">
            <i class="icon-italic">I</i>
          </a>
          <a class="btn-editor btn-not-space" data-role="underline" data-ref="#">
            <i class="icon-underline">U</i>
          </a>
        </div>
        <div class="btn-group">
          <a class="btn-editor btn-not-space" data-role="code" data-ref="#">
            <i class="icon-code-view">
              <>
            </i>
          </a>
        </div>
      </div>
      <div id="editor" contenteditable=""></div>
      <div id="preview" class="editor-preview"></div>
    </div>
  </div>
</fieldset>
Louys Patrice Bessette
  • 33,375
  • 6
  • 36
  • 64