1
0
mirror of https://github.com/citizenfx/cfx-server-data.git synced 2025-03-13 15:27:18 +08:00

Betterchat v2

This commit is contained in:
Jonas Dellinger 2017-05-21 16:52:55 +02:00
parent ffa9488e9a
commit 2e075e0651
21 changed files with 279 additions and 124 deletions

View File

@ -1,3 +1 @@
# betterchat # Chat
> Here will be some kind of documentation

View File

@ -1,4 +1,4 @@
description 'better chat management stuff' description 'chat management stuff'
ui_page 'html/index.html' ui_page 'html/index.html'
@ -8,8 +8,19 @@ server_script 'sv_chat.lua'
files { files {
'html/index.html', 'html/index.html',
'html/index.css', 'html/index.css',
'html/config.default.js',
'html/config.js', 'html/config.js',
'html/App.js', 'html/App.js',
'html/Message.js', 'html/Message.js',
'html/Suggestions.js' 'html/Suggestions.js',
'html/vendor/vue.2.3.3.min.js',
'html/vendor/flexboxgrid.6.3.1.min.css',
'html/vendor/animate.3.5.2.min.css',
'html/vendor/latofonts.css',
'html/vendor/fonts/LatoRegular.woff2',
'html/vendor/fonts/LatoRegular2.woff2',
'html/vendor/fonts/LatoLight2.woff2',
'html/vendor/fonts/LatoLight.woff2',
'html/vendor/fonts/LatoBold.woff2',
'html/vendor/fonts/LatoBold2.woff2',
} }

View File

