javascripthighlightjs

Place cursor at the end of <pre> when contenteditible=true and using highlight.js


I'm using highlight.js to automatically highlight code in a <pre> element. I'm trying to enable realtime syntax highlighting while typing. The code I have now works as desired and highlighting indeed works upon carriage return, but the cursor moves to the beginning of the <pre> element. I would like to move the cursor to the end after the the keyup event detects a carriage return and I reprocess the element to add the syntax highlighting. How can I accomplish this?

An example is located here: https://tools.peer.ooo/snippity

But the code I'm using for the relevant portion is this:

document.addEventListener("keyup", function(e){
    if(e.keyCode == 13){
        document.querySelectorAll('pre').forEach((block) => {
            hljs.highlightBlock(block);

        })
    }
})

How can I put the cursor at the end of the extant input?

Code Snippet

  document.addEventListener('DOMContentLoaded', async () => {
    document.querySelectorAll('pre').forEach((block) => {
      hljs.highlightBlock(block);
      block.classList.add("container")
    })
  })
  var db = new Dexie("snippits");
  db.version(1).stores({
    snippits: "++id, identifier, name, data, *tags"
  });

  document.getElementById("save").addEventListener("click", function() {

    let name = document.getElementById("snipname").value
    let data = document.getElementById("rendered").textContent
    db.snippits.add({
      identifier: "some id",
      name: name,
      data: data
    })
    document.querySelectorAll('pre').forEach((block) => {
      hljs.highlightBlock(block);
    })

  })

  document.getElementById("rendered").addEventListener("blur", function(e) {
    console.log("tabbed out")
    document.querySelectorAll('pre').forEach((block) => {
      hljs.highlightBlock(block);
    })
  })

  document.addEventListener('DOMContentLoaded', async () => {
    const data = await db.snippits.orderBy('id').last()

    console.log(JSON.stringify(data))
    document.getElementById("snipname").value = data.name
    document.getElementById("rendered").textContent = data.data
    document.querySelectorAll('pre').forEach((block) => {
      hljs.highlightBlock(block);
    })
  })

  function onPaste(e) {
    e.preventDefault(); // stop the paste
    const t = e.clipboardData.getData("text"); // grab the pasted content as plain text
    e.target.textContent = t; // set the element's innerHTML to the plain text
    document.querySelectorAll('pre').forEach((block) => {
      hljs.highlightBlock(block);
    })
  }

  document.addEventListener("keyup", function(e) {
    if (e.keyCode == 13) {
      document.querySelectorAll('pre').forEach((block) => {
        hljs.highlightBlock(block);
      })
    }
  })

  const paste = document.getElementById('rendered');
  paste.addEventListener('paste', onPaste);
 html {
    background: rgb(58, 58, 63)
  }

  pre {
    outline: none;
    min-height: 100px;
  }

  input {
    outline: none;
  }

  textarea {
    outline: none;
  }

  /* Comment */
  .hljs-comment,
  .hljs-quote {
    color: #989498;
  }

  /* Red */
  .hljs-variable,
  .hljs-template-variable,
  .hljs-attribute,
  .hljs-tag,
  .hljs-name,
  .hljs-selector-id,
  .hljs-selector-class,
  .hljs-regexp,
  .hljs-link,
  .hljs-deletion {
    color: #dd464c;
  }

  /* Orange */
  .hljs-number,
  .hljs-built_in,
  .hljs-builtin-name,
  .hljs-literal,
  .hljs-type,
  .hljs-params {
    color: #fd8b19;
  }

  /* Yellow */
  .hljs-class .hljs-title {
    color: #fdcc59;
  }

  /* Green */
  .hljs-string,
  .hljs-symbol,
  .hljs-bullet,
  .hljs-addition {
    color: #8fc13e;
  }

  /* Aqua */
  .hljs-meta {
    color: #149b93;
  }

  /* Blue */
  .hljs-function,
  .hljs-section,
  .hljs-title {
    color: #1290bf;
  }

  /* Purple */
  .hljs-keyword,
  .hljs-selector-tag {
    color: #c85e7c;
  }

  .hljs {
    display: block;
    overflow-x: auto;
    background: #322931;
    color: #b9b5b8;
    padding: 0.5em;
  }

  .hljs-emphasis {
    font-style: italic;
  }

  .hljs-strong {
    font-weight: bold;
  }
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.0/css/bulma.min.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@10.1.2/build/styles/default.min.css">
  <script src="https://cdn.jsdelivr.net/npm/dexie@3.0.2/dist/dexie.min.js"></script>
  <script src="https://chr15m.github.io/bugout/bugout.min.js"></script>
  <script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@10.1.2/build/highlight.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/highlightjs-line-numbers.js/2.8.0/highlightjs-line-numbers.min.js"></script>

<section class="section container">
    <input class="input" id="name" placeholder="Search . . ."></input>
    <div id="results" class="container"></div>
    <pre class="containe" id="rendered" contenteditable="true" spellcheck="false"></pre>
    <div class="field is-grouped">
      <input class="input" id="snipname" placeholder="Snippit name . . ."></input>
      <button class="button" id="save">Save</button>
      <button class="button" id="delete">Delete</button>
      <button class="button" id="share">Share</button>
    </div>
    <textarea class="textarea" id="notes" placeholder="Notes . . ."></textarea>
  </section>


