How to implement the @person function through Vue

How to implement the @person function through Vue

This article uses vue, and adds mouse click events and some small page optimizations

Basic structure

Create a sandBox.vue file to write the basic structure of the function

 <div class="content">
    <!--Text box-->
    <div
      class="editor"
      ref="divRef"
      contenteditable
      @keyup="handkeKeyUp"
      @keydown="handleKeyDown"
    ></div>
    <!--Options-->
    <AtDialog
      v-if="showDialog"
      :visible="showDialog"
      :position="position"
      :queryString="queryString"
      @onPickUser="handlePickUser"
      @onHide="handleHide"
      @onShow="handleShow"
    ></AtDialog>
  </div>
<script>
import AtDialog from '../components/AtDialog'
export default {
  name: 'sandBox',
  components: { AtDialog },
  data () {
    return {
      node: '', // Get the node user: '', // The content of the selected item endIndex: '', // The last cursor position queryString: '', // Search value showDialog: false, // Whether to display the pop-up window position: {
        x: 0,
        y: 0
      }//Popup window display position}
  },
  methods: {
    // Get the cursor position getCursorIndex () {
      const selection = window.getSelection()
      return selection.focusOffset //Select the offset of focusNode at the beginning},
    // Get the node getRangeNode () {
      const selection = window.getSelection()
      return selection.focusNode // selected end node},
    // The location where the pop-up window appears getRangeRect () {
      const selection = window.getSelection()
      const range = selection.getRangeAt(0) // is a generic object for managing selection ranges const rect = range.getClientRects()[0] // Select some text and get the range of the selected text const LINE_HEIGHT = 30
      return {
        x: rect.x,
        y: rect.y + LINE_HEIGHT
      }
    },
    // Whether to display @
    showAt() {
      const node = this.getRangeNode()
      if (!node || node.nodeType !== Node.TEXT_NODE) ​​return false
      const content = node.textContent || ''
      const regx = /@([^@\s]*)$/
      const match = regx.exec(content.slice(0, this.getCursorIndex()))
      return match && match.length === 2
    },
    // Get @user getAtUser () {
      const content = this.getRangeNode().textContent || ''
      const regx = /@([^@\s]*)$/
      const match = regx.exec(content.slice(0, this.getCursorIndex()))
      if (match && match.length === 2) {
        return match[1]
      }
      return undefined
    },
    // Create a label createAtButton (user) {
      const btn = document.createElement('span')
      btn.style.display = 'inline-block'
      btn.dataset.user = JSON.stringify(user)
      btn.className = 'at-button'
      btn.contentEditable = 'false'
      btn.textContent = `@${user.name}`
      const wrapper = document.createElement('span')
      wrapper.style.display = 'inline-block'
      wrapper.contentEditable = 'false'
      const spaceElem = document.createElement('span')
      spaceElem.style.whiteSpace = 'pre'
      spaceElem.textContent = '\u200b'
      spaceElem.contentEditable = 'false'
      const clonedSpaceElem = spaceElem.cloneNode(true)
      wrapper.appendChild(spaceElem)
      wrapper.appendChild(btn)
      wrapper.appendChild(clonedSpaceElem)
      return wrapper
    },
    replaceString (raw, replacer) {
      return raw.replace(/@([^@\s]*)$/, replacer)
    },
    // Insert @ tag replaceAtUser (user) {
      const node = this.node
      if (node ​​&& user) {
        const content = node.textContent || ''
        const endIndex = this.endIndex
        const preSlice = this.replaceString(content.slice(0, endIndex), '')
        const restSlice = content.slice(endIndex)
        const parentNode = node.parentNode
        const nextNode = node.nextSibling
        const previousTextNode = new Text(preSlice)
        const nextTextNode = new Text('\u200b' + restSlice) // Add 0 wide characters const atButton = this.createAtButton(user)
        parentNode.removeChild(node)
        // Insert in the text box if (nextNode) {
          parentNode.insertBefore(previousTextNode, nextNode)
          parentNode.insertBefore(atButton, nextNode)
          parentNode.insertBefore(nextTextNode, nextNode)
        } else {
          parentNode.appendChild(previousTextNode)
          parentNode.appendChild(atButton)
          parentNode.appendChild(nextTextNode)
        }
        // Reset the cursor position const range = new Range()
        const selection = window.getSelection()
        range.setStart(nextTextNode, 0)
        range.setEnd(nextTextNode, 0)
        selection.removeAllRanges()
        selection.addRange(range)
      }
    },
    //Keyboard up eventhandkeKeyUp () {
      if (this.showAt()) {
        const node = this.getRangeNode()
        const endIndex = this.getCursorIndex()
        this.node = node
        this.endIndex = endIndex
        this.position = this.getRangeRect()
        this.queryString = this.getAtUser() || ''
        this.showDialog = true
      } else {
        this.showDialog = false
      }
    },
    //Keyboard press event handleKeyDown (e) {
      if (this.showDialog) {
        if (e.code === 'ArrowUp' ||
          e.code === 'ArrowDown' ||
          e.code === 'Enter') {
          e.preventDefault()
        }
      }
    },
    // Hide the selection box after inserting the tag handlePickUser (user) {
      this.replaceAtUser(user)
      this.user = user
      this.showDialog = false
    },
    //Hide the selection box handleHide () {
      this.showDialog = false
    },
    // Display the selection box handleShow () {
      this.showDialog = true
    }
  }
}
</script>
 
