diff --git a/src/views/partial/channel/chatPanel.ejs b/src/views/partial/channel/chatPanel.ejs index 13878c3..b5c4bef 100644 --- a/src/views/partial/channel/chatPanel.ejs +++ b/src/views/partial/channel/chatPanel.ejs @@ -77,7 +77,7 @@ along with this program. If not, see . %> -

+

diff --git a/www/css/channel.css b/www/css/channel.css index 991a17c..1a28cd7 100644 --- a/www/css/channel.css +++ b/www/css/channel.css @@ -274,11 +274,14 @@ span.user-entry{ #chat-panel-prompt{ font-size: 10pt; margin-left: 0.5em; + user-select: none; } #chat-panel-prompt-autocomplete{ position: absolute; text-wrap: nowrap; + cursor: pointer; + user-select: none; font-size: 10pt; z-index: 10; margin: 0; @@ -288,6 +291,7 @@ span.user-entry{ #chat-panel-prompt-autocomplete-filler{ visibility: hidden; + user-select: none; } .toke{ diff --git a/www/css/theme/movie-night.css b/www/css/theme/movie-night.css index 7a1ea20..5b18ac7 100644 --- a/www/css/theme/movie-night.css +++ b/www/css/theme/movie-night.css @@ -288,7 +288,7 @@ p.channel-guide-entry-item{ } #chat-panel-prompt-autocomplete{ - color: var(--focus0); + color: var(--accent0-alt1); text-shadow: var(--focus-glow0-alt0); } diff --git a/www/js/channel/chat.js b/www/js/channel/chat.js index b1518b2..61c51de 100644 --- a/www/js/channel/chat.js +++ b/www/js/channel/chat.js @@ -54,7 +54,9 @@ class chatBox{ setupInput(){ //Chat bar this.chatPrompt.addEventListener("keydown", this.send.bind(this)); - this.chatPrompt.addEventListener("input", this.checkAutocomplete.bind(this)); + this.chatPrompt.addEventListener("keydown", this.tabComplete.bind(this)); + this.chatPrompt.addEventListener("input", this.displayAutocomplete.bind(this)); + this.autocompleteDisplay.addEventListener("click", this.tabComplete.bind(this)); this.sendButton.addEventListener("click", this.send.bind(this)); this.settingsIcon.addEventListener("click", ()=>{this.client.cPanel.setActivePanel(new panelObj(client))}); this.adminIcon.addEventListener("click", ()=>{this.client.cPanel.setActivePanel(new panelObj(client))}); @@ -135,7 +137,7 @@ class chatBox{ catChat(text){ this.chatPrompt.value += text; - this.checkAutocomplete(); + this.displayAutocomplete(); } send(event){ @@ -145,12 +147,84 @@ class chatBox{ } } - checkAutocomplete(event){ + displayAutocomplete(event){ + //Find current match + const match = this.checkAutocomplete(); + + //Set placeholder to space out the autocomplete display + this.autocompletePlaceholder.innerHTML = this.chatPrompt.value; + //Set the autocomplete display + this.autocompleteDisplay.innerHTML = match.match.replace(match.word, ''); + } + + tabComplete(event){ + //If we hit tab or this isn't a keyboard event + if(event.key == "Tab" || event.key == null){ + //Prevent default action + event.preventDefault(); + + //return focus to the chat prompt + this.chatPrompt.focus(); + + //Grab autocompletion match + const match = this.checkAutocomplete() + + //If we have a match + if(match.match != ''){ + //Autocomplete the current word + this.chatPrompt.value += match.match.replace(match.word, ''); + + //Clear out the autocomplete display + this.autocompleteDisplay.innerHTML = ''; + } + } + } + + checkAutocomplete(input = this.chatPrompt.value){ //Rebuild this fucker every time because it really doesn't take that much compute power and emotes/used tokes change //Worst case we could store it persistantly and update as needed but I think that might be much - const dictionary = this.commandPreprocessor.buildAutocompleteDictionary(); - this.autocompletePlaceholder.innerHTML = this.chatPrompt.value; + const dictionary = this.commandPreprocessor.buildAutocompleteDictionary(); + //Split our input by whitespace + const splitInput = input.split(/\s/g); + //Get the current word we're working on + const word = splitInput[splitInput.length - 1]; + let matches = []; + + + //Run through dictionary sets + for(let set of Object.keys(dictionary)){ + //Go through the current definitions of the current dictionary set + //I went with a for loop instead of a filter beacuse I wanted to pull the processed definition with pre/postfix + //and also directly push it into a shared array :P + for(let cmd of dictionary[set].cmds){ + //Append the proper prefix/postfix to the current command + const definition = (`${dictionary[set].prefix}${cmd}${dictionary[set].postfix}`) + + //if definition starts with the current word + if(word == '' ? false : definition.indexOf(word) == 0){ + //Add definition to match list + matches.push(definition); + } + } + } + + //If we found jack shit + if(matches.length == 0){ + //Return jack shit + return { + match: '', + word + }; + //If we got something + }else{ + //return our top match + return { + match: matches[0], + word + }; + + } } handleClientInfo(data){ diff --git a/www/js/channel/commandPreprocessor.js b/www/js/channel/commandPreprocessor.js index cad3d5e..e5cdd3c 100644 --- a/www/js/channel/commandPreprocessor.js +++ b/www/js/channel/commandPreprocessor.js @@ -143,22 +143,56 @@ class commandPreprocessor{ return foundEmote; } + getEmoteNames(){ + //Create an empty array to hold names + let names = []; + + //For every set of emotes + for(let set of Object.keys(this.emotes)){ + //for every emote in the current set of emotes + for(let emote of this.emotes[set]){ + //push the name of the emote to the name list + names.push(emote.name); + } + } + + //return our list of names + return names; + } + buildAutocompleteDictionary(){ //This isn't complete, just a placeholder for now let dictionary = { - tokes: [ - "toke" - ], - serverCMD: [ - "whisper", - "announce", - "serverannounce", - "clear", - "kick" - ], - localCMD:[ - "high" - ] + tokes: { + prefix: '!', + postfix: '', + cmds: [ + "toke" + ] + }, + serverCMD: { + prefix: '!', + postfix: '', + cmds: [ + "whisper", + "announce", + "serverannounce", + "clear", + "kick" + ] + }, + localCMD:{ + prefix: '/', + postfix: '', + cmds: [ + "high" + ] + }, + emotes:{ + prefix:'[', + postfix:']', + cmds: this.getEmoteNames() + } }; return dictionary;