mentions

by tnhu

HTML

<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
<script src="https://unpkg.com/[email protected]/index.js"></script>
<input value="$hello}       $$hello     $hello${world    $hello_world">
<textarea>
$hello}       $$hello     $hello${world    $hello_world

$hello.world

${hello world}

${helloworld }

${helloworld}

${hello

$$$
$$$hello

$$${hello

${

$

$hello

abc

(\$\{?)?[_a-zA-Z][_a-zA-Z0-9.]*\}?

missing $:

\$?[{_a-zA-Z][_a-zA-Z0-9.]*\}?

with $

(\$?[{_a-zA-Z][_a-zA-Z0-9.]*\}?)|(\$)
$hello}       $$hello     $hello${world    $hello_world

$hello.world

${hello world}

${helloworld }
</textarea>
<span id="caret"/>

CSS

html,
body {
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
}

body {
  padding: 10px;
  overflow: hidden;
}

#caret {
  display: inline-block;
  position: absolute;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: rgba(255, 0, 0, .4);
}

input,
textarea {
  width: calc(100% - 40px);
  display: block;
  outline: none;
  margin: 0;
  padding: 4px 8px;
  background: transparent;
}

textarea {
  margin-top: 20px;
  height: 200px;
}

JavaScript

const trigger = ['$', '${'].sort((a, b) => a.length > b.length ? -1 : 1) // Longer trigger goes first
const replacementRule = match => '${' + match + '}'
const triggerSet = new Set(trigger.join(''))
const MatchingRulesLeftRight = replacementRule('sample.text').split('sample.text')

const IdentifiersSet = /[A-Za-z0-9_.]/

var $caret = document.getElementById('caret')


function getTokens(value, atIndex) {
  const valueLen = value.length
  const triggerLen = trigger.length
  let tokenIndex = atIndex
  let tokenLen = 0
  let token, word, wordLen, match = ''

  while (tokenIndex--) {
    const char = value[tokenIndex]

    // If char is out of indentifier and trigger ranges, exit loop
    if (!triggerSet.has(char) && !IdentifiersSet.test(char)) {
      break
    }

    const copiedStr = value.substr(tokenIndex, ++tokenLen)

    for (let i = 0; i < triggerLen; i++) {
      if (copiedStr.indexOf(trigger[i]) === 0) {
        token = copiedStr
        break
      }
    }

    if (token) {
      wordLen = tokenLen
      let index = tokenIndex + wordLen

      while (index < valueLen) {
        if (!IdentifiersSet.test(value[index])) {
          if (MatchingRulesLeftRight.length === 2 && token.indexOf(MatchingRulesLeftRight[0]) === 0 && value.indexOf(MatchingRulesLeftRight[1], index) === index) {
            wordLen += MatchingRulesLeftRight[1].length
          }
          break
        }
        wordLen++
        index++
      }

      word = value.substr(tokenIndex, wordLen)

      for (let i = 0; i < triggerLen; i++) {
        if (word.indexOf(trigger[i]) === 0) {
          match = token.substr(trigger[i].length)
          break
        }
      }

      break
    }
  }

  return {
    match,
    token,
    tokenIndex,
    tokenLen,
    word,
    wordLen
  }
}

function showSuggestions() {
  console.log('Show suggestions...')
}

function hideSuggestions() {
  console.log('Hide suggestions...')
}

function handler(e) {
  const {
    target: input
  } = e
  const nodeName...