1
0
mirror of https://github.com/citizenfx/cfx-server-data.git synced 2025-03-12 23:07:20 +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
> Here will be some kind of documentation
# Chat

View File

@ -1,4 +1,4 @@
description 'better chat management stuff'
description 'chat management stuff'
ui_page 'html/index.html'
@ -8,8 +8,19 @@ server_script 'sv_chat.lua'
files {
'html/index.html',
'html/index.css',
'html/config.default.js',
'html/config.js',
'html/App.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 chatInputActivating = false
RegisterNetEvent('suggestionAdd')
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)
if author == "" then
author = false
@ -19,15 +26,14 @@ AddEventHandler('chatMessage', function(author, color, text)
})
end)
AddEventHandler('chatMessageEx', function(message)
AddEventHandler('chat:addMessage', function(message)
SendNUIMessage({
type = 'ON_MESSAGE',
message = message
})
end)
AddEventHandler('suggestionAdd', function(name, help, params)
Citizen.Trace(name)
AddEventHandler('chat:addSuggestion', function(name, help, params)
SendNUIMessage({
type = 'ON_SUGGESTION_ADD',
suggestion = {
@ -38,6 +44,29 @@ AddEventHandler('suggestionAdd', function(name, help, params)
})
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)
chatInputActive = false
SetNuiFocus(false)
@ -45,20 +74,24 @@ RegisterNUICallback('chatResult', function(data, cb)
if not data.canceled then
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
cb('ok')
end)
RegisterNUICallback('loaded', function(data, cb)
TriggerServerEvent('chatInit');
TriggerServerEvent('chat:init');
cb('ok')
end)
Citizen.CreateThread(function()
SetTextChatEnabled(false)
SetNuiFocus(false)
while true do
Wait(0)

View File

@ -6,6 +6,7 @@ window.APP = {
showInput: false,
showWindow: false,
suggestions: [],
templates: CONFIG.templates,
message: '',
messages: [],
oldMessages: [],
@ -17,7 +18,7 @@ window.APP = {
window.removeEventListener('message', this.listener);
},
mounted() {
axios.post('http://betterchat/loaded', {});
$.post('http://chat/loaded', JSON.stringify({}));
this.listener = window.addEventListener('message', (event) => {
const item = event.data || event.detail; //'detail' is for debuging via browsers
if (this[item.type]) {
@ -31,11 +32,7 @@ window.APP = {
clearTimeout(this.showWindowTimer);
}
this.showWindow = true;
this.showWindowTimer = setTimeout(() => {
if (!this.showInput) {
this.showWindow = false;
}
}, window.CONFIG.fadeTimeout);
this.resetShowWindowTimer();
const messagesObj = this.$refs.messages;
this.$nextTick(() => {
@ -58,17 +55,46 @@ window.APP = {
}
}, 100);
},
ON_MESSAGE(data) {
this.messages.push(data.message);
ON_MESSAGE({ message }) {
this.messages.push(message);
},
ON_SUGGESTION_ADD(data) {
const suggestion = data.suggestion;
ON_CLEAR() {
this.messages = [];
this.oldMessages = [];
this.oldMessagesIndex = -1;
},
ON_SUGGESTION_ADD({ suggestion }) {
if (!suggestion.params) {
suggestion.params = [];
suggestion.params = []; //TODO Move somewhere else
}
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() {
this.resize();
@ -77,6 +103,12 @@ window.APP = {
if (e.which === 38 || e.which === 40) {
e.preventDefault();
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) {
@ -96,71 +128,31 @@ window.APP = {
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;
if (e.shiftKey) {
this.message += '\n';
this.resize();
} else {
if(this.message !== '') {
$.post('http://chat/chatResult', JSON.stringify({
message: this.message,
}));
this.oldMessages.unshift(this.message);
this.message = '';
this.oldMessagesIndex = -1;
this.hideInput();
} else {
this.hideInput(true);
}
}
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) {
hideInput(canceled = false) {
if (canceled) {
axios.post('http://betterchat/chatResult', {
canceled,
});
$.post('http://chat/chatResult', JSON.stringify({ canceled }));
}
this.showInput = false;
clearInterval(this.focusTimer);
this.showWindowTimer = setTimeout(() => {
this.showWindow = false;
}, window.CONFIG.fadeTimeout);
this.resetShowWindowTimer();
},
},
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',
data() {
return {};
},
computed: {
textEscaped() {
return this.template.replace(/{(\d+)}/g, (match, number) => {
return this.args[number] != undefined ? this.escapeHtml(this.args[number]) : match
let s = '';
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: {
escapeHtml(unsafe) {
return unsafe
colorizeOld(str) {
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, '&lt;')
.replace(/>/g, '&gt;')
@ -24,20 +39,27 @@ window.MESSAGE = {
},
},
props: {
templates: {
type: Object,
},
args: {
type: Array,
},
template: {
type: String,
default: window.CONFIG.defaultTemplate,
default: null,
},
templateId: {
type: String,
default: CONFIG.defaultTemplateId,
},
multiline: {
type: Boolean,
default: false,
},
color: {
type: String,
color: { //deprecated
type: Array,
default: false,
},
},
};
});

View File

@ -1,4 +1,4 @@
window.SUGGESTIONS = {
Vue.component('suggestions', {
template: '#suggestions_template',
props: ['message', 'suggestions'],
data() {
@ -21,11 +21,9 @@ window.SUGGESTIONS = {
return false;
}
}
return true;
}
return true;
}).slice(0, 5);
}).slice(0, CONFIG.suggestionLimit);
currentSuggestions.forEach((s) => {
// eslint-disable-next-line no-param-reassign
@ -43,4 +41,4 @@ window.SUGGESTIONS = {
},
},
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;
margin: 0;
padding: 0;
}
.no-grow {
flex-grow: 0;
}
#app {
font-family: 'Lato', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
@ -33,7 +41,7 @@
position: relative;
height: 95%;
font-size: 1.2rem;
margin: 5px;
margin: 10px;
overflow-x: hidden;
overflow-y: hidden;

View File

@ -3,12 +3,17 @@
<head>
<meta charset="utf-8">
<title></title>
<link rel="stylesheet" href="index.css"></link>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/flexboxgrid/6.3.1/flexboxgrid.min.css"></link>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/3.5.2/animate.min.css"></link>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.3/vue.min.js"></script>
<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>
<link href="vendor/latofonts.css" rel="stylesheet">
<link href="vendor/flexboxgrid.6.3.1.min.css" rel="stylesheet"></link>
<link href="vendor/animate.3.5.2.min.css" rel="stylesheet"></link>
<link href="index.css" rel="stylesheet"></link>
<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>
<body>
<div id="app"></div>
@ -19,9 +24,12 @@
<div class="chat-window" :class="{ 'fadeOut animated': !showWindow }">
<div class="chat-messages" ref="messages">
<message v-for="msg in messages"
:templates="templates"
:multiline="msg.multiline"
:args="msg.args"
:color="msg.color"
:template="msg.template"
:template-id="msg.templateId"
:key="msg">
</message>
</div>
@ -31,13 +39,11 @@
<textarea v-model="message"
ref="input"
type="text"
value="/help"
autofocus
@keyup.esc="hideInput"
@keyup="keyUp"
@keydown="keyDown"
@keypress.enter.none.prevent="send"
@keypress.enter.shift.prevent="addLine">
@keypress.enter.prevent="send">
</textarea>
<suggestions :message="message" :suggestions="suggestions">
</suggestions>
@ -87,9 +93,15 @@
<script type="text/javascript">
const instance = new Vue({
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>
</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('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
return
end
@ -12,7 +18,7 @@ AddEventHandler('chatMessageEntered', function(author, message)
if not WasEventCanceled() then
print("No cancel")
TriggerClientEvent('chatMessage', -1, author, { 0, 0, 0 }, message)
TriggerClientEvent('chatMessage', -1, author, { 255, 255, 255 }, message)
end
print(author .. ': ' .. message)
@ -20,9 +26,9 @@ end)
-- player join messages
AddEventHandler('playerActivated', function()
TriggerClientEvent('chatMessage', -1, '', { 0, 0, 0 }, '^2* ' .. GetPlayerName(source) .. ' joined.')
TriggerClientEvent('chatMessage', -1, '', { 255, 255, 255 }, '^2* ' .. GetPlayerName(source) .. ' joined.')
end)
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)