Introduction to the

Rich text editor, can make the Web page like Word, to achieve the text editing, usually used in some of the text processing more systems. There are many mature rich text editors in the industry, such as TinyMCE, wangEditor, Baidu UEditor and so on. There are many rich text editors, but little thought has been given to how to implement a rich text editor from scratch. This paper mainly describes how to implement a simple rich text editor from scratch.

The basic use

For normal HTML tags, you can usually enter only forms, which enter plain text without formatting. Rich text, in contrast to forms, can add custom content styles to input text, such as bold, font colors, backgrounds… . Rich text is implemented by adding a contenteditable property to AN HTML tag, such as div, which allows for custom editing of the contents of the tag. The simplest rich text editor is as follows:

<! DOCTYPE html> <html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
     
</head>
<body>
    <div id="app" style="width: 200px; height: 200px; background-color: antiquewhite;" contenteditable='true'></div>
</body>
</html>
Copy the code

Basic operation

Rich text is similar to Word in that it has many options for manipulating text, such as bold text, adding background colors, indenting paragraphs, and so on. ExecCommand (aCommandName, aShowDefaultUI, aValueArgument), where aCommandName is the command name, aShowDefaultUI is a Boolean, Whether to display the user interface, generally false. Mozilla doesn’t implement it. AValueArgument, an extra parameter, usually null.

Basic operation commands

The following are some examples of rich text manipulation commands

The command value instructions
backcolor Color string Sets the background color of the document
bold null Bold the selected text
createlink The URL string Converts the selected text into a link to the specified URL
indent null Indented text
copy null Copies the selected text to the clipboard
cut null Cut the selection text to the clipboard
inserthorizontalrule null Inserts an HR element at the insert character

Example:

<! DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Hello World! </title> <meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';" />
    <style>
      html, body{
          width: 100%;
          height: 100%;
          padding: 0;
          margin: 0;
      }
      #app{
          display: flex;
          flex-direction: column;
          justify-content: flex-start;
          width: calc(100% - 100px);
          height: calc(100% - 100px);
          padding: 50px;
      }

      .operator-menu{
          display: flex;
          justify-content: flex-start;
          align-items: center;
          width: 100%;
          min-height: 50px;
          background-color: beige;
          padding: 0 10px;
      }
      .edit-area{
          width: 100%;
          min-height: 600px;
          background-color: blanchedalmond;
          padding: 20px;
      }
      .operator-menu-item{
          padding: 5px 10px;
          background-color: cyan;
          border-radius: 10px;
          cursor: pointer;
          margin: 0 5px;
      }
    </style>
  </head>
  <body>
    <div id="app">
      <div class="operator-menu">
        <div class="operator-menu-item" data-fun='fontBold'<div > <div class="operator-menu-item" data-fun='textIndent'> indent </div> <div class="operator-menu-item" data-fun='inserthorizontalrule'> Inserts the delimiter </div> <div class="operator-menu-item" data-fun='linkUrl'</div> <div class="edit-area" contenteditable="true"></div>
    </div>
    <script>
      let operationItems = document.querySelector('.operator-menu'USES the mousedown) / / event listeners, click event leads to a rich text edit box loses focus operationItems. AddEventListener ('mousedown'.function(e) {
        let target = e.target
        let funName = target.getAttribute('data-fun')
        if(! window[funName])returnWindow [funName]() // To prevent default events, otherwise the selected area of the rich text edit box will disappear e.preventDefault()})function fontBold () {
        document.execCommand('bold')}function textIndent () {
        document.execCommand('indent')}function inserthorizontalrule () {
        document.execCommand('inserthorizontalrule')}function linkUrl () {
        document.execCommand('createlink', null, 'www.baidu.com')
      }
    </script>
  </body>
</html>
Copy the code

Text range and selection

  • Text ranges and selection are a very powerful feature in rich text. With text selection, you can customize the selected text. The core is two objects,SelectionandRangeObject. To put it more officially,SelectionObject, represented byThe selected text range or current position of the cursor.RangeObject representationA document fragment containing a node and a portion of a text node. In a nutshell,SelectionAll of the areas on the page that we mouse over,RangeA one-to-many relationship is a single area of the page that we have selected with the mouse. For example, if we want to get the selection object of the current page, we can callvar selection = window.getSelection(), if you want to get the first text selection informationvar rang = selection.getRangeAt(0)To obtain the selected text information, selectrange.toString().
  • A classic use of text ranges and selections is rich text paste format filtering. When we copy text to a rich text editor, we retain the original text format. What if we want to remove the copied default format and keep only plain text?
  • The first thing that comes to mind when a blogger tackles this problem is whether he can listen for the paste event(paste)When pasting text, replace the clipboard contents. There is also a pit in this one, paste operation shear board is not effective. When implementing functional requirements, the initial adoption isRe – match, remove HTML tags.But the text format is multifarious, often appear a variety of strange characters, more problems, and copy large text, page performance problems, this is not a good way to deal with, until later really understandText range and selection, just found this setting, really fragrant. The processing logic of rich text selection is as follows:
  1. Listen for text paste events
  2. Blocking default events (blocking browser default replication actions)
  3. Get copied plain text
  4. Gets the page text selection
  5. Deletes the selected text selection
  6. Creating a text node
  7. Inserts the text node into the selection
  8. Move the focus to the end of the copied text

Gets pasted plain text

let $editArea = document.querySelector('.edit-area')
$editArea.addEventListener('paste', e => {// block the default replication event e.preventDefault()let txt = ' '
    letTXT = e.lipboardData.getData ('text/plain') // Get the page text selection range = window.getSelection().getrangEat (0) // remove the default selected text range.deletecontents () // create a text node to replace the selected textletPasteTxt = document.createTextNode(TXT) // Insert text node range.insertNode(pasteTxt) // move focus to copy text end range.collapse(false)})Copy the code

In addition, there are many operations that can be implemented with selection, such as cursor positioning, selecting the content of the area to wrap other styles, and so on.

Implement manual cursor position to the last character


function keepLastIndex(element) {
    if (element && element.focus){
        element.focus();
    } else {
        return
    }
    let range = document.createRange();
    range.selectNodeContents(element);
    range.collapse(false);
    let sel = window.getSelection();
    sel.removeAllRanges();
    sel.addRange(range);
}

Copy the code

The selected area wraps other styles

function addCode () {
    letSelection = window.getSelection() // Handles the first selection for nowletRange = selection.getrangEat (0) // Copy the original selectionlet cloneNodes = range.clonecontents () // Remove the selection range.deletecontents () // create the content containerlet codeContainer = document.createElement('code')
    codeContainer.appendChild(cloneNodes) // Add text range.insertNode(codeContainer)} to the selectionCopy the code

The attachment

The following is the test code

<! DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Hello World! </title> <! -- https://electronjs.org/docs/tutorial/security#csp-meta-tag -->
    <meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';" />
    <style>
      html, body{
          width: 100%;
          height: 100%;
          padding: 0;
          margin: 0;
      }
      #app{
          display: flex;
          flex-direction: column;
          justify-content: flex-start;
          width: calc(100% - 100px);
          height: calc(100% - 100px);
          padding: 50px;
      }

      .operator-menu{
          display: flex;
          justify-content: flex-start;
          align-items: center;
          width: 100%;
          min-height: 50px;
          background-color: beige;
          padding: 0 10px;
      }
      .edit-area{
          width: 100%;
          min-height: 600px;
          background-color: blanchedalmond;
          padding: 20px;
      }
      .operator-menu-item{
          padding: 5px 10px;
          background-color: cyan;
          border-radius: 10px;
          cursor: pointer;
          margin: 0 5px;
      }
    </style>
  </head>
  <body>
    <div id="app">
      <div class="operator-menu">
        <div class="operator-menu-item" data-fun='fontBold'<div > <div class="operator-menu-item" data-fun='textIndent'> indent </div> <div class="operator-menu-item" data-fun='inserthorizontalrule'> Inserts the delimiter </div> <div class="operator-menu-item" data-fun='linkUrl'<div > <div class="operator-menu-item" data-fun='addCode'>code</div>
      </div>
      <div class="edit-area" contenteditable="true"></div>
    </div>
    <script>
      let operationItems = document.querySelector('.operator-menu'USES the mousedown) / / event listeners, click event leads to a rich text edit box loses focus operationItems. AddEventListener ('mousedown'.function(e) {
        let target = e.target
        let funName = target.getAttribute('data-fun')
        if(! funName)returnWindow [funName]() // To prevent default events, otherwise the selected area of the rich text edit box will disappear e.preventDefault()})let $editArea = document.querySelector('.edit-area')
      $editArea.addEventListener('paste', e => {// block the default replication event e.preventDefault()let txt = ' '
        letTXT = e.lipboardData.getData ('text/plain') // Get the page text selection range = window.getSelection().getrangEat (0) // remove the default selected text range.deletecontents () // create a text node to replace the selected textletPasteTxt = document.createTextNode(TXT) // Insert text node range.insertNode(pasteTxt) // move focus to copy text end range.collapse(false)
        keepLastIndex($editArea)})function fontBold () {
        document.execCommand('bold')}function textIndent () {
        document.execCommand('indent')}function inserthorizontalrule () {
        document.execCommand('inserthorizontalrule')}function linkUrl () {
        document.execCommand('createlink', null, 'www.baidu.com')}function addCode () {
        letSelection = window.getSelection() // Handles the first selection for nowletRange = selection.getrangEat (0) // Copy the original selectionlet cloneNodes = range.clonecontents () // Remove the selection range.deletecontents () // create the content containerlet codeContainer = document.createElement('code')
        codeContainer.appendChild(cloneNodes) // Add text range.insertNode(codeContainer)} to the selectionfunction keepLastIndex(element) {
        if (element && element.focus){
          element.focus();
        } else {
          return
        }
        let range = document.createRange();
        range.selectNodeContents(element);
        range.collapse(false);
        let sel = window.getSelection();
        sel.removeAllRanges();
        sel.addRange(range);
      }
    </script>
  </body>
</html>
Copy the code

The resources

  • Document.execCommand
  • Selection
  • Range