Solution

  • How about this?

    document.addEventListener('DOMContentLoaded', async() => {
      document.querySelectorAll('pre').forEach((block) => {
        hljs.highlightBlock(block);
        block.classList.add("container")
      })
    })
    var db = new Dexie("snippits");
    db.version(1).stores({
      snippits: "++id, identifier, name, data, *tags"
    });
    
    document.getElementById("save").addEventListener("click", function() {
    
      let name = document.getElementById("snipname").value
      let data = document.getElementById("rendered").textContent
      db.snippits.add({
        identifier: "some id",
        name: name,
        data: data
      })
      document.querySelectorAll('pre').forEach((block) => {
        hljs.highlightBlock(block);
      })
    
    })
    
    document.getElementById("rendered").addEventListener("blur", function(e) {
      console.log("tabbed out")
      document.querySelectorAll('pre').forEach((block) => {
        hljs.highlightBlock(block);
      })
    })
    
    document.addEventListener('DOMContentLoaded', async() => {
      const data = await db.snippits.orderBy('id').last()
    
      console.log(JSON.stringify(data))
      document.getElementById("snipname").value = data.name
      document.getElementById("rendered").textContent = data.data
      document.querySelectorAll('pre').forEach((block) => {
    
        hljs.highlightBlock(block);
      })
    })
    
    function onPaste(e) {
      e.preventDefault(); // stop the paste
      const t = e.clipboardData.getData("text"); // grab the pasted content as plain text
      e.target.textContent = t; // set the element's innerHTML to the plain text
      document.querySelectorAll('pre').forEach((block) => {
        hljs.highlightBlock(block);
      })
    }
    
    const editerElem = document.querySelector('#code-snippet')
    editerElem.addEventListener('keyup', (evt) => {
       const val = evt.target.value;
       const outputPre = document.querySelector('#output')
       const output =  hljs.highlightAuto(val);
       outputPre.innerHTML = output.value;
    })
    /*
    document.addEventListener("keyup", function(e) {
      if (e.keyCode == 13) {
        document.querySelector('pre')
          .forEach((block) => {
          const output = hljs.highlightBlock(block);
          block.focus();
        })
      }
    })
    */
    const paste = document.getElementById('rendered');
    paste.addEventListener('paste', onPaste);
    html {
      background: rgb(58, 58, 63)
    }
    
    #the-code-editor {
      display: flex;
      flex-direction: row;
    }
    #the-code-editor textarea, #the-code-editor pre {
      display: block;
      width: 50%;
    }
    pre {
      outline: none;
      min-height: 100px;
    }
    
    input {
      outline: none;
    }
    
    textarea {
      outline: none;
    }
    
    
    /* Comment */
    
    .hljs-comment,
    .hljs-quote {
      color: #989498;
    }
    
    
    /* Red */
    
    .hljs-variable,
    .hljs-template-variable,
    .hljs-attribute,
    .hljs-tag,
    .hljs-name,
    .hljs-selector-id,
    .hljs-selector-class,
    .hljs-regexp,
    .hljs-link,
    .hljs-deletion {
      color: #dd464c;
    }
    
    
    /* Orange */
    
    .hljs-number,
    .hljs-built_in,
    .hljs-builtin-name,
    .hljs-literal,
    .hljs-type,
    .hljs-params {
      color: #fd8b19;
    }
    
    
    /* Yellow */
    
    .hljs-class .hljs-title {
      color: #fdcc59;
    }
    
    
    /* Green */
    
    .hljs-string,
    .hljs-symbol,
    .hljs-bullet,
    .hljs-addition {
      color: #8fc13e;
    }
    
    
    /* Aqua */
    
    .hljs-meta {
      color: #149b93;
    }
    
    
    /* Blue */
    
    .hljs-function,
    .hljs-section,
    .hljs-title {
      color: #1290bf;
    }
    
    
    /* Purple */
    
    .hljs-keyword,
    .hljs-selector-tag {
      color: #c85e7c;
    }
    
    .hljs {
      display: block;
      overflow-x: auto;
      background: #322931;
      color: #b9b5b8;
      padding: 0.5em;
    }
    
    .hljs-emphasis {
      font-style: italic;
    }
    
    .hljs-strong {
      font-weight: bold;
    }
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.0/css/bulma.min.css">
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@10.1.2/build/styles/default.min.css">
    <script src="https://cdn.jsdelivr.net/npm/dexie@3.0.2/dist/dexie.min.js"></script>
    <script src="https://chr15m.github.io/bugout/bugout.min.js"></script>
    <script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@10.1.2/build/highlight.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/highlightjs-line-numbers.js/2.8.0/highlightjs-line-numbers.min.js"></script>
    
    <section class="section container">
      <input class="input" id="name" placeholder="Search . . ."></input>
      <div id="results" class="container"></div>
      <label>The Next Gen Code Editor :</label>
      <br/ >
      <div id="the-code-editor">
          <textarea rows="5" id="code-snippet"></textarea>
          <pre id="output"></pre>
      </div>
      <br />
      <pre class="containe" id="rendered" contenteditable="true" spellcheck="false"></pre>
      <div class="field is-grouped">
        <input class="input" id="snipname" placeholder="Snippit name . . ."></input>
        <button class="button" id="save">Save</button>
        <button class="button" id="delete">Delete</button>
        <button class="button" id="share">Share</button>
      </div>
      <textarea class="textarea" id="notes" placeholder="Notes . . ."></textarea>
    </section>

    Good Luck...