@ -1,10 +1,17 @@
local chatInputActive = false local chatInputActive = false
local chatInputActivating = false local chatInputActivating = false
RegisterNetEvent('suggestionAdd')
RegisterNetEvent('chatMessage') RegisterNetEvent('chatMessage')
RegisterNetEvent('chatMessageEx') RegisterNetEvent('chat:addTemplate')
RegisterNetEvent('chat:addMessage')
RegisterNetEvent('chat:addSuggestion')
RegisterNetEvent('chat:removeSuggestion')
RegisterNetEvent('chat:clear')
-- internal events
RegisterNetEvent('_chat:messageEntered')
--deprecated, use chat:addMessage
AddEventHandler('chatMessage', function(author, color, text) AddEventHandler('chatMessage', function(author, color, text)
if author == "" then if author == "" then
author = false author = false
@ -19,15 +26,14 @@ AddEventHandler('chatMessage', function(author, color, text)
}) })
end) end)
AddEventHandler('chatMessageEx', function(message) AddEventHandler('chat:addMessage', function(message)
SendNUIMessage({ SendNUIMessage({
type = 'ON_MESSAGE', type = 'ON_MESSAGE',
message = message message = message
}) })
end) end)
AddEventHandler('suggestionAdd', function(name, help, params) AddEventHandler('chat:addSuggestion', function(name, help, params)
Citizen.Trace(name)
SendNUIMessage({ SendNUIMessage({
type = 'ON_SUGGESTION_ADD', type = 'ON_SUGGESTION_ADD',
suggestion = { suggestion = {
@ -38,6 +44,29 @@ AddEventHandler('suggestionAdd', function(name, help, params)
}) })
end) end)
AddEventHandler('chat:removeSuggestion', function(name)
SendNUIMessage({
type = 'ON_SUGGESTION_REMOVE',
name = name
})
end)
AddEventHandler('chat:addTemplate', function(id, html)
SendNUIMessage({
type = 'ON_TEMPLATE_ADD',
template = {
id = id,
html = html
}
})
end)
AddEventHandler('chat:clear', function(name)
SendNUIMessage({
type = 'ON_CLEAR'
})
end)
RegisterNUICallback('chatResult', function(data, cb) RegisterNUICallback('chatResult', function(data, cb)
chatInputActive = false chatInputActive = false
SetNuiFocus(false) SetNuiFocus(false)
@ -45,20 +74,24 @@ RegisterNUICallback('chatResult', function(data, cb)
if not data.canceled then if not data.canceled then
local id = PlayerId() local id = PlayerId()
TriggerServerEvent('chatMessageEntered', GetPlayerName(id), data.message) --deprecated
local r, g, b = 0, 0x99, 255
TriggerServerEvent('_chat:messageEntered', GetPlayerName(id), { r, g, b }, data.message)
end end
cb('ok') cb('ok')
end) end)
RegisterNUICallback('loaded', function(data, cb) RegisterNUICallback('loaded', function(data, cb)
TriggerServerEvent('chatInit'); TriggerServerEvent('chat:init');
cb('ok') cb('ok')
end) end)
Citizen.CreateThread(function() Citizen.CreateThread(function()
SetTextChatEnabled(false) SetTextChatEnabled(false)
SetNuiFocus(false)
while true do while true do
Wait(0) Wait(0)

View File

@ -6,6 +6,7 @@ window.APP = {
showInput: false, showInput: false,
showWindow: false, showWindow: false,
suggestions: [], suggestions: [],
templates: CONFIG.templates,
message: '', message: '',
messages: [], messages: [],
oldMessages: [], oldMessages: [],
@ -17,7 +18,7 @@ window.APP = {
window.removeEventListener('message', this.listener); window.removeEventListener('message', this.listener);
}, },
mounted() { mounted() {
axios.post('http://betterchat/loaded', {}); $.post('http://chat/loaded', JSON.stringify({}));
this.listener = window.addEventListener('message', (event) => { this.listener = window.addEventListener('message', (event) => {
const item = event.data || event.detail; //'detail' is for debuging via browsers const item = event.data || event.detail; //'detail' is for debuging via browsers
if (this[item.type]) { if (this[item.type]) {
@ -31,11 +32,7 @@ window.APP = {
clearTimeout(this.showWindowTimer); clearTimeout(this.showWindowTimer);
} }
this.showWindow = true; this.showWindow = true;
this.showWindowTimer = setTimeout(() => { this.resetShowWindowTimer();
if (!this.showInput) {
this.showWindow = false;
}
}, window.CONFIG.fadeTimeout);
const messagesObj = this.$refs.messages; const messagesObj = this.$refs.messages;
this.$nextTick(() => { this.$nextTick(() => {
@ -58,17 +55,46 @@ window.APP = {
} }
}, 100); }, 100);
}, },
ON_MESSAGE(data) { ON_MESSAGE({ message }) {
this.messages.push(data.message); this.messages.push(message);
}, },
ON_SUGGESTION_ADD(data) { ON_CLEAR() {
const suggestion = data.suggestion; this.messages = [];
this.oldMessages = [];
this.oldMessagesIndex = -1;
},
ON_SUGGESTION_ADD({ suggestion }) {
if (!suggestion.params) { if (!suggestion.params) {
suggestion.params = []; suggestion.params = []; //TODO Move somewhere else
} }
this.suggestions.push(suggestion); this.suggestions.push(suggestion);
}, },
ON_SUGGESTION_REMOVE() { ON_SUGGESTION_REMOVE({ name }) {
this.suggestions = this.suggestions.filter((sug) => sug.name !== name)
},
ON_TEMPLATE_ADD({ template }) {
if (this.templates[template.id]) {
this.warn(`Tried to add duplicate template '${template.id}'`)
} else {
this.templates[template.id] = template.html;
}
},
warn(msg) {
this.messages.push({
args: [msg],
template: '^3<b>CHAT-WARN</b>: ^0{0}',
});
},
clearShowWindowTimer() {
clearTimeout(this.showWindowTimer);
},
resetShowWindowTimer() {
this.clearShowWindowTimer();
this.showWindowTimer = setTimeout(() => {
if (!this.showInput) {
this.showWindow = false;
}
}, CONFIG.fadeTimeout);
}, },
keyUp() { keyUp() {
this.resize(); this.resize();
@ -77,6 +103,12 @@ window.APP = {
if (e.which === 38 || e.which === 40) { if (e.which === 38 || e.which === 40) {
e.preventDefault(); e.preventDefault();
this.moveOldMessageIndex(e.which === 38); this.moveOldMessageIndex(e.which === 38);
} else if (e.which == 33) {
const buf = $(this.$refs.messages);
buf.scrollTop(buf.scrollTop() - 50);
} else if (e.which == 34) {
const buf = $(this.$refs.messages);
buf.scrollTop(buf.scrollTop() + 50);
} }
}, },
moveOldMessageIndex(up) { moveOldMessageIndex(up) {
@ -96,71 +128,31 @@ window.APP = {
input.style.height = '5px'; input.style.height = '5px';
input.style.height = `${input.scrollHeight + 2}px`; input.style.height = `${input.scrollHeight + 2}px`;
}, },
addLine() { send(e) {
if (e.shiftKey) {
this.message += '\n'; this.message += '\n';
this.resize(); this.resize();
}, } else {
send(e) { if(this.message !== '') {
if (e.shiftKey || this.message === '') { $.post('http://chat/chatResult', JSON.stringify({
return;
}
axios.post('http://betterchat/chatResult', {
message: this.message, message: this.message,
}); }));
this.oldMessages.unshift(this.message); this.oldMessages.unshift(this.message);
this.message = ''; this.message = '';
this.showInput = false; this.oldMessagesIndex = -1;
this.hideInput();
this.showWindowTimer = setTimeout(() => { } else {
this.showWindow = false; this.hideInput(true);
}, window.CONFIG.fadeTimeout); }
}
}, },
hideInput(canceled) { hideInput(canceled = false) {
if (canceled) { if (canceled) {
axios.post('http://betterchat/chatResult', { $.post('http://chat/chatResult', JSON.stringify({ canceled }));
canceled,
});
} }
this.showInput = false; this.showInput = false;
clearInterval(this.focusTimer); clearInterval(this.focusTimer);
this.resetShowWindowTimer();
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,
},
}));
}; };

View File

@ -1,21 +1,36 @@
window.MESSAGE = { Vue.component('message', {
template: '#message_template', template: '#message_template',
data() { data() {
return {}; return {};
}, },
computed: { computed: {
textEscaped() { textEscaped() {
return this.template.replace(/{(\d+)}/g, (match, number) => { let s = '';
return this.args[number] != undefined ? this.escapeHtml(this.args[number]) : match if (this.template) {
s = this.colorize(this.template);
} else {
s = this.colorize(this.templates[this.templateId]);
}
return s.replace(/{(\d+)}/g, (match, number) => {
const argEscaped = this.args[number] != undefined ? this.escape(this.args[number]) : match
if (number == 0 && this.color) {
//color is deprecated, use templates or ^1 etc.
return this.colorizeOld(argEscaped);
}
return argEscaped;
}); });
}, },
},
created() {
}, },
methods: { methods: {
escapeHtml(unsafe) { colorizeOld(str) {
return unsafe return `<strong style="color: rgb(${this.color[0]}, ${this.color[1]}, ${this.color[2]})">${str}</strong>`
},
colorize(str) {
const s = "<span>" + (str.replace(/\^([0-9]+)/g, (str, color) => `</span><span class="color-${color}">`)) + "</span>";
return s.replace(/<span[^>]*><\/span[^>]*>/g, '');
},
escape(unsafe) {
return String(unsafe)
.replace(/&/g, '&amp;') .replace(/&/g, '&amp;')
.replace(/</g, '&lt;') .replace(/</g, '&lt;')
.replace(/>/g, '&gt;') .replace(/>/g, '&gt;')
@ -24,20 +39,27 @@ window.MESSAGE = {
}, },
}, },
props: { props: {
templates: {
type: Object,
},
args: { args: {
type: Array,
}, },
template: { template: {
type: String, type: String,
default: window.CONFIG.defaultTemplate, default: null,
},
templateId: {
type: String,
default: CONFIG.defaultTemplateId,
}, },
multiline: { multiline: {
type: Boolean, type: Boolean,
default: false, default: false,
}, },
color: { //deprecated
color: { type: Array,
type: String, default: false,
}, },
}, },
}; });

View File

@ -1,4 +1,4 @@
window.SUGGESTIONS = { Vue.component('suggestions', {
template: '#suggestions_template', template: '#suggestions_template',
props: ['message', 'suggestions'], props: ['message', 'suggestions'],
data() { data() {
@ -21,11 +21,9 @@ window.SUGGESTIONS = {
return false; return false;
} }
} }
return true;
} }
return true; return true;
}).slice(0, 5); }).slice(0, CONFIG.suggestionLimit);
currentSuggestions.forEach((s) => { currentSuggestions.forEach((s) => {
// eslint-disable-next-line no-param-reassign // eslint-disable-next-line no-param-reassign
@ -43,4 +41,4 @@ window.SUGGESTIONS = {
}, },
}, },
methods: {}, methods: {},
}; });

View File

@ -0,0 +1,11 @@
// DO NOT EDIT THIS FILE
// Copy it to `config.js` and edit it
window.CONFIG = {
defaultTemplateId: 'default', //This template will be used for normal chat messages
templates: { //You can add static templates here
'default': '<b>{0}</b>: {1}',
'example:important': '<h1>^2{0}</h1>'
},
fadeTimeout: 7000,
suggestionLimit: 5,
};

View File

@ -1,4 +0,0 @@
window.CONFIG = {
defaultTemplate: '<b>{0}</b>: {1}',
fadeTimeout: 7000,
};

View File

@ -1,15 +1,23 @@
.color-0{color: #ffffff;}
.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: #cc0068;}
* { * {
font-family: 'Lato', sans-serif; font-family: 'Lato', sans-serif;
margin: 0; margin: 0;
padding: 0; padding: 0;
} }
.no-grow { .no-grow {
flex-grow: 0; flex-grow: 0;
} }
#app { #app {
font-family: 'Lato', Helvetica, Arial, sans-serif; font-family: 'Lato', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
@ -33,7 +41,7 @@
position: relative; position: relative;
height: 95%; height: 95%;
font-size: 1.2rem; font-size: 1.2rem;
margin: 5px; margin: 10px;
overflow-x: hidden; overflow-x: hidden;
overflow-y: hidden; overflow-y: hidden;

View File

@ -3,12 +3,17 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<title></title> <title></title>
<link rel="stylesheet" href="index.css"></link> <link href="vendor/latofonts.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/flexboxgrid/6.3.1/flexboxgrid.min.css"></link> <link href="vendor/flexboxgrid.6.3.1.min.css" rel="stylesheet"></link>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/3.5.2/animate.min.css"></link> <link href="vendor/animate.3.5.2.min.css" rel="stylesheet"></link>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.3/vue.min.js"></script> <link href="index.css" rel="stylesheet"></link>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.16.1/axios.min.js"></script>
<script type="text/javascript" src="config.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js" type="text/javascript"></script>
<!-- <script src="nui://game/ui/jquery.js" type="text/javascript"></script> -->
<script src="vendor/vue.2.3.3.min.js" type="text/javascript"></script>
<script src="config.default.js" type="text/javascript"></script>
<!-- <script src="config.js" type="text/javascript"></script> -->
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>
@ -19,9 +24,12 @@
<div class="chat-window" :class="{ 'fadeOut animated': !showWindow }"> <div class="chat-window" :class="{ 'fadeOut animated': !showWindow }">
<div class="chat-messages" ref="messages"> <div class="chat-messages" ref="messages">
<message v-for="msg in messages" <message v-for="msg in messages"
:templates="templates"
:multiline="msg.multiline" :multiline="msg.multiline"
:args="msg.args" :args="msg.args"
:color="msg.color"
:template="msg.template" :template="msg.template"
:template-id="msg.templateId"
:key="msg"> :key="msg">
</message> </message>
</div> </div>
@ -31,13 +39,11 @@
<textarea v-model="message" <textarea v-model="message"
ref="input" ref="input"
type="text" type="text"
value="/help"
autofocus autofocus
@keyup.esc="hideInput" @keyup.esc="hideInput"
@keyup="keyUp" @keyup="keyUp"
@keydown="keyDown" @keydown="keyDown"
@keypress.enter.none.prevent="send" @keypress.enter.prevent="send">
@keypress.enter.shift.prevent="addLine">
</textarea> </textarea>
<suggestions :message="message" :suggestions="suggestions"> <suggestions :message="message" :suggestions="suggestions">
</suggestions> </suggestions>
@ -87,9 +93,15 @@
<script type="text/javascript"> <script type="text/javascript">
const instance = new Vue({ const instance = new Vue({
el: '#app', el: '#app',
render: h => h(window.APP), render: h => h(APP),
}); });
window.instance = instance;
window.emulate = (type, detail = {}) => {
detail.type = type;
window.dispatchEvent(new CustomEvent('message', {
detail,
}));
};
</script> </script>
</body> </body>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,48 @@
/* latin-ext */
@font-face {
font-family: 'Lato';
font-style: normal;
font-weight: 300;
src: local('Lato Light'), local('Lato-Light'), url(fonts/LatoLight.woff2);
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Lato';
font-style: normal;
font-weight: 300;
src: local('Lato Light'), local('Lato-Light'), url(fonts/LatoLight2.woff2);
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215;
}
/* latin-ext */
@font-face {
font-family: 'Lato';
font-style: normal;
font-weight: 400;
src: local('Lato Regular'), local('Lato-Regular'), url(fonts/LatoRegular.woff2);
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Lato';
font-style: normal;
font-weight: 400;
src: local('Lato Regular'), local('Lato-Regular'), url(fonts/LatoRegular2.woff2);
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215;
}
/* latin-ext */
@font-face {
font-family: 'Lato';
font-style: normal;
font-weight: 700;
src: local('Lato Bold'), local('Lato-Bold'), url(fonts/LatoBold.woff2);
unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Lato';
font-style: normal;
font-weight: 700;
src: local('Lato Bold'), local('Lato-Bold'), url(fonts/LatoBold2.woff2);
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215;
}

File diff suppressed because one or more lines are too long

View File

@ -1,9 +1,15 @@
RegisterServerEvent('chatCommandEntered') RegisterServerEvent('chatCommandEntered')
RegisterServerEvent('chatMessageEntered') RegisterServerEvent('chatMessageEntered')
RegisterServerEvent('initialSuggestions')
RegisterServerEvent('chat:init')
RegisterServerEvent('chat:addTemplate')
RegisterServerEvent('chat:addMessage')
RegisterServerEvent('chat:addSuggestion')
RegisterServerEvent('chat:removeSuggestion')
RegisterServerEvent('_chat:messageEntered')
RegisterServerEvent('chat:clear')
AddEventHandler('chatMessageEntered', function(author, message) AddEventHandler('_chat:messageEntered', function(author, color, message)
if not message or not author then if not message or not author then
return return
end end
@ -12,7 +18,7 @@ AddEventHandler('chatMessageEntered', function(author, message)
if not WasEventCanceled() then if not WasEventCanceled() then
print("No cancel") print("No cancel")
TriggerClientEvent('chatMessage', -1, author, { 0, 0, 0 }, message) TriggerClientEvent('chatMessage', -1, author, { 255, 255, 255 }, message)
end end
print(author .. ': ' .. message) print(author .. ': ' .. message)
@ -20,9 +26,9 @@ end)
-- player join messages -- player join messages
AddEventHandler('playerActivated', function() AddEventHandler('playerActivated', function()
TriggerClientEvent('chatMessage', -1, '', { 0, 0, 0 }, '^2* ' .. GetPlayerName(source) .. ' joined.') TriggerClientEvent('chatMessage', -1, '', { 255, 255, 255 }, '^2* ' .. GetPlayerName(source) .. ' joined.')
end) end)
AddEventHandler('playerDropped', function(reason) AddEventHandler('playerDropped', function(reason)
TriggerClientEvent('chatMessage', -1, '', { 0, 0, 0 }, '^2* ' .. GetPlayerName(source) ..' left (' .. reason .. ')') TriggerClientEvent('chatMessage', -1, '', { 255, 255, 255 }, '^2* ' .. GetPlayerName(source) ..' left (' .. reason .. ')')
end) end)