2019-03-12 04:15:42 +01:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2019-03-14 20:21:53 +01:00
|
|
|
"encoding/json"
|
2019-03-12 04:15:42 +01:00
|
|
|
"fmt"
|
2019-03-16 15:13:30 +01:00
|
|
|
"strings"
|
2019-03-12 04:15:42 +01:00
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/dennwc/dom/js"
|
2019-03-13 06:09:24 +01:00
|
|
|
"github.com/zorchenhimer/MovieNight/common"
|
2019-03-12 04:15:42 +01:00
|
|
|
)
|
|
|
|
|
2019-03-16 15:13:30 +01:00
|
|
|
const (
|
2019-03-16 19:27:13 +01:00
|
|
|
keyTab = 9
|
|
|
|
keyEnter = 13
|
|
|
|
keyUp = 38
|
|
|
|
keyDown = 40
|
2019-03-16 15:13:30 +01:00
|
|
|
)
|
|
|
|
|
2019-03-15 22:28:29 +01:00
|
|
|
var (
|
2019-03-16 15:13:30 +01:00
|
|
|
currentName string
|
|
|
|
names []string
|
|
|
|
filteredNames []string
|
2019-03-15 22:28:29 +01:00
|
|
|
)
|
2019-03-15 07:19:16 +01:00
|
|
|
|
2019-03-16 15:13:30 +01:00
|
|
|
// The returned value is a bool deciding to prevent the event from propagating
|
|
|
|
func processMessageKey(this js.Value, v []js.Value) interface{} {
|
2019-03-16 16:56:43 +01:00
|
|
|
if len(filteredNames) == 0 || currentName == "" {
|
|
|
|
return false
|
|
|
|
}
|
2019-03-16 15:13:30 +01:00
|
|
|
|
2019-03-16 16:56:43 +01:00
|
|
|
startIdx := v[0].Get("target").Get("selectionStart").Int()
|
|
|
|
keyCode := v[0].Get("keyCode").Int()
|
2019-03-16 15:13:30 +01:00
|
|
|
switch keyCode {
|
|
|
|
case keyUp, keyDown:
|
2019-03-16 16:56:43 +01:00
|
|
|
newidx := 0
|
|
|
|
for i, n := range filteredNames {
|
|
|
|
if n == currentName {
|
|
|
|
newidx = i
|
|
|
|
if keyCode == keyDown {
|
|
|
|
newidx = i + 1
|
|
|
|
if newidx == len(filteredNames) {
|
|
|
|
newidx--
|
|
|
|
}
|
|
|
|
} else if keyCode == keyUp {
|
|
|
|
newidx = i - 1
|
|
|
|
if newidx < 0 {
|
|
|
|
newidx = 0
|
2019-03-16 15:13:30 +01:00
|
|
|
}
|
|
|
|
}
|
2019-03-16 16:56:43 +01:00
|
|
|
break
|
2019-03-16 15:13:30 +01:00
|
|
|
}
|
|
|
|
}
|
2019-03-16 16:56:43 +01:00
|
|
|
currentName = filteredNames[newidx]
|
2019-03-16 19:27:13 +01:00
|
|
|
case keyTab, keyEnter:
|
2019-03-16 16:56:43 +01:00
|
|
|
msg := js.Get("msg")
|
|
|
|
val := msg.Get("value").String()
|
2019-03-19 20:15:11 +01:00
|
|
|
newval := val[:startIdx]
|
|
|
|
|
|
|
|
if i := strings.LastIndex(newval, "@"); i != -1 {
|
|
|
|
newval = newval[:i+1] + currentName
|
|
|
|
}
|
|
|
|
|
|
|
|
endVal := val[startIdx:]
|
2019-03-16 16:56:43 +01:00
|
|
|
if len(val) == startIdx || val[startIdx:][0] != ' ' {
|
|
|
|
// insert a space into val so selection indexing can be one line
|
2019-03-19 20:15:11 +01:00
|
|
|
endVal = " " + endVal
|
2019-03-16 15:13:30 +01:00
|
|
|
}
|
2019-03-19 20:15:11 +01:00
|
|
|
msg.Set("value", newval+endVal)
|
2019-03-16 16:56:43 +01:00
|
|
|
msg.Set("selectionStart", len(newval)+1)
|
|
|
|
msg.Set("selectionEnd", len(newval)+1)
|
|
|
|
|
|
|
|
// Clear out filtered names since it is no longer needed
|
|
|
|
filteredNames = nil
|
2019-03-16 15:13:30 +01:00
|
|
|
default:
|
|
|
|
// We only want to handle the caught keys, so return early
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
updateSuggestionDiv()
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
func processMessage(v []js.Value) {
|
2019-03-16 16:56:43 +01:00
|
|
|
msg := js.Get("msg")
|
|
|
|
text := strings.ToLower(msg.Get("value").String())
|
|
|
|
startIdx := msg.Get("selectionStart").Int()
|
2019-03-16 15:13:30 +01:00
|
|
|
|
|
|
|
filteredNames = nil
|
2019-03-16 16:56:43 +01:00
|
|
|
if len(text) != 0 {
|
|
|
|
if len(names) > 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++
|
|
|
|
}
|
2019-03-16 15:13:30 +01:00
|
|
|
|
2019-03-16 16:56:43 +01:00
|
|
|
// 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 && word[0] == '@' &&
|
|
|
|
caretIdx <= startIdx && startIdx <= caretIdx+len(word) {
|
|
|
|
// fill filtered first so the "modifier" keys can modify it
|
|
|
|
for _, n := range names {
|
|
|
|
if len(word) == 1 || strings.HasPrefix(strings.ToLower(n), word[1:]) {
|
|
|
|
filteredNames = append(filteredNames, n)
|
|
|
|
}
|
2019-03-16 15:13:30 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-16 16:56:43 +01:00
|
|
|
if len(filteredNames) > 0 {
|
2019-03-19 20:15:11 +01:00
|
|
|
currentName = ""
|
2019-03-16 16:56:43 +01:00
|
|
|
break
|
|
|
|
}
|
2019-03-16 15:13:30 +01:00
|
|
|
|
2019-03-16 16:56:43 +01:00
|
|
|
caretIdx += len(word)
|
|
|
|
}
|
2019-03-16 15:13:30 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
updateSuggestionDiv()
|
|
|
|
}
|
|
|
|
|
|
|
|
func updateSuggestionDiv() {
|
|
|
|
const selectedClass = ` class="selectedName"`
|
|
|
|
|
|
|
|
var divs []string
|
|
|
|
if len(filteredNames) > 0 {
|
|
|
|
// set current name to first if not set already
|
|
|
|
if currentName == "" {
|
|
|
|
currentName = filteredNames[0]
|
|
|
|
}
|
|
|
|
|
|
|
|
var hasCurrentName bool
|
|
|
|
divs = make([]string, len(filteredNames))
|
|
|
|
|
|
|
|
// Create inner body of html
|
|
|
|
for i := range filteredNames {
|
|
|
|
divs[i] = "<div"
|
|
|
|
if filteredNames[i] == currentName {
|
|
|
|
hasCurrentName = true
|
|
|
|
divs[i] += selectedClass
|
|
|
|
}
|
|
|
|
divs[i] += ">" + filteredNames[i] + "</div>"
|
|
|
|
}
|
|
|
|
|
|
|
|
if !hasCurrentName {
|
|
|
|
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
|
|
|
|
js.Get("suggestions").Set("innerHTML", strings.Join(divs, "\n"))
|
|
|
|
}
|
|
|
|
|
2019-03-12 04:15:42 +01:00
|
|
|
func recieve(v []js.Value) {
|
2019-03-13 06:09:24 +01:00
|
|
|
if len(v) == 0 {
|
2019-03-14 20:21:53 +01:00
|
|
|
fmt.Println("No data received")
|
2019-03-13 06:09:24 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-03-21 13:47:40 +01:00
|
|
|
chatJSON, err := common.DecodeData(v[0].String())
|
2019-03-13 06:09:24 +01:00
|
|
|
if err != nil {
|
|
|
|
fmt.Printf("Error decoding data: %s\n", err)
|
|
|
|
js.Call("appendMessages", v)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-03-21 13:47:40 +01:00
|
|
|
chat, err := chatJSON.ToData()
|
2019-03-14 20:21:53 +01:00
|
|
|
if err != nil {
|
2019-03-21 13:47:40 +01:00
|
|
|
fmt.Printf("Error converting ChatDataJSON to ChatData of type %d: %v", chatJSON.Type, err)
|
2019-03-14 20:21:53 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
switch chat.Type {
|
2019-03-15 07:19:16 +01:00
|
|
|
case common.DTHidden:
|
2019-03-21 13:47:40 +01:00
|
|
|
h := chat.Data.(common.HiddenMessage)
|
2019-03-15 07:19:16 +01:00
|
|
|
switch h.Type {
|
|
|
|
case common.CdUsers:
|
2019-03-16 15:13:30 +01:00
|
|
|
names = nil
|
2019-03-15 07:19:16 +01:00
|
|
|
for _, i := range h.Data.([]interface{}) {
|
|
|
|
names = append(names, i.(string))
|
|
|
|
}
|
|
|
|
}
|
2019-03-14 20:21:53 +01:00
|
|
|
case common.DTEvent:
|
2019-03-21 13:47:40 +01:00
|
|
|
d := chat.Data.(common.DataEvent)
|
2019-03-19 22:03:34 +01:00
|
|
|
// A server message is the only event that doesn't deal with names.
|
|
|
|
if d.Event != common.EvServerMessage {
|
2019-03-15 07:19:16 +01:00
|
|
|
websocketSend("", common.CdUsers)
|
|
|
|
}
|
2019-03-14 20:21:53 +01:00
|
|
|
// on join or leave, update list of possible user names
|
|
|
|
fallthrough
|
2019-03-15 22:28:29 +01:00
|
|
|
case common.DTChat:
|
2019-03-21 13:47:40 +01:00
|
|
|
js.Call("appendMessages", chat.Data.HTML())
|
2019-03-14 02:24:14 +01:00
|
|
|
case common.DTCommand:
|
2019-03-21 13:47:40 +01:00
|
|
|
d := chat.Data.(common.DataCommand)
|
2019-03-13 18:42:38 +01:00
|
|
|
|
2019-03-15 07:19:16 +01:00
|
|
|
switch d.Command {
|
2019-03-14 02:24:14 +01:00
|
|
|
case common.CmdPlaying:
|
2019-03-15 07:19:16 +01:00
|
|
|
if d.Arguments == nil || len(d.Arguments) == 0 {
|
2019-03-13 18:42:38 +01:00
|
|
|
js.Call("setPlaying", "", "")
|
|
|
|
|
2019-03-15 07:19:16 +01:00
|
|
|
} else if len(d.Arguments) == 1 {
|
|
|
|
js.Call("setPlaying", d.Arguments[0], "")
|
2019-03-13 18:42:38 +01:00
|
|
|
|
2019-03-15 07:19:16 +01:00
|
|
|
} else if len(d.Arguments) == 2 {
|
|
|
|
js.Call("setPlaying", d.Arguments[0], d.Arguments[1])
|
2019-03-13 18:42:38 +01:00
|
|
|
}
|
2019-03-14 02:24:14 +01:00
|
|
|
case common.CmdRefreshPlayer:
|
2019-03-13 18:42:38 +01:00
|
|
|
js.Call("initPlayer", nil)
|
2019-03-14 02:24:14 +01:00
|
|
|
case common.CmdPurgeChat:
|
2019-03-18 03:29:31 +01:00
|
|
|
js.Call("purgeChat", nil)
|
2019-03-21 13:47:40 +01:00
|
|
|
js.Call("appendMessages", d.HTML())
|
2019-03-14 02:24:14 +01:00
|
|
|
case common.CmdHelp:
|
2019-03-16 21:15:45 +01:00
|
|
|
url := "/help"
|
|
|
|
if d.Arguments != nil && len(d.Arguments) > 0 {
|
|
|
|
url = d.Arguments[0]
|
|
|
|
}
|
2019-03-21 13:47:40 +01:00
|
|
|
js.Call("appendMessages", d.HTML())
|
2019-03-16 21:40:39 +01:00
|
|
|
js.Get("window").Call("open", url, "_blank", "menubar=0,status=0,toolbar=0,width=300,height=600")
|
2019-03-13 18:42:38 +01:00
|
|
|
}
|
2019-03-13 06:09:24 +01:00
|
|
|
}
|
2019-03-12 04:15:42 +01:00
|
|
|
}
|
|
|
|
|
2019-03-14 20:21:53 +01:00
|
|
|
func websocketSend(msg string, dataType common.ClientDataType) error {
|
2019-03-23 20:16:02 +01:00
|
|
|
if strings.TrimSpace(msg) == "" {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-03-14 20:21:53 +01:00
|
|
|
data, err := json.Marshal(common.ClientData{
|
|
|
|
Type: dataType,
|
|
|
|
Message: msg,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("could not marshal data: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
js.Call("websocketSend", string(data))
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func send(this js.Value, v []js.Value) interface{} {
|
2019-03-12 04:15:42 +01:00
|
|
|
if len(v) != 1 {
|
2019-03-14 20:21:53 +01:00
|
|
|
showSendError(fmt.Errorf("expected 1 parameter, got %d", len(v)))
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
err := websocketSend(v[0].String(), common.CdMessage)
|
|
|
|
if err != nil {
|
|
|
|
showSendError(err)
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
func showSendError(err error) {
|
|
|
|
if err != nil {
|
|
|
|
fmt.Printf("Could not send: %v\n", err)
|
|
|
|
js.Call("appendMessages", `<div><span style="color: red;">Could not send message</span></div>`)
|
2019-03-12 04:15:42 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-20 03:56:01 +01:00
|
|
|
func isValidColor(this js.Value, v []js.Value) interface{} {
|
|
|
|
if len(v) != 1 {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return common.IsValidColor(v[0].String())
|
|
|
|
}
|
|
|
|
|
|
|
|
func isValidName(this js.Value, v []js.Value) interface{} {
|
|
|
|
if len(v) != 1 {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return common.IsValidName(v[0].String())
|
|
|
|
}
|
|
|
|
|
2019-03-16 19:59:30 +01:00
|
|
|
func debugValues(v []js.Value) {
|
|
|
|
fmt.Printf("currentName %#v\n", currentName)
|
|
|
|
fmt.Printf("names %#v\n", names)
|
|
|
|
fmt.Printf("filteredNames %#v\n", filteredNames)
|
|
|
|
}
|
|
|
|
|
2019-03-12 04:15:42 +01:00
|
|
|
func main() {
|
2019-03-16 15:13:30 +01:00
|
|
|
js.Set("processMessageKey", js.FuncOf(processMessageKey))
|
2019-03-14 20:21:53 +01:00
|
|
|
js.Set("sendMessage", js.FuncOf(send))
|
2019-03-20 03:56:01 +01:00
|
|
|
js.Set("isValidColor", js.FuncOf(isValidColor))
|
|
|
|
js.Set("isValidName", js.FuncOf(isValidName))
|
2019-03-15 22:28:29 +01:00
|
|
|
|
2019-03-16 20:47:48 +01:00
|
|
|
js.Set("recieveMessage", js.CallbackOf(recieve))
|
|
|
|
js.Set("processMessage", js.CallbackOf(processMessage))
|
|
|
|
js.Set("debugValues", js.CallbackOf(debugValues))
|
2019-03-12 04:15:42 +01:00
|
|
|
|
|
|
|
// This is needed so the goroutine does not end
|
|
|
|
for {
|
2019-03-16 20:47:48 +01:00
|
|
|
// heatbeat to keep connection alive to deal with nginx
|
|
|
|
if js.Get("inChat").Bool() {
|
|
|
|
websocketSend("", common.CdPing)
|
|
|
|
}
|
|
|
|
time.Sleep(time.Second * 10)
|
2019-03-12 04:15:42 +01:00
|
|
|
}
|
|
|
|
}
|