diff --git a/resources/[system]/chat/README.md b/resources/[system]/chat/README.md new file mode 100644 index 0000000..600cbb1 --- /dev/null +++ b/resources/[system]/chat/README.md @@ -0,0 +1,3 @@ +# betterchat + +> Here will be some kind of documentation diff --git a/resources/[system]/chat/__resource.lua b/resources/[system]/chat/__resource.lua index 1d9b249..2cc0afb 100644 --- a/resources/[system]/chat/__resource.lua +++ b/resources/[system]/chat/__resource.lua @@ -1,15 +1,15 @@ -description 'chat management stuff' - -ui_page 'html/chat.html' - -client_script 'chat_client.lua' -server_script 'chat_server.lua' - -export 'printChatLine' - -files { - 'html/chat.html', - 'html/chat.css', - 'html/chat.js', - 'html/jquery.faketextbox.js' -} +description 'better chat management stuff' + +ui_page 'html/index.html' + +client_script 'cl_chat.lua' +server_script 'sv_chat.lua' + +files { + 'html/index.html', + 'html/index.css', + 'html/config.js', + 'html/App.js', + 'html/Message.js', + 'html/Suggestions.js' + } diff --git a/resources/[system]/chat/chat_client.lua b/resources/[system]/chat/chat_client.lua deleted file mode 100644 index 4be4f2f..0000000 --- a/resources/[system]/chat/chat_client.lua +++ /dev/null @@ -1,56 +0,0 @@ -local chatInputActive = false -local chatInputActivating = false - -RegisterNetEvent('chatMessage') - -AddEventHandler('chatMessage', function(name, color, message) - SendNUIMessage({ - name = name, - color = color, - message = message - }) -end) - -RegisterNUICallback('chatResult', function(data, cb) - chatInputActive = false - - SetNuiFocus(false) - - if data.message then - local id = PlayerId() - - --local r, g, b = GetPlayerRgbColour(id, _i, _i, _i) - local r, g, b = 0, 0x99, 255 - - TriggerServerEvent('chatMessageEntered', GetPlayerName(id), { r, g, b }, data.message) - end - - cb('ok') -end) - -Citizen.CreateThread(function() - SetTextChatEnabled(false) - - while true do - Wait(0) - - if not chatInputActive then - if IsControlPressed(0, 245) --[[ INPUT_MP_TEXT_CHAT_ALL ]] then - chatInputActive = true - chatInputActivating = true - - SendNUIMessage({ - meta = 'openChatBox' - }) - end - end - - if chatInputActivating then - if not IsControlPressed(0, 245) then - SetNuiFocus(true) - - chatInputActivating = false - end - end - end -end) diff --git a/resources/[system]/chat/chat_server.lua b/resources/[system]/chat/chat_server.lua deleted file mode 100644 index 28370ed..0000000 --- a/resources/[system]/chat/chat_server.lua +++ /dev/null @@ -1,50 +0,0 @@ -RegisterServerEvent('chatCommandEntered') -RegisterServerEvent('chatMessageEntered') - -AddEventHandler('chatMessageEntered', function(name, color, message) - if not name or not color or not message or #color ~= 3 then - return - end - - TriggerEvent('chatMessage', source, name, message) - - if not WasEventCanceled() then - TriggerClientEvent('chatMessage', -1, name, color, message) - end - - print(name .. ': ' .. message) -end) - --- player join messages -AddEventHandler('playerActivated', function() - TriggerClientEvent('chatMessage', -1, '', { 0, 0, 0 }, '^2* ' .. GetPlayerName(source) .. ' joined.') -end) - -AddEventHandler('playerDropped', function(reason) - TriggerClientEvent('chatMessage', -1, '', { 0, 0, 0 }, '^2* ' .. GetPlayerName(source) ..' left (' .. reason .. ')') -end) - --- say command handler -AddEventHandler('rconCommand', function(commandName, args) - if commandName == "say" then - local msg = table.concat(args, ' ') - - TriggerClientEvent('chatMessage', -1, 'console', { 0, 0x99, 255 }, msg) - RconPrint('console: ' .. msg .. "\n") - - CancelEvent() - end -end) - --- tell command handler -AddEventHandler('rconCommand', function(commandName, args) - if commandName == "tell" then - local target = table.remove(args, 1) - local msg = table.concat(args, ' ') - - TriggerClientEvent('chatMessage', tonumber(target), 'console', { 0, 0x99, 255 }, msg) - RconPrint('console: ' .. msg .. "\n") - - CancelEvent() - end -end) diff --git a/resources/[system]/chat/cl_chat.lua b/resources/[system]/chat/cl_chat.lua new file mode 100644 index 0000000..113c422 --- /dev/null +++ b/resources/[system]/chat/cl_chat.lua @@ -0,0 +1,85 @@ +local chatInputActive = false +local chatInputActivating = false + +RegisterNetEvent('suggestionAdd') +RegisterNetEvent('chatMessage') +RegisterNetEvent('chatMessageEx') + +AddEventHandler('chatMessage', function(author, color, text) + if author == "" then + author = false + end + SendNUIMessage({ + type = 'ON_MESSAGE', + message = { + color = color, + multiline = true, + args = { author, text } + } + }) +end) + +AddEventHandler('chatMessageEx', function(message) + SendNUIMessage({ + type = 'ON_MESSAGE', + message = message + }) +end) + +AddEventHandler('suggestionAdd', function(name, help, params) + Citizen.Trace(name) + SendNUIMessage({ + type = 'ON_SUGGESTION_ADD', + suggestion = { + name = name, + help = help, + params = params or nil + } + }) +end) + +RegisterNUICallback('chatResult', function(data, cb) + chatInputActive = false + SetNuiFocus(false) + + if not data.canceled then + local id = PlayerId() + + TriggerServerEvent('chatMessageEntered', GetPlayerName(id), data.message) + end + + cb('ok') +end) + +RegisterNUICallback('loaded', function(data, cb) + TriggerServerEvent('chatInit'); + + cb('ok') +end) + +Citizen.CreateThread(function() + SetTextChatEnabled(false) + + while true do + Wait(0) + + if not chatInputActive then + if IsControlPressed(0, 245) --[[ INPUT_MP_TEXT_CHAT_ALL ]] then + chatInputActive = true + chatInputActivating = true + + SendNUIMessage({ + type = 'ON_OPEN' + }) + end + end + + if chatInputActivating then + if not IsControlPressed(0, 245) then + SetNuiFocus(true) + + chatInputActivating = false + end + end + end +end) diff --git a/resources/[system]/chat/html/App.js b/resources/[system]/chat/html/App.js new file mode 100644 index 0000000..01f1962 --- /dev/null +++ b/resources/[system]/chat/html/App.js @@ -0,0 +1,166 @@ +window.APP = { + template: '#app_template', + name: 'app', + data() { + return { + showInput: false, + showWindow: false, + suggestions: [], + message: '', + messages: [], + oldMessages: [], + oldMessagesIndex: -1, + }; + }, + destroyed() { + clearInterval(this.focusTimer); + window.removeEventListener('message', this.listener); + }, + mounted() { + axios.post('http://betterchat/loaded', {}); + this.listener = window.addEventListener('message', (event) => { + const item = event.data || event.detail; //'detail' is for debuging via browsers + if (this[item.type]) { + this[item.type](item); + } + }); + }, + watch: { + messages() { + if (this.showWindowTimer) { + clearTimeout(this.showWindowTimer); + } + this.showWindow = true; + this.showWindowTimer = setTimeout(() => { + if (!this.showInput) { + this.showWindow = false; + } + }, window.CONFIG.fadeTimeout); + + const messagesObj = this.$refs.messages; + this.$nextTick(() => { + messagesObj.scrollTop = messagesObj.scrollHeight; + }); + }, + }, + methods: { + ON_OPEN() { + this.showInput = true; + this.showWindow = true; + if (this.showWindowTimer) { + clearTimeout(this.showWindowTimer); + } + this.focusTimer = setInterval(() => { + if (this.$refs.input) { + this.$refs.input.focus(); + } else { + clearInterval(this.focusTimer); + } + }, 100); + }, + ON_MESSAGE(data) { + this.messages.push(data.message); + }, + ON_SUGGESTION_ADD(data) { + const suggestion = data.suggestion; + if (!suggestion.params) { + suggestion.params = []; + } + this.suggestions.push(suggestion); + }, + ON_SUGGESTION_REMOVE() { + }, + keyUp() { + this.resize(); + }, + keyDown(e) { + if (e.which === 38 || e.which === 40) { + e.preventDefault(); + this.moveOldMessageIndex(e.which === 38); + } + }, + moveOldMessageIndex(up) { + if (up && this.oldMessages.length > this.oldMessagesIndex + 1) { + this.oldMessagesIndex += 1; + this.message = this.oldMessages[this.oldMessagesIndex]; + } else if (!up && this.oldMessagesIndex - 1 >= 0) { + this.oldMessagesIndex -= 1; + this.message = this.oldMessages[this.oldMessagesIndex]; + } else if (!up && this.oldMessagesIndex - 1 === -1) { + this.oldMessagesIndex = -1; + this.message = ''; + } + }, + resize() { + const input = this.$refs.input; + input.style.height = '5px'; + input.style.height = `${input.scrollHeight + 2}px`; + }, + addLine() { + this.message += '\n'; + this.resize(); + }, + send(e) { + if (e.shiftKey || this.message === '') { + return; + } + axios.post('http://betterchat/chatResult', { + message: this.message, + }); + this.oldMessages.unshift(this.message); + this.message = ''; + this.showInput = false; + + this.showWindowTimer = setTimeout(() => { + this.showWindow = false; + }, window.CONFIG.fadeTimeout); + }, + hideInput(canceled) { + if (canceled) { + axios.post('http://betterchat/chatResult', { + canceled, + }); + } + this.showInput = false; + clearInterval(this.focusTimer); + + this.showWindowTimer = setTimeout(() => { + this.showWindow = false; + }, window.CONFIG.fadeTimeout); + }, + }, + components: { + Message: window.MESSAGE, + Suggestions: window.SUGGESTIONS, + }, +}; + +window.emulate_open = () => { + window.dispatchEvent(new CustomEvent('message', { + detail: { + type: 'ON_OPEN', + }, + })); +}; + +window.emulate_suggestion = (name, help, params = []) => { + window.dispatchEvent(new CustomEvent('message', { + detail: { + type: 'ON_SUGGESTION_ADD', + suggestion: { + name, + help, + params, + }, + }, + })); +}; + +window.emulate_message = (message) => { + window.dispatchEvent(new CustomEvent('message', { + detail: { + type: 'ON_MESSAGE', + message, + }, + })); +}; diff --git a/resources/[system]/chat/html/Message.js b/resources/[system]/chat/html/Message.js new file mode 100644 index 0000000..254c3ff --- /dev/null +++ b/resources/[system]/chat/html/Message.js @@ -0,0 +1,43 @@ +window.MESSAGE = { + template: '#message_template', + data() { + return {}; + }, + computed: { + textEscaped() { + return this.template.replace(/{(\d+)}/g, (match, number) => { + return this.args[number] != undefined ? this.escapeHtml(this.args[number]) : match + }); + }, + }, + created() { + + }, + methods: { + escapeHtml(unsafe) { + return unsafe + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); + }, + }, + props: { + args: { + + }, + template: { + type: String, + default: window.CONFIG.defaultTemplate, + }, + multiline: { + type: Boolean, + default: false, + }, + + color: { + type: String, + }, + }, +}; diff --git a/resources/[system]/chat/html/Suggestions.js b/resources/[system]/chat/html/Suggestions.js new file mode 100644 index 0000000..0fd08d0 --- /dev/null +++ b/resources/[system]/chat/html/Suggestions.js @@ -0,0 +1,46 @@ +window.SUGGESTIONS = { + template: '#suggestions_template', + props: ['message', 'suggestions'], + data() { + return {}; + }, + computed: { + currentSuggestions() { + if (this.message === '') { + return []; + } + const currentSuggestions = this.suggestions.filter((s) => { + if (!s.name.startsWith(this.message)) { + const suggestionSplitted = s.name.split(' '); + const messageSplitted = this.message.split(' '); + for (let i = 0; i < messageSplitted.length; i += 1) { + if (i >= suggestionSplitted.length) { + return i < suggestionSplitted.length + s.params.length; + } + if (suggestionSplitted[i] !== messageSplitted[i]) { + return false; + } + } + + return true; + } + return true; + }).slice(0, 5); + + currentSuggestions.forEach((s) => { + // eslint-disable-next-line no-param-reassign + s.disabled = !s.name.startsWith(this.message); + + s.params.forEach((p, index) => { + const wType = (index === s.params.length - 1) ? '.' : '\\S'; + const regex = new RegExp(`${s.name} (?:\\w+ ){${index}}(?:${wType}*)$`, 'g'); + + // eslint-disable-next-line no-param-reassign + p.disabled = this.message.match(regex) == null; + }); + }); + return currentSuggestions; + }, + }, + methods: {}, +}; diff --git a/resources/[system]/chat/html/chat.css b/resources/[system]/chat/html/chat.css deleted file mode 100644 index 6055b84..0000000 --- a/resources/[system]/chat/html/chat.css +++ /dev/null @@ -1,102 +0,0 @@ -body -{ - background-color: transparent; - margin: 0px; -} - -ul -{ - margin: 0px; - padding: 0px; - list-style-type: none; /* hii */ -} - -#chat -{ - position: absolute; - top: 30px; - left: 30px; - padding: 7px; - width: 30%; - font-size: 20px; - font-family: "Segoe UI", "Segoe UI Symbol", "Segoe UI Emoji", Arial, sans-serif; - color: #fff; - overflow: hidden; - text-shadow: 0px 0px 1px #333; -} - -input.fake -{ - position: absolute; - top: -10000px; - left: -10000px; -} - -#chatInputHas -{ - display: none; -} - -#chatInputHas strong -{ - display: inline-block; - vertical-align: bottom; - text-transform: uppercase; - height: 29px; - line-height: 26px; -} - -#chatInput -{ - background-color: transparent; - border: none; - outline: none !important; - padding: 3px; - - display: inline-block; - vertical-align: middle; - - margin: 0px; - color: #fff; - text-shadow: 0px 0px 1px #333; - - font: inherit; - - white-space: pre; - overflow: hidden; -} - -#chatInput * > div:first-child -{ - display: inline-block; - vertical-align: middle; - height: 23px; - line-height: 20px; -} - -#chatInput .caret -{ - display: inline-block; - min-width: 1px; - height: 22px; - margin-left: 1px; - vertical-align: bottom; - background-color: #fff; - box-shadow: 0px 0px 1px #333; - color: white; -} - -#chatBuffer -{ - height: 240px; - overflow: hidden; -} - -.color-1{color: #ff4444;} -.color-2{color: #99cc00;} -.color-3{color: #ffbb33;} -.color-4{color: #0099cc;} -.color-5{color: #33b5e5;} -.color-6{color: #aa66cc;} -.color-8{color: #cc0000;} -.color-9{color: #cc0000;} \ No newline at end of file diff --git a/resources/[system]/chat/html/chat.html b/resources/[system]/chat/html/chat.html deleted file mode 100644 index c2781e0..0000000 --- a/resources/[system]/chat/html/chat.html +++ /dev/null @@ -1,20 +0,0 @@ - -
- - - - - - -