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

Mysql 5.7.17 winx64 installation tutorial on win7

Software version and platform: MySQL-5.7.17-winx6...

Detailed explanation of how Nginx works

How Nginx works Nginx consists of a core and modu...

How MLSQL Stack makes stream debugging easier

Preface A classmate is investigating MLSQL Stack&...

Solution for converting to inline styles in CSS (css-inline)

Talk about the scene Send Email Embedding HTML in...

Detailed explanation of the marquee attribute in HTML

This tag is not part of HTML3.2 and is only suppo...

Some front-end basics (html, css) encountered in practice

1. The div css mouse hand shape is cursor:pointer;...

Example code of setting label style using CSS selector

CSS Selectors Setting style on the html tag can s...

HTML checkbox Click the description text to select/uncheck the state

In web development, since the checkbox is small an...

Solution to the problem that Docker container cannot be stopped or killed

Docker version 1.13.1 Problem Process A MySQL con...

Introduction to Nginx regular expression related parameters and rules

Preface Recently, I have been helping clients con...

Example of automatic import method of vue3.0 common components

1. Prerequisites We use the require.context metho...

Detailed tutorial of using stimulsoft.reports.js with vue-cli

vue-cli uses stimulsoft.reports.js (nanny-level t...

Vue implements paging function

This article example shares the specific code of ...

How to Develop a Progressive Web App (PWA)

Table of contents Overview Require URL of the app...

How to build a virtual machine with vagrant+virtualBox

1. Introduction Vagrant is a tool for building an...