MovieNight/wasm/suggestions.go
Zorchenhimer 94a4f08467 Make suggestions popup insert wrapped emotes
The suggestions popup will now insert wrapped emotes instead of bare
word emotes.  This is so the autocompletion will still work if the
WrappedEmotesOnly setting is set to true.
2020-04-19 12:50:26 -04:00

196 lines
4.4 KiB
Go

// +build js,wasm
package main
import (
"strings"
"syscall/js"
"github.com/zorchenhimer/MovieNight/common"
)
const (
keyTab = 9
keyEnter = 13
keyEsc = 27
keySpace = 32
keyUp = 38
keyDown = 40
suggestionName = '@'
suggestionEmote = ':'
)
var (
currentSugType rune
currentSug string
filteredSug []string
names []string
emoteNames []string
emotes map[string]string
)
// The returned value is a bool deciding to prevent the event from propagating
func processMessageKey(this js.Value, v []js.Value) interface{} {
startIdx := v[0].Get("target").Get("selectionStart").Int()
keyCode := v[0].Get("keyCode").Int()
ctrl := v[0].Get("ctrlKey").Bool()
if ctrl && keyCode == keySpace {
processMessage(nil)
return true
}
if len(filteredSug) == 0 || currentSug == "" {
return false
}
switch keyCode {
case keyEsc:
filteredSug = nil
currentSug = ""
currentSugType = 0
case keyUp, keyDown:
newidx := 0
for i, n := range filteredSug {
if n == currentSug {
newidx = i
if keyCode == keyDown {
newidx = i + 1
if newidx == len(filteredSug) {
newidx--
}
} else if keyCode == keyUp {
newidx = i - 1
if newidx < 0 {
newidx = 0
}
}
break
}
}
currentSug = filteredSug[newidx]
case keyTab, keyEnter:
msg := global.Get("msg")
val := msg.Get("value").String()
newval := val[:startIdx]
if i := strings.LastIndex(newval, string(currentSugType)); i != -1 {
var offset int
if currentSugType == suggestionName {
offset = 1
}
newval = newval[:i+offset] + ":" + currentSug + ":"
}
endVal := val[startIdx:]
if len(val) == startIdx || val[startIdx:][0] != ' ' {
// insert a space into val so selection indexing can be one line
endVal = " " + endVal
}
msg.Set("value", newval+endVal)
msg.Set("selectionStart", len(newval)+1)
msg.Set("selectionEnd", len(newval)+1)
// Clear out filtered names since it is no longer needed
filteredSug = nil
default:
// We only want to handle the caught keys, so return early
return false
}
updateSuggestionDiv()
return true
}
func processMessage(v []js.Value) {
msg := global.Get("msg")
text := strings.ToLower(msg.Get("value").String())
startIdx := msg.Get("selectionStart").Int()
filteredSug = nil
if len(text) != 0 {
if len(names) > 0 || len(emoteNames) > 0 {
var caretIdx int
textParts := strings.Split(text, " ")
for i, word := range textParts {
// Increase caret index at beginning if not first word to account for spaces
if i != 0 {
caretIdx++
}
// It is possible to have a double space " ", which will lead to an
// empty string element in the slice. Also check that the index of the
// cursor is between the start of the word and the end
if len(word) > 0 && caretIdx <= startIdx && startIdx <= caretIdx+len(word) {
var suggestions []string
if word[0] == suggestionName {
currentSugType = suggestionName
suggestions = names
} else if word[0] == suggestionEmote {
suggestions = emoteNames
currentSugType = suggestionEmote
}
for _, s := range suggestions {
if len(word) == 1 || strings.Contains(strings.ToLower(s), word[1:]) {
filteredSug = append(filteredSug, s)
}
}
}
if len(filteredSug) > 0 {
currentSug = ""
break
}
caretIdx += len(word)
}
}
}
updateSuggestionDiv()
}
func updateSuggestionDiv() {
const selectedClass = ` class="selectedName"`
var divs []string
if len(filteredSug) > 0 {
// set current name to first if not set already
if currentSug == "" {
currentSug = filteredSug[len(filteredSug)-1]
}
var hascurrentSuggestion bool
divs = make([]string, len(filteredSug))
// Create inner body of html
for i := range filteredSug {
divs[i] = "<div"
sug := filteredSug[i]
if sug == currentSug {
hascurrentSuggestion = true
divs[i] += selectedClass
}
divs[i] += ">"
if currentSugType == suggestionEmote {
divs[i] += common.EmoteToHtml(emotes[sug], sug)
}
divs[i] += sug + "</div>"
}
if !hascurrentSuggestion {
divs[0] = divs[0][:4] + selectedClass + divs[0][4:]
}
}
// The \n is so it's easier to read th source in web browsers for the dev
global.Get("suggestions").Set("innerHTML", strings.Join(divs, "\n"))
global.Call("updateSuggestionScroll")
}