<style scoped lang="scss">
  .content {
    font-family: sans-serif;
    h1{
      text-align: center;
    }
  }
  .editor {
    margin: 0 auto;
    width: 600px;
    height: 150px;
    background: #fff;
    border: 1px solid blue;
    border-radius: 5px;
    text-align: left;
    padding: 10px;
    overflow:auto;
    line-height: 30px;
    &:focus {
      outline: none;
    }
  }
</style>

If a click event is added, the node and cursor position must be obtained in the [Keyboard Up Event] and saved to the data

 //Keyboard up eventhandkeKeyUp () {
      if (this.showAt()) {
        const node = this.getRangeNode() // Get the node const endIndex = this.getCursorIndex() // Get the cursor position this.node = node 
        this.endIndex = endIndex 
        this.position = this.getRangeRect()
        this.queryString = this.getAtUser() || ''
        this.showDialog = true
      } else {
        this.showDialog = false
      }
    },

Create a new component and edit the pop-up options 

<template>
<div
  class="wrapper"
  :style="{position:'fixed',top:position.y +'px',left:position.x+'px'}">
  <div v-if="!mockList.length" class="empty">No search results</div>
  <div
    v-for="(item,i) in mockList"
    :key="item.id"
    class="item"
    :class="{'active': i === index}"
    ref="usersRef"
    @click="clickAt($event,item)"
    @mouseenter="hoverAt(i)"
  >
    <div class="name">{{item.name}}</div>
  </div>
</div>
</template>
 
<script>
const mockData = [
  { name: 'HTML', id: 'HTML' },
  { name: 'CSS', id: 'CSS' },
  { name: 'Java', id: 'Java' },
  { name: 'JavaScript', id: 'JavaScript' }
]
export default {
  name: 'AtDialog',
  props: {
    visible: Boolean,
    position: Object,
    queryString: String
  },
  data () {
    return {
      users: [],
      index: -1,
      mockList: mockData
    }
  },
  watch:
    queryString (val) {
      val ? this.mockList = mockData.filter(({ name }) => name.startsWith(val)) : this.mockList = mockData.slice(0)
    }
  },
  mounted () {
    document.addEventListener('keyup', this.keyDownHandler)
  },
  destroyed () {
    document.removeEventListener('keyup', this.keyDownHandler)
  },
  methods: {
    keyDownHandler (e) {
      if (e.code === 'Escape') {
        this.$emit('onHide')
        return
      }
      //Keyboard pressed => ↓
      if (e.code === 'ArrowDown') {
        if (this.index >= this.mockList.length - 1) {
          this.index = 0
        } else {
          this.index = this.index + 1
        }
      }
      //Keyboard pressed => ↑
      if (e.code === 'ArrowUp') {
        if (this.index <= 0) {
          this.index = this.mockList.length - 1
        } else {
          this.index = this.index - 1
        }
      }
      //Keyboard pressed => Enterif (e.code === 'Enter') {
        if (this.mockList.length) {
          const user = {
            name: this.mockList[this.index].name,
            id: this.mockList[this.index].id
          }
          this.$emit('onPickUser', user)
          this.index = -1
        }
      }
    },
    clickAt (e, item) {
      const user = {
        name: item.name,
        id: item.id
      }
      this.$emit('onPickUser', user)
      this.index = -1
    },
    hoverAt (index) {
      this.index = index
    }
  }
}
</script>
 
<style scoped lang="scss">
  .wrapper {
    width: 238px;
    border: 1px solid #e4e7ed;
    border-radius: 4px;
    background-color: #fff;
    box-shadow: 0 2px 12px 0 rgb(0 0 0 / 10%);
    box-sizing: border-box;
    padding: 6px 0;
  }
  .empty{
    font-size: 14px;
    padding: 0 20px;
    color: #999;
  }
  .item {
    font-size: 14px;
    padding: 0 20px;
    line-height: 34px;
    cursor: pointer;
    color: #606266;
    &.active {
      background: #f5f7fa;
      color: blue;
      .id {
        color: blue;
      }
    }
    &:first-child {
      border-radius: 5px 5px 0 0;
    }
    &:last-child {
      border-radius: 0 0 5px 5px;
    }
    .id {
      font-size: 12px;
      color: rgb(83, 81, 81);
    }
  }
</style>

The above is the details of how to implement the @人 function through Vue. For more information about Vue @人 function, please pay attention to other related articles on 123WORDPRESS.COM!

You may also be interested in:
  • Use vue3 to implement a human-cat communication applet
  • Realization of Vue+AI intelligent robot reply function
  • Vue.js implements h5 robot chat (beta version)
  • Vue+tracking.js implements front-end face detection function
  • Multi-person online chat room based on vue and websocket
  • A single-page application function imitating mobile QQ based on Vue2 (access to chatbot)

<<:  Bootstrap 3.0 study notes CSS related supplement

>>:  Linux system file sharing samba configuration tutorial

Recommend

Supplementary article on front-end performance optimization

Preface I looked at the previously published arti...

JavaScript Regular Expressions Explained

Table of contents 1. Regular expression creation ...

Analysis of the principles of Mysql dirty page flush and shrinking table space

mysql dirty pages Due to the WAL mechanism, when ...

MySql quick insert tens of millions of large data examples

In the field of data analysis, database is our go...

How to prohibit vsftpd users from logging in through ssh

Preface vsftp is an easy-to-use and secure ftp se...

How to use Linux tr command

01. Command Overview The tr command can replace, ...

MySQL operations: JSON data type operations

In the previous article, we introduced the detail...